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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user