feat: dashboard Second Brain, essai 7 jours et vérification e-mail
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 7m14s
CI / Deploy production (on server) (push) Successful in 1m25s

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>
This commit is contained in:
Antigravity
2026-08-30 07:19:36 +00:00
parent 69c99e4f4f
commit 80ccc1f6de
95 changed files with 4158 additions and 618 deletions

View File

@@ -3,7 +3,7 @@
import { useState, useEffect, useCallback, useMemo, type ReactNode } from 'react'
import { useRouter } from 'next/navigation'
import { useReducedMotion } from 'motion/react'
import { Inbox, Send, Bell, Mail } from 'lucide-react'
import { Inbox, Send, Bell, Mail, Loader2 } from 'lucide-react'
import { useLanguage } from '@/lib/i18n'
import { useAiConsent } from '@/components/legal/ai-consent-provider'
import { redirectToAiConsentSettings } from '@/lib/consent/ai-consent-redirect'
@@ -152,7 +152,7 @@ interface MindMapData {
}
interface DashboardViewProps {
onNoteSelect: (noteId: string, notebookId: string | null) => void
onNoteSelect: (noteId: string, notebookId: string | null, peekNoteId?: string | null) => void
}
interface GmailStatus {
@@ -217,6 +217,7 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
const [data, setData] = useState<{
recentNotes: BriefingNote[]
inboxCount: number
inboxPreview?: Array<{ id: string; title: string | null; notebookId: string | null }>
dueFlashcards: number
upcomingReminders: BriefingReminder[]
insights: BriefingInsight[]
@@ -491,9 +492,14 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
} : prev)
}, [])
const handleOpenFromInsight = useCallback(async (insight: BriefingInsight, noteId: string) => {
const handleOpenFromInsight = useCallback(async (
insight: BriefingInsight,
noteId: string,
peekNoteId?: string | null,
) => {
await markInsightViewed(insight.id)
onNoteSelect(noteId, null)
const peek = peekNoteId && peekNoteId !== noteId ? peekNoteId : undefined
onNoteSelect(noteId, null, peek)
}, [markInsightViewed, onNoteSelect])
const handleDismissInsight = useCallback(async (insight: BriefingInsight) => {
@@ -636,18 +642,14 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
if (path.noteId) onNoteSelect(path.noteId, path.notebookId ?? null)
break
case 'compare':
if (path.noteId) onNoteSelect(path.noteId, path.notebookId ?? null)
if (path.note2Id) toast.info(t('homeDashboard.pathCompareHint', { title: path.title }))
if (path.noteId) onNoteSelect(path.noteId, path.notebookId ?? null, path.note2Id)
break
case 'addLink':
if (path.noteId) {
onNoteSelect(path.noteId, path.notebookId ?? null)
toast.info(t('homeDashboard.pathAddLinkHint', { link: path.title }))
}
if (path.noteId) onNoteSelect(path.noteId, path.notebookId ?? null, path.note2Id)
break
case 'openInsight': {
const insight = insights.find(i => i.id === path.insightId)
if (insight && path.noteId) handleOpenFromInsight(insight, path.noteId)
if (insight && path.noteId) handleOpenFromInsight(insight, path.noteId, path.note2Id)
break
}
case 'createBridge': {
@@ -772,8 +774,11 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
onClick={handleCapture}
disabled={!captureText.trim() || capturing}
className="absolute bottom-2.5 end-2.5 p-2 bg-ink text-white dark:bg-white dark:text-black rounded-lg disabled:opacity-25 hover:scale-105 active:scale-95 transition-all shadow-sm"
aria-busy={capturing}
>
<Send size={12} />
{capturing
? <Loader2 size={12} className="animate-spin" />
: <Send size={12} />}
</button>
</div>,
)
@@ -952,6 +957,7 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
bridgeNotes={mindMap?.bridgeNotes ?? []}
loading={mindMapLoading}
onOpenInsights={() => router.push('/insights')}
onOpenCluster={(clusterId) => router.push(`/insights?cluster=${clusterId}`)}
onNoteSelect={(nid) => onNoteSelect(nid, null)}
prefersReducedMotion={!!prefersReducedMotion}
/>,
@@ -983,8 +989,10 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
return wrap(
<DashboardInboxWidget
count={inboxCount}
notes={data?.inboxPreview ?? []}
loading={briefingLoading}
onOpen={() => router.push('/home?forceList=1')}
onSelect={onNoteSelect}
/>,
)
case 'revision':
@@ -1114,7 +1122,17 @@ export function DashboardView({ onNoteSelect }: DashboardViewProps) {
</header>
<div className="pb-24">
<DashboardWidgetGrid renderWidget={renderWidget} />
<DashboardWidgetGrid
renderWidget={renderWidget}
isWidgetEmpty={(id) => {
if (briefingLoading) return false
if (id === 'sentiment') return !sentimentLoading && (!sentiment?.available || !sentiment?.dominantEmotion)
if (id === 'reminders') return reminders.length === 0
if (id === 'revision') return dueFlashcards === 0
if (id === 'inbox') return inboxCount === 0
return false
}}
/>
</div>
</div>
</div>