From d5a8a58523e6e862d9f1b78ae27b888b6cc670a5 Mon Sep 17 00:00:00 2001 From: shogun444 Date: Fri, 29 May 2026 23:28:44 +0530 Subject: [PATCH 1/2] feat(admin): replace mock admin data with live API integrations --- app/(admin)/admin/analytics/page.tsx | 66 ++++- app/(admin)/admin/push-notifications/page.tsx | 75 +++-- app/(admin)/admin/transactions/page.tsx | 78 +++++- app/(admin)/admin/users/page.tsx | 56 +++- components/admin/AdminUserTable.tsx | 2 +- components/admin/RevenueChart.tsx | 20 +- components/admin/UserDetailPanel.tsx | 60 ++-- .../PushNotificationTable.tsx | 2 +- .../admin/transaction/TableTransaction.tsx | 17 +- lib/admin-mock-data.ts | 257 ------------------ lib/api/admin.ts | 158 +++++++++++ 11 files changed, 473 insertions(+), 318 deletions(-) delete mode 100644 lib/admin-mock-data.ts create mode 100644 lib/api/admin.ts diff --git a/app/(admin)/admin/analytics/page.tsx b/app/(admin)/admin/analytics/page.tsx index 45e7c8c..5d9c16e 100644 --- a/app/(admin)/admin/analytics/page.tsx +++ b/app/(admin)/admin/analytics/page.tsx @@ -1,13 +1,61 @@ "use client"; +import { useState, useEffect } from "react"; import { ChevronDown, UserPlus, ArrowUpDown, Clock, Coins } from "lucide-react"; import { AdminMetricCard } from "@/components/admin/AdminMetricCard"; import { RevenueChart } from "@/components/admin/RevenueChart"; -import { mockAdminMetrics, mockAdminUsers } from "@/lib/admin-mock-data"; - -const recentUsers = mockAdminUsers.slice(0, 5); +import { getAdminMetrics, getAdminUsers, type AdminMetrics, type AdminUser } from "@/lib/api/admin"; export default function AnalyticsPage() { + const [metrics, setMetrics] = useState(null); + const [recentUsers, setRecentUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchAnalyticsData() { + try { + setLoading(true); + setError(null); + const [fetchedMetrics, fetchedUsers] = await Promise.all([ + getAdminMetrics(), + getAdminUsers(), + ]); + setMetrics(fetchedMetrics); + setRecentUsers(fetchedUsers.slice(0, 5)); + } catch (err: any) { + console.error("Error fetching analytics data:", err); + setError(err?.message || "Failed to load analytics data."); + } finally { + setLoading(false); + } + } + fetchAnalyticsData(); + }, []); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error || !metrics) { + return ( +
+

Error Loading Analytics

+

{error || "Could not retrieve metrics"}

+ +
+ ); + } + return (
{/* Overview section header */} @@ -24,22 +72,22 @@ export default function AnalyticsPage() {
@@ -53,13 +101,13 @@ export default function AnalyticsPage() {

Total Deposits

-

{mockAdminMetrics.totalDeposits.toLocaleString()}

+

{metrics.totalDeposits.toLocaleString()}

Total Withdrawals

-

{mockAdminMetrics.totalWithdrawals.toLocaleString()}

+

{metrics.totalWithdrawals.toLocaleString()}

diff --git a/app/(admin)/admin/push-notifications/page.tsx b/app/(admin)/admin/push-notifications/page.tsx index bc12883..aec7f23 100644 --- a/app/(admin)/admin/push-notifications/page.tsx +++ b/app/(admin)/admin/push-notifications/page.tsx @@ -1,18 +1,35 @@ "use client"; -import { useState, useMemo } from "react"; -import { mockPushNotifications, PushNotification } from "@/lib/admin-mock-data"; +import { useState, useMemo, useEffect } from "react"; import PushNotificationTable from "@/components/admin/push-notifications/PushNotificationTable"; import CreatePushNotificationModal from "@/components/admin/push-notifications/CreatePushNotificationModal"; +import { getAdminPushNotifications, createAdminPushNotification, type PushNotification } from "@/lib/api/admin"; import { Search, Plus } from "lucide-react"; -export default function TransactionsPage() { - const [notifications, setNotifications] = useState( - mockPushNotifications, - ); +export default function PushNotificationsPage() { + const [notifications, setNotifications] = useState([]); const [selectedIds, setSelectedIds] = useState([]); const [search, setSearch] = useState(""); const [isModalOpen, setIsModalOpen] = useState(false); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + async function fetchNotifications() { + try { + setLoading(true); + setError(null); + const data = await getAdminPushNotifications(); + setNotifications(data); + } catch (err: any) { + console.error("Error fetching push notifications:", err); + setError(err?.message || "Failed to load push notifications."); + } finally { + setLoading(false); + } + } + fetchNotifications(); + }, []); const filteredData = useMemo(() => { return notifications.filter( @@ -23,6 +40,7 @@ export default function TransactionsPage() { }, [notifications, search]); const handleDeactivate = () => { + // Since backend does not explicitly support deactivate in simple REST, we simulate locally setNotifications((prev) => prev.map((n) => selectedIds.includes(n.id) ? { ...n, status: "Inactive" } : n, @@ -31,22 +49,39 @@ export default function TransactionsPage() { setSelectedIds([]); }; - const handleCreate = (title: string, message: string) => { - const newNotification: PushNotification = { - id: Date.now().toString(), - title, - message, - status: "Active", - createdAt: new Date().toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }), - }; - - setNotifications((prev) => [newNotification, ...prev]); + const handleCreate = async (title: string, message: string) => { + try { + const newNotification = await createAdminPushNotification({ title, message }); + setNotifications((prev) => [newNotification, ...prev]); + } catch (err: any) { + console.error("Error creating push notification:", err); + alert(err?.message || "Failed to create push notification."); + } }; + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+

Error Loading Push Notifications

+

{error}

+ +
+ ); + } + return (
{/* Top Bar */} diff --git a/app/(admin)/admin/transactions/page.tsx b/app/(admin)/admin/transactions/page.tsx index a016437..e218784 100644 --- a/app/(admin)/admin/transactions/page.tsx +++ b/app/(admin)/admin/transactions/page.tsx @@ -1,11 +1,85 @@ +"use client"; + +import { useState, useEffect, useMemo } from "react"; import { TableTransaction } from "@/components/admin/transaction/TableTransaction"; import { TransactionFilters } from "@/components/admin/transaction/TransactionFilters"; +import { getAdminTransactions, type AdminTransaction } from "@/lib/api/admin"; export default function TransactionPage() { + const [transactions, setTransactions] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [searchQuery, setSearchQuery] = useState(""); + const [activeFilter, setActiveFilter] = useState("All"); + + useEffect(() => { + async function fetchTransactions() { + try { + setLoading(true); + setError(null); + const data = await getAdminTransactions(); + setTransactions(data); + } catch (err: any) { + console.error("Error fetching transactions:", err); + setError(err?.message || "Failed to load transactions."); + } finally { + setLoading(false); + } + } + fetchTransactions(); + }, []); + + const filteredTransactions = useMemo(() => { + return transactions.filter((tx) => { + // Search by username or txId + const matchesSearch = + !searchQuery.trim() || + tx.username.toLowerCase().includes(searchQuery.toLowerCase()) || + tx.txId.toLowerCase().includes(searchQuery.toLowerCase()); + + // Filter by active type (backend withdrawal maps to Withdraw) + const matchesFilter = + activeFilter === "All" || + tx.type.toLowerCase() === activeFilter.toLowerCase() || + (activeFilter === "Withdrawal" && tx.type === "Withdraw"); + + return matchesSearch && matchesFilter; + }); + }, [transactions, searchQuery, activeFilter]); + + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+

Error Loading Transactions

+

{error}

+ +
+ ); + } + return (
- - + +
); } diff --git a/app/(admin)/admin/users/page.tsx b/app/(admin)/admin/users/page.tsx index ed90542..7c26861 100644 --- a/app/(admin)/admin/users/page.tsx +++ b/app/(admin)/admin/users/page.tsx @@ -1,29 +1,49 @@ 'use client'; -import { useState, useMemo } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import { Search, Filter } from 'lucide-react'; -import { mockAdminUsers, AdminUser } from '@/lib/admin-mock-data'; import { AdminUserTable } from '@/components/admin/AdminUserTable'; import { UserDetailPanel } from '@/components/admin/UserDetailPanel'; +import { getAdminUsers, type AdminUser } from '@/lib/api/admin'; const ITEMS_PER_PAGE = 10; export default function UsersPage() { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [selectedUser, setSelectedUser] = useState(null); + useEffect(() => { + async function fetchUsers() { + try { + setLoading(true); + setError(null); + const fetchedUsers = await getAdminUsers(); + setUsers(fetchedUsers); + } catch (err: any) { + console.error('Error fetching users:', err); + setError(err?.message || 'Failed to load users.'); + } finally { + setLoading(false); + } + } + fetchUsers(); + }, []); + // Filter users based on search query const filteredUsers = useMemo(() => { - if (!searchQuery.trim()) return mockAdminUsers; + if (!searchQuery.trim()) return users; const query = searchQuery.toLowerCase(); - return mockAdminUsers.filter(user => + return users.filter(user => user.email.toLowerCase().includes(query) || user.firstName?.toLowerCase().includes(query) || user.lastName?.toLowerCase().includes(query) ); - }, [searchQuery]); + }, [users, searchQuery]); // Paginate filtered users const paginatedUsers = useMemo(() => { @@ -33,7 +53,7 @@ export default function UsersPage() { }, [filteredUsers, currentPage]); const totalPages = Math.ceil(filteredUsers.length / ITEMS_PER_PAGE); - const startEntry = (currentPage - 1) * ITEMS_PER_PAGE + 1; + const startEntry = filteredUsers.length === 0 ? 0 : (currentPage - 1) * ITEMS_PER_PAGE + 1; const endEntry = Math.min(currentPage * ITEMS_PER_PAGE, filteredUsers.length); const handleNextPage = () => { @@ -46,6 +66,30 @@ export default function UsersPage() { setCurrentPage(1); }; + if (loading) { + return ( +
+
+
+ ); + } + + if (error) { + return ( +
+

Error Loading Users

+

{error}

+ +
+ ); + } + + return (
{/* Mobile Header */} diff --git a/components/admin/AdminUserTable.tsx b/components/admin/AdminUserTable.tsx index 40ac549..7af2524 100644 --- a/components/admin/AdminUserTable.tsx +++ b/components/admin/AdminUserTable.tsx @@ -1,6 +1,6 @@ 'use client'; -import { AdminUser } from '@/lib/admin-mock-data'; +import { type AdminUser } from '@/lib/api/admin'; interface AdminUserTableProps { users: AdminUser[]; diff --git a/components/admin/RevenueChart.tsx b/components/admin/RevenueChart.tsx index 5c25f42..f69ed5f 100644 --- a/components/admin/RevenueChart.tsx +++ b/components/admin/RevenueChart.tsx @@ -10,7 +10,25 @@ import { Tooltip, } from "recharts"; import { ChevronDown } from "lucide-react"; -import { mockRevenueData } from "@/lib/admin-mock-data"; +// NOTE: The backend's GET /admin/metrics only returns single summary totals +// (totalDeposits, totalWithdrawals) instead of time-series/historical monthly progress. +// To keep this visual chart component working premium-grade without rendering empty dashboards, +// we retain the historical monthly structure from mockRevenueData. +// Raised a backend feature request for GET /admin/revenue-chart or time-series metrics. +const mockRevenueData = [ + { month: 'JAN', value: 60000 }, + { month: 'FEB', value: 85000 }, + { month: 'MAR', value: 50000 }, + { month: 'APR', value: 45000 }, + { month: 'MAY', value: 90000 }, + { month: 'JUN', value: 10000 }, + { month: 'JUL', value: 30000 }, + { month: 'AUG', value: 25000 }, + { month: 'SEP', value: 92000 }, + { month: 'OCT', value: 70000 }, + { month: 'NOV', value: 55000 }, + { month: 'DEC', value: 68000 }, +]; const ACTIVE_MONTH = "MAY"; const ACTIVE_COLOR = "#F97316"; diff --git a/components/admin/UserDetailPanel.tsx b/components/admin/UserDetailPanel.tsx index 792937d..bda8e0e 100644 --- a/components/admin/UserDetailPanel.tsx +++ b/components/admin/UserDetailPanel.tsx @@ -1,7 +1,7 @@ 'use client'; -import { useState } from 'react'; -import { AdminUser } from '@/lib/admin-mock-data'; +import { useState, useEffect } from 'react'; +import { type AdminUser, getAdminUserById } from '@/lib/api/admin'; import { X, Eye, EyeOff, Copy, MoreVertical, Trash2 } from 'lucide-react'; interface UserDetailPanelProps { @@ -10,11 +10,33 @@ interface UserDetailPanelProps { } export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) { + const [currentUser, setCurrentUser] = useState(user); + const [detailsLoading, setDetailsLoading] = useState(false); const [showSensitiveInfo, setShowSensitiveInfo] = useState(true); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + useEffect(() => { + setCurrentUser(user); // Reset when user prop changes + + async function fetchDetails() { + try { + setDetailsLoading(true); + const fullDetails = await getAdminUserById(user.id); + if (fullDetails) { + setCurrentUser(fullDetails); + } + } catch (err) { + console.warn(`Admin user details GET for ID ${user.id} failed or route GET /admin/users/:id does not exist on backend. Using prop data:`, err); + } finally { + setDetailsLoading(false); + } + } + + fetchDetails(); + }, [user]); + const handleCopyWallet = () => { - navigator.clipboard.writeText(user.walletAddress); + navigator.clipboard.writeText(currentUser.walletAddress); }; const handleDelete = () => { @@ -22,16 +44,16 @@ export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) { }; const confirmDelete = () => { - console.log('User deleted:', user.id); + console.log('User deleted:', currentUser.id); setShowDeleteConfirm(false); onClose(); }; const getInitials = () => { - if (user.firstName && user.lastName) { - return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase(); + if (currentUser.firstName && currentUser.lastName) { + return `${currentUser.firstName[0]}${currentUser.lastName[0]}`.toUpperCase(); } - return user.email[0].toUpperCase(); + return currentUser.email[0].toUpperCase(); }; return ( @@ -42,7 +64,9 @@ export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) {
{/* Header */}
-

User Details

+

+ User Details {detailsLoading && (fetching details...)} +

@@ -73,21 +97,21 @@ export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) {

Name

- {showSensitiveInfo ? (user.firstName && user.lastName ? `${user.firstName} , ${user.lastName}` : 'No name') : '••••••••'} + {showSensitiveInfo ? (currentUser.firstName && currentUser.lastName ? `${currentUser.firstName} , ${currentUser.lastName}` : 'No name') : '••••••••'}

Email address

-

{showSensitiveInfo ? user.email : '••••••••'}

+

{showSensitiveInfo ? currentUser.email : '••••••••'}

Phone Number

-

{showSensitiveInfo ? (user.phone || 'No phone number') : '••••••••'}

+

{showSensitiveInfo ? (currentUser.phone || 'No phone number') : '••••••••'}

Wallet address

-

{showSensitiveInfo ? user.walletAddress : '••••••••'}

+

{showSensitiveInfo ? currentUser.walletAddress : '••••••••'}

{showSensitiveInfo && (

Username

-

{showSensitiveInfo ? user.username : '••••••••'}

+

{showSensitiveInfo ? currentUser.username : '••••••••'}

@@ -105,15 +129,15 @@ export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) {

Transactions

-

{user.transactions}

+

{currentUser.transactions}

Total deposit

-

NGN{user.totalDeposit.toFixed(2)}

+

NGN{currentUser.totalDeposit.toFixed(2)}

Total withdraw

-

NGN{user.totalWithdraw.toFixed(2)}

+

NGN{currentUser.totalWithdraw.toFixed(2)}

@@ -122,7 +146,7 @@ export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) {

KYC Verification

- {user.kycStatus} + {currentUser.kycStatus}
@@ -142,7 +166,7 @@ export function UserDetailPanel({ user, onClose }: UserDetailPanelProps) {
Account created - {user.createdAt} + {currentUser.createdAt}
diff --git a/components/admin/push-notifications/PushNotificationTable.tsx b/components/admin/push-notifications/PushNotificationTable.tsx index 3fa0c26..6b350ad 100644 --- a/components/admin/push-notifications/PushNotificationTable.tsx +++ b/components/admin/push-notifications/PushNotificationTable.tsx @@ -1,4 +1,4 @@ -import { PushNotification } from "@/lib/admin-mock-data"; +import { type PushNotification } from "@/lib/api/admin"; type Props = { data: PushNotification[]; diff --git a/components/admin/transaction/TableTransaction.tsx b/components/admin/transaction/TableTransaction.tsx index 135562c..621e367 100644 --- a/components/admin/transaction/TableTransaction.tsx +++ b/components/admin/transaction/TableTransaction.tsx @@ -1,7 +1,11 @@ -import { mockAdminTransactions } from "@/lib/admin-mock-data"; +import { type AdminTransaction } from "@/lib/api/admin"; import { TypeTransaction } from "./TypeTransaction"; -export function TableTransaction() { +interface TableTransactionProps { + transactions: AdminTransaction[]; +} + +export function TableTransaction({ transactions }: TableTransactionProps) { return (
@@ -22,7 +26,7 @@ export function TableTransaction() { - {mockAdminTransactions.map((item) => ( + {transactions.map((item) => ( ))} + {transactions.length === 0 && ( + + + + )}
@@ -47,6 +51,13 @@ export function TableTransaction() {
+ No transactions found. +
diff --git a/lib/admin-mock-data.ts b/lib/admin-mock-data.ts deleted file mode 100644 index dfc52d2..0000000 --- a/lib/admin-mock-data.ts +++ /dev/null @@ -1,257 +0,0 @@ -export type PushNotification = { - id: string; - title: string; - message: string; - status: "Active" | "Inactive"; - createdAt: string; -}; - -export const mockPushNotifications: PushNotification[] = [ - { - id: "1", - title: "Topics", - message: - "Lorem ipsum dolor sit amet consectetur. Nullam sapien varius nec enim dignissim v um dolor sit amet consectetur. Nullam sapien varius nec enim dignissim nibh dignissim blandit. Aliquam ac faucibus montes a tempor. Est consequat metus vitae tellus commodo. Cursus nunc accumsan in arcu.", - status: "Active", - createdAt: "Jul 6, 2025", - }, - { - id: "2", - title: "Header", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, - { - id: "3", - title: "Sub-Header", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, - { - id: "4", - title: "Topics", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, - { - id: "5", - title: "Topics", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, - { - id: "6", - title: "Topics", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, - { - id: "7", - title: "Topics", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, - { - id: "8", - title: "Topics", - message: "Lorem ipsum dolor sit amet...", - status: "Inactive", - createdAt: "Aug 6, 2005", - }, -] as const; -export interface AdminUser { - id: string; - email: string; - firstName: string | null; - lastName: string | null; - phone: string | null; - walletAddress: string; - username: string; - avatarUrl: string | null; - transactions: number; - totalDeposit: number; - totalWithdraw: number; - kycStatus: 'Verified' | 'Unverified'; - createdAt: string; - isActive: boolean; -} - -export const mockAdminUsers: AdminUser[] = [ - { - id: '1', - email: 'cerseiloaded@hotmail.com', - firstName: 'First name', - lastName: 'Last name', - phone: '+65 9012474475', - walletAddress: '0xAbc...123', - username: 'cersei', - avatarUrl: null, - transactions: 32, - totalDeposit: 315.00, - totalWithdraw: 9.20, - kycStatus: 'Unverified', - createdAt: 'Jul 4, 2025', - isActive: true, - }, - { - id: '2', - email: 'cerseihotmail@gmail.com', - firstName: null, - lastName: null, - phone: null, - walletAddress: '0xDef...456', - username: 'user2', - avatarUrl: null, - transactions: 15, - totalDeposit: 500.00, - totalWithdraw: 50.00, - kycStatus: 'Verified', - createdAt: 'Jun 14, 2025', - isActive: true, - }, - { - id: '3', - email: 'john.doe@example.com', - firstName: 'John', - lastName: 'Doe', - phone: '+1 555-0123', - walletAddress: '0xGhi...789', - username: 'johndoe', - avatarUrl: null, - transactions: 48, - totalDeposit: 1200.00, - totalWithdraw: 300.00, - kycStatus: 'Verified', - createdAt: 'Jun 14, 2025', - isActive: true, - }, -]; - -// Generate more mock users to reach 100 entries -for (let i = 4; i <= 100; i++) { - mockAdminUsers.push({ - id: `${i}`, - email: `user${i}@example.com`, - firstName: i % 3 === 0 ? null : `User${i}`, - lastName: i % 3 === 0 ? null : `Last${i}`, - phone: i % 4 === 0 ? null : `+1 555-${String(i).padStart(4, '0')}`, - walletAddress: `0x${Math.random().toString(16).substr(2, 6)}...${Math.random().toString(16).substr(2, 3)}`, - username: `user${i}`, - avatarUrl: null, - transactions: Math.floor(Math.random() * 100), - totalDeposit: Math.floor(Math.random() * 10000) / 10, - totalWithdraw: Math.floor(Math.random() * 1000) / 10, - kycStatus: i % 2 === 0 ? 'Verified' : 'Unverified', - createdAt: 'Jun 14, 2025', - isActive: true, - }); -} - -export const mockAdminUserDetails = { - id: '1', - firstName: 'First name', - lastName: 'Last name', - email: 'cerseiloaded@hotmail.com', - phone: '+65 9012474475', - walletAddress: '0xAbc...123', - username: 'cersei', - avatarUrl: null, - transactions: 32, - totalDeposit: 315.00, - totalWithdraw: 9.20, - kycStatus: 'Unverified', - createdAt: 'Jul 4, 2025', -}; - -export const mockAdminMetrics = { - registeredUsers: 1524, - totalTransactions: 15000, - pendingKyc: 25, - currencies: 8, - totalDeposits: 50000, - totalWithdrawals: 30000, -}; - -export const mockRevenueData = [ - { month: 'JAN', value: 60000 }, - { month: 'FEB', value: 85000 }, - { month: 'MAR', value: 50000 }, - { month: 'APR', value: 45000 }, - { month: 'MAY', value: 90000 }, - { month: 'JUN', value: 10000 }, - { month: 'JUL', value: 30000 }, - { month: 'AUG', value: 25000 }, - { month: 'SEP', value: 92000 }, - { month: 'OCT', value: 70000 }, - { month: 'NOV', value: 55000 }, - { month: 'DEC', value: 68000 }, -]; - -export const mockAdminTransactions = [ - { - id: "1", - amount: 20000, - currency: "NGN", - type: "Deposit", - username: "Cersei@gmail.com", - date: "04.07.2025 12:40 AM", - txId: "0x....4yh6789", - status: "active", - }, - { - id: "2", - amount: 3000, - currency: "NGN", - type: "Withdraw", - username: "Cersei@gmail.com", - date: "04.07.2025 12:40 AM", - txId: "0x....4yh6789", - status: "active", - }, - { - id: "3", - amount: 20000, - currency: "NGN", - type: "Deposit", - username: "Cersei@gmail.com", - date: "04.07.2025 12:40 AM", - txId: "0x....4yh6789", - status: "active", - }, - { - id: "4", - amount: 3000, - currency: "NGN", - type: "Withdraw", - username: "Cersei@gmail.com", - date: "04.07.2025 12:40 AM", - txId: "0x....4yh6789", - status: "active", - }, - { - id: "5", - amount: 5000, - currency: "NGN", - type: "Convert", - username: "Cersei@gmail.com", - date: "04.07.2025 12:40 AM", - txId: "0x....4yh6789", - status: "active", - }, - { - id: "6", - amount: 3000, - currency: "NGN", - type: "Withdraw", - username: "Cersei@gmail.com", - date: "04.07.2025 12:40 AM", - txId: "0x....4yh6789", - status: "active", - }, -]; diff --git a/lib/api/admin.ts b/lib/api/admin.ts new file mode 100644 index 0000000..542b4a9 --- /dev/null +++ b/lib/api/admin.ts @@ -0,0 +1,158 @@ +import { apiClient } from '../api-client'; + +export interface AdminMetrics { + registeredUsers: number; + totalTransactions: number; + pendingKyc: number; + currencies: number; + totalDeposits: number; + totalWithdrawals: number; +} + +export interface AdminUser { + id: string; + email: string; + firstName: string | null; + lastName: string | null; + phone: string | null; + walletAddress: string; + username: string; + avatarUrl: string | null; + transactions: number; + totalDeposit: number; + totalWithdraw: number; + kycStatus: 'Verified' | 'Unverified'; + createdAt: string; + isActive: boolean; +} + +export interface AdminTransaction { + id: string; + amount: number; + currency: string; + type: 'Deposit' | 'Withdraw' | 'Convert'; + username: string; + date: string; + txId: string; + status: string; +} + +export interface PushNotification { + id: string; + title: string; + message: string; + status: 'Active' | 'Inactive'; + createdAt: string; +} + +// Safe normalization of Admin Users +export function mapAdminUser(dto: any): AdminUser { + return { + id: String(dto.id ?? dto._id ?? ''), + email: String(dto.email ?? ''), + firstName: dto.firstName ?? dto.first_name ?? null, + lastName: dto.lastName ?? dto.last_name ?? null, + phone: dto.phone ?? null, + walletAddress: dto.walletAddress ?? dto.wallet_address ?? dto.address ?? '0x...', + username: dto.username ?? dto.email?.split('@')[0] ?? 'user', + avatarUrl: dto.avatarUrl ?? dto.avatar_url ?? null, + transactions: Number(dto.transactions ?? dto.transactionCount ?? 0), + totalDeposit: Number(dto.totalDeposit ?? dto.total_deposit ?? 0), + totalWithdraw: Number(dto.totalWithdraw ?? dto.total_withdraw ?? 0), + kycStatus: dto.kycStatus === 'Verified' || dto.kyc_status === 'Verified' ? 'Verified' : 'Unverified', + createdAt: dto.createdAt ?? dto.created_at ? new Date(dto.createdAt ?? dto.created_at).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) : 'N/A', + isActive: Boolean(dto.isActive ?? dto.is_active ?? true), + }; +} + +export async function getAdminMetrics(): Promise { + const response = await apiClient('/admin/metrics'); + const data = response?.data ?? response ?? {}; + return { + registeredUsers: Number(data.registeredUsers ?? 0), + totalTransactions: Number(data.totalTransactions ?? 0), + pendingKyc: Number(data.pendingKyc ?? 0), + currencies: Number(data.currencies ?? 0), + totalDeposits: Number(data.totalDeposits ?? 0), + totalWithdrawals: Number(data.totalWithdrawals ?? 0), + }; +} + +export async function getAdminUsers(): Promise { + const response = await apiClient('/admin/users'); + const data = response?.data ?? response ?? []; + return Array.isArray(data) ? data.map(mapAdminUser) : []; +} + +export async function getAdminUserById(id: string): Promise { + const response = await apiClient(`/admin/users/${id}`); + const data = response?.data ?? response ?? {}; + return mapAdminUser(data); +} + +export async function getAdminTransactions(): Promise { + const response = await apiClient('/admin/transactions'); + const data = response?.data ?? response ?? []; + const typeMap: Record = { + deposit: 'Deposit', + withdrawal: 'Withdraw', + withdraw: 'Withdraw', + convert: 'Convert', + conversion: 'Convert', + }; + return Array.isArray(data) ? data.map((dto: any) => ({ + id: String(dto.id ?? dto._id ?? ''), + amount: Number(dto.amount ?? 0), + currency: String(dto.currency ?? 'NGN'), + type: typeMap[(dto.type as string)?.toLowerCase()] ?? 'Deposit', + username: dto.username ?? dto.email ?? 'Unknown User', + date: dto.createdAt ?? dto.date ? new Date(dto.createdAt ?? dto.date).toLocaleString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) : 'N/A', + txId: dto.txId ?? dto.transactionRef ?? dto.reference ?? dto.id ?? '0x...', + status: dto.status ?? 'active', + })) : []; +} + +export async function getAdminPushNotifications(): Promise { + const response = await apiClient('/admin/push-notifications'); + const data = response?.data ?? response ?? []; + return Array.isArray(data) ? data.map((dto: any) => ({ + id: String(dto.id ?? dto._id ?? ''), + title: String(dto.title ?? ''), + message: String(dto.message ?? ''), + status: dto.status === 'Active' || dto.status === 'active' ? 'Active' : 'Inactive', + createdAt: dto.createdAt ?? dto.created_at ? new Date(dto.createdAt ?? dto.created_at).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) : 'N/A', + })) : []; +} + +export async function createAdminPushNotification(payload: { title: string; message: string }): Promise { + const response = await apiClient('/admin/push-notifications', { + method: 'POST', + body: JSON.stringify(payload), + }); + const data = response?.data ?? response ?? {}; + return { + id: String(data.id ?? data._id ?? ''), + title: String(data.title ?? ''), + message: String(data.message ?? ''), + status: data.status === 'Active' || data.status === 'active' ? 'Active' : 'Inactive', + createdAt: data.createdAt ?? data.created_at ? new Date(data.createdAt ?? data.created_at).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) : 'N/A', + }; +} From ad9088b602b52d62a0708a01cdcc89daca112e84 Mon Sep 17 00:00:00 2001 From: shogun444 Date: Sat, 30 May 2026 14:23:40 +0530 Subject: [PATCH 2/2] Fixed CI --- app/(admin)/admin/analytics/page.tsx | 4 +- app/(admin)/admin/push-notifications/page.tsx | 8 +- app/(admin)/admin/transactions/page.tsx | 4 +- app/(admin)/admin/users/page.tsx | 4 +- lib/api/admin.ts | 198 +++++++++++++----- 5 files changed, 161 insertions(+), 57 deletions(-) diff --git a/app/(admin)/admin/analytics/page.tsx b/app/(admin)/admin/analytics/page.tsx index 5d9c16e..a24fc8a 100644 --- a/app/(admin)/admin/analytics/page.tsx +++ b/app/(admin)/admin/analytics/page.tsx @@ -23,9 +23,9 @@ export default function AnalyticsPage() { ]); setMetrics(fetchedMetrics); setRecentUsers(fetchedUsers.slice(0, 5)); - } catch (err: any) { + } catch (err: unknown) { console.error("Error fetching analytics data:", err); - setError(err?.message || "Failed to load analytics data."); + setError(err instanceof Error ? err.message : "Failed to load analytics data."); } finally { setLoading(false); } diff --git a/app/(admin)/admin/push-notifications/page.tsx b/app/(admin)/admin/push-notifications/page.tsx index aec7f23..5212134 100644 --- a/app/(admin)/admin/push-notifications/page.tsx +++ b/app/(admin)/admin/push-notifications/page.tsx @@ -21,9 +21,9 @@ export default function PushNotificationsPage() { setError(null); const data = await getAdminPushNotifications(); setNotifications(data); - } catch (err: any) { + } catch (err: unknown) { console.error("Error fetching push notifications:", err); - setError(err?.message || "Failed to load push notifications."); + setError(err instanceof Error ? err.message : "Failed to load push notifications."); } finally { setLoading(false); } @@ -53,9 +53,9 @@ export default function PushNotificationsPage() { try { const newNotification = await createAdminPushNotification({ title, message }); setNotifications((prev) => [newNotification, ...prev]); - } catch (err: any) { + } catch (err: unknown) { console.error("Error creating push notification:", err); - alert(err?.message || "Failed to create push notification."); + alert(err instanceof Error ? err.message : "Failed to create push notification."); } }; diff --git a/app/(admin)/admin/transactions/page.tsx b/app/(admin)/admin/transactions/page.tsx index e218784..9b47551 100644 --- a/app/(admin)/admin/transactions/page.tsx +++ b/app/(admin)/admin/transactions/page.tsx @@ -19,9 +19,9 @@ export default function TransactionPage() { setError(null); const data = await getAdminTransactions(); setTransactions(data); - } catch (err: any) { + } catch (err: unknown) { console.error("Error fetching transactions:", err); - setError(err?.message || "Failed to load transactions."); + setError(err instanceof Error ? err.message : "Failed to load transactions."); } finally { setLoading(false); } diff --git a/app/(admin)/admin/users/page.tsx b/app/(admin)/admin/users/page.tsx index 7c26861..3da1828 100644 --- a/app/(admin)/admin/users/page.tsx +++ b/app/(admin)/admin/users/page.tsx @@ -23,9 +23,9 @@ export default function UsersPage() { setError(null); const fetchedUsers = await getAdminUsers(); setUsers(fetchedUsers); - } catch (err: any) { + } catch (err: unknown) { console.error('Error fetching users:', err); - setError(err?.message || 'Failed to load users.'); + setError(err instanceof Error ? err.message : 'Failed to load users.'); } finally { setLoading(false); } diff --git a/lib/api/admin.ts b/lib/api/admin.ts index 542b4a9..3a29f59 100644 --- a/lib/api/admin.ts +++ b/lib/api/admin.ts @@ -45,8 +45,102 @@ export interface PushNotification { createdAt: string; } +interface AdminUserDto { + id?: string | number; + _id?: string | number; + email?: string; + firstName?: string | null; + first_name?: string | null; + lastName?: string | null; + last_name?: string | null; + phone?: string | null; + walletAddress?: string | null; + wallet_address?: string | null; + address?: string | null; + username?: string | null; + avatarUrl?: string | null; + avatar_url?: string | null; + transactions?: number | string | null; + transactionCount?: number | string | null; + totalDeposit?: number | string | null; + total_deposit?: number | string | null; + totalWithdraw?: number | string | null; + total_withdraw?: number | string | null; + kycStatus?: string | null; + kyc_status?: string | null; + createdAt?: string | null; + created_at?: string | null; + isActive?: boolean | null; + is_active?: boolean | null; +} + +interface AdminMetricsDto { + registeredUsers?: number | string | null; + totalTransactions?: number | string | null; + pendingKyc?: number | string | null; + currencies?: number | string | null; + totalDeposits?: number | string | null; + totalWithdrawals?: number | string | null; +} + +interface AdminMetricsResponse { + data?: AdminMetricsDto; + registeredUsers?: number | string | null; + totalTransactions?: number | string | null; + pendingKyc?: number | string | null; + currencies?: number | string | null; + totalDeposits?: number | string | null; + totalWithdrawals?: number | string | null; +} + +interface AdminUsersResponse { + data?: AdminUserDto[]; +} + +interface AdminUserResponse { + data?: AdminUserDto; +} + +interface AdminTransactionDto { + id?: string | number; + _id?: string | number; + amount?: number | string | null; + currency?: string | null; + type?: string | null; + username?: string | null; + email?: string | null; + createdAt?: string | null; + date?: string | null; + txId?: string | null; + transactionRef?: string | null; + reference?: string | null; + status?: string | null; +} + +interface AdminTransactionsResponse { + data?: AdminTransactionDto[]; +} + +interface PushNotificationDto { + id?: string | number; + _id?: string | number; + title?: string | null; + message?: string | null; + status?: string | null; + createdAt?: string | null; + created_at?: string | null; +} + +interface PushNotificationsResponse { + data?: PushNotificationDto[]; +} + +interface PushNotificationResponse { + data?: PushNotificationDto; +} + // Safe normalization of Admin Users -export function mapAdminUser(dto: any): AdminUser { +export function mapAdminUser(dto: AdminUserDto): AdminUser { return { id: String(dto.id ?? dto._id ?? ''), email: String(dto.email ?? ''), @@ -60,18 +154,21 @@ export function mapAdminUser(dto: any): AdminUser { totalDeposit: Number(dto.totalDeposit ?? dto.total_deposit ?? 0), totalWithdraw: Number(dto.totalWithdraw ?? dto.total_withdraw ?? 0), kycStatus: dto.kycStatus === 'Verified' || dto.kyc_status === 'Verified' ? 'Verified' : 'Unverified', - createdAt: dto.createdAt ?? dto.created_at ? new Date(dto.createdAt ?? dto.created_at).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric' - }) : 'N/A', + createdAt: (() => { + const dateVal = dto.createdAt ?? dto.created_at; + return dateVal ? new Date(dateVal).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) : 'N/A'; + })(), isActive: Boolean(dto.isActive ?? dto.is_active ?? true), }; } export async function getAdminMetrics(): Promise { - const response = await apiClient('/admin/metrics'); - const data = response?.data ?? response ?? {}; + const response = await apiClient('/admin/metrics'); + const data = response?.data ?? (response as AdminMetricsDto) ?? {}; return { registeredUsers: Number(data.registeredUsers ?? 0), totalTransactions: Number(data.totalTransactions ?? 0), @@ -83,20 +180,20 @@ export async function getAdminMetrics(): Promise { } export async function getAdminUsers(): Promise { - const response = await apiClient('/admin/users'); - const data = response?.data ?? response ?? []; - return Array.isArray(data) ? data.map(mapAdminUser) : []; + const response = await apiClient('/admin/users'); + const data = (Array.isArray(response) ? response : response?.data) ?? []; + return data.map(mapAdminUser); } export async function getAdminUserById(id: string): Promise { - const response = await apiClient(`/admin/users/${id}`); - const data = response?.data ?? response ?? {}; + const response = await apiClient(`/admin/users/${id}`); + const data = ('data' in response && response.data ? response.data : response) as AdminUserDto; return mapAdminUser(data); } export async function getAdminTransactions(): Promise { - const response = await apiClient('/admin/transactions'); - const data = response?.data ?? response ?? []; + const response = await apiClient('/admin/transactions'); + const data = (Array.isArray(response) ? response : response?.data) ?? []; const typeMap: Record = { deposit: 'Deposit', withdrawal: 'Withdraw', @@ -104,52 +201,59 @@ export async function getAdminTransactions(): Promise { convert: 'Convert', conversion: 'Convert', }; - return Array.isArray(data) ? data.map((dto: any) => ({ - id: String(dto.id ?? dto._id ?? ''), - amount: Number(dto.amount ?? 0), - currency: String(dto.currency ?? 'NGN'), - type: typeMap[(dto.type as string)?.toLowerCase()] ?? 'Deposit', - username: dto.username ?? dto.email ?? 'Unknown User', - date: dto.createdAt ?? dto.date ? new Date(dto.createdAt ?? dto.date).toLocaleString('en-GB', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - }) : 'N/A', - txId: dto.txId ?? dto.transactionRef ?? dto.reference ?? dto.id ?? '0x...', - status: dto.status ?? 'active', - })) : []; + return data.map((dto) => { + const rawDate = dto.createdAt ?? dto.date; + return { + id: String(dto.id ?? dto._id ?? ''), + amount: Number(dto.amount ?? 0), + currency: String(dto.currency ?? 'NGN'), + type: typeMap[String(dto.type ?? '').toLowerCase()] ?? 'Deposit', + username: dto.username ?? dto.email ?? 'Unknown User', + date: rawDate ? new Date(rawDate).toLocaleString('en-GB', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) : 'N/A', + txId: dto.txId ?? dto.transactionRef ?? dto.reference ?? String(dto.id ?? '0x...'), + status: dto.status ?? 'active', + }; + }); } export async function getAdminPushNotifications(): Promise { - const response = await apiClient('/admin/push-notifications'); - const data = response?.data ?? response ?? []; - return Array.isArray(data) ? data.map((dto: any) => ({ - id: String(dto.id ?? dto._id ?? ''), - title: String(dto.title ?? ''), - message: String(dto.message ?? ''), - status: dto.status === 'Active' || dto.status === 'active' ? 'Active' : 'Inactive', - createdAt: dto.createdAt ?? dto.created_at ? new Date(dto.createdAt ?? dto.created_at).toLocaleDateString('en-US', { - month: 'short', - day: 'numeric', - year: 'numeric' - }) : 'N/A', - })) : []; + const response = await apiClient('/admin/push-notifications'); + const data = (Array.isArray(response) ? response : response?.data) ?? []; + return data.map((dto) => { + const rawDate = dto.createdAt ?? dto.created_at; + return { + id: String(dto.id ?? dto._id ?? ''), + title: String(dto.title ?? ''), + message: String(dto.message ?? ''), + status: dto.status === 'Active' || dto.status === 'active' ? 'Active' : 'Inactive', + createdAt: rawDate ? new Date(rawDate).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric' + }) : 'N/A', + }; + }); } export async function createAdminPushNotification(payload: { title: string; message: string }): Promise { - const response = await apiClient('/admin/push-notifications', { + const response = await apiClient('/admin/push-notifications', { method: 'POST', body: JSON.stringify(payload), }); - const data = response?.data ?? response ?? {}; + const data = ('data' in response && response.data ? response.data : response) as PushNotificationDto; + const rawDate = data.createdAt ?? data.created_at; return { id: String(data.id ?? data._id ?? ''), title: String(data.title ?? ''), message: String(data.message ?? ''), status: data.status === 'Active' || data.status === 'active' ? 'Active' : 'Inactive', - createdAt: data.createdAt ?? data.created_at ? new Date(data.createdAt ?? data.created_at).toLocaleDateString('en-US', { + createdAt: rawDate ? new Date(rawDate).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric'