Add frontend loading imporvements 2
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* Subscription hook for checking plan features and limits
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { subscriptionService } from '../services/subscription';
|
||||
import {
|
||||
@@ -54,13 +54,14 @@ export const useSubscription = () => {
|
||||
});
|
||||
|
||||
// Derive subscription info from query data or tenant fallback
|
||||
const subscriptionInfo: SubscriptionInfo = {
|
||||
// IMPORTANT: Memoize to prevent infinite re-renders in dependent hooks
|
||||
const subscriptionInfo: SubscriptionInfo = useMemo(() => ({
|
||||
plan: usageSummary?.plan || initialPlan,
|
||||
status: usageSummary?.status || 'active',
|
||||
features: usageSummary?.usage || {},
|
||||
loading: isLoading,
|
||||
error: error ? 'Failed to load subscription data' : undefined,
|
||||
};
|
||||
}), [usageSummary?.plan, usageSummary?.status, usageSummary?.usage, initialPlan, isLoading, error]);
|
||||
|
||||
// Check if user has a specific feature
|
||||
const hasFeature = useCallback(async (featureName: string): Promise<SubscriptionFeature> => {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React, { useState, useCallback, forwardRef } from 'react';
|
||||
import { clsx } from 'clsx';
|
||||
import { useAuthUser, useIsAuthenticated } from '../../../stores';
|
||||
import { useTheme } from '../../../contexts/ThemeContext';
|
||||
import { useTenantInitializer } from '../../../stores/useTenantInitializer';
|
||||
import { useHasAccess } from '../../../hooks/useAccessControl';
|
||||
import { Header } from '../Header';
|
||||
import { Sidebar } from '../Sidebar';
|
||||
@@ -80,9 +78,6 @@ export const AppShell = forwardRef<AppShellRef, AppShellProps>(({
|
||||
const { resolvedTheme } = useTheme();
|
||||
const hasAccess = useHasAccess(); // Check both authentication and demo mode
|
||||
|
||||
// Initialize tenant data for authenticated users
|
||||
useTenantInitializer();
|
||||
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(initialSidebarCollapsed);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
@@ -264,23 +264,6 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
const allUserRoles = [...globalUserRoles, ...tenantRoles];
|
||||
const tenantPermissions = currentTenantAccess?.permissions || [];
|
||||
|
||||
// Debug logging for analytics route
|
||||
if (item.path === '/app/analytics') {
|
||||
console.log('🔍 [Sidebar] Checking analytics menu item:', {
|
||||
path: item.path,
|
||||
requiredRoles: item.requiredRoles,
|
||||
requiredPermissions: item.requiredPermissions,
|
||||
globalUserRoles,
|
||||
tenantRoles,
|
||||
allUserRoles,
|
||||
tenantPermissions,
|
||||
isAuthenticated,
|
||||
hasAccess,
|
||||
user,
|
||||
currentTenantAccess
|
||||
});
|
||||
}
|
||||
|
||||
// If no specific permissions/roles required, allow access
|
||||
if (!item.requiredPermissions && !item.requiredRoles) {
|
||||
return true;
|
||||
@@ -298,10 +281,6 @@ export const Sidebar = forwardRef<SidebarRef, SidebarProps>(({
|
||||
tenantPermissions
|
||||
);
|
||||
|
||||
if (item.path === '/app/analytics') {
|
||||
console.log('🔍 [Sidebar] Analytics canAccessRoute result:', canAccessItem);
|
||||
}
|
||||
|
||||
return canAccessItem;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1041,10 +1041,14 @@
|
||||
}
|
||||
},
|
||||
"itemTypeSelector": {
|
||||
"searchPlaceholder": "Search by name or category...",
|
||||
"title": "Select Type",
|
||||
"description": "Choose what you want to add",
|
||||
"search": {
|
||||
"placeholder": "Search by name or category...",
|
||||
"noResults": "No results found",
|
||||
"resultSingular": "result",
|
||||
"resultPlural": "results",
|
||||
"resultPlural": "results"
|
||||
},
|
||||
"categories": {
|
||||
"all": "All",
|
||||
"daily": "Daily",
|
||||
@@ -1056,6 +1060,63 @@
|
||||
"daily": "Daily",
|
||||
"common": "Common",
|
||||
"setup": "Setup"
|
||||
},
|
||||
"items": {
|
||||
"inventory": {
|
||||
"title": "Inventory",
|
||||
"subtitle": "Add ingredients or products to your inventory",
|
||||
"keywords": ["stock", "ingredients", "products", "warehouse"]
|
||||
},
|
||||
"supplier": {
|
||||
"title": "Supplier",
|
||||
"subtitle": "Add a new supplier or vendor",
|
||||
"keywords": ["vendor", "purchases", "supplies"]
|
||||
},
|
||||
"recipe": {
|
||||
"title": "Recipe",
|
||||
"subtitle": "Create a new recipe or formula",
|
||||
"keywords": ["formula", "preparation", "ingredients"]
|
||||
},
|
||||
"equipment": {
|
||||
"title": "Equipment",
|
||||
"subtitle": "Register bakery equipment or machinery",
|
||||
"keywords": ["oven", "mixer", "equipment", "machine"]
|
||||
},
|
||||
"quality-template": {
|
||||
"title": "Quality Template",
|
||||
"subtitle": "Create a quality control template",
|
||||
"keywords": ["control", "quality", "inspection", "verification"]
|
||||
},
|
||||
"customer-order": {
|
||||
"title": "Customer Order",
|
||||
"subtitle": "Create a new customer order",
|
||||
"keywords": ["order", "customer", "sale"]
|
||||
},
|
||||
"customer": {
|
||||
"title": "Customer",
|
||||
"subtitle": "Add a new customer",
|
||||
"keywords": ["customer", "buyer", "contact"]
|
||||
},
|
||||
"team-member": {
|
||||
"title": "Team Member",
|
||||
"subtitle": "Add a team member or employee",
|
||||
"keywords": ["employee", "worker", "staff"]
|
||||
},
|
||||
"sales-entry": {
|
||||
"title": "Sales Entry",
|
||||
"subtitle": "Record a sales transaction",
|
||||
"keywords": ["sale", "cash", "revenue", "transaction"]
|
||||
},
|
||||
"purchase-order": {
|
||||
"title": "Purchase Order",
|
||||
"subtitle": "Create a purchase order to supplier",
|
||||
"keywords": ["purchase", "order", "supplier"]
|
||||
},
|
||||
"production-batch": {
|
||||
"title": "Production Batch",
|
||||
"subtitle": "Create a new production batch",
|
||||
"keywords": ["production", "batch", "manufacturing"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1426,10 +1426,14 @@
|
||||
}
|
||||
},
|
||||
"itemTypeSelector": {
|
||||
"searchPlaceholder": "Buscar por nombre o categoría...",
|
||||
"title": "Seleccionar Tipo",
|
||||
"description": "Elige qué deseas agregar",
|
||||
"search": {
|
||||
"placeholder": "Buscar por nombre o categoría...",
|
||||
"noResults": "No se encontraron resultados",
|
||||
"resultSingular": "resultado",
|
||||
"resultPlural": "resultados",
|
||||
"resultPlural": "resultados"
|
||||
},
|
||||
"categories": {
|
||||
"all": "Todos",
|
||||
"daily": "Diario",
|
||||
@@ -1441,6 +1445,63 @@
|
||||
"daily": "Diario",
|
||||
"common": "Común",
|
||||
"setup": "Configuración"
|
||||
},
|
||||
"items": {
|
||||
"inventory": {
|
||||
"title": "Inventario",
|
||||
"subtitle": "Agregar ingredientes o productos a tu inventario",
|
||||
"keywords": ["stock", "ingredientes", "productos", "almacén"]
|
||||
},
|
||||
"supplier": {
|
||||
"title": "Proveedor",
|
||||
"subtitle": "Agregar un nuevo proveedor o vendedor",
|
||||
"keywords": ["vendedor", "compras", "suministros"]
|
||||
},
|
||||
"recipe": {
|
||||
"title": "Receta",
|
||||
"subtitle": "Crear una nueva receta o fórmula",
|
||||
"keywords": ["fórmula", "preparación", "ingredientes"]
|
||||
},
|
||||
"equipment": {
|
||||
"title": "Maquinaria",
|
||||
"subtitle": "Registrar equipo o maquinaria de panadería",
|
||||
"keywords": ["horno", "amasadora", "equipo", "máquina"]
|
||||
},
|
||||
"quality-template": {
|
||||
"title": "Plantilla de Calidad",
|
||||
"subtitle": "Crear una plantilla de control de calidad",
|
||||
"keywords": ["control", "calidad", "inspección", "verificación"]
|
||||
},
|
||||
"customer-order": {
|
||||
"title": "Pedido de Cliente",
|
||||
"subtitle": "Crear un nuevo pedido de cliente",
|
||||
"keywords": ["pedido", "orden", "cliente", "venta"]
|
||||
},
|
||||
"customer": {
|
||||
"title": "Cliente",
|
||||
"subtitle": "Agregar un nuevo cliente",
|
||||
"keywords": ["cliente", "comprador", "contacto"]
|
||||
},
|
||||
"team-member": {
|
||||
"title": "Miembro del Equipo",
|
||||
"subtitle": "Agregar un miembro del equipo o empleado",
|
||||
"keywords": ["empleado", "trabajador", "personal", "staff"]
|
||||
},
|
||||
"sales-entry": {
|
||||
"title": "Registro de Ventas",
|
||||
"subtitle": "Registrar una transacción de venta",
|
||||
"keywords": ["venta", "caja", "ingreso", "transacción"]
|
||||
},
|
||||
"purchase-order": {
|
||||
"title": "Orden de Compra",
|
||||
"subtitle": "Crear una orden de compra a proveedor",
|
||||
"keywords": ["compra", "orden", "proveedor", "pedido"]
|
||||
},
|
||||
"production-batch": {
|
||||
"title": "Lote de Producción",
|
||||
"subtitle": "Crear un nuevo lote de producción",
|
||||
"keywords": ["producción", "lote", "fabricación", "elaboración"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,10 +1035,14 @@
|
||||
}
|
||||
},
|
||||
"itemTypeSelector": {
|
||||
"searchPlaceholder": "Bilatu izenaren edo kategoriaren arabera...",
|
||||
"title": "Mota Hautatu",
|
||||
"description": "Aukeratu zer gehitu nahi duzun",
|
||||
"search": {
|
||||
"placeholder": "Bilatu izenaren edo kategoriaren arabera...",
|
||||
"noResults": "Ez da emaitzarik aurkitu",
|
||||
"resultSingular": "emaitza",
|
||||
"resultPlural": "emaitzak",
|
||||
"resultPlural": "emaitzak"
|
||||
},
|
||||
"categories": {
|
||||
"all": "Guztiak",
|
||||
"daily": "Egunerokoa",
|
||||
@@ -1050,6 +1054,63 @@
|
||||
"daily": "Egunerokoa",
|
||||
"common": "Arrunta",
|
||||
"setup": "Konfigurazioa"
|
||||
},
|
||||
"items": {
|
||||
"inventory": {
|
||||
"title": "Inbentarioa",
|
||||
"subtitle": "Gehitu osagaiak edo produktuak zure inbentariora",
|
||||
"keywords": ["stock", "osagaiak", "produktuak", "biltegi"]
|
||||
},
|
||||
"supplier": {
|
||||
"title": "Hornitzailea",
|
||||
"subtitle": "Gehitu hornitzaile edo saltzaile berri bat",
|
||||
"keywords": ["saltzaile", "erosketak", "hornidura"]
|
||||
},
|
||||
"recipe": {
|
||||
"title": "Errezeta",
|
||||
"subtitle": "Sortu errezeta edo formula berri bat",
|
||||
"keywords": ["formula", "prestaketa", "osagaiak"]
|
||||
},
|
||||
"equipment": {
|
||||
"title": "Ekipamendua",
|
||||
"subtitle": "Erregistratu okindegiaren ekipamendua edo makina",
|
||||
"keywords": ["labea", "oramailea", "ekipamendua", "makina"]
|
||||
},
|
||||
"quality-template": {
|
||||
"title": "Kalitate Txantiloia",
|
||||
"subtitle": "Sortu kalitate kontrol txantiloi bat",
|
||||
"keywords": ["kontrola", "kalitatea", "ikuskatzea", "egiaztapena"]
|
||||
},
|
||||
"customer-order": {
|
||||
"title": "Bezeroaren Eskaera",
|
||||
"subtitle": "Sortu bezero eskaera berri bat",
|
||||
"keywords": ["eskaera", "bezeroa", "salmenta"]
|
||||
},
|
||||
"customer": {
|
||||
"title": "Bezeroa",
|
||||
"subtitle": "Gehitu bezero berri bat",
|
||||
"keywords": ["bezeroa", "eroslea", "kontaktua"]
|
||||
},
|
||||
"team-member": {
|
||||
"title": "Taldeko Kidea",
|
||||
"subtitle": "Gehitu taldeko kide edo langile bat",
|
||||
"keywords": ["langilea", "enplegatu", "pertsonal"]
|
||||
},
|
||||
"sales-entry": {
|
||||
"title": "Salmenta Erregistroa",
|
||||
"subtitle": "Erregistratu salmenta transakzio bat",
|
||||
"keywords": ["salmenta", "kutxa", "diru-sarrera", "transakzioa"]
|
||||
},
|
||||
"purchase-order": {
|
||||
"title": "Erosketa Eskaera",
|
||||
"subtitle": "Sortu erosketa eskaera hornitzaileari",
|
||||
"keywords": ["erosketa", "eskaera", "hornitzailea"]
|
||||
},
|
||||
"production-batch": {
|
||||
"title": "Ekoizpen Lotea",
|
||||
"subtitle": "Sortu ekoizpen lote berri bat",
|
||||
"keywords": ["ekoizpena", "lotea", "fabrikazioa"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,18 +69,8 @@ export const useTenantInitializer = () => {
|
||||
const virtualTenantId = localStorage.getItem('virtual_tenant_id');
|
||||
const storedTier = localStorage.getItem('subscription_tier');
|
||||
|
||||
console.log('🔍 [TenantInitializer] Demo mode detected:', {
|
||||
isDemoMode,
|
||||
demoSessionId,
|
||||
virtualTenantId,
|
||||
demoAccountType,
|
||||
storedTier,
|
||||
currentTenant: currentTenant?.id
|
||||
});
|
||||
|
||||
// Guard: If no virtual_tenant_id is available, skip tenant setup
|
||||
if (!virtualTenantId) {
|
||||
console.warn('⚠️ [TenantInitializer] No virtual_tenant_id found in localStorage');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,8 +80,6 @@ export const useTenantInitializer = () => {
|
||||
currentTenant.id === virtualTenantId;
|
||||
|
||||
if (!isValidDemoTenant) {
|
||||
console.log('🔧 [TenantInitializer] Setting up demo tenant...');
|
||||
|
||||
// Determine the appropriate subscription tier based on stored value or account type
|
||||
const subscriptionTier = storedTier as SubscriptionTier || getDemoTierForAccountType(demoAccountType);
|
||||
|
||||
@@ -111,50 +99,40 @@ export const useTenantInitializer = () => {
|
||||
postal_code: '28001',
|
||||
phone: null,
|
||||
is_active: true,
|
||||
subscription_plan: subscriptionTier, // New field name
|
||||
subscription_tier: subscriptionTier, // Deprecated but kept for backward compatibility
|
||||
subscription_plan: subscriptionTier,
|
||||
subscription_tier: subscriptionTier,
|
||||
ml_model_trained: false,
|
||||
last_training_date: null,
|
||||
owner_id: 'demo-user',
|
||||
created_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
console.log(`✅ [TenantInitializer] Setting up tenant with tier: ${subscriptionTier}`);
|
||||
|
||||
// Set the demo tenant as current
|
||||
setCurrentTenant(mockTenant);
|
||||
|
||||
// **CRITICAL: Also set tenant ID in API client**
|
||||
// This ensures API requests include the tenant ID header
|
||||
// Set tenant ID in API client
|
||||
import('../api/client').then(({ apiClient }) => {
|
||||
apiClient.setTenantId(virtualTenantId);
|
||||
console.log('✅ [TenantInitializer] Set API client tenant ID:', virtualTenantId);
|
||||
});
|
||||
|
||||
// For enterprise demos, load child tenants immediately (session is already ready when we navigate here)
|
||||
// For enterprise demos, load child tenants immediately
|
||||
if (demoAccountType === 'enterprise') {
|
||||
console.log('🔄 [TenantInitializer] Loading available tenants for enterprise demo...');
|
||||
const mockUserId = 'demo-user';
|
||||
|
||||
import('../api/services/tenant').then(({ TenantService }) => {
|
||||
const tenantService = new TenantService();
|
||||
tenantService.getUserTenants(mockUserId)
|
||||
.then(tenants => {
|
||||
console.log('📋 [TenantInitializer] Loaded available tenants:', tenants.length);
|
||||
if (tenants.length === 0) {
|
||||
console.warn('⚠️ [TenantInitializer] No child tenants found yet - they may still be cloning');
|
||||
}
|
||||
// Update the tenant store with available tenants
|
||||
import('../stores/tenant.store').then(({ useTenantStore }) => {
|
||||
useTenantStore.getState().setAvailableTenants(tenants);
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ [TenantInitializer] Failed to load available tenants:', error);
|
||||
.catch(() => {
|
||||
// Silently handle error
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [isDemoMode, demoSessionId, demoAccountType, currentTenant, setCurrentTenant]);
|
||||
};;
|
||||
};
|
||||
Reference in New Issue
Block a user