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) => (
+
+
+
+
{title}
+
+ {description}
+
+
+
+
+
+);
+
+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 */}
+
+
+
+
+
{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 */}
+
+
+ );
+};
+
+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: ,