From db9f31e2c38e5b72f55de44bb4b40aadd62e0b87 Mon Sep 17 00:00:00 2001 From: Erik Tiekstra Date: Thu, 17 Oct 2024 11:23:50 +0200 Subject: [PATCH] feat(SW-498): added sitewide alert --- .../(live)/@sitewidealert/[...paths]/page.tsx | 1 + .../[contentType]/[uid]/page.tsx | 1 + app/[lang]/(live)/@sitewidealert/default.tsx | 1 + .../my-pages/[...path]/page.tsx | 1 + app/[lang]/(live)/@sitewidealert/page.tsx | 17 ++++ app/[lang]/(live)/layout.tsx | 3 + components/SitewideAlert/index.tsx | 33 +++++++ .../SitewideAlert/sitewideAlert.module.css | 9 ++ .../TempDesignSystem/Alert/Sidepeek/index.tsx | 49 +++++++++++ .../Alert/Sidepeek/sidepeek.module.css | 3 + .../Alert/Sidepeek/sidepeek.ts | 6 ++ .../TempDesignSystem/Alert/alert.module.css | 77 ++++++++-------- components/TempDesignSystem/Alert/alert.ts | 12 ++- components/TempDesignSystem/Alert/index.tsx | 87 ++++++++++--------- components/TempDesignSystem/Alert/variants.ts | 5 +- lib/trpc/memoizedRequests/index.ts | 4 + server/routers/contentstack/base/query.ts | 2 +- 17 files changed, 226 insertions(+), 85 deletions(-) create mode 100644 app/[lang]/(live)/@sitewidealert/[...paths]/page.tsx create mode 100644 app/[lang]/(live)/@sitewidealert/[contentType]/[uid]/page.tsx create mode 100644 app/[lang]/(live)/@sitewidealert/default.tsx create mode 100644 app/[lang]/(live)/@sitewidealert/my-pages/[...path]/page.tsx create mode 100644 app/[lang]/(live)/@sitewidealert/page.tsx create mode 100644 components/SitewideAlert/index.tsx create mode 100644 components/SitewideAlert/sitewideAlert.module.css create mode 100644 components/TempDesignSystem/Alert/Sidepeek/index.tsx create mode 100644 components/TempDesignSystem/Alert/Sidepeek/sidepeek.module.css create mode 100644 components/TempDesignSystem/Alert/Sidepeek/sidepeek.ts diff --git a/app/[lang]/(live)/@sitewidealert/[...paths]/page.tsx b/app/[lang]/(live)/@sitewidealert/[...paths]/page.tsx new file mode 100644 index 000000000..03a82e5f5 --- /dev/null +++ b/app/[lang]/(live)/@sitewidealert/[...paths]/page.tsx @@ -0,0 +1 @@ +export { default } from "../page" diff --git a/app/[lang]/(live)/@sitewidealert/[contentType]/[uid]/page.tsx b/app/[lang]/(live)/@sitewidealert/[contentType]/[uid]/page.tsx new file mode 100644 index 000000000..2ebaca014 --- /dev/null +++ b/app/[lang]/(live)/@sitewidealert/[contentType]/[uid]/page.tsx @@ -0,0 +1 @@ +export { default } from "../../page" diff --git a/app/[lang]/(live)/@sitewidealert/default.tsx b/app/[lang]/(live)/@sitewidealert/default.tsx new file mode 100644 index 000000000..83ec2818e --- /dev/null +++ b/app/[lang]/(live)/@sitewidealert/default.tsx @@ -0,0 +1 @@ +export { default } from "./page" diff --git a/app/[lang]/(live)/@sitewidealert/my-pages/[...path]/page.tsx b/app/[lang]/(live)/@sitewidealert/my-pages/[...path]/page.tsx new file mode 100644 index 000000000..2ebaca014 --- /dev/null +++ b/app/[lang]/(live)/@sitewidealert/my-pages/[...path]/page.tsx @@ -0,0 +1 @@ +export { default } from "../../page" diff --git a/app/[lang]/(live)/@sitewidealert/page.tsx b/app/[lang]/(live)/@sitewidealert/page.tsx new file mode 100644 index 000000000..618f3b5cb --- /dev/null +++ b/app/[lang]/(live)/@sitewidealert/page.tsx @@ -0,0 +1,17 @@ +import { Suspense } from "react" + +import SitewideAlert, { preload } from "@/components/SitewideAlert" +import { setLang } from "@/i18n/serverContext" + +import type { LangParams, PageArgs } from "@/types/params" + +export default function SitewideAlertPage({ params }: PageArgs) { + setLang(params.lang) + preload() + + return ( + + + + ) +} diff --git a/app/[lang]/(live)/layout.tsx b/app/[lang]/(live)/layout.tsx index 3704178c4..e1c63bbbc 100644 --- a/app/[lang]/(live)/layout.tsx +++ b/app/[lang]/(live)/layout.tsx @@ -22,12 +22,14 @@ export default async function RootLayout({ children, footer, header, + sitewidealert, params, }: React.PropsWithChildren< LayoutArgs & { bookingwidget: React.ReactNode footer: React.ReactNode header: React.ReactNode + sitewidealert: React.ReactNode } >) { setLang(params.lang) @@ -55,6 +57,7 @@ export default async function RootLayout({ + {!env.HIDE_FOR_NEXT_RELEASE && <>{sitewidealert}} {header} {!env.HIDE_FOR_NEXT_RELEASE && <>{bookingwidget}} {children} diff --git a/components/SitewideAlert/index.tsx b/components/SitewideAlert/index.tsx new file mode 100644 index 000000000..2ce624119 --- /dev/null +++ b/components/SitewideAlert/index.tsx @@ -0,0 +1,33 @@ +import { getSiteConfig } from "@/lib/trpc/memoizedRequests" + +import Alert from "../TempDesignSystem/Alert" + +import styles from "./sitewideAlert.module.css" + +export function preload() { + void getSiteConfig() +} + +export default async function SitewideAlert() { + const siteConfig = await getSiteConfig() + + if (!siteConfig?.sitewideAlert) { + return null + } + + const { sitewideAlert } = siteConfig + return ( +
+ +
+ ) +} diff --git a/components/SitewideAlert/sitewideAlert.module.css b/components/SitewideAlert/sitewideAlert.module.css new file mode 100644 index 000000000..f91bf3db5 --- /dev/null +++ b/components/SitewideAlert/sitewideAlert.module.css @@ -0,0 +1,9 @@ +.sitewideAlert { + width: 100%; +} + +.alarm { + position: sticky; + top: 0; + z-index: calc(var(--header-z-index) + 1); +} diff --git a/components/TempDesignSystem/Alert/Sidepeek/index.tsx b/components/TempDesignSystem/Alert/Sidepeek/index.tsx new file mode 100644 index 000000000..db69cf563 --- /dev/null +++ b/components/TempDesignSystem/Alert/Sidepeek/index.tsx @@ -0,0 +1,49 @@ +"use client" + +import { useState } from "react" + +import { ChevronRightIcon } from "@/components/Icons" +import JsonToHtml from "@/components/JsonToHtml" +import Button from "@/components/TempDesignSystem/Button" + +import SidePeek from "../../SidePeek" + +import styles from "./sidepeek.module.css" + +import type { AlertSidepeekProps } from "./sidepeek" + +export default function AlertSidepeek({ + ctaText, + sidePeekContent, +}: AlertSidepeekProps) { + const [sidePeekIsOpen, setSidePeekIsOpen] = useState(false) + const { heading, content } = sidePeekContent + + return ( +
+ + {sidePeekIsOpen ? ( + setSidePeekIsOpen(false)} + > + + + ) : null} +
+ ) +} diff --git a/components/TempDesignSystem/Alert/Sidepeek/sidepeek.module.css b/components/TempDesignSystem/Alert/Sidepeek/sidepeek.module.css new file mode 100644 index 000000000..fccd935aa --- /dev/null +++ b/components/TempDesignSystem/Alert/Sidepeek/sidepeek.module.css @@ -0,0 +1,3 @@ +.alertSidepeek { + flex-shrink: 0; +} diff --git a/components/TempDesignSystem/Alert/Sidepeek/sidepeek.ts b/components/TempDesignSystem/Alert/Sidepeek/sidepeek.ts new file mode 100644 index 000000000..acd7ce35a --- /dev/null +++ b/components/TempDesignSystem/Alert/Sidepeek/sidepeek.ts @@ -0,0 +1,6 @@ +import type { SidepeekContent } from "@/types/trpc/routers/contentstack/siteConfig" + +export interface AlertSidepeekProps { + ctaText: string + sidePeekContent: NonNullable +} diff --git a/components/TempDesignSystem/Alert/alert.module.css b/components/TempDesignSystem/Alert/alert.module.css index f5ba2f58a..728f7d22d 100644 --- a/components/TempDesignSystem/Alert/alert.module.css +++ b/components/TempDesignSystem/Alert/alert.module.css @@ -1,6 +1,4 @@ .alert { - display: flex; - gap: var(--Spacing-x2); overflow: hidden; } @@ -12,34 +10,26 @@ } .content { + width: 100%; + max-width: var(--max-width-navigation); + margin: 0 auto; display: flex; - justify-content: space-between; - align-items: center; gap: var(--Spacing-x2); - padding: var(--Spacing-x-one-and-half) var(--Spacing-x2) - var(--Spacing-x-one-and-half) 0; +} + +.innerContent { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: flex-start; + gap: var(--Spacing-x1); + padding: var(--Spacing-x2) 0; flex-grow: 1; } .textWrapper { display: grid; gap: var(--Spacing-x-half); - padding: var(--Spacing-x1) 0; -} - -.sidepeekCta { - flex-shrink: 0; -} - -.closeButton { - border-width: 0; - padding: 0; - margin: 0; - background-color: transparent; - display: flex; - align-items: center; - flex-shrink: 0; - cursor: pointer; } /* Intent: inline */ @@ -48,59 +38,64 @@ border: 1px solid var(--Base-Border-Subtle); background-color: var(--Base-Surface-Primary-light-Normal); } +.inline .innerContent { + padding-right: var(--Spacing-x3); +} .inline .iconWrapper { padding: var(--Spacing-x-one-and-half); } .inline.alarm .iconWrapper { - background-color: var(--Main-Red-70); + background-color: var(--UI-Semantic-Error); } .inline.warning .iconWrapper { - background-color: var(--Main-Yellow-60); + background-color: var(--UI-Semantic-Warning); } .inline.info .iconWrapper { - background-color: var(--Scandic-Blue-70); + background-color: var(--UI-Semantic-Information); } .inline .icon, .inline .icon * { fill: var(--Base-Surface-Primary-light-Normal); } -.inline .closeButton { - border-left: 1px solid var(--Base-Border-Subtle); - padding: var(--Spacing-x-one-and-half); -} /* Intent: banner */ .banner { - padding: 0 var(--Spacing-x5); + padding: 0 var(--Spacing-x3); border-left-width: 6px; border-left-style: solid; } .banner.alarm { - border-left-color: var(--Main-Red-70); - background-color: var(--Main-Red-00); + border-left-color: var(--UI-Semantic-Error); + background-color: var(--Scandic-Red-00); } .banner.warning { - border-left-color: var(--Main-Yellow-60); - background-color: var(--Main-Yellow-00); + border-left-color: var(--UI-Semantic-Warning); + background-color: var(--Scandic-Yellow-00); } .banner.info { - border-left-color: var(--Scandic-Blue-70); + border-left-color: var(--UI-Semantic-Information); background-color: var(--Scandic-Blue-00); } .banner.alarm .icon, .banner.alarm .icon * { - fill: var(--Main-Red-70); + fill: var(--UI-Semantic-Error); } .banner.warning .icon, .banner.warning .icon * { - fill: var(--Main-Yellow-60); + fill: var(--UI-Semantic-Warning); } .banner.info .icon, .banner.info .icon * { - fill: var(--Scandic-Blue-70); + fill: var(--UI-Semantic-Information); } -.banner .closeButton { - align-self: center; - padding-left: var(--Spacing-x-one-and-half); +@media screen and (min-width: 768px) { + .banner { + padding: 0 var(--Spacing-x5); + } + .innerContent { + flex-direction: row; + align-items: center; + gap: var(--Spacing-x2); + } } diff --git a/components/TempDesignSystem/Alert/alert.ts b/components/TempDesignSystem/Alert/alert.ts index 95fec9d08..20927d7fc 100644 --- a/components/TempDesignSystem/Alert/alert.ts +++ b/components/TempDesignSystem/Alert/alert.ts @@ -8,9 +8,17 @@ import type { SidepeekContent } from "@/types/trpc/routers/contentstack/siteConf export interface AlertProps extends VariantProps { className?: string type: AlertTypeEnum - closeable?: boolean heading?: string text: string + phoneContact?: { + displayText: string + phoneNumber?: string + footnote?: string | null + } | null sidepeekContent?: SidepeekContent | null - sidePeekCtaText?: string | null + sidepeekCtaText?: string | null + link?: { + url: string + title: string + } | null } diff --git a/components/TempDesignSystem/Alert/index.tsx b/components/TempDesignSystem/Alert/index.tsx index 57bd0c5c1..6499c7126 100644 --- a/components/TempDesignSystem/Alert/index.tsx +++ b/components/TempDesignSystem/Alert/index.tsx @@ -1,69 +1,76 @@ -import { ChevronRightIcon, CloseLargeIcon } from "@/components/Icons" -import Button from "@/components/TempDesignSystem/Button" import Body from "@/components/TempDesignSystem/Text/Body" -import { getIntl } from "@/i18n" -import { AlertProps } from "./alert" +import Link from "../Link" +import AlertSidepeek from "./Sidepeek" import { getIconByAlertType } from "./utils" import { alertVariants } from "./variants" import styles from "./alert.module.css" -export default async function Alert({ +import type { AlertProps } from "./alert" + +export default function Alert({ className, variant, type, heading, text, - sidePeekCtaText, + link, + phoneContact, + sidepeekCtaText, sidepeekContent, - closeable = false, }: AlertProps) { const classNames = alertVariants({ className, variant, type, }) - const intl = await getIntl() - const Icon = getIconByAlertType(type) return ( -
- - - +
-
- {heading ? ( - -

{heading}

+ + + +
+
+ {heading ? ( + +

{heading}

+ + ) : null} + + {text} + {phoneContact?.phoneNumber ? ( + <> + {phoneContact.displayText} + + {phoneContact.phoneNumber} + + {phoneContact.footnote ? ( + . ({phoneContact.footnote}) + ) : null} + + ) : null} +
+ {link ? ( + + {link.title} + + ) : null} + {!link && sidepeekCtaText && sidepeekContent ? ( + ) : null} - {text}
- {sidePeekCtaText ? ( - - ) : null}
- {closeable ? ( - - ) : null} -
+
) } diff --git a/components/TempDesignSystem/Alert/variants.ts b/components/TempDesignSystem/Alert/variants.ts index 91087f17b..7397ac1e9 100644 --- a/components/TempDesignSystem/Alert/variants.ts +++ b/components/TempDesignSystem/Alert/variants.ts @@ -16,5 +16,8 @@ export const alertVariants = cva(styles.alert, { [AlertTypeEnum.Alarm]: styles.alarm, }, }, - defaultVariants: {}, + defaultVariants: { + variant: "inline", + type: AlertTypeEnum.Info, + }, }) diff --git a/lib/trpc/memoizedRequests/index.ts b/lib/trpc/memoizedRequests/index.ts index fed1caf99..471d20def 100644 --- a/lib/trpc/memoizedRequests/index.ts +++ b/lib/trpc/memoizedRequests/index.ts @@ -45,3 +45,7 @@ export const getLanguageSwitcher = cache( return serverClient().contentstack.languageSwitcher.get() } ) + +export const getSiteConfig = cache(async function getMemoizedSiteConfig() { + return serverClient().contentstack.base.siteConfig() +}) diff --git a/server/routers/contentstack/base/query.ts b/server/routers/contentstack/base/query.ts index 2a26e980e..3218b8bb6 100644 --- a/server/routers/contentstack/base/query.ts +++ b/server/routers/contentstack/base/query.ts @@ -750,7 +750,7 @@ export const baseQueryRouter = router({ sitewideAlert: sitewideAlert ? { ...sitewideAlert, - phone_contact: contactConfig + phoneContact: contactConfig ? getAlertPhoneContactData(sitewideAlert, contactConfig) : null, }