Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions app/Router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import { ContentPage } from "pages/ContentPage";
import { FaqPage } from "pages/FaqPage/FaqPage";
import { OrganizationDetailPage } from "pages/OrganizationDetailPage";
import { ServiceDetailPage } from "pages/ServiceDetailPage/ServiceDetailPage";
import { BrowseResultsPage } from "pages/BrowseResultsPage/BrowseResultsPage";
import { ResultsPage } from "pages/ResultsPage/ResultsPage";
import { PageHeader } from "components/ui/Navigation/PageHeader";
import { BackButton } from "components/ui/BackButton";
import { SearchHeaderSection } from "components/SearchAndBrowse/Header/SearchHeaderSection";
import { SearchResultsPage } from "pages/SearchResultsPage/SearchResultsPage";
import { PageNotFoundPage } from "pages/PageNotFoundPage/PageNotFoundPage";
import { EventDetailPage } from "pages/EventDetailPage/EventDetailPage";

Expand Down Expand Up @@ -48,7 +47,7 @@ export const Router = () => {
<PageHeader variant="secondary">
<SearchHeaderSection descriptionText="Sign up for programs and access resources." />
</PageHeader>
<SearchResultsPage />
<ResultsPage />
</>
}
/>
Expand Down Expand Up @@ -80,7 +79,7 @@ export const Router = () => {
path="/terms-of-service"
element={<ContentPage pageName="Terms of Service" />}
/>
<Route path="/:categorySlug/results" element={<BrowseResultsPage />} />
<Route path="/:categorySlug/results" element={<ResultsPage />} />
<Route path="*" element={<PageNotFoundPage />} />
</Routes>
);
Expand Down
108 changes: 45 additions & 63 deletions app/components/SearchAndBrowse/SearchMap/SearchMap.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useState, useRef } from "react";
import React, { useState } from "react";
import GoogleMap from "google-map-react";
import { Tooltip } from "react-tippy";
import "react-tippy/dist/tippy.css";
import SearchEntry from "components/SearchAndBrowse/SearchMap/SearchEntry";
import { useAppContext, useAppContextUpdater } from "utils";
import {
useAppContext,
useAppContextUpdater,
SF_MAP_BOUNDS,
COORDS_MID_SAN_FRANCISCO,
} from "utils";
import { groupHitsByLocation, computeGridOffset } from "utils/map";
import { Button } from "components/ui/inline/Button/Button";
import {
Expand Down Expand Up @@ -41,12 +46,8 @@ export const SearchMap = ({
null
);
const [clickedHitId, setClickedHitId] = useState<string | null>(null);
const { userLocation, aroundLatLng, boundingBox } = useAppContext();
const { setAroundLatLng, setBoundingBox } = useAppContextUpdater();

// Stores the LatLngBounds captured when the map first becomes idle.
// Used to restore the original view when the user performs a new search.
const initialBoundsRef = useRef<google.maps.LatLngBounds | null>(null);
const { userLocation } = useAppContext();
const { setBoundingBox } = useAppContextUpdater();

// Pan to custom center and zoom when they change (e.g. from distance filter)
React.useEffect(() => {
Expand Down Expand Up @@ -77,13 +78,17 @@ export const SearchMap = ({
}
}, [googleMapObject, customCenter, customZoom, setBoundingBox]);

// When resetViewCount increments, fit the map back to the initial bounding
// box captured on first load, then re-capture the actual rendered bounds.
// When resetViewCount increments, fit the map back to the full SF view.
// Always use SF_MAP_BOUNDS directly (not previously captured rendered bounds)
// to avoid progressive zoom-out from compounding fitBounds padding.
React.useEffect(() => {
if (!googleMapObject || !resetViewCount || !initialBoundsRef.current)
return;
if (!googleMapObject || !resetViewCount) return;

googleMapObject.fitBounds(initialBoundsRef.current);
const sfBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(SF_MAP_BOUNDS.sw.lat, SF_MAP_BOUNDS.sw.lng),
new google.maps.LatLng(SF_MAP_BOUNDS.ne.lat, SF_MAP_BOUNDS.ne.lng)
);
googleMapObject.fitBounds(sfBounds);

const idleListener = googleMapObject.addListener("idle", () => {
google.maps.event.removeListener(idleListener);
Expand Down Expand Up @@ -126,23 +131,6 @@ export const SearchMap = ({
}
}

const aroundLatLngToMapCenter = {
lat: Number(aroundLatLng.split(",")[0]),
lng: Number(aroundLatLng.split(",")[1]),
};

// Center the map to the user's choice (`aroundLatLng`) with a fallback to our best guess when sniffing their
// location on app start (`userLocation`)
const googleMapsCenter = () => {
if (aroundLatLng) {
return aroundLatLngToMapCenter;
} else if (userLocation) {
return { lat: userLocation?.coords.lat, lng: userLocation?.coords.lng };
} else {
return undefined;
}
};

const groupedHits = groupHitsByLocation(hits);

const markers = Object.keys(groupedHits).flatMap((key) => {
Expand Down Expand Up @@ -215,47 +203,41 @@ export const SearchMap = ({
key: config.GOOGLE_API_KEY,
libraries: ["places"],
}}
center={googleMapsCenter()}
defaultZoom={14}
defaultCenter={COORDS_MID_SAN_FRANCISCO}
defaultZoom={11}
onGoogleApiLoaded={({ map }) => {
// SetMapObject shares the Google Map object across parent/sibling components
// so that they can adjustments to markers, coordinates, layout, etc.,
setMapObject(map);

// If we have a bounding box from App.tsx, fit the map to it
// This ensures the map shows the same area as the search results
if (boundingBox) {
const [neLat, swLng, swLat, neLng] = boundingBox
.split(",")
.map(Number);
const llBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(swLat, swLng), // SW corner
new google.maps.LatLng(neLat, neLng) // NE corner
);
map.fitBounds(llBounds);
initialBoundsRef.current = llBounds;
// Fit the map to the full SF bounding box so every user sees the
// same geographic area on load, regardless of screen size.
// fitBounds adjusts zoom automatically — small screens zoom out more,
// large screens zoom in more, but all of SF is always visible.
const sfBounds = new google.maps.LatLngBounds(
new google.maps.LatLng(
SF_MAP_BOUNDS.sw.lat,
SF_MAP_BOUNDS.sw.lng
),
new google.maps.LatLng(SF_MAP_BOUNDS.ne.lat, SF_MAP_BOUNDS.ne.lng)
);
map.fitBounds(sfBounds);

// Notify that map is initialized
handleSearchMapAction(SearchMapActions.MapInitialized);
} else {
// Fallback: Set initial bounding box from map bounds when first loaded
const idleListener = map.addListener("idle", () => {
// Remove the listener so it only fires once
google.maps.event.removeListener(idleListener);
// After fitBounds settles, capture the actual rendered bounds as
// the initial bounding box for search queries.
const idleListener = map.addListener("idle", () => {
google.maps.event.removeListener(idleListener);

const bounds = map.getBounds();
if (bounds) {
initialBoundsRef.current = bounds;
const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();
const boundingBoxString = `${ne.lat()},${sw.lng()},${sw.lat()},${ne.lng()}`;
setBoundingBox(boundingBoxString);
const bounds = map.getBounds();
if (bounds) {
const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();
const boundingBoxString = `${ne.lat()},${sw.lng()},${sw.lat()},${ne.lng()}`;
setBoundingBox(boundingBoxString);

// Notify that map is initialized
handleSearchMapAction(SearchMapActions.MapInitialized);
}
});
}
handleSearchMapAction(SearchMapActions.MapInitialized);
}
});
}}
options={createMapOptions}
>
Expand Down
2 changes: 1 addition & 1 deletion app/components/ui/SearchResultsHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import styles from "components/SearchAndBrowse/SearchResults/SearchResults.module.scss";
import ClearSearchButton from "components/SearchAndBrowse/Refinements/ClearSearchButton";
import { HITS_PER_PAGE } from "pages/SearchResultsPage/SearchResultsPage";
import { HITS_PER_PAGE } from "../../search/constants";

/**
* Layout component for the header above the search results list that allows for
Expand Down
4 changes: 2 additions & 2 deletions app/hooks/SFOpenData/transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ function transformServiceBasic(service: SFService): Service {
addresses: [],
alsoNamed: "",
alternate_name: null,
application_process: null,
application_process: service.application_process || null,
categories: [],
certified_at: null,
certified: false,
Expand Down Expand Up @@ -290,7 +290,7 @@ export function transformService(
addresses,
alsoNamed: "",
alternate_name: null,
application_process: null,
application_process: serviceWithDetails.application_process || null,
categories: categories.map(transformTaxonomyToCategory),
certified_at: null,
certified: false,
Expand Down
1 change: 1 addition & 0 deletions app/hooks/SFOpenData/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface SFService {
description?: string;
organization_id: string;
url?: string;
application_process?: string;
}

// Location from /resource/rwbr-kbhe.json
Expand Down
Loading
Loading