refactoring

This commit is contained in:
2025-11-10 13:39:12 +01:00
parent 5341a2e4ba
commit ea95188dd7
2 changed files with 216 additions and 120 deletions

View File

@@ -10,6 +10,7 @@ import useNotificationSettings from './hooks/useNotificationSettings';
import useConfigManager from './hooks/useConfigManager';
import useStoreSync from './hooks/useStoreSync';
import useDirtyNavigationGuard from './hooks/useDirtyNavigationGuard';
import useSessionManager from './hooks/useSessionManager';
import NavigationTabs from './components/NavigationTabs';
import LoginView from './components/LoginView';
import DashboardView from './components/DashboardView';
@@ -20,10 +21,7 @@ import ConfirmationDialog from './components/ConfirmationDialog';
import StoreSyncOverlay from './components/StoreSyncOverlay';
import RangePickerModal from './components/RangePickerModal';
const TOKEN_STORAGE_KEY = 'pickupConfigToken';
function App() {
const [session, setSession] = useState(null);
const [credentials, setCredentials] = useState({ email: '', password: '' });
const [stores, setStores] = useState([]);
const [loading, setLoading] = useState(false);
@@ -40,6 +38,10 @@ function App() {
const minSelectableDate = useMemo(() => startOfDay(new Date()), []);
const weekdays = ['Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'];
const unauthorizedResetRef = useRef(() => {});
const notifyUnauthorized = useCallback(() => {
unauthorizedResetRef.current?.();
}, []);
const normalizeConfigEntries = useCallback((entries) => {
if (!Array.isArray(entries)) {
@@ -117,27 +119,21 @@ function App() {
};
}, []);
const handleUnauthorizedRef = useRef(() => {});
const authorizedFetch = useCallback(
async (url, options = {}, tokenOverride) => {
const activeToken = tokenOverride || session?.token;
if (!activeToken) {
throw new Error('Keine aktive Session');
}
const headers = {
Authorization: `Bearer ${activeToken}`,
...(options.headers || {})
};
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
handleUnauthorizedRef.current();
throw new Error('Nicht autorisiert');
}
return response;
},
[session?.token]
);
const {
session,
authorizedFetch,
bootstrapSession,
performLogout,
handleUnauthorized,
storeToken,
getStoredToken
} = useSessionManager({
normalizeConfigEntries,
normalizeAdminSettings,
onUnauthorized: notifyUnauthorized,
setError,
setLoading
});
const {
config,
@@ -185,8 +181,20 @@ function App() {
onDiscard: () => setIsDirty(false)
});
const applyBootstrapResult = useCallback(
(result = {}) => {
if (!result) {
return {};
}
setStores(Array.isArray(result.stores) ? result.stores : []);
setAdminSettings(result.adminSettings ?? null);
setConfig(Array.isArray(result.config) ? result.config : []);
return result;
},
[setStores, setAdminSettings, setConfig]
);
const resetSessionState = useCallback(() => {
setSession(null);
setConfig([]);
setStores([]);
setStatus('');
@@ -195,73 +203,20 @@ function App() {
setAdminSettingsLoading(false);
setAvailableCollapsed(true);
setInitializing(false);
}, [setConfig]);
const handleUnauthorized = useCallback(() => {
resetSessionState();
try {
localStorage.removeItem(TOKEN_STORAGE_KEY);
} catch (storageError) {
console.warn('Konnte Token nicht aus dem Speicher entfernen:', storageError);
}
}, [resetSessionState]);
}, [
setConfig,
setStores,
setStatus,
setError,
setAdminSettings,
setAdminSettingsLoading,
setAvailableCollapsed,
setInitializing
]);
useEffect(() => {
handleUnauthorizedRef.current = handleUnauthorized;
}, [handleUnauthorized]);
const bootstrapSession = useCallback(
async (token, { progress } = {}) => {
if (!token) {
return {};
}
setLoading(true);
setError('');
progress?.update?.('Session wird aufgebaut...', 20);
try {
const response = await fetch('/api/auth/session', {
headers: { Authorization: `Bearer ${token}` }
});
if (response.status === 401) {
handleUnauthorized();
return;
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
setSession({ token, profile: data.profile, isAdmin: data.isAdmin });
progress?.update?.('Betriebe werden geprüft...', 45);
setStores(Array.isArray(data.stores) ? data.stores : []);
setAdminSettings(data.isAdmin ? normalizeAdminSettings(data.adminSettings) : null);
const configResponse = await fetch('/api/config', {
headers: { Authorization: `Bearer ${token}` }
});
if (configResponse.status === 401) {
handleUnauthorized();
return;
}
if (!configResponse.ok) {
throw new Error(`HTTP ${configResponse.status}`);
}
const configData = await configResponse.json();
progress?.update?.('Konfiguration wird geladen...', 75);
setConfig(normalizeConfigEntries(Array.isArray(configData) ? configData : []));
progress?.update?.('Synchronisierung abgeschlossen', 95);
return {
storeRefreshJob: data.storeRefreshJob,
storesFresh: data.storesFresh
};
} catch (err) {
setError(`Session konnte nicht wiederhergestellt werden: ${err.message}`);
} finally {
setLoading(false);
}
return {};
},
[handleUnauthorized, normalizeAdminSettings, normalizeConfigEntries, setConfig]
);
unauthorizedResetRef.current = resetSessionState;
}, [resetSessionState]);
const {
notificationSettings,
@@ -339,14 +294,12 @@ function App() {
}
const data = await response.json();
try {
localStorage.setItem(TOKEN_STORAGE_KEY, data.token);
} catch (storageError) {
console.warn('Konnte Token nicht speichern:', storageError);
}
storeToken(data.token);
clearInterval(ticker);
updateSyncProgress('Zugang bestätigt. Session wird aufgebaut...', 45);
const bootstrapResult = await bootstrapSession(data.token, { progress: { update: updateSyncProgress } });
const bootstrapResult = applyBootstrapResult(
await bootstrapSession(data.token, { progress: { update: updateSyncProgress } })
);
const needsStoreSync = !bootstrapResult?.storesFresh || !!bootstrapResult?.storeRefreshJob;
if (needsStoreSync) {
await syncStoresWithProgress({
@@ -370,20 +323,6 @@ function App() {
}
};
const performLogout = useCallback(async () => {
if (!session?.token) {
handleUnauthorized();
return;
}
try {
await authorizedFetch('/api/auth/logout', { method: 'POST' });
} catch (err) {
console.warn('Logout fehlgeschlagen:', err);
} finally {
handleUnauthorized();
}
}, [session?.token, authorizedFetch, handleUnauthorized]);
const handleLogout = () => {
requestNavigation('dich abzumelden', performLogout);
};
@@ -417,12 +356,7 @@ function App() {
let ticker;
let cancelled = false;
(async () => {
let storedToken = null;
try {
storedToken = localStorage.getItem(TOKEN_STORAGE_KEY);
} catch (err) {
console.warn('Konnte gespeicherten Token nicht lesen:', err);
}
const storedToken = getStoredToken();
if (!storedToken) {
return;
}
@@ -430,7 +364,9 @@ function App() {
try {
startSyncProgress('Session wird wiederhergestellt...', 5, true);
ticker = setInterval(() => nudgeSyncProgress('Session wird wiederhergestellt...', 1, 40), 1000);
const result = await bootstrapSession(storedToken, { progress: { update: updateSyncProgress } });
const result = applyBootstrapResult(
await bootstrapSession(storedToken, { progress: { update: updateSyncProgress } })
);
if (ticker) {
clearInterval(ticker);
ticker = null;
@@ -462,12 +398,14 @@ function App() {
}
};
}, [
applyBootstrapResult,
bootstrapSession,
startSyncProgress,
updateSyncProgress,
finishSyncProgress,
getStoredToken,
nudgeSyncProgress,
syncStoresWithProgress
startSyncProgress,
syncStoresWithProgress,
updateSyncProgress
]);
useEffect(() => {

View File

@@ -0,0 +1,158 @@
import { useCallback, useEffect, useRef, useState } from 'react';
const TOKEN_STORAGE_KEY = 'pickupConfigToken';
const useSessionManager = ({
normalizeConfigEntries,
normalizeAdminSettings,
onUnauthorized,
setError,
setLoading
}) => {
const [session, setSession] = useState(null);
const unauthorizedRef = useRef(onUnauthorized || (() => {}));
useEffect(() => {
unauthorizedRef.current = onUnauthorized || (() => {});
}, [onUnauthorized]);
const handleUnauthorized = useCallback(() => {
setSession(null);
try {
localStorage.removeItem(TOKEN_STORAGE_KEY);
} catch (storageError) {
console.warn('Konnte Token nicht aus dem Speicher entfernen:', storageError);
}
unauthorizedRef.current();
}, []);
const handleUnauthorizedRef = useRef(() => {});
useEffect(() => {
handleUnauthorizedRef.current = handleUnauthorized;
}, [handleUnauthorized]);
const authorizedFetch = useCallback(
async (url, options = {}, tokenOverride) => {
const activeToken = tokenOverride || session?.token;
if (!activeToken) {
throw new Error('Keine aktive Session');
}
const headers = {
Authorization: `Bearer ${activeToken}`,
...(options.headers || {})
};
const response = await fetch(url, { ...options, headers });
if (response.status === 401) {
handleUnauthorizedRef.current();
throw new Error('Nicht autorisiert');
}
return response;
},
[session?.token]
);
const storeToken = useCallback((token) => {
if (!token) {
return;
}
try {
localStorage.setItem(TOKEN_STORAGE_KEY, token);
} catch (storageError) {
console.warn('Konnte Token nicht speichern:', storageError);
}
}, []);
const getStoredToken = useCallback(() => {
try {
return localStorage.getItem(TOKEN_STORAGE_KEY);
} catch (storageError) {
console.warn('Konnte gespeicherten Token nicht lesen:', storageError);
return null;
}
}, []);
const bootstrapSession = useCallback(
async (token, { progress } = {}) => {
if (!token) {
return {};
}
setLoading(true);
setError('');
progress?.update?.('Session wird aufgebaut...', 20);
try {
const response = await fetch('/api/auth/session', {
headers: { Authorization: `Bearer ${token}` }
});
if (response.status === 401) {
handleUnauthorized();
return {};
}
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
const sessionData = { token, profile: data.profile, isAdmin: data.isAdmin };
setSession(sessionData);
progress?.update?.('Betriebe werden geprüft...', 45);
const stores = Array.isArray(data.stores) ? data.stores : [];
const adminSettings = data.isAdmin ? normalizeAdminSettings(data.adminSettings) : null;
const configResponse = await fetch('/api/config', {
headers: { Authorization: `Bearer ${token}` }
});
if (configResponse.status === 401) {
handleUnauthorized();
return {};
}
if (!configResponse.ok) {
throw new Error(`HTTP ${configResponse.status}`);
}
const configData = await configResponse.json();
progress?.update?.('Konfiguration wird geladen...', 75);
const config = normalizeConfigEntries(Array.isArray(configData) ? configData : []);
progress?.update?.('Synchronisierung abgeschlossen', 95);
return {
session: sessionData,
stores,
adminSettings,
config,
storeRefreshJob: data.storeRefreshJob,
storesFresh: data.storesFresh
};
} catch (error) {
setError(`Session konnte nicht wiederhergestellt werden: ${error.message}`);
return {};
} finally {
setLoading(false);
}
},
[normalizeAdminSettings, normalizeConfigEntries, handleUnauthorized, setError, setLoading]
);
const performLogout = useCallback(async () => {
if (!session?.token) {
handleUnauthorized();
return;
}
try {
await authorizedFetch('/api/auth/logout', { method: 'POST' });
} catch (err) {
console.warn('Logout fehlgeschlagen:', err);
} finally {
handleUnauthorized();
}
}, [session?.token, authorizedFetch, handleUnauthorized]);
return {
session,
authorizedFetch,
bootstrapSession,
performLogout,
handleUnauthorized,
storeToken,
getStoredToken
};
};
export default useSessionManager;