From d198b19bef49851e6a04ecc609be89dfdc893464 Mon Sep 17 00:00:00 2001 From: Roland <33993199+rolznz@users.noreply.github.com> Date: Sat, 8 Aug 2026 13:38:46 +0700 Subject: [PATCH] feat: enable typing card name when choosing other card (#2511) * feat: enable typing card name when choosing other card Closes #2457 Co-Authored-By: Claude Fable 5 * fix: reset connect-card dialog form on open and show empty name validation error Co-Authored-By: Claude Fable 5 * chore: use shadcn Button for other-card option in connect dialog Co-Authored-By: Claude Fable 5 --------- Co-authored-by: Claude Fable 5 --- frontend/src/screens/cards/Cards.tsx | 113 ++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/frontend/src/screens/cards/Cards.tsx b/frontend/src/screens/cards/Cards.tsx index 20921d3a..51e7a225 100644 --- a/frontend/src/screens/cards/Cards.tsx +++ b/frontend/src/screens/cards/Cards.tsx @@ -12,7 +12,7 @@ import { ZapIcon, } from "lucide-react"; import React from "react"; -import { Link } from "react-router"; +import { Link, useNavigate } from "react-router"; import twoFiatLogo from "src/assets/cards/2fiat.png"; import freedomiaLogo from "src/assets/cards/freedomia.png"; import redotpayLogo from "src/assets/cards/redotpay.png"; @@ -34,6 +34,9 @@ import { DialogHeader, DialogTitle, } from "src/components/ui/dialog"; +import { FieldError } from "src/components/ui/field"; +import { Input } from "src/components/ui/input"; +import { Label } from "src/components/ui/label"; import { Select, SelectContent, @@ -783,8 +786,100 @@ function ConnectCardDialog({ onOpenChange: (open: boolean) => void; providers: Provider[]; }) { + const navigate = useNavigate(); + const [showOtherCardForm, setShowOtherCardForm] = React.useState(false); + const [otherCardName, setOtherCardName] = React.useState(""); + const [otherCardNameError, setOtherCardNameError] = React.useState(""); + + // The dialog is controlled and opened programmatically (no DialogTrigger), + // so onOpenChange never fires with true — reset the form here instead. + React.useEffect(() => { + if (open) { + setShowOtherCardForm(false); + setOtherCardName(""); + setOtherCardNameError(""); + } + }, [open]); + + const handleOpenChange = (o: boolean) => { + if (o) { + setShowOtherCardForm(false); + setOtherCardName(""); + setOtherCardNameError(""); + } + onOpenChange(o); + }; + + const handleOtherCardSubmit = (e: React.FormEvent) => { + e.preventDefault(); + const cardName = otherCardName.trim(); + if (!cardName) { + setOtherCardNameError("Enter a card name"); + return; + } + sendEvent("debit_card_connect", { name: cardName }); + onOpenChange(false); + navigate( + `/apps/new?app=bitcoin-card-topup&name=${encodeURIComponent(`${cardName} - Bitcoin Card Topup`)}` + ); + }; + + if (showOtherCardForm) { + return ( + + + + Name your card + + We'll use it to label your top-up connection. + + + +
+
+ + { + setOtherCardName(e.target.value); + setOtherCardNameError(""); + }} + placeholder="e.g. Moon" + required + autoComplete="off" + aria-invalid={!!otherCardNameError || undefined} + aria-describedby={ + otherCardNameError ? "other-card-name-error" : undefined + } + /> + + {otherCardNameError} + +
+
+ + +
+
+
+
+ ); + } + return ( - + Pick your card provider @@ -835,13 +930,11 @@ function ConnectCardDialog({ ); })} - { - sendEvent("debit_card_connect", { name: "Other" }); - onOpenChange(false); - }} - className="flex items-center gap-3 rounded-lg border border-dashed border-border p-3 hover:bg-accent/40 transition-colors" +