From 1bdaa57aa3854d32d4fc3076b6166ff96dc9a49e Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Mon, 5 Oct 2026 17:56:10 +0700 Subject: [PATCH] feat(supporters): on-chain verify chips, real avatars/names, fiat total, footer count --- src/components/Layout.tsx | 4 +++ src/i18n/en.ts | 2 ++ src/i18n/es.ts | 2 ++ src/lib/backers.ts | 4 +++ src/pages/SupportersPage.tsx | 57 ++++++++++++++++++++++++++---------- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index 3e32f1d..f93a2fa 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -3,6 +3,7 @@ import { Link, Outlet, useLocation } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { cn } from '@/lib/utils'; import { isNativePlatform } from '@/lib/platform'; +import { BACKERS } from '@/lib/backers'; import { useNavPreferences, useDesktopNavPreferences, type NavTab } from '@/hooks/useNavPreferences'; import { Button } from '@/components/ui/button'; import { Sheet, SheetContent, SheetTrigger, SheetTitle } from '@/components/ui/sheet'; @@ -1101,6 +1102,9 @@ export function Layout({ children, noPadding }: LayoutProps) { > {t('chromeUi.collaborators')} + + {t('walletUi.supporters.supportedBy', { count: BACKERS.length })} + {t('chromeUi.notSponsoredBy')} diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 649307e..eb49678 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -6500,6 +6500,8 @@ walletUi: { anonymousBacker: 'Anonymous supporter', beFirstBacker: 'No public supporters yet — be the first.', backersTotal: '{{count}} supporters · {{amount}} sats raised', + verifiedOnChain: 'On-chain verified', + supportedBy: 'Supported by {{count}}', support: 'Support', supportDesc: 'Fund SkateSpots on Angor — decentralized, on-chain Bitcoin crowdfunding. No middleman, no KYC.', collaborators: 'Collaborators', diff --git a/src/i18n/es.ts b/src/i18n/es.ts index 87fb394..b21dc69 100644 --- a/src/i18n/es.ts +++ b/src/i18n/es.ts @@ -6500,6 +6500,8 @@ walletUi: { anonymousBacker: 'Patrocinador anónimo', beFirstBacker: 'Aún no hay patrocinadores públicos — sé el primero.', backersTotal: '{{count}} patrocinadores · {{amount}} sats recaudados', + verifiedOnChain: 'Verificado on-chain', + supportedBy: 'Apoyado por {{count}}', support: 'Apoyo', supportDesc: 'Financia SkateSpots en Angor — crowdfunding descentralizado on-chain en Bitcoin. Sin intermediarios, sin KYC.', collaborators: 'Colaboradores', diff --git a/src/lib/backers.ts b/src/lib/backers.ts index 0e64a56..b579d58 100644 --- a/src/lib/backers.ts +++ b/src/lib/backers.ts @@ -25,6 +25,8 @@ export interface Backer { logo?: string; /** External link (used when there is no npub). */ url?: string; + /** Investment transaction id — renders a verifiable "on-chain" chip. */ + txid?: string; /** Amount contributed, in satoshis. */ amount?: number; /** Small line under the name, e.g. "Founding backer". */ @@ -34,11 +36,13 @@ export interface Backer { export const BACKERS: Backer[] = [ { npub: 'npub15q3ekfxdvzuc9nhhyh2yv7lwtnfagurllc9rkkrypt40d303jsjsdfujeu', + txid: '4f95d18ff44a86e8302d923f53fe3777c52236ec087b0d374b8e9566995e1bcc', amount: 99000, note: 'Founding supporter', }, { npub: 'npub1t5yjluyvxatlmmcxccv7tdppmp4ae0j850k63323t7j0fe4lsqlsp7mfyl', + txid: '5dbf872fd8f02ecf2b534857b0613e880a021478c6e8e6483c774e95d2ddc565', amount: 99000, note: 'Founding supporter', }, diff --git a/src/pages/SupportersPage.tsx b/src/pages/SupportersPage.tsx index c38ec1a..e257386 100644 --- a/src/pages/SupportersPage.tsx +++ b/src/pages/SupportersPage.tsx @@ -1,8 +1,10 @@ import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; -import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react'; +import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star, ShieldCheck } from 'lucide-react'; import { SupportSkateSpots } from '@/components/SupportSkateSpots'; import { BACKERS, BACKERS_TOTAL_SATS, type Backer } from '@/lib/backers'; +import { useAuthor } from '@/hooks/useAuthor'; +import { useSatsToUsd } from '@/hooks/useExchangeRate'; import { useTranslation } from '@/contexts/language'; const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z'; @@ -207,37 +209,58 @@ function FriendCard({ friend }: { friend: Friend }) { function BackerCard({ backer }: { backer: Backer }) { const { t } = useTranslation(); + const { data } = useAuthor(backer.npub); + const meta = data?.metadata; + const name = backer.name || meta?.display_name || meta?.name; + const avatar = backer.logo || meta?.picture; + const initial = (name || 'S').trim().charAt(0).toUpperCase(); const href = backer.npub ? `/${backer.npub}` : backer.url; - const inner = ( + return (
- {backer.logo ? ( + {avatar ? ( {backer.name { (e.target as HTMLImageElement).style.display = 'none'; }} /> ) : ( - 🛹 + {initial} )}
-
-

- {backer.name || t('walletUi.supporters.anonymousBacker')} -

+
+ {href && backer.npub ? ( + + {name || t('walletUi.supporters.anonymousBacker')} + + ) : href ? ( + + {name || t('walletUi.supporters.anonymousBacker')} + + ) : ( +

{name || t('walletUi.supporters.anonymousBacker')}

+ )} {typeof backer.amount === 'number' && (

{backer.amount.toLocaleString()} sats

)} {backer.note &&

{backer.note}

} + {backer.txid && ( + + + {t('walletUi.supporters.verifiedOnChain')} + + )}
); - - if (!href) return inner; - if (backer.npub) return {inner}; - return {inner}; } // ─── Placeholder card (empty slot) ─────────────────────────────────────────── @@ -257,6 +280,7 @@ function PlaceholderCard({ label }: { label: string }) { export default function SupportersPage() { const { t } = useTranslation(); + const totalUsd = useSatsToUsd(BACKERS_TOTAL_SATS); useSeoMeta({ title: 'Supporters - SkateSpots', description: 'The community keeping SkateSpots alive — supporters, collaborators, partners and friends.', @@ -347,6 +371,9 @@ export default function SupportersPage() { count: BACKERS.length, amount: BACKERS_TOTAL_SATS.toLocaleString(), })} + {typeof totalUsd === 'number' && ( + · ≈ ${totalUsd.toFixed(2)} + )}

) : (