Merge branch 'develop' into feat/sw-703-image-loader

This commit is contained in:
Linus Flood
2024-11-04 15:21:21 +01:00
80 changed files with 1422 additions and 367 deletions

View File

@@ -2,9 +2,10 @@ import { notFound } from "next/navigation"
import { env } from "@/env/server"
import ContentPage from "@/components/ContentType/ContentPage"
import HotelPage from "@/components/ContentType/HotelPage"
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
import CollectionPage from "@/components/ContentType/StaticPages/CollectionPage"
import ContentPage from "@/components/ContentType/StaticPages/ContentPage"
import { setLang } from "@/i18n/serverContext"
import {
@@ -22,6 +23,11 @@ export default function ContentTypePage({
setLang(params.lang)
switch (params.contentType) {
case "collection-page":
if (env.HIDE_FOR_NEXT_RELEASE) {
return notFound()
}
return <CollectionPage />
case "content-page":
if (env.HIDE_FOR_NEXT_RELEASE) {
return notFound()

View File

@@ -1,6 +1,7 @@
import { notFound } from "next/navigation"
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
import { dt } from "@/lib/dt"
import { getLocations, getProfileSafely } from "@/lib/trpc/memoizedRequests"
import { serverClient } from "@/lib/trpc/server"
import { HotelIncludeEnum } from "@/server/routers/hotels/input"
@@ -21,6 +22,17 @@ export default async function SelectRatePage({
}: PageArgs<LangParams & { section: string }, SelectRateSearchParams>) {
setLang(params.lang)
const locations = await getLocations()
if (!locations || "error" in locations) {
return null
}
const hotel = locations.data.find(
(location) =>
"operaId" in location && location.operaId == searchParams.hotel
)
if (!hotel) {
return notFound()
}
const selectRoomParams = new URLSearchParams(searchParams)
const selectRoomParamsObject =
getHotelReservationQueryParams(selectRoomParams)
@@ -29,7 +41,16 @@ export default async function SelectRatePage({
return notFound()
}
const adults = selectRoomParamsObject.room[0].adults // TODO: Handle multiple rooms
const validFromDate =
searchParams.fromDate &&
dt(searchParams.fromDate).isAfter(dt().subtract(1, "day"))
? searchParams.fromDate
: dt().utc().format("YYYY-MM-DD")
const validToDate =
searchParams.toDate && dt(searchParams.toDate).isAfter(validFromDate)
? searchParams.toDate
: dt().utc().add(1, "day").format("YYYY-MM-DD")
const adults = selectRoomParamsObject.room[0].adults || 1 // TODO: Handle multiple rooms
const childrenCount = selectRoomParamsObject.room[0].child?.length
const children = selectRoomParamsObject.room[0].child
? generateChildrenString(selectRoomParamsObject.room[0].child)
@@ -43,8 +64,8 @@ export default async function SelectRatePage({
}),
serverClient().hotel.availability.rooms({
hotelId: parseInt(searchParams.hotel, 10),
roomStayStartDate: searchParams.fromDate,
roomStayEndDate: searchParams.toDate,
roomStayStartDate: validFromDate,
roomStayEndDate: validToDate,
adults,
children,
}),

View File

@@ -1,26 +1,29 @@
import InitLivePreview from "@/components/Current/LivePreview"
import { setLang } from "@/i18n/serverContext"
import "@/app/globals.css"
import "@scandic-hotels/design-system/style.css"
import type { Metadata } from "next"
import TrpcProvider from "@/lib/trpc/Provider"
import InitLivePreview from "@/components/LivePreview"
import { getIntl } from "@/i18n"
import ServerIntlProvider from "@/i18n/Provider"
import { setLang } from "@/i18n/serverContext"
import type { LangParams, LayoutArgs } from "@/types/params"
export const metadata: Metadata = {
description: "New web",
title: "Scandic Hotels",
}
export default function RootLayout({
export default async function RootLayout({
children,
params,
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
setLang(params.lang)
const { defaultLocale, locale, messages } = await getIntl()
return (
<html lang={params.lang}>
<body>
<InitLivePreview />
{children}
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
<TrpcProvider>{children}</TrpcProvider>
</ServerIntlProvider>
</body>
</html>
)

View File

@@ -1,6 +1,14 @@
import { ContentstackLivePreview } from "@contentstack/live-preview-utils"
import { notFound } from "next/navigation"
import HotelPage from "@/components/ContentType/HotelPage"
import LoyaltyPage from "@/components/ContentType/LoyaltyPage"
import CollectionPage from "@/components/ContentType/StaticPages/CollectionPage"
import ContentPage from "@/components/ContentType/StaticPages/ContentPage"
import LoadingSpinner from "@/components/LoadingSpinner"
import { setLang } from "@/i18n/serverContext"
import {
import type {
ContentTypeParams,
LangParams,
PageArgs,
@@ -13,12 +21,32 @@ export default async function PreviewPage({
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
setLang(params.lang)
return (
<div>
<p>
Preview for {params.contentType}:{params.uid} in {params.lang} with
params <pre>{JSON.stringify(searchParams, null, 2)}</pre> goes here
</p>
</div>
)
try {
ContentstackLivePreview.setConfigFromParams(searchParams)
if (!searchParams.live_preview) {
return <LoadingSpinner />
}
switch (params.contentType) {
case "content-page":
return <ContentPage />
case "loyalty-page":
return <LoyaltyPage />
case "collection-page":
return <CollectionPage />
case "hotel-page":
return <HotelPage />
default:
console.log({ PREVIEW: params })
const type = params.contentType
console.error(`Unsupported content type given: ${type}`)
notFound()
}
} catch (error) {
// TODO: throw 500
console.error("Error in preview page")
console.error(error)
throw new Error("Something went wrong")
}
}

View File

@@ -1,6 +1,6 @@
import Footer from "@/components/Current/Footer"
import LangPopup from "@/components/Current/LangPopup"
import InitLivePreview from "@/components/Current/LivePreview"
import InitLivePreview from "@/components/LivePreview"
import SkipToMainContent from "@/components/SkipToMainContent"
import { setLang } from "@/i18n/serverContext"

View File

@@ -1,4 +1,4 @@
import ContentstackLivePreview from "@contentstack/live-preview-utils"
import { ContentstackLivePreview } from "@contentstack/live-preview-utils"
import { previewRequest } from "@/lib/graphql/previewRequest"
import { GetCurrentBlockPage } from "@/lib/graphql/Query/Current/CurrentBlockPage.graphql"

View File

@@ -42,13 +42,17 @@ export default function CardsGrid({
case CardsGridEnum.cards.Card:
return (
<Card
theme={cards_grid.theme ?? "one"}
theme={
cards_grid.theme ?? card.backgroundImage ? "image" : "one"
}
key={card.system.uid}
scriptedTopTitle={card.scripted_top_title}
heading={card.heading}
bodyText={card.body_text}
secondaryButton={card.secondaryButton}
primaryButton={card.primaryButton}
backgroundImage={card.backgroundImage}
imageGradient
/>
)
case CardsGridEnum.cards.TeaserCard:

View File

@@ -70,7 +70,7 @@ function RewardTableHeader({ name, description }: RewardTableHeaderProps) {
<details className={styles.details}>
<summary className={styles.summary}>
<hgroup className={styles.rewardHeader}>
<Title as="h5" level="h2" textTransform={"regular"}>
<Title as="h4" level="h2" textTransform={"regular"}>
{name}
</Title>
<span className={styles.chevron}>

View File

@@ -19,7 +19,7 @@ export default function RewardCard({
<details className={styles.details}>
<summary className={styles.summary}>
<hgroup className={styles.rewardCardHeader}>
<Title as="h5" level="h2" textTransform={"regular"}>
<Title as="h4" level="h2" textTransform={"regular"}>
{title}
</Title>
<span className={styles.chevron}>

View File

@@ -56,7 +56,7 @@ export default async function NextLevelRewardsBlock({
{ level: nextLevelRewards.level?.name }
)}
</Body>
<Title level="h4" as="h5" color="pale" textAlign="center">
<Title level="h4" as="h4" color="pale" textAlign="center">
{reward.label}
</Title>
</div>

View File

@@ -7,7 +7,7 @@ export default async function EmptyPreviousStaysBlock() {
const { formatMessage } = await getIntl()
return (
<section className={styles.container}>
<Title as="h5" level="h3" color="red" textAlign="center">
<Title as="h4" level="h3" color="red" textAlign="center">
{formatMessage({
id: "You have no previous stays.",
})}

View File

@@ -14,7 +14,7 @@ export default async function EmptyUpcomingStaysBlock() {
return (
<section className={styles.container}>
<div className={styles.titleContainer}>
<Title as="h5" level="h3" color="red" className={styles.title}>
<Title as="h4" level="h3" color="red" className={styles.title}>
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{formatMessage({ id: "Where should you go next?" })}

View File

@@ -47,7 +47,7 @@ export default function StayCard({ stay }: StayCardProps) {
height={240}
/>
<footer className={styles.footer}>
<Title as="h5" className={styles.hotel} level="h3">
<Title as="h4" className={styles.hotel} level="h3">
{hotelInformation.hotelName}
</Title>
<div className={styles.date}>

View File

@@ -14,7 +14,7 @@ export default async function EmptyUpcomingStaysBlock() {
return (
<section className={styles.container}>
<div className={styles.titleContainer}>
<Title as="h5" level="h3" color="red" className={styles.title}>
<Title as="h4" level="h3" color="red" className={styles.title}>
{formatMessage({ id: "You have no upcoming stays." })}
<span className={styles.burgundyTitle}>
{formatMessage({ id: "Where should you go next?" })}

View File

@@ -47,10 +47,10 @@ export default function BookingWidgetClient({
const bookingWidgetSearchData: BookingWidgetSearchParams | undefined =
searchParams
? (getFormattedUrlQueryParams(new URLSearchParams(searchParams), {
adults: "number",
age: "number",
bed: "number",
}) as BookingWidgetSearchParams)
adults: "number",
age: "number",
bed: "number",
}) as BookingWidgetSearchParams)
: undefined
const getLocationObj = (destination: string): Location | undefined => {
@@ -67,18 +67,20 @@ export default function BookingWidgetClient({
return undefined
}
const reqFromDate = bookingWidgetSearchData?.fromDate?.toString()
const reqToDate = bookingWidgetSearchData?.toDate?.toString()
const isDateParamValid =
bookingWidgetSearchData?.fromDate &&
bookingWidgetSearchData?.toDate &&
dt(bookingWidgetSearchData?.toDate.toString()).isAfter(
dt(bookingWidgetSearchData?.fromDate.toString())
)
reqFromDate &&
reqToDate &&
dt(reqFromDate).isAfter(dt().subtract(1, "day")) &&
dt(reqToDate).isAfter(dt(reqFromDate))
const selectedLocation = bookingWidgetSearchData
? getLocationObj(
(bookingWidgetSearchData.city ??
bookingWidgetSearchData.hotel) as string
)
(bookingWidgetSearchData.city ??
bookingWidgetSearchData.hotel) as string
)
: undefined
const methods = useForm<BookingWidgetSchema>({

View File

@@ -0,0 +1,22 @@
import { serverClient } from "@/lib/trpc/server"
import StaticPage from ".."
export default async function CollectionPage() {
const collectionPageRes =
await serverClient().contentstack.collectionPage.get()
if (!collectionPageRes) {
return null
}
const { tracking, collectionPage } = collectionPageRes
return (
<StaticPage
content={collectionPage}
tracking={tracking}
pageType="collection"
/>
)
}

View File

@@ -0,0 +1,17 @@
import { serverClient } from "@/lib/trpc/server"
import StaticPage from ".."
export default async function ContentPage() {
const contentPageRes = await serverClient().contentstack.contentPage.get()
if (!contentPageRes) {
return null
}
const { tracking, contentPage } = contentPageRes
return (
<StaticPage content={contentPage} tracking={tracking} pageType="content" />
)
}

View File

@@ -1,5 +1,3 @@
import { serverClient } from "@/lib/trpc/server"
import Blocks from "@/components/Blocks"
import Hero from "@/components/Hero"
import Sidebar from "@/components/Sidebar"
@@ -8,21 +6,22 @@ import Preamble from "@/components/TempDesignSystem/Text/Preamble"
import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK"
import styles from "./contentPage.module.css"
import { staticPageVariants } from "./variants"
export default async function ContentPage() {
const contentPageRes = await serverClient().contentstack.contentPage.get()
import styles from "./staticPage.module.css"
if (!contentPageRes) {
return null
}
import type { StaticPageProps } from "./staticPage"
const { tracking, contentPage } = contentPageRes
const { blocks, hero_image, header, sidebar } = contentPage
export default function StaticPage({
content,
tracking,
pageType,
}: StaticPageProps) {
const { blocks, hero_image, header } = content
return (
<>
<section className={styles.contentPage}>
<section className={staticPageVariants({ pageType })}>
<header className={styles.header}>
<div className={styles.headerContent}>
{header ? (
@@ -54,7 +53,9 @@ export default async function ContentPage() {
{blocks ? <Blocks blocks={blocks} /> : null}
</main>
{sidebar?.length ? <Sidebar blocks={sidebar} /> : null}
{"sidebar" in content && content.sidebar?.length ? (
<Sidebar blocks={content.sidebar} />
) : null}
</div>
</section>

View File

@@ -1,4 +1,4 @@
.contentPage {
.page {
padding-bottom: var(--Spacing-x9);
}
@@ -32,20 +32,27 @@
}
.contentContainer {
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
}
.content .contentContainer {
display: grid;
grid-template-areas:
"main"
"sidebar";
gap: var(--Spacing-x4);
align-items: start;
padding: var(--Spacing-x4) var(--Spacing-x2) 0;
}
.mainContent {
grid-area: main;
display: grid;
gap: var(--Spacing-x4);
width: 100%;
gap: var(--Spacing-x6);
}
.content .mainContent {
grid-area: main;
}
@media (min-width: 768px) {
@@ -58,12 +65,20 @@
.heroContainer {
padding: var(--Spacing-x4) 0;
}
.contentContainer {
max-width: var(--max-width-content);
padding: var(--Spacing-x4) 0 0;
margin: 0 auto;
}
.content .contentContainer {
grid-template-areas: "main sidebar";
grid-template-columns: var(--max-width-text-block) 1fr;
gap: var(--Spacing-x9);
max-width: var(--max-width-content);
margin: 0 auto;
padding: var(--Spacing-x4) 0 0;
}
.mainContent {
gap: var(--Spacing-x9);
}
}

View File

@@ -0,0 +1,15 @@
import { staticPageVariants } from "./variants"
import type { VariantProps } from "class-variance-authority"
import type { TrackingSDKPageData } from "@/types/components/tracking"
import type { CollectionPage } from "@/types/trpc/routers/contentstack/collectionPage"
import type { ContentPage } from "@/types/trpc/routers/contentstack/contentPage"
export interface StaticPageProps
extends Omit<React.HTMLAttributes<HTMLDivElement>, "content">,
VariantProps<typeof staticPageVariants> {
pageType?: "collection" | "content"
content: CollectionPage["collection_page"] | ContentPage["content_page"]
tracking: TrackingSDKPageData
}

View File

@@ -0,0 +1,15 @@
import { cva } from "class-variance-authority"
import styles from "./staticPage.module.css"
export const staticPageVariants = cva(styles.page, {
variants: {
pageType: {
collection: styles.collection,
content: styles.content,
},
},
defaultVariants: {
pageType: "content",
},
})

View File

@@ -14,6 +14,7 @@
outline: none;
padding: 0;
width: 100%;
text-align: left;
}
.body {

View File

@@ -27,6 +27,7 @@
outline: none;
padding: 0;
width: 100%;
text-align: left;
}
.body {
opacity: 0.8;

View File

@@ -11,7 +11,12 @@ import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import styles from "./sectionAccordion.module.css"
import {
StepEnum,
StepStoreKeys,
} from "@/types/components/hotelReservation/enterDetails/step"
import { SectionAccordionProps } from "@/types/components/hotelReservation/selectRate/sectionAccordion"
import { BreakfastPackageEnum } from "@/types/enums/breakfast"
export default function SectionAccordion({
header,
@@ -23,9 +28,27 @@ export default function SectionAccordion({
const currentStep = useEnterDetailsStore((state) => state.currentStep)
const [isComplete, setIsComplete] = useState(false)
const [isOpen, setIsOpen] = useState(false)
const isValid = useEnterDetailsStore((state) => state.isValid[step])
const navigate = useEnterDetailsStore((state) => state.navigate)
const stepData = useEnterDetailsStore((state) => state.userData)
const stepStoreKey = StepStoreKeys[step]
const [title, setTitle] = useState(label)
useEffect(() => {
if (step === StepEnum.selectBed) {
const value = stepData.bedType
value && setTitle(value)
}
// If breakfast step, check if an option has been selected
if (step === StepEnum.breakfast && stepData.breakfast) {
const value = stepData.breakfast
if (value === BreakfastPackageEnum.NO_BREAKFAST) {
setTitle(intl.formatMessage({ id: "No breakfast" }))
} else {
setTitle(intl.formatMessage({ id: "Breakfast buffet" }))
}
}
}, [stepData, stepStoreKey, step, intl])
useEffect(() => {
// We need to set the state on mount because of hydration errors
@@ -39,7 +62,6 @@ export default function SectionAccordion({
function onModify() {
navigate(step)
}
return (
<section className={styles.wrapper} data-open={isOpen} data-step={step}>
<div className={styles.iconWrapper}>
@@ -65,7 +87,7 @@ export default function SectionAccordion({
className={styles.selection}
color="uiTextHighContrast"
>
{label}
{title}
</Subtitle>
</div>
{isComplete && !isOpen && (

View File

@@ -20,7 +20,7 @@ export default function SelectionCard({
return (
<div className={styles.card}>
<div>
<Title className={styles.name} as="h5" level="h3">
<Title className={styles.name} as="h4" level="h3">
{title}
</Title>
<div className={styles.nameInfo}>i</div>

View File

@@ -1,6 +1,6 @@
"use client"
import ContentstackLivePreview from "@contentstack/live-preview-utils"
import { ContentstackLivePreview } from "@contentstack/live-preview-utils"
import { useEffect } from "react"
export default function InitLivePreview() {

View File

@@ -60,7 +60,7 @@ export default function Sidebar({ blocks }: SidebarProps) {
<TeaserCard
title={block.teaser_card.heading}
description={block.teaser_card.body_text}
style={block.teaser_card.theme}
intent={block.teaser_card.theme}
key={block.teaser_card.system.uid}
primaryButton={block.teaser_card.primaryButton}
secondaryButton={block.teaser_card.secondaryButton}

View File

@@ -41,6 +41,7 @@
.content {
margin: var(--Spacing-x0) var(--Spacing-x4);
position: absolute;
display: grid;
gap: var(--Spacing-x2);
}

View File

@@ -35,7 +35,7 @@ export default function LoyaltyCard({
focalPoint={image.focalPoint}
/>
) : null}
<Title as="h5" level="h3" textAlign="center">
<Title as="h4" level="h3" textAlign="center">
{heading}
</Title>
{bodyText ? <Body textAlign="center">{bodyText}</Body> : null}

View File

@@ -41,7 +41,7 @@ function SidePeek({
if (isSSR) {
return (
<div>
<div className={styles.visuallyHidden}>
<h2>{title}</h2>
{children}
</div>

View File

@@ -21,6 +21,12 @@
}
}
.visuallyHidden {
position: absolute;
opacity: 0;
visibility: hidden;
}
.overlay {
position: fixed;
top: 0;
@@ -40,7 +46,7 @@
height: 100vh;
background-color: var(--Base-Background-Primary-Normal);
z-index: var(--sidepeek-z-index);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.85);
outline: none;
}
.modal[data-entering] {

View File

@@ -21,7 +21,7 @@ export default function TeaserCardSidepeek({
const { heading, content, primary_button, secondary_button } = sidePeekContent
return (
<>
<div>
<Button
onPress={() => setSidePeekIsOpen(true)}
theme="base"
@@ -77,6 +77,6 @@ export default function TeaserCardSidepeek({
)}
</div>
</SidePeek>
</>
</div>
)
}

View File

@@ -19,25 +19,23 @@ export default function TeaserCard({
sidePeekButton,
sidePeekContent,
image,
style = "default",
intent,
alwaysStack = false,
className,
}: TeaserCardProps) {
const cardClasses = teaserCardVariants({ style, alwaysStack, className })
const classNames = teaserCardVariants({ intent, alwaysStack, className })
return (
<article className={cardClasses}>
<article className={classNames}>
{image && (
<div className={styles.imageContainer}>
<Image
src={image.url}
alt={image.meta?.alt || ""}
className={styles.backgroundImage}
width={399}
height={201}
focalPoint={image.focalPoint}
/>
</div>
<Image
src={image.url}
alt={image.meta?.alt || ""}
className={styles.image}
width={Math.ceil(image.dimensions.aspectRatio * 200)}
height={200}
focalPoint={image.focalPoint}
/>
)}
<div className={styles.content}>
<Subtitle textAlign="left" type="two" color="black">

View File

@@ -18,24 +18,17 @@
border: 1px solid var(--Base-Border-Subtle);
}
.imageContainer {
.image {
width: 100%;
height: 12.58625rem; /* 201.38px / 16 = 12.58625rem */
overflow: hidden;
}
.backgroundImage {
width: 100%;
height: 100%;
object-fit: cover;
height: 12.5rem; /* 200px */
}
.content {
display: flex;
flex-direction: column;
display: grid;
gap: var(--Spacing-x-one-and-half);
align-items: flex-start;
padding: var(--Spacing-x2) var(--Spacing-x3);
grid-template-rows: auto 1fr auto;
flex-grow: 1;
}
.description {
@@ -53,17 +46,6 @@
width: 100%;
}
.body {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
/* line-height variables are in %, so using the value in rem instead */
max-height: calc(3 * 1.5rem);
}
@media (min-width: 1367px) {
.card:not(.alwaysStack) .ctaContainer {
grid-template-columns: repeat(auto-fit, minmax(0, 1fr));

View File

@@ -4,7 +4,7 @@ import styles from "./teaserCard.module.css"
export const teaserCardVariants = cva(styles.card, {
variants: {
style: {
intent: {
default: styles.default,
featured: styles.featured,
},
@@ -14,7 +14,7 @@ export const teaserCardVariants = cva(styles.card, {
},
},
defaultVariants: {
style: "default",
intent: "default",
alwaysStack: false,
},
})

View File

@@ -4,7 +4,9 @@
font-weight: 500;
}
.h1 {
/* Temporarily remove h1 styling until design tokens är updated */
/* .h1 {
font-family: var(--typography-Title-1-fontFamily);
font-size: clamp(
var(--typography-Title-1-Mobile-fontSize),
@@ -14,9 +16,9 @@
letter-spacing: var(--typography-Title-1-letterSpacing);
line-height: var(--typography-Title-1-lineHeight);
text-decoration: var(--typography-Title-1-textDecoration);
}
} */
.h2 {
.h1 {
font-family: var(--typography-Title-2-fontFamily);
font-size: clamp(
var(--typography-Title-2-Mobile-fontSize),
@@ -26,9 +28,10 @@
letter-spacing: var(--typography-Title-2-letterSpacing);
line-height: var(--typography-Title-2-lineHeight);
text-decoration: var(--typography-Title-2-textDecoration);
font-weight: var(--typography-Title-2-fontWeight);
}
.h3 {
.h2 {
font-family: var(--typography-Title-3-fontFamily);
font-size: clamp(
var(--typography-Title-3-Mobile-fontSize),
@@ -38,9 +41,10 @@
letter-spacing: var(--typography-Title-3-letterSpacing);
line-height: var(--typography-Title-3-lineHeight);
text-decoration: var(--typography-Title-3-textDecoration);
font-weight: var(--typography-Title-3-fontWeight);
}
.h4 {
.h3 {
font-family: var(--typography-Title-4-fontFamily);
font-size: clamp(
var(--typography-Title-4-Mobile-fontSize),
@@ -50,9 +54,10 @@
letter-spacing: var(--typography-Title-4-letterSpacing);
line-height: var(--typography-Title-4-lineHeight);
text-decoration: var(--typography-Title-4-textDecoration);
font-weight: var(--typography-Title-4-fontWeight);
}
.h5 {
.h4 {
font-family: var(--typography-Title-5-fontFamily);
font-size: clamp(
var(--typography-Title-5-Mobile-fontSize),
@@ -62,6 +67,7 @@
letter-spacing: var(--typography-Title-5-letterSpacing);
line-height: var(--typography-Title-5-lineHeight);
text-decoration: var(--typography-Title-5-textDecoration);
font-weight: var(--typography-Title-5-fontWeight);
}
.capitalize {

View File

@@ -26,7 +26,7 @@ const config = {
h2: styles.h2,
h3: styles.h3,
h4: styles.h4,
h5: styles.h5,
h5: styles.h4,
},
},
defaultVariants: {

View File

@@ -95,7 +95,6 @@ export default function useStickyPosition({
}, 100)
resizeObserver = new ResizeObserver(debouncedResizeHandler)
console.log("Initialized ResizeObserver")
}
resizeObserver.observe(document.body)

View File

@@ -5,6 +5,7 @@
#import "./Refs/Card.graphql"
#import "./Refs/LoyaltyCard.graphql"
#import "./Refs/TeaserCard.graphql"
fragment CardsGrid_ContentPage on ContentPageBlocksCardsGrid {
cards_grid {
layout
@@ -39,6 +40,38 @@ fragment CardsGrid_ContentPageRefs on ContentPageBlocksCardsGrid {
}
}
fragment CardsGrid_CollectionPage on CollectionPageBlocksCardsGrid {
cards_grid {
layout
preamble
theme
title
cardConnection(limit: 10) {
edges {
node {
__typename
...CardBlock
...TeaserCardBlock
}
}
}
}
}
fragment CardsGrid_CollectionPageRefs on CollectionPageBlocksCardsGrid {
cards_grid {
cardConnection(limit: 10) {
edges {
node {
__typename
...CardBlockRef
...TeaserCardBlockRef
}
}
}
}
}
fragment CardsGrid_LoyaltyPage on LoyaltyPageBlocksCardsGrid {
cards_grid {
layout

View File

@@ -1,8 +1,10 @@
#import "../AccountPage/Ref.graphql"
#import "../CollectionPage/Ref.graphql"
#import "../ContentPage/Ref.graphql"
#import "../LoyaltyPage/Ref.graphql"
#import "../PageLink/AccountPageLink.graphql"
#import "../PageLink/CollectionPageLink.graphql"
#import "../PageLink/ContentPageLink.graphql"
#import "../PageLink/LoyaltyPageLink.graphql"
@@ -32,6 +34,12 @@ fragment Shortcuts_AccountPage on AccountPageContentShortcuts {
}
}
fragment Shortcuts_CollectionPage on CollectionPageBlocksShortcuts {
shortcuts {
...Shortcuts
}
}
fragment Shortcuts_ContentPage on ContentPageBlocksShortcuts {
shortcuts {
...Shortcuts
@@ -65,6 +73,12 @@ fragment Shortcuts_AccountPageRefs on AccountPageContentShortcuts {
}
}
fragment Shortcuts_CollectionPageRefs on CollectionPageBlocksShortcuts {
shortcuts {
...ShortcutsRefs
}
}
fragment Shortcuts_ContentPageRefs on ContentPageBlocksShortcuts {
shortcuts {
...ShortcutsRefs

View File

@@ -68,3 +68,64 @@ fragment UspGrid_ContentPageRefs on ContentPageBlocksUspGrid {
}
}
}
fragment UspGrid_CollectionPage on CollectionPageBlocksUspGrid {
__typename
usp_grid {
cardsConnection {
edges {
node {
... on UspGrid {
usp_card {
__typename
icon
text {
embedded_itemsConnection {
totalCount
edges {
node {
__typename
...AccountPageLink
...ContentPageLink
...HotelPageLink
...LoyaltyPageLink
}
}
}
json
}
}
}
}
}
}
}
}
fragment UspGrid_CollectionPageRefs on CollectionPageBlocksUspGrid {
usp_grid {
cardsConnection {
edges {
node {
... on UspGrid {
usp_card {
text {
embedded_itemsConnection {
edges {
node {
__typename
...AccountPageRef
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,21 @@
#import "../PageLink/CollectionPageLink.graphql"
#import "../PageLink/ContentPageLink.graphql"
#import "../PageLink/HotelPageLink.graphql"
#import "../PageLink/LoyaltyPageLink.graphql"
fragment NavigationLinks on CollectionPageHeader {
navigation_links {
title
linkConnection {
edges {
node {
__typename
...HotelPageLink
...CollectionPageLink
...ContentPageLink
...LoyaltyPageLink
}
}
}
}
}

View File

@@ -0,0 +1,7 @@
#import "../System.graphql"
fragment CollectionPageRef on CollectionPage {
system {
...System
}
}

View File

@@ -0,0 +1,12 @@
#import "../System.graphql"
fragment CollectionPageLink on CollectionPage {
title
url
system {
...System
}
web {
original_url
}
}

View File

@@ -14,6 +14,14 @@ query GetLoyaltyPageSettings($uid: String!, $locale: String!) {
}
}
query GetCollectionPageSettings($uid: String!, $locale: String!) {
collection_page(uid: $uid, locale: $locale) {
page_settings {
hide_booking_widget
}
}
}
query GetContentPageSettings($uid: String!, $locale: String!) {
content_page(uid: $uid, locale: $locale) {
page_settings {

View File

@@ -0,0 +1,28 @@
#import "../../Fragments/Breadcrumbs/Breadcrumbs.graphql"
#import "../../Fragments/System.graphql"
query GetCollectionPageBreadcrumbs($locale: String!, $uid: String!) {
collection_page(locale: $locale, uid: $uid) {
web {
breadcrumbs {
...Breadcrumbs
}
}
system {
...System
}
}
}
query GetCollectionPageBreadcrumbsRefs($locale: String!, $uid: String!) {
collection_page(locale: $locale, uid: $uid) {
web {
breadcrumbs {
...BreadcrumbsRefs
}
}
system {
...System
}
}
}

View File

@@ -0,0 +1,83 @@
#import "../../Fragments/System.graphql"
#import "../../Fragments/Blocks/CardsGrid.graphql"
#import "../../Fragments/Blocks/Shortcuts.graphql"
#import "../../Fragments/Blocks/UspGrid.graphql"
#import "../../Fragments/CollectionPage/NavigationLinks.graphql"
query GetCollectionPage($locale: String!, $uid: String!) {
collection_page(uid: $uid, locale: $locale) {
hero_image
title
header {
heading
preamble
...NavigationLinks
}
blocks {
__typename
...CardsGrid_CollectionPage
...Shortcuts_CollectionPage
...UspGrid_CollectionPage
}
system {
...System
created_at
updated_at
}
}
}
query GetCollectionPageRefs($locale: String!, $uid: String!) {
collection_page(locale: $locale, uid: $uid) {
header {
navigation_links {
linkConnection {
edges {
node {
__typename
...CollectionPageRef
...ContentPageRef
...HotelPageRef
...LoyaltyPageRef
}
}
}
}
}
blocks {
__typename
...CardsGrid_CollectionPageRefs
...Shortcuts_CollectionPageRefs
...UspGrid_CollectionPageRefs
}
system {
...System
}
}
}
query GetDaDeEnUrlsCollectionPage($uid: String!) {
de: collection_page(locale: "de", uid: $uid) {
url
}
en: collection_page(locale: "en", uid: $uid) {
url
}
da: collection_page(locale: "da", uid: $uid) {
url
}
}
query GetFiNoSvUrlsCollectionPage($uid: String!) {
fi: collection_page(locale: "fi", uid: $uid) {
url
}
no: collection_page(locale: "no", uid: $uid) {
url
}
sv: collection_page(locale: "sv", uid: $uid) {
url
}
}

View File

@@ -9,6 +9,14 @@ query ResolveEntryByUrl($locale: String!, $url: String!) {
}
total
}
all_collection_page(where: { url: $url }, locale: $locale) {
items {
system {
...System
}
}
total
}
all_content_page(where: { url: $url }, locale: $locale) {
items {
system {

View File

@@ -1,5 +1,6 @@
import "server-only"
import { ContentstackLivePreview } from "@contentstack/live-preview-utils"
import { ClientError, GraphQLClient } from "graphql-request"
import { Lang } from "@/constants/languages"
@@ -16,40 +17,54 @@ export async function request<T>(
params?: RequestInit
): Promise<Data<T>> {
try {
if (params?.cache) {
client.requestConfig.cache = params.cache
}
if (params?.headers) {
client.requestConfig.headers = params.headers
}
if (params?.next) {
client.requestConfig.next = params.next
}
client.setHeaders({
access_token: env.CMS_ACCESS_TOKEN,
"Content-Type": "application/json",
})
if (env.PRINT_QUERY) {
const print = (await import("graphql/language/printer")).print
const rawResponse = await client.rawRequest<T>(
print(query as DocumentNode),
variables,
{
access_token: env.CMS_ACCESS_TOKEN,
"Content-Type": "application/json",
const previewHash = ContentstackLivePreview.hash
if (previewHash) {
client.setEndpoint(env.CMS_PREVIEW_URL)
client.setHeader("preview_token", env.CMS_PREVIEW_TOKEN)
client.setHeader("live_preview", previewHash)
} else {
if (params?.cache) {
client.requestConfig.cache = params.cache
}
if (params?.headers) {
client.requestConfig.headers = params.headers
}
if (params?.next) {
client.requestConfig.next = params.next
}
if (env.PRINT_QUERY) {
const print = (await import("graphql/language/printer")).print
const rawResponse = await client.rawRequest<T>(
print(query as DocumentNode),
variables,
{
access_token: env.CMS_ACCESS_TOKEN,
"Content-Type": "application/json",
}
)
/**
* TODO: Send to Monitoring (Logging and Metrics)
*/
console.log({
complexityLimit: rawResponse.headers.get("x-query-complexity"),
})
console.log({
referenceDepth: rawResponse.headers.get("x-reference-depth"),
})
console.log({
resolverCost: rawResponse.headers.get("x-resolver-cost"),
})
return {
data: rawResponse.data,
}
)
/**
* TODO: Send to Monitoring (Logging and Metrics)
*/
console.log({
complexityLimit: rawResponse.headers.get("x-query-complexity"),
})
console.log({
referenceDepth: rawResponse.headers.get("x-reference-depth"),
})
console.log({ resolverCost: rawResponse.headers.get("x-resolver-cost") })
return {
data: rawResponse.data,
}
}
@@ -78,10 +93,6 @@ export async function request<T>(
const response = await client.request<T>({
document: query,
requestHeaders: {
access_token: env.CMS_ACCESS_TOKEN,
"Content-Type": "application/json",
},
variables,
})

View File

@@ -1,6 +1,6 @@
import "server-only"
import ContentstackLivePreview from "@contentstack/live-preview-utils"
import { ContentstackLivePreview } from "@contentstack/live-preview-utils"
import { request as graphqlRequest } from "graphql-request"
import { env } from "@/env/server"

View File

@@ -2,7 +2,6 @@ import { NextResponse } from "next/server"
import { notFound } from "@/server/errors/next"
import { resolve as resolveEntry } from "@/utils/entry"
import { findLang } from "@/utils/languages"
import { removeTrailingSlash } from "@/utils/url"
@@ -18,17 +17,21 @@ export const middleware: NextMiddleware = async (request) => {
const pathWithoutTrailingSlash = removeTrailingSlash(nextUrl.pathname)
const pathNameWithoutLang = pathWithoutTrailingSlash.replace(`/${lang}`, "")
const contentTypePathName = pathWithoutTrailingSlash.replace(`/${lang}`, "")
const isPreview = request.nextUrl.pathname.includes("/preview")
const searchParams = new URLSearchParams(request.nextUrl.searchParams)
const { contentType, uid } = await fetchAndCacheEntry(
pathNameWithoutLang,
isPreview
? contentTypePathName.replace("/preview", "")
: contentTypePathName,
lang
)
if (!contentType || !uid) {
throw notFound(
`Unable to resolve CMS entry for locale "${lang}": ${pathNameWithoutLang}`
`Unable to resolve CMS entry for locale "${lang}": ${contentTypePathName}`
)
}
const headers = getDefaultRequestHeaders(request)
@@ -37,9 +40,9 @@ export const middleware: NextMiddleware = async (request) => {
const isCurrent = contentType ? contentType.indexOf("current") >= 0 : false
if (request.nextUrl.pathname.includes("/preview")) {
if (isPreview) {
if (isCurrent) {
searchParams.set("uri", pathNameWithoutLang.replace("/preview", ""))
searchParams.set("uri", contentTypePathName.replace("/preview", ""))
return NextResponse.rewrite(
new URL(`/${lang}/preview-current?${searchParams.toString()}`, nextUrl),
{
@@ -65,7 +68,7 @@ export const middleware: NextMiddleware = async (request) => {
if (isCurrent) {
searchParams.set("uid", uid)
searchParams.set("uri", pathNameWithoutLang)
searchParams.set("uri", contentTypePathName)
return NextResponse.rewrite(
new URL(
`/${lang}/current-content-page?${searchParams.toString()}`,

437
package-lock.json generated
View File

@@ -10,7 +10,7 @@
"hasInstallScript": true,
"dependencies": {
"@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.24",
"@contentstack/live-preview-utils": "^1.4.0",
"@contentstack/live-preview-utils": "^2.0.4",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.4",
"@netlify/plugin-nextjs": "^5.1.1",
@@ -118,16 +118,15 @@
}
},
"node_modules/@auth/core": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.32.0.tgz",
"integrity": "sha512-3+ssTScBd+1fd0/fscAyQN1tSygXzuhysuVVzB942ggU4mdfiTbv36P0ccVnExKWYJKvu3E2r3/zxXCCAmTOrg==",
"license": "ISC",
"version": "0.37.2",
"resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.2.tgz",
"integrity": "sha512-kUvzyvkcd6h1vpeMAojK2y7+PAV5H+0Cc9+ZlKYDFhDY31AlvsB+GW5vNO4qE3Y07KeQgvNO9U0QUx/fN62kBw==",
"dependencies": {
"@panva/hkdf": "^1.1.1",
"@panva/hkdf": "^1.2.1",
"@types/cookie": "0.6.0",
"cookie": "0.6.0",
"jose": "^5.1.3",
"oauth4webapi": "^2.9.0",
"cookie": "0.7.1",
"jose": "^5.9.3",
"oauth4webapi": "^3.0.0",
"preact": "10.11.3",
"preact-render-to-string": "5.2.3"
},
@@ -2460,13 +2459,16 @@
}
},
"node_modules/@contentstack/live-preview-utils": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@contentstack/live-preview-utils/-/live-preview-utils-1.4.1.tgz",
"integrity": "sha512-CbC+Wtc+t8NkD3qTTZaxl7KHCzNCrnKtpkPoarxig1ffyGk8GeEZwW5tLyEtqQ7zsvwLA+RIuWY55gKQumJQLQ==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@contentstack/live-preview-utils/-/live-preview-utils-2.0.4.tgz",
"integrity": "sha512-+56ExpzsOau5MEEg6sy1czaQMzPmEZAyGYx+LIyqeKKCbxFmTHcvg4Ag4vJ7KUf4LCtoGMUUT/Mkf6hqSkCvDA==",
"dependencies": {
"goober": "^2.1.14",
"just-camel-case": "^4.0.2",
"lodash-es": "^4.17.21",
"morphdom": "^2.6.1",
"mustache": "^4.2.0",
"post-robot": "8.0.31",
"uuid": "^8.3.2"
}
},
@@ -3382,10 +3384,9 @@
}
},
"node_modules/@next/env": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.7.tgz",
"integrity": "sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ==",
"license": "MIT"
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.16.tgz",
"integrity": "sha512-fLrX5TfJzHCbnZ9YUSnGW63tMV3L4nSfhgOQ0iCcX21Pt+VSTDuaLsSuL8J/2XAiVA5AnzvXDpf6pMs60QxOag=="
},
"node_modules/@next/eslint-plugin-next": {
"version": "14.1.4",
@@ -3397,13 +3398,12 @@
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.7.tgz",
"integrity": "sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.16.tgz",
"integrity": "sha512-uFT34QojYkf0+nn6MEZ4gIWQ5aqGF11uIZ1HSxG+cSbj+Mg3+tYm8qXYd3dKN5jqKUm5rBVvf1PBRO/MeQ6rxw==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -3413,13 +3413,12 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.7.tgz",
"integrity": "sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.16.tgz",
"integrity": "sha512-mCecsFkYezem0QiZlg2bau3Xul77VxUD38b/auAjohMA22G9KTJneUYMv78vWoCCFkleFAhY1NIvbyjj1ncG9g==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -3429,13 +3428,12 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.7.tgz",
"integrity": "sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.16.tgz",
"integrity": "sha512-yhkNA36+ECTC91KSyZcgWgKrYIyDnXZj8PqtJ+c2pMvj45xf7y/HrgI17hLdrcYamLfVt7pBaJUMxADtPaczHA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3445,13 +3443,12 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.7.tgz",
"integrity": "sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.16.tgz",
"integrity": "sha512-X2YSyu5RMys8R2lA0yLMCOCtqFOoLxrq2YbazFvcPOE4i/isubYjkh+JCpRmqYfEuCVltvlo+oGfj/b5T2pKUA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3461,13 +3458,12 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.7.tgz",
"integrity": "sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.16.tgz",
"integrity": "sha512-9AGcX7VAkGbc5zTSa+bjQ757tkjr6C/pKS7OK8cX7QEiK6MHIIezBLcQ7gQqbDW2k5yaqba2aDtaBeyyZh1i6Q==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3477,13 +3473,12 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.7.tgz",
"integrity": "sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.16.tgz",
"integrity": "sha512-Klgeagrdun4WWDaOizdbtIIm8khUDQJ/5cRzdpXHfkbY91LxBXeejL4kbZBrpR/nmgRrQvmz4l3OtttNVkz2Sg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -3493,13 +3488,12 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.7.tgz",
"integrity": "sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.16.tgz",
"integrity": "sha512-PwW8A1UC1Y0xIm83G3yFGPiOBftJK4zukTmk7DI1CebyMOoaVpd8aSy7K6GhobzhkjYvqS/QmzcfsWG2Dwizdg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -3509,13 +3503,12 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.7.tgz",
"integrity": "sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz",
"integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==",
"cpu": [
"ia32"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -3525,13 +3518,12 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.7.tgz",
"integrity": "sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA==",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.16.tgz",
"integrity": "sha512-OA7NtfxgirCjfqt+02BqxC3MIgM/JaGjw9tOe4fyZgPsqfseNiMPnCRP44Pfs+Gpo9zPN+SXaFsgP6vk8d571A==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -3703,7 +3695,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz",
"integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -6378,8 +6369,7 @@
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"license": "MIT"
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/google.maps": {
"version": "3.58.0",
@@ -7348,9 +7338,9 @@
}
},
"node_modules/axios": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"version": "1.7.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.6",
@@ -8640,10 +8630,9 @@
"license": "MIT"
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"license": "MIT",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@@ -8764,6 +8753,22 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true
},
"node_modules/cross-domain-safe-weakmap": {
"version": "1.0.29",
"resolved": "https://registry.npmjs.org/cross-domain-safe-weakmap/-/cross-domain-safe-weakmap-1.0.29.tgz",
"integrity": "sha512-VLoUgf2SXnf3+na8NfeUFV59TRZkIJqCIATaMdbhccgtnTlSnHXkyTRwokngEGYdQXx8JbHT9GDYitgR2sdjuA==",
"dependencies": {
"cross-domain-utils": "^2.0.0"
}
},
"node_modules/cross-domain-utils": {
"version": "2.0.38",
"resolved": "https://registry.npmjs.org/cross-domain-utils/-/cross-domain-utils-2.0.38.tgz",
"integrity": "sha512-zZfi3+2EIR9l4chrEiXI2xFleyacsJf8YMLR1eJ0Veb5FTMXeJ3DpxDjZkto2FhL/g717WSELqbptNSo85UJDw==",
"dependencies": {
"zalgo-promise": "^1.0.11"
}
},
"node_modules/cross-fetch": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz",
@@ -9612,6 +9617,18 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/errno": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
@@ -11093,6 +11110,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/goober": {
"version": "2.1.16",
"resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz",
"integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==",
"peerDependencies": {
"csstype": "^3.0.10"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@@ -13259,10 +13284,9 @@
}
},
"node_modules/jose": {
"version": "5.6.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz",
"integrity": "sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==",
"license": "MIT",
"version": "5.9.6",
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz",
"integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -13840,9 +13864,9 @@
"dev": true
},
"node_modules/koa-route/node_modules/path-to-regexp": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
"integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz",
"integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==",
"dev": true,
"dependencies": {
"isarray": "0.0.1"
@@ -14020,12 +14044,15 @@
"integrity": "sha512-Ctgq2lXUpEJo5j1762NOzl2xo7z7pqmVWYai0p07LvAkQ32tbPv3wb+tcUeHEiXhKU5buM4H9MXsXo6OlM6C2g=="
},
"node_modules/lilconfig": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.0.0.tgz",
"integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/lines-and-columns": {
@@ -14036,21 +14063,21 @@
"license": "MIT"
},
"node_modules/lint-staged": {
"version": "15.2.2",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.2.tgz",
"integrity": "sha512-TiTt93OPh1OZOsb5B7k96A/ATl2AjIZo+vnzFZ6oHK5FuTk63ByDtxGQpHm+kFETjEWqgkF95M8FRXKR/LEBcw==",
"version": "15.2.10",
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz",
"integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==",
"dev": true,
"dependencies": {
"chalk": "5.3.0",
"commander": "11.1.0",
"debug": "4.3.4",
"execa": "8.0.1",
"lilconfig": "3.0.0",
"listr2": "8.0.1",
"micromatch": "4.0.5",
"pidtree": "0.6.0",
"string-argv": "0.3.2",
"yaml": "2.3.4"
"chalk": "~5.3.0",
"commander": "~12.1.0",
"debug": "~4.3.6",
"execa": "~8.0.1",
"lilconfig": "~3.1.2",
"listr2": "~8.2.4",
"micromatch": "~4.0.8",
"pidtree": "~0.6.0",
"string-argv": "~0.3.2",
"yaml": "~2.5.0"
},
"bin": {
"lint-staged": "bin/lint-staged.js"
@@ -14063,21 +14090,24 @@
}
},
"node_modules/lint-staged/node_modules/ansi-escapes": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz",
"integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz",
"integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==",
"dev": true,
"dependencies": {
"environment": "^1.0.0"
},
"engines": {
"node": ">=14.16"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"engines": {
"node": ">=12"
@@ -14111,15 +14141,15 @@
}
},
"node_modules/lint-staged/node_modules/cli-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
"dev": true,
"dependencies": {
"restore-cursor": "^4.0.0"
"restore-cursor": "^5.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -14142,18 +14172,35 @@
}
},
"node_modules/lint-staged/node_modules/commander": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
"integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"dev": true,
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/lint-staged/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dev": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/lint-staged/node_modules/emoji-regex": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz",
"integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==",
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz",
"integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==",
"dev": true
},
"node_modules/lint-staged/node_modules/execa": {
@@ -14225,16 +14272,16 @@
}
},
"node_modules/lint-staged/node_modules/listr2": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz",
"integrity": "sha512-ovJXBXkKGfq+CwmKTjluEqFi3p4h8xvkxGQQAQan22YCgef4KZ1mKGjzfGh6PL6AW5Csw0QiQPNuQyH+6Xk3hA==",
"version": "8.2.5",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz",
"integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==",
"dev": true,
"dependencies": {
"cli-truncate": "^4.0.0",
"colorette": "^2.0.20",
"eventemitter3": "^5.0.1",
"log-update": "^6.0.0",
"rfdc": "^1.3.0",
"log-update": "^6.1.0",
"rfdc": "^1.4.1",
"wrap-ansi": "^9.0.0"
},
"engines": {
@@ -14242,14 +14289,14 @@
}
},
"node_modules/lint-staged/node_modules/log-update": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz",
"integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==",
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
"dev": true,
"dependencies": {
"ansi-escapes": "^6.2.0",
"cli-cursor": "^4.0.0",
"slice-ansi": "^7.0.0",
"ansi-escapes": "^7.0.0",
"cli-cursor": "^5.0.0",
"slice-ansi": "^7.1.0",
"strip-ansi": "^7.1.0",
"wrap-ansi": "^9.0.0"
},
@@ -14303,6 +14350,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true
},
"node_modules/lint-staged/node_modules/npm-run-path": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
@@ -14346,51 +14399,36 @@
}
},
"node_modules/lint-staged/node_modules/restore-cursor": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
"dev": true,
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
"onetime": "^7.0.0",
"signal-exit": "^4.1.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/restore-cursor/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/lint-staged/node_modules/restore-cursor/node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
"dev": true,
"dependencies": {
"mimic-fn": "^2.1.0"
"mimic-function": "^5.0.0"
},
"engines": {
"node": ">=6"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lint-staged/node_modules/restore-cursor/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
"node_modules/lint-staged/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@@ -14420,9 +14458,9 @@
}
},
"node_modules/lint-staged/node_modules/string-width": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz",
"integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
"dev": true,
"dependencies": {
"emoji-regex": "^10.3.0",
@@ -14588,6 +14626,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
"node_modules/lodash.assignwith": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz",
@@ -15077,12 +15120,12 @@
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
@@ -15132,6 +15175,18 @@
"node": ">=6"
}
},
"node_modules/mimic-function": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
"dev": true,
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
@@ -15366,12 +15421,11 @@
}
},
"node_modules/next": {
"version": "14.2.7",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.7.tgz",
"integrity": "sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==",
"license": "MIT",
"version": "14.2.16",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.16.tgz",
"integrity": "sha512-LcO7WnFu6lYSvCzZoo1dB+IO0xXz5uEv52HF1IUN0IqVTUIZGHuuR10I5efiLadGt+4oZqTcNZyVVEem/TM5nA==",
"dependencies": {
"@next/env": "14.2.7",
"@next/env": "14.2.16",
"@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
@@ -15386,15 +15440,15 @@
"node": ">=18.17.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "14.2.7",
"@next/swc-darwin-x64": "14.2.7",
"@next/swc-linux-arm64-gnu": "14.2.7",
"@next/swc-linux-arm64-musl": "14.2.7",
"@next/swc-linux-x64-gnu": "14.2.7",
"@next/swc-linux-x64-musl": "14.2.7",
"@next/swc-win32-arm64-msvc": "14.2.7",
"@next/swc-win32-ia32-msvc": "14.2.7",
"@next/swc-win32-x64-msvc": "14.2.7"
"@next/swc-darwin-arm64": "14.2.16",
"@next/swc-darwin-x64": "14.2.16",
"@next/swc-linux-arm64-gnu": "14.2.16",
"@next/swc-linux-arm64-musl": "14.2.16",
"@next/swc-linux-x64-gnu": "14.2.16",
"@next/swc-linux-x64-musl": "14.2.16",
"@next/swc-win32-arm64-msvc": "14.2.16",
"@next/swc-win32-ia32-msvc": "14.2.16",
"@next/swc-win32-x64-msvc": "14.2.16"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -15416,17 +15470,16 @@
}
},
"node_modules/next-auth": {
"version": "5.0.0-beta.19",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.19.tgz",
"integrity": "sha512-YHu1igcAxZPh8ZB7GIM93dqgY6gcAzq66FOhQFheAdOx1raxNcApt05nNyNCSB6NegSiyJ4XOPsaNow4pfDmsg==",
"license": "ISC",
"version": "5.0.0-beta.25",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.25.tgz",
"integrity": "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog==",
"dependencies": {
"@auth/core": "0.32.0"
"@auth/core": "0.37.2"
},
"peerDependencies": {
"@simplewebauthn/browser": "^9.0.1",
"@simplewebauthn/server": "^9.0.2",
"next": "^14 || ^15.0.0-0",
"next": "^14.0.0-0 || ^15.0.0-0",
"nodemailer": "^6.6.5",
"react": "^18.2.0 || ^19.0.0-0"
},
@@ -15559,10 +15612,9 @@
"dev": true
},
"node_modules/oauth4webapi": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.11.1.tgz",
"integrity": "sha512-aNzOnL98bL6izG97zgnZs1PFEyO4WDVRhz2Pd066NPak44w5ESLRCYmJIyey8avSBPOMtBjhF3ZDDm7bIb7UOg==",
"license": "MIT",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.2.tgz",
"integrity": "sha512-KQZkNU+xn02lWrFu5Vjqg9E81yPtDSxUZorRHlLWVoojD+H/0GFbH59kcnz5Thdjj7c4/mYMBPj/mhvGe/kKXA==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@@ -16047,9 +16099,9 @@
}
},
"node_modules/path-to-regexp": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz",
"integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==",
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
"dev": true
},
"node_modules/path-type": {
@@ -16224,6 +16276,16 @@
"node": ">= 0.4"
}
},
"node_modules/post-robot": {
"version": "8.0.31",
"resolved": "https://registry.npmjs.org/post-robot/-/post-robot-8.0.31.tgz",
"integrity": "sha512-nUhtKgtmcgyuPm4RnIhUB3gsDYJBHOgFry3TvOxhIHpgfwYY/T69d4oB90tw4YUllFZUUwqLEv1Wgyg6eOoJ7A==",
"dependencies": {
"cross-domain-safe-weakmap": "^1.0.1",
"cross-domain-utils": "^2.0.0",
"zalgo-promise": "^1.0.3"
}
},
"node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -16365,7 +16427,6 @@
"version": "10.11.3",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz",
"integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
@@ -16375,7 +16436,6 @@
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz",
"integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==",
"license": "MIT",
"dependencies": {
"pretty-format": "^3.8.0"
},
@@ -16422,8 +16482,7 @@
"node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
"license": "MIT"
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
},
"node_modules/process": {
"version": "0.11.10",
@@ -17428,9 +17487,9 @@
}
},
"node_modules/rfdc": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz",
"integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
"dev": true
},
"node_modules/rimraf": {
@@ -19883,10 +19942,13 @@
}
},
"node_modules/yaml": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14"
}
@@ -19958,6 +20020,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zalgo-promise": {
"version": "1.0.48",
"resolved": "https://registry.npmjs.org/zalgo-promise/-/zalgo-promise-1.0.48.tgz",
"integrity": "sha512-LLHANmdm53+MucY9aOFIggzYtUdkSBFxUsy4glTTQYNyK6B3uCPWTbfiGvSrEvLojw0mSzyFJ1/RRLv+QMNdzQ=="
},
"node_modules/zod": {
"version": "3.22.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",

View File

@@ -25,7 +25,7 @@
},
"dependencies": {
"@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.24",
"@contentstack/live-preview-utils": "^1.4.0",
"@contentstack/live-preview-utils": "^2.0.4",
"@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.4",
"@netlify/plugin-nextjs": "^5.1.1",

View File

@@ -11,6 +11,7 @@ const bookingWidgetToggleSchema = z
export const validateBookingWidgetToggleSchema = z.object({
account_page: bookingWidgetToggleSchema,
loyalty_page: bookingWidgetToggleSchema,
collection_page: bookingWidgetToggleSchema,
content_page: bookingWidgetToggleSchema,
hotel_page: bookingWidgetToggleSchema,
current_blocks_page: bookingWidgetToggleSchema,

View File

@@ -2,6 +2,7 @@ import { ValueOf } from "next/dist/shared/lib/constants"
import {
GetAccountPageSettings,
GetCollectionPageSettings,
GetContentPageSettings,
GetCurrentBlocksPageSettings,
GetHotelPageSettings,
@@ -43,6 +44,9 @@ export const bookingwidgetQueryRouter = router({
case ContentTypeEnum.loyaltyPage:
GetPageSettings = GetLoyaltyPageSettings
break
case ContentTypeEnum.collectionPage:
GetPageSettings = GetCollectionPageSettings
break
case ContentTypeEnum.contentPage:
GetPageSettings = GetContentPageSettings
break

View File

@@ -50,6 +50,16 @@ export type GetLoyaltyPageBreadcrumbsRefsData = z.infer<
typeof validateLoyaltyPageBreadcrumbsRefsContentstackSchema
>
export const validateCollectionPageBreadcrumbsRefsContentstackSchema = z.object(
{
collection_page: breadcrumbsRefs,
}
)
export type GetCollectionPageBreadcrumbsRefsData = z.infer<
typeof validateCollectionPageBreadcrumbsRefsContentstackSchema
>
export const validateContentPageBreadcrumbsRefsContentstackSchema = z.object({
content_page: breadcrumbsRefs,
})
@@ -107,3 +117,11 @@ export const validateContentPageBreadcrumbsContentstackSchema = z.object({
export type GetContentPageBreadcrumbsData = z.infer<
typeof validateContentPageBreadcrumbsContentstackSchema
>
export const validateCollectionPageBreadcrumbsContentstackSchema = z.object({
collection_page: page,
})
export type GetCollectionPageBreadcrumbsData = z.infer<
typeof validateCollectionPageBreadcrumbsContentstackSchema
>

View File

@@ -2,6 +2,10 @@ import {
GetMyPagesBreadcrumbs,
GetMyPagesBreadcrumbsRefs,
} from "@/lib/graphql/Query/Breadcrumbs/AccountPage.graphql"
import {
GetCollectionPageBreadcrumbs,
GetCollectionPageBreadcrumbsRefs,
} from "@/lib/graphql/Query/Breadcrumbs/CollectionPage.graphql"
import {
GetContentPageBreadcrumbs,
GetContentPageBreadcrumbsRefs,
@@ -13,12 +17,16 @@ import {
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
GetCollectionPageBreadcrumbsData,
GetCollectionPageBreadcrumbsRefsData,
type GetContentPageBreadcrumbsData,
type GetContentPageBreadcrumbsRefsData,
type GetLoyaltyPageBreadcrumbsData,
type GetLoyaltyPageBreadcrumbsRefsData,
type GetMyPagesBreadcrumbsData,
type GetMyPagesBreadcrumbsRefsData,
validateCollectionPageBreadcrumbsContentstackSchema,
validateCollectionPageBreadcrumbsRefsContentstackSchema,
validateContentPageBreadcrumbsContentstackSchema,
validateContentPageBreadcrumbsRefsContentstackSchema,
validateLoyaltyPageBreadcrumbsContentstackSchema,
@@ -84,6 +92,48 @@ async function getLoyaltyPageBreadcrumbs(variables: Variables) {
)
}
async function getCollectionPageBreadcrumbs(variables: Variables) {
const refsResponse =
await getRefsResponse<GetCollectionPageBreadcrumbsRefsData>(
GetCollectionPageBreadcrumbsRefs,
variables
)
const validatedRefsData =
validateCollectionPageBreadcrumbsRefsContentstackSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
console.error(
`Failed to validate CollectionPpage Breadcrumbs Refs - (uid: ${variables.uid})`
)
console.error(validatedRefsData.error)
return null
}
const tags = getTags(validatedRefsData.data.collection_page, variables)
const response = await getResponse<GetCollectionPageBreadcrumbsData>(
GetCollectionPageBreadcrumbs,
variables,
tags
)
if (!response.data.collection_page.web?.breadcrumbs?.title) {
return null
}
const validatedBreadcrumbsData =
validateCollectionPageBreadcrumbsContentstackSchema.safeParse(response.data)
if (!validatedBreadcrumbsData.success) {
console.error(
`Failed to validate Collectionpage Breadcrumbs Data - (uid: ${variables.uid})`
)
console.error(validatedBreadcrumbsData.error)
return null
}
return getBreadcrumbs(
validatedBreadcrumbsData.data.collection_page,
variables.locale
)
}
async function getContentPageBreadcrumbs(variables: Variables) {
const refsResponse = await getRefsResponse<GetContentPageBreadcrumbsRefsData>(
GetContentPageBreadcrumbsRefs,
@@ -189,6 +239,8 @@ export const breadcrumbsQueryRouter = router({
switch (ctx.contentType) {
case PageTypeEnum.accountPage:
return await getMyPagesBreadcrumbs(variables)
case PageTypeEnum.collectionPage:
return await getCollectionPageBreadcrumbs(variables)
case PageTypeEnum.contentPage:
return await getContentPageBreadcrumbs(variables)
case PageTypeEnum.loyaltyPage:

View File

@@ -0,0 +1,5 @@
import { mergeRouters } from "@/server/trpc"
import { collectionPageQueryRouter } from "./query"
export const collectionPageRouter = mergeRouters(collectionPageQueryRouter)

View File

@@ -0,0 +1,121 @@
import { z } from "zod"
import { discriminatedUnionArray } from "@/lib/discriminatedUnion"
import {
cardGridRefsSchema,
cardsGridSchema,
} from "../schemas/blocks/cardsGrid"
import {
shortcutsRefsSchema,
shortcutsSchema,
} from "../schemas/blocks/shortcuts"
import { uspGridRefsSchema, uspGridSchema } from "../schemas/blocks/uspGrid"
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
import {
linkAndTitleSchema,
linkConnectionRefs,
} from "../schemas/linkConnection"
import { systemSchema } from "../schemas/system"
import { CollectionPageEnum } from "@/types/enums/collectionPage"
// Block schemas
export const collectionPageCards = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.CardsGrid),
})
.merge(cardsGridSchema)
export const collectionPageShortcuts = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.Shortcuts),
})
.merge(shortcutsSchema)
export const collectionPageUspGrid = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.UspGrid),
})
.merge(uspGridSchema)
export const blocksSchema = z.discriminatedUnion("__typename", [
collectionPageCards,
collectionPageShortcuts,
collectionPageUspGrid,
])
const navigationLinksSchema = z
.array(linkAndTitleSchema)
.nullable()
.transform((data) => {
if (!data) {
return null
}
return data
.filter((item) => !!item.link)
.map((item) => ({
url: item.link!.url,
title: item.title || item.link!.title,
}))
})
// Content Page Schema and types
export const collectionPageSchema = z.object({
collection_page: z.object({
hero_image: tempImageVaultAssetSchema,
blocks: discriminatedUnionArray(blocksSchema.options).nullable(),
title: z.string(),
header: z.object({
heading: z.string(),
preamble: z.string(),
navigation_links: navigationLinksSchema,
}),
system: systemSchema.merge(
z.object({
created_at: z.string(),
updated_at: z.string(),
})
),
}),
})
/** REFS */
const collectionPageCardsRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.CardsGrid),
})
.merge(cardGridRefsSchema)
const collectionPageShortcutsRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.Shortcuts),
})
.merge(shortcutsRefsSchema)
const collectionPageUspGridRefs = z
.object({
__typename: z.literal(CollectionPageEnum.ContentStack.blocks.UspGrid),
})
.merge(uspGridRefsSchema)
const collectionPageBlockRefsItem = z.discriminatedUnion("__typename", [
collectionPageShortcutsRefs,
collectionPageCardsRefs,
collectionPageUspGridRefs,
])
const collectionPageHeaderRefs = z.object({
navigation_links: z.array(linkConnectionRefs),
})
export const collectionPageRefsSchema = z.object({
collection_page: z.object({
header: collectionPageHeaderRefs,
blocks: discriminatedUnionArray(
collectionPageBlockRefsItem.options
).nullable(),
system: systemSchema,
}),
})

View File

@@ -0,0 +1,78 @@
import { Lang } from "@/constants/languages"
import { GetCollectionPage } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "@/lib/graphql/request"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import { collectionPageSchema } from "./output"
import {
fetchCollectionPageRefs,
generatePageTags,
getCollectionPageCounter,
validateCollectionPageRefs,
} from "./utils"
import {
TrackingChannelEnum,
type TrackingSDKPageData,
} from "@/types/components/tracking"
import { GetCollectionPageSchema } from "@/types/trpc/routers/contentstack/collectionPage"
export const collectionPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const { lang, uid } = ctx
const collectionPageRefsData = await fetchCollectionPageRefs(lang, uid)
const collectionPageRefs = validateCollectionPageRefs(
collectionPageRefsData,
lang,
uid
)
if (!collectionPageRefs) {
return null
}
const tags = generatePageTags(collectionPageRefs, lang)
getCollectionPageCounter.add(1, { lang, uid })
console.info(
"contentstack.collectionPage start",
JSON.stringify({
query: { lang, uid },
})
)
const response = await request<GetCollectionPageSchema>(
GetCollectionPage,
{ locale: lang, uid },
{
cache: "force-cache",
next: {
tags,
},
}
)
const collectionPage = collectionPageSchema.safeParse(response.data)
if (!collectionPage.success) {
console.error(
`Failed to validate CollectionPage Data - (lang: ${lang}, uid: ${uid})`
)
console.error(collectionPage.error?.format())
return null
}
const tracking: TrackingSDKPageData = {
pageId: collectionPage.data.collection_page.system.uid,
lang: collectionPage.data.collection_page.system.locale as Lang,
publishedDate: collectionPage.data.collection_page.system.updated_at,
createdDate: collectionPage.data.collection_page.system.created_at,
channel: TrackingChannelEnum["collection-page"],
pageType: "collectionpage",
}
return {
collectionPage: collectionPage.data.collection_page,
tracking,
}
}),
})

View File

@@ -0,0 +1,152 @@
import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import { GetCollectionPageRefs } from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { generateTag, generateTagsFromSystem } from "@/utils/generateTag"
import { collectionPageRefsSchema } from "./output"
import { CollectionPageEnum } from "@/types/enums/collectionPage"
import { System } from "@/types/requests/system"
import {
CollectionPageRefs,
GetCollectionPageRefsSchema,
} from "@/types/trpc/routers/contentstack/collectionPage"
const meter = metrics.getMeter("trpc.collectionPage")
// OpenTelemetry metrics: CollectionPage
export const getCollectionPageCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get"
)
const getCollectionPageRefsCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get"
)
const getCollectionPageRefsFailCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get-fail"
)
const getCollectionPageRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.collectionPage.get-success"
)
export async function fetchCollectionPageRefs(lang: Lang, uid: string) {
getCollectionPageRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.collectionPage.refs start",
JSON.stringify({
query: { lang, uid },
})
)
const refsResponse = await request<GetCollectionPageRefsSchema>(
GetCollectionPageRefs,
{ locale: lang, uid },
{
cache: "force-cache",
next: {
tags: [generateTag(lang, uid)],
},
}
)
if (!refsResponse.data) {
const notFoundError = notFound(refsResponse)
getCollectionPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "http_error",
error: JSON.stringify({
code: notFoundError.code,
}),
})
console.error(
"contentstack.collectionPage.refs not found error",
JSON.stringify({
query: {
lang,
uid,
},
error: { code: notFoundError.code },
})
)
throw notFoundError
}
return refsResponse.data
}
export function validateCollectionPageRefs(
data: GetCollectionPageRefsSchema,
lang: Lang,
uid: string
) {
const validatedData = collectionPageRefsSchema.safeParse(data)
if (!validatedData.success) {
getCollectionPageRefsFailCounter.add(1, {
lang,
uid,
error_type: "validation_error",
error: JSON.stringify(validatedData.error),
})
console.error(
"contentstack.collectionPage.refs validation error",
JSON.stringify({
query: { lang, uid },
error: validatedData.error,
})
)
return null
}
getCollectionPageRefsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.collectionPage.refs success",
JSON.stringify({
query: { lang, uid },
})
)
return validatedData.data
}
export function generatePageTags(
validatedData: CollectionPageRefs,
lang: Lang
): string[] {
const connections = getConnections(validatedData)
return [
generateTagsFromSystem(lang, connections),
generateTag(lang, validatedData.collection_page.system.uid),
].flat()
}
export function getConnections({ collection_page }: CollectionPageRefs) {
const connections: System["system"][] = [collection_page.system]
if (collection_page.blocks) {
collection_page.blocks.forEach((block) => {
switch (block.__typename) {
case CollectionPageEnum.ContentStack.blocks.Shortcuts: {
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts)
}
break
}
case CollectionPageEnum.ContentStack.blocks.CardsGrid: {
if (block.cards_grid.length) {
connections.push(...block.cards_grid)
}
break
}
case CollectionPageEnum.ContentStack.blocks.UspGrid: {
if (block.usp_grid.length) {
connections.push(...block.usp_grid)
}
}
}
})
}
return connections
}

View File

@@ -162,6 +162,18 @@ export function getConnections({ content_page }: ContentPageRefs) {
}
}
break
case ContentPageEnum.ContentStack.blocks.CardsGrid: {
if (block.cards_grid.length) {
connections.push(...block.cards_grid)
}
break
}
case ContentPageEnum.ContentStack.blocks.DynamicContent: {
if (block.dynamic_content.link) {
connections.push(block.dynamic_content.link)
}
break
}
case ContentPageEnum.ContentStack.blocks.Shortcuts: {
if (block.shortcuts.shortcuts.length) {
connections.push(...block.shortcuts.shortcuts)

View File

@@ -4,6 +4,7 @@ import { accountPageRouter } from "./accountPage"
import { baseRouter } from "./base"
import { bookingwidgetRouter } from "./bookingwidget"
import { breadcrumbsRouter } from "./breadcrumbs"
import { collectionPageRouter } from "./collectionPage"
import { contentPageRouter } from "./contentPage"
import { hotelPageRouter } from "./hotelPage"
import { languageSwitcherRouter } from "./languageSwitcher"
@@ -21,6 +22,7 @@ export const contentstackRouter = router({
hotelPage: hotelPageRouter,
languageSwitcher: languageSwitcherRouter,
loyaltyPage: loyaltyPageRouter,
collectionPage: collectionPageRouter,
contentPage: contentPageRouter,
myPages: myPagesRouter,
metaData: metaDataRouter,

View File

@@ -7,6 +7,10 @@ import {
GetDaDeEnUrlsAccountPage,
GetFiNoSvUrlsAccountPage,
} from "@/lib/graphql/Query/AccountPage/AccountPage.graphql"
import {
GetDaDeEnUrlsCollectionPage,
GetFiNoSvUrlsCollectionPage,
} from "@/lib/graphql/Query/CollectionPage/CollectionPage.graphql"
import {
GetDaDeEnUrlsContentPage,
GetFiNoSvUrlsContentPage,
@@ -88,6 +92,10 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) {
daDeEnDocument = GetDaDeEnUrlsContentPage
fiNoSvDocument = GetFiNoSvUrlsContentPage
break
case PageTypeEnum.collectionPage:
daDeEnDocument = GetDaDeEnUrlsCollectionPage
fiNoSvDocument = GetFiNoSvUrlsCollectionPage
break
default:
console.error(`type: [${options.contentType}]`)
console.error(`Trying to get a content type that is not supported`)

View File

@@ -11,6 +11,7 @@ import { imageContainerSchema } from "./imageContainer"
import { BlocksEnums } from "@/types/enums/blocks"
import { CardsGridEnum, CardsGridLayoutEnum } from "@/types/enums/cardsGrid"
import { scriptedCardThemeEnum } from "@/types/enums/scriptedCard"
export const cardBlockSchema = z.object({
__typename: z.literal(CardsGridEnum.cards.Card),
@@ -91,7 +92,22 @@ export const teaserCardBlockSchema = z.object({
has_secondary_button: z.boolean().default(false),
secondary_button: buttonSchema,
})
.optional(),
.optional()
.transform((data) => {
if (!data) {
return undefined
}
return {
...data,
primary_button: data.has_primary_button
? data.primary_button
: undefined,
secondary_button: data.has_secondary_button
? data.secondary_button
: undefined,
}
}),
system: systemSchema,
})
@@ -146,7 +162,7 @@ export const cardsGridSchema = z.object({
}),
layout: z.nativeEnum(CardsGridLayoutEnum),
preamble: z.string().optional().default(""),
theme: z.enum(["one", "two", "three"]).nullable(),
theme: z.nativeEnum(scriptedCardThemeEnum).nullable(),
title: z.string().optional().default(""),
})
.transform((data) => {

View File

@@ -5,6 +5,7 @@ import * as pageLinks from "@/server/routers/contentstack/schemas/pageLinks"
const linkUnionSchema = z.discriminatedUnion("__typename", [
pageLinks.contentPageSchema,
pageLinks.collectionPageSchema,
pageLinks.hotelPageSchema,
pageLinks.loyaltyPageSchema,
])

View File

@@ -30,6 +30,16 @@ export const extendedPageLinkSchema = pageLinkSchema.merge(
}),
})
)
export const collectionPageSchema = z
.object({
__typename: z.literal(ContentEnum.blocks.CollectionPage),
})
.merge(extendedPageLinkSchema)
export const collectionPageRefSchema = z.object({
__typename: z.literal(ContentEnum.blocks.CollectionPage),
system: systemSchema,
})
export const contentPageSchema = z
.object({
@@ -67,6 +77,7 @@ export const loyaltyPageRefSchema = z.object({
type Data =
| z.output<typeof accountPageSchema>
| z.output<typeof contentPageSchema>
| z.output<typeof collectionPageSchema>
| z.output<typeof hotelPageSchema>
| z.output<typeof loyaltyPageSchema>
| Object
@@ -83,6 +94,7 @@ export function transform(data: Data) {
url: removeMultipleSlashes(`/${data.system.locale}/${data.url}`),
}
case ContentEnum.blocks.ContentPage:
case ContentEnum.blocks.CollectionPage:
case ContentEnum.blocks.LoyaltyPage:
// TODO: Once all links use this transform
// `web` can be removed and not to be worried
@@ -102,6 +114,7 @@ export function transform(data: Data) {
type RefData =
| z.output<typeof accountPageRefSchema>
| z.output<typeof collectionPageRefSchema>
| z.output<typeof contentPageRefSchema>
| z.output<typeof hotelPageRefSchema>
| z.output<typeof loyaltyPageRefSchema>
@@ -112,6 +125,7 @@ export function transformRef(data: RefData) {
switch (data.__typename) {
case ContentEnum.blocks.AccountPage:
case ContentEnum.blocks.ContentPage:
case ContentEnum.blocks.CollectionPage:
case ContentEnum.blocks.HotelPage:
case ContentEnum.blocks.LoyaltyPage:
return data.system

View File

@@ -7,6 +7,7 @@ import {
transformCardBlockRefs,
} from "../blocks/cardsGrid"
import { scriptedCardThemeEnum } from "@/types/enums/scriptedCard"
import { SidebarEnums } from "@/types/enums/sidebar"
export const scriptedCardsSchema = z.object({
@@ -16,17 +17,7 @@ export const scriptedCardsSchema = z.object({
.default(SidebarEnums.blocks.ScriptedCard),
scripted_card: z
.object({
theme: z
.enum([
"one",
"two",
"three",
"primaryDim",
"primaryDark",
"primaryInverted",
"primaryStrong",
])
.nullable(),
theme: z.nativeEnum(scriptedCardThemeEnum).nullable(),
scripted_cardConnection: z.object({
edges: z.array(
z.object({

View File

@@ -1,4 +1,4 @@
import { CardsGrid } from "@/types/trpc/routers/contentstack/blocks"
import type { CardsGrid } from "@/types/trpc/routers/contentstack/blocks"
export interface CardsGridProps extends Pick<CardsGrid, "cards_grid"> {
firstItem?: boolean

View File

@@ -1,8 +1,13 @@
import type { Block as AccountPageBlock } from "@/types/trpc/routers/contentstack/accountPage"
import type { Block as CollectionPageBlock } from "@/types/trpc/routers/contentstack/collectionPage"
import type { Block as ContentPageBlock } from "@/types/trpc/routers/contentstack/contentPage"
import type { Block as LoyaltyPageBlock } from "@/types/trpc/routers/contentstack/loyaltyPage"
export type Blocks = AccountPageBlock | ContentPageBlock | LoyaltyPageBlock
export type Blocks =
| AccountPageBlock
| CollectionPageBlock
| ContentPageBlock
| LoyaltyPageBlock
export interface BlocksProps {
blocks: Blocks[]

View File

@@ -4,3 +4,10 @@ export enum StepEnum {
details = "details",
payment = "payment",
}
export const StepStoreKeys: Record<StepEnum, "bedType" | "breakfast" | null> = {
"select-bed": "bedType",
breakfast: "breakfast",
details: null,
payment: null,
}

View File

@@ -5,6 +5,7 @@ import type { Lang } from "@/constants/languages"
export enum TrackingChannelEnum {
"scandic-friends" = "scandic-friends",
"static-content-page" = "static-content-page",
"collection-page" = "collection-page",
}
export type TrackingChannel = keyof typeof TrackingChannelEnum

View File

@@ -0,0 +1,9 @@
export namespace CollectionPageEnum {
export namespace ContentStack {
export const enum blocks {
CardsGrid = "CollectionPageBlocksCardsGrid",
Shortcuts = "CollectionPageBlocksShortcuts",
UspGrid = "CollectionPageBlocksUspGrid",
}
}
}

View File

@@ -1,6 +1,7 @@
export namespace ContentEnum {
export const enum blocks {
AccountPage = "AccountPage",
CollectionPage = "CollectionPage",
ContentPage = "ContentPage",
HotelPage = "HotelPage",
ImageContainer = "ImageContainer",

View File

@@ -0,0 +1,9 @@
export enum scriptedCardThemeEnum {
one = "one",
two = "two",
three = "three",
primaryDim = "primaryDim",
primaryDark = "primaryDark",
primaryInverted = "primaryInverted",
primaryStrong = "primaryStrong",
}

View File

@@ -17,7 +17,11 @@ export type StatusParams = {
}
export type ContentTypeParams = {
contentType: "loyalty-page" | "content-page" | "hotel-page"
contentType:
| "loyalty-page"
| "content-page"
| "hotel-page"
| "collection-page"
}
export type ContentTypeWebviewParams = {

View File

@@ -2,6 +2,7 @@ export enum ContentTypeEnum {
accountPage = "account_page",
loyaltyPage = "loyalty_page",
hotelPage = "hotel_page",
collectionPage = "collection_page",
contentPage = "content_page",
currentBlocksPage = "current_blocks_page",
}

View File

@@ -14,6 +14,7 @@ const entryResolveSchema = z.object({
export const validateEntryResolveSchema = z.object({
all_account_page: entryResolveSchema,
all_collection_page: entryResolveSchema,
all_content_page: entryResolveSchema,
all_loyalty_page: entryResolveSchema,
all_current_blocks_page: entryResolveSchema,

View File

@@ -3,5 +3,6 @@ export enum PageTypeEnum {
loyaltyPage = "loyalty-page",
hotelPage = "hotel-page",
contentPage = "content-page",
collectionPage = "collection-page",
currentBlocksPage = "current-blocks-page",
}

View File

@@ -0,0 +1,20 @@
import { z } from "zod"
import {
blocksSchema,
collectionPageRefsSchema,
collectionPageSchema,
} from "@/server/routers/contentstack/collectionPage/output"
export interface GetCollectionPageRefsSchema
extends z.input<typeof collectionPageRefsSchema> {}
export interface CollectionPageRefs
extends z.output<typeof collectionPageRefsSchema> {}
export interface GetCollectionPageSchema
extends z.input<typeof collectionPageSchema> {}
export interface CollectionPage extends z.output<typeof collectionPageSchema> {}
export type Block = z.output<typeof blocksSchema>