diff --git a/app/[lang]/(live)/(public)/loyalty-page/layout.module.css b/app/[lang]/(live)/(public)/loyalty-page/layout.module.css index 4ddc7b911..e17395732 100644 --- a/app/[lang]/(live)/(public)/loyalty-page/layout.module.css +++ b/app/[lang]/(live)/(public)/loyalty-page/layout.module.css @@ -1,10 +1,7 @@ .layout { --max-width: 101.4rem; - --header-height: 4.5rem; display: grid; font-family: var(--ff-fira-sans); - grid-template-rows: var(--header-height) auto 1fr; - min-height: 100dvh; background-color: var(--Brand-Coffee-Subtle); } diff --git a/app/[lang]/(live)/(public)/loyalty-page/layout.tsx b/app/[lang]/(live)/(public)/loyalty-page/layout.tsx index e925346f6..5812eca64 100644 --- a/app/[lang]/(live)/(public)/loyalty-page/layout.tsx +++ b/app/[lang]/(live)/(public)/loyalty-page/layout.tsx @@ -1,20 +1,16 @@ import { firaMono, firaSans } from "@/app/[lang]/(live)/fonts" -import Header from "@/components/MyPages/Header" - import styles from "./layout.module.css" import type { MyPagesLayoutProps } from "@/types/components/myPages/layout" export default async function LoyaltyPagesLayout({ children, - params, }: React.PropsWithChildren) { return (
-
{children}
) diff --git a/app/[lang]/(live)/(public)/loyalty-page/page.module.css b/app/[lang]/(live)/(public)/loyalty-page/page.module.css index 2fc0d7f7a..3311af02b 100644 --- a/app/[lang]/(live)/(public)/loyalty-page/page.module.css +++ b/app/[lang]/(live)/(public)/loyalty-page/page.module.css @@ -9,8 +9,7 @@ .blocks { display: grid; gap: 4.2rem; - padding-left: 2rem; - padding-right: 2rem; + padding: 1.6rem; } @media screen and (min-width: 950px) { diff --git a/app/[lang]/(live)/(public)/loyalty-page/page.tsx b/app/[lang]/(live)/(public)/loyalty-page/page.tsx index 88fab0647..152f7ed34 100644 --- a/app/[lang]/(live)/(public)/loyalty-page/page.tsx +++ b/app/[lang]/(live)/(public)/loyalty-page/page.tsx @@ -1,14 +1,15 @@ import { notFound } from "next/navigation" + import { serverClient } from "@/lib/trpc/server" -import MaxWidth from "@/components/MaxWidth" -import Sidebar from "@/components/Loyalty/Sidebar" import { Blocks } from "@/components/Loyalty/Blocks" - -import type { LangParams, PageArgs, UriParams } from "@/types/params" +import Sidebar from "@/components/Loyalty/Sidebar" +import MaxWidth from "@/components/MaxWidth" import styles from "./page.module.css" +import type { LangParams, PageArgs, UriParams } from "@/types/params" + export default async function LoyaltyPage({ params, searchParams, @@ -19,12 +20,12 @@ export default async function LoyaltyPage({ } const loyaltyPage = await serverClient().contentstack.loyaltyPage.get({ - uri: searchParams.uri, - lang: params.lang, + href: searchParams.uri, + locale: params.lang, }) return ( - +
-
+ -
- + +
) } catch (err) { return notFound() diff --git a/components/Loyalty/Blocks/CardGrid/cardGrid.module.css b/components/Loyalty/Blocks/CardGrid/cardGrid.module.css new file mode 100644 index 000000000..915c86f35 --- /dev/null +++ b/components/Loyalty/Blocks/CardGrid/cardGrid.module.css @@ -0,0 +1,28 @@ +.container { + display: grid; + gap: 2.4rem; +} + +.subtitle { + margin: 0; +} + +.titleContainer { + display: grid; + gap: 0.8rem; +} + +.cardContainer { + display: grid; + gap: 1.6rem; +} + +@media screen and (min-width: 950px) { + .cardContainer { + grid-template-columns: 1fr 1fr; + } + + .cardWrapper:last-child { + grid-column: span 2; + } +} diff --git a/components/Loyalty/Blocks/CardGrid/index.tsx b/components/Loyalty/Blocks/CardGrid/index.tsx new file mode 100644 index 000000000..b6568ce2b --- /dev/null +++ b/components/Loyalty/Blocks/CardGrid/index.tsx @@ -0,0 +1,50 @@ +import { _ } from "@/lib/translation" + +import Card from "@/components/TempDesignSystem/Card" +import Title from "@/components/Title" + +import styles from "./cardGrid.module.css" + +import { CardGridProps, CardProps } from "@/types/components/loyalty/blocks" + +export default function CardGrid({ card_grid }: CardGridProps) { + return ( +
+
+ + {card_grid.title} + + {card_grid.subtitle ? ( + + {card_grid.subtitle} + + ) : null} +
+
+ {card_grid.cards.map((card, i) => ( + + ))} +
+
+ ) +} + +function CardWrapper({ card }: CardProps) { + const link = card.referenceConnection.edges.length + ? { + href: card.referenceConnection.edges[0].node.url, + title: _("Read more"), + } + : undefined + + return ( +
+ +
+ ) +} diff --git a/components/Loyalty/Blocks/DynamicContent/HowItWorks/howItWorks.module.css b/components/Loyalty/Blocks/DynamicContent/HowItWorks/howItWorks.module.css new file mode 100644 index 000000000..27a9a7561 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/HowItWorks/howItWorks.module.css @@ -0,0 +1,11 @@ +.container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 37rem; + border-radius: 1.6rem; + background-color: var(--Base-Fill-Normal); + text-align: center; + margin-right: 1.6rem; +} diff --git a/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx b/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx index 26b3880bd..426cda514 100644 --- a/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx @@ -1,3 +1,13 @@ +import Title from "@/components/Title" + +import styles from "./howItWorks.module.css" + export default function HowItWorks() { - return
+ return ( +
+ + How it works Placeholder + +
+ ) } diff --git a/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx index 52c542a17..888dc3d99 100644 --- a/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx @@ -1,26 +1,50 @@ +import { Check } from "react-feather" + +import { _ } from "@/lib/translation" import { serverClient } from "@/lib/trpc/server" +import Image from "@/components/Image" +import Button from "@/components/TempDesignSystem/Button" +import Link from "@/components/TempDesignSystem/Link" +import Title from "@/components/Title" + +import styles from "./loyaltyLevels.module.css" + +import { LevelCardProps } from "@/types/components/loyalty/blocks" + export default async function LoyaltyLevels() { const data = await serverClient().loyalty.levels.all() return ( -
- {data.map((level) => ( - +
+
+ {data.map((level) => ( + + ))} +
+
+ +
+
+ ) +} + +function LevelCard({ level }: LevelCardProps) { + return ( +
+ {level.tier} + {level.name} +

+ {level.requiredPoints} {_("or")} {level.requiredNights} {_("nights")} +

+ {level.topBenefits.map((benefit) => ( +

+ + {benefit} +

))}
) } - -type LevelCardProps = { - level: { - tier: number - name: string - requiredPoints: number - requiredNights: string - topBenefits: string[] - } -} -function LevelCard({ level }: LevelCardProps) { - return
-} diff --git a/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/loyaltyLevels.module.css b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/loyaltyLevels.module.css new file mode 100644 index 000000000..affa9a47e --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/loyaltyLevels.module.css @@ -0,0 +1,83 @@ +.container { + display: grid; + gap: 2.4rem; +} + +.buttonContainer { + display: flex; + justify-content: center; +} + +.cardContainer { + display: flex; + gap: 0.8rem; + overflow-x: auto; + padding-right: 1.6rem; + margin-right: -1.6rem; + /* Hide scrollbar IE and Edge */ + -ms-overflow-style: none; + /* Hide Scrollbar Firefox */ + scrollbar-width: none; +} + +.card { + display: flex; + flex-direction: column; + align-items: center; + height: 37rem; + min-width: 32rem; + padding: 4rem 1rem; + background-color: var(--Base-Fill-Normal); + border-radius: 1.6rem; + gap: 1.8rem; +} + +.qualifications { + margin: 0; + font-size: var(--typography-Body-Bold-fontSize); + line-height: var(--typography-Body-Bold-lineHeight); + /* font-weight: var(--typography-Body-Bold-fontWeight); -- Tokens not parsable*/ + font-weight: 600; +} + +.benefits { + font-family: var(--fira-sans); + font-size: var(--typography-Body-Regular-fontSize); + line-height: var(--typography-Body-Regular-lineHeight); + margin: 0; + text-align: center; +} + +.icon { + font-family: var(--fira-sans); + position: relative; + top: 0.3rem; + height: 1.4rem; +} + +@media screen and (min-width: 950px) { + .container { + gap: 3.2rem; + } + .cardContainer { + display: grid; + grid-template-columns: repeat( + 12, + auto + ); /* Three columns in the first row */ + padding-right: 0; + margin-right: 0rem; + } + + .card { + min-width: auto; + } + + .card:nth-child(-n + 3) { + grid-column: span 4; + } + + .card:nth-last-child(-n + 4) { + grid-column: span 3; + } +} diff --git a/components/Loyalty/Blocks/DynamicContent/dynamicContent.module.css b/components/Loyalty/Blocks/DynamicContent/dynamicContent.module.css index e69de29bb..a448e08f6 100644 --- a/components/Loyalty/Blocks/DynamicContent/dynamicContent.module.css +++ b/components/Loyalty/Blocks/DynamicContent/dynamicContent.module.css @@ -0,0 +1,28 @@ +.container { + display: grid; + gap: 2.4rem; + overflow: hidden; + margin-right: -1.6rem; + padding-right: 1.6rem; +} + +.titleContainer { + display: grid; + grid-template-areas: "title link"; + grid-template-columns: 1fr max-content; + padding-bottom: 0.8rem; +} + +.title { + grid-area: title; +} + +.link { + grid-area: link; + font-size: var(--typography-Body-Underlined-fontSize); + color: var(--some-black-color, #000); +} + +.subtitle { + margin: 0; +} diff --git a/components/Loyalty/Blocks/DynamicContent/index.tsx b/components/Loyalty/Blocks/DynamicContent/index.tsx index d82699024..0bb4fe45b 100644 --- a/components/Loyalty/Blocks/DynamicContent/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/index.tsx @@ -1,18 +1,27 @@ +import Link from "@/components/TempDesignSystem/Link" import Title from "@/components/Title" + +import HowItWorks from "./HowItWorks" +import LoyaltyLevels from "./LoyaltyLevels" +import OverviewTable from "./OverviewTable" + +import styles from "./dynamicContent.module.css" + +import { DynamicContentProps } from "@/types/components/loyalty/blocks" import { - DynamicContentProps, LoyaltyComponent, LoyaltyComponentEnum, -} from "@/types/components/loyalty/blocks" +} from "@/types/requests/loyaltyPage" function DynamicComponentBlock({ component }: { component: LoyaltyComponent }) { switch (component) { case LoyaltyComponentEnum.how_it_works: - return

How it works

+ return case LoyaltyComponentEnum.loyalty_levels: - return

loyalty_levels

+ return case LoyaltyComponentEnum.overview_table: - return

overview_table

+ // TODO: IMPLEMENT OVERVIEW TABLE! + return default: return null } @@ -21,14 +30,44 @@ function DynamicComponentBlock({ component }: { component: LoyaltyComponent }) { export default function DynamicContent({ dynamicContent, }: DynamicContentProps) { + const link = dynamicContent.link.pageConnection.edges.length + ? dynamicContent.link.pageConnection.edges[0].node.url + : null return ( -
+
- {dynamicContent.title} - {dynamicContent.preamble ?

{dynamicContent.preamble}

: null} - {dynamicContent.link ? <> : null} +
+ {dynamicContent.title && ( + + {dynamicContent.title} + + )} + {link && ( + + {dynamicContent.link.text} + + )} +
+ {dynamicContent.subtitle && ( + + {dynamicContent.subtitle} + + )}
- +
+ +
) } diff --git a/components/Loyalty/Blocks/index.tsx b/components/Loyalty/Blocks/index.tsx index c496fa3e7..3b8b77744 100644 --- a/components/Loyalty/Blocks/index.tsx +++ b/components/Loyalty/Blocks/index.tsx @@ -1,6 +1,8 @@ import JsonToHtml from "@/components/JsonToHtml" import DynamicContentBlock from "@/components/Loyalty/Blocks/DynamicContent" +import CardGrid from "./CardGrid" + import { Blocks as BlocksType, LoyaltyBlocksTypenameEnum, @@ -10,7 +12,7 @@ export function Blocks({ blocks }: { blocks: BlocksType[] }) { return blocks.map((block) => { switch (block.__typename) { case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksCardGrid: - return

Cards

+ return case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent: return ( {block.preamble &&

{block.preamble}

}
- Already a friend?
- Click here to log in + {_("Already a friend?")}
+ {_("Click here to log in")}
diff --git a/components/Loyalty/Sidebar/JoinLoyalty/joinLoyalty.module.css b/components/Loyalty/Sidebar/JoinLoyalty/joinLoyalty.module.css index 8c3b0ac84..a2983576f 100644 --- a/components/Loyalty/Sidebar/JoinLoyalty/joinLoyalty.module.css +++ b/components/Loyalty/Sidebar/JoinLoyalty/joinLoyalty.module.css @@ -2,7 +2,6 @@ display: grid; font-weight: 600; background-color: var(--Base-Background-Elevated); - border-radius: 32px 4px 4px 32px; } .wrapper { @@ -10,7 +9,7 @@ align-items: center; flex-direction: column; gap: 2rem; - padding: 4rem 2rem; + padding: 6rem 2rem; } .preamble { @@ -37,6 +36,10 @@ } @media screen and (min-width: 950px) { + .container { + border-radius: 32px 4px 4px 32px; + } + .wrapper { gap: 3rem; } diff --git a/components/Loyalty/Sidebar/index.tsx b/components/Loyalty/Sidebar/index.tsx index 94cd937d2..f6128fcbe 100644 --- a/components/Loyalty/Sidebar/index.tsx +++ b/components/Loyalty/Sidebar/index.tsx @@ -1,4 +1,5 @@ import JsonToHtml from "@/components/JsonToHtml" + import JoinLoyaltyContact from "./JoinLoyalty" import { Sidebar, SidebarTypenameEnum } from "@/types/requests/loyaltyPage" diff --git a/components/TempDesignSystem/Card/card.module.css b/components/TempDesignSystem/Card/card.module.css new file mode 100644 index 000000000..5f8f8a3e6 --- /dev/null +++ b/components/TempDesignSystem/Card/card.module.css @@ -0,0 +1,15 @@ +.linkCard { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 37rem; + width: 100%; + margin-right: 1.6rem; + border-radius: 1.6rem; + gap: 1rem; + padding: 1.6rem; + + background-color: var(--Base-Fill-Normal); + text-align: center; +} diff --git a/components/TempDesignSystem/Card/card.ts b/components/TempDesignSystem/Card/card.ts new file mode 100644 index 000000000..3c346f423 --- /dev/null +++ b/components/TempDesignSystem/Card/card.ts @@ -0,0 +1,9 @@ +export type CardProps = { + link?: { + href: string + title: string + } + title?: string + subtitle?: string + openInNewTab?: boolean +} diff --git a/components/TempDesignSystem/Card/index.tsx b/components/TempDesignSystem/Card/index.tsx new file mode 100644 index 000000000..11523155c --- /dev/null +++ b/components/TempDesignSystem/Card/index.tsx @@ -0,0 +1,38 @@ +import { _ } from "@/lib/translation" + +import Title from "@/components/Title" + +import Button from "../Button" +import Link from "../Link" +import { CardProps } from "./card" + +import styles from "./card.module.css" + +export default function Card({ + link, + subtitle, + title, + openInNewTab = false, +}: CardProps) { + return ( +
+ {title ? ( + + {title} + + ) : null} + {subtitle ? ( + + {subtitle} + + ) : null} + {link ? ( + + ) : null} +
+ ) +} diff --git a/lib/graphql/Query/ContentTypeUid.graphql b/lib/graphql/Query/ContentTypeUid.graphql new file mode 100644 index 000000000..57dd92233 --- /dev/null +++ b/lib/graphql/Query/ContentTypeUid.graphql @@ -0,0 +1,17 @@ +query GetContentTypeUid($locale: String!, $url: String!) { + all_content_page(where: { url: $url, locale: $locale }) { + items { + __typename + } + } + all_current_blocks_page(where: { url: $url, locale: $locale }) { + items { + __typename + } + } + all_loyalty_page(where: { url: $url, locale: $locale }) { + items { + __typename + } + } +} diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql index 8c6891059..1efefc2c9 100644 --- a/lib/graphql/Query/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage.graphql @@ -11,7 +11,7 @@ query GetLoyaltyPage($locale: String!, $url: String!) { __typename dynamic_content { title - preamble + subtitle component link { text @@ -29,8 +29,8 @@ query GetLoyaltyPage($locale: String!, $url: String!) { ... on LoyaltyPageBlocksCardGrid { __typename card_grid { - heading - preamble + title + subtitle cards { referenceConnection { edges { @@ -42,8 +42,8 @@ query GetLoyaltyPage($locale: String!, $url: String!) { } } } - heading - preamble + title + subtitle } } } diff --git a/middlewares/cmsContent.ts b/middlewares/cmsContent.ts index 183e4a2f5..202d23241 100644 --- a/middlewares/cmsContent.ts +++ b/middlewares/cmsContent.ts @@ -1,19 +1,70 @@ +import { DocumentNode } from "graphql" import { NextResponse } from "next/server" import { findLang } from "@/constants/languages" +import { env } from "@/env/server" +import GetContentTypeUid from "@/lib/graphql/Query/ContentTypeUid.graphql" import type { NextMiddleware } from "next/server" import { MiddlewareMatcher } from "@/types/middleware" +enum PageTypeEnum { + CurrentBlocksPage = "CurrentBlocksPage", + LoyaltyPage = "LoyaltyPage", + ContentPage = "contentPage", +} + +type GetContentTypeUidType = { + all_content_page: { + items: { + __typename: PageTypeEnum.ContentPage + }[] + } + all_loyalty_page: { + items: { + __typename: PageTypeEnum.LoyaltyPage + }[] + } + all_current_blocks_page: { + items: { + __typename?: PageTypeEnum.CurrentBlocksPage + }[] + } +} + +type PageType = keyof typeof PageTypeEnum + export const middleware: NextMiddleware = async (request) => { const { nextUrl } = request const lang = findLang(nextUrl.pathname) - const contentType = "loyaltyPage" const pathNameWithoutLang = nextUrl.pathname.replace(`/${lang}`, "") const searchParams = new URLSearchParams(request.nextUrl.searchParams) + const print = (await import("graphql/language/printer")).print + const result = await fetch(env.CMS_URL, { + method: "POST", + headers: { + access_token: env.CMS_ACCESS_TOKEN, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: print(GetContentTypeUid as DocumentNode), + variables: { + locale: lang, + url: pathNameWithoutLang, + }, + }), + }) + + const pageTypeData = await result.json() + const pageType = pageTypeData.data as GetContentTypeUidType + + const contentType = Object.values(pageType) + .map((val) => val.items[0]) + .find((item) => item?.__typename)?.__typename + if (request.nextUrl.pathname.includes("preview")) { searchParams.set("uri", pathNameWithoutLang.replace("/preview", "")) return NextResponse.rewrite( @@ -23,14 +74,14 @@ export const middleware: NextMiddleware = async (request) => { searchParams.set("uri", pathNameWithoutLang) switch (contentType) { - // case "currentContentPage": - // return NextResponse.rewrite( - // new URL( - // `/${lang}/current-content-page?${searchParams.toString()}`, - // nextUrl - // ) - // ) - case "loyaltyPage": + case PageTypeEnum.CurrentBlocksPage: + return NextResponse.rewrite( + new URL( + `/${lang}/current-content-page?${searchParams.toString()}`, + nextUrl + ) + ) + case PageTypeEnum.LoyaltyPage: return NextResponse.rewrite( new URL(`/${lang}/loyalty-page?${searchParams.toString()}`, nextUrl) ) diff --git a/public/_static/icons/new-friend.png b/public/_static/icons/new-friend.png new file mode 100644 index 000000000..0dfa36710 Binary files /dev/null and b/public/_static/icons/new-friend.png differ diff --git a/server/routers/contentstack/breadcrumbs/query.ts b/server/routers/contentstack/breadcrumbs/query.ts index 7fe6db0f7..e9e141cfa 100644 --- a/server/routers/contentstack/breadcrumbs/query.ts +++ b/server/routers/contentstack/breadcrumbs/query.ts @@ -1,10 +1,11 @@ +import { GetMyPagesBreadcrumbs } from "@/lib/graphql/Query/BreadcrumbsMyPages.graphql" +import { request } from "@/lib/graphql/request" import { badRequestError, internalServerError } from "@/server/errors/trpc" -import { validateBreadcrumbsConstenstackSchema } from "./output" import { publicProcedure, router } from "@/server/trpc" -import { request } from "@/lib/graphql/request" -import { GetMyPagesBreadcrumbs } from "@/lib/graphql/Query/BreadcrumbsMyPages.graphql" import { getBreadcrumbsInput } from "./input" +import { validateBreadcrumbsConstenstackSchema } from "./output" + import { GetMyPagesBreadcrumbsData } from "@/types/requests/myPages/breadcrumbs" export const breadcrumbsQueryRouter = router({ diff --git a/server/routers/contentstack/loyaltyPage/input.ts b/server/routers/contentstack/loyaltyPage/input.ts new file mode 100644 index 000000000..1cddb2b6f --- /dev/null +++ b/server/routers/contentstack/loyaltyPage/input.ts @@ -0,0 +1,10 @@ +import { z } from "zod" + +import { Lang } from "@/constants/languages" + +const langs = Object.keys(Lang) as [keyof typeof Lang] + +export const getLoyaltyPageInput = z.object({ + href: z.string().min(1, { message: "href is required" }), + locale: z.enum(langs), +}) diff --git a/server/routers/loyalty/query.ts b/server/routers/loyalty/query.ts index 2c58d1773..490623a88 100644 --- a/server/routers/loyalty/query.ts +++ b/server/routers/loyalty/query.ts @@ -1,6 +1,7 @@ -import { allLevels } from "./temp" import { protectedProcedure, publicProcedure, router } from "@/server/trpc" +import { allLevels } from "./temp" + function fakingRequest(payload: T): Promise { return new Promise((resolve) => { setTimeout(() => { diff --git a/server/routers/loyalty/temp.ts b/server/routers/loyalty/temp.ts index a2cefa2a3..e8fc56550 100644 --- a/server/routers/loyalty/temp.ts +++ b/server/routers/loyalty/temp.ts @@ -9,6 +9,7 @@ export const allLevels = [ "Always best price", "Book reward nights with points", ], + logo: "/_static/icons/new-friend.png", }, { tier: 2, @@ -20,6 +21,7 @@ export const allLevels = [ "Always best price", "Book reward nights with points", ], + logo: "/_static/icons/new-friend.png", }, { tier: 3, @@ -31,6 +33,7 @@ export const allLevels = [ "Always best price", "Book reward nights with points", ], + logo: "/_static/icons/new-friend.png", }, { tier: 4, @@ -42,6 +45,7 @@ export const allLevels = [ "Always best price", "Book reward nights with points", ], + logo: "/_static/icons/new-friend.png", }, { tier: 5, @@ -53,6 +57,7 @@ export const allLevels = [ "Always best price", "Book reward nights with points", ], + logo: "/_static/icons/new-friend.png", }, { tier: 6, @@ -64,5 +69,18 @@ export const allLevels = [ "Always best price", "Book reward nights with points", ], + logo: "/_static/icons/new-friend.png", + }, + { + tier: 7, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + logo: "/_static/icons/new-friend.png", }, ] diff --git a/types/components/loyalty/blocks.ts b/types/components/loyalty/blocks.ts index 684974712..4efdc7f43 100644 --- a/types/components/loyalty/blocks.ts +++ b/types/components/loyalty/blocks.ts @@ -1,47 +1,46 @@ import { Embeds } from "@/types/requests/embeds" +import { DynamicContentBlock } from "@/types/requests/loyaltyPage" import { PageLink } from "@/types/requests/myPages/navigation" import { Edges } from "@/types/requests/utils/edges" import { RTEDocument } from "@/types/rte/node" -export enum LoyaltyComponentEnum { - loyalty_levels = "loyalty_levels", - how_it_works = "how_it_works", - overview_table = "overview_table", -} - -export type LoyaltyComponent = keyof typeof LoyaltyComponentEnum - -export type DynamicContentBlock = { - dynamic_content: { - title: string - preamble?: string - component: LoyaltyComponent - link: { - text?: string - page: Edges - } - } -} - export type DynamicContentProps = { dynamicContent: DynamicContentBlock["dynamic_content"] } +type Card = { + referenceConnection: Edges + title?: string + subtitle?: string + open_in_new_tab: boolean +} + +export type CardProps = { card: Card } + export type CardGrid = { card_grid: { - heading: string - preamble: string - cards: { - referenceConnection: Edges - heading: string - preamble: string - } + title?: string + subtitle?: string + cards: Card[] } } +export type CardGridProps = CardGrid + export type Content = { content: { embedded_itemsConnection: Edges json: RTEDocument } } + +export type LevelCardProps = { + level: { + tier: number + name: string + requiredPoints: number + requiredNights: string + topBenefits: string[] + logo: string + } +} diff --git a/types/components/loyalty/sidebar.ts b/types/components/loyalty/sidebar.ts index e41441994..2dcb38cbe 100644 --- a/types/components/loyalty/sidebar.ts +++ b/types/components/loyalty/sidebar.ts @@ -1,6 +1,6 @@ import { ContactFields } from "@/types/requests/contactConfig" import { Embeds } from "@/types/requests/embeds" -import { JoinLoyaltyContactEnum } from "@/types/requests/loyaltyPage" +import { JoinLoyaltyContactContact } from "@/types/requests/loyaltyPage" import { Edges } from "@/types/requests/utils/edges" import { RTEDocument } from "@/types/rte/node" @@ -16,5 +16,5 @@ export type Contact = { } export type ContactProps = { - contactBlock: JoinLoyaltyContactEnum[] + contactBlock: JoinLoyaltyContactContact[] } diff --git a/types/requests/contactConfig.ts b/types/requests/contactConfig.ts index 642f6d4ce..5995d432a 100644 --- a/types/requests/contactConfig.ts +++ b/types/requests/contactConfig.ts @@ -49,6 +49,7 @@ export type GetContactConfigData = { // Utility types that extract the possible strings of ContactConfigField, // Which is all the dot notated values of ContactConfig (for example: 'email.name') +// From: https://stackoverflow.com/questions/47057649/typescript-string-dot-notation-of-nested-object#47058976 type PathsToStringProps = T extends string ? [] : { @@ -65,7 +66,7 @@ type Join = T extends [] : never : string -type ContactConfigField = Join, "."> +export type ContactConfigField = Join, "."> export type ContactFields = { display_text?: string diff --git a/types/requests/loyaltyPage.ts b/types/requests/loyaltyPage.ts index 8ebe53154..b97669164 100644 --- a/types/requests/loyaltyPage.ts +++ b/types/requests/loyaltyPage.ts @@ -1,24 +1,16 @@ +import { CardGrid, Content } from "../components/loyalty/blocks" +import { Contact, SidebarContent } from "../components/loyalty/sidebar" +import { PageLink } from "./myPages/navigation" +import { Edges } from "./utils/edges" + import type { AllRequestResponse } from "./utils/all" import type { Typename } from "./utils/typename" -import { Contact, SidebarContent } from "../components/loyalty/sidebar" -import { - CardGrid, - Content, - DynamicContentBlock, -} from "../components/loyalty/blocks" - -export enum SidebarTypenameEnum { - LoyaltyPageSidebarJoinLoyaltyContact = "LoyaltyPageSidebarJoinLoyaltyContact", - LoyaltyPageSidebarContent = "LoyaltyPageSidebarContent", -} - -export type SidebarTypename = keyof typeof SidebarTypenameEnum export enum JoinLoyaltyContactTypenameEnum { LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact = "LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact", } -export type JoinLoyaltyContactEnum = Typename< +export type JoinLoyaltyContactContact = Typename< Contact, JoinLoyaltyContactTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact > @@ -27,11 +19,17 @@ export type JoinLoyaltyContact = { join_loyalty_contact: { title?: string preamble?: string - contact: JoinLoyaltyContactEnum[] - login_button_text: string + contact: JoinLoyaltyContactContact[] } } +export enum SidebarTypenameEnum { + LoyaltyPageSidebarJoinLoyaltyContact = "LoyaltyPageSidebarJoinLoyaltyContact", + LoyaltyPageSidebarContent = "LoyaltyPageSidebarContent", +} + +export type SidebarTypename = keyof typeof SidebarTypenameEnum + export type Sidebar = | Typename | Typename< @@ -39,6 +37,26 @@ export type Sidebar = SidebarTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContact > +export enum LoyaltyComponentEnum { + loyalty_levels = "loyalty_levels", + how_it_works = "how_it_works", + overview_table = "overview_table", +} + +export type LoyaltyComponent = keyof typeof LoyaltyComponentEnum + +export type DynamicContentBlock = { + dynamic_content: { + title?: string + subtitle?: string + component: LoyaltyComponent + link: { + text?: string + pageConnection: Edges + } + } +} + export enum LoyaltyBlocksTypenameEnum { LoyaltyPageBlocksDynamicContent = "LoyaltyPageBlocksDynamicContent", LoyaltyPageBlocksCardGrid = "LoyaltyPageBlocksCardGrid", diff --git a/types/requests/utils/asset.ts b/types/requests/utils/asset.ts index b1d35d8e6..d9dae927e 100644 --- a/types/requests/utils/asset.ts +++ b/types/requests/utils/asset.ts @@ -1,5 +1,5 @@ -import type { EmbedEnum } from "./embeds" import type { Image } from "@/types/image" +import type { EmbedEnum } from "./embeds" import type { Typename } from "./typename" export type SysAsset = Typename diff --git a/utils/contactConfig.ts b/utils/contactConfig.ts index e94eef75f..83a24abf8 100644 --- a/utils/contactConfig.ts +++ b/utils/contactConfig.ts @@ -1,10 +1,11 @@ import { ContactConfig, + ContactConfigField, ContactFieldGroups, } from "@/types/requests/contactConfig" export function getValueFromContactConfig( - keyStrings: string, + keyStrings: ContactConfigField, data: ContactConfig ): string | undefined { const [groupName, key] = keyStrings.split(".") as [