Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep user search results when returning from a detail page #1286

Merged
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
1 change: 1 addition & 0 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ const nextConfig = {
},
experimental: {
instrumentationHook: true,
scrollRestoration: true,
},
};

Expand Down
17 changes: 2 additions & 15 deletions frontend/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { routes } from 'services/routes';

import { Link } from 'components/Link';
import { Display } from 'hooks/useHideOnScrollDown';

import InlineMenu from 'components/InlineMenu';
import { GoToSearchButton } from 'components/GoToSearchButton';
Expand All @@ -15,11 +14,6 @@
export const Header: React.FC = () => {
const menuNode = useRef<HTMLDivElement | null>(null);
const { config, menuItems, isDesktopMenu, intl } = useHeader(menuNode);
/**
* Disabled for now to handle the map on the search page
*/
// const headerState = useHideOnScrollDown(sizes.desktopHeader);
const headerState: Display = 'DISPLAYED';

const headerTop = config.headerTopHtml[intl.locale] ?? config.headerTopHtml.default;
const headerBottom = config.headerBottomHtml[intl.locale] ?? config.headerBottomHtml.default;
Expand All @@ -31,18 +25,11 @@
<HtmlParser template={headerTop} />
</div>
)}
<header
className={cn(
'sticky z-header bg-primary1',
headerState === 'DISPLAYED' ? 'top-0' : '-top-desktopHeader',
)}
role="banner"
id="header"
>
<header className="sticky z-header bg-primary1 top-0" role="banner" id="header">
<div className="h-11 desktop:h-desktopHeader flex justify-between items-center sticky z-header px-3 shadow-sm shrink-0 transition-all duration-300 delay-100">
<Link href={routes.HOME} className="flex items-center">
<div className="shrink-0" id="header_logo">
<img

Check warning on line 32 in frontend/src/components/Header/Header.tsx

View workflow job for this annotation

GitHub Actions / install-and-test

Using `<img>` could result in slower LCP and higher bandwidth. Consider using `<Image />` from `next/image` to automatically optimize images. This may incur additional usage or cost from your provider. See: https://nextjs.org/docs/messages/no-img-element
id="header_logoImg"
className="h-9 w-auto desktop:h-18 mr-3"
alt=""
Expand Down Expand Up @@ -75,7 +62,7 @@
<BurgerMenu
className={cn(isDesktopMenu && 'hidden')}
config={config.menu}
displayState={headerState}
displayState="DISPLAYED"
menuItems={menuItems}
/>
</header>
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/Map/SearchMap/BoundsHandler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { LatLngBounds } from 'leaflet';
import { useEffect } from 'react';
import { useMap } from 'react-leaflet';

interface Props {
bounds: LatLngBounds | null;
}

const BoundsHandler: React.FC<Props> = ({ bounds }) => {
const map = useMap();

useEffect(() => {
if (bounds) {
map.fitBounds(bounds);
}
}, [bounds, map]);

return null;
};

export default BoundsHandler;
3 changes: 1 addition & 2 deletions frontend/src/components/Map/SearchMap/MapContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { getMapConfig } from 'components/Map/config';
import { MapContainer as LeafMapContainer } from 'react-leaflet';
import React, { memo } from 'react';
import { Map } from 'leaflet';

interface Props {
Expand Down Expand Up @@ -28,4 +27,4 @@ const MapContainer: React.FC<Props> = ({ children, whenCreated, hasZoomControl =
);
};

export default memo(MapContainer, () => true);
export default MapContainer;
5 changes: 5 additions & 0 deletions frontend/src/components/Map/SearchMap/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import { ResetView } from 'components/Map/components/ResetView';
import TileLayerManager from 'components/Map/components/TileLayerManager';
import FullscreenControl from 'components/Map/components/FullScreenControl';
import LocateControl from 'components/Map/components/LocateControl';
import { useListAndMapContext } from 'modules/map/ListAndMapContext';
import BoundsHandler from './BoundsHandler';

export type PropsType = {
segments?: { x: number; y: number }[];
Expand All @@ -35,6 +37,7 @@ const SearchMap: React.FC<PropsType> = props => {
};

const { setMapInstance } = useTileLayer();
const { searchBbox, isNavigatedByBrowser } = useListAndMapContext();

return (
<MapContainer whenCreated={setMapInstance} hasZoomControl={props.hasZoomControl}>
Expand All @@ -44,6 +47,8 @@ const SearchMap: React.FC<PropsType> = props => {
<FilterButton openFilterMenu={props.openFilterMenu} />
{props.hasZoomControl === true && <FullscreenControl />}
<ResetView />
{/* Must be below ResetView */}
{isNavigatedByBrowser && <BoundsHandler bounds={searchBbox} />}
<ScaleControl />
<LocateControl />
<SearchMapChildrens {...props} />
Expand Down
37 changes: 24 additions & 13 deletions frontend/src/components/pages/search/Search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export const SearchUI: React.FC<Props> = ({ language }) => {
const { subMenuState, selectFilter, hideSubMenu, currentFilterId } = useFilterSubMenu();
const { menuState, displayMenu, hideMenu, filtersList } = useFilterMenu(selectFilter);

const { bboxState, handleMoveMap } = useBbox();
const { bounds, handleMoveMap } = useBbox();

const isMobile = useMediaPredicate('(max-width: 1024px)');

Expand Down Expand Up @@ -75,7 +75,16 @@ export const SearchUI: React.FC<Props> = ({ language }) => {
mobileMapState,
displayMobileMap,
hideMobileMap,
} = useTrekResults({ filtersState, textFilterState, bboxState, dateFilter, page }, language);
} = useTrekResults(
{
filtersState,
textFilterState,
bboxState: bounds?.toBBoxString() ?? null,
dateFilter,
page,
},
language,
);

const { pageTitle, resultsTitle } = useTitle(filtersState, searchResults?.resultsNumber);

Expand All @@ -84,12 +93,20 @@ export const SearchUI: React.FC<Props> = ({ language }) => {
language,
);

const { setPoints } = useListAndMapContext();
const { setPoints, setSearchBbox, isNavigatedByBrowser } = useListAndMapContext();

useEffect(() => {
if (mapResults) setPoints(mapResults);
if (mapResults) {
setPoints(mapResults);
}
}, [mapResults, setPoints]);

useEffect(() => {
if (bounds && !isNavigatedByBrowser) {
setSearchBbox(bounds);
}
}, [bounds, isNavigatedByBrowser, setSearchBbox]);

const intl = useIntl();

const onRemoveAllFiltersClick = () => {
Expand Down Expand Up @@ -151,10 +168,7 @@ export const SearchUI: React.FC<Props> = ({ language }) => {
/>
)}

<div
className="flex flex-col h-[calc(100vh-theme(height.desktopHeader))]"
id="search_container"
>
<div className="flex flex-col" id="search_container">
{!isMobile && (
<FilterBarNew
dateFilter={dateFilter}
Expand All @@ -166,11 +180,8 @@ export const SearchUI: React.FC<Props> = ({ language }) => {
language={language}
/>
)}
<div className="flex flex-row flex-1 overflow-y-hidden">
<div
id="search_resultCardList"
className="flex flex-col w-full desktop:w-1/2 overflow-y-scroll"
>
<div className="flex flex-row flex-1">
<div id="search_resultCardList" className="flex flex-col w-full desktop:w-1/2">
<div className="p-4 flex-1">
<Loader loaded={!isLoading}>
<div className="flex flex-col desktop:flex-row desktop:justify-between">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const FilterField: React.FC<Props> = ({
/>
<div
className={cn(
'fixed left-0 right-0 bg-white p-8 shadow-inner z-[101]',
'fixed left-0 right-0 top-headerAndFilterBar bg-white p-8 shadow-inner z-[101]',
expanded ? 'block' : 'hidden',
)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const FilterBar: React.FC<Props> = ({
const { FILTERS_CATEGORIES } = useFilterBar();

return (
<div className="flex items-center shadow-lg bg-white z-20 border-l border-gresySoft">
<div className="sticky top-desktopHeader flex items-center shadow-lg bg-white z-20 border-l border-gresySoft">
<div className="inline-block ml-4 mr-4 flex items-center">
<Filter size={24} className="text-primary1" aria-hidden />
<span className="uppercase text-primary1 ml-2">
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/pages/search/components/useBbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { getGlobalConfig } from 'modules/utils/api.config';
import { useState } from 'react';

interface ReturnType {
bboxState: string | null;
bounds: LatLngBounds | null;
handleMoveMap: (bounds: LatLngBounds) => void;
}

const useBbox = (): ReturnType => {
const [bboxState, setBboxState] = useState<string | null>(null);
const [bounds, setBboxState] = useState<LatLngBounds | null>(null);

const handleMoveMap = (bounds: LatLngBounds) => {
if (getGlobalConfig().enableSearchByMap) setBboxState(bounds.toBBoxString());
const handleMoveMap = (nextBounds: LatLngBounds) => {
if (getGlobalConfig().enableSearchByMap) setBboxState(nextBounds);
};

return {
bboxState,
bounds,
handleMoveMap,
};
};
Expand Down
38 changes: 38 additions & 0 deletions frontend/src/hooks/useBrowserNavigationDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useRouter } from 'next/router';
import { useEffect, useRef, useState } from 'react';

// Returns “true” if the page is displayed using the browser's "back" or "forward" buttons
const useBrowserNavigationDetection = () => {
const router = useRouter();
const isNavigatedByBrowserRef = useRef(false);
const [isNavigatedByBrowser, setNavigatedByBrowser] = useState(false);

useEffect(() => {
router.beforePopState(() => {
isNavigatedByBrowserRef.current = true;
return true;
});

const handleRouteChangeStart = () => {
// the router.beforePopState() method is always triggered first
// so we have to play with ref to wait for “routeChangeStart” to be triggered
if (isNavigatedByBrowserRef.current) {
setNavigatedByBrowser(true);
isNavigatedByBrowserRef.current = false;
} else {
setNavigatedByBrowser(false);
}
};

router.events.on('routeChangeStart', handleRouteChangeStart);

return () => {
router.beforePopState(() => true);
router.events.off('routeChangeStart', handleRouteChangeStart);
};
}, [router]);

return isNavigatedByBrowser;
};

export default useBrowserNavigationDetection;
23 changes: 22 additions & 1 deletion frontend/src/modules/map/ListAndMapContext.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,49 @@
import { createContext, useContext, useState } from 'react';
import { LatLngBounds } from 'leaflet';
import useBrowserNavigationDetection from 'hooks/useBrowserNavigationDetection';
import { MapResults } from '../mapResults/interface';

export interface ListAndMapContext {
hoveredCardId: string | null;
points: MapResults;
searchBbox: LatLngBounds | null;
setPoints: (value: MapResults) => void;
setHoveredCardId: (hoveredCardId: string | null) => void;
setSearchBbox: (bbox: LatLngBounds | null) => void;
isNavigatedByBrowser: boolean;
}

const listAndMapContext = createContext<ListAndMapContext>({
hoveredCardId: null,
points: [],
searchBbox: null,
setPoints: (value: MapResults) => value,
setHoveredCardId: (_: string | null) => _,
setSearchBbox: (_: LatLngBounds | null) => _,
isNavigatedByBrowser: false,
});

export const useListAndMapContext = () => useContext(listAndMapContext);

export const ListAndMapProvider: React.FC<React.PropsWithChildren> = ({ children }) => {
const [hoveredCardId, setHoveredCardId] = useState<string | null>(null);
const [points, setPoints] = useState<MapResults>([]);
const [searchBbox, setSearchBbox] = useState<LatLngBounds | null>(null);

const isNavigatedByBrowser = useBrowserNavigationDetection();

return (
<listAndMapContext.Provider value={{ hoveredCardId, setHoveredCardId, points, setPoints }}>
<listAndMapContext.Provider
value={{
hoveredCardId,
setHoveredCardId,
points,
setPoints,
searchBbox,
setSearchBbox,
isNavigatedByBrowser,
}}
>
{children}
</listAndMapContext.Provider>
);
Expand Down
18 changes: 7 additions & 11 deletions frontend/src/pages/trek/[slug].tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { GetServerSideProps, NextPage } from 'next';
import router, { useRouter } from 'next/router';
import { DetailsUI } from 'components/pages/details';
import { useEffect } from 'react';
import { dehydrate, QueryCache, QueryClient } from '@tanstack/react-query';
import { getDetails, getTrekFamily } from 'modules/details/connector';
import { isUrlString } from 'modules/utils/string';
Expand Down Expand Up @@ -34,7 +33,10 @@ export const getServerSideProps: GetServerSideProps = async context => {
queryFn: () => getCommonDictionaries(locale),
});

await queryClient.prefetchQuery({ queryKey: ['details', id, locale], queryFn: () => getDetails(id, locale, commonDictionaries) });
await queryClient.prefetchQuery({
queryKey: ['details', id, locale],
queryFn: () => getDetails(id, locale, commonDictionaries),
});

const details = queryClient.getQueryData<Details>(['details', id, locale]);

Expand All @@ -44,7 +46,6 @@ export const getServerSideProps: GetServerSideProps = async context => {
});

if (details !== undefined) {

const redirect = redirectIfWrongUrl(
id,
details.title,
Expand All @@ -54,9 +55,9 @@ export const getServerSideProps: GetServerSideProps = async context => {
);
if (redirect)
return {
redirect,
};
}
redirect,
};
}

return {
props: {
Expand All @@ -81,11 +82,6 @@ const Trek: NextPage<Props> = ({ errorCode }) => {
const { slug, parentId } = query;
const language = locale ?? getDefaultLanguage();

useEffect(() => {
// Force to scroll top on page refresh
window.history.scrollRestoration = 'manual';
}, []);

if (errorCode === 404) return <Custom404 />;

return <DetailsUI slug={slug} parentId={parentId} language={language} />;
Expand Down
Loading