From c54008cd3d80281f67ceb1dce3f4daee511177f0 Mon Sep 17 00:00:00 2001 From: mkfain Date: Sat, 30 May 2026 20:04:27 +0200 Subject: [PATCH] Surface a hint when the recipient picker is closed with no selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The campaign donate flow opens HDSendBitcoinDialog with a prefilled bitcoin:bc1q…?sp=sp1… URI. BitcoinRecipientInput auto-opens its dropdown so the donor picks between the on-chain address and the silent-payment code — privacy vs. compatibility, the explicit choice the picker was designed around (92608f14). In practice the donor's eye lands on the amount presets first. Tapping $100 counts as an outside click, dismisses the popover, and leaves `recipient` null. The Send button is disabled (correctly — no destination resolved), the input still shows the prefilled URI, and nothing on screen tells the donor what's missing. They eventually discover that re-tapping the recipient input reopens the dropdown. Add a small amber hint with a warning icon directly beneath the recipient input whenever the input has parseable candidates but no selection AND the popover is closed. The whole hint is a button that reopens the popover and refocuses the input on tap, so the recovery takes one click instead of a guessing game. Gate the hint on a new `hasOpenedForQuery` flag that flips true the first time the popover opens for the current query and resets when the query clears. That keeps the hint from flashing for one paint frame between mount and the auto-open effect on prefilled inputs. Regression-of: 92608f14 --- src/components/BitcoinRecipientInput.tsx | 42 +++++++++++++++++++++++- src/locales/ar.json | 3 +- src/locales/en.json | 3 +- src/locales/es.json | 3 +- src/locales/fa.json | 3 +- src/locales/fr.json | 3 +- src/locales/hi.json | 3 +- src/locales/id.json | 3 +- src/locales/km.json | 3 +- src/locales/ps.json | 3 +- src/locales/pt.json | 3 +- src/locales/ru.json | 3 +- src/locales/sn.json | 3 +- src/locales/sw.json | 3 +- src/locales/tr.json | 3 +- src/locales/zh-Hant.json | 3 +- src/locales/zh.json | 3 +- 17 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/components/BitcoinRecipientInput.tsx b/src/components/BitcoinRecipientInput.tsx index d7233366..91660cbb 100644 --- a/src/components/BitcoinRecipientInput.tsx +++ b/src/components/BitcoinRecipientInput.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Bitcoin, EyeOff, QrCode, X } from 'lucide-react'; +import { AlertTriangle, Bitcoin, EyeOff, QrCode, X } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { @@ -108,6 +108,11 @@ export function BitcoinRecipientInput({ // selection. const [query, setQuery] = useState(initialInput ?? ''); const [open, setOpen] = useState(false); + // Tracks whether the popover has been opened at least once for the + // current query. The "choose a payment method" hint suppresses on the + // very first render so callers prefilling the input don't see the hint + // flash for one frame before the auto-open effect runs. + const [hasOpenedForQuery, setHasOpenedForQuery] = useState(false); const [scannerOpen, setScannerOpen] = useState(false); const inputRef = useRef(null); @@ -161,11 +166,20 @@ export function BitcoinRecipientInput({ useEffect(() => { if (trimmed.length === 0) { setOpen(false); + setHasOpenedForQuery(false); return; } if (hasSp || hasBtc) setOpen(true); }, [trimmed, hasSp, hasBtc]); + // Track the first time the popover opens for the current query, so the + // "choose a payment method" hint only appears after the donor has had a + // chance to see (and dismiss) the dropdown — not flash for one paint + // frame between mount and the auto-open effect above. + useEffect(() => { + if (open) setHasOpenedForQuery(true); + }, [open]); + // ── Selection callbacks ─────────────────────────────────────────────── const selectBtc = useCallback( (address: string) => { @@ -331,6 +345,32 @@ export function BitcoinRecipientInput({ + {/* Picker-closed reminder. When the input holds parseable candidates + but the donor hasn't actually picked one yet — typically because + they tapped an amount preset, which counts as an outside-click + and dismisses the popover — the Send button is disabled with no + visible reason. Surface an actionable hint that re-opens the + dropdown so the donor doesn't have to guess that they're meant + to tap the recipient input again. + + Gated on `hasOpenedForQuery` so the hint doesn't flash for one + paint frame between mount and the auto-open effect on prefilled + inputs (campaign donate flow). */} + {hasOpenedForQuery && !popoverOpen && totalItems > 0 && ( + + )} + setScannerOpen(false)} diff --git a/src/locales/ar.json b/src/locales/ar.json index 0574de33..9a9d74da 100644 --- a/src/locales/ar.json +++ b/src/locales/ar.json @@ -1158,7 +1158,8 @@ "bitcoinAddress": "عنوان بيتكوين", "silentPayment": "عنوان دفع صامت", "toLabel": "إلى", - "clear": "مسح المستلم" + "clear": "مسح المستلم", + "choosePaymentMethod": "اختر طريقة الدفع للمتابعة" }, "feeSpeed": { "fastest": "~10 دقائق", diff --git a/src/locales/en.json b/src/locales/en.json index fd0e9c7c..69f1aac7 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1672,7 +1672,8 @@ "bitcoinAddress": "Bitcoin address", "silentPayment": "Silent payment address", "toLabel": "To", - "clear": "Clear recipient" + "clear": "Clear recipient", + "choosePaymentMethod": "Choose a payment method to continue" }, "feeSpeed": { "fastest": "~10 min", diff --git a/src/locales/es.json b/src/locales/es.json index 834890a1..4fcc1a6b 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -1174,7 +1174,8 @@ "bitcoinAddress": "Dirección de Bitcoin", "silentPayment": "Dirección de pago silencioso", "toLabel": "Para", - "clear": "Borrar destinatario" + "clear": "Borrar destinatario", + "choosePaymentMethod": "Elige un método de pago para continuar" }, "feeSpeed": { "fastest": "~10 min", diff --git a/src/locales/fa.json b/src/locales/fa.json index f7f3d01a..8b0b4f7d 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1174,7 +1174,8 @@ "bitcoinAddress": "نشانی بیت‌کوین", "silentPayment": "نشانی پرداخت خاموش", "toLabel": "به", - "clear": "پاک کردن گیرنده" + "clear": "پاک کردن گیرنده", + "choosePaymentMethod": "برای ادامه یک روش پرداخت انتخاب کنید" }, "feeSpeed": { "fastest": "~۱۰ دقیقه", diff --git a/src/locales/fr.json b/src/locales/fr.json index c817f958..0f916f09 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -1597,7 +1597,8 @@ "bitcoinAddress": "Adresse Bitcoin", "silentPayment": "Adresse de paiement silencieux", "toLabel": "À", - "clear": "Effacer le destinataire" + "clear": "Effacer le destinataire", + "choosePaymentMethod": "Choisissez un mode de paiement pour continuer" }, "feeSpeed": { "fastest": "~10 min", diff --git a/src/locales/hi.json b/src/locales/hi.json index 496ba71a..504bd60b 100644 --- a/src/locales/hi.json +++ b/src/locales/hi.json @@ -1543,7 +1543,8 @@ "bitcoinAddress": "Bitcoin एड्रेस", "silentPayment": "साइलेंट पेमेंट एड्रेस", "toLabel": "किसे", - "clear": "प्राप्तकर्ता हटाएँ" + "clear": "प्राप्तकर्ता हटाएँ", + "choosePaymentMethod": "जारी रखने के लिए भुगतान विधि चुनें" }, "feeSpeed": { "fastest": "~10 मिनट", diff --git a/src/locales/id.json b/src/locales/id.json index 468f32bd..f3610541 100644 --- a/src/locales/id.json +++ b/src/locales/id.json @@ -1543,7 +1543,8 @@ "bitcoinAddress": "Alamat Bitcoin", "silentPayment": "Alamat silent payment", "toLabel": "Kepada", - "clear": "Hapus penerima" + "clear": "Hapus penerima", + "choosePaymentMethod": "Pilih metode pembayaran untuk melanjutkan" }, "feeSpeed": { "fastest": "~10 menit", diff --git a/src/locales/km.json b/src/locales/km.json index 2a854dc2..8d462f5e 100644 --- a/src/locales/km.json +++ b/src/locales/km.json @@ -1174,7 +1174,8 @@ "bitcoinAddress": "អាសយដ្ឋាន Bitcoin", "silentPayment": "អាសយដ្ឋានទូទាត់ស្ងាត់", "toLabel": "ជូន", - "clear": "សម្អាតអ្នកទទួល" + "clear": "សម្អាតអ្នកទទួល", + "choosePaymentMethod": "ជ្រើសរើសវិធីសាស្ត្រទូទាត់ដើម្បីបន្ត" }, "feeSpeed": { "fastest": "~១០ នាទី", diff --git a/src/locales/ps.json b/src/locales/ps.json index 7b8e0f25..d02fce9a 100644 --- a/src/locales/ps.json +++ b/src/locales/ps.json @@ -1174,7 +1174,8 @@ "bitcoinAddress": "د بټکوین پته", "silentPayment": "د چوپې ورکړې پته", "toLabel": "ته", - "clear": "ترلاسه‌کوونکی پاک کړئ" + "clear": "ترلاسه‌کوونکی پاک کړئ", + "choosePaymentMethod": "د دوام لپاره د ورکړې طریقه وټاکئ" }, "feeSpeed": { "fastest": "~۱۰ دقیقې", diff --git a/src/locales/pt.json b/src/locales/pt.json index 3537a42c..bee19908 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -1607,7 +1607,8 @@ "bitcoinAddress": "Endereço Bitcoin", "silentPayment": "Endereço de pagamento silencioso", "toLabel": "Para", - "clear": "Limpar destinatário" + "clear": "Limpar destinatário", + "choosePaymentMethod": "Escolha um método de pagamento para continuar" }, "feeSpeed": { "fastest": "~10 min", diff --git a/src/locales/ru.json b/src/locales/ru.json index 66a3bc89..a2240e41 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1607,7 +1607,8 @@ "bitcoinAddress": "Bitcoin-адрес", "silentPayment": "Адрес тихого платежа", "toLabel": "Кому", - "clear": "Очистить получателя" + "clear": "Очистить получателя", + "choosePaymentMethod": "Выберите способ оплаты, чтобы продолжить" }, "feeSpeed": { "fastest": "~10 мин", diff --git a/src/locales/sn.json b/src/locales/sn.json index 7ea71d7f..9008909c 100644 --- a/src/locales/sn.json +++ b/src/locales/sn.json @@ -1174,7 +1174,8 @@ "bitcoinAddress": "Kero yeBitcoin", "silentPayment": "Kero yemubhadharo wakanyararira", "toLabel": "Kuna", - "clear": "Bvisa mugamuchiri" + "clear": "Bvisa mugamuchiri", + "choosePaymentMethod": "Sarudza nzira yokubhadhara kuti uenderere" }, "feeSpeed": { "fastest": "~10 maminitsi", diff --git a/src/locales/sw.json b/src/locales/sw.json index eac00d64..abd25c87 100644 --- a/src/locales/sw.json +++ b/src/locales/sw.json @@ -1502,7 +1502,8 @@ "bitcoinAddress": "Anwani ya Bitcoin", "silentPayment": "Anwani ya malipo ya kimya", "toLabel": "Kwa", - "clear": "Futa mpokeaji" + "clear": "Futa mpokeaji", + "choosePaymentMethod": "Chagua njia ya malipo ili kuendelea" }, "feeSpeed": { "fastest": "~dakika 10", diff --git a/src/locales/tr.json b/src/locales/tr.json index 463e1ed0..0d4d2bdc 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -1542,7 +1542,8 @@ "bitcoinAddress": "Bitcoin adresi", "silentPayment": "Sessiz ödeme adresi", "toLabel": "Alıcı", - "clear": "Alıcıyı temizle" + "clear": "Alıcıyı temizle", + "choosePaymentMethod": "Devam etmek için bir ödeme yöntemi seçin" }, "feeSpeed": { "fastest": "~10 dk", diff --git a/src/locales/zh-Hant.json b/src/locales/zh-Hant.json index 334f0b3e..4a2e006a 100644 --- a/src/locales/zh-Hant.json +++ b/src/locales/zh-Hant.json @@ -1110,7 +1110,8 @@ "bitcoinAddress": "比特幣地址", "silentPayment": "靜默支付地址", "toLabel": "收件人", - "clear": "清除收件人" + "clear": "清除收件人", + "choosePaymentMethod": "請選擇付款方式以繼續" }, "feeSpeed": { "fastest": "~10 分鐘", diff --git a/src/locales/zh.json b/src/locales/zh.json index a5b0762d..3ada7e75 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1174,7 +1174,8 @@ "bitcoinAddress": "比特币地址", "silentPayment": "静默支付地址", "toLabel": "收件人", - "clear": "清除收件人" + "clear": "清除收件人", + "choosePaymentMethod": "请选择支付方式以继续" }, "feeSpeed": { "fastest": "~10 分钟",