'use client' import { useState, useEffect, useCallback } from 'react' import { createPortal } from 'react-dom' import { createShareRequest, removeCollaborator, getNoteCollaborators } from '@/app/actions/notes' import { toast } from 'sonner' import { X, UserPlus, Users, Mail, Trash2, Loader2, Share2, Check } from 'lucide-react' import { cn } from '@/lib/utils' import { useLanguage } from '@/lib/i18n' interface Collaborator { id: string name: string | null email: string | null image: string | null } interface NoteShareDialogProps { noteId: string noteTitle: string onClose: () => void } export function NoteShareDialog({ noteId, noteTitle, onClose }: NoteShareDialogProps) { const { t } = useLanguage() const [email, setEmail] = useState('') const [permission, setPermission] = useState<'view' | 'edit'>('view') const [collaborators, setCollaborators] = useState([]) const [loading, setLoading] = useState(true) const [sending, setSending] = useState(false) const [removingId, setRemovingId] = useState(null) const [sent, setSent] = useState(false) const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) // Close on Escape const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() } document.addEventListener('keydown', onKey) return () => document.removeEventListener('keydown', onKey) }, [onClose]) const loadCollaborators = useCallback(async () => { try { const list = await getNoteCollaborators(noteId) setCollaborators(list as Collaborator[]) } catch { // owner-only view — silently ignore } finally { setLoading(false) } }, [noteId]) useEffect(() => { loadCollaborators() }, [loadCollaborators]) const handleInvite = async (e: React.FormEvent) => { e.preventDefault() const trimmed = email.trim() if (!trimmed) return setSending(true) try { await createShareRequest(noteId, trimmed, permission) setSent(true) setEmail('') toast.success(t('collaboration.toastInviteSentTo', { email: trimmed })) setTimeout(() => setSent(false), 2000) loadCollaborators() } catch (err: any) { const msg = err?.message || t('collaboration.toastSharingError') if (msg.includes('not found')) toast.error(t('collaboration.toastEmailNotFound')) else if (msg.includes('already shared')) toast.error(t('collaboration.toastAlreadySharedUser')) else toast.error(msg) } finally { setSending(false) } } const handleRemove = async (collaboratorId: string, collaboratorEmail: string | null) => { setRemovingId(collaboratorId) try { await removeCollaborator(noteId, collaboratorId) setCollaborators(prev => prev.filter(c => c.id !== collaboratorId)) toast.success(t('collaboration.toastAccessRemoved', { target: collaboratorEmail || t('collaboration.toastUserFallback'), })) } catch { toast.error(t('collaboration.toastRemoveAccessFailed')) } finally { setRemovingId(null) } } if (!mounted) return null return createPortal(
{ if (e.target === e.currentTarget) onClose() }} >
e.stopPropagation()} > {/* Header */}

{t('collaboration.shareCompactTitle')}

{/* Invite form */}
setEmail(e.target.value)} required autoFocus className="w-full pl-9 pr-3 py-2.5 text-[13px] rounded-xl border border-black/15 dark:border-white/15 bg-transparent outline-none focus:ring-2 ring-[#A47148]/30 focus:border-[#A47148] transition-all placeholder:text-foreground/30" />
{/* Permission toggle */}
{(['view', 'edit'] as const).map(p => ( ))}
{/* Collaborators list */}
{t('collaboration.sharedAccessLabel')}
{loading ? (
) : collaborators.length === 0 ? (

{t('collaboration.noCollaboratorsEmpty')}

) : (
    {collaborators.map(c => (
  • {c.image ? {c.name : {(c.name || c.email || '?')[0].toUpperCase()} }

    {c.name || t('collaboration.userFallback')}

    {c.email}

  • ))}
)}
, document.body ) }