Skip to content

Commit 0a4df6c

Browse files
[feat] Add manage-names pagination (#2566)
* Revert fetch size, add pagination * Remove sample data * Cleanup * Address PR comments, make stack based page history more clear * Fix lint errors
1 parent bad980b commit 0a4df6c

File tree

3 files changed

+132
-15
lines changed

3 files changed

+132
-15
lines changed

apps/web/app/(basenames)/api/basenames/getUsernames/route.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,20 @@ export async function GET(request: NextRequest) {
1414
return NextResponse.json({ error: 'Invalid network provided' }, { status: 400 });
1515
}
1616

17-
const response = await fetch(
18-
`https://${cdpBaseUri}/platform/v1/networks/${network}/addresses/${address}/identity?limit=100`,
19-
{
20-
headers: {
21-
Authorization: `Bearer ${process.env.CDP_BEARER_TOKEN}`,
22-
'Content-Type': 'application/json',
23-
},
17+
const page = request.nextUrl.searchParams.get('page');
18+
19+
// Build the URL with pagination parameter if provided
20+
let url = `https://${cdpBaseUri}/platform/v1/networks/${network}/addresses/${address}/identity?limit=50`;
21+
if (page) {
22+
url += `&page=${page}`;
23+
}
24+
25+
const response = await fetch(url, {
26+
headers: {
27+
Authorization: `Bearer ${process.env.CDP_BEARER_TOKEN}`,
28+
'Content-Type': 'application/json',
2429
},
25-
);
30+
});
2631

2732
const data = (await response.json()) as ManagedAddressesResponse;
2833

apps/web/src/components/Basenames/ManageNames/NamesList.tsx

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,18 @@ function NamesLayout({ children }: { children: React.ReactNode }) {
3030
}
3131

3232
export default function NamesList() {
33-
const { namesData, isLoading, error, refetch } = useNameList();
33+
const {
34+
namesData,
35+
isLoading,
36+
error,
37+
refetch,
38+
goToNextPage,
39+
goToPreviousPage,
40+
hasPrevious,
41+
hasNext,
42+
totalCount,
43+
currentPageNumber,
44+
} = useNameList();
3445
const { logError } = useErrors();
3546

3647
const refetchNames = useCallback(() => {
@@ -86,6 +97,45 @@ export default function NamesList() {
8697
/>
8798
))}
8899
</ul>
100+
101+
{/* Pagination Controls */}
102+
{(hasPrevious || hasNext) && (
103+
<div className="mt-8 flex flex-col gap-4">
104+
{/* Page indicator */}
105+
<div className="text-center text-sm text-palette-foregroundMuted">
106+
Page {currentPageNumber}{totalCount} total names
107+
</div>
108+
109+
{/* Navigation buttons */}
110+
<div className="flex justify-center gap-4">
111+
<button
112+
type="button"
113+
onClick={goToPreviousPage}
114+
disabled={!hasPrevious}
115+
className={`flex w-20 items-center justify-center gap-1 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${
116+
hasPrevious
117+
? 'hover:bg-blue-700 bg-blue-600 text-white'
118+
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
119+
}`}
120+
>
121+
Prev
122+
</button>
123+
124+
<button
125+
type="button"
126+
onClick={goToNextPage}
127+
disabled={!hasNext}
128+
className={`flex w-20 items-center justify-center gap-1 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${
129+
hasNext
130+
? 'hover:bg-blue-700 bg-blue-600 text-white'
131+
: 'bg-gray-300 text-gray-500 cursor-not-allowed'
132+
}`}
133+
>
134+
Next
135+
</button>
136+
</div>
137+
</div>
138+
)}
89139
</NamesLayout>
90140
);
91141
}

apps/web/src/components/Basenames/ManageNames/hooks.tsx

Lines changed: 68 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect } from 'react';
1+
import { useCallback, useEffect, useState } from 'react';
22
import { useErrors } from 'apps/web/contexts/Errors';
33
import { useQuery, useQueryClient } from '@tanstack/react-query';
44
import { useAccount, useChainId } from 'wagmi';
@@ -11,6 +11,11 @@ export function useNameList() {
1111
const chainId = useChainId();
1212
const { logError } = useErrors();
1313

14+
// Pagination state - need to maintain a stack of page tokens for proper back navigation
15+
const [currentPage, setCurrentPage] = useState<string | null>(null);
16+
const [pageStack, setPageStack] = useState<(string | null)[]>([null]); // Stack of page tokens for history
17+
const [pageNumber, setPageNumber] = useState<number>(1);
18+
1419
const network = chainId === 8453 ? 'base-mainnet' : 'base-sepolia';
1520

1621
const {
@@ -19,12 +24,15 @@ export function useNameList() {
1924
error,
2025
refetch,
2126
} = useQuery<ManagedAddressesResponse>({
22-
queryKey: ['usernames', address, network],
27+
queryKey: ['usernames', address, network, currentPage],
2328
queryFn: async (): Promise<ManagedAddressesResponse> => {
2429
try {
25-
const response = await fetch(
26-
`/api/basenames/getUsernames?address=${address}&network=${network}`,
27-
);
30+
let url = `/api/basenames/getUsernames?address=${address}&network=${network}`;
31+
if (currentPage) {
32+
url += `&page=${currentPage}`;
33+
}
34+
35+
const response = await fetch(url);
2836
if (!response.ok) {
2937
throw new Error(`Failed to fetch usernames: ${response.statusText}`);
3038
}
@@ -37,7 +45,61 @@ export function useNameList() {
3745
enabled: !!address,
3846
});
3947

40-
return { namesData, isLoading, error, refetch };
48+
// Navigation functions
49+
const goToNextPage = useCallback(() => {
50+
if (namesData?.has_more && namesData?.next_page) {
51+
// Push current page to history stack and move to next page
52+
setPageStack((prev) => [...prev, currentPage]);
53+
setCurrentPage(namesData.next_page);
54+
setPageNumber((prev) => prev + 1);
55+
}
56+
}, [namesData?.has_more, namesData?.next_page, currentPage]);
57+
58+
const goToPreviousPage = useCallback(() => {
59+
if (pageStack.length > 1) {
60+
// Pop from stack to get the page token to go back to
61+
const newStack = [...pageStack];
62+
const targetPageToken = newStack.pop(); // This is the page we want to go back to
63+
64+
if (targetPageToken !== undefined) {
65+
setPageStack(newStack);
66+
setCurrentPage(targetPageToken);
67+
setPageNumber((prev) => prev - 1);
68+
}
69+
}
70+
}, [pageStack]);
71+
72+
const resetPagination = useCallback(() => {
73+
setCurrentPage(null);
74+
setPageStack([null]);
75+
setPageNumber(1);
76+
}, []);
77+
78+
// Pagination info
79+
const totalCount = namesData?.total_count ?? 0;
80+
const hasPrevious = pageStack.length > 1;
81+
const hasNext = namesData?.has_more ?? false;
82+
const currentPageNumber = pageNumber;
83+
84+
// Reset pagination when component mounts or address/network changes
85+
useEffect(() => {
86+
resetPagination();
87+
}, [address, network, resetPagination]);
88+
89+
return {
90+
namesData,
91+
isLoading,
92+
error,
93+
refetch,
94+
// Pagination
95+
goToNextPage,
96+
goToPreviousPage,
97+
resetPagination,
98+
hasPrevious,
99+
hasNext,
100+
totalCount,
101+
currentPageNumber,
102+
};
41103
}
42104

43105
export function useRemoveNameFromUI(domain: Basename) {

0 commit comments

Comments
 (0)