proposal-system/mobile/src/App.tsx
Adam Moussa 47c3b8e5eb Add mobile core: API client, auth, storage, state, and offline support
Keychain token storage, Axios client with async token injection, Cognito
PKCE auth via react-native-app-auth, Redux auth slice, TanStack Query
config, offline draft queue with NetInfo auto-submit, and SHOC theme.
2026-05-17 15:09:23 -04:00

74 lines
2.2 KiB
TypeScript

import React, { useEffect } from 'react';
import { Provider as ReduxProvider, useDispatch } from 'react-redux';
import { PaperProvider } from 'react-native-paper';
import { QueryClientProvider } from '@tanstack/react-query';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import NetInfo from '@react-native-community/netinfo';
import { Alert } from 'react-native';
import { store } from './store';
import { theme } from './theme';
import { queryClient } from './lib/queryClient';
import { RootNavigator } from './navigation/RootNavigator';
import { tokenStorage, userStorage } from './lib/storage';
import { setUser, setLoading } from './store/slices/authSlice';
import { processOfflineQueue } from './hooks/useOfflineDraft';
import { proposalsApi, CreateProposalRequest } from './lib/api/proposals';
function AuthBootstrap({ children }: { children: React.ReactNode }) {
const dispatch = useDispatch();
useEffect(() => {
(async () => {
try {
const tokens = await tokenStorage.get();
const user = await userStorage.get();
if (tokens && user && new Date(tokens.expiresAt) > new Date()) {
dispatch(setUser(user));
} else {
if (tokens) await tokenStorage.clear();
dispatch(setLoading(false));
}
} catch {
dispatch(setLoading(false));
}
})();
}, [dispatch]);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener((state) => {
if (state.isConnected) {
processOfflineQueue((data) =>
proposalsApi.create(data as CreateProposalRequest),
).then((count) => {
if (count > 0) {
Alert.alert(
'Synced',
`${count} offline proposal${count > 1 ? 's' : ''} submitted.`,
);
}
});
}
});
return unsubscribe;
}, []);
return <>{children}</>;
}
export default function App() {
return (
<ReduxProvider store={store}>
<QueryClientProvider client={queryClient}>
<PaperProvider theme={theme}>
<SafeAreaProvider>
<AuthBootstrap>
<RootNavigator />
</AuthBootstrap>
</SafeAreaProvider>
</PaperProvider>
</QueryClientProvider>
</ReduxProvider>
);
}