feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
- Sidebar: dynamic brand-accent colors, brainstorm section restyled - AI chat general: popup panel with expand/collapse, hides when contextual AI open - AI chat contextual: tabs reordered (Actions first), X close button, height fix - Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.) - Global color cleanup: emerald/orange hardcoded → brand-accent dynamic - Brainstorm page: orange → brand-accent throughout - PageEntry animation component added to key pages - Floating AI button: bg-brand-accent instead of hardcoded black - i18n: all 15 locales updated with new AI/billing keys - Billing: freemium quota tracking, BYOK, stripe subscription scaffolding - Admin: integrated into new design - AGENTS.md + CLAUDE.md project rules added
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Download, Upload, Trash2, Loader2, RefreshCw, Sparkles, Database } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
import { useLanguage } from '@/lib/i18n'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { motion } from 'motion/react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export default function DataSettingsPage() {
|
||||
const { t } = useLanguage()
|
||||
@@ -117,114 +118,134 @@ export default function DataSettingsPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const cards = [
|
||||
{
|
||||
icon: Download,
|
||||
iconColor: 'text-zinc-600 dark:text-zinc-400',
|
||||
iconBg: 'bg-zinc-500/10 dark:bg-zinc-500/20',
|
||||
title: t('dataManagement.export.title'),
|
||||
description: t('dataManagement.export.description'),
|
||||
loading: isExporting,
|
||||
loadingText: t('dataManagement.exporting'),
|
||||
buttonText: t('dataManagement.export.button'),
|
||||
onAction: handleExport,
|
||||
btnClass: 'bg-ink text-paper shadow-xl shadow-ink/20 hover:scale-[1.02] active:scale-95',
|
||||
},
|
||||
{
|
||||
icon: Upload,
|
||||
iconColor: 'text-emerald-600 dark:text-emerald-400',
|
||||
iconBg: 'bg-emerald-500/10 dark:bg-emerald-500/20',
|
||||
title: t('dataManagement.import.title'),
|
||||
description: t('dataManagement.import.description'),
|
||||
loading: isImporting,
|
||||
loadingText: t('dataManagement.importing'),
|
||||
buttonText: t('dataManagement.import.button'),
|
||||
onAction: () => document.getElementById('import-file')?.click(),
|
||||
btnClass: 'bg-white dark:bg-white/10 text-ink dark:text-paper border border-border hover:scale-[1.02] active:scale-95',
|
||||
fileInput: true,
|
||||
},
|
||||
{
|
||||
icon: Sparkles,
|
||||
iconColor: 'text-amber-600 dark:text-amber-400',
|
||||
iconBg: 'bg-amber-500/10 dark:bg-amber-500/20',
|
||||
title: t('dataManagement.indexing.title'),
|
||||
description: t('dataManagement.indexing.description'),
|
||||
loading: isReindexing,
|
||||
loadingText: t('dataManagement.exporting'),
|
||||
buttonText: t('dataManagement.indexing.button'),
|
||||
onAction: handleReindex,
|
||||
btnClass: 'bg-white dark:bg-white/10 text-ink dark:text-paper border border-border hover:scale-[1.02] active:scale-95',
|
||||
},
|
||||
{
|
||||
icon: Database,
|
||||
iconColor: 'text-purple-600 dark:text-purple-400',
|
||||
iconBg: 'bg-purple-500/10 dark:bg-purple-500/20',
|
||||
title: t('dataManagement.cleanup.title'),
|
||||
description: t('dataManagement.cleanup.description'),
|
||||
loading: isCleaningUp,
|
||||
loadingText: t('dataManagement.exporting'),
|
||||
buttonText: t('dataManagement.cleanup.button'),
|
||||
onAction: handleCleanup,
|
||||
btnClass: 'bg-white dark:bg-white/10 text-ink dark:text-paper border border-border hover:scale-[1.02] active:scale-95',
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<p className="text-[11px] font-bold uppercase tracking-[0.2em] text-muted-foreground">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-12"
|
||||
>
|
||||
<h3 className="text-[10px] font-bold uppercase tracking-[0.3em] text-concrete">
|
||||
{t('dataManagement.toolsDescription')}
|
||||
</p>
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Export card */}
|
||||
<div className="bg-card rounded-xl border border-border p-6 shadow-sm flex flex-col justify-between transition-all hover:shadow-md">
|
||||
<div className="space-y-4">
|
||||
<div className="w-12 h-12 rounded-full bg-zinc-500/10 flex items-center justify-center text-zinc-600 shrink-0">
|
||||
<Download className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('dataManagement.export.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 leading-relaxed">
|
||||
{t('dataManagement.export.description')}
|
||||
</p>
|
||||
{cards.map((card) => (
|
||||
<div
|
||||
key={card.title}
|
||||
className="bg-white/40 dark:bg-white/5 border border-border rounded-2xl p-8 flex flex-col justify-between group hover:shadow-xl hover:shadow-black/5 transition-all duration-300"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className={cn('w-12 h-12 rounded-2xl flex items-center justify-center shrink-0 border border-border/50', card.iconBg, card.iconColor)}>
|
||||
<card.icon className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-ink">{card.title}</h4>
|
||||
<p className="text-[11px] text-concrete leading-relaxed mt-1.5">
|
||||
{card.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{card.fileInput && (
|
||||
<input type="file" accept=".json" onChange={handleImport} disabled={isImporting} className="hidden" id="import-file" />
|
||||
)}
|
||||
<button
|
||||
onClick={card.onAction}
|
||||
disabled={card.loading}
|
||||
className={cn(
|
||||
'mt-8 w-full py-3.5 rounded-2xl text-[10px] font-bold uppercase tracking-[0.2em] transition-all duration-300',
|
||||
card.btnClass,
|
||||
'disabled:opacity-60 disabled:pointer-events-none'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{card.loading ? <Loader2 className="h-4 w-4 animate-spin" /> : <card.icon className="h-4 w-4" />}
|
||||
{card.loading ? card.loadingText : card.buttonText}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<Button onClick={handleExport} disabled={isExporting} className="mt-6 w-full">
|
||||
{isExporting ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : <Download className="h-4 w-4 mr-2" />}
|
||||
{isExporting ? t('dataManagement.exporting') : t('dataManagement.export.button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Import card */}
|
||||
<div className="bg-card rounded-xl border border-border p-6 shadow-sm flex flex-col justify-between transition-all hover:shadow-md">
|
||||
<div className="space-y-4">
|
||||
<div className="w-12 h-12 rounded-full bg-emerald-500/10 flex items-center justify-center text-emerald-600 shrink-0">
|
||||
<Upload className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('dataManagement.import.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 leading-relaxed">
|
||||
{t('dataManagement.import.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" accept=".json" onChange={handleImport} disabled={isImporting} className="hidden" id="import-file" />
|
||||
<Button onClick={() => document.getElementById('import-file')?.click()} disabled={isImporting} variant="outline" className="mt-6 w-full">
|
||||
{isImporting ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : <Upload className="h-4 w-4 mr-2" />}
|
||||
{isImporting ? t('dataManagement.importing') : t('dataManagement.import.button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Reindex card */}
|
||||
<div className="bg-card rounded-xl border border-border p-6 shadow-sm flex flex-col justify-between transition-all hover:shadow-md">
|
||||
<div className="space-y-4">
|
||||
<div className="w-12 h-12 rounded-full bg-amber-500/10 flex items-center justify-center text-amber-600 shrink-0">
|
||||
<Sparkles className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('dataManagement.indexing.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 leading-relaxed">
|
||||
{t('dataManagement.indexing.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleReindex} disabled={isReindexing} variant="secondary" className="mt-6 w-full">
|
||||
{isReindexing ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : <RefreshCw className="h-4 w-4 mr-2" />}
|
||||
{isReindexing ? t('dataManagement.exporting') : t('dataManagement.indexing.button')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Cleanup card */}
|
||||
<div className="bg-card rounded-xl border border-border p-6 shadow-sm flex flex-col justify-between transition-all hover:shadow-md">
|
||||
<div className="space-y-4">
|
||||
<div className="w-12 h-12 rounded-full bg-purple-500/10 flex items-center justify-center text-purple-600 shrink-0">
|
||||
<Database className="h-6 w-6" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-foreground">{t('dataManagement.cleanup.title')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 leading-relaxed">
|
||||
{t('dataManagement.cleanup.description')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleCleanup} disabled={isCleaningUp} variant="secondary" className="mt-6 w-full">
|
||||
{isCleaningUp ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : <Database className="h-4 w-4 mr-2" />}
|
||||
{isCleaningUp ? t('dataManagement.exporting') : t('dataManagement.cleanup.button')}
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Danger zone */}
|
||||
<div className="bg-destructive/5 rounded-xl border border-destructive/20 p-6 shadow-sm mt-12">
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="w-12 h-12 rounded-full bg-destructive/10 flex items-center justify-center text-destructive shrink-0">
|
||||
<Trash2 className="h-6 w-6" />
|
||||
<div className="bg-rose-50/50 dark:bg-rose-500/5 rounded-2xl border border-rose-200/50 dark:border-rose-500/20 p-8 mt-12">
|
||||
<div className="flex items-center gap-5 mb-8">
|
||||
<div className="p-3 bg-rose-500/10 rounded-2xl text-rose-600 dark:text-rose-400 border border-rose-500/20">
|
||||
<Trash2 size={20} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-xl font-bold text-destructive">{t('dataManagement.dangerZone')}</h3>
|
||||
<p className="text-sm text-muted-foreground">{t('dataManagement.dangerZoneDescription')}</p>
|
||||
<h4 className="text-sm font-bold text-rose-600 dark:text-rose-400">{t('dataManagement.dangerZone')}</h4>
|
||||
<p className="text-[11px] text-concrete mt-0.5">{t('dataManagement.dangerZoneDescription')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 bg-background/50 rounded-lg border border-destructive/10 gap-4">
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between p-6 bg-white/60 dark:bg-black/20 rounded-2xl border border-rose-200/30 dark:border-rose-500/10 gap-4">
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold text-destructive">{t('dataManagement.delete.title')}</p>
|
||||
<p className="text-sm text-muted-foreground">{t('dataManagement.delete.description')}</p>
|
||||
<p className="text-[13px] font-bold text-ink">{t('dataManagement.delete.title')}</p>
|
||||
<p className="text-[11px] text-concrete">{t('dataManagement.delete.description')}</p>
|
||||
</div>
|
||||
<Button variant="destructive" onClick={handleDeleteAll} disabled={isDeleting} className="shrink-0 w-full sm:w-auto">
|
||||
{isDeleting ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : <Trash2 className="h-4 w-4 mr-2" />}
|
||||
{isDeleting ? t('dataManagement.deleting') : t('dataManagement.delete.button')}
|
||||
</Button>
|
||||
<button
|
||||
onClick={handleDeleteAll}
|
||||
disabled={isDeleting}
|
||||
className="shrink-0 px-6 py-3 rounded-2xl bg-rose-600 text-white text-[10px] font-bold uppercase tracking-[0.2em] shadow-xl shadow-rose-600/20 hover:scale-[1.02] active:scale-95 transition-all duration-300 disabled:opacity-60 disabled:pointer-events-none"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
{isDeleting ? <Loader2 className="h-4 w-4 animate-spin" /> : <Trash2 className="h-4 w-4" />}
|
||||
{isDeleting ? t('dataManagement.deleting') : t('dataManagement.delete.button')}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user