'use client' import { useState, useTransition, useEffect, useRef } from 'react' import { useRouter } from 'next/navigation' import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" import { X, Loader2, Mail } from "lucide-react" import { addCollaborator, removeCollaborator, getNoteCollaborators } from "@/app/actions/notes" import { toast } from "sonner" interface Collaborator { id: string name: string | null email: string image: string | null } interface CollaboratorDialogProps { open: boolean onOpenChange: (open: boolean) => void noteId: string noteOwnerId: string currentUserId: string onCollaboratorsChange?: (collaboratorIds: string[]) => void initialCollaborators?: string[] } export function CollaboratorDialog({ open, onOpenChange, noteId, noteOwnerId, currentUserId, onCollaboratorsChange, initialCollaborators = [] }: CollaboratorDialogProps) { const router = useRouter() const [collaborators, setCollaborators] = useState([]) const [localCollaboratorIds, setLocalCollaboratorIds] = useState(initialCollaborators) const [email, setEmail] = useState('') const [isLoading, setIsLoading] = useState(false) const [isPending, startTransition] = useTransition() const [justAddedCollaborator, setJustAddedCollaborator] = useState(false) const isOwner = currentUserId === noteOwnerId const isCreationMode = !noteId const hasLoadedRef = useRef(false) // Load collaborators when dialog opens (only for existing notes) const loadCollaborators = async () => { if (isCreationMode) return setIsLoading(true) try { const result = await getNoteCollaborators(noteId) setCollaborators(result) hasLoadedRef.current = true } catch (error: any) { toast.error(error.message || 'Error loading collaborators') } finally { setIsLoading(false) } } // Load collaborators when dialog opens useEffect(() => { if (open && !isCreationMode && !hasLoadedRef.current && !isLoading) { loadCollaborators() } // Reset when dialog closes if (!open) { hasLoadedRef.current = false } }, [open, isCreationMode]) // Sync initial collaborators when prop changes (creation mode) useEffect(() => { if (isCreationMode) { setLocalCollaboratorIds(initialCollaborators) } }, [initialCollaborators, isCreationMode]) // Handle adding a collaborator const handleAddCollaborator = async (e: React.FormEvent) => { e.preventDefault() if (!email.trim()) return if (isCreationMode) { // Creation mode: just add email as placeholder, will be resolved on note creation if (!localCollaboratorIds.includes(email)) { const newIds = [...localCollaboratorIds, email] setLocalCollaboratorIds(newIds) onCollaboratorsChange?.(newIds) setEmail('') toast.success(`${email} will be added as collaborator when note is created`) } else { toast.warning('This email is already in the list') } } else { // Existing note mode: use server action setJustAddedCollaborator(true) startTransition(async () => { try { const result = await addCollaborator(noteId, email) if (result.success) { setCollaborators([...collaborators, result.user]) setEmail('') toast.success(`${result.user.name || result.user.email} now has access to this note`) // Don't refresh here - it would close the dialog! // The collaborator list is already updated in local state setJustAddedCollaborator(false) } } catch (error: any) { toast.error(error.message || 'Failed to add collaborator') setJustAddedCollaborator(false) } }) } } // Handle removing a collaborator const handleRemoveCollaborator = async (userId: string) => { if (isCreationMode) { // Creation mode: remove from local list const newIds = localCollaboratorIds.filter(id => id !== userId) setLocalCollaboratorIds(newIds) onCollaboratorsChange?.(newIds) } else { // Existing note mode: use server action startTransition(async () => { try { await removeCollaborator(noteId, userId) setCollaborators(collaborators.filter(c => c.id !== userId)) toast.success('Access has been revoked') // Don't refresh here - it would close the dialog! // The collaborator list is already updated in local state } catch (error: any) { toast.error(error.message || 'Failed to remove collaborator') } }) } } return ( { // Prevent dialog from closing when interacting with Sonner toasts // Sonner uses data-sonner-toast NOT data-toast const target = event.target as HTMLElement; const isSonnerElement = target.closest('[data-sonner-toast]') || target.closest('[data-sonner-toaster]') || target.closest('[data-icon]') || target.closest('[data-content]') || target.closest('[data-description]') || target.closest('[data-title]') || target.closest('[data-button]'); if (isSonnerElement) { event.preventDefault(); return; } // Also prevent if target is the toast container itself if (target.getAttribute('data-sonner-toaster') !== null) { event.preventDefault(); return; } }} > Share with collaborators {isOwner ? "Add people to collaborate on this note by their email address." : "You have access to this note. Only the owner can manage collaborators."}
{isOwner && (
setEmail(e.target.value)} disabled={isPending} />
)}
{isLoading ? (
) : isCreationMode ? ( // Creation mode: show emails localCollaboratorIds.length === 0 ? (

No collaborators yet. Add someone above!

) : (
{localCollaboratorIds.map((emailOrId, idx) => (
{emailOrId.charAt(0).toUpperCase()}

Pending Invite

{emailOrId}

Pending
))}
) ) : collaborators.length === 0 ? (

No collaborators yet. {isOwner && "Add someone above!"}

) : (
{collaborators.map((collaborator) => (
{collaborator.name?.charAt(0).toUpperCase() || collaborator.email.charAt(0).toUpperCase()}

{collaborator.name || 'Unnamed User'}

{collaborator.email}

{collaborator.id === noteOwnerId && ( Owner )}
{isOwner && collaborator.id !== noteOwnerId && ( )}
))}
)}
) }