Merged in fix/header-parallel-route (pull request #238)

Static pages sync

Approved-by: Michael Zetterberg
This commit is contained in:
Christel Westerberg
2024-06-18 09:43:01 +00:00
committed by Michael Zetterberg
67 changed files with 759 additions and 278 deletions

View File

@@ -0,0 +1,14 @@
"use client"
import { useParams } from "next/navigation"
import { baseUrls } from "@/constants/routes/baseUrls"
import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher"
import { LangParams } from "@/types/params"
export default function Error() {
const params = useParams<LangParams>()
return <LanguageSwitcher urls={baseUrls} lang={params.lang} />
}

View File

@@ -0,0 +1,9 @@
import { serverClient } from "@/lib/trpc/server"
import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher"
export default async function LanguageSwitcherRoute() {
const data = await serverClient().contentstack.languageSwitcher.get()
return <LanguageSwitcher urls={data.urls} lang={data.lang} />
}

View File

@@ -0,0 +1,21 @@
import { ReactNode } from "react"
import Header from "@/components/Current/Header"
import { LangParams, PageArgs } from "@/types/params"
export default function HeaderLayout({
params,
languageSwitcher,
children,
}: PageArgs<LangParams> & {
languageSwitcher: ReactNode
children: ReactNode
}) {
return (
<>
<Header lang={params.lang} languageSwitcher={languageSwitcher} />
{children}
</>
)
}

View File

@@ -0,0 +1,3 @@
export default function EmptyHeaderPage() {
return null
}

View File

@@ -0,0 +1,5 @@
"use client"
export default function Error() {
return null
}

View File

@@ -0,0 +1,15 @@
import { baseUrls } from "@/constants/routes/baseUrls"
import Header from "@/components/Current/Header"
import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher"
import { LangParams, PageArgs } from "@/types/params"
export default async function HeaderPage({ params }: PageArgs<LangParams>) {
return (
<Header
lang={params.lang}
languageSwitcher={<LanguageSwitcher urls={baseUrls} lang={params.lang} />}
/>
)
}

View File

@@ -1,14 +0,0 @@
import { serverClient } from "@/lib/trpc/server"
import Desktop from "@/components/Current/Header/LanguageSwitcher/Desktop"
import Mobile from "@/components/Current/Header/LanguageSwitcher/Mobile"
export default async function LanguageSwitcher() {
const data = await serverClient().contentstack.languageSwitcher.get()
return (
<>
<Desktop currentLanguage={data.lang} urls={data.urls} />
<Mobile currentLanguage={data.lang} urls={data.urls} />
</>
)
}

View File

@@ -1,3 +0,0 @@
export default async function DefaultLanguageSwitcher() {
return null
}

View File

@@ -7,7 +7,6 @@ import TrpcProvider from "@/lib/trpc/Provider"
import AdobeScript from "@/components/Current/AdobeScript" import AdobeScript from "@/components/Current/AdobeScript"
import Footer from "@/components/Current/Footer" import Footer from "@/components/Current/Footer"
import Header from "@/components/Current/Header"
import VwoScript from "@/components/Current/VwoScript" import VwoScript from "@/components/Current/VwoScript"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import ServerIntlProvider from "@/i18n/Provider" import ServerIntlProvider from "@/i18n/Provider"
@@ -24,10 +23,10 @@ export const metadata: Metadata = {
export default async function RootLayout({ export default async function RootLayout({
children, children,
params, params,
languageSwitcher, header,
}: React.PropsWithChildren< }: React.PropsWithChildren<
LayoutArgs<LangParams> & { LayoutArgs<LangParams> & {
languageSwitcher: React.ReactNode header: React.ReactNode
} }
>) { >) {
const { defaultLocale, locale, messages } = await getIntl() const { defaultLocale, locale, messages } = await getIntl()
@@ -55,12 +54,9 @@ export default async function RootLayout({
<body> <body>
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}> <ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
<TrpcProvider lang={params.lang}> <TrpcProvider lang={params.lang}>
<Header {header}
lang={params.lang}
languageSwitcher={languageSwitcher}
/>
{children} {children}
<Footer /> <Footer lang={params.lang} />
</TrpcProvider> </TrpcProvider>
</ServerIntlProvider> </ServerIntlProvider>
<Script id="page-tracking">{` <Script id="page-tracking">{`

View File

@@ -0,0 +1,11 @@
import { Lang } from "@/constants/languages"
import NotFound from "@/components/Current/NotFound"
import { LangParams, PageArgs } from "@/types/params"
export default function NotFoundPage({ params }: PageArgs<LangParams>) {
const lang = params.lang || Lang.en
return <NotFound lang={lang} />
}

View File

@@ -1,14 +1,9 @@
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import Desktop from "@/components/Current/Header/LanguageSwitcher/Desktop" import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher"
import Mobile from "@/components/Current/Header/LanguageSwitcher/Mobile"
export default async function LanguageSwitcher() { export default async function LanguageSwitcherRoute() {
const data = await serverClient().contentstack.languageSwitcher.get() const data = await serverClient().contentstack.languageSwitcher.get()
return (
<> return <LanguageSwitcher urls={data.urls} lang={data.lang} />
<Desktop currentLanguage={data.lang} urls={data.urls} />
<Mobile currentLanguage={data.lang} urls={data.urls} />
</>
)
} }

View File

@@ -1,3 +0,0 @@
export default function DefaultLanguageSwitcher() {
return null
}

View File

@@ -0,0 +1,14 @@
"use client"
import { useParams } from "next/navigation"
import { baseUrls } from "@/constants/routes/baseUrls"
import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher"
import { LangParams } from "@/types/params"
export default function Error() {
const params = useParams<LangParams>()
return <LanguageSwitcher urls={baseUrls} lang={params.lang} />
}

View File

@@ -5,7 +5,6 @@ import { GetCurrentBlockPageTrackingData } from "@/lib/graphql/Query/CurrentBloc
import { request } from "@/lib/graphql/request" import { request } from "@/lib/graphql/request"
import ContentPage from "@/components/Current/ContentPage" import ContentPage from "@/components/Current/ContentPage"
import Header from "@/components/Current/Header"
import Tracking from "@/components/Current/Tracking" import Tracking from "@/components/Current/Tracking"
import type { LangParams, PageArgs, UriParams } from "@/types/params" import type { LangParams, PageArgs, UriParams } from "@/types/params"
@@ -27,9 +26,7 @@ export default async function CurrentContentPage({
locale: params.lang, locale: params.lang,
url: searchParams.uri, url: searchParams.uri,
}, },
{ { tags: [`${searchParams.uri}-${params.lang}`] }
next: { tags: [`${searchParams.uri}-${params.lang}`] },
}
) )
if (!response.data?.all_current_blocks_page?.total) { if (!response.data?.all_current_blocks_page?.total) {
@@ -43,9 +40,7 @@ export default async function CurrentContentPage({
const pageDataForTracking = await request<TrackingData>( const pageDataForTracking = await request<TrackingData>(
GetCurrentBlockPageTrackingData, GetCurrentBlockPageTrackingData,
{ uid: response.data.all_current_blocks_page.items[0].system.uid }, { uid: response.data.all_current_blocks_page.items[0].system.uid },
{ { tags: [`${searchParams.uri}-en`] }
next: { tags: [`${searchParams.uri}-en`] },
}
) )
const pageData = response.data.all_current_blocks_page.items[0] const pageData = response.data.all_current_blocks_page.items[0]

View File

@@ -17,8 +17,7 @@ import type { LangParams, LayoutArgs } from "@/types/params"
export const fetchCache = "default-no-store" export const fetchCache = "default-no-store"
export const metadata: Metadata = { export const metadata: Metadata = {
description: "New web", title: "Scandic Hotels",
title: "Scandic Hotels New Web",
} }
export default async function RootLayout({ export default async function RootLayout({
@@ -85,8 +84,8 @@ export default async function RootLayout({
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}> <ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
<Header lang={params.lang} languageSwitcher={languageSwitcher} /> <Header lang={params.lang} languageSwitcher={languageSwitcher} />
{children} {children}
<Footer lang={params.lang} />
</ServerIntlProvider> </ServerIntlProvider>
<Footer />
<Script id="page-tracking">{` <Script id="page-tracking">{`
typeof _satellite !== "undefined" && _satellite.pageBottom(); typeof _satellite !== "undefined" && _satellite.pageBottom();
`}</Script> `}</Script>

View File

@@ -1,11 +1,13 @@
import { getIntl } from "@/i18n" "use client"
export default async function NotFound() { import { useParams } from "next/navigation"
const { formatMessage } = await getIntl()
return ( import NotFound from "@/components/Current/NotFound"
<main>
<h1>{formatMessage({ id: "Not found" })}</h1> import { LangParams } from "@/types/params"
<p>{formatMessage({ id: "Could not find requested resource" })}</p>
</main> export default function NotFoundPage() {
) const { lang } = useParams<LangParams>()
return <NotFound lang={lang} />
} }

View File

@@ -53,7 +53,7 @@ export default function RootLayout({
<LangPopup lang={params.lang} /> <LangPopup lang={params.lang} />
<SkipToMainContent /> <SkipToMainContent />
{children} {children}
<Footer /> <Footer lang={params.lang} />
</body> </body>
</html> </html>
) )

View File

@@ -1,18 +0,0 @@
import type { Metadata } from "next"
import type { LangParams, LayoutArgs } from "@/types/params"
export const metadata: Metadata = {
title: "Test Site",
}
export default function RootLayout({
children,
params,
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
}

View File

@@ -1,15 +0,0 @@
import type { Metadata } from "next"
export const metadata: Metadata = {
title: "Hello World",
}
export default function HelloWorldTestPage() {
return (
<main>
<header>
<h1>Hello World!</h1>
</header>
</main>
)
}

View File

@@ -12,13 +12,13 @@ export default function Contact({ sections, system: { locale } }: ContactNode) {
const visitingAddressMessage = getVisitingAddressMessage(locale) const visitingAddressMessage = getVisitingAddressMessage(locale)
return ( return (
<section> <section>
{sections.map((section) => { {sections.map((section, idx) => {
switch (section.__typename) { switch (section.__typename) {
case Section.ContactBlockSectionsExtraInfo: case Section.ContactBlockSectionsExtraInfo:
return <p>{section.extra_info.text}</p> return <p>{section.extra_info.text}</p>
case Section.ContactBlockSectionsMailingAddress: case Section.ContactBlockSectionsMailingAddress:
return ( return (
<p> <p key={`section-mail-${idx}`}>
{section.mailing_address.name} {section.mailing_address.name}
<br /> <br />
{section.mailing_address.street} {section.mailing_address.street}
@@ -30,7 +30,10 @@ export default function Contact({ sections, system: { locale } }: ContactNode) {
) )
case Section.ContactBlockSectionsPhone: case Section.ContactBlockSectionsPhone:
return ( return (
<div className={styles.highlightBlock}> <div
className={styles.highlightBlock}
key={`section-phone-${idx}`}
>
<h3>{section.phone.title}</h3> <h3>{section.phone.title}</h3>
<div className={styles.phoneContainer}> <div className={styles.phoneContainer}>
<svg <svg
@@ -55,10 +58,14 @@ export default function Contact({ sections, system: { locale } }: ContactNode) {
</div> </div>
) )
case Section.ContactBlockSectionsTitle: case Section.ContactBlockSectionsTitle:
return <h2 className={styles.heading}>{section.title.text}</h2> return (
<h2 className={styles.heading} key={`section-heading-${idx}`}>
{section.title.text}
</h2>
)
case Section.ContactBlockSectionsVisitingAddress: case Section.ContactBlockSectionsVisitingAddress:
return ( return (
<p> <p key={`section-visiting-address-${idx}`}>
{visitingAddressMessage}: {section.visiting_address.street}{" "} {visitingAddressMessage}: {section.visiting_address.street}{" "}
</p> </p>
) )

View File

@@ -14,15 +14,15 @@ export default function Blocks({ blocks }: BlocksProps) {
return ( return (
<section className={styles.wrapper}> <section className={styles.wrapper}>
{blocks.map((block) => { {blocks.map((block, idx) => {
const type = block.__typename const type = block.__typename
switch (type) { switch (type) {
case BlocksTypenameEnum.CurrentBlocksPageBlocksList: case BlocksTypenameEnum.CurrentBlocksPageBlocksList:
return <List key={block.__typename} {...block} /> return <List key={`${block.__typename}-${idx}`} {...block} />
case BlocksTypenameEnum.CurrentBlocksPageBlocksPuffs: case BlocksTypenameEnum.CurrentBlocksPageBlocksPuffs:
return <Puffs key={block.__typename} {...block} /> return <Puffs key={`${block.__typename}-${idx}`} {...block} />
case BlocksTypenameEnum.CurrentBlocksPageBlocksText: case BlocksTypenameEnum.CurrentBlocksPageBlocksText:
return <Text key={block.__typename} {...block} /> return <Text key={`${block.__typename}-${idx}`} {...block} />
default: default:
console.log(`Unknown type: (${type})`) console.log(`Unknown type: (${type})`)
return null return null

View File

@@ -6,8 +6,10 @@ import Navigation from "./Navigation"
import styles from "./footer.module.css" import styles from "./footer.module.css"
export default async function Footer() { import { LangParams } from "@/types/params"
const footerData = await serverClient().contentstack.base.footer()
export default async function Footer({ lang }: LangParams) {
const footerData = await serverClient().contentstack.base.footer({ lang })
return ( return (
<footer className={styles.container}> <footer className={styles.container}>
<div className={styles.content}> <div className={styles.content}>

View File

@@ -12,7 +12,7 @@
sans-serif; sans-serif;
} }
.languageSwitcher { .toggle {
display: flex; display: flex;
color: #fff; color: #fff;
padding: 3px 15px; padding: 3px 15px;

View File

@@ -0,0 +1,19 @@
import Desktop from "./Desktop"
import Mobile from "./Mobile"
import { LangParams } from "@/types/params"
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
type LanguageSwitcherProps = LangParams & { urls: LanguageSwitcherData }
export default function LanguageSwitcher({
urls,
lang,
}: LanguageSwitcherProps) {
return (
<>
<Desktop currentLanguage={lang} urls={urls} />
<Mobile currentLanguage={lang} urls={urls} />
</>
)
}

View File

@@ -275,6 +275,7 @@
.logo { .logo {
width: 102.17px; width: 102.17px;
height: 100%; height: 100%;
padding-bottom: 4px;
} }
.listWrapper { .listWrapper {

View File

@@ -23,7 +23,9 @@ export default async function TopMenu({
</a> </a>
<ul className={styles.list}> <ul className={styles.list}>
<li className={styles.langSwitcher}>{languageSwitcher}</li> {languageSwitcher ? (
<li className={styles.langSwitcher}>{languageSwitcher}</li>
) : null}
{links.map(({ link }, i) => ( {links.map(({ link }, i) => (
<li key={link.href + i}> <li key={link.href + i}>

View File

@@ -16,7 +16,9 @@ export default async function Header({
lang, lang,
languageSwitcher, languageSwitcher,
}: LangParams & { languageSwitcher: React.ReactNode }) { }: LangParams & { languageSwitcher: React.ReactNode }) {
const data = await serverClient().contentstack.base.header() const data = await serverClient().contentstack.base.header({
lang,
})
const session = await auth() const session = await auth()
const homeHref = homeHrefs[env.NODE_ENV][lang] const homeHref = homeHrefs[env.NODE_ENV][lang]

View File

@@ -0,0 +1,131 @@
import { Lang } from "@/constants/languages"
type Texts = {
title: string
goToStartPage: {
question: string
link: string
linkText: string
}
goToDestinations: {
question: string
link: string
linkText: string
}
goToOffers: {
question: string
link: string
linkText: string
}
}
export const texts: Record<Lang, Texts> = {
en: {
title: "Sorry, page not found.",
goToStartPage: {
question: "Would you like to go back to the ",
link: "https://www.scandichotels.com/",
linkText: "startpage",
},
goToDestinations: {
question: "Or take a trip to our ",
link: "https://www.scandichotels.com/hotels",
linkText: "destinations",
},
goToOffers: {
question: " or latest ",
link: "https://www.scandichotels.com/weekend-packages-and-offers",
linkText: "offers",
},
},
sv: {
title: "Oj då, vi kunde inte hitta sidan du söker.",
goToStartPage: {
question: "Vill du gå tillbaka till ",
link: "https://www.scandichotels.se/",
linkText: "startsidan",
},
goToDestinations: {
question: "Eller resa till våra ",
link: "https://www.scandichotels.se/hotell",
linkText: "destinationer",
},
goToOffers: {
question: " eller se våra senaste ",
link: "https://www.scandichotels.se/erbjudanden-och-weekendpaket",
linkText: "erbjudanden",
},
},
de: {
title: "Tut uns leid, Seite nicht gefunden.",
goToStartPage: {
question: "Möchten Sie zurück zur ",
link: "https://www.scandichotels.de/",
linkText: "Startseite",
},
goToDestinations: {
question: "Oder machen Sie einen Ausflug zu unseren ",
link: "https://www.scandichotels.de/hotelsuche",
linkText: "Reisezielen",
},
goToOffers: {
question: " und aktuellen ",
link: "https://www.scandichotels.de/angebote-arrangements",
linkText: "Angeboten",
},
},
fi: {
title: "TValitettavasti sivua ei löydy.",
goToStartPage: {
question: "Haluaisitko mennä takaisin ",
link: "https://www.scandichotels.fi/",
linkText: "etusivulle",
},
goToDestinations: {
question: "Voit myös tutustu ",
link: "https://www.scandichotels.fi/hotellit",
linkText: "kohteisiimme",
},
goToOffers: {
question: " tai ajankohtaisiin ",
link: "https://www.scandichotels.fi/tarjoukset",
linkText: "tarjouksiimme",
},
},
no: {
title: "Oi da, vi fant ikke siden du lette etter...",
goToStartPage: {
question: "Vil du gå tilbake til ",
link: "https://www.scandichotels.no/",
linkText: "startsiden",
},
goToDestinations: {
question: "Eller ta en tur til våre ",
link: "https://www.scandichotels.no/hotell",
linkText: "destinasjoner",
},
goToOffers: {
question: " eller siste ",
link: "https://www.scandichotels.no/hotelltilbud",
linkText: "tilbud",
},
},
da: {
title: "Hov - siden kan ikke findes!",
goToStartPage: {
question: "Vil du gå tilbage til ",
link: "https://www.scandichotels.dk/",
linkText: "startsiden",
},
goToDestinations: {
question: "Eller tag en tur til vores ",
link: "https://www.scandichotels.dk/hoteller",
linkText: "destinationer",
},
goToOffers: {
question: " eller seneste ",
link: "https://www.scandichotels.dk/tilbud-og-hotelpakker",
linkText: "tilbud",
},
},
}

View File

@@ -0,0 +1,48 @@
import { texts } from "./Texts"
import styles from "./notFound.module.css"
import { LangParams } from "@/types/params"
export default function NotFound({ lang }: LangParams) {
const infoTexts = texts[lang]
return (
<div className={styles.container}>
<div className={styles.content}>
<h1 className={styles.header}>{infoTexts.title}</h1>
<div className={styles.pitch}>
<p className={styles.text}>
{infoTexts.goToStartPage.question}
<a
className={styles.link}
title={infoTexts.goToStartPage.linkText}
href={infoTexts.goToStartPage.link}
>
{infoTexts.goToStartPage.linkText}
</a>
?
</p>
<p className={styles.text}>
{infoTexts.goToDestinations.question}
<a
className={styles.link}
title={infoTexts.goToDestinations.linkText}
href={infoTexts.goToDestinations.link}
>
{infoTexts.goToDestinations.linkText}
</a>
{infoTexts.goToOffers.question}
<a
className={styles.link}
title={infoTexts.goToOffers.linkText}
href={infoTexts.goToOffers.link}
>
{infoTexts.goToOffers.linkText}
</a>
.
</p>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,70 @@
.container {
margin-top: 0;
background: var(--Main-Grey-White);
position: relative;
border-top: 50px solid var(--Main-Grey-White);
background-clip: content-box;
}
.content {
position: relative;
text-align: center;
box-sizing: content-box;
max-width: 1200px;
margin: 0 auto;
padding: 70px 30px;
}
.header {
font-family:
brandon text,
Arial,
Helvetica,
sans-serif;
font-size: 32px;
line-height: 1;
margin: 0;
text-transform: uppercase;
font-weight: 400;
color: #483729;
}
.pitch {
margin-top: 32px;
margin-bottom: 16px;
}
.text {
font-family:
Helvetica Neue,
Roboto,
Helvetica,
Arial,
sans-serif;
font-weight: 300;
line-height: normal;
text-transform: none;
font-size: 20px;
color: #333;
}
.link {
color: #00838e;
text-decoration: none;
}
@media screen and (min-width: 740px) {
.content {
text-align: left;
}
}
@media screen and (min-width: 950px) {
.header {
font-size: 46px;
}
.text {
font-size: 24px;
}
}

View File

@@ -37,7 +37,10 @@ export default function BenefitCard({
</div> </div>
<div className={styles.benefitComparison}> <div className={styles.benefitComparison}>
{comparedValues.map((benefit, idx) => ( {comparedValues.map((benefit, idx) => (
<div key={idx} className={styles.comparisonItem}> <div
key={`${benefit.valueDetails}-${idx}`}
className={styles.comparisonItem}
>
<BenefitValue benefit={benefit} /> <BenefitValue benefit={benefit} />
</div> </div>
))} ))}

View File

@@ -29,8 +29,9 @@ export default function BenefitList({ levels }: BenefitListProps) {
<BenefitCard <BenefitCard
title={benefit.name} title={benefit.name}
description={benefit.description} description={benefit.description}
comparedValues={levelBenefits.map((benefit) => { comparedValues={levelBenefits.map((benefit, idx) => {
return { return {
key: `${benefit.name}-${idx}`,
value: benefit.value, value: benefit.value,
unlocked: benefit.unlocked, unlocked: benefit.unlocked,
valueDetails: benefit.valueDetails, valueDetails: benefit.valueDetails,

View File

@@ -14,10 +14,15 @@ export function Blocks({ lang, blocks }: BlocksProps & LangParams) {
const firstItem = idx === 0 const firstItem = idx === 0
switch (block.__typename) { switch (block.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid: case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return <CardsGrid cards_grid={block.cards_grid} /> return (
<CardsGrid
key={`${block.cards_grid.title}-${idx}`}
cards_grid={block.cards_grid}
/>
)
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent: case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent:
return ( return (
<section> <section key={`${block.__typename}-${idx}`}>
<JsonToHtml <JsonToHtml
nodes={block.content.content.json.children} nodes={block.content.content.json.children}
embeds={block.content.content.embedded_itemsConnection.edges} embeds={block.content.content.embedded_itemsConnection.edges}
@@ -39,6 +44,7 @@ export function Blocks({ lang, blocks }: BlocksProps & LangParams) {
<DynamicContentBlock <DynamicContentBlock
dynamicContent={dynamicContent} dynamicContent={dynamicContent}
firstItem={firstItem} firstItem={firstItem}
key={`${block.dynamic_content.title}-${idx}`}
/> />
) )
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts: case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
@@ -49,6 +55,7 @@ export function Blocks({ lang, blocks }: BlocksProps & LangParams) {
return ( return (
<Shortcuts <Shortcuts
firstItem={firstItem} firstItem={firstItem}
key={`${block.shortcuts.title}-${idx}`}
shortcuts={shortcuts} shortcuts={shortcuts}
title={block.shortcuts.title} title={block.shortcuts.title}
subtitle={block.shortcuts.preamble} subtitle={block.shortcuts.preamble}

View File

@@ -13,7 +13,7 @@ export function Blocks({ blocks }: BlocksProps) {
switch (block.__typename) { switch (block.__typename) {
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent: case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent:
return ( return (
<section> <section key={`${block.__typename}-${idx}`}>
<JsonToHtml <JsonToHtml
nodes={block.content.content.json.children} nodes={block.content.content.json.children}
embeds={block.content.content.embedded_itemsConnection.edges} embeds={block.content.content.embedded_itemsConnection.edges}
@@ -25,19 +25,26 @@ export function Blocks({ blocks }: BlocksProps) {
<DynamicContentBlock <DynamicContentBlock
dynamicContent={block.dynamic_content} dynamicContent={block.dynamic_content}
firstItem={firstItem} firstItem={firstItem}
key={`${block.dynamic_content.title}-${idx}`}
/> />
) )
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts: case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
return ( return (
<Shortcuts <Shortcuts
firstItem={firstItem} firstItem={firstItem}
key={`${block.shortcuts.title}-${idx}`}
shortcuts={block.shortcuts.shortcuts} shortcuts={block.shortcuts.shortcuts}
subtitle={block.shortcuts.preamble} subtitle={block.shortcuts.preamble}
title={block.shortcuts.title} title={block.shortcuts.title}
/> />
) )
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid: case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
return <CardsGrid cards_grid={block.cards_grid} /> return (
<CardsGrid
cards_grid={block.cards_grid}
key={`${block.cards_grid.title}-${idx}`}
/>
)
default: default:
return null return null
} }

View File

@@ -1,3 +1,5 @@
import { login } from "@/constants/routes/handleAuth"
import { ScandicFriends } from "@/components/Levels" import { ScandicFriends } from "@/components/Levels"
import Button from "@/components/TempDesignSystem/Button" import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
@@ -11,11 +13,12 @@ import Contact from "./Contact"
import styles from "./joinLoyalty.module.css" import styles from "./joinLoyalty.module.css"
import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar" import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar"
import { LangParams } from "@/types/params"
export default async function JoinLoyaltyContact({ export default async function JoinLoyaltyContact({
block, block,
lang, lang,
}: JoinLoyaltyContactProps) { }: JoinLoyaltyContactProps & LangParams) {
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
return ( return (
<section className={styles.container}> <section className={styles.container}>
@@ -28,11 +31,9 @@ export default async function JoinLoyaltyContact({
<Body textAlign="center">{block.preamble}</Body> <Body textAlign="center">{block.preamble}</Body>
) : null} ) : null}
<Button asChild className={styles.link} intent="primary"> <Button asChild className={styles.link} intent="primary">
<Link href="#"> <Link href="#">{formatMessage({ id: "Join Scandic Friends" })}</Link>
{formatMessage({ id: "Join Scandic Friends" })}
</Link>
</Button> </Button>
<Link href={`/${lang}/login`}> <Link href={login[lang]}>
<Footnote textAlign="center" textTransform="bold"> <Footnote textAlign="center" textTransform="bold">
{formatMessage({ id: "Already a friend?" })} <br /> {formatMessage({ id: "Already a friend?" })} <br />
{formatMessage({ id: "Click here to log in" })} {formatMessage({ id: "Click here to log in" })}

View File

@@ -6,15 +6,22 @@ import styles from "./sidebar.module.css"
import { SidebarTypenameEnum } from "@/types/components/loyalty/enums" import { SidebarTypenameEnum } from "@/types/components/loyalty/enums"
import { SidebarProps } from "@/types/components/loyalty/sidebar" import { SidebarProps } from "@/types/components/loyalty/sidebar"
import { LangParams } from "@/types/params"
export default function SidebarLoyalty({ blocks, lang }: SidebarProps) { export default function SidebarLoyalty({
blocks,
lang,
}: SidebarProps & LangParams) {
return ( return (
<aside className={styles.aside}> <aside className={styles.aside}>
{blocks.map((block) => { {blocks.map((block, idx) => {
switch (block.__typename) { switch (block.__typename) {
case SidebarTypenameEnum.LoyaltyPageSidebarContent: case SidebarTypenameEnum.LoyaltyPageSidebarContent:
return ( return (
<section className={styles.content}> <section
className={styles.content}
key={`${block.__typename}-${idx}`}
>
<JsonToHtml <JsonToHtml
embeds={block.content.content.embedded_itemsConnection.edges} embeds={block.content.content.embedded_itemsConnection.edges}
nodes={block.content.content.json.children} nodes={block.content.content.json.children}
@@ -26,6 +33,7 @@ export default function SidebarLoyalty({ blocks, lang }: SidebarProps) {
<JoinLoyaltyContact <JoinLoyaltyContact
block={block.join_loyalty_contact} block={block.join_loyalty_contact}
lang={lang} lang={lang}
key={`${block.join_loyalty_contact.title}-${idx}`}
/> />
) )
default: default:

View File

@@ -47,7 +47,7 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
} }
export default function Content({ lang, content }: ContentProps) { export default function Content({ lang, content }: ContentProps) {
return content.map((item) => { return content.map((item, idx) => {
switch (item.__typename) { switch (item.__typename) {
case ContentEntries.AccountPageContentDynamicContent: case ContentEntries.AccountPageContentDynamicContent:
const link = item.dynamic_content.link.linkConnection.edges.length const link = item.dynamic_content.link.linkConnection.edges.length
@@ -71,6 +71,7 @@ export default function Content({ lang, content }: ContentProps) {
} }
return ( return (
<DynamicComponent <DynamicComponent
key={`${item.dynamic_content.title}-${idx}`}
component={item.dynamic_content.component} component={item.dynamic_content.component}
props={componentProps} props={componentProps}
/> />
@@ -78,6 +79,7 @@ export default function Content({ lang, content }: ContentProps) {
case ContentEntries.AccountPageContentShortcuts: case ContentEntries.AccountPageContentShortcuts:
return ( return (
<Shortcuts <Shortcuts
key={`${item.shortcuts.title}-${idx}`}
shortcuts={item.shortcuts.shortcuts} shortcuts={item.shortcuts.shortcuts}
subtitle={item.shortcuts.preamble} subtitle={item.shortcuts.preamble}
title={item.shortcuts.title} title={item.shortcuts.title}
@@ -85,7 +87,7 @@ export default function Content({ lang, content }: ContentProps) {
) )
case ContentEntries.AccountPageContentTextContent: case ContentEntries.AccountPageContentTextContent:
return ( return (
<section> <section key={`${item.__typename}-${idx}`}>
<JsonToHtml <JsonToHtml
embeds={item.text_content.content.embedded_itemsConnection.edges} embeds={item.text_content.content.embedded_itemsConnection.edges}
nodes={item.text_content.content.json.children} nodes={item.text_content.content.json.children}

View File

@@ -48,7 +48,7 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
export default function Content({ lang, content }: ContentProps) { export default function Content({ lang, content }: ContentProps) {
return ( return (
<> <>
{content.map((item) => { {content.map((item, idx) => {
switch (item.__typename) { switch (item.__typename) {
case ContentEntries.AccountPageContentDynamicContent: case ContentEntries.AccountPageContentDynamicContent:
const link = item.dynamic_content.link.linkConnection.edges.length const link = item.dynamic_content.link.linkConnection.edges.length
@@ -70,6 +70,7 @@ export default function Content({ lang, content }: ContentProps) {
} }
return ( return (
<DynamicComponent <DynamicComponent
key={`${item.dynamic_content.title}-${idx}`}
component={item.dynamic_content.component} component={item.dynamic_content.component}
props={componentProps} props={componentProps}
/> />
@@ -83,6 +84,7 @@ export default function Content({ lang, content }: ContentProps) {
}) })
return ( return (
<Shortcuts <Shortcuts
key={`${item.shortcuts.title}-${idx}`}
shortcuts={shortcuts} shortcuts={shortcuts}
subtitle={item.shortcuts.preamble} subtitle={item.shortcuts.preamble}
title={item.shortcuts.title} title={item.shortcuts.title}
@@ -90,7 +92,7 @@ export default function Content({ lang, content }: ContentProps) {
) )
case ContentEntries.AccountPageContentTextContent: case ContentEntries.AccountPageContentTextContent:
return ( return (
<section> <section key={`${item.__typename}-${idx}`}>
<JsonToHtml <JsonToHtml
embeds={ embeds={
item.text_content.content.embedded_itemsConnection.edges item.text_content.content.embedded_itemsConnection.edges

View File

@@ -49,13 +49,10 @@
line-height: var(--typography-Script-2-lineHeight); line-height: var(--typography-Script-2-lineHeight);
letter-spacing: 0.48px; letter-spacing: 0.48px;
padding: var(--Spacing-x1); padding: var(--Spacing-x1);
margin: 0;
transform: rotate(-3deg); transform: rotate(-3deg);
} }
.divider {
border-bottom-color: var(--divider-color);
}
.heading { .heading {
color: var(--font-color); color: var(--font-color);
} }

View File

@@ -42,10 +42,7 @@ export default function Card({
> >
{scriptedTopTitle ? ( {scriptedTopTitle ? (
<section className={styles.scriptContainer}> <section className={styles.scriptContainer}>
<Title className={styles.scriptedTitle} level="h3"> <h3 className={styles.scriptedTitle}>{scriptedTopTitle}</h3>
{scriptedTopTitle}
</Title>
<Divider className={styles.divider} />
</section> </section>
) : null} ) : null}
<Title as="h5" className={styles.heading} level="h3"> <Title as="h5" className={styles.heading} level="h3">

View File

@@ -0,0 +1,10 @@
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
export const baseUrls: LanguageSwitcherData = {
da: { url: "/da/" },
de: { url: "/de/" },
en: { url: "/en/" },
fi: { url: "/fi/" },
no: { url: "/no/" },
sv: { url: "/sv/" },
}

View File

@@ -1,6 +0,0 @@
describe("Hello World", () => {
it("should have an h1 tag", () => {
cy.visit("/en/test")
cy.get("h1").contains("Hello World")
})
})

View File

@@ -0,0 +1,32 @@
describe("About page", () => {
it("should load in Danish", () => {
cy.visit("/da/kundeservice/sporgsmal-og-svar/om-scandics-website")
cy.get("h1").contains("SCANDICS WEBSITE SPØRGSMÅL OG SVAR")
})
it("should load in German", () => {
cy.visit(
"/de/kundenbetreuung/haufig-gestellte-fragen/nutzung-der-internetseite"
)
cy.get("h1").contains("SCANDICS WEBSEITE FRAGEN UND ANTWORTEN")
})
it("should load in English", () => {
cy.visit(
"/en/customer-service/frequently-asked-questions/using-the-website"
)
cy.get("h1").contains("SCANDIC WEBSITE QUESTIONS AND ANSWERS")
})
it("should load in Finnish", () => {
cy.visit(
"/fi/asiakaspalvelu/usein-kysytyt-kysymykset/tietoja-internetsivuista"
)
cy.get("h1").contains("SCANDIC-VERKKOSIVUT KYSYMYKSIÄ JA VASTAUKSIA")
})
it("should load in Norwegian", () => {
cy.visit("/no/kundeservice/sporsmal-og-svar/bruk-av-nettsiden")
cy.get("h1").contains("SCANDIC-NETTSTEDET SPØRSMÅL OG SVAR")
})
it("should load in Swedish", () => {
cy.visit("/sv/kundservice/fragor-och-svar/om-scandics-webbplats")
cy.get("h1").contains("SCANDICS WEBBPLATS - FRÅGOR OCH SVAR")
})
})

View File

@@ -0,0 +1,26 @@
describe("Sponsoring page", () => {
it("should load in Danish", () => {
cy.visit("/da/sponsorering")
cy.get("h1").contains("SCANDICS SYN PÅ SPONSORATER")
})
it("should load in German", () => {
cy.visit("/de/sponsoring")
cy.get("h1").contains("SPONSORING BEI SCANDIC")
})
it("should load in English", () => {
cy.visit("/en/sponsoring")
cy.get("h1").contains("SCANDIC'S TAKE ON SPONSORSHIP")
})
it("should load in Finnish", () => {
cy.visit("/fi/sponsorointi")
cy.get("h1").contains("SCANDIC JA SPONSOROINTI")
})
it("should load in Norwegian", () => {
cy.visit("/no/vi-sponser")
cy.get("h1").contains("SCANDICS SYN PÅ SPONSING")
})
it("should load in Swedish", () => {
cy.visit("/sv/vi-sponsrar")
cy.get("h1").contains("SÅ SER SCANDIC PÅ SPONSRING")
})
})

View File

@@ -0,0 +1,26 @@
describe("Wifi page", () => {
it("should load in Danish", () => {
cy.visit("/da/oplev-scandic/wifi")
cy.get("h1").contains("FRI WIFI ER EN SELVFØLGE")
})
it("should load in German", () => {
cy.visit("/de/scandic-entdecken/wlan")
cy.get("h1").contains("GRATIS WLAN IST EINE SELBSTVERSTÄNDLICHKEIT")
})
it("should load in English", () => {
cy.visit("/en/explore-scandic/wifi")
cy.get("h1").contains("FREE WI-FI GOES WITHOUT SAYING")
})
it("should load in Finnish", () => {
cy.visit("/fi/koe-scandic/maksuton-internetyhteys")
cy.get("h1").contains("MAKSUTON WI-FI")
})
it("should load in Norwegian", () => {
cy.visit("/no/utforsk-scandic/wifi")
cy.get("h1").contains("SELVFØLGELIG HAR VI GRATIS WIFI")
})
it("should load in Swedish", () => {
cy.visit("/sv/utforska-scandic/wi-fi")
cy.get("h1").contains("FRITT WIFI, SÅ KLART")
})
})

View File

@@ -18,3 +18,13 @@ import "./commands"
// Alternatively you can use CommonJS syntax: // Alternatively you can use CommonJS syntax:
// require('./commands') // require('./commands')
Cypress.on("uncaught:exception", (err) => {
// https://github.com/cypress-io/cypress/issues/27204
// Cypress and React Hydrating the document don't get along
// for some unknown reason. Hopefully, we figure out why eventually
// so we can remove this.
// Maybe React 19 (that has changes to hydration logic) might solve this.
if (/hydration|hydrating/i.test(err.message)) {
return false
}
})

View File

@@ -1,23 +1,35 @@
query GetDaDeEnUrlsCurrentBlocksPage($uid: String!) { query GetDaDeEnUrlsCurrentBlocksPage($uid: String!) {
de: current_blocks_page(uid: $uid, locale: "de") { de: all_current_blocks_page(where: { uid: $uid }, locale: "de") {
url: original_url items {
url
}
} }
en: current_blocks_page(uid: $uid, locale: "en") { en: all_current_blocks_page(where: { uid: $uid }, locale: "en") {
url: original_url items {
url
}
} }
da: current_blocks_page(uid: $uid, locale: "da") { da: all_current_blocks_page(where: { uid: $uid }, locale: "da") {
url: original_url items {
url
}
} }
} }
query GetFiNoSvUrlsCurrentBlocksPage($uid: String!) { query GetFiNoSvUrlsCurrentBlocksPage($uid: String!) {
fi: current_blocks_page(uid: $uid, locale: "fi") { fi: all_current_blocks_page(where: { uid: $uid }, locale: "fi") {
url: original_url items {
url
}
} }
no: current_blocks_page(uid: $uid, locale: "no") { no: all_current_blocks_page(where: { uid: $uid }, locale: "no") {
url: original_url items {
url
}
} }
sv: current_blocks_page(uid: $uid, locale: "sv") { sv: all_current_blocks_page(where: { uid: $uid }, locale: "sv") {
url: original_url items {
url
}
} }
} }

74
lib/graphql/_request.ts Normal file
View File

@@ -0,0 +1,74 @@
import "server-only"
import { GraphQLClient } from "graphql-request"
import { env } from "@/env/server"
import type { DocumentNode } from "graphql"
import type { Data } from "@/types/request"
export async function request<T>(
client: GraphQLClient,
query: string | DocumentNode,
variables?: {},
next?: NextFetchRequestConfig
): Promise<Data<T>> {
try {
if (next) {
client.requestConfig.next = 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,
}
}
const print = (await import("graphql/language/printer")).print
const nr = Math.random()
console.log(`START REQUEST ${nr}`)
console.time(`OUTGOING REQUEST ${nr}`)
console.log(`Sending reqeust to ${env.CMS_URL}`)
console.log(`Query:`, print(query as DocumentNode))
console.log(`Variables:`, variables)
const response = await client.request<T>({
document: query,
requestHeaders: {
access_token: env.CMS_ACCESS_TOKEN,
"Content-Type": "application/json",
},
variables,
})
console.timeEnd(`OUTGOING REQUEST ${nr}`)
console.log({ response })
return { data: response }
} catch (error) {
console.error(error)
throw new Error("Something went wrong")
}
}

View File

@@ -12,9 +12,7 @@ export async function batchRequest<T>(
try { try {
const response = await Promise.allSettled( const response = await Promise.allSettled(
queries.map((query) => queries.map((query) =>
request<T>(query.document, query.variables, { request<T>(query.document, query.variables, { tags: query.tags })
next: { tags: query.tags },
})
) )
) )

View File

@@ -0,0 +1,20 @@
import { DocumentNode } from "graphql"
import { GraphQLClient } from "graphql-request"
import { env } from "@/env/server"
import { request as _request } from "./_request"
import { Data } from "@/types/request"
const client = new GraphQLClient(env.CMS_URL, {
fetch: fetch,
})
export async function edgeRequest<T>(
query: string | DocumentNode,
variables?: {},
next?: NextFetchRequestConfig
): Promise<Data<T>> {
return _request(client, query, variables, next)
}

View File

@@ -1,16 +1,14 @@
import "server-only" import { DocumentNode } from "graphql"
import { GraphQLClient } from "graphql-request" import { GraphQLClient } from "graphql-request"
import { cache } from "react" import { cache } from "react"
import { env } from "@/env/server" import { env } from "@/env/server"
import type { DocumentNode } from "graphql" import { request as _request } from "./_request"
import type { Data } from "@/types/request" import { Data } from "@/types/request"
const client = new GraphQLClient(env.CMS_URL, { const client = new GraphQLClient(env.CMS_URL, {
cache: "force-cache",
fetch: cache(async function ( fetch: cache(async function (
url: URL | RequestInfo, url: URL | RequestInfo,
params: RequestInit | undefined params: RequestInit | undefined
@@ -22,50 +20,7 @@ const client = new GraphQLClient(env.CMS_URL, {
export async function request<T>( export async function request<T>(
query: string | DocumentNode, query: string | DocumentNode,
variables?: {}, variables?: {},
options?: Pick<RequestInit, "cache" | "next"> next?: NextFetchRequestConfig
): Promise<Data<T>> { ): Promise<Data<T>> {
if (options?.cache) { return _request(client, query, variables, next)
client.requestConfig.cache = options.cache
}
if (options?.next) {
client.requestConfig.next = options.next
}
if (env.PRINT_QUERY) {
const { print } = await import("graphql")
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,
}
}
const response = await client.request<T>({
document: query,
requestHeaders: {
access_token: env.CMS_ACCESS_TOKEN,
"Content-Type": "application/json",
},
variables,
})
return { data: response }
} }

View File

@@ -1,6 +1,6 @@
import { NextMiddleware, NextResponse } from "next/server" import { NextMiddleware, NextResponse } from "next/server"
import { findLang } from "./constants/languages" import { findLang, Lang } from "./constants/languages"
import * as authRequired from "./middlewares/authRequired" import * as authRequired from "./middlewares/authRequired"
import * as cmsContent from "./middlewares/cmsContent" import * as cmsContent from "./middlewares/cmsContent"
import * as currentWebLogin from "./middlewares/currentWebLogin" import * as currentWebLogin from "./middlewares/currentWebLogin"
@@ -16,8 +16,10 @@ export const middleware: NextMiddleware = async (request, event) => {
// Without it we shortcircuit early. // Without it we shortcircuit early.
// We use middleware-error route because notFound() requires a root layout // We use middleware-error route because notFound() requires a root layout
// which we do not want. We can move to that once all Current stuff is gone. // which we do not want. We can move to that once all Current stuff is gone.
// Default to English if no lang is found.
return NextResponse.rewrite( return NextResponse.rewrite(
new URL(`/${lang}/middleware-error/404`, request.nextUrl), new URL(`/${Lang.en}/middleware-error/404`, request.nextUrl),
{ {
status: 404, status: 404,
statusText: "Not found", statusText: "Not found",

View File

@@ -13,9 +13,9 @@ package = "netlify-plugin-cypress"
[plugins.inputs] [plugins.inputs]
configFile = "cypress.config.ts" configFile = "cypress.config.ts"
[plugins.inputs.postBuild] [plugins.inputs.postBuild]
enable = true enable = true
start = "npm start" start = "npm start"
wait-on = "http://127.0.0.1:3000/en/test" wait-on = "http://127.0.0.1:3000/en/sponsoring"
wait-on-timeout = "30" # seconds wait-on-timeout = "30" # seconds
[build.environment] [build.environment]

View File

@@ -16,8 +16,8 @@
"start": "node .next/standalone/server.js", "start": "node .next/standalone/server.js",
"test:component": "cypress open --component", "test:component": "cypress open --component",
"test:component:headless": "cypress run --component", "test:component:headless": "cypress run --component",
"test:e2e": "start-server-and-test test:setup http://127.0.0.1:3000/en/test \"cypress open --e2e\"", "test:e2e": "start-server-and-test test:setup http://127.0.0.1:3000/en/sponsoring \"cypress open --e2e\"",
"test:e2e:headless": "start-server-and-test test:setup http://127.0.0.1:3000/en/test \"cypress run --e2e\"", "test:e2e:headless": "start-server-and-test test:setup http://127.0.0.1:3000/en/sponsoring \"cypress run --e2e\"",
"test:setup": "npm run build && npm run start", "test:setup": "npm run build && npm run start",
"preinstall": "export $(cat .env.local | grep -v '^#' | xargs)", "preinstall": "export $(cat .env.local | grep -v '^#' | xargs)",
"update-dotenv": "node update-dotenv.mjs" "update-dotenv": "node update-dotenv.mjs"

File diff suppressed because one or more lines are too long

View File

@@ -37,9 +37,7 @@ export const accountPageQueryRouter = router({
uid, uid,
}, },
{ {
next: { tags: [generateRefsResponseTag(lang, uid)],
tags: [generateRefsResponseTag(lang, uid)],
},
} }
) )
@@ -71,7 +69,7 @@ export const accountPageQueryRouter = router({
locale: lang, locale: lang,
uid, uid,
}, },
{ next: { tags } } { tags }
) )
if (!response.data) { if (!response.data) {

View File

@@ -0,0 +1,5 @@
import { z } from "zod"
import { Lang } from "@/constants/languages"
export const langInput = z.object({ lang: z.nativeEnum(Lang) })

View File

@@ -9,10 +9,15 @@ import {
} from "@/lib/graphql/Query/CurrentHeader.graphql" } from "@/lib/graphql/Query/CurrentHeader.graphql"
import { request } from "@/lib/graphql/request" import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc" import { internalServerError, notFound } from "@/server/errors/trpc"
import { contentstackBaseProcedure, router } from "@/server/trpc" import {
contentstackBaseProcedure,
publicProcedure,
router,
} from "@/server/trpc"
import { generateTag } from "@/utils/generateTag" import { generateTag } from "@/utils/generateTag"
import { langInput } from "./input"
import { import {
type ContactConfigData, type ContactConfigData,
FooterDataRaw, FooterDataRaw,
@@ -47,23 +52,21 @@ export const baseQueryRouter = router({
return validatedContactConfigConfig.data.all_contact_config.items[0] return validatedContactConfigConfig.data.all_contact_config.items[0]
}), }),
header: contentstackBaseProcedure.query(async ({ ctx }) => { header: publicProcedure.input(langInput).query(async ({ input }) => {
const responseRef = await request<HeaderRefDataRaw>(GetCurrentHeaderRef, { const responseRef = await request<HeaderRefDataRaw>(GetCurrentHeaderRef, {
locale: ctx.lang, locale: input.lang,
}) })
const response = await request<HeaderDataRaw>( const response = await request<HeaderDataRaw>(
GetCurrentHeader, GetCurrentHeader,
{ locale: ctx.lang }, { locale: input.lang },
{ {
next: { tags: [
tags: [ generateTag(
generateTag( input.lang,
ctx.lang, responseRef.data.all_current_header.items[0].system.uid
responseRef.data.all_current_header.items[0].system.uid ),
), ],
],
},
} }
) )
@@ -88,25 +91,23 @@ export const baseQueryRouter = router({
logo, logo,
} as HeaderData } as HeaderData
}), }),
footer: contentstackBaseProcedure.query(async ({ ctx }) => { footer: publicProcedure.input(langInput).query(async ({ input }) => {
const responseRef = await request<FooterRefDataRaw>(GetCurrentFooterRef, { const responseRef = await request<FooterRefDataRaw>(GetCurrentFooterRef, {
locale: ctx.lang, locale: input.lang,
}) })
const response = await request<FooterDataRaw>( const response = await request<FooterDataRaw>(
GetCurrentFooter, GetCurrentFooter,
{ {
locale: ctx.lang, locale: input.lang,
}, },
{ {
next: { tags: [
tags: [ generateTag(
generateTag( input.lang,
ctx.lang, responseRef.data.all_current_footer.items[0].system.uid
responseRef.data.all_current_footer.items[0].system.uid ),
), ],
],
},
} }
) )

View File

@@ -68,9 +68,7 @@ export type Variables = {
export async function getRefsResponse<T>(query: string, variables: Variables) { export async function getRefsResponse<T>(query: string, variables: Variables) {
const refsResponse = await request<T>(query, variables, { const refsResponse = await request<T>(query, variables, {
next: { tags: [generateRefsResponseTag(variables.locale, variables.url, affix)],
tags: [generateRefsResponseTag(variables.locale, variables.url, affix)],
},
}) })
if (!refsResponse.data) { if (!refsResponse.data) {
throw notFound(refsResponse) throw notFound(refsResponse)
@@ -91,7 +89,7 @@ export async function getResponse<T>(
variables: Variables, variables: Variables,
tags: string[] tags: string[]
) { ) {
const response = await request<T>(query, variables, { next: { tags } }) const response = await request<T>(query, variables, { tags })
if (!response.data) { if (!response.data) {
throw notFound(response) throw notFound(response)
} }

View File

@@ -1,4 +1,5 @@
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { baseUrls } from "@/constants/routes/baseUrls"
import { batchRequest } from "@/lib/graphql/batchRequest" import { batchRequest } from "@/lib/graphql/batchRequest"
import { import {
GetDaDeEnUrlsAccountPage, GetDaDeEnUrlsAccountPage,
@@ -13,7 +14,7 @@ import {
GetFiNoSvUrlsLoyaltyPage, GetFiNoSvUrlsLoyaltyPage,
} from "@/lib/graphql/Query/LoyaltyPage.graphql" } from "@/lib/graphql/Query/LoyaltyPage.graphql"
import { internalServerError } from "@/server/errors/trpc" import { internalServerError } from "@/server/errors/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc" import { publicProcedure, router } from "@/server/trpc"
import { generateTag } from "@/utils/generateTag" import { generateTag } from "@/utils/generateTag"
@@ -91,12 +92,14 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) {
} }
export const languageSwitcherQueryRouter = router({ export const languageSwitcherQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { get: publicProcedure.query(async ({ ctx }) => {
if (!ctx.uid || !ctx.lang) {
return { lang: ctx.lang, urls: baseUrls }
}
const res = await getLanguageSwitcher({ const res = await getLanguageSwitcher({
contentType: ctx.contentType!, contentType: ctx.contentType!,
uid: ctx.uid, uid: ctx.uid,
}) })
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>( const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
(acc, key) => { (acc, key) => {
const item = res.data[key as Lang]?.items[0] const item = res.data[key as Lang]?.items[0]

View File

@@ -54,9 +54,7 @@ export const loyaltyPageQueryRouter = router({
uid, uid,
}, },
{ {
next: { tags: [generateRefsResponseTag(lang, uid)],
tags: [generateRefsResponseTag(lang, uid)],
},
} }
) )
@@ -90,7 +88,7 @@ export const loyaltyPageQueryRouter = router({
locale: lang, locale: lang,
uid, uid,
}, },
{ next: { tags } } { tags }
) )
if (!response.data) { if (!response.data) {

View File

@@ -60,9 +60,7 @@ export const navigationQueryRouter = router({
GetNavigationMyPagesRefs, GetNavigationMyPagesRefs,
{ locale: lang }, { locale: lang },
{ {
next: { tags: [generateRefsResponseTag(lang, "navigation_my_pages")],
tags: [generateRefsResponseTag(lang, "navigation_my_pages")],
},
} }
) )
@@ -90,7 +88,7 @@ export const navigationQueryRouter = router({
const response = await request<GetNavigationMyPagesData>( const response = await request<GetNavigationMyPagesData>(
GetNavigationMyPages, GetNavigationMyPages,
{ locale: lang }, { locale: lang },
{ next: { tags } } { tags }
) )
if (!response.data) { if (!response.data) {

View File

@@ -2,11 +2,7 @@ import { initTRPC } from "@trpc/server"
import { env } from "@/env/server" import { env } from "@/env/server"
import { import { badRequestError, sessionExpiredError } from "./errors/trpc"
badRequestError,
sessionExpiredError,
unauthorizedError,
} from "./errors/trpc"
import { transformer } from "./transformer" import { transformer } from "./transformer"
import type { Meta } from "@/types/trpc/meta" import type { Meta } from "@/types/trpc/meta"

View File

@@ -12,7 +12,7 @@ export type MainMenuProps = {
links: CurrentHeaderLink[] links: CurrentHeaderLink[]
logo: Image logo: Image
topMenuMobileLinks: TopMenuHeaderLink[] topMenuMobileLinks: TopMenuHeaderLink[]
languageSwitcher: React.ReactNode languageSwitcher: React.ReactNode | null
bookingHref: string bookingHref: string
isLoggedIn: boolean isLoggedIn: boolean
lang: Lang lang: Lang

View File

@@ -6,6 +6,6 @@ export type TopMenuProps = {
frontpageLinkText: string frontpageLinkText: string
homeHref: string homeHref: string
links: TopMenuHeaderLink[] links: TopMenuHeaderLink[]
languageSwitcher: React.ReactNode languageSwitcher: React.ReactNode | null
lang: Lang lang: Lang
} }

View File

@@ -13,7 +13,7 @@ export enum Section {
type ExtraInfo = Typename< type ExtraInfo = Typename<
{ {
extra_info: { extra_info: {
text: string text: string[]
} }
}, },
Section.ContactBlockSectionsExtraInfo Section.ContactBlockSectionsExtraInfo

View File

@@ -1,31 +1,23 @@
import { DocumentNode, print } from "graphql"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { env } from "@/env/server" import { edgeRequest } from "@/lib/graphql/edgeRequest"
import { ResolveEntryByUrl } from "@/lib/graphql/Query/ResolveEntry.graphql" import { ResolveEntryByUrl } from "@/lib/graphql/Query/ResolveEntry.graphql"
import { internalServerError } from "@/server/errors/next" import { internalServerError } from "@/server/errors/next"
import { validateEntryResolveSchema } from "@/types/requests/entry" import { validateEntryResolveSchema } from "@/types/requests/entry"
export async function resolve(url: string, lang = Lang.en) { export async function resolve(url: string, lang = Lang.en) {
const result = await fetch(env.CMS_URL, { const response = await edgeRequest(
method: "POST", ResolveEntryByUrl,
headers: { {
access_token: env.CMS_ACCESS_TOKEN, locale: lang,
"Content-Type": "application/json", url,
}, },
body: JSON.stringify({ {
query: print(ResolveEntryByUrl as DocumentNode), revalidate: 3600,
variables: { }
locale: lang, )
url,
},
}),
})
const { data } = await result.json() const validatedData = validateEntryResolveSchema.safeParse(response.data)
const validatedData = validateEntryResolveSchema.safeParse(data)
if (!validatedData.success) { if (!validatedData.success) {
throw internalServerError(validatedData.error) throw internalServerError(validatedData.error)