diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 90619ec..713876a 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -62,8 +62,8 @@ const Login = (): ReactElement => { if (rolesArray.includes('super_admin')) { navigate('/dashboard'); } else { - // Tenant admin - redirect to tenant dashboard - navigate('/tenant'); + // Tenant admin - redirect to tenant landing page (workspace selector) + navigate('/tenant/landing'); } } }, [isAuthenticated, roles, navigate]); @@ -95,7 +95,7 @@ const Login = (): ReactElement => { if (userRoles.includes('super_admin')) { navigate('/dashboard'); } else { - navigate('/tenant'); + navigate('/tenant/landing'); } } } catch (error: any) { diff --git a/src/pages/tenant/LandingPage.tsx b/src/pages/tenant/LandingPage.tsx new file mode 100644 index 0000000..006b7bf --- /dev/null +++ b/src/pages/tenant/LandingPage.tsx @@ -0,0 +1,252 @@ +import { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import type { ReactElement } from 'react'; +import { + ArrowRight, + Layout as LayoutIcon, + Shield, + FileText, + LayoutGrid, + Database, + ClipboardCheck, + Package, + ArrowUpRight +} from 'lucide-react'; +import { useAppSelector } from '@/hooks/redux-hooks'; +import { moduleService } from '@/services/module-service'; +import type { MyModule } from '@/types/module'; +import { AuthenticatedImage } from '@/components/shared'; + +interface WorkspaceCardProps { + title: string; + description: string; + icon: React.ReactNode; + iconBg: string; + iconColor: string; + onClick: () => void; + isExternal?: boolean; +} + +const WorkspaceCard = ({ + title, + description, + icon, + iconBg, + iconColor, + onClick, + isExternal = false +}: WorkspaceCardProps) => ( +
+
+
+
{icon}
+
+

{title}

+

+ {description} +

+
+ +
+ Open Workspace +
+ {isExternal ? : } +
+
+
+); + +const LandingPage = (): ReactElement => { + const navigate = useNavigate(); + const { user, roles, tenantId } = useAppSelector((state) => state.auth); + const { logoUrl } = useAppSelector((state) => state.theme); + const [modules, setModules] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchModules = async () => { + try { + const response = await moduleService.getMyModules(); + if (response.success && response.data) { + setModules(response.data); + } + } catch (error) { + console.error('Failed to fetch modules:', error); + } finally { + setIsLoading(false); + } + }; + fetchModules(); + }, []); + + const handleLaunchModule = async (moduleId: string) => { + try { + let rolesArray: string[] = []; + if (Array.isArray(roles)) { + rolesArray = roles; + } else if (typeof roles === 'string') { + try { rolesArray = JSON.parse(roles); } catch { rolesArray = []; } + } + + const isSuperAdmin = rolesArray.includes('super_admin'); + const launchTenantId = isSuperAdmin ? tenantId : null; + + const response = await moduleService.launch(moduleId, launchTenantId); + if (response.success && response.data.launch_url) { + window.open(response.data.launch_url, '_blank', 'noopener,noreferrer'); + } + } catch (error) { + console.error('Failed to launch module:', error); + } + }; + + const getUserName = () => { + if (user?.first_name) return user.first_name; + return user?.email?.split('@')[0] || 'User'; + }; + + const getRoleDisplayName = () => { + let rolesArray: string[] = []; + if (Array.isArray(roles)) { + rolesArray = roles; + } else if (typeof roles === 'string') { + try { rolesArray = JSON.parse(roles); } catch { rolesArray = []; } + } + const role = rolesArray[0] || 'User'; + return role.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' '); + }; + + // Icon mapping for modules based on code or name + const getModuleIcon = (module: MyModule) => { + const code = module.name.toUpperCase(); + if (code.includes('CER') || code.includes('ENTITY')) return ; + if (code.includes('COC') || code.includes('CHAIN')) return ; + if (code.includes('LSR') || code.includes('SERVICE')) return ; + if (code.includes('DCT') || code.includes('DATA')) return ; + if (code.includes('TMT') || code.includes('TASK')) return ; + return ; + }; + + const getModuleColor = (index: number) => { + const colors = [ + { bg: '#ECFDF5', text: '#059669' }, // Green + { bg: '#FEF2F2', text: '#DC2626' }, // Red + { bg: '#F5F3FF', text: '#7C3AED' }, // Purple + { bg: '#F0FDFA', text: '#0D9488' }, // Teal + { bg: '#FEFCE8', text: '#CA8A04' }, // Yellow + ]; + return colors[index % colors.length]; + }; + + return ( +
+ {/* Landing Header */} +
+
+ {logoUrl ? ( + + +
+ } + /> + ) : ( +
+ +
+ )} +
+ QAssure + + {getRoleDisplayName()} + +
+
+ +
+
+

{user?.first_name ? `${user.first_name} ${user.last_name || ''}` : getUserName()}

+

{user?.email}

+
+
+ + {user?.first_name ? user.first_name[0].toUpperCase() : 'U'} + +
+
+ + +
+ {/* Welcome Section */} +
+

+ Welcome back, {getUserName()} +

+

+ Select a module below to access your workspace. You can switch between modules anytime from the main navigation. +

+
+ + {/* Workspace Grid */} +
+ {/* Platform Administration Card */} + } + iconBg="#EFF6FF" + iconColor="#2563EB" + onClick={() => navigate('/tenant')} + /> + + {/* Dynamic Module Cards */} + {!isLoading && modules.map((module, index) => { + const color = getModuleColor(index); + return ( + handleLaunchModule(module.id)} + /> + ); + })} + + {/* Loading States */} + {isLoading && [1, 2].map((i) => ( +
+
+
+
+
+
+ ))} +
+
+ + {/* Footer Decoration */} +
+
+ + QAssure Secure Workspace +
+
+ + ); +}; + +export default LandingPage; diff --git a/src/pages/tenant/TenantLogin.tsx b/src/pages/tenant/TenantLogin.tsx index 3d00777..160cee6 100644 --- a/src/pages/tenant/TenantLogin.tsx +++ b/src/pages/tenant/TenantLogin.tsx @@ -68,8 +68,8 @@ const TenantLogin = (): ReactElement => { if (rolesArray.includes('super_admin')) { navigate('/dashboard'); } else { - // Tenant admin - redirect to tenant dashboard - navigate('/tenant'); + // Tenant admin - redirect to tenant landing page (workspace selector) + navigate('/tenant/landing'); } } }, [isAuthenticated, roles, navigate]); @@ -109,7 +109,7 @@ const TenantLogin = (): ReactElement => { if (rolesArray.includes('super_admin')) { navigate('/dashboard'); } else { - navigate('/tenant'); + navigate('/tenant/landing'); } } } catch (error: any) { diff --git a/src/routes/tenant-admin-routes.tsx b/src/routes/tenant-admin-routes.tsx index 3f0c4de..6542fbb 100644 --- a/src/routes/tenant-admin-routes.tsx +++ b/src/routes/tenant-admin-routes.tsx @@ -8,6 +8,7 @@ const Settings = lazy(() => import("@/pages/tenant/Settings")); const Users = lazy(() => import("@/pages/tenant/Users")); const AuditLogs = lazy(() => import("@/pages/tenant/AuditLogs")); const Modules = lazy(() => import("@/pages/tenant/Modules")); +const LandingPage = lazy(() => import("@/pages/tenant/LandingPage")); const Departments = lazy(() => import("@/pages/tenant/Departments")); const Designations = lazy(() => import("@/pages/tenant/Designations")); const WorkflowDefination = lazy( @@ -51,6 +52,10 @@ export interface RouteConfig { // Tenant Admin routes (requires authentication but NOT super_admin role) export const tenantAdminRoutes: RouteConfig[] = [ + { + path: "/tenant/landing", + element: , + }, { path: "/tenant", element: ,