refactor(frontend): migrate Profile.tsx to the banner system
Load/save/delete failures now call showError instead of setting local error state. The profile-not-loaded-yet fallback gains a profileLoadFailed boolean so it can still distinguish "loading" from "failed to load" without the error text itself, which now lives only in the (transient) banner.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { api, BASE_URL } from "../api/client";
|
import { api, BASE_URL } from "../api/client";
|
||||||
|
import { showError } from "../banner";
|
||||||
import { ColorField } from "../components/ColorField";
|
import { ColorField } from "../components/ColorField";
|
||||||
import { GarminConnection } from "../components/GarminConnection";
|
import { GarminConnection } from "../components/GarminConnection";
|
||||||
import { NullableNumberField } from "../components/NullableNumberField";
|
import { NullableNumberField } from "../components/NullableNumberField";
|
||||||
@@ -87,7 +88,7 @@ const AUTO_SAVE_DELAY_MS = 600;
|
|||||||
|
|
||||||
export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
||||||
const [profile, setProfile] = useState<ProfileType | null>(null);
|
const [profile, setProfile] = useState<ProfileType | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [profileLoadFailed, setProfileLoadFailed] = useState(false);
|
||||||
const [saved, setSaved] = useState(false);
|
const [saved, setSaved] = useState(false);
|
||||||
// Mirrors `profile` synchronously (state updates don't apply until the
|
// Mirrors `profile` synchronously (state updates don't apply until the
|
||||||
// next render), so set() always debounces from the latest edit rather
|
// next render), so set() always debounces from the latest edit rather
|
||||||
@@ -100,7 +101,10 @@ export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
|||||||
const [deleting, setDeleting] = useState(false);
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.getProfile().then(setProfile).catch((e) => setError(String(e)));
|
api.getProfile().then(setProfile).catch((e) => {
|
||||||
|
showError(String(e));
|
||||||
|
setProfileLoadFailed(true);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Flush a still-pending debounced save on unmount (e.g. the user edits a
|
// Flush a still-pending debounced save on unmount (e.g. the user edits a
|
||||||
@@ -121,11 +125,10 @@ export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
|||||||
const updated = await api.updateProfile(next);
|
const updated = await api.updateProfile(next);
|
||||||
profileRef.current = updated;
|
profileRef.current = updated;
|
||||||
setProfile(updated);
|
setProfile(updated);
|
||||||
setError(null);
|
|
||||||
setSaved(true);
|
setSaved(true);
|
||||||
onSaved?.(updated);
|
onSaved?.(updated);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(String(e));
|
showError(String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +156,6 @@ export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
|||||||
|
|
||||||
async function deleteAccount() {
|
async function deleteAccount() {
|
||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
setError(null);
|
|
||||||
try {
|
try {
|
||||||
await api.deleteProfile();
|
await api.deleteProfile();
|
||||||
// Deletion doesn't clear the session cookie -- follow with a real
|
// Deletion doesn't clear the session cookie -- follow with a real
|
||||||
@@ -166,7 +168,7 @@ export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
|||||||
document.body.appendChild(form);
|
document.body.appendChild(form);
|
||||||
form.submit();
|
form.submit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(String(e));
|
showError(String(e));
|
||||||
setDeleting(false);
|
setDeleting(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,14 +176,13 @@ export function Profile({ onSaved }: { onSaved?: (p: ProfileType) => void }) {
|
|||||||
if (!profile) {
|
if (!profile) {
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
{error ? <p className="error">{error}</p> : <p>Loading...</p>}
|
{profileLoadFailed ? <p className="empty-state">Couldn't load profile.</p> : <p>Loading...</p>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page profile-page">
|
<div className="page profile-page">
|
||||||
{error && <p className="error">{error}</p>}
|
|
||||||
{saved && <p className="garmin-connection-message">Saved.</p>}
|
{saved && <p className="garmin-connection-message">Saved.</p>}
|
||||||
|
|
||||||
<fieldset className="kind-editor">
|
<fieldset className="kind-editor">
|
||||||
|
|||||||
Reference in New Issue
Block a user