import { useState } from 'react'; import { Link } from 'react-router-dom'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { useAuthor } from '@/hooks/useAuthor'; import { useAcceptCrewInvite, useDeclineCrewInvite } from '@/hooks/useCrewInvites'; import { useToast } from '@/hooks/useToast'; import { genUserName } from '@/lib/genUserName'; import { parseAddrCoord, type CrewInvite } from '@/lib/types'; import { Users, Check, X, Loader2, ShieldCheck } from 'lucide-react'; import { cn } from '@/lib/utils'; /** * A single crew-invite notification card with Accept / Decline actions. * Accepting publishes the invitee's membership attestation (making them a * confirmed member); declining dismisses the invite locally. */ export function CrewInviteCard({ invite, onResolved, compact, }: { invite: CrewInvite; onResolved?: () => void; compact?: boolean; }) { const inviter = useAuthor(invite.inviter); const inviterName = inviter.data?.metadata?.name ?? genUserName(invite.inviter); const inviterPic = inviter.data?.metadata?.picture; const { toast } = useToast(); const { mutateAsync: accept, isPending: accepting } = useAcceptCrewInvite(); const { mutateAsync: decline, isPending: declining } = useDeclineCrewInvite(); const [done, setDone] = useState<'accepted' | 'declined' | null>(null); const coord = parseAddrCoord(invite.crewCoord); const crewHref = coord ? `/crew/${coord.pubkey}/${coord.identifier}` : '/crews'; const handleAccept = async () => { try { await accept({ invite }); setDone('accepted'); toast({ title: `Welcome to ${invite.crewName}! 🛹`, description: 'Your membership is confirmed.', }); onResolved?.(); } catch (err) { toast({ title: 'Could not accept', description: err instanceof Error ? err.message : 'Please try again.', variant: 'destructive', }); } }; const handleDecline = async () => { try { await decline({ invite }); setDone('declined'); onResolved?.(); } catch { toast({ title: 'Could not decline', variant: 'destructive' }); } }; if (done) { return (
{done === 'accepted' ? ( <> You joined {invite.crewName} ) : ( <> Declined invite to {invite.crewName} )}
); } const busy = accepting || declining; return (

{inviterName.slice(0, 2).toUpperCase()} {inviterName} {' '} invited you to join{' '} {invite.crewName} {invite.role === 'admin' && ( Admin )}

{invite.note && (

“{invite.note}”

)}
); } // Keep a tiny local encoder to avoid importing nip19 into every card render path. import { nip19 } from 'nostr-tools'; function inviterNpubSafe(pubkey: string): string { try { return nip19.npubEncode(pubkey); } catch { return pubkey; } }