import { useMemo, useState } from 'react'; import { useSeoMeta } from '@unhead/react'; import { Link } from 'react-router-dom'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Skeleton } from '@/components/ui/skeleton'; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/sheet'; import { useMintDirectory, type MintDirectoryEntry } from '@/hooks/useMintDirectory'; import { useCashu } from '@/hooks/wallet/useCashu'; import { useToast } from '@/hooks/useToast'; import { isNativePlatform } from '@/lib/platform'; import { useTranslation } from '@/contexts/language'; import { cn } from '@/lib/utils'; import { Search, Coins, ExternalLink, Plus, Loader2, Check, ShieldAlert, Activity, ChevronRight, Copy, Info, Star } from 'lucide-react'; function hostOf(url: string): string { try { return new URL(url).host; } catch { return url; } } function healthBadge(mint: MintDirectoryEntry) { if (mint.archived) { return { label: 'walletUi.mints.archived', className: 'border-gray-600 bg-gray-800/60 text-gray-400' }; } if (mint.degraded) { return { label: 'walletUi.mints.degraded', className: 'border-amber-500/40 bg-amber-500/10 text-amber-400' }; } if (mint.online === false) { return { label: 'walletUi.mints.offline', className: 'border-red-500/40 bg-red-500/10 text-red-400' }; } return { label: 'walletUi.mints.online', className: 'border-[#ccff00]/40 bg-[#ccff00]/10 text-[#ccff00]' }; } function MintRow({ mint, added, canAdd, adding, onAdd, onSelect, }: { mint: MintDirectoryEntry; added: boolean; canAdd: boolean; adding: boolean; onAdd: () => void; onSelect: () => void; }) { const { t } = useTranslation(); const health = healthBadge(mint); return (
{mint.iconUrl ? ( { (e.currentTarget as HTMLImageElement).style.display = 'none'; }} /> ) : (
)}

{/* A real button (not a card-wide role=button) so there are no nested interactive elements — the Add button and the terms link live alongside it. */}

{t(health.label)} {typeof mint.latencyMs === 'number' && mint.online && ( {mint.latencyMs} ms )}

{hostOf(mint.url)}

{mint.descriptionLong && (

{mint.descriptionLong}

)}
{typeof mint.nutCount === 'number' && {mint.nutCount} NUTs} {mint.version && {mint.version}} {mint.tosUrl && ( {t('walletUi.mints.terms')} )}
{/* Community signal — ratings + reliability from MintRadar. */} {((typeof mint.reviewAvgRating === 'number' && (mint.reviewCount ?? 0) > 0) || typeof mint.reliabilityScore === 'number' || typeof mint.uptimePct24h === 'number') && (
{typeof mint.reviewAvgRating === 'number' && (mint.reviewCount ?? 0) > 0 && ( {mint.reviewAvgRating.toFixed(1)} ({mint.reviewCount}) )} {typeof mint.reliabilityScore === 'number' && ( {t('walletUi.mints.reliability')} {mint.reliabilityScore}/100 )} {typeof mint.uptimePct24h === 'number' && ( {Math.round(mint.uptimePct24h)}% up · 24h )}
)}
{added ? ( {t('walletUi.mints.addedBadge')} ) : canAdd ? ( ) : null}
); } export default function MintsPage() { const { t } = useTranslation(); const { data, isLoading, error } = useMintDirectory(); const { coco, mints, isInitialized, refreshMints } = useCashu(); const { toast } = useToast(); const [query, setQuery] = useState(''); const [onlineOnly, setOnlineOnly] = useState(false); const [addingUrl, setAddingUrl] = useState(null); const [selected, setSelected] = useState(null); const copyUrl = async (url: string) => { try { await navigator.clipboard.writeText(url); toast({ title: t('walletUi.mints.urlCopied') }); } catch { toast({ title: t('walletUi.share.copyFailed'), variant: 'destructive' }); } }; useSeoMeta({ title: t('seoUi.mintsTitle'), description: t('seoUi.mintsDesc'), }); const canAdd = isNativePlatform() && isInitialized && !!coco; const addedUrls = useMemo( () => new Set(mints.map((m) => m.url.toLowerCase())), [mints], ); const filtered = useMemo(() => { const list = data ?? []; const q = query.trim().toLowerCase(); return list .filter((m) => { if (onlineOnly && (m.archived || m.online === false)) return false; if (!q) return true; return ( m.url.toLowerCase().includes(q) || (m.name ?? '').toLowerCase().includes(q) ); }) .sort((a, b) => { const rank = (m: MintDirectoryEntry) => m.archived ? 3 : m.online === false ? 2 : m.degraded ? 1 : 0; const r = rank(a) - rank(b); if (r !== 0) return r; return (a.latencyMs ?? 9999) - (b.latencyMs ?? 9999); }); }, [data, query, onlineOnly]); const handleAdd = async (url: string) => { if (!coco) return; setAddingUrl(url); try { await coco.mint.addMint(url, { trusted: true }); await refreshMints(); toast({ title: t('walletUi.mints.added'), description: t('walletUi.mints.addedDesc', { host: hostOf(url) }) }); } catch (e) { toast({ title: t('walletUi.mints.couldNotAdd'), description: e instanceof Error ? e.message : t('walletUi.addToCollection.somethingWrong'), variant: 'destructive', }); } finally { setAddingUrl(null); } }; return ( <>

{t('walletUi.mints.title')}

{t('walletUi.mints.subtitle')}

setQuery(e.target.value)} placeholder={t('walletUi.mints.searchPlaceholder')} className="h-11 border-gray-700 bg-gray-900 pl-10 text-white" />
{!isLoading && !error && (

{t('walletUi.mints.count', { filtered: filtered.length, total: data?.length ?? 0 })}

)} {isLoading && (
{Array.from({ length: 6 }).map((_, i) => ( ))}
)} {error && !isLoading && (

{error instanceof Error ? error.message : t('walletUi.mints.directoryError')}

{t('walletUi.mints.directoryHint')}

)} {!isLoading && !error && ( <> {!canAdd && (

{t('walletUi.mints.appOnly')}

)}
{filtered.map((mint) => ( handleAdd(mint.url)} onSelect={() => setSelected(mint)} /> ))}
{filtered.length === 0 && (

{t('walletUi.mints.noMatch', { query })}

)}

{t('walletUi.mints.footer')}

)} {/* Mint detail — everything the directory knows, plus what to watch for. */} { if (!o) setSelected(null); }} > {selected && ( <>
{selected.iconUrl ? ( { (e.currentTarget as HTMLImageElement).style.display = 'none'; }} /> ) : (
)}
{selected.name || hostOf(selected.url)} {hostOf(selected.url)}
{healthBadge(selected).label}
{selected.descriptionLong && (

{selected.descriptionLong}

)}
{[ { label: t('walletUi.mints.communityRating'), value: typeof selected.reviewAvgRating === 'number' && (selected.reviewCount ?? 0) > 0 ? `${selected.reviewAvgRating.toFixed(1)} / 5 · ${selected.reviewCount}` : '—', }, { label: t('walletUi.mints.reliabilityLabel'), value: typeof selected.reliabilityScore === 'number' ? `${selected.reliabilityScore} / 100` : '—', }, { label: t('walletUi.mints.uptime'), value: typeof selected.uptimePct24h === 'number' && typeof selected.uptimePct7d === 'number' ? `${Math.round(selected.uptimePct24h)}% / ${Math.round(selected.uptimePct7d)}%` : '—', }, { label: t('walletUi.mints.location'), value: selected.serverLocation ?? '—' }, { label: t('walletUi.mints.latency'), value: selected.online && typeof selected.latencyMs === 'number' ? `${selected.latencyMs} ms` : '—', }, { label: t('walletUi.mints.nuts'), value: typeof selected.nutCount === 'number' ? String(selected.nutCount) : '—', }, { label: t('walletUi.mints.units'), value: selected.units?.length ? selected.units.join(', ') : '—', }, { label: t('walletUi.mints.version'), value: selected.version ?? '—' }, ].map((f) => (

{f.label}

{f.value}

))}

{typeof selected.reviewAvgRating === 'number' && (selected.reviewCount ?? 0) > 0 ? t('walletUi.mints.ratedBy', { rating: selected.reviewAvgRating.toFixed(1), count: selected.reviewCount ?? 0 }) : t('walletUi.mints.noRating')}{' '} {t('walletUi.mints.reviewsNote1')}{' '} {t('walletUi.mints.openMintRadar')} {' '} {t('walletUi.mints.reviewsNote2')}

{typeof selected.auditNMints === 'number' && (

{t('walletUi.mints.liveness', { mints: selected.auditNMints.toLocaleString(), melts: (selected.auditNMelts ?? 0).toLocaleString() })} {selected.auditRecentTotal ? t('walletUi.mints.livenessErrors', { errors: selected.auditRecentErrors ?? 0, total: selected.auditRecentTotal }) : ''} .

)}
{addedUrls.has(selected.url.toLowerCase()) ? ( {t('walletUi.mints.inWallet')} ) : canAdd ? ( ) : null} {selected.tosUrl && ( )}

{t('walletUi.mints.whatsAMint')}

{t('walletUi.mints.whatsAMintDesc')}

{t('walletUi.mints.mintWarning')}

{t('walletUi.mints.healthDisclaimer')}

)}
); }