@@ -3,6 +3,7 @@ import { BaseInput } from '@app/components/common/inputs/BaseInput/BaseInput';
33import { BaseRow } from '@app/components/common/BaseRow/BaseRow' ;
44import { BaseButton } from '@app/components/common/BaseButton/BaseButton' ;
55import { BaseSpin } from '@app/components/common/BaseSpin/BaseSpin' ;
6+ import { Alert } from 'antd' ;
67import * as S from './SendForm.styles' ;
78import { truncateString } from '@app/utils/utils' ;
89import useBalanceData from '@app/hooks/useBalanceData' ;
@@ -29,7 +30,7 @@ export type tiers = 'low' | 'med' | 'high';
2930
3031const SendForm : React . FC < SendFormProps > = ( { onSend } ) => {
3132 const { balanceData, isLoading } = useBalanceData ( ) ;
32- const { isAuthenticated, login, token, loading : authLoading } = useWalletAuth ( ) ; // Use the auth hook
33+ const { isAuthenticated, login, token, loading : authLoading , checkWalletHealth , walletHealth , healthLoading } = useWalletAuth ( ) ; // Use the auth hook
3334
3435 const [ loading , setLoading ] = useState ( false ) ;
3536
@@ -48,6 +49,7 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
4849 } ) ;
4950
5051 const [ txSize , setTxSize ] = useState < number | null > ( null ) ;
52+ const [ txSizeCalculating , setTxSizeCalculating ] = useState ( false ) ;
5153
5254 const [ enableRBF , setEnableRBF ] = useState ( false ) ; // Default to false
5355
@@ -59,18 +61,42 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
5961 return validateBech32Address ( address ) ;
6062 } , [ ] ) ;
6163
64+ // Health check when component mounts or when authentication status changes
65+ useEffect ( ( ) => {
66+ if ( isAuthenticated ) {
67+ checkWalletHealth ( ) ;
68+ }
69+ // eslint-disable-next-line react-hooks/exhaustive-deps
70+ } , [ isAuthenticated ] ) ; // Only depend on isAuthenticated to prevent excessive calls
71+
6272 // First useEffect - Transaction size calculation
6373 useEffect ( ( ) => {
6474 const debounceTimeout = setTimeout ( ( ) => {
6575 const fetchTransactionSize = async ( ) => {
6676 if ( isValidAddress ( formData . address ) && isDetailsOpen ) {
77+ // Prevent multiple simultaneous transaction size calculations
78+ if ( txSizeCalculating ) {
79+ console . log ( 'Transaction size calculation already in progress, skipping' ) ;
80+ return ;
81+ }
82+
6783 try {
84+ setTxSizeCalculating ( true ) ;
85+
6886 if ( ! isAuthenticated ) {
6987 console . log ( 'Not Authenticated.' ) ;
7088 await login ( ) ;
89+ return ;
90+ }
91+
92+ // Check wallet health before making transaction calculations
93+ const health = await checkWalletHealth ( ) ;
94+ if ( ! health || health . status !== 'healthy' || ! health . chain_synced ) {
95+ console . log ( 'Wallet not ready (unhealthy or not synced), skipping transaction calculation' ) ;
96+ return ;
7197 }
7298
73- const response = await fetch ( `${ config . walletBaseURL } /calculate-tx-size` , {
99+ let response = await fetch ( `${ config . walletBaseURL } /calculate-tx-size` , {
74100 method : 'POST' ,
75101 headers : {
76102 'Content-Type' : 'application/json' ,
@@ -83,21 +109,43 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
83109 } ) ,
84110 } ) ;
85111
112+ // Handle 401 by re-authenticating and retrying
86113 if ( response . status === 401 ) {
87114 const errorText = await response . text ( ) ;
88115 if ( errorText . includes ( 'Token expired' ) || errorText . includes ( 'Unauthorized: Invalid token' ) ) {
89- console . log ( 'Session expired. Please log in again .' ) ;
116+ console . log ( 'Session expired. Re-authenticating and retrying.. .' ) ;
90117 deleteWalletToken ( ) ;
91118 await login ( ) ;
119+
120+ // Retry the request with the new token
121+ response = await fetch ( `${ config . walletBaseURL } /calculate-tx-size` , {
122+ method : 'POST' ,
123+ headers : {
124+ 'Content-Type' : 'application/json' ,
125+ Authorization : `Bearer ${ token } ` ,
126+ } ,
127+ body : JSON . stringify ( {
128+ recipient_address : formData . address ,
129+ spend_amount : parseInt ( formData . amount ) ,
130+ priority_rate : feeRate ,
131+ } ) ,
132+ } ) ;
133+
134+ if ( ! response . ok ) {
135+ throw new Error ( `HTTP error! status: ${ response . status } ` ) ;
136+ }
137+ } else {
138+ throw new Error ( errorText ) ;
92139 }
93- throw new Error ( errorText ) ;
94140 }
95141
96142 const result = await response . json ( ) ;
97143 setTxSize ( result . txSize ) ;
98144 } catch ( error ) {
99145 console . error ( 'Error fetching transaction size:' , error ) ;
100146 setTxSize ( null ) ;
147+ } finally {
148+ setTxSizeCalculating ( false ) ;
101149 }
102150 }
103151 } ;
@@ -106,7 +154,8 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
106154 } , 500 ) ;
107155
108156 return ( ) => clearTimeout ( debounceTimeout ) ;
109- } , [ formData . address , formData . amount , feeRate , isAuthenticated , login , token , isDetailsOpen , isValidAddress ] ) ;
157+ // eslint-disable-next-line react-hooks/exhaustive-deps
158+ } , [ formData . address , formData . amount , feeRate , isDetailsOpen ] ) ; // Only depend on actual form changes
110159
111160 // Second useEffect - Fee calculation
112161 useEffect ( ( ) => {
@@ -172,6 +221,12 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
172221 const handleSend = async ( ) => {
173222 if ( loading || inValidAmount ) return ;
174223
224+ // Check wallet health before allowing transaction
225+ if ( ! isAuthenticated || ! walletHealth || walletHealth . status !== 'healthy' ||
226+ ! walletHealth . chain_synced ) {
227+ return ; // Don't proceed if wallet is not ready
228+ }
229+
175230 setLoading ( true ) ;
176231
177232 const selectedFee = feeRate ; // The user-selected fee rate
@@ -308,7 +363,7 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
308363 </ S . TiersContainer >
309364 < BaseRow justify = { 'center' } >
310365 < S . SendFormButton
311- disabled = { loading || isLoading || inValidAmount || authLoading || addressError }
366+ disabled = { loading || isLoading || inValidAmount || authLoading || addressError || ! isWalletReady }
312367 onClick = { handleSend }
313368 size = "large"
314369 type = "primary"
@@ -319,11 +374,119 @@ const SendForm: React.FC<SendFormProps> = ({ onSend }) => {
319374 </ S . FormSpacer >
320375 ) ;
321376
377+ // Check if wallet is ready for transactions (healthy + synced = ready)
378+ const isWalletReady = isAuthenticated && walletHealth && walletHealth . status === 'healthy' &&
379+ walletHealth . chain_synced ;
380+
381+ // Render wallet status indicator
382+ const renderWalletStatus = ( ) => {
383+ if ( ! isAuthenticated ) {
384+ return (
385+ < Alert
386+ message = "Wallet Not Connected"
387+ description = "Please authenticate with your wallet to access transaction features."
388+ type = "warning"
389+ showIcon
390+ style = { {
391+ marginBottom : '1rem' ,
392+ backgroundColor : 'var(--background-color-secondary)' ,
393+ border : '1px solid var(--border-color-base)' ,
394+ color : 'var(--text-main-color)'
395+ } }
396+ />
397+ ) ;
398+ }
399+
400+ if ( healthLoading ) {
401+ return (
402+ < Alert
403+ message = "Checking Wallet Status..."
404+ type = "info"
405+ showIcon
406+ style = { {
407+ marginBottom : '1rem' ,
408+ backgroundColor : 'var(--background-color-secondary)' ,
409+ border : '1px solid var(--border-color-base)' ,
410+ color : 'var(--text-main-color)'
411+ } }
412+ />
413+ ) ;
414+ }
415+
416+ if ( ! walletHealth ) {
417+ return (
418+ < Alert
419+ message = "Wallet Status Unknown"
420+ description = "Unable to connect to wallet service. Please check if the wallet is running."
421+ type = "error"
422+ showIcon
423+ style = { {
424+ marginBottom : '1rem' ,
425+ backgroundColor : 'var(--background-color-secondary)' ,
426+ border : '1px solid var(--border-color-base)' ,
427+ color : 'var(--text-main-color)'
428+ } }
429+ />
430+ ) ;
431+ }
432+
433+ if ( walletHealth . status !== 'healthy' ) {
434+ return (
435+ < Alert
436+ message = "Wallet Unhealthy"
437+ description = "The wallet service is not functioning properly."
438+ type = "error"
439+ showIcon
440+ style = { {
441+ marginBottom : '1rem' ,
442+ backgroundColor : 'var(--background-color-secondary)' ,
443+ border : '1px solid var(--border-color-base)' ,
444+ color : 'var(--text-main-color)'
445+ } }
446+ />
447+ ) ;
448+ }
449+
450+ if ( ! walletHealth . chain_synced ) {
451+ return (
452+ < Alert
453+ message = "Blockchain Not Synced"
454+ description = { `The wallet is still syncing with the blockchain. Connected to ${ walletHealth . peer_count } peers. Please wait for sync to complete before sending transactions.` }
455+ type = "warning"
456+ showIcon
457+ style = { {
458+ marginBottom : '1rem' ,
459+ backgroundColor : 'var(--background-color-secondary)' ,
460+ border : '1px solid var(--border-color-base)' ,
461+ color : 'var(--text-main-color)'
462+ } }
463+ />
464+ ) ;
465+ }
466+
467+ // Wallet is healthy and synced
468+ return (
469+ < Alert
470+ message = "Wallet Ready"
471+ description = { `Wallet is online and synced. Connected to ${ walletHealth . peer_count } peers.` }
472+ type = "success"
473+ showIcon
474+ style = { {
475+ marginBottom : '1rem' ,
476+ backgroundColor : 'var(--background-color-secondary)' ,
477+ border : '1px solid var(--border-color-base)' ,
478+ color : 'var(--text-main-color)'
479+ } }
480+ />
481+ ) ;
482+ } ;
483+
322484 return (
323485 < BaseSpin spinning = { isLoading || loading || authLoading } >
324486 < S . SendBody justify = { 'center' } >
325487 < S . FormSpacer >
326488 < S . FormHeader > Send</ S . FormHeader >
489+ { renderWalletStatus ( ) }
327490 { isDetailsOpen ? (
328491 < >
329492 < S . Recipient >
0 commit comments