"use client"; import { useI18n } from "@/components/I18nProvider"; import { useState } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { UserRound, LockKeyhole, Mail } from "lucide-react"; import { api } from "./StartGame"; import { PasswordForm } from "./PasswordForm"; import { displayNameSchema } from "@/shared/account"; import { defaultGlowColor, profileAvatarMaxBytes, profileAvatarMimeTypes, type ProfileAppearance, } from "@/shared/profile-appearance"; import { Username } from "./Username"; import { PlayerAvatar } from "./PixelArt"; export function AccountSettings({ accountId, email, displayName, bio, avatarPath, appearance, passwordUpdated = false, }: { accountId: string; email: string; displayName: string; bio: string; avatarPath: string | null; appearance: ProfileAppearance; passwordUpdated?: boolean; }) { const { t } = useI18n(); const [name, setName] = useState(displayName), [about, setAbout] = useState(bio), [message, setMessage] = useState(""), [error, setError] = useState(""), [busy, setBusy] = useState(false); const [recoveryMessage, setRecoveryMessage] = useState(""), [recoveryError, setRecoveryError] = useState(""), [sending, setSending] = useState(false); const router = useRouter(); const [color, setColor] = useState(appearance.glow_color ?? defaultGlowColor); const [colorBusy, setColorBusy] = useState(false), [colorError, setColorError] = useState(""), [colorSaved, setColorSaved] = useState(false); const [photoPath, setPhotoPath] = useState(avatarPath), [photoBusy, setPhotoBusy] = useState(false), [photoError, setPhotoError] = useState(""), [photoSaved, setPhotoSaved] = useState(false); async function uploadAvatar(event: React.ChangeEvent) { const input = event.currentTarget; const file = input.files?.[0]; input.value = ""; setPhotoError(""); setPhotoSaved(false); if (!file) return; if (file.size > profileAvatarMaxBytes) { setPhotoError("Images must be 5 MB or smaller."); return; } if ( !profileAvatarMimeTypes.includes( file.type as (typeof profileAvatarMimeTypes)[number], ) ) { setPhotoError("Choose a JPG, PNG or WebP image."); return; } setPhotoBusy(true); try { const form = new FormData(); form.append("photo", file); const result = await api<{ profile: { avatar_path: string } }>( "profile/avatar", form, ); setPhotoPath(result.profile.avatar_path); setPhotoSaved(true); router.refresh(); } catch (error) { setPhotoError((error as Error).message); } finally { setPhotoBusy(false); } } async function removeAvatar() { setPhotoBusy(true); setPhotoError(""); setPhotoSaved(false); try { await api("profile", { avatarPath: null }, "PATCH"); setPhotoPath(null); setPhotoSaved(true); router.refresh(); } catch (error) { setPhotoError((error as Error).message); } finally { setPhotoBusy(false); } } async function saveColor(event: React.FormEvent) { event.preventDefault(); setColorBusy(true); setColorError(""); setColorSaved(false); try { await api("profile", { glowColor: color }, "PATCH"); setColorSaved(true); router.refresh(); } catch (error) { setColorError((error as Error).message); } finally { setColorBusy(false); } } async function save(event: React.FormEvent) { event.preventDefault(); setError(""); setMessage(""); const valid = displayNameSchema.safeParse(name); if (!valid.success) { setError(valid.error.issues[0].message); return; } setBusy(true); try { const data = await api<{ profile: { display_name: string; bio: string }; }>( "profile", { displayName: valid.data, bio: about }, "PATCH", ); setName(data.profile.display_name); setAbout(data.profile.bio); setMessage("Public profile saved."); router.refresh(); } catch (e) { setError((e as Error).message); } finally { setBusy(false); } } async function recover() { setSending(true); setRecoveryError(""); try { const data = await api<{ message: string }>("auth", { action: "recover", email, }); setRecoveryMessage(data.message); } catch (e) { setRecoveryError((e as Error).message); } finally { setSending(false); } } return (

{t("Your public profile.")}

{t("A name and description for other explorers to see.")}

{t("View public profile")} →

{photoPath && ( )}
{photoError && (

{t(photoError)}

)} {photoSaved && (

{t("Profile photo saved.")}

)}