Files
Momento/memento-note/components/create-notebook-dialog.tsx
Antigravity d90b29b34f
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 2m3s
feat: hierarchical notebooks (tree), remove all list view code, delete 22 unused files
- Add parentId to Notebook model (tree structure)
- Update sidebar to render parent/child notebooks with expand/collapse
- Add sub-notebook creation from parent notebook
- Remove 'list' from NotesViewMode type everywhere
- Delete 22 unused components, hooks, and UI files
- Wrap revalidatePath in try-catch to prevent save 500
- Update notebook API to support parentId in creation
2026-05-09 21:02:23 +00:00

133 lines
5.4 KiB
TypeScript

'use client'
import { useState } from 'react'
import { motion, AnimatePresence } from 'motion/react'
import { useLanguage } from '@/lib/i18n'
import { useNotebooks } from '@/context/notebooks-context'
interface CreateNotebookDialogProps {
open?: boolean
onOpenChange?: (open: boolean) => void
parentNotebookId?: string | null
}
export function CreateNotebookDialog({ open, onOpenChange, parentNotebookId }: CreateNotebookDialogProps) {
const { t } = useLanguage()
const { createNotebookOptimistic, notebooks } = useNotebooks()
const [name, setName] = useState('')
const [selectedParentId, setSelectedParentId] = useState<string | null>(parentNotebookId ?? null)
const [isSubmitting, setIsSubmitting] = useState(false)
const rootNotebooks = notebooks.filter(nb => !nb.parentId)
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!name.trim()) return
setIsSubmitting(true)
try {
await createNotebookOptimistic({
name: name.trim(),
icon: 'folder',
color: '#64748B',
parentId: selectedParentId,
})
setName('')
onOpenChange?.(false)
} catch (err) {
console.error('Failed to create notebook:', err)
} finally {
setIsSubmitting(false)
}
}
const handleClose = () => {
setName('')
setSelectedParentId(parentNotebookId ?? null)
onOpenChange?.(false)
}
return (
<AnimatePresence>
{open && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={handleClose}
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
/>
<motion.div
initial={{ opacity: 0, scale: 0.92, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.92, y: 20 }}
transition={{ type: 'spring', damping: 28, stiffness: 280 }}
className="relative w-full max-w-md bg-[#F2F0E9] dark:bg-zinc-900 border border-black/10 dark:border-white/10 shadow-2xl rounded-2xl p-8"
>
<h3 className="text-2xl font-memento-serif font-medium text-foreground mb-2">
{t('notebook.createNew') || 'Nouveau carnet'}
</h3>
<p className="text-sm text-muted-foreground mb-6 font-light">
{t('notebook.createDescription') || 'Donnez un nom à votre nouveau carnet.'}
</p>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-foreground mb-2">
{t('notebook.name') || 'Nom du carnet'}
</label>
<input
autoFocus
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t('notebook.namePlaceholder') || 'Ex. : Projets, Recherche…'}
className="w-full bg-white dark:bg-zinc-800 border border-black/12 dark:border-white/15 rounded-lg px-4 py-3 outline-none focus:border-foreground/40 dark:focus:border-white/40 transition-colors font-memento-serif italic text-lg text-foreground placeholder:text-muted-foreground/40"
/>
</div>
{!parentNotebookId && rootNotebooks.length > 0 && (
<div>
<label className="block text-[11px] uppercase tracking-widest font-bold text-muted-foreground mb-2">
{t('notebook.parentNotebook') || 'Carnet parent'}
</label>
<select
value={selectedParentId || ''}
onChange={(e) => setSelectedParentId(e.target.value || null)}
className="w-full bg-white dark:bg-zinc-800 border border-black/12 dark:border-white/15 rounded-lg px-4 py-3 outline-none focus:border-foreground/40 transition-colors text-sm text-foreground"
>
<option value="">{t('notebook.noParent') || 'Aucun (racine)'}</option>
{rootNotebooks.map(nb => (
<option key={nb.id} value={nb.id}>{nb.name}</option>
))}
</select>
</div>
)}
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={handleClose}
className="flex-1 py-3 border border-black/12 dark:border-white/12 rounded-xl text-sm font-medium text-muted-foreground hover:bg-black/5 dark:hover:bg-white/5 transition-colors"
>
{t('notebook.cancel') || 'Annuler'}
</button>
<button
type="submit"
disabled={!name.trim() || isSubmitting}
className="flex-1 py-3 bg-foreground text-background rounded-xl text-sm font-medium hover:opacity-90 transition-opacity disabled:opacity-40 disabled:cursor-not-allowed"
>
{isSubmitting
? (t('notebook.creating') || 'Création…')
: (t('notebook.create') || 'Créer')}
</button>
</div>
</form>
</motion.div>
</div>
)}
</AnimatePresence>
)
}