import { useMemo, useState } from 'react'; import { useSeoMeta } from '@unhead/react'; 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, Copy, Check, ChevronDown } from 'lucide-react'; import { SupportSkateSpots } from '@/components/SupportSkateSpots'; import { SilentPaymentsCard } from '@/components/SilentPaymentsCard'; import { AngorProjectCard, ANGOR_PROJECT_URL } from '@/components/AngorProjectCard'; import { WaysToSupport } from '@/components/WaysToSupport'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { BACKERS, BACKERS_TOTAL_SATS, tierOf } from '@/lib/backers'; import { formatDistanceToNow } from '@/lib/dateFnsI18n'; import { useAuthor } from '@/hooks/useAuthor'; import { useGrowthStats } from '@/hooks/useGrowthStats'; import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions'; import { useSatsToUsd } from '@/hooks/useExchangeRate'; import { useZapLeaderboard } from '@/hooks/useZapLeaderboard'; import { useTranslation } from '@/contexts/language'; 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). // 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 — // the visible cap below is what keeps it short. const SUPPORTERS_SINCE = Math.floor(Date.UTC(2025, 11, 1) / 1000); // 2025-12-01 const VISIBLE_LIMIT = 12; /** npub → hex pubkey (useAuthor queries relays by HEX authors). */ function npubToHex(npub?: string): string | undefined { if (!npub) return undefined; try { const d = nip19.decode(npub); return d.type === 'npub' ? d.data : undefined; } catch { return undefined; } } // ─── Static data — edit these arrays to add/remove entries ────────────────── interface Collaborator { name: string; role: string; avatar?: string; // URL or leave blank for placeholder npub?: string; // Nostr npub — links to their profile url?: string; // fallback link } interface Partner { name: string; logo?: string; // URL to logo (SVG/PNG transparent preferred) url: string; description: string; } interface Friend { name: string; logo?: string; // URL to logo (SVG/PNG transparent preferred, min 200×200px) invertLogo?: boolean; // true if logo is dark and needs inversion for dark backgrounds url: string; tagline?: string; } const COLLABORATORS: Collaborator[] = [ // Add collaborators here — mix of Nostr users and external people // Example: // { name: 'Alice', role: 'Developer', npub: 'npub1...', avatar: 'https://...' }, // { name: 'Bob', role: 'Relay Operator', url: 'https://...' }, ]; const PARTNERS: Partner[] = [ { name: 'SNT4EVER', logo: 'https://blossom.ditto.pub/f6e1d75ed8123b860ca139ee2d43a0a1238b89a0bec24752d9fe48cc41aa5d4c.png', url: 'https://www.snt4ever.cat/', description: 'Bringing back up Sants Estació', }, // Add partners here // Format: { name: 'Example', logo: 'https://...', url: 'https://...', description: 'Short description' } // Logo: PNG/WebP transparent background preferred ]; const FRIENDS: Friend[] = [ { name: 'No Teeth Skateboarding', logo: 'https://blossom.dreamith.to/61bb7055930c442dfe1dfe5b8a8be5417f9b62f68ed79dc4ffbbddce5d4f122c.png', url: 'https://www.instagram.com/noteethskateboarding/', tagline: 'Hand-made Long Lasting Skateboards', }, { name: 'SNT4EVER', logo: 'https://blossom.ditto.pub/f6e1d75ed8123b860ca139ee2d43a0a1238b89a0bec24752d9fe48cc41aa5d4c.png', invertLogo: true, url: 'https://www.snt4ever.cat/', tagline: 'Bringing back up Sants Estació', }, { name: 'RK69 DIY Skatepark', logo: 'https://blossom.ditto.pub/0d1d3855e7922b4ad7c10332e14723a16525e7aa26d95c58ea1dc1ec75514c3c.png', url: 'https://www.instagram.com/rk69diyskatepark', tagline: 'DIY Skatepark 101%', }, { name: 'Suan Luang Backyard', logo: 'https://blossom.ditto.pub/8ac40ffbf9db9c9bff20e9b34d627a31b9c2044fe4f534948292516319684523.jpeg', url: 'https://www.slbskate.com/', tagline: "Bangkok's most famous Backyard DIY Spot", }, // Add more friends here ]; // ─── "What your support pays for" (words only — no figures) ────────────────── const PAYS_FOR = [ { icon: Server, key: 'walletUi.supporters.payRelay', desc: 'walletUi.supporters.payRelayDesc' }, { icon: Globe, key: 'walletUi.supporters.payDomain', desc: 'walletUi.supporters.payDomainDesc' }, { icon: Bot, key: 'walletUi.supporters.payAi', desc: 'walletUi.supporters.payAiDesc' }, { icon: Code2, key: 'walletUi.supporters.payDev', desc: 'walletUi.supporters.payDevDesc' }, { icon: Briefcase, key: 'walletUi.supporters.payOps', desc: 'walletUi.supporters.payOpsDesc' }, { icon: Megaphone, key: 'walletUi.supporters.payMarketing', desc: 'walletUi.supporters.payMarketingDesc' }, ] as const; // ─── Silent-payments donation card (BIP-353 name → BIP-321 URI) ────────────── // ─── Bitcoin grantmaker inquiry ────────────────────────────────────────────── 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(''); const [website, setWebsite] = useState(''); const [program, setProgram] = useState(''); const [grantSize, setGrantSize] = useState(''); const [kyc, setKyc] = useState(''); const [fund, setFund] = useState(''); const [ack, setAck] = useState(false); const [reviewOpen, setReviewOpen] = useState(false); const [copied, setCopied] = useState(false); 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', '', `Organization: ${org.trim()}`, `Contact: ${contact.trim()}`, website.trim() ? `Website: ${website.trim()}` : '', program.trim() ? `Program / round: ${program.trim()}` : '', grantSize.trim() ? `Grant size: ${grantSize.trim()}` : '', kyc.trim() ? `KYC / KYB process: ${kyc.trim()}` : '', fund.trim() ? `What we would like to fund: ${fund.trim()}` : '', '', '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 (

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

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

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

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

setOrg(e.target.value)} placeholder="e.g. OpenSats" className={inputClass} required aria-required="true" />
setContact(e.target.value)} placeholder="npub / NIP-05 / email / @handle" className={inputClass} required aria-required="true" />

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

setWebsite(e.target.value)} placeholder="https://…" className={inputClass} aria-invalid={!!websiteError} aria-describedby={websiteError ? 'grant-website-error' : undefined} /> {websiteError && }

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

{PAYS_FOR.slice(0, 3).map(({ icon: Icon, key, desc }) => (

{t(key)}

{t(desc)}

))}
{t('walletUi.supporters.grantsMoreDetails')} {t('walletUi.supporters.grantsOptional')}
setProgram(e.target.value)} placeholder="e.g. Q1 2027 grants" className={inputClass} />
setGrantSize(e.target.value)} placeholder="e.g. 5–25M sats" className={inputClass} />
setKyc(e.target.value)} placeholder="e.g. KYB + grantee KYC" className={inputClass} />