SkateSpots/src/components/CrewTreasuryCard.tsx
SkateSpots 0b796440f1 feat(i18n): Spanish batch 2 — nav/chrome, events/shops/tours, crews/DMs, settings/auth, wallet/stash/newsletter/stats
~3,470 new keys (5,770 total, EN=ES), ~115 more files wired.
- nav + chromeUi: header, More menu, burger menu, footer, map toolbar widgets
  (sort/trust/weather), spot-type label helper.
- placesUi: events, shops, tours, food. socialUi: crews, DMs, notifications,
  collectives, crew chat/coordinator. settingsUi: settings, auth/signup,
  onboarding, profile edit, legal. walletUi: wallet/Cashu, stash, mints,
  app shop, newsletter, community stats, leaderboards, profile.
- Fixed 738 usages where an agent dropped the socialUi. prefix (caught by check:i18n).
2026-10-02 10:11:06 +07:00

120 lines
4.8 KiB
TypeScript

import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import { ZapButton } from '@/components/ZapButton';
import { useCrewGoal } from '@/hooks/useCrewGoal';
import { useTranslation } from '@/contexts/language';
import { cn } from '@/lib/utils';
import { Target, Zap, Users, CheckCircle2 } from 'lucide-react';
/**
* Crew treasury — a transparent, zap-funded NIP-75 goal. Contributions are
* ordinary zaps to the goal event that settle to the founder's own Lightning
* wallet; SkateSpots never custodies funds. Progress is tallied from public
* zap receipts, so anyone can audit it.
*/
export function CrewTreasuryCard({ goalId }: { goalId?: string }) {
const { t } = useTranslation();
const { data: goal, isLoading } = useCrewGoal(goalId);
if (!goalId) return null;
if (isLoading) {
return (
<Card className="bg-black/90 border-2 border-orange-500/30">
<CardHeader>
<Skeleton className="h-5 w-40" />
</CardHeader>
<CardContent className="space-y-3">
<Skeleton className="h-3 w-full" />
<Skeleton className="h-3 w-2/3" />
</CardContent>
</Card>
);
}
if (!goal) return null;
const pct = Math.min(100, goal.percent);
const reached = goal.percent >= 100;
const now = Math.floor(Date.now() / 1000);
const closed = goal.closedAt ? goal.closedAt < now : false;
const daysLeft = goal.closedAt ? Math.ceil((goal.closedAt - now) / 86400) : null;
return (
<Card
className={cn(
'overflow-hidden border-2 bg-black/90',
reached ? 'border-green-500/50' : 'border-orange-500/40',
)}
>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg font-black uppercase text-white">
<Target className={cn('h-5 w-5', reached ? 'text-green-400' : 'text-orange-400')} />
{t('socialUi.treasury.title')}
</CardTitle>
{goal.summary && <p className="text-xs text-gray-400">{goal.summary}</p>}
</CardHeader>
<CardContent className="space-y-4">
{goal.description && (
<p className="whitespace-pre-wrap text-sm text-gray-300">{goal.description}</p>
)}
{/* Progress bar */}
<div className="space-y-1.5">
<div className="flex items-end justify-between text-sm">
<span className={cn('font-black', reached ? 'text-green-400' : 'text-orange-400')}>
{goal.raisedSats.toLocaleString()} {t('common.sats')}
</span>
<span className="text-xs text-gray-500">
{t('socialUi.treasury.ofGoal', { target: goal.targetSats.toLocaleString() })}
</span>
</div>
<div className="h-3 w-full overflow-hidden rounded-full bg-white/5">
<div
className={cn(
'h-full rounded-full transition-all duration-700',
reached
? 'bg-gradient-to-r from-green-500 to-emerald-400'
: 'bg-gradient-to-r from-orange-500 to-amber-400',
)}
style={{ width: `${pct}%` }}
/>
</div>
<div className="flex items-center justify-between text-[11px] text-gray-500">
<span className="flex items-center gap-1">
{reached ? (
<span className="flex items-center gap-1 font-bold text-green-400">
<CheckCircle2 className="h-3 w-3" /> {t('socialUi.treasury.goalReached')}
</span>
) : (
<span className="font-bold text-orange-300">{t('socialUi.treasury.funded', { percent: goal.percent })}</span>
)}
</span>
<span className="flex items-center gap-1">
<Users className="h-3 w-3" />
{goal.contributions === 1
? t('socialUi.treasury.contributionOne', { count: goal.contributions })
: t('socialUi.treasury.contributionMany', { count: goal.contributions })}
{daysLeft !== null && daysLeft > 0 && ` · ${t('socialUi.treasury.daysLeft', { days: daysLeft })}`}
{closed && ` · ${t('socialUi.treasury.closed')}`}
</span>
</div>
</div>
{/* Contribute — zaps the goal event; settles to the founder's wallet. */}
{!closed && (
<div className="space-y-2">
<ZapButton
target={goal.event}
className="w-full justify-center bg-orange-500 text-black hover:bg-orange-600"
/>
<p className="flex items-center justify-center gap-1 text-center text-[10px] text-gray-600">
<Zap className="h-2.5 w-2.5" />
{t('socialUi.treasury.nonCustodial')}
</p>
</div>
)}
</CardContent>
</Card>
);
}