import { useState } from 'react'; import { Link } from 'react-router-dom'; import { labProfilePathFromKind } from '@/lib/labRoutes'; 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 { useTranslation } from '@/contexts/language'; 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 { t } = useTranslation(); 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 ? labProfilePathFromKind(coord.kind, 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: t('socialUi.collectiveJoinRequest.addedTitle', { name: requesterName, collective: request.collectiveName }), description: t('socialUi.collectiveJoinRequest.addedDesc'), }); onResolved?.(); } catch (err) { toast({ title: t('socialUi.collectiveJoinRequest.couldNotAdd'), description: err instanceof Error ? err.message : t('common.tryAgain'), variant: 'destructive', }); } }; const handleDismiss = async () => { try { await dismiss(request.id); setDone('dismissed'); onResolved?.(); } catch { toast({ title: t('socialUi.collectiveJoinRequest.couldNotDismiss'), variant: 'destructive' }); } }; if (done) { return (
{done === 'added' ? ( <> {t('socialUi.collectiveJoinRequest.addedDone')} {requesterName} {t('socialUi.collectiveJoinRequest.addedTo')}{' '} {request.collectiveName} ) : ( <> {t('socialUi.collectiveJoinRequest.dismissedDone', { name: requesterName })} )}
); } const busy = approving || dismissing; return (

{requesterName.slice(0, 2).toUpperCase()} {requesterName} {' '} {t('socialUi.collectiveJoinRequest.wantsToJoin')}{' '} {request.collectiveName}

{request.note && (

“{request.note}”

)} {alreadyMember ? (

{t('socialUi.collectiveJoinRequest.alreadyOnRoster')}

) : !profileLoading && !profile ? ( /* Profile couldn't be resolved — never trap the owner. Offer a link to open the collective plus a way to clear the notification. */
) : (
)}
); }