Skip to content
2 changes: 1 addition & 1 deletion app/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("<App />", () => {
coords: { lat: 37.7749, lng: -122.4194 },
inSanFrancisco: false,
},
aroundUserLocationRadius: "all",
aroundUserLocationRadius: 1600,
setAroundRadius: expect.any(Function),
setAroundLatLng: expect.any(Function),
});
Expand Down
8 changes: 5 additions & 3 deletions app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export const App = () => {
const location = useLocation();
const [userLocation, setUserLocation] = useState<UserLocation | null>(null);
const [aroundLatLng, setAroundLatLng] = useState<string>("");
const [aroundUserLocationRadius, setAroundRadius] = useState<AroundRadius>(
"all" as const
);
const [aroundUserLocationRadius, setAroundRadius] =
useState<AroundRadius>(1600);
const [boundingBox, setBoundingBox] = useState<string | undefined>(undefined);

useEffect(() => {
getLocation().then((userLocation) => {
Expand Down Expand Up @@ -60,6 +60,8 @@ export const App = () => {
setAroundLatLng,
aroundUserLocationRadius,
setAroundRadius,
boundingBox,
setBoundingBox,
};

return (
Expand Down
56 changes: 51 additions & 5 deletions app/components/SearchAndBrowse/SearchMap/SearchMap.tsx

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: 😌 Really nice comments in handleSearchThisAreaClick() otherwise those operations are pretty opaque not behind meaningful names.

Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,43 @@ export const SearchMap = ({
null
);
const { userLocation, aroundLatLng } = useAppContext();
const { setAroundLatLng } = useAppContextUpdater();
const { setAroundLatLng, setAroundRadius, setBoundingBox } =
useAppContextUpdater();

// Dynamically calculate search radius based on zoom level

function handleSearchThisAreaClick() {
const center = googleMapObject?.getCenter();
if (center?.lat() && center?.lng()) {
setAroundLatLng(`${center.lat()}, ${center.lng()}`);
const map = googleMapObject;
if (map) {
// Get the visible bounds of the map
const bounds = map.getBounds();
if (bounds) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (if-minor): Out of pure curiosity, did you find that bounds might be falsy sometimes?

const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();

// Format as Algolia expects: "lat1,lng1,lat2,lng2"
// Where (lat1, lng1) is the top-left (NW) corner and (lat2, lng2) is the bottom-right (SE) corner
const boundingBoxString = `${ne.lat()},${sw.lng()},${sw.lat()},${ne.lng()}`;

// Update the bounding box for search
setBoundingBox(boundingBoxString);

// Set aroundRadius to "all" to disable radius-based filtering
setAroundRadius("all");

// Keep center point updated for reference (used for map centering)
const center = map.getCenter();
if (center) {
const centerStr = `${center.lat()}, ${center.lng()}`;
setAroundLatLng(centerStr);
}

// Notify SearchResultsPage component to reset pagination
handleSearchMapAction(SearchMapActions.SearchThisArea);
}
} else {
handleSearchMapAction(SearchMapActions.SearchThisArea);
}
handleSearchMapAction(SearchMapActions.SearchThisArea);
}

const aroundLatLngToMapCenter = {
Expand Down Expand Up @@ -127,6 +156,23 @@ export const SearchMap = ({
// SetMapObject shares the Google Map object across parent/sibling components
// so that they can adjustments to markers, coordinates, layout, etc.,
setMapObject(map);

// Set initial bounding box when map is first loaded
const idleListener = map.addListener("idle", () => {
// Remove the listener so it only fires once
google.maps.event.removeListener(idleListener);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: Man this kind of variable access in JS (via "hoisting") always breaks my brain. What about expanding this comment so folks unfamiliar with gmaps might understand this a little better:

Remove the listener so it only fires once on initialization because  the `idle` event will continue to fire when the map finishes panning, zooming, or loading.


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);
}
});
}}
options={createMapOptions}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@
.searchResultsHeader {
display: flex;
justify-content: space-between;
align-items: center;
align-items: baseline;

h2 {
font-size: 20px;
font-size: 16px;
}

@media screen and (max-width: $break-tablet-p) {
Expand All @@ -80,7 +80,7 @@
border-bottom: 1px solid $border-gray;

h2 {
font-size: 18px;
font-size: 14px;
}

a {
Expand Down
19 changes: 8 additions & 11 deletions app/components/SearchAndBrowse/SearchResults/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ import styles from "./SearchResults.module.scss";
import ClearSearchButton from "../Refinements/ClearSearchButton";
import { Loader } from "components/ui/Loader";
import ResultsPagination from "components/SearchAndBrowse/Pagination/ResultsPagination";
import { SearchResultsHeader } from "components/ui/SearchResultsHeader";

export enum SearchMapActions {
SearchThisArea,
MapInitialized,
}

const SearchResults = ({
mobileMapIsCollapsed,
}: {
mobileMapIsCollapsed: boolean;
}) => {
const { refine: refinePagination } = usePagination();
const { refine: refinePagination, currentRefinement: currentPage } =
usePagination();
const {
// Results type is algoliasearchHelper.SearchResults<SearchHit>
results: searchResults,
Expand Down Expand Up @@ -55,15 +58,6 @@ const SearchResults = ({
</div>
);

const searchResultsHeader = () => {
return (
<div className={styles.searchResultsHeader}>
<h2>{searchResults.nbHits} results</h2>
<ClearSearchButton />
</div>
);
};

const handleAction = (searchMapAction: SearchMapActions) => {
switch (searchMapAction) {
case SearchMapActions.SearchThisArea:
Expand All @@ -83,7 +77,10 @@ const SearchResults = ({
<NoResultsDisplay />
) : (
<>
{searchResultsHeader()}
<SearchResultsHeader
currentPage={currentPage}
totalResults={searchResults.nbHits}
/>
{searchMapHitData.hits.map((hit: TransformedSearchHit, index) => (
<SearchResult
hit={hit}
Expand Down
2 changes: 1 addition & 1 deletion app/components/SearchAndBrowse/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const Sidebar = ({
const { setAroundRadius } = useAppContextUpdater();

useClickOutside(
filterMenuRef,
filterMenuRef as React.RefObject<HTMLElement>,
() => setFilterMenuVisible(false),
filterMenuVisible
);
Expand Down
2 changes: 1 addition & 1 deletion app/components/ui/Navigation/MobileNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const MobileNavigation = ({ menuData }: MobileNavigationProps) => {
const mobileNavTextDisplay = mobileNavigationIsOpen ? "Close" : "Menu";

useClickOutside(
filterMenuRef,
filterMenuRef as React.RefObject<HTMLElement>,
(event: MouseEvent) => {
// Prevents collison between handling the outside click and clicking on the
// "Close" button
Expand Down
30 changes: 26 additions & 4 deletions app/components/ui/SearchResultsHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import React, { ReactNode } from "react";
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";

/**
* Layout component for the header above the search results list that allows for
* flexible composition of child components.
*/
export const SearchResultsHeader = ({ children }: { children: ReactNode }) => (
<div className={styles.searchResultsHeader}>{children}</div>
);
export const SearchResultsHeader = ({
currentPage,
totalResults,
}: {
currentPage: number;
totalResults: number;
}) => {
const firstResultIndex = currentPage * HITS_PER_PAGE + 1;
const lastResultIndex = Math.min(
(currentPage + 1) * HITS_PER_PAGE,
totalResults
);
return (
<div className={styles.searchResultsHeader}>
<h2 style={{ fontWeight: 500, fontSize: 16 }}>
Showing <span style={{ fontWeight: 700 }}>{firstResultIndex}</span> -{" "}
<span style={{ fontWeight: 700 }}>{lastResultIndex}</span> of{" "}
<span style={{ fontWeight: 700 }}>{totalResults}</span> results
</h2>
<ClearSearchButton />
</div>
);
};
15 changes: 15 additions & 0 deletions app/pages/BrowseResultsPage/BrowseResultsPage.module.scss
Original file line number Diff line number Diff line change
@@ -1 +1,16 @@
@use "~components/SearchAndBrowse/SearchAndBrowseResultsPage.module.scss";

.loadingContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
text-align: center;

p {
margin-top: 1rem;
font-size: 1.1rem;
color: #666;
}
}
Loading
Loading