Merged in feat/header-footer (pull request #215)
Feat: Current header on new web
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 950px) {
|
||||
.desktop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import Desktop from "@/components/Current/Header/LanguageSwitcher/Desktop"
|
||||
import Mobile from "@/components/Current/Header/LanguageSwitcher/Mobile"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import {
|
||||
ContentTypeParams,
|
||||
LangParams,
|
||||
PageArgs,
|
||||
UIDParams,
|
||||
} from "@/types/params"
|
||||
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export default async function LanguageSwitcher({
|
||||
params,
|
||||
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {
|
||||
let urls: LanguageSwitcherData
|
||||
let lang: Lang
|
||||
switch (params.contentType) {
|
||||
case "loyalty-page":
|
||||
const data =
|
||||
await serverClient().contentstack.loyaltyPage.languageSwitcher()
|
||||
urls = data.urls
|
||||
lang = data.lang
|
||||
break
|
||||
case "content-page":
|
||||
// TODO: Implement this
|
||||
return null
|
||||
default:
|
||||
const type: never = params.contentType
|
||||
console.error(`Unsupported content type given: ${type}`)
|
||||
notFound()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={styles.desktop}>
|
||||
<Desktop currentLanguage={params.lang} urls={urls} />
|
||||
</section>
|
||||
<section className={styles.mobile}>
|
||||
<Mobile currentLanguage={params.lang} urls={urls} />
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
3
app/[lang]/(live)/@languageSwitcher/default.tsx
Normal file
3
app/[lang]/(live)/@languageSwitcher/default.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
export default async function LanguageSwitcher() {
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 950px) {
|
||||
.desktop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import Desktop from "@/components/Current/Header/LanguageSwitcher/Desktop"
|
||||
import Mobile from "@/components/Current/Header/LanguageSwitcher/Mobile"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
export default async function LanguageSwitcher() {
|
||||
const data = await serverClient().contentstack.accountPage.languageSwitcher()
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={styles.desktop}>
|
||||
<Desktop currentLanguage={data.lang} urls={data.urls} />
|
||||
</section>
|
||||
<section className={styles.mobile}>
|
||||
<Mobile currentLanguage={data.lang} urls={data.urls} />
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import Script from "next/script"
|
||||
import TrpcProvider from "@/lib/trpc/Provider"
|
||||
|
||||
import AdobeScript from "@/components/Current/AdobeScript"
|
||||
import Header from "@/components/Current/Header"
|
||||
import VwoScript from "@/components/Current/VwoScript"
|
||||
|
||||
import type { Metadata } from "next"
|
||||
@@ -20,7 +21,12 @@ export const metadata: Metadata = {
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||
languageSwitcher,
|
||||
}: React.PropsWithChildren<
|
||||
LayoutArgs<LangParams> & {
|
||||
languageSwitcher: React.ReactNode
|
||||
}
|
||||
>) {
|
||||
return (
|
||||
<html lang={params.lang}>
|
||||
<head>
|
||||
@@ -43,7 +49,8 @@ export default async function RootLayout({
|
||||
<VwoScript />
|
||||
</head>
|
||||
<body>
|
||||
<TrpcProvider lang={params.lang}>{children}</TrpcProvider>
|
||||
<TrpcProvider lang={params.lang}>
|
||||
<Header lang={params.lang} languageSwitcher={languageSwitcher} />{children}</TrpcProvider>
|
||||
<Script id="page-tracking">{`
|
||||
typeof _satellite !== "undefined" && _satellite.pageBottom();
|
||||
`}</Script>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "../page"
|
||||
17
app/[lang]/(live-current)/@languageSwitcher/page.module.css
Normal file
17
app/[lang]/(live-current)/@languageSwitcher/page.module.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (min-width: 950px) {
|
||||
.desktop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
48
app/[lang]/(live-current)/@languageSwitcher/page.tsx
Normal file
48
app/[lang]/(live-current)/@languageSwitcher/page.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { batchRequest } from "@/lib/graphql/batchRequest"
|
||||
import {
|
||||
GetDaDeEnUrlsCurrentBlocksPage,
|
||||
GetFiNoSvUrlsCurrentBlocksPage,
|
||||
} from "@/lib/graphql/Query/LanguageSwitcherCurrent.graphql"
|
||||
|
||||
import Desktop from "@/components/Current/Header/LanguageSwitcher/Desktop"
|
||||
import Mobile from "@/components/Current/Header/LanguageSwitcher/Mobile"
|
||||
|
||||
import styles from "./page.module.css"
|
||||
|
||||
import { LangParams, PageArgs, UIDParams, UriParams } from "@/types/params"
|
||||
import { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export default async function LanguageSwitcher({
|
||||
params,
|
||||
searchParams,
|
||||
}: PageArgs<LangParams, UriParams & UIDParams>) {
|
||||
if (!searchParams.uid) {
|
||||
return null
|
||||
}
|
||||
|
||||
const variables = {
|
||||
uid: searchParams.uid,
|
||||
}
|
||||
|
||||
const { data: urls } = await batchRequest<LanguageSwitcherData>([
|
||||
{
|
||||
document: GetDaDeEnUrlsCurrentBlocksPage,
|
||||
variables,
|
||||
},
|
||||
{
|
||||
document: GetFiNoSvUrlsCurrentBlocksPage,
|
||||
variables,
|
||||
},
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={styles.desktop}>
|
||||
<Desktop currentLanguage={params.lang} urls={urls} />
|
||||
</section>
|
||||
<section className={styles.mobile}>
|
||||
<Mobile currentLanguage={params.lang} urls={urls} />
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -60,7 +60,6 @@ export default async function CurrentContentPage({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header lang={params.lang} uid={pageData.system.uid} />
|
||||
<ContentPage data={response.data} />
|
||||
<Tracking pageData={trackingData} />
|
||||
</>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Script from "next/script"
|
||||
|
||||
import AdobeScript from "@/components/Current/AdobeScript"
|
||||
import Footer from "@/components/Current/Footer"
|
||||
import Header from "@/components/Current/Header"
|
||||
import LangPopup from "@/components/Current/LangPopup"
|
||||
import SkipToMainContent from "@/components/SkipToMainContent"
|
||||
|
||||
@@ -21,7 +22,10 @@ export const metadata: Metadata = {
|
||||
export default function RootLayout({
|
||||
children,
|
||||
params,
|
||||
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||
languageSwitcher,
|
||||
}: React.PropsWithChildren<
|
||||
LayoutArgs<LangParams> & { languageSwitcher: React.ReactNode }
|
||||
>) {
|
||||
return (
|
||||
<html lang={params.lang}>
|
||||
<head>
|
||||
@@ -75,6 +79,7 @@ export default function RootLayout({
|
||||
<body className="theme-00Corecolours theme-X0Oldcorecolours">
|
||||
<LangPopup lang={params.lang} />
|
||||
<SkipToMainContent lang={params.lang} />
|
||||
<Header lang={params.lang} languageSwitcher={languageSwitcher} />
|
||||
{children}
|
||||
<Footer lang={params.lang} />
|
||||
<Script id="page-tracking">{`
|
||||
|
||||
@@ -88,3 +88,9 @@ body {
|
||||
overflow-x: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-inline-start: 0;
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
@@ -4,5 +4,4 @@
|
||||
display: block;
|
||||
padding-bottom: 50px;
|
||||
width: 100%;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
.button {
|
||||
background-color: #02838e;
|
||||
color: #fff;
|
||||
padding: 5px 15px;
|
||||
display: inline-block;
|
||||
line-height: 20px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 50px;
|
||||
height: 32px;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.button:focus,
|
||||
.button:active {
|
||||
box-shadow: 0 0 1px 2px #b4defa;
|
||||
outline: none;
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.button {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
height: auto;
|
||||
padding: 12px 32px;
|
||||
}
|
||||
}
|
||||
13
components/Current/Header/BookingButton/index.tsx
Normal file
13
components/Current/Header/BookingButton/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import "@scandic-hotels/design-system/current/style.css"
|
||||
|
||||
import { _ } from "@/lib/translation"
|
||||
|
||||
import styles from "./bookingButton.module.css"
|
||||
|
||||
export default function BookingButton({ href }: { href: string }) {
|
||||
return (
|
||||
<a className={styles.button} href={href}>
|
||||
{_("Book")}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
@@ -1,21 +1,76 @@
|
||||
.container {
|
||||
position: relative;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
.toggle {
|
||||
display: flex;
|
||||
color: #fff;
|
||||
padding: 3px 15px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0 auto;
|
||||
cursor: pointer;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.hiddenAccessible {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000em;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.caret {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin-left: 2px;
|
||||
vertical-align: middle;
|
||||
border-top: 5px dashed;
|
||||
border-right: 5px solid transparent;
|
||||
border-left: 5px solid transparent;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: -4px;
|
||||
margin-right: 3px;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
background-clip: padding-box;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, .15);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
display: none;
|
||||
float: left;
|
||||
font-size: 1rem;
|
||||
left: 0;
|
||||
list-style: none;
|
||||
margin: 2px 0 0;
|
||||
min-width: 160px;
|
||||
padding: 5px 0;
|
||||
position: absolute;
|
||||
margin: 2px 0 0;
|
||||
list-style: none;
|
||||
font-size: 16px;
|
||||
text-align: left;
|
||||
top: 100%;
|
||||
z-index: 11;
|
||||
background-color: #fff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.15);
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.li {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropdown.isOpen {
|
||||
@@ -29,6 +84,7 @@
|
||||
font-weight: 400;
|
||||
padding: 3px 20px;
|
||||
white-space: nowrap;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link:hover {
|
||||
@@ -37,9 +93,8 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.active>.link {
|
||||
.active > .link {
|
||||
background-color: #00838e;
|
||||
color: #fff;
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"use client"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
|
||||
import { languages } from "@/constants/languages"
|
||||
import { Lang, languages } from "@/constants/languages"
|
||||
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
|
||||
import styles from "./desktop.module.css"
|
||||
|
||||
@@ -39,80 +41,41 @@ export default function Desktop({
|
||||
}, [close])
|
||||
|
||||
return (
|
||||
<div className="dropdown-container navbar-language-selector " ref={divRef}>
|
||||
<div className={styles.container} ref={divRef}>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className="navbar-language-selector__toggler"
|
||||
data-js="dropdown-toggler"
|
||||
className={styles.toggle}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
<svg
|
||||
focusable="false"
|
||||
className="icon icon--xs icon--white"
|
||||
viewBox="0 0 32 32"
|
||||
>
|
||||
<svg focusable="false" className={styles.icon} viewBox="0 0 32 32">
|
||||
<use xlinkHref="/_static/img/icons/sprites.svg#icon-globe"></use>
|
||||
</svg>
|
||||
{currentLanguage}
|
||||
<span className="hidden--accessible">Choose language</span>
|
||||
<span className="caret"></span>
|
||||
{languages[currentLanguage]}
|
||||
<span className={styles.hiddenAccessible}>Choose language</span>
|
||||
<span className={styles.caret}></span>
|
||||
</button>
|
||||
<ul className={`${styles.dropdown} ${isOpen ? styles.isOpen : ""}`}>
|
||||
<li
|
||||
className={
|
||||
currentLanguage === languages.en ? styles.active : undefined
|
||||
{Object.keys(urls).map((key) => {
|
||||
const url = urls[key as Lang]?.url
|
||||
if (url) {
|
||||
return (
|
||||
<li
|
||||
key={key}
|
||||
className={`${styles.li} ${currentLanguage === key ? styles.active : ""}`}
|
||||
>
|
||||
{urls[key as Lang]?.isExternal ? (
|
||||
<Link className={styles.link} href={url}>
|
||||
{languages[key as Lang]}
|
||||
</Link>
|
||||
) : (
|
||||
<a className={styles.link} href={url}>
|
||||
{languages[key as Lang]}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
>
|
||||
<a className={styles.link} href={urls.en?.url}>
|
||||
{languages.en}
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className={
|
||||
currentLanguage === languages.sv ? styles.active : undefined
|
||||
}
|
||||
>
|
||||
<a className={styles.link} href={urls.sv?.url}>
|
||||
{languages.sv}
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className={
|
||||
currentLanguage === languages.no ? styles.active : undefined
|
||||
}
|
||||
>
|
||||
<a className={styles.link} href={urls.no?.url}>
|
||||
{languages.no}
|
||||
</a>
|
||||
</li>
|
||||
{/* When we have 6 languages in Contenstack, danish url should come from urls.da?.url */}
|
||||
<li
|
||||
className={
|
||||
currentLanguage === languages.da ? styles.active : undefined
|
||||
}
|
||||
>
|
||||
<a className={styles.link} href="https://www.scandichotels.dk/">
|
||||
{languages.da}
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className={
|
||||
currentLanguage === languages.fi ? styles.active : undefined
|
||||
}
|
||||
>
|
||||
<a className={styles.link} href={urls.fi?.url}>
|
||||
{languages.fi}
|
||||
</a>
|
||||
</li>
|
||||
<li
|
||||
className={
|
||||
currentLanguage === languages.de ? styles.active : undefined
|
||||
}
|
||||
>
|
||||
<a className={styles.link} href={urls.de?.url}>
|
||||
{languages.de}
|
||||
</a>
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
import { useState } from "react"
|
||||
|
||||
import { languages } from "@/constants/languages"
|
||||
import { Lang, languages } from "@/constants/languages"
|
||||
|
||||
import styles from "./mobile.module.css"
|
||||
|
||||
@@ -14,42 +14,33 @@ export default function Mobile({
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
function toggleOpen() {
|
||||
setIsOpen(prevIsOpen => !prevIsOpen)
|
||||
setIsOpen((prevIsOpen) => !prevIsOpen)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="navbar-language-selector">
|
||||
<div>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className="navbar-language-selector__toggler "
|
||||
data-js="collapsible-toggler"
|
||||
data-target="language-menu"
|
||||
className={styles.toggle}
|
||||
onClick={toggleOpen}
|
||||
>
|
||||
{currentLanguage}{" "}
|
||||
{languages[currentLanguage]}{" "}
|
||||
<span className={`${styles.arrow} ${isOpen ? styles.open : ""}`}></span>
|
||||
<span className="hidden--accessible">Choose language</span>
|
||||
<span className={styles.hiddenAccessible}>Choose language</span>
|
||||
</button>
|
||||
<ul className={`${styles.dropdown} ${isOpen ? styles.isOpen : ""}`} data-collapsable="language-menu">
|
||||
<li className={`navbar-language-selector__item ${currentLanguage === languages.en ? "is-active" : ""}`}>
|
||||
<a href={urls.en?.url}>{languages.en}</a>
|
||||
</li>
|
||||
<li className={`navbar-language-selector__item ${currentLanguage === languages.sv ? "is-active" : ""}`}>
|
||||
<a href={urls.sv?.url}>{languages.sv}</a>
|
||||
</li>
|
||||
<li className={`navbar-language-selector__item ${currentLanguage === languages.no ? "is-active" : ""}`}>
|
||||
<a href={urls.no?.url}>{languages.no}</a>
|
||||
</li>
|
||||
{/* When we have 6 languages in Contenstack, danish url should come from urls.da?.url */}
|
||||
<li className={`navbar-language-selector__item ${currentLanguage === languages.da ? "is-active" : ""}`}>
|
||||
<a href="https://www.scandichotels.dk/">{languages.da}</a>
|
||||
</li>
|
||||
<li className={`navbar-language-selector__item ${currentLanguage === languages.fi ? "is-active" : ""}`}>
|
||||
<a href={urls.fi?.url}>{languages.fi}</a>
|
||||
</li>
|
||||
<li className={`navbar-language-selector__item ${currentLanguage === languages.de ? "is-active" : ""}`}>
|
||||
<a href={urls.de?.url}>{languages.de}</a>
|
||||
</li>
|
||||
<ul className={`${styles.dropdown} ${isOpen ? styles.isOpen : ""}`}>
|
||||
{Object.keys(urls).map((key) => {
|
||||
const url = urls[key as Lang]?.url
|
||||
if (url) {
|
||||
return (
|
||||
<li key={key} className={styles.li}>
|
||||
<a href={url} className={styles.link}>
|
||||
{languages[key as Lang]}
|
||||
</a>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,35 @@
|
||||
.toggle {
|
||||
font-size: 14px;
|
||||
padding: 5px 0;
|
||||
display: block;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
margin: 0 auto;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.hiddenAccessible {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -100000em;
|
||||
top: auto;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.li {
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
font-family: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.link {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
line-height: 22.4px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
"use client"
|
||||
import { useParams } from "next/navigation"
|
||||
import { useState } from "react"
|
||||
|
||||
import Image from "@/components/Image"
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { login } from "@/constants/routes/handleAuth"
|
||||
import { myPages } from "@/constants/routes/myPages"
|
||||
import { _ } from "@/lib/translation"
|
||||
|
||||
import Mobile from "../LanguageSwitcher/Mobile"
|
||||
import Image from "@/components/Image"
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
|
||||
import BookingButton from "../BookingButton"
|
||||
|
||||
import styles from "./mainMenu.module.css"
|
||||
|
||||
import type { MainMenuProps } from "@/types/components/current/header/mainMenu"
|
||||
|
||||
export default function MainMenu({
|
||||
currentLanguage,
|
||||
export function MainMenu({
|
||||
frontpageLinkText,
|
||||
homeHref,
|
||||
links,
|
||||
logo,
|
||||
topMenuMobileLinks,
|
||||
urls,
|
||||
languageSwitcher,
|
||||
bookingHref,
|
||||
isLoggedIn,
|
||||
lang,
|
||||
}: MainMenuProps) {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
@@ -32,54 +41,93 @@ export default function MainMenu({
|
||||
itemType="http://schema.org/Organization"
|
||||
>
|
||||
<meta itemProp="name" content="Scandic" />
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={`${styles.expanderBtn} ${isOpen ? styles.expanded : ""}`}
|
||||
data-js="main-nav-toggler"
|
||||
data-target="#main-menu"
|
||||
onClick={toogleIsOpen}
|
||||
type="button"
|
||||
>
|
||||
<span className={styles.iconBars}></span>
|
||||
<span className={styles.hiddenAccessible}>Menu</span>
|
||||
</button>
|
||||
|
||||
<a
|
||||
className={styles.logoLink}
|
||||
href={homeHref}
|
||||
id="scandic-logo"
|
||||
itemProp="url"
|
||||
>
|
||||
<span className={styles.hiddenAccessible}>{frontpageLinkText}</span>
|
||||
<Image
|
||||
alt="Scandic Hotels logo"
|
||||
className={styles.logo}
|
||||
data-js="scandiclogoimg"
|
||||
data-nosvgsrc="/_static/img/scandic-logotype.png"
|
||||
itemProp="logo"
|
||||
height={22}
|
||||
src={logo.url}
|
||||
width={logo.dimension.width}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<nav>
|
||||
<ul
|
||||
className={`${styles.list} ${isOpen ? styles.isOpen : ""}`}
|
||||
data-collapsable="main-menu"
|
||||
id="main-menu"
|
||||
<nav className={styles.navBar}>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
className={`${styles.expanderBtn} ${isOpen ? styles.expanded : ""}`}
|
||||
onClick={toogleIsOpen}
|
||||
type="button"
|
||||
>
|
||||
{links.map((link) => (
|
||||
<li className={styles.li} key={link.href}>
|
||||
<a className={styles.link} href={link.href}>
|
||||
{link.title}
|
||||
<span className={styles.iconBars}></span>
|
||||
<span className={styles.hiddenAccessible}>Menu</span>
|
||||
</button>
|
||||
|
||||
<a className={styles.logoLink} href={homeHref}>
|
||||
<span className={styles.hiddenAccessible}>{frontpageLinkText}</span>
|
||||
<Image
|
||||
alt="Scandic Hotels logo"
|
||||
className={styles.logo}
|
||||
data-js="scandiclogoimg"
|
||||
data-nosvgsrc="/_static/img/scandic-logotype.png"
|
||||
itemProp="logo"
|
||||
height={22}
|
||||
src={logo.url}
|
||||
width={logo.dimension.width}
|
||||
/>
|
||||
</a>
|
||||
|
||||
<ul
|
||||
className={`${styles.listWrapper} ${isOpen ? styles.isOpen : ""}`}
|
||||
>
|
||||
<ul className={styles.linkRow}>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<li>
|
||||
<div className={styles.loggedInLogo} />
|
||||
</li>
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<Link
|
||||
className={styles.mobileLinkButton}
|
||||
href={myPages[lang]}
|
||||
>
|
||||
{_("My pages")}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<li>
|
||||
<Image
|
||||
src="/_static/img/icon-scandic-friends.svg"
|
||||
alt="Scanidc Friends Logo"
|
||||
height={35}
|
||||
width={35}
|
||||
className={styles.scandicFriendsLogo}
|
||||
/>
|
||||
</li>
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<Link
|
||||
className={styles.mobileLinkButton}
|
||||
href={login[lang]}
|
||||
>
|
||||
{_("Log in")}
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<span className={styles.mobileSeparator} />
|
||||
</li>
|
||||
<li className={styles.mobileLinkRow}>
|
||||
<a className={styles.mobileLinkButton} href="">
|
||||
{_("Find booking")}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className={styles.mainLinks}>
|
||||
{links.map((link, i) => (
|
||||
<li className={styles.li} key={link.href + i}>
|
||||
<a className={styles.link} href={link.href}>
|
||||
{link.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<ul className={styles.mobileList}>
|
||||
{topMenuMobileLinks.map(({ link }) => (
|
||||
<li className={styles.mobileLi} key={link.href}>
|
||||
{topMenuMobileLinks.map(({ link }, i) => (
|
||||
<li className={styles.mobileLi} key={link.href + i}>
|
||||
<a className={styles.mobileLink} href={link.href}>
|
||||
{link.title}
|
||||
</a>
|
||||
@@ -87,12 +135,13 @@ export default function MainMenu({
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{urls ? (
|
||||
<li className={styles.mobileLi}>
|
||||
<Mobile currentLanguage={currentLanguage} urls={urls} />
|
||||
</li>
|
||||
{languageSwitcher ? (
|
||||
<li className={styles.mobileLi}>{languageSwitcher}</li>
|
||||
) : null}
|
||||
</ul>
|
||||
<div className={styles.buttonContainer}>
|
||||
<BookingButton href={bookingHref} />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,26 +8,34 @@
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 99999;
|
||||
height: 52.39px;
|
||||
}
|
||||
|
||||
.container {
|
||||
box-sizing: content-box;
|
||||
display: grid;
|
||||
/** Third column is Book button */
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
max-width: 1200px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mainLinks {
|
||||
background-color: #f3f2f1;
|
||||
}
|
||||
|
||||
.navBar {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 80px 1fr;
|
||||
grid-template-rows: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expanderBtn {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
justify-self: flex-start;
|
||||
left: 0;
|
||||
padding: 0.75rem 0.5rem 1rem;
|
||||
padding: 11px 8px 16px;
|
||||
transition: 0.3s;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -36,12 +44,12 @@
|
||||
.iconBars::after,
|
||||
.iconBars::before {
|
||||
background: #757575;
|
||||
border-radius: 0.1428571429rem;
|
||||
border-radius: 2.3px;
|
||||
display: inline-block;
|
||||
height: 0.2857142857rem;
|
||||
height: 5px;
|
||||
position: relative;
|
||||
transition: 0.3s;
|
||||
width: 2rem;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.iconBars::after,
|
||||
@@ -49,15 +57,15 @@
|
||||
content: "";
|
||||
left: 0;
|
||||
position: absolute;
|
||||
transform-origin: 0.1428571429rem center;
|
||||
transform-origin: 2.286px center;
|
||||
}
|
||||
|
||||
.iconBars::after {
|
||||
top: -0.5rem;
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
.iconBars::before {
|
||||
top: 0.5rem;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.expanded .iconBars {
|
||||
@@ -68,7 +76,7 @@
|
||||
.expanded .iconBars::before {
|
||||
top: 0;
|
||||
transform-origin: 50% 50%;
|
||||
width: 2rem;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.expanded .iconBars::after {
|
||||
@@ -90,21 +98,28 @@
|
||||
}
|
||||
|
||||
.logoLink {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-self: center;
|
||||
display: inline;
|
||||
height: 100%;
|
||||
width: 80px;
|
||||
padding: 16px 0 8px;
|
||||
}
|
||||
|
||||
.list {
|
||||
.logo {
|
||||
width: 80px;
|
||||
object-fit: fill;
|
||||
}
|
||||
.listWrapper {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #e3e0db;
|
||||
display: none;
|
||||
list-style: none;
|
||||
overflow-y: visible;
|
||||
padding-bottom: 20px;
|
||||
margin: 0;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.list.isOpen {
|
||||
.listWrapper.isOpen {
|
||||
display: block;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
@@ -122,13 +137,14 @@
|
||||
|
||||
.link {
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 0.875rem;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 20px;
|
||||
@@ -140,6 +156,57 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.linkRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid #e3e0db;
|
||||
background-color: #f3f2f1 !important;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.scandicFriendsLogo {
|
||||
margin-right: 4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.mobileLinkRow {
|
||||
margin: 6px 0;
|
||||
padding: 15px 15px 15px 5px;
|
||||
}
|
||||
|
||||
.loggedInLogo {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
border-radius: 50px;
|
||||
background-color: #000;
|
||||
margin-right: 4px;
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.mobileLinkButton {
|
||||
font-size: 14px;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif !important;
|
||||
font-weight: 700;
|
||||
background-color: transparent !important;
|
||||
text-decoration: none;
|
||||
color: #000;
|
||||
outline-color: transparent;
|
||||
}
|
||||
|
||||
.mobileSeparator {
|
||||
border-left: 1px solid #e3e0db;
|
||||
height: 35px;
|
||||
margin-bottom: -12px;
|
||||
margin-left: -1px;
|
||||
margin-top: -12px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobileList {
|
||||
padding-top: 6px;
|
||||
}
|
||||
@@ -148,59 +215,78 @@
|
||||
display: block;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
line-height: 22.4px;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.mobileLink {
|
||||
color: #000;
|
||||
display: block;
|
||||
font-family: Helvetica;
|
||||
font-size: 0.875rem;
|
||||
padding: 5px 0;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1366px) {
|
||||
.logoLink {
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
.li {
|
||||
background-color: #f3f2f1;
|
||||
}
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
@media screen and (min-width: 950px) {
|
||||
.mainMenu {
|
||||
box-shadow: none;
|
||||
background-color: hsla(0, 0%, 100%, 0.95);
|
||||
position: relative;
|
||||
z-index: unset;
|
||||
height: 85.09px;
|
||||
}
|
||||
|
||||
.container {
|
||||
gap: 30px;
|
||||
grid-template-columns: minmax(100px, auto) 1fr;
|
||||
padding: 0px 30px;
|
||||
}
|
||||
|
||||
.mainLinks {
|
||||
padding-top: 2.5px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.navBar {
|
||||
grid-template-columns: 132.18px 1fr auto;
|
||||
align-content: center;
|
||||
padding-bottom: 2px;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.expanderBtn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: none;
|
||||
min-width: 98px;
|
||||
.logoLink {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 30px 10px 30px 15px;
|
||||
width: auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.list {
|
||||
.logo {
|
||||
width: 102.17px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
border-top: none;
|
||||
display: block;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
position: static;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list.isOpen {
|
||||
.listWrapper.isOpen {
|
||||
position: static;
|
||||
}
|
||||
|
||||
@@ -208,6 +294,7 @@
|
||||
display: table-cell;
|
||||
float: none;
|
||||
vertical-align: middle;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.link {
|
||||
@@ -218,6 +305,10 @@
|
||||
padding: 30px 15px;
|
||||
}
|
||||
|
||||
.linkRow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobileList {
|
||||
display: none;
|
||||
padding-top: 0px;
|
||||
@@ -226,4 +317,28 @@
|
||||
.mobileLi {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.buttonContainer {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.mainMenu {
|
||||
height: 82.4px;
|
||||
}
|
||||
.navBar {
|
||||
grid-template-columns: 140px auto 1fr;
|
||||
}
|
||||
|
||||
.logoLink {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 27px 30px 26px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.listWrapper {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,37 +1,48 @@
|
||||
import Desktop from "../LanguageSwitcher/Desktop"
|
||||
import { login, logout } from "@/constants/routes/handleAuth"
|
||||
|
||||
import { auth } from "@/auth"
|
||||
|
||||
import styles from "./topMenu.module.css"
|
||||
|
||||
import type { TopMenuProps } from "@/types/components/current/header/topMenu"
|
||||
|
||||
export default function TopMenu({ currentLanguage, frontpageLinkText, homeHref, links, urls }: TopMenuProps) {
|
||||
export default async function TopMenu({
|
||||
frontpageLinkText,
|
||||
homeHref,
|
||||
links,
|
||||
languageSwitcher,
|
||||
lang,
|
||||
}: TopMenuProps) {
|
||||
const session = await auth()
|
||||
|
||||
return (
|
||||
<div className={styles.topMenu}>
|
||||
<div className={styles.container}>
|
||||
<a
|
||||
className={styles.homeLink}
|
||||
href={homeHref}
|
||||
>
|
||||
<a className={styles.homeLink} href={homeHref}>
|
||||
{frontpageLinkText}
|
||||
</a>
|
||||
|
||||
<ul className={styles.list}>
|
||||
{urls ? (
|
||||
<li className="nav-secondary__item hidden-xxsmall hidden-xsmall hidden-small">
|
||||
<Desktop currentLanguage={currentLanguage} urls={urls} />
|
||||
</li>
|
||||
) : null}
|
||||
<li className={styles.langSwitcher}>{languageSwitcher}</li>
|
||||
|
||||
{links.map(({ link }) => (
|
||||
<li key={link.href}>
|
||||
<a
|
||||
className={styles.link}
|
||||
href={link.href}
|
||||
>
|
||||
{links.map(({ link }, i) => (
|
||||
<li key={link.href + i}>
|
||||
<a className={styles.link} href={link.href}>
|
||||
{link.title}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.loginContainer}>
|
||||
{session ? (
|
||||
<a href={logout[lang]} className={styles.loginLink}>
|
||||
Log out
|
||||
</a>
|
||||
) : (
|
||||
<a href={login[lang]} className={styles.loginLink}>
|
||||
Log in
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
background-color: #8d3a7c;
|
||||
color: #fff;
|
||||
display: none;
|
||||
font-size: 0.8125rem;
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
@@ -23,6 +24,7 @@
|
||||
.list {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
line-height: 22.4px;
|
||||
}
|
||||
|
||||
.link {
|
||||
@@ -30,6 +32,21 @@
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
text-decoration: none;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
.langSwitcher {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
@@ -38,14 +55,44 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
@media screen and (min-width: 950px) {
|
||||
.topMenu {
|
||||
background-color: #3d3835;
|
||||
display: block;
|
||||
}
|
||||
.list {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.link {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.loginContainer {
|
||||
margin-left: 10px;
|
||||
background-color: #f3f2f1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loginLink {
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
color: #000;
|
||||
font-family:
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
display: grid;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1366px) {
|
||||
@media screen and (max-width: 950px) {
|
||||
.header {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@@ -1,86 +1,52 @@
|
||||
import { homeHrefs } from "@/constants/homeHrefs"
|
||||
import { languages } from "@/constants/languages"
|
||||
import { env } from "@/env/server"
|
||||
import { batchRequest } from "@/lib/graphql/batchRequest"
|
||||
import { GetHeader } from "@/lib/graphql/Query/Header.graphql"
|
||||
import {
|
||||
GetDaDeEnUrls,
|
||||
GetFiNoSvUrls,
|
||||
} from "@/lib/graphql/Query/LanguageSwitcher.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import MainMenu from "./MainMenu"
|
||||
import { auth } from "@/auth"
|
||||
|
||||
import { MainMenu } from "./MainMenu"
|
||||
import OfflineBanner from "./OfflineBanner"
|
||||
import TopMenu from "./TopMenu"
|
||||
|
||||
import styles from "./header.module.css"
|
||||
|
||||
import type { HeaderProps } from "@/types/components/current/header"
|
||||
import { LangParams } from "@/types/params"
|
||||
import type { HeaderQueryData } from "@/types/requests/header"
|
||||
import type { LanguageSwitcherQueryData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export default async function Header({ lang, uid }: LangParams & HeaderProps) {
|
||||
try {
|
||||
const variables = {
|
||||
locale: lang,
|
||||
uid,
|
||||
}
|
||||
export default async function Header({
|
||||
lang,
|
||||
languageSwitcher,
|
||||
}: LangParams & { languageSwitcher: React.ReactNode }) {
|
||||
const data = await serverClient().contentstack.config.header()
|
||||
const session = await auth()
|
||||
|
||||
const { data } = await request<HeaderQueryData>(
|
||||
GetHeader,
|
||||
{ locale: lang },
|
||||
{ next: { tags: [`header-${lang}`] } }
|
||||
)
|
||||
const { data: urls } = await batchRequest<LanguageSwitcherQueryData>([
|
||||
{
|
||||
document: GetDaDeEnUrls,
|
||||
tags: [`DA-DE-EN-${uid}`],
|
||||
variables,
|
||||
},
|
||||
{
|
||||
document: GetFiNoSvUrls,
|
||||
tags: [`FI-NO-SV-${uid}`],
|
||||
variables,
|
||||
},
|
||||
])
|
||||
const homeHref = homeHrefs[env.NODE_ENV][lang]
|
||||
const { frontpage_link_text, logo, menu, top_menu } = data
|
||||
|
||||
if (!data.all_header.items.length) {
|
||||
return null
|
||||
}
|
||||
const topMenuMobileLinks = top_menu.links
|
||||
.filter((link) => link.show_on_mobile)
|
||||
.sort((a, b) => (a.sort_order_mobile < b.sort_order_mobile ? 1 : -1))
|
||||
|
||||
const currentLanguage = languages[lang]
|
||||
const homeHref = homeHrefs[env.NODE_ENV][lang]
|
||||
const { frontpage_link_text, logoConnection, menu, top_menu } =
|
||||
data.all_header.items[0]
|
||||
const logo = logoConnection.edges?.[0]?.node
|
||||
const topMenuMobileLinks = top_menu.links
|
||||
.filter((link) => link.show_on_mobile)
|
||||
.sort((a, b) => (a.sort_order_mobile < b.sort_order_mobile ? 1 : -1))
|
||||
|
||||
return (
|
||||
<header className={styles.header} role="banner">
|
||||
<OfflineBanner />
|
||||
<TopMenu
|
||||
currentLanguage={currentLanguage}
|
||||
frontpageLinkText={frontpage_link_text}
|
||||
homeHref={homeHref}
|
||||
links={top_menu.links}
|
||||
urls={urls}
|
||||
/>
|
||||
<MainMenu
|
||||
currentLanguage={currentLanguage}
|
||||
frontpageLinkText={frontpage_link_text}
|
||||
homeHref={homeHref}
|
||||
links={menu.links}
|
||||
logo={logo}
|
||||
topMenuMobileLinks={topMenuMobileLinks}
|
||||
urls={urls}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<header className={styles.header} role="banner">
|
||||
<OfflineBanner />
|
||||
<TopMenu
|
||||
frontpageLinkText={frontpage_link_text}
|
||||
homeHref={homeHref}
|
||||
links={top_menu.links}
|
||||
languageSwitcher={languageSwitcher}
|
||||
lang={lang}
|
||||
/>
|
||||
<MainMenu
|
||||
frontpageLinkText={frontpage_link_text}
|
||||
homeHref={homeHref}
|
||||
links={menu.links}
|
||||
logo={logo}
|
||||
topMenuMobileLinks={topMenuMobileLinks}
|
||||
languageSwitcher={languageSwitcher}
|
||||
bookingHref={homeHref}
|
||||
isLoggedIn={!!session}
|
||||
lang={lang}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
.hamburger {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.line {
|
||||
background-color: var(--some-black-color, #1c1b1f);
|
||||
border-radius: 0.8rem;
|
||||
height: 0.2rem;
|
||||
width: 2.5rem;
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import styles from "./hamburger.module.css"
|
||||
|
||||
export default function Hamburger() {
|
||||
return (
|
||||
<button className={styles.hamburger} type="button">
|
||||
<div className={styles.line} />
|
||||
<div className={styles.line} />
|
||||
<div className={styles.line} />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import Image from "@/components/Image"
|
||||
|
||||
import styles from "./language.module.css"
|
||||
|
||||
export default function LanguageSwitcher() {
|
||||
return (
|
||||
<div className={styles.switcher}>
|
||||
<Image
|
||||
alt="Swedish flag"
|
||||
height={21}
|
||||
src="/_static/icons/sweden.svg"
|
||||
width={21}
|
||||
/>
|
||||
<span>SV / SEK</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
.switcher {
|
||||
align-items: center;
|
||||
display: none;
|
||||
font-family: var(--typography-Body-Regular-fontFamily);
|
||||
font-size: 1.4rem;
|
||||
font-weight: 400;
|
||||
gap: 0.6rem;
|
||||
line-height: 1.6rem;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.switcher {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import Link from "next/link"
|
||||
|
||||
import Image from "@/components/Image"
|
||||
|
||||
import styles from "./logo.module.css"
|
||||
|
||||
import { LogoProps } from "@/types/components/header/logo"
|
||||
|
||||
export default async function Logo({ title, height, width, src }: LogoProps) {
|
||||
return (
|
||||
<Link className={styles.link} href="#">
|
||||
<Image alt={title} height={height} src={src} width={width} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.link {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
.header {
|
||||
align-items: center;
|
||||
background-color: var(--some-white-color, #fff);
|
||||
box-shadow: 0px 1.0006656646728516px 1.0006656646728516px 0px #0000000d;
|
||||
display: grid;
|
||||
gap: 3rem;
|
||||
grid-template-columns: 1fr auto auto;
|
||||
height: var(--header-height);
|
||||
|
||||
padding: 0 2rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.header {
|
||||
background-color: var(--some-grey-color, #ececec);
|
||||
border-bottom: 0.1rem solid var(--some-grey-color, #ccc);
|
||||
box-shadow: none;
|
||||
gap: 3.2rem;
|
||||
grid-template-columns: 1fr 19rem auto auto;
|
||||
padding: 0 2.4rem;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import { GetHeader } from "@/lib/graphql/Query/Header.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
|
||||
import Logo from "./Logo"
|
||||
|
||||
import styles from "./header.module.css"
|
||||
|
||||
import type { LangParams } from "@/types/params"
|
||||
import { HeaderQueryData } from "@/types/requests/header"
|
||||
|
||||
export default async function Header({ lang }: LangParams) {
|
||||
const { data } = await request<HeaderQueryData>(GetHeader, {
|
||||
locale: lang,
|
||||
})
|
||||
|
||||
if (
|
||||
!data.all_header.items.length ||
|
||||
!data.all_header.items?.[0].logoConnection.totalCount
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const logo = data.all_header.items[0].logoConnection.edges[0]
|
||||
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<Logo
|
||||
title={logo.node.title}
|
||||
height={logo.node.dimension.height}
|
||||
width={logo.node.dimension.width}
|
||||
src={logo.node.url}
|
||||
/>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/new-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/new-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 2,
|
||||
@@ -44,7 +44,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/good-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/good-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 3,
|
||||
@@ -67,7 +67,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/close-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/close-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 4,
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/new-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/new-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 2,
|
||||
@@ -44,7 +44,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/good-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/good-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 3,
|
||||
@@ -67,7 +67,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/close-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/close-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 4,
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/new-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/new-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 2,
|
||||
@@ -44,7 +44,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/good-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/good-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 3,
|
||||
@@ -67,7 +67,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/close-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/close-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 4,
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/est-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/new-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/new-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 2,
|
||||
@@ -44,7 +44,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/good-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/good-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 3,
|
||||
@@ -67,7 +67,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyaltylevels/close-friend.svg"
|
||||
"logo": "/_static/icons/loyaltyLevels/close-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 4,
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltyLevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import styles from "./contactRow.module.css"
|
||||
import type { ContactRowProps } from "@/types/components/loyalty/sidebar"
|
||||
|
||||
export default async function ContactRow({ contact }: ContactRowProps) {
|
||||
const data = await serverClient().contentstack.contactConfig.get()
|
||||
const data = await serverClient().contentstack.config.contact()
|
||||
|
||||
const val = getValueFromContactConfig(contact.contact_field, data)
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/dear-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/dear-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 5,
|
||||
@@ -127,7 +127,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/loyal-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/loyal-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 6,
|
||||
@@ -164,7 +164,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/true-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/true-friend.svg"
|
||||
},
|
||||
{
|
||||
"tier": 7,
|
||||
@@ -194,7 +194,7 @@
|
||||
"href": ""
|
||||
}
|
||||
],
|
||||
"logo": "/_static/icons/best-friend.png"
|
||||
"logo": "/_static/icons/loyaltylevels/best-friend.svg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -62,3 +62,39 @@ query GetAccountPageRefs($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetDaDeEnUrlsAccountPage($uid: String!) {
|
||||
de: all_account_page(where: { uid: $uid }, locale: "de") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
en: all_account_page(where: { uid: $uid }, locale: "en") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
da: all_account_page(where: { uid: $uid }, locale: "da") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetFiNoSvUrlsAccountPage($uid: String!) {
|
||||
fi: all_account_page(where: { uid: $uid }, locale: "fi") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
no: all_account_page(where: { uid: $uid }, locale: "no") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
sv: all_account_page(where: { uid: $uid }, locale: "sv") {
|
||||
items {
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#import "../Fragments/Image.graphql"
|
||||
#import "../Fragments/Refs/System.graphql"
|
||||
|
||||
query GetHeader($locale: String!) {
|
||||
all_header(limit: 1, locale: $locale) {
|
||||
query GetCurrentHeader($locale: String!) {
|
||||
all_current_header(limit: 1, locale: $locale) {
|
||||
items {
|
||||
frontpage_link_text
|
||||
logoConnection {
|
||||
@@ -30,3 +31,13 @@ query GetHeader($locale: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetCurrentHeaderRef($locale: String!) {
|
||||
all_current_header(limit: 1, locale: $locale) {
|
||||
items {
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
query GetDaDeEnUrls($uid: String!) {
|
||||
query GetDaDeEnUrlsCurrentBlocksPage($uid: String!) {
|
||||
de: current_blocks_page(uid: $uid, locale: "de") {
|
||||
url: original_url
|
||||
}
|
||||
en: current_blocks_page(uid: $uid, locale: "en") {
|
||||
url: original_url
|
||||
}
|
||||
da: current_blocks_page(uid: $uid, locale: "da") {
|
||||
url: original_url
|
||||
}
|
||||
}
|
||||
|
||||
query GetFiNoSvUrls($uid: String!) {
|
||||
query GetFiNoSvUrlsCurrentBlocksPage($uid: String!) {
|
||||
fi: current_blocks_page(uid: $uid, locale: "fi") {
|
||||
url: original_url
|
||||
}
|
||||
@@ -18,4 +21,3 @@ query GetFiNoSvUrls($uid: String!) {
|
||||
url: original_url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,3 +245,57 @@ query GetLoyaltyPageRefs($locale: String!, $uid: String!) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetDaDeEnUrlsLoyaltyPage($uid: String!) {
|
||||
de: all_loyalty_page(where: { uid: $uid }, locale: "de") {
|
||||
items {
|
||||
web {
|
||||
original_url
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
en: all_loyalty_page(where: { uid: $uid }, locale: "en") {
|
||||
items {
|
||||
web {
|
||||
original_url
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
da: all_loyalty_page(where: { uid: $uid }, locale: "da") {
|
||||
items {
|
||||
web {
|
||||
original_url
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
query GetFiNoSvUrlsLoyaltyPage($uid: String!) {
|
||||
sv: all_loyalty_page(where: { uid: $uid }, locale: "sv") {
|
||||
items {
|
||||
web {
|
||||
original_url
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
no: all_loyalty_page(where: { uid: $uid }, locale: "no") {
|
||||
items {
|
||||
web {
|
||||
original_url
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
fi: all_loyalty_page(where: { uid: $uid }, locale: "fi") {
|
||||
items {
|
||||
web {
|
||||
original_url
|
||||
}
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,12 +54,18 @@ export const middleware: NextMiddleware = async (request) => {
|
||||
}
|
||||
|
||||
if (isCurrent) {
|
||||
searchParams.set("uid", uid)
|
||||
searchParams.set("uri", pathNameWithoutLang)
|
||||
return NextResponse.rewrite(
|
||||
new URL(
|
||||
`/${lang}/current-content-page?${searchParams.toString()}`,
|
||||
nextUrl
|
||||
)
|
||||
),
|
||||
{
|
||||
request: {
|
||||
headers,
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
29
public/_static/img/icon-scandic-friends.svg
Normal file
29
public/_static/img/icon-scandic-friends.svg
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="86px" height="85px" viewBox="0 0 86 85" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Page 1</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g fill="#020203">
|
||||
<polygon id="Fill-1" points="35.8989 74.2547 41.3609 74.9957 41.1749 76.3727 37.3479 75.8537 36.9619 78.6917 40.5599 79.1807 40.3729 80.5567 36.7739 80.0677 36.1449 84.7137 34.5099 84.4907"></polygon>
|
||||
<g id="Group-32" transform="translate(0.000000, 0.212600)">
|
||||
<path d="M50.4209,78.4689 C51.4549,78.2469 51.8999,77.4989 51.6809,76.4809 C51.4479,75.4059 50.8599,74.9409 49.7999,75.1689 L48.4269,75.4659 L49.1349,78.7469 L50.4209,78.4689 Z M46.5529,74.5969 L50.1169,73.8279 C51.5299,73.5229 52.8289,73.9829 53.2369,75.8779 C53.5189,77.1799 53.1559,78.3089 52.0879,78.7169 L52.0939,78.7449 C53.1199,78.6269 53.7629,79.0669 54.1249,80.4679 C54.2789,81.1159 54.4639,81.9049 54.6239,82.5069 C54.7589,82.9949 55.0509,83.1849 55.2939,83.2799 L53.4689,83.6739 C53.2749,83.5239 53.1369,83.2279 53.0079,82.8399 C52.8199,82.2449 52.7119,81.6759 52.5249,80.9469 C52.2599,79.8499 51.8159,79.4419 50.7129,79.6799 L49.3979,79.9629 L50.3429,84.3489 L48.7309,84.6969 L46.5529,74.5969 Z" id="Fill-2"></path>
|
||||
<polygon id="Fill-4" points="57.6152 71.0411 59.0562 70.2391 64.0772 79.2611 62.6362 80.0631"></polygon>
|
||||
<polygon id="Fill-6" points="63.5429 67.1603 67.5029 63.2213 68.4829 64.2063 65.6919 66.9813 67.7119 69.0133 70.3389 66.4013 71.3179 67.3863 68.6909 69.9983 71.0189 72.3373 73.8919 69.4803 74.8709 70.4653 70.8279 74.4853"></polygon>
|
||||
<polygon id="Fill-8" points="70.6416 58.6983 71.5006 56.8813 79.7366 57.4683 79.7476 57.4413 72.7936 54.1493 73.4626 52.7373 82.7956 57.1553 81.9356 58.9723 73.5166 58.3003 73.5046 58.3263 80.6426 61.7043 79.9736 63.1163"></polygon>
|
||||
<path d="M83.8027,46.3058 L83.8777,44.9488 C83.9527,43.5908 83.0887,42.9788 80.0717,42.8118 C77.1677,42.6528 76.1297,43.0878 76.0517,44.5178 L75.9767,45.8748 L83.8027,46.3058 Z M74.6437,47.4518 L74.8057,44.5218 C74.9837,41.2858 77.1147,40.9978 80.1597,41.1658 C83.2067,41.3338 85.2927,41.8548 85.1147,45.0888 L84.9537,48.0198 L74.6437,47.4518 Z" id="Fill-10"></path>
|
||||
<path d="M80.7158,32.4347 L80.9648,32.3567 C82.1658,31.9817 82.6108,31.2677 82.2698,30.1767 C81.9598,29.1827 81.1008,28.9047 80.3678,29.1327 C79.3328,29.4557 79.0398,30.1217 79.0538,31.0887 L79.0168,32.2827 C78.9118,33.8917 78.3598,34.7457 76.9508,35.1847 C75.1548,35.7437 73.7818,34.8387 73.2038,32.9877 C72.4168,30.4597 73.9078,29.7067 74.9318,29.3877 L75.1528,29.3187 L75.6438,30.8937 L75.4358,30.9577 C74.5378,31.2387 74.1378,31.8027 74.4448,32.7837 C74.6538,33.4597 75.2308,33.9927 76.2788,33.6657 C77.1238,33.4027 77.4168,32.8407 77.4908,31.6657 L77.5458,30.4817 C77.6088,28.9327 78.1798,28.0877 79.5328,27.6657 C81.6318,27.0107 82.8988,28.0107 83.5188,29.9987 C84.2808,32.4447 82.8218,33.5047 81.4428,33.9357 L81.2068,34.0097 L80.7158,32.4347 Z" id="Fill-12"></path>
|
||||
<path d="M9.393,54.3595 L9.642,54.2845 C10.846,53.9155 11.294,53.2045 10.959,52.1115 C10.655,51.1155 9.797,50.8335 9.064,51.0565 C8.026,51.3745 7.731,52.0385 7.738,53.0055 L7.695,54.1985 C7.581,55.8055 7.023,56.6575 5.612,57.0885 C3.814,57.6375 2.446,56.7245 1.879,54.8715 C1.105,52.3385 2.602,51.5945 3.626,51.2825 L3.847,51.2135 L4.329,52.7915 L4.122,52.8545 C3.222,53.1295 2.82,53.6915 3.121,54.6735 C3.327,55.3515 3.899,55.8875 4.951,55.5665 C5.795,55.3075 6.093,54.7485 6.173,53.5745 L6.234,52.3895 C6.306,50.8395 6.881,49.9995 8.236,49.5855 C10.339,48.9425 11.6,49.9495 12.209,51.9405 C12.957,54.3895 11.494,55.4415 10.11,55.8655 L9.874,55.9375 L9.393,54.3595 Z" id="Fill-14"></path>
|
||||
<path d="M7.0551,37.4132 L7.2731,37.4232 C9.1371,37.5062 10.9251,38.3382 10.8181,40.7672 C10.7021,43.3682 8.9901,44.1022 5.3031,43.9392 C1.6181,43.7762 -0.0239,42.8922 0.0921,40.2762 C0.2221,37.3422 2.3991,37.2642 3.4681,37.3112 L3.6561,37.3202 L3.5831,38.9682 L3.4101,38.9602 C2.4561,38.9182 1.3441,39.1582 1.3341,40.3462 C1.2811,41.5592 2.0091,42.1422 5.3331,42.2902 C8.6581,42.4372 9.5211,41.9252 9.5741,40.7112 C9.6351,39.3382 8.1101,39.1102 7.1571,39.0682 L6.9831,39.0602 L7.0551,37.4132 Z" id="Fill-16"></path>
|
||||
<path d="M9.9301,27.5294 L4.7731,26.6764 L4.7611,26.7034 L8.9951,29.7864 L9.9301,27.5294 Z M3.1021,27.0804 L3.9101,25.1304 L14.4921,26.5684 L13.8331,28.1584 L11.2501,27.7784 L10.0951,30.5714 L12.1781,32.1544 L11.5201,33.7444 L3.1021,27.0804 Z" id="Fill-18"></path>
|
||||
<polygon id="Fill-20" points="9.6425 15.6002 11.0265 14.1412 18.6755 17.2642 18.6955 17.2422 13.1075 11.9452 14.1825 10.8122 21.6815 17.9202 20.2975 19.3802 12.5015 16.1182 12.4825 16.1392 18.2165 21.5752 17.1415 22.7092"></polygon>
|
||||
<path d="M26.75,13.086 L27.974,12.493 C29.197,11.9 29.314,10.846 27.994,8.126 C26.727,5.508 25.841,4.813 24.553,5.437 L23.33,6.03 L26.75,13.086 Z M21.302,5.63 L23.944,4.35 C26.862,2.935 28.147,4.659 29.479,7.406 C30.811,10.153 31.368,12.231 28.452,13.644 L25.808,14.925 L21.302,5.63 Z" id="Fill-22"></path>
|
||||
<polygon id="Fill-24" points="34.7387 0.7355 36.3637 0.4585 38.0997 10.6415 36.4727 10.9185"></polygon>
|
||||
<path d="M50.4892,7.2931 L50.4632,7.5081 C50.2372,9.3621 49.2702,11.0811 46.8572,10.7891 C44.2712,10.4751 43.6692,8.7101 44.1142,5.0481 C44.5582,1.3841 45.5662,-0.1849 48.1662,0.1311 C51.0832,0.4851 50.9932,2.6611 50.8632,3.7241 L50.8422,3.9111 L49.2032,3.7111 L49.2242,3.5401 C49.3392,2.5911 49.1842,1.4651 48.0012,1.3651 C46.7942,1.2181 46.1572,1.8991 45.7572,5.2031 C45.3562,8.5071 45.8012,9.4071 47.0082,9.5531 C48.3722,9.7191 48.7152,8.2151 48.8302,7.2671 L48.8502,7.0951 L50.4892,7.2931 Z" id="Fill-26"></path>
|
||||
<polygon id="Fill-28" points="21.3622 75.4327 20.0582 74.3387 74.5042 9.4527 75.8072 10.5467"></polygon>
|
||||
<polygon id="Fill-30" points="10.9316 75.5675 9.6276 74.4735 64.0736 9.5885 65.3776 10.6815"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.2 KiB |
@@ -188,3 +188,24 @@ export const validateAccountPageRefsSchema = z.object({
|
||||
export type AccountPageRefsDataRaw = z.infer<
|
||||
typeof validateAccountPageRefsSchema
|
||||
>
|
||||
|
||||
export const validateLanguageSwitcherData = z.object({
|
||||
en: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
da: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
de: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
fi: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
sv: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
no: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { batchRequest } from "@/lib/graphql/batchRequest"
|
||||
import {
|
||||
GetAccountPage,
|
||||
GetAccountPageRefs,
|
||||
GetDaDeEnUrlsAccountPage,
|
||||
GetFiNoSvUrlsAccountPage,
|
||||
} from "@/lib/graphql/Query/AccountPage.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||
@@ -18,11 +22,16 @@ import {
|
||||
AccountPageRefsDataRaw,
|
||||
validateAccountPageRefsSchema,
|
||||
validateAccountPageSchema,
|
||||
validateLanguageSwitcherData,
|
||||
} from "./output"
|
||||
import { getConnections } from "./utils"
|
||||
|
||||
import { ContentEntries } from "@/types/components/myPages/myPage/enums"
|
||||
import { Embeds } from "@/types/requests/embeds"
|
||||
import {
|
||||
LanguageSwitcherData,
|
||||
LanguageSwitcherQueryDataRaw,
|
||||
} from "@/types/requests/languageSwitcher"
|
||||
import { Edges } from "@/types/requests/utils/edges"
|
||||
import { RTEDocument } from "@/types/rte/node"
|
||||
|
||||
@@ -131,4 +140,38 @@ export const accountPageQueryRouter = router({
|
||||
|
||||
return accountPage
|
||||
}),
|
||||
languageSwitcher: contentstackProcedure.query(async ({ ctx }) => {
|
||||
const variables = {
|
||||
uid: ctx.uid,
|
||||
}
|
||||
|
||||
const res = await batchRequest<LanguageSwitcherQueryDataRaw>([
|
||||
{
|
||||
document: GetDaDeEnUrlsAccountPage,
|
||||
variables,
|
||||
},
|
||||
{
|
||||
document: GetFiNoSvUrlsAccountPage,
|
||||
variables,
|
||||
},
|
||||
])
|
||||
|
||||
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
|
||||
(acc, key) => {
|
||||
const item = res.data[key as Lang]?.items[0]
|
||||
const url = item ? `/${key}${item.url}` : undefined
|
||||
return { ...acc, [key]: { url, isExternal: false } }
|
||||
},
|
||||
{} as LanguageSwitcherData
|
||||
)
|
||||
|
||||
const validatedLanguageSwitcherData =
|
||||
validateLanguageSwitcherData.safeParse(urls)
|
||||
|
||||
if (!validatedLanguageSwitcherData.success) {
|
||||
throw internalServerError(validatedLanguageSwitcherData.error)
|
||||
}
|
||||
|
||||
return { lang: ctx.lang, urls }
|
||||
}),
|
||||
})
|
||||
|
||||
5
server/routers/contentstack/config/index.ts
Normal file
5
server/routers/contentstack/config/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { mergeRouters } from "@/server/trpc"
|
||||
|
||||
import { configQueryRouter } from "./query"
|
||||
|
||||
export const configRouter = mergeRouters(configQueryRouter)
|
||||
135
server/routers/contentstack/config/output.ts
Normal file
135
server/routers/contentstack/config/output.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import { z } from "zod"
|
||||
|
||||
import { Image } from "@/types/image"
|
||||
|
||||
// Help me write this zod schema based on the type ContactConfig
|
||||
export const validateContactConfigSchema = z.object({
|
||||
all_contact_config: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
email: z.object({
|
||||
name: z.string().nullable(),
|
||||
address: z.string().nullable(),
|
||||
}),
|
||||
email_loyalty: z.object({
|
||||
name: z.string().nullable(),
|
||||
address: z.string().nullable(),
|
||||
}),
|
||||
mailing_address: z.object({
|
||||
zip: z.string().nullable(),
|
||||
street: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
city: z.string().nullable(),
|
||||
country: z.string().nullable(),
|
||||
}),
|
||||
phone: z.object({
|
||||
number: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
}),
|
||||
phone_loyalty: z.object({
|
||||
number: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
}),
|
||||
visiting_address: z.object({
|
||||
zip: z.string().nullable(),
|
||||
country: z.string().nullable(),
|
||||
city: z.string().nullable(),
|
||||
street: z.string().nullable(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
export enum ContactFieldGroupsEnum {
|
||||
email = "email",
|
||||
email_loyalty = "email_loyalty",
|
||||
mailing_address = "mailing_address",
|
||||
phone = "phone",
|
||||
phone_loyalty = "phone_loyalty",
|
||||
visiting_address = "visiting_address",
|
||||
}
|
||||
|
||||
export type ContactFieldGroups = keyof typeof ContactFieldGroupsEnum
|
||||
|
||||
export type ContactConfigData = z.infer<typeof validateContactConfigSchema>
|
||||
|
||||
export type ContactConfig = ContactConfigData["all_contact_config"]["items"][0]
|
||||
|
||||
export type ContactFields = {
|
||||
display_text: string | null
|
||||
contact_field: string
|
||||
}
|
||||
|
||||
export const validateHeaderConfigSchema = z.object({
|
||||
all_current_header: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
frontpage_link_text: z.string(),
|
||||
logoConnection: z.object({
|
||||
edges: z.array(
|
||||
z.object({
|
||||
node: z.object({
|
||||
description: z.string().optional().nullable(),
|
||||
dimension: z.object({
|
||||
height: z.number(),
|
||||
width: z.number(),
|
||||
}),
|
||||
metadata: z.any().nullable(),
|
||||
system: z.object({
|
||||
uid: z.string(),
|
||||
}),
|
||||
title: z.string().nullable(),
|
||||
url: z.string().nullable(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
menu: z.object({
|
||||
links: z.array(
|
||||
z.object({
|
||||
href: z.string(),
|
||||
title: z.string(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
top_menu: z.object({
|
||||
links: z.array(
|
||||
z.object({
|
||||
link: z.object({
|
||||
href: z.string(),
|
||||
title: z.string(),
|
||||
}),
|
||||
show_on_mobile: z.boolean(),
|
||||
sort_order_mobile: z.number(),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
export type HeaderDataRaw = z.infer<typeof validateHeaderConfigSchema>
|
||||
|
||||
export type HeaderData = Omit<
|
||||
HeaderDataRaw["all_current_header"]["items"][0],
|
||||
"logoConnection"
|
||||
> & {
|
||||
logo: Image
|
||||
}
|
||||
|
||||
const validateHeaderRefConfigSchema = z.object({
|
||||
all_current_header: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
system: z.object({
|
||||
content_type_uid: z.string(),
|
||||
uid: z.string(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
export type HeaderRefDataRaw = z.infer<typeof validateHeaderRefConfigSchema>
|
||||
84
server/routers/contentstack/config/query.ts
Normal file
84
server/routers/contentstack/config/query.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
|
||||
import {
|
||||
GetCurrentHeader,
|
||||
GetCurrentHeaderRef,
|
||||
} from "@/lib/graphql/Query/CurrentHeader.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||
import { contentstackProcedure, publicProcedure, router } from "@/server/trpc"
|
||||
|
||||
import { generateTag } from "@/utils/generateTag"
|
||||
|
||||
import {
|
||||
type ContactConfigData,
|
||||
HeaderData,
|
||||
HeaderDataRaw,
|
||||
HeaderRefDataRaw,
|
||||
validateContactConfigSchema,
|
||||
validateHeaderConfigSchema,
|
||||
} from "./output"
|
||||
|
||||
export const configQueryRouter = router({
|
||||
contact: contentstackProcedure.query(async ({ ctx }) => {
|
||||
const { lang } = ctx
|
||||
|
||||
const response = await request<ContactConfigData>(GetContactConfig, {
|
||||
locale: lang,
|
||||
})
|
||||
|
||||
if (!response.data) {
|
||||
throw notFound(response)
|
||||
}
|
||||
|
||||
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
|
||||
response.data
|
||||
)
|
||||
|
||||
if (!validatedContactConfigConfig.success) {
|
||||
throw internalServerError(validatedContactConfigConfig.error)
|
||||
}
|
||||
|
||||
return validatedContactConfigConfig.data.all_contact_config.items[0]
|
||||
}),
|
||||
header: publicProcedure.query(async ({ ctx }) => {
|
||||
const responseRef = await request<HeaderRefDataRaw>(GetCurrentHeaderRef, {
|
||||
locale: ctx.lang,
|
||||
})
|
||||
|
||||
const response = await request<HeaderDataRaw>(
|
||||
GetCurrentHeader,
|
||||
{ locale: ctx.lang },
|
||||
{
|
||||
next: {
|
||||
tags: [
|
||||
generateTag(
|
||||
ctx.lang,
|
||||
responseRef.data.all_current_header.items[0].system.uid
|
||||
),
|
||||
],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if (!response.data) {
|
||||
throw notFound(response)
|
||||
}
|
||||
|
||||
const validatedHeaderConfig = validateHeaderConfigSchema.safeParse(
|
||||
response.data
|
||||
)
|
||||
|
||||
if (!validatedHeaderConfig.success) {
|
||||
throw internalServerError(validatedHeaderConfig.error)
|
||||
}
|
||||
|
||||
const logo =
|
||||
validatedHeaderConfig.data.all_current_header.items[0].logoConnection
|
||||
.edges?.[0]?.node
|
||||
|
||||
return {
|
||||
...validatedHeaderConfig.data.all_current_header.items[0],
|
||||
logo,
|
||||
} as HeaderData
|
||||
}),
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
import { mergeRouters } from "@/server/trpc"
|
||||
|
||||
import { contactConfigQueryRouter } from "./query"
|
||||
|
||||
export const contactConfigRouter = mergeRouters(contactConfigQueryRouter)
|
||||
@@ -1,60 +0,0 @@
|
||||
import { z } from "zod"
|
||||
|
||||
// Help me write this zod schema based on the type ContactConfig
|
||||
export const validateContactConfigSchema = z.object({
|
||||
all_contact_config: z.object({
|
||||
items: z.array(
|
||||
z.object({
|
||||
email: z.object({
|
||||
name: z.string().nullable(),
|
||||
address: z.string().nullable(),
|
||||
}),
|
||||
email_loyalty: z.object({
|
||||
name: z.string().nullable(),
|
||||
address: z.string().nullable(),
|
||||
}),
|
||||
mailing_address: z.object({
|
||||
zip: z.string().nullable(),
|
||||
street: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
city: z.string().nullable(),
|
||||
country: z.string().nullable(),
|
||||
}),
|
||||
phone: z.object({
|
||||
number: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
}),
|
||||
phone_loyalty: z.object({
|
||||
number: z.string().nullable(),
|
||||
name: z.string().nullable(),
|
||||
}),
|
||||
visiting_address: z.object({
|
||||
zip: z.string().nullable(),
|
||||
country: z.string().nullable(),
|
||||
city: z.string().nullable(),
|
||||
street: z.string().nullable(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
}),
|
||||
})
|
||||
|
||||
export enum ContactFieldGroupsEnum {
|
||||
email = "email",
|
||||
email_loyalty = "email_loyalty",
|
||||
mailing_address = "mailing_address",
|
||||
phone = "phone",
|
||||
phone_loyalty = "phone_loyalty",
|
||||
visiting_address = "visiting_address",
|
||||
}
|
||||
|
||||
export type ContactFieldGroups = keyof typeof ContactFieldGroupsEnum
|
||||
|
||||
export type ContactConfigData = z.infer<typeof validateContactConfigSchema>
|
||||
|
||||
export type ContactConfig = ContactConfigData["all_contact_config"]["items"][0]
|
||||
|
||||
export type ContactFields = {
|
||||
display_text: string | null
|
||||
contact_field: string
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
|
||||
import { request } from "@/lib/graphql/request"
|
||||
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||
import { contentstackProcedure, router } from "@/server/trpc"
|
||||
|
||||
import { type ContactConfigData, validateContactConfigSchema } from "./output"
|
||||
|
||||
export const contactConfigQueryRouter = router({
|
||||
get: contentstackProcedure.query(async ({ ctx }) => {
|
||||
const { lang } = ctx
|
||||
|
||||
const response = await request<ContactConfigData>(GetContactConfig, {
|
||||
locale: lang,
|
||||
})
|
||||
|
||||
if (!response.data) {
|
||||
throw notFound(response)
|
||||
}
|
||||
|
||||
const validatedContactConfigConfig = validateContactConfigSchema.safeParse(
|
||||
response.data
|
||||
)
|
||||
|
||||
if (!validatedContactConfigConfig.success) {
|
||||
throw internalServerError(validatedContactConfigConfig.error)
|
||||
}
|
||||
|
||||
return validatedContactConfigConfig.data.all_contact_config.items[0]
|
||||
}),
|
||||
})
|
||||
@@ -2,14 +2,14 @@ import { router } from "@/server/trpc"
|
||||
|
||||
import { accountPageRouter } from "./accountPage"
|
||||
import { breadcrumbsRouter } from "./breadcrumbs"
|
||||
import { contactConfigRouter } from "./contactConfig"
|
||||
import { configRouter } from "./config"
|
||||
import { loyaltyPageRouter } from "./loyaltyPage"
|
||||
import { myPagesRouter } from "./myPages"
|
||||
|
||||
export const contentstackRouter = router({
|
||||
breadcrumbs: breadcrumbsRouter,
|
||||
accountPage: accountPageRouter,
|
||||
contactConfig: contactConfigRouter,
|
||||
config: configRouter,
|
||||
loyaltyPage: loyaltyPageRouter,
|
||||
myPages: myPagesRouter,
|
||||
})
|
||||
|
||||
@@ -306,3 +306,24 @@ export const validateLoyaltyPageRefsSchema = z.object({
|
||||
export type LoyaltyPageRefsDataRaw = z.infer<
|
||||
typeof validateLoyaltyPageRefsSchema
|
||||
>
|
||||
|
||||
export const validateLanguageSwitcherData = z.object({
|
||||
en: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
da: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
de: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
fi: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
sv: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
no: z
|
||||
.object({ url: z.string().optional(), isExternal: z.boolean() })
|
||||
.nullable(),
|
||||
})
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { batchRequest } from "@/lib/graphql/batchRequest"
|
||||
import {
|
||||
GetDaDeEnUrlsLoyaltyPage,
|
||||
GetFiNoSvUrlsLoyaltyPage,
|
||||
GetLoyaltyPage,
|
||||
GetLoyaltyPageRefs,
|
||||
} from "@/lib/graphql/Query/LoyaltyPage.graphql"
|
||||
@@ -17,12 +21,17 @@ import { removeEmptyObjects } from "../../utils"
|
||||
import {
|
||||
LoyaltyPage,
|
||||
type LoyaltyPageRefsDataRaw,
|
||||
validateLanguageSwitcherData,
|
||||
validateLoyaltyPageRefsSchema,
|
||||
validateLoyaltyPageSchema,
|
||||
} from "./output"
|
||||
import { getConnections } from "./utils"
|
||||
|
||||
import { LoyaltyBlocksTypenameEnum } from "@/types/components/loyalty/enums"
|
||||
import {
|
||||
LanguageSwitcherData,
|
||||
LanguageSwitcherQueryDataRaw,
|
||||
} from "@/types/requests/languageSwitcher"
|
||||
|
||||
function makeButtonObject(button: any) {
|
||||
return {
|
||||
@@ -172,4 +181,44 @@ export const loyaltyPageQueryRouter = router({
|
||||
// Assert LoyaltyPage type to get correct typings for RTE fields
|
||||
return validatedLoyaltyPage.data as LoyaltyPage
|
||||
}),
|
||||
languageSwitcher: contentstackProcedure.query(async ({ ctx }) => {
|
||||
const variables = {
|
||||
uid: ctx.uid,
|
||||
}
|
||||
|
||||
const res = await batchRequest<LanguageSwitcherQueryDataRaw>([
|
||||
{
|
||||
document: GetDaDeEnUrlsLoyaltyPage,
|
||||
variables,
|
||||
},
|
||||
{
|
||||
document: GetFiNoSvUrlsLoyaltyPage,
|
||||
variables,
|
||||
},
|
||||
])
|
||||
|
||||
const urls = Object.keys(res.data).reduce<LanguageSwitcherData>(
|
||||
(acc, key) => {
|
||||
const item = res.data[key as Lang]?.items[0]
|
||||
const url = item
|
||||
? item.web?.original_url || `/${key}${item.url}`
|
||||
: undefined
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[key]: { url, isExternal: !!item?.web?.original_url },
|
||||
}
|
||||
},
|
||||
{} as LanguageSwitcherData
|
||||
)
|
||||
|
||||
const validatedLanguageSwitcherData =
|
||||
validateLanguageSwitcherData.safeParse(urls)
|
||||
|
||||
if (!validatedLanguageSwitcherData.success) {
|
||||
throw internalServerError(validatedLanguageSwitcherData.error)
|
||||
}
|
||||
|
||||
return { lang: ctx.lang, urls }
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -9,11 +9,11 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/new-friend.svg",
|
||||
},
|
||||
{
|
||||
tier: 2,
|
||||
name: "New Friend",
|
||||
name: "Good Friend",
|
||||
requiredPoints: 50000,
|
||||
requiredNights: "X",
|
||||
benefits: [
|
||||
@@ -21,11 +21,11 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/good-friend.svg",
|
||||
},
|
||||
{
|
||||
tier: 3,
|
||||
name: "New Friend",
|
||||
name: "Close Friend",
|
||||
requiredPoints: 50000,
|
||||
requiredNights: "X",
|
||||
benefits: [
|
||||
@@ -33,11 +33,11 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/close-friend.svg",
|
||||
},
|
||||
{
|
||||
tier: 4,
|
||||
name: "New Friend",
|
||||
name: "Dear Friend",
|
||||
requiredPoints: 50000,
|
||||
requiredNights: "X",
|
||||
benefits: [
|
||||
@@ -45,11 +45,11 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/dear-friend.svg",
|
||||
},
|
||||
{
|
||||
tier: 5,
|
||||
name: "New Friend",
|
||||
name: "Loyal Friend",
|
||||
requiredPoints: 50000,
|
||||
requiredNights: "X",
|
||||
benefits: [
|
||||
@@ -57,11 +57,11 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/loyal-friend.svg",
|
||||
},
|
||||
{
|
||||
tier: 6,
|
||||
name: "New Friend",
|
||||
name: "True Friend",
|
||||
requiredPoints: 50000,
|
||||
requiredNights: "X",
|
||||
benefits: [
|
||||
@@ -69,11 +69,11 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/true-friend.svg",
|
||||
},
|
||||
{
|
||||
tier: 7,
|
||||
name: "New Friend",
|
||||
name: "Best Friend",
|
||||
requiredPoints: 50000,
|
||||
requiredNights: "X",
|
||||
benefits: [
|
||||
@@ -81,6 +81,6 @@ export const allLevels = [
|
||||
"Always best price",
|
||||
"Book reward nights with points",
|
||||
],
|
||||
logo: "/_static/icons/new-friend.png",
|
||||
logo: "/_static/icons/loyaltyLevels/best-friend.svg",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import type { HeaderLink, TopMenuHeaderLink } from "@/types/requests/header"
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import type { Image } from "@/types/image"
|
||||
import type { LanguageSwitcherQueryData } from "@/types/requests/languageSwitcher"
|
||||
import type {
|
||||
CurrentHeaderLink,
|
||||
TopMenuHeaderLink,
|
||||
} from "@/types/requests/currentHeader"
|
||||
|
||||
export type MainMenuProps = {
|
||||
currentLanguage: string
|
||||
frontpageLinkText: string
|
||||
homeHref: string
|
||||
links: HeaderLink[]
|
||||
links: CurrentHeaderLink[]
|
||||
logo: Image
|
||||
topMenuMobileLinks: TopMenuHeaderLink[]
|
||||
urls: LanguageSwitcherQueryData
|
||||
languageSwitcher: React.ReactNode
|
||||
bookingHref: string
|
||||
isLoggedIn: boolean
|
||||
lang: Lang
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { LanguageSwitcherQueryData } from "@/types/requests/languageSwitcher"
|
||||
import type { TopMenuHeaderLink } from "@/types/requests/header"
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import type { TopMenuHeaderLink } from "@/types/requests/currentHeader"
|
||||
|
||||
export type TopMenuProps = {
|
||||
currentLanguage: string
|
||||
frontpageLinkText: string
|
||||
homeHref: string
|
||||
links: TopMenuHeaderLink[]
|
||||
urls: LanguageSwitcherQueryData
|
||||
languageSwitcher: React.ReactNode
|
||||
lang: Lang
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { LanguageSwitcherQueryData } from "@/types/requests/languageSwitcher"
|
||||
import { Lang } from "@/constants/languages"
|
||||
|
||||
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||
|
||||
export type LanguageSwitcherLink = {
|
||||
href: string
|
||||
@@ -6,6 +8,6 @@ export type LanguageSwitcherLink = {
|
||||
}
|
||||
|
||||
export type LanguageSwitcherProps = {
|
||||
currentLanguage: string
|
||||
urls: LanguageSwitcherQueryData
|
||||
currentLanguage: Lang
|
||||
urls: LanguageSwitcherData
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ContactFields } from "@/server/routers/contentstack/contactConfig/output"
|
||||
import { ContactFields } from "@/server/routers/contentstack/config/output"
|
||||
import {
|
||||
JoinLoyaltyContact,
|
||||
Sidebar,
|
||||
|
||||
36
types/requests/currentHeader.ts
Normal file
36
types/requests/currentHeader.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { Image } from "../image"
|
||||
import type { AllRequestResponse } from "./utils/all"
|
||||
import type { Edges } from "./utils/edges"
|
||||
|
||||
export type CurrentHeaderLink = {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type TopMenuHeaderLink = {
|
||||
link: {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
show_on_mobile: boolean
|
||||
sort_order_mobile: number
|
||||
}
|
||||
|
||||
export type CurrentHeaderLinks = {
|
||||
links: CurrentHeaderLink[]
|
||||
}
|
||||
|
||||
export type TopMenuCurrentHeaderLinks = {
|
||||
links: TopMenuHeaderLink[]
|
||||
}
|
||||
|
||||
export type CurrentHeader = {
|
||||
frontpage_link_text: string
|
||||
logoConnection: Edges<Image>
|
||||
menu: CurrentHeaderLinks
|
||||
top_menu: TopMenuCurrentHeaderLinks
|
||||
}
|
||||
|
||||
export type GetCurrentHeaderData = {
|
||||
all_current_header: AllRequestResponse<CurrentHeader>
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
import type { Image } from "../image"
|
||||
import type { EdgesWithTotalCount } from "./utils/edges"
|
||||
|
||||
export type HeaderLink = {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type TopMenuHeaderLink = {
|
||||
link: {
|
||||
href: string
|
||||
title: string
|
||||
}
|
||||
show_on_mobile: boolean
|
||||
sort_order_mobile: number
|
||||
}
|
||||
|
||||
export type HeaderLinks = {
|
||||
links: HeaderLink[]
|
||||
}
|
||||
|
||||
export type TopMenuHeaderLinks = {
|
||||
links: TopMenuHeaderLink[]
|
||||
}
|
||||
|
||||
export type HeaderQueryData = {
|
||||
all_header: {
|
||||
items: {
|
||||
frontpage_link_text: string
|
||||
logoConnection: EdgesWithTotalCount<Image>
|
||||
menu: HeaderLinks
|
||||
top_menu: TopMenuHeaderLinks
|
||||
}[]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,23 @@
|
||||
type LanguageResult = {
|
||||
type CurrentLanguageResult = {
|
||||
url: string
|
||||
isExternal?: boolean
|
||||
}
|
||||
|
||||
export type LanguageSwitcherQueryData = {
|
||||
export type LanguageSwitcherData = {
|
||||
da: CurrentLanguageResult | undefined
|
||||
de: CurrentLanguageResult | undefined
|
||||
en: CurrentLanguageResult | undefined
|
||||
fi: CurrentLanguageResult | undefined
|
||||
no: CurrentLanguageResult | undefined
|
||||
sv: CurrentLanguageResult | undefined
|
||||
}
|
||||
|
||||
type LanguageResult = {
|
||||
items: { web?: { original_url?: string | null }; url: string }[]
|
||||
}
|
||||
|
||||
export type LanguageSwitcherQueryDataRaw = {
|
||||
da: LanguageResult | undefined
|
||||
de: LanguageResult | undefined
|
||||
en: LanguageResult | undefined
|
||||
fi: LanguageResult | undefined
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
ContactConfig,
|
||||
ContactFieldGroups,
|
||||
} from "@/server/routers/contentstack/contactConfig/output"
|
||||
} from "@/server/routers/contentstack/config/output"
|
||||
|
||||
export function getValueFromContactConfig(
|
||||
keyString: string,
|
||||
|
||||
Reference in New Issue
Block a user