@@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
22import {
33 Activity ,
44 AlertCircle ,
5+ Check ,
56 ShieldCheck ,
67 TrendingUp ,
78 Wallet as WalletIcon ,
@@ -17,6 +18,7 @@ import { useToast } from "../context/ToastContext";
1718import { Tabs , TabsContent , TabsList , TabsTrigger } from "./Tabs" ;
1819import { FormField } from "../forms" ;
1920import { useDepositMutation , useWithdrawMutation } from "../hooks/useVaultMutations" ;
21+ import { useTokenAllowance } from "../hooks/useTokenAllowance" ;
2022import CopyButton from "./CopyButton" ;
2123import { copyTextToClipboard } from "../lib/clipboard" ;
2224
@@ -153,6 +155,14 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
153155
154156 const depositMutation = useDepositMutation ( ) ;
155157 const withdrawMutation = useWithdrawMutation ( ) ;
158+ const { approvalStatus, needsApproval, approve, resetApproval } =
159+ useTokenAllowance ( walletAddress ) ;
160+
161+ // Reset approval when deposit amount changes
162+ useEffect ( ( ) => {
163+ resetApproval ( ) ;
164+ // eslint-disable-next-line react-hooks/exhaustive-deps
165+ } , [ amount ] ) ;
156166
157167 useEffect ( ( ) => {
158168 const handleTrigger = ( ) => {
@@ -632,6 +642,131 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
632642 </ div >
633643 </ div >
634644
645+ { /* Approval wizard — only shown for deposit tab when allowance is insufficient */ }
646+ { tab === "deposit" && isValidAmount && needsApproval ( enteredAmount ) && (
647+ < div
648+ className = "glass-panel"
649+ style = { {
650+ padding : "14px 16px" ,
651+ marginBottom : "12px" ,
652+ border : approvalStatus === "confirmed"
653+ ? "1px solid rgba(0, 240, 255, 0.4)"
654+ : "1px solid rgba(255, 159, 10, 0.4)" ,
655+ background : approvalStatus === "confirmed"
656+ ? "rgba(0, 240, 255, 0.05)"
657+ : "rgba(255, 159, 10, 0.05)" ,
658+ } }
659+ >
660+ { /* Step indicators */ }
661+ < div className = "flex items-center gap-sm" style = { { marginBottom : "10px" } } >
662+ { /* Step 1 */ }
663+ < div
664+ className = "flex items-center gap-xs"
665+ style = { {
666+ fontSize : "0.78rem" ,
667+ fontWeight : 600 ,
668+ color : approvalStatus === "confirmed"
669+ ? "var(--accent-cyan)"
670+ : "rgba(255, 159, 10, 0.9)" ,
671+ } }
672+ >
673+ < div
674+ style = { {
675+ width : "20px" ,
676+ height : "20px" ,
677+ borderRadius : "50%" ,
678+ display : "flex" ,
679+ alignItems : "center" ,
680+ justifyContent : "center" ,
681+ background : approvalStatus === "confirmed"
682+ ? "var(--accent-cyan)"
683+ : "rgba(255, 159, 10, 0.2)" ,
684+ border : approvalStatus === "confirmed"
685+ ? "none"
686+ : "1px solid rgba(255, 159, 10, 0.6)" ,
687+ fontSize : "0.7rem" ,
688+ color : approvalStatus === "confirmed" ? "#000" : "rgba(255, 159, 10, 0.9)" ,
689+ } }
690+ >
691+ { approvalStatus === "confirmed" ? < Check size = { 12 } /> : "1" }
692+ </ div >
693+ Approve USDC
694+ </ div >
695+ < div style = { { flex : 1 , height : "1px" , background : "var(--border-glass)" } } />
696+ { /* Step 2 */ }
697+ < div
698+ className = "flex items-center gap-xs"
699+ style = { {
700+ fontSize : "0.78rem" ,
701+ fontWeight : 600 ,
702+ color : approvalStatus === "confirmed"
703+ ? "var(--text-primary)"
704+ : "var(--text-secondary)" ,
705+ } }
706+ >
707+ < div
708+ style = { {
709+ width : "20px" ,
710+ height : "20px" ,
711+ borderRadius : "50%" ,
712+ display : "flex" ,
713+ alignItems : "center" ,
714+ justifyContent : "center" ,
715+ background : "rgba(255,255,255,0.05)" ,
716+ border : "1px solid var(--border-glass)" ,
717+ fontSize : "0.7rem" ,
718+ } }
719+ >
720+ 2
721+ </ div >
722+ Deposit
723+ </ div >
724+ </ div >
725+
726+ { approvalStatus !== "confirmed" && (
727+ < p style = { { fontSize : "0.82rem" , color : "var(--text-secondary)" , marginBottom : "10px" } } >
728+ You need to approve the vault contract to spend{ " " }
729+ < strong style = { { color : "var(--text-primary)" } } >
730+ { enteredAmount . toFixed ( 2 ) } USDC
731+ </ strong > { " " }
732+ before depositing.
733+ </ p >
734+ ) }
735+
736+ { approvalStatus === "error" && (
737+ < p style = { { fontSize : "0.82rem" , color : "var(--text-error)" , marginBottom : "10px" } } >
738+ Approval failed. Please try again.
739+ </ p >
740+ ) }
741+
742+ { approvalStatus !== "confirmed" && (
743+ < button
744+ type = "button"
745+ className = "btn btn-outline"
746+ style = { { width : "100%" , padding : "10px" , display : "flex" , alignItems : "center" , justifyContent : "center" , gap : "8px" } }
747+ disabled = { approvalStatus === "pending" || ! walletAddress }
748+ onClick = { async ( ) => {
749+ try {
750+ await approve ( enteredAmount ) ;
751+ toast . success ( { title : "USDC Approved" , description : "You can now proceed with your deposit." } ) ;
752+ } catch {
753+ toast . error ( { title : "Approval Failed" , description : "Could not approve USDC. Please try again." } ) ;
754+ }
755+ } }
756+ >
757+ { approvalStatus === "pending" ? (
758+ < >
759+ < Loader2 size = { 14 } style = { { animation : "spin 0.9s linear infinite" } } />
760+ Approving...
761+ </ >
762+ ) : (
763+ "Approve USDC"
764+ ) }
765+ </ button >
766+ ) }
767+ </ div >
768+ ) }
769+
635770 < button
636771 className = "btn btn-primary"
637772 style = { {
@@ -643,7 +778,10 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
643778 gap : "8px" ,
644779 } }
645780 type = "submit"
646- disabled = { isSubmitDisabled }
781+ disabled = {
782+ isSubmitDisabled ||
783+ ( tab === "deposit" && isValidAmount && needsApproval ( enteredAmount ) && approvalStatus !== "confirmed" )
784+ }
647785 >
648786 { isProcessing === tab ? (
649787 < >
@@ -655,7 +793,7 @@ const VaultDashboard: React.FC<VaultDashboardProps> = ({
655793 Waiting for confirmation...
656794 </ >
657795 ) : tab === "deposit" ? (
658- isCapReached ? "Vault is full" : "Approve & Deposit"
796+ isCapReached ? "Vault is full" : "Deposit"
659797 ) : (
660798 "Withdraw Funds"
661799 ) }
0 commit comments