"use client"; import { useState } from "react"; import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { useTranslations } from "next-intl"; import { useLocale, SUPPORTED_LOCALES, type Locale } from "@/lib/i18n/provider"; import { AvatarUploader } from "./avatar-uploader"; const USERNAME_PATTERN = /^[a-z0-9_]{3,20}$/; type UserProps = { name: string; email: string; image: string | null; locale: string; bio: string | null; privateBio: string | null; isPrivate: boolean; hasCustomAvatar: boolean; username: string | null; useGravatar: boolean; }; export function SettingsForm({ user }: { user: UserProps }) { const t = useTranslations("settingsForm"); const t_common = useTranslations("common"); const { setLocale } = useLocale(); const [avatarImage, setAvatarImage] = useState(user.image); const [hasCustomAvatar, setHasCustomAvatar] = useState(user.hasCustomAvatar); const [name, setName] = useState(user.name); const [bio, setBio] = useState(user.bio ?? ""); const [privateBio, setPrivateBio] = useState(user.privateBio ?? ""); const [saving, setSaving] = useState(false); const [savingBio, setSavingBio] = useState(false); const [isPrivate, setIsPrivate] = useState(user.isPrivate); const [savingPrivacy, setSavingPrivacy] = useState(false); const [username, setUsername] = useState(user.username ?? ""); const [savingUsername, setSavingUsername] = useState(false); const [usernameError, setUsernameError] = useState(null); const [useGravatar, setUseGravatar] = useState(user.useGravatar); const [savingGravatar, setSavingGravatar] = useState(false); async function saveProfile() { setSaving(true); try { const res = await fetch("/api/v1/users/me", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name }), }); if (res.ok) toast.success(t_common("saved")); else toast.error(t_common("saveFailed")); } finally { setSaving(false); } } async function saveBios() { setSavingBio(true); try { const res = await fetch("/api/v1/users/me", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ bio: bio.trim() || null, privateBio: privateBio.trim() || null, }), }); if (res.ok) toast.success(t_common("saved")); else toast.error(t_common("saveFailed")); } finally { setSavingBio(false); } } const bioUnchanged = bio === (user.bio ?? "") && privateBio === (user.privateBio ?? ""); async function saveUsername() { setUsernameError(null); if (!USERNAME_PATTERN.test(username)) { setUsernameError(t("usernameInvalid")); return; } setSavingUsername(true); try { const res = await fetch("/api/v1/users/me", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username }), }); if (res.ok) toast.success(t_common("saved")); else if (res.status === 409) setUsernameError(t("usernameTaken")); else toast.error(t_common("saveFailed")); } catch { toast.error(t_common("saveFailed")); } finally { setSavingUsername(false); } } async function saveUseGravatar(checked: boolean) { setSavingGravatar(true); const previous = useGravatar; setUseGravatar(checked); try { const res = await fetch("/api/v1/users/me", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ useGravatar: checked }), }); if (res.ok) { // Only affects the displayed avatar for accounts without a custom // upload — matches the server's own condition in api/v1/users/me. if (!hasCustomAvatar) { const data = await res.json() as { avatarUrl?: string | null }; setAvatarImage(data.avatarUrl ?? null); } toast.success(t_common("saved")); } else { setUseGravatar(previous); toast.error(t_common("saveFailed")); } } catch { setUseGravatar(previous); toast.error(t_common("saveFailed")); } finally { setSavingGravatar(false); } } async function savePrivacy(checked: boolean) { setSavingPrivacy(true); const previous = isPrivate; setIsPrivate(checked); try { const res = await fetch("/api/v1/users/me", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ isPrivate: checked }), }); if (res.ok) toast.success(t_common("saved")); else { setIsPrivate(previous); toast.error(t_common("saveFailed")); } } catch { setIsPrivate(previous); toast.error(t_common("saveFailed")); } finally { setSavingPrivacy(false); } } return (

{t("profile")}

{ setAvatarImage(image); setHasCustomAvatar(custom); }} />

{t("useGravatar")}

{t("useGravatarDescription")}

{ void saveUseGravatar(checked); }} />
setName(e.target.value)} />

{t("username")}

{t("usernameDescription")}

@ { setUsername(e.target.value.toLowerCase()); setUsernameError(null); }} maxLength={20} className="max-w-xs" />
{usernameError &&

{usernameError}

}

{t("bio")}

{t("publicBioDescription")}