fix(app): harden account and workflow state

This commit is contained in:
cesnimda
2026-08-24 20:21:09 +02:00
parent e7cacad7d6
commit dca5daa1a2
32 changed files with 811 additions and 86 deletions
+32 -4
View File
@@ -1,31 +1,50 @@
import { useEffect, useState } from "react";
import { api } from "../api";
import { AUTH_USER_CHANGED_EVENT, getAuthUserKey } from "../auth";
import { Company } from "../types";
import { useViewResource, ViewResourceError } from "./useViewResource";
let cachedCompanies: Company[] | null = null;
let inflight: Promise<Company[]> | null = null;
let cacheOwner = "";
function resetForOwner(owner: string) {
if (cacheOwner === owner) return;
cacheOwner = owner;
cachedCompanies = null;
inflight = null;
}
function cachedForCurrentOwner() {
const owner = getAuthUserKey();
resetForOwner(owner);
return cachedCompanies;
}
async function fetchCompanies(): Promise<Company[]> {
const owner = getAuthUserKey();
resetForOwner(owner);
if (cachedCompanies) return cachedCompanies;
if (inflight) return inflight;
inflight = api
const request = api
.get<Company[]>('/companies')
.then((r) => {
cachedCompanies = r.data;
if (cacheOwner === owner && getAuthUserKey() === owner) cachedCompanies = r.data;
return r.data;
})
.finally(() => {
inflight = null;
if (inflight === request) inflight = null;
});
inflight = request;
return inflight;
}
export function invalidateCompaniesCache() {
cachedCompanies = null;
inflight = null;
}
export function useCompanies(): {
@@ -37,7 +56,7 @@ export function useCompanies(): {
} {
const [cacheBust, setCacheBust] = useState(0);
const resource = useViewResource(fetchCompanies, {
initialData: cachedCompanies ?? [],
initialData: cachedForCurrentOwner() ?? [],
errorMessage: 'Unable to load companies right now.',
deps: [cacheBust],
});
@@ -48,6 +67,15 @@ export function useCompanies(): {
}
}, [resource.data, resource.error]);
useEffect(() => {
const reloadForAccount = () => {
resetForOwner(getAuthUserKey());
setCacheBust((value) => value + 1);
};
window.addEventListener(AUTH_USER_CHANGED_EVENT, reloadForAccount);
return () => window.removeEventListener(AUTH_USER_CHANGED_EVENT, reloadForAccount);
}, []);
return {
companies: resource.data,
loading: resource.loading,
@@ -66,6 +66,7 @@ export function useViewResource<T>(
const [error, setError] = useState<ViewResourceError | null>(null);
const hasLoadedRef = useRef(hasLoaded);
const loadRef = useRef(load);
const requestSequence = useRef(0);
useEffect(() => {
hasLoadedRef.current = hasLoaded;
@@ -78,18 +79,22 @@ export function useViewResource<T>(
const reload = useCallback(async () => {
if (!enabled) return;
const requestId = ++requestSequence.current;
const alreadyLoaded = hasLoadedRef.current;
setLoading(!alreadyLoaded);
setRefreshing(alreadyLoaded);
try {
const next = await loadRef.current();
if (requestId !== requestSequence.current) return;
setData(next);
setError(null);
setHasLoaded(true);
} catch (err: any) {
if (requestId !== requestSequence.current) return;
setError(normalizeError(err, errorMessage));
setHasLoaded(true);
} finally {
if (requestId !== requestSequence.current) return;
setLoading(false);
setRefreshing(false);
}
@@ -97,6 +102,7 @@ export function useViewResource<T>(
useEffect(() => {
if (!enabled) {
requestSequence.current += 1;
setLoading(false);
return;
}