fix: sync current header with static-pages
This commit is contained in:
@@ -60,7 +60,7 @@ export default async function CurrentContentPage({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header lang={params.lang} uid={pageData.system.uid} />
|
<Header lang={params.lang} />
|
||||||
<ContentPage data={response.data} />
|
<ContentPage data={response.data} />
|
||||||
<Tracking pageData={trackingData} />
|
<Tracking pageData={trackingData} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Script from "next/script"
|
|||||||
|
|
||||||
import AdobeScript from "@/components/Current/AdobeScript"
|
import AdobeScript from "@/components/Current/AdobeScript"
|
||||||
import Footer from "@/components/Current/Footer"
|
import Footer from "@/components/Current/Footer"
|
||||||
|
import Header from "@/components/Current/Header"
|
||||||
import LangPopup from "@/components/Current/LangPopup"
|
import LangPopup from "@/components/Current/LangPopup"
|
||||||
import SkipToMainContent from "@/components/SkipToMainContent"
|
import SkipToMainContent from "@/components/SkipToMainContent"
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ export default function RootLayout({
|
|||||||
<body className="theme-00Corecolours theme-X0Oldcorecolours">
|
<body className="theme-00Corecolours theme-X0Oldcorecolours">
|
||||||
<LangPopup lang={params.lang} />
|
<LangPopup lang={params.lang} />
|
||||||
<SkipToMainContent lang={params.lang} />
|
<SkipToMainContent lang={params.lang} />
|
||||||
|
<Header lang={params.lang} />
|
||||||
{children}
|
{children}
|
||||||
<Footer lang={params.lang} />
|
<Footer lang={params.lang} />
|
||||||
<Script id="page-tracking">{`
|
<Script id="page-tracking">{`
|
||||||
|
|||||||
@@ -3,20 +3,16 @@ import { useState } from "react"
|
|||||||
|
|
||||||
import Image from "@/components/Image"
|
import Image from "@/components/Image"
|
||||||
|
|
||||||
import Mobile from "../LanguageSwitcher/Mobile"
|
|
||||||
|
|
||||||
import styles from "./mainMenu.module.css"
|
import styles from "./mainMenu.module.css"
|
||||||
|
|
||||||
import type { MainMenuProps } from "@/types/components/current/header/mainMenu"
|
import type { MainMenuProps } from "@/types/components/current/header/mainMenu"
|
||||||
|
|
||||||
export default function MainMenu({
|
export default function MainMenu({
|
||||||
currentLanguage,
|
|
||||||
frontpageLinkText,
|
frontpageLinkText,
|
||||||
homeHref,
|
homeHref,
|
||||||
links,
|
links,
|
||||||
logo,
|
logo,
|
||||||
topMenuMobileLinks,
|
topMenuMobileLinks,
|
||||||
urls,
|
|
||||||
}: MainMenuProps) {
|
}: MainMenuProps) {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
@@ -69,8 +65,8 @@ export default function MainMenu({
|
|||||||
data-collapsable="main-menu"
|
data-collapsable="main-menu"
|
||||||
id="main-menu"
|
id="main-menu"
|
||||||
>
|
>
|
||||||
{links.map((link) => (
|
{links.map((link, i) => (
|
||||||
<li className={styles.li} key={link.href}>
|
<li className={styles.li} key={link.href + i}>
|
||||||
<a className={styles.link} href={link.href}>
|
<a className={styles.link} href={link.href}>
|
||||||
{link.title}
|
{link.title}
|
||||||
</a>
|
</a>
|
||||||
@@ -78,8 +74,8 @@ export default function MainMenu({
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
<ul className={styles.mobileList}>
|
<ul className={styles.mobileList}>
|
||||||
{topMenuMobileLinks.map(({ link }) => (
|
{topMenuMobileLinks.map(({ link }, i) => (
|
||||||
<li className={styles.mobileLi} key={link.href}>
|
<li className={styles.mobileLi} key={link.href + i}>
|
||||||
<a className={styles.mobileLink} href={link.href}>
|
<a className={styles.mobileLink} href={link.href}>
|
||||||
{link.title}
|
{link.title}
|
||||||
</a>
|
</a>
|
||||||
@@ -87,11 +83,9 @@ export default function MainMenu({
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{urls ? (
|
{/* {languageSwitcher ? (
|
||||||
<li className={styles.mobileLi}>
|
<li className={styles.mobileLi}>{languageSwitcher}</li>
|
||||||
<Mobile currentLanguage={currentLanguage} urls={urls} />
|
) : null} */}
|
||||||
</li>
|
|
||||||
) : null}
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,29 @@
|
|||||||
import Desktop from "../LanguageSwitcher/Desktop"
|
|
||||||
|
|
||||||
import styles from "./topMenu.module.css"
|
import styles from "./topMenu.module.css"
|
||||||
|
|
||||||
import type { TopMenuProps } from "@/types/components/current/header/topMenu"
|
import type { TopMenuProps } from "@/types/components/current/header/topMenu"
|
||||||
|
|
||||||
export default function TopMenu({ currentLanguage, frontpageLinkText, homeHref, links, urls }: TopMenuProps) {
|
export default function TopMenu({
|
||||||
|
frontpageLinkText,
|
||||||
|
homeHref,
|
||||||
|
links,
|
||||||
|
}: TopMenuProps) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.topMenu}>
|
<div className={styles.topMenu}>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<a
|
<a className={styles.homeLink} href={homeHref}>
|
||||||
className={styles.homeLink}
|
|
||||||
href={homeHref}
|
|
||||||
>
|
|
||||||
{frontpageLinkText}
|
{frontpageLinkText}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{urls ? (
|
{/* {languageSwitcher ? (
|
||||||
<li className="nav-secondary__item hidden-xxsmall hidden-xsmall hidden-small">
|
<li className="nav-secondary__item hidden-xxsmall hidden-xsmall hidden-small">
|
||||||
<Desktop currentLanguage={currentLanguage} urls={urls} />
|
{languageSwitcher}
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null} */}
|
||||||
|
|
||||||
{links.map(({ link }) => (
|
{links.map(({ link }, i) => (
|
||||||
<li key={link.href}>
|
<li key={link.href + i}>
|
||||||
<a
|
<a className={styles.link} href={link.href}>
|
||||||
className={styles.link}
|
|
||||||
href={link.href}
|
|
||||||
>
|
|
||||||
{link.title}
|
{link.title}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { homeHrefs } from "@/constants/homeHrefs"
|
import { homeHrefs } from "@/constants/homeHrefs"
|
||||||
import { languages } from "@/constants/languages"
|
|
||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
import { batchRequest } from "@/lib/graphql/batchRequest"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
import { GetHeader } from "@/lib/graphql/Query/Header.graphql"
|
|
||||||
import {
|
|
||||||
GetDaDeEnUrls,
|
|
||||||
GetFiNoSvUrls,
|
|
||||||
} from "@/lib/graphql/Query/LanguageSwitcher.graphql"
|
|
||||||
import { request } from "@/lib/graphql/request"
|
|
||||||
|
|
||||||
import MainMenu from "./MainMenu"
|
import MainMenu from "./MainMenu"
|
||||||
import OfflineBanner from "./OfflineBanner"
|
import OfflineBanner from "./OfflineBanner"
|
||||||
@@ -15,72 +8,33 @@ import TopMenu from "./TopMenu"
|
|||||||
|
|
||||||
import styles from "./header.module.css"
|
import styles from "./header.module.css"
|
||||||
|
|
||||||
import type { HeaderProps } from "@/types/components/current/header"
|
|
||||||
import { LangParams } from "@/types/params"
|
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) {
|
export default async function Header({ lang }: LangParams) {
|
||||||
try {
|
const data = await serverClient().contentstack.config.header()
|
||||||
const variables = {
|
|
||||||
locale: lang,
|
|
||||||
uid,
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data } = await request<HeaderQueryData>(
|
const homeHref = homeHrefs[env.NODE_ENV][lang]
|
||||||
GetHeader,
|
const { frontpage_link_text, logo, menu, top_menu } = data
|
||||||
{ 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,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
if (!data.all_header.items.length) {
|
const topMenuMobileLinks = top_menu.links
|
||||||
return null
|
.filter((link) => link.show_on_mobile)
|
||||||
}
|
.sort((a, b) => (a.sort_order_mobile < b.sort_order_mobile ? 1 : -1))
|
||||||
|
|
||||||
const currentLanguage = languages[lang]
|
return (
|
||||||
const homeHref = homeHrefs[env.NODE_ENV][lang]
|
<header className={styles.header} role="banner">
|
||||||
const { frontpage_link_text, logoConnection, menu, top_menu } =
|
<OfflineBanner />
|
||||||
data.all_header.items[0]
|
<TopMenu
|
||||||
const logo = logoConnection.edges?.[0]?.node
|
frontpageLinkText={frontpage_link_text}
|
||||||
const topMenuMobileLinks = top_menu.links
|
homeHref={homeHref}
|
||||||
.filter((link) => link.show_on_mobile)
|
links={top_menu.links}
|
||||||
.sort((a, b) => (a.sort_order_mobile < b.sort_order_mobile ? 1 : -1))
|
/>
|
||||||
|
<MainMenu
|
||||||
return (
|
frontpageLinkText={frontpage_link_text}
|
||||||
<header className={styles.header} role="banner">
|
homeHref={homeHref}
|
||||||
<OfflineBanner />
|
links={menu.links}
|
||||||
<TopMenu
|
logo={logo}
|
||||||
currentLanguage={currentLanguage}
|
topMenuMobileLinks={topMenuMobileLinks}
|
||||||
frontpageLinkText={frontpage_link_text}
|
/>
|
||||||
homeHref={homeHref}
|
</header>
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import styles from "./contactRow.module.css"
|
|||||||
import type { ContactRowProps } from "@/types/components/loyalty/sidebar"
|
import type { ContactRowProps } from "@/types/components/loyalty/sidebar"
|
||||||
|
|
||||||
export default async function ContactRow({ contact }: ContactRowProps) {
|
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)
|
const val = getValueFromContactConfig(contact.contact_field, data)
|
||||||
|
|
||||||
|
|||||||
32
lib/graphql/Query/CurrentHeader.graphql
Normal file
32
lib/graphql/Query/CurrentHeader.graphql
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#import "../Fragments/Image.graphql"
|
||||||
|
|
||||||
|
query GetCurrentHeader($locale: String!) {
|
||||||
|
all_current_header(limit: 1, locale: $locale) {
|
||||||
|
items {
|
||||||
|
frontpage_link_text
|
||||||
|
logoConnection {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
...Image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu {
|
||||||
|
links {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
top_menu {
|
||||||
|
links {
|
||||||
|
link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
|
show_on_mobile
|
||||||
|
sort_order_mobile
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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)
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { Image } from "@/types/image"
|
||||||
|
|
||||||
// Help me write this zod schema based on the type ContactConfig
|
// Help me write this zod schema based on the type ContactConfig
|
||||||
export const validateContactConfigSchema = z.object({
|
export const validateContactConfigSchema = z.object({
|
||||||
all_contact_config: z.object({
|
all_contact_config: z.object({
|
||||||
@@ -58,3 +60,61 @@ export type ContactFields = {
|
|||||||
display_text: string | null
|
display_text: string | null
|
||||||
contact_field: string
|
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(),
|
||||||
|
dimension: z.object({
|
||||||
|
height: z.number(),
|
||||||
|
width: z.number(),
|
||||||
|
}),
|
||||||
|
metadata: z.any().nullable(),
|
||||||
|
system: z.object({
|
||||||
|
uid: z.string(),
|
||||||
|
}),
|
||||||
|
title: z.string(),
|
||||||
|
url: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
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
|
||||||
|
}
|
||||||
66
server/routers/contentstack/config/query.ts
Normal file
66
server/routers/contentstack/config/query.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { GetContactConfig } from "@/lib/graphql/Query/ContactConfig.graphql"
|
||||||
|
import { GetCurrentHeader } 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 {
|
||||||
|
type ContactConfigData,
|
||||||
|
HeaderData,
|
||||||
|
HeaderDataRaw,
|
||||||
|
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 }) => {
|
||||||
|
console.log({ ctx })
|
||||||
|
const response = await request<HeaderDataRaw>(
|
||||||
|
GetCurrentHeader,
|
||||||
|
{ locale: ctx.lang },
|
||||||
|
{ next: { tags: [`header-${ctx.lang}`] } }
|
||||||
|
)
|
||||||
|
|
||||||
|
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,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]
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
5
server/routers/contentstack/header/index.ts
Normal file
5
server/routers/contentstack/header/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { mergeRouters } from "@/server/trpc"
|
||||||
|
|
||||||
|
import { accountPageQueryRouter } from "./query"
|
||||||
|
|
||||||
|
export const accountPageRouter = mergeRouters(accountPageQueryRouter)
|
||||||
190
server/routers/contentstack/header/output.ts
Normal file
190
server/routers/contentstack/header/output.ts
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
|
import {
|
||||||
|
ContentEntries,
|
||||||
|
DynamicContentComponents,
|
||||||
|
} from "@/types/components/myPages/myPage/enums"
|
||||||
|
import { Embeds } from "@/types/requests/embeds"
|
||||||
|
import { PageLinkEnum } from "@/types/requests/pageLinks"
|
||||||
|
import { Edges } from "@/types/requests/utils/edges"
|
||||||
|
import { RTEDocument } from "@/types/rte/node"
|
||||||
|
|
||||||
|
const accountPageShortcuts = z.object({
|
||||||
|
__typename: z.literal(ContentEntries.AccountPageContentShortcuts),
|
||||||
|
shortcuts: z.object({
|
||||||
|
title: z.string().nullable(),
|
||||||
|
preamble: z.string().nullable(),
|
||||||
|
shortcuts: z.array(
|
||||||
|
z.object({
|
||||||
|
linkConnection: z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: z.object({
|
||||||
|
system: z.object({
|
||||||
|
uid: z.string(),
|
||||||
|
locale: z.nativeEnum(Lang),
|
||||||
|
}),
|
||||||
|
original_url: z.string().nullable().optional(),
|
||||||
|
url: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
totalCount: z.number(),
|
||||||
|
}),
|
||||||
|
text: z.string().nullable(),
|
||||||
|
open_in_new_tab: z.boolean(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const accountPageDynamicContent = z.object({
|
||||||
|
__typename: z.literal(ContentEntries.AccountPageContentDynamicContent),
|
||||||
|
dynamic_content: z.object({
|
||||||
|
title: z.string().nullable(),
|
||||||
|
preamble: z.string().nullable(),
|
||||||
|
component: z.nativeEnum(DynamicContentComponents),
|
||||||
|
link: z.object({
|
||||||
|
linkConnection: z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: z.object({
|
||||||
|
system: z.object({
|
||||||
|
uid: z.string(),
|
||||||
|
locale: z.nativeEnum(Lang),
|
||||||
|
}),
|
||||||
|
url: z.string(),
|
||||||
|
original_url: z.string().nullable().optional(),
|
||||||
|
title: z.string(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
totalCount: z.number(),
|
||||||
|
}),
|
||||||
|
link_text: z.string(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const accountPageTextContent = z.object({
|
||||||
|
__typename: z.literal(ContentEntries.AccountPageContentTextContent),
|
||||||
|
text_content: z.object({
|
||||||
|
content: z.object({
|
||||||
|
json: z.any(),
|
||||||
|
embedded_itemsConnection: z.object({
|
||||||
|
edges: z.array(z.any()),
|
||||||
|
totalCount: z.number(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
type TextContentRaw = z.infer<typeof accountPageTextContent>
|
||||||
|
|
||||||
|
type DynamicContentRaw = z.infer<typeof accountPageDynamicContent>
|
||||||
|
|
||||||
|
type ShortcutsRaw = z.infer<typeof accountPageShortcuts>
|
||||||
|
|
||||||
|
export type Shortcuts = Omit<ShortcutsRaw, "shortcuts"> & {
|
||||||
|
shortcuts: Omit<ShortcutsRaw["shortcuts"], "shortcuts"> & {
|
||||||
|
shortcuts: {
|
||||||
|
text?: string
|
||||||
|
openInNewTab: boolean
|
||||||
|
url: string
|
||||||
|
title: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RteTextContent = Omit<TextContentRaw, "text_content"> & {
|
||||||
|
text_content: {
|
||||||
|
content: {
|
||||||
|
json: RTEDocument
|
||||||
|
embedded_itemsConnection: Edges<Embeds>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AccountPageContentItem =
|
||||||
|
| DynamicContentRaw
|
||||||
|
| Shortcuts
|
||||||
|
| RteTextContent
|
||||||
|
|
||||||
|
const accountPageContentItem = z.discriminatedUnion("__typename", [
|
||||||
|
accountPageShortcuts,
|
||||||
|
accountPageDynamicContent,
|
||||||
|
accountPageTextContent,
|
||||||
|
])
|
||||||
|
|
||||||
|
export const validateAccountPageSchema = z.object({
|
||||||
|
account_page: z.object({
|
||||||
|
url: z.string(),
|
||||||
|
title: z.string(),
|
||||||
|
content: z.array(accountPageContentItem),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AccountPageDataRaw = z.infer<typeof validateAccountPageSchema>
|
||||||
|
|
||||||
|
type AccountPageRaw = AccountPageDataRaw["account_page"]
|
||||||
|
|
||||||
|
export type AccountPage = Omit<AccountPageRaw, "content"> & {
|
||||||
|
content: AccountPageContentItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refs types
|
||||||
|
const pageConnectionRefs = z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: z.object({
|
||||||
|
__typename: z.nativeEnum(PageLinkEnum),
|
||||||
|
system: z.object({
|
||||||
|
content_type_uid: z.string(),
|
||||||
|
uid: z.string(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
const accountPageShortcutsRefs = z.object({
|
||||||
|
__typename: z.literal(ContentEntries.AccountPageContentShortcuts),
|
||||||
|
shortcuts: z.object({
|
||||||
|
shortcuts: z.array(
|
||||||
|
z.object({
|
||||||
|
linkConnection: pageConnectionRefs,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const accountPageDynamicContentRefs = z.object({
|
||||||
|
__typename: z.literal(ContentEntries.AccountPageContentDynamicContent),
|
||||||
|
dynamic_content: z.object({
|
||||||
|
link: z.object({
|
||||||
|
linkConnection: pageConnectionRefs,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
const accountPageContentItemRefs = z.discriminatedUnion("__typename", [
|
||||||
|
accountPageDynamicContentRefs,
|
||||||
|
accountPageShortcutsRefs,
|
||||||
|
])
|
||||||
|
|
||||||
|
export const validateAccountPageRefsSchema = z.object({
|
||||||
|
account_page: z.object({
|
||||||
|
content: z.array(accountPageContentItemRefs),
|
||||||
|
system: z.object({
|
||||||
|
content_type_uid: z.string(),
|
||||||
|
uid: z.string(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type AccountPageRefsDataRaw = z.infer<
|
||||||
|
typeof validateAccountPageRefsSchema
|
||||||
|
>
|
||||||
134
server/routers/contentstack/header/query.ts
Normal file
134
server/routers/contentstack/header/query.ts
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import {
|
||||||
|
GetAccountPage,
|
||||||
|
GetAccountPageRefs,
|
||||||
|
} from "@/lib/graphql/Query/AccountPage.graphql"
|
||||||
|
import { request } from "@/lib/graphql/request"
|
||||||
|
import { internalServerError, notFound } from "@/server/errors/trpc"
|
||||||
|
import { contentstackProcedure, router } from "@/server/trpc"
|
||||||
|
|
||||||
|
import {
|
||||||
|
generateRefsResponseTag,
|
||||||
|
generateTag,
|
||||||
|
generateTags,
|
||||||
|
} from "@/utils/generateTag"
|
||||||
|
|
||||||
|
import { removeEmptyObjects } from "../../utils"
|
||||||
|
import {
|
||||||
|
type AccountPage,
|
||||||
|
AccountPageRefsDataRaw,
|
||||||
|
validateAccountPageRefsSchema,
|
||||||
|
validateAccountPageSchema,
|
||||||
|
} from "./output"
|
||||||
|
import { getConnections } from "./utils"
|
||||||
|
|
||||||
|
import { ContentEntries } from "@/types/components/myPages/myPage/enums"
|
||||||
|
import { Embeds } from "@/types/requests/embeds"
|
||||||
|
import { Edges } from "@/types/requests/utils/edges"
|
||||||
|
import { RTEDocument } from "@/types/rte/node"
|
||||||
|
|
||||||
|
export const accountPageQueryRouter = router({
|
||||||
|
get: contentstackProcedure.query(async ({ ctx }) => {
|
||||||
|
const { lang, uid } = ctx
|
||||||
|
|
||||||
|
const refsResponse = await request<AccountPageRefsDataRaw>(
|
||||||
|
GetAccountPageRefs,
|
||||||
|
{
|
||||||
|
locale: lang,
|
||||||
|
uid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
next: {
|
||||||
|
tags: [generateRefsResponseTag(lang, uid)],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!refsResponse.data) {
|
||||||
|
throw notFound(refsResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove empty objects from a fetched content type. Needed since
|
||||||
|
// Contentstack returns empty objects for all non queried blocks in modular blocks.
|
||||||
|
// This is an ongoing support case in Contentstack, ticker number #00031579
|
||||||
|
const cleanedData = removeEmptyObjects(refsResponse.data)
|
||||||
|
|
||||||
|
const validatedAccountPageRefs =
|
||||||
|
validateAccountPageRefsSchema.safeParse(cleanedData)
|
||||||
|
if (!validatedAccountPageRefs.success) {
|
||||||
|
throw internalServerError(validatedAccountPageRefs.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
const connections = getConnections(validatedAccountPageRefs.data)
|
||||||
|
|
||||||
|
const tags = [
|
||||||
|
generateTags(lang, connections),
|
||||||
|
generateTag(lang, validatedAccountPageRefs.data.account_page.system.uid),
|
||||||
|
].flat()
|
||||||
|
|
||||||
|
const response = await request<AccountPageRefsDataRaw>(
|
||||||
|
GetAccountPage,
|
||||||
|
{
|
||||||
|
locale: lang,
|
||||||
|
uid,
|
||||||
|
},
|
||||||
|
{ next: { tags } }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.data) {
|
||||||
|
throw notFound(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
const validatedAccountPage = validateAccountPageSchema.safeParse(
|
||||||
|
response.data
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!validatedAccountPage.success) {
|
||||||
|
throw internalServerError(validatedAccountPage.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Make returned data nicer
|
||||||
|
const content = validatedAccountPage.data.account_page.content.map(
|
||||||
|
(block) => {
|
||||||
|
switch (block.__typename) {
|
||||||
|
case ContentEntries.AccountPageContentDynamicContent:
|
||||||
|
return block
|
||||||
|
case ContentEntries.AccountPageContentShortcuts:
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
shortcuts: {
|
||||||
|
...block.shortcuts,
|
||||||
|
shortcuts: block.shortcuts.shortcuts.map((shortcut) => ({
|
||||||
|
text: shortcut.text,
|
||||||
|
openInNewTab: shortcut.open_in_new_tab,
|
||||||
|
...shortcut.linkConnection.edges[0].node,
|
||||||
|
url:
|
||||||
|
shortcut.linkConnection.edges[0].node.original_url ||
|
||||||
|
`/${shortcut.linkConnection.edges[0].node.system.locale}${shortcut.linkConnection.edges[0].node.url}`,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case ContentEntries.AccountPageContentTextContent:
|
||||||
|
return {
|
||||||
|
...block,
|
||||||
|
text_content: {
|
||||||
|
content: {
|
||||||
|
json: block.text_content.content.json as RTEDocument,
|
||||||
|
embedded_itemsConnection: block.text_content.content
|
||||||
|
.embedded_itemsConnection as Edges<Embeds>,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const accountPage = {
|
||||||
|
...validatedAccountPage.data.account_page,
|
||||||
|
content,
|
||||||
|
} as AccountPage
|
||||||
|
|
||||||
|
return accountPage
|
||||||
|
}),
|
||||||
|
})
|
||||||
31
server/routers/contentstack/header/utils.ts
Normal file
31
server/routers/contentstack/header/utils.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { AccountPageRefsDataRaw } from "./output"
|
||||||
|
|
||||||
|
import { ContentEntries } from "@/types/components/myPages/myPage/enums"
|
||||||
|
import type { Edges } from "@/types/requests/utils/edges"
|
||||||
|
import type { NodeRefs } from "@/types/requests/utils/refs"
|
||||||
|
|
||||||
|
export function getConnections(refs: AccountPageRefsDataRaw) {
|
||||||
|
const connections: Edges<NodeRefs>[] = []
|
||||||
|
if (refs.account_page.content) {
|
||||||
|
refs.account_page.content.forEach((item) => {
|
||||||
|
switch (item.__typename) {
|
||||||
|
case ContentEntries.AccountPageContentShortcuts: {
|
||||||
|
item.shortcuts.shortcuts.forEach((shortcut) => {
|
||||||
|
if (shortcut.linkConnection.edges.length) {
|
||||||
|
connections.push(shortcut.linkConnection)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case ContentEntries.AccountPageContentDynamicContent: {
|
||||||
|
if (item.dynamic_content.link.linkConnection.edges.length) {
|
||||||
|
connections.push(item.dynamic_content.link.linkConnection)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return connections
|
||||||
|
}
|
||||||
@@ -2,14 +2,14 @@ import { router } from "@/server/trpc"
|
|||||||
|
|
||||||
import { accountPageRouter } from "./accountPage"
|
import { accountPageRouter } from "./accountPage"
|
||||||
import { breadcrumbsRouter } from "./breadcrumbs"
|
import { breadcrumbsRouter } from "./breadcrumbs"
|
||||||
import { contactConfigRouter } from "./contactConfig"
|
import { configRouter } from "./config"
|
||||||
import { loyaltyPageRouter } from "./loyaltyPage"
|
import { loyaltyPageRouter } from "./loyaltyPage"
|
||||||
import { myPagesRouter } from "./myPages"
|
import { myPagesRouter } from "./myPages"
|
||||||
|
|
||||||
export const contentstackRouter = router({
|
export const contentstackRouter = router({
|
||||||
breadcrumbs: breadcrumbsRouter,
|
breadcrumbs: breadcrumbsRouter,
|
||||||
accountPage: accountPageRouter,
|
accountPage: accountPageRouter,
|
||||||
contactConfig: contactConfigRouter,
|
config: configRouter,
|
||||||
loyaltyPage: loyaltyPageRouter,
|
loyaltyPage: loyaltyPageRouter,
|
||||||
myPages: myPagesRouter,
|
myPages: myPagesRouter,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import type { HeaderLink, TopMenuHeaderLink } from "@/types/requests/header"
|
|
||||||
import type { Image } from "@/types/image"
|
import type { Image } from "@/types/image"
|
||||||
import type { LanguageSwitcherQueryData } from "@/types/requests/languageSwitcher"
|
import type { HeaderLink, TopMenuHeaderLink } from "@/types/requests/header"
|
||||||
|
|
||||||
export type MainMenuProps = {
|
export type MainMenuProps = {
|
||||||
currentLanguage: string
|
|
||||||
frontpageLinkText: string
|
frontpageLinkText: string
|
||||||
homeHref: string
|
homeHref: string
|
||||||
links: HeaderLink[]
|
links: HeaderLink[]
|
||||||
logo: Image
|
logo: Image
|
||||||
topMenuMobileLinks: TopMenuHeaderLink[]
|
topMenuMobileLinks: TopMenuHeaderLink[]
|
||||||
urls: LanguageSwitcherQueryData
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import type { LanguageSwitcherQueryData } from "@/types/requests/languageSwitcher"
|
|
||||||
import type { TopMenuHeaderLink } from "@/types/requests/header"
|
import type { TopMenuHeaderLink } from "@/types/requests/header"
|
||||||
|
|
||||||
export type TopMenuProps = {
|
export type TopMenuProps = {
|
||||||
currentLanguage: string
|
|
||||||
frontpageLinkText: string
|
frontpageLinkText: string
|
||||||
homeHref: string
|
homeHref: string
|
||||||
links: TopMenuHeaderLink[]
|
links: TopMenuHeaderLink[]
|
||||||
urls: LanguageSwitcherQueryData
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ContactFields } from "@/server/routers/contentstack/contactConfig/output"
|
import { ContactFields } from "@/server/routers/contentstack/config/output"
|
||||||
import {
|
import {
|
||||||
JoinLoyaltyContact,
|
JoinLoyaltyContact,
|
||||||
Sidebar,
|
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 TopMenuCurrentHeaderLink = {
|
||||||
|
link: {
|
||||||
|
href: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
show_on_mobile: boolean
|
||||||
|
sort_order_mobile: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CurrentHeaderLinks = {
|
||||||
|
links: CurrentHeaderLink[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TopMenuCurrentHeaderLinks = {
|
||||||
|
links: TopMenuCurrentHeaderLink[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CurrentHeader = {
|
||||||
|
frontpage_link_text: string
|
||||||
|
logoConnection: Edges<Image>
|
||||||
|
menu: CurrentHeaderLinks
|
||||||
|
top_menu: TopMenuCurrentHeaderLinks
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetCurrentHeaderData = {
|
||||||
|
all_current_header: AllRequestResponse<CurrentHeader>
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ContactConfig,
|
ContactConfig,
|
||||||
ContactFieldGroups,
|
ContactFieldGroups,
|
||||||
} from "@/server/routers/contentstack/contactConfig/output"
|
} from "@/server/routers/contentstack/config/output"
|
||||||
|
|
||||||
export function getValueFromContactConfig(
|
export function getValueFromContactConfig(
|
||||||
keyString: string,
|
keyString: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user