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:
@@ -6,6 +6,8 @@ import { updateAISettings } from '@/app/actions/ai-settings'
|
||||
import { toast } from 'sonner'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { Globe, Bell } from 'lucide-react'
|
||||
import { motion } from 'motion/react'
|
||||
import { PageEntry } from '@/components/page-entry'
|
||||
|
||||
interface GeneralSettingsClientProps {
|
||||
initialSettings: {
|
||||
@@ -51,7 +53,7 @@ export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClient
|
||||
await updateAISettings({ desktopNotifications: enabled })
|
||||
toast.success(t('settings.settingsSaved') || 'Saved')
|
||||
}
|
||||
|
||||
|
||||
const handleAutoSaveChange = async (enabled: boolean) => {
|
||||
setAutoSave(enabled)
|
||||
await updateAISettings({ autoSave: enabled })
|
||||
@@ -59,29 +61,32 @@ export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClient
|
||||
}
|
||||
|
||||
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('generalSettings.description')}
|
||||
</p>
|
||||
</h3>
|
||||
|
||||
{/* 2-column card grid */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Language card */}
|
||||
<div className="bg-card rounded-lg border border-border p-6 shadow-sm flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
|
||||
<Globe className="h-5 w-5" />
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-xl p-8 space-y-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-concrete border border-border">
|
||||
<Globe size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-foreground">{t('settings.language')}</h3>
|
||||
<p className="text-sm text-muted-foreground">{t('settings.selectLanguage')}</p>
|
||||
<div className="space-y-0.5">
|
||||
<h4 className="text-base font-bold text-ink">{t('settings.language')}</h4>
|
||||
<p className="text-[11px] text-concrete">{t('settings.selectLanguage')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-2">
|
||||
|
||||
<div className="relative group">
|
||||
<select
|
||||
value={language}
|
||||
onChange={(e) => handleLanguageChange(e.target.value)}
|
||||
className="w-full h-11 px-4 bg-muted border border-border rounded-lg text-foreground text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none appearance-none cursor-pointer transition-colors"
|
||||
className="w-full bg-white/50 dark:bg-black/40 border border-border rounded-xl px-5 py-3.5 text-sm outline-none focus:ring-1 ring-brand-accent/20 appearance-none cursor-pointer transition-all hover:bg-white dark:hover:bg-black/60 text-ink font-medium"
|
||||
>
|
||||
<option value="auto">{t('profile.autoDetect')}</option>
|
||||
<option value="en">English</option>
|
||||
@@ -100,75 +105,76 @@ export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClient
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="pl">Polski</option>
|
||||
</select>
|
||||
<div className="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none text-muted-foreground">
|
||||
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /></svg>
|
||||
<div className="absolute right-5 top-1/2 -translate-y-1/2 pointer-events-none opacity-40 text-concrete">
|
||||
<svg width="10" height="6" viewBox="0 0 10 6" fill="none">
|
||||
<path d="M1 1L5 5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Notifications card */}
|
||||
<div className="bg-card rounded-lg border border-border p-6 shadow-sm flex flex-col gap-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-primary/10 flex items-center justify-center text-primary shrink-0">
|
||||
<Bell className="h-5 w-5" />
|
||||
<div className="bg-white/40 dark:bg-white/5 border border-border rounded-xl p-8 space-y-6">
|
||||
<div className="flex items-center gap-5">
|
||||
<div className="p-3 bg-paper dark:bg-white/10 rounded-2xl text-concrete border border-border">
|
||||
<Bell size={18} />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-foreground">{t('settings.notifications')}</h3>
|
||||
<p className="text-sm text-muted-foreground">{t('settings.notificationsDesc')}</p>
|
||||
<div className="space-y-0.5">
|
||||
<h4 className="text-base font-bold text-ink">{t('settings.notifications')}</h4>
|
||||
<p className="text-[11px] text-concrete">{t('settings.notificationsDesc')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 space-y-5">
|
||||
{/* Email toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{t('settings.emailNotifications')}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('settings.emailNotificationsDesc')}</p>
|
||||
|
||||
<div className="space-y-6 divide-y divide-border/40 text-left">
|
||||
<div className="flex items-center justify-between pt-0">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-bold text-ink">{t('settings.emailNotifications')}</p>
|
||||
<p className="text-[10px] text-concrete leading-relaxed">{t('settings.emailNotificationsDesc')}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={emailNotifications}
|
||||
onClick={() => handleEmailNotificationsChange(!emailNotifications)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary/30 ${emailNotifications ? 'bg-primary' : 'bg-muted-foreground/30'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${emailNotifications ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={emailNotifications}
|
||||
onChange={(e) => handleEmailNotificationsChange(e.target.checked)}
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-ink" />
|
||||
</label>
|
||||
</div>
|
||||
{/* Desktop toggle */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{t('settings.desktopNotifications')}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('settings.desktopNotificationsDesc')}</p>
|
||||
|
||||
<div className="flex items-center justify-between pt-6">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-bold text-ink">{t('settings.desktopNotifications')}</p>
|
||||
<p className="text-[10px] text-concrete leading-relaxed">{t('settings.desktopNotificationsDesc')}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={desktopNotifications}
|
||||
onClick={() => handleDesktopNotificationsChange(!desktopNotifications)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary/30 ${desktopNotifications ? 'bg-primary' : 'bg-muted-foreground/30'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${desktopNotifications ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={desktopNotifications}
|
||||
onChange={(e) => handleDesktopNotificationsChange(e.target.checked)}
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-ink" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="border-t border-border pt-5 flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium text-foreground">{t('settings.autoSave') || 'Auto-Save'}</p>
|
||||
<p className="text-xs text-muted-foreground">{t('settings.autoSaveDesc') || 'Sauvegarder automatiquement les modifications'}</p>
|
||||
|
||||
<div className="flex items-center justify-between pt-6">
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs font-bold text-ink">{t('settings.autoSave') || 'Auto-Save'}</p>
|
||||
<p className="text-[10px] text-concrete leading-relaxed">{t('settings.autoSaveDesc') || 'Sauvegarder automatiquement les modifications'}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked={autoSave}
|
||||
onClick={() => handleAutoSaveChange(!autoSave)}
|
||||
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-primary/30 ${autoSave ? 'bg-primary' : 'bg-muted-foreground/30'}`}
|
||||
>
|
||||
<span className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${autoSave ? 'translate-x-6' : 'translate-x-1'}`} />
|
||||
</button>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="sr-only peer"
|
||||
checked={autoSave}
|
||||
onChange={(e) => handleAutoSaveChange(e.target.checked)}
|
||||
/>
|
||||
<div className="w-11 h-6 bg-gray-200 dark:bg-white/10 rounded-full peer peer-checked:after:translate-x-[20px] peer-checked:after:border-white after:content-[''] after:absolute after:top-[4px] after:left-[4px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all duration-300 ease-in-out peer-checked:bg-ink" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user