From 5f1e2940914c11e0c1759c1f7db93548bd930aa5 Mon Sep 17 00:00:00 2001 From: g Date: Wed, 31 Dec 2025 05:46:24 +0000 Subject: [PATCH] Add pagination to admin user, vendor, and ban lists Introduces pagination controls and server-side paginated fetching for blocked users, users, and vendors in the admin dashboard. Improves error handling in server API responses and validates order ID in OrderDetailsModal. Updates git-info.json with latest commit metadata. --- app/dashboard/admin/ban/page.tsx | 52 ++++++++++++++++++++++-- app/dashboard/admin/users/page.tsx | 47 +++++++++++++++++++-- components/admin/OrderDetailsModal.tsx | 15 ++++++- components/admin/VendorsCard.tsx | 56 +++++++++++++++++++++++--- lib/server-api.ts | 40 ++++++++++++++++-- public/git-info.json | 4 +- 6 files changed, 194 insertions(+), 20 deletions(-) diff --git a/app/dashboard/admin/ban/page.tsx b/app/dashboard/admin/ban/page.tsx index 1941368..9f3f37e 100644 --- a/app/dashboard/admin/ban/page.tsx +++ b/app/dashboard/admin/ban/page.tsx @@ -33,6 +33,15 @@ export default function AdminBanPage() { const [blockedUsers, setBlockedUsers] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [banDialogOpen, setBanDialogOpen] = useState(false); + const [page, setPage] = useState(1); + const [pagination, setPagination] = useState<{ + page: number; + limit: number; + total: number; + totalPages: number; + hasNextPage: boolean; + hasPrevPage: boolean; + } | null>(null); const [banData, setBanData] = useState({ telegramUserId: "", reason: "", @@ -41,13 +50,25 @@ export default function AdminBanPage() { useEffect(() => { fetchBlockedUsers(); - }, []); + }, [page]); const fetchBlockedUsers = async () => { try { setLoading(true); - const data = await fetchClient("/admin/blocked-users"); - setBlockedUsers(data); + const data = await fetchClient<{ + success: boolean; + blockedUsers: BlockedUser[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + hasNextPage: boolean; + hasPrevPage: boolean; + }; + }>(`/admin/blocked-users?page=${page}&limit=25`); + setBlockedUsers(data.blockedUsers); + setPagination(data.pagination); } catch (error) { console.error("Failed to fetch blocked users:", error); toast({ @@ -395,6 +416,31 @@ export default function AdminBanPage() { )} + {pagination && pagination.totalPages > 1 && ( +
+
+ Showing page {pagination.page} of {pagination.totalPages} ({pagination.total} total) +
+
+ + +
+
+ )} diff --git a/app/dashboard/admin/users/page.tsx b/app/dashboard/admin/users/page.tsx index 3be91f9..bbe5716 100644 --- a/app/dashboard/admin/users/page.tsx +++ b/app/dashboard/admin/users/page.tsx @@ -25,6 +25,19 @@ interface TelegramUser { createdAt?: string; } +interface PaginationResponse { + success: boolean; + users: TelegramUser[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + hasNextPage: boolean; + hasPrevPage: boolean; + }; +} + function formatCurrency(amount: number): string { return new Intl.NumberFormat('en-GB', { style: 'currency', @@ -37,16 +50,19 @@ export default function AdminUsersPage() { const [loading, setLoading] = useState(true); const [users, setUsers] = useState([]); const [searchQuery, setSearchQuery] = useState(""); + const [page, setPage] = useState(1); + const [pagination, setPagination] = useState(null); useEffect(() => { fetchUsers(); - }, []); + }, [page]); const fetchUsers = async () => { try { setLoading(true); - const data = await fetchClient("/admin/users"); - setUsers(data); + const data = await fetchClient(`/admin/users?page=${page}&limit=25`); + setUsers(data.users); + setPagination(data.pagination); } catch (error: any) { console.error("Failed to fetch users:", error); toast({ @@ -300,6 +316,31 @@ export default function AdminUsersPage() { )} + {pagination && pagination.totalPages > 1 && ( +
+
+ Showing page {pagination.page} of {pagination.totalPages} ({pagination.total} total users) +
+
+ + +
+
+ )} diff --git a/components/admin/OrderDetailsModal.tsx b/components/admin/OrderDetailsModal.tsx index 6b5d2bc..cda7277 100644 --- a/components/admin/OrderDetailsModal.tsx +++ b/components/admin/OrderDetailsModal.tsx @@ -157,10 +157,21 @@ export default function OrderDetailsModal({ orderId, open, onOpenChange }: Order setLoading(true); setError(null); try { + // Validate orderId before making request + if (!orderId || orderId === 'undefined' || orderId === 'null') { + throw new Error('Order ID is required'); + } + + // Ensure orderId is a valid number or string + const orderIdStr = String(orderId).trim(); + if (!orderIdStr || orderIdStr === 'undefined' || orderIdStr === 'null') { + throw new Error('Invalid order ID'); + } + // Fetch full order details from admin endpoint // Use /admin/orders/:orderId (fetchClient will add /api prefix and backend URL) - console.log(`Fetching order details for order #${orderId}`); - const orderData = await fetchClient(`/admin/orders/${orderId}`); + console.log(`Fetching order details for order #${orderIdStr}`); + const orderData = await fetchClient(`/admin/orders/${orderIdStr}`); console.log("Order data received:", orderData); diff --git a/components/admin/VendorsCard.tsx b/components/admin/VendorsCard.tsx index 6160dc0..1034254 100644 --- a/components/admin/VendorsCard.tsx +++ b/components/admin/VendorsCard.tsx @@ -10,18 +10,37 @@ interface Vendor { lastLogin?: string; } +interface PaginationResponse { + success: boolean; + vendors: Vendor[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + hasNextPage: boolean; + hasPrevPage: boolean; + }; +} + export default function VendorsCard() { const [vendors, setVendors] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [resetTokens, setResetTokens] = useState>({}); + const [page, setPage] = useState(1); + const [pagination, setPagination] = useState(null); useEffect(() => { let mounted = true; (async () => { try { - const data = await fetchClient("/admin/vendors"); - if (mounted) setVendors(data); + setLoading(true); + const data = await fetchClient(`/admin/vendors?page=${page}&limit=10`); + if (mounted) { + setVendors(data.vendors); + setPagination(data.pagination); + } } catch (e: any) { if (mounted) setError(e?.message || "Failed to load vendors"); } finally { @@ -29,7 +48,7 @@ export default function VendorsCard() { } })(); return () => { mounted = false; }; - }, []); + }, [page]); async function generateResetToken(vendorId: string) { try { @@ -55,8 +74,9 @@ export default function VendorsCard() { ) : vendors.length === 0 ? (

No vendors found

) : ( -
- {vendors.map((vendor) => ( + <> +
+ {vendors.map((vendor) => (
@@ -88,7 +108,31 @@ export default function VendorsCard() { )}
))} -
+
+ {pagination && pagination.totalPages > 1 && ( +
+ + Page {pagination.page} of {pagination.totalPages} ({pagination.total} total) + +
+ + +
+
+ )} + )}
); diff --git a/lib/server-api.ts b/lib/server-api.ts index 204a6da..fcbd0d0 100644 --- a/lib/server-api.ts +++ b/lib/server-api.ts @@ -86,8 +86,26 @@ export async function fetchServer( // Handle other errors if (!res.ok) { - const errorData = await res.json().catch(() => ({})); - const errorMessage = errorData.message || errorData.error || `Request failed: ${res.status} ${res.statusText}`; + let errorData; + try { + errorData = await res.json(); + } catch { + errorData = {}; + } + + // Handle new error format: { success: false, error: { message: "...", code: "..." } } + // or old format: { error: "...", message: "..." } + let errorMessage: string; + if (errorData.error?.message) { + errorMessage = errorData.error.message; + } else if (typeof errorData.error === 'string') { + errorMessage = errorData.error; + } else if (errorData.message) { + errorMessage = errorData.message; + } else { + errorMessage = `Request failed: ${res.status} ${res.statusText}`; + } + throw new Error(errorMessage); } @@ -96,10 +114,24 @@ export async function fetchServer( return {} as T; } - return await res.json(); + try { + const data = await res.json(); + return data; + } catch (parseError) { + // If JSON parsing fails, throw a proper error + throw new Error(`Failed to parse response from ${endpoint}: ${parseError instanceof Error ? parseError.message : String(parseError)}`); + } } catch (error) { console.error(`Server request to ${endpoint} failed:`, error); - throw error; + // Ensure we always throw an Error instance, not an object + if (error instanceof Error) { + throw error; + } else if (typeof error === 'string') { + throw new Error(error); + } else { + const errorStr = error && typeof error === 'object' ? JSON.stringify(error) : String(error); + throw new Error(`Request failed: ${errorStr}`); + } } } diff --git a/public/git-info.json b/public/git-info.json index 44bb8a8..d7004a3 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "96638f9", - "buildTime": "2025-12-31T05:14:19.565Z" + "commitHash": "0062aa2", + "buildTime": "2025-12-31T05:39:04.712Z" } \ No newline at end of file