diff --git a/components/ContentType/LoyaltyPage.tsx b/components/ContentType/LoyaltyPage.tsx index a7e3d07ad..e0cb7b42a 100644 --- a/components/ContentType/LoyaltyPage.tsx +++ b/components/ContentType/LoyaltyPage.tsx @@ -8,10 +8,11 @@ import styles from "./loyaltyPage.module.css" export default async function LoyaltyPage() { const loyaltyPage = await serverClient().contentstack.loyaltyPage.get() - return (
- {loyaltyPage.sidebar ? : null} + {loyaltyPage.sidebar.length ? ( + + ) : null} {loyaltyPage.blocks ? : null} diff --git a/components/ContentType/loyaltyPage.module.css b/components/ContentType/loyaltyPage.module.css index d6b3c2533..46bc93ea9 100644 --- a/components/ContentType/loyaltyPage.module.css +++ b/components/ContentType/loyaltyPage.module.css @@ -15,17 +15,23 @@ @media screen and (min-width: 950px) { .content { gap: 2.7rem; - grid-template-columns: 30rem 1fr; padding-bottom: 17.5rem; padding-left: 2.4rem; padding-right: 2.4rem; padding-top: 5.8rem; } + .content:has(> aside) { + grid-template-columns: 30rem 1fr; + } + .blocks { gap: 6.4rem; padding-left: 0; padding-right: 0; + } + + .hasLeftSidebar .blocks { grid-column: 2 / -1; } } diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitCard/benefitCard.module.css b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitCard/benefitCard.module.css new file mode 100644 index 000000000..cd6e21f5a --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitCard/benefitCard.module.css @@ -0,0 +1,78 @@ +.benefitCard { + background-color: var(--Main-Grey-White); + border: 1px solid var(--Base-Border-Subtle); + border-radius: var(--Corner-radius-Small); + color: var(--Main-Brand-Burgundy); + padding: 0 var(--Spacing-x2); + z-index: 2; + grid-column: 1/3; +} + +.benefitValueContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--Spacing-x-one-and-half); +} + +.benefitValue { + background-color: var(--Main-Brand-Burgundy); + border-radius: var(--Corner-radius-Medium); + color: var(--Base-Surface-Primary-Hover-alt, #fff); + font-size: var(--typography-Footnote-Regular-fontSize); + padding: var(--Spacing-x-half) var(--Spacing-x1); +} + +.benefitValueDetails { + font-size: var(--typography-Footnote-Regular-fontSize); + text-align: center; +} + +.benefitCardHeader { + display: grid; + grid-template-columns: 1fr auto; +} + +.benefitCardDescription { + line-height: 150%; +} + +.benefitInfo { + border-bottom: 1px solid var(--Base-Border-Subtle); + padding: var(--Spacing-x-one-and-half) 0; +} + +.benefitComparison { + display: grid; + grid-template-columns: 1fr 1fr; +} + +.comparisonItem { + display: flex; + justify-content: center; + align-items: center; + padding: var(--Spacing-x-one-and-half); +} + +.details[open] .chevron { + transform: rotate(180deg); +} + +.chevron { + display: flex; + align-items: center; +} + +.summary::-webkit-details-marker { + display: none; +} + +.summary { + list-style: none; +} + +@media screen and (min-width: 950px) { + .benefitComparison { + grid-template-columns: 1fr 1fr 1fr; + } +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitCard/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitCard/index.tsx new file mode 100644 index 000000000..0097f2562 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitCard/index.tsx @@ -0,0 +1,63 @@ +import { ChevronDown, Minus } from "react-feather" + +import CheckCircle from "@/components/Icons/CheckCircle" +import Title from "@/components/Title" + +import styles from "./benefitCard.module.css" + +import { + BenefitCardProps, + BenefitValueProps, +} from "@/types/components/loyalty/blocks" + +function BenefitValue({ benefit }: BenefitValueProps) { + if (!benefit.unlocked) { + return + } + if (!benefit.value) { + return + } + return ( +
+ {benefit.value} + {benefit.valueDetails && ( + + {benefit.valueDetails} + + )} +
+ ) +} + +export default function BenefitCard({ + comparedValues, + title, + description, +}: BenefitCardProps) { + return ( +
+
+
+ +
+ + {title} + + + + +
+
+

{description}

+
+
+
+ {comparedValues.map((benefit, idx) => ( +
+ +
+ ))} +
+
+ ) +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitList/benefitList.module.css b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitList/benefitList.module.css new file mode 100644 index 000000000..e8f910fe8 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitList/benefitList.module.css @@ -0,0 +1,49 @@ +.benefitCardWrapper { + position: relative; + display: grid; + grid-template-columns: 1fr 1fr; + grid-column: 1/3; + padding: var(--Spacing-x2); + padding-top: 0; +} + +.firstColumn { + background-color: var(--Main-Brand-PalePeach); + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 50%; + z-index: 1; +} + +.secondColumn { + background-color: var(--Base-Background-Normal); + position: absolute; + position: absolute; + top: 0; + bottom: 0; + margin-bottom: calc(var(--Spacing-x2) * -1); + left: 50%; + right: 0; + z-index: 1; + border-top-left-radius: var(--Corner-radius-Medium); +} + +@media screen and (min-width: 950px) { + .benefitCardWrapper { + grid-column: 1/4; + } + + .firstColumn { + width: calc((100%) / 3); + right: calc(100% / 3 * 2); + margin-left: 0; + } + + .secondColumn { + width: calc(100% / 3); + left: calc(100% / 3); + right: calc(100% / 3); + } +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitList/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitList/index.tsx new file mode 100644 index 000000000..d5f6a6a22 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/BenefitList/index.tsx @@ -0,0 +1,43 @@ +import BenefitCard from "../BenefitCard" + +import styles from "./benefitList.module.css" + +import { + BenefitListProps, + ComparisonLevel, +} from "@/types/components/loyalty/blocks" + +export default function BenefitList({ levels }: BenefitListProps) { + const highestTier = levels.reduce( + (acc: ComparisonLevel | null, level: ComparisonLevel) => { + if (!acc) { + return level + } + return level.tier > acc.tier ? level : acc + }, + null + ) + + return highestTier?.benefits + .filter((benefit) => benefit.unlocked) + .map((benefit, idx) => { + const levelBenefits = levels.map((level) => level.benefits[idx]) + return ( +
+
+
+ { + return { + value: benefit.value, + unlocked: benefit.unlocked, + valueDetails: benefit.valueDetails, + } + })} + /> +
+ ) + }) +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/LevelSummary/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/LevelSummary/index.tsx new file mode 100644 index 000000000..b270da342 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/LevelSummary/index.tsx @@ -0,0 +1,15 @@ +import Image from "@/components/Image" + +import styles from "./levelSummary.module.css" + +import { LevelSummaryProps } from "@/types/components/loyalty/blocks" + +export default function LevelSummary({ level }: LevelSummaryProps) { + return ( +
+ {level.name} + {level.requirement} +

{level.description}

+
+ ) +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/LevelSummary/levelSummary.module.css b/components/Loyalty/Blocks/DynamicContent/OverviewTable/LevelSummary/levelSummary.module.css new file mode 100644 index 000000000..ea7a4938f --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/LevelSummary/levelSummary.module.css @@ -0,0 +1,20 @@ +.levelSummary { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--Spacing-x2); +} + +.levelRequirements { + background-color: var(--Main-Brand-Burgundy); + border-radius: var(--Corner-radius-Medium); + color: #f7e1d5; + padding: var(--Spacing-x-half) var(--Spacing-x1); +} + +.levelSummaryText { + color: var(--Main-Brand-Burgundy); + font-size: var(--typography-Footnote-Regular-fontSize); + line-height: var(--typography-Body-Regular-lineHeight); + margin: 0; +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/Title/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/Title/index.tsx new file mode 100644 index 000000000..c22192ef2 --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/Title/index.tsx @@ -0,0 +1,20 @@ +import { Fragment } from "react" + +import Title from "@/components/Title" + +import styles from "./overviewTableTitle.module.css" + +import { OverviewTableTitleProps } from "@/types/components/loyalty/blocks" + +export default function OverviewTableTitle({ texts }: OverviewTableTitleProps) { + return ( + + {texts.map(({ text, highlight }, idx) => ( + <Fragment key={idx}> + <span className={highlight ? styles.highlight : ""}>{text}</span> + {idx < texts.length - 1 && " "} + </Fragment> + ))} + + ) +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/Title/overviewTableTitle.module.css b/components/Loyalty/Blocks/DynamicContent/OverviewTable/Title/overviewTableTitle.module.css new file mode 100644 index 000000000..50ab7016d --- /dev/null +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/Title/overviewTableTitle.module.css @@ -0,0 +1,3 @@ +.highlight { + color: var(--Base-Text-Primary-Accent); +} diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx index dfc89237f..af359a9ea 100644 --- a/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx @@ -1,27 +1,21 @@ "use client" -import { Fragment, useState } from "react" -import { Minus } from "react-feather" +import { Dispatch, SetStateAction, useState } from "react" +import { type Key } from "react-aria-components" import { Lang } from "@/constants/languages" import { _ } from "@/lib/translation" -import CheckCircle from "@/components/Icons/CheckCircle" -import ChevronDown from "@/components/Icons/ChevronDown" -import Image from "@/components/Image" -import Title from "@/components/Title" +import Select from "@/components/TempDesignSystem/Form/Select" import levelsData from "./data/EN.json" +import BenefitList from "./BenefitList" +import LevelSummary from "./LevelSummary" +import OverviewTableTitle from "./Title" import styles from "./overviewTable.module.css" -import { - BenefitCardProps, - BenefitValueProps, - ComparisonLevel, - LevelSummaryProps, - OverviewTableTitleProps, -} from "@/types/components/loyalty/blocks" +import { ComparisonLevel } from "@/types/components/loyalty/blocks" // These should ultimately be fetched from Contentstack const titleTranslations = { @@ -52,78 +46,26 @@ const titleTranslations = { ], } -function OverviewTableTitle({ texts }: OverviewTableTitleProps) { - return ( - - {texts.map(({ text, highlight }, idx) => ( - <Fragment key={idx}> - <span className={highlight ? styles.highlight : ""}>{text}</span> - {idx < texts.length - 1 && " "} - </Fragment> - ))} - - ) -} - function getLevelByTier(tier: number) { return levelsData.levels.find( (level) => level.tier === tier ) as ComparisonLevel } -function createComparison(levelA: ComparisonLevel, levelB: ComparisonLevel) { - const unlockedBenefitsA = levelA.benefits.filter( - (benefit) => benefit.unlocked - ) - const unlockedBenefitsB = levelB.benefits.filter( - (benefit) => benefit.unlocked - ) - - const higherLevelBenefits = - unlockedBenefitsA.length > unlockedBenefitsB.length - ? unlockedBenefitsA - : unlockedBenefitsB - - return higherLevelBenefits.map((benefit, idx) => { - const aBenefit = levelA.benefits[idx] - const bBenefit = levelB.benefits[idx] - return ( - - ) - }) -} - export default function OverviewTable() { const [selectedLevelA, setSelectedLevelA] = useState(getLevelByTier(1)) const [selectedLevelB, setSelectedLevelB] = useState(getLevelByTier(2)) + const [selectedLevelC, setSelectedLevelC] = useState(getLevelByTier(3)) - // TODO Come up with a nice wat to make these two a single reusable function - function handleSelectChangeA(event: React.ChangeEvent) { - const tier = parseInt(event.target.value) - const level = getLevelByTier(tier) - setSelectedLevelA(level) - } - - function handleSelectChangeB(event: React.ChangeEvent) { - const tier = parseInt(event.target.value) - const level = getLevelByTier(tier) - setSelectedLevelB(level) + function handleSelectChange( + callback: Dispatch> + ) { + return (key: Key) => { + if (typeof key === "number") { + const level = getLevelByTier(key) + callback(level) + } + } } const levelOptions = levelsData.levels.map((level) => ({ @@ -143,15 +85,15 @@ export default function OverviewTable() { eleifend mi in nulla posuere.

-
-
-
+
- {createComparison(selectedLevelA, selectedLevelB)} +
-
- ) -} - -type SelectProps = { - options: { - label: string - value: number - }[] - defaultOption: number - onChange: (event: React.ChangeEvent) => void -} -// TODO: replace with Select component from TempDesignSystem -function Select({ options, defaultOption, onChange }: SelectProps) { - return ( -
- - - - - -
- ) -} - -function LevelSummary({ level }: LevelSummaryProps) { - return ( -
- {level.name} - {level.requirement} -

{level.description}

-
- ) -} - -function BenefitCard({ comparedValues, title, description }: BenefitCardProps) { - return ( -
-
-
- -
- - {title} - - - - -
-
-

{description}

-
-
-
-
- -
-
- +
+
+
+ + level.tier === selectedLevelB.tier + ) as ComparisonLevel + } + /> +
+
+