'use client'; import { useState, useTransition, useEffect, useRef } from 'react'; import { useTranslations } from 'next-intl'; import { fetchCurrentUser, changePasswordAction, uploadAvatarAction, deleteAvatarAction, updateAccentColorAction, } from '@/lib/auth-actions'; import { useAuthStore } from '@/lib/stores/auth-store'; import { BRAND_YELLOW } from '@/components/brand/brand'; import { normalizeHexColor } from '@/lib/color'; const DEFAULT_ACCENT = BRAND_YELLOW; export function AccountSettingsForm() { const t = useTranslations('settings'); const tAuth = useTranslations('auth'); const { user, setUser, bumpAvatarVersion } = useAuthStore(); const [isLocalUser, setIsLocalUser] = useState(null); const [username, setUsername] = useState(''); const [hasAvatar, setHasAvatar] = useState(false); const [avatarKey, setAvatarKey] = useState(0); // Password change state const [pwError, setPwError] = useState(null); const [pwSuccess, setPwSuccess] = useState(false); const [pwMismatch, setPwMismatch] = useState(false); const [isPwPending, startPwTransition] = useTransition(); const pwFormRef = useRef(null); // Avatar upload/delete state const [avatarError, setAvatarError] = useState(null); const [avatarSuccess, setAvatarSuccess] = useState(false); const [isAvatarPending, startAvatarTransition] = useTransition(); // Accent color state const [accentColor, setAccentColor] = useState(DEFAULT_ACCENT); const [hexInput, setHexInput] = useState(DEFAULT_ACCENT); const isHexValid = normalizeHexColor(hexInput) !== null; const [accentSuccess, setAccentSuccess] = useState(false); const [accentError, setAccentError] = useState(null); const [isAccentPending, startAccentTransition] = useTransition(); useEffect(() => { fetchCurrentUser().then((u) => { if (u) { setIsLocalUser(u.isLocalUser ?? false); setUsername(u.displayName ?? u.username ?? ''); setHasAvatar(!!u.hasAvatar); const c = u.accentColor ?? DEFAULT_ACCENT; setAccentColor(c); setHexInput(c); } }); }, []); async function handlePasswordSubmit(e: React.FormEvent) { e.preventDefault(); setPwError(null); setPwSuccess(false); setPwMismatch(false); const formData = new FormData(e.currentTarget); const currentPassword = formData.get('currentPassword') as string; const newPassword = formData.get('newPassword') as string; const confirmPassword = formData.get('confirmPassword') as string; if (newPassword !== confirmPassword) { setPwMismatch(true); return; } startPwTransition(async () => { const result = await changePasswordAction(currentPassword, newPassword); if (result) setPwError(result.error); }); } async function handleAvatarUpload(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; setAvatarError(null); setAvatarSuccess(false); const formData = new FormData(); formData.append('file', file); startAvatarTransition(async () => { const result = await uploadAvatarAction(formData); if (result.success) { setAvatarSuccess(true); setHasAvatar(true); setAvatarKey((k) => k + 1); bumpAvatarVersion(); if (user) setUser({ ...user, hasAvatar: true }); } else { setAvatarError(result.error ?? 'uploadError'); } }); } function handleDeleteAvatar() { setAvatarError(null); setAvatarSuccess(false); startAvatarTransition(async () => { const result = await deleteAvatarAction(); if (result.success) { setHasAvatar(false); setAvatarSuccess(true); setAvatarKey((k) => k + 1); if (user) setUser({ ...user, hasAvatar: false }); } else { setAvatarError(result.error ?? 'deleteError'); } }); } function handleSaveAccentColor() { if (!isHexValid) return; setAccentSuccess(false); setAccentError(null); startAccentTransition(async () => { const result = await updateAccentColorAction(accentColor); if (result.success) { setAccentSuccess(true); if (user) setUser({ ...user, accentColor }); } else { setAccentError(result.error ?? 'saveError'); } }); } function handleResetAccentColor() { setAccentColor(DEFAULT_ACCENT); setHexInput(DEFAULT_ACCENT); setAccentSuccess(false); setAccentError(null); startAccentTransition(async () => { const result = await updateAccentColorAction(null); if (result.success) { setAccentSuccess(true); if (user) setUser({ ...user, accentColor: null }); } }); } const userInitial = username.charAt(0).toUpperCase() || '?'; return (
{/* ── Avatar section ─────────────────────────────────────── */}

{t('account.avatarLabel')}

{hasAvatar ? ( setHasAvatar(false)} /> ) : (
{userInitial}
)}
{t('account.avatarHelp')}
{avatarSuccess && (
{hasAvatar ? t('account.uploadSuccess') : t('account.deleteAvatarSuccess')}
)} {avatarError && (
{avatarError === 'deleteError' ? t('account.deleteAvatarError') : t('account.uploadError')}
)}
{hasAvatar && ( )}
{/* ── Accent color section ────────────────────────────────── */}

{t('account.accentColorLabel')}

{t('account.accentColorHelp')}

{ setAccentColor(e.target.value); setHexInput(e.target.value); }} className="h-10 w-16 cursor-pointer rounded-md border border-input p-1" /> { const raw = e.target.value; setHexInput(raw); const n = normalizeHexColor(raw); if (n) setAccentColor(n); }} onBlur={() => { const n = normalizeHexColor(hexInput); if (n) setHexInput(n); }} className={`h-10 w-28 rounded-md border bg-background px-3 py-2 font-mono text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring ${ isHexValid ? 'border-input' : 'border-destructive' }`} />
{!isHexValid && (

{t('account.accentColorHexInvalid')}

)} {accentSuccess && (
{t('account.accentColorSuccess')}
)} {accentError && (
{t('account.accentColorError')}
)}
{/* ── Password section ────────────────────────────────────── */}

{t('account.passwordSectionTitle')}

{isLocalUser === false && (
{t('account.ldapManagedNotice')}
)} {isLocalUser === true && ( <> {pwSuccess && (
{tAuth('changePassword.success')}
)} {pwError && (
{tAuth(`changePassword.${pwError}`)}
)} {pwMismatch && (
{tAuth('changePassword.passwordMismatch')}
)}
{ setPwError(null); setPwMismatch(false); }} className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" />
{ setPwError(null); setPwMismatch(false); }} className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" />
{ setPwError(null); setPwMismatch(false); }} className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" />
)}
); }