1+ import React , { Suspense } from "react"
12import React , { useMemo } from "react"
23import { useQuery } from "@tanstack/react-query"
34import { Helmet } from "react-helmet"
45import {
5- Area ,
6- AreaChart ,
7- CartesianGrid ,
8- ResponsiveContainer ,
9- Tooltip ,
10- XAxis ,
11- YAxis ,
6+ Area ,
7+ AreaChart ,
8+ CartesianGrid ,
9+ ResponsiveContainer ,
10+ Tooltip ,
11+ XAxis ,
12+ YAxis ,
1213} from "recharts"
1314import { EmptyState } from "../components/states/emptyState"
1415import { ErrorState } from "../components/states/errorState"
1516import TxHashLink from "../components/TxHashLink"
17+ import { useTreasury } from "../hooks/useTreasury"
18+ import { DashboardStatsSkeleton , EmptyState } from "../components/SkeletonLoader"
1619import TreasuryHealthChart , {
1720 type TreasuryPoint ,
1821} from "../components/treasury/TreasuryHealthChart"
@@ -24,20 +27,20 @@ const CHART_WINDOW_DAYS = 7
2427const STROOPS_PER_USDC = 10000000
2528
2629interface TreasuryStats {
27- total_deposited_usdc : string
28- total_disbursed_usdc : string
29- scholars_funded : number
30- active_proposals : number
31- donors_count : number
30+ total_deposited_usdc : string
31+ total_disbursed_usdc : string
32+ scholars_funded : number
33+ active_proposals : number
34+ donors_count : number
3235}
3336
3437interface TreasuryEvent {
35- type : "deposit" | "disburse"
36- amount ?: string
37- address ?: string
38- scholar ?: string
39- tx_hash : string
40- created_at : string
38+ type : "deposit" | "disburse"
39+ amount ?: string
40+ address ?: string
41+ scholar ?: string
42+ tx_hash : string
43+ created_at : string
4144}
4245
4346interface TreasuryActivityResponse {
@@ -108,6 +111,28 @@ const buildTreasuryChartData = (events: TreasuryEvent[]): TreasuryPoint[] => {
108111}
109112
110113const Treasury : React . FC = ( ) => {
114+ const { stats, activity, isLoading, isError } = useTreasury ( )
115+
116+ const formatUSDC = ( stroops : string ) => {
117+ const usdc = Number ( stroops ) / 10000000
118+ return usdc . toLocaleString ( "en-US" , {
119+ minimumFractionDigits : 0 ,
120+ maximumFractionDigits : 2 ,
121+ } )
122+ }
123+
124+ const formatAmount = ( stroops : string ) => {
125+ const usdc = Number ( stroops ) / 10000000
126+ return usdc . toLocaleString ( "en-US" , {
127+ minimumFractionDigits : 0 ,
128+ maximumFractionDigits : 2 ,
129+ } )
130+ }
131+
132+ const formatAddress = ( address : string ) => {
133+ if ( address . length <= 8 ) return address
134+ return `${ address . slice ( 0 , 4 ) } ...${ address . slice ( - 4 ) } `
135+ }
111136 const { scholarshipTreasury } = useContractIds ( )
112137 const { balance : treasuryUSDC , isLoading : treasuryLoading } =
113138 useUSDC ( scholarshipTreasury )
@@ -188,11 +213,64 @@ const Treasury: React.FC = () => {
188213 } )
189214 }
190215
191- const formatAddress = ( address : string ) => {
192- if ( address . length <= 8 ) return address
193- return `${ address . slice ( 0 , 4 ) } ...${ address . slice ( - 4 ) } `
194- }
216+ const formatTime = ( timestamp : string ) => {
217+ const date = new Date ( timestamp )
218+ const now = new Date ( )
219+ const diffMs = now . getTime ( ) - date . getTime ( )
220+ const diffMins = Math . floor ( diffMs / 60000 )
221+ const diffHours = Math . floor ( diffMins / 60 )
222+ const diffDays = Math . floor ( diffHours / 24 )
223+
224+ if ( diffMins < 60 ) return `${ diffMins } m ago`
225+ if ( diffHours < 24 ) return `${ diffHours } h ago`
226+ return `${ diffDays } d ago`
227+ }
195228
229+ const displayStats = stats
230+ ? {
231+ totalTreasury : `${ formatUSDC ( stats . total_deposited_usdc ) } USDC` ,
232+ totalDisbursed : `${ formatUSDC ( stats . total_disbursed_usdc ) } USDC` ,
233+ scholarsFunded : stats . scholars_funded . toString ( ) ,
234+ donorsCount : stats . donors_count . toString ( ) ,
235+ }
236+ : {
237+ totalTreasury : isLoading ? "Loading…" : "0 USDC" ,
238+ totalDisbursed : isLoading ? "Loading…" : "0 USDC" ,
239+ scholarsFunded : isLoading ? "..." : "0" ,
240+ donorsCount : isLoading ? "..." : "0" ,
241+ }
242+
243+ const deposits = ( activity ?? [ ] )
244+ . filter ( ( e ) => e . type === "deposit" )
245+ . slice ( 0 , 2 )
246+ const disbursements = ( activity ?? [ ] )
247+ . filter ( ( e ) => e . type === "disburse" )
248+ . slice ( 0 , 2 )
249+
250+ const siteUrl = "https://learnvault.app"
251+ const title = `Treasury - ${ displayStats . totalTreasury } - ${ displayStats . scholarsFunded } Scholars Funded - LearnVault`
252+ const description = `LearnVault's decentralized scholarship treasury holds ${ displayStats . totalTreasury } and has funded ${ displayStats . scholarsFunded } scholars. View real-time inflows and disbursements.`
253+
254+ const chartData = [
255+ { name : "Mon" , inflows : 4000 , outflows : 2400 } ,
256+ { name : "Tue" , inflows : 3000 , outflows : 1398 } ,
257+ { name : "Wed" , inflows : 2000 , outflows : 9800 } ,
258+ { name : "Thu" , inflows : 2780 , outflows : 3908 } ,
259+ { name : "Fri" , inflows : 1890 , outflows : 4800 } ,
260+ { name : "Sat" , inflows : 2390 , outflows : 3800 } ,
261+ { name : "Sun" , inflows : 3490 , outflows : 4300 } ,
262+ ]
263+
264+ return (
265+ < div className = "p-12 max-w-7xl mx-auto min-h-screen text-white animate-in fade-in duration-1000" >
266+ < Helmet >
267+ < title > { title } </ title >
268+ < meta property = "og:title" content = { title } />
269+ < meta property = "og:description" content = { description } />
270+ < meta property = "og:image" content = { `${ siteUrl } /og-image.png` } />
271+ < meta property = "og:url" content = { `${ siteUrl } /treasury` } />
272+ < meta name = "twitter:card" content = "summary_large_image" />
273+ </ Helmet >
196274 const formatTime = (timestamp: string) => {
197275 const date = new Date ( timestamp )
198276 if ( Number . isNaN ( date . getTime ( ) ) ) return "Unknown time"
@@ -274,43 +352,51 @@ const Treasury: React.FC = () => {
274352 < meta name = "twitter:card" content = "summary_large_image" />
275353 </ Helmet >
276354
277- < header className = "text-center mb-20 relative" >
278- < div className = "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-brand-cyan/20 blur-[100px] rounded-full -z-10" />
279- < h1 className = "text-7xl font-black mb-4 tracking-tighter text-gradient" >
280- Treasury Dashboard
281- </ h1 >
282- < p className = "text-white/40 text-lg max-w-2xl mx-auto font-medium" >
283- Real-time transparency into the LearnVault decentralized scholarship
284- fund.
285- </ p >
286- </ header >
287-
288- < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 mb-20" >
289- < StatCard
290- label = "Total in Treasury"
291- value = { displayStats . totalTreasury }
292- icon = { "\u{1F4B0}" }
293- color = "text-brand-cyan"
294- />
295- < StatCard
296- label = "Total Disbursed"
297- value = { displayStats . totalDisbursed }
298- icon = { "\u{1F4B8}" }
299- color = "text-brand-purple"
300- />
301- < StatCard
302- label = "Scholars Funded"
303- value = { displayStats . scholarsFunded }
304- icon = { "\u{1F393}" }
305- color = "text-brand-emerald"
306- />
307- < StatCard
308- label = "Global Donors"
309- value = { displayStats . donorsCount }
310- icon = { "\u{1F30D}" }
311- color = "text-brand-blue"
312- />
313- </ div >
355+ < header className = "text-center mb-20 relative" >
356+ < div className = "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-brand-cyan/20 blur-[100px] rounded-full -z-10" />
357+ < h1 className = "text-7xl font-black mb-4 tracking-tighter text-gradient" >
358+ Treasury Dashboard
359+ </ h1 >
360+ < p className = "text-white/40 text-lg max-w-2xl mx-auto font-medium" >
361+ Real-time transparency into the LearnVault decentralized scholarship
362+ fund.
363+ </ p >
364+ </ header >
365+
366+ { isLoading ? (
367+ < DashboardStatsSkeleton />
368+ ) : isError ? (
369+ < div className = "glass-card p-8 rounded-[3rem] border border-white/5 text-center text-red-400" >
370+ Failed to load treasury stats.
371+ </ div >
372+ ) : (
373+ < div className = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8 mb-20" >
374+ < StatCard
375+ label = "Total in Treasury"
376+ value = { displayStats . totalTreasury }
377+ icon = { "\u{1F4B0}" }
378+ color = "text-brand-cyan"
379+ />
380+ < StatCard
381+ label = "Total Disbursed"
382+ value = { displayStats . totalDisbursed }
383+ icon = { "\u{1F4B8}" }
384+ color = "text-brand-purple"
385+ />
386+ < StatCard
387+ label = "Scholars Funded"
388+ value = { displayStats . scholarsFunded }
389+ icon = { "\u{1F393}" }
390+ color = "text-brand-emerald"
391+ />
392+ < StatCard
393+ label = "Global Donors"
394+ value = { displayStats . donorsCount }
395+ icon = { "\u{1F30D}" }
396+ color = "text-brand-blue"
397+ />
398+ </ div >
399+ ) }
314400
315401 < div className = "mb-20" >
316402 < div className = "glass-card p-10 rounded-[3rem] relative overflow-hidden" >
@@ -392,43 +478,43 @@ const Treasury: React.FC = () => {
392478 ) }
393479 </ div >
394480
395- < div className = "mt-20 text-center" >
396- < button className = "iridescent-border px-12 py-5 rounded-2xl font-black text-lg uppercase tracking-widest hover:scale-105 active:scale-95 transition-all group overflow-hidden shadow-2xl shadow-brand-cyan/20" >
397- < span className = "relative z-10" > Donate to Treasury</ span >
398- </ button >
399- </ div >
400- </ div >
401- )
481+ < div className = "mt-20 text-center" >
482+ < button className = "iridescent-border px-12 py-5 rounded-2xl font-black text-lg uppercase tracking-widest hover:scale-105 active:scale-95 transition-all group overflow-hidden shadow-2xl shadow-brand-cyan/20" >
483+ < span className = "relative z-10" > Donate to Treasury</ span >
484+ </ button >
485+ </ div >
486+ </ div >
487+ )
402488}
403489
404490const StatCard : React . FC < {
405- label : string
406- value : string
407- icon : string
408- color : string
491+ label : string
492+ value : string
493+ icon : string
494+ color : string
409495} > = ({ label , value , icon , color } ) => (
410- < div className = "glass-card p-8 rounded-4xl hover:border-white/20 transition-all hover:-translate-y-2 group" >
411- < div className = "text-3xl mb-4 group-hover:scale-125 transition-transform duration-500" >
412- { icon }
413- </ div >
414- < p className = "text-[10px] uppercase font-black text-white/30 tracking-[2px] mb-1" >
415- { label }
416- </ p >
417- < p className = { `text-2xl font-black ${ color } tracking-tight` } > { value } </ p >
418- </ div >
496+ < div className = "glass-card p-8 rounded-4xl hover:border-white/20 transition-all hover:-translate-y-2 group" >
497+ < div className = "text-3xl mb-4 group-hover:scale-125 transition-transform duration-500" >
498+ { icon }
499+ </ div >
500+ < p className = "text-[10px] uppercase font-black text-white/30 tracking-[2px] mb-1" >
501+ { label }
502+ </ p >
503+ < p className = { `text-2xl font-black ${ color } tracking-tight` } > { value } </ p >
504+ </ div >
419505)
420506
421507const LegendItem: React.FC< { color : string ; label : string } > = ({
422- color,
423- label,
508+ color ,
509+ label ,
424510} ) => (
425- < div className = "flex items-center gap-2" >
426- < div
427- className = "w-3 h-3 rounded-full shadow-[0_0_10px_rgba(0,0,0,0.5)]"
428- style = { { backgroundColor : color } }
429- />
430- < span className = "text-xs font-bold text-white/60" > { label } </ span >
431- </ div >
511+ < div className = "flex items-center gap-2" >
512+ < div
513+ className = "w-3 h-3 rounded-full shadow-[0_0_10px_rgba(0,0,0,0.5)]"
514+ style = { { backgroundColor : color } }
515+ />
516+ < span className = "text-xs font-bold text-white/60" > { label } </ span >
517+ </ div >
432518)
433519
434520const ChartSkeleton = () => (
0 commit comments