Merged in fix/header-parallel-route (pull request #238)
Static pages sync Approved-by: Michael Zetterberg
This commit is contained in:
@@ -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} />
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
21
app/[lang]/(live)/@header/[...paths]/layout.tsx
Normal file
21
app/[lang]/(live)/@header/[...paths]/layout.tsx
Normal 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}
|
||||
</>
|
||||
)
|
||||
}
|
||||
3
app/[lang]/(live)/@header/[...paths]/page.tsx
Normal file
3
app/[lang]/(live)/@header/[...paths]/page.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default function EmptyHeaderPage() {
|
||||
return null
|
||||
}
|
||||
5
app/[lang]/(live)/@header/error.tsx
Normal file
5
app/[lang]/(live)/@header/error.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
"use client"
|
||||
|
||||
export default function Error() {
|
||||
return null
|
||||
}
|
||||
15
app/[lang]/(live)/@header/page.tsx
Normal file
15
app/[lang]/(live)/@header/page.tsx
Normal 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} />}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -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} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export default async function DefaultLanguageSwitcher() {
|
||||
return null
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import TrpcProvider from "@/lib/trpc/Provider"
|
||||
|
||||
import AdobeScript from "@/components/Current/AdobeScript"
|
||||
import Footer from "@/components/Current/Footer"
|
||||
import Header from "@/components/Current/Header"
|
||||
import VwoScript from "@/components/Current/VwoScript"
|
||||
import { getIntl } from "@/i18n"
|
||||
import ServerIntlProvider from "@/i18n/Provider"
|
||||
@@ -24,10 +23,10 @@ export const metadata: Metadata = {
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
languageSwitcher,
|
||||
header,
|
||||
}: React.PropsWithChildren<
|
||||
LayoutArgs<LangParams> & {
|
||||
languageSwitcher: React.ReactNode
|
||||
header: React.ReactNode
|
||||
}
|
||||
>) {
|
||||
const { defaultLocale, locale, messages } = await getIntl()
|
||||
@@ -55,12 +54,9 @@ export default async function RootLayout({
|
||||
<body>
|
||||
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||
<TrpcProvider lang={params.lang}>
|
||||
<Header
|
||||
lang={params.lang}
|
||||
languageSwitcher={languageSwitcher}
|
||||
/>
|
||||
{header}
|
||||
{children}
|
||||
<Footer />
|
||||
<Footer lang={params.lang} />
|
||||
</TrpcProvider>
|
||||
</ServerIntlProvider>
|
||||
<Script id="page-tracking">{`
|
||||
|
||||
11
app/[lang]/(live)/middleware-error/404/page.tsx
Normal file
11
app/[lang]/(live)/middleware-error/404/page.tsx
Normal 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} />
|
||||
}
|
||||
@@ -1,14 +1,9 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import Desktop from "@/components/Current/Header/LanguageSwitcher/Desktop"
|
||||
import Mobile from "@/components/Current/Header/LanguageSwitcher/Mobile"
|
||||
import LanguageSwitcher from "@/components/Current/Header/LanguageSwitcher"
|
||||
|
||||
export default async function LanguageSwitcher() {
|
||||
export default async function LanguageSwitcherRoute() {
|
||||
const data = await serverClient().contentstack.languageSwitcher.get()
|
||||
return (
|
||||
<>
|
||||
<Desktop currentLanguage={data.lang} urls={data.urls} />
|
||||
<Mobile currentLanguage={data.lang} urls={data.urls} />
|
||||
</>
|
||||
)
|
||||
|
||||
return <LanguageSwitcher urls={data.urls} lang={data.lang} />
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function DefaultLanguageSwitcher() {
|
||||
return null
|
||||
}
|
||||
14
app/[lang]/(live-current)/@languageSwitcher/error.tsx
Normal file
14
app/[lang]/(live-current)/@languageSwitcher/error.tsx
Normal 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} />
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { GetCurrentBlockPageTrackingData } from "@/lib/graphql/Query/CurrentBloc
|
||||
import { request } from "@/lib/graphql/request"
|
||||
|
||||
import ContentPage from "@/components/Current/ContentPage"
|
||||
import Header from "@/components/Current/Header"
|
||||
import Tracking from "@/components/Current/Tracking"
|
||||
|
||||
import type { LangParams, PageArgs, UriParams } from "@/types/params"
|
||||
@@ -27,9 +26,7 @@ export default async function CurrentContentPage({
|
||||
locale: params.lang,
|
||||
url: searchParams.uri,
|
||||
},
|
||||
{
|
||||
next: { tags: [`${searchParams.uri}-${params.lang}`] },
|
||||
}
|
||||
{ tags: [`${searchParams.uri}-${params.lang}`] }
|
||||
)
|
||||
|
||||
if (!response.data?.all_current_blocks_page?.total) {
|
||||
@@ -43,9 +40,7 @@ export default async function CurrentContentPage({
|
||||
const pageDataForTracking = await request<TrackingData>(
|
||||
GetCurrentBlockPageTrackingData,
|
||||
{ uid: response.data.all_current_blocks_page.items[0].system.uid },
|
||||
{
|
||||
next: { tags: [`${searchParams.uri}-en`] },
|
||||
}
|
||||
{ tags: [`${searchParams.uri}-en`] }
|
||||
)
|
||||
|
||||
const pageData = response.data.all_current_blocks_page.items[0]
|
||||
|
||||
@@ -17,8 +17,7 @@ import type { LangParams, LayoutArgs } from "@/types/params"
|
||||
export const fetchCache = "default-no-store"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
description: "New web",
|
||||
title: "Scandic Hotels New Web",
|
||||
title: "Scandic Hotels",
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
@@ -85,8 +84,8 @@ export default async function RootLayout({
|
||||
<ServerIntlProvider intl={{ defaultLocale, locale, messages }}>
|
||||
<Header lang={params.lang} languageSwitcher={languageSwitcher} />
|
||||
{children}
|
||||
<Footer lang={params.lang} />
|
||||
</ServerIntlProvider>
|
||||
<Footer />
|
||||
<Script id="page-tracking">{`
|
||||
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
||||
`}</Script>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { getIntl } from "@/i18n"
|
||||
"use client"
|
||||
|
||||
export default async function NotFound() {
|
||||
const { formatMessage } = await getIntl()
|
||||
return (
|
||||
<main>
|
||||
<h1>{formatMessage({ id: "Not found" })}</h1>
|
||||
<p>{formatMessage({ id: "Could not find requested resource" })}</p>
|
||||
</main>
|
||||
)
|
||||
import { useParams } from "next/navigation"
|
||||
|
||||
import NotFound from "@/components/Current/NotFound"
|
||||
|
||||
import { LangParams } from "@/types/params"
|
||||
|
||||
export default function NotFoundPage() {
|
||||
const { lang } = useParams<LangParams>()
|
||||
|
||||
return <NotFound lang={lang} />
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function RootLayout({
|
||||
<LangPopup lang={params.lang} />
|
||||
<SkipToMainContent />
|
||||
{children}
|
||||
<Footer />
|
||||
<Footer lang={params.lang} />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -12,13 +12,13 @@ export default function Contact({ sections, system: { locale } }: ContactNode) {
|
||||
const visitingAddressMessage = getVisitingAddressMessage(locale)
|
||||
return (
|
||||
<section>
|
||||
{sections.map((section) => {
|
||||
{sections.map((section, idx) => {
|
||||
switch (section.__typename) {
|
||||
case Section.ContactBlockSectionsExtraInfo:
|
||||
return <p>{section.extra_info.text}</p>
|
||||
case Section.ContactBlockSectionsMailingAddress:
|
||||
return (
|
||||
<p>
|
||||
<p key={`section-mail-${idx}`}>
|
||||
{section.mailing_address.name}
|
||||
<br />
|
||||
{section.mailing_address.street}
|
||||
@@ -30,7 +30,10 @@ export default function Contact({ sections, system: { locale } }: ContactNode) {
|
||||
)
|
||||
case Section.ContactBlockSectionsPhone:
|
||||
return (
|
||||
<div className={styles.highlightBlock}>
|
||||
<div
|
||||
className={styles.highlightBlock}
|
||||
key={`section-phone-${idx}`}
|
||||
>
|
||||
<h3>{section.phone.title}</h3>
|
||||
<div className={styles.phoneContainer}>
|
||||
<svg
|
||||
@@ -55,10 +58,14 @@ export default function Contact({ sections, system: { locale } }: ContactNode) {
|
||||
</div>
|
||||
)
|
||||
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:
|
||||
return (
|
||||
<p>
|
||||
<p key={`section-visiting-address-${idx}`}>
|
||||
{visitingAddressMessage}: {section.visiting_address.street}{" "}
|
||||
</p>
|
||||
)
|
||||
|
||||
@@ -14,15 +14,15 @@ export default function Blocks({ blocks }: BlocksProps) {
|
||||
|
||||
return (
|
||||
<section className={styles.wrapper}>
|
||||
{blocks.map((block) => {
|
||||
{blocks.map((block, idx) => {
|
||||
const type = block.__typename
|
||||
switch (type) {
|
||||
case BlocksTypenameEnum.CurrentBlocksPageBlocksList:
|
||||
return <List key={block.__typename} {...block} />
|
||||
return <List key={`${block.__typename}-${idx}`} {...block} />
|
||||
case BlocksTypenameEnum.CurrentBlocksPageBlocksPuffs:
|
||||
return <Puffs key={block.__typename} {...block} />
|
||||
return <Puffs key={`${block.__typename}-${idx}`} {...block} />
|
||||
case BlocksTypenameEnum.CurrentBlocksPageBlocksText:
|
||||
return <Text key={block.__typename} {...block} />
|
||||
return <Text key={`${block.__typename}-${idx}`} {...block} />
|
||||
default:
|
||||
console.log(`Unknown type: (${type})`)
|
||||
return null
|
||||
|
||||
@@ -6,8 +6,10 @@ import Navigation from "./Navigation"
|
||||
|
||||
import styles from "./footer.module.css"
|
||||
|
||||
export default async function Footer() {
|
||||
const footerData = await serverClient().contentstack.base.footer()
|
||||
import { LangParams } from "@/types/params"
|
||||
|
||||
export default async function Footer({ lang }: LangParams) {
|
||||
const footerData = await serverClient().contentstack.base.footer({ lang })
|
||||
return (
|
||||
<footer className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.languageSwitcher {
|
||||
.toggle {
|
||||
display: flex;
|
||||
color: #fff;
|
||||
padding: 3px 15px;
|
||||
|
||||
19
components/Current/Header/LanguageSwitcher/index.tsx
Normal file
19
components/Current/Header/LanguageSwitcher/index.tsx
Normal 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} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -275,6 +275,7 @@
|
||||
.logo {
|
||||
width: 102.17px;
|
||||
height: 100%;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
|
||||
@@ -23,7 +23,9 @@ export default async function TopMenu({
|
||||
</a>
|
||||
|
||||
<ul className={styles.list}>
|
||||
<li className={styles.langSwitcher}>{languageSwitcher}</li>
|
||||
{languageSwitcher ? (
|
||||
<li className={styles.langSwitcher}>{languageSwitcher}</li>
|
||||
) : null}
|
||||
|
||||
{links.map(({ link }, i) => (
|
||||
<li key={link.href + i}>
|
||||
|
||||
@@ -16,7 +16,9 @@ export default async function Header({
|
||||
lang,
|
||||
languageSwitcher,
|
||||
}: LangParams & { languageSwitcher: React.ReactNode }) {
|
||||
const data = await serverClient().contentstack.base.header()
|
||||
const data = await serverClient().contentstack.base.header({
|
||||
lang,
|
||||
})
|
||||
const session = await auth()
|
||||
|
||||
const homeHref = homeHrefs[env.NODE_ENV][lang]
|
||||
|
||||
131
components/Current/NotFound/Texts.ts
Normal file
131
components/Current/NotFound/Texts.ts
Normal 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",
|
||||
},
|
||||
},
|
||||
}
|
||||
48
components/Current/NotFound/index.tsx
Normal file
48
components/Current/NotFound/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
70
components/Current/NotFound/notFound.module.css
Normal file
70
components/Current/NotFound/notFound.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,10 @@ export default function BenefitCard({
|
||||
</div>
|
||||
<div className={styles.benefitComparison}>
|
||||
{comparedValues.map((benefit, idx) => (
|
||||
<div key={idx} className={styles.comparisonItem}>
|
||||
<div
|
||||
key={`${benefit.valueDetails}-${idx}`}
|
||||
className={styles.comparisonItem}
|
||||
>
|
||||
<BenefitValue benefit={benefit} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -29,8 +29,9 @@ export default function BenefitList({ levels }: BenefitListProps) {
|
||||
<BenefitCard
|
||||
title={benefit.name}
|
||||
description={benefit.description}
|
||||
comparedValues={levelBenefits.map((benefit) => {
|
||||
comparedValues={levelBenefits.map((benefit, idx) => {
|
||||
return {
|
||||
key: `${benefit.name}-${idx}`,
|
||||
value: benefit.value,
|
||||
unlocked: benefit.unlocked,
|
||||
valueDetails: benefit.valueDetails,
|
||||
|
||||
@@ -14,10 +14,15 @@ export function Blocks({ lang, blocks }: BlocksProps & LangParams) {
|
||||
const firstItem = idx === 0
|
||||
switch (block.__typename) {
|
||||
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:
|
||||
return (
|
||||
<section>
|
||||
<section key={`${block.__typename}-${idx}`}>
|
||||
<JsonToHtml
|
||||
nodes={block.content.content.json.children}
|
||||
embeds={block.content.content.embedded_itemsConnection.edges}
|
||||
@@ -39,6 +44,7 @@ export function Blocks({ lang, blocks }: BlocksProps & LangParams) {
|
||||
<DynamicContentBlock
|
||||
dynamicContent={dynamicContent}
|
||||
firstItem={firstItem}
|
||||
key={`${block.dynamic_content.title}-${idx}`}
|
||||
/>
|
||||
)
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
|
||||
@@ -49,6 +55,7 @@ export function Blocks({ lang, blocks }: BlocksProps & LangParams) {
|
||||
return (
|
||||
<Shortcuts
|
||||
firstItem={firstItem}
|
||||
key={`${block.shortcuts.title}-${idx}`}
|
||||
shortcuts={shortcuts}
|
||||
title={block.shortcuts.title}
|
||||
subtitle={block.shortcuts.preamble}
|
||||
|
||||
@@ -13,7 +13,7 @@ export function Blocks({ blocks }: BlocksProps) {
|
||||
switch (block.__typename) {
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent:
|
||||
return (
|
||||
<section>
|
||||
<section key={`${block.__typename}-${idx}`}>
|
||||
<JsonToHtml
|
||||
nodes={block.content.content.json.children}
|
||||
embeds={block.content.content.embedded_itemsConnection.edges}
|
||||
@@ -25,19 +25,26 @@ export function Blocks({ blocks }: BlocksProps) {
|
||||
<DynamicContentBlock
|
||||
dynamicContent={block.dynamic_content}
|
||||
firstItem={firstItem}
|
||||
key={`${block.dynamic_content.title}-${idx}`}
|
||||
/>
|
||||
)
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksShortcuts:
|
||||
return (
|
||||
<Shortcuts
|
||||
firstItem={firstItem}
|
||||
key={`${block.shortcuts.title}-${idx}`}
|
||||
shortcuts={block.shortcuts.shortcuts}
|
||||
subtitle={block.shortcuts.preamble}
|
||||
title={block.shortcuts.title}
|
||||
/>
|
||||
)
|
||||
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardsGrid:
|
||||
return <CardsGrid cards_grid={block.cards_grid} />
|
||||
return (
|
||||
<CardsGrid
|
||||
cards_grid={block.cards_grid}
|
||||
key={`${block.cards_grid.title}-${idx}`}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { login } from "@/constants/routes/handleAuth"
|
||||
|
||||
import { ScandicFriends } from "@/components/Levels"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
@@ -11,11 +13,12 @@ import Contact from "./Contact"
|
||||
import styles from "./joinLoyalty.module.css"
|
||||
|
||||
import type { JoinLoyaltyContactProps } from "@/types/components/loyalty/sidebar"
|
||||
import { LangParams } from "@/types/params"
|
||||
|
||||
export default async function JoinLoyaltyContact({
|
||||
block,
|
||||
lang,
|
||||
}: JoinLoyaltyContactProps) {
|
||||
}: JoinLoyaltyContactProps & LangParams) {
|
||||
const { formatMessage } = await getIntl()
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
@@ -28,11 +31,9 @@ export default async function JoinLoyaltyContact({
|
||||
<Body textAlign="center">{block.preamble}</Body>
|
||||
) : null}
|
||||
<Button asChild className={styles.link} intent="primary">
|
||||
<Link href="#">
|
||||
{formatMessage({ id: "Join Scandic Friends" })}
|
||||
</Link>
|
||||
<Link href="#">{formatMessage({ id: "Join Scandic Friends" })}</Link>
|
||||
</Button>
|
||||
<Link href={`/${lang}/login`}>
|
||||
<Link href={login[lang]}>
|
||||
<Footnote textAlign="center" textTransform="bold">
|
||||
{formatMessage({ id: "Already a friend?" })} <br />
|
||||
{formatMessage({ id: "Click here to log in" })}
|
||||
|
||||
@@ -6,15 +6,22 @@ import styles from "./sidebar.module.css"
|
||||
|
||||
import { SidebarTypenameEnum } from "@/types/components/loyalty/enums"
|
||||
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 (
|
||||
<aside className={styles.aside}>
|
||||
{blocks.map((block) => {
|
||||
{blocks.map((block, idx) => {
|
||||
switch (block.__typename) {
|
||||
case SidebarTypenameEnum.LoyaltyPageSidebarContent:
|
||||
return (
|
||||
<section className={styles.content}>
|
||||
<section
|
||||
className={styles.content}
|
||||
key={`${block.__typename}-${idx}`}
|
||||
>
|
||||
<JsonToHtml
|
||||
embeds={block.content.content.embedded_itemsConnection.edges}
|
||||
nodes={block.content.content.json.children}
|
||||
@@ -26,6 +33,7 @@ export default function SidebarLoyalty({ blocks, lang }: SidebarProps) {
|
||||
<JoinLoyaltyContact
|
||||
block={block.join_loyalty_contact}
|
||||
lang={lang}
|
||||
key={`${block.join_loyalty_contact.title}-${idx}`}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
|
||||
@@ -47,7 +47,7 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
|
||||
}
|
||||
|
||||
export default function Content({ lang, content }: ContentProps) {
|
||||
return content.map((item) => {
|
||||
return content.map((item, idx) => {
|
||||
switch (item.__typename) {
|
||||
case ContentEntries.AccountPageContentDynamicContent:
|
||||
const link = item.dynamic_content.link.linkConnection.edges.length
|
||||
@@ -71,6 +71,7 @@ export default function Content({ lang, content }: ContentProps) {
|
||||
}
|
||||
return (
|
||||
<DynamicComponent
|
||||
key={`${item.dynamic_content.title}-${idx}`}
|
||||
component={item.dynamic_content.component}
|
||||
props={componentProps}
|
||||
/>
|
||||
@@ -78,6 +79,7 @@ export default function Content({ lang, content }: ContentProps) {
|
||||
case ContentEntries.AccountPageContentShortcuts:
|
||||
return (
|
||||
<Shortcuts
|
||||
key={`${item.shortcuts.title}-${idx}`}
|
||||
shortcuts={item.shortcuts.shortcuts}
|
||||
subtitle={item.shortcuts.preamble}
|
||||
title={item.shortcuts.title}
|
||||
@@ -85,7 +87,7 @@ export default function Content({ lang, content }: ContentProps) {
|
||||
)
|
||||
case ContentEntries.AccountPageContentTextContent:
|
||||
return (
|
||||
<section>
|
||||
<section key={`${item.__typename}-${idx}`}>
|
||||
<JsonToHtml
|
||||
embeds={item.text_content.content.embedded_itemsConnection.edges}
|
||||
nodes={item.text_content.content.json.children}
|
||||
|
||||
@@ -48,7 +48,7 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
|
||||
export default function Content({ lang, content }: ContentProps) {
|
||||
return (
|
||||
<>
|
||||
{content.map((item) => {
|
||||
{content.map((item, idx) => {
|
||||
switch (item.__typename) {
|
||||
case ContentEntries.AccountPageContentDynamicContent:
|
||||
const link = item.dynamic_content.link.linkConnection.edges.length
|
||||
@@ -70,6 +70,7 @@ export default function Content({ lang, content }: ContentProps) {
|
||||
}
|
||||
return (
|
||||
<DynamicComponent
|
||||
key={`${item.dynamic_content.title}-${idx}`}
|
||||
component={item.dynamic_content.component}
|
||||
props={componentProps}
|
||||
/>
|
||||
@@ -83,6 +84,7 @@ export default function Content({ lang, content }: ContentProps) {
|
||||
})
|
||||
return (
|
||||
<Shortcuts
|
||||
key={`${item.shortcuts.title}-${idx}`}
|
||||
shortcuts={shortcuts}
|
||||
subtitle={item.shortcuts.preamble}
|
||||
title={item.shortcuts.title}
|
||||
@@ -90,7 +92,7 @@ export default function Content({ lang, content }: ContentProps) {
|
||||
)
|
||||
case ContentEntries.AccountPageContentTextContent:
|
||||
return (
|
||||
<section>
|
||||
<section key={`${item.__typename}-${idx}`}>
|
||||
<JsonToHtml
|
||||
embeds={
|
||||
item.text_content.content.embedded_itemsConnection.edges
|
||||
|
||||
@@ -49,13 +49,10 @@
|
||||
line-height: var(--typography-Script-2-lineHeight);
|
||||
letter-spacing: 0.48px;
|
||||
padding: var(--Spacing-x1);
|
||||
margin: 0;
|
||||
transform: rotate(-3deg);
|
||||
}
|
||||
|
||||
.divider {
|
||||
border-bottom-color: var(--divider-color);
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--font-color);
|
||||
}
|
||||
|
||||
@@ -42,10 +42,7 @@ export default function Card({
|
||||
>
|
||||
{scriptedTopTitle ? (
|
||||
<section className={styles.scriptContainer}>
|
||||
<Title className={styles.scriptedTitle} level="h3">
|
||||
{scriptedTopTitle}
|
||||
</Title>
|
||||
<Divider className={styles.divider} />
|
||||
<h3 className={styles.scriptedTitle}>{scriptedTopTitle}</h3>
|
||||
</section>
|
||||
) : null}
|
||||
<Title as="h5" className={styles.heading} level="h3">
|
||||
|
||||
10
constants/routes/baseUrls.ts
Normal file
10
constants/routes/baseUrls.ts
Normal 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/" },
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
describe("Hello World", () => {
|
||||
it("should have an h1 tag", () => {
|
||||
cy.visit("/en/test")
|
||||
cy.get("h1").contains("Hello World")
|
||||
})
|
||||
})
|
||||
32
cypress/e2e/static-pages/about.cy.ts
Normal file
32
cypress/e2e/static-pages/about.cy.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
26
cypress/e2e/static-pages/sponsoring.cy.ts
Normal file
26
cypress/e2e/static-pages/sponsoring.cy.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
26
cypress/e2e/static-pages/wifi.cy.ts
Normal file
26
cypress/e2e/static-pages/wifi.cy.ts
Normal 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")
|
||||
})
|
||||
})
|
||||
@@ -18,3 +18,13 @@ import "./commands"
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// 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
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
query GetDaDeEnUrlsCurrentBlocksPage($uid: String!) {
|
||||
de: current_blocks_page(uid: $uid, locale: "de") {
|
||||
url: original_url
|
||||
de: all_current_blocks_page(where: { uid: $uid }, locale: "de") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
en: current_blocks_page(uid: $uid, locale: "en") {
|
||||
url: original_url
|
||||
en: all_current_blocks_page(where: { uid: $uid }, locale: "en") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
da: current_blocks_page(uid: $uid, locale: "da") {
|
||||
url: original_url
|
||||
da: all_current_blocks_page(where: { uid: $uid }, locale: "da") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetFiNoSvUrlsCurrentBlocksPage($uid: String!) {
|
||||
fi: current_blocks_page(uid: $uid, locale: "fi") {
|
||||
url: original_url
|
||||
fi: all_current_blocks_page(where: { uid: $uid }, locale: "fi") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
no: current_blocks_page(uid: $uid, locale: "no") {
|
||||
url: original_url
|
||||
no: all_current_blocks_page(where: { uid: $uid }, locale: "no") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
sv: current_blocks_page(uid: $uid, locale: "sv") {
|
||||
url: original_url
|
||||
sv: all_current_blocks_page(where: { uid: $uid }, locale: "sv") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
74
lib/graphql/_request.ts
Normal file
74
lib/graphql/_request.ts
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,7 @@ export async function batchRequest<T>(
|
||||
try {
|
||||
const response = await Promise.allSettled(
|
||||
queries.map((query) =>
|
||||
request<T>(query.document, query.variables, {
|
||||
next: { tags: query.tags },
|
||||
})
|
||||
request<T>(query.document, query.variables, { tags: query.tags })
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
20
lib/graphql/edgeRequest.ts
Normal file
20
lib/graphql/edgeRequest.ts
Normal 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)
|
||||
}
|
||||
@@ -1,16 +1,14 @@
|
||||
import "server-only"
|
||||
|
||||
import { DocumentNode } from "graphql"
|
||||
import { GraphQLClient } from "graphql-request"
|
||||
import { cache } from "react"
|
||||
|
||||
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, {
|
||||
cache: "force-cache",
|
||||
fetch: cache(async function (
|
||||
url: URL | RequestInfo,
|
||||
params: RequestInit | undefined
|
||||
@@ -22,50 +20,7 @@ const client = new GraphQLClient(env.CMS_URL, {
|
||||
export async function request<T>(
|
||||
query: string | DocumentNode,
|
||||
variables?: {},
|
||||
options?: Pick<RequestInit, "cache" | "next">
|
||||
next?: NextFetchRequestConfig
|
||||
): Promise<Data<T>> {
|
||||
if (options?.cache) {
|
||||
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 }
|
||||
return _request(client, query, variables, next)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 cmsContent from "./middlewares/cmsContent"
|
||||
import * as currentWebLogin from "./middlewares/currentWebLogin"
|
||||
@@ -16,8 +16,10 @@ export const middleware: NextMiddleware = async (request, event) => {
|
||||
// Without it we shortcircuit early.
|
||||
// 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.
|
||||
|
||||
// Default to English if no lang is found.
|
||||
return NextResponse.rewrite(
|
||||
new URL(`/${lang}/middleware-error/404`, request.nextUrl),
|
||||
new URL(`/${Lang.en}/middleware-error/404`, request.nextUrl),
|
||||
{
|
||||
status: 404,
|
||||
statusText: "Not found",
|
||||
|
||||
@@ -13,9 +13,9 @@ package = "netlify-plugin-cypress"
|
||||
[plugins.inputs]
|
||||
configFile = "cypress.config.ts"
|
||||
[plugins.inputs.postBuild]
|
||||
enable = true
|
||||
start = "npm start"
|
||||
wait-on = "http://127.0.0.1:3000/en/test"
|
||||
enable = true
|
||||
start = "npm start"
|
||||
wait-on = "http://127.0.0.1:3000/en/sponsoring"
|
||||
wait-on-timeout = "30" # seconds
|
||||
|
||||
[build.environment]
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"start": "node .next/standalone/server.js",
|
||||
"test:component": "cypress open --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:headless": "start-server-and-test test:setup http://127.0.0.1:3000/en/test \"cypress run --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/sponsoring \"cypress run --e2e\"",
|
||||
"test:setup": "npm run build && npm run start",
|
||||
"preinstall": "export $(cat .env.local | grep -v '^#' | xargs)",
|
||||
"update-dotenv": "node update-dotenv.mjs"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -37,9 +37,7 @@ export const accountPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, uid)],
|
||||
},
|
||||
tags: [generateRefsResponseTag(lang, uid)],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -71,7 +69,7 @@ export const accountPageQueryRouter = router({
|
||||
locale: lang,
|
||||
uid,
|
||||
},
|
||||
{ next: { tags } }
|
||||
{ tags }
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
|
||||
5
server/routers/contentstack/base/input.ts
Normal file
5
server/routers/contentstack/base/input.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
export const langInput = z.object({ lang: z.nativeEnum(Lang) })
|
||||
@@ -9,10 +9,15 @@ import {
|
||||
} from "@/lib/graphql/Query/CurrentHeader.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
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 { langInput } from "./input"
|
||||
import {
|
||||
type ContactConfigData,
|
||||
FooterDataRaw,
|
||||
@@ -47,23 +52,21 @@ export const baseQueryRouter = router({
|
||||
|
||||
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, {
|
||||
locale: ctx.lang,
|
||||
locale: input.lang,
|
||||
})
|
||||
|
||||
const response = await request<HeaderDataRaw>(
|
||||
GetCurrentHeader,
|
||||
{ locale: ctx.lang },
|
||||
{ locale: input.lang },
|
||||
{
|
||||
next: {
|
||||
tags: [
|
||||
generateTag(
|
||||
ctx.lang,
|
||||
responseRef.data.all_current_header.items[0].system.uid
|
||||
),
|
||||
],
|
||||
},
|
||||
tags: [
|
||||
generateTag(
|
||||
input.lang,
|
||||
responseRef.data.all_current_header.items[0].system.uid
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -88,25 +91,23 @@ export const baseQueryRouter = router({
|
||||
logo,
|
||||
} as HeaderData
|
||||
}),
|
||||
footer: contentstackBaseProcedure.query(async ({ ctx }) => {
|
||||
footer: publicProcedure.input(langInput).query(async ({ input }) => {
|
||||
const responseRef = await request<FooterRefDataRaw>(GetCurrentFooterRef, {
|
||||
locale: ctx.lang,
|
||||
locale: input.lang,
|
||||
})
|
||||
|
||||
const response = await request<FooterDataRaw>(
|
||||
GetCurrentFooter,
|
||||
{
|
||||
locale: ctx.lang,
|
||||
locale: input.lang,
|
||||
},
|
||||
{
|
||||
next: {
|
||||
tags: [
|
||||
generateTag(
|
||||
ctx.lang,
|
||||
responseRef.data.all_current_footer.items[0].system.uid
|
||||
),
|
||||
],
|
||||
},
|
||||
tags: [
|
||||
generateTag(
|
||||
input.lang,
|
||||
responseRef.data.all_current_footer.items[0].system.uid
|
||||
),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -68,9 +68,7 @@ export type Variables = {
|
||||
|
||||
export async function getRefsResponse<T>(query: string, variables: 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) {
|
||||
throw notFound(refsResponse)
|
||||
@@ -91,7 +89,7 @@ export async function getResponse<T>(
|
||||
variables: Variables,
|
||||
tags: string[]
|
||||
) {
|
||||
const response = await request<T>(query, variables, { next: { tags } })
|
||||
const response = await request<T>(query, variables, { tags })
|
||||
if (!response.data) {
|
||||
throw notFound(response)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { baseUrls } from "@/constants/routes/baseUrls"
|
||||
import { batchRequest } from "@/lib/graphql/batchRequest"
|
||||
import {
|
||||
GetDaDeEnUrlsAccountPage,
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
GetFiNoSvUrlsLoyaltyPage,
|
||||
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
|
||||
import { internalServerError } from "@/server/errors/trpc"
|
||||
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
|
||||
import { publicProcedure, router } from "@/server/trpc"
|
||||
|
||||
import { generateTag } from "@/utils/generateTag"
|
||||
|
||||
@@ -91,12 +92,14 @@ async function getLanguageSwitcher(options: LanguageSwitcherVariables) {
|
||||
}
|
||||
|
||||
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({
|
||||
contentType: ctx.contentType!,
|
||||
uid: ctx.uid,
|
||||
})
|
||||
|
||||
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
|
||||
(acc, key) => {
|
||||
const item = res.data[key as Lang]?.items[0]
|
||||
|
||||
@@ -54,9 +54,7 @@ export const loyaltyPageQueryRouter = router({
|
||||
uid,
|
||||
},
|
||||
{
|
||||
next: {
|
||||
tags: [generateRefsResponseTag(lang, uid)],
|
||||
},
|
||||
tags: [generateRefsResponseTag(lang, uid)],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -90,7 +88,7 @@ export const loyaltyPageQueryRouter = router({
|
||||
locale: lang,
|
||||
uid,
|
||||
},
|
||||
{ next: { tags } }
|
||||
{ tags }
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
|
||||
@@ -60,9 +60,7 @@ export const navigationQueryRouter = router({
|
||||
GetNavigationMyPagesRefs,
|
||||
{ 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>(
|
||||
GetNavigationMyPages,
|
||||
{ locale: lang },
|
||||
{ next: { tags } }
|
||||
{ tags }
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
|
||||
@@ -2,11 +2,7 @@ import { initTRPC } from "@trpc/server"
|
||||
|
||||
import { env } from "@/env/server"
|
||||
|
||||
import {
|
||||
badRequestError,
|
||||
sessionExpiredError,
|
||||
unauthorizedError,
|
||||
} from "./errors/trpc"
|
||||
import { badRequestError, sessionExpiredError } from "./errors/trpc"
|
||||
import { transformer } from "./transformer"
|
||||
|
||||
import type { Meta } from "@/types/trpc/meta"
|
||||
|
||||
@@ -12,7 +12,7 @@ export type MainMenuProps = {
|
||||
links: CurrentHeaderLink[]
|
||||
logo: Image
|
||||
topMenuMobileLinks: TopMenuHeaderLink[]
|
||||
languageSwitcher: React.ReactNode
|
||||
languageSwitcher: React.ReactNode | null
|
||||
bookingHref: string
|
||||
isLoggedIn: boolean
|
||||
lang: Lang
|
||||
|
||||
@@ -6,6 +6,6 @@ export type TopMenuProps = {
|
||||
frontpageLinkText: string
|
||||
homeHref: string
|
||||
links: TopMenuHeaderLink[]
|
||||
languageSwitcher: React.ReactNode
|
||||
languageSwitcher: React.ReactNode | null
|
||||
lang: Lang
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export enum Section {
|
||||
type ExtraInfo = Typename<
|
||||
{
|
||||
extra_info: {
|
||||
text: string
|
||||
text: string[]
|
||||
}
|
||||
},
|
||||
Section.ContactBlockSectionsExtraInfo
|
||||
|
||||
@@ -1,31 +1,23 @@
|
||||
import { DocumentNode, print } from "graphql"
|
||||
|
||||
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 { internalServerError } from "@/server/errors/next"
|
||||
|
||||
import { validateEntryResolveSchema } from "@/types/requests/entry"
|
||||
|
||||
export async function resolve(url: string, lang = Lang.en) {
|
||||
const result = await fetch(env.CMS_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
access_token: env.CMS_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
const response = await edgeRequest(
|
||||
ResolveEntryByUrl,
|
||||
{
|
||||
locale: lang,
|
||||
url,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: print(ResolveEntryByUrl as DocumentNode),
|
||||
variables: {
|
||||
locale: lang,
|
||||
url,
|
||||
},
|
||||
}),
|
||||
})
|
||||
{
|
||||
revalidate: 3600,
|
||||
}
|
||||
)
|
||||
|
||||
const { data } = await result.json()
|
||||
|
||||
const validatedData = validateEntryResolveSchema.safeParse(data)
|
||||
const validatedData = validateEntryResolveSchema.safeParse(response.data)
|
||||
|
||||
if (!validatedData.success) {
|
||||
throw internalServerError(validatedData.error)
|
||||
|
||||
Reference in New Issue
Block a user