Save local admin console and unified auth changes before pulling remote UI updates

This commit is contained in:
Shanmuga Krishnan S M
2026-08-19 08:34:31 +05:30
parent 1eebd10f3a
commit bba33ef05b
6 changed files with 857 additions and 423 deletions

View File

@@ -2,6 +2,7 @@ import { createContext, useContext, useState, useEffect, useCallback } from 'rea
import type { ReactNode } from 'react';
import { getBackendUrl } from '@/lib/utils';
import GoogleAuthModal from '@/components/Auth/GoogleAuthModal';
import { getStoredImsSession, clearImsSession, saveImsSession, loginWithIms, type StudentInfo } from '@/services/imsService';
// ──────────────────────────────────────────────────
// Types
@@ -13,15 +14,28 @@ export interface AuthUser {
name: string;
pictureUrl?: string;
verifiedStudent: boolean;
role?: 'ROLE_STUDENT' | 'ROLE_TRANSPORT' | 'ROLE_SUPER_ADMIN' | 'ROLE_USER';
regNumber?: string;
department?: string;
}
export interface LoginCredentialsResult {
success: boolean;
message?: string;
redirectTo?: string;
role?: string;
}
interface AuthContextType {
user: AuthUser | null;
isAuthenticated: boolean;
isVerifiedStudent: boolean;
isAdmin: boolean;
isTransportAdmin: boolean;
isLoading: boolean;
isAuthModalOpen: boolean;
loginWithGoogle: () => void;
loginWithCredentials: (username: string, password: string) => Promise<LoginCredentialsResult>;
openAuthModal: () => void;
closeAuthModal: () => void;
logout: () => void;
@@ -43,9 +57,12 @@ const AuthContext = createContext<AuthContextType>({
user: null,
isAuthenticated: false,
isVerifiedStudent: false,
isAdmin: false,
isTransportAdmin: false,
isLoading: true,
isAuthModalOpen: false,
loginWithGoogle: () => {},
loginWithCredentials: async () => ({ success: false }),
openAuthModal: () => {},
closeAuthModal: () => {},
logout: () => {},
@@ -54,29 +71,13 @@ const AuthContext = createContext<AuthContextType>({
export const useAuth = () => useContext(AuthContext);
// ──────────────────────────────────────────────────
// Google GSI global type
// Storage Keys
// ──────────────────────────────────────────────────
declare global {
interface Window {
google?: {
accounts: {
id: {
initialize: (config: Record<string, unknown>) => void;
prompt: (callback?: (notification: { isNotDisplayed: () => boolean; isSkippedMoment: () => boolean }) => void) => void;
renderButton: (element: HTMLElement, config: Record<string, unknown>) => void;
revoke: (email: string, callback: () => void) => void;
};
};
};
}
}
// ──────────────────────────────────────────────────
// Provider
// ──────────────────────────────────────────────────
const STORAGE_KEY = 'rit_auth_user';
const GOOGLE_STORAGE_KEY = 'rit_auth_user';
const ADMIN_TOKEN_KEY = 'RIT_ADMIN_TOKEN';
const ADMIN_ROLE_KEY = 'RIT_ADMIN_ROLE';
const ADMIN_USER_KEY = 'RIT_ADMIN_USER';
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<AuthUser | null>(null);
@@ -86,7 +87,54 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const openAuthModal = useCallback(() => setIsAuthModalOpen(true), []);
const closeAuthModal = useCallback(() => setIsAuthModalOpen(false), []);
// Handle the credential response from Google GSI
// Restore any active session on app load
useEffect(() => {
// 1. Check Admin Session
const adminToken = localStorage.getItem(ADMIN_TOKEN_KEY);
const adminRole = localStorage.getItem(ADMIN_ROLE_KEY);
const adminUser = localStorage.getItem(ADMIN_USER_KEY);
if (adminToken && adminRole) {
setUser({
id: 999999,
name: adminUser || (adminRole === 'ROLE_TRANSPORT' ? 'Transport Admin' : 'Super Admin'),
email: adminRole === 'ROLE_TRANSPORT' ? 'transport@ritchennai.edu.in' : 'admin@ritchennai.edu.in',
verifiedStudent: false,
role: adminRole as any,
});
setIsLoading(false);
return;
}
// 2. Check IMS Student Session
const imsSession = getStoredImsSession();
if (imsSession?.student) {
setUser({
id: Number(imsSession.student.regNumber) || 1,
name: imsSession.student.name,
email: imsSession.student.email,
regNumber: imsSession.student.regNumber,
department: imsSession.student.department,
verifiedStudent: true,
role: 'ROLE_STUDENT',
});
setIsLoading(false);
return;
}
// 3. Check Google Session
const googleStored = localStorage.getItem(GOOGLE_STORAGE_KEY);
if (googleStored) {
try {
const parsed = JSON.parse(googleStored) as AuthUser;
setUser({ ...parsed, role: 'ROLE_USER' });
} catch {
localStorage.removeItem(GOOGLE_STORAGE_KEY);
}
}
setIsLoading(false);
}, []);
// Handle Google OAuth GSI
const handleCredentialResponse = useCallback(async (response: { credential: string }) => {
try {
const res = await fetch(getBackendUrl('/api/auth/google'), {
@@ -95,15 +143,13 @@ export function AuthProvider({ children }: { children: ReactNode }) {
body: JSON.stringify({ credential: response.credential }),
});
if (!res.ok) {
console.error('Auth failed:', res.status);
return;
if (res.ok) {
const userData: AuthUser = await res.json();
const userObj: AuthUser = { ...userData, role: 'ROLE_USER' };
setUser(userObj);
localStorage.setItem(GOOGLE_STORAGE_KEY, JSON.stringify(userObj));
setIsAuthModalOpen(false);
}
const userData: AuthUser = await res.json();
setUser(userData);
localStorage.setItem(STORAGE_KEY, JSON.stringify(userData));
setIsAuthModalOpen(false);
} catch (err) {
console.error('Google auth error:', err);
}
@@ -111,42 +157,8 @@ export function AuthProvider({ children }: { children: ReactNode }) {
// Initialize Google GSI
useEffect(() => {
// 1. Restore session from localStorage
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
try {
const parsed = JSON.parse(stored) as AuthUser;
setUser(parsed);
// Validate session is still valid against backend
fetch(getBackendUrl(`/api/auth/me?email=${encodeURIComponent(parsed.email)}`))
.then((res) => {
if (!res.ok) {
localStorage.removeItem(STORAGE_KEY);
setUser(null);
} else {
return res.json();
}
})
.then((freshData) => {
if (freshData) {
setUser(freshData);
localStorage.setItem(STORAGE_KEY, JSON.stringify(freshData));
}
})
.catch(() => {
// Backend offline, keep cached session
});
} catch {
localStorage.removeItem(STORAGE_KEY);
}
}
setIsLoading(false);
// 2. Initialize Google GSI when the script loads
const initGSI = () => {
if (!window.google || !GOOGLE_CLIENT_ID) return;
window.google.accounts.id.initialize({
client_id: GOOGLE_CLIENT_ID,
callback: handleCredentialResponse,
@@ -169,19 +181,134 @@ export function AuthProvider({ children }: { children: ReactNode }) {
}, [handleCredentialResponse]);
const loginWithGoogle = useCallback(() => {
// Open the clean Google Sign-In modal directly
setIsAuthModalOpen(true);
// Also trigger One Tap prompt as a secondary background hint if supported
if (window.google) {
try {
window.google.accounts.id.prompt();
} catch {
// Silently ignore if browser blocks One Tap
}
} catch {}
}
}, []);
// ─────────────────────────────────────────────────────────────
// UNIFIED CREDENTIALS SIGN-IN (Students & Admins)
// ─────────────────────────────────────────────────────────────
const loginWithCredentials = useCallback(async (username: string, password: string): Promise<LoginCredentialsResult> => {
const cleanedUser = username.trim();
const cleanedPass = password.trim();
if (!cleanedUser || !cleanedPass) {
return { success: false, message: 'Please enter both username/register number and password.' };
}
// 1. First, check if credentials match Admin / Transport
try {
const adminRes = await fetch(getBackendUrl('/api/admin/login'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: cleanedUser, password: cleanedPass }),
});
if (adminRes.ok) {
const data = await adminRes.json();
if (data.success && data.token && data.role) {
localStorage.setItem(ADMIN_TOKEN_KEY, data.token);
localStorage.setItem(ADMIN_ROLE_KEY, data.role);
localStorage.setItem(ADMIN_USER_KEY, data.username);
const adminUserObj: AuthUser = {
id: 999999,
name: data.username,
email: data.role === 'ROLE_TRANSPORT' ? 'transport@ritchennai.edu.in' : 'admin@ritchennai.edu.in',
verifiedStudent: false,
role: data.role,
};
setUser(adminUserObj);
setIsAuthModalOpen(false);
return {
success: true,
role: data.role,
redirectTo: '/admin',
message: `${data.username} logged in successfully`,
};
}
}
} catch {
// If admin endpoint fails or is offline, check fallback
}
// Direct local check for Admin credentials fallback
if (
(cleanedUser.equalsIgnoreCase?.('Transport') || cleanedUser.toLowerCase() === 'transport') &&
cleanedPass === 'RIT@2026'
) {
const role = 'ROLE_TRANSPORT';
localStorage.setItem(ADMIN_TOKEN_KEY, 'TRANSPORT_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'Transport Admin');
setUser({
id: 999999,
name: 'Transport Admin',
email: 'transport@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')
) {
const role = 'ROLE_SUPER_ADMIN';
localStorage.setItem(ADMIN_TOKEN_KEY, 'ADMIN_SESSION_TOKEN_RIT_2026');
localStorage.setItem(ADMIN_ROLE_KEY, role);
localStorage.setItem(ADMIN_USER_KEY, 'Super Admin');
setUser({
id: 999999,
name: 'Super Admin',
email: 'admin@ritchennai.edu.in',
verifiedStudent: false,
role,
});
setIsAuthModalOpen(false);
return { success: true, role, redirectTo: '/admin' };
}
// 2. Next, check IMS Student Login
try {
const imsResult = await loginWithIms(cleanedUser, cleanedPass);
if (imsResult.success && imsResult.student) {
const studentObj: AuthUser = {
id: Number(imsResult.student.regNumber) || 1,
name: imsResult.student.name,
email: imsResult.student.email,
regNumber: imsResult.student.regNumber,
department: imsResult.student.department,
verifiedStudent: true,
role: 'ROLE_STUDENT',
};
setUser(studentObj);
setIsAuthModalOpen(false);
return {
success: true,
role: 'ROLE_STUDENT',
redirectTo: '/dashboard',
message: 'Student login successful',
};
}
} catch {
// Fallback
}
return {
success: false,
message: 'Invalid credentials. Enter your Register Number or Admin credentials (Password: rit@2026 / RIT@2026).',
};
}, []);
const logout = useCallback(() => {
if (user?.email && window.google) {
try {
@@ -189,19 +316,29 @@ export function AuthProvider({ children }: { children: ReactNode }) {
} catch {}
}
setUser(null);
localStorage.removeItem(STORAGE_KEY);
localStorage.removeItem(GOOGLE_STORAGE_KEY);
localStorage.removeItem(ADMIN_TOKEN_KEY);
localStorage.removeItem(ADMIN_ROLE_KEY);
localStorage.removeItem(ADMIN_USER_KEY);
clearImsSession();
setIsAuthModalOpen(false);
}, [user]);
const isAdmin = user?.role === 'ROLE_SUPER_ADMIN' || user?.role === 'ROLE_TRANSPORT';
const isTransportAdmin = user?.role === 'ROLE_TRANSPORT';
return (
<AuthContext.Provider
value={{
user,
isAuthenticated: !!user,
isVerifiedStudent: !!user?.verifiedStudent,
isAdmin,
isTransportAdmin,
isLoading,
isAuthModalOpen,
loginWithGoogle,
loginWithCredentials,
openAuthModal,
closeAuthModal,
logout,