feat(supporters): show Angor supporters with amounts on /supporters
This commit is contained in:
parent
3d7fc9af4f
commit
3f94d9be30
5 changed files with 142 additions and 1 deletions
|
|
@ -1777,6 +1777,11 @@ Nostr events signed by a **project identity** (derived from the founder's wallet
|
|||
`angor:members`, `angor:media`). Live: displayName "Skate Spots", NIP-05
|
||||
`owner@skate-spots.org`, lud16 `skatespots@minibits.cash` (single-sourced in
|
||||
`src/lib/platform.ts`), picture on `blossom.angor.io`.
|
||||
- **In-app display:** `/supporters` has a **Supporters** section (curated) fed by
|
||||
**`src/lib/backers.ts`** — `{ npub, amount (sats), note }` or `{ name, logo, url }`.
|
||||
Amounts come from the investment txs' Taproot stage outputs. Discovery helper:
|
||||
`~/.config/skatespots/angor/angor-backers.mjs` (decrypts the founder's investment DMs →
|
||||
`work/backers.json`). ⚠️ Don't list a backer without consent.
|
||||
|
||||
### Tooling — machine-local, deliberately NOT in the repo
|
||||
`~/.config/skatespots/angor/` → `angor-metadata.mjs` + `blossom-upload.mjs` (own
|
||||
|
|
|
|||
|
|
@ -6495,6 +6495,11 @@ walletUi: {
|
|||
community: 'COMMUNITY',
|
||||
heroDesc: 'SkateSpots is built by and for the community. Supporters, collaborators, partners and friends keeping this free and decentralized.',
|
||||
backAngor: 'Back us on Angor',
|
||||
backers: 'Supporters',
|
||||
backersDesc: 'People who backed SkateSpots on Angor — decentralized, on-chain Bitcoin. Thank you.',
|
||||
anonymousBacker: 'Anonymous supporter',
|
||||
beFirstBacker: 'No public supporters yet — be the first.',
|
||||
backersTotal: '{{count}} supporters · {{amount}} sats raised',
|
||||
support: 'Support',
|
||||
supportDesc: 'Fund SkateSpots on Angor — decentralized, on-chain Bitcoin crowdfunding. No middleman, no KYC.',
|
||||
collaborators: 'Collaborators',
|
||||
|
|
|
|||
|
|
@ -6495,6 +6495,11 @@ walletUi: {
|
|||
community: 'COMUNIDAD',
|
||||
heroDesc: 'SkateSpots lo construye y lo mantiene la comunidad. Patrocinadores, colaboradores, socios y amigos que lo mantienen libre y descentralizado.',
|
||||
backAngor: 'Apóyanos en Angor',
|
||||
backers: 'Patrocinadores',
|
||||
backersDesc: 'Personas que han apoyado SkateSpots en Angor — Bitcoin descentralizado on-chain. Gracias.',
|
||||
anonymousBacker: 'Patrocinador anónimo',
|
||||
beFirstBacker: 'Aún no hay patrocinadores públicos — sé el primero.',
|
||||
backersTotal: '{{count}} patrocinadores · {{amount}} sats recaudados',
|
||||
support: 'Apoyo',
|
||||
supportDesc: 'Financia SkateSpots en Angor — crowdfunding descentralizado on-chain en Bitcoin. Sin intermediarios, sin KYC.',
|
||||
collaborators: 'Colaboradores',
|
||||
|
|
|
|||
48
src/lib/backers.ts
Normal file
48
src/lib/backers.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Curated list of Angor supporters shown on /supporters.
|
||||
*
|
||||
* Deliberately opt-in: only list people/organisations who are happy to be
|
||||
* shown. Discover the raw list (privately) with:
|
||||
* node ~/.config/skatespots/angor/angor-backers.mjs
|
||||
* → writes ~/.config/skatespots/angor/work/backers.json (npub + txid).
|
||||
* The amount = the sum of the investment transaction's Taproot stage outputs
|
||||
* (per-backer `amount` = 99,000 sats here; total matches the app's "0.00198 BTC").
|
||||
*
|
||||
* Entries:
|
||||
* - people: { npub, amount } → links to their profile; add `name` to override
|
||||
* - shops / orgs: { name, logo, url, amount } (logo: PNG/WebP, transparent, ≥200px)
|
||||
* - hide identity: { anonymous: true, note }
|
||||
*
|
||||
* ⚠️ Don't add someone without their consent — a backer's npub + "backed this
|
||||
* project" is new, linkable information even though the key is public.
|
||||
*/
|
||||
export interface Backer {
|
||||
/** Display name; falls back to "Anonymous supporter". */
|
||||
name?: string;
|
||||
/** Nostr profile — renders a skate placeholder + links to /<npub>. */
|
||||
npub?: string;
|
||||
/** Logo for a shop/brand/org (shown instead of the placeholder). */
|
||||
logo?: string;
|
||||
/** External link (used when there is no npub). */
|
||||
url?: string;
|
||||
/** Amount contributed, in satoshis. */
|
||||
amount?: number;
|
||||
/** Small line under the name, e.g. "Founding backer". */
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export const BACKERS: Backer[] = [
|
||||
{
|
||||
npub: 'npub15q3ekfxdvzuc9nhhyh2yv7lwtnfagurllc9rkkrypt40d303jsjsdfujeu',
|
||||
amount: 99000,
|
||||
note: 'Founding supporter',
|
||||
},
|
||||
{
|
||||
npub: 'npub1t5yjluyvxatlmmcxccv7tdppmp4ae0j850k63323t7j0fe4lsqlsp7mfyl',
|
||||
amount: 99000,
|
||||
note: 'Founding supporter',
|
||||
},
|
||||
];
|
||||
|
||||
/** Total raised across the listed supporters, in satoshis. */
|
||||
export const BACKERS_TOTAL_SATS = BACKERS.reduce((sum, b) => sum + (b.amount ?? 0), 0);
|
||||
|
|
@ -2,6 +2,7 @@ import { useSeoMeta } from '@unhead/react';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { ExternalLink, Zap, Heart, Users, Handshake, Building2, Star } from 'lucide-react';
|
||||
import { SupportSkateSpots } from '@/components/SupportSkateSpots';
|
||||
import { BACKERS, BACKERS_TOTAL_SATS, type Backer } from '@/lib/backers';
|
||||
import { useTranslation } from '@/contexts/language';
|
||||
|
||||
const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
|
||||
|
|
@ -202,6 +203,43 @@ function FriendCard({ friend }: { friend: Friend }) {
|
|||
);
|
||||
}
|
||||
|
||||
// ─── Backer card (Angor backers) ─────────────────────────────────────────────
|
||||
|
||||
function BackerCard({ backer }: { backer: Backer }) {
|
||||
const { t } = useTranslation();
|
||||
const href = backer.npub ? `/${backer.npub}` : backer.url;
|
||||
|
||||
const inner = (
|
||||
<div className="flex flex-col items-center gap-3 p-5 rounded-xl bg-black/60 border border-white/10 hover:border-[#22d3ee]/40 hover:bg-[#22d3ee]/5 transition-all group text-center">
|
||||
<div className="w-16 h-16 rounded-2xl bg-white/5 border border-white/10 flex items-center justify-center overflow-hidden group-hover:border-[#22d3ee]/40 transition-all">
|
||||
{backer.logo ? (
|
||||
<img
|
||||
src={backer.logo}
|
||||
alt={backer.name || 'Backer'}
|
||||
className="w-12 h-12 object-contain"
|
||||
onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }}
|
||||
/>
|
||||
) : (
|
||||
<span className="text-2xl">🛹</span>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-bold text-sm text-white group-hover:text-[#22d3ee] transition-colors">
|
||||
{backer.name || t('walletUi.supporters.anonymousBacker')}
|
||||
</p>
|
||||
{typeof backer.amount === 'number' && (
|
||||
<p className="text-xs font-mono mt-0.5 text-[#22d3ee]/80">{backer.amount.toLocaleString()} sats</p>
|
||||
)}
|
||||
{backer.note && <p className="text-xs text-gray-500 mt-0.5">{backer.note}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (!href) return inner;
|
||||
if (backer.npub) return <Link to={`/${backer.npub}`}>{inner}</Link>;
|
||||
return <a href={href} target="_blank" rel="noopener noreferrer">{inner}</a>;
|
||||
}
|
||||
|
||||
// ─── Placeholder card (empty slot) ───────────────────────────────────────────
|
||||
|
||||
function PlaceholderCard({ label }: { label: string }) {
|
||||
|
|
@ -288,7 +326,47 @@ export default function SupportersPage() {
|
|||
<SupportSkateSpots className="mt-4" />
|
||||
</section>
|
||||
|
||||
{/* ── 2. Collaborators ── */}
|
||||
{/* ── 2. Backers (Angor) ── */}
|
||||
<section>
|
||||
<SectionHeader
|
||||
icon={Zap}
|
||||
title={t('walletUi.supporters.backers')}
|
||||
subtitle={t('walletUi.supporters.backersDesc')}
|
||||
color="#22d3ee"
|
||||
/>
|
||||
|
||||
{BACKERS.length > 0 ? (
|
||||
<>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
|
||||
{BACKERS.map((b) => (
|
||||
<BackerCard key={b.npub || b.name || b.url} backer={b} />
|
||||
))}
|
||||
</div>
|
||||
<p className="mt-4 text-center text-sm text-gray-400">
|
||||
{t('walletUi.supporters.backersTotal', {
|
||||
count: BACKERS.length,
|
||||
amount: BACKERS_TOTAL_SATS.toLocaleString(),
|
||||
})}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<div className="rounded-xl border border-dashed border-[#22d3ee]/20 p-8 text-center">
|
||||
<Zap className="h-10 w-10 mx-auto mb-3 text-[#22d3ee]/30" />
|
||||
<p className="text-gray-500 text-sm mb-1">{t('walletUi.supporters.beFirstBacker')}</p>
|
||||
<a
|
||||
href={ANGOR_PROJECT_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1.5 text-xs font-bold text-[#22d3ee]/80 hover:text-[#22d3ee] transition-colors"
|
||||
>
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
{t('walletUi.supporters.backAngor')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* ── 3. Collaborators ── */}
|
||||
<section>
|
||||
<SectionHeader
|
||||
icon={Users}
|
||||
|
|
|
|||
Loading…
Reference in a new issue