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:
@@ -10,16 +10,13 @@ import type { PageArgs } from "@/types/params"
|
||||
export { generateMetadata } from "@/utils/metadata/generateMetadata"
|
||||
|
||||
export default async function DestinationCityPagePage(
|
||||
props: PageArgs<object, { view?: "map"; filterFromUrl?: string }>
|
||||
props: PageArgs<object, { view?: "map" }>
|
||||
) {
|
||||
const searchParams = await props.searchParams
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<Suspense fallback={<DestinationCityPageSkeleton />}>
|
||||
<DestinationCityPage
|
||||
isMapView={searchParams.view === "map"}
|
||||
filterFromUrl={searchParams.filterFromUrl}
|
||||
/>
|
||||
<DestinationCityPage isMapView={searchParams.view === "map"} />
|
||||
</Suspense>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,20 +5,16 @@ import DestinationCountryPageSkeleton from "@/components/ContentType/Destination
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import type { PageArgs } from "@/types/params"
|
||||
|
||||
export { generateMetadata } from "@/utils/metadata/generateMetadata"
|
||||
|
||||
export default async function DestinationCountryPagePage(
|
||||
props: PageArgs<object, { view?: "map"; filterFromUrl?: string }>
|
||||
) {
|
||||
const searchParams = await props.searchParams
|
||||
export default async function DestinationCountryPagePage() {
|
||||
// props: PageArgs<{}, { view?: "map"; }>
|
||||
// const searchParams = await props.searchParams
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
<Suspense fallback={<DestinationCountryPageSkeleton />}>
|
||||
<DestinationCountryPage
|
||||
// isMapView={searchParams.view === "map"} // Disabled until further notice
|
||||
filterFromUrl={searchParams.filterFromUrl}
|
||||
isMapView={false}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
@@ -44,16 +44,24 @@ export default function HotelList() {
|
||||
return
|
||||
}
|
||||
|
||||
function handleBoundsChanged() {
|
||||
debouncedUpdateVisibleHotels()
|
||||
}
|
||||
|
||||
coreLib.event.addListener(map, "bounds_changed", handleBoundsChanged)
|
||||
coreLib.event.addListener(
|
||||
map,
|
||||
"bounds_changed",
|
||||
debouncedUpdateVisibleHotels
|
||||
)
|
||||
return () => {
|
||||
coreLib.event.clearListeners(map, "bounds_changed")
|
||||
}
|
||||
}, [map, coreLib, debouncedUpdateVisibleHotels])
|
||||
|
||||
useEffect(() => {
|
||||
if (!map) {
|
||||
return
|
||||
}
|
||||
|
||||
setVisibleHotels(getVisibleHotels(activeHotels, map))
|
||||
}, [map, activeHotels])
|
||||
|
||||
if (isLoading) {
|
||||
return <HotelListSkeleton />
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ export default function CityMap({
|
||||
defaultLocation,
|
||||
}: CityMapProps) {
|
||||
const intl = useIntl()
|
||||
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
||||
const { activeHotels, allFilters, filterFromUrl } = useDestinationDataStore(
|
||||
(state) => ({
|
||||
activeHotels: state.activeHotels,
|
||||
allFilters: state.allFilters,
|
||||
activeFilters: state.activeFilters,
|
||||
filterFromUrl: state.filterFromUrl,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function CityMap({
|
||||
>
|
||||
<Typography variant="Title/sm">
|
||||
<h1 className={styles.title}>
|
||||
{getHeadingText(intl, city.name, allFilters, activeFilters[0])}
|
||||
{getHeadingText(intl, city.name, allFilters, filterFromUrl)}
|
||||
</h1>
|
||||
</Typography>
|
||||
<HotelList />
|
||||
|
||||
@@ -28,7 +28,6 @@ import DestinationPageSidePeek from "../Sidepeek"
|
||||
import StaticMap from "../StaticMap"
|
||||
import TopImages from "../TopImages"
|
||||
import DestinationTracking from "../Tracking"
|
||||
import { getHeadingText } from "../utils"
|
||||
import CityMap from "./CityMap"
|
||||
import DestinationCityPageSkeleton from "./DestinationCityPageSkeleton"
|
||||
|
||||
@@ -36,12 +35,10 @@ import styles from "./destinationCityPage.module.css"
|
||||
|
||||
interface DestinationCityPageProps {
|
||||
isMapView: boolean
|
||||
filterFromUrl?: string
|
||||
}
|
||||
|
||||
export default async function DestinationCityPage({
|
||||
isMapView,
|
||||
filterFromUrl,
|
||||
}: DestinationCityPageProps) {
|
||||
const intl = await getIntl()
|
||||
const lang = await getLang()
|
||||
@@ -94,7 +91,6 @@ export default async function DestinationCityPage({
|
||||
<DestinationDataProvider
|
||||
allHotels={allHotels}
|
||||
allFilters={allFilters}
|
||||
filterFromUrl={filterFromUrl}
|
||||
sortItems={sortItems}
|
||||
pathname={pathname}
|
||||
>
|
||||
@@ -120,16 +116,7 @@ export default async function DestinationCityPage({
|
||||
{blocks && <Blocks blocks={blocks} />}
|
||||
</main>
|
||||
<aside className={styles.sidebar}>
|
||||
<SidebarContentWrapper
|
||||
hasActiveFilter={!!filterFromUrl}
|
||||
preamble={preamble}
|
||||
heading={getHeadingText(
|
||||
intl,
|
||||
city.name,
|
||||
allFilters,
|
||||
filterFromUrl
|
||||
)}
|
||||
>
|
||||
<SidebarContentWrapper preamble={preamble} location={city.name}>
|
||||
<ExperienceList experiences={experiences} />
|
||||
{has_sidepeek && sidepeek_content ? (
|
||||
<DestinationPageSidePeek
|
||||
|
||||
@@ -28,11 +28,11 @@ export default function CountryMap({
|
||||
defaultLocation,
|
||||
}: CountryMapProps) {
|
||||
const intl = useIntl()
|
||||
const { activeHotels, allFilters, activeFilters } = useDestinationDataStore(
|
||||
const { activeHotels, allFilters, filterFromUrl } = useDestinationDataStore(
|
||||
(state) => ({
|
||||
activeHotels: state.activeHotels,
|
||||
allFilters: state.allFilters,
|
||||
activeFilters: state.activeFilters,
|
||||
filterFromUrl: state.filterFromUrl,
|
||||
})
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function CountryMap({
|
||||
>
|
||||
<Typography variant="Title/sm">
|
||||
<h1 className={styles.title}>
|
||||
{getHeadingText(intl, country, allFilters, activeFilters[0])}
|
||||
{getHeadingText(intl, country, allFilters, filterFromUrl)}
|
||||
</h1>
|
||||
</Typography>
|
||||
<CityList />
|
||||
|
||||
@@ -28,7 +28,6 @@ import SidebarContentWrapper from "../SidebarContentWrapper"
|
||||
import DestinationPageSidePeek from "../Sidepeek"
|
||||
import TopImages from "../TopImages"
|
||||
import DestinationTracking from "../Tracking"
|
||||
import { getHeadingText } from "../utils"
|
||||
import CountryMap from "./CountryMap"
|
||||
import DestinationCountryPageSkeleton from "./DestinationCountryPageSkeleton"
|
||||
|
||||
@@ -36,12 +35,10 @@ import styles from "./destinationCountryPage.module.css"
|
||||
|
||||
interface DestinationCountryPageProps {
|
||||
isMapView: boolean
|
||||
filterFromUrl?: string
|
||||
}
|
||||
|
||||
export default async function DestinationCountryPage({
|
||||
isMapView,
|
||||
filterFromUrl,
|
||||
}: DestinationCountryPageProps) {
|
||||
const intl = await getIntl()
|
||||
const lang = await getLang()
|
||||
@@ -93,7 +90,6 @@ export default async function DestinationCountryPage({
|
||||
allHotels={allHotels}
|
||||
allCities={allCities}
|
||||
allFilters={allFilters}
|
||||
filterFromUrl={filterFromUrl}
|
||||
sortItems={sortItems}
|
||||
pathname={pathname}
|
||||
>
|
||||
@@ -123,14 +119,8 @@ export default async function DestinationCountryPage({
|
||||
</main>
|
||||
<aside className={styles.sidebar}>
|
||||
<SidebarContentWrapper
|
||||
hasActiveFilter={!!filterFromUrl}
|
||||
preamble={preamble}
|
||||
heading={getHeadingText(
|
||||
intl,
|
||||
translatedCountry,
|
||||
allFilters,
|
||||
filterFromUrl
|
||||
)}
|
||||
location={translatedCountry}
|
||||
>
|
||||
<ExperienceList experiences={experiences} />
|
||||
{has_sidepeek && sidepeek_content ? (
|
||||
|
||||
@@ -1,37 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import { useRef } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import useStickyPosition from "@scandic-hotels/common/hooks/useStickyPosition"
|
||||
import { StickyElementNameEnum } from "@scandic-hotels/common/stores/sticky-position"
|
||||
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"
|
||||
|
||||
interface SidebarContentWrapperProps extends React.PropsWithChildren {
|
||||
preamble: string
|
||||
hasActiveFilter: boolean
|
||||
heading: string
|
||||
location: string
|
||||
}
|
||||
|
||||
export default function SidebarContentWrapper({
|
||||
preamble,
|
||||
hasActiveFilter,
|
||||
heading,
|
||||
location,
|
||||
children,
|
||||
}: SidebarContentWrapperProps) {
|
||||
const intl = useIntl()
|
||||
const sidebarRef = useRef<HTMLDivElement>(null)
|
||||
const { allFilters, filterFromUrl } = useDestinationDataStore((state) => ({
|
||||
allFilters: state.allFilters,
|
||||
filterFromUrl: state.filterFromUrl,
|
||||
}))
|
||||
useStickyPosition({
|
||||
ref: sidebarRef,
|
||||
name: StickyElementNameEnum.DESTINATION_SIDEBAR,
|
||||
})
|
||||
|
||||
const heading = getHeadingText(intl, location, allFilters, filterFromUrl)
|
||||
|
||||
return (
|
||||
<div ref={sidebarRef} className={styles.sidebarContent}>
|
||||
<Typography variant="Title/md">
|
||||
<h1 className={styles.heading}>{heading}</h1>
|
||||
</Typography>
|
||||
{!hasActiveFilter ? (
|
||||
{!filterFromUrl ? (
|
||||
<Typography variant="Body/Paragraph/mdRegular">
|
||||
<p className={styles.text}>{preamble}</p>
|
||||
</Typography>
|
||||
|
||||
@@ -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"
|
||||
|
||||
export function getHeadingText(
|
||||
intl: IntlShape,
|
||||
location: string,
|
||||
allFilters: CategorizedHotelFilters,
|
||||
filter?: string
|
||||
filterFromUrl: HotelFilter | null
|
||||
) {
|
||||
if (filter) {
|
||||
if (filterFromUrl) {
|
||||
const facilityFilter = allFilters.facilityFilters.find(
|
||||
(f) => f.slug === filter
|
||||
(f) => f.id === filterFromUrl.id
|
||||
)
|
||||
const surroudingsFilter = allFilters.surroundingsFilters.find(
|
||||
(f) => f.slug === filter
|
||||
(f) => f.id === filterFromUrl.id
|
||||
)
|
||||
|
||||
if (facilityFilter) {
|
||||
|
||||
@@ -53,8 +53,10 @@ export default function Filter({ filters }: FilterProps) {
|
||||
<Checkbox
|
||||
name={filter.name}
|
||||
value={filter.slug}
|
||||
onChange={() => togglePendingFilter(filter.slug)}
|
||||
isSelected={!!pendingFilters.find((f) => f === filter.slug)}
|
||||
onChange={() => togglePendingFilter(filter)}
|
||||
isSelected={
|
||||
!!pendingFilters.find((pf) => pf.id === filter.id)
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
@@ -75,8 +77,10 @@ export default function Filter({ filters }: FilterProps) {
|
||||
<Checkbox
|
||||
name={filter.name}
|
||||
value={filter.slug}
|
||||
onChange={() => togglePendingFilter(filter.slug)}
|
||||
isSelected={!!pendingFilters.find((f) => f === filter.slug)}
|
||||
onChange={() => togglePendingFilter(filter)}
|
||||
isSelected={
|
||||
!!pendingFilters.find((pf) => pf.id === filter.id)
|
||||
}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
|
||||
@@ -43,6 +43,7 @@ export default function DestinationFilterAndSort({
|
||||
basePath,
|
||||
pendingCount,
|
||||
activeFilters,
|
||||
filterFromUrl,
|
||||
clearPendingFilters,
|
||||
resetPendingValues,
|
||||
setIsLoading,
|
||||
@@ -56,6 +57,7 @@ export default function DestinationFilterAndSort({
|
||||
pendingCount:
|
||||
listType === "city" ? state.pendingCityCount : state.pendingHotelCount,
|
||||
activeFilters: state.activeFilters,
|
||||
filterFromUrl: state.filterFromUrl,
|
||||
clearPendingFilters: state.actions.clearPendingFilters,
|
||||
resetPendingValues: state.actions.resetPendingValues,
|
||||
setIsLoading: state.actions.setIsLoading,
|
||||
@@ -84,7 +86,7 @@ export default function DestinationFilterAndSort({
|
||||
function submitAndClose(close: () => void) {
|
||||
setIsLoading(true)
|
||||
const sort = pendingSort
|
||||
const filters = pendingFilters
|
||||
const filters = [...pendingFilters]
|
||||
const parsedUrl = new URL(window.location.href)
|
||||
const searchParams = parsedUrl.searchParams
|
||||
if (sort === defaultSort && searchParams.has("sort")) {
|
||||
@@ -92,14 +94,26 @@ export default function DestinationFilterAndSort({
|
||||
} else if (sort !== defaultSort) {
|
||||
searchParams.set("sort", sort)
|
||||
}
|
||||
const [firstFilter, ...remainingFilters] = filters
|
||||
|
||||
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 {
|
||||
searchParams.delete("filter")
|
||||
}
|
||||
|
||||
@@ -24,10 +24,14 @@ export default function DestinationDataProviderContent({
|
||||
const filterParam = searchParams.get("filter")
|
||||
const filters = []
|
||||
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) {
|
||||
filters.push(lastPathPart)
|
||||
filters.push(filterFromUrl)
|
||||
}
|
||||
if (filterParam) {
|
||||
filters.push(...filterParam.split("&"))
|
||||
|
||||
@@ -15,7 +15,6 @@ export default function DestinationDataProvider({
|
||||
allCities = [],
|
||||
allHotels,
|
||||
allFilters,
|
||||
filterFromUrl,
|
||||
sortItems,
|
||||
pathname,
|
||||
children,
|
||||
@@ -28,7 +27,6 @@ export default function DestinationDataProvider({
|
||||
allCities,
|
||||
allHotels,
|
||||
allFilters,
|
||||
filterFromUrl,
|
||||
pathname,
|
||||
sortItems,
|
||||
searchParams,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
type HotelFilter,
|
||||
type HotelListingHotelData,
|
||||
type HotelSortItem,
|
||||
HotelSortOption,
|
||||
@@ -25,12 +26,12 @@ const HOTEL_SORTING_STRATEGIES: Partial<
|
||||
|
||||
export function getFilteredHotels(
|
||||
hotels: HotelListingHotelData[],
|
||||
filters: string[]
|
||||
filters: HotelFilter[]
|
||||
) {
|
||||
if (filters.length) {
|
||||
return hotels.filter(({ hotel }) =>
|
||||
filters.every((filter) =>
|
||||
hotel.detailedFacilities.some((facility) => facility.slug === filter)
|
||||
hotel.detailedFacilities.some((facility) => facility.id === filter.id)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,26 +29,30 @@ export function createDestinationDataStore({
|
||||
allCities,
|
||||
allHotels,
|
||||
allFilters,
|
||||
filterFromUrl,
|
||||
pathname,
|
||||
sortItems,
|
||||
searchParams,
|
||||
}: InitialState) {
|
||||
const defaultSort =
|
||||
sortItems.find((s) => s.isDefault)?.value ?? sortItems[0].value
|
||||
const allFilterSlugs = Object.values(allFilters).flatMap(
|
||||
(filter: HotelFilter[]) => filter.map((f) => f.slug)
|
||||
)
|
||||
const flattenedFilters = Object.values(allFilters).flat<HotelFilter[]>()
|
||||
const allFilterSlugs = flattenedFilters.map((filter) => filter.slug)
|
||||
const activeFilters: HotelFilter[] = []
|
||||
let filterFromUrl: HotelFilter | null = null
|
||||
|
||||
const activeFilters: string[] = filterFromUrl ? [filterFromUrl] : []
|
||||
const basePathnameWithoutFilters = getBasePathNameWithoutFilters(
|
||||
pathname,
|
||||
allFilterSlugs
|
||||
)
|
||||
if (basePathnameWithoutFilters !== pathname) {
|
||||
const pathParts = pathname.split("/")
|
||||
const lastPathPart = pathParts[pathParts.length - 1]
|
||||
activeFilters.push(lastPathPart)
|
||||
filterFromUrl =
|
||||
flattenedFilters.find(
|
||||
(filter) => filter.slug === pathParts[pathParts.length - 1]
|
||||
) ?? null
|
||||
if (filterFromUrl) {
|
||||
activeFilters.push(filterFromUrl)
|
||||
}
|
||||
}
|
||||
|
||||
let activeSort = defaultSort
|
||||
@@ -65,13 +69,16 @@ export function createDestinationDataStore({
|
||||
|
||||
return create<DestinationDataState>((set) => ({
|
||||
actions: {
|
||||
updateActiveFiltersAndSort(filters, sort) {
|
||||
updateActiveFiltersAndSort(filterSlugs, sort) {
|
||||
return set(
|
||||
produce((state: DestinationDataState) => {
|
||||
const newSort =
|
||||
sort && isValidSortOption(sort, state.sortItems)
|
||||
? sort
|
||||
: state.defaultSort
|
||||
const filters = flattenedFilters.filter((filter) =>
|
||||
filterSlugs.includes(filter.slug)
|
||||
)
|
||||
const filteredHotels = getFilteredHotels(state.allHotels, filters)
|
||||
const sortedHotels = getSortedHotels(filteredHotels, newSort)
|
||||
const filteredCities = state.allHotels.length
|
||||
@@ -86,16 +93,22 @@ export function createDestinationDataStore({
|
||||
if (
|
||||
JSON.stringify(filters) !== JSON.stringify(state.activeFilters)
|
||||
) {
|
||||
const facilityFiltersUsed = filters.filter((f) =>
|
||||
state.allFilters.facilityFilters
|
||||
.map((ff) => ff.slug)
|
||||
.includes(f)
|
||||
)
|
||||
const surroundingsFiltersUsed = filters.filter((f) =>
|
||||
state.allFilters.surroundingsFilters
|
||||
.map((sf) => sf.slug)
|
||||
.includes(f)
|
||||
)
|
||||
const facilityFiltersUsed = filters
|
||||
.filter(
|
||||
(f) =>
|
||||
!!state.allFilters.facilityFilters.find(
|
||||
(ff) => ff.id === f.id
|
||||
)
|
||||
)
|
||||
.map((f) => f.slug)
|
||||
const surroundingsFiltersUsed = filters
|
||||
.filter(
|
||||
(f) =>
|
||||
!!state.allFilters.surroundingsFilters.find(
|
||||
(sf) => sf.id === f.id
|
||||
)
|
||||
)
|
||||
.map((f) => f.slug)
|
||||
|
||||
trackFilterChangeEvent(
|
||||
facilityFiltersUsed,
|
||||
@@ -133,9 +146,12 @@ export function createDestinationDataStore({
|
||||
togglePendingFilter(filter) {
|
||||
return set(
|
||||
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
|
||||
? state.pendingFilters.filter((f) => f !== filter)
|
||||
? state.pendingFilters.filter((f) => f.id !== filterId)
|
||||
: [...state.pendingFilters, filter]
|
||||
const pendingHotels = getFilteredHotels(state.allHotels, filters)
|
||||
const pendingCities = state.allHotels.length
|
||||
@@ -180,7 +196,7 @@ export function createDestinationDataStore({
|
||||
activeFilters,
|
||||
pendingFilters: activeFilters,
|
||||
allFilters,
|
||||
allFilterSlugs,
|
||||
filterFromUrl,
|
||||
basePathnameWithoutFilters,
|
||||
sortItems,
|
||||
isLoading: false,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { DestinationCityListItem } from "@scandic-hotels/trpc/types/destinationCityPage"
|
||||
import type {
|
||||
CategorizedHotelFilters,
|
||||
HotelFilter,
|
||||
HotelListingHotelData,
|
||||
HotelSortItem,
|
||||
HotelSortOption,
|
||||
@@ -8,9 +9,12 @@ import type {
|
||||
import type { ReadonlyURLSearchParams } from "next/navigation"
|
||||
|
||||
interface Actions {
|
||||
updateActiveFiltersAndSort: (filters: string[], sort: string | null) => void
|
||||
updateActiveFiltersAndSort: (
|
||||
filterSlugs: string[],
|
||||
sort: string | null
|
||||
) => void
|
||||
setPendingSort: (sort: HotelSortOption) => void
|
||||
togglePendingFilter: (filter: string) => void
|
||||
togglePendingFilter: (filter: HotelFilter) => void
|
||||
clearPendingFilters: () => void
|
||||
resetPendingValues: () => void
|
||||
setIsLoading: (isLoading: boolean) => void
|
||||
@@ -31,12 +35,12 @@ export interface DestinationDataState {
|
||||
pendingSort: HotelSortOption
|
||||
activeSort: HotelSortOption
|
||||
defaultSort: HotelSortOption
|
||||
pendingFilters: string[]
|
||||
activeFilters: string[]
|
||||
pendingFilters: HotelFilter[]
|
||||
activeFilters: HotelFilter[]
|
||||
filterFromUrl: HotelFilter | null
|
||||
pendingHotelCount: number
|
||||
pendingCityCount: number
|
||||
allFilters: CategorizedHotelFilters
|
||||
allFilterSlugs: string[]
|
||||
basePathnameWithoutFilters: string
|
||||
sortItems: HotelSortItem[]
|
||||
isLoading: boolean
|
||||
@@ -48,6 +52,5 @@ export interface InitialState
|
||||
"allHotels" | "allCities" | "sortItems" | "allFilters"
|
||||
> {
|
||||
pathname: string
|
||||
filterFromUrl?: string
|
||||
searchParams: ReadonlyURLSearchParams
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { FacilityEnum } from "@scandic-hotels/common/constants/facilities"
|
||||
import type { z } from "zod"
|
||||
|
||||
import type {
|
||||
@@ -87,6 +88,7 @@ export type HotelInput = z.input<typeof hotelInputSchema>
|
||||
export type RoomType = Pick<Room, "roomTypes" | "name">
|
||||
|
||||
export interface HotelFilter {
|
||||
id: FacilityEnum
|
||||
name: string
|
||||
slug: string
|
||||
filterType: string
|
||||
|
||||
@@ -73,6 +73,7 @@ export function getFiltersFromHotels(
|
||||
)
|
||||
return filter
|
||||
? {
|
||||
id: filter.id,
|
||||
name: filter.name,
|
||||
slug: filter.slug,
|
||||
filterType: filter.filter,
|
||||
|
||||
Reference in New Issue
Block a user