Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/web/src/app/(overview)/history/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { useWallet } from "@/providers/StellarWalletProvider";
import DashboardLayout from "@/components/layouts/DashboardLayout";
import { ConnectWalletPrompt } from "@/components/layouts/ProtectedRoute";
import HistoryTable from "@/components/modules/history/HistoryTable";
import HistoryTableSkeleton from "@/components/modules/history/HistoryTableSkeleton";
import { columns } from "@/components/modules/history/columns";
import { HistoryRecord } from "@/services/types";
import AppSelect from "@/components/molecules/AppSelect";
Expand Down Expand Up @@ -250,8 +249,10 @@ const HistoryPage = () => {
page={page}
limit={limit}
totalCount={filteredData.length}
onPageChange={setPage}
onLimitChange={setLimit}
onPageChange={(nextPage) => updateHistoryParams({ page: nextPage })}
onLimitChange={(nextLimit) =>
updateHistoryParams({ page: 1, limit: nextLimit })
}
Comment on lines +252 to +255
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Undefined handler causes a compile-time failure.

Line 252 and Line 254 call updateHistoryParams, but that symbol is not defined/imported in this file. This blocks the build.

💡 Minimal fix
-                            onPageChange={(nextPage) => updateHistoryParams({ page: nextPage })}
-                            onLimitChange={(nextLimit) =>
-                                updateHistoryParams({ page: 1, limit: nextLimit })
-                            }
+                            onPageChange={(nextPage) => setPage(nextPage)}
+                            onLimitChange={(nextLimit) => {
+                                setPage(1);
+                                setLimit(nextLimit);
+                            }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/app/`(overview)/history/page.tsx around lines 252 - 255, The
build fails because the onPageChange and onLimitChange handlers call
updateHistoryParams which is not defined or imported; define or import the
missing symbol and wire it to the component state/update function used by this
page (e.g., implement a local function updateHistoryParams that updates the
pagination/query params or import the existing helper that does so), then
replace the inline handlers to call that defined function (keep the signatures
used: updateHistoryParams({ page: nextPage }) and updateHistoryParams({ page: 1,
limit: nextLimit })) so the references in onPageChange and onLimitChange
resolve.

onExport={handleExportCSV}
isLoading={isLoading}
/>
Expand Down
54 changes: 7 additions & 47 deletions apps/web/src/components/modules/history/HistoryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,12 @@ import {
TableHeader,
} from "@/components/ui/table";

import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";

import { HistoryRecord } from "@/services/types";
import AppSelect from "@/components/molecules/AppSelect";
import { validPageLimits } from "@/lib/constants";
import { Download } from "lucide-react";
import { Button } from "@/components/ui/button";
import SlidingPagination from "@/components/molecules/SlidingPagination";
import ActionsCell from "./ActionsCell";
import { format } from "date-fns";

Expand Down Expand Up @@ -335,44 +327,12 @@ const HistoryTable = ({
<p className="text-sm text-zinc-500" aria-live="polite" aria-atomic="true">
Showing {data.length} of {totalCount} transactions
</p>
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
onClick={(e) => {
e.preventDefault();
if (page > 1) onPageChange(page - 1);
}}
className={page <= 1 ? "pointer-events-none opacity-50" : "cursor-pointer"}
/>
</PaginationItem>
{Array.from({ length: Math.min(5, pageCount) }).map((_, i) => (
<PaginationItem key={i}>
<PaginationLink
href="#"
onClick={(e) => {
e.preventDefault();
onPageChange(i + 1);
}}
isActive={page === i + 1}
>
{i + 1}
</PaginationLink>
</PaginationItem>
))}
<PaginationItem>
<PaginationNext
href="#"
onClick={(e) => {
e.preventDefault();
if (page < pageCount) onPageChange(page + 1);
}}
className={page >= pageCount ? "pointer-events-none opacity-50" : "cursor-pointer"}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
<SlidingPagination
page={page}
pageCount={pageCount}
onPageChange={onPageChange}
className="mx-0 w-auto justify-end"
/>
</div>
</div>
);
Expand Down
66 changes: 13 additions & 53 deletions apps/web/src/components/modules/payment-stream/StreamsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,11 @@ import {
TableHeader,
} from "@/components/ui/table";

import {
Pagination,
PaginationNext,
PaginationLink,
PaginationContent,
PaginationPrevious,
PaginationEllipsis,
} from "@/components/ui/pagination";

import { streamColumns } from "./streamColumns";
import { StreamRecord } from "@/lib/validations";
import { validPageLimits } from "@/lib/constants";
import AppSelect from "@/components/molecules/AppSelect";
import SlidingPagination from "@/components/molecules/SlidingPagination";

interface StreamsTableProps {
data: StreamRecord[];
Expand Down Expand Up @@ -86,9 +78,6 @@ function StreamsTable({
router.push(`?${params.toString()}`);
};

const canPreviousPage = page > 1;
const canNextPage = page < pageCount;

const pageSize = validPageLimits.map((limit) => ({
label: `${limit} per page`,
value: limit.toString(),
Expand Down Expand Up @@ -204,8 +193,8 @@ function StreamsTable({

{/* Pagination */}
{totalCount > 0 && (
<Pagination>
<PaginationContent className="hidden lg:flex items-center space-x-4">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div className="hidden lg:flex items-center space-x-4">
<p className="text-sm font-medium text-zinc-300">Showing</p>
<AppSelect
options={pageSize}
Expand All @@ -217,50 +206,21 @@ function StreamsTable({
setValue={handlePageSizeChange}
title="Rows per page"
/>
</PaginationContent>
</div>

<PaginationContent className="hidden lg:flex flex-col sm:flex-row sm:space-y-0 sm:space-x-6 lg:space-x-8">
<div className="flex w-[100px] items-center justify-center text-sm font-medium text-zinc-300" aria-live="polite" aria-atomic="true">
Page {page} of {pageCount}
</div>
</PaginationContent>

<PaginationContent className="gap-2">
<PaginationPrevious
onClick={() => updatePage(page - 1)}
disabled={!canPreviousPage}
/>
{Array.from(
{ length: pageCount > 3 ? 3 : pageCount },
(_, index) => (
<PaginationLink
key={`streams-pagination-${index}`}
onClick={() => updatePage(index + 1)}
isActive={page === index + 1}
>
{index + 1}
</PaginationLink>
)
)}

{pageCount > 3 ? (
<PaginationContent className="flex items-center space-x-4">
<PaginationEllipsis />
<PaginationLink
onClick={() => updatePage(pageCount)}
isActive={page === pageCount}
>
{pageCount}
</PaginationLink>
</PaginationContent>
) : null}

<PaginationNext
onClick={() => updatePage(page + 1)}
disabled={!canNextPage}
/>
</PaginationContent>
</Pagination>
</div>
Comment on lines 211 to +215
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Broken JSX structure in pagination summary block.

Line 211 opens PaginationContent, but Line 215 closes </div>. This is a parser error. Also, PaginationContent isn’t imported, so this block won’t compile as written.

💡 Suggested fix
-                    <PaginationContent className="hidden lg:flex flex-col sm:flex-row sm:space-y-0 sm:space-x-6 lg:space-x-8">
+                    <div className="hidden lg:flex flex-col sm:flex-row sm:space-y-0 sm:space-x-6 lg:space-x-8">
                         <div className="flex w-[100px] items-center justify-center text-sm font-medium text-zinc-300" aria-live="polite" aria-atomic="true">
                             Page {page} of {pageCount}
                         </div>
-                    </div>
+                    </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<PaginationContent className="hidden lg:flex flex-col sm:flex-row sm:space-y-0 sm:space-x-6 lg:space-x-8">
<div className="flex w-[100px] items-center justify-center text-sm font-medium text-zinc-300" aria-live="polite" aria-atomic="true">
Page {page} of {pageCount}
</div>
</PaginationContent>
<PaginationContent className="gap-2">
<PaginationPrevious
onClick={() => updatePage(page - 1)}
disabled={!canPreviousPage}
/>
{Array.from(
{ length: pageCount > 3 ? 3 : pageCount },
(_, index) => (
<PaginationLink
key={`streams-pagination-${index}`}
onClick={() => updatePage(index + 1)}
isActive={page === index + 1}
>
{index + 1}
</PaginationLink>
)
)}
{pageCount > 3 ? (
<PaginationContent className="flex items-center space-x-4">
<PaginationEllipsis />
<PaginationLink
onClick={() => updatePage(pageCount)}
isActive={page === pageCount}
>
{pageCount}
</PaginationLink>
</PaginationContent>
) : null}
<PaginationNext
onClick={() => updatePage(page + 1)}
disabled={!canNextPage}
/>
</PaginationContent>
</Pagination>
</div>
<div className="hidden lg:flex flex-col sm:flex-row sm:space-y-0 sm:space-x-6 lg:space-x-8">
<div className="flex w-[100px] items-center justify-center text-sm font-medium text-zinc-300" aria-live="polite" aria-atomic="true">
Page {page} of {pageCount}
</div>
</div>
🧰 Tools
🪛 Biome (2.4.9)

[error] 211-211: Expected corresponding JSX closing tag for 'PaginationContent'.

(parse)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/modules/payment-stream/StreamsTable.tsx` around lines
211 - 215, The JSX has a mismatched tag and missing import: replace the stray
closing </div> with a closing </PaginationContent> to correctly match the
opening PaginationContent element and ensure PaginationContent is imported at
top of the file (or use the intended component name if different); confirm the
inner div that displays "Page {page} of {pageCount}" remains as a child of
PaginationContent and that the variables page and pageCount are in scope.


<SlidingPagination
page={page}
pageCount={pageCount}
onPageChange={updatePage}
className="mx-0 w-auto justify-end"
/>
</div>
)}
</div>
);
Expand Down
123 changes: 123 additions & 0 deletions apps/web/src/components/molecules/SlidingPagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"use client";

import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { usePagination } from "@/hooks/use-pagination";

type SlidingPaginationProps = {
page: number;
pageCount: number;
onPageChange: (p: number) => void;
className?: string;
};

const clampPage = (value: number, min: number, max: number) =>
Math.min(Math.max(value, min), max);

const SlidingPagination = ({
page,
pageCount,
onPageChange,
className,
}: SlidingPaginationProps) => {
const safePageCount = Math.max(1, pageCount);
const currentPage = clampPage(page, 1, safePageCount);
const items = usePagination({
currentPage,
pageCount: safePageCount,
siblingCount: 2,
});

const navigate = (targetPage: number) => {
const nextPage = clampPage(targetPage, 1, safePageCount);
if (nextPage !== currentPage) {
onPageChange(nextPage);
}
};

return (
<Pagination className={className}>
<PaginationContent>
<PaginationItem>
<PaginationLink
href="#"
size="default"
disabled={currentPage === 1}
onClick={(e) => {
e.preventDefault();
navigate(1);
}}
>
First
</PaginationLink>
</PaginationItem>

<PaginationItem>
<PaginationPrevious
href="#"
disabled={currentPage === 1}
onClick={(e) => {
e.preventDefault();
navigate(currentPage - 1);
}}
/>
</PaginationItem>

{items.map((item) => (
<PaginationItem
key={item.type === "ellipsis" ? item.key : `page-${item.page}`}
>
{item.type === "ellipsis" ? (
<PaginationEllipsis />
) : (
<PaginationLink
href="#"
isActive={item.page === currentPage}
onClick={(e) => {
e.preventDefault();
navigate(item.page);
}}
>
{item.page}
</PaginationLink>
)}
</PaginationItem>
))}

<PaginationItem>
<PaginationNext
href="#"
disabled={currentPage === safePageCount}
onClick={(e) => {
e.preventDefault();
navigate(currentPage + 1);
}}
/>
</PaginationItem>

<PaginationItem>
<PaginationLink
href="#"
size="default"
disabled={currentPage === safePageCount}
onClick={(e) => {
e.preventDefault();
navigate(safePageCount);
}}
>
Last
</PaginationLink>
</PaginationItem>
</PaginationContent>
</Pagination>
);
};

export default SlidingPagination;
69 changes: 69 additions & 0 deletions apps/web/src/hooks/use-pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useMemo } from "react";

export type PaginationRangeItem =
| { type: "page"; page: number }
| { type: "ellipsis"; key: "left" | "right" };

type UsePaginationParams = {
currentPage: number;
pageCount: number;
siblingCount?: number;
};

const range = (start: number, end: number) =>
Array.from({ length: end - start + 1 }, (_, index) => start + index);

export const getPaginationRange = ({
currentPage,
pageCount,
siblingCount = 2,
}: UsePaginationParams): PaginationRangeItem[] => {
const safePageCount = Math.max(1, pageCount);
const safeCurrentPage = Math.min(Math.max(1, currentPage), safePageCount);
const windowSize = siblingCount * 2 + 1;

if (safePageCount <= 7) {
return range(1, safePageCount).map((page) => ({ type: "page", page }));
}

let left = Math.max(2, safeCurrentPage - siblingCount);
let right = Math.min(safePageCount - 1, safeCurrentPage + siblingCount);

while (right - left + 1 < windowSize) {
if (left > 2) {
left -= 1;
continue;
}

if (right < safePageCount - 1) {
right += 1;
continue;
}

break;
}

const showLeftEllipsis = left > 2;
const showRightEllipsis = right < safePageCount - 1;

const items: PaginationRangeItem[] = [{ type: "page", page: 1 }];

if (showLeftEllipsis) {
items.push({ type: "ellipsis", key: "left" });
}

range(left, right).forEach((page) => {
items.push({ type: "page", page });
});

if (showRightEllipsis) {
items.push({ type: "ellipsis", key: "right" });
}

items.push({ type: "page", page: safePageCount });

return items;
};

export const usePagination = (params: UsePaginationParams) =>
useMemo(() => getPaginationRange(params), [params]);