diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints.tsx
similarity index 100%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints.tsx
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints.tsx
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants.ts b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants.ts
similarity index 100%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants.ts
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants.ts
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx
similarity index 100%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/index.tsx
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/index.tsx
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/row.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css
similarity index 100%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/row.module.css
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/row.module.css
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css
similarity index 100%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/desktop.module.css
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/desktop.module.css
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx
similarity index 71%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx
index 8b244f67b..3d50eb609 100644
--- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/index.tsx
+++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/index.tsx
@@ -1,15 +1,12 @@
-"use client"
-
import { useIntl } from "react-intl"
-import { ChevronDownIcon } from "@/components/Icons"
import Body from "@/components/TempDesignSystem/Text/Body"
import Row from "./Row"
import styles from "./desktop.module.css"
-import type { TablePropsPagination } from "@/types/components/myPages/myPage/earnAndBurn"
+import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
const tableHeadings = [
"Arrival date",
@@ -19,11 +16,7 @@ const tableHeadings = [
"Points",
]
-export default function DesktopTable({
- transactions,
- showMore,
- hasMore,
-}: TablePropsPagination) {
+export default function DesktopTable({ transactions }: TableProps) {
const intl = useIntl()
return (
@@ -51,19 +44,6 @@ export default function DesktopTable({
))}
- {hasMore ? (
-
-
-
- ) : null}
) : (
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx
similarity index 78%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx
index f77e12d6f..639f73f9b 100644
--- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/index.tsx
+++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/index.tsx
@@ -1,23 +1,16 @@
-"use client"
-
import { useIntl } from "react-intl"
import { dt } from "@/lib/dt"
-import { ChevronDownIcon } from "@/components/Icons"
-import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/AwardPoints"
+import AwardPoints from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/AwardPoints"
import Body from "@/components/TempDesignSystem/Text/Body"
import { getLang } from "@/i18n/serverContext"
import styles from "./mobile.module.css"
-import type { TablePropsPagination } from "@/types/components/myPages/myPage/earnAndBurn"
+import type { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
-export default function MobileTable({
- transactions,
- showMore,
- hasMore,
-}: TablePropsPagination) {
+export default function MobileTable({ transactions }: TableProps) {
const intl = useIntl()
return (
@@ -71,18 +64,6 @@ export default function MobileTable({
)}
-
- {hasMore ? (
-
- ) : null}
)
}
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css
similarity index 100%
rename from components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Mobile/mobile.module.css
rename to components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Mobile/mobile.module.css
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx
new file mode 100644
index 000000000..c8f6ff6c9
--- /dev/null
+++ b/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx
@@ -0,0 +1,126 @@
+"use client"
+
+import { useEffect, useState } from "react"
+
+import { trpc } from "@/lib/trpc/client"
+
+import { ChevronRightIcon } from "@/components/Icons"
+
+import DesktopTable from "./Desktop"
+import MobileTable from "./Mobile"
+
+import styles from "../earnAndBurn.module.css"
+
+import { Transactions } from "@/types/components/myPages/myPage/earnAndBurn"
+
+function PaginationButton({
+ children,
+ isActive,
+ handleClick,
+ disabled,
+}: React.PropsWithChildren<{
+ disabled: boolean
+ isActive?: boolean
+ handleClick: () => void
+}>) {
+ return (
+
+ )
+}
+
+function Pagination({
+ pageCount,
+ isFetching,
+ handlePageChange,
+ currentPage,
+}: {
+ pageCount: number
+ isFetching: boolean
+ handlePageChange: (page: number) => void
+ currentPage: number
+}) {
+ const isOnFirstPage = currentPage === 1
+ const isOnLastPage = currentPage === pageCount
+ return (
+
+
{
+ handlePageChange(currentPage - 1)
+ }}
+ >
+
+
+ {[...Array(pageCount)].map((_, idx) => (
+
{
+ handlePageChange(idx + 1)
+ }}
+ >
+ {idx + 1}
+
+ ))}
+
{
+ handlePageChange(currentPage + 1)
+ }}
+ >
+
+
+
+ )
+}
+
+export default function TransactionTable() {
+ const limit = 5
+ const [page, setPage] = useState(1)
+ const [totalPages, setTotalPages] = useState(0)
+ const [currentTransactions, setCurrentTransactions] = useState(
+ []
+ )
+ const { data, isFetching, isLoading } =
+ trpc.user.transaction.friendTransactions.useQuery({
+ limit,
+ page,
+ })
+ // Should the active page be mirroried in the URL with params?
+ // That way the actual fetch could be moved up and Mobile/Desktop can be strictly server side
+ useEffect(() => {
+ if (typeof data?.data.pages === "number") {
+ setTotalPages(data?.data.pages)
+ }
+ }, [data?.data.pages])
+
+ useEffect(() => {
+ if (data?.data.transactions) {
+ setCurrentTransactions(data.data.transactions)
+ }
+ }, [data?.data.transactions])
+
+ return !currentTransactions.length ? (
+ "Loading..." // Add loading state table
+ ) : (
+ <>
+
+
+ {totalPages > 1 ? (
+
+ ) : null}
+ >
+ )
+}
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx
deleted file mode 100644
index 8a2c0050a..000000000
--- a/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-"use client"
-
-import { useState } from "react"
-
-import DesktopTable from "./Desktop"
-import MobileTable from "./Mobile"
-
-import { TableProps } from "@/types/components/myPages/myPage/earnAndBurn"
-
-export function TransactionTable({ transactions }: TableProps) {
- const [transactionDisplayCount, setTransactionDisplayCount] = useState(5)
-
- const showMoreTransactions = () => {
- setTransactionDisplayCount((count) => count + 5)
- }
-
- const displayedTransactions = transactions.slice(0, transactionDisplayCount)
- const hasMoreTransactions = transactions.length > transactionDisplayCount
-
- return (
- <>
-
-
- >
- )
-}
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css b/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css
index 230868385..6dc2aa8e8 100644
--- a/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css
+++ b/components/MyPages/Blocks/Points/EarnAndBurn/earnAndBurn.module.css
@@ -2,3 +2,37 @@
display: grid;
gap: var(--Spacing-x3);
}
+
+.pagination {
+ display: flex;
+ justify-content: center;
+ padding: var(--Spacing-x2);
+ background-color: var(--Base-Surface-Primary-light-Normal);
+ border-radius: var(--Corner-radius-Rounded);
+ margin: auto;
+ gap: var(--Spacing-x5);
+}
+
+.paginationButton {
+ background-color: transparent;
+ border: none;
+ height: 32px;
+ width: 32px;
+ font-size: var(--typography-Body-Bold-fontSize);
+ font-weight: var(--typography-Body-Bold-fontWeight);
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.chevronLeft {
+ transform: rotate(180deg);
+ height: 100%;
+}
+
+.paginationButtonActive {
+ color: var(--WHITE);
+ background-color: var(--Base-Text-Accent);
+ border-radius: var(--Corner-radius-Rounded);
+}
diff --git a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx
index 521e8d954..3eb3d5496 100644
--- a/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx
+++ b/components/MyPages/Blocks/Points/EarnAndBurn/index.tsx
@@ -4,7 +4,7 @@ import SectionContainer from "@/components/Section/Container"
import SectionHeader from "@/components/Section/Header"
import SectionLink from "@/components/Section/Link"
-import { TransactionTable } from "./TransactionTable"
+import JourneyTable from "./JourneyTable"
import type { AccountPageComponentProps } from "@/types/components/myPages/myPage/accountPage"
@@ -13,17 +13,18 @@ export default async function EarnAndBurn({
subtitle,
title,
}: AccountPageComponentProps) {
- const transactions =
- await serverClient().user.transaction.friendTransactions()
- if (!transactions) {
+ const transactionsData =
+ await serverClient().user.transaction.friendTransactions({
+ limit: 10,
+ page: 1,
+ })
+ if (!transactionsData) {
return null
}
return (
-
-
-
+
)
diff --git a/server/routers/user/input.ts b/server/routers/user/input.ts
index c7aa4102b..822b1d46d 100644
--- a/server/routers/user/input.ts
+++ b/server/routers/user/input.ts
@@ -29,3 +29,9 @@ export const saveCardInput = z.object({
transactionId: z.string(),
merchantId: z.string().optional(),
})
+export const friendTransactionsInput = z
+ .object({
+ limit: z.number().int().positive(),
+ page: z.number().int().positive(),
+ })
+ .default({ limit: 5, page: 1 })
diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts
index 946ec5945..a01ee15f7 100644
--- a/server/routers/user/query.ts
+++ b/server/routers/user/query.ts
@@ -19,6 +19,7 @@ import { getMembership, getMembershipCards } from "@/utils/user"
import encryptValue from "../utils/encryptValue"
import {
+ friendTransactionsInput,
getUserInputSchema,
initiateSaveCardInput,
saveCardInput,
@@ -453,54 +454,69 @@ export const userQueryRouter = router({
}),
}),
transaction: router({
- friendTransactions: protectedProcedure.query(async (opts) => {
- const apiResponse = await api.get(api.endpoints.v1.friendTransactions, {
- cache: "no-store",
- headers: {
- Authorization: `Bearer ${opts.ctx.session.token.access_token}`,
- },
- })
+ friendTransactions: protectedProcedure
+ .input(friendTransactionsInput)
+ .query(async ({ ctx, input }) => {
+ const { limit, page } = input
+ const apiResponse = await api.get(api.endpoints.v1.friendTransactions, {
+ headers: {
+ Authorization: `Bearer ${ctx.session.token.access_token}`,
+ },
+ next: { revalidate: 30 * 60 * 1000 },
+ })
- if (!apiResponse.ok) {
- // switch (apiResponse.status) {
- // case 400:
- // throw badRequestError()
- // case 401:
- // throw unauthorizedError()
- // case 403:
- // throw forbiddenError()
- // default:
- // throw internalServerError()
- // }
- console.error(`API Response Failed - Getting Friend Transactions`)
- console.error(`User: (${JSON.stringify(opts.ctx.session.user)})`)
- console.error(apiResponse)
- return null
- }
+ if (!apiResponse.ok) {
+ // switch (apiResponse.status) {
+ // case 400:
+ // throw badRequestError()
+ // case 401:
+ // throw unauthorizedError()
+ // case 403:
+ // throw forbiddenError()
+ // default:
+ // throw internalServerError()
+ // }
+ console.error(`API Response Failed - Getting Friend Transactions`)
+ console.error(`User: (${JSON.stringify(ctx.session.user)})`)
+ console.error(apiResponse)
+ return null
+ }
- const apiJson = await apiResponse.json()
- const verifiedData = getFriendTransactionsSchema.safeParse(apiJson)
- if (!verifiedData.success) {
- console.error(`Failed to validate Friend Transactions Data`)
- console.error(`User: (${JSON.stringify(opts.ctx.session.user)})`)
- console.error(verifiedData.error)
- return null
- }
+ const apiJson = await apiResponse.json()
+ const verifiedData = getFriendTransactionsSchema.safeParse(apiJson)
+ if (!verifiedData.success) {
+ console.error(`Failed to validate Friend Transactions Data`)
+ console.error(`User: (${JSON.stringify(ctx.session.user)})`)
+ console.error(verifiedData.error)
+ return null
+ }
- return {
- data: verifiedData.data.data.map(({ attributes }) => {
- return {
- awardPoints: attributes.awardPoints,
- checkinDate: attributes.checkinDate,
- checkoutDate: attributes.checkoutDate,
- city: attributes.hotelInformation?.city,
- confirmationNumber: attributes.confirmationNumber,
- hotelName: attributes.hotelInformation?.name,
- nights: attributes.nights,
- }
- }),
- }
- }),
+ const pageData = verifiedData.data.data.slice(
+ limit * (page - 1),
+ limit * page
+ )
+
+ return {
+ data: {
+ transactions: pageData.map(({ attributes }) => {
+ return {
+ awardPoints: attributes.awardPoints,
+ checkinDate: attributes.checkinDate,
+ checkoutDate: attributes.checkoutDate,
+ city: attributes.hotelInformation?.city,
+ confirmationNumber: attributes.confirmationNumber,
+ hotelName: attributes.hotelInformation?.name,
+ nights: attributes.nights,
+ }
+ }),
+
+ pages: Math.ceil(verifiedData.data.data.length / limit),
+ },
+ meta: {
+ totalPages: Math.ceil(verifiedData.data.data.length / limit),
+ },
+ }
+ }),
}),
creditCards: protectedProcedure.query(async function ({ ctx }) {
diff --git a/types/components/myPages/myPage/earnAndBurn.ts b/types/components/myPages/myPage/earnAndBurn.ts
index 7791aea22..06effe106 100644
--- a/types/components/myPages/myPage/earnAndBurn.ts
+++ b/types/components/myPages/myPage/earnAndBurn.ts
@@ -1,4 +1,4 @@
-import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/Desktop/Row/awardPointsVariants"
+import { awardPointsVariants } from "@/components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/Desktop/Row/awardPointsVariants"
import type { VariantProps } from "class-variance-authority"
@@ -10,9 +10,9 @@ export type TransactionResponse = Awaited<
>
export type TransactionsNonNullResponseObject = NonNullable
export type Transactions =
- NonNullable["data"]
+ NonNullable["data"]["transactions"]
export type Transaction =
- NonNullable["data"][number]
+ NonNullable["data"]["transactions"][number]
export type ClientEarnAndBurnProps = {
initialData: TransactionsNonNullResponseObject
@@ -27,11 +27,6 @@ export interface TableProps {
transactions: Transactions
}
-export interface TablePropsPagination extends TableProps {
- showMore: () => void
- hasMore: boolean
-}
-
export interface RowProps {
transaction: Transaction
}