diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx index fbefa9a574..5227977542 100644 --- a/web/src/routes/__root.tsx +++ b/web/src/routes/__root.tsx @@ -1,26 +1,83 @@ import type { QueryClient } from '@tanstack/react-query'; -import { createRootRouteWithContext, Outlet, redirect } from '@tanstack/react-router'; +import { + createRootRouteWithContext, + Outlet, + type ParsedLocation, + redirect, +} from '@tanstack/react-router'; import { AppLoaderPage } from '../pages/AppLoaderPage/AppLoaderPage'; +import { useSetupWizardStore } from '../pages/SetupPage/useSetupWizardStore'; import { SnackbarManager } from '../shared/defguard-ui/providers/snackbar/SnackbarManager'; import { useAuth } from '../shared/hooks/useAuth'; -import { getUserMeQueryOptions } from '../shared/query'; +import { + getSettingsEssentialsQueryOptions, + getUserMeQueryOptions, +} from '../shared/query'; interface RouterContext { queryClient: QueryClient; } +// Handles the initial wizard redirect. +// All routes should redirect to the setup wizard if the initial setup is not completed. +const handleWizardRedirect = async ({ + location, + context, +}: { + location: ParsedLocation; + context: RouterContext; +}) => { + const settingsEssentials = ( + await ( + await context.queryClient.ensureQueryData(getSettingsEssentialsQueryOptions) + )() + ).data; + + // Tries to access any route but setup is not completed + const setupNotCompletedAnyAccess = + !settingsEssentials.initial_setup_completed && + !location.pathname.startsWith('/setup-wizard'); + + // Tries to access setup wizard but setup is already completed + const setupCompletedButAccessingWizard = + settingsEssentials.initial_setup_completed && + location.pathname.startsWith('/setup-wizard'); + + if (setupNotCompletedAnyAccess) { + useSetupWizardStore.getState().reset(); + throw redirect({ to: '/setup-wizard', replace: true }); + } else if (setupCompletedButAccessingWizard) { + throw redirect({ to: '/auth/login', replace: true }); + } +}; + export const Route = createRootRouteWithContext()({ component: RootComponent, beforeLoad: async ({ location, context }) => { - if (location.pathname.startsWith('/auth')) { + await handleWizardRedirect({ + location, + context, + }); + + if ( + location.pathname.startsWith('/auth') || + location.pathname.startsWith('/setup-wizard') + ) { return; } + try { const user = ( await ( await context.queryClient.ensureQueryData(getUserMeQueryOptions) )() ).data; + + // Invalid user object + if (!user.id) { + throw redirect({ to: '/auth/login', replace: true }); + } + useAuth.getState().setUser(user); } catch (_) { useAuth.getState().reset(); diff --git a/web/src/shared/query.ts b/web/src/shared/query.ts index 401db9b28d..68fa20f71e 100644 --- a/web/src/shared/query.ts +++ b/web/src/shared/query.ts @@ -168,3 +168,14 @@ export const getActivityLogStreamsQueryOptions = queryOptions({ queryKey: ['activity_log_stream'], select: (resp) => resp.data, }); + +export const getSettingsEssentialsQueryOptions = queryOptions({ + queryFn: () => api.settings.getSettingsEssentials, + queryKey: ['settings-essentials'], + throwOnError: false, + retry: false, + refetchOnWindowFocus: false, + refetchOnMount: true, + refetchOnReconnect: true, + staleTime: 60_000, +});