fix(F-01): fix avatar upload persistence on profile page

Replaced the one-shot initialized flag with a dirty flag that allows
the useEffect to re-sync local state from server data after a
successful save. Previously, once initialized was set to true, the
effect never ran again so avatar changes were lost on refetch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 22:35:35 +02:00
parent 957d661567
commit 452928760a

View File

@@ -12,7 +12,7 @@ export function ProfileSection() {
const [displayName, setDisplayName] = useState("");
const [bio, setBio] = useState("");
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
const [initialized, setInitialized] = useState(false);
const [dirty, setDirty] = useState(false);
const [message, setMessage] = useState<{
type: "success" | "error";
text: string;
@@ -21,13 +21,12 @@ export function ProfileSection() {
const fileInputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
if (profile && !initialized) {
if (profile && !dirty) {
setDisplayName(profile.displayName ?? "");
setBio(profile.bio ?? "");
setAvatarUrl(profile.avatarUrl ?? null);
setInitialized(true);
}
}, [profile, initialized]);
}, [profile, dirty]);
async function handleSave(e: React.FormEvent) {
e.preventDefault();
@@ -38,6 +37,7 @@ export function ProfileSection() {
avatarUrl,
bio: bio.trim() || undefined,
});
setDirty(false);
setMessage({ type: "success", text: "Profile updated" });
} catch (err) {
setMessage({ type: "error", text: (err as Error).message });
@@ -68,6 +68,7 @@ export function ProfileSection() {
try {
const result = await apiUpload<{ filename: string }>("/api/images", file);
setAvatarUrl(result.filename);
setDirty(true);
} catch {
setMessage({ type: "error", text: "Avatar upload failed." });
} finally {
@@ -147,7 +148,7 @@ export function ProfileSection() {
{avatarUrl && (
<button
type="button"
onClick={() => setAvatarUrl(null)}
onClick={() => { setAvatarUrl(null); setDirty(true); }}
className="block text-xs text-red-500 hover:text-red-700 mt-0.5"
>
Remove
@@ -175,7 +176,7 @@ export function ProfileSection() {
id="displayName"
type="text"
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
onChange={(e) => { setDisplayName(e.target.value); setDirty(true); }}
maxLength={100}
placeholder="Your display name"
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-gray-200"
@@ -193,7 +194,7 @@ export function ProfileSection() {
<textarea
id="bio"
value={bio}
onChange={(e) => setBio(e.target.value)}
onChange={(e) => { setBio(e.target.value); setDirty(true); }}
maxLength={500}
rows={3}
placeholder="Tell others about yourself and your gear interests"