wip: initial stab trpc pagination

This commit is contained in:
Arvid Norlin
2024-08-16 13:27:52 +02:00
parent 6c15f1ae3a
commit 8c75b9bcd7
15 changed files with 243 additions and 138 deletions

View File

@@ -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({
))}
</tbody>
</table>
{hasMore ? (
<div className={styles.footer}>
<button
className={styles.loadMoreButton}
onClick={() => {
showMore()
}}
>
<ChevronDownIcon color="burgundy" height={24} width={24} />
{intl.formatMessage({ id: "Show more" })}
</button>
</div>
) : null}
</div>
) : (
<table className={styles.table}>

View File

@@ -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({
)}
</tbody>
</table>
{hasMore ? (
<button
className={styles.loadMoreButton}
onClick={() => {
showMore()
}}
>
<ChevronDownIcon height={24} width={24} />
{intl.formatMessage({ id: "Show more" })}
</button>
) : null}
</div>
)
}

View File

@@ -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 (
<button
disabled={disabled}
onClick={handleClick}
className={`${styles.paginationButton} ${isActive ? styles.paginationButtonActive : ""}`}
>
{children}
</button>
)
}
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 (
<div className={styles.pagination}>
<PaginationButton
disabled={isFetching || isOnFirstPage}
handleClick={() => {
handlePageChange(currentPage - 1)
}}
>
<ChevronRightIcon className={styles.chevronLeft} />
</PaginationButton>
{[...Array(pageCount)].map((_, idx) => (
<PaginationButton
isActive={currentPage === idx + 1}
disabled={isFetching || currentPage === idx + 1}
key={idx}
handleClick={() => {
handlePageChange(idx + 1)
}}
>
{idx + 1}
</PaginationButton>
))}
<PaginationButton
disabled={isFetching || isOnLastPage}
handleClick={() => {
handlePageChange(currentPage + 1)
}}
>
<ChevronRightIcon />
</PaginationButton>
</div>
)
}
export default function TransactionTable() {
const limit = 5
const [page, setPage] = useState(1)
const [totalPages, setTotalPages] = useState(0)
const [currentTransactions, setCurrentTransactions] = useState<Transactions>(
[]
)
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
) : (
<>
<MobileTable transactions={currentTransactions} />
<DesktopTable transactions={currentTransactions} />
{totalPages > 1 ? (
<Pagination
handlePageChange={setPage}
pageCount={totalPages}
isFetching={isFetching}
currentPage={page}
/>
) : null}
</>
)
}

View File

@@ -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 (
<>
<MobileTable
transactions={displayedTransactions}
showMore={showMoreTransactions}
hasMore={hasMoreTransactions}
/>
<DesktopTable
transactions={displayedTransactions}
showMore={showMoreTransactions}
hasMore={hasMoreTransactions}
/>
</>
)
}

View File

@@ -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);
}

View File

@@ -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 (
<SectionContainer>
<SectionHeader title={title} link={link} subtitle={subtitle} />
<TransactionTable transactions={transactions.data} />
<JourneyTable />
<SectionLink link={link} variant="mobile" />
</SectionContainer>
)

View File

@@ -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 })

View File

@@ -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 }) {

View File

@@ -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<TransactionResponse>
export type Transactions =
NonNullable<TransactionsNonNullResponseObject>["data"]
NonNullable<TransactionsNonNullResponseObject>["data"]["transactions"]
export type Transaction =
NonNullable<TransactionsNonNullResponseObject>["data"][number]
NonNullable<TransactionsNonNullResponseObject>["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
}