11"use client" ;
22
3- import { useState , useMemo } from "react" ;
3+ import { useState , useMemo , useCallback } from "react" ;
44import { SectionTitle } from "@/components/shared/section-title" ;
55import { CampaignToolbar } from "@/features/roi/components/campaign-toolbar" ;
66import { CampaignList } from "@/features/roi/components/campaign-list" ;
7- import { mockCampaigns } from "@/features/roi/data/mock-campaigns" ;
8- import type { CampaignStatus } from "@/features/roi/types/campaign.types" ;
7+ import type { Campaign , CampaignStatus } from "@/features/roi/types/campaign.types" ;
8+ import { useUserInvestments } from "@/features/investments/hooks/useUserInvestments.hook" ;
9+ import type { InvestmentFromApi } from "@/features/investments/services/investment.service" ;
10+ import { ClaimROIService } from "@/features/claim-roi/services/claim.service" ;
11+ import { useWalletContext } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider" ;
12+ import { signTransaction } from "@tokenization/tw-blocks-shared/src/wallet-kit/wallet-kit" ;
13+ import { SendTransactionService } from "@/lib/sendTransactionService" ;
14+ import { toastSuccessWithTx } from "@/lib/toastWithTx" ;
15+ import { toast } from "sonner" ;
16+
17+ function toCampaign ( inv : InvestmentFromApi ) : Campaign {
18+ return {
19+ id : inv . campaign . id ,
20+ title : inv . campaign . name ,
21+ description : inv . campaign . description ?? "" ,
22+ status : inv . campaign . status as CampaignStatus ,
23+ loansCompleted : 0 ,
24+ minInvestCents : Number ( inv . usdcAmount ) * 100 ,
25+ currency : "USD" ,
26+ vaultId : inv . campaign . vaultId ?? null ,
27+ } ;
28+ }
29+
30+ function deduplicateByCampaign ( investments : InvestmentFromApi [ ] ) : Campaign [ ] {
31+ const seen = new Set < string > ( ) ;
32+ const campaigns : Campaign [ ] = [ ] ;
33+ for ( const inv of investments ) {
34+ if ( ! seen . has ( inv . campaign . id ) ) {
35+ seen . add ( inv . campaign . id ) ;
36+ campaigns . push ( toCampaign ( inv ) ) ;
37+ }
38+ }
39+ return campaigns ;
40+ }
941
1042export default function MyInvestmentsPage ( ) {
1143 const [ search , setSearch ] = useState ( "" ) ;
1244 const [ filter , setFilter ] = useState < CampaignStatus | "all" > ( "all" ) ;
45+ const { data : investments , isLoading } = useUserInvestments ( ) ;
46+ const { walletAddress } = useWalletContext ( ) ;
47+
48+ const campaigns = useMemo (
49+ ( ) => deduplicateByCampaign ( investments ?? [ ] ) ,
50+ [ investments ] ,
51+ ) ;
1352
1453 const filteredCampaigns = useMemo ( ( ) => {
15- return mockCampaigns . filter ( ( c ) => {
54+ return campaigns . filter ( ( c ) => {
1655 const matchesStatus = filter === "all" || c . status === filter ;
1756 const matchesSearch =
1857 search . trim ( ) === "" ||
1958 c . title . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ||
2059 c . description . toLowerCase ( ) . includes ( search . toLowerCase ( ) ) ;
2160 return matchesStatus && matchesSearch ;
2261 } ) ;
23- } , [ search , filter ] ) ;
62+ } , [ campaigns , search , filter ] ) ;
63+
64+ const handleClaimRoi = useCallback (
65+ async ( campaignId : string ) => {
66+ const campaign = campaigns . find ( ( c ) => c . id === campaignId ) ;
67+
68+ if ( ! campaign ?. vaultId ) {
69+ toast . error ( "Vault contract not available for this campaign." ) ;
70+ return ;
71+ }
72+
73+ if ( ! walletAddress ) {
74+ toast . error ( "Please connect your wallet to claim ROI." ) ;
75+ return ;
76+ }
77+
78+ try {
79+ const svc = new ClaimROIService ( ) ;
80+ const claimResponse = await svc . claimROI ( {
81+ vaultContractId : campaign . vaultId ,
82+ beneficiaryAddress : walletAddress ,
83+ } ) ;
84+
85+ if ( ! claimResponse ?. success || ! claimResponse ?. xdr ) {
86+ throw new Error (
87+ claimResponse ?. message ?? "Failed to build claim transaction." ,
88+ ) ;
89+ }
90+
91+ const signedTxXdr = await signTransaction ( {
92+ unsignedTransaction : claimResponse . xdr ,
93+ address : walletAddress ,
94+ } ) ;
95+
96+ const sender = new SendTransactionService ( ) ;
97+ const submitResponse = await sender . sendTransaction ( {
98+ signedXdr : signedTxXdr ,
99+ } ) ;
100+
101+ if ( submitResponse . status !== "SUCCESS" ) {
102+ throw new Error (
103+ submitResponse . message ?? "Transaction submission failed." ,
104+ ) ;
105+ }
106+
107+ toastSuccessWithTx ( "ROI claimed successfully!" , submitResponse . hash ) ;
108+ } catch ( e ) {
109+ const msg = e instanceof Error ? e . message : "Unexpected error while claiming ROI." ;
110+ toast . error ( msg ) ;
111+ }
112+ } ,
113+ [ campaigns , walletAddress ] ,
114+ ) ;
24115
25116 return (
26117 < div className = "flex flex-col gap-6" >
@@ -32,7 +123,13 @@ export default function MyInvestmentsPage() {
32123 onSearchChange = { setSearch }
33124 onFilterChange = { setFilter }
34125 />
35- < CampaignList campaigns = { filteredCampaigns } />
126+ { isLoading ? (
127+ < p className = "text-sm text-muted-foreground text-center py-8" >
128+ Loading your investments...
129+ </ p >
130+ ) : (
131+ < CampaignList campaigns = { filteredCampaigns } onClaimRoi = { handleClaimRoi } />
132+ ) }
36133 </ div >
37134 ) ;
38135}
0 commit comments