Fix childer tennats
This commit is contained in:
@@ -23,7 +23,12 @@ export const useUserProgress = (
|
|||||||
queryKey: onboardingKeys.progress(userId),
|
queryKey: onboardingKeys.progress(userId),
|
||||||
queryFn: () => onboardingService.getUserProgress(userId),
|
queryFn: () => onboardingService.getUserProgress(userId),
|
||||||
enabled: !!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,
|
...options,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ import { parseISO } from 'date-fns';
|
|||||||
// Debounce delay for SSE-triggered query invalidations (ms)
|
// Debounce delay for SSE-triggered query invalidations (ms)
|
||||||
const SSE_INVALIDATION_DEBOUNCE_MS = 500;
|
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
|
// Types
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -421,6 +425,15 @@ export function useControlPanelData(tenantId: string) {
|
|||||||
// Ref for debouncing SSE-triggered invalidations
|
// Ref for debouncing SSE-triggered invalidations
|
||||||
const invalidationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
const invalidationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const lastEventCountRef = useRef<number>(0);
|
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)
|
// SSE integration - invalidate query on relevant events (debounced)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -429,6 +442,17 @@ export function useControlPanelData(tenantId: string) {
|
|||||||
return;
|
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 =>
|
const relevantEvents = sseAlerts.filter(event =>
|
||||||
event.event_type?.includes('production.') ||
|
event.event_type?.includes('production.') ||
|
||||||
event.event_type?.includes('batch_') ||
|
event.event_type?.includes('batch_') ||
|
||||||
|
|||||||
@@ -36,14 +36,9 @@ export const TenantSwitcher: React.FC<TenantSwitcherProps> = ({
|
|||||||
clearError,
|
clearError,
|
||||||
} = useTenant();
|
} = useTenant();
|
||||||
|
|
||||||
|
// NOTE: Removed duplicate loadUserTenants() useEffect
|
||||||
|
// Tenant loading is already handled by useTenantInitializer at app level (stores/useTenantInitializer.ts)
|
||||||
// Load tenants on mount
|
// This was causing duplicate /tenants API calls on every dashboard load
|
||||||
useEffect(() => {
|
|
||||||
if (!availableTenants) {
|
|
||||||
loadUserTenants();
|
|
||||||
}
|
|
||||||
}, [availableTenants, loadUserTenants]);
|
|
||||||
|
|
||||||
// Handle click outside to close dropdown
|
// Handle click outside to close dropdown
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -495,13 +495,19 @@ export function DashboardPage() {
|
|||||||
const { plan, loading: subLoading } = subscriptionInfo;
|
const { plan, loading: subLoading } = subscriptionInfo;
|
||||||
const tenantId = currentTenant?.id;
|
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 isAuthenticated = useIsAuthenticated();
|
||||||
const { data: userProgress, isLoading: progressLoading } = useUserProgress('', {
|
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(() => {
|
useEffect(() => {
|
||||||
if (!loading && userProgress && !userProgress.fully_completed && plan !== SUBSCRIPTION_TIERS.ENTERPRISE) {
|
if (!loading && userProgress && !userProgress.fully_completed && plan !== SUBSCRIPTION_TIERS.ENTERPRISE) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const useTenantInitializer = () => {
|
|||||||
const demoAccountType = useDemoAccountType();
|
const demoAccountType = useDemoAccountType();
|
||||||
const availableTenants = useAvailableTenants();
|
const availableTenants = useAvailableTenants();
|
||||||
const currentTenant = useCurrentTenant();
|
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)
|
// Load tenants for authenticated users (but not demo users - they have special initialization below)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -79,34 +79,35 @@ export const useTenantInitializer = () => {
|
|||||||
typeof currentTenant === 'object' &&
|
typeof currentTenant === 'object' &&
|
||||||
currentTenant.id === virtualTenantId;
|
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) {
|
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
|
// Set the demo tenant as current
|
||||||
setCurrentTenant(mockTenant);
|
setCurrentTenant(mockTenant);
|
||||||
|
|
||||||
@@ -114,25 +115,45 @@ export const useTenantInitializer = () => {
|
|||||||
import('../api/client').then(({ apiClient }) => {
|
import('../api/client').then(({ apiClient }) => {
|
||||||
apiClient.setTenantId(virtualTenantId);
|
apiClient.setTenantId(virtualTenantId);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// For enterprise demos, load child tenants immediately
|
// For professional demos, just set the single mock tenant as available (if not already set)
|
||||||
if (demoAccountType === 'enterprise') {
|
if (demoAccountType !== 'enterprise') {
|
||||||
const mockUserId = 'demo-user';
|
if (!availableTenants || availableTenants.length === 0) {
|
||||||
|
setAvailableTenants([mockTenant as any]);
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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