diff --git a/app/[lang]/(live)/(public)/loyalty-page/page.module.css b/app/[lang]/(live)/(public)/loyalty-page/page.module.css index c8531f21d..2fc0d7f7a 100644 --- a/app/[lang]/(live)/(public)/loyalty-page/page.module.css +++ b/app/[lang]/(live)/(public)/loyalty-page/page.module.css @@ -15,8 +15,8 @@ @media screen and (min-width: 950px) { .content { - gap: 10rem; - grid-template-columns: 25rem 1fr; + gap: 2.7rem; + grid-template-columns: 30rem 1fr; padding-bottom: 17.5rem; padding-left: 2.4rem; padding-right: 2.4rem; diff --git a/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx b/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx new file mode 100644 index 000000000..26b3880bd --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/HowItWorks/index.tsx @@ -0,0 +1,3 @@ +export default function HowItWorks() { + return
+} diff --git a/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx new file mode 100644 index 000000000..52c542a17 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/LoyaltyLevels/index.tsx @@ -0,0 +1,26 @@ +import { serverClient } from "@/lib/trpc/server" + +export default async function LoyaltyLevels() { + const data = await serverClient().loyalty.levels.all() + + return ( +
+ {data.map((level) => ( + + ))} +
+ ) +} + +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/OverviewTable/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx new file mode 100644 index 000000000..c90c221e6 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx @@ -0,0 +1,3 @@ +export default function OverviewTable() { + return
+} diff --git a/components/Loyalty/Blocks/DynamicContent/dynamicContent.module.css b/components/Loyalty/Blocks/DynamicContent/dynamicContent.module.css new file mode 100644 index 000000000..e69de29bb diff --git a/components/Loyalty/Blocks/DynamicContent/index.tsx b/components/Loyalty/Blocks/DynamicContent/index.tsx new file mode 100644 index 000000000..d82699024 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/index.tsx @@ -0,0 +1,34 @@ +import Title from "@/components/Title" +import { + DynamicContentProps, + LoyaltyComponent, + LoyaltyComponentEnum, +} from "@/types/components/loyalty/blocks" + +function DynamicComponentBlock({ component }: { component: LoyaltyComponent }) { + switch (component) { + case LoyaltyComponentEnum.how_it_works: + return

How it works

+ case LoyaltyComponentEnum.loyalty_levels: + return

loyalty_levels

+ case LoyaltyComponentEnum.overview_table: + return

overview_table

+ default: + return null + } +} + +export default function DynamicContent({ + dynamicContent, +}: DynamicContentProps) { + return ( +
+
+ {dynamicContent.title} + {dynamicContent.preamble ?

{dynamicContent.preamble}

: null} + {dynamicContent.link ? <> : null} +
+ +
+ ) +} diff --git a/components/Loyalty/Blocks/index.tsx b/components/Loyalty/Blocks/index.tsx index 659a5e22d..c496fa3e7 100644 --- a/components/Loyalty/Blocks/index.tsx +++ b/components/Loyalty/Blocks/index.tsx @@ -1,4 +1,5 @@ import JsonToHtml from "@/components/JsonToHtml" +import DynamicContentBlock from "@/components/Loyalty/Blocks/DynamicContent" import { Blocks as BlocksType, @@ -18,7 +19,7 @@ export function Blocks({ blocks }: { blocks: BlocksType[] }) { /> ) case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent: - return

Dynamic

+ return default: return null } diff --git a/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/contactRow.module.css b/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/contactRow.module.css new file mode 100644 index 000000000..47bd6b899 --- /dev/null +++ b/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/contactRow.module.css @@ -0,0 +1,21 @@ +.container { + display: grid; + text-align: center; + gap: 0.4rem; + padding: 1rem; +} + +.title { + font-family: var(--fira-sans); + font-size: 1.6rem; + font-weight: 700; + + margin: 0; +} + +.value { + font-family: var(--fira-sans); + font-size: 1.6rem; + font-weight: 400; + margin: 0; +} diff --git a/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx b/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx new file mode 100644 index 000000000..d7a95aec9 --- /dev/null +++ b/components/Loyalty/Sidebar/JoinLoyalty/Contact/ContactRow/index.tsx @@ -0,0 +1,25 @@ +import { serverClient } from "@/lib/trpc/server" +import { getValueFromContactConfig } from "@/utils/contactConfig" + +import styles from "./contactRow.module.css" + +import { Lang } from "@/constants/languages" +import type { ContactFields } from "@/types/requests/contactConfig" + +export default async function ContactRow({ + contact, +}: { + contact: ContactFields +}) { + const data = await serverClient().contentstack.contactConfig.get({ + lang: Lang.en, + }) + + const val = getValueFromContactConfig(contact.contact_field, data) + return ( +
+

{contact.display_text}

+

{val}

+
+ ) +} diff --git a/components/Loyalty/Sidebar/JoinLoyalty/Contact/contact.module.css b/components/Loyalty/Sidebar/JoinLoyalty/Contact/contact.module.css new file mode 100644 index 000000000..38dbcdf35 --- /dev/null +++ b/components/Loyalty/Sidebar/JoinLoyalty/Contact/contact.module.css @@ -0,0 +1,16 @@ +.contactContainer { + display: none; +} + +@media screen and (min-width: 950px) { + .contactContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + border-top: 0.5px solid var(--Base-Border-Disabled); + padding: 3.4rem; + text-align: center; + gap: 6.2rem; + } +} diff --git a/components/Loyalty/Sidebar/JoinLoyalty/Contact/index.tsx b/components/Loyalty/Sidebar/JoinLoyalty/Contact/index.tsx index 351362cff..639219dd4 100644 --- a/components/Loyalty/Sidebar/JoinLoyalty/Contact/index.tsx +++ b/components/Loyalty/Sidebar/JoinLoyalty/Contact/index.tsx @@ -1,8 +1,25 @@ -import { Lang } from "@/constants/languages" -import { serverClient } from "@/lib/trpc/server" +import Title from "@/components/Title" +import ContactRow from "./ContactRow" -export default function Contact({ lang }: { lang: Lang }) { - const data = serverClient().contentstack.contactConfig.get({ lang }) +import styles from "./contact.module.css" - return
+import { JoinLoyaltyContactTypenameEnum } from "@/types/requests/loyaltyPage" +import type { ContactProps } from "@/types/components/loyalty/sidebar" + +export default async function Contact({ contactBlock }: ContactProps) { + return ( +
+ Contact us +
+ {contactBlock.map(({ contact, __typename }) => { + switch (__typename) { + case JoinLoyaltyContactTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact: + return + default: + return null + } + })} +
+
+ ) } diff --git a/components/Loyalty/Sidebar/JoinLoyalty/index.tsx b/components/Loyalty/Sidebar/JoinLoyalty/index.tsx index ec44c4df9..7e2586f24 100644 --- a/components/Loyalty/Sidebar/JoinLoyalty/index.tsx +++ b/components/Loyalty/Sidebar/JoinLoyalty/index.tsx @@ -4,10 +4,10 @@ import Button from "@/components/TempDesignSystem/Button" import Link from "@/components/TempDesignSystem/Link" import Image from "@/components/Image" -import type { JoinLoyaltyContact } from "@/types/requests/loyaltyPage" - import styles from "./joinLoyalty.module.css" +import type { JoinLoyaltyContact } from "@/types/requests/loyaltyPage" + export default function JoinLoyaltyContact({ block, }: { @@ -35,11 +35,7 @@ export default function JoinLoyaltyContact({ - {block.contact - ? block.contact.map((contact, i) => ( - - )) - : null} + {block.contact && } ) } diff --git a/lib/graphql/Query/LoyaltyPage.graphql b/lib/graphql/Query/LoyaltyPage.graphql index c8c50b246..8c6891059 100644 --- a/lib/graphql/Query/LoyaltyPage.graphql +++ b/lib/graphql/Query/LoyaltyPage.graphql @@ -81,7 +81,8 @@ query GetLoyaltyPage($locale: String!, $url: String!) { ... on LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact { __typename contact { - contact_fields + display_text + contact_field } } } diff --git a/server/index.ts b/server/index.ts index f9aa0e258..ceb06b7fa 100644 --- a/server/index.ts +++ b/server/index.ts @@ -3,10 +3,12 @@ import { router } from "./trpc" /** Routers */ import { contentstackRouter } from "./routers/contentstack" import { userRouter } from "./routers/user" +import { loyaltyRouter } from "./routers/loyalty" export const appRouter = router({ contentstack: contentstackRouter, user: userRouter, + loyalty: loyaltyRouter, }) export type AppRouter = typeof appRouter diff --git a/server/routers/loyalty/index.ts b/server/routers/loyalty/index.ts new file mode 100644 index 000000000..412aa2846 --- /dev/null +++ b/server/routers/loyalty/index.ts @@ -0,0 +1,5 @@ +import { mergeRouters } from "@/server/trpc" + +import { lotaltyQueryRouter } from "./query" + +export const loyaltyRouter = mergeRouters(lotaltyQueryRouter) diff --git a/server/routers/loyalty/query.ts b/server/routers/loyalty/query.ts new file mode 100644 index 000000000..2c58d1773 --- /dev/null +++ b/server/routers/loyalty/query.ts @@ -0,0 +1,27 @@ +import { allLevels } from "./temp" +import { protectedProcedure, publicProcedure, router } from "@/server/trpc" + +function fakingRequest(payload: T): Promise { + return new Promise((resolve) => { + setTimeout(() => { + resolve(payload) + }, 1500) + }) +} + +export const lotaltyQueryRouter = router({ + levels: router({ + all: publicProcedure.query(async function ({ ctx }) { + // TODO: Make request to get user data from Scandic API + return await fakingRequest(allLevels) + }), + current: protectedProcedure.query(async function (opts) { + // TODO: Make request to get user data from Scandic API + return await fakingRequest<(typeof allLevels)[number]>(allLevels[1]) + }), + next: protectedProcedure.query(async function (opts) { + // TODO: Make request to get user data from Scandic API + return await fakingRequest<(typeof allLevels)[number]>(allLevels[2]) + }), + }), +}) diff --git a/server/routers/loyalty/temp.ts b/server/routers/loyalty/temp.ts new file mode 100644 index 000000000..a2cefa2a3 --- /dev/null +++ b/server/routers/loyalty/temp.ts @@ -0,0 +1,68 @@ +export const allLevels = [ + { + tier: 1, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + }, + { + tier: 2, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + }, + { + tier: 3, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + }, + { + tier: 4, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + }, + { + tier: 5, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + }, + { + tier: 6, + name: "New Friend", + requiredPoints: 50000, + requiredNights: "X", + topBenefits: [ + "15% on food on weekends", + "Always best price", + "Book reward nights with points", + ], + }, +] diff --git a/types/components/loyalty/blocks.ts b/types/components/loyalty/blocks.ts new file mode 100644 index 000000000..684974712 --- /dev/null +++ b/types/components/loyalty/blocks.ts @@ -0,0 +1,47 @@ +import { Embeds } from "@/types/requests/embeds" +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"] +} + +export type CardGrid = { + card_grid: { + heading: string + preamble: string + cards: { + referenceConnection: Edges + heading: string + preamble: string + } + } +} + +export type Content = { + content: { + embedded_itemsConnection: Edges + json: RTEDocument + } +} diff --git a/types/components/loyalty/sidebar.ts b/types/components/loyalty/sidebar.ts new file mode 100644 index 000000000..e41441994 --- /dev/null +++ b/types/components/loyalty/sidebar.ts @@ -0,0 +1,20 @@ +import { ContactFields } from "@/types/requests/contactConfig" +import { Embeds } from "@/types/requests/embeds" +import { JoinLoyaltyContactEnum } from "@/types/requests/loyaltyPage" +import { Edges } from "@/types/requests/utils/edges" +import { RTEDocument } from "@/types/rte/node" + +export type SidebarContent = { + content: { + embedded_itemsConnection: Edges + json: RTEDocument + } +} + +export type Contact = { + contact: ContactFields +} + +export type ContactProps = { + contactBlock: JoinLoyaltyContactEnum[] +} diff --git a/types/requests/contactConfig.ts b/types/requests/contactConfig.ts index ba34c2b0e..642f6d4ce 100644 --- a/types/requests/contactConfig.ts +++ b/types/requests/contactConfig.ts @@ -1,48 +1,73 @@ import { AllRequestResponse } from "./utils/all" export type ContactConfig = { - items: [ - { - email: { - name: string - address: string - } - email_loyalty: { - name: string - address: string - } - mailing_address: { - zip: string - street: string - name: string - city: string - country: string - } - phone: { - number: string - naem: string - } - phone_loyalty: { - number: string - name: string - } - visiting_address: { - zip: string - country: string - city: string - street: string - } - }, - ] + email: { + name?: string + address?: string + } + email_loyalty: { + name?: string + address?: string + } + mailing_address: { + zip?: string + street?: string + name?: string + city?: string + country?: string + } + phone: { + number?: string + name?: string + } + phone_loyalty: { + number?: string + name?: string + } + visiting_address: { + zip?: string + country?: string + city?: string + street?: string + } } -type FlattenKeys = T extends object - ? { - [K in keyof T]-?: `${K & string}.${FlattenKeys}` - }[keyof T] - : "" -export type ContactField = FlattenKeys +export enum ContactFieldGroupsEnum { + email = "email", + email_loyalty = "email_loyalty", + mailing_address = "mailing_address", + phone = "phone", + phone_loyalty = "phone_loyalty", + visiting_address = "visiting_address", +} + +export type ContactFieldGroups = keyof typeof ContactFieldGroupsEnum export type GetContactConfigData = { all_contact_config: AllRequestResponse } + +// Utility types that extract the possible strings of ContactConfigField, +// Which is all the dot notated values of ContactConfig (for example: 'email.name') +type PathsToStringProps = T extends string + ? [] + : { + [K in Extract]: [K, ...PathsToStringProps] + }[Extract] + +type Join = T extends [] + ? never + : T extends [infer F] + ? F + : T extends [infer F, ...infer R] + ? F extends string + ? `${F}${D}${Join, D>}` + : never + : string + +type ContactConfigField = Join, "."> + +export type ContactFields = { + display_text?: string + contact_field: ContactConfigField +} diff --git a/types/requests/loyaltyPage.ts b/types/requests/loyaltyPage.ts index 2318ffdd2..8ebe53154 100644 --- a/types/requests/loyaltyPage.ts +++ b/types/requests/loyaltyPage.ts @@ -1,10 +1,11 @@ -import { PageLink } from "./myPages/navigation" -import { Edges } from "./utils/edges" import type { AllRequestResponse } from "./utils/all" import type { Typename } from "./utils/typename" -import type { RTEDocument } from "../rte/node" -import type { Embeds } from "./embeds" -import type { ContactField } from "./contactConfig" +import { Contact, SidebarContent } from "../components/loyalty/sidebar" +import { + CardGrid, + Content, + DynamicContentBlock, +} from "../components/loyalty/blocks" export enum SidebarTypenameEnum { LoyaltyPageSidebarJoinLoyaltyContact = "LoyaltyPageSidebarJoinLoyaltyContact", @@ -13,24 +14,11 @@ export enum SidebarTypenameEnum { export type SidebarTypename = keyof typeof SidebarTypenameEnum -type SidebarContent = { - content: { - embedded_itemsConnection: Edges - json: RTEDocument - } -} - -type Contact = { - contact: { - contact_fields: ContactField[] - } -} - -enum JoinLoyaltyContactTypenameEnum { +export enum JoinLoyaltyContactTypenameEnum { LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact = "LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact", } -type JoinLoyaltyContactEnum = Typename< +export type JoinLoyaltyContactEnum = Typename< Contact, JoinLoyaltyContactTypenameEnum.LoyaltyPageSidebarJoinLoyaltyContactBlockContactContact > @@ -57,43 +45,10 @@ export enum LoyaltyBlocksTypenameEnum { LoyaltyPageBlocksContent = "LoyaltyPageBlocksContent", } -type CardGrid = { - card_grid: { - heading: string - preamble: string - cards: { - referenceConnection: Edges - heading: string - preamble: string - } - } -} - -type Content = { - content: { - embedded_itemsConnection: Edges - json: RTEDocument - } -} - -type LoyaltyComponent = "loyalty_levels" | "how_it_works" | "overview_table" - -type DynamicContent = { - dynamic_content: { - title: string - preamble?: string - component: LoyaltyComponent - link: { - text?: string - page: Edges - } - } -} - export type Blocks = | Typename | Typename< - DynamicContent, + DynamicContentBlock, LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksDynamicContent > | Typename diff --git a/utils/contactConfig.ts b/utils/contactConfig.ts new file mode 100644 index 000000000..e94eef75f --- /dev/null +++ b/utils/contactConfig.ts @@ -0,0 +1,17 @@ +import { + ContactConfig, + ContactFieldGroups, +} from "@/types/requests/contactConfig" + +export function getValueFromContactConfig( + keyStrings: string, + data: ContactConfig +): string | undefined { + const [groupName, key] = keyStrings.split(".") as [ + ContactFieldGroups, + keyof ContactConfig[ContactFieldGroups], + ] + const fieldGroup = data[groupName] + + return fieldGroup[key] +}