155 lines
4.6 KiB
TypeScript
155 lines
4.6 KiB
TypeScript
import { ApolloClient, ApolloProvider, HttpLink, InMemoryCache, from } from '@apollo/client';
|
|
import { loadDevMessages, loadErrorMessages } from '@apollo/client/dev';
|
|
import { onError } from '@apollo/client/link/error';
|
|
import { Box, Center, Spinner } from '@chakra-ui/react';
|
|
import '@client/assets/styles/index.css';
|
|
import '@client/assets/styles/nerd-fonts-icons.css';
|
|
import { Provider as ChakraProvider } from '@client/components/ui/provider';
|
|
import { Toaster } from '@client/components/ui/toaster';
|
|
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
|
import { StrictMode, useEffect, useState } from 'react';
|
|
import ReactDOM from 'react-dom/client';
|
|
import { AuthProvider, useRestAuth } from './hooks/auth';
|
|
import { IdentityProvider, useIdentityQuery } from './hooks/identity';
|
|
|
|
// TanStackRouter - Import the generated route tree
|
|
import { Session } from '@generated/types';
|
|
import { routeTree } from './routeTree.gen';
|
|
|
|
// Create a new router instance
|
|
export const router = createRouter({
|
|
routeTree,
|
|
context: {
|
|
isAuthenticated: false
|
|
}
|
|
});
|
|
|
|
// Register the router instance for type safety
|
|
declare module '@tanstack/react-router' {
|
|
interface Register {
|
|
router: typeof router;
|
|
}
|
|
}
|
|
|
|
// Environment
|
|
const mode = import.meta.env.VITE_MODE || 'prod';
|
|
const isProd = mode === 'prod';
|
|
|
|
export const appName = import.meta.env.VITE_APP_NAME || 'project-ln';
|
|
|
|
const host = import.meta.env.VITE_HOST || 'localhost';
|
|
const port = import.meta.env.VITE_PORT || '3000';
|
|
export const url = `https://${host}:${port}`;
|
|
const graphqlUri = `${url}/graphql`;
|
|
|
|
function App() {
|
|
const [authHeaders, setAuthHeaders] = useState<Session | null>(null);
|
|
const [initialLoading, setInitialLoading] = useState<boolean>(true);
|
|
const { refreshToken } = useRestAuth();
|
|
|
|
useEffect(() => {
|
|
const getPreviousSession = async () => {
|
|
const session = await refreshToken();
|
|
if (session !== undefined) setAuthHeaders(session);
|
|
setInitialLoading(false);
|
|
};
|
|
if (initialLoading) getPreviousSession();
|
|
}, []);
|
|
|
|
if (initialLoading)
|
|
return (
|
|
<StrictMode>
|
|
<ChakraProvider>
|
|
<Box pos="absolute" inset="0">
|
|
<Center h="full">
|
|
<Spinner color="colorPalette.500" />
|
|
</Center>
|
|
</Box>
|
|
</ChakraProvider>
|
|
</StrictMode>
|
|
);
|
|
|
|
const httpLink = new HttpLink({
|
|
uri: graphqlUri,
|
|
credentials: 'include',
|
|
headers: authHeaders != null ? { authorization: JSON.stringify(authHeaders) } : undefined
|
|
});
|
|
|
|
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
|
|
if (graphQLErrors != null && graphQLErrors.length > 0)
|
|
graphQLErrors.map(async error => {
|
|
if (error.message.includes('jwt expired')) {
|
|
const session = await refreshToken();
|
|
if (session !== undefined) setAuthHeaders(session);
|
|
return forward(operation);
|
|
}
|
|
if (!isProd)
|
|
console.error(
|
|
`[GraphQlError]: Message: ${error.message}, Location: ${JSON.stringify(error.locations)}, Path: ${error.path}`
|
|
);
|
|
});
|
|
|
|
if (networkError != null && graphQLErrors == null) console.error(networkError);
|
|
});
|
|
|
|
if (!isProd) {
|
|
loadDevMessages();
|
|
loadErrorMessages();
|
|
}
|
|
|
|
const client = new ApolloClient({
|
|
uri: graphqlUri,
|
|
link: from([errorLink, httpLink]),
|
|
cache: new InMemoryCache(),
|
|
connectToDevTools: !isProd,
|
|
credentials: 'include'
|
|
});
|
|
|
|
return (
|
|
<StrictMode>
|
|
<AuthProvider
|
|
value={{
|
|
headers: authHeaders,
|
|
setHeaders: (session: Session | null) => {
|
|
setAuthHeaders(session);
|
|
}
|
|
}}
|
|
>
|
|
<ApolloProvider client={client}>
|
|
<ChakraProvider>
|
|
<Toaster />
|
|
<RouterProviderWrapper authHeaders={authHeaders} />
|
|
</ChakraProvider>
|
|
</ApolloProvider>
|
|
</AuthProvider>
|
|
</StrictMode>
|
|
);
|
|
}
|
|
|
|
function RouterProviderWrapper({ authHeaders }: { authHeaders: Session | null }) {
|
|
const userId = authHeaders?.userId;
|
|
const { data, loading, refetch } = useIdentityQuery({ skip: userId == null });
|
|
|
|
if (loading)
|
|
return (
|
|
<Box pos="absolute" inset="0">
|
|
<Center h="full">
|
|
<Spinner color="colorPalette.500" />
|
|
</Center>
|
|
</Box>
|
|
);
|
|
|
|
return (
|
|
<IdentityProvider
|
|
value={userId != null && data != null ? { data: data.identity, refetch } : null}
|
|
>
|
|
<RouterProvider router={router} context={{ isAuthenticated: authHeaders != null }} />
|
|
</IdentityProvider>
|
|
);
|
|
}
|
|
|
|
const rootElement = document.getElementById('root')!;
|
|
if (!rootElement.innerHTML) {
|
|
const root = ReactDOM.createRoot(rootElement);
|
|
root.render(<App />);
|
|
}
|