From 8c75b9bcd7b59ab586aec3935fe8bf4cc8204fda Mon Sep 17 00:00:00 2001 From: Arvid Norlin Date: Fri, 16 Aug 2024 13:27:52 +0200 Subject: [PATCH] wip: initial stab trpc pagination --- .../Desktop/Row/AwardPoints.tsx | 0 .../Desktop/Row/awardPointsVariants.ts | 0 .../Desktop/Row/index.tsx | 0 .../Desktop/Row/row.module.css | 0 .../Desktop/desktop.module.css | 0 .../Desktop/index.tsx | 24 +--- .../Mobile/index.tsx | 25 +--- .../Mobile/mobile.module.css | 0 .../Points/EarnAndBurn/JourneyTable/index.tsx | 126 ++++++++++++++++++ .../EarnAndBurn/TransactionTable/index.tsx | 34 ----- .../Points/EarnAndBurn/earnAndBurn.module.css | 34 +++++ .../Blocks/Points/EarnAndBurn/index.tsx | 15 ++- server/routers/user/input.ts | 6 + server/routers/user/query.ts | 106 ++++++++------- .../components/myPages/myPage/earnAndBurn.ts | 11 +- 15 files changed, 243 insertions(+), 138 deletions(-) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/AwardPoints.tsx (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/awardPointsVariants.ts (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/index.tsx (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/Row/row.module.css (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/desktop.module.css (100%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Desktop/index.tsx (71%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Mobile/index.tsx (78%) rename components/MyPages/Blocks/Points/EarnAndBurn/{TransactionTable => JourneyTable}/Mobile/mobile.module.css (100%) create mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/JourneyTable/index.tsx delete mode 100644 components/MyPages/Blocks/Points/EarnAndBurn/TransactionTable/index.tsx 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 }