From a66b645b45d1665a704bd99f78cde97d0c70c27a Mon Sep 17 00:00:00 2001 From: CAESAR MCMXCVII Date: Mon, 1 Jun 2026 07:22:15 +0100 Subject: [PATCH 1/2] Add-inline-validation-for-trade-amount-input-field --- src/components/common/TradeDialog.tsx | 49 +++++++++++++++++++-------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/src/components/common/TradeDialog.tsx b/src/components/common/TradeDialog.tsx index 218c366..728850b 100644 --- a/src/components/common/TradeDialog.tsx +++ b/src/components/common/TradeDialog.tsx @@ -42,10 +42,14 @@ const TradeDialog: React.FC = ({ isSubmitting = false, }) => { const [amountText, setAmountText] = useState('1'); + const [touched, setTouched] = useState(false); const amountInputRef = useRef(null); useEffect(() => { - if (open) setAmountText('1'); + if (open) { + setAmountText('1'); + setTouched(false); + } }, [open]); const parsedAmount = useMemo(() => { @@ -54,10 +58,18 @@ const TradeDialog: React.FC = ({ return Number(normalized); }, [amountText]); - const amountValid = - Number.isFinite(parsedAmount) && - parsedAmount > 0 && - (side !== 'sell' || parsedAmount <= availableHoldings); + const validationError = useMemo((): string | null => { + const normalized = amountText.trim(); + if (!normalized) return 'Please enter an amount.'; + if (!Number.isFinite(parsedAmount)) return 'Amount must be a valid number.'; + if (parsedAmount <= 0) return 'Amount must be greater than zero.'; + if (side === 'sell' && parsedAmount > availableHoldings) + return `You can't sell more than your holdings (${formatNumber(availableHoldings)} keys).`; + return null; + }, [amountText, parsedAmount, side, availableHoldings]); + + const amountValid = validationError === null; + const showError = touched && validationError !== null; const title = side === 'buy' ? 'Buy keys' : 'Sell keys'; const confirmLabel = side === 'buy' ? 'Confirm buy' : 'Confirm sell'; @@ -110,19 +122,33 @@ const TradeDialog: React.FC = ({ ref={amountInputRef} inputMode="decimal" value={amountText} - onChange={event => setAmountText(event.target.value)} + onChange={event => { + setAmountText(event.target.value); + setTouched(true); + }} + onBlur={() => setTouched(true)} disabled={isSubmitting} className={cn( 'w-full rounded-xl border bg-white/[0.04] px-3 py-2 text-white outline-none transition-colors', 'border-white/10 focus:border-amber-500/50 focus:ring-2 focus:ring-amber-500/15', - !amountValid && amountText.trim() - ? 'border-red-500/40' - : '' + showError ? 'border-red-500/60' : '' )} aria-label="Trade amount" + aria-describedby={showError ? 'trade-amount-error' : undefined} + aria-invalid={showError || undefined} data-focus-order="1" data-testid="trade-dialog-amount" /> + {showError && ( + + )}
= ({ className="text-white/45" /> )} - {side === 'sell' && parsedAmount > availableHoldings && ( -
- You can’t sell more than your current holdings. -
- )}
{/* From 096a80ef2bf19fd9861519d24cf7c7d1c47b17ec Mon Sep 17 00:00:00 2001 From: CAESAR MCMXCVII Date: Mon, 1 Jun 2026 07:30:11 +0100 Subject: [PATCH 2/2] Add-inline-validation-for-trade-amount-input-field --- src/pages/LandingPage.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 80d8066..a00488b 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -30,7 +30,6 @@ import EmptyTransactionTimelineState from '@/components/common/EmptyTransactionT import TradeDialog, { type TradeSide } from '@/components/common/TradeDialog'; import NetworkMismatchBanner from '@/components/common/NetworkMismatchBanner'; import StellarConnectionQualityBadge from '@/components/common/StellarConnectionQualityBadge'; -import { useEthersProvider } from '@/hooks/useEthersProvider'; import { useNetworkMismatch } from '@/hooks/useNetworkMismatch'; import showToast from '@/utils/toast.util'; import { getSignatureErrorMessage } from '@/utils/errorHandling.utils'; @@ -265,7 +264,6 @@ function LandingPage() { const [tradeSide, setTradeSide] = useState('buy'); const [tradeDialogOpen, setTradeDialogOpen] = useState(false); const [tradeSubmitting, setTradeSubmitting] = useState(false); - const tradeFeeEstimateProvider = useEthersProvider(); const prefersReducedMotion = usePrefersReducedMotion(); const [sortOption, setSortOption] = useState(() => { if (typeof window === 'undefined') return 'featured'; @@ -1246,7 +1244,6 @@ function LandingPage() { availableHoldings={featuredHoldings} keyPriceStroops={resolveCreatorKeyPriceStroops(featuredCreator)} isSubmitting={tradeSubmitting} - networkFeeEstimateProvider={tradeFeeEstimateProvider} onOpenChange={setTradeDialogOpen} onConfirm={handleConfirmTrade} />