ln/app/client/main.tsx
2025-06-01 17:54:57 +03:00

155 lines
4.7 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_DOMAIN_NAME || 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 />);
}