Merged in feat/SW-165-correct-labels (pull request #427)

Feat/SW-165 correct labels

* feat(SW-165): sort friend transactions and return additional properties

* feat(SW-165): Added points being calculated label

* feat(SW-165): added transactionDate for transactions without checkinDate

* feat(SW-165): Updated description copy for various reward types

* feat(SW-165): filter out expired transactions

* feat(SW-165): removed Mobile table and unified them into Table instead

* feat(SW-165): Added bookingUrl to friend transactions

* fix(SW-165): style fixes

* fix(SW-165): fix issues from merge

* fix(SW-165): remove comment

* fix(SW-165): fixed booking urls not being set and smaller fixes

* fix(SW-165): added comment regarding 'BALFWD'


Approved-by: Michael Zetterberg
Approved-by: Christel Westerberg
This commit is contained in:
Tobias Johansson
2024-08-21 13:54:55 +00:00
parent 72ebc14f7d
commit 1ec1033267
21 changed files with 292 additions and 239 deletions

View File

@@ -7,9 +7,8 @@ import { trpc } from "@/lib/trpc/client"
import LoadingSpinner from "@/components/LoadingSpinner"
import DesktopTable from "./Desktop"
import MobileTable from "./Mobile"
import Pagination from "./Pagination"
import Table from "./Table"
import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn"
@@ -40,8 +39,7 @@ export default function TransactionTable({
<LoadingSpinner />
) : (
<>
<MobileTable transactions={data?.data.transactions || []} />
<DesktopTable transactions={data?.data.transactions || []} />
<Table transactions={data?.data.transactions || []} />
{data && data.meta.totalPages > 1 ? (
<Pagination
handlePageChange={setPage}

View File

@@ -1,26 +0,0 @@
import { Lang } from "@/constants/languages"
import { awardPointsVariants } from "./awardPointsVariants"
import type {
AwardPointsProps,
AwardPointsVariantProps,
} from "@/types/components/myPages/myPage/earnAndBurn"
export default function AwardPoints({ awardPoints }: AwardPointsProps) {
let variant: AwardPointsVariantProps["variant"] = undefined
if (awardPoints > 0) {
variant = "addition"
} else if (awardPoints < 0) {
variant = "negation"
awardPoints = Math.abs(awardPoints)
}
const classNames = awardPointsVariants({
variant,
})
// sv hardcoded to force space on thousands
const formatter = new Intl.NumberFormat(Lang.sv)
return <td className={classNames}>{formatter.format(awardPoints)} pts</td>
}

View File

@@ -1,35 +0,0 @@
"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import useLang from "@/hooks/useLang"
import AwardPoints from "./AwardPoints"
import styles from "./row.module.css"
import type { RowProps } from "@/types/components/myPages/myPage/earnAndBurn"
export default function Row({ transaction }: RowProps) {
const intl = useIntl()
const lang = useLang()
const description =
transaction.hotelName && transaction.city
? `${transaction.hotelName}, ${transaction.city} ${transaction.nights} ${intl.formatMessage({ id: "nights" })}`
: `${transaction.nights} ${intl.formatMessage({ id: "nights" })}`
const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY")
const departure = dt(transaction.checkoutDate)
.locale(lang)
.format("DD MMM YYYY")
return (
<tr className={styles.tr}>
<td className={styles.td}>{arrival}</td>
<td className={styles.td}>{description}</td>
<td className={styles.td}>{transaction.confirmationNumber}</td>
<td className={styles.td}>{departure}</td>
<AwardPoints awardPoints={transaction.awardPoints} />
</tr>
)
}

View File

@@ -1,69 +0,0 @@
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints"
import Body from "@/components/TempDesignSystem/Text/Body"
import useLang from "@/hooks/useLang"
import styles from "./mobile.module.css"
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
export default function MobileTable({ transactions }: TableProps) {
const intl = useIntl()
const lang = useLang()
return (
<div className={styles.container}>
<table className={styles.table}>
<thead className={styles.thead}>
<tr>
<Body asChild>
<th className={styles.th}>
{intl.formatMessage({ id: "Transactions" })}
</th>
</Body>
<Body asChild>
<th className={styles.th}>
{intl.formatMessage({ id: "Points" })}
</th>
</Body>
</tr>
</thead>
<tbody>
{transactions.length ? (
transactions.map((transaction, idx) => (
<tr
className={styles.tr}
key={`${transaction.confirmationNumber}-${idx}`}
>
<td className={`${styles.td} ${styles.transactionDetails}`}>
<span className={styles.transactionDate}>
{dt(transaction.checkinDate)
.locale(lang)
.format("DD MMM YYYY")}
</span>
{transaction.hotelName && transaction.city ? (
<span>{`${transaction.hotelName}, ${transaction.city}`}</span>
) : null}
<span>
{`${transaction.nights} ${intl.formatMessage({ id: transaction.nights === 1 ? "night" : "nights" })}`}
</span>
</td>
<AwardPoints awardPoints={transaction.awardPoints} />
</tr>
))
) : (
<tr>
<td className={styles.placeholder} colSpan={2}>
{intl.formatMessage({
id: "There are no transactions to display",
})}
</td>
</tr>
)}
</tbody>
</table>
</div>
)
}

View File

@@ -1,52 +0,0 @@
.table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
}
.thead {
background-color: var(--Main-Grey-10);
}
.th {
padding: var(--Spacing-x2);
}
.tr {
border-top: 1px solid var(--Main-Grey-10);
}
.td {
padding: var(--Spacing-x2);
}
.transactionDetails {
display: grid;
font-size: var(--typography-Footnote-Regular-fontSize);
}
.transactionDate {
font-weight: 700;
}
.placeholder {
text-align: center;
padding: var(--Spacing-x4);
border: 1px solid var(--Main-Grey-10);
}
.loadMoreButton {
background-color: var(--Main-Grey-10);
border: none;
display: flex;
align-items: center;
justify-content: center;
gap: var(--Spacing-x-half);
padding: var(--Spacing-x2);
width: 100%;
}
@media screen and (min-width: 768px) {
.container {
display: none;
}
}

View File

@@ -0,0 +1,40 @@
import { useIntl } from "react-intl"
import { Lang } from "@/constants/languages"
import { awardPointsVariants } from "./awardPointsVariants"
import type { AwardPointsVariantProps } from "@/types/components/myPages/myPage/earnAndBurn"
export default function AwardPoints({
awardPoints,
isCalculated,
}: {
awardPoints: number
isCalculated: boolean
}) {
let variant: AwardPointsVariantProps["variant"] = undefined
const intl = useIntl()
if (isCalculated) {
if (awardPoints > 0) {
variant = "addition"
} else if (awardPoints < 0) {
variant = "negation"
awardPoints = Math.abs(awardPoints)
}
}
const classNames = awardPointsVariants({
variant,
})
// sv hardcoded to force space on thousands
const formatter = new Intl.NumberFormat(Lang.sv)
return (
<td className={classNames}>
{isCalculated
? formatter.format(awardPoints)
: intl.formatMessage({ id: "Points being calculated" })}
</td>
)
}

View File

@@ -0,0 +1,82 @@
"use client"
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
import Link from "@/components/TempDesignSystem/Link"
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 { RewardTransactionTypes } from "@/types/components/myPages/myPage/enums"
export default function Row({ transaction }: RowProps) {
const intl = useIntl()
const lang = useLang()
const nightString = `${transaction.nights} ${transaction.nights === 1 ? intl.formatMessage({ id: "night" }) : intl.formatMessage({ id: "nights" })}`
let description =
transaction.hotelName && transaction.city
? `${transaction.hotelName}, ${transaction.city} ${nightString}`
: `${nightString}`
switch (transaction.type) {
case RewardTransactionTypes.stay:
if (transaction.hotelId === "ORS")
description = intl.formatMessage({ id: "Former Scandic Hotel" })
break
case RewardTransactionTypes.ancillary:
description = intl.formatMessage({ id: "Extras to your booking" })
break
case RewardTransactionTypes.enrollment:
description = intl.formatMessage({ id: "Sign up bonus" })
break
case RewardTransactionTypes.mastercard_points:
description = intl.formatMessage({ id: "Scandic Friends Mastercard" })
break
case RewardTransactionTypes.tui_points:
description = intl.formatMessage({ id: "TUI Points" })
case RewardTransactionTypes.stayAdj:
if (transaction.confirmationNumber === "BALFWD")
description = intl.formatMessage({
id: "Points earned prior to May 1, 2021",
})
break
case RewardTransactionTypes.pointShop:
description = intl.formatMessage({ id: "Scandic Friends Point Shop" })
break
}
const arrival = dt(transaction.checkinDate).locale(lang).format("DD MMM YYYY")
const transactionDate = dt(transaction.transactionDate)
.locale(lang)
.format("DD MMM YYYY")
return (
<tr className={styles.tr}>
<AwardPoints
awardPoints={transaction.awardPoints}
isCalculated={transaction.pointsCalculated}
/>
<td className={`${styles.td} ${styles.description}`}>{description}</td>
<td className={styles.td}>
{transaction.type === RewardTransactionTypes.stay &&
transaction.bookingUrl ? (
<Link variant="underscored" href={transaction.bookingUrl}>
{transaction.confirmationNumber}
</Link>
) : (
transaction.confirmationNumber
)}
</td>
<td className={styles.td}>
{transaction.checkinDate ? arrival : transactionDate}
</td>
</tr>
)
}

View File

@@ -1,13 +1,21 @@
.tr {
border: 1px solid #e6e9ec;
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) var(--Spacing-x4);
padding: var(--Spacing-x2);
position: relative;
text-align: left;
text-wrap: nowrap;
}
.description {
font-weight: var(--typography-Body-Bold-fontWeight);
}
.addition {
@@ -17,8 +25,7 @@
.addition::before {
color: var(--Secondary-Light-On-Surface-Accent);
content: "+";
left: var(--Spacing-x2);
position: absolute;
margin-right: var(--Spacing-x-half);
}
.negation {
@@ -28,6 +35,11 @@
.negation::before {
color: var(--Base-Text-Accent);
content: "-";
left: var(--Spacing-x2);
position: absolute;
margin-right: var(--Spacing-x-half);
}
@media screen and (min-width: 768px) {
.td {
padding: var(--Spacing-x3);
}
}

View File

@@ -1,50 +1,48 @@
"use client"
import { useIntl } from "react-intl"
import Body from "@/components/TempDesignSystem/Text/Body"
import Row from "./Row"
import styles from "./desktop.module.css"
import styles from "./table.module.css"
import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
const tableHeadings = [
"Arrival date",
"Points",
"Description",
"Booking number",
"Transaction date",
"Points",
"Arrival date",
]
export default function DesktopTable({ transactions }: TableProps) {
export default function Table({ transactions }: TableProps) {
const intl = useIntl()
return (
<div className={styles.container}>
{transactions.length ? (
<div>
<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, idx) => (
<Row
key={`${transaction.confirmationNumber}-${idx}`}
transaction={transaction}
/>
<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>
))}
</tbody>
</table>
</div>
</tr>
</thead>
<tbody>
{transactions.map((transaction, index) => (
<Row
key={`${transaction.confirmationNumber}-${index}`}
transaction={transaction}
/>
))}
</tbody>
</table>
) : (
<table className={styles.table}>
<thead className={styles.thead}>

View File

@@ -1,5 +1,8 @@
.container {
display: none;
display: flex;
flex-direction: column;
overflow-x: auto;
border-radius: var(--Corner-radius-Small);
}
.table {
@@ -17,7 +20,8 @@
.th {
text-align: left;
padding: 20px 32px;
text-wrap: nowrap;
padding: var(--Spacing-x2);
}
.placeholder {
@@ -49,9 +53,10 @@
}
@media screen and (min-width: 768px) {
.container {
display: flex;
flex-direction: column;
gap: 16px;
overflow-x: auto;
border-radius: var(--Corner-radius-Large);
}
.th {
padding: var(--Spacing-x2) var(--Spacing-x3);
}
}