chore: snapshot before performance optimization
This commit is contained in:
20
keep-notes/app/(main)/admin/loading.tsx
Normal file
20
keep-notes/app/(main)/admin/loading.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function AdminLoading() {
|
||||
return (
|
||||
<div className="space-y-6 animate-pulse">
|
||||
<div>
|
||||
<div className="h-9 w-48 bg-muted rounded-md mb-2" />
|
||||
<div className="h-4 w-72 bg-muted rounded-md" />
|
||||
</div>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className="rounded-lg border border-border bg-white dark:bg-zinc-900 p-6 space-y-4">
|
||||
<div className="h-5 w-40 bg-muted rounded" />
|
||||
<div className="h-px bg-border" />
|
||||
<div className="space-y-3">
|
||||
<div className="h-4 w-full bg-muted rounded" />
|
||||
<div className="h-4 w-3/4 bg-muted rounded" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -9,8 +9,11 @@ export default async function MainLayout({
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
const session = await auth();
|
||||
const initialLanguage = await detectUserLanguage();
|
||||
// Run auth + language detection in parallel
|
||||
const [session, initialLanguage] = await Promise.all([
|
||||
auth(),
|
||||
detectUserLanguage(),
|
||||
]);
|
||||
|
||||
return (
|
||||
<ProvidersWrapper initialLanguage={initialLanguage}>
|
||||
|
||||
@@ -449,7 +449,8 @@ export default function HomePage() {
|
||||
onEdit={(note, readOnly) => setEditingNote({ note, readOnly })}
|
||||
/>
|
||||
|
||||
{!isTabs && showRecentNotes && (
|
||||
{/* Recent notes section hidden in masonry mode — notes are already visible in the grid below */}
|
||||
{false && !isTabs && showRecentNotes && (
|
||||
<RecentNotesSection
|
||||
recentNotes={recentNotes}
|
||||
onEdit={(note, readOnly) => setEditingNote({ note, readOnly })}
|
||||
|
||||
@@ -1,238 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { SettingsSection } from '@/components/settings'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Loader2, CheckCircle, XCircle, RefreshCw, Database, BrainCircuit } from 'lucide-react'
|
||||
import { cleanupAllOrphans, syncAllEmbeddings } from '@/app/actions/notes'
|
||||
import { toast } from 'sonner'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import Link from 'next/link'
|
||||
import { useLabels } from '@/context/LabelContext'
|
||||
import { useNotebooks } from '@/context/notebooks-context'
|
||||
import { useNoteRefresh } from '@/context/NoteRefreshContext'
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { t } = useLanguage()
|
||||
const router = useRouter()
|
||||
const { refreshLabels } = useLabels()
|
||||
const { refreshNotebooks } = useNotebooks()
|
||||
const { triggerRefresh } = useNoteRefresh()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [cleanupLoading, setCleanupLoading] = useState(false)
|
||||
const [syncLoading, setSyncLoading] = useState(false)
|
||||
const [status, setStatus] = useState<'idle' | 'success' | 'error'>('idle')
|
||||
const [result, setResult] = useState<any>(null)
|
||||
const [config, setConfig] = useState<any>(null)
|
||||
|
||||
const checkConnection = async () => {
|
||||
setLoading(true)
|
||||
setStatus('idle')
|
||||
setResult(null)
|
||||
try {
|
||||
const res = await fetch('/api/ai/test')
|
||||
const data = await res.json()
|
||||
|
||||
setConfig({
|
||||
provider: data.provider,
|
||||
status: res.ok ? 'connected' : 'disconnected'
|
||||
})
|
||||
|
||||
if (res.ok) {
|
||||
setStatus('success')
|
||||
setResult(data)
|
||||
} else {
|
||||
setStatus('error')
|
||||
setResult(data)
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error)
|
||||
setStatus('error')
|
||||
setResult({ message: error.message, stack: error.stack })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSync = async () => {
|
||||
setSyncLoading(true)
|
||||
try {
|
||||
const result = await syncAllEmbeddings()
|
||||
if (result.success) {
|
||||
toast.success(t('settings.indexingComplete', { count: result.count ?? 0 }))
|
||||
triggerRefresh()
|
||||
router.refresh()
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast.error(t('settings.indexingError'))
|
||||
} finally {
|
||||
setSyncLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCleanup = async () => {
|
||||
setCleanupLoading(true)
|
||||
try {
|
||||
const result = await cleanupAllOrphans()
|
||||
if (result.success) {
|
||||
const errCount = Array.isArray(result.errors) ? result.errors.length : 0
|
||||
if (result.created === 0 && result.deleted === 0 && errCount === 0) {
|
||||
toast.info(t('settings.cleanupNothing'))
|
||||
} else {
|
||||
const base = t('settings.cleanupDone', {
|
||||
created: result.created ?? 0,
|
||||
deleted: result.deleted ?? 0,
|
||||
})
|
||||
toast.success(errCount > 0 ? `${base} (${t('settings.cleanupWithErrors')})` : base)
|
||||
}
|
||||
await refreshLabels()
|
||||
await refreshNotebooks()
|
||||
triggerRefresh()
|
||||
router.refresh()
|
||||
} else {
|
||||
toast.error(t('settings.cleanupError'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
toast.error(t('settings.cleanupError'))
|
||||
} finally {
|
||||
setCleanupLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold mb-2">{t('settings.title')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t('settings.description')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Link href="/settings/ai">
|
||||
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
|
||||
<BrainCircuit className="h-6 w-6 text-purple-500 mb-2" />
|
||||
<h3 className="font-semibold">{t('aiSettings.title')}</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{t('aiSettings.description')}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/settings/profile">
|
||||
<div className="p-4 border rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors cursor-pointer">
|
||||
<RefreshCw className="h-6 w-6 text-primary mb-2" />
|
||||
<h3 className="font-semibold">{t('profile.title')}</h3>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{t('profile.description')}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* AI Diagnostics */}
|
||||
<SettingsSection
|
||||
title={t('diagnostics.title')}
|
||||
icon={<span className="text-2xl">🔍</span>}
|
||||
description={t('diagnostics.description')}
|
||||
>
|
||||
<div className="grid grid-cols-2 gap-4 py-4">
|
||||
<div className="p-4 rounded-lg bg-secondary/50">
|
||||
<p className="text-sm font-medium text-muted-foreground mb-1">{t('diagnostics.configuredProvider')}</p>
|
||||
<p className="text-lg font-mono">{config?.provider || '...'}</p>
|
||||
</div>
|
||||
<div className="p-4 rounded-lg bg-secondary/50">
|
||||
<p className="text-sm font-medium text-muted-foreground mb-1">{t('diagnostics.apiStatus')}</p>
|
||||
<div className="flex items-center gap-2">
|
||||
{status === 'success' && <CheckCircle className="text-green-500 w-5 h-5" />}
|
||||
{status === 'error' && <XCircle className="text-red-500 w-5 h-5" />}
|
||||
<span className={`text-sm font-medium ${status === 'success' ? 'text-green-600 dark:text-green-400' :
|
||||
status === 'error' ? 'text-red-600 dark:text-red-400' :
|
||||
'text-gray-600'
|
||||
}`}>
|
||||
{status === 'success' ? t('diagnostics.operational') :
|
||||
status === 'error' ? t('diagnostics.errorStatus') :
|
||||
t('diagnostics.checking')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{result && (
|
||||
<div className="space-y-2 mt-4">
|
||||
<h3 className="text-sm font-medium">{t('diagnostics.testDetails')}</h3>
|
||||
<div className={`p-4 rounded-md font-mono text-xs overflow-x-auto ${status === 'error'
|
||||
? 'bg-red-50 text-red-900 border border-red-200 dark:bg-red-950 dark:text-red-100 dark:border-red-900'
|
||||
: 'bg-slate-950 text-slate-50'
|
||||
}`}>
|
||||
<pre>{JSON.stringify(result, null, 2)}</pre>
|
||||
</div>
|
||||
|
||||
{status === 'error' && (
|
||||
<div className="text-sm text-red-600 dark:text-red-400 mt-2">
|
||||
<p className="font-bold">{t('diagnostics.troubleshootingTitle')}</p>
|
||||
<ul className="list-disc list-inside mt-1 space-y-1">
|
||||
<li>{t('diagnostics.tip1')}</li>
|
||||
<li>{t('diagnostics.tip2')}</li>
|
||||
<li>{t('diagnostics.tip3')}</li>
|
||||
<li>{t('diagnostics.tip4')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex justify-end">
|
||||
<Button variant="outline" size="sm" onClick={checkConnection} disabled={loading}>
|
||||
{loading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <RefreshCw className="w-4 h-4 mr-2" />}
|
||||
{t('general.testConnection')}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Maintenance */}
|
||||
<SettingsSection
|
||||
title={t('settings.maintenance')}
|
||||
icon={<span className="text-2xl">🔧</span>}
|
||||
description={t('settings.maintenanceDescription')}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<div className="flex items-center justify-between gap-4 p-4 border border-border rounded-lg bg-card">
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-medium flex items-center gap-2">
|
||||
{t('settings.cleanTags')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('settings.cleanTagsDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="secondary" onClick={handleCleanup} disabled={cleanupLoading} className="shrink-0">
|
||||
{cleanupLoading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <Database className="w-4 h-4 mr-2" />}
|
||||
{t('general.clean')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-4 p-4 border border-border rounded-lg bg-card">
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-medium flex items-center gap-2">
|
||||
{t('settings.semanticIndexing')}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('settings.semanticIndexingDescription')}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="secondary" onClick={handleSync} disabled={syncLoading} className="shrink-0">
|
||||
{syncLoading ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : <BrainCircuit className="w-4 h-4 mr-2" />}
|
||||
{t('general.indexAll')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
</div>
|
||||
)
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
// Immediate redirect to the first settings sub-page
|
||||
// This avoids loading the heavy settings/page.tsx client component on first visit
|
||||
export default function SettingsIndexPage() {
|
||||
redirect('/settings/general')
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ import { auth } from '@/auth'
|
||||
import { redirect } from 'next/navigation'
|
||||
import { ProfileForm } from './profile-form'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Sparkles } from 'lucide-react'
|
||||
import { ProfilePageHeader } from '@/components/profile-page-header'
|
||||
import { AISettingsLinkCard } from './ai-settings-link-card'
|
||||
|
||||
@@ -14,30 +12,24 @@ export default async function ProfilePage() {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
select: { name: true, email: true, role: true }
|
||||
})
|
||||
// Parallel queries
|
||||
const [user, aiSettings] = await Promise.all([
|
||||
prisma.user.findUnique({
|
||||
where: { id: session.user.id },
|
||||
select: { name: true, email: true, role: true }
|
||||
}),
|
||||
prisma.userAISettings.findUnique({
|
||||
where: { userId: session.user.id }
|
||||
})
|
||||
])
|
||||
|
||||
if (!user) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
// Get user AI settings
|
||||
let userAISettings = { preferredLanguage: 'auto', showRecentNotes: false }
|
||||
try {
|
||||
const aiSettings = await prisma.userAISettings.findUnique({
|
||||
where: { userId: session.user.id }
|
||||
})
|
||||
|
||||
if (aiSettings) {
|
||||
userAISettings = {
|
||||
preferredLanguage: aiSettings.preferredLanguage || 'auto',
|
||||
showRecentNotes: aiSettings.showRecentNotes ?? false
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching AI settings:', error)
|
||||
const userAISettings = {
|
||||
preferredLanguage: aiSettings?.preferredLanguage || 'auto',
|
||||
showRecentNotes: aiSettings?.showRecentNotes ?? false
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import prisma from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { sendEmail } from '@/lib/mail'
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
async function checkAdmin() {
|
||||
const session = await auth()
|
||||
@@ -29,11 +30,9 @@ export async function testSMTP() {
|
||||
|
||||
export async function getSystemConfig() {
|
||||
await checkAdmin()
|
||||
const configs = await prisma.systemConfig.findMany()
|
||||
return configs.reduce((acc, conf) => {
|
||||
acc[conf.key] = conf.value
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
// Reuse the cached version from lib/config
|
||||
const { getSystemConfig: getCachedConfig } = await import('@/lib/config')
|
||||
return getCachedConfig()
|
||||
}
|
||||
|
||||
export async function updateSystemConfig(data: Record<string, string>) {
|
||||
@@ -45,8 +44,6 @@ export async function updateSystemConfig(data: Record<string, string>) {
|
||||
Object.entries(data).filter(([key, value]) => value !== '' && value !== null && value !== undefined)
|
||||
)
|
||||
|
||||
|
||||
|
||||
const operations = Object.entries(filteredData).map(([key, value]) =>
|
||||
prisma.systemConfig.upsert({
|
||||
where: { key },
|
||||
@@ -56,6 +53,10 @@ export async function updateSystemConfig(data: Record<string, string>) {
|
||||
)
|
||||
|
||||
await prisma.$transaction(operations)
|
||||
|
||||
// Invalidate cache after update
|
||||
revalidateTag('system-config', '/settings')
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
console.error('Failed to update settings:', error)
|
||||
|
||||
@@ -410,7 +410,6 @@ export async function createNote(data: {
|
||||
isMarkdown: data.isMarkdown || false,
|
||||
size: data.size || 'small',
|
||||
embedding: null, // Generated in background
|
||||
sharedWith: data.sharedWith && data.sharedWith.length > 0 ? JSON.stringify(data.sharedWith) : null,
|
||||
autoGenerated: data.autoGenerated || null,
|
||||
notebookId: data.notebookId || null,
|
||||
}
|
||||
|
||||
@@ -55,9 +55,10 @@ export async function GET() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse and validate embedding
|
||||
// Validate embedding
|
||||
try {
|
||||
const embedding = JSON.parse(note.embedding)
|
||||
if (!note.embedding) continue
|
||||
const embedding = JSON.parse(note.embedding) as number[]
|
||||
const validation = validateEmbedding(embedding)
|
||||
|
||||
if (!validation.valid) {
|
||||
|
||||
@@ -15,9 +15,8 @@ export async function GET() {
|
||||
notes.forEach((note: any) => {
|
||||
if (note.labels) {
|
||||
try {
|
||||
const parsed = JSON.parse(note.labels);
|
||||
if (Array.isArray(parsed)) {
|
||||
parsed.forEach((l: string) => uniqueLabels.add(l));
|
||||
if (Array.isArray(note.labels)) {
|
||||
(note.labels as string[]).forEach((l: string) => uniqueLabels.add(l));
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore error
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function POST() {
|
||||
allNotes.forEach(note => {
|
||||
if (note.labels) {
|
||||
try {
|
||||
const parsed: string[] = JSON.parse(note.labels)
|
||||
const parsed: string[] = Array.isArray(note.labels) ? (note.labels as string[]) : []
|
||||
if (Array.isArray(parsed)) {
|
||||
parsed.forEach(l => {
|
||||
if (l && l.trim()) labelsInNotes.add(l.trim())
|
||||
@@ -81,7 +81,7 @@ export async function POST() {
|
||||
allNotes.forEach(note => {
|
||||
if (note.labels) {
|
||||
try {
|
||||
const parsed: string[] = JSON.parse(note.labels)
|
||||
const parsed: string[] = Array.isArray(note.labels) ? (note.labels as string[]) : []
|
||||
if (Array.isArray(parsed)) {
|
||||
parsed.forEach(l => usedLabelsSet.add(l.toLowerCase()))
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export async function PUT(
|
||||
for (const note of allNotes) {
|
||||
if (note.labels) {
|
||||
try {
|
||||
const noteLabels: string[] = JSON.parse(note.labels)
|
||||
const noteLabels: string[] = Array.isArray(note.labels) ? (note.labels as string[]) : []
|
||||
const updatedLabels = noteLabels.map(l =>
|
||||
l.toLowerCase() === currentLabel.name.toLowerCase() ? newName : l
|
||||
)
|
||||
@@ -123,7 +123,7 @@ export async function PUT(
|
||||
await prisma.note.update({
|
||||
where: { id: note.id },
|
||||
data: {
|
||||
labels: JSON.stringify(updatedLabels)
|
||||
labels: updatedLabels as any
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -211,7 +211,7 @@ export async function DELETE(
|
||||
for (const note of allNotes) {
|
||||
if (note.labels) {
|
||||
try {
|
||||
const noteLabels: string[] = JSON.parse(note.labels)
|
||||
const noteLabels: string[] = Array.isArray(note.labels) ? (note.labels as string[]) : []
|
||||
const filteredLabels = noteLabels.filter(
|
||||
l => l.toLowerCase() !== label.name.toLowerCase()
|
||||
)
|
||||
@@ -220,7 +220,7 @@ export async function DELETE(
|
||||
await prisma.note.update({
|
||||
where: { id: note.id },
|
||||
data: {
|
||||
labels: filteredLabels.length > 0 ? JSON.stringify(filteredLabels) : null
|
||||
labels: (filteredLabels.length > 0 ? filteredLabels : null) as any
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function GET(request: NextRequest) {
|
||||
orderBy: { name: 'asc' }
|
||||
},
|
||||
_count: {
|
||||
select: { notes: true }
|
||||
select: { notes: { where: { isArchived: false } } }
|
||||
}
|
||||
},
|
||||
orderBy: { order: 'asc' }
|
||||
@@ -82,7 +82,7 @@ export async function POST(request: NextRequest) {
|
||||
include: {
|
||||
labels: true,
|
||||
_count: {
|
||||
select: { notes: true }
|
||||
select: { notes: { where: { isArchived: false } } }
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import prisma from '@/lib/prisma'
|
||||
import { auth } from '@/auth'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
import { reconcileLabelsAfterNoteMove } from '@/app/actions/notes'
|
||||
|
||||
// POST /api/notes/[id]/move - Move a note to a notebook (or to Inbox)
|
||||
@@ -77,7 +76,9 @@ export async function POST(
|
||||
|
||||
await reconcileLabelsAfterNoteMove(id, targetNotebookId)
|
||||
|
||||
revalidatePath('/')
|
||||
// No revalidatePath('/') here — the client-side triggerRefresh() in
|
||||
// notebooks-context.tsx handles the refresh. Avoiding server-side
|
||||
// revalidation prevents a double-refresh (server + client).
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
|
||||
@@ -87,10 +87,10 @@ export async function PUT(
|
||||
const updateData: any = { ...body }
|
||||
|
||||
if ('checkItems' in body) {
|
||||
updateData.checkItems = body.checkItems ? JSON.stringify(body.checkItems) : null
|
||||
updateData.checkItems = body.checkItems ?? null
|
||||
}
|
||||
if ('labels' in body) {
|
||||
updateData.labels = body.labels ? JSON.stringify(body.labels) : null
|
||||
updateData.labels = body.labels ?? null
|
||||
}
|
||||
updateData.updatedAt = new Date()
|
||||
|
||||
|
||||
@@ -83,9 +83,9 @@ export async function POST(request: NextRequest) {
|
||||
content: content || '',
|
||||
color: color || 'default',
|
||||
type: type || 'text',
|
||||
checkItems: checkItems ? JSON.stringify(checkItems) : null,
|
||||
labels: labels ? JSON.stringify(labels) : null,
|
||||
images: images ? JSON.stringify(images) : null,
|
||||
checkItems: checkItems ?? null,
|
||||
labels: labels ?? null,
|
||||
images: images ?? null,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -147,11 +147,11 @@ export async function PUT(request: NextRequest) {
|
||||
if (content !== undefined) updateData.content = content
|
||||
if (color !== undefined) updateData.color = color
|
||||
if (type !== undefined) updateData.type = type
|
||||
if (checkItems !== undefined) updateData.checkItems = checkItems ? JSON.stringify(checkItems) : null
|
||||
if (labels !== undefined) updateData.labels = labels ? JSON.stringify(labels) : null
|
||||
if (checkItems !== undefined) updateData.checkItems = checkItems ?? null
|
||||
if (labels !== undefined) updateData.labels = labels ?? null
|
||||
if (isPinned !== undefined) updateData.isPinned = isPinned
|
||||
if (isArchived !== undefined) updateData.isArchived = isArchived
|
||||
if (images !== undefined) updateData.images = images ? JSON.stringify(images) : null
|
||||
if (images !== undefined) updateData.images = images ?? null
|
||||
|
||||
const note = await prisma.note.update({
|
||||
where: { id },
|
||||
|
||||
@@ -6,7 +6,6 @@ import { SessionProviderWrapper } from "@/components/session-provider-wrapper";
|
||||
import { getAISettings } from "@/app/actions/ai-settings";
|
||||
import { getUserSettings } from "@/app/actions/user-settings";
|
||||
import { ThemeInitializer } from "@/components/theme-initializer";
|
||||
import { getThemeScript } from "@/lib/theme-script";
|
||||
import { auth } from "@/auth";
|
||||
|
||||
const inter = Inter({
|
||||
@@ -32,6 +31,12 @@ export const viewport: Viewport = {
|
||||
themeColor: "#f59e0b",
|
||||
};
|
||||
|
||||
function getHtmlClass(theme?: string): string {
|
||||
if (theme === 'dark') return 'dark';
|
||||
if (theme === 'midnight') return 'dark';
|
||||
return '';
|
||||
}
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -46,16 +51,9 @@ export default async function RootLayout({
|
||||
getUserSettings(userId)
|
||||
])
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<html suppressHydrationWarning>
|
||||
<html suppressHydrationWarning className={getHtmlClass(userSettings.theme)}>
|
||||
<body className={inter.className}>
|
||||
<script
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: getThemeScript(userSettings.theme),
|
||||
}}
|
||||
/>
|
||||
<SessionProviderWrapper>
|
||||
<ThemeInitializer theme={userSettings.theme} fontSize={aiSettings.fontSize} />
|
||||
{children}
|
||||
|
||||
Reference in New Issue
Block a user