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>
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user