import { useState } from 'react'; import { Link } from 'react-router-dom'; import { nip19 } from 'nostr-tools'; import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { useAuthor } from '@/hooks/useAuthor'; import { useLabProfile } from '@/hooks/useLabProfiles'; import { useApproveCollectiveJoinRequest } from '@/hooks/useCollectiveJoinRequests'; import { useDismissCrewNotification } from '@/hooks/useCrewDismissals'; import { useToast } from '@/hooks/useToast'; import { genUserName } from '@/lib/genUserName'; import { parseAddrCoord, type CollectiveJoinRequest } from '@/lib/types'; import { UserPlus, X, Loader2, Hand, Check } from 'lucide-react'; import { cn } from '@/lib/utils'; function npubSafe(pubkey: string): string { try { return nip19.npubEncode(pubkey); } catch { return pubkey; } } /** * An incoming "request to join this collective" for a Lab-collective owner. * * One-tap flow: the owner sees who wants in and hits **Add to collective** — * that appends the requester to the owner's own Lab-profile roster and clears * the request. Only the owner can edit their own event, so no forged membership * is possible. While the profile loads (or if it can't be resolved) the card * degrades gracefully to a link + Dismiss so the owner is never stuck. */ export function CollectiveJoinRequestCard({ request, onResolved, compact, }: { request: CollectiveJoinRequest; onResolved?: () => void; compact?: boolean; }) { const requester = useAuthor(request.requester); const requesterName = requester.data?.metadata?.name ?? genUserName(request.requester); const requesterPic = requester.data?.metadata?.picture; const { toast } = useToast(); const coord = parseAddrCoord(request.collectiveCoord); const collectiveHref = coord ? `/coach/${coord.pubkey}/${coord.identifier}` : '/lab'; // Resolve the collective so we can add the member in one click. const { data: profile, isLoading: profileLoading } = useLabProfile( coord?.identifier ?? '', coord?.pubkey, ); const { mutateAsync: approve, isPending: approving } = useApproveCollectiveJoinRequest(); const { mutateAsync: dismiss, isPending: dismissing } = useDismissCrewNotification(); const [done, setDone] = useState<'added' | 'dismissed' | null>(null); const alreadyMember = !!profile && profile.members.some((m) => m.toLowerCase() === request.requester.toLowerCase()); const handleAdd = async () => { if (!profile) return; try { await approve({ request, profile }); setDone('added'); toast({ title: `Added ${requesterName} to ${request.collectiveName} 🛹`, description: "They'll confirm from their side to appear as a verified member.", }); onResolved?.(); } catch (err) { toast({ title: 'Could not add member', description: err instanceof Error ? err.message : 'Please try again.', variant: 'destructive', }); } }; const handleDismiss = async () => { try { await dismiss(request.id); setDone('dismissed'); onResolved?.(); } catch { toast({ title: 'Could not dismiss', variant: 'destructive' }); } }; if (done) { return (
“{request.note}”
)} {alreadyMember ? (