Files
Momento/memento-note/app/(main)/settings/general/general-settings-client.tsx
Antigravity bd495be965
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
feat: design system overhaul — sidebar, AI chats, settings, brainstorm, color cleanup
- 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
2026-05-16 12:59:30 +00:00

181 lines
8.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState } from 'react'
import { useLanguage } from '@/lib/i18n'
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: {
preferredLanguage: string
emailNotifications: boolean
desktopNotifications: boolean
autoSave: boolean
}
}
export function GeneralSettingsClient({ initialSettings }: GeneralSettingsClientProps) {
const { t, setLanguage: setContextLanguage } = useLanguage()
const router = useRouter()
const [language, setLanguage] = useState(initialSettings.preferredLanguage || 'auto')
const [emailNotifications, setEmailNotifications] = useState(initialSettings.emailNotifications ?? false)
const [desktopNotifications, setDesktopNotifications] = useState(initialSettings.desktopNotifications ?? false)
const [autoSave, setAutoSave] = useState(initialSettings.autoSave ?? true)
const handleLanguageChange = async (value: string) => {
setLanguage(value)
await updateAISettings({ preferredLanguage: value as any })
if (value === 'auto') {
localStorage.removeItem('user-language')
document.cookie = 'user-language=;path=/;max-age=0'
toast.success(t('settings.languageAuto') || 'Language set to Auto')
} else {
localStorage.setItem('user-language', value)
document.cookie = `user-language=${value};path=/;max-age=${60 * 60 * 24 * 365};samesite=lax`
setContextLanguage(value as any)
toast.success(t('profile.languageUpdateSuccess') || 'Language updated')
}
setTimeout(() => router.refresh(), 300)
}
const handleEmailNotificationsChange = async (enabled: boolean) => {
setEmailNotifications(enabled)
await updateAISettings({ emailNotifications: enabled })
toast.success(t('settings.settingsSaved') || 'Saved')
}
const handleDesktopNotificationsChange = async (enabled: boolean) => {
setDesktopNotifications(enabled)
await updateAISettings({ desktopNotifications: enabled })
toast.success(t('settings.settingsSaved') || 'Saved')
}
const handleAutoSaveChange = async (enabled: boolean) => {
setAutoSave(enabled)
await updateAISettings({ autoSave: enabled })
toast.success(t('settings.settingsSaved') || 'Saved')
}
return (
<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')}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<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 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 group">
<select
value={language}
onChange={(e) => handleLanguageChange(e.target.value)}
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>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="de">Deutsch</option>
<option value="fa">فارسی</option>
<option value="it">Italiano</option>
<option value="pt">Português</option>
<option value="ru">Русский</option>
<option value="zh"></option>
<option value="ja"></option>
<option value="ko"></option>
<option value="ar">العربية</option>
<option value="hi">ि</option>
<option value="nl">Nederlands</option>
<option value="pl">Polski</option>
</select>
<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>
<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 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="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>
<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>
<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>
<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="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>
<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>
</motion.div>
)
}