feat(brainstorm): UX fixes — modal confirmation + destination carnet + race fix
UX Pro Max appliqué selon les best practices 2026 (Heptabase, Obsidian, Tana):
- confirmation-dialogs (HIGH): modal de confirmation avant brainstorm
- success-feedback (MEDIUM): toast avec destination du carnet
- nav-state-active (MEDIUM): indication claire de la session active
3 fixes:
1. Modal de confirmation (brainstorm-confirm-dialog.tsx, 181 lignes):
- Design system respecté: brand-accent, memento-paper, font-memento-serif
- Aperçu seed en card paper
- Sélecteur de carnet avec animation d'expansion
- Bouton 'Annuler' + 'Lancer le brainstorm' avec loader
- role=dialog, aria-modal, aria-label
- focus-visible, keyboard nav
- backdrop-blur-sm + click-to-close
- Lazy loaded via next/dynamic
2. Race condition fix (brainstorm-page.tsx L111):
- Auto-select session désactivé si urlSeed présent
- urlSeed + urlInviteToken court-circuitent le useEffect
3. Toast avec destination (handleConvert + noteCreatedIn key):
- message: 'Note créée dans [Nom du carnet]'
- useConvertIdea accepte notebookId optionnel
- API convert accepte notebookId via zod schema
- useConvertIdea(mutationFn) accepte string | {ideaId, notebookId}
i18n: 9 nouvelles clés brainstorm.* en EN/FR
This commit is contained in:
@@ -50,6 +50,8 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useHydrated } from '@/lib/use-hydrated'
|
||||
import dynamic from 'next/dynamic'
|
||||
const BrainstormConfirmDialog = dynamic(() => import('@/components/brainstorm/brainstorm-confirm-dialog').then(m => ({ default: m.BrainstormConfirmDialog })), { ssr: false })
|
||||
import { formatDistanceToNow } from 'date-fns'
|
||||
import { fr } from 'date-fns/locale/fr'
|
||||
import { enUS } from 'date-fns/locale/en-US'
|
||||
@@ -715,14 +717,23 @@ const GridCard = memo(function GridCard({
|
||||
aiIllustrationEnabled,
|
||||
onOpen,
|
||||
onTogglePin,
|
||||
onDeleteNote,
|
||||
onMoveToNotebook,
|
||||
onDeleteNote, onMoveToNotebook,
|
||||
onNoteIllustrationGenerated,
|
||||
onNoteIllustrationDeleted,
|
||||
onOpenHistory,
|
||||
isOverlay = false,
|
||||
}: GridCardSharedProps) {
|
||||
const router = useRouter()
|
||||
const [brainstormNote, setBrainstormNote] = useState<Note | null>(null)
|
||||
|
||||
const handleBrainstormConfirm = (notebookId: string | null) => {
|
||||
if (!brainstormNote) return
|
||||
const seed = `${brainstormNote.title || untitled}\n\n${getNotePlainExcerpt(brainstormNote, 200)}`.trim()
|
||||
const params = new URLSearchParams({ seed: seed.slice(0, 300), sourceNoteId: brainstormNote.id })
|
||||
if (notebookId) params.set('notebookId', notebookId)
|
||||
router.push(`/brainstorm?${params.toString()}`)
|
||||
setBrainstormNote(null)
|
||||
}
|
||||
const { t, language } = useLanguage()
|
||||
const hydrated = useHydrated()
|
||||
const title = getNoteDisplayTitle(note, untitled)
|
||||
@@ -741,11 +752,11 @@ const GridCard = memo(function GridCard({
|
||||
|
||||
const handleBrainstormClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
const seed = `${title}\n\n${excerpt}`.trim() || title
|
||||
router.push(`/brainstorm?seed=${encodeURIComponent(seed.slice(0, 300))}&sourceNoteId=${note.id}`)
|
||||
setBrainstormNote(note)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
initial={isOverlay || !hydrated ? false : { opacity: 0, y: 15 }}
|
||||
animate={isOverlay ? undefined : { opacity: 1, y: 0 }}
|
||||
@@ -851,5 +862,13 @@ const GridCard = memo(function GridCard({
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<BrainstormConfirmDialog
|
||||
open={brainstormNote !== null}
|
||||
onClose={() => setBrainstormNote(null)}
|
||||
onConfirm={(notebookId) => { if (brainstormNote) handleBrainstormConfirm(notebookId) }}
|
||||
note={brainstormNote}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user