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 (
);
}
return (
<>
{pageTitle}
{isTrial && recruiter?.subscriptionSettings === "trial" && }
>
);
}
const App = () => {
return (
);
};
export default App;