Skip to content

Commit 2d07d3c

Browse files
authored
Merge pull request #2 from trinnode/issues-resolved-trinnode
feat: Add withdrawal confirmation modal
2 parents 4963eb4 + 7e96a42 commit 2d07d3c

2 files changed

Lines changed: 319 additions & 1 deletion

File tree

frontend/src/components/VaultDashboard.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import ApiStatusBanner from "./ApiStatusBanner";
77
import VaultPerformanceChart from "./VaultPerformanceChart";
88
import { useToast } from "../context/ToastContext";
99
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./Tabs";
10+
import WithdrawalConfirmationModal from "./WithdrawalConfirmationModal";
1011
import { FormField, SubmitButton } from "../forms";
1112
import { useDepositMutation, useWithdrawMutation } from "../hooks/useVaultMutations";
1213
import TransactionStatus, { type ActionStatus } from "./TransactionStatus";
@@ -134,6 +135,10 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
134135
const toast = useToast();
135136
const [activeTab, setActiveTab] = useState<TransactionTab>("deposit");
136137
const [amount, setAmount] = useState("");
138+
const [isProcessing, setIsProcessing] = useState<"deposit" | "withdraw" | null>(null);
139+
const [pendingBalanceChange, setPendingBalanceChange] = useState(0);
140+
const [showWithdrawalConfirm, setShowWithdrawalConfirm] = useState(false);
141+
const [pendingWithdrawalAmount, setPendingWithdrawalAmount] = useState(0);
137142
const [touched, setTouched] =
138143
useState<Record<TransactionTab, boolean>>(INITIAL_TOUCHED_STATE);
139144

@@ -238,8 +243,22 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
238243
}
239244
};
240245

246+
const handleWithdrawalCancel = () => {
247+
setShowWithdrawalConfirm(false);
248+
setPendingWithdrawalAmount(0);
249+
};
250+
241251
return (
242252
<div className="vault-dashboard gap-lg">
253+
<WithdrawalConfirmationModal
254+
isOpen={showWithdrawalConfirm}
255+
amount={pendingWithdrawalAmount}
256+
estimatedFee={estimatedUsdcFee}
257+
onConfirm={handleWithdrawalConfirm}
258+
onCancel={handleWithdrawalCancel}
259+
isProcessing={isProcessing === "withdraw"}
260+
/>
261+
243262
<div className="vault-dashboard-stats">
244263
<div className="glass-panel" style={{ padding: "32px" }}>
245264
{error && (
@@ -432,7 +451,25 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
432451
</div>
433452

434453
<div className="vault-dashboard-actions">
435-
<div className="glass-panel" style={{ padding: "32px", position: "relative", overflow: "hidden" }}>
454+
<div
455+
className="glass-panel"
456+
style={{ padding: "32px", position: "relative", overflow: "hidden" }}
457+
>
458+
<div
459+
style={{
460+
position: "absolute",
461+
top: "-50px",
462+
right: "-50px",
463+
width: "150px",
464+
height: "150px",
465+
background: "var(--accent-purple)",
466+
filter: "blur(80px)",
467+
opacity: 0.2,
468+
borderRadius: "50%",
469+
pointerEvents: "none",
470+
}}
471+
/>
472+
436473
{!walletAddress && (
437474
<div
438475
className="wallet-overlay"
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
import React from "react";
2+
import { X } from "./icons";
3+
4+
interface WithdrawalConfirmationModalProps {
5+
isOpen: boolean;
6+
amount: number;
7+
estimatedFee: number;
8+
onConfirm: () => void;
9+
onCancel: () => void;
10+
isProcessing?: boolean;
11+
}
12+
13+
const WithdrawalConfirmationModal: React.FC<WithdrawalConfirmationModalProps> = ({
14+
isOpen,
15+
amount,
16+
estimatedFee,
17+
onConfirm,
18+
onCancel,
19+
isProcessing = false,
20+
}) => {
21+
if (!isOpen) return null;
22+
23+
const totalAmount = amount + estimatedFee;
24+
25+
return (
26+
<div
27+
style={{
28+
position: "fixed",
29+
inset: 0,
30+
background: "rgba(0, 0, 0, 0.5)",
31+
backdropFilter: "blur(4px)",
32+
display: "flex",
33+
alignItems: "center",
34+
justifyContent: "center",
35+
zIndex: 1000,
36+
padding: "16px",
37+
}}
38+
onClick={onCancel}
39+
>
40+
<div
41+
className="glass-panel"
42+
style={{
43+
padding: "32px",
44+
maxWidth: "420px",
45+
width: "100%",
46+
position: "relative",
47+
onClick: (e) => e.stopPropagation(),
48+
}}
49+
>
50+
{/* Close Button */}
51+
<button
52+
onClick={onCancel}
53+
disabled={isProcessing}
54+
style={{
55+
position: "absolute",
56+
top: "16px",
57+
right: "16px",
58+
background: "transparent",
59+
border: "none",
60+
cursor: isProcessing ? "not-allowed" : "pointer",
61+
color: "var(--text-secondary)",
62+
opacity: isProcessing ? 0.5 : 1,
63+
transition: "color 0.2s",
64+
display: "flex",
65+
alignItems: "center",
66+
justifyContent: "center",
67+
padding: "8px",
68+
}}
69+
onMouseEnter={(e) => {
70+
if (!isProcessing) {
71+
(e.currentTarget as HTMLButtonElement).style.color =
72+
"var(--text-primary)";
73+
}
74+
}}
75+
onMouseLeave={(e) => {
76+
(e.currentTarget as HTMLButtonElement).style.color =
77+
"var(--text-secondary)";
78+
}}
79+
>
80+
<X size={20} />
81+
</button>
82+
83+
{/* Header */}
84+
<h2
85+
style={{
86+
fontSize: "1.5rem",
87+
marginBottom: "8px",
88+
fontFamily: "var(--font-display)",
89+
fontWeight: 700,
90+
}}
91+
>
92+
Confirm Withdrawal
93+
</h2>
94+
<p
95+
style={{
96+
color: "var(--text-secondary)",
97+
fontSize: "0.9rem",
98+
marginBottom: "24px",
99+
}}
100+
>
101+
Please review the details before confirming your withdrawal.
102+
</p>
103+
104+
{/* Divider */}
105+
<div
106+
style={{
107+
height: "1px",
108+
background: "var(--border-glass)",
109+
margin: "24px 0",
110+
}}
111+
/>
112+
113+
{/* Details */}
114+
<div style={{ marginBottom: "24px" }}>
115+
<div
116+
style={{
117+
display: "flex",
118+
justifyContent: "space-between",
119+
alignItems: "center",
120+
marginBottom: "16px",
121+
}}
122+
>
123+
<span style={{ color: "var(--text-secondary)", fontSize: "0.9rem" }}>
124+
Withdrawal Amount
125+
</span>
126+
<span
127+
style={{
128+
fontSize: "1.1rem",
129+
fontWeight: 600,
130+
fontFamily: "var(--font-display)",
131+
}}
132+
>
133+
{amount.toFixed(2)} USDC
134+
</span>
135+
</div>
136+
137+
<div
138+
style={{
139+
display: "flex",
140+
justifyContent: "space-between",
141+
alignItems: "center",
142+
marginBottom: "16px",
143+
}}
144+
>
145+
<span style={{ color: "var(--text-secondary)", fontSize: "0.9rem" }}>
146+
Estimated Network Fee
147+
</span>
148+
<span
149+
style={{
150+
fontSize: "1.1rem",
151+
fontWeight: 600,
152+
fontFamily: "var(--font-display)",
153+
}}
154+
>
155+
{estimatedFee.toFixed(6)} USDC
156+
</span>
157+
</div>
158+
159+
<div
160+
style={{
161+
height: "1px",
162+
background: "var(--border-glass)",
163+
margin: "16px 0",
164+
}}
165+
/>
166+
167+
<div
168+
style={{
169+
display: "flex",
170+
justifyContent: "space-between",
171+
alignItems: "center",
172+
}}
173+
>
174+
<span style={{ color: "var(--text-secondary)", fontSize: "0.9rem" }}>
175+
Total Deducted
176+
</span>
177+
<span
178+
style={{
179+
fontSize: "1.2rem",
180+
fontWeight: 700,
181+
fontFamily: "var(--font-display)",
182+
color: "var(--accent-cyan)",
183+
}}
184+
>
185+
{totalAmount.toFixed(6)} USDC
186+
</span>
187+
</div>
188+
</div>
189+
190+
{/* Warning */}
191+
<div
192+
style={{
193+
background: "var(--bg-muted)",
194+
border: "1px solid var(--border-glass)",
195+
borderRadius: "var(--radius-md)",
196+
padding: "12px",
197+
marginBottom: "24px",
198+
}}
199+
>
200+
<p
201+
style={{
202+
color: "var(--text-secondary)",
203+
fontSize: "0.85rem",
204+
lineHeight: "1.5",
205+
}}
206+
>
207+
<strong style={{ color: "var(--text-primary)" }}>Note:</strong> This
208+
action will immediately withdraw your funds from the vault. Your
209+
shares will be burned and cannot be recovered.
210+
</p>
211+
</div>
212+
213+
{/* Action Buttons */}
214+
<div
215+
style={{
216+
display: "flex",
217+
gap: "12px",
218+
justifyContent: "flex-end",
219+
}}
220+
>
221+
<button
222+
onClick={onCancel}
223+
disabled={isProcessing}
224+
style={{
225+
padding: "12px 24px",
226+
borderRadius: "var(--radius-md)",
227+
border: "1px solid var(--border-glass)",
228+
background: "transparent",
229+
color: "var(--text-primary)",
230+
fontSize: "0.95rem",
231+
fontWeight: 600,
232+
cursor: isProcessing ? "not-allowed" : "pointer",
233+
opacity: isProcessing ? 0.5 : 1,
234+
transition: "all 0.2s",
235+
}}
236+
onMouseEnter={(e) => {
237+
if (!isProcessing) {
238+
(e.currentTarget as HTMLButtonElement).style.background =
239+
"var(--bg-muted)";
240+
}
241+
}}
242+
onMouseLeave={(e) => {
243+
(e.currentTarget as HTMLButtonElement).style.background =
244+
"transparent";
245+
}}
246+
>
247+
Cancel
248+
</button>
249+
<button
250+
onClick={onConfirm}
251+
disabled={isProcessing}
252+
style={{
253+
padding: "12px 24px",
254+
borderRadius: "var(--radius-md)",
255+
border: "none",
256+
background: "var(--accent-cyan)",
257+
color: "var(--bg-main)",
258+
fontSize: "0.95rem",
259+
fontWeight: 600,
260+
cursor: isProcessing ? "not-allowed" : "pointer",
261+
opacity: isProcessing ? 0.7 : 1,
262+
transition: "all 0.2s",
263+
}}
264+
onMouseEnter={(e) => {
265+
if (!isProcessing) {
266+
(e.currentTarget as HTMLButtonElement).style.opacity = "0.9";
267+
}
268+
}}
269+
onMouseLeave={(e) => {
270+
(e.currentTarget as HTMLButtonElement).style.opacity = "1";
271+
}}
272+
>
273+
{isProcessing ? "Processing..." : "Confirm Withdrawal"}
274+
</button>
275+
</div>
276+
</div>
277+
</div>
278+
);
279+
};
280+
281+
export default WithdrawalConfirmationModal;

0 commit comments

Comments
 (0)