fix: unify theme system - fix theme switching persistence
- Unified localStorage key to 'theme-preference' across all components
- Fixed header.tsx using wrong localStorage key ('theme' instead of 'theme-preference')
- Added localStorage hybrid persistence for instant theme changes
- Removed router.refresh() which was causing stale data revert
- Replaced Blue theme with Sepia
- Consolidated auth() calls to prevent race conditions
- Updated UserSettingsData types to include all themes
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useCallback } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { StickyNote, Plus, Tag as TagIcon, Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, LucideIcon } from 'lucide-react'
|
||||
import { StickyNote, Plus, Tag, Folder, Briefcase, FileText, Zap, BarChart3, Globe, Sparkles, Book, Heart, Crown, Music, Building2, LucideIcon, Plane, ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import { useNotebooks } from '@/context/notebooks-context'
|
||||
import { useNotebookDrag } from '@/context/notebook-drag-context'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -13,6 +14,7 @@ import { DeleteNotebookDialog } from './delete-notebook-dialog'
|
||||
import { EditNotebookDialog } from './edit-notebook-dialog'
|
||||
import { NotebookSummaryDialog } from './notebook-summary-dialog'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { useLabels } from '@/context/LabelContext'
|
||||
|
||||
// Map icon names to lucide-react components
|
||||
const ICON_MAP: Record<string, LucideIcon> = {
|
||||
@@ -28,6 +30,7 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
||||
'crown': Crown,
|
||||
'music': Music,
|
||||
'building': Building2,
|
||||
'flight_takeoff': Plane,
|
||||
}
|
||||
|
||||
// Function to get icon component by name
|
||||
@@ -43,23 +46,24 @@ export function NotebooksList() {
|
||||
const { t } = useLanguage()
|
||||
const { notebooks, currentNotebook, deleteNotebook, moveNoteToNotebookOptimistic, isLoading } = useNotebooks()
|
||||
const { draggedNoteId, dragOverNotebookId, dragOver } = useNotebookDrag()
|
||||
const { labels } = useLabels()
|
||||
|
||||
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
|
||||
const [editingNotebook, setEditingNotebook] = useState<any>(null)
|
||||
const [deletingNotebook, setDeletingNotebook] = useState<any>(null)
|
||||
const [summaryNotebook, setSummaryNotebook] = useState<any>(null) // NEW: Summary dialog state (IA6)
|
||||
const [summaryNotebook, setSummaryNotebook] = useState<any>(null)
|
||||
const [expandedNotebook, setExpandedNotebook] = useState<string | null>(null)
|
||||
|
||||
const currentNotebookId = searchParams.get('notebook')
|
||||
|
||||
// Handle drop on a notebook
|
||||
const handleDrop = useCallback(async (e: React.DragEvent, notebookId: string | null) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation() // Prevent triggering notebook click
|
||||
e.stopPropagation()
|
||||
const noteId = e.dataTransfer.getData('text/plain')
|
||||
|
||||
if (noteId) {
|
||||
await moveNoteToNotebookOptimistic(noteId, notebookId)
|
||||
// No need for router.refresh() - triggerRefresh() is already called in moveNoteToNotebookOptimistic
|
||||
}
|
||||
|
||||
dragOver(null)
|
||||
@@ -92,14 +96,27 @@ export function NotebooksList() {
|
||||
router.push(`/?${params.toString()}`)
|
||||
}
|
||||
|
||||
const handleToggleExpand = (notebookId: string) => {
|
||||
setExpandedNotebook(expandedNotebook === notebookId ? null : notebookId)
|
||||
}
|
||||
|
||||
const handleLabelFilter = (labelName: string, notebookId: string) => {
|
||||
const params = new URLSearchParams(searchParams)
|
||||
const currentLabels = params.get('labels')?.split(',').filter(Boolean) || []
|
||||
|
||||
if (currentLabels.includes(labelName)) {
|
||||
params.set('labels', currentLabels.filter((l: string) => l !== labelName).join(','))
|
||||
} else {
|
||||
params.set('labels', [...currentLabels, labelName].join(','))
|
||||
}
|
||||
|
||||
params.set('notebook', notebookId)
|
||||
router.push(`/?${params.toString()}`)
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="my-2">
|
||||
<div className="px-4 mb-2">
|
||||
<span className="text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
{t('nav.notebooks')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="px-4 py-2">
|
||||
<div className="text-xs text-gray-500">{t('common.loading')}</div>
|
||||
</div>
|
||||
@@ -109,93 +126,143 @@ export function NotebooksList() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Notebooks Section */}
|
||||
<div className="my-2">
|
||||
{/* Section Header */}
|
||||
<div className="px-4 flex items-center justify-between mb-1">
|
||||
<span className="text-xs font-medium text-slate-400 dark:text-slate-500 uppercase tracking-wider">
|
||||
{t('nav.notebooks')}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300"
|
||||
<div className="flex flex-col pt-1">
|
||||
{/* Header with Add Button */}
|
||||
<div className="flex items-center justify-between px-6 py-2 mt-2 group cursor-pointer text-gray-500 hover:text-gray-800 dark:hover:text-gray-300">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider">{t('nav.notebooks') || 'NOTEBOOKS'}</span>
|
||||
<button
|
||||
onClick={() => setIsCreateDialogOpen(true)}
|
||||
className="p-1 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full transition-colors"
|
||||
title={t('notebooks.create') || 'Create notebook'}
|
||||
>
|
||||
<Plus className="h-3 w-3" />
|
||||
</Button>
|
||||
<Plus className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* "Notes générales" (Inbox) */}
|
||||
<button
|
||||
onClick={() => handleSelectNotebook(null)}
|
||||
onDrop={(e) => handleDrop(e, null)}
|
||||
onDragOver={(e) => handleDragOver(e, null)}
|
||||
onDragLeave={handleDragLeave}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-200",
|
||||
!currentNotebookId && pathname === '/' && !searchParams.get('search')
|
||||
? "bg-[#FEF3C6] text-amber-900 shadow-lg"
|
||||
: "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:translate-x-1"
|
||||
)}
|
||||
>
|
||||
<StickyNote className="h-5 w-5" />
|
||||
<span className={cn("text-sm font-medium", !currentNotebookId && pathname === '/' && !searchParams.get('search') && "font-semibold")}>{t('nav.generalNotes')}</span>
|
||||
</button>
|
||||
|
||||
{/* Notebooks List */}
|
||||
{/* Notebooks Loop */}
|
||||
{notebooks.map((notebook: any) => {
|
||||
const isActive = currentNotebookId === notebook.id
|
||||
const isExpanded = expandedNotebook === notebook.id
|
||||
const isDragOver = dragOverNotebookId === notebook.id
|
||||
|
||||
// Get the icon component
|
||||
// Get icon component
|
||||
const NotebookIcon = getNotebookIcon(notebook.icon || 'folder')
|
||||
|
||||
return (
|
||||
<div key={notebook.id} className="group flex items-center">
|
||||
<button
|
||||
onClick={() => handleSelectNotebook(notebook.id)}
|
||||
onDrop={(e) => handleDrop(e, notebook.id)}
|
||||
onDragOver={(e) => handleDragOver(e, notebook.id)}
|
||||
onDragLeave={handleDragLeave}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all duration-200",
|
||||
isActive
|
||||
? "bg-[#FEF3C6] text-amber-900 shadow-lg"
|
||||
: "text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800/50 hover:translate-x-1",
|
||||
isDragOver && "ring-2 ring-blue-500 ring-dashed"
|
||||
)}
|
||||
>
|
||||
{/* Icon with notebook color */}
|
||||
<div key={notebook.id} className="group flex flex-col">
|
||||
{isActive ? (
|
||||
// Active notebook with expanded labels - STYLE MATCH Sidebar
|
||||
<div
|
||||
className="h-5 w-5 rounded flex items-center justify-center"
|
||||
style={{
|
||||
backgroundColor: isActive ? 'white' : notebook.color || '#6B7280',
|
||||
color: isActive ? (notebook.color || '#6B7280') : 'white'
|
||||
}}
|
||||
onDrop={(e) => handleDrop(e, notebook.id)}
|
||||
onDragOver={(e) => handleDragOver(e, notebook.id)}
|
||||
onDragLeave={handleDragLeave}
|
||||
className={cn(
|
||||
"flex flex-col mr-2 rounded-r-full overflow-hidden transition-all",
|
||||
!notebook.color && "bg-blue-50 dark:bg-blue-900/20",
|
||||
isDragOver && "ring-2 ring-blue-500 ring-dashed"
|
||||
)}
|
||||
style={notebook.color ? { backgroundColor: `${notebook.color}20` } : undefined}
|
||||
>
|
||||
<NotebookIcon className="h-3 w-3" />
|
||||
</div>
|
||||
<span className={cn("truncate flex-1 text-left text-sm", isActive && "font-semibold")}>{notebook.name}</span>
|
||||
{notebook.notesCount > 0 && (
|
||||
<span className={cn(
|
||||
"ml-auto text-[10px] font-medium px-1.5 py-0.5 rounded",
|
||||
isActive
|
||||
? "bg-amber-900/20 text-amber-900"
|
||||
: "text-gray-500"
|
||||
)}>
|
||||
{notebook.notesCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
{/* Header - allow pointer events for expand button */}
|
||||
<div className="pointer-events-auto flex items-center justify-between px-6 py-3">
|
||||
<div className="flex items-center gap-4 min-w-0">
|
||||
<NotebookIcon
|
||||
className={cn("w-5 h-5 flex-shrink-0 fill-current", !notebook.color && "text-blue-700 dark:text-blue-100")}
|
||||
style={notebook.color ? { color: notebook.color } : undefined}
|
||||
/>
|
||||
<span
|
||||
className={cn("text-sm font-medium tracking-wide truncate max-w-[120px]", !notebook.color && "text-blue-700 dark:text-blue-100")}
|
||||
style={notebook.color ? { color: notebook.color } : undefined}
|
||||
>
|
||||
{notebook.name}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleToggleExpand(notebook.id)}
|
||||
className={cn("transition-colors p-1 flex-shrink-0", !notebook.color && "text-blue-600 hover:text-blue-800 dark:text-blue-200 dark:hover:text-blue-100")}
|
||||
style={notebook.color ? { color: notebook.color } : undefined}
|
||||
>
|
||||
<ChevronDown className={cn("w-4 h-4 transition-transform", isExpanded && "rotate-180")} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Actions (visible on hover) */}
|
||||
<NotebookActions
|
||||
notebook={notebook}
|
||||
onEdit={() => setEditingNotebook(notebook)}
|
||||
onDelete={() => setDeletingNotebook(notebook)}
|
||||
onSummary={() => setSummaryNotebook(notebook)} // NEW: Summary action (IA6)
|
||||
/>
|
||||
{/* Contextual Labels Tree */}
|
||||
{isExpanded && labels.length > 0 && (
|
||||
<div className="flex flex-col pb-2">
|
||||
{labels.map((label: any) => (
|
||||
<button
|
||||
key={label.id}
|
||||
onClick={() => handleLabelFilter(label.name, notebook.id)}
|
||||
className={cn(
|
||||
"pointer-events-auto flex items-center gap-4 pl-12 pr-4 py-2 hover:bg-black/5 dark:hover:bg-white/10 transition-colors rounded-r-full mr-2",
|
||||
searchParams.get('labels')?.includes(label.name) && "font-bold text-gray-900 dark:text-white"
|
||||
)}
|
||||
>
|
||||
<Tag className="w-4 h-4 text-gray-500" />
|
||||
<span className="text-xs font-medium text-gray-600 dark:text-gray-300">
|
||||
{label.name}
|
||||
</span>
|
||||
</button>
|
||||
))}
|
||||
<button
|
||||
onClick={() => router.push('/settings/labels')}
|
||||
className="pointer-events-auto flex items-center gap-2 pl-12 pr-4 py-2 mt-1 text-gray-500 hover:text-gray-800 hover:bg-black/5 dark:hover:bg-white/10 rounded-r-full mr-2 transition-colors group/label"
|
||||
>
|
||||
<Plus className="w-3 h-3 group-hover/label:scale-110 transition-transform" />
|
||||
<span className="text-xs font-medium opacity-80">{t('sidebar.editLabels') || 'Edit Labels'}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
// Inactive notebook
|
||||
<div
|
||||
onDrop={(e) => handleDrop(e, notebook.id)}
|
||||
onDragOver={(e) => handleDragOver(e, notebook.id)}
|
||||
onDragLeave={handleDragLeave}
|
||||
className={cn(
|
||||
"flex items-center group relative",
|
||||
isDragOver && "ring-2 ring-blue-500 ring-dashed rounded-r-full mr-2"
|
||||
)}
|
||||
>
|
||||
<div className="w-full flex">
|
||||
<button
|
||||
onClick={() => handleSelectNotebook(notebook.id)}
|
||||
className={cn(
|
||||
"pointer-events-auto flex items-center gap-4 px-6 py-3 rounded-r-full mr-2 text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800/50 transition-colors w-full pr-10",
|
||||
isDragOver && "opacity-50"
|
||||
)}
|
||||
>
|
||||
<NotebookIcon className="w-5 h-5 flex-shrink-0" />
|
||||
<span className="text-sm font-medium tracking-wide truncate flex-1 text-left">{notebook.name}</span>
|
||||
{notebook.notesCount > 0 && (
|
||||
<span className="text-xs text-gray-400 ml-2 flex-shrink-0">({notebook.notesCount})</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Expand button separate from main click */}
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); handleToggleExpand(notebook.id); }}
|
||||
className={cn(
|
||||
"absolute right-4 top-1/2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 transition-colors rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 opacity-0 group-hover:opacity-100",
|
||||
expandedNotebook === notebook.id && "opacity-100 rotate-180"
|
||||
)}
|
||||
>
|
||||
<ChevronDown className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Actions (visible on hover) */}
|
||||
<div className="absolute right-2 opacity-0 group-hover:opacity-100 transition-opacity z-10" style={{ right: '40px' }}>
|
||||
<NotebookActions
|
||||
notebook={notebook}
|
||||
onEdit={() => setEditingNotebook(notebook)}
|
||||
onDelete={() => setDeletingNotebook(notebook)}
|
||||
onSummary={() => setSummaryNotebook(notebook)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
@@ -229,7 +296,7 @@ export function NotebooksList() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Notebook Summary Dialog (IA6) */}
|
||||
{/* Notebook Summary Dialog */}
|
||||
<NotebookSummaryDialog
|
||||
open={!!summaryNotebook}
|
||||
onOpenChange={(open) => {
|
||||
|
||||
Reference in New Issue
Block a user