Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"use client";

import { useEffect, useState } from "react";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from "@tokenization/ui/dialog";
import { Button } from "@tokenization/ui/button";
import { Input } from "@tokenization/ui/input";
import { Label } from "@tokenization/ui/label";
import { Loader2 } from "lucide-react";
import { useWalletContext } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider";
import { useUpdateRoiPercentage } from "@/features/campaigns/hooks/useUpdateRoiPercentage";
import { getRoiPercentage } from "@/features/campaigns/services/campaigns.api";
import { toast } from "sonner";

interface UpdateRoiDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
campaignName: string;
vaultId: string;
onUpdated: () => void;
}

export function UpdateRoiDialog({
open,
onOpenChange,
campaignName,
vaultId,
onUpdated,
}: UpdateRoiDialogProps) {
const [percentage, setPercentage] = useState("");
const [isLoadingCurrent, setIsLoadingCurrent] = useState(false);
const { walletAddress } = useWalletContext();

useEffect(() => {
if (!open || !walletAddress) return;
setIsLoadingCurrent(true);
getRoiPercentage(vaultId, walletAddress)
.then(({ roiPercentage }) => setPercentage(roiPercentage))
.catch(() => setPercentage(""))
.finally(() => setIsLoadingCurrent(false));
}, [open, vaultId, walletAddress]);

const { execute, isSubmitting, error } = useUpdateRoiPercentage({
onSuccess: () => {
toast.success("ROI percentage updated successfully");
onUpdated();
onOpenChange(false);
setPercentage("");
},
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
const parsed = Number(percentage);
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 100) return;
execute(vaultId, parsed);
};

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-full! sm:max-w-lg!">
<DialogHeader>
<DialogTitle>Update ROI Percentage — {campaignName}</DialogTitle>
<DialogDescription>
Set a new ROI percentage for this campaign&apos;s vault. Value must be between 0 and 100.
</DialogDescription>
</DialogHeader>

<form onSubmit={handleSubmit} className="flex flex-col gap-4">
<div className="space-y-2">
<Label htmlFor="roiPercentage">ROI Percentage (%)</Label>
<Input
id="roiPercentage"
type="number"
step="0.01"
min="0"
max="100"
placeholder="e.g. 12"
value={percentage}
onChange={(e) => setPercentage(e.target.value)}
disabled={isSubmitting || isLoadingCurrent}
autoComplete="off"
/>
</div>

<p className="text-xs text-muted-foreground">
Vault:{" "}
<span className="font-mono text-foreground">{vaultId}</span>
</p>

{error ? (
<p className="text-sm text-destructive">{error}</p>
) : null}

<Button
type="submit"
disabled={isSubmitting || isLoadingCurrent || !percentage}
className="w-full cursor-pointer"
>
{isSubmitting ? (
<div className="flex items-center justify-center gap-2">
<Loader2 className="h-4 w-4 animate-spin" />
<span>Updating ROI...</span>
</div>
) : (
"Update ROI Percentage"
)}
</Button>
</form>
</DialogContent>
</Dialog>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,15 @@ import Link from "next/link";
import { TableCell, TableRow } from "@tokenization/ui/table";
import { Badge } from "@tokenization/ui/badge";
import { Button } from "@tokenization/ui/button";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
} from "@tokenization/ui/dropdown-menu";
import { cn } from "@tokenization/shared/lib/utils";
import { ArrowUpCircle, Landmark } from "lucide-react";
import { ArrowUpCircle, Landmark, MoreHorizontal, Percent } from "lucide-react";
import { useWalletContext } from "@tokenization/tw-blocks-shared/src/wallet-kit/WalletProvider";
import { CAMPAIGN_STATUS_CONFIG } from "@/features/campaigns/constants/campaign-status";
import { formatCurrency } from "@/lib/utils";
Expand All @@ -15,7 +22,7 @@ import { useVaultUsdcBalance } from "@/features/campaigns/hooks/useVaultUsdcBala
import { ToggleVaultButton } from "@/features/campaigns/components/roi/ToggleVaultButton";
import type { RoiTableRowProps } from "./types";

export function RoiTableRow({ campaign, onAddFunds }: RoiTableRowProps) {
export function RoiTableRow({ campaign, onAddFunds, onUpdateRoi }: RoiTableRowProps) {
const statusCfg = CAMPAIGN_STATUS_CONFIG[campaign.status];
const { walletAddress } = useWalletContext();
const [vaultEnabled, setVaultEnabled] = useState<boolean | null>(null);
Expand All @@ -36,8 +43,10 @@ export function RoiTableRow({ campaign, onAddFunds }: RoiTableRowProps) {
<TableRow className="border-border hover:bg-secondary/30 transition-colors">
<TableCell>
<div className="flex flex-col gap-0.5">
<span className="text-sm font-bold text-foreground">{campaign.name}</span>
<span className="text-xs text-text-muted line-clamp-1 max-w-xs">
<span className="text-sm font-bold text-foreground overflow-hidden text-ellipsis whitespace-nowrap">
{campaign.name}
</span>
<span className="text-xs text-text-muted overflow-hidden text-ellipsis whitespace-nowrap max-w-xs">
{campaign.description}
</span>
</div>
Expand All @@ -62,18 +71,7 @@ export function RoiTableRow({ campaign, onAddFunds }: RoiTableRowProps) {
</TableCell>

<TableCell className="text-right">
<div className="flex items-center justify-end gap-1.5 flex-wrap">
<Button
size="sm"
variant="ghost"
className="cursor-pointer text-primary hover:text-primary/80 gap-1 text-xs font-semibold"
asChild
>
<Link href={`/campaigns/loans/${campaign.escrowId}`}>
<Landmark className="size-3.5" />
Gestionar Préstamos
</Link>
</Button>
<div className="flex items-center justify-end gap-1.5">
{campaign.vaultId && (
<ToggleVaultButton
vaultId={campaign.vaultId}
Expand All @@ -82,14 +80,42 @@ export function RoiTableRow({ campaign, onAddFunds }: RoiTableRowProps) {
onToggled={handleToggled}
/>
)}
<Button
size="sm"
className="cursor-pointer gap-1 text-xs"
onClick={() => onAddFunds(campaign)}
>
<ArrowUpCircle className="size-3.5" />
Subir Fondos
</Button>

<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="sm"
variant="outline"
className="cursor-pointer h-8 w-8 p-0"
aria-label="Actions"
>
<MoreHorizontal className="size-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild className="cursor-pointer">
<Link href={`/campaigns/loans/${campaign.escrowId}`}>
<Landmark className="size-3.5" />
Gestionar Préstamos
</Link>
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => onAddFunds(campaign)}
>
<ArrowUpCircle className="size-3.5" />
Subir Fondos
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
className="cursor-pointer"
onClick={() => onUpdateRoi(campaign)}
>
<Percent className="size-3.5" />
Actualizar ROI
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</TableCell>
</TableRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { RoiTableProps } from "./types";

const PAGE_SIZE = 4;

export function RoiTable({ campaigns, onAddFunds }: RoiTableProps) {
export function RoiTable({ campaigns, onAddFunds, onUpdateRoi }: RoiTableProps) {
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);

const visible = campaigns.slice(0, visibleCount);
Expand Down Expand Up @@ -46,6 +46,7 @@ export function RoiTable({ campaigns, onAddFunds }: RoiTableProps) {
key={campaign.id}
campaign={campaign}
onAddFunds={onAddFunds}
onUpdateRoi={onUpdateRoi}
/>
))}
</TableBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,22 @@
"use client";

import { StatItem } from "@/components/shared/stat-item";
import { RoiTable } from "@/features/campaigns/components/roi/roi-table";
import { FundRoiDialog } from "@/features/campaigns/components/roi/FundRoiDialog";
import { UpdateRoiDialog } from "@/features/campaigns/components/roi/UpdateRoiDialog";
import { useRoi } from "@/features/campaigns/hooks/use-roi";
import { useCampaigns } from "@/features/campaigns/hooks/use-campaigns";

const SUMMARY_STATS = [
{
label: "Total Activo",
value: "$1,350,000",
description: "+12.5% vs mes anterior",
},
{
label: "Retorno Promedio",
value: "8.4%",
description: "Objetivo anual: 9.0%",
},
{
label: "Beneficiarios",
value: "1,248",
description: "+154 nuevos este mes",
},
];

export function RoiView() {
const { data: campaigns = [] } = useCampaigns();
const {
fundsDialogCampaign,
fundDialogOpen,
openFundsDialog,
closeFundsDialog,
roiDialogCampaign,
roiDialogOpen,
openRoiDialog,
closeRoiDialog,
} = useRoi();

return (
Expand All @@ -46,30 +32,10 @@ export function RoiView() {
<RoiTable
campaigns={campaigns}
onAddFunds={openFundsDialog}
onUpdateRoi={openRoiDialog}
/>
</div>

{/* Financial summary */}
{/* <div className="flex flex-col gap-4">
<h3 className="text-base font-semibold text-foreground">
Resumen Financiero
</h3>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{SUMMARY_STATS.map((stat) => (
<div
key={stat.label}
className="rounded-xl border border-border bg-card p-5 shadow-card"
>
<StatItem
label={stat.label}
value={stat.value}
description={stat.description}
/>
</div>
))}
</div>
</div> */}

{/* Dialogs */}
{fundsDialogCampaign?.vaultId ? (
<FundRoiDialog
Expand All @@ -80,6 +46,16 @@ export function RoiView() {
onFunded={closeFundsDialog}
/>
) : null}

{roiDialogCampaign?.vaultId ? (
<UpdateRoiDialog
open={roiDialogOpen}
onOpenChange={(open) => { if (!open) closeRoiDialog(); }}
campaignName={roiDialogCampaign.name}
vaultId={roiDialogCampaign.vaultId}
onUpdated={closeRoiDialog}
/>
) : null}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ export interface RoiFormValues {
export interface RoiTableProps {
campaigns: Campaign[];
onAddFunds: (campaign: Campaign) => void;
onUpdateRoi: (campaign: Campaign) => void;
}

export interface RoiTableRowProps {
campaign: Campaign;
onAddFunds: (campaign: Campaign) => void;
onUpdateRoi: (campaign: Campaign) => void;
}

export interface CreateRoiDialogProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export function useRoi() {
const [fundsDialogCampaign, setFundsDialogCampaign] = useState<Campaign | null>(null);
const [fundDialogOpen, setFundDialogOpen] = useState(false);

const [roiDialogCampaign, setRoiDialogCampaign] = useState<Campaign | null>(null);
const [roiDialogOpen, setRoiDialogOpen] = useState(false);

function openFundsDialog(campaign: Campaign) {
setFundsDialogCampaign(campaign);
setFundDialogOpen(true);
Expand All @@ -15,10 +18,24 @@ export function useRoi() {
setFundsDialogCampaign(null);
}

function openRoiDialog(campaign: Campaign) {
setRoiDialogCampaign(campaign);
setRoiDialogOpen(true);
}

function closeRoiDialog() {
setRoiDialogOpen(false);
setRoiDialogCampaign(null);
}

return {
fundsDialogCampaign,
fundDialogOpen,
openFundsDialog,
closeFundsDialog,
roiDialogCampaign,
roiDialogOpen,
openRoiDialog,
closeRoiDialog,
};
}
Loading