diff --git a/components/MyPages/AccountPage/Content.tsx b/components/MyPages/AccountPage/Content.tsx
index e91dd0e74..99bd03a99 100644
--- a/components/MyPages/AccountPage/Content.tsx
+++ b/components/MyPages/AccountPage/Content.tsx
@@ -39,7 +39,7 @@ function DynamicComponent({ component, props }: AccountPageContentProps) {
case DynamicContentComponents.expiring_points:
return
case DynamicContentComponents.earn_and_burn:
- return
+ return
default:
return null
}
diff --git a/components/MyPages/Blocks/Points/CurrentPointsBalance/index.tsx b/components/MyPages/Blocks/Points/CurrentPointsBalance/index.tsx
index 440cc6f11..6e93d9ba9 100644
--- a/components/MyPages/Blocks/Points/CurrentPointsBalance/index.tsx
+++ b/components/MyPages/Blocks/Points/CurrentPointsBalance/index.tsx
@@ -2,7 +2,7 @@ import { _ } from "@/lib/translation"
import styles from "./currentPointsBalance.module.css"
-const CurrentPointsBalance = () => {
+function CurrentPointsBalance() {
const points = 30000
return (
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx
index 694966524..21546c39c 100644
--- a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx
+++ b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx
@@ -1,44 +1,101 @@
+"use client"
+
+import { dt } from "@/lib/dt"
import { _ } from "@/lib/translation"
+import { trpc } from "@/lib/trpc/client"
import Button from "@/components/TempDesignSystem/Button"
import styles from "./earnAndBurn.module.css"
-const EarnAndBurn = () => {
+import {
+ EarnAndBurnProps,
+ Page,
+ RowProps,
+} from "@/types/components/myPages/myPage/earnAndBurn"
+
+const tableHeadings = [
+ _("Arrival date"),
+ _("Description"),
+ _("Booking number"),
+ _("Transaction date"),
+ _("Points"),
+]
+
+function EarnAndBurn({ lang }: EarnAndBurnProps) {
+ const { data, hasNextPage, isFetching, fetchNextPage } =
+ trpc.user.transaction.friendTransactions.useInfiniteQuery(
+ { limit: 5 },
+ {
+ getNextPageParam: (lastPage: Page) => lastPage.nextCursor,
+ }
+ )
+
+ function loadMoreData() {
+ if (hasNextPage) {
+ fetchNextPage()
+ }
+ }
+
+ const transactions = data?.pages.flatMap((page) => page.data) ?? []
+
return (
- | {_("Arrival date")} |
- {_("Description")} |
- {_("Booking number")} |
- {_("Transaction date")} |
- {_("Points")} |
+ {tableHeadings.map((heading) => (
+
+ {heading}
+ |
+ ))}
-
- | 23 May 2023 |
- Scandic Mölndal, Mölndal 2 nights |
- 5123456576 |
- 26 May 2023 |
- 30000 |
-
-
- | 23 May 2023 |
- Scandic Mölndal, Mölndal 2 nights |
- 5123456576 |
- 26 May 2023 |
- -30000 |
-
+ {transactions.map((transaction) => (
+
+ ))}
-
)
}
+function Row({ transaction, lang }: RowProps) {
+ const description = `${_(transaction.hotelName)}, ${transaction.city} ${transaction.nights} ${_("nights")}`
+ const arrival = dt(transaction.checkInDate).locale(lang).format("DD MMM YYYY")
+ const departure = dt(transaction.checkOutDate)
+ .locale(lang)
+ .format("DD MMM YYYY")
+ const values = [
+ arrival,
+ description,
+ transaction.confirmationNumber,
+ departure,
+ transaction.awardPoints,
+ ]
+ return (
+
+ {values.map((value, idx) => (
+ |
+ {value}
+ |
+ ))}
+
+ )
+}
+
export default EarnAndBurn
diff --git a/components/MyPages/Blocks/Points/ExpiringPoints/index.tsx b/components/MyPages/Blocks/Points/ExpiringPoints/index.tsx
index 07815048c..98cf04ab0 100644
--- a/components/MyPages/Blocks/Points/ExpiringPoints/index.tsx
+++ b/components/MyPages/Blocks/Points/ExpiringPoints/index.tsx
@@ -2,7 +2,7 @@ import { _ } from "@/lib/translation"
import styles from "./expiringPoints.module.css"
-export const ExpiringPoints = () => {
+export function ExpiringPoints() {
return (
diff --git a/lib/api/endpoints.ts b/lib/api/endpoints.ts
index 2efc39cd8..2dd4f268c 100644
--- a/lib/api/endpoints.ts
+++ b/lib/api/endpoints.ts
@@ -9,6 +9,7 @@ export namespace endpoints {
profile = "profile/v1/Profile",
upcomingStays = "booking/v1/Stays/future",
previousStays = "booking/v1/Stays/past",
+ friendTransactions = "profile/v1/Transaction/friendTransactions",
}
}
diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts
index a130e6ae2..2baf98618 100644
--- a/server/routers/user/input.ts
+++ b/server/routers/user/input.ts
@@ -12,3 +12,8 @@ export const soonestUpcomingStaysInput = z
limit: z.number().int().positive(),
})
.default({ limit: 3 })
+
+export const friendTransactionsInput = z.object({
+ cursor: z.number().int().nullish(),
+ limit: z.number().min(0).default(5),
+})
diff --git a/server/routers/user/output.ts b/server/routers/user/output.ts
index 1ad24f2ae..0432b7ed1 100644
--- a/server/routers/user/output.ts
+++ b/server/routers/user/output.ts
@@ -93,3 +93,72 @@ export const getStaysSchema = z.object({
type GetStaysData = z.infer
export type Stay = GetStaysData["data"][number]
+
+export const getFriendTransactionsSchema = z.object({
+ data: z.array(
+ z.object({
+ attributes: z.object({
+ hotelOperaId: z.string(),
+ confirmationNumber: z.string(),
+ checkInDate: z.string(),
+ checkOutDate: z.string(),
+ nights: z.number(),
+ awardPoints: z.number(),
+ pointsCalculated: z.boolean(),
+ hotelInformation: z.object({
+ hotelName: z.string(),
+ city: z.string(),
+ hotelContent: z.object({
+ images: z.object({
+ metaData: z.object({
+ title: z.string(),
+ altText: z.string(),
+ altText_En: z.string(),
+ copyRight: z.string(),
+ }),
+ imageSizes: z.object({
+ tiny: z.string(),
+ small: z.string(),
+ medium: z.string(),
+ large: z.string(),
+ }),
+ }),
+ }),
+ }),
+ }),
+ relationships: z.object({
+ hotel: z.object({
+ links: z.object({
+ related: z.string(),
+ }),
+ data: z.object({
+ id: z.string(),
+ type: z.string(),
+ }),
+ }),
+ booking: z.object({
+ links: z.object({
+ related: z.string(),
+ }),
+ data: z.object({
+ id: z.string(),
+ type: z.string(),
+ }),
+ }),
+ }),
+ type: z.string(),
+ })
+ ),
+ links: z
+ .object({
+ self: z.string(),
+ offset: z.number(),
+ limit: z.number(),
+ totalCount: z.number(),
+ })
+ .nullable(),
+})
+
+type GetFriendTransactionsData = z.infer
+
+export type Transaction = GetFriendTransactionsData["data"][number]
diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts
index e22ad465d..e0c2de017 100644
--- a/server/routers/user/query.ts
+++ b/server/routers/user/query.ts
@@ -8,9 +8,14 @@ import {
} from "@/server/errors/trpc"
import { protectedProcedure, router } from "@/server/trpc"
-import { staysInput } from "./input"
-import { getStaysSchema, getUserSchema } from "./output"
+import { friendTransactionsInput, staysInput } from "./input"
+import {
+ getFriendTransactionsSchema,
+ getStaysSchema,
+ getUserSchema,
+} from "./output"
import { benefits, extendedUser, nextLevelPerks } from "./temp"
+import friendTransactionsMockJson from "./tempFriendTransactions.json"
function fakingRequest(payload: T): Promise {
return new Promise((resolve) => {
@@ -177,4 +182,83 @@ export const userQueryRouter = router({
}
}),
}),
+ transaction: router({
+ friendTransactions: protectedProcedure
+ .input(friendTransactionsInput)
+ .query(async (opts) => {
+ try {
+ const { limit, cursor } = opts.input
+
+ const params = new URLSearchParams()
+ params.set("limit", limit.toString())
+
+ if (cursor) {
+ params.set("offset", cursor.toString())
+ }
+ // TODO: att these once API data is confirmed to be available
+ // const apiResponse = await api.get(
+ // api.endpoints.v1.friendTransactions,
+ // {
+ // headers: {
+ // Authorization: `Bearer ${opts.ctx.session.token.access_token}`,
+ // },
+ // },
+ // params
+ // )
+
+ // if (!apiResponse.ok) {
+ // switch (apiResponse.status) {
+ // case 400:
+ // throw badRequestError()
+ // case 401:
+ // throw unauthorizedError()
+ // case 403:
+ // throw forbiddenError()
+ // default:
+ // throw internalServerError()
+ // }
+ // }
+
+ // const apiJson = await apiResponse.json()
+ const apiJson = friendTransactionsMockJson
+ debugger
+
+ if (!apiJson.data?.length) {
+ // throw internalServerError()
+ }
+
+ const verifiedData = getFriendTransactionsSchema.safeParse(apiJson)
+
+ if (!verifiedData.success) {
+ console.info(`Get Friend Transactions - Verified Data Error`)
+ console.error(verifiedData.error)
+ throw badRequestError()
+ }
+
+ const nextCursor =
+ verifiedData.data.links &&
+ verifiedData.data.links.offset < verifiedData.data.links.totalCount
+ ? verifiedData.data.links.offset
+ : undefined
+
+ return {
+ data: verifiedData.data.data.map(({ attributes }) => ({
+ checkInDate: attributes.checkInDate,
+ checkOutDate: attributes.checkOutDate,
+ awardPoints: attributes.awardPoints,
+ hotelName: attributes.hotelInformation.hotelName,
+ city: attributes.hotelInformation.city,
+ nights: attributes.nights,
+ confirmationNumber: attributes.confirmationNumber,
+ })),
+
+ nextCursor,
+ }
+ } catch (error) {
+ console.info(`Get Friend Transactions Error`)
+ console.error(error)
+ throw internalServerError()
+ }
+ }),
+ }),
})
diff --git a/server/routers/user/tempFriendTransactions.json b/server/routers/user/tempFriendTransactions.json
new file mode 100644
index 000000000..b3340d021
--- /dev/null
+++ b/server/routers/user/tempFriendTransactions.json
@@ -0,0 +1,116 @@
+{
+ "data": [
+ {
+ "attributes": {
+ "hotelOperaId": "216",
+ "confirmationNumber": "991646189",
+ "checkInDate": "2023-09-16",
+ "checkOutDate": "2023-09-17",
+ "transactionDate": "2023-04-18",
+ "nights": 1,
+ "awardPoints": 1863,
+ "pointsCalculated": true,
+ "hotelInformation": {
+ "hotelName": "Scandic Landvetter",
+ "city": "Stockholm",
+ "hotelContent": {
+ "images": {
+ "metaData": {
+ "title": "Lobby",
+ "altText": "lobby at scandic landvetter in gothenburg",
+ "altText_En": "lobby at scandic landvetter in gothenburg",
+ "copyRight": "Werner Nystrand"
+ },
+ "imageSizes": {
+ "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/1cz71gn106ej1mz7u4nr/Scandic-Landvetter-lobby-0013-2-vald.jpg",
+ "small": "https://test3.scandichotels.com/imagevault/publishedmedia/29ejr75mwp7riv63nz0x/Scandic-Landvetter-lobby-0013-2-vald.jpg",
+ "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/bldh2liyfddkv74szp9v/Scandic-Landvetter-lobby-0013-2-vald.jpg",
+ "large": "https://test3.scandichotels.com/imagevault/publishedmedia/kbmpmkb714o028ufcgu4/Scandic-Landvetter-lobby-0013-2-vald.jpg"
+ }
+ }
+ }
+ }
+ },
+ "relationships": {
+ "hotel": {
+ "links": {
+ "related": "https://api-test.scandichotels.com/hotels/V0/fdea883a-8092-4604-8afb-032391a59009/hotels"
+ },
+ "data": {
+ "id": "d98c7ab1-ebaa-4102-b351-758daf1ddf55",
+ "type": "hotels"
+ }
+ },
+ "booking": {
+ "links": {
+ "related": "https://api-test.scandichotels.com/booking/v1/bookings/991646189"
+ },
+ "data": {
+ "id": "991646189",
+ "type": "booking"
+ }
+ }
+ },
+ "type": "stay"
+ },
+ {
+ "attributes": {
+ "hotelOperaId": "216",
+ "confirmationNumber": "991646190",
+ "checkInDate": "2023-09-16",
+ "checkOutDate": "2023-09-17",
+ "transactionDate": "2023-04-18",
+ "nights": 1,
+ "awardPoints": 1863,
+ "pointsCalculated": true,
+ "hotelInformation": {
+ "hotelName": "Scandic Landvetter",
+ "city": "Stockholm",
+ "hotelContent": {
+ "images": {
+ "metaData": {
+ "title": "Lobby",
+ "altText": "lobby at scandic landvetter in gothenburg",
+ "altText_En": "lobby at scandic landvetter in gothenburg",
+ "copyRight": "Werner Nystrand"
+ },
+ "imageSizes": {
+ "tiny": "https://test3.scandichotels.com/imagevault/publishedmedia/1cz71gn106ej1mz7u4nr/Scandic-Landvetter-lobby-0013-2-vald.jpg",
+ "small": "https://test3.scandichotels.com/imagevault/publishedmedia/29ejr75mwp7riv63nz0x/Scandic-Landvetter-lobby-0013-2-vald.jpg",
+ "medium": "https://test3.scandichotels.com/imagevault/publishedmedia/bldh2liyfddkv74szp9v/Scandic-Landvetter-lobby-0013-2-vald.jpg",
+ "large": "https://test3.scandichotels.com/imagevault/publishedmedia/kbmpmkb714o028ufcgu4/Scandic-Landvetter-lobby-0013-2-vald.jpg"
+ }
+ }
+ }
+ }
+ },
+ "relationships": {
+ "hotel": {
+ "links": {
+ "related": "https://api-test.scandichotels.com/hotels/V0/fdea883a-8092-4604-8afb-032391a59009/hotels"
+ },
+ "data": {
+ "id": "d98c7ab1-ebaa-4102-b351-758daf1ddf55",
+ "type": "hotels"
+ }
+ },
+ "booking": {
+ "links": {
+ "related": "https://api-test.scandichotels.com/booking/v1/bookings/991646189"
+ },
+ "data": {
+ "id": "991646189",
+ "type": "booking"
+ }
+ }
+ },
+ "type": "stay"
+ }
+ ],
+ "links": {
+ "self": "https://api-test.scandichotels.com/profile/v1/transaction/friendTransactions?language=en&offset=1&limit=20",
+ "offset": 2,
+ "limit": 20,
+ "totalCount": 40
+ }
+}
diff --git a/types/components/myPages/myPage/earnAndBurn.ts b/types/components/myPages/myPage/earnAndBurn.ts
new file mode 100644
index 000000000..7c0547b78
--- /dev/null
+++ b/types/components/myPages/myPage/earnAndBurn.ts
@@ -0,0 +1,33 @@
+import { Lang } from "@/constants/languages"
+
+export type EarnAndBurnProps = {
+ lang: Lang
+}
+
+type Transaction = {
+ checkInDate: string
+ checkOutDate: string
+ awardPoints: number
+ hotelName: string
+ city: string
+ nights: number
+ confirmationNumber: string
+}
+
+export type Page = {
+ data: Transaction[]
+ nextCursor?: number
+}
+
+export type RowProps = {
+ lang: Lang
+ transaction: {
+ checkInDate: string
+ checkOutDate: string
+ awardPoints: number
+ hotelName: string
+ city: string
+ nights: number
+ confirmationNumber: string
+ }
+}