22
33import { memo } from 'react' ;
44import dynamic from 'next/dynamic' ;
5- import { Card , CardContent , CardHeader , CardTitle } from '@/components/ui' ;
6- import { CurrencyDisplay } from '@/components/shared' ;
5+ import { Card , CardContent , CardHeader , CardTitle , Skeleton } from '@/components/ui' ;
6+ import { CurrencyDisplay , ErrorDisplay , StatCard } from '@/components/shared' ;
77import { Users , AlertTriangle , ArrowUpRight , Activity , DollarSign } from 'lucide-react' ;
8- import dynamic from 'next/dynamic' ;
9-
10- const PlatformVolumeChart = dynamic ( ( ) => import ( '@/components/charts/PlatformVolumeChart' ) , {
11- ssr : false ,
12- loading : ( ) => < div className = "h-[300px] bg-slate-50 animate-pulse rounded-xl w-full" />
13- } ) ;
8+ import { useAdminStats } from '@/lib/api/hooks' ;
149
1510
16- import { Skeleton } from '@/components/ui' ;
17- import { StatCard } from '@/components/shared' ;
18-
19- const PlatformVolumeChart = dynamic ( ( ) => import ( '@/components/charts/PlatformVolumeChart' ) , {
20- ssr : false ,
21- loading : ( ) => < Skeleton className = "h-[300px] w-full rounded-xl" /> ,
22- } ) ;
2311
2412// Memoised so future additions of state to the parent won't re-render the chart.
2513const AdminChartSection = memo ( function AdminChartSection ( ) {
@@ -37,7 +25,24 @@ const AdminChartSection = memo(function AdminChartSection() {
3725 ) ;
3826} ) ;
3927
28+ function StatCardSkeleton ( ) {
29+ return (
30+ < Card className = "bg-card border shadow-sm" >
31+ < CardHeader className = "flex flex-row items-center justify-between pb-2" >
32+ < Skeleton className = "h-3 w-24" />
33+ < Skeleton className = "h-8 w-8 rounded-lg" />
34+ </ CardHeader >
35+ < CardContent className = "p-3 sm:p-4 space-y-2" >
36+ < Skeleton className = "h-7 w-32" />
37+ < Skeleton className = "h-3 w-20" />
38+ </ CardContent >
39+ </ Card >
40+ ) ;
41+ }
42+
4043export default function AdminOverviewPage ( ) {
44+ const { data : stats , isLoading, error, refetch } = useAdminStats ( ) ;
45+
4146 return (
4247 < div className = "space-y-6" >
4348 < div >
@@ -47,104 +52,59 @@ export default function AdminOverviewPage() {
4752 </ p >
4853 </ div >
4954
50- < div className = "grid gap-3 grid-cols-2 lg:grid-cols-4" >
51- < StatCard
52- title = "Total Processed (30d)"
53- icon = { Activity }
54- value = { < CurrencyDisplay amount = { 1452310.89 } /> }
55- trend = { { icon : ArrowUpRight , label : "+12.5% from last month" , color : "text-green-500" } }
56- />
57- < StatCard
58- title = "Platform Fees Generated"
59- icon = { DollarSign }
60- value = { < CurrencyDisplay amount = { 14523.10 } /> }
61- trend = { { label : "1.0% flat fee across volume" } }
55+ { error && (
56+ < ErrorDisplay
57+ message = { `${ error } Showing the last known figures.` }
58+ onRetry = { refetch }
6259 />
63- < StatCard
64- title = "Active Merchants"
65- icon = { Users }
66- value = "142"
67- trend = { { icon : ArrowUpRight , label : "+12 new this week" , color : "text-green-500" } }
68- />
69- < StatCard
70- title = "Pending KYB Reviews"
71- icon = { AlertTriangle }
72- value = "8"
73- variant = "destructive"
74- trend = { { label : "Requires immediate action" } }
75- />
76- < Card className = "bg-card border shadow-sm relative overflow-hidden" >
77- < div className = "absolute top-0 right-0 w-24 h-24 bg-primary/5 rounded-full -mr-10 -mt-10 blur-xl" > </ div >
78- < CardHeader className = "flex flex-row items-center justify-between pb-2 z-10 relative" >
79- < CardTitle className = "text-sm font-medium text-muted-foreground" > Total Processed (30d)</ CardTitle >
80- < Activity className = "h-4 w-4 text-primary" />
81- </ CardHeader >
82- < CardContent className = "p-3 sm:p-4 z-10 relative" >
83- < div className = "text-xl sm:text-2xl font-bold text-foreground" >
84- < CurrencyDisplay amount = { 1452310.89 } />
85- </ div >
86- < p className = "text-xs text-success flex items-center mt-1" >
87- < ArrowUpRight className = "h-3 w-3 mr-1" />
88- +12.5% from last month
89- </ p >
90- </ CardContent >
91- </ Card >
92-
93- < Card className = "bg-card border shadow-sm" >
94- < CardHeader className = "flex flex-row items-center justify-between pb-2" >
95- < CardTitle className = "text-sm font-medium text-muted-foreground" > Platform Fees Generated</ CardTitle >
96- < DollarSign className = "h-4 w-4 text-success" />
97- </ CardHeader >
98- < CardContent className = "p-3 sm:p-4" >
99- < div className = "text-xl sm:text-2xl font-bold text-foreground" >
100- < CurrencyDisplay amount = { 14523.10 } />
101- </ div >
102- < p className = "text-xs text-muted-foreground mt-1" >
103- 1.0% flat fee across volume
104- </ p >
105- </ CardContent >
106- </ Card >
107-
108- < Card className = "bg-card border shadow-sm" >
109- < CardHeader className = "flex flex-row items-center justify-between pb-2" >
110- < CardTitle className = "text-sm font-medium text-muted-foreground" > Active Merchants</ CardTitle >
111- < Users className = "h-4 w-4 text-primary" />
112- </ CardHeader >
113- < CardContent className = "p-3 sm:p-4" >
114- < div className = "text-xl sm:text-2xl font-bold text-foreground" > 142</ div >
115- < p className = "text-xs text-success flex items-center mt-1" >
116- < ArrowUpRight className = "h-3 w-3 mr-1" />
117- +12 new this week
118- </ p >
119- </ CardContent >
120- </ Card >
60+ ) }
12161
122- < Card className = "col-span-1 bg-destructive/10 border-destructive/20 shadow-sm" >
123- < CardHeader className = "flex flex-row items-center justify-between pb-2" >
124- < CardTitle className = "text-sm font-medium text-destructive" > Pending KYB Reviews</ CardTitle >
125- < AlertTriangle className = "h-4 w-4 text-destructive" />
126- </ CardHeader >
127- < CardContent className = "p-3 sm:p-4" >
128- < div className = "text-xl sm:text-2xl font-bold text-destructive" > 8</ div >
129- < p className = "text-xs text-destructive/80 mt-1" >
130- Requires immediate action
131- </ p >
132- </ CardContent >
133- </ Card >
62+ < div className = "grid gap-3 grid-cols-2 lg:grid-cols-4" >
63+ { isLoading ? (
64+ Array . from ( { length : 4 } , ( _ , i ) => < StatCardSkeleton key = { i } /> )
65+ ) : (
66+ < >
67+ < StatCard
68+ title = "Total Processed (30d)"
69+ icon = { Activity }
70+ color = "primary"
71+ value = { < CurrencyDisplay amount = { stats . totalProcessed } /> }
72+ trend = { {
73+ icon : ArrowUpRight ,
74+ label : `+${ stats . totalProcessedChangePct } % from last month` ,
75+ color : 'text-success' ,
76+ } }
77+ />
78+ < StatCard
79+ title = "Platform Fees Generated"
80+ icon = { DollarSign }
81+ color = "emerald"
82+ value = { < CurrencyDisplay amount = { stats . platformFees } /> }
83+ trend = { { label : `${ stats . feeRatePct . toFixed ( 1 ) } % flat fee across volume` } }
84+ />
85+ < StatCard
86+ title = "Active Merchants"
87+ icon = { Users }
88+ color = "blue"
89+ value = { stats . activeMerchants . toLocaleString ( ) }
90+ trend = { {
91+ icon : ArrowUpRight ,
92+ label : `+${ stats . newMerchantsThisWeek } new this week` ,
93+ color : 'text-success' ,
94+ } }
95+ />
96+ < StatCard
97+ title = "Pending KYB Reviews"
98+ icon = { AlertTriangle }
99+ value = { stats . pendingKyb . toLocaleString ( ) }
100+ variant = "destructive"
101+ trend = { { label : 'Requires immediate action' } }
102+ />
103+ </ >
104+ ) }
134105 </ div >
135106
136107 < div className = "grid gap-4 md:grid-cols-7" >
137- < Card className = "col-span-4 bg-card border shadow-sm" >
138- < CardHeader >
139- < CardTitle > Platform Volume vs Fees</ CardTitle >
140- </ CardHeader >
141- < CardContent className = "pl-2" >
142- < div className = "mt-4" >
143- < PlatformVolumeChart height = { 300 } />
144- </ div >
145- </ CardContent >
146- </ Card >
147- { /* Chart section is memoised */ }
148108 < AdminChartSection />
149109
150110 < Card className = "col-span-3 bg-card border shadow-sm" >
0 commit comments