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
This commit is contained in:
@@ -7,12 +7,18 @@ import { setLang } from "@/i18n/serverContext"
|
|||||||
import type { ContentTypeParams, LangParams, PageArgs } from "@/types/params"
|
import type { ContentTypeParams, LangParams, PageArgs } from "@/types/params"
|
||||||
import { PageContentTypeEnum } from "@/types/requests/contentType"
|
import { PageContentTypeEnum } from "@/types/requests/contentType"
|
||||||
|
|
||||||
|
const IGNORED_CONTENT_TYPES = [
|
||||||
|
PageContentTypeEnum.hotelPage,
|
||||||
|
PageContentTypeEnum.destinationCityPage,
|
||||||
|
PageContentTypeEnum.destinationCountryPage,
|
||||||
|
]
|
||||||
|
|
||||||
export default function PageBreadcrumbs({
|
export default function PageBreadcrumbs({
|
||||||
params,
|
params,
|
||||||
}: PageArgs<LangParams & ContentTypeParams>) {
|
}: PageArgs<LangParams & ContentTypeParams>) {
|
||||||
setLang(params.lang)
|
setLang(params.lang)
|
||||||
|
|
||||||
if (params.contentType === PageContentTypeEnum.hotelPage) {
|
if (IGNORED_CONTENT_TYPES.includes(params.contentType)) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import { isSignupPage } from "@/constants/routes/signup"
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
import { getHotelPage } from "@/lib/trpc/memoizedRequests"
|
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 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 HotelPage from "@/components/ContentType/HotelPage"
|
||||||
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
|
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
|
||||||
import StartPage from "@/components/ContentType/StartPage"
|
import StartPage from "@/components/ContentType/StartPage"
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
.pageContainer {
|
|
||||||
display: grid;
|
|
||||||
max-width: var(--max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.pageContainer {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<>
|
|
||||||
<div className={styles.pageContainer}>
|
|
||||||
<h1>Destination City Page</h1>
|
|
||||||
</div>
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<TrackingSDK pageData={tracking} />
|
|
||||||
</Suspense>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
.pageContainer {
|
|
||||||
display: grid;
|
|
||||||
max-width: var(--max-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.pageContainer {
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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 (
|
|
||||||
<>
|
|
||||||
<div className={styles.pageContainer}>
|
|
||||||
<h1>Destination Country Page</h1>
|
|
||||||
</div>
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
<TrackingSDK pageData={tracking} />
|
|
||||||
</Suspense>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<div className={styles.pageContainer}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||||
|
<Breadcrumbs variant={PageContentTypeEnum.destinationCityPage} />
|
||||||
|
</Suspense>
|
||||||
|
<TopImages images={images} />
|
||||||
|
</header>
|
||||||
|
<main className={styles.mainSection}>
|
||||||
|
{/* TODO: Add hotel listing by cityIdentifier */}
|
||||||
|
{">>>> MAIN CONTENT <<<<"}
|
||||||
|
</main>
|
||||||
|
<aside className={styles.sidebar}>
|
||||||
|
<SidebarContentWrapper>
|
||||||
|
<Title level="h2">{heading}</Title>
|
||||||
|
<Body color="uiTextMediumContrast">{preamble}</Body>
|
||||||
|
<ul className={styles.experienceList}>
|
||||||
|
{experiencesList.map(({ Icon, name }) => (
|
||||||
|
<li key={name}>
|
||||||
|
<Chip variant="tag">
|
||||||
|
<Icon width={20} height={20} />
|
||||||
|
{name}
|
||||||
|
</Chip>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{has_sidepeek && (
|
||||||
|
<DestinationPageSidePeek
|
||||||
|
buttonText={sidepeek_button_text}
|
||||||
|
sidePeekContent={sidepeek_content}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{destination_settings.city && (
|
||||||
|
<div className={styles.mapWrapper}>
|
||||||
|
<StaticMap
|
||||||
|
city={destination_settings.city}
|
||||||
|
country={destination_settings.country}
|
||||||
|
width={320}
|
||||||
|
height={200}
|
||||||
|
zoomLevel={10}
|
||||||
|
altText={intl.formatMessage({ id: "Map of the city center" })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SidebarContentWrapper>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<TrackingSDK pageData={tracking} />
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<div className={styles.pageContainer}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<Suspense fallback={<BreadcrumbsSkeleton />}>
|
||||||
|
<Breadcrumbs variant={PageContentTypeEnum.destinationCityPage} />
|
||||||
|
</Suspense>
|
||||||
|
<TopImages images={images} />
|
||||||
|
</header>
|
||||||
|
<main className={styles.mainSection}>
|
||||||
|
{/* TODO: Add city listing by cityIdentifier */}
|
||||||
|
{">>>> MAIN CONTENT <<<<"}
|
||||||
|
</main>
|
||||||
|
<aside className={styles.sidebar}>
|
||||||
|
<SidebarContentWrapper>
|
||||||
|
<Title level="h2">{heading}</Title>
|
||||||
|
<Body color="uiTextMediumContrast">{preamble}</Body>
|
||||||
|
<ul className={styles.experienceList}>
|
||||||
|
{experiencesList.map(({ Icon, name }) => (
|
||||||
|
<li key={name}>
|
||||||
|
<Chip variant="tag">
|
||||||
|
<Icon width={20} height={20} />
|
||||||
|
{name}
|
||||||
|
</Chip>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{has_sidepeek && (
|
||||||
|
<DestinationPageSidePeek
|
||||||
|
buttonText={sidepeek_button_text}
|
||||||
|
sidePeekContent={sidepeek_content}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={styles.mapWrapper}>
|
||||||
|
<StaticMap
|
||||||
|
country={destination_settings.country}
|
||||||
|
width={320}
|
||||||
|
height={200}
|
||||||
|
zoomLevel={3}
|
||||||
|
altText={intl.formatMessage({ id: "Map of the country" })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SidebarContentWrapper>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
<Suspense fallback={null}>
|
||||||
|
<TrackingSDK pageData={tracking} />
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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<HTMLDivElement>(null)
|
||||||
|
useStickyPosition({
|
||||||
|
ref: sidebarRef,
|
||||||
|
name: StickyElementNameEnum.DESTINATION_SIDEBAR,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={sidebarRef} className={styles.sidebarContent}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
components/ContentType/DestinationPage/Sidepeek/index.tsx
Normal file
52
components/ContentType/DestinationPage/Sidepeek/index.tsx
Normal file
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
onPress={() => setSidePeekIsOpen(true)}
|
||||||
|
theme="base"
|
||||||
|
variant="icon"
|
||||||
|
intent="text"
|
||||||
|
size="small"
|
||||||
|
wrapping
|
||||||
|
>
|
||||||
|
{buttonText}
|
||||||
|
<ChevronRightSmallIcon />
|
||||||
|
</Button>
|
||||||
|
<SidePeek
|
||||||
|
title={heading}
|
||||||
|
isOpen={sidePeekIsOpen}
|
||||||
|
handleClose={() => setSidePeekIsOpen(false)}
|
||||||
|
>
|
||||||
|
<JsonToHtml
|
||||||
|
nodes={content.json.children}
|
||||||
|
embeds={content.embedded_itemsConnection.edges}
|
||||||
|
/>
|
||||||
|
</SidePeek>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.ctaContainer {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Spacing-x2);
|
||||||
|
}
|
||||||
34
components/ContentType/DestinationPage/TopImages/index.tsx
Normal file
34
components/ContentType/DestinationPage/TopImages/index.tsx
Normal file
@@ -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 (
|
||||||
|
<div className={styles.imageWrapper}>
|
||||||
|
{images.slice(0, 3).map((image, index) => (
|
||||||
|
<Image
|
||||||
|
key={image.url}
|
||||||
|
src={image.url}
|
||||||
|
alt={image.meta.alt || image.meta.caption || ""}
|
||||||
|
width={index === 0 ? maxWidth : maxWidth / 3}
|
||||||
|
height={Math.ceil(
|
||||||
|
(index === 0 ? maxWidth : maxWidth / 3) /
|
||||||
|
image.dimensions.aspectRatio
|
||||||
|
)}
|
||||||
|
focalPoint={image.focalPoint}
|
||||||
|
className={styles.image}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
59
components/ContentType/DestinationPage/utils.ts
Normal file
59
components/ContentType/DestinationPage/utils.ts
Normal file
@@ -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<IconProps>; 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 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ export default function HotelCardDialogImage({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className={styles.tripAdvisor}>
|
<div className={styles.tripAdvisor}>
|
||||||
<Chip intent="secondary" className={styles.tripAdvisor}>
|
<Chip className={styles.tripAdvisor}>
|
||||||
<TripAdvisorIcon color="burgundy" />
|
<TripAdvisorIcon color="burgundy" />
|
||||||
{ratings}
|
{ratings}
|
||||||
</Chip>
|
</Chip>
|
||||||
|
|||||||
@@ -14,14 +14,16 @@ function getCenter({
|
|||||||
city?: string
|
city?: string
|
||||||
country?: string
|
country?: string
|
||||||
}): string | undefined {
|
}): string | undefined {
|
||||||
switch (true) {
|
if (coordinates) {
|
||||||
case !!coordinates:
|
return `${coordinates.lat},${coordinates.lng}`
|
||||||
return `${coordinates.lat},${coordinates.lng}`
|
|
||||||
case !!country:
|
|
||||||
return `${city}, ${country}`
|
|
||||||
default:
|
|
||||||
return city
|
|
||||||
}
|
}
|
||||||
|
if (city && country) {
|
||||||
|
return `${city}, ${country}`
|
||||||
|
}
|
||||||
|
if (country) {
|
||||||
|
return country
|
||||||
|
}
|
||||||
|
return city
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StaticMap({
|
export default function StaticMap({
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hotelHeaderWidth.breadcrumbs {
|
.headerWidth.breadcrumbs {
|
||||||
max-width: min(var(--max-width-page), calc(100% - var(--max-width-spacing)));
|
max-width: min(var(--max-width-page), calc(100% - var(--max-width-spacing)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ export const breadcrumbsVariants = cva(styles.breadcrumbs, {
|
|||||||
[PageContentTypeEnum.accountPage]: styles.fullWidth,
|
[PageContentTypeEnum.accountPage]: styles.fullWidth,
|
||||||
[PageContentTypeEnum.contentPage]: styles.contentWidth,
|
[PageContentTypeEnum.contentPage]: styles.contentWidth,
|
||||||
[PageContentTypeEnum.collectionPage]: styles.contentWidth,
|
[PageContentTypeEnum.collectionPage]: styles.contentWidth,
|
||||||
[PageContentTypeEnum.destinationOverviewPage]: styles.contentWidth,
|
[PageContentTypeEnum.destinationOverviewPage]: styles.fullWidth,
|
||||||
[PageContentTypeEnum.destinationCountryPage]: styles.contentWidth,
|
[PageContentTypeEnum.destinationCountryPage]: styles.fullWidth,
|
||||||
[PageContentTypeEnum.destinationCityPage]: styles.contentWidth,
|
[PageContentTypeEnum.destinationCityPage]: styles.fullWidth,
|
||||||
[PageContentTypeEnum.hotelPage]: styles.hotelHeaderWidth,
|
[PageContentTypeEnum.hotelPage]: styles.headerWidth,
|
||||||
[PageContentTypeEnum.loyaltyPage]: styles.fullWidth,
|
[PageContentTypeEnum.loyaltyPage]: styles.fullWidth,
|
||||||
[PageContentTypeEnum.startPage]: styles.contentWidth,
|
[PageContentTypeEnum.startPage]: styles.contentWidth,
|
||||||
default: styles.fullWidth,
|
default: styles.fullWidth,
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
div.chip {
|
div.chip {
|
||||||
align-items: center;
|
--chip-text-color: var(--Base-Text-High-contrast);
|
||||||
border-radius: var(--Corner-radius-xLarge);
|
--chip-background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
height: 22px;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
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 {
|
.chip *,
|
||||||
background-color: var(--Scandic-Red-90);
|
.chip svg * {
|
||||||
color: var(--Primary-Dark-On-Surface-Accent);
|
fill: var(--chip-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary {
|
.chip.burgundy {
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
--chip-text-color: var(--Primary-Dark-On-Surface-Text);
|
||||||
color: var(--Primary-Light-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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,9 @@ import { chipVariants } from "./variants"
|
|||||||
|
|
||||||
import type { ChipProps } from "./chip"
|
import type { ChipProps } from "./chip"
|
||||||
|
|
||||||
export default function Chip({
|
export default function Chip({ children, className, variant }: ChipProps) {
|
||||||
children,
|
|
||||||
className,
|
|
||||||
intent,
|
|
||||||
variant,
|
|
||||||
}: ChipProps) {
|
|
||||||
const classNames = chipVariants({
|
const classNames = chipVariants({
|
||||||
className,
|
className,
|
||||||
intent,
|
|
||||||
variant,
|
variant,
|
||||||
})
|
})
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -4,16 +4,14 @@ import styles from "./chip.module.css"
|
|||||||
|
|
||||||
export const chipVariants = cva(styles.chip, {
|
export const chipVariants = cva(styles.chip, {
|
||||||
variants: {
|
variants: {
|
||||||
intent: {
|
|
||||||
primary: styles.primary,
|
|
||||||
secondary: styles.secondary,
|
|
||||||
},
|
|
||||||
variant: {
|
variant: {
|
||||||
default: styles.default,
|
default: styles.default,
|
||||||
|
burgundy: styles.burgundy,
|
||||||
|
transparent: styles.transparent,
|
||||||
|
tag: styles.tag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
intent: "primary",
|
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"Bed": "Seng type",
|
"Bed": "Seng type",
|
||||||
"Bed options": "Sengemuligheder",
|
"Bed options": "Sengemuligheder",
|
||||||
"Bed type": "Seng type",
|
"Bed type": "Seng type",
|
||||||
|
"Bike friendly": "Cykelvenlig",
|
||||||
"Birth date": "Fødselsdato",
|
"Birth date": "Fødselsdato",
|
||||||
"Book": "Book",
|
"Book": "Book",
|
||||||
"Book a table online": "Book et bord online",
|
"Book a table online": "Book et bord online",
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
"Choose room": "Vælg rum",
|
"Choose room": "Vælg rum",
|
||||||
"Cities": "Byer",
|
"Cities": "Byer",
|
||||||
"City": "By",
|
"City": "By",
|
||||||
|
"City pulse": "Byens puls",
|
||||||
"City/State": "By/Stat",
|
"City/State": "By/Stat",
|
||||||
"Clear all filters": "Ryd alle filtre",
|
"Clear all filters": "Ryd alle filtre",
|
||||||
"Clear searches": "Ryd søgninger",
|
"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 delete credit card, please try again later.": "Kunne ikke slette kreditkort. Prøv venligst igen senere.",
|
||||||
"Failed to verify membership": "Medlemskab ikke verificeret",
|
"Failed to verify membership": "Medlemskab ikke verificeret",
|
||||||
"Fair": "Messe",
|
"Fair": "Messe",
|
||||||
|
"Family friendly": "Familievenlig",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
"Filter and sort": "Filtrer og sorter",
|
"Filter and sort": "Filtrer og sorter",
|
||||||
"Filter by": "Filtrer efter",
|
"Filter by": "Filtrer efter",
|
||||||
@@ -191,6 +194,7 @@
|
|||||||
"Hi {firstName}!": "Hei {firstName}!",
|
"Hi {firstName}!": "Hei {firstName}!",
|
||||||
"High floor": "Højt niveau",
|
"High floor": "Højt niveau",
|
||||||
"Highest level": "Højeste niveau",
|
"Highest level": "Højeste niveau",
|
||||||
|
"Hiking": "Vandring",
|
||||||
"Home": "Hjem",
|
"Home": "Hjem",
|
||||||
"Hospital": "Hospital",
|
"Hospital": "Hospital",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
@@ -219,6 +223,7 @@
|
|||||||
"Join at no cost": "Tilmeld dig uden omkostninger",
|
"Join at no cost": "Tilmeld dig uden omkostninger",
|
||||||
"Join now": "Tilmeld dig nu",
|
"Join now": "Tilmeld dig nu",
|
||||||
"Join or log in while booking for member pricing.": "Tilmeld dig eller log ind under booking for medlemspris.",
|
"Join or log in while booking for member pricing.": "Tilmeld dig eller log ind under booking for medlemspris.",
|
||||||
|
"Kayaking": "Kajakroning",
|
||||||
"King bed": "Kingsize-seng",
|
"King bed": "Kingsize-seng",
|
||||||
"Language": "Sprog",
|
"Language": "Sprog",
|
||||||
"Last name": "Efternavn",
|
"Last name": "Efternavn",
|
||||||
@@ -247,6 +252,8 @@
|
|||||||
"Main menu": "Hovedmenu",
|
"Main menu": "Hovedmenu",
|
||||||
"Manage preferences": "Administrer præferencer",
|
"Manage preferences": "Administrer præferencer",
|
||||||
"Map": "Kort",
|
"Map": "Kort",
|
||||||
|
"Map of the city center": "Kort over byens centrum",
|
||||||
|
"Map of the country": "Kort over landet",
|
||||||
"Map of {hotelName}": "Map of {hotelName}",
|
"Map of {hotelName}": "Map of {hotelName}",
|
||||||
"Marketing city": "Marketing by",
|
"Marketing city": "Marketing by",
|
||||||
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Maks {max, plural, one {{range} gæst} other {{range} gæster}}",
|
"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",
|
"Monday": "Mandag",
|
||||||
"Month": "Måned",
|
"Month": "Måned",
|
||||||
"Museum": "Museum",
|
"Museum": "Museum",
|
||||||
|
"Museums": "Museer",
|
||||||
"My communication preferences": "Mine kommunikationspræferencer",
|
"My communication preferences": "Mine kommunikationspræferencer",
|
||||||
"My membership cards": "Mine medlemskort",
|
"My membership cards": "Mine medlemskort",
|
||||||
"My pages": "Mine sider",
|
"My pages": "Mine sider",
|
||||||
@@ -282,6 +290,7 @@
|
|||||||
"Nearby companies": "Nærliggende virksomheder",
|
"Nearby companies": "Nærliggende virksomheder",
|
||||||
"New password": "Nyt kodeord",
|
"New password": "Nyt kodeord",
|
||||||
"Next": "Næste",
|
"Next": "Næste",
|
||||||
|
"Nightlife": "Natteliv",
|
||||||
"Nights needed to level up": "Nætter nødvendige for at komme i niveau",
|
"Nights needed to level up": "Nætter nødvendige for at komme i niveau",
|
||||||
"No": "Nej",
|
"No": "Nej",
|
||||||
"No availability": "Ingen tilgængelighed",
|
"No availability": "Ingen tilgængelighed",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"Bed": "Bettentyp",
|
"Bed": "Bettentyp",
|
||||||
"Bed options": "Bettoptionen",
|
"Bed options": "Bettoptionen",
|
||||||
"Bed type": "Bettentyp",
|
"Bed type": "Bettentyp",
|
||||||
|
"Bike friendly": "Fahrradfreundlich",
|
||||||
"Birth date": "Geburtsdatum",
|
"Birth date": "Geburtsdatum",
|
||||||
"Book": "Buchen",
|
"Book": "Buchen",
|
||||||
"Book a table online": "Tisch online buchen",
|
"Book a table online": "Tisch online buchen",
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"Choose room": "Zimmer wählen",
|
"Choose room": "Zimmer wählen",
|
||||||
"Cities": "Städte",
|
"Cities": "Städte",
|
||||||
"City": "Stadt",
|
"City": "Stadt",
|
||||||
|
"City pulse": "Stadtpuls",
|
||||||
"City/State": "Stadt/Zustand",
|
"City/State": "Stadt/Zustand",
|
||||||
"Clear all filters": "Alle Filter löschen",
|
"Clear all filters": "Alle Filter löschen",
|
||||||
"Clear searches": "Suche 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 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",
|
"Failed to verify membership": "Medlemskab nicht verifiziert",
|
||||||
"Fair": "Messe",
|
"Fair": "Messe",
|
||||||
|
"Family friendly": "Familienfreundlich",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
"Filter and sort": "Filtern und sortieren",
|
"Filter and sort": "Filtern und sortieren",
|
||||||
"Filter by": "Filtern nach",
|
"Filter by": "Filtern nach",
|
||||||
@@ -190,6 +193,7 @@
|
|||||||
"Hi {firstName}!": "Hallo {firstName}!",
|
"Hi {firstName}!": "Hallo {firstName}!",
|
||||||
"High floor": "Hohes Level",
|
"High floor": "Hohes Level",
|
||||||
"Highest level": "Höchstes Level",
|
"Highest level": "Höchstes Level",
|
||||||
|
"Hiking": "Wandern",
|
||||||
"Home": "Heim",
|
"Home": "Heim",
|
||||||
"Hospital": "Krankenhaus",
|
"Hospital": "Krankenhaus",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
@@ -218,6 +222,7 @@
|
|||||||
"Join at no cost": "Kostenlos beitreten",
|
"Join at no cost": "Kostenlos beitreten",
|
||||||
"Join now": "Mitglied werden",
|
"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.",
|
"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",
|
"King bed": "Kingsize-Bett",
|
||||||
"Language": "Sprache",
|
"Language": "Sprache",
|
||||||
"Last name": "Nachname",
|
"Last name": "Nachname",
|
||||||
@@ -246,6 +251,8 @@
|
|||||||
"Main menu": "Hauptmenü",
|
"Main menu": "Hauptmenü",
|
||||||
"Manage preferences": "Verwalten von Voreinstellungen",
|
"Manage preferences": "Verwalten von Voreinstellungen",
|
||||||
"Map": "Karte",
|
"Map": "Karte",
|
||||||
|
"Map of the city center": "Karte des Stadtzentrums",
|
||||||
|
"Map of the country": "Karte des Landes",
|
||||||
"Map of {hotelName}": "Map of {hotelName}",
|
"Map of {hotelName}": "Map of {hotelName}",
|
||||||
"Marketing city": "Marketingstadt",
|
"Marketing city": "Marketingstadt",
|
||||||
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} gast} other {{range} gäste}}",
|
"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",
|
"Monday": "Montag",
|
||||||
"Month": "Monat",
|
"Month": "Monat",
|
||||||
"Museum": "Museum",
|
"Museum": "Museum",
|
||||||
|
"Museums": "Museen",
|
||||||
"My communication preferences": "Meine Kommunikationseinstellungen",
|
"My communication preferences": "Meine Kommunikationseinstellungen",
|
||||||
"My membership cards": "Meine Mitgliedskarten",
|
"My membership cards": "Meine Mitgliedskarten",
|
||||||
"My pages": "Meine Seiten",
|
"My pages": "Meine Seiten",
|
||||||
@@ -280,6 +288,7 @@
|
|||||||
"Nearby companies": "Nahe gelegene Unternehmen",
|
"Nearby companies": "Nahe gelegene Unternehmen",
|
||||||
"New password": "Neues Kennwort",
|
"New password": "Neues Kennwort",
|
||||||
"Next": "Nächste",
|
"Next": "Nächste",
|
||||||
|
"Nightlife": "Nachtleben",
|
||||||
"Nights needed to level up": "Nächte, die zum Levelaufstieg benötigt werden",
|
"Nights needed to level up": "Nächte, die zum Levelaufstieg benötigt werden",
|
||||||
"No": "Nein",
|
"No": "Nein",
|
||||||
"No availability": "Keine Verfügbarkeit",
|
"No availability": "Keine Verfügbarkeit",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"Bed": "Bed",
|
"Bed": "Bed",
|
||||||
"Bed options": "Bed options",
|
"Bed options": "Bed options",
|
||||||
"Bed type": "Bed type",
|
"Bed type": "Bed type",
|
||||||
|
"Bike friendly": "Bike friendly",
|
||||||
"Birth date": "Birth date",
|
"Birth date": "Birth date",
|
||||||
"Book": "Book",
|
"Book": "Book",
|
||||||
"Book a table online": "Book a table online",
|
"Book a table online": "Book a table online",
|
||||||
@@ -98,6 +99,7 @@
|
|||||||
"Choose room": "Choose room",
|
"Choose room": "Choose room",
|
||||||
"Cities": "Cities",
|
"Cities": "Cities",
|
||||||
"City": "City",
|
"City": "City",
|
||||||
|
"City pulse": "City pulse",
|
||||||
"City/State": "City/State",
|
"City/State": "City/State",
|
||||||
"Clear all filters": "Clear all filters",
|
"Clear all filters": "Clear all filters",
|
||||||
"Clear searches": "Clear searches",
|
"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 delete credit card, please try again later.": "Failed to delete credit card, please try again later.",
|
||||||
"Failed to verify membership": "Failed to verify membership",
|
"Failed to verify membership": "Failed to verify membership",
|
||||||
"Fair": "Fair",
|
"Fair": "Fair",
|
||||||
|
"Family friendly": "Family friendly",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
"Filter and sort": "Filter and sort",
|
"Filter and sort": "Filter and sort",
|
||||||
"Filter by": "Filter by",
|
"Filter by": "Filter by",
|
||||||
@@ -207,6 +210,7 @@
|
|||||||
"Hi {firstName}!": "Hi {firstName}!",
|
"Hi {firstName}!": "Hi {firstName}!",
|
||||||
"High floor": "High floor",
|
"High floor": "High floor",
|
||||||
"Highest level": "Highest level",
|
"Highest level": "Highest level",
|
||||||
|
"Hiking": "Hiking",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"Hospital": "Hospital",
|
"Hospital": "Hospital",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
@@ -235,6 +239,7 @@
|
|||||||
"Join at no cost": "Join at no cost",
|
"Join at no cost": "Join at no cost",
|
||||||
"Join now": "Join now",
|
"Join now": "Join now",
|
||||||
"Join or log in while booking for member pricing.": "Join or log in while booking for member pricing.",
|
"Join or log in while booking for member pricing.": "Join or log in while booking for member pricing.",
|
||||||
|
"Kayaking": "Kayaking",
|
||||||
"King bed": "King bed",
|
"King bed": "King bed",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
"Last name": "Last name",
|
"Last name": "Last name",
|
||||||
@@ -265,6 +270,8 @@
|
|||||||
"Manage booking": "Manage booking",
|
"Manage booking": "Manage booking",
|
||||||
"Manage preferences": "Manage preferences",
|
"Manage preferences": "Manage preferences",
|
||||||
"Map": "Map",
|
"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}",
|
"Map of {hotelName}": "Map of {hotelName}",
|
||||||
"Marketing city": "Marketing city",
|
"Marketing city": "Marketing city",
|
||||||
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} guest} other {{range} guests}}",
|
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} guest} other {{range} guests}}",
|
||||||
@@ -291,6 +298,7 @@
|
|||||||
"Monday": "Monday",
|
"Monday": "Monday",
|
||||||
"Month": "Month",
|
"Month": "Month",
|
||||||
"Museum": "Museum",
|
"Museum": "Museum",
|
||||||
|
"Museums": "Museums",
|
||||||
"My communication preferences": "My communication preferences",
|
"My communication preferences": "My communication preferences",
|
||||||
"My membership cards": "My membership cards",
|
"My membership cards": "My membership cards",
|
||||||
"My pages": "My pages",
|
"My pages": "My pages",
|
||||||
@@ -304,6 +312,7 @@
|
|||||||
"Nearby companies": "Nearby companies",
|
"Nearby companies": "Nearby companies",
|
||||||
"New password": "New password",
|
"New password": "New password",
|
||||||
"Next": "Next",
|
"Next": "Next",
|
||||||
|
"Nightlife": "Nightlife",
|
||||||
"Nights needed to level up": "Nights needed to level up",
|
"Nights needed to level up": "Nights needed to level up",
|
||||||
"No": "No",
|
"No": "No",
|
||||||
"No availability": "No availability",
|
"No availability": "No availability",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"Bed": "Vuodetyyppi",
|
"Bed": "Vuodetyyppi",
|
||||||
"Bed options": "Vuodevaihtoehdot",
|
"Bed options": "Vuodevaihtoehdot",
|
||||||
"Bed type": "Vuodetyyppi",
|
"Bed type": "Vuodetyyppi",
|
||||||
|
"Bike friendly": "Pyöräystävällinen",
|
||||||
"Birth date": "Syntymäaika",
|
"Birth date": "Syntymäaika",
|
||||||
"Book": "Varaa",
|
"Book": "Varaa",
|
||||||
"Book a table online": "Varaa pöytä verkossa",
|
"Book a table online": "Varaa pöytä verkossa",
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
"Choose room": "Valitse huone",
|
"Choose room": "Valitse huone",
|
||||||
"Cities": "Kaupungit",
|
"Cities": "Kaupungit",
|
||||||
"City": "Kaupunki",
|
"City": "Kaupunki",
|
||||||
|
"City pulse": "Kaupungin syke",
|
||||||
"City/State": "Kaupunki/Osavaltio",
|
"City/State": "Kaupunki/Osavaltio",
|
||||||
"Clear all filters": "Tyhjennä kaikki suodattimet",
|
"Clear all filters": "Tyhjennä kaikki suodattimet",
|
||||||
"Clear searches": "Tyhjennä haut",
|
"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 delete credit card, please try again later.": "Luottokortin poistaminen epäonnistui, yritä myöhemmin uudelleen.",
|
||||||
"Failed to verify membership": "Jäsenyys ei verifioitu",
|
"Failed to verify membership": "Jäsenyys ei verifioitu",
|
||||||
"Fair": "Messukeskus",
|
"Fair": "Messukeskus",
|
||||||
|
"Family friendly": "Perheystävällinen",
|
||||||
"Filter": "Suodatin",
|
"Filter": "Suodatin",
|
||||||
"Filter and sort": "Suodata ja lajittele",
|
"Filter and sort": "Suodata ja lajittele",
|
||||||
"Filter by": "Suodatusperuste",
|
"Filter by": "Suodatusperuste",
|
||||||
@@ -191,6 +194,7 @@
|
|||||||
"Hi {firstName}!": "Hi {firstName}!",
|
"Hi {firstName}!": "Hi {firstName}!",
|
||||||
"High floor": "Korkea taso",
|
"High floor": "Korkea taso",
|
||||||
"Highest level": "Korkein taso",
|
"Highest level": "Korkein taso",
|
||||||
|
"Hiking": "Hiking",
|
||||||
"Home": "Kotiin",
|
"Home": "Kotiin",
|
||||||
"Hospital": "Sairaala",
|
"Hospital": "Sairaala",
|
||||||
"Hotel": "Hotelli",
|
"Hotel": "Hotelli",
|
||||||
@@ -219,6 +223,7 @@
|
|||||||
"Join at no cost": "Liity maksutta",
|
"Join at no cost": "Liity maksutta",
|
||||||
"Join now": "Liity jäseneksi",
|
"Join now": "Liity jäseneksi",
|
||||||
"Join or log in while booking for member pricing.": "Liity tai kirjaudu sisään, kun varaat jäsenhinnan.",
|
"Join or log in while booking for member pricing.": "Liity tai kirjaudu sisään, kun varaat jäsenhinnan.",
|
||||||
|
"Kayaking": "Melonta",
|
||||||
"King bed": "King-vuode",
|
"King bed": "King-vuode",
|
||||||
"Language": "Kieli",
|
"Language": "Kieli",
|
||||||
"Last name": "Sukunimi",
|
"Last name": "Sukunimi",
|
||||||
@@ -247,6 +252,8 @@
|
|||||||
"Main menu": "Päävalikko",
|
"Main menu": "Päävalikko",
|
||||||
"Manage preferences": "Asetusten hallinta",
|
"Manage preferences": "Asetusten hallinta",
|
||||||
"Map": "Kartta",
|
"Map": "Kartta",
|
||||||
|
"Map of the city center": "Kartta kaupungin keskustasta",
|
||||||
|
"Map of the country": "Kartta maasta",
|
||||||
"Map of {hotelName}": "Map of {hotelName}",
|
"Map of {hotelName}": "Map of {hotelName}",
|
||||||
"Marketing city": "Markkinointikaupunki",
|
"Marketing city": "Markkinointikaupunki",
|
||||||
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} vieras} other {{range} vieraita}}",
|
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} vieras} other {{range} vieraita}}",
|
||||||
@@ -269,6 +276,7 @@
|
|||||||
"Monday": "Maanantai",
|
"Monday": "Maanantai",
|
||||||
"Month": "Kuukausi",
|
"Month": "Kuukausi",
|
||||||
"Museum": "Museo",
|
"Museum": "Museo",
|
||||||
|
"Museums": "Museot",
|
||||||
"My communication preferences": "Viestintämieltymykseni",
|
"My communication preferences": "Viestintämieltymykseni",
|
||||||
"My membership cards": "Jäsenkorttini",
|
"My membership cards": "Jäsenkorttini",
|
||||||
"My pages": "Omat sivut",
|
"My pages": "Omat sivut",
|
||||||
@@ -282,6 +290,7 @@
|
|||||||
"Nearby companies": "Läheiset yritykset",
|
"Nearby companies": "Läheiset yritykset",
|
||||||
"New password": "Uusi salasana",
|
"New password": "Uusi salasana",
|
||||||
"Next": "Seuraava",
|
"Next": "Seuraava",
|
||||||
|
"Nightlife": "Yöelämä",
|
||||||
"Nights needed to level up": "Yöt, joita tarvitaan tasolle",
|
"Nights needed to level up": "Yöt, joita tarvitaan tasolle",
|
||||||
"No": "Ei",
|
"No": "Ei",
|
||||||
"No availability": "Ei saatavuutta",
|
"No availability": "Ei saatavuutta",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"Bed": "Seng type",
|
"Bed": "Seng type",
|
||||||
"Bed options": "Sengemuligheter",
|
"Bed options": "Sengemuligheter",
|
||||||
"Bed type": "Seng type",
|
"Bed type": "Seng type",
|
||||||
|
"Bike friendly": "Sykkelvennlig",
|
||||||
"Birth date": "Fødselsdato",
|
"Birth date": "Fødselsdato",
|
||||||
"Book": "Bestill",
|
"Book": "Bestill",
|
||||||
"Book a table online": "Bestill bord online",
|
"Book a table online": "Bestill bord online",
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
"Choose room": "Velg rom",
|
"Choose room": "Velg rom",
|
||||||
"Cities": "Byer",
|
"Cities": "Byer",
|
||||||
"City": "By",
|
"City": "By",
|
||||||
|
"City pulse": "Byens puls",
|
||||||
"City/State": "By/Stat",
|
"City/State": "By/Stat",
|
||||||
"Clear all filters": "Fjern alle filtre",
|
"Clear all filters": "Fjern alle filtre",
|
||||||
"Clear searches": "Tømme søk",
|
"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 delete credit card, please try again later.": "Kunne ikke slette kredittkortet, prøv igjen senere.",
|
||||||
"Failed to verify membership": "Medlemskap ikke verifisert",
|
"Failed to verify membership": "Medlemskap ikke verifisert",
|
||||||
"Fair": "Messe",
|
"Fair": "Messe",
|
||||||
|
"Family friendly": "Familievennlig",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
"Filter and sort": "Filtrer og sorter",
|
"Filter and sort": "Filtrer og sorter",
|
||||||
"Filter by": "Filtrer etter",
|
"Filter by": "Filtrer etter",
|
||||||
@@ -190,6 +193,7 @@
|
|||||||
"Hi {firstName}!": "Hei {firstName}!",
|
"Hi {firstName}!": "Hei {firstName}!",
|
||||||
"High floor": "Høy nivå",
|
"High floor": "Høy nivå",
|
||||||
"Highest level": "Høyeste nivå",
|
"Highest level": "Høyeste nivå",
|
||||||
|
"Hiking": "Fotturer",
|
||||||
"Home": "Hjem",
|
"Home": "Hjem",
|
||||||
"Hospital": "Sykehus",
|
"Hospital": "Sykehus",
|
||||||
"Hotel": "Hotel",
|
"Hotel": "Hotel",
|
||||||
@@ -218,6 +222,7 @@
|
|||||||
"Join at no cost": "Bli med uten kostnad",
|
"Join at no cost": "Bli med uten kostnad",
|
||||||
"Join now": "Bli medlem nå",
|
"Join now": "Bli medlem nå",
|
||||||
"Join or log in while booking for member pricing.": "Bli med eller logg inn under bestilling for medlemspris.",
|
"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",
|
"King bed": "King-size-seng",
|
||||||
"Language": "Språk",
|
"Language": "Språk",
|
||||||
"Last name": "Etternavn",
|
"Last name": "Etternavn",
|
||||||
@@ -246,7 +251,9 @@
|
|||||||
"Main menu": "Hovedmeny",
|
"Main menu": "Hovedmeny",
|
||||||
"Manage preferences": "Administrer preferanser",
|
"Manage preferences": "Administrer preferanser",
|
||||||
"Map": "Kart",
|
"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",
|
"Marketing city": "Markedsføringsby",
|
||||||
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Maks {max, plural, one {{range} gjest} other {{range} gjester}}",
|
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Maks {max, plural, one {{range} gjest} other {{range} gjester}}",
|
||||||
"Meetings & Conferences": "Møter & Konferanser",
|
"Meetings & Conferences": "Møter & Konferanser",
|
||||||
@@ -268,6 +275,7 @@
|
|||||||
"Monday": "Mandag",
|
"Monday": "Mandag",
|
||||||
"Month": "Måned",
|
"Month": "Måned",
|
||||||
"Museum": "Museum",
|
"Museum": "Museum",
|
||||||
|
"Museums": "Museums",
|
||||||
"My communication preferences": "Mine kommunikasjonspreferanser",
|
"My communication preferences": "Mine kommunikasjonspreferanser",
|
||||||
"My membership cards": "Mine medlemskort",
|
"My membership cards": "Mine medlemskort",
|
||||||
"My pages": "Mine sider",
|
"My pages": "Mine sider",
|
||||||
@@ -281,6 +289,7 @@
|
|||||||
"Nearby companies": "Nærliggende selskaper",
|
"Nearby companies": "Nærliggende selskaper",
|
||||||
"New password": "Nytt passord",
|
"New password": "Nytt passord",
|
||||||
"Next": "Neste",
|
"Next": "Neste",
|
||||||
|
"Nightlife": "Natteliv",
|
||||||
"Nights needed to level up": "Netter som trengs for å komme opp i nivå",
|
"Nights needed to level up": "Netter som trengs for å komme opp i nivå",
|
||||||
"No": "Nei",
|
"No": "Nei",
|
||||||
"No availability": "Ingen tilgjengelighet",
|
"No availability": "Ingen tilgjengelighet",
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"Bed": "Sängtyp",
|
"Bed": "Sängtyp",
|
||||||
"Bed options": "Sängalternativ",
|
"Bed options": "Sängalternativ",
|
||||||
"Bed type": "Sängtyp",
|
"Bed type": "Sängtyp",
|
||||||
|
"Bike friendly": "Cykelvänligt",
|
||||||
"Birth date": "Födelsedatum",
|
"Birth date": "Födelsedatum",
|
||||||
"Book": "Boka",
|
"Book": "Boka",
|
||||||
"Book a table online": "Boka ett bord online",
|
"Book a table online": "Boka ett bord online",
|
||||||
@@ -91,6 +92,7 @@
|
|||||||
"Choose room": "Välj rum",
|
"Choose room": "Välj rum",
|
||||||
"Cities": "Städer",
|
"Cities": "Städer",
|
||||||
"City": "Ort",
|
"City": "Ort",
|
||||||
|
"City pulse": "Stadspuls",
|
||||||
"City/State": "Ort",
|
"City/State": "Ort",
|
||||||
"Clear all filters": "Rensa alla filter",
|
"Clear all filters": "Rensa alla filter",
|
||||||
"Clear searches": "Rensa tidigare sökningar",
|
"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 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",
|
"Failed to verify membership": "Medlemskap inte verifierat",
|
||||||
"Fair": "Mässa",
|
"Fair": "Mässa",
|
||||||
|
"Family friendly": "Familjevänligt",
|
||||||
"Filter": "Filter",
|
"Filter": "Filter",
|
||||||
"Filter and sort": "Filtrera och sortera",
|
"Filter and sort": "Filtrera och sortera",
|
||||||
"Filter by": "Filtrera på",
|
"Filter by": "Filtrera på",
|
||||||
@@ -190,6 +193,7 @@
|
|||||||
"Hi {firstName}!": "Hej {firstName}!",
|
"Hi {firstName}!": "Hej {firstName}!",
|
||||||
"High floor": "Högt upp",
|
"High floor": "Högt upp",
|
||||||
"Highest level": "Högsta nivå",
|
"Highest level": "Högsta nivå",
|
||||||
|
"Hiking": "Vandring",
|
||||||
"Home": "Hem",
|
"Home": "Hem",
|
||||||
"Hospital": "Sjukhus",
|
"Hospital": "Sjukhus",
|
||||||
"Hotel": "Hotell",
|
"Hotel": "Hotell",
|
||||||
@@ -218,6 +222,7 @@
|
|||||||
"Join at no cost": "Gå med utan kostnad",
|
"Join at no cost": "Gå med utan kostnad",
|
||||||
"Join now": "Gå med nu",
|
"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.",
|
"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",
|
"King bed": "King size-säng",
|
||||||
"Language": "Språk",
|
"Language": "Språk",
|
||||||
"Last name": "Efternamn",
|
"Last name": "Efternamn",
|
||||||
@@ -246,7 +251,9 @@
|
|||||||
"Main menu": "Huvudmeny",
|
"Main menu": "Huvudmeny",
|
||||||
"Manage preferences": "Hantera inställningar",
|
"Manage preferences": "Hantera inställningar",
|
||||||
"Map": "Karta",
|
"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",
|
"Marketing city": "Marknadsföringsstad",
|
||||||
"Max {max, plural, one {{range} guest} other {{range} guests}}": "Max {max, plural, one {{range} gäst} other {{range} gäster}}",
|
"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",
|
"Meetings & Conferences": "Möten & Konferenser",
|
||||||
@@ -268,6 +275,7 @@
|
|||||||
"Monday": "Måndag",
|
"Monday": "Måndag",
|
||||||
"Month": "Månad",
|
"Month": "Månad",
|
||||||
"Museum": "Museum",
|
"Museum": "Museum",
|
||||||
|
"Museums": "Museer",
|
||||||
"My communication preferences": "Mina kommunikationspreferenser",
|
"My communication preferences": "Mina kommunikationspreferenser",
|
||||||
"My membership cards": "Mina medlemskort",
|
"My membership cards": "Mina medlemskort",
|
||||||
"My pages": "Mina sidor",
|
"My pages": "Mina sidor",
|
||||||
@@ -281,6 +289,7 @@
|
|||||||
"Nearby companies": "Närliggande företag",
|
"Nearby companies": "Närliggande företag",
|
||||||
"New password": "Nytt lösenord",
|
"New password": "Nytt lösenord",
|
||||||
"Next": "Nästa",
|
"Next": "Nästa",
|
||||||
|
"Nightlife": "Nattliv",
|
||||||
"Nights needed to level up": "Nätter som behövs för att gå upp i nivå",
|
"Nights needed to level up": "Nätter som behövs för att gå upp i nivå",
|
||||||
"No": "Nej",
|
"No": "Nej",
|
||||||
"No availability": "Ingen tillgänglighet",
|
"No availability": "Ingen tillgänglighet",
|
||||||
|
|||||||
7
lib/graphql/Fragments/DestinationCityPage/Ref.graphql
Normal file
7
lib/graphql/Fragments/DestinationCityPage/Ref.graphql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#import "../System.graphql"
|
||||||
|
|
||||||
|
fragment DestinationCityPageRef on DestinationCityPage {
|
||||||
|
system {
|
||||||
|
...System
|
||||||
|
}
|
||||||
|
}
|
||||||
7
lib/graphql/Fragments/DestinationCountryPage/Ref.graphql
Normal file
7
lib/graphql/Fragments/DestinationCountryPage/Ref.graphql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#import "../System.graphql"
|
||||||
|
|
||||||
|
fragment DestinationCountryPageRef on DestinationCountryPage {
|
||||||
|
system {
|
||||||
|
...System
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,69 @@
|
|||||||
#import "../../Fragments/System.graphql"
|
#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!) {
|
query GetDestinationCityPage($locale: String!, $uid: String!) {
|
||||||
destination_city_page(uid: $uid, locale: $locale) {
|
destination_city_page(uid: $uid, locale: $locale) {
|
||||||
title
|
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 {
|
||||||
...System
|
...System
|
||||||
created_at
|
created_at
|
||||||
@@ -16,6 +77,32 @@ query GetDestinationCityPage($locale: String!, $uid: String!) {
|
|||||||
|
|
||||||
query GetDestinationCityPageRefs($locale: String!, $uid: String!) {
|
query GetDestinationCityPageRefs($locale: String!, $uid: String!) {
|
||||||
destination_city_page(locale: $locale, uid: $uid) {
|
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 {
|
||||||
...System
|
...System
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,50 @@
|
|||||||
#import "../../Fragments/System.graphql"
|
#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!) {
|
query GetDestinationCountryPage($locale: String!, $uid: String!) {
|
||||||
destination_country_page(uid: $uid, locale: $locale) {
|
destination_country_page(uid: $uid, locale: $locale) {
|
||||||
title
|
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 {
|
||||||
...System
|
...System
|
||||||
created_at
|
created_at
|
||||||
@@ -16,6 +58,22 @@ query GetDestinationCountryPage($locale: String!, $uid: String!) {
|
|||||||
|
|
||||||
query GetDestinationCountryPageRefs($locale: String!, $uid: String!) {
|
query GetDestinationCountryPageRefs($locale: String!, $uid: String!) {
|
||||||
destination_country_page(locale: $locale, uid: $uid) {
|
destination_country_page(locale: $locale, uid: $uid) {
|
||||||
|
sidepeek_content {
|
||||||
|
content {
|
||||||
|
embedded_itemsConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
__typename
|
||||||
|
...AccountPageRef
|
||||||
|
...ContentPageRef
|
||||||
|
...LoyaltyPageRef
|
||||||
|
...HotelPageRef
|
||||||
|
...CollectionPageRef
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
system {
|
system {
|
||||||
...System
|
...System
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,180 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
|
||||||
|
|
||||||
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
export const destinationCityPageSchema = z.object({
|
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
||||||
destination_city_page: z.object({
|
import {
|
||||||
title: z.string(),
|
TrackingChannelEnum,
|
||||||
system: systemSchema.merge(
|
type TrackingSDKPageData,
|
||||||
z.object({
|
} from "@/types/components/tracking"
|
||||||
created_at: z.string(),
|
import { Country } from "@/types/enums/country"
|
||||||
updated_at: z.string(),
|
|
||||||
})
|
export const destinationCityPageSchema = z
|
||||||
),
|
.object({
|
||||||
}),
|
destination_city_page: z.object({
|
||||||
trackingProps: z.object({
|
title: z.string(),
|
||||||
url: 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 */
|
/** REFS */
|
||||||
export const destinationCityPageRefsSchema = z.object({
|
export const destinationCityPageRefsSchema = z.object({
|
||||||
destination_city_page: 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,
|
system: systemSchema,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,11 +20,8 @@ import {
|
|||||||
getDestinationCityPageRefsSuccessCounter,
|
getDestinationCityPageRefsSuccessCounter,
|
||||||
getDestinationCityPageSuccessCounter,
|
getDestinationCityPageSuccessCounter,
|
||||||
} from "./telemetry"
|
} from "./telemetry"
|
||||||
|
import { generatePageTags } from "./utils"
|
||||||
|
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import type {
|
import type {
|
||||||
GetDestinationCityPageData,
|
GetDestinationCityPageData,
|
||||||
GetDestinationCityPageRefsSchema,
|
GetDestinationCityPageRefsSchema,
|
||||||
@@ -91,6 +88,8 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
JSON.stringify({ query: { lang, uid } })
|
JSON.stringify({ query: { lang, uid } })
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const tags = generatePageTags(validatedRefsData.data, lang)
|
||||||
|
|
||||||
getDestinationCityPageCounter.add(1, { lang, uid: `${uid}` })
|
getDestinationCityPageCounter.add(1, { lang, uid: `${uid}` })
|
||||||
console.info(
|
console.info(
|
||||||
"contentstack.destinationCityPage start",
|
"contentstack.destinationCityPage start",
|
||||||
@@ -107,7 +106,7 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
{
|
{
|
||||||
cache: "force-cache",
|
cache: "force-cache",
|
||||||
next: {
|
next: {
|
||||||
tags: [generateTag(lang, uid)],
|
tags,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -129,22 +128,22 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
throw notFoundError
|
throw notFoundError
|
||||||
}
|
}
|
||||||
|
|
||||||
const destinationCityPage = destinationCityPageSchema.safeParse(
|
const validatedDestinationCityPage = destinationCityPageSchema.safeParse(
|
||||||
response.data
|
response.data
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!destinationCityPage.success) {
|
if (!validatedDestinationCityPage.success) {
|
||||||
getDestinationCityPageFailCounter.add(1, {
|
getDestinationCityPageFailCounter.add(1, {
|
||||||
lang,
|
lang,
|
||||||
uid: `${uid}`,
|
uid: `${uid}`,
|
||||||
error_type: "validation_error",
|
error_type: "validation_error",
|
||||||
error: JSON.stringify(destinationCityPage.error),
|
error: JSON.stringify(validatedDestinationCityPage.error),
|
||||||
})
|
})
|
||||||
console.error(
|
console.error(
|
||||||
"contentstack.destinationCityPage validation error",
|
"contentstack.destinationCityPage validation error",
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
query: { lang, uid },
|
query: { lang, uid },
|
||||||
error: destinationCityPage.error,
|
error: validatedDestinationCityPage.error,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
@@ -158,22 +157,6 @@ export const destinationCityPageQueryRouter = router({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const system = destinationCityPage.data.destination_city_page.system
|
return validatedDestinationCityPage.data
|
||||||
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,
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
37
server/routers/contentstack/destinationCityPage/utils.ts
Normal file
37
server/routers/contentstack/destinationCityPage/utils.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -1,25 +1,120 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
|
||||||
|
|
||||||
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
|
|
||||||
export const destinationCountryPageSchema = z.object({
|
import type { ImageVaultAsset } from "@/types/components/imageVault"
|
||||||
destination_country_page: z.object({
|
import {
|
||||||
title: z.string(),
|
TrackingChannelEnum,
|
||||||
system: systemSchema.merge(
|
type TrackingSDKPageData,
|
||||||
z.object({
|
} from "@/types/components/tracking"
|
||||||
created_at: z.string(),
|
import { Country } from "@/types/enums/country"
|
||||||
updated_at: z.string(),
|
|
||||||
})
|
export const destinationCountryPageSchema = z
|
||||||
),
|
.object({
|
||||||
}),
|
destination_country_page: z.object({
|
||||||
trackingProps: z.object({
|
title: z.string(),
|
||||||
url: 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 */
|
/** REFS */
|
||||||
export const destinationCountryPageRefsSchema = z.object({
|
export const destinationCountryPageRefsSchema = z.object({
|
||||||
destination_country_page: 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,
|
system: systemSchema,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,11 +20,8 @@ import {
|
|||||||
getDestinationCountryPageRefsSuccessCounter,
|
getDestinationCountryPageRefsSuccessCounter,
|
||||||
getDestinationCountryPageSuccessCounter,
|
getDestinationCountryPageSuccessCounter,
|
||||||
} from "./telemetry"
|
} from "./telemetry"
|
||||||
|
import { generatePageTags } from "./utils"
|
||||||
|
|
||||||
import {
|
|
||||||
TrackingChannelEnum,
|
|
||||||
type TrackingSDKPageData,
|
|
||||||
} from "@/types/components/tracking"
|
|
||||||
import type {
|
import type {
|
||||||
GetDestinationCountryPageData,
|
GetDestinationCountryPageData,
|
||||||
GetDestinationCountryPageRefsSchema,
|
GetDestinationCountryPageRefsSchema,
|
||||||
@@ -91,6 +88,8 @@ export const destinationCountryPageQueryRouter = router({
|
|||||||
JSON.stringify({ query: { lang, uid } })
|
JSON.stringify({ query: { lang, uid } })
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const tags = generatePageTags(validatedRefsData.data, lang)
|
||||||
|
|
||||||
getDestinationCountryPageCounter.add(1, { lang, uid: `${uid}` })
|
getDestinationCountryPageCounter.add(1, { lang, uid: `${uid}` })
|
||||||
console.info(
|
console.info(
|
||||||
"contentstack.destinationCountryPage start",
|
"contentstack.destinationCountryPage start",
|
||||||
@@ -107,7 +106,7 @@ export const destinationCountryPageQueryRouter = router({
|
|||||||
{
|
{
|
||||||
cache: "force-cache",
|
cache: "force-cache",
|
||||||
next: {
|
next: {
|
||||||
tags: [generateTag(lang, uid)],
|
tags,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -158,23 +157,6 @@ export const destinationCountryPageQueryRouter = router({
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const system = destinationCountryPage.data.destination_country_page.system
|
return destinationCountryPage.data
|
||||||
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,
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
32
server/routers/contentstack/destinationCountryPage/utils.ts
Normal file
32
server/routers/contentstack/destinationCountryPage/utils.ts
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -52,6 +52,33 @@ export const contentPageRefSchema = z.object({
|
|||||||
system: systemSchema,
|
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
|
export const hotelPageSchema = z
|
||||||
.object({
|
.object({
|
||||||
__typename: z.literal(ContentEnum.blocks.HotelPage),
|
__typename: z.literal(ContentEnum.blocks.HotelPage),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export enum StickyElementNameEnum {
|
|||||||
BOOKING_WIDGET = "BOOKING_WIDGET",
|
BOOKING_WIDGET = "BOOKING_WIDGET",
|
||||||
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
|
HOTEL_TAB_NAVIGATION = "HOTEL_TAB_NAVIGATION",
|
||||||
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
|
HOTEL_STATIC_MAP = "HOTEL_STATIC_MAP",
|
||||||
|
DESTINATION_SIDEBAR = "DESTINATION_SIDEBAR",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StickyElement {
|
export interface StickyElement {
|
||||||
@@ -34,6 +35,8 @@ const priorityMap: Record<StickyElementNameEnum, number> = {
|
|||||||
|
|
||||||
[StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3,
|
[StickyElementNameEnum.HOTEL_TAB_NAVIGATION]: 3,
|
||||||
[StickyElementNameEnum.HOTEL_STATIC_MAP]: 3,
|
[StickyElementNameEnum.HOTEL_STATIC_MAP]: 3,
|
||||||
|
|
||||||
|
[StickyElementNameEnum.DESTINATION_SIDEBAR]: 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStickyPositionStore = create<StickyStore>((set, get) => ({
|
const useStickyPositionStore = create<StickyStore>((set, get) => ({
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ export namespace ContentEnum {
|
|||||||
AccountPage = "AccountPage",
|
AccountPage = "AccountPage",
|
||||||
CollectionPage = "CollectionPage",
|
CollectionPage = "CollectionPage",
|
||||||
ContentPage = "ContentPage",
|
ContentPage = "ContentPage",
|
||||||
|
DestinationCityPage = "DestinationCityPage",
|
||||||
|
DestinationCountryPage = "DestinationCountryPage",
|
||||||
|
DestinationOverviewPage = "DestinationOverviewPage",
|
||||||
HotelPage = "HotelPage",
|
HotelPage = "HotelPage",
|
||||||
ImageContainer = "ImageContainer",
|
ImageContainer = "ImageContainer",
|
||||||
LoyaltyPage = "LoyaltyPage",
|
LoyaltyPage = "LoyaltyPage",
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import type {
|
|||||||
|
|
||||||
export interface GetDestinationCityPageData
|
export interface GetDestinationCityPageData
|
||||||
extends z.input<typeof destinationCityPageSchema> {}
|
extends z.input<typeof destinationCityPageSchema> {}
|
||||||
export interface DestinationCityPage
|
interface DestinationCityPage
|
||||||
extends z.output<typeof destinationCityPageSchema> {}
|
extends z.output<typeof destinationCityPageSchema> {}
|
||||||
|
export type DestinationCityPageData = DestinationCityPage["destinationCityPage"]
|
||||||
|
|
||||||
export interface GetDestinationCityPageRefsSchema
|
export interface GetDestinationCityPageRefsSchema
|
||||||
extends z.input<typeof destinationCityPageRefsSchema> {}
|
extends z.input<typeof destinationCityPageRefsSchema> {}
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ import type {
|
|||||||
|
|
||||||
export interface GetDestinationCountryPageData
|
export interface GetDestinationCountryPageData
|
||||||
extends z.input<typeof destinationCountryPageSchema> {}
|
extends z.input<typeof destinationCountryPageSchema> {}
|
||||||
export interface DestinationCountryPage
|
interface DestinationCountryPage
|
||||||
extends z.output<typeof destinationCountryPageSchema> {}
|
extends z.output<typeof destinationCountryPageSchema> {}
|
||||||
|
export type DestinationCountryPageData =
|
||||||
|
DestinationCountryPage["destinationCountryPage"]
|
||||||
|
|
||||||
export interface GetDestinationCountryPageRefsSchema
|
export interface GetDestinationCountryPageRefsSchema
|
||||||
extends z.input<typeof destinationCountryPageRefsSchema> {}
|
extends z.input<typeof destinationCountryPageRefsSchema> {}
|
||||||
|
|||||||
Reference in New Issue
Block a user