import { useMemo, useState } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link, useNavigate } 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 { 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 { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; // 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 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 canSend = !!org.trim() && !!contact.trim() && ack && !!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'); return (

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

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

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

setOrg(e.target.value)} placeholder="e.g. OpenSats" />
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" />