Skip to content

Commit 2358c62

Browse files
committed
Add a state reducer
1 parent 2d214c9 commit 2358c62

2 files changed

Lines changed: 114 additions & 24 deletions

File tree

src/App.tsx

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { useDashboardData } from './hooks/useDashboardData';
1818
import { useAppUsage } from './hooks/useAppUsage';
1919
import { useAppFilters, APPS_PER_PAGE } from './hooks/useAppFilters';
2020
import { useRepoView } from './hooks/useRepoView';
21+
import { useAppState } from './hooks/useAppState';
2122
import type { Repository } from './types';
2223

2324
const Container = styled.div`
@@ -162,16 +163,12 @@ const ThemeButton = styled.button`
162163
`;
163164

164165
function App() {
165-
const [token, setToken] = useState('');
166-
const [enterpriseUrl, setEnterpriseUrl] = useState('');
167-
const [isConnected, setIsConnected] = useState(false);
168-
const [selectedOrg, setSelectedOrg] = useState('');
169-
const [inactiveDays, setInactiveDays] = useState(90);
166+
const { state, dispatch, setToken, setEnterpriseUrl, setSelectedOrg, setInactiveDays } = useAppState();
167+
const {
168+
token, enterpriseUrl, isConnected, selectedOrg, inactiveDays,
169+
isFirstLoad, usageLoadingStarted, smoothedAuditProgress, usageRefreshKey,
170+
} = state;
170171
const [allRepositories] = useState<Repository[]>([]);
171-
const [isFirstLoad, setIsFirstLoad] = useState(true);
172-
const [usageLoadingStarted, setUsageLoadingStarted] = useState(false);
173-
const [smoothedAuditProgress, setSmoothedAuditProgress] = useState({ checked: 0, total: 0, found: 0 });
174-
const [usageRefreshKey, setUsageRefreshKey] = useState(0);
175172

176173
const {
177174
organizations,
@@ -239,35 +236,37 @@ function App() {
239236

240237
// Track when first load completes
241238
if (isFirstLoad && !loading && usageLoadingStarted && !usageLoading && installations.length > 0) {
242-
setIsFirstLoad(false);
243-
setUsageLoadingStarted(false);
239+
dispatch({ type: 'FIRST_LOAD_COMPLETE' });
244240
}
245241

246242
// Smooth the audit progress - only allow values to increase
247243
useEffect(() => {
248244
if (usageProgress) {
249-
setSmoothedAuditProgress(prev => ({
250-
checked: Math.max(prev.checked, usageProgress.appsChecked),
251-
total: Math.max(prev.total, usageProgress.totalApps),
252-
found: Math.max(prev.found, usageProgress.appsFound),
253-
}));
245+
dispatch({
246+
type: 'UPDATE_AUDIT_PROGRESS',
247+
payload: {
248+
checked: usageProgress.appsChecked,
249+
total: usageProgress.totalApps,
250+
found: usageProgress.appsFound,
251+
},
252+
});
254253
}
255-
}, [usageProgress]);
254+
}, [usageProgress, dispatch]);
256255

257256
// Reset smoothed progress when audit log checking is complete
258257
useEffect(() => {
259258
if (!isFirstLoad && smoothedAuditProgress.total > 0 && smoothedAuditProgress.checked >= smoothedAuditProgress.total) {
260259
const timer = setTimeout(() => {
261-
setSmoothedAuditProgress({ checked: 0, total: 0, found: 0 });
260+
dispatch({ type: 'RESET_AUDIT_PROGRESS' });
262261
}, 1000);
263262
return () => clearTimeout(timer);
264263
}
265-
}, [isFirstLoad, smoothedAuditProgress.checked, smoothedAuditProgress.total]);
264+
}, [isFirstLoad, smoothedAuditProgress.checked, smoothedAuditProgress.total, dispatch]);
266265

267266
// Load app usage when apps are loaded and config is ready
268267
useEffect(() => {
269268
if (apps.size > 0 && organizations.length > 0 && configLoaded) {
270-
setUsageLoadingStarted(true);
269+
dispatch({ type: 'USAGE_LOADING_STARTED' });
271270
const slugs = Array.from(apps.keys());
272271
const orgLogins = organizations.map(o => o.login);
273272
loadUsage(orgLogins, slugs);
@@ -278,12 +277,10 @@ function App() {
278277
const handleConnect = async () => {
279278
if (isConnected) {
280279
refreshData();
281-
setUsageRefreshKey(prev => prev + 1);
280+
dispatch({ type: 'RECONNECT' });
282281
setAppsPage(1);
283-
setIsFirstLoad(true);
284-
setUsageLoadingStarted(false);
285282
} else {
286-
setIsConnected(true);
283+
dispatch({ type: 'CONNECT' });
287284
}
288285
};
289286

src/hooks/useAppState.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { useReducer, useCallback } from 'react';
2+
3+
interface AuditProgress {
4+
checked: number;
5+
total: number;
6+
found: number;
7+
}
8+
9+
export interface AppState {
10+
token: string;
11+
enterpriseUrl: string;
12+
isConnected: boolean;
13+
selectedOrg: string;
14+
inactiveDays: number;
15+
isFirstLoad: boolean;
16+
usageLoadingStarted: boolean;
17+
smoothedAuditProgress: AuditProgress;
18+
usageRefreshKey: number;
19+
}
20+
21+
type AppAction =
22+
| { type: 'SET_TOKEN'; payload: string }
23+
| { type: 'SET_ENTERPRISE_URL'; payload: string }
24+
| { type: 'SET_SELECTED_ORG'; payload: string }
25+
| { type: 'SET_INACTIVE_DAYS'; payload: number }
26+
| { type: 'CONNECT' }
27+
| { type: 'RECONNECT' }
28+
| { type: 'FIRST_LOAD_COMPLETE' }
29+
| { type: 'USAGE_LOADING_STARTED' }
30+
| { type: 'UPDATE_AUDIT_PROGRESS'; payload: AuditProgress }
31+
| { type: 'RESET_AUDIT_PROGRESS' };
32+
33+
const initialState: AppState = {
34+
token: '',
35+
enterpriseUrl: '',
36+
isConnected: false,
37+
selectedOrg: '',
38+
inactiveDays: 90,
39+
isFirstLoad: true,
40+
usageLoadingStarted: false,
41+
smoothedAuditProgress: { checked: 0, total: 0, found: 0 },
42+
usageRefreshKey: 0,
43+
};
44+
45+
function appReducer(state: AppState, action: AppAction): AppState {
46+
switch (action.type) {
47+
case 'SET_TOKEN':
48+
return { ...state, token: action.payload };
49+
case 'SET_ENTERPRISE_URL':
50+
return { ...state, enterpriseUrl: action.payload };
51+
case 'SET_SELECTED_ORG':
52+
return { ...state, selectedOrg: action.payload };
53+
case 'SET_INACTIVE_DAYS':
54+
return { ...state, inactiveDays: action.payload };
55+
case 'CONNECT':
56+
return { ...state, isConnected: true };
57+
case 'RECONNECT':
58+
return {
59+
...state,
60+
usageRefreshKey: state.usageRefreshKey + 1,
61+
isFirstLoad: true,
62+
usageLoadingStarted: false,
63+
};
64+
case 'FIRST_LOAD_COMPLETE':
65+
return { ...state, isFirstLoad: false, usageLoadingStarted: false };
66+
case 'USAGE_LOADING_STARTED':
67+
return { ...state, usageLoadingStarted: true };
68+
case 'UPDATE_AUDIT_PROGRESS':
69+
return {
70+
...state,
71+
smoothedAuditProgress: {
72+
checked: Math.max(state.smoothedAuditProgress.checked, action.payload.checked),
73+
total: Math.max(state.smoothedAuditProgress.total, action.payload.total),
74+
found: Math.max(state.smoothedAuditProgress.found, action.payload.found),
75+
},
76+
};
77+
case 'RESET_AUDIT_PROGRESS':
78+
return { ...state, smoothedAuditProgress: { checked: 0, total: 0, found: 0 } };
79+
default:
80+
return state;
81+
}
82+
}
83+
84+
export function useAppState() {
85+
const [state, dispatch] = useReducer(appReducer, initialState);
86+
87+
const setToken = useCallback((token: string) => dispatch({ type: 'SET_TOKEN', payload: token }), []);
88+
const setEnterpriseUrl = useCallback((url: string) => dispatch({ type: 'SET_ENTERPRISE_URL', payload: url }), []);
89+
const setSelectedOrg = useCallback((org: string) => dispatch({ type: 'SET_SELECTED_ORG', payload: org }), []);
90+
const setInactiveDays = useCallback((days: number) => dispatch({ type: 'SET_INACTIVE_DAYS', payload: days }), []);
91+
92+
return { state, dispatch, setToken, setEnterpriseUrl, setSelectedOrg, setInactiveDays };
93+
}

0 commit comments

Comments
 (0)