fix(profile): don't clobber edits with a slow fetch; show decoration load errors
Pronouns, timezone and avatar decoration applied the mount-time fetch result unconditionally, overwriting a value the user had already edited; the decoration panel also showed "None" on any fetch failure and let the user save over a real decoration. Add cancelled/dirty guards (mirroring ProfileStatus) and an explicit load-error state with Retry. Fixes #46 Fixes #47 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -751,22 +751,32 @@ function ProfilePronouns() {
|
||||
|
||||
const [pronouns, setPronouns] = useState<string>('');
|
||||
const [savedPronouns, setSavedPronouns] = useState<string>('');
|
||||
// True once the user has edited the field — guards against the mount-time
|
||||
// fetch below clobbering a fresh edit if it resolves late (mirrors
|
||||
// ProfileStatus's statusDirtyRef in this file).
|
||||
const pronounsDirtyRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
mx.http
|
||||
.authedRequest<{ 'm.pronouns': string }>(
|
||||
Method.Get,
|
||||
`/profile/${encodeURIComponent(userId)}/m.pronouns`,
|
||||
)
|
||||
.then((res) => {
|
||||
if (cancelled || pronounsDirtyRef.current) return;
|
||||
const val = res['m.pronouns'] ?? '';
|
||||
setPronouns(val);
|
||||
setSavedPronouns(val);
|
||||
})
|
||||
.catch(() => {
|
||||
if (cancelled || pronounsDirtyRef.current) return;
|
||||
setPronouns('');
|
||||
setSavedPronouns('');
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [mx, userId]);
|
||||
|
||||
const [saveState, savePronouns] = useAsyncCallback(
|
||||
@@ -788,10 +798,12 @@ function ProfilePronouns() {
|
||||
const saving = saveState.status === AsyncStatus.Loading;
|
||||
|
||||
const handleChange: ChangeEventHandler<HTMLInputElement> = (evt) => {
|
||||
pronounsDirtyRef.current = true;
|
||||
setPronouns(evt.currentTarget.value);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
pronounsDirtyRef.current = true;
|
||||
setPronouns(savedPronouns);
|
||||
};
|
||||
|
||||
@@ -875,10 +887,15 @@ function ProfileTimezone() {
|
||||
|
||||
const [timezone, setTimezone] = useState<string>('');
|
||||
const [savedTimezone, setSavedTimezone] = useState<string>('');
|
||||
// True once the user has edited the field — guards against the mount-time
|
||||
// fetch below clobbering a fresh edit if it resolves late (mirrors
|
||||
// ProfileStatus's statusDirtyRef in this file).
|
||||
const timezoneDirtyRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const cached = getAccountData<{ timezone: string }>(mx, 'im.lotus.timezone');
|
||||
if (cached?.timezone) {
|
||||
if (cached?.timezone && !timezoneDirtyRef.current) {
|
||||
setTimezone(cached.timezone);
|
||||
setSavedTimezone(cached.timezone);
|
||||
}
|
||||
@@ -889,6 +906,7 @@ function ProfileTimezone() {
|
||||
`/user/${encodeURIComponent(userId)}/account_data/im.lotus.timezone`,
|
||||
)
|
||||
.then((res) => {
|
||||
if (cancelled || timezoneDirtyRef.current) return;
|
||||
const val = res.timezone ?? '';
|
||||
setTimezone(val);
|
||||
setSavedTimezone(val);
|
||||
@@ -896,6 +914,9 @@ function ProfileTimezone() {
|
||||
.catch(() => {
|
||||
/* no stored timezone yet */
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [mx, userId]);
|
||||
|
||||
const [saveState, saveTimezone] = useAsyncCallback(
|
||||
@@ -921,7 +942,13 @@ function ProfileTimezone() {
|
||||
);
|
||||
const saving = saveState.status === AsyncStatus.Loading;
|
||||
|
||||
const handleChange = (value: string) => {
|
||||
timezoneDirtyRef.current = true;
|
||||
setTimezone(value);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
timezoneDirtyRef.current = true;
|
||||
setTimezone(savedTimezone);
|
||||
};
|
||||
|
||||
@@ -955,7 +982,7 @@ function ProfileTimezone() {
|
||||
{ value: '', label: '— select timezone —' },
|
||||
...COMMON_TIMEZONES.map((tz) => ({ value: tz, label: tz })),
|
||||
]}
|
||||
onChange={setTimezone}
|
||||
onChange={handleChange}
|
||||
disabled={saving}
|
||||
aria-label="Timezone"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user