feat: add card grid component
This commit is contained in:
28
components/Loyalty/Blocks/CardGrid/cardGrid.module.css
Normal file
28
components/Loyalty/Blocks/CardGrid/cardGrid.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
50
components/Loyalty/Blocks/CardGrid/index.tsx
Normal file
50
components/Loyalty/Blocks/CardGrid/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user