Merged in monorepo-step-1 (pull request #1080)
Migrate to a monorepo setup - step 1 * Move web to subfolder /apps/scandic-web * Yarn + transitive deps - Move to yarn - design-system package removed for now since yarn doesn't support the parameter for token (ie project currently broken) - Add missing transitive dependencies as Yarn otherwise prevents these imports - VS Code doesn't pick up TS path aliases unless you open /apps/scandic-web instead of root (will be fixed with monorepo) * Pin framer-motion to temporarily fix typing issue https://github.com/adobe/react-spectrum/issues/7494 * Pin zod to avoid typ error There seems to have been a breaking change in the types returned by zod where error is now returned as undefined instead of missing in the type. We should just handle this but to avoid merge conflicts just pin the dependency for now. * Pin react-intl version Pin version of react-intl to avoid tiny type issue where formatMessage does not accept a generic any more. This will be fixed in a future commit, but to avoid merge conflicts just pin for now. * Pin typescript version Temporarily pin version as newer versions as stricter and results in a type error. Will be fixed in future commit after merge. * Setup workspaces * Add design-system as a monorepo package * Remove unused env var DESIGN_SYSTEM_ACCESS_TOKEN * Fix husky for monorepo setup * Update netlify.toml * Add lint script to root package.json * Add stub readme * Fix react-intl formatMessage types * Test netlify.toml in root * Remove root toml * Update netlify.toml publish path * Remove package-lock.json * Update build for branch/preview builds Approved-by: Linus Flood
This commit is contained in:
committed by
Linus Flood
parent
667cab6fb6
commit
80100e7631
@@ -0,0 +1,176 @@
|
||||
"use client"
|
||||
|
||||
import { useReducer } from "react"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import {
|
||||
type MembershipLevel,
|
||||
membershipLevels,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
import Select from "@/components/TempDesignSystem/Select"
|
||||
|
||||
import LargeTable from "./LargeTable"
|
||||
import LevelSummary from "./LevelSummary"
|
||||
import { getInitialState, getLevel, reducer } from "./reducer"
|
||||
import RewardList from "./RewardList"
|
||||
import YourLevel from "./YourLevelScript"
|
||||
|
||||
import styles from "./overviewTable.module.css"
|
||||
|
||||
import type { Key } from "react-aria-components"
|
||||
|
||||
import {
|
||||
type ComparisonLevel,
|
||||
type DesktopSelectColumns,
|
||||
type MobileColumnHeaderProps,
|
||||
OverviewTableActionsEnum,
|
||||
type OverviewTableClientProps,
|
||||
} from "@/types/components/overviewTable"
|
||||
|
||||
function getLevelNamesForSelect(level: MembershipLevel, levelName: string) {
|
||||
const levelToNumber = membershipLevels[level]
|
||||
return [levelToNumber, levelName].join(" ")
|
||||
}
|
||||
|
||||
export default function OverviewTableClient({
|
||||
activeMembership,
|
||||
levels,
|
||||
}: OverviewTableClientProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
const [selectionState, dispatch] = useReducer(
|
||||
reducer,
|
||||
{ activeMembership, levels },
|
||||
getInitialState
|
||||
)
|
||||
|
||||
function handleSelectChange(actionType: OverviewTableActionsEnum) {
|
||||
return (key: Key) => {
|
||||
dispatch({
|
||||
payload: getLevel(key as MembershipLevel, levels),
|
||||
type: actionType,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const levelOptions = levels.map((level) => ({
|
||||
label: getLevelNamesForSelect(level.level_id, level.name),
|
||||
value: level.level_id,
|
||||
}))
|
||||
|
||||
const activeMembershipLevel = activeMembership ?? null
|
||||
|
||||
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_id ? (
|
||||
<YourLevel />
|
||||
) : null}
|
||||
<MembershipLevelIcon
|
||||
level={selectedLevelMobile.level_id}
|
||||
color="red"
|
||||
height="50"
|
||||
width="100"
|
||||
/>
|
||||
</div>
|
||||
<LevelSummary
|
||||
level={
|
||||
levels.find(
|
||||
(level) => level.level_id === selectedLevelMobile.level_id
|
||||
)!
|
||||
}
|
||||
showDescription={false}
|
||||
/>
|
||||
<Select
|
||||
aria-label={intl.formatMessage({ id: "Level" })}
|
||||
name={`reward` + column}
|
||||
label={intl.formatMessage({ id: "Level" })}
|
||||
items={levelOptions}
|
||||
value={selectedLevelMobile.level_id}
|
||||
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={`reward` + column}
|
||||
label={intl.formatMessage({ id: "Level" })}
|
||||
items={levelOptions}
|
||||
value={selectedLevelDesktop.level_id}
|
||||
onSelect={handleSelectChange(actionEnumDesktop)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.mobileColumns}>
|
||||
<div className={styles.columnHeaderContainer}>
|
||||
<MobileColumnHeader column={"A"} />
|
||||
<MobileColumnHeader column={"B"} />
|
||||
</div>
|
||||
<RewardList
|
||||
levels={[
|
||||
selectionState.selectedLevelAMobile,
|
||||
selectionState.selectedLevelBMobile,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.columns}>
|
||||
<LargeTable
|
||||
levels={[
|
||||
selectionState.selectedLevelADesktop,
|
||||
selectionState.selectedLevelBDesktop,
|
||||
selectionState.selectedLevelCDesktop,
|
||||
]}
|
||||
activeLevel={activeMembershipLevel}
|
||||
Select={SelectDesktop}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.largeTableContainer}>
|
||||
<LargeTable levels={levels} activeLevel={activeMembershipLevel} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
.header {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.iconRow {
|
||||
border-bottom: none;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import MembershipLevelIcon from "@/components/Levels/Icon"
|
||||
|
||||
import LevelSummary from "../../LevelSummary"
|
||||
import YourLevel from "../../YourLevelScript"
|
||||
|
||||
import styles from "./desktopHeader.module.css"
|
||||
|
||||
import type {
|
||||
DesktopSelectColumns,
|
||||
LargeTableProps,
|
||||
} from "@/types/components/overviewTable"
|
||||
|
||||
export default function DesktopHeader({
|
||||
levels,
|
||||
activeLevel,
|
||||
Select,
|
||||
}: LargeTableProps) {
|
||||
return (
|
||||
<thead className={styles.header}>
|
||||
<tr className={styles.iconRow}>
|
||||
<th className={styles.verticalTableHeader} />
|
||||
{levels.map((level, idx) => {
|
||||
return (
|
||||
<th key={"image" + level.level_id + idx} className={styles.iconTh}>
|
||||
{activeLevel === level.level_id ? <YourLevel /> : null}
|
||||
<MembershipLevelIcon
|
||||
color="red"
|
||||
level={level.level_id}
|
||||
height="50"
|
||||
width="100"
|
||||
/>
|
||||
</th>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
<tr>
|
||||
<th />
|
||||
{levels.map((level, idx) => {
|
||||
return (
|
||||
<th
|
||||
key={"summary" + level.level_id + 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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ChevronDown } from "react-feather"
|
||||
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import {
|
||||
findAvailableRewards,
|
||||
getGroupedLabelAndDescription,
|
||||
getGroupedRewards,
|
||||
} from "@/utils/loyaltyTable"
|
||||
|
||||
import RewardValue from "../RewardValue"
|
||||
import DesktopHeader from "./DesktopHeader"
|
||||
|
||||
import styles from "./largeTable.module.css"
|
||||
|
||||
import type {
|
||||
LargeTableProps,
|
||||
RewardTableHeaderProps,
|
||||
} from "@/types/components/overviewTable"
|
||||
|
||||
export default function LargeTable({
|
||||
levels,
|
||||
activeLevel,
|
||||
Select,
|
||||
}: LargeTableProps) {
|
||||
const keyedGroupedRewards = getGroupedRewards(levels)
|
||||
|
||||
return (
|
||||
<table className={styles.table}>
|
||||
<DesktopHeader
|
||||
levels={levels}
|
||||
activeLevel={activeLevel}
|
||||
Select={Select}
|
||||
/>
|
||||
<tbody className={styles.tbody}>
|
||||
{Object.entries(keyedGroupedRewards).map(
|
||||
([key, groupedRewards], idx) => {
|
||||
const { label, description } =
|
||||
getGroupedLabelAndDescription(groupedRewards)
|
||||
|
||||
return (
|
||||
<tr key={key + idx} className={styles.tr}>
|
||||
<th scope={"row"} className={styles.rewardTh}>
|
||||
<RewardTableHeader name={label} description={description} />
|
||||
</th>
|
||||
{levels.map((level, idx) => {
|
||||
const rewardIdsInGroup = groupedRewards.map(
|
||||
(b) => b.reward_id
|
||||
)
|
||||
const reward = findAvailableRewards(rewardIdsInGroup, level)
|
||||
return (
|
||||
<td
|
||||
key={`${reward?.reward_id}-${idx}`}
|
||||
className={styles.td}
|
||||
>
|
||||
<RewardValue reward={reward} />
|
||||
</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
}
|
||||
|
||||
function RewardTableHeader({ name, description }: RewardTableHeaderProps) {
|
||||
return (
|
||||
<details className={styles.details}>
|
||||
<summary className={styles.summary}>
|
||||
<hgroup className={styles.rewardHeader}>
|
||||
<Title as="h4" level="h2" textTransform={"regular"}>
|
||||
{name}
|
||||
</Title>
|
||||
<span className={styles.chevron}>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</hgroup>
|
||||
</summary>
|
||||
<p
|
||||
className={styles.rewardDescription}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
.table {
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
background-color: var(--UI-Opacity-White-100);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
color: var(--UI-Grey-100);
|
||||
}
|
||||
|
||||
.tr {
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
}
|
||||
|
||||
.tr:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.td {
|
||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.rewardTh {
|
||||
padding: var(--Spacing-x3) var(--Spacing-x2);
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
font-weight: var(--typography-Caption-Regular-fontWeight);
|
||||
}
|
||||
|
||||
.details[open] .chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.rewardHeader {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-columns: 1fr auto;
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.rewardDescription {
|
||||
margin: 0;
|
||||
padding-top: var(--Spacing-x1);
|
||||
text-align: start;
|
||||
padding-right: calc(var(--Spacing-x3) + var(--Spacing-x1));
|
||||
}
|
||||
|
||||
.chevron {
|
||||
display: flex;
|
||||
align-self: start;
|
||||
color: var(--UI-Grey-80);
|
||||
}
|
||||
|
||||
.summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summary {
|
||||
list-style: none;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import styles from "./levelSummary.module.css"
|
||||
|
||||
import type { LevelSummaryProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function LevelSummary({
|
||||
level,
|
||||
showDescription = true,
|
||||
}: LevelSummaryProps) {
|
||||
const intl = useIntl()
|
||||
|
||||
const pointsMsg: React.ReactNode = level.required_nights
|
||||
? intl.formatMessage(
|
||||
{
|
||||
id: "{pointsAmount, number} points or {nightsAmount, number} nights",
|
||||
},
|
||||
{
|
||||
pointsAmount: level.required_points,
|
||||
nightsAmount: level.required_nights,
|
||||
highlight: (str) => <span className={styles.redText}>{str}</span>,
|
||||
}
|
||||
)
|
||||
: intl.formatMessage(
|
||||
{ id: "{pointsAmount, number} points" },
|
||||
{ pointsAmount: level.required_points }
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={styles.levelSummary}>
|
||||
<span className={styles.levelRequirements}>{pointsMsg}</span>
|
||||
{showDescription && (
|
||||
<p className={styles.levelSummaryText}>{level.description}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.levelSummary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x3);
|
||||
padding-bottom: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.levelRequirements {
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
background-color: var(--Scandic-Brand-Pale-Peach);
|
||||
color: var(--Scandic-Peach-80);
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.levelSummaryText {
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
line-height: var(--typography-Body-Regular-lineHeight);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.levelRequirements {
|
||||
padding: var(--Spacing-x-half) var(--Spacing-x1);
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 1367px) {
|
||||
.levelRequirements {
|
||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||
}
|
||||
|
||||
.levelSummaryText {
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ChevronDown } from "react-feather"
|
||||
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
|
||||
import RewardValue from "../../RewardValue"
|
||||
|
||||
import styles from "./rewardCard.module.css"
|
||||
|
||||
import type { RewardCardProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function RewardCard({
|
||||
comparedValues,
|
||||
title,
|
||||
description,
|
||||
}: RewardCardProps) {
|
||||
return (
|
||||
<div className={styles.rewardCard}>
|
||||
<div className={styles.rewardInfo}>
|
||||
<details className={styles.details}>
|
||||
<summary className={styles.summary}>
|
||||
<hgroup className={styles.rewardCardHeader}>
|
||||
<Title as="h4" level="h2" textTransform={"regular"}>
|
||||
{title}
|
||||
</Title>
|
||||
<span className={styles.chevron}>
|
||||
<ChevronDown />
|
||||
</span>
|
||||
</hgroup>
|
||||
</summary>
|
||||
<p
|
||||
className={styles.rewardCardDescription}
|
||||
dangerouslySetInnerHTML={{ __html: description }}
|
||||
/>
|
||||
</details>
|
||||
</div>
|
||||
<div className={styles.rewardComparison}>
|
||||
{comparedValues.map((reward, idx) => (
|
||||
<div
|
||||
key={`${reward?.reward_id}-${idx}`}
|
||||
className={styles.comparisonItem}
|
||||
>
|
||||
<RewardValue reward={reward} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
.rewardCard {
|
||||
padding-bottom: var(--Spacing-x-one-and-half);
|
||||
grid-column: 1/3;
|
||||
}
|
||||
|
||||
.rewardCardHeader {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
}
|
||||
|
||||
.rewardCardDescription {
|
||||
font-size: var(--typography-Caption-Regular-fontSize);
|
||||
line-height: 150%;
|
||||
padding-right: var(--Spacing-x4);
|
||||
}
|
||||
|
||||
.rewardInfo {
|
||||
padding-bottom: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.rewardComparison {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.comparisonItem {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: var(--Spacing-x-one-and-half);
|
||||
}
|
||||
|
||||
.details[open] .chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.chevron {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--UI-Grey-80);
|
||||
}
|
||||
|
||||
.summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summary {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.rewardComparison {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
findAvailableRewards,
|
||||
getGroupedLabelAndDescription,
|
||||
getGroupedRewards,
|
||||
} from "@/utils/loyaltyTable"
|
||||
|
||||
import RewardCard from "./Card"
|
||||
|
||||
import styles from "./rewardList.module.css"
|
||||
|
||||
import type { RewardListProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function RewardList({ levels }: RewardListProps) {
|
||||
const keyedGroupedRewards = getGroupedRewards(levels)
|
||||
|
||||
return Object.values(keyedGroupedRewards).map((groupedRewards) => {
|
||||
const rewardIdsInGroup = groupedRewards.map((b) => b.reward_id)
|
||||
|
||||
const { label, description } = getGroupedLabelAndDescription(groupedRewards)
|
||||
|
||||
const levelRewards = levels.map((level) => {
|
||||
return findAvailableRewards(rewardIdsInGroup, level)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
key={levelRewards[0]?.reward_id ?? ""}
|
||||
className={styles.rewardCardWrapper}
|
||||
>
|
||||
<RewardCard
|
||||
title={label}
|
||||
description={description}
|
||||
comparedValues={levelRewards}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.rewardCardWrapper {
|
||||
border-bottom: 1px solid var(--Base-Border-Subtle);
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-column: 1/3;
|
||||
padding-top: 0;
|
||||
margin: var(--Spacing-x1) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.rewardCardWrapper:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.rewardCardWrapper {
|
||||
grid-column: 1/4;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Minus } from "react-feather"
|
||||
|
||||
import CheckCircle from "@/components/Icons/CheckCircle"
|
||||
|
||||
import styles from "./rewardValue.module.css"
|
||||
|
||||
import type { RewardValueProps } from "@/types/components/overviewTable"
|
||||
|
||||
export default function RewardValue({ reward }: RewardValueProps) {
|
||||
if (!reward) {
|
||||
return <Minus color="var(--UI-Grey-40)" />
|
||||
}
|
||||
if (!reward.value) {
|
||||
return <CheckCircle height={32} width={32} color="green" />
|
||||
}
|
||||
return (
|
||||
<div className={styles.rewardValueContainer}>
|
||||
<span className={styles.rewardValue}>{reward.value}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.rewardValueContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--Spacing-x-half);
|
||||
padding: 0 var(--Spacing-x4) 0 var(--Spacing-x4);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.rewardValue {
|
||||
font-size: var(--typography-Body-Bold-fontSize);
|
||||
font-weight: var(--typography-Body-Bold-fontWeight);
|
||||
}
|
||||
|
||||
.rewardValueDetails {
|
||||
font-size: var(--typography-Footnote-Regular-fontSize);
|
||||
text-align: center;
|
||||
color: var(--UI-Grey-80);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { getMembershipLevelSafely } from "@/lib/trpc/memoizedRequests"
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import SectionWrapper from "../SectionWrapper"
|
||||
import OverviewTableClient from "./Client"
|
||||
|
||||
import type { OverviewTableProps } from "@/types/components/blocks/dynamicContent"
|
||||
|
||||
export default async function OverviewTable({
|
||||
dynamic_content,
|
||||
firstItem,
|
||||
}: OverviewTableProps) {
|
||||
const [levels, membershipLevel] = await Promise.all([
|
||||
serverClient().contentstack.rewards.all(),
|
||||
getMembershipLevelSafely(),
|
||||
])
|
||||
|
||||
return (
|
||||
<SectionWrapper dynamic_content={dynamic_content} firstItem={firstItem}>
|
||||
<OverviewTableClient
|
||||
levels={levels}
|
||||
activeMembership={membershipLevel?.membershipLevel ?? null}
|
||||
/>
|
||||
</SectionWrapper>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
.intro {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x3);
|
||||
}
|
||||
|
||||
.largeTableContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: none;
|
||||
position: relative;
|
||||
background-color: var(--UI-Opacity-White-100);
|
||||
border-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
|
||||
.mobileColumns {
|
||||
background-color: var(--UI-Opacity-White-100);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
margin: 0 calc(0px - var(--Spacing-x2)) calc(0px - var(--Spacing-x9))
|
||||
calc(0px - var(--Spacing-x2));
|
||||
padding-bottom: var(--Spacing-x9);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.columnHeaderContainer {
|
||||
display: contents;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.columnHeader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x2);
|
||||
padding: var(--Spacing-x4) var(--Spacing-x2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.columnHeader:nth-child(1) {
|
||||
padding-right: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.columnHeader:nth-child(2) {
|
||||
padding-left: var(--Spacing-x1);
|
||||
border-top-left-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
|
||||
.columnHeader:nth-child(2):has(+ .columnHeader) {
|
||||
padding-left: var(--Spacing-x1);
|
||||
padding-right: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
.columnHeader:nth-child(3) {
|
||||
padding-left: var(--Spacing-x1);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.mobileColumns {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.mobileColumns {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.columnHeaderContainer {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
.columnHeader:nth-child(2) {
|
||||
border-top-right-radius: var(--Corner-radius-Medium);
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1367px) {
|
||||
.columns {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.intro {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.largeTableContainer {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import {
|
||||
type MembershipLevel,
|
||||
MembershipLevelEnum,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import { getSteppedUpLevel } from "@/utils/user"
|
||||
|
||||
import {
|
||||
type LevelWithRewards,
|
||||
OverviewTableActionsEnum,
|
||||
type OverviewTableClientProps,
|
||||
type OverviewTableReducerAction,
|
||||
} from "@/types/components/overviewTable"
|
||||
|
||||
export function getLevel(
|
||||
membershipLevel: MembershipLevel,
|
||||
levels: LevelWithRewards[]
|
||||
) {
|
||||
return levels.find((level) => level.level_id === membershipLevel)!
|
||||
}
|
||||
|
||||
export function getInitialState({
|
||||
activeMembership,
|
||||
levels,
|
||||
}: OverviewTableClientProps) {
|
||||
if (!activeMembership) {
|
||||
return {
|
||||
selectedLevelAMobile: getLevel(MembershipLevelEnum.L1, levels),
|
||||
selectedLevelBMobile: getLevel(MembershipLevelEnum.L2, levels),
|
||||
selectedLevelADesktop: getLevel(MembershipLevelEnum.L1, levels),
|
||||
selectedLevelBDesktop: getLevel(MembershipLevelEnum.L2, levels),
|
||||
selectedLevelCDesktop: getLevel(MembershipLevelEnum.L3, levels),
|
||||
}
|
||||
}
|
||||
const level = MembershipLevelEnum[activeMembership]
|
||||
|
||||
switch (level) {
|
||||
case MembershipLevelEnum.L6:
|
||||
return {
|
||||
selectedLevelAMobile: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelBMobile: getLevel(MembershipLevelEnum.L7, levels),
|
||||
selectedLevelADesktop: getLevel(MembershipLevelEnum.L5, levels),
|
||||
selectedLevelBDesktop: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelCDesktop: getLevel(MembershipLevelEnum.L7, levels),
|
||||
}
|
||||
case MembershipLevelEnum.L7:
|
||||
return {
|
||||
selectedLevelAMobile: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelBMobile: getLevel(MembershipLevelEnum.L7, levels),
|
||||
selectedLevelADesktop: getLevel(MembershipLevelEnum.L6, levels),
|
||||
selectedLevelBDesktop: getLevel(MembershipLevelEnum.L7, levels),
|
||||
selectedLevelCDesktop: getLevel(MembershipLevelEnum.L1, levels),
|
||||
}
|
||||
default:
|
||||
return {
|
||||
selectedLevelAMobile: getLevel(level, levels),
|
||||
selectedLevelBMobile: getLevel(getSteppedUpLevel(level, 1), levels),
|
||||
selectedLevelADesktop: getLevel(level, levels),
|
||||
selectedLevelBDesktop: getLevel(getSteppedUpLevel(level, 1), levels),
|
||||
selectedLevelCDesktop: getLevel(getSteppedUpLevel(level, 2), levels),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function reducer(state: any, action: OverviewTableReducerAction) {
|
||||
switch (action.type) {
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_A_MOBILE:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelAMobile: action.payload,
|
||||
}
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_B_MOBILE:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelBMobile: action.payload,
|
||||
}
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_A_DESKTOP:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelADesktop: action.payload,
|
||||
}
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_B_DESKTOP:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelBDesktop: action.payload,
|
||||
}
|
||||
case OverviewTableActionsEnum.SET_SELECTED_LEVEL_C_DESKTOP:
|
||||
return {
|
||||
...state,
|
||||
selectedLevelCDesktop: action.payload,
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user