'use client' import { Note, NOTE_COLORS, NoteColor } from '@/lib/types' import { Card } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Pin, Bell, GripVertical, X } from 'lucide-react' import { useState, useEffect, useTransition, useOptimistic } from 'react' import { useSession } from 'next-auth/react' import { useRouter } from 'next/navigation' import { deleteNote, toggleArchive, togglePin, updateColor, updateNote, getNoteAllUsers, leaveSharedNote } from '@/app/actions/notes' import { cn } from '@/lib/utils' import { formatDistanceToNow } from 'date-fns' import { fr } from 'date-fns/locale' import { MarkdownContent } from './markdown-content' import { LabelBadge } from './label-badge' import { NoteImages } from './note-images' import { NoteChecklist } from './note-checklist' import { NoteActions } from './note-actions' import { CollaboratorDialog } from './collaborator-dialog' import { CollaboratorAvatars } from './collaborator-avatars' import { useLabels } from '@/context/LabelContext' interface NoteCardProps { note: Note onEdit?: (note: Note, readOnly?: boolean) => void isDragging?: boolean isDragOver?: boolean } export function NoteCard({ note, onEdit, isDragging, isDragOver }: NoteCardProps) { const router = useRouter() const { refreshLabels } = useLabels() const { data: session } = useSession() const [isPending, startTransition] = useTransition() const [isDeleting, setIsDeleting] = useState(false) const [showCollaboratorDialog, setShowCollaboratorDialog] = useState(false) const [collaborators, setCollaborators] = useState([]) const [owner, setOwner] = useState(null) const colorClasses = NOTE_COLORS[note.color as NoteColor] || NOTE_COLORS.default // Optimistic UI state for instant feedback const [optimisticNote, addOptimisticNote] = useOptimistic( note, (state, newProps: Partial) => ({ ...state, ...newProps }) ) const currentUserId = session?.user?.id const canManageCollaborators = currentUserId && note.userId && currentUserId === note.userId const isSharedNote = currentUserId && note.userId && currentUserId !== note.userId const isOwner = currentUserId && note.userId && currentUserId === note.userId // Load collaborators when note changes useEffect(() => { const loadCollaborators = async () => { if (note.userId) { try { const users = await getNoteAllUsers(note.id) setCollaborators(users) // Owner is always first in the list if (users.length > 0) { setOwner(users[0]) } } catch (error) { console.error('Failed to load collaborators:', error) setCollaborators([]) } } } loadCollaborators() }, [note.id, note.userId]) const handleDelete = async () => { if (confirm('Are you sure you want to delete this note?')) { setIsDeleting(true) try { await deleteNote(note.id) // Refresh global labels to reflect garbage collection await refreshLabels() } catch (error) { console.error('Failed to delete note:', error) setIsDeleting(false) } } } const handleTogglePin = async () => { startTransition(async () => { addOptimisticNote({ isPinned: !note.isPinned }) await togglePin(note.id, !note.isPinned) router.refresh() }) } const handleToggleArchive = async () => { startTransition(async () => { addOptimisticNote({ isArchived: !note.isArchived }) await toggleArchive(note.id, !note.isArchived) router.refresh() }) } const handleColorChange = async (color: string) => { startTransition(async () => { addOptimisticNote({ color }) await updateColor(note.id, color) router.refresh() }) } const handleSizeChange = async (size: 'small' | 'medium' | 'large') => { startTransition(async () => { addOptimisticNote({ size }) await updateNote(note.id, { size }) router.refresh() }) } const handleCheckItem = async (checkItemId: string) => { if (note.type === 'checklist' && note.checkItems) { const updatedItems = note.checkItems.map(item => item.id === checkItemId ? { ...item, checked: !item.checked } : item ) startTransition(async () => { addOptimisticNote({ checkItems: updatedItems }) await updateNote(note.id, { checkItems: updatedItems }) router.refresh() }) } } const handleLeaveShare = async () => { if (confirm('Are you sure you want to leave this shared note?')) { try { await leaveSharedNote(note.id) setIsDeleting(true) // Hide the note from view } catch (error) { console.error('Failed to leave share:', error) } } } if (isDeleting) return null return ( { // Only trigger edit if not clicking on buttons const target = e.target as HTMLElement if (!target.closest('button') && !target.closest('[role="checkbox"]') && !target.closest('.drag-handle')) { // For shared notes, pass readOnly flag onEdit?.(note, !!isSharedNote) // Pass second parameter as readOnly flag (convert to boolean) } }} > {/* Drag Handle - Visible only on mobile/touch devices */}
{/* Pin Button - Visible on hover or if pinned, always accessible */} {/* Reminder Icon - Move slightly if pin button is there */} {note.reminder && new Date(note.reminder) > new Date() && ( )} {/* Title */} {optimisticNote.title && (

{optimisticNote.title}

)} {/* Shared badge */} {isSharedNote && owner && (
Shared by {owner.name || owner.email}
)} {/* Images Component */} {/* Link Previews */} {optimisticNote.links && optimisticNote.links.length > 0 && (
{optimisticNote.links.map((link, idx) => ( e.stopPropagation()} > {link.imageUrl && ( )} {/* Content */} {optimisticNote.type === 'text' ? (
) : ( )} {/* Labels */} {optimisticNote.labels && optimisticNote.labels.length > 0 && (
{optimisticNote.labels.map((label) => ( ))}
)} {/* Collaborators */} {optimisticNote.userId && collaborators.length > 0 && ( )} {/* Creation Date */}
{formatDistanceToNow(new Date(note.createdAt), { addSuffix: true, locale: fr })}
{/* Action Bar Component - Only for owner */} {isOwner && ( setShowCollaboratorDialog(true)} className="absolute bottom-0 left-0 right-0 p-2 opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity" /> )} {/* Collaborator Dialog */} {currentUserId && note.userId && (
e.stopPropagation()}>
)} ) }