feat(brainstorm): UX fixes — modal confirmation + destination carnet + race fix
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 4m29s
CI / Deploy production (on server) (push) Has been skipped

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:
Antigravity
2026-07-05 19:44:07 +00:00
parent d1336b1fea
commit 02b835fb13
7 changed files with 234 additions and 14 deletions

View File

@@ -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}
/>
</>
)
})