feat(supporters): top 21, extravagant auto tiers + tier ladder with supporter avatars
This commit is contained in:
parent
66d83123cd
commit
cc1883cf82
3 changed files with 85 additions and 16 deletions
|
|
@ -6501,6 +6501,7 @@ walletUi: {
|
|||
supportersTotals: '{{count}} supporters · {{angor}} sats via Angor · {{zap}} sats zapped',
|
||||
quietNote: 'Plus everyone who gave quietly — thank you. ❤️',
|
||||
tiersNote: 'Tiers: Supporter under 100k sats · Patron 100k–1M · Legend 1M+',
|
||||
tierEmpty: 'No one yet — be the first.',
|
||||
joinSupporters: 'Join {{count}} supporters',
|
||||
showAll: 'Show all {{count}}',
|
||||
showLess: 'Show less',
|
||||
|
|
|
|||
|
|
@ -6501,6 +6501,7 @@ walletUi: {
|
|||
supportersTotals: '{{count}} patrocinadores · {{angor}} sats vía Angor · {{zap}} sats zapeados',
|
||||
quietNote: 'Y a todos los que aportaron en silencio — gracias. ❤️',
|
||||
tiersNote: 'Niveles: Colaborador menos de 100k sats · Patrón 100k–1M · Leyenda 1M+',
|
||||
tierEmpty: 'Nadie todavía — sé el primero.',
|
||||
joinSupporters: 'Únete a {{count}} patrocinadores',
|
||||
showAll: 'Ver los {{count}}',
|
||||
showLess: 'Ver menos',
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa0
|
|||
// 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 = 10;
|
||||
const VISIBLE_LIMIT = 21;
|
||||
|
||||
// ─── Static data — edit these arrays to add/remove entries ──────────────────
|
||||
|
||||
|
|
@ -248,11 +248,77 @@ interface SupporterEntry {
|
|||
}
|
||||
|
||||
const TIER_META = {
|
||||
supporter: { color: '#22d3ee', key: 'walletUi.supporters.tierSupporter' },
|
||||
patron: { color: '#f7931a', key: 'walletUi.supporters.tierPatron' },
|
||||
legend: { color: '#facc15', key: 'walletUi.supporters.tierLegend' },
|
||||
supporter: { color: '#22d3ee', key: 'walletUi.supporters.tierSupporter', emoji: '🛹' },
|
||||
patron: { color: '#f7931a', key: 'walletUi.supporters.tierPatron', emoji: '🔥' },
|
||||
legend: { color: '#facc15', key: 'walletUi.supporters.tierLegend', emoji: '👑💎✨' },
|
||||
} as const;
|
||||
|
||||
const TIER_ORDER = ['legend', 'patron', 'supporter'] as const;
|
||||
const TIER_RANGE: Record<(typeof TIER_ORDER)[number], string> = {
|
||||
legend: '1M+',
|
||||
patron: '100k–1M',
|
||||
supporter: '< 100k',
|
||||
};
|
||||
|
||||
function MemberAvatar({ entry }: { entry: SupporterEntry }) {
|
||||
const { data } = useAuthor(entry.anonymous ? undefined : entry.npub);
|
||||
const meta = data?.metadata;
|
||||
const name = entry.anonymous
|
||||
? '★'
|
||||
: entry.name || meta?.display_name || meta?.name || entry.npub || '';
|
||||
const avatar = entry.anonymous ? undefined : (entry.logo || meta?.picture);
|
||||
return (
|
||||
<Avatar className="h-6 w-6 ring-1 ring-white/15" title={name}>
|
||||
{avatar && <AvatarImage src={avatar} alt={name} />}
|
||||
<AvatarFallback className="text-[10px] font-black text-white/70">
|
||||
{entry.anonymous ? '🛹' : name.replace(/[^a-zA-Z0-9]/g, '').charAt(0).toUpperCase() || 'S'}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
);
|
||||
}
|
||||
|
||||
function TierLadder({ supporters }: { supporters: SupporterEntry[] }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="mt-5 grid grid-cols-1 gap-3 sm:grid-cols-3">
|
||||
{TIER_ORDER.map((tier) => {
|
||||
const meta = TIER_META[tier];
|
||||
const members = supporters.filter((s) => tierOf(s.sats) === tier);
|
||||
return (
|
||||
<div
|
||||
key={tier}
|
||||
className="rounded-xl border p-3"
|
||||
style={{
|
||||
borderColor: `${meta.color}55`,
|
||||
background: `linear-gradient(135deg, ${meta.color}14, transparent)`,
|
||||
boxShadow: tier === 'legend' ? `0 0 18px ${meta.color}33` : undefined,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-sm font-black uppercase tracking-wide" style={{ color: meta.color }}>
|
||||
{meta.emoji} {t(meta.key)}
|
||||
</span>
|
||||
<span className="text-[10px] text-gray-500">{TIER_RANGE[tier]}</span>
|
||||
</div>
|
||||
{members.length > 0 ? (
|
||||
<div className="mt-2 flex flex-wrap items-center gap-1">
|
||||
{members.slice(0, 10).map((m) => (
|
||||
<MemberAvatar key={m.key} entry={m} />
|
||||
))}
|
||||
{members.length > 10 && (
|
||||
<span className="text-[10px] text-gray-500">+{members.length - 10}</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<p className="mt-2 text-[10px] text-gray-600">{t('walletUi.supporters.tierEmpty')}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SupporterRow({ rank, entry }: { rank: number; entry: SupporterEntry }) {
|
||||
const { t } = useTranslation();
|
||||
const { data } = useAuthor(entry.anonymous ? undefined : entry.npub);
|
||||
|
|
@ -262,7 +328,7 @@ function SupporterRow({ rank, entry }: { rank: number; entry: SupporterEntry })
|
|||
: entry.name || meta?.display_name || meta?.name
|
||||
|| (entry.npub ? `${entry.npub.slice(0, 12)}…${entry.npub.slice(-6)}` : t('walletUi.supporters.anonymousBacker'));
|
||||
const avatar = entry.anonymous ? undefined : (entry.logo || meta?.picture);
|
||||
const tier = TIER_META[tierOf(entry.angorSats || undefined)];
|
||||
const tier = TIER_META[tierOf(entry.sats)];
|
||||
const profileHref = !entry.anonymous && entry.npub ? `/${entry.npub}` : undefined;
|
||||
const linkHref = profileHref || entry.url;
|
||||
const proofUrl = entry.txid
|
||||
|
|
@ -301,14 +367,17 @@ function SupporterRow({ rank, entry }: { rank: number; entry: SupporterEntry })
|
|||
{entry.zap && <Zap className="h-3 w-3 shrink-0 text-[#f7931a]" />}
|
||||
</div>
|
||||
<div className="mt-0.5 flex items-center gap-2">
|
||||
{entry.angor && (
|
||||
<span
|
||||
className="rounded-full px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wide"
|
||||
style={{ background: `${tier.color}22`, color: tier.color }}
|
||||
>
|
||||
{t(tier.key)}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="rounded-full px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-wide"
|
||||
style={{
|
||||
background: `${tier.color}22`,
|
||||
color: tier.color,
|
||||
border: `1px solid ${tier.color}55`,
|
||||
boxShadow: entry.sats >= 1_000_000 ? `0 0 10px ${tier.color}66` : undefined,
|
||||
}}
|
||||
>
|
||||
{tier.emoji} {t(tier.key)}
|
||||
</span>
|
||||
{entry.note && !entry.anonymous && <span className="text-[11px] text-gray-500">{entry.note}</span>}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -589,9 +658,7 @@ export default function SupportersPage() {
|
|||
<p className="mt-2 text-center text-xs text-gray-600">
|
||||
{t('walletUi.supporters.quietNote')}
|
||||
</p>
|
||||
<p className="mt-3 text-center text-[11px] text-gray-600">
|
||||
{t('walletUi.supporters.tiersNote')}
|
||||
</p>
|
||||
<TierLadder supporters={supporters} />
|
||||
</>
|
||||
) : (
|
||||
<div className="rounded-xl border border-dashed border-[#22d3ee]/20 p-8 text-center">
|
||||
|
|
|
|||
Loading…
Reference in a new issue