11"use client" ;
22
3- import { useCallback , useEffect , useState } from "react" ;
3+ import dynamic from "next/dynamic" ;
4+ import { useCallback , useEffect , useMemo , useState } from "react" ;
45import { usePathname , useRouter , useSearchParams } from "next/navigation" ;
56import { useLocale , useTranslations } from "next-intl" ;
67import Skeleton from "react-loading-skeleton" ;
78import "react-loading-skeleton/dist/skeleton.css" ;
8- import PaymentDetailModal from "@/components/PaymentDetailModal" ;
9- import PaymentDetailsSheet from "@/components/PaymentDetailsSheet" ;
10- import ExportCsvButton from "@/components/ExportCsvButton" ;
11- import TransactionFilterSidebar from "@/components/TransactionFilterSidebar" ;
129import { localeToLanguageTag } from "@/i18n/config" ;
1310import { toast } from "sonner" ;
1411import {
1512 useHydrateMerchantStore ,
1613 useMerchantApiKey ,
1714 useMerchantId ,
1815} from "@/lib/merchant-store" ;
19- import { buildPaymentHistorySearchParams } from "@/lib/payment-history-filters" ;
2016import { useTransactionFilters } from "@/hooks/useTransactionFilters" ;
2117import { usePaymentSocket } from "@/lib/usePaymentSocket" ;
2218
19+ const ExportCsvButton = dynamic ( ( ) => import ( "@/components/ExportCsvButton" ) , {
20+ ssr : false ,
21+ loading : ( ) => (
22+ < div className = "h-10 w-28 rounded-xl border border-[#E8E8E8] bg-[#F9F9F9]" aria-hidden = "true" />
23+ ) ,
24+ } ) ;
25+ const PaymentDetailModal = dynamic ( ( ) => import ( "@/components/PaymentDetailModal" ) , { ssr : false } ) ;
26+ const PaymentDetailsSheet = dynamic ( ( ) => import ( "@/components/PaymentDetailsSheet" ) , { ssr : false } ) ;
27+ const TransactionFilterSidebar = dynamic ( ( ) => import ( "@/components/TransactionFilterSidebar" ) , {
28+ ssr : false ,
29+ loading : ( ) => (
30+ < div className = "hidden h-[420px] w-[320px] rounded-2xl border border-[#E8E8E8] bg-[#F9F9F9] lg:block" aria-hidden = "true" />
31+ ) ,
32+ } ) ;
33+
2334interface Payment {
2435 id : string ;
2536 amount : string ;
@@ -60,6 +71,10 @@ export default function PaymentHistoryPage() {
6071 const searchParams = useSearchParams ( ) ;
6172 const apiKey = useMerchantApiKey ( ) ;
6273 const merchantId = useMerchantId ( ) ;
74+ const currentPage = useMemo ( ( ) => {
75+ const parsed = Number ( searchParams . get ( "page" ) ?? "1" ) ;
76+ return Number . isFinite ( parsed ) && parsed > 0 ? Math . floor ( parsed ) : 1 ;
77+ } , [ searchParams ] ) ;
6378
6479 useHydrateMerchantStore ( ) ;
6580
@@ -81,6 +96,19 @@ export default function PaymentHistoryPage() {
8196 onClearFilter,
8297 onClearAll,
8398 } = useTransactionFilters ( pushSearchParams , searchParams ) ;
99+ const handlePageChange = useCallback (
100+ ( page : number ) => {
101+ const nextPage = Math . max ( 1 , page ) ;
102+ const params = new URLSearchParams ( searchParams . toString ( ) ) ;
103+ if ( nextPage === 1 ) {
104+ params . delete ( "page" ) ;
105+ } else {
106+ params . set ( "page" , String ( nextPage ) ) ;
107+ }
108+ pushSearchParams ( params ) ;
109+ } ,
110+ [ pushSearchParams , searchParams ] ,
111+ ) ;
84112
85113 // ── UI state ────────────────────────────────────────────────────────────────
86114 const [ payments , setPayments ] = useState < Payment [ ] > ( [ ] ) ;
@@ -159,7 +187,7 @@ export default function PaymentHistoryPage() {
159187
160188 const apiUrl = process . env . NEXT_PUBLIC_API_URL || "http://localhost:4000" ;
161189 const params = new URLSearchParams ( searchParams . toString ( ) ) ;
162- params . set ( "page" , "1" ) ;
190+ params . set ( "page" , currentPage . toString ( ) ) ;
163191 params . set ( "limit" , LIMIT . toString ( ) ) ;
164192
165193 const response = await fetch ( `${ apiUrl } /api/payments?${ params . toString ( ) } ` , {
@@ -182,13 +210,16 @@ export default function PaymentHistoryPage() {
182210
183211 fetchPayments ( ) ;
184212 return ( ) => controller . abort ( ) ;
185- } , [ searchParams , apiKey , t ] ) ;
213+ } , [ searchParams , currentPage , apiKey , t ] ) ;
186214
187215 // ── Handlers ─────────────────────────────────────────────────────────────────
188216 const handlePaymentClick = ( paymentId : string ) => {
189217 setSelectedPayment ( paymentId ) ;
190218 setIsSheetOpen ( true ) ;
191219 } ;
220+ const totalPages = Math . max ( 1 , Math . ceil ( totalCount / LIMIT ) ) ;
221+ const pageStart = totalCount === 0 ? 0 : ( currentPage - 1 ) * LIMIT + 1 ;
222+ const pageEnd = Math . min ( currentPage * LIMIT , totalCount ) ;
192223
193224 // ── Loading state ─────────────────────────────────────────────────────────────
194225 if ( loading ) {
@@ -430,7 +461,9 @@ export default function PaymentHistoryPage() {
430461 { /* Results count */ }
431462 < div className = "flex items-center justify-between px-2" >
432463 < p className = "text-xs text-[#6B6B6B] font-medium" >
433- { t ( "showingResults" , { shown : payments . length , total : totalCount } ) }
464+ { totalPages > 1
465+ ? `Showing ${ pageStart } -${ pageEnd } of ${ totalCount } `
466+ : t ( "showingResults" , { shown : payments . length , total : totalCount } ) }
434467 </ p >
435468 </ div >
436469
@@ -501,9 +534,29 @@ export default function PaymentHistoryPage() {
501534 </ div >
502535 ) }
503536
504- { totalCount > LIMIT && (
505- < div className = "flex items-center justify-center py-6" >
506- < p className = "text-[10px] font-bold uppercase tracking-widest text-[#A0A0A0]" > End of list (Showing { LIMIT } most recent)</ p >
537+ { totalPages > 1 && (
538+ < div className = "flex flex-col items-center justify-between gap-3 border-t border-[#F0F0F0] py-6 sm:flex-row" >
539+ < p className = "text-[10px] font-bold uppercase tracking-widest text-[#A0A0A0]" >
540+ Page { currentPage } of { totalPages }
541+ </ p >
542+ < nav className = "flex items-center gap-2" aria-label = "Transaction history pagination" >
543+ < button
544+ type = "button"
545+ onClick = { ( ) => handlePageChange ( currentPage - 1 ) }
546+ disabled = { currentPage <= 1 || isFilterPending }
547+ className = "inline-flex min-h-10 items-center rounded-xl border border-[#E8E8E8] bg-white px-4 text-[10px] font-bold uppercase tracking-widest text-[#0A0A0A] transition-all hover:bg-[#F5F5F5] disabled:cursor-not-allowed disabled:opacity-40"
548+ >
549+ Previous
550+ </ button >
551+ < button
552+ type = "button"
553+ onClick = { ( ) => handlePageChange ( currentPage + 1 ) }
554+ disabled = { currentPage >= totalPages || isFilterPending }
555+ className = "inline-flex min-h-10 items-center rounded-xl bg-[#0A0A0A] px-4 text-[10px] font-bold uppercase tracking-widest text-white transition-all hover:bg-[#2A2A2A] disabled:cursor-not-allowed disabled:opacity-40"
556+ >
557+ Next
558+ </ button >
559+ </ nav >
507560 </ div >
508561 ) }
509562 </ div >
@@ -518,4 +571,4 @@ export default function PaymentHistoryPage() {
518571 ) }
519572 </ div >
520573 ) ;
521- }
574+ }
0 commit comments