Merged in feat/CJ-17-points-expiration-table (pull request #527)
Feat/CJ-17 points expiration table * feat(CJ-17): Added point expiration table and refactored to use Table component * feat(CJ-17): Use Table component inside Row * fix(CJ-117): Added missing css class and update date formatting * fix(CJ-117): Added copy of membershipLevel route with a protectedProcedure Approved-by: Christel Westerberg
This commit is contained in:
@@ -19,7 +19,7 @@ import type {
|
|||||||
import { LoyaltyComponentEnum } from "@/types/components/loyalty/enums"
|
import { LoyaltyComponentEnum } from "@/types/components/loyalty/enums"
|
||||||
|
|
||||||
async function DynamicComponentBlock({ component }: DynamicComponentProps) {
|
async function DynamicComponentBlock({ component }: DynamicComponentProps) {
|
||||||
const membershipLevel = await serverClient().user.membershipLevel()
|
const membershipLevel = await serverClient().user.safeMembershipLevel()
|
||||||
switch (component) {
|
switch (component) {
|
||||||
case LoyaltyComponentEnum.how_it_works:
|
case LoyaltyComponentEnum.how_it_works:
|
||||||
return <HowItWorks />
|
return <HowItWorks />
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import CurrentBenefitsBlock from "@/components/MyPages/Blocks/Benefits/CurrentLe
|
|||||||
import NextLevelBenefitsBlock from "@/components/MyPages/Blocks/Benefits/NextLevel"
|
import NextLevelBenefitsBlock from "@/components/MyPages/Blocks/Benefits/NextLevel"
|
||||||
import Overview from "@/components/MyPages/Blocks/Overview"
|
import Overview from "@/components/MyPages/Blocks/Overview"
|
||||||
import EarnAndBurn from "@/components/MyPages/Blocks/Points/EarnAndBurn"
|
import EarnAndBurn from "@/components/MyPages/Blocks/Points/EarnAndBurn"
|
||||||
|
import ExpiringPoints from "@/components/MyPages/Blocks/Points/ExpiringPoints"
|
||||||
|
import PointsOverview from "@/components/MyPages/Blocks/Points/Overview"
|
||||||
import Shortcuts from "@/components/MyPages/Blocks/Shortcuts"
|
import Shortcuts from "@/components/MyPages/Blocks/Shortcuts"
|
||||||
import PreviousStays from "@/components/MyPages/Blocks/Stays/Previous"
|
import PreviousStays from "@/components/MyPages/Blocks/Stays/Previous"
|
||||||
import SoonestStays from "@/components/MyPages/Blocks/Stays/Soonest"
|
import SoonestStays from "@/components/MyPages/Blocks/Stays/Soonest"
|
||||||
@@ -10,8 +12,6 @@ import UpcomingStays from "@/components/MyPages/Blocks/Stays/Upcoming"
|
|||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
import { removeMultipleSlashes } from "@/utils/url"
|
import { removeMultipleSlashes } from "@/utils/url"
|
||||||
|
|
||||||
import PointsOverview from "../Blocks/Points/Overview"
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AccountPageContentProps,
|
AccountPageContentProps,
|
||||||
ContentProps,
|
ContentProps,
|
||||||
@@ -38,9 +38,7 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
|
|||||||
case DynamicContentComponents.next_benefits:
|
case DynamicContentComponents.next_benefits:
|
||||||
return <NextLevelBenefitsBlock {...props} />
|
return <NextLevelBenefitsBlock {...props} />
|
||||||
case DynamicContentComponents.expiring_points:
|
case DynamicContentComponents.expiring_points:
|
||||||
// TODO: Add once available
|
return <ExpiringPoints {...props} />
|
||||||
// return <ExpiringPoints />
|
|
||||||
return null
|
|
||||||
case DynamicContentComponents.earn_and_burn:
|
case DynamicContentComponents.earn_and_burn:
|
||||||
return <EarnAndBurn {...props} />
|
return <EarnAndBurn {...props} />
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.awardPoints {
|
||||||
|
color: var(--Base-Text-High-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.addition {
|
||||||
|
color: var(--Secondary-Light-On-Surface-Accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.addition::before {
|
||||||
|
color: var(--Secondary-Light-On-Surface-Accent);
|
||||||
|
content: "+";
|
||||||
|
margin-right: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.negation {
|
||||||
|
color: var(--Base-Text-Accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.negation::before {
|
||||||
|
color: var(--Base-Text-Accent);
|
||||||
|
content: "-";
|
||||||
|
margin-right: var(--Spacing-x-half);
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
import styles from "./row.module.css"
|
import styles from "./awardPoints.module.css"
|
||||||
|
|
||||||
export const awardPointsVariants = cva(styles.td, {
|
export const awardPointsVariants = cva(styles.awardPoints, {
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
addition: styles.addition,
|
addition: styles.addition,
|
||||||
@@ -2,6 +2,8 @@ import { useIntl } from "react-intl"
|
|||||||
|
|
||||||
import { Lang } from "@/constants/languages"
|
import { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
|
||||||
import { awardPointsVariants } from "./awardPointsVariants"
|
import { awardPointsVariants } from "./awardPointsVariants"
|
||||||
|
|
||||||
import type { AwardPointsVariantProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
import type { AwardPointsVariantProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||||
@@ -9,14 +11,16 @@ import type { AwardPointsVariantProps } from "@/types/components/myPages/myPage/
|
|||||||
export default function AwardPoints({
|
export default function AwardPoints({
|
||||||
awardPoints,
|
awardPoints,
|
||||||
isCalculated,
|
isCalculated,
|
||||||
|
isExpiringPoints = false,
|
||||||
}: {
|
}: {
|
||||||
awardPoints: number
|
awardPoints: number
|
||||||
isCalculated: boolean
|
isCalculated: boolean
|
||||||
|
isExpiringPoints?: boolean
|
||||||
}) {
|
}) {
|
||||||
let variant: AwardPointsVariantProps["variant"] = undefined
|
let variant: AwardPointsVariantProps["variant"] = null
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
if (isCalculated) {
|
if (isCalculated && !isExpiringPoints) {
|
||||||
if (awardPoints > 0) {
|
if (awardPoints > 0) {
|
||||||
variant = "addition"
|
variant = "addition"
|
||||||
} else if (awardPoints < 0) {
|
} else if (awardPoints < 0) {
|
||||||
@@ -31,10 +35,10 @@ export default function AwardPoints({
|
|||||||
// sv hardcoded to force space on thousands
|
// sv hardcoded to force space on thousands
|
||||||
const formatter = new Intl.NumberFormat(Lang.sv)
|
const formatter = new Intl.NumberFormat(Lang.sv)
|
||||||
return (
|
return (
|
||||||
<td className={classNames}>
|
<Body textTransform="bold" className={classNames}>
|
||||||
{isCalculated
|
{isCalculated
|
||||||
? formatter.format(awardPoints)
|
? formatter.format(awardPoints)
|
||||||
: intl.formatMessage({ id: "Points being calculated" })}
|
: intl.formatMessage({ id: "Points being calculated" })}
|
||||||
</td>
|
</Body>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,8 @@ import { trpc } from "@/lib/trpc/client"
|
|||||||
|
|
||||||
import LoadingSpinner from "@/components/LoadingSpinner"
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|
||||||
|
import ClientTable from "./ClientTable"
|
||||||
import Pagination from "./Pagination"
|
import Pagination from "./Pagination"
|
||||||
import Table from "./Table"
|
|
||||||
|
|
||||||
import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn"
|
import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ export default function TransactionTable({
|
|||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Table transactions={data?.data.transactions || []} />
|
<ClientTable transactions={data?.data.transactions || []} />
|
||||||
{data && data.meta.totalPages > 1 ? (
|
{data && data.meta.totalPages > 1 ? (
|
||||||
<Pagination
|
<Pagination
|
||||||
handlePageChange={setPage}
|
handlePageChange={setPage}
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import { useIntl } from "react-intl"
|
|||||||
import { webviews } from "@/constants/routes/webviews"
|
import { webviews } from "@/constants/routes/webviews"
|
||||||
import { dt } from "@/lib/dt"
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/AwardPoints"
|
||||||
import Link from "@/components/TempDesignSystem/Link"
|
import Link from "@/components/TempDesignSystem/Link"
|
||||||
|
import Table from "@/components/TempDesignSystem/Table"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
import AwardPoints from "./AwardPoints"
|
|
||||||
|
|
||||||
import styles from "./row.module.css"
|
|
||||||
|
|
||||||
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||||
import { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums"
|
import { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums"
|
||||||
|
|
||||||
@@ -80,18 +79,22 @@ export default function Row({ transaction }: RowProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={styles.tr}>
|
<Table.TR>
|
||||||
<AwardPoints
|
<Table.TD>
|
||||||
awardPoints={transaction.awardPoints}
|
<AwardPoints
|
||||||
isCalculated={transaction.pointsCalculated}
|
awardPoints={transaction.awardPoints}
|
||||||
/>
|
isCalculated={transaction.pointsCalculated}
|
||||||
<td className={`${styles.td} ${styles.description}`}>{description}</td>
|
/>
|
||||||
<td className={styles.td}>{renderConfirmationNumber()}</td>
|
</Table.TD>
|
||||||
<td className={styles.td}>
|
<Table.TD>
|
||||||
|
<Body textTransform="bold">{description}</Body>
|
||||||
|
</Table.TD>
|
||||||
|
<Table.TD>{renderConfirmationNumber()}</Table.TD>
|
||||||
|
<Table.TD>
|
||||||
{transaction.checkinDate && transaction.confirmationNumber !== "BALFWD"
|
{transaction.checkinDate && transaction.confirmationNumber !== "BALFWD"
|
||||||
? arrival
|
? arrival
|
||||||
: null}
|
: null}
|
||||||
</td>
|
</Table.TD>
|
||||||
</tr>
|
</Table.TR>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
.container {
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder {
|
||||||
|
width: 100%;
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
border: 1px solid var(--Scandic-Brand-Pale-Peach);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import Table from "@/components/TempDesignSystem/Table"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
|
||||||
|
import Row from "./Row"
|
||||||
|
|
||||||
|
import styles from "./clientTable.module.css"
|
||||||
|
|
||||||
|
import type { ClientTableProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
||||||
|
|
||||||
|
const tableHeadings = [
|
||||||
|
"Points",
|
||||||
|
"Description",
|
||||||
|
"Booking number",
|
||||||
|
"Arrival date",
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function ClientTable({ transactions }: ClientTableProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<Table>
|
||||||
|
<Table.THead>
|
||||||
|
<Table.TR>
|
||||||
|
{tableHeadings.map((heading) => (
|
||||||
|
<Table.TH key={heading}>
|
||||||
|
<Body textTransform="bold">
|
||||||
|
{intl.formatMessage({ id: heading })}
|
||||||
|
</Body>
|
||||||
|
</Table.TH>
|
||||||
|
))}
|
||||||
|
</Table.TR>
|
||||||
|
</Table.THead>
|
||||||
|
<Table.TBody>
|
||||||
|
{transactions.length ? (
|
||||||
|
transactions.map((transaction, index) => (
|
||||||
|
<Row
|
||||||
|
key={`${transaction.confirmationNumber}-${index}`}
|
||||||
|
transaction={transaction}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Table.TR className={styles.placeholder}>
|
||||||
|
<Table.TD colSpan={tableHeadings.length}>
|
||||||
|
{intl.formatMessage({ id: "No transactions available" })}
|
||||||
|
</Table.TD>
|
||||||
|
</Table.TR>
|
||||||
|
)}
|
||||||
|
</Table.TBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
.tr {
|
|
||||||
border-bottom: 1px solid var(--Scandic-Brand-Pale-Peach);
|
|
||||||
&:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.td {
|
|
||||||
background-color: #fff;
|
|
||||||
color: var(--UI-Text-High-contrast);
|
|
||||||
padding: var(--Spacing-x2);
|
|
||||||
position: relative;
|
|
||||||
text-align: left;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
|
||||||
font-weight: var(--typography-Body-Bold-fontWeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
.addition {
|
|
||||||
color: var(--Secondary-Light-On-Surface-Accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.addition::before {
|
|
||||||
color: var(--Secondary-Light-On-Surface-Accent);
|
|
||||||
content: "+";
|
|
||||||
margin-right: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.negation {
|
|
||||||
color: var(--Base-Text-Accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.negation::before {
|
|
||||||
color: var(--Base-Text-Accent);
|
|
||||||
content: "-";
|
|
||||||
margin-right: var(--Spacing-x-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.td {
|
|
||||||
padding: var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
|
||||||
|
|
||||||
import Row from "./Row"
|
|
||||||
|
|
||||||
import styles from "./table.module.css"
|
|
||||||
|
|
||||||
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
|
|
||||||
|
|
||||||
const tableHeadings = [
|
|
||||||
"Points",
|
|
||||||
"Description",
|
|
||||||
"Booking number",
|
|
||||||
"Arrival date",
|
|
||||||
]
|
|
||||||
|
|
||||||
export default function Table({ transactions }: TableProps) {
|
|
||||||
const intl = useIntl()
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
{transactions.length ? (
|
|
||||||
<table className={styles.table}>
|
|
||||||
<thead className={styles.thead}>
|
|
||||||
<tr>
|
|
||||||
{tableHeadings.map((heading) => (
|
|
||||||
<th key={heading} className={styles.th}>
|
|
||||||
<Body textTransform="bold">
|
|
||||||
{intl.formatMessage({ id: heading })}
|
|
||||||
</Body>
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{transactions.map((transaction, index) => (
|
|
||||||
<Row
|
|
||||||
key={`${transaction.confirmationNumber}-${index}`}
|
|
||||||
transaction={transaction}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
) : (
|
|
||||||
<table className={styles.table}>
|
|
||||||
<thead className={styles.thead}>
|
|
||||||
<tr>
|
|
||||||
{tableHeadings.map((heading) => (
|
|
||||||
<th key={heading} className={styles.th}>
|
|
||||||
{heading}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td colSpan={tableHeadings.length} className={styles.placeholder}>
|
|
||||||
{intl.formatMessage({ id: "No transactions available" })}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow-x: auto;
|
|
||||||
border-radius: var(--Corner-radius-Small);
|
|
||||||
}
|
|
||||||
|
|
||||||
.table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border-spacing: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thead {
|
|
||||||
background-color: var(--Scandic-Brand-Pale-Peach);
|
|
||||||
border-left: 1px solid var(--Scandic-Brand-Pale-Peach);
|
|
||||||
border-right: 1px solid var(--Scandic-Brand-Pale-Peach);
|
|
||||||
color: var(--Main-Brand-Burgundy);
|
|
||||||
}
|
|
||||||
|
|
||||||
.th {
|
|
||||||
text-align: left;
|
|
||||||
text-wrap: nowrap;
|
|
||||||
padding: var(--Spacing-x2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder {
|
|
||||||
width: 100%;
|
|
||||||
padding: 24px;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid var(--Scandic-Brand-Pale-Peach);
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
background-color: var(--Scandic-Brand-Pale-Peach);
|
|
||||||
border-left: 1px solid var(--Scandic-Brand-Pale-Peach);
|
|
||||||
border-right: 1px solid var(--Scandic-Brand-Pale-Peach);
|
|
||||||
display: flex;
|
|
||||||
padding: 20px 32px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loadMoreButton {
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--Main-Brand-Burgundy);
|
|
||||||
font-size: var(--typography-Caption-Bold-Desktop-fontSize);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--Spacing-x-half);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
.container {
|
|
||||||
border-radius: var(--Corner-radius-Large);
|
|
||||||
}
|
|
||||||
|
|
||||||
.th {
|
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-x: auto;
|
||||||
|
border-radius: var(--Corner-radius-Small);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
border-radius: var(--Corner-radius-Large);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { dt } from "@/lib/dt"
|
||||||
|
|
||||||
|
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/AwardPoints"
|
||||||
|
import Table from "@/components/TempDesignSystem/Table"
|
||||||
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
const tableHeadings = ["Points", "Expiration Date"]
|
||||||
|
|
||||||
|
export default function ExpiringPointsTable({
|
||||||
|
points,
|
||||||
|
expirationDate,
|
||||||
|
}: {
|
||||||
|
points: number
|
||||||
|
expirationDate: string
|
||||||
|
}) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const lang = useLang()
|
||||||
|
const expiration = dt(expirationDate).locale(lang).format("DD MMM YYYY")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<Table.THead>
|
||||||
|
<Table.TR>
|
||||||
|
{tableHeadings.map((heading) => (
|
||||||
|
<Table.TH key={heading}>
|
||||||
|
<Body textTransform="bold">
|
||||||
|
{intl.formatMessage({ id: heading })}
|
||||||
|
</Body>
|
||||||
|
</Table.TH>
|
||||||
|
))}
|
||||||
|
</Table.TR>
|
||||||
|
</Table.THead>
|
||||||
|
<Table.TBody>
|
||||||
|
<Table.TR>
|
||||||
|
<Table.TD>
|
||||||
|
<AwardPoints awardPoints={points} isCalculated isExpiringPoints />
|
||||||
|
</Table.TD>
|
||||||
|
<Table.TD>{expiration}</Table.TD>
|
||||||
|
</Table.TR>
|
||||||
|
</Table.TBody>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,27 +1,30 @@
|
|||||||
import { getIntl } from "@/i18n"
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
import styles from "./expiringPoints.module.css"
|
import SectionContainer from "@/components/Section/Container"
|
||||||
|
import SectionHeader from "@/components/Section/Header"
|
||||||
|
|
||||||
|
import ExpiringPointsTable from "./ExpiringPointsTable"
|
||||||
|
|
||||||
|
import { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
|
||||||
|
|
||||||
|
export default async function ExpiringPoints({
|
||||||
|
link,
|
||||||
|
subtitle,
|
||||||
|
title,
|
||||||
|
}: AccountPageComponentProps) {
|
||||||
|
const membershipLevel = await serverClient().user.membershipLevel()
|
||||||
|
|
||||||
|
if (!membershipLevel?.pointsToExpire || !membershipLevel?.pointsExpiryDate) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export async function ExpiringPoints() {
|
|
||||||
const { formatMessage } = await getIntl()
|
|
||||||
return (
|
return (
|
||||||
<table className={styles.table}>
|
<SectionContainer>
|
||||||
<thead className={styles.thead}>
|
<SectionHeader title={title} link={link} subtitle={subtitle} />
|
||||||
<tr>
|
<ExpiringPointsTable
|
||||||
<th className={styles.th}>{formatMessage({ id: "Arrival date" })}</th>
|
points={membershipLevel.pointsToExpire}
|
||||||
<th className={styles.th}>{formatMessage({ id: "Points" })}</th>
|
expirationDate={membershipLevel.pointsExpiryDate}
|
||||||
</tr>
|
/>
|
||||||
</thead>
|
</SectionContainer>
|
||||||
<tbody>
|
|
||||||
<tr className={styles.tr}>
|
|
||||||
<td className={styles.td}>23 May 2023</td>
|
|
||||||
<td className={styles.td}>30000</td>
|
|
||||||
</tr>
|
|
||||||
<tr className={styles.tr}>
|
|
||||||
<td className={styles.td}>23 May 2023</td>
|
|
||||||
<td className={styles.td}>-15000</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import styles from "./table.module.css"
|
import styles from "./table.module.css"
|
||||||
|
|
||||||
function TD({ children }: React.PropsWithChildren) {
|
function TD({
|
||||||
return <td className={styles.td}>{children}</td>
|
children,
|
||||||
|
...rest
|
||||||
|
}: React.PropsWithChildren<React.TdHTMLAttributes<HTMLTableCellElement>>) {
|
||||||
|
return (
|
||||||
|
<td className={styles.td} {...rest}>
|
||||||
|
{children}
|
||||||
|
</td>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TD
|
export default TD
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import styles from "./table.module.css"
|
import styles from "./table.module.css"
|
||||||
|
|
||||||
function TR({ children }: React.PropsWithChildren) {
|
function TR({
|
||||||
return <tr className={styles.tr}>{children}</tr>
|
children,
|
||||||
|
...rest
|
||||||
|
}: React.PropsWithChildren<React.HTMLAttributes<HTMLTableRowElement>>) {
|
||||||
|
return (
|
||||||
|
<tr className={styles.tr} {...rest}>
|
||||||
|
{children}
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TR
|
export default TR
|
||||||
|
|||||||
@@ -18,10 +18,22 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.th {
|
.th {
|
||||||
padding: var(--Spacing-x2) var(--Spacing-x3);
|
padding: var(--Spacing-x2);
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td {
|
.td {
|
||||||
padding: var(--Spacing-x3);
|
padding: var(--Spacing-x2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.th {
|
||||||
|
padding: var(--Spacing-x2) var(--Spacing-x3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.td {
|
||||||
|
padding: var(--Spacing-x3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -357,7 +357,16 @@ export const userQueryRouter = router({
|
|||||||
lastName: verifiedData.data.lastName,
|
lastName: verifiedData.data.lastName,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
membershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
|
membershipLevel: protectedProcedure.query(async function ({ ctx }) {
|
||||||
|
const verifiedData = await getVerifiedUser({ session: ctx.session })
|
||||||
|
if (!verifiedData || "error" in verifiedData) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const membershipLevel = getMembership(verifiedData.data.memberships)
|
||||||
|
return membershipLevel
|
||||||
|
}),
|
||||||
|
safeMembershipLevel: safeProtectedProcedure.query(async function ({ ctx }) {
|
||||||
if (!ctx.session) {
|
if (!ctx.session) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Table/Row/awardPointsVariants"
|
import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/AwardPoints/awardPointsVariants"
|
||||||
|
|
||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export type EarnAndBurnProps = {
|
|||||||
lang: Lang
|
lang: Lang
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableProps {
|
export interface ClientTableProps {
|
||||||
transactions: Transactions
|
transactions: Transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user