Merged in feat/best-friend-hero (pull request #338)
Feat(SW-170): Update overview hero Approved-by: Christel Westerberg
This commit is contained in:
36
components/Icons/Copy.tsx
Normal file
36
components/Icons/Copy.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { iconVariants } from "./variants"
|
||||
|
||||
import type { IconProps } from "@/types/components/icon"
|
||||
|
||||
export default function CopyIcon({ className, color, ...props }: IconProps) {
|
||||
const classNames = iconVariants({ className, color })
|
||||
return (
|
||||
<svg
|
||||
className={classNames}
|
||||
fill="none"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<mask
|
||||
height="20"
|
||||
id="mask0_1572_4523"
|
||||
maskUnits="userSpaceOnUse"
|
||||
style={{ maskType: "alpha" }}
|
||||
width="20"
|
||||
x="0"
|
||||
y="0"
|
||||
>
|
||||
<rect width="20" height="20" fill="#D9D9D9" />
|
||||
</mask>
|
||||
<g mask="url(#mask0_1572_4523)">
|
||||
<path
|
||||
d="M7.54804 15.4742C7.17293 15.4742 6.85587 15.3447 6.59685 15.0857C6.33783 14.8267 6.20831 14.5096 6.20831 14.1345V4.64737C6.20831 4.27226 6.33783 3.95519 6.59685 3.69616C6.85587 3.43713 7.17293 3.30762 7.54804 3.30762H15.0352C15.4103 3.30762 15.7273 3.43713 15.9864 3.69616C16.2454 3.95519 16.3749 4.27226 16.3749 4.64737V14.1345C16.3749 14.5096 16.2454 14.8267 15.9864 15.0857C15.7273 15.3447 15.4103 15.4742 15.0352 15.4742H7.54804ZM7.54804 14.3909H15.0352C15.0993 14.3909 15.158 14.3642 15.2115 14.3108C15.2649 14.2574 15.2916 14.1986 15.2916 14.1345V4.64737C15.2916 4.58326 15.2649 4.52449 15.2115 4.47106C15.158 4.41764 15.0993 4.39093 15.0352 4.39093H7.54804C7.48393 4.39093 7.42517 4.41764 7.37175 4.47106C7.31832 4.52449 7.2916 4.58326 7.2916 4.64737V14.1345C7.2916 14.1986 7.31832 14.2574 7.37175 14.3108C7.42517 14.3642 7.48393 14.3909 7.54804 14.3909ZM4.96473 18.0575C4.58963 18.0575 4.27257 17.928 4.01354 17.669C3.75451 17.41 3.625 17.0929 3.625 16.7178V6.68901C3.625 6.53528 3.67642 6.40657 3.77927 6.30289C3.8821 6.19921 4.00977 6.14737 4.16227 6.14737C4.31476 6.14737 4.44389 6.19921 4.54967 6.30289C4.65543 6.40657 4.70831 6.53528 4.70831 6.68901V16.7178C4.70831 16.7819 4.73502 16.8407 4.78844 16.8941C4.84187 16.9475 4.90063 16.9742 4.96473 16.9742H12.9935C13.1473 16.9742 13.276 17.0257 13.3796 17.1285C13.4833 17.2313 13.5352 17.359 13.5352 17.5115C13.5352 17.664 13.4833 17.7931 13.3796 17.8989C13.276 18.0047 13.1473 18.0575 12.9935 18.0575H4.96473Z"
|
||||
fill="#060606"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import SoonestStays from "@/components/MyPages/Blocks/Stays/Soonest"
|
||||
import UpcomingStays from "@/components/MyPages/Blocks/Stays/Upcoming"
|
||||
import { removeMultipleSlashes } from "@/utils/url"
|
||||
|
||||
import PointsOverview from "../Blocks/Points/Overview"
|
||||
|
||||
import {
|
||||
AccountPageContentProps,
|
||||
ContentProps,
|
||||
@@ -23,6 +25,8 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
|
||||
switch (component) {
|
||||
case DynamicContentComponents.membership_overview:
|
||||
return <Overview {...props} />
|
||||
case DynamicContentComponents.points_overview:
|
||||
return <PointsOverview {...props} />
|
||||
case DynamicContentComponents.previous_stays:
|
||||
return <PreviousStays {...props} />
|
||||
case DynamicContentComponents.soonest_stays:
|
||||
|
||||
@@ -8,6 +8,7 @@ import CurrentBenefitsBlock from "../../Blocks/Benefits/CurrentLevel"
|
||||
import NextLevelBenefitsBlock from "../../Blocks/Benefits/NextLevel"
|
||||
import CurrentPointsBalance from "../../Blocks/Points/CurrentPointsBalance"
|
||||
import EarnAndBurn from "../../Blocks/Points/EarnAndBurn"
|
||||
import PointsOverview from "../../Blocks/Points/Overview"
|
||||
|
||||
import {
|
||||
AccountPageContentProps,
|
||||
@@ -21,14 +22,9 @@ import {
|
||||
function DynamicComponent({ component, props }: AccountPageContentProps) {
|
||||
switch (component) {
|
||||
case DynamicContentComponents.membership_overview:
|
||||
return (
|
||||
<Overview
|
||||
lang={props.lang}
|
||||
link={props.link}
|
||||
subtitle={null}
|
||||
title={props.title}
|
||||
/>
|
||||
)
|
||||
return <Overview {...props} />
|
||||
case DynamicContentComponents.points_overview:
|
||||
return <PointsOverview {...props} />
|
||||
case DynamicContentComponents.current_benefits:
|
||||
return <CurrentBenefitsBlock {...props} />
|
||||
case DynamicContentComponents.next_benefits:
|
||||
|
||||
@@ -8,7 +8,6 @@ import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
import Chip from "@/components/TempDesignSystem/Chip"
|
||||
import Grids from "@/components/TempDesignSystem/Grids"
|
||||
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||
import { getIntl } from "@/i18n"
|
||||
@@ -30,9 +29,8 @@ export default async function NextLevelBenefitsBlock({
|
||||
return null
|
||||
}
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
user.memberships[0].membershipLevel as MembershipLevelEnum,
|
||||
lang,
|
||||
"nextLevel"
|
||||
user.memberships[0].nextLevel as MembershipLevelEnum,
|
||||
lang
|
||||
)
|
||||
if (!nextLevel) {
|
||||
// TODO: handle this case, when missing or when user is top level?
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
"use client"
|
||||
|
||||
import Image from "@/components/Image"
|
||||
import CopyIcon from "@/components/Icons/Copy"
|
||||
import Button from "@/components/TempDesignSystem/Button"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import type { User } from "@/types/user"
|
||||
import styles from "./copybutton.module.css"
|
||||
|
||||
export default function CopyButton({ memberships }: Pick<User, "memberships">) {
|
||||
import type { CopyButtonProps } from "@/types/components/myPages/membership"
|
||||
|
||||
export default function CopyButton({ membershipNumber }: CopyButtonProps) {
|
||||
function handleCopy() {
|
||||
const membership = getMembership(memberships)
|
||||
console.log(`COPIED! (${membership ? membership.membershipNumber : "N/A"})`)
|
||||
navigator.clipboard.writeText(membershipNumber)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={handleCopy} type="button" variant="icon">
|
||||
<Image
|
||||
alt="Copy Icon"
|
||||
height={20}
|
||||
src="/_static/icons/copy.svg"
|
||||
width={20}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleCopy}
|
||||
className={styles.button}
|
||||
type="button"
|
||||
variant="icon"
|
||||
size="small"
|
||||
intent="tertiary"
|
||||
>
|
||||
<CopyIcon color="pale" />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
.hero {
|
||||
border-radius: var(--Corner-radius-xLarge);
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-columns: 1fr;
|
||||
padding: var(--Spacing-x7) var(--Spacing-x6);
|
||||
}
|
||||
|
||||
.burgundy {
|
||||
background-color: var(--Scandic-Brand-Burgundy);
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
7
components/MyPages/Blocks/Overview/Friend/Hero/hero.ts
Normal file
7
components/MyPages/Blocks/Overview/Friend/Hero/hero.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { VariantProps } from "class-variance-authority"
|
||||
|
||||
import { heroVariants } from "./heroVariants"
|
||||
|
||||
export interface HeroProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, "color">,
|
||||
VariantProps<typeof heroVariants> {}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./hero.module.css"
|
||||
|
||||
export const heroVariants = cva(styles.hero, {
|
||||
variants: {
|
||||
color: {
|
||||
burgundy: styles.burgundy,
|
||||
red: styles.red,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "red",
|
||||
},
|
||||
})
|
||||
7
components/MyPages/Blocks/Overview/Friend/Hero/index.tsx
Normal file
7
components/MyPages/Blocks/Overview/Friend/Hero/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { HeroProps } from "./hero"
|
||||
import { heroVariants } from "./heroVariants"
|
||||
|
||||
export default function Hero({ className, color, children }: HeroProps) {
|
||||
const classNames = heroVariants({ className, color })
|
||||
return <section className={classNames}>{children}</section>
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
|
||||
import styles from "./membershipLevel.module.css"
|
||||
|
||||
import type { MembershipLevelProps } from "@/types/components/myPages/membershipLevel"
|
||||
import type { MembershipLevelProps } from "@/types/components/myPages/membership"
|
||||
|
||||
export default function MembershipLevel({ level }: MembershipLevelProps) {
|
||||
switch (level) {
|
||||
@@ -0,0 +1,34 @@
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import CopyButton from "../../Buttons/CopyButton"
|
||||
import { MembershipNumberProps } from "./membershipNumber"
|
||||
import { membershipNumberVariants } from "./membershipNumberVariants"
|
||||
|
||||
import styles from "./membershipNumber.module.css"
|
||||
|
||||
export default async function MembershipNumber({
|
||||
className,
|
||||
color,
|
||||
membership,
|
||||
}: MembershipNumberProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
const classNames = membershipNumberVariants({ className, color })
|
||||
|
||||
return (
|
||||
<div className={classNames}>
|
||||
<Caption color="pale">
|
||||
{formatMessage({ id: "Membership ID" })}
|
||||
{": "}
|
||||
</Caption>
|
||||
<span className={styles.icon}>
|
||||
<Caption className={styles.icon} color="pale">
|
||||
{membership.membershipNumber ?? "N/A"}
|
||||
</Caption>
|
||||
{membership && (
|
||||
<CopyButton membershipNumber={membership.membershipNumber} />
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.membershipContainer {
|
||||
align-items: center;
|
||||
background: var(--Scandic-Brand-Burgundy);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: center;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x7) 0 var(--Spacing-x7);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding-left: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.burgundy {
|
||||
background-color: var(--Main-Brand-Burgundy);
|
||||
}
|
||||
|
||||
.red {
|
||||
background-color: var(--Scandic-Brand-Scandic-Red);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.membershipContainer {
|
||||
grid-template-columns: auto auto;
|
||||
padding: 0 0 0 var(--Spacing-x2);
|
||||
gap: var(--Spacing-x-half);
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { VariantProps } from "class-variance-authority"
|
||||
|
||||
import { membershipNumberVariants } from "./membershipNumberVariants"
|
||||
|
||||
import { User } from "@/types/user"
|
||||
|
||||
export interface MembershipNumberProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, "color">,
|
||||
VariantProps<typeof membershipNumberVariants> {
|
||||
membership: User["memberships"][number]
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./membershipNumber.module.css"
|
||||
|
||||
export const membershipNumberVariants = cva(styles.membershipContainer, {
|
||||
variants: {
|
||||
color: {
|
||||
burgundy: styles.burgundy,
|
||||
red: styles.red,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "burgundy",
|
||||
},
|
||||
})
|
||||
@@ -9,7 +9,7 @@
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--Spacing-x1);
|
||||
gap: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
.levelLabel {
|
||||
@@ -35,14 +35,7 @@
|
||||
background: var(--Scandic-Brand-Burgundy);
|
||||
border-radius: var(--Corner-radius-Small);
|
||||
display: grid;
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-columns: 1fr;
|
||||
justify-items: center;
|
||||
padding: var(--Spacing-x1) var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.membershipContainer {
|
||||
grid-template-columns: auto auto;
|
||||
}
|
||||
padding: var(--Spacing-x1) var(--Spacing-x7) 0 var(--Spacing-x7);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
import { membershipLevels } from "@/constants/membershipLevels"
|
||||
|
||||
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
||||
import Caption from "@/components/TempDesignSystem/Text/Caption"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getMembership } from "@/utils/user"
|
||||
import { getMembership, isHighestMembership } from "@/utils/user"
|
||||
|
||||
import MembershipLevel from "./MemberShipLevel"
|
||||
import { MembershipNumberProps } from "./MemershipNumber/membershipNumber"
|
||||
import MembershipLevel from "./MembershipLevel"
|
||||
import MembershipNumber from "./MemershipNumber"
|
||||
|
||||
import styles from "./friend.module.css"
|
||||
|
||||
import type { UserProps } from "@/types/components/myPages/user"
|
||||
|
||||
export default async function Friend({ user }: UserProps) {
|
||||
export default async function Friend({
|
||||
user,
|
||||
color,
|
||||
}: UserProps & Pick<MembershipNumberProps, "color">) {
|
||||
const { formatMessage } = await getIntl()
|
||||
const membership = getMembership(user.memberships)
|
||||
if (!membership?.membershipLevel) {
|
||||
return null
|
||||
}
|
||||
const isHighestLevel = isHighestMembership(membership.membershipLevel)
|
||||
|
||||
return (
|
||||
<section className={styles.friend}>
|
||||
<header className={styles.header}>
|
||||
<BiroScript className={styles.levelLabel} color="pale">
|
||||
{formatMessage({ id: "Current level" })}:
|
||||
</BiroScript>
|
||||
<Body color="white" textTransform="bold" textAlign="center">
|
||||
{formatMessage(
|
||||
isHighestLevel
|
||||
? { id: "Highest level" }
|
||||
: { id: "Your current level" }
|
||||
)}
|
||||
</Body>
|
||||
{membership ? (
|
||||
<MembershipLevel
|
||||
level={membershipLevels[membership.membershipLevel]}
|
||||
@@ -35,12 +44,7 @@ export default async function Friend({ user }: UserProps) {
|
||||
<Title className={styles.name} color="pale" level="h3">
|
||||
{user.name}
|
||||
</Title>
|
||||
<div className={styles.membershipContainer}>
|
||||
<Caption color="pale">
|
||||
{formatMessage({ id: "Membership ID" })}:{" "}
|
||||
{membership ? membership.membershipNumber : "N/A"}
|
||||
</Caption>
|
||||
</div>
|
||||
<MembershipNumber membership={membership} color={color} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import type { UserProps } from "@/types/components/myPages/user"
|
||||
|
||||
export default async function ExpiringPoints({ user }: UserProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
const membership = getMembership(user.memberships)
|
||||
// TODO - add correct points when available from API
|
||||
if (!membership /* || !membership.expiringPoints*/) {
|
||||
// TODO: handle this case?
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
<Body color="white" textTransform="bold" textAlign="center">
|
||||
{membership.currentPoints} {formatMessage({ id: "points expiring by" })}{" "}
|
||||
{membership.expirationDate}
|
||||
</Body>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import BiroScript from "@/components/TempDesignSystem/Text/BiroScript"
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import styles from "./nextLevel.module.css"
|
||||
|
||||
import type { UserProps } from "@/types/components/myPages/user"
|
||||
import type { LangParams } from "@/types/params"
|
||||
|
||||
export default async function NextLevel({
|
||||
user,
|
||||
lang,
|
||||
}: UserProps & LangParams) {
|
||||
const { formatMessage } = await getIntl()
|
||||
const membership = getMembership(user.memberships)
|
||||
|
||||
if (!membership?.membershipLevel) {
|
||||
// TODO: handle this case?
|
||||
return null
|
||||
}
|
||||
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
membership.membershipLevel,
|
||||
lang,
|
||||
"nextLevel"
|
||||
)
|
||||
if (!nextLevel) {
|
||||
// TODO: already at top level, no next level exists
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<section>
|
||||
<Body color="white" textAlign="center">
|
||||
{formatMessage({ id: "Next level" })}:
|
||||
</Body>
|
||||
<Title
|
||||
className={styles.nextLevel}
|
||||
color="white"
|
||||
level="h3"
|
||||
textAlign="center"
|
||||
>
|
||||
{nextLevel?.name || "N/A"}
|
||||
<BiroScript>{formatMessage({ id: "Coming up" })}!</BiroScript>
|
||||
</Title>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
.nextLevel {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.nextLevel {
|
||||
gap: var(--Spacing-x1);
|
||||
grid-template-columns: auto auto;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.points {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x5);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.points {
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
row-gap: 0;
|
||||
column-gap: var(--Spacing-x2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import styles from "./container.module.css"
|
||||
|
||||
export default function PointsContainer({ children }: React.PropsWithChildren) {
|
||||
return <section className={styles.points}>{children}</section>
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./pointsColumn.module.css"
|
||||
|
||||
import type {
|
||||
NightsColumn,
|
||||
PointsColumn,
|
||||
PointsColumnProps,
|
||||
} from "@/types/components/myPages/points"
|
||||
|
||||
export const YourPointsColumn = ({ points }: PointsColumn) =>
|
||||
PointsColumn({
|
||||
points,
|
||||
title: "Your points",
|
||||
subtitle: "as of today",
|
||||
})
|
||||
|
||||
export const NextLevelPointsColumn = ({
|
||||
points,
|
||||
subtitleParam,
|
||||
}: PointsColumn) =>
|
||||
PointsColumn({
|
||||
points,
|
||||
title: "Points needed to level up",
|
||||
subtitleParam,
|
||||
subtitle: "next level:",
|
||||
})
|
||||
|
||||
export const StayOnLevelColumn = ({ points, subtitleParam }: PointsColumn) =>
|
||||
PointsColumn({
|
||||
points,
|
||||
title: "Points needed to stay on level",
|
||||
subtitleParam,
|
||||
subtitle: "by",
|
||||
})
|
||||
|
||||
export const NextLevelNightsColumn = ({
|
||||
nights,
|
||||
subtitleParam,
|
||||
}: NightsColumn) =>
|
||||
PointsColumn({
|
||||
nights,
|
||||
title: "Nights needed to level up",
|
||||
subtitleParam,
|
||||
subtitle: "by",
|
||||
})
|
||||
|
||||
async function PointsColumn({
|
||||
points,
|
||||
nights,
|
||||
title,
|
||||
subtitle,
|
||||
subtitleParam,
|
||||
}: PointsColumnProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
|
||||
return (
|
||||
<article>
|
||||
<Body
|
||||
color="white"
|
||||
textTransform="bold"
|
||||
textAlign="center"
|
||||
className={styles.firstRow}
|
||||
>
|
||||
{formatMessage({
|
||||
id: title,
|
||||
})}
|
||||
</Body>
|
||||
<Title color="white" level="h2" textAlign="center">
|
||||
{points ?? nights ?? "N/A"}
|
||||
</Title>
|
||||
<Body color="white" textAlign="center">
|
||||
{formatMessage({ id: subtitle })} {subtitleParam}
|
||||
</Body>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@media screen and (min-width: 768px) {
|
||||
.firstRow {
|
||||
align-content: flex-end;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,30 @@
|
||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||
import Title from "@/components/TempDesignSystem/Text/Title"
|
||||
import { getIntl } from "@/i18n"
|
||||
import { MembershipLevelEnum } from "@/constants/membershipLevels"
|
||||
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import styles from "./totalPoints.module.css"
|
||||
import PointsContainer from "./Container"
|
||||
import { NextLevelPointsColumn, YourPointsColumn } from "./PointsColumn"
|
||||
|
||||
import type { UserProps } from "@/types/components/myPages/user"
|
||||
import { UserProps } from "@/types/components/myPages/user"
|
||||
import { LangParams } from "@/types/params"
|
||||
|
||||
export default async function Points({ user }: UserProps) {
|
||||
const { formatMessage } = await getIntl()
|
||||
export default async function Points({ user, lang }: UserProps & LangParams) {
|
||||
const membership = getMembership(user.memberships)
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
membership?.nextLevel as MembershipLevelEnum,
|
||||
lang
|
||||
)
|
||||
|
||||
return (
|
||||
<section className={styles.points}>
|
||||
<article>
|
||||
<Body color="white" textAlign="center">
|
||||
{formatMessage({ id: "Total Points" })}
|
||||
</Body>
|
||||
<Title color="white" level="h2" textAlign="center">
|
||||
{membership ? membership.currentPoints : "N/A"}
|
||||
</Title>
|
||||
</article>
|
||||
<article>
|
||||
<Body color="white" textAlign="center">
|
||||
{formatMessage({ id: "Points until next level" })}
|
||||
{/* TODO */}
|
||||
</Body>
|
||||
<Title color="white" level="h2" textAlign="center">
|
||||
{membership ? membership.currentPoints : "N/A"}
|
||||
</Title>
|
||||
</article>
|
||||
</section>
|
||||
<PointsContainer>
|
||||
<YourPointsColumn points={membership?.currentPoints} />
|
||||
{nextLevel && (
|
||||
<NextLevelPointsColumn
|
||||
points={membership?.pointsRequiredToNextlevel}
|
||||
subtitleParam={nextLevel.name}
|
||||
/>
|
||||
)}
|
||||
</PointsContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
.points {
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.points {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
|
||||
import NextLevel from "./NextLevel"
|
||||
import ExpiringPoints from "./ExpiringPoints"
|
||||
import Points from "./Points"
|
||||
|
||||
import styles from "./stats.module.css"
|
||||
@@ -11,9 +11,9 @@ import type { LangParams } from "@/types/params"
|
||||
export default function Stats({ user, lang }: UserProps & LangParams) {
|
||||
return (
|
||||
<section className={styles.stats}>
|
||||
<Points user={user} />
|
||||
<Divider variant="default" color="white" />
|
||||
<NextLevel user={user} lang={lang} />
|
||||
<Points user={user} lang={lang} />
|
||||
<Divider variant="default" color="pale" />
|
||||
<ExpiringPoints user={user} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
|
||||
import Hero from "./Friend/Hero"
|
||||
import Friend from "./Friend"
|
||||
import Stats from "./Stats"
|
||||
|
||||
@@ -23,14 +24,15 @@ export default async function Overview({
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader link={link} subtitle={subtitle} title={title} topTitle />
|
||||
<section className={styles.overview}>
|
||||
<Friend user={user} />
|
||||
<Hero color="red">
|
||||
<Friend user={user} color="burgundy" />
|
||||
<Divider className={styles.divider} color="peach" />
|
||||
<Stats user={user} lang={lang} />
|
||||
</section>
|
||||
</Hero>
|
||||
<SectionLink link={link} variant="mobile" />
|
||||
</SectionContainer>
|
||||
)
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
.overview {
|
||||
background-color: var(--Scandic-Brand-Scandic-Red);
|
||||
border-radius: var(--Corner-radius-xLarge);
|
||||
display: grid;
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-columns: 1fr;
|
||||
padding: var(--Spacing-x7) var(--Spacing-x6);
|
||||
}
|
||||
|
||||
.divider {
|
||||
padding-top: var(--Spacing-x2);
|
||||
}
|
||||
@@ -25,11 +16,6 @@
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.overview {
|
||||
gap: var(--Spacing-x2);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ async function CurrentPointsBalance({
|
||||
<SectionContainer>
|
||||
<SectionHeader title={title} link={link} subtitle={subtitle} />
|
||||
<div className={styles.card}>
|
||||
<h2>{`${formatMessage({ id: "Total Points" })}*`}</h2>
|
||||
<h2>{`${formatMessage({ id: "Your points" })}*`}</h2>
|
||||
<p className={styles.points}>
|
||||
{`${formatMessage({ id: "Points" })}: ${membership ? membership.currentPoints : "N/A"}`}
|
||||
</p>
|
||||
|
||||
55
components/MyPages/Blocks/Points/Overview/Points/index.tsx
Normal file
55
components/MyPages/Blocks/Points/Overview/Points/index.tsx
Normal file
@@ -0,0 +1,55 @@
|
||||
import {
|
||||
MembershipLevelEnum,
|
||||
membershipLevels,
|
||||
} from "@/constants/membershipLevels"
|
||||
|
||||
import PointsContainer from "@/components/MyPages/Blocks/Overview/Stats/Points/Container"
|
||||
import {
|
||||
NextLevelNightsColumn,
|
||||
NextLevelPointsColumn,
|
||||
StayOnLevelColumn,
|
||||
YourPointsColumn,
|
||||
} from "@/components/MyPages/Blocks/Overview/Stats/Points/PointsColumn"
|
||||
import { getMembershipLevelObject } from "@/utils/membershipLevel"
|
||||
import { getMembership } from "@/utils/user"
|
||||
|
||||
import { UserProps } from "@/types/components/myPages/user"
|
||||
import { LangParams } from "@/types/params"
|
||||
|
||||
/* TODO */
|
||||
export default async function Points({ user, lang }: UserProps & LangParams) {
|
||||
const membership = getMembership(user.memberships)
|
||||
const nextLevel = getMembershipLevelObject(
|
||||
membership?.nextLevel as MembershipLevelEnum,
|
||||
lang
|
||||
)
|
||||
|
||||
return (
|
||||
<PointsContainer>
|
||||
<YourPointsColumn points={membership?.currentPoints} />
|
||||
{nextLevel && (
|
||||
<>
|
||||
{membership?.currentPoints ? (
|
||||
<StayOnLevelColumn
|
||||
points={membership?.currentPoints} //TODO
|
||||
subtitleParam={membership?.expirationDate}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<NextLevelPointsColumn
|
||||
points={membership?.pointsRequiredToNextlevel}
|
||||
subtitleParam={nextLevel.name}
|
||||
/>
|
||||
{nextLevel?.level === membershipLevels.L7 && (
|
||||
<NextLevelNightsColumn
|
||||
nights={100} //TODO
|
||||
subtitleParam={membership?.expirationDate}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PointsContainer>
|
||||
)
|
||||
}
|
||||
39
components/MyPages/Blocks/Points/Overview/index.tsx
Normal file
39
components/MyPages/Blocks/Points/Overview/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import { serverClient } from "@/lib/trpc/server"
|
||||
|
||||
import SectionContainer from "@/components/Section/Container"
|
||||
import SectionHeader from "@/components/Section/Header"
|
||||
import SectionLink from "@/components/Section/Link"
|
||||
import Divider from "@/components/TempDesignSystem/Divider"
|
||||
|
||||
import Friend from "../../Overview/Friend"
|
||||
import Hero from "../../Overview/Friend/Hero"
|
||||
import Stats from "../../Overview/Stats"
|
||||
|
||||
import styles from "./overview.module.css"
|
||||
|
||||
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
|
||||
import type { LangParams } from "@/types/params"
|
||||
|
||||
export default async function PointsOverview({
|
||||
link,
|
||||
subtitle,
|
||||
title,
|
||||
lang,
|
||||
}: AccountPageComponentProps & LangParams) {
|
||||
const user = await serverClient().user.get()
|
||||
if (!user) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<SectionHeader link={link} subtitle={subtitle} title={title} topTitle />
|
||||
<Hero color="burgundy">
|
||||
<Friend user={user} color="red" />
|
||||
<Divider className={styles.divider} color="peach" />
|
||||
<Stats user={user} lang={lang} />
|
||||
</Hero>
|
||||
<SectionLink link={link} variant="mobile" />
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
.divider {
|
||||
padding-top: var(--Spacing-x2);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.divider {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,10 @@
|
||||
border-bottom-color: var(--Scandic-Brand-Burgundy);
|
||||
}
|
||||
|
||||
.pale {
|
||||
border-bottom-color: var(--Primary-Dark-On-Surface-Text);
|
||||
}
|
||||
|
||||
.peach {
|
||||
border-bottom-color: var(--Primary-Light-On-Surface-Divider);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export const dividerVariants = cva(styles.divider, {
|
||||
beige: styles.beige,
|
||||
white: styles.white,
|
||||
subtle: styles.subtle,
|
||||
pale: styles.pale,
|
||||
},
|
||||
opacity: {
|
||||
100: styles.opacity100,
|
||||
|
||||
Reference in New Issue
Block a user