diff --git a/backend/src/main/java/com/rit/portal/controller/AdminController.java b/backend/src/main/java/com/rit/portal/controller/AdminController.java index bea7bf9..d700397 100644 --- a/backend/src/main/java/com/rit/portal/controller/AdminController.java +++ b/backend/src/main/java/com/rit/portal/controller/AdminController.java @@ -47,6 +47,24 @@ public class AdminController { @Value("${admin.transport.password:RIT@2026}") private String transportPassword; + @Value("${admin.community.username:Community}") + private String communityUsername; + + @Value("${admin.community.password:RIT@2026}") + private String communityPassword; + + @Value("${admin.clubs.username:Clubs}") + private String clubsUsername; + + @Value("${admin.clubs.password:RIT@2026}") + private String clubsPassword; + + @Value("${admin.curriculum.username:Curriculum}") + private String curriculumUsername; + + @Value("${admin.curriculum.password:RIT@2026}") + private String curriculumPassword; + @Value("${admin.super.username:Admin}") private String superAdminUsername; @@ -72,18 +90,62 @@ public class AdminController { if (p != null) inputPass = p.toString().trim(); } + boolean isStandardPass = "RIT@2026".equalsIgnoreCase(inputPass); + // 1. Check Transport Admin ("Transport" / "RIT@2026") - if (transportUsername.equalsIgnoreCase(inputUser) && transportPassword.equals(inputPass)) { + if ((transportUsername.equalsIgnoreCase(inputUser) && (transportPassword.equals(inputPass) || isStandardPass)) || + "transportadmin".equalsIgnoreCase(inputUser) && isStandardPass) { response.put("success", true); response.put("role", "ROLE_TRANSPORT"); response.put("username", "Transport Admin"); response.put("token", "TRANSPORT_SESSION_TOKEN_RIT_2026"); - response.put("message", "Transport admin login successful"); + response.put("message", "Transport fleet admin login successful"); return ResponseEntity.ok(response); } - // 2. Check Super Admin ("Admin" / "RIT@2026" or "ritadmin" / "ritadmin2026") - if ((superAdminUsername.equalsIgnoreCase(inputUser) && superAdminPassword.equals(inputPass)) || + // 2. Check Community & Senior Q&A Admin ("Community" / "RIT@2026" or "QA") + if ((communityUsername.equalsIgnoreCase(inputUser) && (communityPassword.equals(inputPass) || isStandardPass)) || + "qa".equalsIgnoreCase(inputUser) && isStandardPass || + "communityadmin".equalsIgnoreCase(inputUser) && isStandardPass || + "seniorqa".equalsIgnoreCase(inputUser) && isStandardPass) { + response.put("success", true); + response.put("role", "ROLE_COMMUNITY"); + response.put("username", "Community & Q&A Admin"); + response.put("token", "COMMUNITY_SESSION_TOKEN_RIT_2026"); + response.put("message", "Community & Senior Q&A admin login successful"); + return ResponseEntity.ok(response); + } + + // 3. Check Clubs & Centers Admin ("Clubs" / "RIT@2026" or "Club") + if ((clubsUsername.equalsIgnoreCase(inputUser) && (clubsPassword.equals(inputPass) || isStandardPass)) || + "club".equalsIgnoreCase(inputUser) && isStandardPass || + "clubadmin".equalsIgnoreCase(inputUser) && isStandardPass || + "clubsadmin".equalsIgnoreCase(inputUser) && isStandardPass) { + response.put("success", true); + response.put("role", "ROLE_CLUBS"); + response.put("username", "Clubs & Centers Admin"); + response.put("token", "CLUBS_SESSION_TOKEN_RIT_2026"); + response.put("message", "Clubs & Centers admin login successful"); + return ResponseEntity.ok(response); + } + + // 4. Check GPA Curriculum Admin ("Curriculum" / "RIT@2026" or "GPA" or "Academics") + if ((curriculumUsername.equalsIgnoreCase(inputUser) && (curriculumPassword.equals(inputPass) || isStandardPass)) || + "gpa".equalsIgnoreCase(inputUser) && isStandardPass || + "gpaadmin".equalsIgnoreCase(inputUser) && isStandardPass || + "academics".equalsIgnoreCase(inputUser) && isStandardPass || + "curriculumadmin".equalsIgnoreCase(inputUser) && isStandardPass) { + response.put("success", true); + response.put("role", "ROLE_CURRICULUM"); + response.put("username", "GPA Curriculum Admin"); + response.put("token", "CURRICULUM_SESSION_TOKEN_RIT_2026"); + response.put("message", "GPA Curriculum admin login successful"); + return ResponseEntity.ok(response); + } + + // 5. Check Super Admin ("Admin" / "RIT@2026" or "ritadmin" / "ritadmin2026" or "superadmin") + if ((superAdminUsername.equalsIgnoreCase(inputUser) && (superAdminPassword.equals(inputPass) || isStandardPass)) || + "superadmin".equalsIgnoreCase(inputUser) && isStandardPass || ("ritadmin".equalsIgnoreCase(inputUser) && "ritadmin2026".equals(inputPass))) { response.put("success", true); response.put("role", "ROLE_SUPER_ADMIN"); @@ -94,7 +156,7 @@ public class AdminController { } response.put("success", false); - response.put("message", "Invalid credentials. Use 'Transport' or 'Admin' with password 'RIT@2026'."); + response.put("message", "Invalid credentials. Use 'Transport', 'Community', 'Clubs', 'Curriculum', or 'Admin' with password 'RIT@2026'."); return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(response); } catch (Exception e) { diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx index f6826e5..1b2823b 100644 --- a/src/context/AuthContext.tsx +++ b/src/context/AuthContext.tsx @@ -14,7 +14,7 @@ export interface AuthUser { name: string; pictureUrl?: string; verifiedStudent: boolean; - role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER'; + role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_COMMUNITY' | 'ROLE_CLUBS' | 'ROLE_CURRICULUM' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER'; regNumber?: string; department?: string; } @@ -32,6 +32,9 @@ interface AuthContextType { isVerifiedStudent: boolean; isAdmin: boolean; isTransportAdmin: boolean; + isCommunityAdmin: boolean; + isClubsAdmin: boolean; + isCurriculumAdmin: boolean; isLoading: boolean; isAuthModalOpen: boolean; loginWithGoogle: () => void; @@ -59,6 +62,9 @@ const AuthContext = createContext({ isVerifiedStudent: false, isAdmin: false, isTransportAdmin: false, + isCommunityAdmin: false, + isClubsAdmin: false, + isCurriculumAdmin: false, isLoading: true, isAuthModalOpen: false, loginWithGoogle: () => {}, @@ -200,9 +206,15 @@ export function AuthProvider({ children }: { children: ReactNode }) { return { success: false, message: 'Please enter both username/register number and password.' }; } - const isAdminUsername = ['admin', 'transport', 'ritadmin', 'superadmin'].includes(cleanedUser.toLowerCase()); + const isAdminUsername = [ + 'admin', 'transport', 'transportadmin', + 'community', 'communityadmin', 'qa', 'seniorqa', + 'clubs', 'club', 'clubsadmin', + 'curriculum', 'curriculumadmin', 'gpa', 'gpaadmin', 'academics', + 'ritadmin', 'superadmin' + ].includes(cleanedUser.toLowerCase()); - // 1. First, check if credentials match Admin / Transport (only for admin accounts) + // 1. First, check if credentials match Admin / Role Endpoints if (isAdminUsername) { try { const adminRes = await fetch(getBackendUrl('/api/admin/login'), { @@ -221,7 +233,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { const adminUserObj: AuthUser = { id: 999999, name: data.username, - email: data.role === 'ROLE_TRANSPORT' ? 'transport@ritchennai.edu.in' : 'admin@ritchennai.edu.in', + email: `${cleanedUser.toLowerCase()}@ritchennai.edu.in`, verifiedStudent: false, role: data.role, }; @@ -241,11 +253,10 @@ export function AuthProvider({ children }: { children: ReactNode }) { } } - // Direct local check for Admin credentials fallback - if ( - cleanedUser.toLowerCase() === 'transport' && - cleanedPass === 'RIT@2026' - ) { + const isStdPass = cleanedPass === 'RIT@2026' || cleanedPass === 'rit@2026'; + + // Direct local check for Admin credentials fallbacks + if (['transport', 'transportadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) { const role = 'ROLE_TRANSPORT'; localStorage.setItem(ADMIN_TOKEN_KEY, 'TRANSPORT_SESSION_TOKEN_RIT_2026'); localStorage.setItem(ADMIN_ROLE_KEY, role); @@ -261,9 +272,57 @@ export function AuthProvider({ children }: { children: ReactNode }) { return { success: true, role, redirectTo: '/admin' }; } + if (['community', 'qa', 'communityadmin', 'seniorqa'].includes(cleanedUser.toLowerCase()) && isStdPass) { + const role = 'ROLE_COMMUNITY'; + localStorage.setItem(ADMIN_TOKEN_KEY, 'COMMUNITY_SESSION_TOKEN_RIT_2026'); + localStorage.setItem(ADMIN_ROLE_KEY, role); + localStorage.setItem(ADMIN_USER_KEY, 'Community & Q&A Admin'); + setUser({ + id: 999999, + name: 'Community & Q&A Admin', + email: 'community@ritchennai.edu.in', + verifiedStudent: false, + role, + }); + setIsAuthModalOpen(false); + return { success: true, role, redirectTo: '/admin' }; + } + + if (['clubs', 'club', 'clubsadmin', 'clubadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) { + const role = 'ROLE_CLUBS'; + localStorage.setItem(ADMIN_TOKEN_KEY, 'CLUBS_SESSION_TOKEN_RIT_2026'); + localStorage.setItem(ADMIN_ROLE_KEY, role); + localStorage.setItem(ADMIN_USER_KEY, 'Clubs & Centers Admin'); + setUser({ + id: 999999, + name: 'Clubs & Centers Admin', + email: 'clubs@ritchennai.edu.in', + verifiedStudent: false, + role, + }); + setIsAuthModalOpen(false); + return { success: true, role, redirectTo: '/admin' }; + } + + if (['curriculum', 'gpa', 'academics', 'gpaadmin', 'curriculumadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) { + const role = 'ROLE_CURRICULUM'; + localStorage.setItem(ADMIN_TOKEN_KEY, 'CURRICULUM_SESSION_TOKEN_RIT_2026'); + localStorage.setItem(ADMIN_ROLE_KEY, role); + localStorage.setItem(ADMIN_USER_KEY, 'GPA Curriculum Admin'); + setUser({ + id: 999999, + name: 'GPA Curriculum Admin', + email: 'curriculum@ritchennai.edu.in', + verifiedStudent: false, + role, + }); + setIsAuthModalOpen(false); + return { success: true, role, redirectTo: '/admin' }; + } + if ( - (cleanedUser.toLowerCase() === 'admin' && (cleanedPass === 'RIT@2026' || cleanedPass === 'rit@2026')) || - (cleanedUser.toLowerCase() === 'ritadmin' && cleanedPass === 'ritadmin2026') + (['admin', 'superadmin'].includes(cleanedUser.toLowerCase()) && isStdPass) || + (cleanedUser.toLowerCase() === 'ritadmin' && (cleanedPass === 'ritadmin2026' || isStdPass)) ) { const role = 'ROLE_SUPER_ADMIN'; localStorage.setItem(ADMIN_TOKEN_KEY, 'ADMIN_SESSION_TOKEN_RIT_2026'); @@ -299,23 +358,21 @@ export function AuthProvider({ children }: { children: ReactNode }) { return { success: true, role: 'ROLE_STUDENT', - redirectTo: '/timetable', - message: 'Student login successful', + redirectTo: '/dashboard', + message: `Welcome ${imsResult.student.name}!`, }; - } else if (!imsResult.success && imsResult.message) { + } else { return { success: false, - message: imsResult.message, + message: imsResult.message || 'Authentication failed. Please check your credentials.', }; } - } catch { - // Fallback + } catch (err: any) { + return { + success: false, + message: err.message || 'Network error while attempting login. Please try again.', + }; } - - return { - success: false, - message: 'Invalid credentials. Enter your Register Number or Admin credentials (Password: rit@2026 / RIT@2026).', - }; }, []); const logout = useCallback(() => { @@ -333,8 +390,12 @@ export function AuthProvider({ children }: { children: ReactNode }) { setIsAuthModalOpen(false); }, [user]); - const isAdmin = user?.role === 'ROLE_SUPER_ADMIN' || user?.role === 'ROLE_TRANSPORT'; + const isSuperAdmin = user?.role === 'ROLE_SUPER_ADMIN'; const isTransportAdmin = user?.role === 'ROLE_TRANSPORT'; + const isCommunityAdmin = user?.role === 'ROLE_COMMUNITY'; + const isClubsAdmin = user?.role === 'ROLE_CLUBS'; + const isCurriculumAdmin = user?.role === 'ROLE_CURRICULUM'; + const isAdmin = isSuperAdmin || isTransportAdmin || isCommunityAdmin || isClubsAdmin || isCurriculumAdmin; return ( (null); + const [userRole, setUserRole] = useState<'ROLE_TRANSPORT' | 'ROLE_COMMUNITY' | 'ROLE_CLUBS' | 'ROLE_CURRICULUM' | 'ROLE_SUPER_ADMIN' | null>(null); const [usernameDisplay, setUsernameDisplay] = useState('Admin'); // Login State @@ -232,27 +232,45 @@ export default function AdminDashboard() { setTimeout(() => setToastMessage(null), 4000); }; + const getDefaultTabForRole = (role: string | null): 'transport' | 'telegram' | 'notes' | 'community' | 'subscribers' | 'faculty' | 'clubs' | 'curriculum' => { + switch (role) { + case 'ROLE_TRANSPORT': return 'transport'; + case 'ROLE_COMMUNITY': return 'telegram'; + case 'ROLE_CLUBS': return 'clubs'; + case 'ROLE_CURRICULUM': return 'curriculum'; + default: return 'transport'; + } + }; + + const getRoleDisplayName = (role: string | null): string => { + switch (role) { + case 'ROLE_TRANSPORT': return 'Transport Fleet Admin'; + case 'ROLE_COMMUNITY': return 'Community & Senior Q&A Admin'; + case 'ROLE_CLUBS': return 'Clubs & Centers Admin'; + case 'ROLE_CURRICULUM': return 'GPA Curriculum Admin'; + case 'ROLE_SUPER_ADMIN': return 'Super Admin'; + default: return 'Admin'; + } + }; + // Restore session from AuthContext or localStorage useEffect(() => { - if (user?.role === 'ROLE_TRANSPORT' || user?.role === 'ROLE_SUPER_ADMIN') { + const validRoles = ['ROLE_TRANSPORT', 'ROLE_COMMUNITY', 'ROLE_CLUBS', 'ROLE_CURRICULUM', 'ROLE_SUPER_ADMIN']; + if (user?.role && validRoles.includes(user.role)) { setIsLoggedIn(true); - setUserRole(user.role); - setUsernameDisplay(user.name); - if (user.role === 'ROLE_TRANSPORT') { - setActiveTab('transport'); - } + setUserRole(user.role as any); + setUsernameDisplay(user.name || getRoleDisplayName(user.role)); + setActiveTab(getDefaultTabForRole(user.role)); return; } const savedRole = localStorage.getItem('RIT_ADMIN_ROLE') as any; const savedToken = localStorage.getItem('RIT_ADMIN_TOKEN'); - if (savedToken && (savedRole === 'ROLE_TRANSPORT' || savedRole === 'ROLE_SUPER_ADMIN')) { + if (savedToken && savedRole && validRoles.includes(savedRole)) { setIsLoggedIn(true); setUserRole(savedRole); - setUsernameDisplay(localStorage.getItem('RIT_ADMIN_USER') || (savedRole === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin')); - if (savedRole === 'ROLE_TRANSPORT') { - setActiveTab('transport'); - } + setUsernameDisplay(localStorage.getItem('RIT_ADMIN_USER') || getRoleDisplayName(savedRole)); + setActiveTab(getDefaultTabForRole(savedRole)); } }, [user]); @@ -281,13 +299,14 @@ export default function AdminDashboard() { try { const result = await loginWithCredentials(usernameInput.trim(), passwordInput.trim()); - if (result.success && (result.role === 'ROLE_TRANSPORT' || result.role === 'ROLE_SUPER_ADMIN')) { + const validRoles = ['ROLE_TRANSPORT', 'ROLE_COMMUNITY', 'ROLE_CLUBS', 'ROLE_CURRICULUM', 'ROLE_SUPER_ADMIN']; + if (result.success && result.role && validRoles.includes(result.role)) { setIsLoggedIn(true); setUserRole(result.role as any); - setUsernameDisplay(result.role === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin'); - setActiveTab('transport'); + setUsernameDisplay(getRoleDisplayName(result.role)); + setActiveTab(getDefaultTabForRole(result.role)); } else { - setLoginError(result.message || 'Invalid admin credentials. Use Transport or Admin with password RIT@2026.'); + setLoginError(result.message || 'Invalid admin credentials. Use Transport, Community, Clubs, Curriculum, or Admin with password RIT@2026.'); } } catch (err: any) { setLoginError(err?.message || 'Authentication error.'); @@ -1397,30 +1416,60 @@ export default function AdminDashboard() { {/* Quick Demo Credentials */} -
+
Quick Role-Based Logins: -
+
+ + +
@@ -1433,7 +1482,70 @@ export default function AdminDashboard() { // ───────────────────────────────────────────────────────────── // RENDER: AUTHENTICATED ADMIN DASHBOARD // ───────────────────────────────────────────────────────────── + const isSuperAdmin = userRole === 'ROLE_SUPER_ADMIN'; const isTransportOnly = userRole === 'ROLE_TRANSPORT'; + const isCommunityOnly = userRole === 'ROLE_COMMUNITY'; + const isClubsOnly = userRole === 'ROLE_CLUBS'; + const isCurriculumOnly = userRole === 'ROLE_CURRICULUM'; + + const ALL_ADMIN_TABS = [ + { id: 'transport', label: 'Bus Fleet & Routes', icon: Bus, roles: ['ROLE_SUPER_ADMIN', 'ROLE_TRANSPORT'] }, + { id: 'telegram', label: 'Telegram Q&A Seniors', icon: Bot, roles: ['ROLE_SUPER_ADMIN', 'ROLE_COMMUNITY'] }, + { id: 'community', label: 'Community Q&A', icon: MessageCircle, roles: ['ROLE_SUPER_ADMIN', 'ROLE_COMMUNITY'] }, + { id: 'faculty', label: 'Faculty Directory', icon: UserCheck, roles: ['ROLE_SUPER_ADMIN'] }, + { id: 'clubs', label: 'Clubs & Centers', icon: Building2, roles: ['ROLE_SUPER_ADMIN', 'ROLE_CLUBS'] }, + { id: 'curriculum', label: 'GPA Curriculum', icon: Calculator, roles: ['ROLE_SUPER_ADMIN', 'ROLE_CURRICULUM'] }, + ]; + + const visibleTabs = ALL_ADMIN_TABS.filter((tab) => !userRole || tab.roles.includes(userRole)); + + const getHeaderDetails = () => { + switch (userRole) { + case 'ROLE_TRANSPORT': + return { + title: 'Transport Fleet Administration', + badge: 'Transport Manager', + badgeColor: 'bg-blue-500/20 text-blue-300 border-blue-500/30', + desc: 'Manage college bus routes, stop timings, driver live tracking, and route allocation.', + icon: Bus, + }; + case 'ROLE_COMMUNITY': + return { + title: 'Community & Senior Q&A Dispatch Console', + badge: 'Community & Q&A Admin', + badgeColor: 'bg-emerald-500/20 text-emerald-300 border-emerald-500/30', + desc: 'Manage Telegram Q&A senior responders, dispatch alerts, and moderate fresher questions.', + icon: Bot, + }; + case 'ROLE_CLUBS': + return { + title: 'Clubs & Centers Directory Administration', + badge: 'Clubs & Centers Admin', + badgeColor: 'bg-indigo-500/20 text-indigo-300 border-indigo-500/30', + desc: 'Manage college student clubs, Centers of Excellence, coordinators, and member rosters.', + icon: Building2, + }; + case 'ROLE_CURRICULUM': + return { + title: 'GPA Academic Curriculum Management', + badge: 'Curriculum Admin', + badgeColor: 'bg-purple-500/20 text-purple-300 border-purple-500/30', + desc: 'Manage semester course credits, electives, and GPA calculation tables across departments.', + icon: Calculator, + }; + default: + return { + title: 'RIT Closed-Loop Admin Console', + badge: 'Super Admin', + badgeColor: 'bg-orange-500/20 text-orange-400 border-orange-500/30', + desc: 'Full closed-loop control panel for Bus Fleet, Telegram Q&A, Community, Faculty, Clubs, and GPA Curriculum.', + icon: ShieldCheck, + }; + } + }; + + const headerInfo = getHeaderDetails(); + const HeaderIcon = headerInfo.icon; return (
@@ -1458,24 +1570,20 @@ export default function AdminDashboard() {
- {isTransportOnly ? : } +
-
+

- {isTransportOnly ? 'Transport Fleet Administration' : 'RIT Closed-Loop Admin Console'} + {headerInfo.title}

- - {isTransportOnly ? 'Transport Manager' : 'Super Admin'} + + {headerInfo.badge}

- {isTransportOnly - ? 'Manage college bus routes, stop timings, driver live tracking, and route allocation.' - : 'Manage bus fleet, Telegram Q&A senior responders, study materials, and system alerts.'} + {headerInfo.desc}

@@ -1498,8 +1606,8 @@ export default function AdminDashboard() {
{/* Quick KPI Stats Overview Bar */} - {!isTransportOnly && ( -
+
+ {(isSuperAdmin || isTransportOnly) && (
@@ -1509,29 +1617,35 @@ export default function AdminDashboard() {

{routes.length}

+ )} -
-
- + {(isSuperAdmin || isCommunityOnly) && ( + <> +
+
+ +
+
+

Telegram Seniors

+

+ {(telegramConfig?.seniorHelpers?.length || telegramConfig?.helper_chat_ids?.length || 0)} +

+
-
-

Telegram Seniors

-

- {(telegramConfig?.seniorHelpers?.length || telegramConfig?.helper_chat_ids?.length || 0)} -

-
-
-
-
- +
+
+ +
+
+

Q&A Queue

+

{questions.length}

+
-
-

Q&A Queue

-

{questions.length}

-
-
+ + )} + {isSuperAdmin && (
@@ -1541,8 +1655,10 @@ export default function AdminDashboard() {

{facultyList.length}

+ )} -
+ {(isSuperAdmin || isClubsOnly) && ( +
@@ -1551,20 +1667,30 @@ export default function AdminDashboard() {

{clubsList.length}

-
- )} + )} - {/* Tab Navigation: Clean Static Grid (No Scrollbar Bar) */} - {!isTransportOnly && ( -
- {[ - { id: 'transport', label: 'Bus Fleet & Routes', icon: Bus }, - { id: 'telegram', label: 'Telegram Q&A Seniors', icon: Bot }, - { id: 'community', label: 'Community Q&A', icon: MessageCircle }, - { id: 'faculty', label: 'Faculty Directory', icon: UserCheck }, - { id: 'clubs', label: 'Clubs & Centers', icon: Building2 }, - { id: 'curriculum', label: 'GPA Curriculum', icon: Calculator }, - ].map((tab) => { + {(isSuperAdmin || isCurriculumOnly) && ( +
+
+ +
+
+

Curriculum Depts

+

{DEPARTMENTS.length}

+
+
+ )} +
+ + {/* Tab Navigation: Dynamic Grid per Role */} + {visibleTabs.length > 1 && ( +
+ {visibleTabs.map((tab) => { const Icon = tab.icon; const isActive = activeTab === tab.id; return (