mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-05 12:44:22 +00:00
feat(frontend): migrate useAllSetting to TanStack Query
Replaces the hand-rolled fetch + dirty-tracking hook with useAllSettings backed by useQuery + useMutation. The draft (current edits) is kept in local state and reset whenever query.data lands. saveAll posts the draft via a mutation; on success, invalidating ['settings'] refetches and the useEffect resets the draft so saveDisabled flips back to true. staleTime: Infinity prevents refetchOnWindowFocus from clobbering in-flight edits — settings only change in response to this user's own save. setSpinning stays as a pass-through to a local flag so the existing restartPanel flow in SettingsPage keeps showing its spinner.
This commit is contained in:
parent
dff509b394
commit
bbb7af65f6
4 changed files with 73 additions and 71 deletions
67
frontend/src/api/queries/useAllSettings.ts
Normal file
67
frontend/src/api/queries/useAllSettings.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { HttpUtil } from '@/utils';
|
||||
import { AllSetting } from '@/models/setting';
|
||||
import { keys } from '@/api/queryKeys';
|
||||
|
||||
interface ApiMsg<T = unknown> {
|
||||
success?: boolean;
|
||||
obj?: T;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
async function fetchAllSetting(): Promise<unknown> {
|
||||
const msg = await HttpUtil.post('/panel/setting/all', undefined, { silent: true }) as ApiMsg;
|
||||
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch settings');
|
||||
return msg.obj;
|
||||
}
|
||||
|
||||
export function useAllSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const [draft, setDraft] = useState<AllSetting>(() => new AllSetting());
|
||||
const [extraSpinning, setExtraSpinning] = useState(false);
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: keys.settings.all(),
|
||||
queryFn: fetchAllSetting,
|
||||
staleTime: Infinity,
|
||||
});
|
||||
|
||||
const server = useMemo(() => new AllSetting(query.data), [query.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (query.data !== undefined) {
|
||||
setDraft(new AllSetting(query.data));
|
||||
}
|
||||
}, [query.data]);
|
||||
|
||||
const updateSetting = useCallback((patch: Partial<AllSetting>) => {
|
||||
setDraft((prev) => {
|
||||
const next = new AllSetting(prev);
|
||||
Object.assign(next, patch);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const saveMut = useMutation({
|
||||
mutationFn: async (next: AllSetting) =>
|
||||
HttpUtil.post('/panel/setting/update', next) as Promise<ApiMsg>,
|
||||
onSuccess: (msg) => {
|
||||
if (msg?.success) queryClient.invalidateQueries({ queryKey: keys.settings.all() });
|
||||
},
|
||||
});
|
||||
|
||||
const saveAll = useCallback(() => saveMut.mutateAsync(draft), [saveMut, draft]);
|
||||
const saveDisabled = useMemo(() => server.equals(draft), [server, draft]);
|
||||
|
||||
return {
|
||||
allSetting: draft,
|
||||
updateSetting,
|
||||
fetched: query.data !== undefined,
|
||||
spinning: extraSpinning || saveMut.isPending,
|
||||
setSpinning: setExtraSpinning,
|
||||
saveDisabled,
|
||||
saveAll,
|
||||
};
|
||||
}
|
||||
|
|
@ -6,4 +6,8 @@ export const keys = {
|
|||
root: () => ['nodes'] as const,
|
||||
list: () => ['nodes', 'list'] as const,
|
||||
},
|
||||
settings: {
|
||||
root: () => ['settings'] as const,
|
||||
all: () => ['settings', 'all'] as const,
|
||||
},
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -1,69 +0,0 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { HttpUtil } from '@/utils';
|
||||
import { AllSetting } from '@/models/setting';
|
||||
|
||||
interface ApiMsg<T = unknown> {
|
||||
success?: boolean;
|
||||
obj?: T;
|
||||
}
|
||||
|
||||
export function useAllSetting() {
|
||||
const [allSetting, setAllSetting] = useState<AllSetting>(() => new AllSetting());
|
||||
const [oldAllSetting, setOldAllSetting] = useState<AllSetting>(() => new AllSetting());
|
||||
const [fetched, setFetched] = useState(false);
|
||||
const [spinning, setSpinning] = useState(false);
|
||||
const fetchedRef = useRef(false);
|
||||
|
||||
const applyServerState = useCallback((obj: unknown) => {
|
||||
setAllSetting(new AllSetting(obj));
|
||||
setOldAllSetting(new AllSetting(obj));
|
||||
}, []);
|
||||
|
||||
const fetchAll = useCallback(async () => {
|
||||
const msg = await HttpUtil.post('/panel/setting/all') as ApiMsg;
|
||||
if (msg?.success) {
|
||||
applyServerState(msg.obj);
|
||||
fetchedRef.current = true;
|
||||
setFetched(true);
|
||||
}
|
||||
}, [applyServerState]);
|
||||
|
||||
const saveAll = useCallback(async () => {
|
||||
setSpinning(true);
|
||||
try {
|
||||
const msg = await HttpUtil.post('/panel/setting/update', allSetting) as ApiMsg;
|
||||
if (msg?.success) await fetchAll();
|
||||
} finally {
|
||||
setSpinning(false);
|
||||
}
|
||||
}, [allSetting, fetchAll]);
|
||||
|
||||
const updateSetting = useCallback((patch: Partial<AllSetting>) => {
|
||||
setAllSetting((prev) => {
|
||||
const next = new AllSetting(prev);
|
||||
Object.assign(next, patch);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const saveDisabled = useMemo(
|
||||
() => allSetting.equals(oldAllSetting),
|
||||
[allSetting, oldAllSetting],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
fetchAll();
|
||||
}, [fetchAll]);
|
||||
|
||||
return {
|
||||
allSetting,
|
||||
updateSetting,
|
||||
fetched,
|
||||
spinning,
|
||||
setSpinning,
|
||||
saveDisabled,
|
||||
fetchAll,
|
||||
saveAll,
|
||||
};
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ import { HttpUtil, PromiseUtil } from '@/utils';
|
|||
import { setMessageInstance } from '@/utils/messageBus';
|
||||
import { useTheme } from '@/hooks/useTheme';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { useAllSetting } from '@/hooks/useAllSetting';
|
||||
import { useAllSettings } from '@/api/queries/useAllSettings';
|
||||
import AppSidebar from '@/components/AppSidebar';
|
||||
import GeneralTab from './GeneralTab';
|
||||
import SecurityTab from './SecurityTab';
|
||||
|
|
@ -92,7 +92,7 @@ export default function SettingsPage() {
|
|||
setSpinning,
|
||||
saveDisabled,
|
||||
saveAll,
|
||||
} = useAllSetting();
|
||||
} = useAllSettings();
|
||||
|
||||
const [entryHost, setEntryHost] = useState('');
|
||||
const [entryPort, setEntryPort] = useState('');
|
||||
|
|
|
|||
Loading…
Reference in a new issue