@@ -20,6 +20,10 @@ import {
2020 ArrowDownRight ,
2121} from "lucide-react"
2222
23+ type SupabaseUser = {
24+ created_at : string ;
25+ } ;
26+
2327const dashboardStats = [
2428 {
2529 title : "Total Users" ,
@@ -151,16 +155,51 @@ const systemHealth = [
151155export default function AdminDashboard ( ) {
152156 const [ currentTime , setCurrentTime ] = useState ( new Date ( ) )
153157 const [ animatedStats , setAnimatedStats ] = useState ( dashboardStats . map ( ( ) => 0 ) )
158+ const [ totalUsers , setTotalUsers ] = useState < string | null > ( null )
159+ const [ totalUsersChange , setTotalUsersChange ] = useState < string > ( "" )
160+ const [ totalUsersTrend , setTotalUsersTrend ] = useState < "up" | "down" > ( "up" )
154161
155162 useEffect ( ( ) => {
156-
157- localStorage . setItem ( "isAdmin" , "true" )
163+ // Fetch real total users and previous month users
164+ fetch ( "/api/admin-users" )
165+ . then ( res => res . json ( ) )
166+ . then ( data => {
167+ if ( data . users ) {
168+ const users : SupabaseUser [ ] = data . users ;
169+ setTotalUsers ( users . length . toLocaleString ( ) ) ;
170+
171+ // Calculate previous month user count
172+ const now = new Date ( ) ;
173+ const prevMonth = now . getMonth ( ) === 0 ? 11 : now . getMonth ( ) - 1 ;
174+ const prevYear = now . getMonth ( ) === 0 ? now . getFullYear ( ) - 1 : now . getFullYear ( ) ;
175+ const prevMonthUsers = users . filter ( ( u ) => {
176+ const d = new Date ( u . created_at ) ;
177+ return d . getMonth ( ) === prevMonth && d . getFullYear ( ) === prevYear ;
178+ } ) . length ;
179+
180+ // Calculate change percentage
181+ const thisMonthUsers = users . filter ( ( u ) => {
182+ const d = new Date ( u . created_at ) ;
183+ return d . getMonth ( ) === now . getMonth ( ) && d . getFullYear ( ) === now . getFullYear ( ) ;
184+ } ) . length ;
185+
186+ if ( prevMonthUsers > 0 ) {
187+ const change = ( ( thisMonthUsers - prevMonthUsers ) / prevMonthUsers ) * 100 ;
188+ setTotalUsersChange ( `${ change > 0 ? "+" : "" } ${ change . toFixed ( 1 ) } %` ) ;
189+ setTotalUsersTrend ( change >= 0 ? "up" : "down" ) ;
190+ } else {
191+ setTotalUsersChange ( "N/A" ) ;
192+ setTotalUsersTrend ( "up" ) ;
193+ }
194+ }
195+ } )
196+ } , [ ] )
158197
198+ useEffect ( ( ) => {
199+ localStorage . setItem ( "isAdmin" , "true" )
159200 const timer = setInterval ( ( ) => {
160201 setCurrentTime ( new Date ( ) )
161202 } , 1000 )
162-
163-
164203 dashboardStats . forEach ( ( stat , i ) => {
165204 const value = parseInt ( stat . value . replace ( / [ ^ \d ] / g, "" ) )
166205 const step = Math . ceil ( value / 30 )
@@ -173,7 +212,6 @@ export default function AdminDashboard() {
173212 } , 20 )
174213 setTimeout ( ( ) => clearInterval ( interval ) , 600 )
175214 } )
176-
177215 return ( ) => clearInterval ( timer )
178216 } , [ ] )
179217
@@ -203,6 +241,13 @@ export default function AdminDashboard() {
203241 }
204242 }
205243
244+ // When rendering dashboardStats, override Total Users value if totalUsers is available
245+ const statsToShow = dashboardStats . map ( stat =>
246+ stat . title === "Total Users"
247+ ? { ...stat , value : totalUsers ?? stat . value , change : totalUsersChange , trend : totalUsersTrend }
248+ : stat
249+ )
250+
206251 return (
207252 < div className = "bg-black space-y-8 md:space-y-14 min-h-screen px-4 py-8 md:px-8 lg:px-16 relative overflow-x-hidden" >
208253
@@ -237,7 +282,7 @@ export default function AdminDashboard() {
237282
238283
239284 < div className = "grid gap-4 sm:gap-6 md:gap-8 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 relative z-10" >
240- { dashboardStats . map ( ( stat , i ) => (
285+ { statsToShow . map ( ( stat , i ) => (
241286 < div className = "group" key = { stat . title } >
242287 < Card
243288 className = {
@@ -265,7 +310,9 @@ export default function AdminDashboard() {
265310 </ CardHeader >
266311 < CardContent className = "relative z-10" >
267312 < div className = "text-2xl sm:text-3xl font-extrabold text-zinc-900 dark:text-white flex items-end gap-2 tracking-tight" >
268- { stat . value . match ( / \d / ) ? (
313+ { stat . title === "Total Users" && totalUsers ? (
314+ < span > { totalUsers } </ span >
315+ ) : stat . value . match ( / \d / ) ? (
269316 < span > { animatedStats [ i ] . toLocaleString ( ) } </ span >
270317 ) : (
271318 < span > { stat . value } </ span >
0 commit comments