diff --git a/src/AppRouter.tsx b/src/AppRouter.tsx index a7d36a4..0aacf5d 100644 --- a/src/AppRouter.tsx +++ b/src/AppRouter.tsx @@ -194,6 +194,8 @@ export function AppRouter() { } /> } /> } /> + {/* Friendly alias: people naturally type /support. */} + } /> } /> {/* Marketplace */} } /> diff --git a/src/components/WaysToSupport.tsx b/src/components/WaysToSupport.tsx index d07e55e..8480e8e 100644 --- a/src/components/WaysToSupport.tsx +++ b/src/components/WaysToSupport.tsx @@ -1,11 +1,20 @@ import { useTranslation } from '@/contexts/language'; +import { cn } from '@/lib/utils'; /** * "Ways to support" — three purpose-first cards; each scrolls to its rail by id * (#support-lightning / #support-silent / #support-angor), so it works anywhere * those anchors exist (/supporters and /donate). */ -export function WaysToSupport({ className }: { className?: string }) { +export function WaysToSupport({ + className, + active, + onSelect, +}: { + className?: string; + active?: string; + onSelect?: (id: string) => void; +}) { const { t } = useTranslation(); const ways = [ { id: 'support-lightning', emoji: '⚡', title: t('walletUi.supporters.wayLightningTitle'), desc: t('walletUi.supporters.wayLightningDesc') }, @@ -13,16 +22,29 @@ export function WaysToSupport({ className }: { className?: string }) { { id: 'support-angor', emoji: '⛓', title: t('walletUi.supporters.wayAngorTitle'), desc: t('walletUi.supporters.wayAngorDesc') }, ]; return ( -
+
{ways.map((w) => ( ))}
diff --git a/src/pages/SupportersPage.tsx b/src/pages/SupportersPage.tsx index 73a1ccd..8d2fc49 100644 --- a/src/pages/SupportersPage.tsx +++ b/src/pages/SupportersPage.tsx @@ -20,6 +20,7 @@ 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). @@ -27,7 +28,7 @@ import { Button } from '@/components/ui/button'; // 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 = 21; +const VISIBLE_LIMIT = 12; /** npub → hex pubkey (useAuthor queries relays by HEX authors). */ function npubToHex(npub?: string): string | undefined { @@ -261,15 +262,15 @@ function SectionHeader({ color?: string; }) { return ( -
-
- -

+
+
+ +

{title}

-
+
- {subtitle &&

{subtitle}

} + {subtitle &&

{subtitle}

}
); } @@ -547,19 +548,6 @@ function SupporterRow({ rank, entry }: { rank: number; entry: SupporterEntry }) ); } -// ─── Placeholder card (empty slot) ─────────────────────────────────────────── - -function PlaceholderCard({ label }: { label: string }) { - return ( -
-
- ? -
-

{label}

-
- ); -} - // ─── Main page ──────────────────────────────────────────────────────────────── export default function SupportersPage() { @@ -571,6 +559,8 @@ export default function SupportersPage() { const totalUsd = useSatsToUsd(allSats); const { data: stats } = useGrowthStats(); const [showAll, setShowAll] = useState(false); + const [tab, setTab] = useState<'support' | 'backers' | 'community' | 'grants'>('support'); + const [rail, setRail] = useState<'support-lightning' | 'support-silent' | 'support-angor'>('support-lightning'); // One ranked wall: Angor backers + Lightning zaps to the project, merged by npub. const supporters = useMemo(() => { @@ -652,317 +642,284 @@ export default function SupportersPage() { return ( <> -
+
{/* ── Hero ── */}
-
⚡
-

+

{t('walletUi.supporters.our')}{' '} {t('walletUi.supporters.community')}

-

+

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

- - - {t('walletUi.supporters.backAngor')} - - - - {t('walletUi.supporters.donatePageLink')} → - +
+ + + {t('walletUi.supporters.backAngor')} + + + + {t('walletUi.supporters.donatePageLink')} → + +
+
+ {supporters.length.toLocaleString()} {t('walletUi.supporters.backers')} + + {allSats.toLocaleString()} sats + {typeof totalUsd === 'number' && ( + <> + + ≈ ${totalUsd.toFixed(2)} + + )} +
- {/* ── 1. Support ── */} -
- - - - - {/* Lightning rail — the in-app way to chip in (zap publicly or pay quietly). */} -
- -
- - {/* Silent payments — private on-chain (and Lightning) via a BIP-353 name. */} -
- -
-
- -
-
- - {/* ── 2. Bitcoin grantmakers ── */} -
- - -
- - {/* ── 3. What your support pays for (words only) ── */} -
- -
- {PAYS_FOR.map(({ icon: Icon, key, desc }) => ( -
setTab(value as 'support' | 'backers' | 'community' | 'grants')} + > + + {[ + { value: 'support', label: t('walletUi.supporters.support') }, + { value: 'backers', label: t('walletUi.supporters.backers') }, + { value: 'community', label: t('walletUi.supporters.community') }, + { value: 'grants', label: t('walletUi.supporters.grantsSection') }, + ].map((item) => ( + - -
- {t(key)} -

{t(desc)}

-
-
+ {item.label} + ))} -
-
+ - {/* ── 3. Impact so far (what the community built) ── */} - {impact.length > 0 && ( -
- -
- {impact.map(({ key, value }) => ( -
-
{value.toLocaleString()}
-
{t(key)}
-
- ))} + {/* ── Support rails ── */} + +
+

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

+

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

+
+ setRail(id as typeof rail)} /> +
+ {rail === 'support-lightning' && } + {rail === 'support-silent' && } + {rail === 'support-angor' && }
-

- - {t('walletUi.supporters.impactSeeStats')} - - {' · '} - {t('walletUi.supporters.impactAsOf')} -

-
- )} - {/* ── 4. Supporters (Angor + Lightning zaps) ── */} -
- - - {supporters.length > 0 ? ( - <> -
- {(showAll ? supporters : supporters.slice(0, VISIBLE_LIMIT)).map((entry, i) => ( - +
+ +
+ {PAYS_FOR.map(({ icon: Icon, key, desc }) => ( +
+ +
+ {t(key)} +

{t(desc)}

+
+
))}
- {supporters.length > VISIBLE_LIMIT && ( -
- -
- )} +
+ -

- {t('walletUi.supporters.supportersTotals', { - count: supporters.length, - angor: BACKERS_TOTAL_SATS.toLocaleString(), - zap: zapTotal.toLocaleString(), - })} - {typeof totalUsd === 'number' && ( - · ≈ ${totalUsd.toFixed(2)} - )} -

-

- {t('walletUi.supporters.quietNote')} -

- {latestAt > 0 && ( -

- {t('walletUi.supporters.updatedAt', { - when: formatDistanceToNow(new Date(latestAt * 1000), { addSuffix: true }), - })} + {/* ── Backers, impact and tiers ── */} + + {impact.length > 0 && ( +

+ +
+ {impact.map(({ key, value }) => ( +
+
{value.toLocaleString()}
+
{t(key)}
+
+ ))} +
+

+ + {t('walletUi.supporters.impactSeeStats')} + + {' · '} + {t('walletUi.supporters.impactAsOf')}

+
+ )} + +
+ + {supporters.length > 0 ? ( + <> +
+ {(showAll ? supporters : supporters.slice(0, VISIBLE_LIMIT)).map((entry, i) => ( + + ))} +
+ {supporters.length > VISIBLE_LIMIT && ( +
+ +
+ )} +

+ {t('walletUi.supporters.supportersTotals', { + count: supporters.length, + angor: BACKERS_TOTAL_SATS.toLocaleString(), + zap: zapTotal.toLocaleString(), + })} + {typeof totalUsd === 'number' && · ≈ ${totalUsd.toFixed(2)}} +

+

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

+ {latestAt > 0 && ( +

+ {t('walletUi.supporters.updatedAt', { when: formatDistanceToNow(new Date(latestAt * 1000), { addSuffix: true }) })} +

+ )} + + + ) : ( +
+ +

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

+ + {t('walletUi.supporters.backAngor')} + +
)} - - - ) : ( -
- -

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

- - - {t('walletUi.supporters.backAngor')} - -
- )} -
+
+ - {/* ── 6. Collaborators ── */} -
- - - {COLLABORATORS.length > 0 ? ( -
- {COLLABORATORS.map((c) => ( - - ))} -
- ) : ( -
- -

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

-

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

- - - {t('walletUi.supporters.viewNostrHub')} - -
- )} -
- - {/* ── 7. Partners ── */} -
- - - {PARTNERS.length > 0 ? ( -
- {PARTNERS.map((p) => ( - - ))} -
- ) : ( - <> -
- {Array.from({ length: 4 }).map((_, i) => ( - + {/* ── Community, mission, collaborators, partners, friends ── */} + +
+
+ +

+ {t('walletUi.supporters.whyTitle1')}{' '} + {t('walletUi.supporters.whyTitle2')} +

+
+

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

+
+ {[ + { emoji: '🛹', title: t('walletUi.supporters.why1Title'), desc: t('walletUi.supporters.why1Desc') }, + { emoji: '⚡', title: t('walletUi.supporters.why2Title'), desc: t('walletUi.supporters.why2Desc') }, + { emoji: '🌍', title: t('walletUi.supporters.why3Title'), desc: t('walletUi.supporters.why3Desc') }, + ].map(({ emoji, title, desc }) => ( +
+
{emoji}
+

{title}

+

{desc}

+
))}
-
-

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

-
- - )} -
+
- {/* ── 8. Friends ── */} -
- - -
- {FRIENDS.map((f) => ( - - ))} - {/* Fill remaining slots up to 5 with placeholders */} - {FRIENDS.length < 5 && Array.from({ length: 5 - FRIENDS.length }).map((_, i) => ( - - ))} -
-
-

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

-

- {t('walletUi.supporters.wantListedDesc')} -

-
-
- - {/* ── Mission ── */} -
-
-
-
- -
-

- {t('walletUi.supporters.whyTitle1')} {t('walletUi.supporters.whyTitle2')} -

-

- {t('walletUi.supporters.whyDesc')} -

-
- {[ - { emoji: '🛹', title: t('walletUi.supporters.why1Title'), desc: t('walletUi.supporters.why1Desc') }, - { emoji: '⚡', title: t('walletUi.supporters.why2Title'), desc: t('walletUi.supporters.why2Desc') }, - { emoji: '🌍', title: t('walletUi.supporters.why3Title'), desc: t('walletUi.supporters.why3Desc') }, - ].map(({ emoji, title, desc }) => ( -
-
{emoji}
-

{title}

-

{desc}

+
+ + {COLLABORATORS.length > 0 ? ( +
+ {COLLABORATORS.map((c) => )}
- ))} -
-
-
+ ) : ( +
+ +

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

+

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

+ + {t('walletUi.supporters.viewNostrHub')} + +
+ )} + + +
+ + {PARTNERS.length > 0 ? ( +
+ {PARTNERS.map((p) => )} +
+ ) : ( +
+ {t('walletUi.supporters.wantPartner')} +
+ )} +
+ +
+ +
+ {FRIENDS.map((f) => )} +
+
+

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

+

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

+
+
+ + + {/* ── Grantmakers ── */} + + + + + {/* ── Footer ── */} -
+

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

-

- {t('walletUi.supporters.license')} +

+ {t('walletUi.supporters.license')} {' '}·{' '} - {t('nav.about')} + {t('nav.about')} {' '}·{' '} - {t('walletUi.supporters.faq')} + {t('walletUi.supporters.faq')}