The CookieConsentBanner uses useLanguage() hook but was rendered outside of LanguageProvider in RootLayout. Added LanguageProvider wrapper to fix the runtime error.
203 lines
9.9 KiB
TypeScript
203 lines
9.9 KiB
TypeScript
'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, Shield } from 'lucide-react'
|
|
import { motion } from 'motion/react'
|
|
import { openCookiePreferences } from '@/lib/consent/cookie-consent'
|
|
|
|
|
|
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'))
|
|
} 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'))
|
|
}
|
|
setTimeout(() => router.refresh(), 300)
|
|
}
|
|
|
|
const handleEmailNotificationsChange = async (enabled: boolean) => {
|
|
setEmailNotifications(enabled)
|
|
await updateAISettings({ emailNotifications: enabled })
|
|
toast.success(t('settings.settingsSaved'))
|
|
}
|
|
|
|
const handleDesktopNotificationsChange = async (enabled: boolean) => {
|
|
setDesktopNotifications(enabled)
|
|
await updateAISettings({ desktopNotifications: enabled })
|
|
toast.success(t('settings.settingsSaved'))
|
|
}
|
|
|
|
const handleAutoSaveChange = async (enabled: boolean) => {
|
|
setAutoSave(enabled)
|
|
await updateAISettings({ autoSave: enabled })
|
|
toast.success(t('settings.settingsSaved'))
|
|
}
|
|
|
|
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">{t('languages.en')}</option>
|
|
<option value="fr">{t('languages.fr')}</option>
|
|
<option value="es">{t('languages.es')}</option>
|
|
<option value="de">{t('languages.de')}</option>
|
|
<option value="fa">{t('languages.fa')}</option>
|
|
<option value="it">{t('languages.it')}</option>
|
|
<option value="pt">{t('languages.pt')}</option>
|
|
<option value="ru">{t('languages.ru')}</option>
|
|
<option value="zh">{t('languages.zh')}</option>
|
|
<option value="ja">{t('languages.ja')}</option>
|
|
<option value="ko">{t('languages.ko')}</option>
|
|
<option value="ar">{t('languages.ar')}</option>
|
|
<option value="hi">{t('languages.hi')}</option>
|
|
<option value="nl">{t('languages.nl')}</option>
|
|
<option value="pl">{t('languages.pl')}</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')}</p>
|
|
<p className="text-[10px] text-concrete leading-relaxed">{t('settings.autoSaveDesc')}</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 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">
|
|
<Shield size={18} />
|
|
</div>
|
|
<div className="space-y-0.5">
|
|
<h4 className="text-base font-bold text-ink">{t('consent.preferences.title')}</h4>
|
|
<p className="text-[11px] text-concrete">{t('consent.preferences.description')}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="pt-2">
|
|
<button
|
|
onClick={() => openCookiePreferences()}
|
|
className="w-full px-5 py-3.5 bg-white dark:bg-white/10 border border-border rounded-xl text-xs font-bold uppercase tracking-[0.25em] text-ink dark:text-paper hover:scale-[1.01] active:scale-95 transition-all duration-300 shadow-sm"
|
|
>
|
|
{t('consent.banner.manage')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
)
|
|
}
|