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