feat(BOOK-53): Added component for SEO filters and support filter switching
Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -32,8 +32,6 @@ export default function AccordionSection({ accordion, title }: AccordionProps) {
|
||||
<SectionHeader textTransform="uppercase" title={title} />
|
||||
<Accordion
|
||||
className={`${styles.accordion} ${allAccordionsVisible ? styles.allVisible : ""}`}
|
||||
theme="light"
|
||||
variant="card"
|
||||
>
|
||||
{accordion.map((acc) =>
|
||||
acc ? (
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||
import { SeoFilters } from "@/components/ContentType/DestinationPage/SeoFilters"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
@@ -59,6 +60,7 @@ export default async function DestinationCityPage({
|
||||
sidepeek_button_text,
|
||||
sidepeek_content,
|
||||
destination_settings,
|
||||
seo_filters,
|
||||
} = destinationCityPage
|
||||
|
||||
const allHotels = await getHotelsByCityIdentifier(cityIdentifier)
|
||||
@@ -114,6 +116,7 @@ export default async function DestinationCityPage({
|
||||
<main className={styles.mainContent}>
|
||||
<HotelListing />
|
||||
{blocks && <Blocks blocks={blocks} />}
|
||||
<SeoFilters seoFilters={seo_filters} location={city.name} />
|
||||
</main>
|
||||
<aside className={styles.sidebar}>
|
||||
<SidebarContentWrapper preamble={preamble} location={city.name}>
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import Breadcrumbs from "@/components/Breadcrumbs"
|
||||
import { SeoFilters } from "@/components/ContentType/DestinationPage/SeoFilters"
|
||||
import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
@@ -59,6 +60,7 @@ export default async function DestinationCountryPage({
|
||||
sidepeek_button_text,
|
||||
sidepeek_content,
|
||||
destination_settings,
|
||||
seo_filters,
|
||||
} = destinationCountryPage
|
||||
|
||||
const [allHotels, allCities] = await Promise.all([
|
||||
@@ -116,6 +118,10 @@ export default async function DestinationCountryPage({
|
||||
<main className={styles.mainContent}>
|
||||
<CityListing />
|
||||
{blocks && <Blocks blocks={blocks} />}
|
||||
<SeoFilters
|
||||
seoFilters={seo_filters}
|
||||
location={translatedCountry}
|
||||
/>
|
||||
</main>
|
||||
<aside className={styles.sidebar}>
|
||||
<SidebarContentWrapper
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
"use client"
|
||||
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Accordion from "@scandic-hotels/design-system/Accordion"
|
||||
import AccordionItem from "@scandic-hotels/design-system/Accordion/AccordionItem"
|
||||
import Link from "@scandic-hotels/design-system/Link"
|
||||
|
||||
import { useDestinationDataStore } from "@/stores/destination-data"
|
||||
|
||||
import styles from "./seoFilters.module.css"
|
||||
|
||||
import type { HotelFilter } from "@scandic-hotels/trpc/types/hotel"
|
||||
|
||||
interface SeoFiltersProps {
|
||||
seoFilters: {
|
||||
facilityFilters: HotelFilter[]
|
||||
surroundingsFilters: HotelFilter[]
|
||||
} | null
|
||||
location: string
|
||||
}
|
||||
|
||||
export function SeoFilters({ seoFilters, location }: SeoFiltersProps) {
|
||||
const intl = useIntl()
|
||||
const { basePath } = useDestinationDataStore((state) => ({
|
||||
basePath: state.basePathnameWithoutFilters,
|
||||
}))
|
||||
|
||||
if (!seoFilters) {
|
||||
return null
|
||||
}
|
||||
|
||||
const { facilityFilters, surroundingsFilters } = seoFilters
|
||||
|
||||
if (!facilityFilters.length && !surroundingsFilters.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className={styles.seoFilters}>
|
||||
<Accordion className={styles.accordion}>
|
||||
{facilityFilters.length > 0 ? (
|
||||
<AccordionItem
|
||||
title={intl.formatMessage(
|
||||
{ defaultMessage: "{location} Hotel facilities" },
|
||||
{ location }
|
||||
)}
|
||||
showAsSubtitle
|
||||
>
|
||||
<ul className={styles.filterList}>
|
||||
{facilityFilters.map((filter) => (
|
||||
<li key={filter.id}>
|
||||
<Link
|
||||
href={`${basePath}/${filter.slug}`}
|
||||
color="Text/Interactive/Secondary"
|
||||
textDecoration="underline"
|
||||
>
|
||||
{filter.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</AccordionItem>
|
||||
) : null}
|
||||
{surroundingsFilters.length > 0 ? (
|
||||
<AccordionItem
|
||||
title={intl.formatMessage(
|
||||
{ defaultMessage: "{location} Hotel surroundings" },
|
||||
{ location }
|
||||
)}
|
||||
showAsSubtitle
|
||||
>
|
||||
<ul className={styles.filterList}>
|
||||
{surroundingsFilters.map((filter) => (
|
||||
<li key={filter.id}>
|
||||
<Link
|
||||
href={`${basePath}/${filter.slug}`}
|
||||
color="Text/Interactive/Secondary"
|
||||
textDecoration="underline"
|
||||
>
|
||||
{filter.name}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</AccordionItem>
|
||||
) : null}
|
||||
</Accordion>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.filterList {
|
||||
column-count: 2;
|
||||
list-style-type: none;
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.filterList {
|
||||
column-count: 3;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ export default async function AmenitiesSidePeek({
|
||||
defaultMessage: "Close",
|
||||
})}
|
||||
>
|
||||
<Accordion>
|
||||
<Accordion type="sidepeek">
|
||||
<ParkingAccordionItem
|
||||
parking={parking.parking}
|
||||
elevatorPitch={parking.parkingElevatorPitch}
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function AccessibilityAccordionItem({
|
||||
})}
|
||||
iconName={IconName.Accessibility}
|
||||
className={styles.accordionItem}
|
||||
variant="sidepeek"
|
||||
type="sidepeek"
|
||||
onOpen={() => trackAccordionClick("amenities:accessibility")}
|
||||
>
|
||||
<div className={styles.accessibilityContent}>
|
||||
|
||||
@@ -439,12 +439,12 @@ export default function BookedRoomSidePeek({
|
||||
/>
|
||||
</div>
|
||||
{hotelRoom ? (
|
||||
<Accordion>
|
||||
<Accordion type="sidepeek">
|
||||
<AccordionItem
|
||||
title={intl.formatMessage({
|
||||
defaultMessage: "Room details",
|
||||
})}
|
||||
variant="sidepeek"
|
||||
type="sidepeek"
|
||||
>
|
||||
<RoomDetails
|
||||
roomDescription={hotelRoom.descriptions.medium}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function DestinationDataProviderContent({
|
||||
filters.push(...filterParam.split("&"))
|
||||
}
|
||||
|
||||
updateActiveFiltersAndSort(filters, sort)
|
||||
updateActiveFiltersAndSort(filters, sort, filterFromUrl)
|
||||
}, [params, updateActiveFiltersAndSort, basePath])
|
||||
|
||||
return <>{children}</>
|
||||
|
||||
@@ -69,7 +69,7 @@ export function createDestinationDataStore({
|
||||
|
||||
return create<DestinationDataState>((set) => ({
|
||||
actions: {
|
||||
updateActiveFiltersAndSort(filterSlugs, sort) {
|
||||
updateActiveFiltersAndSort(filterSlugs, sort, filterSlugFromUrl) {
|
||||
return set(
|
||||
produce((state: DestinationDataState) => {
|
||||
const newSort =
|
||||
@@ -79,6 +79,10 @@ export function createDestinationDataStore({
|
||||
const filters = flattenedFilters.filter((filter) =>
|
||||
filterSlugs.includes(filter.slug)
|
||||
)
|
||||
const filterFromUrl =
|
||||
flattenedFilters.find(
|
||||
(filter) => filter.slug === filterSlugFromUrl
|
||||
) ?? null
|
||||
const filteredHotels = getFilteredHotels(state.allHotels, filters)
|
||||
const sortedHotels = getSortedHotels(filteredHotels, newSort)
|
||||
const filteredCities = state.allHotels.length
|
||||
@@ -121,6 +125,7 @@ export function createDestinationDataStore({
|
||||
state.activeHotels = sortedHotels
|
||||
state.activeCities = sortedCities
|
||||
|
||||
state.filterFromUrl = filterFromUrl
|
||||
state.pendingFilters = filters
|
||||
state.pendingSort = newSort
|
||||
state.pendingHotelCount = filteredHotels.length
|
||||
|
||||
@@ -11,7 +11,8 @@ import type { ReadonlyURLSearchParams } from "next/navigation"
|
||||
interface Actions {
|
||||
updateActiveFiltersAndSort: (
|
||||
filterSlugs: string[],
|
||||
sort: string | null
|
||||
sort: string | null,
|
||||
filterSlugFromUrl: string | null
|
||||
) => void
|
||||
setPendingSort: (sort: HotelSortOption) => void
|
||||
togglePendingFilter: (filter: HotelFilter) => void
|
||||
|
||||
Reference in New Issue
Block a user