Briefing granulaire, pistes rapides puis enrichissement async, layout persisté v5, suggestions agents, intégration Gmail et navigation sidebar alignée sur /home. Co-authored-by: Cursor <cursoragent@cursor.com>
166 lines
6.5 KiB
TypeScript
166 lines
6.5 KiB
TypeScript
'use client'
|
|
|
|
import { motion } from 'motion/react'
|
|
import { Loader2 } from 'lucide-react'
|
|
import {
|
|
ArrowRight, GitBranch, Lightbulb, Link2, BookOpen, Inbox,
|
|
GraduationCap, Compass, Plus, Sparkles, PenLine,
|
|
} from 'lucide-react'
|
|
import type { LucideIcon } from 'lucide-react'
|
|
import { useLanguage } from '@/lib/i18n'
|
|
import { DashboardWidgetTitleRow } from '@/components/dashboard-widget-title-row'
|
|
import type { DashboardPath, DashboardPathType } from '@/lib/dashboard/path-types'
|
|
|
|
const TYPE_META: Record<DashboardPathType, { Icon: LucideIcon; accent: string }> = {
|
|
continue: { Icon: PenLine, accent: 'text-ink' },
|
|
connect: { Icon: Link2, accent: 'text-brand-accent' },
|
|
'add-link': { Icon: Plus, accent: 'text-emerald-600' },
|
|
bridge: { Icon: GitBranch, accent: 'text-violet-600' },
|
|
research: { Icon: Sparkles, accent: 'text-sky-600' },
|
|
explore: { Icon: Compass, accent: 'text-amber-600' },
|
|
organize: { Icon: Inbox, accent: 'text-brand-accent' },
|
|
review: { Icon: GraduationCap, accent: 'text-brand-accent' },
|
|
resurface: { Icon: Lightbulb, accent: 'text-brand-accent' },
|
|
daily: { Icon: BookOpen, accent: 'text-concrete' },
|
|
}
|
|
|
|
export interface DashboardNextPathsProps {
|
|
paths: DashboardPath[]
|
|
loading?: boolean
|
|
enriching?: boolean
|
|
focusNoteTitle?: string | null
|
|
onAction: (path: DashboardPath) => void
|
|
prefersReducedMotion?: boolean
|
|
}
|
|
|
|
export function DashboardNextPaths({
|
|
paths,
|
|
loading,
|
|
enriching,
|
|
focusNoteTitle,
|
|
onAction,
|
|
prefersReducedMotion,
|
|
}: DashboardNextPathsProps) {
|
|
const { t } = useLanguage()
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="rounded-2xl border border-brand-accent/20 bg-gradient-to-br from-white via-white to-brand-accent/[0.04] dark:from-zinc-900 dark:via-zinc-900 dark:to-brand-accent/[0.06] shadow-sm overflow-hidden">
|
|
<div className="px-5 pt-4 pb-3 border-b border-border/15">
|
|
<DashboardWidgetTitleRow
|
|
widgetId="next-paths"
|
|
title={t('homeDashboard.pathsTitle')}
|
|
className="mb-0"
|
|
/>
|
|
{focusNoteTitle && (
|
|
<p className="text-[11px] text-concrete mt-1">
|
|
{t('homeDashboard.pathsFromNote', { title: focusNoteTitle })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col items-center justify-center gap-3 px-5 py-10">
|
|
<Loader2 size={22} className="text-brand-accent animate-spin" strokeWidth={2} />
|
|
<p className="text-[11px] text-concrete text-center leading-relaxed max-w-[240px]">
|
|
{t('homeDashboard.pathsLoading')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (paths.length === 0) {
|
|
return (
|
|
<div className="rounded-2xl border border-border/25 bg-white/60 dark:bg-zinc-900/60 p-5">
|
|
<DashboardWidgetTitleRow
|
|
widgetId="next-paths"
|
|
title={t('homeDashboard.pathsTitle')}
|
|
className="mb-2"
|
|
/>
|
|
<p className="text-[10px] text-concrete italic">{t('homeDashboard.pathsEmpty')}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const hero = paths[0]
|
|
const rest = paths.slice(1, 5)
|
|
const HeroIcon = TYPE_META[hero.type].Icon
|
|
|
|
return (
|
|
<div className="rounded-2xl border border-brand-accent/20 bg-gradient-to-br from-white via-white to-brand-accent/[0.04] dark:from-zinc-900 dark:via-zinc-900 dark:to-brand-accent/[0.06] shadow-sm overflow-hidden">
|
|
<div className="px-5 pt-4 pb-3 border-b border-border/15">
|
|
<DashboardWidgetTitleRow
|
|
widgetId="next-paths"
|
|
title={t('homeDashboard.pathsTitle')}
|
|
className="mb-0"
|
|
actions={enriching ? (
|
|
<span className="inline-flex items-center gap-1.5 text-[8px] font-mono uppercase text-concrete">
|
|
<Loader2 size={10} className="text-brand-accent animate-spin" />
|
|
{t('homeDashboard.pathsEnriching')}
|
|
</span>
|
|
) : undefined}
|
|
/>
|
|
{focusNoteTitle && (
|
|
<p className="text-[11px] text-concrete mt-1">
|
|
{t('homeDashboard.pathsFromNote', { title: focusNoteTitle })}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<motion.button
|
|
type="button"
|
|
onClick={() => onAction(hero)}
|
|
whileHover={prefersReducedMotion ? undefined : { y: -1 }}
|
|
className="w-full text-start px-5 py-4 hover:bg-brand-accent/[0.03] transition-colors border-b border-border/10"
|
|
>
|
|
<div className="flex items-start gap-3">
|
|
<div className={`w-9 h-9 rounded-xl bg-brand-accent/10 flex items-center justify-center shrink-0 ${TYPE_META[hero.type].accent}`}>
|
|
<HeroIcon size={16} strokeWidth={1.75} />
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-[8px] font-mono font-bold uppercase tracking-wider text-concrete mb-0.5">
|
|
{t(`homeDashboard.pathTypes.${hero.type}`)}
|
|
{hero.score ? ` · ${hero.score}%` : ''}
|
|
</p>
|
|
<p className="text-sm font-serif font-semibold text-ink dark:text-dark-ink leading-snug">
|
|
{hero.title}
|
|
</p>
|
|
<p className="text-[11px] text-concrete leading-relaxed mt-1 line-clamp-2">
|
|
{hero.description}
|
|
</p>
|
|
</div>
|
|
<span className="shrink-0 inline-flex items-center gap-1 text-[9px] font-mono font-bold uppercase text-brand-accent mt-1">
|
|
{t(`homeDashboard.pathActions.${hero.actionKey}`)}
|
|
<ArrowRight size={10} />
|
|
</span>
|
|
</div>
|
|
</motion.button>
|
|
|
|
{rest.length > 0 && (
|
|
<div className="divide-y divide-border/10">
|
|
{rest.map(path => {
|
|
const meta = TYPE_META[path.type]
|
|
const Icon = meta.Icon
|
|
return (
|
|
<button
|
|
key={path.id}
|
|
type="button"
|
|
onClick={() => onAction(path)}
|
|
className="w-full flex items-center gap-3 px-5 py-3 text-start hover:bg-stone-50/80 dark:hover:bg-zinc-950/40 transition-colors"
|
|
>
|
|
<Icon size={13} className={`shrink-0 ${meta.accent}`} />
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-[11px] font-medium text-ink dark:text-dark-ink truncate">{path.title}</p>
|
|
<p className="text-[9px] text-concrete truncate">{path.description}</p>
|
|
</div>
|
|
<span className="text-[8px] font-mono uppercase text-brand-accent shrink-0">
|
|
{t(`homeDashboard.pathActions.${path.actionKey}`)}
|
|
</span>
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|