@@ -38,6 +38,7 @@ import TradeDialog, { type TradeSide } from '@/components/common/TradeDialog';
3838import NetworkMismatchBanner from '@/components/common/NetworkMismatchBanner' ;
3939import StellarConnectionQualityBadge from '@/components/common/StellarConnectionQualityBadge' ;
4040import { useNetworkMismatch } from '@/hooks/useNetworkMismatch' ;
41+ import { useTradeMutation , useWalletHoldings } from '@/hooks/useWallet' ;
4142import showToast from '@/utils/toast.util' ;
4243import { getSignatureErrorMessage } from '@/utils/errorHandling.utils' ;
4344import { formatCompactNumber , formatNumber } from '@/utils/numberFormat.utils' ;
@@ -185,6 +186,7 @@ const BASE_RETRY_DELAY_MS = 800;
185186const PAGE_SIZE = 6 ;
186187const FETCH_RETRY_ACTION_LABEL = 'Try again' ;
187188const DEMO_HELD_KEY_QUANTITIES = [ 0 , 2 , 1 ] as const ;
189+ const DEMO_WALLET_ADDRESS = 'demo-wallet-address' ;
188190const FINAL_FETCH_ERROR_COPY =
189191 'Unable to load live creators right now. Showing fallback creators.' ;
190192const CREATOR_REFRESH_SHORTCUT_LABEL = 'Ctrl/Cmd + Alt + R' ;
@@ -714,20 +716,28 @@ function LandingPage() {
714716 handleRetryCreatorFetch ( ) ;
715717 } ;
716718
719+ const tradeMutation = useTradeMutation ( DEMO_WALLET_ADDRESS ) ;
720+ const { data : cachedHoldings = [ ] } = useWalletHoldings ( DEMO_WALLET_ADDRESS ) ;
721+
717722 const heldKeyPositions = useMemo (
718723 ( ) =>
719- holdingsCreators . map ( ( creator , index ) => ( {
720- creatorId : creator . id ,
721- quantity :
724+ holdingsCreators . map ( ( creator , index ) => {
725+ const cached = cachedHoldings . find ( h => h . creatorId === creator . id ) ;
726+ const baseQuantity =
722727 index === 0
723728 ? featuredHoldings
724- : ( DEMO_HELD_KEY_QUANTITIES [ index ] ?? 0 ) ,
725- priceStroops : creator . priceStroops ,
726- price : creator . price ,
727- isPriceLoading : isPriceRefreshing ,
728- isPriceStale : creatorsAreStale ,
729- } ) ) ,
730- [ holdingsCreators , creatorsAreStale , featuredHoldings , isPriceRefreshing ]
729+ : ( DEMO_HELD_KEY_QUANTITIES [ index ] ?? 0 ) ;
730+ return {
731+ creatorId : creator . id ,
732+ quantity : cached ?. quantity ?? baseQuantity ,
733+ priceStroops : creator . priceStroops ,
734+ price : creator . price ,
735+ isPriceLoading : isPriceRefreshing ,
736+ isPriceStale : creatorsAreStale ,
737+ pending : cached ?. pending ?? false ,
738+ } ;
739+ } ) ,
740+ [ holdingsCreators , creatorsAreStale , featuredHoldings , isPriceRefreshing , cachedHoldings ]
731741 ) ;
732742 const portfolioValue = useMemo (
733743 ( ) => calculatePortfolioValue ( heldKeyPositions ) ,
@@ -784,37 +794,37 @@ function LandingPage() {
784794 } ;
785795
786796 const handleConfirmTrade = async ( amount : number ) => {
787- const previousHoldings = featuredHoldings ;
788- const creatorName = featuredCreator ?. title ?? 'Unknown creator' ;
789797 setTradeSubmitting ( true ) ;
790798
791799 try {
792- showToast . loading (
793- tradeSide === 'buy'
794- ? `Submitting buy for ${ amount } key${ amount === 1 ? '' : 's' } ...`
795- : `Submitting sell for ${ amount } key${ amount === 1 ? '' : 's' } ...`
796- ) ;
797-
798- await new Promise < void > ( resolve => window . setTimeout ( resolve , 900 ) ) ;
799-
800- setFeaturedHoldings ( current =>
801- tradeSide === 'buy'
802- ? current + amount
803- : Math . max ( 0 , current - amount )
804- ) ;
805-
806- await new Promise < void > ( resolve => window . setTimeout ( resolve , 250 ) ) ;
807-
808- showToast . transactionSuccess (
809- 'Trade confirmed' ,
810- tradeSide === 'buy'
811- ? `Bought ${ formatNumber ( amount ) } key${ amount === 1 ? '' : 's' } from ${ creatorName } `
812- : `Sold ${ formatNumber ( amount ) } key${ amount === 1 ? '' : 's' } from ${ creatorName } `
813- ) ;
800+ if ( tradeSide === 'buy' ) {
801+ showToast . loading (
802+ `Submitting buy for ${ amount } key${ amount === 1 ? '' : 's' } ...`
803+ ) ;
804+ await tradeMutation . mutateAsync ( {
805+ creatorId : '1' ,
806+ amount,
807+ priceStroops : resolveCreatorKeyPriceStroops ( featuredCreator ) ,
808+ price : featuredCreator ?. price ,
809+ } ) ;
810+ setFeaturedHoldings ( current => current + amount ) ;
811+ } else {
812+ showToast . loading (
813+ `Submitting sell for ${ amount } key${ amount === 1 ? '' : 's' } ...`
814+ ) ;
815+ await new Promise < void > ( resolve => window . setTimeout ( resolve , 900 ) ) ;
816+ setFeaturedHoldings ( current => Math . max ( 0 , current - amount ) ) ;
817+ await new Promise < void > ( resolve => window . setTimeout ( resolve , 250 ) ) ;
818+ showToast . transactionSuccess (
819+ 'Trade confirmed' ,
820+ `Holdings refreshed: -${ formatNumber ( amount ) } keys.`
821+ ) ;
822+ }
814823 setTradeDialogOpen ( false ) ;
815824 } catch ( error ) {
816- setFeaturedHoldings ( previousHoldings ) ;
817- showToast . error ( getSignatureErrorMessage ( error ) ) ;
825+ if ( tradeSide === 'sell' ) {
826+ showToast . error ( getSignatureErrorMessage ( error ) ) ;
827+ }
818828 } finally {
819829 setTradeSubmitting ( false ) ;
820830 }
@@ -1355,12 +1365,21 @@ function LandingPage() {
13551365 return (
13561366 < div
13571367 key = { position . creatorId }
1358- className = "rounded-2xl border border-white/10 bg-white/[0.03] p-4"
1368+ className = { cn (
1369+ 'rounded-2xl border border-white/10 bg-white/[0.03] p-4 transition-opacity' ,
1370+ position . pending && 'opacity-60'
1371+ ) }
13591372 >
13601373 < div className = "truncate text-sm font-bold text-white" >
13611374 { creator ?. title ?? 'Unknown creator' }
13621375 </ div >
13631376 < div className = "mt-1 text-xs text-white/55" >
1377+ { position . pending && (
1378+ < span className = "mr-2 inline-flex items-center gap-1 rounded-full bg-amber-400/10 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-400" >
1379+ < span className = "size-2.5 animate-spin rounded-full border-2 border-amber-400/30 border-t-amber-400" />
1380+ Pending
1381+ </ span >
1382+ ) }
13641383 { formatNumber ( position . quantity ) } keys ·{ ' ' }
13651384 { position . isPriceLoading
13661385 ? 'Refreshing price'
0 commit comments