-
- <%= inline_svg_tag "solid_search.svg", class: 'h-4 w-4 fill-current text-gray-2' %>
-
-
- <%= @form.search_field(
- :keyword,
- value: "#{ params.dig('search', 'keyword') }",
- class: "block px-10 h-46px w-full py-3.5 px-4 rounded-6px text-base text-gray-3 focus:outline-none",
- id: "search-keyword-input",
- placeholder: "Try \"Mental Health Nonprofits\"",
- data: {
- action: "input->search#displayClearKeywordButton",
- search_target: "keywordInput",
- }
- ) %>
-
-
+
+
+
+
+ <%= inline_svg_tag "solid_search.svg", class: 'h-4 w-4 fill-current text-gray-2' %>
+
+ <%= @form.search_field(
+ :keyword,
+ value: "#{ params.dig('search', 'keyword') }",
+ class: "rounded-l-md border-none block px-10 h-46px w-full py-3.5 px-4 text-base text-gray-3 focus:outline-none",
+ id: "search-keyword-input",
+ placeholder: "Try \"Mental Health Nonprofits\"",
+ data: {
+ action: "input->search#displayClearKeywordButton",
+ search_target: "keywordInput",
+ }
+ ) %>
+
+
+
+
+
+
+
+ <% @locations.each do |location| %>
+ -
+ <%= location %>
+
+ <% end %>
+ -
+ <%= inline_svg_tag "my-location.svg", size:"12*12", class:"h-7 mr-2 fill-current text-gray-3" %>
+ Search near me
+
+
+
+
+ <%= @form.hidden_field :lat, value: @current_location[:latitude], data: { geolocation_target: "formLatitude" } %>
+ <%= @form.hidden_field :lon, value: @current_location[:longitude], data: { geolocation_target: "formLongitude" } %>
-
<%# Search Button %>
<%= @form.submit(
'Search',
diff --git a/app/components/search_bar/component.rb b/app/components/search_bar/component.rb
index 112c61ef6..b7cccc411 100644
--- a/app/components/search_bar/component.rb
+++ b/app/components/search_bar/component.rb
@@ -1,13 +1,10 @@
-# frozen_string_literal: true
-
-# search bar view component
-# rubocop:disable Lint/MissingSuper
-# rubocop:disable Style/Documentation
module SearchBar
class Component < ApplicationViewComponent
- def initialize(form:, search:)
+ def initialize(form:, search:, current_location:, locations:)
@form = form
@search = search
+ @current_location = current_location
+ @locations = locations
end
def options
@@ -18,5 +15,3 @@ def options
end
end
end
-# rubocop:enable Lint/MissingSuper
-# rubocop:enable Style/Documentation
diff --git a/app/components/search_pills/component.html.slim b/app/components/search_pills/component.html.slim
index 7c24fac11..3ea84b575 100644
--- a/app/components/search_pills/component.html.slim
+++ b/app/components/search_pills/component.html.slim
@@ -2,50 +2,51 @@ div class="w-full bg-gray-9"
div data-controller="tabs" data-tabs-active-tab=("border-b-4 border-blue-medium")
div class="flex"
/ Clear-Counter button
- span class="flex justify-center items-center w-14 pl-5 py-3 pr-2 text-xs bg-blue-pale"
+ span class="flex items-center justify-center py-3 pl-5 pr-2 text-xs w-14 bg-blue-pale"
span class="inline-flex hidden items-center px-1 py-0.5 border border-blue-medium rounded-full bg-white" data-search-target="pillsCounterWrapper"
span data-search-target="pillsCounter" class="mr-0.5"
button type="button" data-action="search#clearCheckedPills"
= inline_svg_tag "x-icon.svg", class: 'h-2 w-2 fill-current stroke-current stroke-1 text-blue-medium ml-1 relative'
= inline_svg_tag "solid_filters.svg", class: 'h-4 w-4 fill-current text-gray-2 -ml-0.5 relative', data: { 'search-target': "filtersIcon" }
/ Tabs
- ul class="flex flex-1 gap-x-6 pl-5 pr-6 text-sm list-none overflow-x-auto overflow-y-hidden bg-blue-pale text-gray-2"
+ ul class="flex flex-1 pl-5 pr-6 overflow-x-auto overflow-y-hidden text-sm list-none gap-x-6 bg-blue-pale text-gray-2"
- @tabs_labels.each do |tab_label|
li data-action="click->tabs#change" data-tabs-target="tab"
a class="inline-block py-3 whitespace-nowrap" href="#" = tab_label
li data-action="click->modal#open"
button class="inline-block py-3 whitespace-nowrap" type="button" id="advanced-filters-button"
| Advanced Filters
- span class="relative hidden w-2 h-2 rounded-full bg-salmon ml-1 mb-2" id="appliedIcon"
+ span class="relative hidden w-2 h-2 mb-2 ml-1 rounded-full bg-salmon" id="appliedIcon"
/ Panels
div class="bg-white"
/ Causes
- div class="flex flex-wrap gap-x-2 gap-y-3 hidden max-h-28 py-4 px-6 border-l border-b border-r overflow-y-auto" data-tabs-target="panel"
+ div class="flex flex-wrap hidden px-6 py-4 overflow-y-auto border-b border-l border-r gap-x-2 gap-y-3 max-h-28" data-tabs-target="panel"
- @causes.each do |cause|
= render SearchPills::Pill::Component.new(name: "search[causes][]", value: cause.name, checked: @params.dig(:search, :causes)&.include?(cause.name), options: { multiple: true })
= render SearchPills::MoreFiltersButton::Component.new
/ Location
div class="flex flex-col hidden border-b md:flex-row md:items-center" data-tabs-target="panel"
- div class="py-4 pl-6 text-gray-2 text-sm"
+ div class="py-4 pl-6 text-sm text-gray-2"
span class="inline-flex items-center"
= inline_svg_tag "location-dot.svg", class: "h-3 w-3 fill-current text-blue-medium mr-1"
- | Nashville
+ p data-geolocation-target="currentLocation"
+ = @current_location[:city]
div class="flex w-full md:w-auto md:py-3.5 md:pl-7"
- @radii_in_miles.each do |radius|
= render SearchPills::Button::Component.new(name: "search[distance]", value: miles_to_km(radius), checked: @params.dig(:search, :distance) == miles_to_km(radius).to_s, copy: radius == "Any" ? radius : "#{radius} mi")
/ Services
- div class="flex flex-wrap gap-x-2 gap-y-3 hidden max-h-28 py-4 px-6 border-l border-b border-r overflow-y-auto" data-tabs-target="panel"
+ div class="flex flex-wrap hidden px-6 py-4 overflow-y-auto border-b border-l border-r gap-x-2 gap-y-3 max-h-28" data-tabs-target="panel"
- @services.each do |service|
= render SearchPills::Pill::Component.new(name: "search[services][#{service.cause.name}][]", value: service.name, checked: @params.dig('search', 'services', service.cause.name)&.include?(service.name), options: { multiple: true })
= render SearchPills::MoreFiltersButton::Component.new
/ Populations Served
- div class="flex flex-wrap gap-x-2 gap-y-3 hidden max-h-28 py-4 px-6 border-l border-b border-r overflow-y-auto" data-tabs-target="panel"
+ div class="flex flex-wrap hidden px-6 py-4 overflow-y-auto border-b border-l border-r gap-x-2 gap-y-3 max-h-28" data-tabs-target="panel"
- @beneficiary_subcategories.each do |subcategory|
= render SearchPills::Pill::Component.new(name: "search[beneficiary_groups][#{subcategory.beneficiary_group.name}][]", value: subcategory.name, checked: @params.dig('search', 'beneficiary_groups', subcategory.beneficiary_group.name)&.include?(subcategory.name), options: { multiple: true })
= render SearchPills::MoreFiltersButton::Component.new
/ Hours
- div class="flex hidden gap-x-2 w-full py-4 px-6 border-l border-b border-r" data-tabs-target="panel"
+ div class="flex hidden w-full px-6 py-4 border-b border-l border-r gap-x-2" data-tabs-target="panel"
= render SearchPills::Pill::Component.new(name: "search[open_now]", value: true, checked: @params.dig('search', 'open_now') == 'true') do
| Open Now
= render SearchPills::Pill::Component.new(name: "search[open_weekends]", value: true, checked: @params.dig('search', 'open_weekends') == 'true') do
diff --git a/app/components/search_pills/component.rb b/app/components/search_pills/component.rb
index 2f4fc757c..51a9965f7 100644
--- a/app/components/search_pills/component.rb
+++ b/app/components/search_pills/component.rb
@@ -1,9 +1,10 @@
# frozen_string_literal: true
class SearchPills::Component < ApplicationViewComponent
- def initialize(causes:, services:, beneficiary_subcategories:, params:)
+ def initialize(causes:, services:, current_location:, beneficiary_subcategories:, params:)
@causes = causes
@services = services
+ @current_location = current_location
@beneficiary_subcategories = beneficiary_subcategories
@params = params
@tabs_labels = ["Causes", "Location", "Services", "Populations Served", "Hours"]
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5ad119d21..e2cf253dc 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class ApplicationController < ActionController::Base
+ include Locationable
before_action :store_user_location!, if: :storable_location?
include Pagy::Backend
diff --git a/app/controllers/causes_controller.rb b/app/controllers/causes_controller.rb
index de5063394..594577002 100644
--- a/app/controllers/causes_controller.rb
+++ b/app/controllers/causes_controller.rb
@@ -10,8 +10,20 @@ def index
def show
@cause = Cause.find_by(name: params[:name])
+ @search = Search.new(
+ city: @current_location[:city],
+ state: @current_location[:state],
+ lat: @current_location[:latitude],
+ lon: @current_location[:longitude],
+ causes: [@cause.name]
+ )
+ @locations_by_services = if @search.save
+ @search.results
+ else
+ filtered_locations = Location.locations_with(@cause)
+ Location.sort_by_more_services(filtered_locations)
+ end
+
authorize @cause
- filtered_locations = Location.locations_with_(@cause)
- @locations_by_services = Location.sort_by_more_services(filtered_locations)
end
end
diff --git a/app/controllers/cities_controller.rb b/app/controllers/cities_controller.rb
new file mode 100644
index 000000000..eb519e5f9
--- /dev/null
+++ b/app/controllers/cities_controller.rb
@@ -0,0 +1,11 @@
+class CitiesController < ApplicationController
+ skip_before_action :authenticate_user!
+
+ def show
+ city_name = params[:city]
+ cookies[:city] = city_name
+
+ skip_authorization
+ redirect_to root_path
+ end
+end
diff --git a/app/controllers/concerns/locationable.rb b/app/controllers/concerns/locationable.rb
new file mode 100644
index 000000000..2aaf73681
--- /dev/null
+++ b/app/controllers/concerns/locationable.rb
@@ -0,0 +1,61 @@
+module Locationable
+ extend ActiveSupport::Concern
+ LOCAL_HOST_IP = "127.0.0.1"
+ DEFAULT_LATITUDE = 36.16404968727089
+ DEFAULT_LONGITUDE = -86.78125827725053
+ DEFAULT_STATE = "Tennessee"
+ DEFAULT_COUNTRY = "USA"
+ AVAILABLE_CITIES = ["Nashville", "Atlantic City", "Search all"].freeze
+ DEFAULT_CITY = self::AVAILABLE_CITIES.first
+
+ included do
+ before_action :set_location
+ end
+
+ def set_location
+ @current_location = current_location
+ @locations = self.class::AVAILABLE_CITIES
+ end
+
+ private
+
+ def current_location
+ location_from_params ||
+ location_from_cookie ||
+ default_location
+ # TODO
+ # || location_from_ip || location_from_session
+ end
+
+ def location_from_params
+ return nil if params&.dig("search", "lat").blank? || params&.dig("search", "lon").blank? || params&.dig("search", "city").blank?
+ {
+ city: params&.dig("search", "city"),
+ state: params&.dig("search", "state"),
+ country: params&.dig("search", "country"),
+ latitude: params&.dig("search", "lat"),
+ longitude: params&.dig("search", "lon")
+ }
+ end
+
+ def location_from_cookie
+ return nil if cookies[:city].blank?
+ {
+ city: cookies[:city],
+ state: cookies[:state],
+ country: cookies[:country],
+ latitude: cookies[:latitude],
+ longitude: cookies[:longitude]
+ }
+ end
+
+ def default_location
+ {
+ city: DEFAULT_CITY,
+ state: DEFAULT_STATE,
+ country: DEFAULT_COUNTRY,
+ latitude: DEFAULT_LATITUDE,
+ longitude: DEFAULT_LONGITUDE
+ }
+ end
+end
diff --git a/app/controllers/searches_controller.rb b/app/controllers/searches_controller.rb
index dcfd7a51a..c6bf4369e 100644
--- a/app/controllers/searches_controller.rb
+++ b/app/controllers/searches_controller.rb
@@ -10,7 +10,12 @@ def show
end
set_search_pills_data
- @search = params["search"].present? ? Search.new(create_params) : Search.new
+ @search = params["search"].present? ? Search.new(create_params) : Search.new(
+ city: @current_location[:city],
+ state: @current_location[:state],
+ lat: @current_location[:latitude],
+ lon: @current_location[:longitude]
+ )
@search.save
@pagy, @results = pagy(@search.results)
puts @search.errors.full_messages if @search.results.any?
@@ -18,14 +23,14 @@ def show
authorize @search
end
+ private
+
def create_params
params.require(:search).permit(:distance, :city, :state, :lat, :lon,
:open_now, :open_weekends, :keyword,
:zipcode, causes: [], services: {}, beneficiary_groups: {})
end
- private
-
def set_search_pills_data
set_causes
set_services
diff --git a/app/javascript/controllers/dropdown_controller.js b/app/javascript/controllers/custom_dropdown_controller.js
similarity index 100%
rename from app/javascript/controllers/dropdown_controller.js
rename to app/javascript/controllers/custom_dropdown_controller.js
diff --git a/app/javascript/controllers/extend_dropdown_controller.js b/app/javascript/controllers/extend_dropdown_controller.js
index 2ec745099..68185614e 100644
--- a/app/javascript/controllers/extend_dropdown_controller.js
+++ b/app/javascript/controllers/extend_dropdown_controller.js
@@ -1,4 +1,4 @@
-import Dropdown from './dropdown_controller.js'
+import Dropdown from './custom_dropdown_controller.js'
// Extend Dropdown to close it when you press ESC.
diff --git a/app/javascript/controllers/geolocation_controller.js b/app/javascript/controllers/geolocation_controller.js
new file mode 100644
index 000000000..8040f1054
--- /dev/null
+++ b/app/javascript/controllers/geolocation_controller.js
@@ -0,0 +1,81 @@
+import { Controller } from "@hotwired/stimulus";
+import { useCookies } from "./mixins/useCookies";
+
+ const options = {
+ enableHighAccuracy: true,
+ timeout: 5000,
+ maximumAge: 0
+ };
+
+ const CITIES = {
+ "Nashville" : { latitude: 36.16404968727089, longitude: -86.78125827725053 },
+ "Atlantic City" : { latitude: 39.3625, longitude: -74.425 },
+ "Search all": { latitude: 37.0902, longitude: -95.7129 }
+ }
+export default class extends Controller {
+ static targets = [ "currentLocation", "formLatitude", "formLongitude" ]
+
+ connect() {
+ useCookies(this)
+ this.latitude = this.getCookie("latitude")
+ this.longitude = this.getCookie("longitude")
+ this.currentCity = this.getCookie("city")
+ }
+
+ async getCurrentPosition() {
+ navigator.geolocation.getCurrentPosition(this.success.bind(this), this.error, options);
+ }
+
+ async success(position) {
+ const coordinates = position.coords;
+ this.latitude = coordinates.latitude
+ this.longitude = coordinates.longitude
+ this.currentCity = await this.findNearestCity(coordinates)
+ this.rememberLocation()
+ this.updateCityAndForm()
+ }
+
+ error(err) {
+ console.warn(`ERROR(${err.code}): ${err.message}`)
+ }
+
+ async findNearestCity(coordinates) {
+ let response;
+ const geocoder = new google.maps.Geocoder()
+ const coords= { lat: coordinates.latitude, lng: coordinates.longitude }
+ response = await geocoder.geocode({ location: coords })
+ if (response.results[0]) {
+ return response.results[0].address_components[3].long_name
+ } else {
+ console.warning('No location found');
+ }
+ }
+
+ rememberLocation() {
+ this.setCookie("latitude", this.latitude)
+ this.setCookie("longitude", this.longitude)
+ this.setCookie("city", this.currentCity)
+ }
+
+ updateLocation(event) {
+ this.currentCity = event.target.innerText
+ this.latitude = CITIES[this.currentCity].latitude
+ this.longitude = CITIES[this.currentCity].longitude
+ this.rememberLocation()
+ this.updateCityAndForm()
+ }
+
+ updateCityAndForm() {
+ this.currentLocationTargets.forEach(target => target.innerText = this.currentCity);
+ if (this.hasFormLatitudeTarget && this.hasFormLongitudeTarget) {
+ this.formLongitudeTarget.value = this.longitude;
+ this.formLatitudeTarget.value = this.latitude;
+ }
+
+ // Dispatch a custom event indicating the location has changed
+ const event = new CustomEvent('locationUpdated', {
+ detail: { latitude: this.latitude, longitude: this.longitude }
+ });
+ window.dispatchEvent(event);
+ }
+}
diff --git a/app/javascript/controllers/index.js b/app/javascript/controllers/index.js
index f0dc97211..8744cb896 100644
--- a/app/javascript/controllers/index.js
+++ b/app/javascript/controllers/index.js
@@ -5,11 +5,12 @@ import { Application } from "@hotwired/stimulus"
import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers"
import Carousel from 'stimulus-carousel'
import { Autocomplete } from "stimulus-autocomplete"
-
+import { Dropdown } from "tailwindcss-stimulus-components"
const application = Application.start()
application.register('carousel', Carousel)
application.register('autocomplete', Autocomplete)
+application.register('dropdown', Dropdown)
const context = require.context("controllers", true, /_controller\.js$/)
const contextComponents = require.context("../../components", true, /_controller\.js$/)
application.load(
diff --git a/app/javascript/controllers/mixins/useCookies.js b/app/javascript/controllers/mixins/useCookies.js
new file mode 100644
index 000000000..b03f62e74
--- /dev/null
+++ b/app/javascript/controllers/mixins/useCookies.js
@@ -0,0 +1,12 @@
+export const useCookies = controller => {
+ Object.assign(controller, {
+ getCookie(key) {
+ const cookieValue = document.cookie.split('; ').find(row => row.startsWith(`${key}=`))?.split('=')[1];
+ return cookieValue || null;
+ },
+
+ setCookie(key, value) {
+ document.cookie = `${key}=${value};path=/`;
+ }
+ })
+}
diff --git a/app/javascript/controllers/places_controller.js b/app/javascript/controllers/places_controller.js
index 0f9a9ac90..ef7c33d8b 100644
--- a/app/javascript/controllers/places_controller.js
+++ b/app/javascript/controllers/places_controller.js
@@ -1,4 +1,5 @@
import { Controller } from "@hotwired/stimulus"
+import { useCookies } from "./mixins/useCookies"
export default class extends Controller {
static targets = [ "field", "map", "latitude", "longitude", "marker", "popup" ]
@@ -12,6 +13,7 @@ export default class extends Controller {
}
connect() {
+ useCookies(this)
this.resetMarkers();
this.cleanLocalStorage();
@@ -99,8 +101,11 @@ export default class extends Controller {
initMap() {
this.map = new google.maps.Map(this.mapTarget, {
- center: new google.maps.LatLng(this.latitudeValue || Number(this.latitudeTarget.value) || 36.16404968727089, this.longitudeValue || Number(this.longitudeTarget.value) || -86.78125827725053),
- zoom: (this.zoomValue || 10),
+ center: new google.maps.LatLng(
+ this.getCookie("latitude") || this.latitudeValue || Number(this.latitudeTarget.value) || 36.16404968727089,
+ this.getCookie("longitude") || this.longitudeValue || Number(this.longitudeTarget.value) || -86.78125827725053
+ ),
+ zoom: this.zoomLevel(),
mapTypeControl: true,
mapTypeControlOptions: {
style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
@@ -108,19 +113,6 @@ export default class extends Controller {
}
})
- function success (position) {
- if (document.getElementById('search_lat') && document.getElementById('search_lon')) {
- document.getElementById('search_lat').value = position.coords.latitude;
- document.getElementById('search_lon').value = position.coords.longitude;
- }
- }
-
- if(!navigator.geolocation) {
- console.log('Geolocation is not supported by your browser');
- } else {
- navigator.geolocation.getCurrentPosition(success);
- }
-
const image = this.imageurlValue
const clickedImage = this.clickedimageurlValue
@@ -291,4 +283,12 @@ export default class extends Controller {
this.scheduledFuncId = setTimeout(func, delay);
};
}
+
+ zoomLevel() {
+ if (this.getCookie("city") == "Search all") {
+ return 3;
+ } else {
+ return this.zoomValue || 10;
+ }
+ }
}
diff --git a/app/javascript/controllers/search_controller.js b/app/javascript/controllers/search_controller.js
index b000145af..56fc5426b 100644
--- a/app/javascript/controllers/search_controller.js
+++ b/app/javascript/controllers/search_controller.js
@@ -21,8 +21,12 @@ export default class extends Controller {
connect() {
useDebounce(this, { wait: 250 });
useDispatch(this)
- this.updatePillsCounter()
- this.updateRadioButtonsClass()
+ if (this.hasPillTarget) {
+ this.updatePillsCounter()
+ this.updateRadioButtonsClass()
+ }
+
+ window.addEventListener('locationUpdated', this.handleLocationUpdate.bind(this));
}
initialize() {
@@ -205,7 +209,13 @@ export default class extends Controller {
button.checked = true
button.classList.add("selected-button")
}
- this.updateFiltersState()
- this.submitForm()
+ }
+
+ handleLocationUpdate(event) {
+ this.submitForm();
+ }
+
+ disconnect() {
+ window.removeEventListener('locationUpdated', this.handleLocationUpdate.bind(this));
}
}
diff --git a/app/javascript/controllers/slideover_controller.js b/app/javascript/controllers/slideover_controller.js
index 0d9442189..3908afdfa 100644
--- a/app/javascript/controllers/slideover_controller.js
+++ b/app/javascript/controllers/slideover_controller.js
@@ -1,4 +1,4 @@
-import Dropdown from './dropdown_controller.js'
+import Dropdown from './custom_dropdown_controller.js'
export default class extends Dropdown {
static targets = ['menu', 'overlay']
diff --git a/app/models/location.rb b/app/models/location.rb
index b8f1d24cd..0e9727457 100644
--- a/app/models/location.rb
+++ b/app/models/location.rb
@@ -32,8 +32,10 @@ class Location < ActiveRecord::Base
scope :active, -> { joins(:organization).where(organization: {active: true}) }
scope :public_address, -> { where(public_address: true) }
scope :besides_po_boxes, -> { where(po_box: false) }
- # scope :in_nashville, -> { where("ST_DWithin(lonlat, ST_GeographyFromText('SRID=4326;POINT(-86.78125827725053 36.16404968727089)'), 1000000) = true") }
scope :locations_with_, ->(cause) { group(:id).joins(:causes).where(causes: {id: cause.id}) }
+ scope :national, -> { joins(:organization).where(organization: {scope_of_work: "National"}) }
+ scope :international, -> { joins(:organization).where(organization: {scope_of_work: "International"}) }
+ scope :national_and_international, -> { national.or(international) }
has_many :office_hours
has_many :favorite_locations, dependent: :destroy
diff --git a/app/models/search.rb b/app/models/search.rb
index 68c120859..75fe8e0fe 100644
--- a/app/models/search.rb
+++ b/app/models/search.rb
@@ -20,18 +20,38 @@ def save
end
def execute_search
- filters = {
+ @results = (city == "Search all") ? Location.active : geolocation_query
+
+ # Filter and keyword search
+ @results = Location.joins(:organization).where(id: Locations::FilterQuery.call(filters, @results).ids)
+ @results = keyword.present? ? Locations::KeywordQuery.call({keyword: keyword}, @results) : @results
+ end
+
+ private
+
+ def geolocation_query
+ @results = Locations::GeolocationQuery.call(geo_filters)
+ # Merge with national or international locations
+ national_or_international_locations = Location.national_and_international.ids
+ Location.where(id: @results.ids + national_or_international_locations).distinct
+ end
+
+ def filters
+ {
address: {city: city.presence, state: state.presence, zipcode: zipcode.presence},
open_now: ActiveModel::Type::Boolean.new.cast(open_now),
open_weekends: ActiveModel::Type::Boolean.new.cast(open_weekends),
beneficiary_groups: beneficiary_groups,
services: services,
- causes: causes,
- distance: distance.presence&.to_i,
- lat: lat.presence&.to_f, lon: lon.presence&.to_f
+ causes: causes
}
+ end
- @results = Location.where(id: Locations::FilterQuery.call(filters, Location.active).pluck(:id))
- @results = keyword.present? ? Locations::KeywordQuery.call({keyword: keyword}, @results) : @results
+ def geo_filters
+ {
+ distance: distance.presence&.to_i,
+ lat: lat.presence&.to_f,
+ lon: lon.presence&.to_f
+ }
end
end
diff --git a/app/queries/locations/filter_query.rb b/app/queries/locations/filter_query.rb
index 56eabda18..bdf040053 100644
--- a/app/queries/locations/filter_query.rb
+++ b/app/queries/locations/filter_query.rb
@@ -2,18 +2,12 @@
module Locations
class FilterQuery
- DEFAULT_LOCATION = {
- latitude: 36.16404968727089,
- longitude: -86.78125827725053
- }.freeze
-
attr_reader :locations
class << self
def call(params = {}, locations = Location.active)
scope = locations
- scope = geo_near(scope, starting_coordinates(params[:lat], params[:lon]), params[:distance])
- scope = by_address(scope, params[:address])
+ # scope = by_address(scope, params[:address])
scope = by_cause(scope, params[:causes])
scope = by_service(scope, params[:services])
scope = by_beneficiary_groups_served(scope, params[:beneficiary_groups])
diff --git a/app/queries/locations/geolocation_query.rb b/app/queries/locations/geolocation_query.rb
new file mode 100644
index 000000000..5256d1809
--- /dev/null
+++ b/app/queries/locations/geolocation_query.rb
@@ -0,0 +1,37 @@
+module Locations
+ class GeolocationQuery
+ DEFAULT_LOCATION = {
+ latitude: 36.16404968727089,
+ longitude: -86.78125827725053
+ }.freeze
+ DEFAULT_DISTANCE = 80 # km
+
+ attr_reader :locations
+
+ class << self
+ def call(params = {}, locations = Location.active)
+ scope = locations
+ geo_near(scope, starting_coordinates(params[:lat], params[:lon]), params[:distance])
+ end
+
+ def geo_near(scope, coords, distance)
+ return scope if scope.empty?
+
+ distance = DEFAULT_DISTANCE if distance.blank? || distance.zero?
+
+ scope.where(
+ "ST_DWithin(lonlat, :point, :distance)",
+ {point: coords, distance: distance * 1000} # wants meters not kms
+ )
+ end
+
+ def starting_coordinates(lat, lon)
+ if lat.nil? || lon.nil?
+ Geo.to_wkt(Geo.point(DEFAULT_LOCATION[:longitude], DEFAULT_LOCATION[:latitude]))
+ else
+ Geo.to_wkt(Geo.point(lon, lat))
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/random_coordinates_generator.rb b/app/services/random_coordinates_generator.rb
new file mode 100644
index 000000000..5493afc59
--- /dev/null
+++ b/app/services/random_coordinates_generator.rb
@@ -0,0 +1,25 @@
+class RandomCoordinatesGenerator < ApplicationService
+ EARTH_RADIOUS = 6371 # km
+ ONE_DEGREE = EARTH_RADIOUS * 2 * Math::PI / 360 * 1000 # 1° latitude in meters
+
+ def initialize(central_lat:, central_lng:, max_radius:)
+ @central_lat = central_lat
+ @central_lng = central_lng
+ @max_radius = max_radius
+ end
+
+ def call
+ random_coords = {}
+
+ dx, dy = random_point_in_disk
+ random_coords[:lat] = @central_lat + (dy / ONE_DEGREE)
+ random_coords[:lng] = @central_lng + (dx / (ONE_DEGREE * Math.cos(@central_lat * Math::PI / 180)))
+ random_coords
+ end
+
+ def random_point_in_disk
+ r = @max_radius * (rand**0.5)
+ theta = rand * 2 * Math::PI
+ [r * Math.cos(theta), r * Math.sin(theta)]
+ end
+end
diff --git a/app/views/causes/show.html.slim b/app/views/causes/show.html.slim
index 5e208679a..6df6acdd0 100644
--- a/app/views/causes/show.html.slim
+++ b/app/views/causes/show.html.slim
@@ -1,5 +1,5 @@
-div class="relative flex justify-center items-center h-72 bg-blue-gradient-3"
- div class="absolute flex justify-center w-full overflow-hidden" aria-hidden="true"
+div class="flex relative justify-center items-center h-72 bg-blue-gradient-3"
+ div class="flex overflow-hidden absolute justify-center w-full" aria-hidden="true"
div class="grow"
= inline_svg_tag 'new-blur-left.svg', size:'723*288', class: "md:w-full"
div class="grow"
@@ -11,26 +11,38 @@ div class="relative flex justify-center items-center h-72 bg-blue-gradient-3"
span class="absolute top-16 right-6 md:top-52 525px:right-21%" aria-hidden="true"
= inline_svg_tag 'simple-heart.svg', size: '22*22', class: "fill-current text-blue-dark"
- h1 class="absolute px-4 text-3xl leading-normal font-bold text-center tracking-wide text-blue-dark dark:text-white"
+ h1 class="absolute px-4 text-3xl font-bold tracking-wide leading-normal text-center text-blue-dark dark:text-white"
= @cause.name
-section class="w-5/6 max-w-6xl mx-auto mt-8 mb-44 md:mt-20"
+section class="mx-auto mt-8 mb-44 w-5/6 max-w-6xl md:mt-20"
h2 class="font-sans text-lg tracking-wide text-blue-medium"
= link_to 'Discover', discover_path, class: "font-bold underline"
span class="mx-1"
| /
= @cause.name
- - if @locations_by_services.empty?
- div class="flex flex-col items-center mt-5"
- = inline_svg_tag 'empty_state_4.svg', class: "w-80", aria_hidden: true
- div class="w-60 -mt-5 text-center"
- h3 class="mb-5 font-bold text-lg leading-6"
- | We currently don’t have any nonprofits that match this cause.
- p class="mb-10 text-sm"
- | We are constantly adding new nonprofits to our platform, please check back soon!
- = link_to "Go Back to Discover", discover_path, class: "text-blue-medium"
- - else
- div class="grid grid-cols-auto-fill-18 justify-items-stretch gap-7 mt-12"
- - @locations_by_services.each do |location|
- = render DiscoverNonprofitCard::Component.new(user: current_user, location: location)
+ = form_with model: @search, \
+ url: cause_path(@cause), \
+ method: :get, \
+ data: { \
+ controller: "search", \
+ "search-target": "form", \
+ turbo_frame: "causes_locations", \
+ turbo_action: "replace" } do |f|
+ = f.hidden_field :cause_id, value: @cause.id
+ = f.hidden_field :lat, value: @current_location[:latitude], data: { geolocation_target: "formLatitude" }
+ = f.hidden_field :lon, value: @current_location[:longitude], data: { geolocation_target: "formLongitude" }
+
+ - if @locations_by_services.empty?
+ div class="flex flex-col items-center mt-5"
+ = inline_svg_tag 'empty_state_4.svg', class: "w-80", aria_hidden: true
+ div class="-mt-5 w-60 text-center"
+ h3 class="mb-5 text-lg font-bold leading-6"
+ | We currently don’t have any nonprofits that match this cause.
+ p class="mb-10 text-sm"
+ | We are constantly adding new nonprofits to our platform, please check back soon!
+ = link_to "Go Back to Discover", discover_path, class: "text-blue-medium"
+ - else
+ div class="grid gap-7 justify-items-stretch mt-12 grid-cols-auto-fill-18"
+ - @locations_by_services.each do |location|
+ = render DiscoverNonprofitCard::Component.new(user: current_user, location: location)
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index 77c63332b..bf7d6b4fc 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -1,16 +1,16 @@
main
- section class="relative hidden bg-electric-teal" data-controller="banner" data-banner-target="banner"
- div class="flex max-w-3xl mx-auto py-7 md:py-8"
+ section class="hidden relative bg-electric-teal" data-controller="banner" data-banner-target="banner"
+ div class="flex py-7 mx-auto max-w-3xl md:py-8"
div class="hidden md:block md:pl-4 lg:pl-0"
= inline_svg_tag 'megaphone.svg', size: '65*70'
- div class="pl-8 pr-10 md:px-0 md:ml-8 text-gray-1 "
- div class="text-sm md:text-base mb-1.5 font-bold"
- | We officially launched in Nashville!
+ div class="pr-10 pl-8 md:px-0 md:ml-8 text-gray-1"
+ div class="mb-1.5 text-sm font-bold md:text-base"
+ | We officially launched in Nashville & Atlantic City!
div class="text-xs leading-5 md:leading-6 md:text-sm md:pr-2"
- | We currently only have nonprofit listings in Nashville, TN.
+ | We currently only have nonprofit listings in Nashville, TN and Atlantic City, NJ.
br
| We will enable searching by City, State, and Zip Code once we expand to other cities.
- div class="absolute inset-y-0 right-0 flex items-start pt-1 pr-1 sm:pt-1.5 sm:pr-5 sm:items-start"
+ div class="flex absolute inset-y-0 right-0 items-start pt-1 pr-1 sm:pt-1.5 sm:pr-5 sm:items-start"
button class="flex p-2 rounded-md hover:bg-gray" type="button" data-action="banner#closeBanner"
span.sr-only Dismiss
= inline_svg_tag 'x-icon.svg', size: '14*14'
@@ -22,18 +22,35 @@ main
| Search for listings of nonprofit organizations based on your needs.
= form_with model: @search, url: search_path, method: :get do |f|
.c-form class="py-0 mx-auto sm:px-3 sm:flex-col sm:justify-center sm:max-w-4xl"
- div class="relative w-full sm:max-w-xl mb-7 sm:mb-0"
- = f.text_field :keyword, autocomplete: "search", class:"c-input pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"", data: { test_id: "home_search_input" }
- = inline_svg_tag 'search-icon.svg', class:"absolute top-1/3 left-4"
+ div class="flex relative p-0 mb-7 w-full rounded-md divide-x c-input sm:max-w-xl sm:mb-0"
+ div class="flex relative w-full"
+ = f.text_field :keyword, autocomplete: "search", class:"border-none rounded-l-md pl-10 m-0 w-full", placeholder: "Try \"Mental Health Nonprofits\"", data: { test_id: "home_search_input" }
+ = inline_svg_tag 'search-icon.svg', class:"absolute top-1/3 left-4"
+ div class="flex m-0 w-52 font-medium bg-white rounded-r-md cursor-pointer" data-controller="dropdown" data-action="click@window->dropdown#hide touchstart@window->dropdown#hide keydown.up->dropdown#previousItem keydown.down->dropdown#nextItem keydown.esc->dropdown#hide"
+ button type="button" class="flex items-center pl-2 md:w-52 border-l-gray" data-action="dropdown#toggle:stop"
+ = inline_svg_tag "location-dot.svg", size:"9*9", class:"h-7 mr-2 fill-current"
+ p data-geolocation-target="currentLocation"
+ = @current_location[:city]
+ div data-dropdown-target="menu" class="flex absolute right-0 top-11 z-10 w-52"
+ ul class="overflow-hidden w-full text-sm bg-white rounded border shadow-lg"
+ - @locations.each do |location|
+ li class="block px-4 py-2 text-gray-3 hover:bg-seafoam" data-action="click->dropdown#toggle click->geolocation#updateLocation" data-dropdown-target="menuItem"
+ = location
+ li class="block flex items-center px-4 py-1 font-medium text-gray-3 border-gray-5 hover:bg-seafoam focus:bg-seafoam" data-action="click->dropdown#toggle click->geolocation#updateLocation" data-dropdown-target="menuItem"
+ = inline_svg_tag "my-location.svg", size:"12*12", class:"h-7 mr-2 fill-current text-gray-3"
+ | Search near me
+ div
+ = f.hidden_field :lat, value: @current_location[:latitude], data: { geolocation_target: "formLatitude" }
+ = f.hidden_field :lon, value: @current_location[:longitude], data: { geolocation_target: "formLongitude" }
div
= f.submit "Search", class:"c-button mx-10 mt-6", data: { test_id: "home_search_btn" }
section class="pt-6 text-gray-2 sm:pt-10"
h2 class="pb-8 text-2xl font-bold text-center uppercase sm:pb-5"
| How it works
- div class="flex flex-col mx-auto max-w-7xl lg:flex-row justify-evenly xl:px-16"
- div class="max-w-xs mx-auto text-center mb-9 lg:mb-0"
+ div class="flex flex-col justify-evenly mx-auto max-w-7xl lg:flex-row xl:px-16"
+ div class="mx-auto mb-9 max-w-xs text-center lg:mb-0"
div class="flex items-end mb-2 h-36"
- = inline_svg_tag 'search-needs.svg', class:'mx-auto'
+ = inline_svg_tag 'search-needs.svg', class:'mx-auto'
h5 class="pb-3 font-bold uppercase md:pb-1"
| 1. Search for your needs
p.px-8
@@ -45,15 +62,15 @@ main
| 2. Connect with organizations
p.px-8
| Browse through nonprofit profiles to find the one that matches your needs.
- div class="max-w-xs mx-auto text-center"
+ div class="mx-auto max-w-xs text-center"
div class="flex items-end mb-2 h-36"
= inline_svg_tag 'help.svg', class:'mx-auto'
h5 class="pb-3 font-bold uppercase md:pb-1"
| 3. Receive help
p class="px-5"
| Found a nonprofit that meets your needs criteria? Reach out through the contact information provided on their individual profiles.
- section class="flex flex-col items-center px-6 overflow-hidden"
- div class="relative max-w-xl pt-32 mb-14"
+ section class="flex overflow-hidden flex-col items-center px-6"
+ div class="relative pt-32 mb-14 max-w-xl"
h2 class="mb-6 text-2xl font-bold text-center uppercase text-gray-2 dark:text-white"
| Browse by causes
p class="text-center"
@@ -62,7 +79,7 @@ main
| Scroll through listings to browse for nonprofits meeting your needs.
= inline_svg_tag "browse-causes-green-blur.svg", class: "absolute -left-1/4 top-0 -z-1 w-full", aria_hidden: true
= inline_svg_tag "browse-causes-purple-blur.svg", class: "absolute -right-1/4 top-0 -z-1 w-full", aria_hidden: true
- nav class="w-full max-w-5xl mb-9"
+ nav class="mb-9 w-full max-w-5xl"
= render CausesList::Component.new(causes: Cause.top(limit: 9), \
icon_svg_options: {class: "w-1/2", aria_hidden: true}, \
list_options: { class: "grid grid-cols-auto-fill-6.75 justify-items-center gap-y-6"}, \
@@ -70,32 +87,32 @@ main
icon_wrapper_options: { class: "thick-icon w-20 h-20 transition-colors group-hover:bg-blue-medium", aria_hidden: true})
= link_to "see more", discover_path << "/#causes-section", class: "px-10 py-4 rounded-md mb-32 text-white leading-5 uppercase tracking-wider font-bold bg-blue-dark transition-colors hover:bg-blue-medium"
- section class="flex flex-col mx-auto md:flex-row max-w-7xl justify-evenly sm:mb-1 525px:pb-20 md:px-8"
- div class="relative flex flex-col justify-center mb-16 text-center md:mx-0 md:mb-0"
- div class="max-w-sm py-4 mx-auto"
+ section class="flex flex-col justify-evenly mx-auto max-w-7xl md:flex-row sm:mb-1 525px:pb-20 md:px-8"
+ div class="flex relative flex-col justify-center mb-16 text-center md:mx-0 md:mb-0"
+ div class="py-4 mx-auto max-w-sm"
h2 class="px-10 pb-4 text-2xl font-bold text-center text-gray-2"
| We want to make living and giving simpler.
p class="px-4 pt-3 pb-6 md:pb-10"
| We are a nonprofit search tool connecting you with resources in your community.
= link_to "Start your search", '#search', class:'c-button inline-block text-white bg-blue-medium'
- div class="hidden sm:block sm:absolute -top-20 -left-32 -z-1"
+ div class="hidden -top-20 -left-32 sm:block sm:absolute -z-1"
= inline_svg_tag 'lw-desk-blur-left.svg'
- div class="right-0 hidden overflow-hidden sm:block sm:absolute -top-40 xl:-right-28 -z-1"
+ div class="hidden overflow-hidden right-0 -top-40 sm:block sm:absolute xl:-right-28 -z-1"
= inline_svg_tag 'lw-desk-blur-right.svg'
div class="absolute -left-6 -z-1 sm:hidden"
= inline_svg_tag 'lw-mob-blur-left.svg', size:'320*428'
- div class="absolute right-0 overflow-visible -top-40 -z-1 sm:hidden"
+ div class="overflow-visible absolute right-0 -top-40 -z-1 sm:hidden"
= inline_svg_tag 'lw-mob-blur-right.svg'
div class="flex justify-center px-0"
= image_tag 'board-members.png', class:"525px:rounded-xl object-cover h-350px sm:h-full"
section class="relative bg-seafoam"
- div class="relative max-w-md pb-20 mx-auto text-center"
- h2 class="px-5 pt-20 text-2xl font-bold text-center sm:px-0 md:pt-28 text-blue-dark pb-11"
+ div class="relative pb-20 mx-auto max-w-md text-center"
+ h2 class="px-5 pt-20 pb-11 text-2xl font-bold text-center sm:px-0 md:pt-28 text-blue-dark"
| Are you a nonprofit organization interested in joining our listings?
= link_to 'Add or Claim a Nonprofit', new_nonprofit_request_path, class:'c-button inline-block my-1 text-white bg-blue-dark'
- div class="top-0 left-0 hidden -z-1 sm:block sm:absolute"
+ div class="hidden top-0 left-0 -z-1 sm:block sm:absolute"
= inline_svg_tag 'lc-desk-blur-left.svg'
- div class="top-0 right-0 hidden -z-1 sm:block sm:absolute"
+ div class="hidden top-0 right-0 -z-1 sm:block sm:absolute"
= inline_svg_tag 'lc-desk-blur-right.svg'
- div class="absolute bottom-0 right-0 -z-1 sm:hidden"
+ div class="absolute right-0 bottom-0 -z-1 sm:hidden"
= inline_svg_tag 'lc-mob-blur-right.svg'
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 69b9be654..0f125bcfa 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -28,12 +28,12 @@
- <%= render Navbar::Component.new(signed_in: user_signed_in?) %>
+ <%= render Navbar::Component.new(signed_in: user_signed_in?, current_location: @current_location, locations: @locations) %>
<%= render "shared/flash_messages" %>
diff --git a/app/views/locations/show.html.slim b/app/views/locations/show.html.slim
index 6183d928a..6da19a02c 100644
--- a/app/views/locations/show.html.slim
+++ b/app/views/locations/show.html.slim
@@ -1,5 +1,5 @@
= form_with model: @search, scope: :search, url: search_path, class: '', method: :get do |f|
- = render SearchBar::Component.new(form: f, search: @search)
+ = render SearchBar::Component.new(form: f, search: @search, current_location: @current_location, locations: @locations)
div class=""
div class="flex flex-row justify-between w-full bg-gray-9 md:hidden" data-controller="modal"
div class="flex flex-col flex-wrap justify-center w-full sm:pb-14 md:gap-4 md:flex-row sm:p-7"
diff --git a/app/views/searches/_preview.html.slim b/app/views/searches/_preview.html.slim
index 3b94f436d..866e76361 100644
--- a/app/views/searches/_preview.html.slim
+++ b/app/views/searches/_preview.html.slim
@@ -8,14 +8,14 @@
turbo_action: "replace", \
controller: 'search modal', \
'search-target': 'form'}) do |f|
- = render SearchBar::Component.new(form: f, search: @search)
+ = render SearchBar::Component.new(form: f, search: @search, current_location: @current_location, locations: @locations)
= turbo_frame_tag "search-pills", src: search_path() do
div class="w-full bg-gray-9"
div
div data-tabs-active-tab=("border-b-4 border-blue-medium")
- div class="flex pb-14 bg-white border-b"
+ div class="flex bg-white border-b pb-14"
/ Clear-Counter button
- span class="flex justify-center items-center w-14 pl-5 py-3 pr-2 text-xs bg-blue-pale"
+ span class="flex items-center justify-center py-3 pl-5 pr-2 text-xs w-14 bg-blue-pale"
span class="inline-flex hidden items-center px-1 py-0.5 border border-blue-medium rounded-full bg-white" data-search-target="pillsCounterWrapper"
span class="mr-0.5" data-search-target="pillsCounter"
button type="button"
@@ -23,7 +23,7 @@
= inline_svg_tag "solid_filters.svg", class: 'h-4 w-4 fill-current text-gray-2 -ml-0.5 relative', data: { 'search-target': "filtersIcon" }
/ Tabs
- tabs_labels = ['Causes', 'Location', 'Services', 'Populations Served', 'Hours']
- ul class="flex flex-1 gap-x-6 pl-5 pr-6 text-sm list-none overflow-x-auto overflow-y-hidden bg-blue-pale text-gray-2"
+ ul class="flex flex-1 pl-5 pr-6 overflow-x-auto overflow-y-hidden text-sm list-none gap-x-6 bg-blue-pale text-gray-2"
- tabs_labels.each do |tab_label|
li
span class="inline-block py-3 whitespace-nowrap"
@@ -31,11 +31,11 @@
li
button class="inline-block py-3 whitespace-nowrap" type="button" id="advanced-filters-button" disabled="true"
| Advanced Filters
- span class="relative hidden w-2 h-2 rounded-full bg-salmon ml-1 mb-2" id="appliedIcon"
+ span class="relative hidden w-2 h-2 mb-2 ml-1 rounded-full bg-salmon" id="appliedIcon"
= turbo_frame_tag "search-locations", src: search_path() do
- div class="md:flex w-full md:h-85vh" data-controller="tabs" data-tabs-active-tab="border-blue-dark bg-blue-dark text-white" data-tabs-inactive-tab="border-gray-6 bg-transparent text-gray-3"
- div class="w-full md:border-r max-w-sm border-gray-7"
+ div class="w-full md:flex md:h-85vh" data-controller="tabs" data-tabs-active-tab="border-blue-dark bg-blue-dark text-white" data-tabs-inactive-tab="border-gray-6 bg-transparent text-gray-3"
+ div class="w-full max-w-sm md:border-r border-gray-7"
div class="fixed inset-0 flex items-center justify-center hidden min-h-screen overflow-y-auto animated fadeIn" data-action="click->modal#closeBackground keyup@window->modal#closeWithKeyboard" data-modal-target="container" style=("z-index: 9999;")
div class="relative w-full max-h-full max-w-375px"
div class="flex flex-row justify-between w-full p-4 bg-gray-9 md:hidden"
@@ -48,22 +48,22 @@
| Map
// Search result cards (mobile)
div class="flex flex-col w-full bg-white md:h-85vh md:overflow-y-auto md:hidden" data-tabs-target="panel"
- div class="flex justify-center items-center h-full"
+ div class="flex items-center justify-center h-full"
div class="spinner"
// Search result cards
div class="hidden w-full bg-white md:h-85vh md:flex md:flex-col md:overflow-y-auto"
- div class="flex justify-center items-center h-full"
+ div class="flex items-center justify-center h-full"
div class="spinner"
//Map mobile
- div class="md:hidden relative w-full"
+ div class="relative w-full md:hidden"
div data-tabs-target="panel" data-controller="places result-card--component" data-action="google-maps-callback@window->places#initMap" data-places-zoom-value="10" data-places-imageurl-value="#{asset_path 'markergc.png'}" data-places-clickedimageurl-value="#{asset_path 'markerinvgc.png'}" data-places-popup-url-value="/map_popup/new?location_id="
div class="absolute inset-0 w-full h-full xs:h-85vh" data-places-target="map"
div class="hidden"
div class="hidden" data-places-target="latitude"
div class="hidden" data-places-target="longitude"
//Map desktop
- div class="hidden md:block relative w-full"
+ div class="relative hidden w-full md:block"
div data-controller="places result-card--component" data-action="google-maps-callback@window->places#initMap" data-places-zoom-value="10" data-places-imageurl-value="#{asset_path 'markergc.png'}" data-places-clickedimageurl-value="#{asset_path 'markerinvgc.png'}" data-places-popup-url-value="/map_popup/new?location_id="
div class="absolute inset-0 w-full h-full xs:h-85vh" data-places-target="map"
div class="hidden"
diff --git a/app/views/searches/show.html.slim b/app/views/searches/show.html.slim
index 012406613..75cc68204 100644
--- a/app/views/searches/show.html.slim
+++ b/app/views/searches/show.html.slim
@@ -9,13 +9,13 @@
turbo_action: "replace", \
controller: 'search modal', \
'search-target': 'form'}) do |f|
- = render SearchBar::Component.new(form: f, search: @search)
+ = render SearchBar::Component.new(form: f, search: @search, current_location: @current_location, locations: @locations)
= turbo_frame_tag "search-pills" do
- = render SearchPills::Component.new(causes: @top_10_causes, services: @top_10_services, beneficiary_subcategories: @top_10_beneficiary_subcategories, params: params)
+ = render SearchPills::Component.new(causes: @top_10_causes, services: @top_10_services, beneficiary_subcategories: @top_10_beneficiary_subcategories, params: params, current_location: @current_location)
= turbo_frame_tag "search-locations" do
div class="flex flex-row justify-center w-full md:h-85vh" data-controller="tabs" data-tabs-active-tab="border-blue-dark bg-blue-dark text-white" data-tabs-inactive-tab="border-gray-6 bg-transparent text-gray-3"
div class="flex flex-row justify-center w-full min-h-full"
- div class="flex flex-col w-full md:border-r max-w-sm border-gray-7"
+ div class="flex flex-col w-full max-w-sm md:border-r border-gray-7"
div class="fixed inset-0 flex items-center justify-center hidden min-h-screen overflow-y-auto animated fadeIn" data-action="click->modal#closeBackground keyup@window->modal#closeWithKeyboard" data-modal-target="container" style=("z-index: 9999;")
div class="relative w-full max-h-full max-w-375px"
= render partial: 'filter', locals: { form: f, search: @search, services: @services, causes: @causes, beneficiary_groups: @beneficiary_groups }
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 0c77f3840..2e364d5d8 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -277,8 +277,8 @@
#
# ==> Configure responses to match Hotwire/Turbo behavior.
- config.responder.error_status = :unprocessable_entity
- config.responder.redirect_status = :see_other
+ # config.responder.error_status = :unprocessable_entity
+ # config.responder.redirect_status = :see_other
# ==> Mountable engine configurations
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
diff --git a/config/routes.rb b/config/routes.rb
index a3c5e0138..a0662d866 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,5 +1,3 @@
-# frozen_string_literal: true
-
Rails.application.routes.draw do
require "sidekiq/web"
mount Sidekiq::Web => "/sidekiq"
@@ -72,4 +70,8 @@
resources :autocomplete, only: %i[index]
root to: "home#index"
+
+ # Custom routes for city-based search
+ get "/atlantic_city", to: "cities#show", city: "Atlantic City", as: :atlantic_city
+ get "/nashville", to: "cities#show", city: "Nashville", as: :nashville
end
diff --git a/db/seeds.rb b/db/seeds.rb
index 677397c88..701dff899 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -53,4 +53,9 @@
phone.number = "222-333-4444"
main = false
end
+ # Create random location around cities in US
+ Rake::Task["populate:random_locations"].invoke
+
+ # Create organizations and causes association
+ Rake::Task["populate:seed_organizations_causes"].invoke
end
diff --git a/lib/assets/us_cities_coords.xlsx b/lib/assets/us_cities_coords.xlsx
new file mode 100644
index 000000000..ebfd3305b
Binary files /dev/null and b/lib/assets/us_cities_coords.xlsx differ
diff --git a/lib/tasks/populate.rake b/lib/tasks/populate.rake
index baf48c3b6..42a3e4909 100644
--- a/lib/tasks/populate.rake
+++ b/lib/tasks/populate.rake
@@ -18,4 +18,33 @@ namespace :populate do
end
end
end
+
+ desc "Seed organizations and causes association"
+ task seed_organizations_causes: :environment do
+ Organization.all.find_each do |organization|
+ OrganizationCause.create!(organization: organization, cause: Cause.all.sample)
+ organization.update!(active: true)
+ end
+ end
+
+ desc "Seed random Locations arround 1000 US cities"
+ task random_locations: :environment do
+ wb = Roo::Spreadsheet.open "./lib/assets/us_cities_coords.xlsx"
+ sheet = wb.sheet(0)
+ cities = sheet.parse(place_name: "place_name", latitude: "latitude", longitude: "longitude", clean: true)
+
+ cities.each do |city|
+ 2.times do
+ random_coords = RandomCoordinatesGenerator.call(central_lat: city[:latitude].to_f, central_lng: city[:longitude].to_f, max_radius: 1000)
+ Location.create!(
+ organization_id: Organization.all.sample.id,
+ name: Faker::Company.name,
+ address: city[:place_name],
+ latitude: random_coords[:lat],
+ longitude: random_coords[:lng],
+ offer_services: false
+ )
+ end
+ end
+ end
end