import { RouterProvider, createRouter } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; import { QueryClientProvider } from "@tanstack/react-query"; import { queryClient } from "./lib/query"; import { useAuthStore } from "@/stores/auth/authStore"; import { RouterContext } from "@/types/routerContext"; import { useState, useEffect, useRef } from "react"; import NotFound from "@/app/pages/404/page"; import { Toaster } from "@/components/ui/sonner"; import { Helmet } from "react-helmet-async"; import TrialDialog from "./app/pages/billing/components/TrialDialog"; import { useBillingStore } from "@/stores/billing/BillingStore"; import useUserStore from "@/stores/Data/useUserStore"; import { useOnboarding } from "@/hooks/useOnboarding"; import { useQuery } from "@tanstack/react-query"; import { getCalls } from "@/api/authApi"; import { getTheme } from "./api/theme"; import { useTheme } from "@/hooks/useTheme"; import { GoogleOAuthProvider } from "@react-oauth/google"; const createAppRouter = (authStore: RouterContext["authStore"]) => createRouter({ routeTree, context: { authStore, queryClient, } as RouterContext, defaultPreloadDelay: 100, defaultNotFoundComponent: () => , }); declare module "@tanstack/react-router" { interface Register { router: typeof createAppRouter; } } function InnerApp() { const authStore = useAuthStore(); const [router] = useState(() => createAppRouter(authStore)); const { user, isAuthenticated, isHydrated } = useAuthStore(); const { isTrial } = useBillingStore(); const recruiter = useUserStore((state: unknown) => (state as { user: { subscriptionSettings?: string } | null }).user); const { themeData } = useTheme(); const hasHydrated = useRef(false); // Simplified initialization const [isInitializing, setIsInitializing] = useState(true); useEffect(() => { if (!hasHydrated.current) { hasHydrated.current = true; // Hydrate auth state useAuthStore.getState().hydrate().finally(() => { // Remove HTML loader after hydration const htmlLoader = document.getElementById('initial-loader'); if (htmlLoader) { htmlLoader.style.opacity = '0'; setTimeout(() => htmlLoader.remove(), 100); } // Mark initialization as complete setIsInitializing(false); }); } }, []); // Defer non-critical API calls until after initial render const shouldFetchCalls = !!(user && isAuthenticated && isHydrated && !isInitializing); const { data: callsData, error: callsError } = useQuery({ queryKey: ["calls"], queryFn: () => new Promise(resolve => { // Defer this call by 1 second to not block initial render setTimeout(() => resolve(getCalls()), 1000); }), enabled: shouldFetchCalls, staleTime: 1000 * 60 * 15, // 15 minutes cache refetchInterval: false, // Disable automatic refetching refetchOnWindowFocus: false, retry: 1, refetchOnMount: false, }); const minutesLimits = (callsData as { minutesLimits?: unknown })?.minutesLimits; const { shouldShowOnboarding, isStrictOnboarding } = useOnboarding(minutesLimits as { totalAllowed: number; minutesUsed: number; freeMinutes: number; secondsRemainder: number; } | undefined); // Defer theme loading significantly - not critical for initial render const { data: themeQueryData, error: themeError } = useQuery({ queryKey: ["theme"], queryFn: () => new Promise(resolve => { // Defer theme loading by 2 seconds setTimeout(() => resolve(getTheme()), 2000); }), enabled: shouldFetchCalls && !isInitializing, // Only after user data is loaded staleTime: 1000 * 60 * 60 * 4, // 4 hours cache retry: 1, refetchOnWindowFocus: false, }); // Check if user should see onboarding but can skip (non-subscribed users) const shouldShowSkippableOnboarding = !!( user && isAuthenticated && isHydrated && user.isOnboarded && !user.role?.includes('superAdmin') && user.userType !== 'enterprise' && minutesLimits && (minutesLimits as { minutesUsed: number }).minutesUsed > 0 && !isStrictOnboarding ); // Remove debug logs and optimize theme effect useEffect(() => { // Only log in development if (process.env.NODE_ENV === 'development') { if (themeQueryData) { console.log("Theme loaded:", (themeQueryData as { data?: { data?: { theme?: unknown } } })?.data?.data?.theme); } if (themeError) { console.error("Failed to fetch theme:", themeError); } } }, [themeQueryData, themeError]); // Optimize onboarding logic with memoization and reduced dependencies useEffect(() => { // Only run onboarding logic if user is authenticated and hydrated and not initializing if (!user || !isAuthenticated || !isHydrated || isInitializing) return; // Prevent redirect loops - if we're already on onboarding, don't redirect again if (window.location.pathname === '/onboarding') return; // Only log in development if (process.env.NODE_ENV === 'development') { console.log("Onboarding check:", { user: user?.email, pathname: window.location.pathname, shouldShowOnboarding: shouldShowOnboarding(), isStrictOnboarding }); } // Force onboarding for required cases (can't skip) if (shouldShowOnboarding()) { router.navigate({ to: '/onboarding' }); return; } // If strict onboarding, block navigation away from onboarding if (isStrictOnboarding) { router.navigate({ to: '/onboarding' }); return; } // Show skippable onboarding only on login/dashboard visit, but don't force it on every navigation if (shouldShowSkippableOnboarding && (window.location.pathname === '/' || window.location.pathname === '/dashboard') && !sessionStorage.getItem('onboardingSkipped')) { router.navigate({ to: '/onboarding' }); } }, [user, isAuthenticated, isHydrated, isInitializing, shouldShowOnboarding, isStrictOnboarding, shouldShowSkippableOnboarding, router]); // If getCalls fails, do not log out or break session, just ignore onboarding logic if (callsError) { // Optionally log error, but do not break session // console.error('Failed to fetch call limits:', callsError); } // Use theme title if available, otherwise fall back to user company name or default const pageTitle = themeData?.title || user?.companyName || "AI Dashboard"; // Show loading spinner while initializing if (isInitializing) { return (

Loading...

); } return ( <>
{pageTitle} {isTrial && recruiter?.subscriptionSettings === "trial" && }
); } const App = () => { return (
); }; export default App;