Merge branch 'develop' into feat/build-initial-hotel-page-component

This commit is contained in:
Chuma McPhoy
2024-06-28 13:11:50 +02:00
35 changed files with 433 additions and 396 deletions

View File

@@ -85,7 +85,7 @@ function LevelCard({ formatMessage, lang, level }: LevelCardProps) {
} }
return ( return (
<article className={styles.card}> <article className={styles.card}>
<Title className={styles.tierHeading} level="h4"> <Title className={styles.levelHeading} level="h4">
{level.level} {level.level}
</Title> </Title>
{Level ? <Level color="primaryLightOnSurfaceAccent" /> : null} {Level ? <Level color="primaryLightOnSurfaceAccent" /> : null}

View File

@@ -30,7 +30,7 @@
padding: var(--Spacing-x5) var(--Spacing-x1); padding: var(--Spacing-x5) var(--Spacing-x1);
} }
.tierHeading { .levelHeading {
color: var(--Scandic-Peach-70); color: var(--Scandic-Peach-70);
} }

View File

@@ -1,9 +1,5 @@
.benefitCard { .benefitCard {
background-color: var(--Scandic-Opacity-White-100); padding-bottom: var(--Spacing-x-one-and-half);
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; z-index: 2;
grid-column: 1/3; grid-column: 1/3;
} }
@@ -16,11 +12,11 @@
.benefitCardDescription { .benefitCardDescription {
font-size: var(--typography-Caption-Regular-fontSize); font-size: var(--typography-Caption-Regular-fontSize);
line-height: 150%; line-height: 150%;
padding-right: var(--Spacing-x4);
} }
.benefitInfo { .benefitInfo {
border-bottom: 1px solid var(--Base-Border-Subtle); padding-bottom: var(--Spacing-x-one-and-half);
padding: var(--Spacing-x-one-and-half) 0;
} }
.benefitComparison { .benefitComparison {
@@ -32,7 +28,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: var(--Spacing-x-one-and-half); padding-top: var(--Spacing-x-one-and-half);
} }
.details[open] .chevron { .details[open] .chevron {
@@ -42,6 +38,7 @@
.chevron { .chevron {
display: flex; display: flex;
align-items: center; align-items: center;
color: var(--UI-Grey-80);
} }
.summary::-webkit-details-marker { .summary::-webkit-details-marker {

View File

@@ -1,10 +1,15 @@
.benefitCardWrapper { .benefitCardWrapper {
border-bottom: 1px solid var(--Base-Border-Subtle);
position: relative; position: relative;
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-column: 1/3; grid-column: 1/3;
padding: var(--Spacing-x2);
padding-top: 0; padding-top: 0;
margin: var(--Spacing-x1) var(--Spacing-x2);
}
.benefitCardWrapper:last-child {
border: none;
} }
@media screen and (min-width: 950px) { @media screen and (min-width: 950px) {

View File

@@ -1,24 +1,14 @@
import { getHighestLevel } from "@/utils/loyaltyTable"
import BenefitCard from "../BenefitCard" import BenefitCard from "../BenefitCard"
import styles from "./benefitList.module.css" import styles from "./benefitList.module.css"
import { import { BenefitListProps } from "@/types/components/loyalty/blocks"
BenefitListProps,
ComparisonLevel,
} from "@/types/components/loyalty/blocks"
export default function BenefitList({ levels }: BenefitListProps) { export default function BenefitList({ levels }: BenefitListProps) {
const highestTier = levels.reduce( const highestLevel = getHighestLevel(levels)
(acc: ComparisonLevel | null, level: ComparisonLevel) => { return highestLevel?.benefits
if (!acc) {
return level
}
return level.tier > acc.tier ? level : acc
},
null
)
return highestTier?.benefits
.filter((benefit) => benefit.unlocked) .filter((benefit) => benefit.unlocked)
.map((benefit, idx) => { .map((benefit, idx) => {
const levelBenefits = levels.map((level) => level.benefits[idx]) const levelBenefits = levels.map((level) => level.benefits[idx])

View File

@@ -0,0 +1,28 @@
.iconRow {
border-bottom: none;
position: sticky;
top: 0;
z-index: 1;
}
.verticalTableHeader {
min-width: 242px;
}
.iconTh {
padding: var(--Spacing-x5) var(--Spacing-x2) var(--Spacing-x2);
font-weight: var(--typography-Caption-Regular-fontWeight);
vertical-align: bottom;
}
.summaryTh {
font-size: var(--typography-Caption-Regular-fontSize);
font-weight: var(--typography-Caption-Regular-fontWeight);
padding: 0 var(--Spacing-x2) var(--Spacing-x2);
vertical-align: top;
}
.select {
font-weight: var(--typography-Caption-Regular-fontWeight);
padding: 0 var(--Spacing-x2) var(--Spacing-x2);
}

View File

@@ -0,0 +1,63 @@
import Image from "@/components/Image"
import LevelSummary from "../../LevelSummary"
import YourLevel from "../../YourLevelScript"
import styles from "./desktopHeader.module.css"
import {
DesktopSelectColumns,
LargeTableProps,
} from "@/types/components/loyalty/blocks"
export default function DesktopHeader({
levels,
activeLevel,
Select,
}: LargeTableProps) {
return (
<thead>
<tr className={styles.iconRow}>
<th className={styles.verticalTableHeader} />
{levels.map((level, idx) => {
return (
<th key={"image" + level.level + idx} className={styles.iconTh}>
{activeLevel === level.level ? <YourLevel /> : null}
<Image
height={50}
width={100}
alt={level.name}
src={level.icon}
/>
</th>
)
})}
</tr>
<tr>
<th />
{levels.map((level, idx) => {
return (
<th
key={"summary" + level.level + idx}
className={styles.summaryTh}
>
<LevelSummary level={level} />
</th>
)
})}
</tr>
{Select && (
<tr>
<th />
{["A", "B", "C"].map((column, idx) => {
return (
<th key={column + idx} className={styles.select}>
<Select column={column as DesktopSelectColumns["column"]} />
</th>
)
})}
</tr>
)}
</thead>
)
}

View File

@@ -1,10 +1,10 @@
import { ChevronDown } from "react-feather" import { ChevronDown } from "react-feather"
import Image from "@/components/Image"
import Title from "@/components/TempDesignSystem/Text/Title" import Title from "@/components/TempDesignSystem/Text/Title"
import { getHighestLevel } from "@/utils/loyaltyTable"
import BenefitValue from "../BenefitValue" import BenefitValue from "../BenefitValue"
import LevelSummary from "../LevelSummary" import DesktopHeader from "./DesktopHeader"
import styles from "./largeTable.module.css" import styles from "./largeTable.module.css"
@@ -13,57 +13,41 @@ import {
LargeTableProps, LargeTableProps,
} from "@/types/components/loyalty/blocks" } from "@/types/components/loyalty/blocks"
export default function LargeTable({ levels }: LargeTableProps) { export default function LargeTable({
levels,
activeLevel,
Select,
}: LargeTableProps) {
const highestLevel = getHighestLevel(levels)
return ( return (
<table className={styles.table}> <table className={styles.table}>
<thead> <DesktopHeader
<tr className={styles.iconRow}> levels={levels}
<th className={styles.verticalTableHeader} /> activeLevel={activeLevel}
Select={Select}
{levels.map((level) => { />
<tbody className={styles.tbody}>
{highestLevel?.benefits
.filter((benefit) => benefit.unlocked)
.map((benefit, index) => {
return ( return (
<th key={level.tier} className={styles.iconTh}> <tr key={benefit.name} className={styles.tr}>
<Image <th scope={"row"} className={styles.benefitTh}>
height={50} <BenefitTableHeader
width={100} name={benefit.name}
alt={level.name} description={benefit.description}
src={level.icon} />
/> </th>
</th> {levels.map((level, idx) => {
return (
<td key={"icon" + level.level + idx} className={styles.td}>
<BenefitValue benefit={level.benefits[index]} />
</td>
)
})}
</tr>
) )
})} })}
</tr>
<tr>
<th />
{levels.map((level) => {
return (
<th key={level.tier} className={styles.summaryTh}>
<LevelSummary level={level} />
</th>
)
})}
</tr>
</thead>
<tbody>
{levels[0].benefits.map((benefit, index) => {
return (
<tr key={benefit.name} className={styles.tr}>
<th scope={"row"} className={styles.benefitTh}>
<BenefitTableHeader
name={benefit.name}
description={benefit.description}
/>
</th>
{levels.map((level) => {
return (
<td key={level.tier} className={styles.td}>
<BenefitValue benefit={level.benefits[index]} />
</td>
)
})}
</tr>
)
})}
</tbody> </tbody>
</table> </table>
) )

View File

@@ -3,44 +3,26 @@
border-collapse: collapse; border-collapse: collapse;
background-color: var(--Scandic-Opacity-White-100); background-color: var(--Scandic-Opacity-White-100);
border-radius: var(--Corner-radius-Medium); border-radius: var(--Corner-radius-Medium);
color: var(--UI-Grey-100);
} }
.iconRow { .tr {
border-bottom: none; border-bottom: 1px solid var(--Base-Border-Subtle);
position: sticky;
top: 0;
z-index: 1;
} }
.verticalTableHeader { .tr:last-child {
width: 242px; border: none;
}
.iconTh {
padding: var(--Spacing-x3) var(--Spacing-x2) var(--Spacing-x2);
background-color: #fff;
}
.summaryTh {
font-size: var(--typography-Caption-Regular-fontSize);
font-weight: 400;
padding: 0 var(--Spacing-x3) var(--Spacing-x2);
vertical-align: top;
} }
.td { .td {
border-top: 1px solid var(--Scandic-Beige-20);
border-bottom: 1px solid var(--Scandic-Beige-20);
font-size: var(--typography-Footnote-Regular-fontSize); font-size: var(--typography-Footnote-Regular-fontSize);
text-align: center; text-align: center;
} }
.benefitTh { .benefitTh {
border-top: 1px solid var(--Scandic-Beige-20);
border-bottom: 1px solid var(--Scandic-Beige-20);
padding: var(--Spacing-x3) var(--Spacing-x2); padding: var(--Spacing-x3) var(--Spacing-x2);
font-size: var(--typography-Caption-Regular-fontSize); font-size: var(--typography-Caption-Regular-fontSize);
font-weight: 400; font-weight: var(--typography-Caption-Regular-fontWeight);
} }
.details[open] .chevron { .details[open] .chevron {
@@ -58,12 +40,13 @@
margin: 0; margin: 0;
padding-top: var(--Spacing-x1); padding-top: var(--Spacing-x1);
text-align: start; text-align: start;
padding-right: calc(24px + var(--Spacing-x1)); padding-right: calc(var(--Spacing-x3) + var(--Spacing-x1));
} }
.chevron { .chevron {
display: flex; display: flex;
align-self: start; align-self: start;
color: var(--UI-Grey-80);
} }
.summary::-webkit-details-marker { .summary::-webkit-details-marker {

View File

@@ -1,14 +1,17 @@
import Image from "@/components/Image"
import styles from "./levelSummary.module.css" import styles from "./levelSummary.module.css"
import { LevelSummaryProps } from "@/types/components/loyalty/blocks" import { LevelSummaryProps } from "@/types/components/loyalty/blocks"
export default function LevelSummary({ level }: LevelSummaryProps) { export default function LevelSummary({
level,
showDescription = true,
}: LevelSummaryProps) {
return ( return (
<div className={styles.levelSummary}> <div className={styles.levelSummary}>
<span className={styles.levelRequirements}>{level.requirement}</span> <span className={styles.levelRequirements}>{level.requirement}</span>
<p className={styles.levelSummaryText}>{level.description}</p> {showDescription && (
<p className={styles.levelSummaryText}>{level.description}</p>
)}
</div> </div>
) )
} }

View File

@@ -3,22 +3,27 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: var(--Spacing-x3); gap: var(--Spacing-x3);
padding-bottom: var(--Spacing-x1);
} }
.levelRequirements { .levelRequirements {
border-radius: var(--Corner-radius-Medium); border-radius: var(--Corner-radius-Medium);
background-color: var(--Scandic-Brand-Pale-Peach); background-color: var(--Scandic-Brand-Pale-Peach);
color: var(--Scandic-Peach-80); color: var(--Scandic-Peach-80);
padding: var(--Spacing-x-half) var(--Spacing-x1); padding: var(--Spacing-x-half) var(--Spacing-x5);
} }
.levelSummaryText { .levelSummaryText {
color: var(--Main-Brand-Burgundy);
font-size: var(--typography-Caption-Regular-fontSize); font-size: var(--typography-Caption-Regular-fontSize);
line-height: var(--typography-Body-Regular-lineHeight); line-height: var(--typography-Body-Regular-lineHeight);
margin: 0; margin: 0;
} }
@media screen and (min-width: 950px) {
.levelRequirements {
padding: var(--Spacing-x-half) var(--Spacing-x1);
}
}
@media screen and (min-width: 1367px) { @media screen and (min-width: 1367px) {
.levelRequirements { .levelRequirements {
font-size: var(--typography-Footnote-Regular-fontSize); font-size: var(--typography-Footnote-Regular-fontSize);

View File

@@ -1,20 +0,0 @@
import { Fragment } from "react"
import Title from "@/components/TempDesignSystem/Text/Title"
import styles from "./overviewTableTitle.module.css"
import { OverviewTableTitleProps } from "@/types/components/loyalty/blocks"
export default function OverviewTableTitle({ texts }: OverviewTableTitleProps) {
return (
<Title>
{texts.map(({ text, highlight }, idx) => (
<Fragment key={idx}>
<span className={highlight ? styles.highlight : ""}>{text}</span>
{idx < texts.length - 1 && " "}
</Fragment>
))}
</Title>
)
}

View File

@@ -1,3 +0,0 @@
.highlight {
color: var(--Base-Text-Primary-Accent);
}

View File

@@ -0,0 +1,19 @@
import { useIntl } from "react-intl"
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
import styles from "./yourLevel.module.css"
export default function YourLevel() {
const intl = useIntl()
return (
<BiroScript
className={styles.script}
color="peach80"
type="two"
textAlign={"center"}
>
{intl.formatMessage({ id: "Your level" })}
</BiroScript>
)
}

View File

@@ -0,0 +1,10 @@
.script {
transform: rotate(-4deg);
padding-bottom: var(--Spacing-x-half);
}
@media screen and (min-width: 950px) {
.script {
padding-bottom: var(--Spacing-x1);
}
}

View File

@@ -1,7 +1,7 @@
{ {
"levels": [ "levels": [
{ {
"tier": 1, "level": 1,
"name": "New Friend", "name": "New Friend",
"requirement": "0p", "requirement": "0p",
"description": "Det her er starten på noget smukt. Som New Friend kan du godt glæde dig til at opdage alt det skønne ved Scandic.", "description": "Det her er starten på noget smukt. Som New Friend kan du godt glæde dig til at opdage alt det skønne ved Scandic.",
@@ -76,7 +76,7 @@
] ]
}, },
{ {
"tier": 2, "level": 2,
"name": "Good Friend", "name": "Good Friend",
"requirement": "5 000p", "requirement": "5 000p",
"description": "Du har været her meget på det sidste! Lad os tage venskabet videre ét behageligt ophold ad gangen.", "description": "Du har været her meget på det sidste! Lad os tage venskabet videre ét behageligt ophold ad gangen.",
@@ -151,7 +151,7 @@
] ]
}, },
{ {
"tier": 3, "level": 3,
"name": "Close Friend", "name": "Close Friend",
"requirement": "10 000p", "requirement": "10 000p",
"description": "Vi har lært hinanden bedre at kende, og det gør din oplevelse hos Scandic meget mere personlig.", "description": "Vi har lært hinanden bedre at kende, og det gør din oplevelse hos Scandic meget mere personlig.",
@@ -227,7 +227,7 @@
] ]
}, },
{ {
"tier": 4, "level": 4,
"name": "Dear Friend", "name": "Dear Friend",
"requirement": "25 000p", "requirement": "25 000p",
"description": "Her er vist grobund for et livslangt venskab, og det betyder, at du får adgang til meget mere med Scandic.", "description": "Her er vist grobund for et livslangt venskab, og det betyder, at du får adgang til meget mere med Scandic.",
@@ -304,7 +304,7 @@
] ]
}, },
{ {
"tier": 5, "level": 5,
"name": "Loyal Friend", "name": "Loyal Friend",
"requirement": "50 000p", "requirement": "50 000p",
"description": "Du har været hos os på mange ophold, så derfor vil vi gerne give dig nogle af vores bedste bonusser.", "description": "Du har været hos os på mange ophold, så derfor vil vi gerne give dig nogle af vores bedste bonusser.",
@@ -381,7 +381,7 @@
] ]
}, },
{ {
"tier": 6, "level": 6,
"name": "True Friend", "name": "True Friend",
"requirement": "100 000p", "requirement": "100 000p",
"description": "Vi er glade for, at du besøger os, uanset om det er høj- eller lavsæson. Derfor får du endnu flere skræddersyede fordele.", "description": "Vi er glade for, at du besøger os, uanset om det er høj- eller lavsæson. Derfor får du endnu flere skræddersyede fordele.",
@@ -458,7 +458,7 @@
] ]
}, },
{ {
"tier": 7, "level": 7,
"name": "Best Friend", "name": "Best Friend",
"requirement": "400 000p", "requirement": "400 000p",
"description": "Det bliver simpelthen ikke bedre end det her, når det kommer til de helt eksklusive oplevelser!", "description": "Det bliver simpelthen ikke bedre end det her, når det kommer til de helt eksklusive oplevelser!",

View File

@@ -1,7 +1,7 @@
{ {
"levels": [ "levels": [
{ {
"tier": 1, "level": 1,
"name": "New Friend", "name": "New Friend",
"requirement": "0p or 0 nights", "requirement": "0p or 0 nights",
"description": "Dies ist der Beginn von etwas Wunderbarem: Als New Friend können Sie sich auf eine Reise voller herrlicher Scandic-Entdeckungen freuen.", "description": "Dies ist der Beginn von etwas Wunderbarem: Als New Friend können Sie sich auf eine Reise voller herrlicher Scandic-Entdeckungen freuen.",
@@ -55,7 +55,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": false "unlocked": false
}, },
{ {
@@ -76,7 +76,7 @@
] ]
}, },
{ {
"tier": 2, "level": 2,
"name": "Good Friend", "name": "Good Friend",
"requirement": "5 000p", "requirement": "5 000p",
"description": "Sie waren in letzter Zeit viel bei uns! Und ehrlich gesagt haben wir das Gefühl, dass wir auf einer Wellenlänge sind die vielen angenehmen Aufenthalte und lustigen Überraschungen sprechen für sich.", "description": "Sie waren in letzter Zeit viel bei uns! Und ehrlich gesagt haben wir das Gefühl, dass wir auf einer Wellenlänge sind die vielen angenehmen Aufenthalte und lustigen Überraschungen sprechen für sich.",
@@ -130,7 +130,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": false "unlocked": false
}, },
{ {
@@ -151,7 +151,7 @@
] ]
}, },
{ {
"tier": 3, "level": 3,
"name": "Close Friend", "name": "Close Friend",
"requirement": "10 000p", "requirement": "10 000p",
"description": "Jetzt wird es ernst: Wir lernen uns wirklich besser kennen, was bedeutet, dass Ihre Zeit mit Scandic noch viel persönlicher wird.", "description": "Jetzt wird es ernst: Wir lernen uns wirklich besser kennen, was bedeutet, dass Ihre Zeit mit Scandic noch viel persönlicher wird.",
@@ -206,7 +206,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": false "unlocked": false
}, },
{ {
@@ -227,7 +227,7 @@
] ]
}, },
{ {
"tier": 4, "level": 4,
"name": "Dear Friend", "name": "Dear Friend",
"requirement": "25 000p", "requirement": "25 000p",
"description": "Ein Hoch auf uns! Unser Verhältnis scheint sich in Richtung Freunde fürs Leben zu entwickeln was auch bedeutet, dass Sie Zugang zu einer ganzen Menge mehr Scandic bekommen.", "description": "Ein Hoch auf uns! Unser Verhältnis scheint sich in Richtung Freunde fürs Leben zu entwickeln was auch bedeutet, dass Sie Zugang zu einer ganzen Menge mehr Scandic bekommen.",
@@ -283,7 +283,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": false "unlocked": false
}, },
{ {
@@ -304,7 +304,7 @@
] ]
}, },
{ {
"tier": 5, "level": 5,
"name": "Loyal Friend", "name": "Loyal Friend",
"requirement": "50 000p", "requirement": "50 000p",
"description": "Sie haben uns während zahlreicher Aufenthalte, Happy Hours und Workouts im Fitnessstudio die Treue gehalten deshalb wollen wir uns mit einigen unserer großartigsten Belohnungen bei Ihnen revanchieren.", "description": "Sie haben uns während zahlreicher Aufenthalte, Happy Hours und Workouts im Fitnessstudio die Treue gehalten deshalb wollen wir uns mit einigen unserer großartigsten Belohnungen bei Ihnen revanchieren.",
@@ -360,7 +360,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": false "unlocked": false
}, },
{ {
@@ -381,7 +381,7 @@
] ]
}, },
{ {
"tier": 6, "level": 6,
"name": "True Friend", "name": "True Friend",
"requirement": "100 000p", "requirement": "100 000p",
"description": "s spielt keine Rolle, ob Haupt- oder Nebensaison: Sie sind immer für uns da. Genießen Sie noch mehr individuelle Vorteile genau nach Ihrem Geschmack.", "description": "s spielt keine Rolle, ob Haupt- oder Nebensaison: Sie sind immer für uns da. Genießen Sie noch mehr individuelle Vorteile genau nach Ihrem Geschmack.",
@@ -437,7 +437,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": true "unlocked": true
}, },
{ {
@@ -458,7 +458,7 @@
] ]
}, },
{ {
"tier": 7, "level": 7,
"name": "Best Friend", "name": "Best Friend",
"requirement": "400 000p", "requirement": "400 000p",
"description": "Für eine Freundschaft wie diese gibt es im Grunde keine passenden Worte, aber wir versuchen es trotzdem: Denn es könnte gar nichts Besseres geben, wenn es um sehr, sehr exklusive Erlebnisse geht!", "description": "Für eine Freundschaft wie diese gibt es im Grunde keine passenden Worte, aber wir versuchen es trotzdem: Denn es könnte gar nichts Besseres geben, wenn es um sehr, sehr exklusive Erlebnisse geht!",
@@ -514,7 +514,7 @@
}, },
{ {
"name": "48-Stunden-Zimmergarantie", "name": "48-Stunden-Zimmergarantie",
"description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garantiert ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?", "description": "Pssst! Diesen ganz besonderen Leckerbissen bekommen bei uns nur die Wenigsten! Also aufgepasst: Selbst wenn wir völlig ausgebucht sind, erhalten Sie bei uns garanlevelt ein Zimmer, wenn Sie 48Stunden im Voraus buchen. Ist das nicht einfach unglaublich?",
"unlocked": true "unlocked": true
}, },
{ {

View File

@@ -1,7 +1,7 @@
{ {
"levels": [ "levels": [
{ {
"tier": 1, "level": 1,
"name": "New Friend", "name": "New Friend",
"requirement": "0p", "requirement": "0p",
"description": "This is the start of something beautiful: as a New Friend, get ready for a journey of delightful Scandic discoveries.", "description": "This is the start of something beautiful: as a New Friend, get ready for a journey of delightful Scandic discoveries.",
@@ -76,7 +76,7 @@
] ]
}, },
{ {
"tier": 2, "level": 2,
"name": "Good Friend", "name": "Good Friend",
"requirement": "5 000p", "requirement": "5 000p",
"description": "You've been around a lot lately! And honestly, we feel like we're vibing - one enjoyable stay at a time.", "description": "You've been around a lot lately! And honestly, we feel like we're vibing - one enjoyable stay at a time.",
@@ -151,7 +151,7 @@
] ]
}, },
{ {
"tier": 3, "level": 3,
"name": "Close Friend", "name": "Close Friend",
"requirement": "10 000p", "requirement": "10 000p",
"description": "It's serious now: we're really getting to know each other which makes your Scandic experiences a lot more personal.", "description": "It's serious now: we're really getting to know each other which makes your Scandic experiences a lot more personal.",
@@ -227,7 +227,7 @@
] ]
}, },
{ {
"tier": 4, "level": 4,
"name": "Dear Friend", "name": "Dear Friend",
"requirement": "25 000p", "requirement": "25 000p",
"description": "Cheers to us! This seems to be going in the direction of friends for life - and that comes with access to a-whole-lotta-more of Scandic.", "description": "Cheers to us! This seems to be going in the direction of friends for life - and that comes with access to a-whole-lotta-more of Scandic.",
@@ -304,7 +304,7 @@
] ]
}, },
{ {
"tier": 5, "level": 5,
"name": "Loyal Friend", "name": "Loyal Friend",
"requirement": "50 000p", "requirement": "50 000p",
"description": "You've stuck with us through stays, after works and gym sessions - so we'll stick with you through some of our very best rewards.", "description": "You've stuck with us through stays, after works and gym sessions - so we'll stick with you through some of our very best rewards.",
@@ -381,7 +381,7 @@
] ]
}, },
{ {
"tier": 6, "level": 6,
"name": "True Friend", "name": "True Friend",
"requirement": "100 000p", "requirement": "100 000p",
"description": "It doesn't matter if it's peak or off season, you're always there for us. Enjoy even more tailored perks - just the way you like them.", "description": "It doesn't matter if it's peak or off season, you're always there for us. Enjoy even more tailored perks - just the way you like them.",
@@ -458,7 +458,7 @@
] ]
}, },
{ {
"tier": 7, "level": 7,
"name": "Best Friend", "name": "Best Friend",
"requirement": "400 000p", "requirement": "400 000p",
"description": "There are no words for a bond like this, but here's a few anyway: It simply doesn't get any better when it comes to very, very exclusive experiences!", "description": "There are no words for a bond like this, but here's a few anyway: It simply doesn't get any better when it comes to very, very exclusive experiences!",

View File

@@ -1,7 +1,7 @@
{ {
"levels": [ "levels": [
{ {
"tier": 1, "level": 1,
"name": "New Friend", "name": "New Friend",
"requirement": "0p", "requirement": "0p",
"description": "Olemme uuden ja upean kynnyksellä: New Friend-ystävänä pääset nauttimaan kaikesta ihanasta, mitä Scandic tarjoaa.", "description": "Olemme uuden ja upean kynnyksellä: New Friend-ystävänä pääset nauttimaan kaikesta ihanasta, mitä Scandic tarjoaa.",
@@ -76,7 +76,7 @@
] ]
}, },
{ {
"tier": 2, "level": 2,
"name": "Good Friend", "name": "Good Friend",
"requirement": "5 000p", "requirement": "5 000p",
"description": "Kiva, että olet vieraillut meillä, ja tuntuu, että ystävyytemme on hyvässä nosteessa. Tästä on hyvä jatkaa, yksi yöpyminen ja iloinen yllätys kerrallaan!", "description": "Kiva, että olet vieraillut meillä, ja tuntuu, että ystävyytemme on hyvässä nosteessa. Tästä on hyvä jatkaa, yksi yöpyminen ja iloinen yllätys kerrallaan!",
@@ -151,7 +151,7 @@
] ]
}, },
{ {
"tier": 3, "level": 3,
"name": "Close Friend", "name": "Close Friend",
"requirement": "10 000p", "requirement": "10 000p",
"description": "Onpa kiva, että olet vieraillut meillä näin usein! Nyt etusi vain paranevat, sillä olemmehan jo enemmän kuin hyvän päivän tuttuja.", "description": "Onpa kiva, että olet vieraillut meillä näin usein! Nyt etusi vain paranevat, sillä olemmehan jo enemmän kuin hyvän päivän tuttuja.",
@@ -227,7 +227,7 @@
] ]
}, },
{ {
"tier": 4, "level": 4,
"name": "Dear Friend", "name": "Dear Friend",
"requirement": "25 000p", "requirement": "25 000p",
"description": "Kippis syventyvälle ystävyydellemme. Nyt pääset nauttimaan liudasta uusia etuja.", "description": "Kippis syventyvälle ystävyydellemme. Nyt pääset nauttimaan liudasta uusia etuja.",
@@ -304,7 +304,7 @@
] ]
}, },
{ {
"tier": 5, "level": 5,
"name": "Loyal Friend", "name": "Loyal Friend",
"requirement": "50 000p", "requirement": "50 000p",
"description": "Kiva, että olemme saaneet jakaa paljon yhteisiä hetkiä. Olet tosiaan nimesi arvoinen Loyal Friend! Haluamme panostaa ystävyyteemme myös jatkossa ja annammekin sinulle kasan uusia, ihania etuja.", "description": "Kiva, että olemme saaneet jakaa paljon yhteisiä hetkiä. Olet tosiaan nimesi arvoinen Loyal Friend! Haluamme panostaa ystävyyteemme myös jatkossa ja annammekin sinulle kasan uusia, ihania etuja.",
@@ -381,7 +381,7 @@
] ]
}, },
{ {
"tier": 6, "level": 6,
"name": "True Friend", "name": "True Friend",
"requirement": "100 000p", "requirement": "100 000p",
"description": "Onpa ollut ihana nähdä sinua näin paljon viime aikoina. Tosiystävän tapaan haluamme palkita sinua entistä yksilöllisemmillä eduilla.", "description": "Onpa ollut ihana nähdä sinua näin paljon viime aikoina. Tosiystävän tapaan haluamme palkita sinua entistä yksilöllisemmillä eduilla.",
@@ -458,7 +458,7 @@
] ]
}, },
{ {
"tier": 7, "level": 7,
"name": "Best Friend", "name": "Best Friend",
"requirement": "400 000p", "requirement": "400 000p",
"description": "Ystävyytemme on vailla vertaa. Koska sanat eivät riitä kiittämään ystävyydestämme, pääset nyt käsiksi kaikkein eksklusiivisimpiin elämyksiin.", "description": "Ystävyytemme on vailla vertaa. Koska sanat eivät riitä kiittämään ystävyydestämme, pääset nyt käsiksi kaikkein eksklusiivisimpiin elämyksiin.",

View File

@@ -1,7 +1,7 @@
{ {
"levels": [ "levels": [
{ {
"tier": 1, "level": 1,
"name": "New Friend", "name": "New Friend",
"requirement": "0p", "requirement": "0p",
"description": "Dette er starten på noe vakkert: Som en New Friend, gjør deg klar for en reise fylt av fine Scandic-opplevelser.", "description": "Dette er starten på noe vakkert: Som en New Friend, gjør deg klar for en reise fylt av fine Scandic-opplevelser.",
@@ -76,7 +76,7 @@
] ]
}, },
{ {
"tier": 2, "level": 2,
"name": "Good Friend", "name": "Good Friend",
"requirement": "5 000p", "requirement": "5 000p",
"description": "Du har vært her mye i det siste! Og ærlig talt føler vi at vi er på bølgelengde ett behagelig opphold og én morsom overraskelse om gangen.", "description": "Du har vært her mye i det siste! Og ærlig talt føler vi at vi er på bølgelengde ett behagelig opphold og én morsom overraskelse om gangen.",
@@ -151,7 +151,7 @@
] ]
}, },
{ {
"tier": 3, "level": 3,
"name": "Close Friend", "name": "Close Friend",
"requirement": "10 000p", "requirement": "10 000p",
"description": "Nå er det seriøst: Vi begynner virkelig å bli kjent med hverandre, noe som gjør Scandic-opplevelsen din mye mer personlig.", "description": "Nå er det seriøst: Vi begynner virkelig å bli kjent med hverandre, noe som gjør Scandic-opplevelsen din mye mer personlig.",
@@ -227,7 +227,7 @@
] ]
}, },
{ {
"tier": 4, "level": 4,
"name": "Dear Friend", "name": "Dear Friend",
"requirement": "25 000p", "requirement": "25 000p",
"description": "Hurra for oss! Dette ser ut til å gå i retning av venner for livet og det kommer med tilgang til mye mer av Scandic.", "description": "Hurra for oss! Dette ser ut til å gå i retning av venner for livet og det kommer med tilgang til mye mer av Scandic.",
@@ -304,7 +304,7 @@
] ]
}, },
{ {
"tier": 5, "level": 5,
"name": "Loyal Friend", "name": "Loyal Friend",
"requirement": "50 000p", "requirement": "50 000p",
"description": "Du har vært lojal mot oss gjennom opphold, happy hours og treningsøkter så vi er der for deg med noen av de aller beste fordelene våre.", "description": "Du har vært lojal mot oss gjennom opphold, happy hours og treningsøkter så vi er der for deg med noen av de aller beste fordelene våre.",
@@ -381,7 +381,7 @@
] ]
}, },
{ {
"tier": 6, "level": 6,
"name": "True Friend", "name": "True Friend",
"requirement": "100 000p", "requirement": "100 000p",
"description": "Det spiller ingen rolle om det er høysesong eller lavsesong, du er alltid der for oss. Nyt enda flere skreddersydde fordeler akkurat slik du liker dem.", "description": "Det spiller ingen rolle om det er høysesong eller lavsesong, du er alltid der for oss. Nyt enda flere skreddersydde fordeler akkurat slik du liker dem.",
@@ -458,7 +458,7 @@
] ]
}, },
{ {
"tier": 7, "level": 7,
"name": "Best Friend", "name": "Best Friend",
"requirement": "400 000p", "requirement": "400 000p",
"description": "Det finnes ikke ord for et bånd som dette, men vi gjør et forsøk allikevel: Det blir bare ikke bedre når det gjelder svært eksklusive opplevelser!", "description": "Det finnes ikke ord for et bånd som dette, men vi gjør et forsøk allikevel: Det blir bare ikke bedre når det gjelder svært eksklusive opplevelser!",

View File

@@ -1,7 +1,7 @@
{ {
"levels": [ "levels": [
{ {
"tier": 1, "level": 1,
"name": "New Friend", "name": "New Friend",
"requirement": "0p or 0 nights", "requirement": "0p or 0 nights",
"description": "Detta är början på något speciellt, vi kan känna det. Som New Friend bjuder dina vistelser på nya upptäckter.", "description": "Detta är början på något speciellt, vi kan känna det. Som New Friend bjuder dina vistelser på nya upptäckter.",
@@ -76,7 +76,7 @@
] ]
}, },
{ {
"tier": 2, "level": 2,
"name": "Good Friend", "name": "Good Friend",
"requirement": "5 000p", "requirement": "5 000p",
"description": "Vi har setts mycket på senaste tiden och det känns typ som att vi har nåt speciellt på gång en vistelse och rolig överraskning i taget.", "description": "Vi har setts mycket på senaste tiden och det känns typ som att vi har nåt speciellt på gång en vistelse och rolig överraskning i taget.",
@@ -151,7 +151,7 @@
] ]
}, },
{ {
"tier": 3, "level": 3,
"name": "Close Friend", "name": "Close Friend",
"requirement": "10 000p", "requirement": "10 000p",
"description": "Det börjar bli seriöst: nu när vi lärt känna varann bättre blir också din upplevelse lite personligare.", "description": "Det börjar bli seriöst: nu när vi lärt känna varann bättre blir också din upplevelse lite personligare.",
@@ -227,7 +227,7 @@
] ]
}, },
{ {
"tier": 4, "level": 4,
"name": "Dear Friend", "name": "Dear Friend",
"requirement": "25 000p", "requirement": "25 000p",
"description": "Är det bara en känsla eller ligger det vänner för livet-vibbar i luften? Skål för oss och för att du nu får ännu mer av Scandic.", "description": "Är det bara en känsla eller ligger det vänner för livet-vibbar i luften? Skål för oss och för att du nu får ännu mer av Scandic.",
@@ -304,7 +304,7 @@
] ]
}, },
{ {
"tier": 5, "level": 5,
"name": "Loyal Friend", "name": "Loyal Friend",
"requirement": "50 000p", "requirement": "50 000p",
"description": "Du har varit hos oss under otaliga resor och fler nätter än vi kan räkna, så därför vill vi ge dig några av våra allra bästa förmåner.", "description": "Du har varit hos oss under otaliga resor och fler nätter än vi kan räkna, så därför vill vi ge dig några av våra allra bästa förmåner.",
@@ -380,7 +380,7 @@
] ]
}, },
{ {
"tier": 6, "level": 6,
"name": "True Friend", "name": "True Friend",
"requirement": "100 000p", "requirement": "100 000p",
"description": "Du är alltid där för oss, oavsett om det är hög- eller lågsäsong. Därför vill vi ge dig fler förmåner precis som du gillar.", "description": "Du är alltid där för oss, oavsett om det är hög- eller lågsäsong. Därför vill vi ge dig fler förmåner precis som du gillar.",
@@ -457,7 +457,7 @@
] ]
}, },
{ {
"tier": 7, "level": 7,
"name": "Best Friend", "name": "Best Friend",
"requirement": "400 000p", "requirement": "400 000p",
"description": "Det är svårt att sätta ord på ett så här starkt band, men här kommer några ändå: det går inte att få bättre upplevelser än så här!", "description": "Det är svårt att sätta ord på ett så här starkt band, men här kommer några ändå: det går inte att få bättre upplevelser än så här!",

View File

@@ -1,8 +1,6 @@
"use client" "use client"
import { useParams } from "next/navigation"
import { useReducer } from "react" import { useReducer } from "react"
import { type Key } from "react-aria-components"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
@@ -21,16 +19,21 @@ import SV from "./data/SV.json"
import BenefitList from "./BenefitList" import BenefitList from "./BenefitList"
import LargeTable from "./LargeTable" import LargeTable from "./LargeTable"
import LevelSummary from "./LevelSummary" import LevelSummary from "./LevelSummary"
import YourLevel from "./YourLevelScript"
import styles from "./overviewTable.module.css" import styles from "./overviewTable.module.css"
import type { Key } from "react-aria-components"
import { import {
ComparisonLevel, ComparisonLevel,
DesktopSelectColumns,
MobileColumnHeaderProps,
overviewTableActionsEnum, overviewTableActionsEnum,
OverviewTableProps, OverviewTableProps,
OverviewTableReducerAction, OverviewTableReducerAction,
} from "@/types/components/loyalty/blocks" } from "@/types/components/loyalty/blocks"
import { User } from "@/types/user" import type { User } from "@/types/user"
const levelsTranslations = { const levelsTranslations = {
[Lang.en]: EN, [Lang.en]: EN,
@@ -40,9 +43,10 @@ const levelsTranslations = {
[Lang.fi]: FI, [Lang.fi]: FI,
[Lang.de]: DE, [Lang.de]: DE,
} }
function getTranslatedLevelByTier(tier: membershipLevels, lang: Lang) {
function getTranslatedLevel(membershipLevel: membershipLevels, lang: Lang) {
return levelsTranslations[lang].levels.find( return levelsTranslations[lang].levels.find(
(level) => level.tier === tier (level) => level.level === membershipLevel
) as ComparisonLevel ) as ComparisonLevel
} }
@@ -50,40 +54,40 @@ function getInitialState({ user, lang }: { user?: User; lang: Lang }) {
const membership = user?.memberships ? getMembership(user.memberships) : null const membership = user?.memberships ? getMembership(user.memberships) : null
if (!membership?.membershipLevel) { if (!membership?.membershipLevel) {
return { return {
selectedLevelAMobile: getTranslatedLevelByTier(1, lang), selectedLevelAMobile: getTranslatedLevel(1, lang),
selectedLevelBMobile: getTranslatedLevelByTier(2, lang), selectedLevelBMobile: getTranslatedLevel(2, lang),
selectedLevelADesktop: getTranslatedLevelByTier(1, lang), selectedLevelADesktop: getTranslatedLevel(1, lang),
selectedLevelBDesktop: getTranslatedLevelByTier(2, lang), selectedLevelBDesktop: getTranslatedLevel(2, lang),
selectedLevelCDesktop: getTranslatedLevelByTier(3, lang), selectedLevelCDesktop: getTranslatedLevel(3, lang),
} }
} }
if (!membership.membershipLevel) return null if (!membership.membershipLevel) return null
const tier = membershipLevels[membership.membershipLevel] const level = membershipLevels[membership.membershipLevel]
switch (tier) { switch (level) {
case 6: case 6:
return { return {
selectedLevelAMobile: getTranslatedLevelByTier(6, lang), selectedLevelAMobile: getTranslatedLevel(6, lang),
selectedLevelBMobile: getTranslatedLevelByTier(7, lang), selectedLevelBMobile: getTranslatedLevel(7, lang),
selectedLevelADesktop: getTranslatedLevelByTier(5, lang), selectedLevelADesktop: getTranslatedLevel(5, lang),
selectedLevelBDesktop: getTranslatedLevelByTier(6, lang), selectedLevelBDesktop: getTranslatedLevel(6, lang),
selectedLevelCDesktop: getTranslatedLevelByTier(7, lang), selectedLevelCDesktop: getTranslatedLevel(7, lang),
} }
case 7: case 7:
return { return {
selectedLevelAMobile: getTranslatedLevelByTier(6, lang), selectedLevelAMobile: getTranslatedLevel(6, lang),
selectedLevelBMobile: getTranslatedLevelByTier(7, lang), selectedLevelBMobile: getTranslatedLevel(7, lang),
selectedLevelADesktop: getTranslatedLevelByTier(6, lang), selectedLevelADesktop: getTranslatedLevel(6, lang),
selectedLevelBDesktop: getTranslatedLevelByTier(7, lang), selectedLevelBDesktop: getTranslatedLevel(7, lang),
selectedLevelCDesktop: getTranslatedLevelByTier(1, lang), selectedLevelCDesktop: getTranslatedLevel(1, lang),
} }
default: default:
return { return {
selectedLevelAMobile: getTranslatedLevelByTier(tier, lang), selectedLevelAMobile: getTranslatedLevel(level, lang),
selectedLevelBMobile: getTranslatedLevelByTier(tier + 1, lang), selectedLevelBMobile: getTranslatedLevel(level + 1, lang),
selectedLevelADesktop: getTranslatedLevelByTier(tier, lang), selectedLevelADesktop: getTranslatedLevel(level, lang),
selectedLevelBDesktop: getTranslatedLevelByTier(tier + 1, lang), selectedLevelBDesktop: getTranslatedLevel(level + 1, lang),
selectedLevelCDesktop: getTranslatedLevelByTier(tier + 2, lang), selectedLevelCDesktop: getTranslatedLevel(level + 2, lang),
} }
} }
} }
@@ -134,7 +138,7 @@ export default function OverviewTable({ user }: OverviewTableProps) {
return (key: Key) => { return (key: Key) => {
if (typeof key === "number") { if (typeof key === "number") {
dispatch({ dispatch({
payload: getTranslatedLevelByTier(key, lang), payload: getTranslatedLevel(key, lang),
type: actionType, type: actionType,
}) })
} }
@@ -142,68 +146,106 @@ export default function OverviewTable({ user }: OverviewTableProps) {
} }
const levelOptions = levelsData.levels.map((level) => ({ const levelOptions = levelsData.levels.map((level) => ({
label: level.name, label: [level.level, level.name].join(" "),
value: level.tier, value: level.level,
})) }))
const activeMembership = user?.memberships
? getMembership(user.memberships)
: null
let activeMembershipLevel: membershipLevels | null = null
if (activeMembership?.membershipLevel) {
activeMembershipLevel = membershipLevels[activeMembership?.membershipLevel]
}
function MobileColumnHeader({ column }: MobileColumnHeaderProps) {
let selectedLevelMobile: ComparisonLevel
let actionEnumMobile: overviewTableActionsEnum
switch (column) {
case "A":
selectedLevelMobile = selectionState.selectedLevelAMobile
actionEnumMobile = overviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE
break
case "B":
selectedLevelMobile = selectionState.selectedLevelBMobile
actionEnumMobile = overviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE
break
default:
return null
}
return (
<div className={styles.columnHeader}>
<div className={styles.icon}>
{activeMembershipLevel === selectedLevelMobile.level ? (
<YourLevel />
) : null}
<Image
src={selectedLevelMobile.icon}
alt={selectedLevelMobile.name}
height={50}
width={100}
/>
</div>
<LevelSummary
level={
levelsData.levels.find(
(level) => level.level === selectedLevelMobile.level
) as ComparisonLevel
}
showDescription={false}
/>
<Select
aria-label={intl.formatMessage({ id: "Level" })}
name={`benefit` + column}
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectedLevelMobile.level}
onSelect={handleSelectChange(actionEnumMobile)}
/>
</div>
)
}
function SelectDesktop({ column }: DesktopSelectColumns) {
let selectedLevelDesktop: ComparisonLevel
let actionEnumDesktop: overviewTableActionsEnum
switch (column) {
case "A":
selectedLevelDesktop = selectionState.selectedLevelADesktop
actionEnumDesktop =
overviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP
break
case "B":
selectedLevelDesktop = selectionState.selectedLevelBDesktop
actionEnumDesktop =
overviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP
break
case "C":
selectedLevelDesktop = selectionState.selectedLevelCDesktop
actionEnumDesktop =
overviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP
break
default:
return null
}
return (
<Select
aria-label={intl.formatMessage({ id: "Level" })}
name={`benefit` + column}
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectedLevelDesktop.level}
onSelect={handleSelectChange(actionEnumDesktop)}
/>
)
}
return ( return (
<div> <div>
<div className={styles.mobileColumns}> <div className={styles.mobileColumns}>
<div className={styles.columnHeaderContainer}> <div className={styles.columnHeaderContainer}>
<div className={styles.columnHeader}> <MobileColumnHeader column={"A"} />
<Select <MobileColumnHeader column={"B"} />
name="benefitA"
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectionState.selectedLevelAMobile.tier}
onSelect={handleSelectChange(
overviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE
)}
/>
<Image
className={styles.icon}
src={selectionState.selectedLevelAMobile.icon}
alt={selectionState.selectedLevelAMobile.name}
height={50}
width={100}
/>
<LevelSummary
level={
levelsData.levels.find(
(level) =>
level.tier === selectionState.selectedLevelAMobile.tier
) as ComparisonLevel
}
/>
</div>
<div className={styles.columnHeader}>
<Select
name="benefitB"
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectionState.selectedLevelBMobile.tier}
onSelect={handleSelectChange(
overviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE
)}
/>
<Image
className={styles.icon}
src={selectionState.selectedLevelBMobile.icon}
alt={selectionState.selectedLevelBMobile.name}
height={50}
width={100}
/>
<LevelSummary
level={
levelsData.levels.find(
(level) =>
level.tier === selectionState.selectedLevelBMobile.tier
) as ComparisonLevel
}
/>
</div>
</div> </div>
<BenefitList <BenefitList
levels={[ levels={[
@@ -213,100 +255,22 @@ export default function OverviewTable({ user }: OverviewTableProps) {
/> />
</div> </div>
<div className={styles.columns}> <div className={styles.columns}>
<div className={styles.columnHeaderContainer}> <LargeTable
<div className={styles.columnHeader}>
<Select
name="benefitA"
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectionState.selectedLevelADesktop.tier}
onSelect={handleSelectChange(
overviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP
)}
/>
<Image
className={styles.icon}
src={selectionState.selectedLevelADesktop.icon}
alt={selectionState.selectedLevelADesktop.name}
height={50}
width={100}
/>
<LevelSummary
level={
levelsData.levels.find(
(level) =>
level.tier === selectionState.selectedLevelADesktop.tier
) as ComparisonLevel
}
/>
</div>
<div className={styles.columnHeader}>
<Select
name="benefitB"
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectionState.selectedLevelBDesktop.tier}
onSelect={handleSelectChange(
overviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP
)}
/>
<Image
className={styles.icon}
src={selectionState.selectedLevelBDesktop.icon}
alt={selectionState.selectedLevelBDesktop.name}
height={50}
width={100}
/>
<LevelSummary
level={
levelsData.levels.find(
(level) =>
level.tier === selectionState.selectedLevelBDesktop.tier
) as ComparisonLevel
}
/>
</div>
<div className={styles.columnHeader}>
<Select
name="benefitC"
label={intl.formatMessage({ id: "Level" })}
items={levelOptions}
value={selectionState.selectedLevelCDesktop.tier}
onSelect={handleSelectChange(
overviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP
)}
/>
<Image
className={styles.icon}
src={selectionState.selectedLevelCDesktop.icon}
alt={selectionState.selectedLevelCDesktop.name}
height={50}
width={100}
/>
<LevelSummary
level={
levelsData.levels.find(
(level) =>
level.tier === selectionState.selectedLevelCDesktop.tier
) as ComparisonLevel
}
/>
</div>
</div>
<BenefitList
levels={[ levels={[
selectionState.selectedLevelADesktop, selectionState.selectedLevelADesktop,
selectionState.selectedLevelBDesktop, selectionState.selectedLevelBDesktop,
selectionState.selectedLevelCDesktop, selectionState.selectedLevelCDesktop,
]} ]}
activeLevel={activeMembershipLevel}
Select={SelectDesktop}
/> />
</div> </div>
<div className={styles.largeTableContainer}> <div className={styles.largeTableContainer}>
{/* Remove `as` once we have real data */} {/* Remove `as` once we have real data */}
<LargeTable levels={levelsData.levels as ComparisonLevel[]} /> <LargeTable
levels={levelsData.levels as ComparisonLevel[]}
activeLevel={activeMembershipLevel}
/>
</div> </div>
</div> </div>
) )

View File

@@ -11,15 +11,17 @@
display: none; display: none;
position: relative; position: relative;
background-color: var(--Scandic-Opacity-White-100); background-color: var(--Scandic-Opacity-White-100);
border-radius: var(--Corner-radius-Medium);
} }
.mobileColumns { .mobileColumns {
background-color: var(--Scandic-Opacity-White-100);
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
margin: 0 calc(var(--Spacing-x2) * -1); margin: 0 calc(var(--Spacing-x2) * -1) calc(var(--Spacing-x9) * -1)
padding-top: var(--Spacing-x2); calc(var(--Spacing-x2) * -1);
padding-bottom: var(--Spacing-x9);
position: relative; position: relative;
background-color: var(--Scandic-Opacity-White-100);
} }
.columnHeaderContainer { .columnHeaderContainer {
@@ -33,7 +35,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);
padding: var(--Spacing-x2); padding: var(--Spacing-x4) var(--Spacing-x2);
justify-content: flex-end;
} }
.icon { .icon {
@@ -71,9 +74,7 @@
} }
.columns { .columns {
display: grid; display: block;
grid-template-columns: 1fr 1fr 1fr;
margin: 0 calc(var(--Spacing-x2) * -1);
} }
} }

View File

@@ -1,13 +1,19 @@
.container { .container {
/* These negative margins are needed for horizontally scrollable lists of cards */ /* These negative margins are needed for getting the right background color for mobile loyalty table overview */
margin-right: calc(0px - var(--Spacing-x2)); margin: 0 calc(0px - var(--Spacing-x2)) calc(0px - var(--Spacing-x9))
calc(0px - var(--Spacing-x2));
overflow-x: hidden; overflow-x: hidden;
padding-right: var(--Spacing-x2); padding: 0 var(--Spacing-x2) var(--Spacing-x9) var(--Spacing-x2);
} }
.header { .header {
display: grid; display: grid;
gap: var(--Spacing-x1); gap: var(--Spacing-x1);
padding-bottom: var(--Spacing-x2);
}
.tableTitle {
text-wrap: balance;
} }
.preamble { .preamble {
@@ -22,4 +28,8 @@
margin-right: var(--Spacing-x0); margin-right: var(--Spacing-x0);
margin-left: var(--Spacing-x0); margin-left: var(--Spacing-x0);
} }
.header {
width: 800px;
}
} }

View File

@@ -5,8 +5,8 @@ import { auth } from "@/auth"
import SectionContainer from "@/components/Section/Container" import SectionContainer from "@/components/Section/Container"
import Header from "@/components/Section/Header" import Header from "@/components/Section/Header"
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle" import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
import Title from "@/components/TempDesignSystem/Text/Title"
import OverviewTableTitle from "./OverviewTable/Title"
import HowItWorks from "./HowItWorks" import HowItWorks from "./HowItWorks"
import LoyaltyLevels from "./LoyaltyLevels" import LoyaltyLevels from "./LoyaltyLevels"
import OverviewTable from "./OverviewTable" import OverviewTable from "./OverviewTable"
@@ -36,35 +36,6 @@ async function DynamicComponentBlock({ component }: DynamicComponentProps) {
} }
} }
// These should ultimately be fetched from Contentstack
const titleTranslations = {
[Lang.en]: [
{ text: "7 delightful levels", highlight: true },
{ text: "of friendship", highlight: false },
],
// TODO: Add translations for the following languages
[Lang.da]: [
{ text: "7 delightful levels", highlight: true },
{ text: "of friendship", highlight: false },
],
[Lang.no]: [
{ text: "7 delightful levels", highlight: true },
{ text: "of friendship", highlight: false },
],
[Lang.sv]: [
{ text: "7 delightful levels", highlight: true },
{ text: "of friendship", highlight: false },
],
[Lang.fi]: [
{ text: "7 delightful levels", highlight: true },
{ text: "of friendship", highlight: false },
],
[Lang.de]: [
{ text: "7 delightful levels", highlight: true },
{ text: "of friendship", highlight: false },
],
}
export default function DynamicContent({ export default function DynamicContent({
dynamicContent, dynamicContent,
firstItem, firstItem,
@@ -77,12 +48,11 @@ export default function DynamicContent({
const isOverviewTable = const isOverviewTable =
dynamicContent.component === LoyaltyComponentEnum.overview_table dynamicContent.component === LoyaltyComponentEnum.overview_table
return ( return (
<SectionContainer className={styles.container}> <SectionContainer className={styles.container}>
{isOverviewTable ? ( {isOverviewTable ? (
<div className={styles.header}> <div className={styles.header}>
<OverviewTableTitle texts={titleTranslations[Lang.en]} /> <Title className={styles.tableTitle}> {dynamicContent.title}</Title>
<Subtitle>{dynamicContent.subtitle}</Subtitle> <Subtitle>{dynamicContent.subtitle}</Subtitle>
</div> </div>
) : displayHeader ? ( ) : displayHeader ? (

View File

@@ -46,6 +46,10 @@
color: var(--Scandic-Brand-Pale-Peach); color: var(--Scandic-Brand-Pale-Peach);
} }
.peach80 {
color: var(--Scandic-Peach-80);
}
.plosa { .plosa {
color: var(--Theme-Primary-Light-On-Surface-Accent); color: var(--Theme-Primary-Light-On-Surface-Accent);
} }

View File

@@ -8,6 +8,7 @@ const config = {
black: styles.black, black: styles.black,
burgundy: styles.burgundy, burgundy: styles.burgundy,
pale: styles.pale, pale: styles.pale,
peach80: styles.peach80,
primaryLightOnSurfaceAccent: styles.plosa, primaryLightOnSurfaceAccent: styles.plosa,
}, },
textAlign: { textAlign: {

View File

@@ -89,5 +89,6 @@
"You have no previous stays.": "Du har ingen tidligere ophold.", "You have no previous stays.": "Du har ingen tidligere ophold.",
"You have no upcoming stays.": "Du har ingen kommende ophold.", "You have no upcoming stays.": "Du har ingen kommende ophold.",
"Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!",
"Your level": "Dit niveau",
"Zip code": "Postnummer" "Zip code": "Postnummer"
} }

View File

@@ -89,5 +89,6 @@
"You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.", "You have no previous stays.": "Sie haben keine vorherigen Aufenthalte.",
"You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.", "You have no upcoming stays.": "Sie haben keine bevorstehenden Aufenthalte.",
"Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!", "Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!",
"Your level": "Dein ebene",
"Zip code": "PLZ" "Zip code": "PLZ"
} }

View File

@@ -89,5 +89,6 @@
"You have no previous stays.": "You have no previous stays.", "You have no previous stays.": "You have no previous stays.",
"You have no upcoming stays.": "You have no upcoming stays.", "You have no upcoming stays.": "You have no upcoming stays.",
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
"Your level": "Your level",
"Zip code": "Zip code" "Zip code": "Zip code"
} }

View File

@@ -89,5 +89,6 @@
"You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.", "You have no previous stays.": "Sinulla ei ole aiempaa oleskelua.",
"You have no upcoming stays.": "Sinulla ei ole tulevia oleskeluja.", "You have no upcoming stays.": "Sinulla ei ole tulevia oleskeluja.",
"Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!", "Your Challenges Conquer & Earn!": "Voita ja ansaitse haasteesi!",
"Your level": "Tasosi",
"Zip code": "Postinumero" "Zip code": "Postinumero"
} }

View File

@@ -89,5 +89,6 @@
"You have no previous stays.": "Du har ingen tidligere opphold.", "You have no previous stays.": "Du har ingen tidligere opphold.",
"You have no upcoming stays.": "Du har ingen kommende opphold.", "You have no upcoming stays.": "Du har ingen kommende opphold.",
"Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!",
"Your level": "Ditt nivå",
"Zip code": "Post kode" "Zip code": "Post kode"
} }

View File

@@ -89,5 +89,6 @@
"You have no previous stays.": "Du har inga tidigare vistelser.", "You have no previous stays.": "Du har inga tidigare vistelser.",
"You have no upcoming stays.": "Du har inga kommande vistelser.", "You have no upcoming stays.": "Du har inga kommande vistelser.",
"Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!",
"Your level": "Din nivå",
"Zip code": "Postnummer" "Zip code": "Postnummer"
} }

View File

@@ -30,13 +30,6 @@ export type Content = { content: RteBlockContent["content"]["content"] }
type Benefit = { title: string } type Benefit = { title: string }
type OverviewTableTitleTranslation = {
text: string
highlight: boolean
}
export type OverviewTableTitleProps = { texts: OverviewTableTitleTranslation[] }
export type OverviewTableProps = { user: User | null } export type OverviewTableProps = { user: User | null }
export type Level = { export type Level = {
@@ -54,7 +47,7 @@ export type LevelCardProps = {
} }
export type ComparisonLevel = { export type ComparisonLevel = {
tier: membershipLevels level: membershipLevels
name: string name: string
description: string description: string
requirement: string requirement: string
@@ -72,6 +65,7 @@ export type ComparisonLevel = {
export type LevelSummaryProps = { export type LevelSummaryProps = {
level: ComparisonLevel level: ComparisonLevel
showDescription?: boolean
} }
export type BenefitCardProps = { export type BenefitCardProps = {
@@ -94,8 +88,18 @@ export type BenefitListProps = {
levels: ComparisonLevel[] levels: ComparisonLevel[]
} }
export type MobileColumnHeaderProps = {
column: "A" | "B"
}
export type DesktopSelectColumns = {
column: MobileColumnHeaderProps["column"] | "C"
}
export type LargeTableProps = { export type LargeTableProps = {
levels: ComparisonLevel[] levels: ComparisonLevel[]
activeLevel: membershipLevels | null
Select?: (column: DesktopSelectColumns) => JSX.Element | null
} }
export type BenefitTableHeaderProps = { export type BenefitTableHeaderProps = {

13
utils/loyaltyTable.ts Normal file
View File

@@ -0,0 +1,13 @@
import { ComparisonLevel } from "@/types/components/loyalty/blocks"
export function getHighestLevel(levels: ComparisonLevel[]) {
return levels.reduce(
(acc: ComparisonLevel | null, level: ComparisonLevel) => {
if (!acc) {
return level
}
return level.level > acc.level ? level : acc
},
null
)
}