diff --git a/keep-notes/components/note-card.tsx b/keep-notes/components/note-card.tsx index c790741..6111de7 100644 --- a/keep-notes/components/note-card.tsx +++ b/keep-notes/components/note-card.tsx @@ -20,11 +20,11 @@ import { AlertDialogHeader, AlertDialogTitle, } from '@/components/ui/alert-dialog' -import { Pin, Bell, GripVertical, X, Link2, FolderOpen, StickyNote, LogOut, Trash2 } from 'lucide-react' +import { Pin, Bell, GripVertical, X, Link2, FolderOpen, StickyNote, LucideIcon, Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, LogOut, Trash2 } from 'lucide-react' import { useState, useEffect, useTransition, useOptimistic, memo } from 'react' import { useSession } from 'next-auth/react' import { useRouter, useSearchParams } from 'next/navigation' -import { deleteNote, toggleArchive, togglePin, updateColor, updateNote, updateSize, getNoteAllUsers, leaveSharedNote, removeFusedBadge, restoreNote, permanentDeleteNote, createNote } from '@/app/actions/notes' +import { deleteNote, toggleArchive, togglePin, updateColor, updateNote, updateSize, getNoteAllUsers, leaveSharedNote, removeFusedBadge, createNote } from '@/app/actions/notes' import { cn } from '@/lib/utils' import { formatDistanceToNow, Locale } from 'date-fns' import { enUS } from 'date-fns/locale/en-US' @@ -48,7 +48,6 @@ import { NoteImages } from './note-images' import { NoteChecklist } from './note-checklist' import { NoteActions } from './note-actions' import { CollaboratorDialog } from './collaborator-dialog' -import { useCardSizeMode } from '@/hooks/use-card-size-mode' import { CollaboratorAvatars } from './collaborator-avatars' import { ConnectionsBadge } from './connections-badge' import { ConnectionsOverlay } from './connections-overlay' @@ -60,7 +59,6 @@ import { useNoteRefresh } from '@/context/NoteRefreshContext' import { useLanguage } from '@/lib/i18n' import { useNotebooks } from '@/context/notebooks-context' import { toast } from 'sonner' -import { getNotebookIcon } from '@/lib/notebook-icon' // Mapping of supported languages to date-fns locales const localeMap: Record = { @@ -85,6 +83,28 @@ function getDateLocale(language: string): Locale { return localeMap[language] || enUS } +// Map icon names to lucide-react components +const ICON_MAP: Record = { + 'folder': Folder, + 'briefcase': Briefcase, + 'document': FileText, + 'lightning': Zap, + 'chart': BarChart3, + 'globe': Globe, + 'sparkle': Sparkles, + 'book': Book, + 'heart': Heart, + 'crown': Crown, + 'music': Music, + 'building': Building2, +} + +// Function to get icon component by name +function getNotebookIcon(iconName: string): LucideIcon { + const IconComponent = ICON_MAP[iconName] || Folder + return IconComponent +} + interface NoteCardProps { note: Note onEdit?: (note: Note, readOnly?: boolean) => void @@ -145,7 +165,6 @@ export const NoteCard = memo(function NoteCard({ const [, startTransition] = useTransition() const [isDeleting, setIsDeleting] = useState(false) const [showDeleteDialog, setShowDeleteDialog] = useState(false) - const [showPermanentDeleteDialog, setShowPermanentDeleteDialog] = useState(false) const [showCollaboratorDialog, setShowCollaboratorDialog] = useState(false) const [collaborators, setCollaborators] = useState([]) const [owner, setOwner] = useState(null) @@ -185,10 +204,6 @@ export const NoteCard = memo(function NoteCard({ const isSharedNote = currentUserId && note.userId && currentUserId !== note.userId const isOwner = currentUserId && note.userId && currentUserId === note.userId - // Card size mode from settings - const cardSizeMode = useCardSizeMode() - const isUniformMode = cardSizeMode === 'uniform' - // Load collaborators only for shared notes (not owned by current user) useEffect(() => { // Skip API call for notes owned by current user — no need to fetch collaborators @@ -272,16 +287,26 @@ export const NoteCard = memo(function NoteCard({ }) } - const handleSizeChange = (size: 'small' | 'medium' | 'large') => { - // Notifier le parent immédiatement (hors transition) — c'est lui - // qui détient la source de vérité via localNotes - onSizeChange?.(size) - onResize?.() + const handleSizeChange = async (size: 'small' | 'medium' | 'large') => { + startTransition(async () => { + // Instant visual feedback for the card itself + addOptimisticNote({ size }) - // Persister en arrière-plan - updateSize(note.id, size).catch(err => - console.error('Failed to update note size:', err) - ) + // Notify parent so it can update its local state + onSizeChange?.(size) + + // Trigger layout refresh + onResize?.() + setTimeout(() => onResize?.(), 300) + + // Update server in background + + try { + await updateSize(note.id, size); + } catch (error) { + console.error('Failed to update note size:', error); + } + }) } const handleCheckItem = async (checkItemId: string) => { @@ -308,27 +333,6 @@ export const NoteCard = memo(function NoteCard({ } } - const handleRestore = async () => { - try { - await restoreNote(note.id) - setIsDeleting(true) // Hide the note from trash view - toast.success(t('trash.noteRestored')) - } catch (error) { - console.error('Failed to restore note:', error) - } - } - - const handlePermanentDelete = async () => { - setIsDeleting(true) - try { - await permanentDeleteNote(note.id) - toast.success(t('trash.notePermanentlyDeleted')) - } catch (error) { - console.error('Failed to permanently delete note:', error) - setIsDeleting(false) - } - } - const handleRemoveFusedBadge = async (e: React.MouseEvent) => { e.stopPropagation() // Prevent opening the note editor startTransition(async () => { @@ -355,11 +359,10 @@ export const NoteCard = memo(function NoteCard({ data-testid="note-card" data-draggable="true" data-note-id={note.id} - data-size={isUniformMode ? 'small' : optimisticNote.size} - style={{ minHeight: isUniformMode ? 'auto' : getMinHeight(optimisticNote.size) }} - draggable={!isTrashView} + data-size={optimisticNote.size} + style={{ minHeight: getMinHeight(optimisticNote.size) }} + draggable={true} onDragStart={(e) => { - if (isTrashView) return e.dataTransfer.setData('text/plain', note.id) e.dataTransfer.effectAllowed = 'move' e.dataTransfer.setData('text/html', '') // Prevent ghost image in some browsers @@ -385,8 +388,7 @@ export const NoteCard = memo(function NoteCard({ } }} > - {/* Drag Handle - Only visible on mobile/touch devices, not in trash */} - {!isTrashView && ( + {/* Drag Handle - Only visible on mobile/touch devices */}
- )} - {/* Move to Notebook Dropdown Menu - Hidden in trash */} - {!isTrashView && ( + {/* Move to Notebook Dropdown Menu */}
e.stopPropagation()} className="absolute top-2 right-2 z-20"> @@ -433,10 +433,8 @@ export const NoteCard = memo(function NoteCard({
- )} - {/* Pin Button - Visible on hover or if pinned, hidden in trash */} - {!isTrashView && ( + {/* Pin Button - Visible on hover or if pinned */} - )} @@ -465,36 +462,6 @@ export const NoteCard = memo(function NoteCard({ /> )} - {/* Memory Echo Badges - Fusion + Connections (BEFORE Title) */} -
- {/* Fusion Badge with remove button */} - {note.autoGenerated && ( -
- - {t('memoryEcho.fused')} - -
- )} - - {/* Connections Badge */} - { - // Only open overlay if note is NOT open in editor - // (to avoid having 2 Dialogs with 2 close buttons) - if (!isNoteOpenInEditor) { - setShowConnectionsOverlay(true) - } - }} - /> -
- {/* Title */} {optimisticNote.title && (

@@ -611,22 +578,19 @@ export const NoteCard = memo(function NoteCard({ )} - {/* Action Bar Component - hide destructive actions for shared notes */} - {!isSharedNote && ( + {/* Action Bar Component - Always show for now to fix regression */} + {true && ( setShowDeleteDialog(true)} onShareCollaborators={() => setShowCollaboratorDialog(true)} - isTrashView={isTrashView} - onRestore={handleRestore} - onPermanentDelete={() => setShowPermanentDeleteDialog(true)} className="absolute bottom-0 left-0 right-0 p-2 opacity-100 md:opacity-0 group-hover:opacity-100 transition-opacity" /> )} @@ -644,6 +608,18 @@ export const NoteCard = memo(function NoteCard({ )} + {/* Connections Badge - Bottom right (spec: amber, absolute) */} +
+ { + if (!isNoteOpenInEditor) { + setShowConnectionsOverlay(true) + } + }} + /> +
+ {/* Connections Overlay */}
e.stopPropagation()}> setShowConnectionsOverlay(false)} noteId={note.id} onOpenNote={(connNoteId) => { + setShowConnectionsOverlay(false) const params = new URLSearchParams(searchParams.toString()) params.set('note', connNoteId) router.push(`?${params.toString()}`) @@ -690,10 +667,10 @@ export const NoteCard = memo(function NoteCard({ )} {/* Fusion Modal */} - {fusionNotes && fusionNotes.length > 0 && ( + {fusionNotes.length > 0 && (
e.stopPropagation()}> 0} onClose={() => setFusionNotes([])} notes={fusionNotes} onConfirmFusion={async ({ title, content }, options) => { @@ -715,6 +692,7 @@ export const NoteCard = memo(function NoteCard({ } } toast.success(t('toast.notesFusionSuccess')) + setFusionNotes([]) triggerRefresh() }} /> @@ -738,24 +716,6 @@ export const NoteCard = memo(function NoteCard({ - - {/* Permanent Delete Confirmation Dialog (Trash view only) */} - - - - {t('trash.permanentDelete')} - - {t('trash.permanentDeleteConfirm')} - - - - {t('common.cancel') || 'Cancel'} - - {t('trash.permanentDelete')} - - - - ) }) \ No newline at end of file