feat(supporters): add silent-payments donation card (BIP-353 name + QR)
This commit is contained in:
parent
34cf930657
commit
1691d49930
3 changed files with 80 additions and 2 deletions
|
|
@ -6500,6 +6500,11 @@ walletUi: {
|
|||
sourceAngor: 'Angor',
|
||||
supportersTotals: '{{count}} supporters · {{angor}} sats via Angor · {{zap}} sats zapped',
|
||||
quietNote: 'Plus everyone who gave quietly — thank you. ❤️',
|
||||
silentTitle: 'Private on-chain (silent payments)',
|
||||
silentDesc: 'One name, a fresh address for every payment — on-chain via silent payments, or Lightning from the same record. Nothing to reuse, nothing to link.',
|
||||
silentCopyName: 'Copy name',
|
||||
silentCopied: 'Copied',
|
||||
silentWallets: 'Needs a wallet that supports payment names / silent payments: Sparrow, Cake, Dana, Phoenix, BlueWallet, Wasabi, Nunchuk or BitBox02.',
|
||||
tiersNote: 'Tiers: Supporter under 100k sats · Patron 100k–1M · Legend 1M+',
|
||||
tierEmpty: 'No one yet — be the first.',
|
||||
joinSupporters: 'Join {{count}} supporters',
|
||||
|
|
|
|||
|
|
@ -6500,6 +6500,11 @@ walletUi: {
|
|||
sourceAngor: 'Angor',
|
||||
supportersTotals: '{{count}} patrocinadores · {{angor}} sats vía Angor · {{zap}} sats zapeados',
|
||||
quietNote: 'Y a todos los que aportaron en silencio — gracias. ❤️',
|
||||
silentTitle: 'On-chain privado (silent payments)',
|
||||
silentDesc: 'Un nombre, una dirección nueva en cada pago — on-chain con silent payments, o Lightning desde el mismo registro. Sin reutilizar direcciones, nada que enlazar.',
|
||||
silentCopyName: 'Copiar nombre',
|
||||
silentCopied: 'Copiado',
|
||||
silentWallets: 'Requiere una cartera compatible con nombres de pago / silent payments: Sparrow, Cake, Dana, Phoenix, BlueWallet, Wasabi, Nunchuk o BitBox02.',
|
||||
tiersNote: 'Niveles: Colaborador menos de 100k sats · Patrón 100k–1M · Leyenda 1M+',
|
||||
tierEmpty: 'Nadie todavía — sé el primero.',
|
||||
joinSupporters: 'Únete a {{count}} patrocinadores',
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { Link } 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 } from 'lucide-react';
|
||||
import QRCode from 'qrcode';
|
||||
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star, ShieldCheck, Server, Globe, Bot, Code2, Briefcase, Megaphone, TrendingUp, Check, Copy } from 'lucide-react';
|
||||
import { SupportSkateSpots } from '@/components/SupportSkateSpots';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { BACKERS, BACKERS_TOTAL_SATS, tierOf } from '@/lib/backers';
|
||||
|
|
@ -15,6 +16,13 @@ import { useTranslation } from '@/contexts/language';
|
|||
|
||||
const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
|
||||
|
||||
// Silent-payments "payment name" (BIP-353) → BIP-321 URI with a silent payment
|
||||
// address (sp) + a BOLT 12 Lightning offer (lno). Resolved from
|
||||
// skatespots.user._bitcoin-payment.silentpayments.net (2026-10-05). If the name
|
||||
// is re-pointed, update SILENT_URI from the DNS TXT record.
|
||||
const SILENT_NAME = 'skatespots@silentpayments.net';
|
||||
const SILENT_URI = 'bitcoin:?sp=sp1qq2xlxmvh5tpkjw460cfjf5ndz87fc45k0fm3glyalqxq4m436u8nyqa745dyh55pz4vchhjzam5tmlvvlp6whhkqmdk3e7xvfp4y80jhvq40lyl3&lno=lno1pgqppmsrse80qf0aara4slvcjxrvu6j2rp5ftmjy4yntlsmsutpkvkt6878s8y79jwe96rpahf567zyt0lhe26sxpsace8n9u5l359n254e639mqqgpas2cxvhc6ftcr6wrh6nv36mz53qhsfvwrxvgf8mnvq4tsg0z2plqqxvv8geddvc60pys4ja0ajvhhhka2zzfdsnmkvygu872fa0awyqvv8rrpg6k0kg8zpxh8hy3hm9pr35hfa73s92mk38xfqfgfz98v0w34c4z8un6ljxlfjcu69m3de0nqarvh8k8kqqey5kgfmvyfhrg3wltewtj8xn35aevy96dws40hygx7xpfu2k0ty8rm4mv262df8f9q3z2strn4cmh8svss';
|
||||
|
||||
// Only count support received from this point (the project's public launch era).
|
||||
// Zaps to the platform before this are ignored. NOTE: as of 2026-10-05 every
|
||||
// platform zap is already ≥ 2026-01, so this doesn't currently trim the list —
|
||||
|
|
@ -117,6 +125,63 @@ const PAYS_FOR = [
|
|||
{ icon: Megaphone, key: 'walletUi.supporters.payMarketing', desc: 'walletUi.supporters.payMarketingDesc' },
|
||||
] as const;
|
||||
|
||||
// ─── Silent-payments donation card (BIP-353 name → BIP-321 URI) ──────────────
|
||||
|
||||
function SilentPaymentsCard({ className }: { className?: string }) {
|
||||
const { t } = useTranslation();
|
||||
const [qr, setQr] = useState<string | null>(null);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
QRCode.toDataURL(SILENT_URI, { width: 200, margin: 1 })
|
||||
.then((url) => { if (active) setQr(url); })
|
||||
.catch(() => { if (active) setQr(null); });
|
||||
return () => { active = false; };
|
||||
}, []);
|
||||
|
||||
const copy = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(SILENT_NAME);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
} catch { /* clipboard blocked */ }
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`rounded-2xl border-2 border-white/15 bg-black/60 p-6 ${className ?? ''}`}>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span className="flex h-9 w-9 items-center justify-center rounded-xl bg-white/5 text-lg">🕵️</span>
|
||||
<div>
|
||||
<h3 className="text-lg font-black uppercase leading-tight text-white">
|
||||
{t('walletUi.supporters.silentTitle')}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-400">{t('walletUi.supporters.silentDesc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex flex-col items-center gap-4 sm:flex-row sm:items-start">
|
||||
{qr && (
|
||||
<img src={qr} alt={SILENT_NAME} className="h-[200px] w-[200px] shrink-0 rounded-xl bg-white p-2" />
|
||||
)}
|
||||
<div className="min-w-0 text-center sm:text-left">
|
||||
<p className="break-all font-mono text-sm text-white">{SILENT_NAME}</p>
|
||||
<button
|
||||
onClick={copy}
|
||||
className="mt-2 inline-flex min-h-0 min-w-0 items-center gap-1.5 rounded-full border border-white/20 px-3 py-1.5 text-xs font-bold text-gray-200 transition-colors hover:bg-white/5"
|
||||
>
|
||||
{copied ? <Check className="h-3 w-3" /> : <Copy className="h-3 w-3" />}
|
||||
{copied ? t('walletUi.supporters.silentCopied') : t('walletUi.supporters.silentCopyName')}
|
||||
</button>
|
||||
<p className="mt-3 text-[11px] leading-relaxed text-gray-500">
|
||||
{t('walletUi.supporters.silentWallets')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Section header ───────────────────────────────────────────────────────────
|
||||
|
||||
function SectionHeader({
|
||||
|
|
@ -577,6 +642,9 @@ export default function SupportersPage() {
|
|||
|
||||
{/* Lightning rail — the in-app way to chip in (zap publicly or pay quietly). */}
|
||||
<SupportSkateSpots className="mt-4" />
|
||||
|
||||
{/* Silent payments — private on-chain (and Lightning) via a BIP-353 name. */}
|
||||
<SilentPaymentsCard className="mt-4" />
|
||||
</section>
|
||||
|
||||
{/* ── 2. What your support pays for (words only) ── */}
|
||||
|
|
|
|||
Loading…
Reference in a new issue