feat(BOOK-54): Adjusted filter functionality to save filters as query parameters instead of paths

* feat(BOOK-54): Destination filters now matching on id instead of slug in preparation for filters from Contentstack

Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
Erik Tiekstra
2025-09-18 13:03:01 +00:00
parent 32a817fa72
commit 948c86479a
18 changed files with 136 additions and 102 deletions

View File

@@ -10,16 +10,13 @@ import type { PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata" export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function DestinationCityPagePage( export default async function DestinationCityPagePage(
props: PageArgs<object, { view?: "map"; filterFromUrl?: string }> props: PageArgs<object, { view?: "map" }>
) { ) {
const searchParams = await props.searchParams const searchParams = await props.searchParams
return ( return (
<div className={styles.page}> <div className={styles.page}>
<Suspense fallback={<DestinationCityPageSkeleton />}> <Suspense fallback={<DestinationCityPageSkeleton />}>
<DestinationCityPage <DestinationCityPage isMapView={searchParams.view === "map"} />
isMapView={searchParams.view === "map"}
filterFromUrl={searchParams.filterFromUrl}
/>
</Suspense> </Suspense>
</div> </div>
) )

View File

@@ -5,20 +5,16 @@ import DestinationCountryPageSkeleton from "@/components/ContentType/Destination
import styles from "./page.module.css" import styles from "./page.module.css"
import type { PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/metadata/generateMetadata" export { generateMetadata } from "@/utils/metadata/generateMetadata"
export default async function DestinationCountryPagePage( export default async function DestinationCountryPagePage() {
props: PageArgs<object, { view?: "map"; filterFromUrl?: string }> // props: PageArgs<{}, { view?: "map"; }>
) { // const searchParams = await props.searchParams
const searchParams = await props.searchParams
return ( return (
<div className={styles.page}> <div className={styles.page}>
<Suspense fallback={<DestinationCountryPageSkeleton />}> <Suspense fallback={<DestinationCountryPageSkeleton />}>
<DestinationCountryPage <DestinationCountryPage
// isMapView={searchParams.view === "map"} // Disabled until further notice // isMapView={searchParams.view === "map"} // Disabled until further notice
filterFromUrl={searchParams.filterFromUrl}
isMapView={false} isMapView={false}
/> />
</Suspense> </Suspense>

View File

@@ -44,16 +44,24 @@ export default function HotelList() {
return return
} }
function handleBoundsChanged() { coreLib.event.addListener(
debouncedUpdateVisibleHotels() map,
} "bounds_changed",
debouncedUpdateVisibleHotels
coreLib.event.addListener(map, "bounds_changed", handleBoundsChanged) )
return () => { return () => {
coreLib.event.clearListeners(map, "bounds_changed") coreLib.event.clearListeners(map, "bounds_changed")
} }
}, [map, coreLib, debouncedUpdateVisibleHotels]) }, [map, coreLib, debouncedUpdateVisibleHotels])
useEffect(() => {
if (!map) {
return
}
setVisibleHotels(getVisibleHotels(activeHotels, map))
}, [map, activeHotels])
if (isLoading) { if (isLoading) {
return <HotelListSkeleton /> return <HotelListSkeleton />
} }

View File

@@ -29,11 +29,11 @@ export default function CityMap({
defaultLocation, defaultLocation,
}: CityMapProps) { }: CityMapProps) {
const intl = useIntl() const intl = useIntl()
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore( const { activeHotels, allFilters, filterFromUrl } = useDestinationDataStore(
(state) => ({ (state) => ({
activeHotels: state.activeHotels, activeHotels: state.activeHotels,
allFilters: state.allFilters, allFilters: state.allFilters,
activeFilters: state.activeFilters, filterFromUrl: state.filterFromUrl,
}) })
) )
@@ -47,7 +47,7 @@ export default function CityMap({
> >
<Typography variant="Title/sm"> <Typography variant="Title/sm">
<h1 className={styles.title}> <h1 className={styles.title}>
{getHeadingText(intl, city.name, allFilters, activeFilters[0])} {getHeadingText(intl, city.name, allFilters, filterFromUrl)}
</h1> </h1>
</Typography> </Typography>
<HotelList /> <HotelList />

View File

@@ -28,7 +28,6 @@ import DestinationPageSidePeek from "../Sidepeek"
import StaticMap from "../StaticMap" import StaticMap from "../StaticMap"
import TopImages from "../TopImages" import TopImages from "../TopImages"
import DestinationTracking from "../Tracking" import DestinationTracking from "../Tracking"
import { getHeadingText } from "../utils"
import CityMap from "./CityMap" import CityMap from "./CityMap"
import DestinationCityPageSkeleton from "./DestinationCityPageSkeleton" import DestinationCityPageSkeleton from "./DestinationCityPageSkeleton"
@@ -36,12 +35,10 @@ import styles from "./destinationCityPage.module.css"
interface DestinationCityPageProps { interface DestinationCityPageProps {
isMapView: boolean isMapView: boolean
filterFromUrl?: string
} }
export default async function DestinationCityPage({ export default async function DestinationCityPage({
isMapView, isMapView,
filterFromUrl,
}: DestinationCityPageProps) { }: DestinationCityPageProps) {
const intl = await getIntl() const intl = await getIntl()
const lang = await getLang() const lang = await getLang()
@@ -94,7 +91,6 @@ export default async function DestinationCityPage({
<DestinationDataProvider <DestinationDataProvider
allHotels={allHotels} allHotels={allHotels}
allFilters={allFilters} allFilters={allFilters}
filterFromUrl={filterFromUrl}
sortItems={sortItems} sortItems={sortItems}
pathname={pathname} pathname={pathname}
> >
@@ -120,16 +116,7 @@ export default async function DestinationCityPage({
{blocks && <Blocks blocks={blocks} />} {blocks && <Blocks blocks={blocks} />}
</main> </main>
<aside className={styles.sidebar}> <aside className={styles.sidebar}>
<SidebarContentWrapper <SidebarContentWrapper preamble={preamble} location={city.name}>
hasActiveFilter={!!filterFromUrl}
preamble={preamble}
heading={getHeadingText(
intl,
city.name,
allFilters,
filterFromUrl
)}
>
<ExperienceList experiences={experiences} /> <ExperienceList experiences={experiences} />
{has_sidepeek && sidepeek_content ? ( {has_sidepeek && sidepeek_content ? (
<DestinationPageSidePeek <DestinationPageSidePeek

View File

@@ -28,11 +28,11 @@ export default function CountryMap({
defaultLocation, defaultLocation,
}: CountryMapProps) { }: CountryMapProps) {
const intl = useIntl() const intl = useIntl()
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore( const { activeHotels, allFilters, filterFromUrl } = useDestinationDataStore(
(state) => ({ (state) => ({
activeHotels: state.activeHotels, activeHotels: state.activeHotels,
allFilters: state.allFilters, allFilters: state.allFilters,
activeFilters: state.activeFilters, filterFromUrl: state.filterFromUrl,
}) })
) )
@@ -46,7 +46,7 @@ export default function CountryMap({
> >
<Typography variant="Title/sm"> <Typography variant="Title/sm">
<h1 className={styles.title}> <h1 className={styles.title}>
{getHeadingText(intl, country, allFilters, activeFilters[0])} {getHeadingText(intl, country, allFilters, filterFromUrl)}
</h1> </h1>
</Typography> </Typography>
<CityList /> <CityList />

View File

@@ -28,7 +28,6 @@ import SidebarContentWrapper from "../SidebarContentWrapper"
import DestinationPageSidePeek from "../Sidepeek" import DestinationPageSidePeek from "../Sidepeek"
import TopImages from "../TopImages" import TopImages from "../TopImages"
import DestinationTracking from "../Tracking" import DestinationTracking from "../Tracking"
import { getHeadingText } from "../utils"
import CountryMap from "./CountryMap" import CountryMap from "./CountryMap"
import DestinationCountryPageSkeleton from "./DestinationCountryPageSkeleton" import DestinationCountryPageSkeleton from "./DestinationCountryPageSkeleton"
@@ -36,12 +35,10 @@ import styles from "./destinationCountryPage.module.css"
interface DestinationCountryPageProps { interface DestinationCountryPageProps {
isMapView: boolean isMapView: boolean
filterFromUrl?: string
} }
export default async function DestinationCountryPage({ export default async function DestinationCountryPage({
isMapView, isMapView,
filterFromUrl,
}: DestinationCountryPageProps) { }: DestinationCountryPageProps) {
const intl = await getIntl() const intl = await getIntl()
const lang = await getLang() const lang = await getLang()
@@ -93,7 +90,6 @@ export default async function DestinationCountryPage({
allHotels={allHotels} allHotels={allHotels}
allCities={allCities} allCities={allCities}
allFilters={allFilters} allFilters={allFilters}
filterFromUrl={filterFromUrl}
sortItems={sortItems} sortItems={sortItems}
pathname={pathname} pathname={pathname}
> >
@@ -123,14 +119,8 @@ export default async function DestinationCountryPage({
</main> </main>
<aside className={styles.sidebar}> <aside className={styles.sidebar}>
<SidebarContentWrapper <SidebarContentWrapper
hasActiveFilter={!!filterFromUrl}
preamble={preamble} preamble={preamble}
heading={getHeadingText( location={translatedCountry}
intl,
translatedCountry,
allFilters,
filterFromUrl
)}
> >
<ExperienceList experiences={experiences} /> <ExperienceList experiences={experiences} />
{has_sidepeek && sidepeek_content ? ( {has_sidepeek && sidepeek_content ? (

View File

@@ -1,37 +1,47 @@
"use client" "use client"
import { useRef } from "react" import { useRef } from "react"
import { useIntl } from "react-intl"
import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition" import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition"
import { StickyElementNameEnum } from "@scandic-hotels/common/stores/sticky-position" import { StickyElementNameEnum } from "@scandic-hotels/common/stores/sticky-position"
import { Typography } from "@scandic-hotels/design-system/Typography" import { Typography } from "@scandic-hotels/design-system/Typography"
import { useDestinationDataStore } from "@/stores/destination-data"
import { getHeadingText } from "@/components/ContentType/DestinationPage/utils"
import styles from "./sidebarContentWrapper.module.css" import styles from "./sidebarContentWrapper.module.css"
interface SidebarContentWrapperProps extends React.PropsWithChildren { interface SidebarContentWrapperProps extends React.PropsWithChildren {
preamble: string preamble: string
hasActiveFilter: boolean location: string
heading: string
} }
export default function SidebarContentWrapper({ export default function SidebarContentWrapper({
preamble, preamble,
hasActiveFilter, location,
heading,
children, children,
}: SidebarContentWrapperProps) { }: SidebarContentWrapperProps) {
const intl = useIntl()
const sidebarRef = useRef<HTMLDivElement>(null) const sidebarRef = useRef<HTMLDivElement>(null)
const { allFilters, filterFromUrl } = useDestinationDataStore((state) => ({
allFilters: state.allFilters,
filterFromUrl: state.filterFromUrl,
}))
useStickyPosition({ useStickyPosition({
ref: sidebarRef, ref: sidebarRef,
name: StickyElementNameEnum.DESTINATION_SIDEBAR, name: StickyElementNameEnum.DESTINATION_SIDEBAR,
}) })
const heading = getHeadingText(intl, location, allFilters, filterFromUrl)
return ( return (
<div ref={sidebarRef} className={styles.sidebarContent}> <div ref={sidebarRef} className={styles.sidebarContent}>
<Typography variant="Title/md"> <Typography variant="Title/md">
<h1 className={styles.heading}>{heading}</h1> <h1 className={styles.heading}>{heading}</h1>
</Typography> </Typography>
{!hasActiveFilter ? ( {!filterFromUrl ? (
<Typography variant="Body/Paragraph/mdRegular"> <Typography variant="Body/Paragraph/mdRegular">
<p className={styles.text}>{preamble}</p> <p className={styles.text}>{preamble}</p>
</Typography> </Typography>

View File

@@ -1,18 +1,21 @@
import type { CategorizedHotelFilters } from "@scandic-hotels/trpc/types/hotel" import type {
CategorizedHotelFilters,
HotelFilter,
} from "@scandic-hotels/trpc/types/hotel"
import type { IntlShape } from "react-intl" import type { IntlShape } from "react-intl"
export function getHeadingText( export function getHeadingText(
intl: IntlShape, intl: IntlShape,
location: string, location: string,
allFilters: CategorizedHotelFilters, allFilters: CategorizedHotelFilters,
filter?: string filterFromUrl: HotelFilter | null
) { ) {
if (filter) { if (filterFromUrl) {
const facilityFilter = allFilters.facilityFilters.find( const facilityFilter = allFilters.facilityFilters.find(
(f) => f.slug === filter (f) => f.id === filterFromUrl.id
) )
const surroudingsFilter = allFilters.surroundingsFilters.find( const surroudingsFilter = allFilters.surroundingsFilters.find(
(f) => f.slug === filter (f) => f.id === filterFromUrl.id
) )
if (facilityFilter) { if (facilityFilter) {

View File

@@ -53,8 +53,10 @@ export default function Filter({ filters }: FilterProps) {
<Checkbox <Checkbox
name={filter.name} name={filter.name}
value={filter.slug} value={filter.slug}
onChange={() => togglePendingFilter(filter.slug)} onChange={() => togglePendingFilter(filter)}
isSelected={!!pendingFilters.find((f) => f === filter.slug)} isSelected={
!!pendingFilters.find((pf) => pf.id === filter.id)
}
/> />
</li> </li>
))} ))}
@@ -75,8 +77,10 @@ export default function Filter({ filters }: FilterProps) {
<Checkbox <Checkbox
name={filter.name} name={filter.name}
value={filter.slug} value={filter.slug}
onChange={() => togglePendingFilter(filter.slug)} onChange={() => togglePendingFilter(filter)}
isSelected={!!pendingFilters.find((f) => f === filter.slug)} isSelected={
!!pendingFilters.find((pf) => pf.id === filter.id)
}
/> />
</li> </li>
))} ))}

View File

@@ -43,6 +43,7 @@ export default function DestinationFilterAndSort({
basePath, basePath,
pendingCount, pendingCount,
activeFilters, activeFilters,
filterFromUrl,
clearPendingFilters, clearPendingFilters,
resetPendingValues, resetPendingValues,
setIsLoading, setIsLoading,
@@ -56,6 +57,7 @@ export default function DestinationFilterAndSort({
pendingCount: pendingCount:
listType === "city" ? state.pendingCityCount : state.pendingHotelCount, listType === "city" ? state.pendingCityCount : state.pendingHotelCount,
activeFilters: state.activeFilters, activeFilters: state.activeFilters,
filterFromUrl: state.filterFromUrl,
clearPendingFilters: state.actions.clearPendingFilters, clearPendingFilters: state.actions.clearPendingFilters,
resetPendingValues: state.actions.resetPendingValues, resetPendingValues: state.actions.resetPendingValues,
setIsLoading: state.actions.setIsLoading, setIsLoading: state.actions.setIsLoading,
@@ -84,7 +86,7 @@ export default function DestinationFilterAndSort({
function submitAndClose(close: () => void) { function submitAndClose(close: () => void) {
setIsLoading(true) setIsLoading(true)
const sort = pendingSort const sort = pendingSort
const filters = pendingFilters const filters = [...pendingFilters]
const parsedUrl = new URL(window.location.href) const parsedUrl = new URL(window.location.href)
const searchParams = parsedUrl.searchParams const searchParams = parsedUrl.searchParams
if (sort === defaultSort && searchParams.has("sort")) { if (sort === defaultSort && searchParams.has("sort")) {
@@ -92,14 +94,26 @@ export default function DestinationFilterAndSort({
} else if (sort !== defaultSort) { } else if (sort !== defaultSort) {
searchParams.set("sort", sort) searchParams.set("sort", sort)
} }
const [firstFilter, ...remainingFilters] = filters
parsedUrl.pathname = basePath parsedUrl.pathname = basePath
if (firstFilter) {
parsedUrl.pathname += `/${firstFilter}` // We need to check if one of the filters in the URL is also in the pending filters.
// This is only the case when the user has navigated directly to a filter URL
// e.g. /destination/copenhagen/spa and then opens the filter modal and changes the sort or adds/removes other filters.
// In this case we will keep the filter in the URL and not add it to the "filter" search param.
if (filterFromUrl) {
const foundFilterIndex = filters.findIndex(
(f) => f.id === filterFromUrl.id
)
if (foundFilterIndex !== -1) {
parsedUrl.pathname += `/${filterFromUrl.slug}`
filters.splice(foundFilterIndex, 1)
}
} }
if (remainingFilters.length > 0) {
searchParams.set("filter", remainingFilters.join("&")) if (filters.length > 0) {
const filterSlugs = filters.map((f) => f.slug)
searchParams.set("filter", filterSlugs.join("&"))
} else { } else {
searchParams.delete("filter") searchParams.delete("filter")
} }

View File

@@ -24,10 +24,14 @@ export default function DestinationDataProviderContent({
const filterParam = searchParams.get("filter") const filterParam = searchParams.get("filter")
const filters = [] const filters = []
const pathParts = currentPathname.split("/") const pathParts = currentPathname.split("/")
const lastPathPart = pathParts[pathParts.length - 1] const filterFromUrl = pathParts[pathParts.length - 1]
// Even though the user filter is stored in the query param,
// we also support having it as the last part of the pathname
// e.g. /destination/copenhagen/spa
// In this case we need to add it to the filters array
if (basePath !== currentPathname) { if (basePath !== currentPathname) {
filters.push(lastPathPart) filters.push(filterFromUrl)
} }
if (filterParam) { if (filterParam) {
filters.push(...filterParam.split("&")) filters.push(...filterParam.split("&"))

View File

@@ -15,7 +15,6 @@ export default function DestinationDataProvider({
allCities = [], allCities = [],
allHotels, allHotels,
allFilters, allFilters,
filterFromUrl,
sortItems, sortItems,
pathname, pathname,
children, children,
@@ -28,7 +27,6 @@ export default function DestinationDataProvider({
allCities, allCities,
allHotels, allHotels,
allFilters, allFilters,
filterFromUrl,
pathname, pathname,
sortItems, sortItems,
searchParams, searchParams,

View File

@@ -1,4 +1,5 @@
import { import {
type HotelFilter,
type HotelListingHotelData, type HotelListingHotelData,
type HotelSortItem, type HotelSortItem,
HotelSortOption, HotelSortOption,
@@ -25,12 +26,12 @@ const HOTEL_SORTING_STRATEGIES: Partial<
export function getFilteredHotels( export function getFilteredHotels(
hotels: HotelListingHotelData[], hotels: HotelListingHotelData[],
filters: string[] filters: HotelFilter[]
) { ) {
if (filters.length) { if (filters.length) {
return hotels.filter(({ hotel }) => return hotels.filter(({ hotel }) =>
filters.every((filter) => filters.every((filter) =>
hotel.detailedFacilities.some((facility) => facility.slug === filter) hotel.detailedFacilities.some((facility) => facility.id === filter.id)
) )
) )
} }

View File

@@ -29,26 +29,30 @@ export function createDestinationDataStore({
allCities, allCities,
allHotels, allHotels,
allFilters, allFilters,
filterFromUrl,
pathname, pathname,
sortItems, sortItems,
searchParams, searchParams,
}: InitialState) { }: InitialState) {
const defaultSort = const defaultSort =
sortItems.find((s) => s.isDefault)?.value ?? sortItems[0].value sortItems.find((s) => s.isDefault)?.value ?? sortItems[0].value
const allFilterSlugs = Object.values(allFilters).flatMap( const flattenedFilters = Object.values(allFilters).flat<HotelFilter[]>()
(filter: HotelFilter[]) => filter.map((f) => f.slug) const allFilterSlugs = flattenedFilters.map((filter) => filter.slug)
) const activeFilters: HotelFilter[] = []
let filterFromUrl: HotelFilter | null = null
const activeFilters: string[] = filterFromUrl ? [filterFromUrl] : []
const basePathnameWithoutFilters = getBasePathNameWithoutFilters( const basePathnameWithoutFilters = getBasePathNameWithoutFilters(
pathname, pathname,
allFilterSlugs allFilterSlugs
) )
if (basePathnameWithoutFilters !== pathname) { if (basePathnameWithoutFilters !== pathname) {
const pathParts = pathname.split("/") const pathParts = pathname.split("/")
const lastPathPart = pathParts[pathParts.length - 1] filterFromUrl =
activeFilters.push(lastPathPart) flattenedFilters.find(
(filter) => filter.slug === pathParts[pathParts.length - 1]
) ?? null
if (filterFromUrl) {
activeFilters.push(filterFromUrl)
}
} }
let activeSort = defaultSort let activeSort = defaultSort
@@ -65,13 +69,16 @@ export function createDestinationDataStore({
return create<DestinationDataState>((set) => ({ return create<DestinationDataState>((set) => ({
actions: { actions: {
updateActiveFiltersAndSort(filters, sort) { updateActiveFiltersAndSort(filterSlugs, sort) {
return set( return set(
produce((state: DestinationDataState) => { produce((state: DestinationDataState) => {
const newSort = const newSort =
sort && isValidSortOption(sort, state.sortItems) sort && isValidSortOption(sort, state.sortItems)
? sort ? sort
: state.defaultSort : state.defaultSort
const filters = flattenedFilters.filter((filter) =>
filterSlugs.includes(filter.slug)
)
const filteredHotels = getFilteredHotels(state.allHotels, filters) const filteredHotels = getFilteredHotels(state.allHotels, filters)
const sortedHotels = getSortedHotels(filteredHotels, newSort) const sortedHotels = getSortedHotels(filteredHotels, newSort)
const filteredCities = state.allHotels.length const filteredCities = state.allHotels.length
@@ -86,16 +93,22 @@ export function createDestinationDataStore({
if ( if (
JSON.stringify(filters) !== JSON.stringify(state.activeFilters) JSON.stringify(filters) !== JSON.stringify(state.activeFilters)
) { ) {
const facilityFiltersUsed = filters.filter((f) => const facilityFiltersUsed = filters
state.allFilters.facilityFilters .filter(
.map((ff) => ff.slug) (f) =>
.includes(f) !!state.allFilters.facilityFilters.find(
) (ff) => ff.id === f.id
const surroundingsFiltersUsed = filters.filter((f) => )
state.allFilters.surroundingsFilters )
.map((sf) => sf.slug) .map((f) => f.slug)
.includes(f) const surroundingsFiltersUsed = filters
) .filter(
(f) =>
!!state.allFilters.surroundingsFilters.find(
(sf) => sf.id === f.id
)
)
.map((f) => f.slug)
trackFilterChangeEvent( trackFilterChangeEvent(
facilityFiltersUsed, facilityFiltersUsed,
@@ -133,9 +146,12 @@ export function createDestinationDataStore({
togglePendingFilter(filter) { togglePendingFilter(filter) {
return set( return set(
produce((state: DestinationDataState) => { produce((state: DestinationDataState) => {
const isActive = state.pendingFilters.includes(filter) const filterId = filter.id
const isActive = !!state.pendingFilters.find(
(pf) => pf.id === filterId
)
const filters = isActive const filters = isActive
? state.pendingFilters.filter((f) => f !== filter) ? state.pendingFilters.filter((f) => f.id !== filterId)
: [...state.pendingFilters, filter] : [...state.pendingFilters, filter]
const pendingHotels = getFilteredHotels(state.allHotels, filters) const pendingHotels = getFilteredHotels(state.allHotels, filters)
const pendingCities = state.allHotels.length const pendingCities = state.allHotels.length
@@ -180,7 +196,7 @@ export function createDestinationDataStore({
activeFilters, activeFilters,
pendingFilters: activeFilters, pendingFilters: activeFilters,
allFilters, allFilters,
allFilterSlugs, filterFromUrl,
basePathnameWithoutFilters, basePathnameWithoutFilters,
sortItems, sortItems,
isLoading: false, isLoading: false,

View File

@@ -1,6 +1,7 @@
import type { DestinationCityListItem } from "@scandic-hotels/trpc/types/destinationCityPage" import type { DestinationCityListItem } from "@scandic-hotels/trpc/types/destinationCityPage"
import type { import type {
CategorizedHotelFilters, CategorizedHotelFilters,
HotelFilter,
HotelListingHotelData, HotelListingHotelData,
HotelSortItem, HotelSortItem,
HotelSortOption, HotelSortOption,
@@ -8,9 +9,12 @@ import type {
import type { ReadonlyURLSearchParams } from "next/navigation" import type { ReadonlyURLSearchParams } from "next/navigation"
interface Actions { interface Actions {
updateActiveFiltersAndSort: (filters: string[], sort: string | null) => void updateActiveFiltersAndSort: (
filterSlugs: string[],
sort: string | null
) => void
setPendingSort: (sort: HotelSortOption) => void setPendingSort: (sort: HotelSortOption) => void
togglePendingFilter: (filter: string) => void togglePendingFilter: (filter: HotelFilter) => void
clearPendingFilters: () => void clearPendingFilters: () => void
resetPendingValues: () => void resetPendingValues: () => void
setIsLoading: (isLoading: boolean) => void setIsLoading: (isLoading: boolean) => void
@@ -31,12 +35,12 @@ export interface DestinationDataState {
pendingSort: HotelSortOption pendingSort: HotelSortOption
activeSort: HotelSortOption activeSort: HotelSortOption
defaultSort: HotelSortOption defaultSort: HotelSortOption
pendingFilters: string[] pendingFilters: HotelFilter[]
activeFilters: string[] activeFilters: HotelFilter[]
filterFromUrl: HotelFilter | null
pendingHotelCount: number pendingHotelCount: number
pendingCityCount: number pendingCityCount: number
allFilters: CategorizedHotelFilters allFilters: CategorizedHotelFilters
allFilterSlugs: string[]
basePathnameWithoutFilters: string basePathnameWithoutFilters: string
sortItems: HotelSortItem[] sortItems: HotelSortItem[]
isLoading: boolean isLoading: boolean
@@ -48,6 +52,5 @@ export interface InitialState
"allHotels" | "allCities" | "sortItems" | "allFilters" "allHotels" | "allCities" | "sortItems" | "allFilters"
> { > {
pathname: string pathname: string
filterFromUrl?: string
searchParams: ReadonlyURLSearchParams searchParams: ReadonlyURLSearchParams
} }

View File

@@ -1,3 +1,4 @@
import type { FacilityEnum } from "@scandic-hotels/common/constants/facilities"
import type { z } from "zod" import type { z } from "zod"
import type { import type {
@@ -87,6 +88,7 @@ export type HotelInput = z.input<typeof hotelInputSchema>
export type RoomType = Pick<Room, "roomTypes" | "name"> export type RoomType = Pick<Room, "roomTypes" | "name">
export interface HotelFilter { export interface HotelFilter {
id: FacilityEnum
name: string name: string
slug: string slug: string
filterType: string filterType: string

View File

@@ -73,6 +73,7 @@ export function getFiltersFromHotels(
) )
return filter return filter
? { ? {
id: filter.id,
name: filter.name, name: filter.name,
slug: filter.slug, slug: filter.slug,
filterType: filter.filter, filterType: filter.filter,