Skip to content

Commit e148371

Browse files
authored
Merge pull request #158 from ToryMic/fix/26-trivial-frontend-implement-max-balance-button
[#26] [Trivial] Frontend: Implement Max Balance Button
2 parents af0a471 + b33ba35 commit e148371

2 files changed

Lines changed: 129 additions & 0 deletions

File tree

frontend/src/components/VaultDashboard.test.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,33 @@ describe("VaultDashboard", () => {
123123
expect(screen.getByText("1350.50")).toBeInTheDocument();
124124
});
125125

126+
it("fills the input with max allowable amount via MAX button", async () => {
127+
renderDashboard("GABC123");
128+
129+
expect(await screen.findByText(/Approve & Deposit/i)).toBeInTheDocument();
130+
131+
const maxButton = screen.getByRole("button", { name: "MAX" });
132+
fireEvent.click(maxButton);
133+
const input = screen.getByPlaceholderText("0.00");
134+
expect(input).toHaveValue(1250.5);
135+
136+
fireEvent.click(screen.getByRole("button", { name: "Withdraw" }));
137+
fireEvent.click(maxButton);
138+
expect(input).toHaveValue(1250.5);
139+
});
140+
141+
it("prevents transactions above the max allowable amount", async () => {
142+
renderDashboard("GABC123");
143+
144+
expect(await screen.findByText(/Approve & Deposit/i)).toBeInTheDocument();
145+
146+
const input = screen.getByPlaceholderText("0.00");
147+
fireEvent.change(input, { target: { value: "2000" } });
148+
fireEvent.click(screen.getByRole("button", { name: "Approve & Deposit" }));
149+
150+
expect(screen.getByText(/Amount exceeds maximum/i)).toBeInTheDocument();
151+
});
152+
126153
it("shows a normalized API error message when data loading fails", async () => {
127154
vi.useRealTimers();
128155
vi.stubGlobal(

frontend/src/components/VaultDashboard.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,66 @@ interface VaultDashboardProps {
1313
usdcBalance?: number;
1414
}
1515

16+
const VaultDashboard: React.FC<VaultDashboardProps> = ({ walletAddress, usdcBalance = 0 }) => {
17+
const { formattedTvl, formattedApy, summary, error, isLoading } = useVault();
18+
const toast = useToast();
19+
const [activeTab, setActiveTab] = useState<"deposit" | "withdraw">("deposit");
20+
const [amount, setAmount] = useState("");
21+
const [isProcessing, setIsProcessing] = useState(false);
22+
const [pendingBalanceChange, setPendingBalanceChange] = useState(0);
23+
24+
const yieldRate = formattedApy;
25+
const tvl = formattedTvl;
26+
const strategy = summary.strategy;
27+
const availableBalance = Math.max(0, usdcBalance + pendingBalanceChange);
28+
const estimatedUsdcFee = (() => {
29+
const feeMatch = summary.networkFeeEstimate.match(/([0-9]*\.?[0-9]+)\s*USDC/i);
30+
return feeMatch ? Number(feeMatch[1]) : 0;
31+
})();
32+
const maxDepositAmount = Math.max(0, availableBalance - estimatedUsdcFee);
33+
const maxWithdrawAmount = availableBalance;
34+
const maxAllowableAmount = activeTab === "deposit" ? maxDepositAmount : maxWithdrawAmount;
35+
36+
const handleTransaction = () => {
37+
const value = Number(amount);
38+
if (!walletAddress || !amount || isNaN(value)) {
39+
toast.warning({
40+
title: "Enter a valid amount",
41+
description: "Choose a wallet and amount before submitting the transaction.",
42+
});
43+
return;
44+
}
45+
if (value > maxAllowableAmount) {
46+
toast.warning({
47+
title: "Amount exceeds maximum",
48+
description:
49+
activeTab === "deposit"
50+
? `You can deposit up to ${maxDepositAmount.toFixed(2)} USDC based on your available balance and fees.`
51+
: `You can withdraw up to ${maxWithdrawAmount.toFixed(2)} USDC.`,
52+
});
53+
return;
54+
}
55+
setIsProcessing(true);
56+
57+
// Simulate transaction delay
58+
setTimeout(() => {
59+
if (activeTab === "deposit") {
60+
setPendingBalanceChange((prev) => prev + value);
61+
}
62+
if (activeTab === "withdraw") {
63+
setPendingBalanceChange((prev) => prev - value);
64+
}
65+
setAmount("");
66+
setIsProcessing(false);
67+
toast.success({
68+
title: activeTab === "deposit" ? "Deposit queued" : "Withdrawal queued",
69+
description:
70+
activeTab === "deposit"
71+
? `${value.toFixed(2)} USDC has been added to your pending vault activity.`
72+
: `${value.toFixed(2)} USDC has been added to your pending withdrawal activity.`,
73+
});
74+
}, 2000);
75+
};
1676
const VaultDashboard: React.FC<VaultDashboardProps> = ({
1777
walletAddress,
1878
usdcBalance = 0,
@@ -47,6 +107,8 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
47107
return;
48108
}
49109

110+
<div className="glass-panel panel-padding-mobile" style={{ padding: '32px' }}>
111+
{error && <ApiStatusBanner error={error} />}
50112
if (actionType === "withdraw" && value > availableBalance) {
51113
toast.warning({
52114
title: "Insufficient balance",
@@ -114,6 +176,9 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
114176
</div>
115177
</div>
116178

179+
{/* Right Column - User Interaction */}
180+
<div style={{ flex: '1 1 400px' }}>
181+
<div className="glass-panel panel-padding-mobile" style={{ padding: '32px', position: 'relative', overflow: 'hidden' }}>
117182
<div
118183
style={{
119184
height: "1px",
@@ -244,6 +309,43 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
244309
}}
245310
/>
246311

312+
<div className="flex justify-between items-center" style={{ marginBottom: '16px' }}>
313+
<div style={{ color: 'var(--text-secondary)', fontSize: '0.9rem' }}>
314+
{activeTab === 'deposit' ? 'Amount to deposit' : 'Amount to withdraw'}
315+
</div>
316+
<div style={{ color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
317+
Balance: <span style={{ color: 'var(--text-primary)', fontWeight: 600 }}>{walletAddress ? availableBalance.toFixed(2) : '0.00'}</span>
318+
</div>
319+
</div>
320+
321+
<div className="input-group" style={{ marginBottom: '24px' }}>
322+
<div className="input-wrapper">
323+
<span style={{ color: 'var(--text-secondary)', paddingRight: '12px', borderRight: '1px solid var(--border-glass)', marginRight: '16px' }}>USDC</span>
324+
<input
325+
className="input-field"
326+
type="number"
327+
placeholder="0.00"
328+
value={amount}
329+
onChange={(e) => setAmount(e.target.value)}
330+
/>
331+
<button
332+
style={{
333+
color: 'var(--accent-cyan)',
334+
fontSize: '0.8rem',
335+
fontWeight: 600,
336+
background: 'var(--accent-cyan-dim)',
337+
padding: '4px 10px',
338+
borderRadius: '6px'
339+
}}
340+
onClick={() =>
341+
setAmount(maxAllowableAmount.toFixed(2))
342+
}
343+
disabled={!walletAddress || maxAllowableAmount <= 0}
344+
>
345+
MAX
346+
</button>
347+
</div>
348+
</div>
247349
{!walletAddress && (
248350
<div
249351
style={{

0 commit comments

Comments
 (0)