import { useState, useRef, useEffect } from 'react'; import { User, Mail, Lock, Palette, Save, Loader2, Globe, Camera, X, Calendar, Plus, Trash2, Copy, Eye, EyeOff } from 'lucide-react'; import { useAuth } from '../contexts/AuthContext'; import { useTheme } from '../contexts/ThemeContext'; import { useLanguage } from '../contexts/LanguageContext'; import { themes, getThemeGroups } from '../themes'; import api from '../services/api'; import toast from 'react-hot-toast'; export default function Settings() { const { user, updateUser } = useAuth(); const { theme, setTheme } = useTheme(); const { t, language, setLanguage } = useLanguage(); const [profile, setProfile] = useState({ name: user?.name || '', display_name: user?.display_name || '', email: user?.email || '', }); const [passwords, setPasswords] = useState({ currentPassword: '', newPassword: '', confirmPassword: '', }); const [savingProfile, setSavingProfile] = useState(false); const [savingPassword, setSavingPassword] = useState(false); const handleLanguageChange = async (lang) => { setLanguage(lang); try { const res = await api.put('/auth/profile', { language: lang }); updateUser(res.data.user); } catch { // Language is still saved locally even if API fails } }; const [activeSection, setActiveSection] = useState('profile'); const [uploadingAvatar, setUploadingAvatar] = useState(false); const fileInputRef = useRef(null); // CalDAV token state const [caldavTokens, setCaldavTokens] = useState([]); const [caldavLoading, setCaldavLoading] = useState(false); const [newTokenName, setNewTokenName] = useState(''); const [creatingToken, setCreatingToken] = useState(false); const [newlyCreatedToken, setNewlyCreatedToken] = useState(null); const [tokenVisible, setTokenVisible] = useState(false); useEffect(() => { if (activeSection === 'caldav') { setCaldavLoading(true); api.get('/calendar/caldav-tokens') .then(r => setCaldavTokens(r.data.tokens || [])) .catch(() => {}) .finally(() => setCaldavLoading(false)); } }, [activeSection]); const handleCreateToken = async (e) => { e.preventDefault(); if (!newTokenName.trim()) return; setCreatingToken(true); try { const res = await api.post('/calendar/caldav-tokens', { name: newTokenName.trim() }); setNewlyCreatedToken(res.data.plainToken); setTokenVisible(false); setNewTokenName(''); const r = await api.get('/calendar/caldav-tokens'); setCaldavTokens(r.data.tokens || []); } catch (err) { toast.error(err.response?.data?.error || t('settings.caldav.createFailed')); } finally { setCreatingToken(false); } }; const handleRevokeToken = async (id) => { if (!confirm(t('settings.caldav.revokeConfirm'))) return; try { await api.delete(`/calendar/caldav-tokens/${id}`); setCaldavTokens(prev => prev.filter(tk => tk.id !== id)); toast.success(t('settings.caldav.revoked')); } catch { toast.error(t('settings.caldav.revokeFailed')); } }; const groups = getThemeGroups(); const avatarColors = [ '#6366f1', '#8b5cf6', '#a855f7', '#d946ef', '#ec4899', '#f43f5e', '#ef4444', '#f97316', '#eab308', '#22c55e', '#14b8a6', '#06b6d4', '#3b82f6', '#2563eb', '#7c3aed', '#64748b', ]; const handleProfileSave = async (e) => { e.preventDefault(); setSavingProfile(true); try { const res = await api.put('/auth/profile', { name: profile.name, display_name: profile.display_name, email: profile.email, theme, language, avatar_color: user?.avatar_color, }); updateUser(res.data.user); toast.success(t('settings.profileSaved')); } catch (err) { toast.error(err.response?.data?.error || t('settings.profileSaveFailed')); } finally { setSavingProfile(false); } }; const handlePasswordSave = async (e) => { e.preventDefault(); if (passwords.newPassword !== passwords.confirmPassword) { toast.error(t('settings.passwordMismatch')); return; } setSavingPassword(true); try { await api.put('/auth/password', { currentPassword: passwords.currentPassword, newPassword: passwords.newPassword, }); setPasswords({ currentPassword: '', newPassword: '', confirmPassword: '' }); toast.success(t('settings.passwordChanged')); } catch (err) { toast.error(err.response?.data?.error || t('settings.passwordChangeFailed')); } finally { setSavingPassword(false); } }; const handleAvatarColor = async (color) => { try { const res = await api.put('/auth/profile', { avatar_color: color }); updateUser(res.data.user); } catch { // Ignore } }; const handleAvatarUpload = async (e) => { const file = e.target.files?.[0]; if (!file) return; if (!file.type.startsWith('image/')) { toast.error(t('settings.avatarInvalidType')); return; } if (file.size > 2 * 1024 * 1024) { toast.error(t('settings.avatarTooLarge')); return; } setUploadingAvatar(true); try { const res = await api.post('/auth/avatar', file, { headers: { 'Content-Type': file.type }, }); updateUser(res.data.user); toast.success(t('settings.avatarUploaded')); } catch (err) { toast.error(err.response?.data?.error || t('settings.avatarUploadFailed')); } finally { setUploadingAvatar(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; const handleAvatarRemove = async () => { try { const res = await api.delete('/auth/avatar'); updateUser(res.data.user); toast.success(t('settings.avatarRemoved')); } catch { toast.error(t('settings.avatarRemoveFailed')); } }; const sections = [ { id: 'profile', label: t('settings.profile'), icon: User }, { id: 'password', label: t('settings.password'), icon: Lock }, { id: 'language', label: t('settings.language'), icon: Globe }, { id: 'themes', label: t('settings.themes'), icon: Palette }, { id: 'caldav', label: t('settings.caldav.title'), icon: Calendar }, ]; return (
{t('settings.subtitle')}
{t('settings.avatarHint')}
{t('settings.avatarColorHint')}
{t('settings.subtitle')}
{t('settings.caldav.subtitle')}
{t('settings.caldav.serverUrl')}
{`${window.location.origin}/caldav/`}
{t('settings.caldav.username')}
{user?.email}
{t('settings.caldav.hint')}
{t('settings.caldav.newTokenCreated')}
{t('settings.caldav.newTokenHint')}
{tokenVisible ? newlyCreatedToken : 'โข'.repeat(48)}
{t('settings.caldav.noTokens')}
) : ({tk.name}
{t('settings.caldav.created')}: {new Date(tk.created_at).toLocaleDateString()} {tk.last_used_at && ` ยท ${t('settings.caldav.lastUsed')}: ${new Date(tk.last_used_at).toLocaleDateString()}`}