Fix childer tennats
This commit is contained in:
@@ -23,7 +23,12 @@ export const useUserProgress = (
|
||||
queryKey: onboardingKeys.progress(userId),
|
||||
queryFn: () => onboardingService.getUserProgress(userId),
|
||||
enabled: !!userId,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
// OPTIMIZATION: Once onboarding is fully completed, it won't change back
|
||||
// Use longer staleTime (5 min) and gcTime (30 min) to reduce API calls
|
||||
// The select function below will update staleTime based on completion status
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes (increased from 30s - completed status rarely changes)
|
||||
gcTime: 30 * 60 * 1000, // 30 minutes - keep in cache longer
|
||||
refetchOnWindowFocus: false, // Don't refetch on window focus for onboarding status
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -20,6 +20,10 @@ import { parseISO } from 'date-fns';
|
||||
// Debounce delay for SSE-triggered query invalidations (ms)
|
||||
const SSE_INVALIDATION_DEBOUNCE_MS = 500;
|
||||
|
||||
// Delay before SSE invalidations are allowed after initial load (ms)
|
||||
// This prevents duplicate API calls when SSE events arrive during/right after initial fetch
|
||||
const SSE_INITIAL_LOAD_GRACE_PERIOD_MS = 3000;
|
||||
|
||||
// ============================================================
|
||||
// Types
|
||||
// ============================================================
|
||||
@@ -421,6 +425,15 @@ export function useControlPanelData(tenantId: string) {
|
||||
// Ref for debouncing SSE-triggered invalidations
|
||||
const invalidationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const lastEventCountRef = useRef<number>(0);
|
||||
// Track when the initial data was successfully fetched to avoid immediate SSE refetches
|
||||
const initialLoadTimestampRef = useRef<number | null>(null);
|
||||
|
||||
// Update initial load timestamp when query succeeds
|
||||
useEffect(() => {
|
||||
if (query.isSuccess && !initialLoadTimestampRef.current) {
|
||||
initialLoadTimestampRef.current = Date.now();
|
||||
}
|
||||
}, [query.isSuccess]);
|
||||
|
||||
// SSE integration - invalidate query on relevant events (debounced)
|
||||
useEffect(() => {
|
||||
@@ -429,6 +442,17 @@ export function useControlPanelData(tenantId: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
// OPTIMIZATION: Skip SSE-triggered invalidation during grace period after initial load
|
||||
// This prevents duplicate API calls when SSE events arrive during/right after the initial fetch
|
||||
if (initialLoadTimestampRef.current) {
|
||||
const timeSinceInitialLoad = Date.now() - initialLoadTimestampRef.current;
|
||||
if (timeSinceInitialLoad < SSE_INITIAL_LOAD_GRACE_PERIOD_MS) {
|
||||
// Update the event count ref so we don't process these events later
|
||||
lastEventCountRef.current = sseAlerts.length;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const relevantEvents = sseAlerts.filter(event =>
|
||||
event.event_type?.includes('production.') ||
|
||||
event.event_type?.includes('batch_') ||
|
||||
|
||||
@@ -36,14 +36,9 @@ export const TenantSwitcher: React.FC<TenantSwitcherProps> = ({
|
||||
clearError,
|
||||
} = useTenant();
|
||||
|
||||
|
||||
|
||||
// Load tenants on mount
|
||||
useEffect(() => {
|
||||
if (!availableTenants) {
|
||||
loadUserTenants();
|
||||
}
|
||||
}, [availableTenants, loadUserTenants]);
|
||||
// NOTE: Removed duplicate loadUserTenants() useEffect
|
||||
// Tenant loading is already handled by useTenantInitializer at app level (stores/useTenantInitializer.ts)
|
||||
// This was causing duplicate /tenants API calls on every dashboard load
|
||||
|
||||
// Handle click outside to close dropdown
|
||||
useEffect(() => {
|
||||
|
||||
@@ -495,13 +495,19 @@ export function DashboardPage() {
|
||||
const { plan, loading: subLoading } = subscriptionInfo;
|
||||
const tenantId = currentTenant?.id;
|
||||
|
||||
// Fetch onboarding progress
|
||||
// Check if in demo mode - demo users don't need onboarding check
|
||||
// (backend returns fully_completed=true for demo users anyway, but we skip the API call entirely)
|
||||
const isDemoMode = localStorage.getItem('demo_mode') === 'true';
|
||||
|
||||
// Fetch onboarding progress - SKIP for demo users and enterprise tier
|
||||
// Demo users are pre-configured through cloning, so onboarding is always complete
|
||||
const isAuthenticated = useIsAuthenticated();
|
||||
const { data: userProgress, isLoading: progressLoading } = useUserProgress('', {
|
||||
enabled: !!isAuthenticated && plan !== SUBSCRIPTION_TIERS.ENTERPRISE
|
||||
enabled: !!isAuthenticated && !isDemoMode && plan !== SUBSCRIPTION_TIERS.ENTERPRISE
|
||||
});
|
||||
|
||||
const loading = subLoading || progressLoading;
|
||||
// Don't wait for progressLoading if demo mode (we're not fetching it)
|
||||
const loading = subLoading || (!isDemoMode && progressLoading);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && userProgress && !userProgress.fully_completed && plan !== SUBSCRIPTION_TIERS.ENTERPRISE) {
|
||||
|
||||
@@ -54,7 +54,7 @@ export const useTenantInitializer = () => {
|
||||
const demoAccountType = useDemoAccountType();
|
||||
const availableTenants = useAvailableTenants();
|
||||
const currentTenant = useCurrentTenant();
|
||||
const { loadUserTenants, setCurrentTenant } = useTenantActions();
|
||||
const { loadUserTenants, setCurrentTenant, setAvailableTenants } = useTenantActions();
|
||||
|
||||
// Load tenants for authenticated users (but not demo users - they have special initialization below)
|
||||
useEffect(() => {
|
||||
@@ -79,34 +79,35 @@ export const useTenantInitializer = () => {
|
||||
typeof currentTenant === 'object' &&
|
||||
currentTenant.id === virtualTenantId;
|
||||
|
||||
// Determine the appropriate subscription tier based on stored value or account type
|
||||
const subscriptionTier = storedTier as SubscriptionTier || getDemoTierForAccountType(demoAccountType);
|
||||
|
||||
// Get appropriate tenant details based on account type
|
||||
const tenantDetails = getTenantDetailsForAccountType(demoAccountType);
|
||||
|
||||
// Create a complete tenant object matching TenantResponse structure
|
||||
const mockTenant = {
|
||||
id: virtualTenantId,
|
||||
name: tenantDetails.name,
|
||||
subdomain: `demo-${demoSessionId.slice(0, 8)}`,
|
||||
business_type: tenantDetails.business_type,
|
||||
business_model: tenantDetails.business_model,
|
||||
description: tenantDetails.description,
|
||||
address: 'Demo Address',
|
||||
city: 'Madrid',
|
||||
postal_code: '28001',
|
||||
phone: null,
|
||||
is_active: true,
|
||||
subscription_plan: subscriptionTier,
|
||||
subscription_tier: subscriptionTier,
|
||||
ml_model_trained: false,
|
||||
last_training_date: null,
|
||||
owner_id: 'demo-user',
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Only set current tenant if not already valid
|
||||
if (!isValidDemoTenant) {
|
||||
// Determine the appropriate subscription tier based on stored value or account type
|
||||
const subscriptionTier = storedTier as SubscriptionTier || getDemoTierForAccountType(demoAccountType);
|
||||
|
||||
// Get appropriate tenant details based on account type
|
||||
const tenantDetails = getTenantDetailsForAccountType(demoAccountType);
|
||||
|
||||
// Create a complete tenant object matching TenantResponse structure
|
||||
const mockTenant = {
|
||||
id: virtualTenantId,
|
||||
name: tenantDetails.name,
|
||||
subdomain: `demo-${demoSessionId.slice(0, 8)}`,
|
||||
business_type: tenantDetails.business_type,
|
||||
business_model: tenantDetails.business_model,
|
||||
description: tenantDetails.description,
|
||||
address: 'Demo Address',
|
||||
city: 'Madrid',
|
||||
postal_code: '28001',
|
||||
phone: null,
|
||||
is_active: true,
|
||||
subscription_plan: subscriptionTier,
|
||||
subscription_tier: subscriptionTier,
|
||||
ml_model_trained: false,
|
||||
last_training_date: null,
|
||||
owner_id: 'demo-user',
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
// Set the demo tenant as current
|
||||
setCurrentTenant(mockTenant);
|
||||
|
||||
@@ -114,25 +115,45 @@ export const useTenantInitializer = () => {
|
||||
import('../api/client').then(({ apiClient }) => {
|
||||
apiClient.setTenantId(virtualTenantId);
|
||||
});
|
||||
}
|
||||
|
||||
// For enterprise demos, load child tenants immediately
|
||||
if (demoAccountType === 'enterprise') {
|
||||
const mockUserId = 'demo-user';
|
||||
|
||||
import('../api/services/tenant').then(({ TenantService }) => {
|
||||
const tenantService = new TenantService();
|
||||
tenantService.getUserTenants(mockUserId)
|
||||
.then(tenants => {
|
||||
import('../stores/tenant.store').then(({ useTenantStore }) => {
|
||||
useTenantStore.getState().setAvailableTenants(tenants);
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// Silently handle error
|
||||
});
|
||||
});
|
||||
// For professional demos, just set the single mock tenant as available (if not already set)
|
||||
if (demoAccountType !== 'enterprise') {
|
||||
if (!availableTenants || availableTenants.length === 0) {
|
||||
setAvailableTenants([mockTenant as any]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// For enterprise demos, ALWAYS ensure child tenants are loaded
|
||||
// Check if we need to load child tenants:
|
||||
// - availableTenants is empty/null, OR
|
||||
// - availableTenants only has the parent (length === 1)
|
||||
const needsChildTenants = !availableTenants || availableTenants.length <= 1;
|
||||
|
||||
if (needsChildTenants) {
|
||||
console.log('[useTenantInitializer] Enterprise demo - loading child tenants for parent:', virtualTenantId);
|
||||
|
||||
import('../api/services/tenant').then(({ TenantService }) => {
|
||||
const tenantService = new TenantService();
|
||||
// Use getChildTenants with the parent tenant ID (virtualTenantId)
|
||||
// This calls GET /tenants/{tenant_id}/children
|
||||
tenantService.getChildTenants(virtualTenantId)
|
||||
.then(childTenants => {
|
||||
console.log('[useTenantInitializer] Enterprise demo - loaded child tenants:', childTenants?.length, childTenants);
|
||||
// Combine parent tenant with children for the tenant switcher
|
||||
const allTenants = [mockTenant as any, ...childTenants];
|
||||
setAvailableTenants(allTenants);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('[useTenantInitializer] Enterprise demo - failed to load child tenants:', error);
|
||||
// Fallback: at least set the parent mock tenant as available so TenantSwitcher renders
|
||||
if (!availableTenants || availableTenants.length === 0) {
|
||||
setAvailableTenants([mockTenant as any]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [isDemoMode, demoSessionId, demoAccountType, currentTenant, setCurrentTenant]);
|
||||
}, [isDemoMode, demoSessionId, demoAccountType, currentTenant, availableTenants, setCurrentTenant, setAvailableTenants]);
|
||||
};
|
||||
Reference in New Issue
Block a user