From a88a033e308a0ca15ca0f15be94d645872070bfa Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Fri, 24 Jan 2025 12:06:43 +0000 Subject: [PATCH] Merged in feat/SW-1450-destination-page-cs-components (pull request #1204) feat(SW-1450): added components in destination pages from cs * feat(SW-1450): added components in destination pages from cs * feat(SW-1450): added correct refs and removed classNames Approved-by: Fredrik Thorsson --- .../[contentType]/[uid]/@breadcrumbs/page.tsx | 8 +- .../(public)/[contentType]/[uid]/page.tsx | 4 +- .../destinationCityPage.module.css | 10 - .../ContentType/DestinationCityPage/index.tsx | 28 --- .../destinationCountryPage.module.css | 10 - .../DestinationCountryPage/index.tsx | 28 --- .../destinationCityPage.module.css | 64 ++++++ .../DestinationCityPage/index.tsx | 100 ++++++++++ .../destinationCountryPage.module.css | 64 ++++++ .../DestinationCountryPage/index.tsx | 97 ++++++++++ .../SidebarContentWrapper/index.tsx | 25 +++ .../sidebarContentWrapper.module.css | 12 ++ .../DestinationPage/Sidepeek/index.tsx | 52 +++++ .../Sidepeek/sidepeek.module.css | 4 + .../DestinationPage/TopImages/index.tsx | 34 ++++ .../TopImages/topImages.module.css | 62 ++++++ .../ContentType/DestinationPage/utils.ts | 59 ++++++ .../HotelCardDialogImage/index.tsx | 2 +- components/Maps/StaticMap/index.tsx | 16 +- .../Breadcrumbs/breadcrumbs.module.css | 2 +- .../TempDesignSystem/Breadcrumbs/variants.ts | 8 +- .../TempDesignSystem/Chip/chip.module.css | 32 ++- components/TempDesignSystem/Chip/index.tsx | 8 +- components/TempDesignSystem/Chip/variants.ts | 8 +- i18n/dictionaries/da.json | 9 + i18n/dictionaries/de.json | 9 + i18n/dictionaries/en.json | 9 + i18n/dictionaries/fi.json | 9 + i18n/dictionaries/no.json | 11 +- i18n/dictionaries/sv.json | 11 +- .../Fragments/DestinationCityPage/Ref.graphql | 7 + .../DestinationCountryPage/Ref.graphql | 7 + .../DestinationCityPage.graphql | 87 +++++++++ .../DestinationCountryPage.graphql | 58 ++++++ .../destinationCityPage/output.ts | 183 ++++++++++++++++-- .../contentstack/destinationCityPage/query.ts | 35 +--- .../contentstack/destinationCityPage/utils.ts | 37 ++++ .../destinationCountryPage/output.ts | 123 ++++++++++-- .../destinationCountryPage/query.ts | 28 +-- .../destinationCountryPage/utils.ts | 32 +++ .../routers/contentstack/schemas/pageLinks.ts | 27 +++ stores/sticky-position.ts | 3 + types/enums/content.ts | 3 + .../contentstack/destinationCityPage.ts | 3 +- .../contentstack/destinationCountryPage.ts | 4 +- 45 files changed, 1237 insertions(+), 195 deletions(-) delete mode 100644 components/ContentType/DestinationCityPage/destinationCityPage.module.css delete mode 100644 components/ContentType/DestinationCityPage/index.tsx delete mode 100644 components/ContentType/DestinationCountryPage/destinationCountryPage.module.css delete mode 100644 components/ContentType/DestinationCountryPage/index.tsx create mode 100644 components/ContentType/DestinationPage/DestinationCityPage/destinationCityPage.module.css create mode 100644 components/ContentType/DestinationPage/DestinationCityPage/index.tsx create mode 100644 components/ContentType/DestinationPage/DestinationCountryPage/destinationCountryPage.module.css create mode 100644 components/ContentType/DestinationPage/DestinationCountryPage/index.tsx create mode 100644 components/ContentType/DestinationPage/SidebarContentWrapper/index.tsx create mode 100644 components/ContentType/DestinationPage/SidebarContentWrapper/sidebarContentWrapper.module.css create mode 100644 components/ContentType/DestinationPage/Sidepeek/index.tsx create mode 100644 components/ContentType/DestinationPage/Sidepeek/sidepeek.module.css create mode 100644 components/ContentType/DestinationPage/TopImages/index.tsx create mode 100644 components/ContentType/DestinationPage/TopImages/topImages.module.css create mode 100644 components/ContentType/DestinationPage/utils.ts create mode 100644 lib/graphql/Fragments/DestinationCityPage/Ref.graphql create mode 100644 lib/graphql/Fragments/DestinationCountryPage/Ref.graphql create mode 100644 server/routers/contentstack/destinationCityPage/utils.ts create mode 100644 server/routers/contentstack/destinationCountryPage/utils.ts diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx index 4c5e7751b..939af980e 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/@breadcrumbs/page.tsx @@ -7,12 +7,18 @@ import { setLang } from "@/i18n/serverContext" import type { ContentTypeParams, LangParams, PageArgs } from "@/types/params" import { PageContentTypeEnum } from "@/types/requests/contentType" +const IGNORED_CONTENT_TYPES = [ + PageContentTypeEnum.hotelPage, + PageContentTypeEnum.destinationCityPage, + PageContentTypeEnum.destinationCountryPage, +] + export default function PageBreadcrumbs({ params, }: PageArgs) { setLang(params.lang) - if (params.contentType === PageContentTypeEnum.hotelPage) { + if (IGNORED_CONTENT_TYPES.includes(params.contentType)) { return null } diff --git a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx index 9a87f1e3c..2497d9e16 100644 --- a/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx +++ b/app/[lang]/(live)/(public)/[contentType]/[uid]/page.tsx @@ -5,9 +5,9 @@ import { isSignupPage } from "@/constants/routes/signup" import { env } from "@/env/server" import { getHotelPage } from "@/lib/trpc/memoizedRequests" -import DestinationCityPage from "@/components/ContentType/DestinationCityPage" -import DestinationCountryPage from "@/components/ContentType/DestinationCountryPage" import DestinationOverviewPage from "@/components/ContentType/DestinationOverviewPage" +import DestinationCityPage from "@/components/ContentType/DestinationPage/DestinationCityPage" +import DestinationCountryPage from "@/components/ContentType/DestinationPage/DestinationCountryPage" import HotelPage from "@/components/ContentType/HotelPage" import LoyaltyPage from "@/components/ContentType/LoyaltyPage" import StartPage from "@/components/ContentType/StartPage" diff --git a/components/ContentType/DestinationCityPage/destinationCityPage.module.css b/components/ContentType/DestinationCityPage/destinationCityPage.module.css deleted file mode 100644 index c58807d98..000000000 --- a/components/ContentType/DestinationCityPage/destinationCityPage.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.pageContainer { - display: grid; - max-width: var(--max-width); -} - -@media screen and (min-width: 768px) { - .pageContainer { - margin: 0 auto; - } -} diff --git a/components/ContentType/DestinationCityPage/index.tsx b/components/ContentType/DestinationCityPage/index.tsx deleted file mode 100644 index 16dbedb62..000000000 --- a/components/ContentType/DestinationCityPage/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Suspense } from "react" - -import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests" - -import TrackingSDK from "@/components/TrackingSDK" - -import styles from "./destinationCityPage.module.css" - -export default async function DestinationCityPage() { - const pageData = await getDestinationCityPage() - - if (!pageData) { - return null - } - - const { tracking, destinationCityPage } = pageData - - return ( - <> -
-

Destination City Page

-
- - - - - ) -} diff --git a/components/ContentType/DestinationCountryPage/destinationCountryPage.module.css b/components/ContentType/DestinationCountryPage/destinationCountryPage.module.css deleted file mode 100644 index c58807d98..000000000 --- a/components/ContentType/DestinationCountryPage/destinationCountryPage.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.pageContainer { - display: grid; - max-width: var(--max-width); -} - -@media screen and (min-width: 768px) { - .pageContainer { - margin: 0 auto; - } -} diff --git a/components/ContentType/DestinationCountryPage/index.tsx b/components/ContentType/DestinationCountryPage/index.tsx deleted file mode 100644 index c49f1886e..000000000 --- a/components/ContentType/DestinationCountryPage/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Suspense } from "react" - -import { getDestinationCountryPage } from "@/lib/trpc/memoizedRequests" - -import TrackingSDK from "@/components/TrackingSDK" - -import styles from "./destinationCountryPage.module.css" - -export default async function DestinationCountryPage() { - const pageData = await getDestinationCountryPage() - - if (!pageData) { - return null - } - - const { tracking, destinationCountryPage } = pageData - - return ( - <> -
-

Destination Country Page

-
- - - - - ) -} diff --git a/components/ContentType/DestinationPage/DestinationCityPage/destinationCityPage.module.css b/components/ContentType/DestinationPage/DestinationCityPage/destinationCityPage.module.css new file mode 100644 index 000000000..58ba82047 --- /dev/null +++ b/components/ContentType/DestinationPage/DestinationCityPage/destinationCityPage.module.css @@ -0,0 +1,64 @@ +.pageContainer { + --map-desktop-width: 23.75rem; + display: grid; + grid-template-areas: + "header" + "sidebar" + "mainSection"; + width: 100%; + gap: var(--Spacing-x4); +} + +.header { + grid-area: header; +} + +.mainSection { + grid-area: mainSection; + padding-bottom: var(--Spacing-x7); + min-height: 500px; /* This is a temporary value because of no content atm */ +} + +.sidebar { + grid-area: sidebar; + width: 100%; + height: 100%; + background-color: var(--Base-Surface-Subtle-Normal); +} + +.experienceList { + list-style: none; + display: flex; + gap: var(--Spacing-x1); + flex-wrap: wrap; +} + +.mapWrapper { + width: 100%; + height: 200px; + display: flex; + justify-content: center; + align-items: center; +} + +.mapWrapper img { + border-radius: var(--Corner-radius-Large); + overflow: hidden; +} + +@media screen and (min-width: 768px) { + .pageContainer { + max-width: var(--max-width-page); + margin: 0 auto; + gap: var(--Spacing-x4); + } +} + +@media screen and (min-width: 1367px) { + .pageContainer { + grid-template-areas: + "header sidebar" + "mainSection sidebar"; + grid-template-columns: 1fr var(--map-desktop-width); + } +} diff --git a/components/ContentType/DestinationPage/DestinationCityPage/index.tsx b/components/ContentType/DestinationPage/DestinationCityPage/index.tsx new file mode 100644 index 000000000..9578ba244 --- /dev/null +++ b/components/ContentType/DestinationPage/DestinationCityPage/index.tsx @@ -0,0 +1,100 @@ +import { Suspense } from "react" + +import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests" + +import Breadcrumbs from "@/components/Breadcrumbs" +import StaticMap from "@/components/Maps/StaticMap" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" +import Chip from "@/components/TempDesignSystem/Chip" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" +import TrackingSDK from "@/components/TrackingSDK" +import { getIntl } from "@/i18n" + +import SidebarContentWrapper from "../SidebarContentWrapper" +import DestinationPageSidePeek from "../Sidepeek" +import TopImages from "../TopImages" +import { mapExperiencesToListData } from "../utils" + +import styles from "./destinationCityPage.module.css" + +import { PageContentTypeEnum } from "@/types/requests/contentType" + +export default async function DestinationCityPage() { + const [intl, pageData] = await Promise.all([ + getIntl(), + getDestinationCityPage(), + ]) + + if (!pageData) { + return null + } + + const { tracking, destinationCityPage } = pageData + const { + images, + heading, + preamble, + experiences, + has_sidepeek, + sidepeek_button_text, + sidepeek_content, + destination_settings, + } = destinationCityPage + const experiencesList = await mapExperiencesToListData(experiences) + + return ( + <> +
+
+ }> + + + +
+
+ {/* TODO: Add hotel listing by cityIdentifier */} + {">>>> MAIN CONTENT <<<<"} +
+ +
+ + + + + ) +} diff --git a/components/ContentType/DestinationPage/DestinationCountryPage/destinationCountryPage.module.css b/components/ContentType/DestinationPage/DestinationCountryPage/destinationCountryPage.module.css new file mode 100644 index 000000000..58ba82047 --- /dev/null +++ b/components/ContentType/DestinationPage/DestinationCountryPage/destinationCountryPage.module.css @@ -0,0 +1,64 @@ +.pageContainer { + --map-desktop-width: 23.75rem; + display: grid; + grid-template-areas: + "header" + "sidebar" + "mainSection"; + width: 100%; + gap: var(--Spacing-x4); +} + +.header { + grid-area: header; +} + +.mainSection { + grid-area: mainSection; + padding-bottom: var(--Spacing-x7); + min-height: 500px; /* This is a temporary value because of no content atm */ +} + +.sidebar { + grid-area: sidebar; + width: 100%; + height: 100%; + background-color: var(--Base-Surface-Subtle-Normal); +} + +.experienceList { + list-style: none; + display: flex; + gap: var(--Spacing-x1); + flex-wrap: wrap; +} + +.mapWrapper { + width: 100%; + height: 200px; + display: flex; + justify-content: center; + align-items: center; +} + +.mapWrapper img { + border-radius: var(--Corner-radius-Large); + overflow: hidden; +} + +@media screen and (min-width: 768px) { + .pageContainer { + max-width: var(--max-width-page); + margin: 0 auto; + gap: var(--Spacing-x4); + } +} + +@media screen and (min-width: 1367px) { + .pageContainer { + grid-template-areas: + "header sidebar" + "mainSection sidebar"; + grid-template-columns: 1fr var(--map-desktop-width); + } +} diff --git a/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx b/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx new file mode 100644 index 000000000..971cd54d9 --- /dev/null +++ b/components/ContentType/DestinationPage/DestinationCountryPage/index.tsx @@ -0,0 +1,97 @@ +import { Suspense } from "react" + +import { getDestinationCountryPage } from "@/lib/trpc/memoizedRequests" + +import Breadcrumbs from "@/components/Breadcrumbs" +import StaticMap from "@/components/Maps/StaticMap" +import BreadcrumbsSkeleton from "@/components/TempDesignSystem/Breadcrumbs/BreadcrumbsSkeleton" +import Chip from "@/components/TempDesignSystem/Chip" +import Body from "@/components/TempDesignSystem/Text/Body" +import Title from "@/components/TempDesignSystem/Text/Title" +import TrackingSDK from "@/components/TrackingSDK" +import { getIntl } from "@/i18n" + +import SidebarContentWrapper from "../SidebarContentWrapper" +import DestinationPageSidePeek from "../Sidepeek" +import TopImages from "../TopImages" +import { mapExperiencesToListData } from "../utils" + +import styles from "./destinationCountryPage.module.css" + +import { PageContentTypeEnum } from "@/types/requests/contentType" + +export default async function DestinationCountryPage() { + const [intl, pageData] = await Promise.all([ + getIntl(), + getDestinationCountryPage(), + ]) + + if (!pageData) { + return null + } + + const { tracking, destinationCountryPage } = pageData + const { + images, + heading, + preamble, + experiences, + has_sidepeek, + sidepeek_button_text, + sidepeek_content, + destination_settings, + } = destinationCountryPage + const experiencesList = await mapExperiencesToListData(experiences) + + return ( + <> +
+
+ }> + + + +
+
+ {/* TODO: Add city listing by cityIdentifier */} + {">>>> MAIN CONTENT <<<<"} +
+ +
+ + + + + ) +} diff --git a/components/ContentType/DestinationPage/SidebarContentWrapper/index.tsx b/components/ContentType/DestinationPage/SidebarContentWrapper/index.tsx new file mode 100644 index 000000000..15ef68571 --- /dev/null +++ b/components/ContentType/DestinationPage/SidebarContentWrapper/index.tsx @@ -0,0 +1,25 @@ +"use client" + +import { useRef } from "react" + +import { StickyElementNameEnum } from "@/stores/sticky-position" + +import useStickyPosition from "@/hooks/useStickyPosition" + +import styles from "./sidebarContentWrapper.module.css" + +export default function SidebarContentWrapper({ + children, +}: React.PropsWithChildren) { + const sidebarRef = useRef(null) + useStickyPosition({ + ref: sidebarRef, + name: StickyElementNameEnum.DESTINATION_SIDEBAR, + }) + + return ( +
+ {children} +
+ ) +} diff --git a/components/ContentType/DestinationPage/SidebarContentWrapper/sidebarContentWrapper.module.css b/components/ContentType/DestinationPage/SidebarContentWrapper/sidebarContentWrapper.module.css new file mode 100644 index 000000000..f41dc74be --- /dev/null +++ b/components/ContentType/DestinationPage/SidebarContentWrapper/sidebarContentWrapper.module.css @@ -0,0 +1,12 @@ +.sidebarContent { + display: grid; + align-content: start; + gap: var(--Spacing-x2); + padding: var(--Spacing-x4); +} + +@media screen and (min-width: 1367px) { + .sidebarContent { + position: sticky; + } +} diff --git a/components/ContentType/DestinationPage/Sidepeek/index.tsx b/components/ContentType/DestinationPage/Sidepeek/index.tsx new file mode 100644 index 000000000..e52097ff0 --- /dev/null +++ b/components/ContentType/DestinationPage/Sidepeek/index.tsx @@ -0,0 +1,52 @@ +"use client" + +import { useState } from "react" + +import { ChevronRightSmallIcon } from "@/components/Icons" +import JsonToHtml from "@/components/JsonToHtml" +import Button from "@/components/TempDesignSystem/Button" +import SidePeek from "@/components/TempDesignSystem/SidePeek" + +import type { DestinationCityPageData } from "@/types/trpc/routers/contentstack/destinationCityPage" +import type { DestinationCountryPageData } from "@/types/trpc/routers/contentstack/destinationCountryPage" + +interface DestinationPageSidepeekProps { + buttonText: string + sidePeekContent: NonNullable< + | DestinationCityPageData["sidepeek_content"] + | DestinationCountryPageData["sidepeek_content"] + > +} +export default function DestinationPageSidepeek({ + buttonText, + sidePeekContent, +}: DestinationPageSidepeekProps) { + const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false) + const { heading, content } = sidePeekContent + + return ( +
+ + setSidePeekIsOpen(false)} + > + + +
+ ) +} diff --git a/components/ContentType/DestinationPage/Sidepeek/sidepeek.module.css b/components/ContentType/DestinationPage/Sidepeek/sidepeek.module.css new file mode 100644 index 000000000..e40062540 --- /dev/null +++ b/components/ContentType/DestinationPage/Sidepeek/sidepeek.module.css @@ -0,0 +1,4 @@ +.ctaContainer { + display: grid; + gap: var(--Spacing-x2); +} diff --git a/components/ContentType/DestinationPage/TopImages/index.tsx b/components/ContentType/DestinationPage/TopImages/index.tsx new file mode 100644 index 000000000..954fb36a0 --- /dev/null +++ b/components/ContentType/DestinationPage/TopImages/index.tsx @@ -0,0 +1,34 @@ +"use client" + +import Image from "@/components/Image" + +import styles from "./topImages.module.css" + +import type { ImageVaultAsset } from "@/types/components/imageVault" + +interface TopImageProps { + images: ImageVaultAsset[] +} + +export default function TopImages({ images }: TopImageProps) { + const maxWidth = 1020 + + return ( +
+ {images.slice(0, 3).map((image, index) => ( + {image.meta.alt + ))} +
+ ) +} diff --git a/components/ContentType/DestinationPage/TopImages/topImages.module.css b/components/ContentType/DestinationPage/TopImages/topImages.module.css new file mode 100644 index 000000000..b08aabfc8 --- /dev/null +++ b/components/ContentType/DestinationPage/TopImages/topImages.module.css @@ -0,0 +1,62 @@ +.imageWrapper { + max-width: var(--max-width-page); + margin: 0 auto; +} + +.image { + height: 200px; + max-height: 40dvh; + width: 100%; + border-radius: var(--Corner-radius-Medium); +} + +@media screen and (max-width: 767px) { + .image:not(:first-child) { + display: none; + } +} + +@media screen and (min-width: 768px) { + .imageWrapper { + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: repeat(2, 1fr); + gap: var(--Spacing-x1); + } + + .imageWrapper > .image:first-child { + grid-column: span 2; + grid-row: span 2; + height: 300px; + } + + .imageWrapper > .image:only-child { + grid-column: span 3; + } + + .imageWrapper > .image:nth-child(2):nth-last-child(1) { + grid-column: span 1; + grid-row: span 2; + height: 300px; + } + + .imageWrapper > .image:nth-child(2):nth-last-child(2), + .imageWrapper > .image:nth-child(3) { + grid-column: span 1; + grid-row: span 1; + height: calc(150px - var(--Spacing-x-half)); + max-height: calc(20dvh - var(--Spacing-x-half)); + } +} + +@media screen and (min-width: 1367px) { + .imageWrapper > .image:first-child, + .imageWrapper > .image:nth-child(2):nth-last-child(1) { + height: 400px; + } + + .imageWrapper > .image:nth-child(2):nth-last-child(2), + .imageWrapper > .image:nth-child(3) { + height: calc(200px - var(--Spacing-x-half)); + } +} diff --git a/components/ContentType/DestinationPage/utils.ts b/components/ContentType/DestinationPage/utils.ts new file mode 100644 index 000000000..022550ee7 --- /dev/null +++ b/components/ContentType/DestinationPage/utils.ts @@ -0,0 +1,59 @@ +import { + BikeIcon, + CityIcon, + FamilyIcon, + KayakingIcon, + MuseumIcon, + NightlifeIcon, + StarFilledIcon, +} from "@/components/Icons" +import { getIntl } from "@/i18n" + +import type { FC } from "react" + +import type { IconProps } from "@/types/components/icon" + +export async function mapExperiencesToListData( + experiences: string[] +): Promise<{ Icon: FC; name: string }[]> { + const intl = await getIntl() + + return experiences.map((experience) => { + switch (experience) { + case "Hiking": + return { + Icon: StarFilledIcon, + name: intl.formatMessage({ id: "Hiking" }), + } + case "Kayaking": + return { + Icon: KayakingIcon, + name: intl.formatMessage({ id: "Kayaking" }), + } + case "Bike friendly": + return { + Icon: BikeIcon, + name: intl.formatMessage({ id: "Bike friendly" }), + } + case "Museums": + return { Icon: MuseumIcon, name: intl.formatMessage({ id: "Museums" }) } + case "Family friendly": + return { + Icon: FamilyIcon, + name: intl.formatMessage({ id: "Family friendly" }), + } + case "City pulse": + return { + Icon: CityIcon, + name: intl.formatMessage({ id: "City pulse" }), + } + case "Nightlife": + return { + Icon: NightlifeIcon, + name: intl.formatMessage({ id: "Nightlife" }), + } + default: + return { Icon: StarFilledIcon, name: experience } + } + }) +} diff --git a/components/HotelReservation/HotelCardDialog/HotelCardDialogImage/index.tsx b/components/HotelReservation/HotelCardDialog/HotelCardDialogImage/index.tsx index 7e5caa2cb..9c6ad0b3c 100644 --- a/components/HotelReservation/HotelCardDialog/HotelCardDialogImage/index.tsx +++ b/components/HotelReservation/HotelCardDialog/HotelCardDialogImage/index.tsx @@ -31,7 +31,7 @@ export default function HotelCardDialogImage({ /> )}
- + {ratings} diff --git a/components/Maps/StaticMap/index.tsx b/components/Maps/StaticMap/index.tsx index 510b6b1b8..e71a64a65 100644 --- a/components/Maps/StaticMap/index.tsx +++ b/components/Maps/StaticMap/index.tsx @@ -14,14 +14,16 @@ function getCenter({ city?: string country?: string }): string | undefined { - switch (true) { - case !!coordinates: - return `${coordinates.lat},${coordinates.lng}` - case !!country: - return `${city}, ${country}` - default: - return city + if (coordinates) { + return `${coordinates.lat},${coordinates.lng}` } + if (city && country) { + return `${city}, ${country}` + } + if (country) { + return country + } + return city } export default function StaticMap({ diff --git a/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css b/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css index a0dfee42d..bc607a43c 100644 --- a/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css +++ b/components/TempDesignSystem/Breadcrumbs/breadcrumbs.module.css @@ -10,7 +10,7 @@ padding-bottom: 0; } -.hotelHeaderWidth.breadcrumbs { +.headerWidth.breadcrumbs { max-width: min(var(--max-width-page), calc(100% - var(--max-width-spacing))); } diff --git a/components/TempDesignSystem/Breadcrumbs/variants.ts b/components/TempDesignSystem/Breadcrumbs/variants.ts index 35fa1963f..68423b7a7 100644 --- a/components/TempDesignSystem/Breadcrumbs/variants.ts +++ b/components/TempDesignSystem/Breadcrumbs/variants.ts @@ -10,10 +10,10 @@ export const breadcrumbsVariants = cva(styles.breadcrumbs, { [PageContentTypeEnum.accountPage]: styles.fullWidth, [PageContentTypeEnum.contentPage]: styles.contentWidth, [PageContentTypeEnum.collectionPage]: styles.contentWidth, - [PageContentTypeEnum.destinationOverviewPage]: styles.contentWidth, - [PageContentTypeEnum.destinationCountryPage]: styles.contentWidth, - [PageContentTypeEnum.destinationCityPage]: styles.contentWidth, - [PageContentTypeEnum.hotelPage]: styles.hotelHeaderWidth, + [PageContentTypeEnum.destinationOverviewPage]: styles.fullWidth, + [PageContentTypeEnum.destinationCountryPage]: styles.fullWidth, + [PageContentTypeEnum.destinationCityPage]: styles.fullWidth, + [PageContentTypeEnum.hotelPage]: styles.headerWidth, [PageContentTypeEnum.loyaltyPage]: styles.fullWidth, [PageContentTypeEnum.startPage]: styles.contentWidth, default: styles.fullWidth, diff --git a/components/TempDesignSystem/Chip/chip.module.css b/components/TempDesignSystem/Chip/chip.module.css index efebaee95..de1310fac 100644 --- a/components/TempDesignSystem/Chip/chip.module.css +++ b/components/TempDesignSystem/Chip/chip.module.css @@ -1,19 +1,31 @@ div.chip { - align-items: center; - border-radius: var(--Corner-radius-xLarge); + --chip-text-color: var(--Base-Text-High-contrast); + --chip-background-color: var(--Base-Surface-Primary-light-Normal); display: flex; - gap: var(--Spacing-x-half); - height: 22px; justify-content: center; + align-items: center; padding: var(--Spacing-x-half) var(--Spacing-x1); + gap: var(--Spacing-x-half); + border-radius: var(--Corner-radius-Small); + color: var(--chip-text-color); + background-color: var(--chip-background-color); } -.primary { - background-color: var(--Scandic-Red-90); - color: var(--Primary-Dark-On-Surface-Accent); +.chip *, +.chip svg * { + fill: var(--chip-text-color); } -.secondary { - background-color: var(--Base-Surface-Primary-light-Normal); - color: var(--Primary-Light-On-Surface-Text); +.chip.burgundy { + --chip-text-color: var(--Primary-Dark-On-Surface-Text); + --chip-background-color: var(--Base-Text-High-contrast); +} + +.chip.transparent { + --chip-text-color: var(--UI-Input-Controls-On-Fill-Normal); + --chip-background-color: rgba(64, 57, 55, 0.9); +} + +.chip.tag { + --chip-background-color: var(--Base-Surface-Subtle-Hover); } diff --git a/components/TempDesignSystem/Chip/index.tsx b/components/TempDesignSystem/Chip/index.tsx index 11009cd7e..5cc0e1755 100644 --- a/components/TempDesignSystem/Chip/index.tsx +++ b/components/TempDesignSystem/Chip/index.tsx @@ -4,15 +4,9 @@ import { chipVariants } from "./variants" import type { ChipProps } from "./chip" -export default function Chip({ - children, - className, - intent, - variant, -}: ChipProps) { +export default function Chip({ children, className, variant }: ChipProps) { const classNames = chipVariants({ className, - intent, variant, }) return ( diff --git a/components/TempDesignSystem/Chip/variants.ts b/components/TempDesignSystem/Chip/variants.ts index e9d30cd64..fe5e27970 100644 --- a/components/TempDesignSystem/Chip/variants.ts +++ b/components/TempDesignSystem/Chip/variants.ts @@ -4,16 +4,14 @@ import styles from "./chip.module.css" export const chipVariants = cva(styles.chip, { variants: { - intent: { - primary: styles.primary, - secondary: styles.secondary, - }, variant: { default: styles.default, + burgundy: styles.burgundy, + transparent: styles.transparent, + tag: styles.tag, }, }, defaultVariants: { - intent: "primary", variant: "default", }, }) diff --git a/i18n/dictionaries/da.json b/i18n/dictionaries/da.json index b75535fb9..fb28b2a2b 100644 --- a/i18n/dictionaries/da.json +++ b/i18n/dictionaries/da.json @@ -54,6 +54,7 @@ "Bed": "Seng type", "Bed options": "Sengemuligheder", "Bed type": "Seng type", + "Bike friendly": "Cykelvenlig", "Birth date": "Fødselsdato", "Book": "Book", "Book a table online": "Book et bord online", @@ -91,6 +92,7 @@ "Choose room": "Vælg rum", "Cities": "Byer", "City": "By", + "City pulse": "Byens puls", "City/State": "By/Stat", "Clear all filters": "Ryd alle filtre", "Clear searches": "Ryd søgninger", @@ -159,6 +161,7 @@ "Failed to delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.", "Failed to verify membership": "Medlemskab ikke verificeret", "Fair": "Messe", + "Family friendly": "Familievenlig", "Filter": "Filter", "Filter and sort": "Filtrer og sorter", "Filter by": "Filtrer efter", @@ -191,6 +194,7 @@ "Hi {firstName}!": "Hei {firstName}!", "High floor": "Højt niveau", "Highest level": "Højeste niveau", + "Hiking": "Vandring", "Home": "Hjem", "Hospital": "Hospital", "Hotel": "Hotel", @@ -219,6 +223,7 @@ "Join at no cost": "Tilmeld dig uden omkostninger", "Join now": "Tilmeld dig nu", "Join or log in while booking for member pricing.": "Tilmeld dig eller log ind under booking for medlemspris.", + "Kayaking": "Kajakroning", "King bed": "Kingsize-seng", "Language": "Sprog", "Last name": "Efternavn", @@ -247,6 +252,8 @@ "Main menu": "Hovedmenu", "Manage preferences": "Administrer præferencer", "Map": "Kort", + "Map of the city center": "Kort over byens centrum", + "Map of the country": "Kort over landet", "Map of {hotelName}": "Map of {hotelName}", "Marketing city": "Marketing by", "Max {max, plural, one {{range} guest} other {{range} guests}}": "Maks {max, plural, one {{range} gæst} other {{range} gæster}}", @@ -269,6 +276,7 @@ "Monday": "Mandag", "Month": "Måned", "Museum": "Museum", + "Museums": "Museer", "My communication preferences": "Mine kommunikationspræferencer", "My membership cards": "Mine medlemskort", "My pages": "Mine sider", @@ -282,6 +290,7 @@ "Nearby companies": "Nærliggende virksomheder", "New password": "Nyt kodeord", "Next": "Næste", + "Nightlife": "Natteliv", "Nights needed to level up": "Nætter nødvendige for at komme i niveau", "No": "Nej", "No availability": "Ingen tilgængelighed", diff --git a/i18n/dictionaries/de.json b/i18n/dictionaries/de.json index 07b923fd9..fd1ce2867 100644 --- a/i18n/dictionaries/de.json +++ b/i18n/dictionaries/de.json @@ -54,6 +54,7 @@ "Bed": "Bettentyp", "Bed options": "Bettoptionen", "Bed type": "Bettentyp", + "Bike friendly": "Fahrradfreundlich", "Birth date": "Geburtsdatum", "Book": "Buchen", "Book a table online": "Tisch online buchen", @@ -90,6 +91,7 @@ "Choose room": "Zimmer wählen", "Cities": "Städte", "City": "Stadt", + "City pulse": "Stadtpuls", "City/State": "Stadt/Zustand", "Clear all filters": "Alle Filter löschen", "Clear searches": "Suche löschen", @@ -158,6 +160,7 @@ "Failed to delete credit card, please try again later.": "Kreditkarte konnte nicht gelöscht werden. Bitte versuchen Sie es später noch einmal.", "Failed to verify membership": "Medlemskab nicht verifiziert", "Fair": "Messe", + "Family friendly": "Familienfreundlich", "Filter": "Filter", "Filter and sort": "Filtern und sortieren", "Filter by": "Filtern nach", @@ -190,6 +193,7 @@ "Hi {firstName}!": "Hallo {firstName}!", "High floor": "Hohes Level", "Highest level": "Höchstes Level", + "Hiking": "Wandern", "Home": "Heim", "Hospital": "Krankenhaus", "Hotel": "Hotel", @@ -218,6 +222,7 @@ "Join at no cost": "Kostenlos beitreten", "Join now": "Mitglied werden", "Join or log in while booking for member pricing.": "Treten Sie Scandic Friends bei oder loggen Sie sich ein, um den Mitgliederpreis zu erhalten.", + "Kayaking": "Kajakfahren", "King bed": "Kingsize-Bett", "Language": "Sprache", "Last name": "Nachname", @@ -246,6 +251,8 @@ "Main menu": "Hauptmenü", "Manage preferences": "Verwalten von Voreinstellungen", "Map": "Karte", + "Map of the city center": "Karte des Stadtzentrums", + "Map of the country": "Karte des Landes", "Map of {hotelName}": "Map of {hotelName}", "Marketing city": "Marketingstadt", "Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} gast} other {{range} gäste}}", @@ -267,6 +274,7 @@ "Monday": "Montag", "Month": "Monat", "Museum": "Museum", + "Museums": "Museen", "My communication preferences": "Meine Kommunikationseinstellungen", "My membership cards": "Meine Mitgliedskarten", "My pages": "Meine Seiten", @@ -280,6 +288,7 @@ "Nearby companies": "Nahe gelegene Unternehmen", "New password": "Neues Kennwort", "Next": "Nächste", + "Nightlife": "Nachtleben", "Nights needed to level up": "Nächte, die zum Levelaufstieg benötigt werden", "No": "Nein", "No availability": "Keine Verfügbarkeit", diff --git a/i18n/dictionaries/en.json b/i18n/dictionaries/en.json index 4c86388aa..015a4a5e3 100644 --- a/i18n/dictionaries/en.json +++ b/i18n/dictionaries/en.json @@ -54,6 +54,7 @@ "Bed": "Bed", "Bed options": "Bed options", "Bed type": "Bed type", + "Bike friendly": "Bike friendly", "Birth date": "Birth date", "Book": "Book", "Book a table online": "Book a table online", @@ -98,6 +99,7 @@ "Choose room": "Choose room", "Cities": "Cities", "City": "City", + "City pulse": "City pulse", "City/State": "City/State", "Clear all filters": "Clear all filters", "Clear searches": "Clear searches", @@ -170,6 +172,7 @@ "Failed to delete credit card, please try again later.": "Failed to delete credit card, please try again later.", "Failed to verify membership": "Failed to verify membership", "Fair": "Fair", + "Family friendly": "Family friendly", "Filter": "Filter", "Filter and sort": "Filter and sort", "Filter by": "Filter by", @@ -207,6 +210,7 @@ "Hi {firstName}!": "Hi {firstName}!", "High floor": "High floor", "Highest level": "Highest level", + "Hiking": "Hiking", "Home": "Home", "Hospital": "Hospital", "Hotel": "Hotel", @@ -235,6 +239,7 @@ "Join at no cost": "Join at no cost", "Join now": "Join now", "Join or log in while booking for member pricing.": "Join or log in while booking for member pricing.", + "Kayaking": "Kayaking", "King bed": "King bed", "Language": "Language", "Last name": "Last name", @@ -265,6 +270,8 @@ "Manage booking": "Manage booking", "Manage preferences": "Manage preferences", "Map": "Map", + "Map of the city center": "Map of the city center", + "Map of the country": "Map of the country", "Map of {hotelName}": "Map of {hotelName}", "Marketing city": "Marketing city", "Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} guest} other {{range} guests}}", @@ -291,6 +298,7 @@ "Monday": "Monday", "Month": "Month", "Museum": "Museum", + "Museums": "Museums", "My communication preferences": "My communication preferences", "My membership cards": "My membership cards", "My pages": "My pages", @@ -304,6 +312,7 @@ "Nearby companies": "Nearby companies", "New password": "New password", "Next": "Next", + "Nightlife": "Nightlife", "Nights needed to level up": "Nights needed to level up", "No": "No", "No availability": "No availability", diff --git a/i18n/dictionaries/fi.json b/i18n/dictionaries/fi.json index cd21f5ec0..76cf0283c 100644 --- a/i18n/dictionaries/fi.json +++ b/i18n/dictionaries/fi.json @@ -54,6 +54,7 @@ "Bed": "Vuodetyyppi", "Bed options": "Vuodevaihtoehdot", "Bed type": "Vuodetyyppi", + "Bike friendly": "Pyöräystävällinen", "Birth date": "Syntymäaika", "Book": "Varaa", "Book a table online": "Varaa pöytä verkossa", @@ -91,6 +92,7 @@ "Choose room": "Valitse huone", "Cities": "Kaupungit", "City": "Kaupunki", + "City pulse": "Kaupungin syke", "City/State": "Kaupunki/Osavaltio", "Clear all filters": "Tyhjennä kaikki suodattimet", "Clear searches": "Tyhjennä haut", @@ -159,6 +161,7 @@ "Failed to delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.", "Failed to verify membership": "Jäsenyys ei verifioitu", "Fair": "Messukeskus", + "Family friendly": "Perheystävällinen", "Filter": "Suodatin", "Filter and sort": "Suodata ja lajittele", "Filter by": "Suodatusperuste", @@ -191,6 +194,7 @@ "Hi {firstName}!": "Hi {firstName}!", "High floor": "Korkea taso", "Highest level": "Korkein taso", + "Hiking": "Hiking", "Home": "Kotiin", "Hospital": "Sairaala", "Hotel": "Hotelli", @@ -219,6 +223,7 @@ "Join at no cost": "Liity maksutta", "Join now": "Liity jäseneksi", "Join or log in while booking for member pricing.": "Liity tai kirjaudu sisään, kun varaat jäsenhinnan.", + "Kayaking": "Melonta", "King bed": "King-vuode", "Language": "Kieli", "Last name": "Sukunimi", @@ -247,6 +252,8 @@ "Main menu": "Päävalikko", "Manage preferences": "Asetusten hallinta", "Map": "Kartta", + "Map of the city center": "Kartta kaupungin keskustasta", + "Map of the country": "Kartta maasta", "Map of {hotelName}": "Map of {hotelName}", "Marketing city": "Markkinointikaupunki", "Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} vieras} other {{range} vieraita}}", @@ -269,6 +276,7 @@ "Monday": "Maanantai", "Month": "Kuukausi", "Museum": "Museo", + "Museums": "Museot", "My communication preferences": "Viestintämieltymykseni", "My membership cards": "Jäsenkorttini", "My pages": "Omat sivut", @@ -282,6 +290,7 @@ "Nearby companies": "Läheiset yritykset", "New password": "Uusi salasana", "Next": "Seuraava", + "Nightlife": "Yöelämä", "Nights needed to level up": "Yöt, joita tarvitaan tasolle", "No": "Ei", "No availability": "Ei saatavuutta", diff --git a/i18n/dictionaries/no.json b/i18n/dictionaries/no.json index 51388333b..f89d6c7bf 100644 --- a/i18n/dictionaries/no.json +++ b/i18n/dictionaries/no.json @@ -54,6 +54,7 @@ "Bed": "Seng type", "Bed options": "Sengemuligheter", "Bed type": "Seng type", + "Bike friendly": "Sykkelvennlig", "Birth date": "Fødselsdato", "Book": "Bestill", "Book a table online": "Bestill bord online", @@ -91,6 +92,7 @@ "Choose room": "Velg rom", "Cities": "Byer", "City": "By", + "City pulse": "Byens puls", "City/State": "By/Stat", "Clear all filters": "Fjern alle filtre", "Clear searches": "Tømme søk", @@ -158,6 +160,7 @@ "Failed to delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.", "Failed to verify membership": "Medlemskap ikke verifisert", "Fair": "Messe", + "Family friendly": "Familievennlig", "Filter": "Filter", "Filter and sort": "Filtrer og sorter", "Filter by": "Filtrer etter", @@ -190,6 +193,7 @@ "Hi {firstName}!": "Hei {firstName}!", "High floor": "Høy nivå", "Highest level": "Høyeste nivå", + "Hiking": "Fotturer", "Home": "Hjem", "Hospital": "Sykehus", "Hotel": "Hotel", @@ -218,6 +222,7 @@ "Join at no cost": "Bli med uten kostnad", "Join now": "Bli medlem nå", "Join or log in while booking for member pricing.": "Bli med eller logg inn under bestilling for medlemspris.", + "Kayaking": "Kajakkpadling", "King bed": "King-size-seng", "Language": "Språk", "Last name": "Etternavn", @@ -246,7 +251,9 @@ "Main menu": "Hovedmeny", "Manage preferences": "Administrer preferanser", "Map": "Kart", - "Map of {hotelName}": "Map of {hotelName}", + "Map of the city center": "Kart over sentrum", + "Map of the country": "Kart over landet", + "Map of {hotelName}": "Kart over {hotelName}", "Marketing city": "Markedsføringsby", "Max {max, plural, one {{range} guest} other {{range} guests}}": "Maks {max, plural, one {{range} gjest} other {{range} gjester}}", "Meetings & Conferences": "Møter & Konferanser", @@ -268,6 +275,7 @@ "Monday": "Mandag", "Month": "Måned", "Museum": "Museum", + "Museums": "Museums", "My communication preferences": "Mine kommunikasjonspreferanser", "My membership cards": "Mine medlemskort", "My pages": "Mine sider", @@ -281,6 +289,7 @@ "Nearby companies": "Nærliggende selskaper", "New password": "Nytt passord", "Next": "Neste", + "Nightlife": "Natteliv", "Nights needed to level up": "Netter som trengs for å komme opp i nivå", "No": "Nei", "No availability": "Ingen tilgjengelighet", diff --git a/i18n/dictionaries/sv.json b/i18n/dictionaries/sv.json index 54383d2c6..706ae69d6 100644 --- a/i18n/dictionaries/sv.json +++ b/i18n/dictionaries/sv.json @@ -54,6 +54,7 @@ "Bed": "Sängtyp", "Bed options": "Sängalternativ", "Bed type": "Sängtyp", + "Bike friendly": "Cykelvänligt", "Birth date": "Födelsedatum", "Book": "Boka", "Book a table online": "Boka ett bord online", @@ -91,6 +92,7 @@ "Choose room": "Välj rum", "Cities": "Städer", "City": "Ort", + "City pulse": "Stadspuls", "City/State": "Ort", "Clear all filters": "Rensa alla filter", "Clear searches": "Rensa tidigare sökningar", @@ -158,6 +160,7 @@ "Failed to delete credit card, please try again later.": "Det gick inte att ta bort kreditkortet, försök igen senare.", "Failed to verify membership": "Medlemskap inte verifierat", "Fair": "Mässa", + "Family friendly": "Familjevänligt", "Filter": "Filter", "Filter and sort": "Filtrera och sortera", "Filter by": "Filtrera på", @@ -190,6 +193,7 @@ "Hi {firstName}!": "Hej {firstName}!", "High floor": "Högt upp", "Highest level": "Högsta nivå", + "Hiking": "Vandring", "Home": "Hem", "Hospital": "Sjukhus", "Hotel": "Hotell", @@ -218,6 +222,7 @@ "Join at no cost": "Gå med utan kostnad", "Join now": "Gå med nu", "Join or log in while booking for member pricing.": "Bli medlem eller logga in när du bokar för medlemspriser.", + "Kayaking": "Kajakpaddling", "King bed": "King size-säng", "Language": "Språk", "Last name": "Efternamn", @@ -246,7 +251,9 @@ "Main menu": "Huvudmeny", "Manage preferences": "Hantera inställningar", "Map": "Karta", - "Map of {hotelName}": "Map of {hotelName}", + "Map of the city center": "Karta över stadskärnan", + "Map of the country": "Karta över landet", + "Map of {hotelName}": "Karta över {hotelName}", "Marketing city": "Marknadsföringsstad", "Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} gäst} other {{range} gäster}}", "Meetings & Conferences": "Möten & Konferenser", @@ -268,6 +275,7 @@ "Monday": "Måndag", "Month": "Månad", "Museum": "Museum", + "Museums": "Museer", "My communication preferences": "Mina kommunikationspreferenser", "My membership cards": "Mina medlemskort", "My pages": "Mina sidor", @@ -281,6 +289,7 @@ "Nearby companies": "Närliggande företag", "New password": "Nytt lösenord", "Next": "Nästa", + "Nightlife": "Nattliv", "Nights needed to level up": "Nätter som behövs för att gå upp i nivå", "No": "Nej", "No availability": "Ingen tillgänglighet", diff --git a/lib/graphql/Fragments/DestinationCityPage/Ref.graphql b/lib/graphql/Fragments/DestinationCityPage/Ref.graphql new file mode 100644 index 000000000..44330d446 --- /dev/null +++ b/lib/graphql/Fragments/DestinationCityPage/Ref.graphql @@ -0,0 +1,7 @@ +#import "../System.graphql" + +fragment DestinationCityPageRef on DestinationCityPage { + system { + ...System + } +} diff --git a/lib/graphql/Fragments/DestinationCountryPage/Ref.graphql b/lib/graphql/Fragments/DestinationCountryPage/Ref.graphql new file mode 100644 index 000000000..05804f48c --- /dev/null +++ b/lib/graphql/Fragments/DestinationCountryPage/Ref.graphql @@ -0,0 +1,7 @@ +#import "../System.graphql" + +fragment DestinationCountryPageRef on DestinationCountryPage { + system { + ...System + } +} diff --git a/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql b/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql index 111d72e0e..373064e17 100644 --- a/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql +++ b/lib/graphql/Query/DestinationCityPage/DestinationCityPage.graphql @@ -1,8 +1,69 @@ #import "../../Fragments/System.graphql" +#import "../../Fragments/PageLink/ContentPageLink.graphql" +#import "../../Fragments/PageLink/LoyaltyPageLink.graphql" +#import "../../Fragments/PageLink/AccountPageLink.graphql" +#import "../../Fragments/PageLink/CollectionPageLink.graphql" +#import "../../Fragments/PageLink/HotelPageLink.graphql" + +#import "../../Fragments/AccountPage/Ref.graphql" +#import "../../Fragments/ContentPage/Ref.graphql" +#import "../../Fragments/LoyaltyPage/Ref.graphql" +#import "../../Fragments/HotelPage/Ref.graphql" +#import "../../Fragments/CollectionPage/Ref.graphql" +#import "../../Fragments/DestinationCountryPage/Ref.graphql" query GetDestinationCityPage($locale: String!, $uid: String!) { destination_city_page(uid: $uid, locale: $locale) { title + destination_settings { + countryConnection { + edges { + node { + ... on DestinationCountryPage { + title + url + destination_settings { + country + } + } + } + } + } + city_denmark + city_finland + city_germany + city_norway + city_poland + city_sweden + } + heading + preamble + experiences { + destination_experiences + } + images { + image + } + has_sidepeek + sidepeek_button_text + sidepeek_content { + heading + content { + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageLink + ...ContentPageLink + ...CollectionPageLink + ...HotelPageLink + ...LoyaltyPageLink + } + } + } + json + } + } system { ...System created_at @@ -16,6 +77,32 @@ query GetDestinationCityPage($locale: String!, $uid: String!) { query GetDestinationCityPageRefs($locale: String!, $uid: String!) { destination_city_page(locale: $locale, uid: $uid) { + destination_settings { + countryConnection { + edges { + node { + __typename + ...DestinationCountryPageRef + } + } + } + } + sidepeek_content { + content { + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageRef + ...ContentPageRef + ...LoyaltyPageRef + ...HotelPageRef + ...CollectionPageRef + } + } + } + } + } system { ...System } diff --git a/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql b/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql index dd628e279..ecf7a5c48 100644 --- a/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql +++ b/lib/graphql/Query/DestinationCountryPage/DestinationCountryPage.graphql @@ -1,8 +1,50 @@ #import "../../Fragments/System.graphql" +#import "../../Fragments/PageLink/ContentPageLink.graphql" +#import "../../Fragments/PageLink/LoyaltyPageLink.graphql" +#import "../../Fragments/PageLink/AccountPageLink.graphql" +#import "../../Fragments/PageLink/CollectionPageLink.graphql" +#import "../../Fragments/PageLink/HotelPageLink.graphql" + +#import "../../Fragments/AccountPage/Ref.graphql" +#import "../../Fragments/ContentPage/Ref.graphql" +#import "../../Fragments/LoyaltyPage/Ref.graphql" +#import "../../Fragments/HotelPage/Ref.graphql" +#import "../../Fragments/CollectionPage/Ref.graphql" query GetDestinationCountryPage($locale: String!, $uid: String!) { destination_country_page(uid: $uid, locale: $locale) { title + destination_settings { + country + } + heading + preamble + experiences { + destination_experiences + } + images { + image + } + has_sidepeek + sidepeek_button_text + sidepeek_content { + heading + content { + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageLink + ...ContentPageLink + ...CollectionPageLink + ...HotelPageLink + ...LoyaltyPageLink + } + } + } + json + } + } system { ...System created_at @@ -16,6 +58,22 @@ query GetDestinationCountryPage($locale: String!, $uid: String!) { query GetDestinationCountryPageRefs($locale: String!, $uid: String!) { destination_country_page(locale: $locale, uid: $uid) { + sidepeek_content { + content { + embedded_itemsConnection { + edges { + node { + __typename + ...AccountPageRef + ...ContentPageRef + ...LoyaltyPageRef + ...HotelPageRef + ...CollectionPageRef + } + } + } + } + } system { ...System } diff --git a/server/routers/contentstack/destinationCityPage/output.ts b/server/routers/contentstack/destinationCityPage/output.ts index 54ec7d334..56b42102f 100644 --- a/server/routers/contentstack/destinationCityPage/output.ts +++ b/server/routers/contentstack/destinationCityPage/output.ts @@ -1,25 +1,180 @@ import { z } from "zod" +import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks" + +import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { systemSchema } from "../schemas/system" -export const destinationCityPageSchema = z.object({ - destination_city_page: z.object({ - title: z.string(), - system: systemSchema.merge( - z.object({ - created_at: z.string(), - updated_at: z.string(), - }) - ), - }), - trackingProps: z.object({ - url: z.string(), - }), -}) +import type { ImageVaultAsset } from "@/types/components/imageVault" +import { + TrackingChannelEnum, + type TrackingSDKPageData, +} from "@/types/components/tracking" +import { Country } from "@/types/enums/country" + +export const destinationCityPageSchema = z + .object({ + destination_city_page: z.object({ + title: z.string(), + destination_settings: z + .object({ + countryConnection: z + .object({ + edges: z.array( + z.object({ + node: z.object({ + destination_settings: z.object({ + country: z.nativeEnum(Country), + }), + }), + }) + ), + }) + .transform( + (countryConnection) => + countryConnection.edges[0].node.destination_settings.country + ), + city_denmark: z.string().optional().nullable(), + city_finland: z.string().optional().nullable(), + city_germany: z.string().optional().nullable(), + city_poland: z.string().optional().nullable(), + city_norway: z.string().optional().nullable(), + city_sweden: z.string().optional().nullable(), + }) + .transform( + ({ + countryConnection: country, + city_denmark, + city_finland, + city_germany, + city_norway, + city_poland, + city_sweden, + }) => { + switch (country) { + case Country.Denmark: + return { country, city: city_denmark } + case Country.Finland: + return { country, city: city_finland } + case Country.Germany: + return { country, city: city_germany } + case Country.Poland: + return { country, city: city_poland } + case Country.Norway: + return { country, city: city_norway } + case Country.Sweden: + return { country, city: city_sweden } + default: + throw new Error(`Invalid country: ${country}`) + } + } + ), + heading: z.string(), + preamble: z.string(), + experiences: z + .object({ + destination_experiences: z.array(z.string()), + }) + .transform(({ destination_experiences }) => destination_experiences), + images: z + .array(z.object({ image: tempImageVaultAssetSchema })) + .transform((images) => + images + .map((image) => image.image) + .filter((image): image is ImageVaultAsset => !!image) + ), + has_sidepeek: z.boolean().default(false), + sidepeek_button_text: z.string().default(""), + sidepeek_content: z.object({ + heading: z.string(), + content: z.object({ + json: z.any(), + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z + .discriminatedUnion("__typename", [ + pageLinks.accountPageSchema, + pageLinks.contentPageSchema, + pageLinks.hotelPageSchema, + pageLinks.loyaltyPageSchema, + pageLinks.collectionPageSchema, + ]) + .transform((data) => { + const link = pageLinks.transform(data) + if (link) { + return link + } + return data + }), + }) + ), + }), + }), + }), + system: systemSchema.merge( + z.object({ + created_at: z.string(), + updated_at: z.string(), + }) + ), + }), + trackingProps: z.object({ + url: z.string(), + }), + }) + .transform((data) => { + const destinationCityPage = data.destination_city_page + const system = destinationCityPage.system + const trackingUrl = data.trackingProps.url + + const tracking: TrackingSDKPageData = { + pageId: system.uid, + domainLanguage: system.locale, + publishDate: system.updated_at, + createDate: system.created_at, + channel: TrackingChannelEnum["destination-page"], + pageType: "staticcontentpage", + pageName: trackingUrl, + siteSections: trackingUrl, + siteVersion: "new-web", + } + + return { + destinationCityPage, + tracking, + } + }) /** REFS */ export const destinationCityPageRefsSchema = z.object({ destination_city_page: z.object({ + destination_settings: z.object({ + countryConnection: z.object({ + edges: z.array( + z.object({ + node: pageLinks.destinationCountryPageRefSchema, + }) + ), + }), + }), + sidepeek_content: z.object({ + content: z.object({ + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z.discriminatedUnion("__typename", [ + pageLinks.accountPageRefSchema, + pageLinks.contentPageRefSchema, + pageLinks.hotelPageRefSchema, + pageLinks.loyaltyPageRefSchema, + pageLinks.collectionPageRefSchema, + ]), + }) + ), + }), + }), + }), system: systemSchema, }), }) diff --git a/server/routers/contentstack/destinationCityPage/query.ts b/server/routers/contentstack/destinationCityPage/query.ts index de6f6014e..c0e500b94 100644 --- a/server/routers/contentstack/destinationCityPage/query.ts +++ b/server/routers/contentstack/destinationCityPage/query.ts @@ -20,11 +20,8 @@ import { getDestinationCityPageRefsSuccessCounter, getDestinationCityPageSuccessCounter, } from "./telemetry" +import { generatePageTags } from "./utils" -import { - TrackingChannelEnum, - type TrackingSDKPageData, -} from "@/types/components/tracking" import type { GetDestinationCityPageData, GetDestinationCityPageRefsSchema, @@ -91,6 +88,8 @@ export const destinationCityPageQueryRouter = router({ JSON.stringify({ query: { lang, uid } }) ) + const tags = generatePageTags(validatedRefsData.data, lang) + getDestinationCityPageCounter.add(1, { lang, uid: `${uid}` }) console.info( "contentstack.destinationCityPage start", @@ -107,7 +106,7 @@ export const destinationCityPageQueryRouter = router({ { cache: "force-cache", next: { - tags: [generateTag(lang, uid)], + tags, }, } ) @@ -129,22 +128,22 @@ export const destinationCityPageQueryRouter = router({ throw notFoundError } - const destinationCityPage = destinationCityPageSchema.safeParse( + const validatedDestinationCityPage = destinationCityPageSchema.safeParse( response.data ) - if (!destinationCityPage.success) { + if (!validatedDestinationCityPage.success) { getDestinationCityPageFailCounter.add(1, { lang, uid: `${uid}`, error_type: "validation_error", - error: JSON.stringify(destinationCityPage.error), + error: JSON.stringify(validatedDestinationCityPage.error), }) console.error( "contentstack.destinationCityPage validation error", JSON.stringify({ query: { lang, uid }, - error: destinationCityPage.error, + error: validatedDestinationCityPage.error, }) ) return null @@ -158,22 +157,6 @@ export const destinationCityPageQueryRouter = router({ }) ) - const system = destinationCityPage.data.destination_city_page.system - const tracking: TrackingSDKPageData = { - pageId: system.uid, - domainLanguage: lang, - publishDate: system.updated_at, - createDate: system.created_at, - channel: TrackingChannelEnum["destination-page"], - pageType: "staticcontentpage", - pageName: destinationCityPage.data.trackingProps.url, - siteSections: destinationCityPage.data.trackingProps.url, - siteVersion: "new-web", - } - - return { - destinationCityPage: destinationCityPage.data.destination_city_page, - tracking, - } + return validatedDestinationCityPage.data }), }) diff --git a/server/routers/contentstack/destinationCityPage/utils.ts b/server/routers/contentstack/destinationCityPage/utils.ts new file mode 100644 index 000000000..767a04170 --- /dev/null +++ b/server/routers/contentstack/destinationCityPage/utils.ts @@ -0,0 +1,37 @@ +import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" + +import type { System } from "@/types/requests/system" +import type { GetDestinationCityPageRefsSchema } from "@/types/trpc/routers/contentstack/destinationCityPage" +import type { Lang } from "@/constants/languages" + +export function generatePageTags( + validatedData: GetDestinationCityPageRefsSchema, + lang: Lang +): string[] { + const connections = getConnections(validatedData) + return [ + generateTagsFromSystem(lang, connections), + generateTag(lang, validatedData.destination_city_page.system.uid), + ].flat() +} + +export function getConnections({ + destination_city_page, +}: GetDestinationCityPageRefsSchema) { + const connections: System["system"][] = [destination_city_page.system] + + connections.push( + destination_city_page.destination_settings.countryConnection.edges[0].node + .system + ) + + if (destination_city_page.sidepeek_content) { + destination_city_page.sidepeek_content.content.embedded_itemsConnection.edges.forEach( + ({ node }) => { + connections.push(node.system) + } + ) + } + + return connections +} diff --git a/server/routers/contentstack/destinationCountryPage/output.ts b/server/routers/contentstack/destinationCountryPage/output.ts index 07d01ad7d..04261d78a 100644 --- a/server/routers/contentstack/destinationCountryPage/output.ts +++ b/server/routers/contentstack/destinationCountryPage/output.ts @@ -1,25 +1,120 @@ import { z } from "zod" +import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks" + +import { tempImageVaultAssetSchema } from "../schemas/imageVault" import { systemSchema } from "../schemas/system" -export const destinationCountryPageSchema = z.object({ - destination_country_page: z.object({ - title: z.string(), - system: systemSchema.merge( - z.object({ - created_at: z.string(), - updated_at: z.string(), - }) - ), - }), - trackingProps: z.object({ - url: z.string(), - }), -}) +import type { ImageVaultAsset } from "@/types/components/imageVault" +import { + TrackingChannelEnum, + type TrackingSDKPageData, +} from "@/types/components/tracking" +import { Country } from "@/types/enums/country" + +export const destinationCountryPageSchema = z + .object({ + destination_country_page: z.object({ + title: z.string(), + destination_settings: z.object({ + country: z.nativeEnum(Country), + }), + heading: z.string(), + preamble: z.string(), + experiences: z + .object({ + destination_experiences: z.array(z.string()), + }) + .transform(({ destination_experiences }) => destination_experiences), + images: z + .array(z.object({ image: tempImageVaultAssetSchema })) + .transform((images) => + images + .map((image) => image.image) + .filter((image): image is ImageVaultAsset => !!image) + ), + has_sidepeek: z.boolean().default(false), + sidepeek_button_text: z.string().default(""), + sidepeek_content: z.object({ + heading: z.string(), + content: z.object({ + json: z.any(), + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z + .discriminatedUnion("__typename", [ + pageLinks.accountPageSchema, + pageLinks.contentPageSchema, + pageLinks.hotelPageSchema, + pageLinks.loyaltyPageSchema, + pageLinks.collectionPageSchema, + ]) + .transform((data) => { + const link = pageLinks.transform(data) + if (link) { + return link + } + return data + }), + }) + ), + }), + }), + }), + system: systemSchema.merge( + z.object({ + created_at: z.string(), + updated_at: z.string(), + }) + ), + }), + trackingProps: z.object({ + url: z.string(), + }), + }) + .transform((data) => { + const countryPageData = data.destination_country_page + const system = countryPageData.system + const trackingUrl = data.trackingProps.url + const tracking: TrackingSDKPageData = { + pageId: system.uid, + domainLanguage: system.locale, + publishDate: system.updated_at, + createDate: system.created_at, + channel: TrackingChannelEnum["destination-page"], + pageType: "staticcontentpage", + pageName: trackingUrl, + siteSections: trackingUrl, + siteVersion: "new-web", + } + + return { + destinationCountryPage: countryPageData, + tracking, + } + }) /** REFS */ export const destinationCountryPageRefsSchema = z.object({ destination_country_page: z.object({ + sidepeek_content: z.object({ + content: z.object({ + embedded_itemsConnection: z.object({ + edges: z.array( + z.object({ + node: z.discriminatedUnion("__typename", [ + pageLinks.accountPageRefSchema, + pageLinks.contentPageRefSchema, + pageLinks.hotelPageRefSchema, + pageLinks.loyaltyPageRefSchema, + pageLinks.collectionPageRefSchema, + ]), + }) + ), + }), + }), + }), system: systemSchema, }), }) diff --git a/server/routers/contentstack/destinationCountryPage/query.ts b/server/routers/contentstack/destinationCountryPage/query.ts index 48ede62e9..8893e8f95 100644 --- a/server/routers/contentstack/destinationCountryPage/query.ts +++ b/server/routers/contentstack/destinationCountryPage/query.ts @@ -20,11 +20,8 @@ import { getDestinationCountryPageRefsSuccessCounter, getDestinationCountryPageSuccessCounter, } from "./telemetry" +import { generatePageTags } from "./utils" -import { - TrackingChannelEnum, - type TrackingSDKPageData, -} from "@/types/components/tracking" import type { GetDestinationCountryPageData, GetDestinationCountryPageRefsSchema, @@ -91,6 +88,8 @@ export const destinationCountryPageQueryRouter = router({ JSON.stringify({ query: { lang, uid } }) ) + const tags = generatePageTags(validatedRefsData.data, lang) + getDestinationCountryPageCounter.add(1, { lang, uid: `${uid}` }) console.info( "contentstack.destinationCountryPage start", @@ -107,7 +106,7 @@ export const destinationCountryPageQueryRouter = router({ { cache: "force-cache", next: { - tags: [generateTag(lang, uid)], + tags, }, } ) @@ -158,23 +157,6 @@ export const destinationCountryPageQueryRouter = router({ }) ) - const system = destinationCountryPage.data.destination_country_page.system - const tracking: TrackingSDKPageData = { - pageId: system.uid, - domainLanguage: lang, - publishDate: system.updated_at, - createDate: system.created_at, - channel: TrackingChannelEnum["destination-page"], - pageType: "staticcontentpage", - pageName: destinationCountryPage.data.trackingProps.url, - siteSections: destinationCountryPage.data.trackingProps.url, - siteVersion: "new-web", - } - - return { - destinationCountryPage: - destinationCountryPage.data.destination_country_page, - tracking, - } + return destinationCountryPage.data }), }) diff --git a/server/routers/contentstack/destinationCountryPage/utils.ts b/server/routers/contentstack/destinationCountryPage/utils.ts new file mode 100644 index 000000000..416a44933 --- /dev/null +++ b/server/routers/contentstack/destinationCountryPage/utils.ts @@ -0,0 +1,32 @@ +import { generateTag, generateTagsFromSystem } from "@/utils/generateTag" + +import type { System } from "@/types/requests/system" +import type { GetDestinationCountryPageRefsSchema } from "@/types/trpc/routers/contentstack/destinationCountryPage" +import type { Lang } from "@/constants/languages" + +export function generatePageTags( + validatedData: GetDestinationCountryPageRefsSchema, + lang: Lang +): string[] { + const connections = getConnections(validatedData) + return [ + generateTagsFromSystem(lang, connections), + generateTag(lang, validatedData.destination_country_page.system.uid), + ].flat() +} + +export function getConnections({ + destination_country_page, +}: GetDestinationCountryPageRefsSchema) { + const connections: System["system"][] = [destination_country_page.system] + + if (destination_country_page.sidepeek_content) { + destination_country_page.sidepeek_content.content.embedded_itemsConnection.edges.forEach( + ({ node }) => { + connections.push(node.system) + } + ) + } + + return connections +} diff --git a/server/routers/contentstack/schemas/pageLinks.ts b/server/routers/contentstack/schemas/pageLinks.ts index b23c85d96..2cab74ebe 100644 --- a/server/routers/contentstack/schemas/pageLinks.ts +++ b/server/routers/contentstack/schemas/pageLinks.ts @@ -52,6 +52,33 @@ export const contentPageRefSchema = z.object({ system: systemSchema, }) +export const destinationCityPageSchema = z + .object({ + __typename: z.literal(ContentEnum.blocks.DestinationCityPage), + }) + .merge(pageLinkSchema) + +export const destinationCityPageRefSchema = z.object({ + __typename: z.literal(ContentEnum.blocks.DestinationCityPage), + system: systemSchema, +}) + +export const destinationCountryPageSchema = z + .object({ + __typename: z.literal(ContentEnum.blocks.DestinationCountryPage), + }) + .merge(pageLinkSchema) + +export const destinationCountryPageRefSchema = z.object({ + __typename: z.literal(ContentEnum.blocks.DestinationCountryPage), + system: systemSchema, +}) + +export const destinationOverviewPageRefSchema = z.object({ + __typename: z.literal(ContentEnum.blocks.DestinationOverviewPage), + system: systemSchema, +}) + export const hotelPageSchema = z .object({ __typename: z.literal(ContentEnum.blocks.HotelPage), diff --git a/stores/sticky-position.ts b/stores/sticky-position.ts index 93328b9e9..3e1f2cd71 100644 --- a/stores/sticky-position.ts +++ b/stores/sticky-position.ts @@ -5,6 +5,7 @@ export enum StickyElementNameEnum { BOOKING_WIDGET = "BOOKING_WIDGET", HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION", HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP", + DESTINATION_SIDEBAR = "DESTINATION_SIDEBAR", } export interface StickyElement { @@ -34,6 +35,8 @@ const priorityMap: Record = { [StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3, [StickyElementNameEnum.HOTEL_STATIC_MAP]: 3, + + [StickyElementNameEnum.DESTINATION_SIDEBAR]: 3, } const useStickyPositionStore = create((set, get) => ({ diff --git a/types/enums/content.ts b/types/enums/content.ts index cc2a84e15..fbf69cae3 100644 --- a/types/enums/content.ts +++ b/types/enums/content.ts @@ -3,6 +3,9 @@ export namespace ContentEnum { AccountPage = "AccountPage", CollectionPage = "CollectionPage", ContentPage = "ContentPage", + DestinationCityPage = "DestinationCityPage", + DestinationCountryPage = "DestinationCountryPage", + DestinationOverviewPage = "DestinationOverviewPage", HotelPage = "HotelPage", ImageContainer = "ImageContainer", LoyaltyPage = "LoyaltyPage", diff --git a/types/trpc/routers/contentstack/destinationCityPage.ts b/types/trpc/routers/contentstack/destinationCityPage.ts index 352a18a24..e3fad58ab 100644 --- a/types/trpc/routers/contentstack/destinationCityPage.ts +++ b/types/trpc/routers/contentstack/destinationCityPage.ts @@ -7,8 +7,9 @@ import type { export interface GetDestinationCityPageData extends z.input {} -export interface DestinationCityPage +interface DestinationCityPage extends z.output {} +export type DestinationCityPageData = DestinationCityPage["destinationCityPage"] export interface GetDestinationCityPageRefsSchema extends z.input {} diff --git a/types/trpc/routers/contentstack/destinationCountryPage.ts b/types/trpc/routers/contentstack/destinationCountryPage.ts index c6b5b1688..53ad26dd1 100644 --- a/types/trpc/routers/contentstack/destinationCountryPage.ts +++ b/types/trpc/routers/contentstack/destinationCountryPage.ts @@ -7,8 +7,10 @@ import type { export interface GetDestinationCountryPageData extends z.input {} -export interface DestinationCountryPage +interface DestinationCountryPage extends z.output {} +export type DestinationCountryPageData = + DestinationCountryPage["destinationCountryPage"] export interface GetDestinationCountryPageRefsSchema extends z.input {}