diff --git a/app/assets/javascripts/members.js.erb b/app/assets/javascripts/members.js.erb index ab00a33b25..13d95fefe7 100644 --- a/app/assets/javascripts/members.js.erb +++ b/app/assets/javascripts/members.js.erb @@ -30,3 +30,44 @@ if (document.getElementById("membermap") !== null) { }); } + +$(document).on('click', '#find-me', function(e) { + e.preventDefault(); + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition(function(position) { + $('#member_latitude').val(position.coords.latitude); + $('#member_longitude').val(position.coords.longitude); + updateMap(position.coords.latitude, position.coords.longitude); + }); + } else { + alert("Geolocation is not supported by this browser."); + } +}); + +if (document.getElementById("map") !== null) { + var map = L.map('map').setView([0, 0], 2); + var marker; + + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap contributors', + maxZoom: 18 + }).addTo(map); + + map.on('click', function(e) { + updateMarker(e.latlng); + $('#member_latitude').val(e.latlng.lat); + $('#member_longitude').val(e.latlng.lng); + }); + + function updateMarker(latlng) { + if (marker) { + map.removeLayer(marker); + } + marker = L.marker(latlng).addTo(map); + } + + function updateMap(lat, lng) { + map.setView([lat, lng], 13); + updateMarker(L.latLng(lat, lng)); + } +} diff --git a/app/controllers/members_controller.rb b/app/controllers/members_controller.rb index 160746519f..5809a3bad4 100644 --- a/app/controllers/members_controller.rb +++ b/app/controllers/members_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class MembersController < ApplicationController - load_and_authorize_resource except: %i(finish_signup unsubscribe view_follows view_followers show) + load_and_authorize_resource except: %i(finish_signup unsubscribe view_follows view_followers show set_location update_location) skip_authorize_resource only: %i(nearby unsubscribe finish_signup) respond_to :html, :json, :rss @@ -86,6 +86,36 @@ def finish_signup end end + def set_location + @member = Member.find_by_slug!(params[:id]) + authorize! :update, @member + end + + def update_location + @member = Member.find_by_slug!(params[:id]) + authorize! :update, @member + + if params[:member][:latitude].present? && params[:member][:longitude].present? + lat = params[:member][:latitude].to_f.round(2) + lng = params[:member][:longitude].to_f.round(2) + params[:member][:latitude] = lat + params[:member][:longitude] = lng + + results = Geocoder.search([lat, lng]) + if results.first + params[:member][:location] = results.first.city || results.first.town || results.first.village || results.first.hamlet + else + params[:member][:location] = "Location near #{lat}, #{lng}" + end + end + + if @member.update(member_params) + redirect_to member_path(@member), notice: 'Location updated.' + else + render :set_location + end + end + private EMAIL_TYPE_STRING = { @@ -94,7 +124,7 @@ def finish_signup }.freeze def member_params - params.require(:member).permit(:login_name, :tos_agreement, :email, :newsletter) + params.require(:member).permit(:login_name, :tos_agreement, :email, :newsletter, :location, :latitude, :longitude) end def member_json_fields diff --git a/app/views/members/set_location.html.haml b/app/views/members/set_location.html.haml new file mode 100644 index 0000000000..f912a74474 --- /dev/null +++ b/app/views/members/set_location.html.haml @@ -0,0 +1,23 @@ +- content_for :title, "Set your location" + +%h1 Set your location + += form_for @member, url: update_location_member_path(@member), method: :put do |f| + .form-group + = f.label :location + = f.text_field :location, class: 'form-control' + + .form-group + = f.label :latitude + = f.text_field :latitude, class: 'form-control', readonly: true + + .form-group + = f.label :longitude + = f.text_field :longitude, class: 'form-control', readonly: true + + #map.set-location-map + + .form-group + %button#find-me.btn.btn-default Find my location + + = f.submit 'Update location', class: 'btn btn-primary' diff --git a/app/views/members/show.html.haml b/app/views/members/show.html.haml index d4654b8f89..c7e0d252ba 100644 --- a/app/views/members/show.html.haml +++ b/app/views/members/show.html.haml @@ -60,6 +60,9 @@ = link_to edit_member_registration_path, class: 'btn btn-block' do = member_icon = t('members.edit_profile') + = link_to set_location_member_path(@member), class: 'btn btn-block' do + = icon('fas', 'map-marker') + Set location - if can?(:create, Notification) && current_member != @member = link_to new_message_path(recipient_id: @member.id), class: 'btn btn-block' do diff --git a/config/routes.rb b/config/routes.rb index 084cf98e0b..ca3c808df4 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -102,6 +102,10 @@ resources :timeline resources :members, param: :slug do + member do + get :set_location + put :update_location + end resources :gardens resources :seeds resources :plantings diff --git a/spec/features/members/set_location_spec.rb b/spec/features/members/set_location_spec.rb new file mode 100644 index 0000000000..475785fe4b --- /dev/null +++ b/spec/features/members/set_location_spec.rb @@ -0,0 +1,83 @@ +require 'rails_helper' + +RSpec.feature 'Set location', type: :feature do + let(:member) { FactoryBot.create(:member) } + + before do + login_as(member, scope: :member) + end + + scenario 'member sets their location by clicking on the map', js: true do + visit set_location_member_path(member) + + # Test clicking on the map + page.execute_script("map.fire('click', { latlng: L.latLng(40.7128, -74.0060) })") + expect(find('#member_latitude').value).to eq('40.7128') + expect(find('#member_longitude').value).to eq('-74.006') + + # Mock geocoding + geocoder_result = instance_double('Geocoder::Result::Nominatim', + city: 'New York', + town: nil, + village: nil, + hamlet: nil) + allow(Geocoder).to receive(:search).with([40.71, -74.01]).and_return([geocoder_result]) + + click_button 'Update location' + + expect(page).to have_content('Location updated.') + member.reload + expect(member.location).to eq('New York') + expect(member.latitude).to eq(40.71) + expect(member.longitude).to eq(-74.01) + end + + scenario 'member uses "Find my location"', js: true do + visit set_location_member_path(member) + + # Mock browser's geolocation + page.execute_script(" + navigator.geolocation.getCurrentPosition = function(success) { + var position = { coords: { latitude: 34.0522, longitude: -118.2437 } }; + success(position); + } + ") + + click_button 'Find my location' + + expect(find('#member_latitude').value).to eq('34.0522') + expect(find('#member_longitude').value).to eq('-118.2437') + + # Mock geocoding + geocoder_result = instance_double('Geocoder::Result::Nominatim', + city: 'Los Angeles', + town: nil, + village: nil, + hamlet: nil) + allow(Geocoder).to receive(:search).with([34.05, -118.24]).and_return([geocoder_result]) + + click_button 'Update location' + + expect(page).to have_content('Location updated.') + member.reload + expect(member.location).to eq('Los Angeles') + expect(member.latitude).to eq(34.05) + expect(member.longitude).to eq(-118.24) + end + + scenario 'geocoding fails', js: true do + visit set_location_member_path(member) + + page.execute_script("map.fire('click', { latlng: L.latLng(1.2345, 6.7890) })") + + allow(Geocoder).to receive(:search).with([1.23, 6.79]).and_return([]) + + click_button 'Update location' + + expect(page).to have_content('Location updated.') + member.reload + expect(member.location).to eq('Location near 1.23, 6.79') + expect(member.latitude).to eq(1.23) + expect(member.longitude).to eq(6.79) + end +end