From 1af3a5f6ef41c08d40d26b6e84b00f7d8fae8277 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sun, 1 Nov 2026 20:37:35 +0700 Subject: [PATCH] feat: improve Bitcoin grantmaker inquiry UX --- src/i18n/en.ts | 18 +++- src/i18n/es.ts | 18 +++- src/pages/SupportersPage.tsx | 195 +++++++++++++++++++++++++++-------- 3 files changed, 182 insertions(+), 49 deletions(-) diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 8bd6b85..1acb60e 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -6683,11 +6683,12 @@ walletUi: { our: 'OUR', community: 'COMMUNITY', heroDesc: 'SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.', + grantsTab: 'Grants', grantsSection: 'Bitcoin grantmakers', grantsSectionDesc: 'For organizations that issue grants to open-source Bitcoin projects.', grantsTitle: 'Open to sponsorships · Bitcoin grantmakers', - grantsDisclaimer: 'Want to donate to this project? Fill the form. Not to KYC us — to KYC YOU.', - grantsDisclaimerDesc: "This lane is only for grantmakers that KYC/KYB their grantees. If you're a skater who just wants to donate, use the Lightning, Silent Payments or Angor rails above — nobody needs your passport.", + grantsDisclaimer: 'Grantmaker inquiry. This lane is for organizations that already KYC/KYB their grantees.', + grantsDisclaimerDesc: "If you just want to donate, use the Lightning, Silent Payments or Angor rails above — no identity check.", grantsOrg: 'Organization', grantsContact: 'Contact (npub, email or handle)', grantsWebsite: 'Website', @@ -6695,6 +6696,19 @@ walletUi: { grantsSize: 'Grant size', grantsKyc: 'KYC / KYB process', grantsFund: 'What would you like to fund?', + grantsFundTitle: 'What a grant can fund', + grantsMoreDetails: 'Add grant details', + grantsOptional: 'Optional', + grantsRequired: 'Organization, contact and confirmation are required.', + grantsContactHint: 'Prefer an npub or NIP-05. Email or @handle is only reference text for the private DM.', + grantsWebsiteInvalid: 'Enter a valid http(s) website URL.', + grantsReview: 'Review message', + grantsReviewTitle: 'Review your private message', + grantsReviewDesc: 'Nothing has been sent. Check the message before opening the private DM.', + grantsOpenDm: 'Open private DM', + grantsCopy: 'Copy message', + grantsCopied: 'Copied', + grantsCopyFailed: 'Could not copy the message', grantsAck: 'I confirm we are a grantmaker that KYC/KYB our grantees.', grantsSend: 'Open grant conversation', grantsNoKycTitle: 'No KYC of SkateSpots', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index 2eb8f98..945d754 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -6683,11 +6683,12 @@ walletUi: { our: 'NUESTRA', community: 'COMUNIDAD', heroDesc: 'SkateSpots lo construye y lo mantiene la comunidad. Patrocinadores, colaboradores, socios y amigos que lo mantienen libre y descentralizado.', + grantsTab: 'Ayudas', grantsSection: 'Grantmakers de Bitcoin', grantsSectionDesc: 'Para organizaciones que conceden ayudas a proyectos Bitcoin de código abierto.', grantsTitle: 'Abierto a patrocinios · grantmakers de Bitcoin', - grantsDisclaimer: '¿Quieres donar a este proyecto? Rellena el formulario. No para hacernos KYC a nosotros, sino para hacerte KYC a TI.', - grantsDisclaimerDesc: 'Este canal es solo para grantmakers que hacen KYC/KYB a sus beneficiarios. Si eres un skater que solo quiere donar, usa las vías de Lightning, Silent Payments o Angor de arriba: nadie necesita tu pasaporte.', + grantsDisclaimer: 'Consulta para grantmakers. Este canal es para organizaciones que ya hacen KYC/KYB a sus beneficiarios.', + grantsDisclaimerDesc: 'Si solo quieres donar, usa las vías de Lightning, Silent Payments o Angor de arriba: sin comprobación de identidad.', grantsOrg: 'Organización', grantsContact: 'Contacto (npub, email o usuario)', grantsWebsite: 'Sitio web', @@ -6695,6 +6696,19 @@ walletUi: { grantsSize: 'Tamaño de la ayuda', grantsKyc: 'Proceso de KYC / KYB', grantsFund: '¿Qué te gustaría financiar?', + grantsFundTitle: 'Qué puede financiar una ayuda', + grantsMoreDetails: 'Añadir detalles de la ayuda', + grantsOptional: 'Opcional', + grantsRequired: 'La organización, el contacto y la confirmación son obligatorios.', + grantsContactHint: 'Mejor un npub o NIP-05. El email o @usuario solo es texto de referencia para el DM privado.', + grantsWebsiteInvalid: 'Introduce una URL http(s) válida.', + grantsReview: 'Revisar mensaje', + grantsReviewTitle: 'Revisa tu mensaje privado', + grantsReviewDesc: 'Todavía no se ha enviado nada. Revisa el mensaje antes de abrir el DM privado.', + grantsOpenDm: 'Abrir DM privado', + grantsCopy: 'Copiar mensaje', + grantsCopied: 'Copiado', + grantsCopyFailed: 'No se pudo copiar el mensaje', grantsAck: 'Confirmo que somos un grantmaker que hace KYC/KYB a sus beneficiarios.', grantsSend: 'Abrir conversación de ayuda', grantsNoKycTitle: 'Sin KYC de SkateSpots', diff --git a/src/pages/SupportersPage.tsx b/src/pages/SupportersPage.tsx index 8d2fc49..fa26beb 100644 --- a/src/pages/SupportersPage.tsx +++ b/src/pages/SupportersPage.tsx @@ -1,8 +1,8 @@ import { useMemo, useState } from 'react'; import { useSeoMeta } from '@unhead/react'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useSearchParams } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; -import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star, ShieldCheck, Server, Globe, Bot, Code2, Briefcase, Megaphone, TrendingUp, Info, Lock, EyeOff } from 'lucide-react'; +import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star, ShieldCheck, Server, Globe, Bot, Code2, Briefcase, Megaphone, TrendingUp, Info, Lock, EyeOff, Copy, Check, ChevronDown } from 'lucide-react'; import { SupportSkateSpots } from '@/components/SupportSkateSpots'; import { SilentPaymentsCard } from '@/components/SilentPaymentsCard'; import { AngorProjectCard, ANGOR_PROJECT_URL } from '@/components/AngorProjectCard'; @@ -20,7 +20,9 @@ import { Input } from '@/components/ui/input'; import { Textarea } from '@/components/ui/textarea'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { useToast } from '@/hooks/useToast'; // Only count support received from this point (the project's public launch era). @@ -133,6 +135,7 @@ const PAYS_FOR = [ function GrantmakerForm() { const { t } = useTranslation(); const navigate = useNavigate(); + const { toast } = useToast(); const owner = PLATFORM_OWNER_PUBKEYS[0]; const [org, setOrg] = useState(''); const [contact, setContact] = useState(''); @@ -142,8 +145,13 @@ function GrantmakerForm() { const [kyc, setKyc] = useState(''); const [fund, setFund] = useState(''); const [ack, setAck] = useState(false); + const [reviewOpen, setReviewOpen] = useState(false); + const [copied, setCopied] = useState(false); - const canSend = !!org.trim() && !!contact.trim() && ack && !!owner; + const websiteError = website.trim() && !/^https?:\/\/\S+$/i.test(website.trim()) + ? t('walletUi.supporters.grantsWebsiteInvalid') + : ''; + const canSend = !!org.trim() && !!contact.trim() && ack && !websiteError && !!owner; const message = [ 'Bitcoin grantmaker inquiry — SkateSpots', '', @@ -158,6 +166,24 @@ function GrantmakerForm() { 'I confirm this is a grantmaker inquiry and we KYC/KYB our grantees.', ].filter(Boolean).join('\n'); + const copyMessage = async () => { + try { + await navigator.clipboard.writeText(message); + setCopied(true); + toast({ title: t('walletUi.supporters.grantsCopied') }); + window.setTimeout(() => setCopied(false), 2000); + } catch { + toast({ title: t('walletUi.supporters.grantsCopyFailed'), variant: 'destructive' }); + } + }; + + const openDm = () => { + if (!canSend || !owner) return; + navigate(`/messages/${nip19.npubEncode(owner)}?message=${encodeURIComponent(message)}`); + }; + + const inputClass = 'border-[#f7931a]/25 bg-black/50 text-white placeholder:text-gray-500'; + return (
@@ -167,41 +193,93 @@ function GrantmakerForm() {

{t('walletUi.supporters.grantsTitle')}

{t('walletUi.supporters.grantsDisclaimer')}

-

{t('walletUi.supporters.grantsDisclaimerDesc')}

+

{t('walletUi.supporters.grantsDisclaimerDesc')}

-
+

+ {t('walletUi.supporters.grantsRequired')} +

+ +
- - setOrg(e.target.value)} placeholder="e.g. OpenSats" /> + + setOrg(e.target.value)} placeholder="e.g. OpenSats" className={inputClass} required aria-required="true" />
- - setContact(e.target.value)} placeholder="npub, email or @handle" /> -
-
- - setWebsite(e.target.value)} placeholder="https://…" /> -
-
- - setProgram(e.target.value)} placeholder="e.g. Q1 2027 grants" /> -
-
- - setGrantSize(e.target.value)} placeholder="e.g. 5–25M sats" /> -
-
- - setKyc(e.target.value)} placeholder="e.g. KYB + grantee KYC" /> + + setContact(e.target.value)} placeholder="npub / NIP-05 / email / @handle" className={inputClass} required aria-required="true" /> +

{t('walletUi.supporters.grantsContactHint')}

- -