feat(SW-66, SW-348): search functionality and ui

This commit is contained in:
Simon Emanuelsson
2024-08-28 10:47:57 +02:00
parent b9dbcf7d90
commit af850c90e7
437 changed files with 7663 additions and 9881 deletions

View File

@@ -2,7 +2,7 @@ import { headers } from "next/headers"
import { redirect } from "next/navigation" import { redirect } from "next/navigation"
import { overview } from "@/constants/routes/myPages" import { overview } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import { auth } from "@/auth" import { auth } from "@/auth"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
@@ -27,7 +27,7 @@ export default async function ProtectedLayout({
redirect(redirectURL) redirect(redirectURL)
} }
const user = await serverClient().user.get() const user = await getProfile()
if (user && "error" in user) { if (user && "error" in user) {
// redirect(redirectURL) // redirect(redirectURL)

View File

@@ -1,4 +1,4 @@
import Breadcrumbs from "@/components/MyPages/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { LangParams, PageArgs } from "@/types/params" import { LangParams, PageArgs } from "@/types/params"

View File

@@ -1,6 +1,6 @@
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import Content from "@/components/MyPages/AccountPage/Content" import Blocks from "@/components/Blocks"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import TrackingSDK from "@/components/TrackingSDK" import TrackingSDK from "@/components/TrackingSDK"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
@@ -10,7 +10,7 @@ import styles from "./page.module.css"
import type { LangParams, PageArgs } from "@/types/params" import type { LangParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/generateMetadata" export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata"
export default async function MyPages({ export default async function MyPages({
params, params,
@@ -30,8 +30,8 @@ export default async function MyPages({
<> <>
<main className={styles.blocks}> <main className={styles.blocks}>
<Title>{accountPage.heading}</Title> <Title>{accountPage.heading}</Title>
{accountPage.content.length ? ( {accountPage.content?.length ? (
<Content content={accountPage.content} /> <Blocks blocks={accountPage.content} />
) : ( ) : (
<p>{formatMessage({ id: "No content published" })}</p> <p>{formatMessage({ id: "No content published" })}</p>
)} )}

View File

@@ -1,4 +1,4 @@
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import Form from "@/components/Forms/Edit/Profile" import Form from "@/components/Forms/Edit/Profile"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
@@ -10,7 +10,7 @@ export default async function EditProfileSlot({
}: PageArgs<LangParams>) { }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)
const user = await serverClient().user.get() const user = await getProfile()
if (!user || "error" in user) { if (!user || "error" in user) {
return null return null
} }

View File

@@ -1,6 +1,6 @@
import { languages, languageSelect } from "@/constants/languages" import { languages, languageSelect } from "@/constants/languages"
import { profileEdit } from "@/constants/routes/myPages" import { profileEdit } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import { import {
CalendarIcon, CalendarIcon,
@@ -16,7 +16,7 @@ import Link from "@/components/TempDesignSystem/Link"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css" import styles from "./page.module.css"
@@ -25,7 +25,7 @@ import { LangParams, PageArgs } from "@/types/params"
export default async function Profile({ params }: PageArgs<LangParams>) { export default async function Profile({ params }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
const user = await serverClient().user.get() const user = await getProfile()
if (!user || "error" in user) { if (!user || "error" in user) {
return null return null
} }
@@ -44,7 +44,7 @@ export default async function Profile({ params }: PageArgs<LangParams>) {
</Title> </Title>
</hgroup> </hgroup>
<Button asChild intent="primary" size="small" theme="base"> <Button asChild intent="primary" size="small" theme="base">
<Link prefetch={false} color="none" href={profileEdit[getLang()]}> <Link prefetch={false} color="none" href={profileEdit[params.lang]}>
{formatMessage({ id: "Edit profile" })} {formatMessage({ id: "Edit profile" })}
</Link> </Link>
</Button> </Button>

View File

@@ -1,5 +1,5 @@
import ProfilePage from "../page" import ProfilePage from "../page"
export { generateMetadata } from "@/utils/generateMetadata" export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata"
export default ProfilePage export default ProfilePage

View File

@@ -7,7 +7,7 @@ import { setLang } from "@/i18n/serverContext"
import { LangParams, PageArgs } from "@/types/params" import { LangParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/generateMetadata" export { generateMetadataAccountPage as generateMetadata } from "@/utils/generateMetadata"
export default async function ProfilePage({ params }: PageArgs<LangParams>) { export default async function ProfilePage({ params }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)

View File

@@ -1,4 +1,4 @@
import Breadcrumbs from "@/components/MyPages/Breadcrumbs" import Breadcrumbs from "@/components/Breadcrumbs"
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { LangParams, PageArgs } from "@/types/params" import { LangParams, PageArgs } from "@/types/params"

View File

@@ -1,9 +1,8 @@
import { setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { LangParams, PageArgs } from "@/types/params" import type { LangParams, PageArgs } from "@/types/params"
export default function HotelReservationPage({ params }: PageArgs<LangParams>) { export default function HotelReservationPage({ params }: PageArgs<LangParams>) {
setLang(params.lang) setLang(params.lang)
return null return null
} }

View File

@@ -1,17 +1,17 @@
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import BookingWidget from "@/components/BookingWidget" import BookingWidget, { preload } from "@/components/BookingWidget"
export default async function BookingWidgetPage() { export default async function BookingWidgetPage() {
preload()
// Get the booking widget show/hide status based on page specific settings // Get the booking widget show/hide status based on page specific settings
const bookingWidgetToggle = const bookingWidgetToggle =
await serverClient().contentstack.bookingwidget.getToggle() await serverClient().contentstack.bookingwidget.toggle.get()
return ( if (bookingWidgetToggle.hideBookingWidget) {
<> return null
{bookingWidgetToggle && bookingWidgetToggle.hideBookingWidget ? null : ( }
<BookingWidget />
)} return <BookingWidget />
</>
)
} }

View File

@@ -17,7 +17,7 @@ import { ToastHandler } from "@/components/TempDesignSystem/Toasts"
import { preloadUserTracking } from "@/components/TrackingSDK" import { preloadUserTracking } from "@/components/TrackingSDK"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import ServerIntlProvider from "@/i18n/Provider" import ServerIntlProvider from "@/i18n/Provider"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import type { LangParams, LayoutArgs } from "@/types/params" import type { LangParams, LayoutArgs } from "@/types/params"
@@ -37,7 +37,7 @@ export default async function RootLayout({
const { defaultLocale, locale, messages } = await getIntl() const { defaultLocale, locale, messages } = await getIntl()
return ( return (
<html lang={getLang()}> <html lang={params.lang}>
<head> <head>
<AdobeSDKScript /> <AdobeSDKScript />
<Script data-cookieconsent="ignore" src="/_static/js/cookie-bot.js" /> <Script data-cookieconsent="ignore" src="/_static/js/cookie-bot.js" />

View File

@@ -1,4 +1,4 @@
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import styles from "./page.module.css" import styles from "./page.module.css"
@@ -11,7 +11,7 @@ export default function MiddlewareError({
return ( return (
<div className={styles.layout}> <div className={styles.layout}>
Middleware Error {getLang()}: {params.status} Middleware Error {params.lang}: {params.status}
</div> </div>
) )
} }

View File

@@ -1,7 +1,7 @@
import { notFound } from "next/navigation" import { notFound } from "next/navigation"
import { GetCurrentBlockPage } from "@/lib/graphql/Query/CurrentBlockPage.graphql" import { GetCurrentBlockPage } from "@/lib/graphql/Query/Current/CurrentBlockPage.graphql"
import { GetCurrentBlockPageTrackingData } from "@/lib/graphql/Query/CurrentBlockPageTrackingData.graphql" import { GetCurrentBlockPageTrackingData } from "@/lib/graphql/Query/Current/CurrentBlockPageTrackingData.graphql"
import { request } from "@/lib/graphql/request" import { request } from "@/lib/graphql/request"
import ContentPage from "@/components/Current/ContentPage" import ContentPage from "@/components/Current/ContentPage"

View File

@@ -11,7 +11,7 @@ import LangPopup from "@/components/Current/LangPopup"
import SkipToMainContent from "@/components/SkipToMainContent" import SkipToMainContent from "@/components/SkipToMainContent"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import ServerIntlProvider from "@/i18n/Provider" import ServerIntlProvider from "@/i18n/Provider"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import type { Metadata } from "next" import type { Metadata } from "next"
@@ -34,7 +34,7 @@ export default async function RootLayout({
const { defaultLocale, locale, messages } = await getIntl() const { defaultLocale, locale, messages } = await getIntl()
return ( return (
<html lang={getLang()}> <html lang={params.lang}>
<head> <head>
{/* eslint-disable-next-line @next/next/no-css-tags */} {/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/_static/css/core.css" /> <link rel="stylesheet" href="/_static/css/core.css" />
@@ -45,7 +45,7 @@ export default async function RootLayout({
strategy="beforeInteractive" strategy="beforeInteractive"
data-blockingmode="auto" data-blockingmode="auto"
data-cbid="6d539de8-3e67-4f0f-a0df-8cef9070f712" data-cbid="6d539de8-3e67-4f0f-a0df-8cef9070f712"
data-culture={getLang()} data-culture={params.lang}
id="Cookiebot" id="Cookiebot"
src="https://consent.cookiebot.com/uc.js" src="https://consent.cookiebot.com/uc.js"
/> />

View File

@@ -1,5 +1,5 @@
import InitLivePreview from "@/components/Current/LivePreview" import InitLivePreview from "@/components/Current/LivePreview"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import type { Metadata } from "next" import type { Metadata } from "next"
@@ -17,7 +17,7 @@ export default function RootLayout({
setLang(params.lang) setLang(params.lang)
return ( return (
<html lang={getLang()}> <html lang={params.lang}>
<body> <body>
<InitLivePreview /> <InitLivePreview />
{children} {children}

View File

@@ -1,4 +1,4 @@
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { import {
ContentTypeParams, ContentTypeParams,
@@ -16,8 +16,8 @@ export default async function PreviewPage({
return ( return (
<div> <div>
<p> <p>
Preview for {params.contentType}:{params.uid} in {getLang()} with params{" "} Preview for {params.contentType}:{params.uid} in {params.lang} with
<pre>{JSON.stringify(searchParams, null, 2)}</pre> goes here params <pre>{JSON.stringify(searchParams, null, 2)}</pre> goes here
</p> </p>
</div> </div>
) )

View File

@@ -1,10 +1,8 @@
import Script from "next/script"
import Footer from "@/components/Current/Footer" import Footer from "@/components/Current/Footer"
import LangPopup from "@/components/Current/LangPopup" import LangPopup from "@/components/Current/LangPopup"
import InitLivePreview from "@/components/Current/LivePreview" import InitLivePreview from "@/components/Current/LivePreview"
import SkipToMainContent from "@/components/SkipToMainContent" import SkipToMainContent from "@/components/SkipToMainContent"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import type { Metadata } from "next" import type { Metadata } from "next"
@@ -24,7 +22,7 @@ export default function RootLayout({
setLang(params.lang) setLang(params.lang)
return ( return (
<html lang={getLang()}> <html lang={params.lang}>
<head> <head>
{/* eslint-disable-next-line @next/next/no-css-tags */} {/* eslint-disable-next-line @next/next/no-css-tags */}
<link rel="stylesheet" href="/_static/css/core.css" /> <link rel="stylesheet" href="/_static/css/core.css" />

View File

@@ -1,11 +1,11 @@
import ContentstackLivePreview from "@contentstack/live-preview-utils" import ContentstackLivePreview from "@contentstack/live-preview-utils"
import { previewRequest } from "@/lib/graphql/previewRequest" import { previewRequest } from "@/lib/graphql/previewRequest"
import { GetCurrentBlockPage } from "@/lib/graphql/Query/CurrentBlockPage.graphql" import { GetCurrentBlockPage } from "@/lib/graphql/Query/Current/CurrentBlockPage.graphql"
import ContentPage from "@/components/Current/ContentPage" import ContentPage from "@/components/Current/ContentPage"
import LoadingSpinner from "@/components/Current/LoadingSpinner" import LoadingSpinner from "@/components/Current/LoadingSpinner"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import type { PageArgs, PreviewParams } from "@/types/params" import type { PageArgs, PreviewParams } from "@/types/params"
import { LangParams } from "@/types/params" import { LangParams } from "@/types/params"
@@ -26,7 +26,7 @@ export default async function CurrentPreviewPage({
const response = await previewRequest<GetCurrentBlockPageData>( const response = await previewRequest<GetCurrentBlockPageData>(
GetCurrentBlockPage, GetCurrentBlockPage,
{ locale: getLang(), url: searchParams.uri } { locale: params.lang, url: searchParams.uri }
) )
if (!response.data?.all_current_blocks_page?.total) { if (!response.data?.all_current_blocks_page?.total) {

View File

@@ -1,11 +1,11 @@
import { headers } from "next/headers" import { headers } from "next/headers"
import { notFound, redirect } from "next/navigation" import { notFound, redirect } from "next/navigation"
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import AccountPage from "@/components/ContentType/Webviews/AccountPage" import AccountPage from "@/components/Webviews/AccountPage"
import LoyaltyPage from "@/components/ContentType/Webviews/LoyaltyPage" import LoyaltyPage from "@/components/Webviews/LoyaltyPage"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import { import {
ContentTypeWebviewParams, ContentTypeWebviewParams,
@@ -18,7 +18,7 @@ export default async function ContentTypePage({
params, params,
}: PageArgs<LangParams & ContentTypeWebviewParams & UIDParams, {}>) { }: PageArgs<LangParams & ContentTypeWebviewParams & UIDParams, {}>) {
setLang(params.lang) setLang(params.lang)
const user = await serverClient().user.get() const user = await getProfile()
if (!user) { if (!user) {
console.log(`[webview:page] unable to load user`) console.log(`[webview:page] unable to load user`)
@@ -31,8 +31,8 @@ export default async function ContentTypePage({
case "forbidden": // fall through case "forbidden": // fall through
case "token_expired": case "token_expired":
const h = headers() const h = headers()
const returnURL = `/${getLang()}/webview${h.get("x-pathname")!}` const returnURL = `/${params.lang}/webview${h.get("x-pathname")!}`
const redirectURL = `/${getLang()}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}` const redirectURL = `/${params.lang}/webview/refresh?returnUrl=${encodeURIComponent(returnURL)}`
console.log(`[webview:page] user error, redirecting to: ${redirectURL}`) console.log(`[webview:page] user error, redirecting to: ${redirectURL}`)
redirect(redirectURL) redirect(redirectURL)
case "notfound": case "notfound":

View File

@@ -8,7 +8,7 @@ import TrpcProvider from "@/lib/trpc/Provider"
import AdobeSDKScript from "@/components/Current/AdobeSDKScript" import AdobeSDKScript from "@/components/Current/AdobeSDKScript"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import ServerIntlProvider from "@/i18n/Provider" import ServerIntlProvider from "@/i18n/Provider"
import { getLang, setLang } from "@/i18n/serverContext" import { setLang } from "@/i18n/serverContext"
import styles from "./layout.module.css" import styles from "./layout.module.css"
@@ -28,7 +28,7 @@ export default async function RootLayout({
const { defaultLocale, locale, messages } = await getIntl() const { defaultLocale, locale, messages } = await getIntl()
return ( return (
<html lang={getLang()}> <html lang={params.lang}>
<head> <head>
<AdobeSDKScript /> <AdobeSDKScript />
<Script id="ensure-adobeDataLayer">{` <Script id="ensure-adobeDataLayer">{`

View File

@@ -0,0 +1,60 @@
import { revalidateTag } from "next/cache"
import { headers } from "next/headers"
import { Lang } from "@/constants/languages"
import { env } from "@/env/server"
import { badRequest, internalServerError } from "@/server/errors/next"
import { generateTag } from "@/utils/generateTag"
// This file is primarily to be used locally to test
// purging your cache for new (and old) requests
export async function POST() {
try {
const headersList = headers()
const secret = headersList.get("x-revalidate-secret")
if (secret !== env.REVALIDATE_SECRET) {
console.error(`Invalid Secret`)
console.error({ secret })
return badRequest({
now: Date.now(),
revalidated: false,
})
}
const affix = headersList.get("x-affix")
const identifier = headersList.get("x-identifier")
const lang = headersList.get("x-lang")
if (lang && identifier) {
if (affix) {
const tag = generateTag(lang as Lang, identifier, affix)
console.info(
`Revalidated tag for [lang: ${lang}, identifier: ${identifier}, affix: ${affix}]`
)
console.info(`Tag: ${tag}`)
revalidateTag(tag)
} else {
const tag = generateTag(lang as Lang, identifier)
console.info(
`Revalidated tag for [lang: ${lang}, identifier: ${identifier}]`
)
console.info(`Tag: ${tag}`)
revalidateTag(tag)
}
} else {
console.info(`Missing lang and/or identifier`)
console.info(`lang: ${lang}, identifier: ${identifier}`)
return badRequest({
now: Date.now(),
revalidated: false,
})
}
return Response.json({ revalidated: true, now: Date.now() })
} catch (error) {
console.error("Failed to revalidate tag(s)")
console.error(error)
return internalServerError({ revalidated: false, now: Date.now() })
}
}

View File

@@ -5,7 +5,7 @@ import { z } from "zod"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
import { env } from "@/env/server" import { env } from "@/env/server"
import { internalServerError } from "@/server/errors/next" import { badRequest, internalServerError } from "@/server/errors/next"
import { affix as bookingwidgetAffix } from "@/server/routers/contentstack/bookingwidget/utils" import { affix as bookingwidgetAffix } from "@/server/routers/contentstack/bookingwidget/utils"
import { affix as breadcrumbsAffix } from "@/server/routers/contentstack/breadcrumbs/utils" import { affix as breadcrumbsAffix } from "@/server/routers/contentstack/breadcrumbs/utils"
import { languageSwitcherAffix } from "@/server/routers/contentstack/languageSwitcher/utils" import { languageSwitcherAffix } from "@/server/routers/contentstack/languageSwitcher/utils"
@@ -48,15 +48,10 @@ export async function POST(request: NextRequest) {
if (secret !== env.REVALIDATE_SECRET) { if (secret !== env.REVALIDATE_SECRET) {
console.error(`Invalid Secret`) console.error(`Invalid Secret`)
console.error({ secret }) console.error({ secret })
return Response.json( return badRequest({
{ now: Date.now(),
now: Date.now(), revalidated: false,
revalidated: false, })
},
{
status: 400,
}
)
} }
const data = await request.json() const data = await request.json()

View File

@@ -5,8 +5,8 @@ import ContentCard from "@/components/TempDesignSystem/ContentCard"
import Grids from "@/components/TempDesignSystem/Grids" import Grids from "@/components/TempDesignSystem/Grids"
import LoyaltyCard from "@/components/TempDesignSystem/LoyaltyCard" import LoyaltyCard from "@/components/TempDesignSystem/LoyaltyCard"
import type { CardsGridProps } from "@/types/components/content/blocks" import type { CardsGridProps } from "@/types/components/blocks/cardsGrid"
import { CardsGridEnum } from "@/types/components/content/enums" import { CardsGridEnum } from "@/types/enums/cardsGrid"
export default function CardsGrid({ export default function CardsGrid({
cards_grid, cards_grid,
@@ -22,36 +22,37 @@ export default function CardsGrid({
<Grids.Stackable> <Grids.Stackable>
{cards_grid.cards.map((card) => { {cards_grid.cards.map((card) => {
switch (card.__typename) { switch (card.__typename) {
case CardsGridEnum.Card: case CardsGridEnum.cards.Card: {
return card.isContentCard ? ( return card.isContentCard ? (
<ContentCard <ContentCard
key={card.system.uid} key={card.system.uid}
title={card.heading || ""} description={card.body_text}
description={card.body_text || ""} backgroundImage={card.backgroundImage}
primaryButton={card.primaryButton} primaryButton={card.primaryButton}
secondaryButton={card.secondaryButton} secondaryButton={card.secondaryButton}
sidePeekButton={card.sidePeekButton} sidePeekButton={card.sidePeekButton}
backgroundImage={card.background_image}
style="default" style="default"
title={card.heading}
/> />
) : ( ) : (
<Card <Card
theme={cards_grid.theme || "one"}
key={card.system.uid} key={card.system.uid}
scriptedTopTitle={card.scripted_top_title}
heading={card.heading}
bodyText={card.body_text} bodyText={card.body_text}
secondaryButton={card.secondaryButton} heading={card.heading}
primaryButton={card.primaryButton} primaryButton={card.primaryButton}
secondaryButton={card.secondaryButton}
scriptedTopTitle={card.scripted_top_title}
theme={cards_grid.theme || "one"}
/> />
) )
case CardsGridEnum.LoyaltyCard: }
case CardsGridEnum.cards.LoyaltyCard:
return ( return (
<LoyaltyCard <LoyaltyCard
key={card.system.uid} key={card.system.uid}
image={card.image}
heading={card.heading}
bodyText={card.body_text} bodyText={card.body_text}
heading={card.heading}
image={card.image}
link={card.link} link={card.link}
/> />
) )

View File

@@ -1,5 +1,5 @@
import { MembershipLevelEnum } from "@/constants/membershipLevels" import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header" import SectionHeader from "@/components/Section/Header"
@@ -8,7 +8,6 @@ import Grids from "@/components/TempDesignSystem/Grids"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import { getMembershipLevelObject } from "@/utils/membershipLevel" import { getMembershipLevelObject } from "@/utils/membershipLevel"
import { getMembership } from "@/utils/user"
import styles from "./current.module.css" import styles from "./current.module.css"
@@ -19,21 +18,16 @@ export default async function CurrentBenefitsBlock({
subtitle, subtitle,
link, link,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const user = await serverClient().user.get() const user = await getProfile()
// TAKE NOTE: we need clarification on how benefits stack from different levels // TAKE NOTE: we need clarification on how benefits stack from different levels
// in order to determine if a benefit is specific to a level or if it is a cumulative benefit // in order to determine if a benefit is specific to a level or if it is a cumulative benefit
// we might have to add a new boolean property "exclusive" or similar // we might have to add a new boolean property "exclusive" or similar
if (!user || "error" in user) { if (!user || "error" in user || !user.membership) {
return null
}
const membership = getMembership(user.memberships)
if (!membership) {
// TODO: handle this case?
return null return null
} }
const currentLevel = getMembershipLevelObject( const currentLevel = getMembershipLevelObject(
user.memberships[0].membershipLevel as MembershipLevelEnum, user.membership.membershipLevel as MembershipLevelEnum,
getLang() getLang()
) )
if (!currentLevel) { if (!currentLevel) {

View File

@@ -1,7 +1,7 @@
import { Lock } from "react-feather" import { Lock } from "react-feather"
import { MembershipLevelEnum } from "@/constants/membershipLevels" import { MembershipLevelEnum } from "@/constants/membershipLevels"
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header" import SectionHeader from "@/components/Section/Header"
@@ -24,12 +24,12 @@ export default async function NextLevelBenefitsBlock({
link, link,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const intl = await getIntl() const intl = await getIntl()
const user = await serverClient().user.get() const user = await getProfile()
if (!user || "error" in user) { if (!user || "error" in user || !user.membership) {
return null return null
} }
const nextLevel = getMembershipLevelObject( const nextLevel = getMembershipLevelObject(
user.memberships[0].nextLevel as MembershipLevelEnum, user.membership.nextLevel as MembershipLevelEnum,
getLang() getLang()
) )
if (!nextLevel) { if (!nextLevel) {

View File

@@ -0,0 +1,27 @@
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import SectionWrapper from "../SectionWrapper"
import styles from "./howItWorks.module.css"
import type { HowItWorksProps } from "@/types/components/blocks/dynamicContent"
export default async function HowItWorks({
dynamic_content,
firstItem,
}: HowItWorksProps) {
const intl = await getIntl()
const displayHeader = !!(
dynamic_content.link ||
dynamic_content.subtitle ||
dynamic_content.title
)
return (
<SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
<section className={styles.container}>
<Title level="h3">{intl.formatMessage({ id: "How it works" })}</Title>
</section>
</SectionWrapper>
)
}

View File

@@ -20,27 +20,35 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import levelsData from "@/data/loyaltyLevels" import levelsData from "@/data/loyaltyLevels"
import SectionWrapper from "../SectionWrapper"
import styles from "./loyaltyLevels.module.css" import styles from "./loyaltyLevels.module.css"
import type { Level, LevelCardProps } from "@/types/components/content/blocks" import type { LoyaltyLevelsProps } from "@/types/components/blocks/dynamicContent"
import type { Level, LevelCardProps } from "@/types/components/overviewTable"
export default function LoyaltyLevels() { export default function LoyaltyLevels({
dynamic_content,
firstItem,
}: LoyaltyLevelsProps) {
const params = useParams() const params = useParams()
const lang = params.lang as Lang const lang = params.lang as Lang
const { formatMessage } = useIntl() const { formatMessage } = useIntl()
const { levels } = levelsData[lang] const { levels } = levelsData[lang]
return ( return (
<section className={styles.cardContainer}> <SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
{levels.map((level: Level) => ( <section className={styles.cardContainer}>
<LevelCard {levels.map((level: Level) => (
key={level.level} <LevelCard
formatMessage={formatMessage} key={level.level}
lang={lang} formatMessage={formatMessage}
level={level} lang={lang}
/> level={level}
))} />
</section> ))}
</section>
</SectionWrapper>
) )
} }

View File

@@ -2,11 +2,12 @@ import Caption from "@/components/TempDesignSystem/Text/Caption"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import CopyButton from "../../Buttons/CopyButton" import CopyButton from "../../Buttons/CopyButton"
import { MembershipNumberProps } from "./membershipNumber"
import { membershipNumberVariants } from "./membershipNumberVariants" import { membershipNumberVariants } from "./membershipNumberVariants"
import styles from "./membershipNumber.module.css" import styles from "./membershipNumber.module.css"
import type { MembershipNumberProps } from "@/types/components/myPages/membershipNumber"
export default async function MembershipNumber({ export default async function MembershipNumber({
className, className,
color, color,
@@ -23,7 +24,7 @@ export default async function MembershipNumber({
</Caption> </Caption>
<span className={styles.icon}> <span className={styles.icon}>
<Caption className={styles.icon} color="pale" asChild> <Caption className={styles.icon} color="pale" asChild>
<code>{membership.membershipNumber ?? "N/A"}</code> <code>{membership?.membershipNumber ?? "N/A"}</code>
</Caption> </Caption>
{membership && ( {membership && (
<CopyButton membershipNumber={membership.membershipNumber} /> <CopyButton membershipNumber={membership.membershipNumber} />

View File

@@ -3,22 +3,20 @@ import { membershipLevels } from "@/constants/membershipLevels"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
import { getMembership, isHighestMembership } from "@/utils/user" import { isHighestMembership } from "@/utils/user"
import { MembershipNumberProps } from "./MemershipNumber/membershipNumber"
import MembershipLevel from "./MembershipLevel" import MembershipLevel from "./MembershipLevel"
import MembershipNumber from "./MemershipNumber"
import styles from "./friend.module.css" import styles from "./friend.module.css"
import type { UserProps } from "@/types/components/myPages/user" import type { FriendProps } from "@/types/components/myPages/friend"
export default async function Friend({ export default async function Friend({
user, children,
color, membership,
}: UserProps & Pick<MembershipNumberProps, "color">) { name,
}: FriendProps) {
const { formatMessage } = await getIntl() const { formatMessage } = await getIntl()
const membership = getMembership(user.memberships)
if (!membership?.membershipLevel) { if (!membership?.membershipLevel) {
return null return null
} }
@@ -45,9 +43,9 @@ export default async function Friend({
</header> </header>
<div className={styles.membership}> <div className={styles.membership}>
<Title className={styles.name} color="pale" level="h3"> <Title className={styles.name} color="pale" level="h3">
{user.name} {name}
</Title> </Title>
<MembershipNumber membership={membership} color={color} /> {children}
</div> </div>
</section> </section>
) )

View File

@@ -1,12 +1,12 @@
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header" import SectionHeader from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link" import SectionLink from "@/components/Section/Link"
import Divider from "@/components/TempDesignSystem/Divider" import Divider from "@/components/TempDesignSystem/Divider"
import { getLang } from "@/i18n/serverContext"
import Hero from "./Friend/Hero" import Hero from "./Friend/Hero"
import MembershipNumber from "./Friend/MembershipNumber"
import Friend from "./Friend" import Friend from "./Friend"
import Stats from "./Stats" import Stats from "./Stats"
@@ -19,7 +19,7 @@ export default async function Overview({
subtitle, subtitle,
title, title,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const user = await serverClient().user.get() const user = await getProfile()
if (!user || "error" in user) { if (!user || "error" in user) {
return null return null
} }
@@ -28,7 +28,9 @@ export default async function Overview({
<SectionContainer> <SectionContainer>
<SectionHeader link={link} preamble={subtitle} title={title} topTitle /> <SectionHeader link={link} preamble={subtitle} title={title} topTitle />
<Hero color="red"> <Hero color="red">
<Friend user={user} color="burgundy" /> <Friend membership={user.membership} name={user.name}>
<MembershipNumber color="burgundy" membership={user.membership} />
</Friend>
<Divider className={styles.divider} color="peach" /> <Divider className={styles.divider} color="peach" />
<Stats user={user} /> <Stats user={user} />
</Hero> </Hero>

View File

@@ -6,7 +6,7 @@ import BenefitValue from "../BenefitValue"
import styles from "./benefitCard.module.css" import styles from "./benefitCard.module.css"
import type { BenefitCardProps } from "@/types/components/loyalty/blocks" import type { BenefitCardProps } from "@/types/components/overviewTable"
export default function BenefitCard({ export default function BenefitCard({
comparedValues, comparedValues,

View File

@@ -4,7 +4,7 @@ import BenefitCard from "../BenefitCard"
import styles from "./benefitList.module.css" import styles from "./benefitList.module.css"
import type { BenefitListProps } from "@/types/components/content/blocks" import type { BenefitListProps } from "@/types/components/overviewTable"
export default function BenefitList({ levels }: BenefitListProps) { export default function BenefitList({ levels }: BenefitListProps) {
return getUnlockedBenefits(levels).map((benefit) => { return getUnlockedBenefits(levels).map((benefit) => {

View File

@@ -4,7 +4,7 @@ import CheckCircle from "@/components/Icons/CheckCircle"
import styles from "./benefitValue.module.css" import styles from "./benefitValue.module.css"
import type { BenefitValueProps } from "@/types/components/loyalty/blocks" import type { BenefitValueProps } from "@/types/components/overviewTable"
export default function BenefitValue({ benefit }: BenefitValueProps) { export default function BenefitValue({ benefit }: BenefitValueProps) {
if (!benefit.unlocked) { if (!benefit.unlocked) {

View File

@@ -31,9 +31,9 @@ import {
DesktopSelectColumns, DesktopSelectColumns,
type MobileColumnHeaderProps, type MobileColumnHeaderProps,
overviewTableActionsEnum, overviewTableActionsEnum,
type OverviewTableProps, type OverviewTableClientProps,
OverviewTableReducerAction, OverviewTableReducerAction,
} from "@/types/components/loyalty/blocks" } from "@/types/components/overviewTable"
import type { User } from "@/types/user" import type { User } from "@/types/user"
const levelsTranslations = { const levelsTranslations = {
@@ -126,9 +126,9 @@ function reducer(state: any, action: OverviewTableReducerAction) {
} }
} }
export default function OverviewTable({ export default function OverviewTableClient({
activeMembership, activeMembership,
}: OverviewTableProps) { }: OverviewTableClientProps) {
const intl = useIntl() const intl = useIntl()
const lang = useLang() const lang = useLang()
const levelsData = levelsTranslations[lang] const levelsData = levelsTranslations[lang]
@@ -241,6 +241,7 @@ export default function OverviewTable({
/> />
) )
} }
return ( return (
<div> <div>
<div className={styles.mobileColumns}> <div className={styles.mobileColumns}>

View File

@@ -8,7 +8,7 @@ import styles from "./desktopHeader.module.css"
import type { import type {
DesktopSelectColumns, DesktopSelectColumns,
LargeTableProps, LargeTableProps,
} from "@/types/components/loyalty/blocks" } from "@/types/components/overviewTable"
export default function DesktopHeader({ export default function DesktopHeader({
levels, levels,

View File

@@ -11,7 +11,7 @@ import styles from "./largeTable.module.css"
import type { import type {
BenefitTableHeaderProps, BenefitTableHeaderProps,
LargeTableProps, LargeTableProps,
} from "@/types/components/content/blocks" } from "@/types/components/overviewTable"
export default function LargeTable({ export default function LargeTable({
levels, levels,

View File

@@ -1,6 +1,6 @@
import styles from "./levelSummary.module.css" import styles from "./levelSummary.module.css"
import type { LevelSummaryProps } from "@/types/components/content/blocks" import type { LevelSummaryProps } from "@/types/components/overviewTable"
export default function LevelSummary({ export default function LevelSummary({
level, level,

View File

@@ -0,0 +1,18 @@
import { serverClient } from "@/lib/trpc/server"
import SectionWrapper from "../SectionWrapper"
import OverviewTableClient from "./Client"
import type { OverviewTableProps } from "@/types/components/blocks/dynamicContent"
export default async function OverviewTable({
dynamic_content,
firstItem,
}: OverviewTableProps) {
const membershipLevel = await serverClient().user.safeMembershipLevel()
return (
<SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
<OverviewTableClient activeMembership={membershipLevel} />
</SectionWrapper>
)
}

View File

@@ -6,14 +6,15 @@ import { useIntl } from "react-intl"
import { webviews } from "@/constants/routes/webviews" import { webviews } from "@/constants/routes/webviews"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/AwardPoints"
import Link from "@/components/TempDesignSystem/Link" import Link from "@/components/TempDesignSystem/Link"
import Table from "@/components/TempDesignSystem/Table" import Table from "@/components/TempDesignSystem/Table"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import AwardPoints from "../../../AwardPoints"
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn" import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
import { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums" import { Transactions } from "@/types/enums/transactions"
export default function Row({ transaction }: RowProps) { export default function Row({ transaction }: RowProps) {
const intl = useIntl() const intl = useIntl()
@@ -29,8 +30,8 @@ export default function Row({ transaction }: RowProps) {
: `${nightString}` : `${nightString}`
switch (transaction.type) { switch (transaction.type) {
case RewardTransactionTypes.stay: case Transactions.rewardType.stay:
case RewardTransactionTypes.stayAdj: case Transactions.rewardType.stayAdj:
if (transaction.hotelId === "ORS") { if (transaction.hotelId === "ORS") {
description = intl.formatMessage({ id: "Former Scandic Hotel" }) description = intl.formatMessage({ id: "Former Scandic Hotel" })
} }
@@ -40,19 +41,19 @@ export default function Row({ transaction }: RowProps) {
}) })
} }
break break
case RewardTransactionTypes.ancillary: case Transactions.rewardType.ancillary:
description = intl.formatMessage({ id: "Extras to your booking" }) description = intl.formatMessage({ id: "Extras to your booking" })
break break
case RewardTransactionTypes.enrollment: case Transactions.rewardType.enrollment:
description = intl.formatMessage({ id: "Sign up bonus" }) description = intl.formatMessage({ id: "Sign up bonus" })
break break
case RewardTransactionTypes.mastercard_points: case Transactions.rewardType.mastercard_points:
description = intl.formatMessage({ id: "Scandic Friends Mastercard" }) description = intl.formatMessage({ id: "Scandic Friends Mastercard" })
break break
case RewardTransactionTypes.tui_points: case Transactions.rewardType.tui_points:
description = intl.formatMessage({ id: "TUI Points" }) description = intl.formatMessage({ id: "TUI Points" })
case RewardTransactionTypes.pointShop: case Transactions.rewardType.pointShop:
description = intl.formatMessage({ id: "Scandic Friends Point Shop" }) description = intl.formatMessage({ id: "Scandic Friends Point Shop" })
break break
} }
@@ -65,8 +66,8 @@ export default function Row({ transaction }: RowProps) {
if ( if (
!isWebview && !isWebview &&
transaction.bookingUrl && transaction.bookingUrl &&
(transaction.type === RewardTransactionTypes.stay || (transaction.type === Transactions.rewardType.stay ||
transaction.type === RewardTransactionTypes.rewardNight) transaction.type === Transactions.rewardType.rewardNight)
) { ) {
return ( return (
<Link variant="underscored" href={transaction.bookingUrl}> <Link variant="underscored" href={transaction.bookingUrl}>

View File

@@ -6,7 +6,7 @@ import JourneyTable from "./JourneyTable"
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage" import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
export default async function EarnAndBurn({ export default function EarnAndBurn({
link, link,
subtitle, subtitle,
title, title,

View File

@@ -4,11 +4,12 @@ import { useIntl } from "react-intl"
import { dt } from "@/lib/dt" import { dt } from "@/lib/dt"
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/AwardPoints"
import Table from "@/components/TempDesignSystem/Table" import Table from "@/components/TempDesignSystem/Table"
import Body from "@/components/TempDesignSystem/Text/Body" import Body from "@/components/TempDesignSystem/Text/Body"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import AwardPoints from "../../EarnAndBurn/AwardPoints"
const tableHeadings = ["Points", "Expiration Date"] const tableHeadings = ["Points", "Expiration Date"]
export default function ExpiringPointsTable({ export default function ExpiringPointsTable({

View File

@@ -1,18 +1,16 @@
import { import { MembershipLevelEnum } from "@/constants/membershipLevels"
MembershipLevelEnum,
membershipLevels,
} from "@/constants/membershipLevels"
import PointsContainer from "@/components/MyPages/Blocks/Overview/Stats/Points/Container" import { getIntl } from "@/i18n"
import { getMembershipLevelObject } from "@/utils/membershipLevel"
import { getMembership } from "@/utils/user"
import PointsContainer from "../../../Overview/Stats/Points/Container"
import { import {
NextLevelNightsColumn, NextLevelNightsColumn,
NextLevelPointsColumn, NextLevelPointsColumn,
StayOnLevelColumn, StayOnLevelColumn,
YourPointsColumn, YourPointsColumn,
} from "@/components/MyPages/Blocks/Overview/Stats/Points/PointsColumn" } from "../../../Overview/Stats/Points/PointsColumn"
import { getIntl } from "@/i18n"
import { getMembershipLevelObject } from "@/utils/membershipLevel"
import { getMembership } from "@/utils/user"
import { UserProps } from "@/types/components/myPages/user" import { UserProps } from "@/types/components/myPages/user"
import { LangParams } from "@/types/params" import { LangParams } from "@/types/params"

View File

@@ -1,4 +1,4 @@
import { serverClient } from "@/lib/trpc/server" import { getProfile } from "@/lib/trpc/memoizedRequests"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header" import SectionHeader from "@/components/Section/Header"
@@ -7,6 +7,7 @@ import Divider from "@/components/TempDesignSystem/Divider"
import Friend from "../../Overview/Friend" import Friend from "../../Overview/Friend"
import Hero from "../../Overview/Friend/Hero" import Hero from "../../Overview/Friend/Hero"
import MembershipNumber from "../../Overview/Friend/MembershipNumber"
import Stats from "../../Overview/Stats" import Stats from "../../Overview/Stats"
import styles from "./overview.module.css" import styles from "./overview.module.css"
@@ -18,7 +19,7 @@ export default async function PointsOverview({
subtitle, subtitle,
title, title,
}: AccountPageComponentProps) { }: AccountPageComponentProps) {
const user = await serverClient().user.get() const user = await getProfile()
if (!user || "error" in user) { if (!user || "error" in user) {
return null return null
} }
@@ -27,7 +28,9 @@ export default async function PointsOverview({
<SectionContainer> <SectionContainer>
<SectionHeader link={link} preamble={subtitle} title={title} topTitle /> <SectionHeader link={link} preamble={subtitle} title={title} topTitle />
<Hero color="burgundy"> <Hero color="burgundy">
<Friend user={user} color="red" /> <Friend membership={user.membership} name={user.name}>
<MembershipNumber color="red" membership={user.membership} />
</Friend>
<Divider className={styles.divider} color="peach" /> <Divider className={styles.divider} color="peach" />
<Stats user={user} /> <Stats user={user} />
</Hero> </Hero>

View File

@@ -0,0 +1,46 @@
import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./sectionWrapper.module.css"
import type { DynamicContentProps } from "@/types/components/blocks/dynamicContent"
import { DynamicContentEnum } from "@/types/enums/dynamicContent"
export default function SectionWrapper({
children,
dynamic_content,
firstItem,
}: React.PropsWithChildren<DynamicContentProps>) {
const displayHeader = !!(
dynamic_content.link ||
dynamic_content.subtitle ||
dynamic_content.title
)
const isOverviewTable =
dynamic_content.component ===
DynamicContentEnum.Blocks.components.overview_table
return (
<SectionContainer className={styles.container}>
{isOverviewTable ? (
<div className={styles.header}>
<Title className={styles.tableTitle}> {dynamic_content.title}</Title>
<Subtitle>{dynamic_content.subtitle}</Subtitle>
</div>
) : displayHeader ? (
<SectionHeader
link={dynamic_content.link}
preamble={dynamic_content.subtitle}
title={dynamic_content.title}
topTitle={firstItem}
/>
) : null}
{children}
{displayHeader ? (
<SectionLink link={dynamic_content.link} variant="mobile" />
) : null}
</SectionContainer>
)
}

View File

@@ -42,7 +42,7 @@ export default function ClientPreviousStays({
// TS having a hard time with the filtered type. // TS having a hard time with the filtered type.
// This is only temporary as we will not return null // This is only temporary as we will not return null
// later on when we handle errors appropriately. // later on when we handle errors appropriately.
const filteredStays = (data?.pages.filter((page) => page && page.data) ?? const filteredStays = (data?.pages.filter((page) => page?.data) ??
[]) as unknown as PreviousStaysNonNullResponseObject[] []) as unknown as PreviousStaysNonNullResponseObject[]
const stays = filteredStays.flatMap((page) => page.data) const stays = filteredStays.flatMap((page) => page.data)

Some files were not shown because too many files have changed in this diff Show More