CRITICAL FIX: This resolves the root cause of dashboard loading state issue. Problem: - Dashboard components stayed in loading state even with API 200 OK responses - React Query hooks had `enabled: !!tenantId` flag (from previous fix) - But tenantId was always empty string because useTenantInitializer was never called Root Cause: The useTenantInitializer hook was only called in UnifiedOnboardingWizard, not at the app level. This hook is responsible for: 1. Loading user tenants when authenticated (loadUserTenants) 2. Automatically setting first tenant as current if none is set 3. Creating mock tenant for demo mode with proper tenantId Fix: Added useTenantInitializer() call to AppContent component in App.tsx. Now it runs on every app load, ensuring: - Authenticated users: tenants loaded from API, first one set as current - Demo mode: mock tenant created with valid demo-tenant-id - tenantId is available before dashboard queries execute Flow after fix: 1. App loads → useTenantInitializer runs 2. Tenant loaded/created → tenantId populated 3. Dashboard loads → React Query hooks enabled (tenantId exists) 4. Queries execute → Data fetched → Components render Related files: - frontend/src/stores/useTenantInitializer.ts (hook definition) - frontend/src/stores/tenant.store.ts (tenant state management) - frontend/src/pages/app/DashboardPage.tsx (uses currentTenant.id)
86 lines
2.6 KiB
TypeScript
86 lines
2.6 KiB
TypeScript
import { Suspense } from 'react';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
|
import { BrowserRouter, useNavigate } from 'react-router-dom';
|
|
import { I18nextProvider } from 'react-i18next';
|
|
import { Toaster } from 'react-hot-toast';
|
|
import { ErrorBoundary } from './components/layout/ErrorBoundary';
|
|
import { LoadingSpinner } from './components/ui';
|
|
import { AppRouter } from './router/AppRouter';
|
|
import { ThemeProvider } from './contexts/ThemeContext';
|
|
import { AuthProvider } from './contexts/AuthContext';
|
|
import { SSEProvider } from './contexts/SSEContext';
|
|
import { SubscriptionEventsProvider } from './contexts/SubscriptionEventsContext';
|
|
import GlobalSubscriptionHandler from './components/auth/GlobalSubscriptionHandler';
|
|
import { CookieBanner } from './components/ui/CookieConsent';
|
|
import { useTenantInitializer } from './stores/useTenantInitializer';
|
|
import i18n from './i18n';
|
|
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 5 * 60 * 1000,
|
|
gcTime: 10 * 60 * 1000,
|
|
retry: 3,
|
|
refetchOnWindowFocus: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
function AppContent() {
|
|
const navigate = useNavigate();
|
|
|
|
// Initialize tenant data when user is authenticated or in demo mode
|
|
useTenantInitializer();
|
|
|
|
return (
|
|
<>
|
|
<Suspense fallback={<LoadingSpinner overlay />}>
|
|
<AppRouter />
|
|
<GlobalSubscriptionHandler />
|
|
<CookieBanner onPreferencesClick={() => navigate('/cookie-preferences')} />
|
|
<Toaster
|
|
position="top-right"
|
|
toastOptions={{
|
|
duration: 4000,
|
|
style: {
|
|
background: '#363636',
|
|
color: '#fff',
|
|
},
|
|
}}
|
|
/>
|
|
</Suspense>
|
|
</>
|
|
);
|
|
}
|
|
|
|
function App() {
|
|
return (
|
|
<ErrorBoundary>
|
|
<QueryClientProvider client={queryClient}>
|
|
<I18nextProvider i18n={i18n}>
|
|
<BrowserRouter
|
|
future={{
|
|
v7_startTransition: true,
|
|
v7_relativeSplatPath: true,
|
|
}}
|
|
>
|
|
<ThemeProvider>
|
|
<AuthProvider>
|
|
<SSEProvider>
|
|
<SubscriptionEventsProvider>
|
|
<AppContent />
|
|
</SubscriptionEventsProvider>
|
|
</SSEProvider>
|
|
</AuthProvider>
|
|
</ThemeProvider>
|
|
</BrowserRouter>
|
|
<ReactQueryDevtools initialIsOpen={false} />
|
|
</I18nextProvider>
|
|
</QueryClientProvider>
|
|
</ErrorBoundary>
|
|
);
|
|
}
|
|
|
|
export default App;
|