import { useState } from 'react';
import { UserPlus, Copy, Check, Share2, Edit, Send, Loader2, ShieldCheck } from 'lucide-react';
import { Link } from 'react-router-dom';
import { nip19 } from 'nostr-tools';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { Switch } from '@/components/ui/switch';
import { Label } from '@/components/ui/label';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { useToast } from '@/hooks/useToast';
import { useTranslation } from '@/contexts/language';
import { useAuthor } from '@/hooks/useAuthor';
import { useSendCrewInvite } from '@/hooks/useCrewInvites';
import { genUserName } from '@/lib/genUserName';
import { cn } from '@/lib/utils';
import type { SkateCrew } from '@/lib/types';
interface CrewInviteDialogProps {
crew: SkateCrew;
/** Whether the viewer is the founder (gets the extra "add member" edit link + admin toggle). */
isFounder: boolean;
triggerClassName?: string;
}
/** Try to resolve a raw string (npub / nprofile / hex) to a hex pubkey. */
function resolvePubkey(input: string): string | null {
const trimmed = input.trim();
if (!trimmed) return null;
// Bare hex
if (/^[0-9a-f]{64}$/i.test(trimmed)) return trimmed.toLowerCase();
try {
const decoded = nip19.decode(trimmed);
if (decoded.type === 'npub') return decoded.data.toLowerCase();
if (decoded.type === 'nprofile') return decoded.data.pubkey.toLowerCase();
} catch {
return null;
}
return null;
}
/** Small live preview of who a pubkey resolves to (avatar + name). */
function InviteePreview({ pubkey }: { pubkey: string }) {
const author = useAuthor(pubkey);
const name = author.data?.metadata?.name ?? genUserName(pubkey);
const pic = author.data?.metadata?.picture;
return (
{name.slice(0, 2).toUpperCase()}
{name}
{nip19.npubEncode(pubkey)}
);
}
/**
* Invite flow for crew moderators (founder + admins).
*
* Two ways to invite:
* 1. **Send invite** — publishes a structured invite (kind 36229) addressed to a
* specific npub. That person gets a notification with Accept / Decline.
* When the FOUNDER sends it, the invitee is pre-listed so accepting instantly
* confirms membership. Admins can invite too (founder auto-confirms on accept).
* 2. **Share link** — copy/share a link for people not yet on Nostr.
*/
export function CrewInviteDialog({ crew, isFounder, triggerClassName }: CrewInviteDialogProps) {
const { t } = useTranslation();
const { toast } = useToast();
const [open, setOpen] = useState(false);
const [copiedLink, setCopiedLink] = useState(false);
const [copiedMsg, setCopiedMsg] = useState(false);
// Send-invite state
const [inviteeInput, setInviteeInput] = useState('');
const [note, setNote] = useState('');
const [asAdmin, setAsAdmin] = useState(false);
const { mutateAsync: sendInvite, isPending: sending } = useSendCrewInvite();
const resolvedPubkey = resolvePubkey(inviteeInput);
const inputLooksLikeKey = inviteeInput.trim().length > 0;
const invalidKey = inputLooksLikeKey && !resolvedPubkey;
const crewUrl = `${window.location.origin}/crew/${crew.pubkey}/${crew.identifier}`;
const inviteMessage =
`${t('socialUi.inviteDialog.messageIntro', { crew: crew.name })}\n${crewUrl}\n\n` +
`${t('socialUi.inviteDialog.messageBody')}`;
const copy = async (text: string, which: 'link' | 'msg') => {
try {
await navigator.clipboard.writeText(text);
if (which === 'link') {
setCopiedLink(true);
setTimeout(() => setCopiedLink(false), 2000);
} else {
setCopiedMsg(true);
setTimeout(() => setCopiedMsg(false), 2000);
}
toast({ title: t('common.copied') });
} catch {
toast({ title: t('socialUi.inviteDialog.copyFailed'), variant: 'destructive' });
}
};
const nativeShare = async () => {
if (navigator.share) {
try {
await navigator.share({ title: t('socialUi.inviteDialog.shareTitle', { crew: crew.name }), text: inviteMessage, url: crewUrl });
} catch {
/* cancelled */
}
} else {
copy(inviteMessage, 'msg');
}
};
const handleSend = async () => {
if (!resolvedPubkey) return;
try {
await sendInvite({
crew,
invitee: resolvedPubkey,
role: asAdmin ? 'admin' : 'member',
note,
isFounder,
});
toast({
title: t('socialUi.inviteDialog.sentTitle'),
description: t('socialUi.inviteDialog.sentDesc'),
});
setInviteeInput('');
setNote('');
setAsAdmin(false);
setOpen(false);
} catch (err) {
toast({
title: t('socialUi.inviteDialog.couldNotSend'),
description: err instanceof Error ? err.message : t('common.tryAgain'),
variant: 'destructive',
});
}
};
return (
);
}