diff --git a/apps/investor-tokenization/src/app/my-investments/page.tsx b/apps/investor-tokenization/src/app/my-investments/page.tsx index 6dce46a..d52a94c 100644 --- a/apps/investor-tokenization/src/app/my-investments/page.tsx +++ b/apps/investor-tokenization/src/app/my-investments/page.tsx @@ -1,141 +1,12 @@ "use client"; -import { useState, useMemo, useCallback } from "react"; -import { SectionTitle } from "@/components/shared/section-title"; -import { CampaignToolbar } from "@/features/roi/components/campaign-toolbar"; -import { CampaignList } from "@/features/roi/components/campaign-list"; -import type { Campaign, CampaignStatus } from "@/features/roi/types/campaign.types"; -import { useUserInvestments } from "@/features/investments/hooks/useUserInvestments.hook"; -import type { InvestmentFromApi } from "@/features/investments/services/investment.service"; -import { ClaimROIService } from "@/features/claim-roi/services/claim.service"; -import { useWalletContext } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider"; -import { signTransaction } from "@tokenization/tw-blocks-shared/src/wallet-kit/wallet-kit"; -import { SendTransactionService } from "@/lib/sendTransactionService"; -import { toastSuccessWithTx } from "@/lib/toastWithTx"; -import { toast } from "sonner"; - -function toCampaign(inv: InvestmentFromApi): Campaign { - return { - id: inv.campaign.id, - title: inv.campaign.name, - description: inv.campaign.description ?? "", - status: inv.campaign.status as CampaignStatus, - loansCompleted: 0, - investedAmount: Number(inv.usdcAmount), - currency: "USDC", - vaultId: inv.campaign.vaultId ?? null, - escrowId: inv.campaign.escrowId, - poolSize: Number(inv.campaign.poolSize), - }; -} - -function aggregateByCampaign(investments: InvestmentFromApi[]): Campaign[] { - const map = new Map(); - for (const inv of investments) { - const existing = map.get(inv.campaign.id); - if (existing) { - existing.investedAmount += Number(inv.usdcAmount); - } else { - map.set(inv.campaign.id, toCampaign(inv)); - } - } - return Array.from(map.values()); -} +import { InvestmentsView } from "@/features/investments/InvestmentsView"; +import { WalletGate } from "@/components/shared/WalletGate"; export default function MyInvestmentsPage() { - const [search, setSearch] = useState(""); - const [filter, setFilter] = useState("all"); - const { data: investments, isLoading } = useUserInvestments(); - const { walletAddress } = useWalletContext(); - - const campaigns = useMemo( - () => aggregateByCampaign(investments ?? []), - [investments], - ); - - const filteredCampaigns = useMemo(() => { - return campaigns.filter((c) => { - const matchesStatus = filter === "all" || c.status === filter; - const matchesSearch = - search.trim() === "" || - c.title.toLowerCase().includes(search.toLowerCase()) || - c.description.toLowerCase().includes(search.toLowerCase()); - return matchesStatus && matchesSearch; - }); - }, [campaigns, search, filter]); - - const handleClaimRoi = useCallback( - async (campaignId: string) => { - const campaign = campaigns.find((c) => c.id === campaignId); - - if (!campaign?.vaultId) { - toast.error("Vault contract not available for this campaign."); - return; - } - - if (!walletAddress) { - toast.error("Please connect your wallet to claim ROI."); - return; - } - - try { - const svc = new ClaimROIService(); - const claimResponse = await svc.claimROI({ - vaultContractId: campaign.vaultId, - beneficiaryAddress: walletAddress, - }); - - if (!claimResponse?.success || !claimResponse?.xdr) { - throw new Error( - claimResponse?.message ?? "Failed to build claim transaction.", - ); - } - - const signedTxXdr = await signTransaction({ - unsignedTransaction: claimResponse.xdr, - address: walletAddress, - }); - - const sender = new SendTransactionService({ - baseURL: process.env.NEXT_PUBLIC_CORE_API_URL, - apiKey: process.env.NEXT_PUBLIC_INVESTORS_API_KEY, - }); - const submitResponse = await sender.sendTransaction({ - signedXdr: signedTxXdr, - }); - - if (submitResponse.status !== "SUCCESS") { - throw new Error( - submitResponse.message ?? "Transaction submission failed.", - ); - } - - toastSuccessWithTx("ROI claimed successfully!", submitResponse.hash); - } catch (e) { - const msg = e instanceof Error ? e.message : "Unexpected error while claiming ROI."; - toast.error(msg); - } - }, - [campaigns, walletAddress], - ); - return ( -
- - - {isLoading ? ( -

- Loading your investments... -

- ) : ( - - )} -
+ + + ); } diff --git a/apps/investor-tokenization/src/components/shared/WalletGate.tsx b/apps/investor-tokenization/src/components/shared/WalletGate.tsx new file mode 100644 index 0000000..67922b4 --- /dev/null +++ b/apps/investor-tokenization/src/components/shared/WalletGate.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useWalletContext } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider"; +import { useWallet } from "@tokenization/tw-blocks-shared/src/wallet-kit/useWallet"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@tokenization/ui/card"; +import { Button } from "@tokenization/ui/button"; +import { Wallet } from "lucide-react"; +import { useRouter } from "next/navigation"; +import type { ReactNode } from "react"; + +type WalletGateProps = { + children: ReactNode; +}; + +/** + * Protege el contenido: si no hay wallet conectada, muestra una tarjeta + * para conectar en lugar del contenido. Usa Card (no Dialog) para evitar + * conflictos de z-index con el modal del wallet kit. + */ +export function WalletGate({ children }: WalletGateProps) { + const { walletAddress } = useWalletContext(); + const { handleConnect } = useWallet(); + const router = useRouter(); + + if (walletAddress) { + return <>{children}; + } + + return ( +
+ + + + + Conecta tu billetera + + + Necesitas conectar tu billetera para ver tus inversiones y acceder a + esta sección. + + + + + + + +
+ ); +} diff --git a/apps/investor-tokenization/src/features/investments/InvestmentsView.tsx b/apps/investor-tokenization/src/features/investments/InvestmentsView.tsx index d66f5e6..53d2174 100644 --- a/apps/investor-tokenization/src/features/investments/InvestmentsView.tsx +++ b/apps/investor-tokenization/src/features/investments/InvestmentsView.tsx @@ -17,7 +17,6 @@ export const InvestmentsView = () => { data: investments, isLoading, isError, - error, } = useUserInvestments(); if (!walletAddress) { @@ -37,6 +36,10 @@ export const InvestmentsView = () => { ); } + // Sin backend de investments por wallet: mostrar vista normal con lista vacía + // en lugar de error (401 o endpoint inexistente) + const investmentList = isError ? [] : (investments ?? []); + if (isLoading) { return (
@@ -48,21 +51,6 @@ export const InvestmentsView = () => { ); } - if (isError) { - return ( -
-
-

My Investments

-

- Error loading investments: {error instanceof Error ? error.message : "Unknown error"} -

-
-
- ); - } - - const investmentList = investments ?? []; - const totalInvested = React.useMemo(() => { return investmentList.reduce((sum, inv) => sum + Number(inv.usdcAmount), 0); }, [investmentList]);