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
29 changes: 26 additions & 3 deletions frontend/app/asset-owner/plans/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useMemo, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import {
AlertCircle,
ArrowLeft,
Expand All @@ -22,6 +22,9 @@ import {
type InheritancePlanDraft,
} from "@/app/lib/validation/inheritancePlan";
import { invokeCreatePlan } from "@/app/services/inheritanceContractService";
import { KYCRequiredGuard } from "@/components/kyc/KYCRequiredGuard";
import { CrossChainDepositSection } from "@/components/plans/CrossChainDepositSection";
import { CrossChainWalletProvider } from "@/context/CrossChainWalletContext";
import { useWallet } from "@/context/WalletContext";
import { formatAddress } from "@/util/address";
import { YieldCalculatorWidget } from "@/components/dashboard/YieldCalculatorWidget";
Expand Down Expand Up @@ -63,6 +66,11 @@ export default function CreateInheritancePlanPage() {
const [submissionState, setSubmissionState] = useState<SubmissionState>("idle");
const [submitMessage, setSubmitMessage] = useState("");
const [createdPlanId, setCreatedPlanId] = useState<string | null>(null);
const [bridgeTransferId, setBridgeTransferId] = useState<string | null>(null);

const handleBridgeComplete = useCallback((transferId: string) => {
setBridgeTransferId(transferId);
}, []);

const hydratedDraft = useMemo(
() => ({ ...draft, owner: address ?? draft.owner }),
Expand Down Expand Up @@ -205,7 +213,9 @@ export default function CreateInheritancePlanPage() {
const showErrors = touched || submissionState === "error";

return (
<div className="animate-fade-in space-y-6">
<KYCRequiredGuard>
<CrossChainWalletProvider>
<div className="animate-fade-in space-y-6">
<div>
<p className="text-xs font-semibold uppercase tracking-[0.18em] text-primary">
New inheritance plan
Expand Down Expand Up @@ -440,6 +450,17 @@ export default function CreateInheritancePlanPage() {
/>
)}
</div>

<CrossChainDepositSection
amount={draft.amount}
onAmountChange={(amount) => updateDraft("amount", amount)}
onBridgeComplete={handleBridgeComplete}
/>
{bridgeTransferId && (
<p className="text-xs text-emerald-300">
Cross-chain deposit ready. Transfer ID: {bridgeTransferId}
</p>
)}
</div>
)}

Expand Down Expand Up @@ -737,6 +758,8 @@ export default function CreateInheritancePlanPage() {
)}
</div>
</section>
</div>
</div>
</CrossChainWalletProvider>
</KYCRequiredGuard>
);
}
81 changes: 81 additions & 0 deletions frontend/components/plans/BridgeFeeBreakdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"use client";

import { Loader2 } from "lucide-react";
import type { BridgeQuote } from "@/lib/bridge/types";

interface BridgeFeeBreakdownProps {
quote: BridgeQuote | null;
isLoading?: boolean;
}

function formatUsd(value: number): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD",
minimumFractionDigits: 2,
}).format(value);
}

export function BridgeFeeBreakdown({ quote, isLoading }: BridgeFeeBreakdownProps) {
if (isLoading) {
return (
<div className="rounded-xl border border-[#2A3338] bg-[#0A0F11] p-4 flex items-center gap-3">
<Loader2 size={16} className="animate-spin text-[#33C5E0]" />
<p className="text-sm text-[#92A5A8]">Fetching Allbridge route and fees…</p>
</div>
);
}

if (!quote) {
return (
<div className="rounded-xl border border-dashed border-[#2A3338] bg-[#0A0F11]/50 p-4">
<p className="text-sm text-[#92A5A8]">
Enter a deposit amount to see bridging fees and estimated delivery time.
</p>
</div>
);
}

const rows = [
{ label: "Relayer fee", value: quote.relayerFeeUsd },
{ label: "Source chain gas", value: quote.gasFeeUsd },
{ label: "Destination fee", value: quote.destinationFeeUsd },
];

return (
<div className="rounded-xl border border-[#2A3338] bg-[#0A0F11] p-4 space-y-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<h4 className="text-xs font-semibold uppercase tracking-wider text-[#33C5E0]">
Bridging Fee Breakdown
</h4>
<span className="text-[11px] text-[#92A5A8]">
~{quote.estimatedDurationMinutes} min via Allbridge
</span>
</div>

<dl className="space-y-2">
{rows.map((row) => (
<div key={row.label} className="flex items-center justify-between text-sm">
<dt className="text-[#92A5A8]">{row.label}</dt>
<dd className="font-mono text-slate-200">{formatUsd(row.value)}</dd>
</div>
))}
<div className="border-t border-[#2A3338] pt-2 flex items-center justify-between">
<dt className="text-sm font-medium text-slate-100">Total fees</dt>
<dd className="font-mono text-[#33C5E0] font-semibold">
{formatUsd(quote.totalFeeUsd)}
</dd>
</div>
</dl>

<div className="rounded-lg bg-white/5 px-3 py-2 text-xs text-[#92A5A8]">
Estimated receive on Stellar:{" "}
<span className="font-mono text-slate-200">
{quote.estimatedReceiveAmount} {quote.estimatedReceiveSymbol}
</span>
</div>
</div>
);
}

export default BridgeFeeBreakdown;
97 changes: 97 additions & 0 deletions frontend/components/plans/BridgeProgressStepper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"use client";

import { Check, Circle, Loader2, X } from "lucide-react";
import type { BridgeStep, BridgeStepStatus } from "@/lib/bridge/types";

interface BridgeProgressStepperProps {
steps: BridgeStep[];
}

function StepIcon({ status }: { status: BridgeStepStatus }) {
if (status === "completed") {
return (
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-[#48BB78]/20 text-[#48BB78]">
<Check size={14} />
</span>
);
}
if (status === "active") {
return (
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-[#33C5E0]/20 text-[#33C5E0]">
<Loader2 size={14} className="animate-spin" />
</span>
);
}
if (status === "error") {
return (
<span className="flex h-7 w-7 items-center justify-center rounded-full bg-[#F56565]/20 text-[#F56565]">
<X size={14} />
</span>
);
}
return (
<span className="flex h-7 w-7 items-center justify-center rounded-full border border-[#2A3338] text-[#92A5A8]">
<Circle size={10} />
</span>
);
}

function statusLabel(status: BridgeStepStatus): string {
switch (status) {
case "completed":
return "Completed";
case "active":
return "In progress";
case "error":
return "Failed";
default:
return "Pending";
}
}

export function BridgeProgressStepper({ steps }: BridgeProgressStepperProps) {
return (
<ol className="space-y-0" aria-label="Cross-chain bridge progress">
{steps.map((step, index) => (
<li key={step.id} className="relative flex gap-4 pb-6 last:pb-0">
{index < steps.length - 1 && (
<span
className={`absolute left-3.5 top-7 h-[calc(100%-1.75rem)] w-px ${
step.status === "completed" ? "bg-[#48BB78]/50" : "bg-[#2A3338]"
}`}
aria-hidden="true"
/>
)}

<div className="relative z-10 shrink-0">
<StepIcon status={step.status} />
</div>

<div className="min-w-0 flex-1 pt-0.5">
<div className="flex flex-wrap items-center gap-2">
<p className="text-sm font-medium text-slate-100">{step.label}</p>
<span
className={`text-[10px] uppercase tracking-wider px-2 py-0.5 rounded-full ${
step.status === "completed"
? "bg-[#48BB7814] text-[#48BB78]"
: step.status === "active"
? "bg-[#33C5E014] text-[#33C5E0]"
: step.status === "error"
? "bg-[#F5656514] text-[#F56565]"
: "bg-white/5 text-[#92A5A8]"
}`}
>
{statusLabel(step.status)}
</span>
</div>
{step.detail && (
<p className="mt-1 text-xs text-[#92A5A8] break-words">{step.detail}</p>
)}
</div>
</li>
))}
</ol>
);
}

export default BridgeProgressStepper;
Loading
Loading