refactor(ux): consolidate BMAD skills, update design system, and clean up Prisma generated client

This commit is contained in:
Sepehr Ramezani
2026-04-19 19:21:27 +02:00
parent 5296c4da2c
commit 25529a24b8
2476 changed files with 127934 additions and 101962 deletions

View File

@@ -23,7 +23,7 @@ import { cn } from '@/lib/utils'
import { NoteInlineEditor } from '@/components/note-inline-editor'
import { useLanguage } from '@/lib/i18n'
import { getNoteDisplayTitle } from '@/lib/note-preview'
import { updateFullOrderWithoutRevalidation, createNote } from '@/app/actions/notes'
import { updateFullOrderWithoutRevalidation, createNote, deleteNote } from '@/app/actions/notes'
import {
GripVertical,
Hash,
@@ -33,8 +33,17 @@ import {
Clock,
Plus,
Loader2,
Trash2,
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { toast } from 'sonner'
import { formatDistanceToNow } from 'date-fns'
import { fr } from 'date-fns/locale/fr'
@@ -105,14 +114,18 @@ function SortableNoteListItem({
note,
selected,
onSelect,
onDelete,
reorderLabel,
deleteLabel,
language,
untitledLabel,
}: {
note: Note
selected: boolean
onSelect: () => void
onDelete: () => void
reorderLabel: string
deleteLabel: string
language: string
untitledLabel: string
}) {
@@ -231,6 +244,20 @@ function SortableNoteListItem({
)}
</div>
</div>
{/* Delete button - visible on hover */}
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onDelete()
}}
className="flex items-center px-2 text-red-500/60 opacity-0 transition-opacity group-hover:opacity-100 hover:text-red-600"
aria-label={deleteLabel}
title={deleteLabel}
>
<Trash2 className="h-4 w-4" />
</button>
</div>
)
}
@@ -242,6 +269,7 @@ export function NotesTabsView({ notes, onEdit, currentNotebookId }: NotesTabsVie
const [items, setItems] = useState<Note[]>(notes)
const [selectedId, setSelectedId] = useState<string | null>(null)
const [isCreating, startCreating] = useTransition()
const [noteToDelete, setNoteToDelete] = useState<Note | null>(null)
useEffect(() => {
// Only reset when notes are added or removed, NOT on content/field changes
@@ -254,7 +282,15 @@ export function NotesTabsView({ notes, onEdit, currentNotebookId }: NotesTabsVie
return prev.map((p) => {
const fresh = notes.find((n) => n.id === p.id)
if (!fresh) return p
return { ...fresh, title: p.title, content: p.content, labels: p.labels }
// Use fresh labels from server if they've changed (e.g., global label deletion)
const labelsChanged = JSON.stringify(fresh.labels?.sort()) !== JSON.stringify(p.labels?.sort())
return {
...fresh,
title: p.title,
content: p.content,
// Always use server labels if different (for global label changes)
labels: labelsChanged ? fresh.labels : p.labels
}
})
}
// Different set (add/remove): full sync
@@ -386,7 +422,9 @@ export function NotesTabsView({ notes, onEdit, currentNotebookId }: NotesTabsVie
note={note}
selected={note.id === selectedId}
onSelect={() => setSelectedId(note.id)}
onDelete={() => setNoteToDelete(note)}
reorderLabel={t('notes.reorderTabs')}
deleteLabel={t('notes.delete')}
language={language}
untitledLabel={t('notes.untitled')}
/>
@@ -430,6 +468,45 @@ export function NotesTabsView({ notes, onEdit, currentNotebookId }: NotesTabsVie
<p className="text-sm">{t('notes.selectNote') || 'Sélectionnez une note'}</p>
</div>
)}
{/* Delete Confirmation Dialog */}
<Dialog open={!!noteToDelete} onOpenChange={() => setNoteToDelete(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('notes.confirmDeleteTitle') || t('notes.delete')}</DialogTitle>
<DialogDescription>
{t('notes.confirmDelete') || 'Are you sure you want to delete this note?'}
{noteToDelete && (
<span className="mt-2 block font-medium text-foreground">
"{getNoteDisplayTitle(noteToDelete, t('notes.untitled'))}"
</span>
)}
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button variant="outline" onClick={() => setNoteToDelete(null)}>
{t('common.cancel')}
</Button>
<Button
variant="destructive"
onClick={async () => {
if (!noteToDelete) return
try {
await deleteNote(noteToDelete.id)
setItems((prev) => prev.filter((n) => n.id !== noteToDelete.id))
setSelectedId((prev) => (prev === noteToDelete.id ? null : prev))
setNoteToDelete(null)
toast.success(t('notes.deleted'))
} catch {
toast.error(t('notes.deleteFailed'))
}
}}
>
{t('notes.delete')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}