1- import { useState } from 'react' ;
1+ import { useState , useMemo } from 'react' ;
22import { openContractCall } from '@stacks/connect' ;
33import {
44 stringUtf8CV ,
@@ -11,6 +11,7 @@ import { network, appDetails, userSession } from '../utils/stacks';
1111import { CONTRACT_ADDRESS , CONTRACT_NAME } from '../config/contracts' ;
1212import { toMicroSTX , formatSTX } from '../lib/utils' ;
1313import { useTipContext } from '../context/TipContext' ;
14+ import { useBalance } from '../hooks/useBalance' ;
1415import ConfirmDialog from './ui/confirm-dialog' ;
1516import TxStatus from './ui/tx-status' ;
1617
@@ -30,6 +31,18 @@ export default function SendTip({ addToast }) {
3031 const [ recipientError , setRecipientError ] = useState ( '' ) ;
3132 const [ amountError , setAmountError ] = useState ( '' ) ;
3233
34+ const senderAddress = useMemo ( ( ) => {
35+ try {
36+ return userSession . loadUserData ( ) . profile . stxAddress . mainnet ;
37+ } catch {
38+ return null ;
39+ }
40+ } , [ ] ) ;
41+
42+ const { balance, loading : balanceLoading , refetch : refetchBalance } = useBalance ( senderAddress ) ;
43+
44+ const balanceSTX = balance !== null ? Number ( balance ) / 1_000_000 : null ;
45+
3346 const isValidStacksAddress = ( address ) => {
3447 if ( ! address ) return false ;
3548 const trimmed = address . trim ( ) ;
@@ -59,6 +72,8 @@ export default function SendTip({ addToast }) {
5972 setAmountError ( `Minimum tip is ${ MIN_TIP_STX } STX` ) ;
6073 } else if ( parsed > MAX_TIP_STX ) {
6174 setAmountError ( `Maximum tip is ${ MAX_TIP_STX . toLocaleString ( ) } STX` ) ;
75+ } else if ( balanceSTX !== null && parsed > balanceSTX ) {
76+ setAmountError ( 'Insufficient balance' ) ;
6277 } else {
6378 setAmountError ( '' ) ;
6479 }
@@ -97,6 +112,11 @@ export default function SendTip({ addToast }) {
97112 return ;
98113 }
99114
115+ if ( balanceSTX !== null && parsedAmount > balanceSTX ) {
116+ addToast ( 'Insufficient STX balance for this tip' , 'warning' ) ;
117+ return ;
118+ }
119+
100120 setShowConfirm ( true ) ;
101121 } ;
102122
@@ -139,6 +159,7 @@ export default function SendTip({ addToast }) {
139159 setAmount ( '' ) ;
140160 setMessage ( '' ) ;
141161 notifyTipSent ( ) ;
162+ refetchBalance ( ) ;
142163 addToast ( 'Tip sent successfully! Transaction: ' + data . txId , 'success' ) ;
143164 } ,
144165 onCancel : ( ) => {
@@ -160,6 +181,31 @@ export default function SendTip({ addToast }) {
160181 < div className = "max-w-md mx-auto p-6 bg-white dark:bg-gray-900 rounded-xl shadow-lg border border-gray-100 dark:border-gray-800" >
161182 < h2 className = "text-2xl font-bold mb-6 text-gray-800 dark:text-gray-100" > Send a Tip</ h2 >
162183
184+ { senderAddress && (
185+ < div className = "mb-5 flex items-center justify-between bg-gray-50 dark:bg-gray-800 rounded-lg px-4 py-3 border border-gray-200 dark:border-gray-700" >
186+ < div >
187+ < p className = "text-xs text-gray-500 dark:text-gray-400" > Your Balance</ p >
188+ < p className = "text-lg font-semibold text-gray-800 dark:text-gray-100" >
189+ { balanceLoading
190+ ? 'Loading...'
191+ : balanceSTX !== null
192+ ? `${ balanceSTX . toLocaleString ( undefined , { minimumFractionDigits : 2 , maximumFractionDigits : 6 } ) } STX`
193+ : 'Unavailable' }
194+ </ p >
195+ </ div >
196+ < button
197+ onClick = { refetchBalance }
198+ disabled = { balanceLoading }
199+ className = "text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 disabled:opacity-50"
200+ title = "Refresh balance"
201+ >
202+ < svg className = { `w-4 h-4 ${ balanceLoading ? 'animate-spin' : '' } ` } fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
203+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = { 2 } d = "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
204+ </ svg >
205+ </ button >
206+ </ div >
207+ ) }
208+
163209 < div className = "space-y-5" >
164210 < div >
165211 < label className = "block text-sm font-semibold text-gray-600 dark:text-gray-300 mb-2" >
0 commit comments