11"use client"
22
3- import { useState } from "react"
3+ import { useCallback , useEffect , useState } from "react"
44import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card"
55import { Button } from "@/components/ui/button"
66import { Input } from "@/components/ui/input"
@@ -31,57 +31,46 @@ import {
3131 Zap ,
3232} from "lucide-react"
3333
34+ /** Canonical money envelope from the API. See docs/api-conventions.md. */
35+ interface Money {
36+ currency : string
37+ amountMinor : number
38+ amountMajor : number
39+ }
40+
3441interface Transaction {
3542 id : string
36- type : "deposit" | "withdrawal" | "investment" | "return"
37- amount : number
38- status : "completed" | "pending" | "failed"
43+ type : string
44+ amount : Money
45+ status : string
3946 date : string
4047 description : string
41- method ?: string
48+ method ?: string | null
49+ }
50+
51+ interface WalletSummaryPayload {
52+ wallet : {
53+ internalBalance : Money
54+ walletAddress : string | null
55+ }
56+ transactions : Array < {
57+ id : string
58+ type : string
59+ amount : Money
60+ status : string
61+ method : string | null
62+ description : string
63+ reference : string | null
64+ timestamp : string
65+ } >
4266}
4367
4468interface FundsManagementProps {
4569 currentBalance : number
4670 onBalanceUpdate : ( newBalance : number ) => void
4771}
4872
49- const mockTransactions : Transaction [ ] = [
50- {
51- id : "1" ,
52- type : "deposit" ,
53- amount : 5000 ,
54- status : "completed" ,
55- date : "2024-12-20" ,
56- description : "Bank transfer deposit" ,
57- method : "Bank Transfer" ,
58- } ,
59- {
60- id : "2" ,
61- type : "investment" ,
62- amount : 2000 ,
63- status : "completed" ,
64- date : "2024-12-19" ,
65- description : "Investment in Toyota Corolla 2020" ,
66- } ,
67- {
68- id : "3" ,
69- type : "return" ,
70- amount : 150 ,
71- status : "completed" ,
72- date : "2024-12-18" ,
73- description : "Monthly return from Honda Civic" ,
74- } ,
75- {
76- id : "4" ,
77- type : "withdrawal" ,
78- amount : 1000 ,
79- status : "pending" ,
80- date : "2024-12-17" ,
81- description : "Withdrawal to bank account" ,
82- method : "Bank Transfer" ,
83- } ,
84- ]
73+ const CREDIT_TYPES = new Set ( [ "deposit" , "wallet_funding" ] )
8574
8675export function FundsManagement ( { currentBalance, onBalanceUpdate } : FundsManagementProps ) {
8776 const [ isDepositOpen , setIsDepositOpen ] = useState ( false )
@@ -91,9 +80,43 @@ export function FundsManagement({ currentBalance, onBalanceUpdate }: FundsManage
9180 const [ depositMethod , setDepositMethod ] = useState ( "" )
9281 const [ withdrawMethod , setWithdrawMethod ] = useState ( "" )
9382 const [ isProcessing , setIsProcessing ] = useState ( false )
94- const [ transactions , setTransactions ] = useState < Transaction [ ] > ( mockTransactions )
83+ const [ transactions , setTransactions ] = useState < Transaction [ ] > ( [ ] )
84+ const [ isLoadingTransactions , setIsLoadingTransactions ] = useState ( true )
85+ const [ transactionsError , setTransactionsError ] = useState < string | null > ( null )
9586 const { toast } = useToast ( )
9687
88+ const fetchWalletSummary = useCallback ( async ( ) => {
89+ setIsLoadingTransactions ( true )
90+ setTransactionsError ( null )
91+ try {
92+ const response = await fetch ( "/api/wallet/summary" )
93+ const payload = ( await response . json ( ) ) as WalletSummaryPayload & { message ?: string }
94+ if ( ! response . ok ) {
95+ throw new Error ( payload . message || "Unable to load wallet activity." )
96+ }
97+
98+ setTransactions (
99+ payload . transactions . map ( ( transaction ) => ( {
100+ id : transaction . id ,
101+ type : transaction . type ,
102+ amount : transaction . amount ,
103+ status : transaction . status ,
104+ date : transaction . timestamp ,
105+ description : transaction . description ,
106+ method : transaction . method ,
107+ } ) ) ,
108+ )
109+ } catch ( error ) {
110+ setTransactionsError ( error instanceof Error ? error . message : "Unable to load wallet activity." )
111+ } finally {
112+ setIsLoadingTransactions ( false )
113+ }
114+ } , [ ] )
115+
116+ useEffect ( ( ) => {
117+ void fetchWalletSummary ( )
118+ } , [ fetchWalletSummary ] )
119+
97120 const handleDeposit = async ( ) => {
98121 if ( ! depositAmount || ! depositMethod ) {
99122 toast ( {
@@ -106,34 +129,27 @@ export function FundsManagement({ currentBalance, onBalanceUpdate }: FundsManage
106129
107130 setIsProcessing ( true )
108131 try {
109- // Simulate API call
110- await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) )
132+ const response = await fetch ( "/api/payments/initialize" , {
133+ method : "POST" ,
134+ headers : { "Content-Type" : "application/json" } ,
135+ body : JSON . stringify ( { amountNgn : Number . parseFloat ( depositAmount ) } ) ,
136+ } )
111137
112- const amount = Number . parseFloat ( depositAmount )
113- const newTransaction : Transaction = {
114- id : Date . now ( ) . toString ( ) ,
115- type : "deposit" ,
116- amount,
117- status : "completed" ,
118- date : new Date ( ) . toISOString ( ) . split ( "T" ) [ 0 ] ,
119- description : `${ depositMethod } deposit` ,
120- method : depositMethod ,
138+ const payload = await response . json ( )
139+ if ( ! response . ok ) {
140+ throw new Error ( payload . message || "Unable to initialize payment." )
121141 }
122142
123- setTransactions ( [ newTransaction , ...transactions ] )
124- onBalanceUpdate ( currentBalance + amount )
125- setDepositAmount ( "" )
126- setDepositMethod ( "" )
127- setIsDepositOpen ( false )
143+ const redirectUrl = payload ?. payment ?. authorizationUrl
144+ if ( ! redirectUrl ) {
145+ throw new Error ( "Missing payment authorization URL." )
146+ }
128147
129- toast ( {
130- title : "Deposit Successful" ,
131- description : `$${ amount . toLocaleString ( ) } has been added to your account` ,
132- } )
148+ window . location . href = redirectUrl
133149 } catch ( error ) {
134150 toast ( {
135151 title : "Deposit Failed" ,
136- description : "Please try again later" ,
152+ description : error instanceof Error ? error . message : "Please try again later" ,
137153 variant : "destructive" ,
138154 } )
139155 } finally {
@@ -161,44 +177,17 @@ export function FundsManagement({ currentBalance, onBalanceUpdate }: FundsManage
161177 return
162178 }
163179
164- setIsProcessing ( true )
165- try {
166- // Simulate API call
167- await new Promise ( ( resolve ) => setTimeout ( resolve , 2000 ) )
168-
169- const newTransaction : Transaction = {
170- id : Date . now ( ) . toString ( ) ,
171- type : "withdrawal" ,
172- amount,
173- status : "pending" ,
174- date : new Date ( ) . toISOString ( ) . split ( "T" ) [ 0 ] ,
175- description : `Withdrawal to ${ withdrawMethod } ` ,
176- method : withdrawMethod ,
177- }
178-
179- setTransactions ( [ newTransaction , ...transactions ] )
180- onBalanceUpdate ( currentBalance - amount )
181- setWithdrawAmount ( "" )
182- setWithdrawMethod ( "" )
183- setIsWithdrawOpen ( false )
184-
185- toast ( {
186- title : "Withdrawal Initiated" ,
187- description : `$${ amount . toLocaleString ( ) } withdrawal is being processed` ,
188- } )
189- } catch ( error ) {
190- toast ( {
191- title : "Withdrawal Failed" ,
192- description : "Please try again later" ,
193- variant : "destructive" ,
194- } )
195- } finally {
196- setIsProcessing ( false )
197- }
180+ // Withdrawals are not wired up to a payout provider yet, so report the
181+ // real "unavailable" state instead of fabricating a success transition.
182+ toast ( {
183+ title : "Withdrawals Unavailable" ,
184+ description : "Withdrawals aren't available yet. Please check back soon." ,
185+ variant : "destructive" ,
186+ } )
198187 }
199188
200189 const getStatusIcon = ( status : string ) => {
201- switch ( status ) {
190+ switch ( status . toLowerCase ( ) ) {
202191 case "completed" :
203192 return < CheckCircle className = "h-4 w-4 text-green-500" />
204193 case "pending" :
@@ -213,29 +202,24 @@ export function FundsManagement({ currentBalance, onBalanceUpdate }: FundsManage
213202 const getTransactionIcon = ( type : string ) => {
214203 switch ( type ) {
215204 case "deposit" :
205+ case "wallet_funding" :
216206 return < ArrowDownLeft className = "h-4 w-4 text-green-500" />
217- case "withdrawal " :
207+ case "wallet_debit " :
218208 return < ArrowUpRight className = "h-4 w-4 text-red-500" />
219- case "investment " :
209+ case "pool_investment " :
220210 return < TrendingUp className = "h-4 w-4 text-blue-500" />
221- case "return" :
222- return < DollarSign className = "h-4 w-4 text-green-500" />
223211 default :
224212 return < DollarSign className = "h-4 w-4 text-gray-500" />
225213 }
226214 }
227215
228- const totalDeposits = transactions
229- . filter ( ( t ) => t . type === "deposit" && t . status === "completed" )
230- . reduce ( ( sum , t ) => sum + t . amount , 0 )
231-
232- const totalWithdrawals = transactions
233- . filter ( ( t ) => t . type === "withdrawal" && t . status === "completed" )
234- . reduce ( ( sum , t ) => sum + t . amount , 0 )
216+ const totalInvestments = transactions
217+ . filter ( ( t ) => t . type === "pool_investment" )
218+ . reduce ( ( sum , t ) => sum + t . amount . amountMajor , 0 )
235219
236- const totalInvestments = transactions . filter ( ( t ) => t . type === "investment" ) . reduce ( ( sum , t ) => sum + t . amount , 0 )
237-
238- const totalReturns = transactions . filter ( ( t ) => t . type === "return" ) . reduce ( ( sum , t ) => sum + t . amount , 0 )
220+ const totalReturns = transactions
221+ . filter ( ( t ) => t . type === "return" )
222+ . reduce ( ( sum , t ) => sum + t . amount . amountMajor , 0 )
239223
240224 return (
241225 < div className = "space-y-6" >
@@ -474,41 +458,56 @@ export function FundsManagement({ currentBalance, onBalanceUpdate }: FundsManage
474458 < CardDescription className = "text-muted-foreground" > Recent account activity</ CardDescription >
475459 </ CardHeader >
476460 < CardContent >
477- < div className = "space-y-4" >
478- { transactions . slice ( 0 , 5 ) . map ( ( transaction ) => (
479- < div key = { transaction . id } className = "flex items-center justify-between p-3 bg-muted rounded-lg" >
480- < div className = "flex items-center space-x-3" >
481- { getTransactionIcon ( transaction . type ) }
482- < div >
483- < p className = "font-medium text-foreground capitalize" > { transaction . type } </ p >
484- < p className = "text-sm text-muted-foreground" > { transaction . description } </ p >
485- < p className = "text-xs text-muted-foreground" > { transaction . date } </ p >
461+ { transactionsError ? (
462+ < div className = "flex flex-col items-center justify-center gap-3 py-8 text-center" >
463+ < AlertCircle className = "h-6 w-6 text-destructive" />
464+ < p className = "text-sm text-muted-foreground" > { transactionsError } </ p >
465+ < Button variant = "outline" size = "sm" onClick = { ( ) => void fetchWalletSummary ( ) } >
466+ Try again
467+ </ Button >
468+ </ div >
469+ ) : isLoadingTransactions ? (
470+ < div className = "space-y-3" >
471+ { Array . from ( { length : 3 } ) . map ( ( _ , index ) => (
472+ < div key = { index } className = "h-16 rounded-lg bg-muted animate-pulse" />
473+ ) ) }
474+ </ div >
475+ ) : transactions . length === 0 ? (
476+ < div className = "py-8 text-center text-sm text-muted-foreground" > No transactions yet.</ div >
477+ ) : (
478+ < div className = "space-y-4" >
479+ { transactions . slice ( 0 , 5 ) . map ( ( transaction ) => (
480+ < div key = { transaction . id } className = "flex items-center justify-between p-3 bg-muted rounded-lg" >
481+ < div className = "flex items-center space-x-3" >
482+ { getTransactionIcon ( transaction . type ) }
483+ < div >
484+ < p className = "font-medium text-foreground capitalize" >
485+ { transaction . type . replace ( / _ / g, " " ) }
486+ </ p >
487+ < p className = "text-sm text-muted-foreground" > { transaction . description } </ p >
488+ < p className = "text-xs text-muted-foreground" >
489+ { new Date ( transaction . date ) . toLocaleDateString ( ) }
490+ </ p >
491+ </ div >
486492 </ div >
487- </ div >
488- < div className = "text-right" >
489- < div className = "flex items-center space-x-2" >
490- < span
491- className = { `font-bold ${
492- transaction . type === "deposit" || transaction . type === "return"
493- ? "text-green-500"
494- : "text-red-500"
495- } `}
496- >
497- { transaction . type === "deposit" || transaction . type === "return" ? "+" : "-" } $
498- { transaction . amount . toLocaleString ( ) }
499- </ span >
500- { getStatusIcon ( transaction . status ) }
493+ < div className = "text-right" >
494+ < div className = "flex items-center space-x-2" >
495+ < span
496+ className = { `font-bold ${
497+ CREDIT_TYPES . has ( transaction . type ) ? "text-green-500" : "text-red-500"
498+ } `}
499+ >
500+ { CREDIT_TYPES . has ( transaction . type ) ? "+" : "-" } $
501+ { transaction . amount . amountMajor . toLocaleString ( ) }
502+ </ span >
503+ { getStatusIcon ( transaction . status ) }
504+ </ div >
505+ { transaction . method && < p className = "text-xs text-muted-foreground" > { transaction . method } </ p > }
501506 </ div >
502- { transaction . method && < p className = "text-xs text-muted-foreground" > { transaction . method } </ p > }
503507 </ div >
504- </ div >
505- ) ) }
506- </ div >
507- < div className = "mt-4 text-center" >
508- < Button variant = "outline" className = "border-border text-foreground hover:bg-muted" >
509- View All Transactions
510- </ Button >
511- </ div >
508+ ) ) }
509+ </ div >
510+ ) }
512511 </ CardContent >
513512 </ Card >
514513 </ div >
0 commit comments