Files
Momento/memento-note/components/note-editor/note-editor-peek-host.tsx
Antigravity 80ccc1f6de
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 7m14s
CI / Deploy production (on server) (push) Successful in 1m25s
feat: dashboard Second Brain, essai 7 jours et vérification e-mail
Rendre le dashboard actionnable (inbox, peek, carte mentale), aligner la facturation sur l’essai 7 jours, et bloquer le login e-mail tant que l’adresse n’est pas confirmée.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 07:19:36 +00:00

122 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect, useCallback, type ReactNode } from 'react'
import { AnimatePresence } from 'framer-motion'
import { useRouter, useSearchParams } from 'next/navigation'
import { toast } from 'sonner'
import type { Note } from '@/lib/types'
import { getNoteById } from '@/app/actions/notes'
import { NOTE_REQUEST_SAVE_EVENT } from '@/lib/note-change-sync'
import {
NOTE_PEEK_OPEN_EVENT,
NOTE_PEEK_CLOSE_EVENT,
type NotePeekOpenDetail,
} from '@/lib/note-peek-sync'
import { NoteEditorSplitPeek } from './note-editor-split-peek'
import { useLanguage } from '@/lib/i18n'
interface NoteEditorPeekHostProps {
noteId: string
fullPage?: boolean
children: ReactNode
}
export function NoteEditorPeekHost({ noteId, fullPage, children }: NoteEditorPeekHostProps) {
const router = useRouter()
const searchParams = useSearchParams()
const peekNoteId = searchParams.get('peekNote')
const { t, language } = useLanguage()
const isRtl = language === 'fa' || language === 'ar'
const [peekState, setPeekState] = useState<{ note: Note; blockId?: string } | null>(null)
const stripPeekFromUrl = useCallback(() => {
if (!searchParams.get('peekNote')) return
const params = new URLSearchParams(searchParams.toString())
params.delete('peekNote')
const qs = params.toString()
router.replace(qs ? `/home?${qs}` : '/home', { scroll: false })
}, [router, searchParams])
useEffect(() => {
const onOpenPeek = (event: Event) => {
const detail = (event as CustomEvent<NotePeekOpenDetail>).detail
if (!detail?.noteId) return
if (detail.noteId === noteId) return
void getNoteById(detail.noteId).then((fetched) => {
if (fetched) {
setPeekState({ note: fetched, blockId: detail.blockId })
} else {
toast.error(t('notePeek.loadFailed'))
}
})
}
const onClosePeek = () => setPeekState(null)
window.addEventListener(NOTE_PEEK_OPEN_EVENT, onOpenPeek)
window.addEventListener(NOTE_PEEK_CLOSE_EVENT, onClosePeek)
return () => {
window.removeEventListener(NOTE_PEEK_OPEN_EVENT, onOpenPeek)
window.removeEventListener(NOTE_PEEK_CLOSE_EVENT, onClosePeek)
}
}, [noteId, t])
// Dashboard « Comparer / Lier » : ouvrir la note liée à droite dès que léditeur est monté.
useEffect(() => {
if (!peekNoteId || peekNoteId === noteId) return
let cancelled = false
void getNoteById(peekNoteId).then((fetched) => {
if (cancelled) return
if (fetched) {
setPeekState(prev => (prev?.note.id === fetched.id ? prev : { note: fetched }))
} else {
toast.error(t('notePeek.loadFailed'))
}
})
return () => { cancelled = true }
}, [peekNoteId, noteId, t])
const handleClosePeek = useCallback(() => {
setPeekState(null)
stripPeekFromUrl()
}, [stripPeekFromUrl])
const handleOpenPeekFully = useCallback(() => {
if (!peekState) return
window.dispatchEvent(new CustomEvent(NOTE_REQUEST_SAVE_EVENT, {
detail: { noteId, reason: 'before-peek-full-open' },
}))
const params = new URLSearchParams(searchParams.toString())
params.set('openNote', peekState.note.id)
params.delete('peekNote')
router.replace(params.toString() ? `/home?${params.toString()}` : '/home', { scroll: false })
setPeekState(null)
}, [noteId, peekState, router, searchParams])
const shellClass = fullPage
? 'flex flex-1 min-h-0 h-full w-full items-stretch overflow-hidden'
: 'relative flex min-h-0 flex-1 flex-col overflow-hidden'
return (
<div className={`${shellClass}${peekState ? (isRtl ? ' flex-row-reverse' : ' flex-row') : ''}`}>
<div className="flex-1 min-w-0 flex flex-col overflow-hidden">{children}</div>
<AnimatePresence initial={false}>
{peekState && (
<NoteEditorSplitPeek
key={peekState.note.id}
note={peekState.note}
blockId={peekState.blockId}
onClose={handleClosePeek}
onOpenFully={handleOpenPeekFully}
onBackToDashboard={
searchParams.get('from') === 'dashboard'
? () => router.replace('/home')
: undefined
}
/>
)}
</AnimatePresence>
</div>
)
}