import { useSeoMeta } from '@unhead/react'; import { useQuery } from '@tanstack/react-query'; import { Layout } from '@/components/Layout'; import { Link } from 'react-router-dom'; import { ExternalLink, Zap, Heart, Users } from 'lucide-react'; const GEYSER_PROJECT_URL = 'https://geyser.fund/project/skatespots?hero=satsnfacts'; const GEYSER_LEADERBOARD_URL = 'https://geyser.fund/project/skatespots/leaderboard'; const GEYSER_API_URL = 'https://api.geyser.fund/graphql'; interface GeyserContributor { user: { id: string; username: string; imageUrl?: string; heroLink?: string; }; amount: number; // sats funder?: boolean; } function formatSats(sats: number): string { if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(2)}M`; if (sats >= 1_000) return `${Math.round(sats / 1_000).toLocaleString()}k`; return sats.toLocaleString(); } function useGeyserLeaderboard() { return useQuery({ queryKey: ['geyser-leaderboard'], queryFn: async () => { try { const res = await fetch(GEYSER_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: `{ projectGet(where: { name: "skatespots" }) { funders(input: { orderBy: { amountFunded: desc }, pagination: { take: 100 } }) { amountFunded user { id username imageUrl heroLink } } } }`, }), }); const json = await res.json(); const funders = json?.data?.projectGet?.funders ?? []; return funders.map((f: { amountFunded: number; user: GeyserContributor['user'] }) => ({ user: f.user, amount: f.amountFunded, })); } catch { return []; } }, staleTime: 5 * 60 * 1000, // 5 min cache retry: false, }); } function SupporterCard({ contributor }: { contributor: GeyserContributor }) { const isAnon = !contributor.user.username || contributor.user.username.toLowerCase().startsWith('anon'); const profileUrl = contributor.user.heroLink || `https://geyser.fund/user/${contributor.user.id}`; const card = (
{contributor.user.imageUrl ? ( {contributor.user.username ) : (
๐Ÿ›น
)}

{isAnon ? 'Anon ๐Ÿ›น' : contributor.user.username}

{formatSats(contributor.amount)} sats

); if (isAnon) return card; return ( {card} ); } export default function SupportersPage() { useSeoMeta({ title: 'Supporters - SkateSpots', description: 'The community keeping SkateSpots alive. Powered by Geyser and Bitcoin Lightning.', }); const { data: contributors = [], isLoading } = useGeyserLeaderboard(); return (
{/* Hero */}
โšก

OUR{' '} SUPPORTERS

SkateSpots is community-powered. These skaters and Bitcoiners help keep the project free, decentralized, and built for the streets.

Become a Supporter
{/* Individuals section */}

Plebs

Every sat counts โ€” ordered by cumulative contributions.

{isLoading ? (
{Array.from({ length: 12 }).map((_, i) => (
))}
) : contributors.length > 0 ? (
{contributors.map((c, i) => ( ))}
) : (
๐Ÿ›น

Be the first supporter!

The leaderboard will update once contributions come in.

)} {/* Geyser leaderboard link */} {/* Powered by Geyser */}
{/* Mission section */}

Why We Need Support

SkateSpots is 100% free, open-source, and built on Nostr โ€” a decentralized protocol with no central server to shut it down. We don't run ads, sell your data, or answer to investors. Keeping the lights on and building new features relies entirely on the skate community. Every sat you contribute goes directly toward development, relay hosting, and keeping this tool free for every skater on earth.

{[ { emoji: '๐Ÿ›น', title: 'Nostr-native', desc: 'No central server. Your data lives on relays you choose.' }, { emoji: 'โšก', title: 'Bitcoin powered', desc: 'Earn sats for mapping spots. Spend them at local shops.' }, { emoji: '๐ŸŒ', title: 'Free forever', desc: 'Open source, no paywalls, no tracking, no VC funding.' }, ].map(({ emoji, title, desc }) => (
{emoji}

{title}

{desc}

))}
{/* Payment options */}

Send Sats

Via Geyser

Contribute via the Geyser campaign โ€” your name goes on the leaderboard above.

Open campaign
{/* Footer links */}

Thank you. Every sat keeps the spots on the map. ๐Ÿ›นโšก

License {' '}·{' '} About {' '}·{' '} FAQ

); }