feat: add card grid component

This commit is contained in:
Christel Westerberg
2024-04-26 08:19:19 +02:00
parent 2ddabf1e50
commit 00f30811cf
33 changed files with 575 additions and 121 deletions

View File

@@ -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);
}

View File

@@ -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<MyPagesLayoutProps>) {
return (
<div
className={`${firaMono.variable} ${firaSans.variable} ${styles.layout}`}
>
<Header lang={params.lang} />
{children}
</div>
)

View File

@@ -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) {

View File

@@ -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 (
<MaxWidth className={styles.content} tag="main">
<section className={styles.content}>
<aside>
{loyaltyPage.sidebar
? loyaltyPage.sidebar.map((block, i) => (
@@ -32,10 +33,10 @@ export default async function LoyaltyPage({
))
: null}
</aside>
<section className={styles.blocks}>
<MaxWidth className={styles.blocks} tag="main">
<Blocks blocks={loyaltyPage.blocks} />
</section>
</MaxWidth>
</MaxWidth>
</section>
)
} catch (err) {
return notFound()

View File

@@ -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;
}
}

View File

@@ -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 (
<section className={styles.container}>
<header className={styles.titleContainer}>
<Title as="h3" level="h2" weight="semiBold" uppercase>
{card_grid.title}
</Title>
{card_grid.subtitle ? (
<Title
as="h5"
level="h3"
weight="regular"
className={styles.subtitle}
>
{card_grid.subtitle}
</Title>
) : null}
</header>
<div className={styles.cardContainer}>
{card_grid.cards.map((card, i) => (
<CardWrapper key={`${card.title}+${i}`} card={card} />
))}
</div>
</section>
)
}
function CardWrapper({ card }: CardProps) {
const link = card.referenceConnection.edges.length
? {
href: card.referenceConnection.edges[0].node.url,
title: _("Read more"),
}
: undefined
return (
<div className={styles.cardWrapper}>
<Card subtitle={card.subtitle} title={card.title} link={link} />
</div>
)
}

View File

@@ -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;
}

View File

@@ -1,3 +1,13 @@
import Title from "@/components/Title"
import styles from "./howItWorks.module.css"
export default function HowItWorks() {
return <div></div>
return (
<div className={styles.container}>
<Title level="h3" uppercase>
How it works Placeholder
</Title>
</div>
)
}

View File

@@ -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 (
<div>
{data.map((level) => (
<LevelCard key={level.tier} level={level} />
<section className={styles.container}>
<div className={styles.cardContainer}>
{data.map((level) => (
<LevelCard key={level.tier} level={level} />
))}
</div>
<div className={styles.buttonContainer}>
<Button intent="primary" asChild>
<Link href={"/compare-all-levels"}>{_("Compare all levels")}</Link>
</Button>
</div>
</section>
)
}
function LevelCard({ level }: LevelCardProps) {
return (
<div className={styles.card}>
<Title level="h4">{level.tier}</Title>
<Image src={level.logo} alt={level.name} width={140} height={54} />
<p className={styles.qualifications}>
{level.requiredPoints} {_("or")} {level.requiredNights} {_("nights")}
</p>
{level.topBenefits.map((benefit) => (
<p key={benefit} className={styles.benefits}>
<Check className={styles.icon} />
{benefit}
</p>
))}
</div>
)
}
type LevelCardProps = {
level: {
tier: number
name: string
requiredPoints: number
requiredNights: string
topBenefits: string[]
}
}
function LevelCard({ level }: LevelCardProps) {
return <div></div>
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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 <p>How it works</p>
return <HowItWorks />
case LoyaltyComponentEnum.loyalty_levels:
return <p>loyalty_levels</p>
return <LoyaltyLevels />
case LoyaltyComponentEnum.overview_table:
return <p>overview_table</p>
// TODO: IMPLEMENT OVERVIEW TABLE!
return <OverviewTable />
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 (
<section>
<section className={styles.container}>
<header>
<Title level="h3">{dynamicContent.title}</Title>
{dynamicContent.preamble ? <p>{dynamicContent.preamble}</p> : null}
{dynamicContent.link ? <></> : null}
<div className={styles.titleContainer}>
{dynamicContent.title && (
<Title
as="h3"
level="h2"
className={styles.title}
weight="semiBold"
uppercase
>
{dynamicContent.title}
</Title>
)}
{link && (
<Link className={styles.link} href={link}>
{dynamicContent.link.text}
</Link>
)}
</div>
{dynamicContent.subtitle && (
<Title
as="h5"
level="h3"
weight="regular"
className={styles.subtitle}
>
{dynamicContent.subtitle}
</Title>
)}
</header>
<DynamicComponentBlock component={dynamicContent.component} />
<div>
<DynamicComponentBlock component={dynamicContent.component} />
</div>
</section>
)
}

View File

@@ -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 <p>Cards</p>
return <CardGrid card_grid={block.card_grid} />
case LoyaltyBlocksTypenameEnum.LoyaltyPageBlocksContent:
return (
<JsonToHtml

View File

@@ -1,8 +1,11 @@
import Title from "@/components/Title"
import Contact from "./Contact"
import { _ } from "@/lib/translation"
import Image from "@/components/Image"
import Button from "@/components/TempDesignSystem/Button"
import Link from "@/components/TempDesignSystem/Link"
import Image from "@/components/Image"
import Title from "@/components/Title"
import Contact from "./Contact"
import styles from "./joinLoyalty.module.css"
@@ -26,12 +29,12 @@ export default function JoinLoyaltyContact({
/>
{block.preamble && <p className={styles.preamble}>{block.preamble}</p>}
<Button intent="primary">
<span>{block.login_button_text}</span>
<span>{_("Join Scandic Friends")}</span>
</Button>
<div className={styles.linkContainer}>
<Link href="/login" className={styles.logoutLink}>
Already a friend? <br />
Click here to log in
{_("Already a friend?")} <br />
{_("Click here to log in")}
</Link>
</div>
</div>

View File

@@ -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;
}

View File

@@ -1,4 +1,5 @@
import JsonToHtml from "@/components/JsonToHtml"
import JoinLoyaltyContact from "./JoinLoyalty"
import { Sidebar, SidebarTypenameEnum } from "@/types/requests/loyaltyPage"

View File

@@ -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;
}

View File

@@ -0,0 +1,9 @@
export type CardProps = {
link?: {
href: string
title: string
}
title?: string
subtitle?: string
openInNewTab?: boolean
}

View File

@@ -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 (
<div className={styles.linkCard}>
{title ? (
<Title level="h3" weight="semiBold">
{title}
</Title>
) : null}
{subtitle ? (
<Title level="h5" weight="light">
{subtitle}
</Title>
) : null}
{link ? (
<Button asChild intent="primary">
<Link href={link.href} target={openInNewTab ? "_blank" : undefined}>
{link.title}
</Link>
</Button>
) : null}
</div>
)
}

View File

@@ -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
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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)
)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -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({

View File

@@ -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),
})

View File

@@ -1,6 +1,7 @@
import { allLevels } from "./temp"
import { protectedProcedure, publicProcedure, router } from "@/server/trpc"
import { allLevels } from "./temp"
function fakingRequest<T>(payload: T): Promise<T> {
return new Promise((resolve) => {
setTimeout(() => {

View File

@@ -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",
},
]

View File

@@ -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<PageLink>
}
}
}
export type DynamicContentProps = {
dynamicContent: DynamicContentBlock["dynamic_content"]
}
type Card = {
referenceConnection: Edges<PageLink>
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<PageLink>
heading: string
preamble: string
}
title?: string
subtitle?: string
cards: Card[]
}
}
export type CardGridProps = CardGrid
export type Content = {
content: {
embedded_itemsConnection: Edges<Embeds>
json: RTEDocument
}
}
export type LevelCardProps = {
level: {
tier: number
name: string
requiredPoints: number
requiredNights: string
topBenefits: string[]
logo: string
}
}

View File

@@ -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[]
}

View File

@@ -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> = T extends string
? []
: {
@@ -65,7 +66,7 @@ type Join<T extends string[], D extends string> = T extends []
: never
: string
type ContactConfigField = Join<PathsToStringProps<ContactConfig>, ".">
export type ContactConfigField = Join<PathsToStringProps<ContactConfig>, ".">
export type ContactFields = {
display_text?: string

View File

@@ -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<SidebarContent, SidebarTypenameEnum.LoyaltyPageSidebarContent>
| 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<PageLink>
}
}
}
export enum LoyaltyBlocksTypenameEnum {
LoyaltyPageBlocksDynamicContent = "LoyaltyPageBlocksDynamicContent",
LoyaltyPageBlocksCardGrid = "LoyaltyPageBlocksCardGrid",

View File

@@ -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<Image, EmbedEnum.SysAsset>

View File

@@ -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 [