import { ShieldCheck, Loader2, HeartHandshake, Users } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip'; import { Link } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useAuthor } from '@/hooks/useAuthor'; import { useVouches, useVouchFor } from '@/hooks/useVouches'; import { useToast } from '@/hooks/useToast'; import { genUserName } from '@/lib/genUserName'; import { cn } from '@/lib/utils'; interface VouchCardProps { /** Provider coordinate `kind:pubkey:d`. */ coord: string; /** Provider pubkey (to hide the button for the owner). */ providerPubkey: string; /** Provider display name. */ providerName: string; accentText?: string; accentBg?: string; } function VoucherAvatar({ pubkey }: { pubkey: string }) { const author = useAuthor(pubkey); const meta = author.data?.metadata; const name = meta?.name ?? genUserName(pubkey); const npub = nip19.npubEncode(pubkey); return ( {name.slice(0, 2).toUpperCase()} {name} ); } /** * The community anti-fraud primitive. Skaters vouch for real providers; the * verdict is trust-weighted so fake profiles with only cold/fake vouches read as * "new / unvouched" while genuinely-known people surface as scene-verified. */ export function VouchCard({ coord, providerPubkey, providerName, accentText = 'text-teal-400', accentBg = 'bg-teal-500 hover:bg-teal-600', }: VouchCardProps) { const { user } = useCurrentUser(); const { data, isLoading } = useVouches(coord); const { mutateAsync: vouch, isPending } = useVouchFor(); const { toast } = useToast(); const isOwner = user?.pubkey.toLowerCase() === providerPubkey.toLowerCase(); // Trust tier from the trust-weighted score. const tier = data.trustedVouchers >= 3 || data.weightedScore >= 150 ? 'verified' : data.count > 0 ? 'vouched' : 'new'; const handleVouch = async () => { try { await vouch({ coord, providerName, retract: data.viewerVouched }); toast({ title: data.viewerVouched ? 'Vouch removed' : 'Vouched!', description: data.viewerVouched ? `You no longer vouch for ${providerName}.` : `You vouched for ${providerName}. The scene trusts its own.`, }); } catch (error) { toast({ title: 'Could not vouch', description: error instanceof Error ? error.message : 'Please try again.', variant: 'destructive', }); } }; return (

Scene Vouches

{tier === 'verified' && ( Scene-verified )} {tier === 'vouched' && ( Vouched )} {tier === 'new' && ( New · unvouched )}
{isLoading ? (

Checking the scene…

) : data.count === 0 ? (

No vouches yet. Vouches from skaters in your circles are the best signal that someone's the real deal — not a random who wandered in.

) : (
{data.vouchers.slice(0, 6).map((pk) => ( ))}
{data.count} vouch{data.count !== 1 ? 'es' : ''}
{data.trustedVouchers > 0 && (

🛡 {data.trustedVouchers} from skaters in your web of trust

)}
)} {user && !isOwner && ( )} {isOwner && (

Skaters who know your work can vouch for you here.

)}
); }