Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Student Login — AttendAI Pro</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet"> | |
| <link rel="stylesheet" href="css/main.css"> | |
| <link rel="stylesheet" href="css/auth.css"> | |
| <!-- Google Identity Services --> | |
| <script src="https://accounts.google.com/gsi/client" async defer></script> | |
| </head> | |
| <body class="auth-body"> | |
| <div class="auth-page"> | |
| <!-- Left Panel --> | |
| <div class="auth-left"> | |
| <div class="auth-left-content"> | |
| <a href="index.html" class="auth-back"> | |
| ← Back to Home | |
| </a> | |
| <div class="auth-brand"> | |
| <div class="auth-logo-icon student-gradient"> | |
| <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"> | |
| <path d="M22 10v6M2 10l10-5 10 5-10 5z"/> | |
| <path d="M6 12v5c3 3 9 3 12 0v-5"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <div class="auth-app-name">AttendAI Pro</div> | |
| <div class="auth-portal-name">Student Portal</div> | |
| </div> | |
| </div> | |
| <h2 class="auth-headline">Mark your attendance<br>the smart way</h2> | |
| <p class="auth-desc">Secure, fast, and tamper-proof attendance using face recognition and GPS verification.</p> | |
| <div class="auth-features"> | |
| <div class="auth-feature"><span>🔒</span> Google OAuth — No passwords needed</div> | |
| <div class="auth-feature"><span>🤖</span> Face recognition verification</div> | |
| <div class="auth-feature"><span>📍</span> GPS geofencing ensures you're in class</div> | |
| <div class="auth-feature"><span>⚡</span> Mark attendance in under 3 seconds</div> | |
| </div> | |
| </div> | |
| <div class="auth-left-footer"> | |
| <div class="auth-security-badges"> | |
| <span>🔐 Encrypted</span> | |
| <span>✓ ISO Compliant</span> | |
| <span>🛡️ Zero Proxy</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Panel --> | |
| <div class="auth-right"> | |
| <div class="auth-form-wrapper"> | |
| <!-- Login Card --> | |
| <div class="auth-card" id="loginCard"> | |
| <div class="auth-card-header"> | |
| <h1>Student Login</h1> | |
| <p>Sign in with your institutional Google account</p> | |
| </div> | |
| <!-- Alert Area --> | |
| <div id="alertArea" style="margin-bottom:16px;display:none"></div> | |
| <!-- Role Reminder --> | |
| <div class="role-reminder student-reminder"> | |
| <div class="reminder-icon">🎓</div> | |
| <div> | |
| <strong>Students Only</strong> | |
| <p>Use your institutional email. Your account must be pre-registered by your administrator.</p> | |
| </div> | |
| </div> | |
| <!-- Google Sign In Button --> | |
| <div style="margin: 24px 0;"> | |
| <button class="btn btn-google btn-full" id="googleSignInBtn" onclick="initiateGoogleLogin()"> | |
| <img src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg" alt="Google"> | |
| Continue with Google | |
| </button> | |
| </div> | |
| <!-- Loading State --> | |
| <div id="loadingState" class="auth-loading hidden"> | |
| <div class="spinner spinner-dark"></div> | |
| <span id="loadingText">Authenticating with Google...</span> | |
| </div> | |
| <div class="auth-divider"> | |
| <span>Verification process</span> | |
| </div> | |
| <div class="auth-steps-preview"> | |
| <div class="auth-step-item active"> | |
| <div class="step-dot">1</div> | |
| <span>Google Authentication</span> | |
| </div> | |
| <div class="step-connector"></div> | |
| <div class="auth-step-item"> | |
| <div class="step-dot">2</div> | |
| <span>Database Verification</span> | |
| </div> | |
| <div class="step-connector"></div> | |
| <div class="auth-step-item"> | |
| <div class="step-dot">3</div> | |
| <span>Access Granted</span> | |
| </div> | |
| </div> | |
| <div class="auth-notice-box"> | |
| <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 8v4M12 16h.01"/></svg> | |
| <span>Only pre-registered students can log in. If you're new, contact your institution's admin.</span> | |
| </div> | |
| </div> | |
| <!-- Unauthorized Message --> | |
| <div class="auth-card hidden" id="unauthorizedCard"> | |
| <div class="auth-error-state"> | |
| <div class="error-icon">🚫</div> | |
| <h2>Unauthorized Email</h2> | |
| <p id="unauthorizedMsg">Your Google account is not registered in our system. Please contact your institution's administrator to register your account.</p> | |
| <div class="error-details" id="errorEmail"></div> | |
| <button class="btn btn-primary btn-full" onclick="resetLogin()"> | |
| Try Different Account | |
| </button> | |
| <a href="index.html" class="btn btn-secondary btn-full" style="margin-top:10px"> | |
| Go Back to Home | |
| </a> | |
| </div> | |
| </div> | |
| <!-- Welcome Back (brief) --> | |
| <div class="auth-card hidden" id="welcomeCard"> | |
| <div class="auth-success-state"> | |
| <img id="welcomeAvatar" src="" alt="Profile" class="avatar avatar-xl" style="margin:0 auto 16px;display:block"> | |
| <h2>Welcome back!</h2> | |
| <p id="welcomeName"></p> | |
| <div class="auth-loading" style="margin-top:20px"> | |
| <div class="spinner spinner-dark"></div> | |
| <span>Redirecting to your dashboard...</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="auth-footer-text"> | |
| Are you a teacher? <a href="teacher-login.html">Teacher Login →</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="js/config.js"></script> | |
| <script> | |
| // Check if already authenticated | |
| const existingUser = AuthState.get(); | |
| if (existingUser && existingUser.role === 'student') { | |
| window.location.href = 'student-dashboard.html'; | |
| } else if (existingUser && existingUser.role === 'teacher') { | |
| window.location.href = 'teacher-dashboard.html'; | |
| } else if (existingUser && existingUser.role === 'admin') { | |
| window.location.href = 'admin-panel.html'; | |
| } | |
| function showAlert(message, type = 'danger') { | |
| const area = document.getElementById('alertArea'); | |
| area.style.display = 'block'; | |
| area.innerHTML = `<div class="alert alert-${type}"><span class="alert-icon">${type === 'danger' ? '❌' : type === 'success' ? '✅' : 'ℹ️'}</span><div>${message}</div></div>`; | |
| } | |
| function showLoading(text = 'Verifying your account...') { | |
| document.getElementById('loadingState').classList.remove('hidden'); | |
| document.getElementById('loadingText').textContent = text; | |
| document.getElementById('googleSignInBtn').disabled = true; | |
| } | |
| function hideLoading() { | |
| document.getElementById('loadingState').classList.add('hidden'); | |
| document.getElementById('googleSignInBtn').disabled = false; | |
| } | |
| function showUnauthorized(email, message) { | |
| document.getElementById('loginCard').classList.add('hidden'); | |
| document.getElementById('unauthorizedCard').classList.remove('hidden'); | |
| document.getElementById('errorEmail').textContent = `Attempted email: ${email}`; | |
| if (message) document.getElementById('unauthorizedMsg').textContent = message; | |
| } | |
| function showWelcome(user) { | |
| document.getElementById('loginCard').classList.add('hidden'); | |
| document.getElementById('welcomeCard').classList.remove('hidden'); | |
| document.getElementById('welcomeAvatar').src = user.picture; | |
| document.getElementById('welcomeName').textContent = `${user.email} — Redirecting...`; | |
| } | |
| function resetLogin() { | |
| document.getElementById('loginCard').classList.remove('hidden'); | |
| document.getElementById('unauthorizedCard').classList.add('hidden'); | |
| document.getElementById('alertArea').style.display = 'none'; | |
| hideLoading(); | |
| if (typeof google !== 'undefined' && google.accounts) { | |
| google.accounts.id.disableAutoSelect(); | |
| } | |
| } | |
| async function verifyStudentInDB(googleUser) { | |
| try { | |
| showLoading('Checking student database...'); | |
| // Search for user by email | |
| const result = await API.get('users', { | |
| search: googleUser.email, | |
| limit: 100 | |
| }); | |
| const users = result.data || []; | |
| const user = users.find(u => | |
| u.email && u.email.toLowerCase() === googleUser.email.toLowerCase() | |
| ); | |
| if (!user) { | |
| showUnauthorized(googleUser.email, 'Your email is not registered in the student database. Please contact your institution administrator to register your account.'); | |
| await Logger.log('LOGIN_FAILED', 'auth', { email: googleUser.email, reason: 'not_registered', role: 'student' }, 'failure'); | |
| return; | |
| } | |
| if (user.role !== 'student') { | |
| showUnauthorized(googleUser.email, `Your account is registered as "${user.role}", not as a student. Please use the correct login portal.`); | |
| await Logger.log('LOGIN_FAILED', 'auth', { email: googleUser.email, reason: 'wrong_role', role: user.role }, 'failure'); | |
| return; | |
| } | |
| if (user.is_active === false) { | |
| showUnauthorized(googleUser.email, 'Your student account has been deactivated. Please contact your institution administrator.'); | |
| return; | |
| } | |
| // Merge Google profile data with DB record | |
| const sessionUser = { | |
| id: user.id, | |
| google_id: googleUser.sub, | |
| email: user.email, | |
| name: googleUser.name || user.name, | |
| picture: googleUser.picture || '', | |
| role: user.role, | |
| roll_number: user.roll_number, | |
| department: user.department, | |
| institution: user.institution, | |
| face_data: user.face_data | |
| }; | |
| // Update last login | |
| await API.patch('users', user.id, { | |
| last_login: Date.now(), | |
| name: sessionUser.name, | |
| picture: sessionUser.picture | |
| }); | |
| AuthState.save(sessionUser); | |
| await Logger.log('LOGIN_SUCCESS', 'auth', { email: googleUser.email, role: 'student' }, 'success'); | |
| showWelcome(sessionUser); | |
| setTimeout(() => { window.location.href = 'student-dashboard.html'; }, 1800); | |
| } catch (err) { | |
| console.error('Verification error:', err); | |
| hideLoading(); | |
| showAlert('System error during verification. Please try again.'); | |
| } | |
| } | |
| // Google OAuth callback | |
| function handleGoogleCredential(response) { | |
| try { | |
| // Decode JWT payload (verification happens server-side in production) | |
| const parts = response.credential.split('.'); | |
| if (parts.length !== 3) throw new Error('Invalid token'); | |
| const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/'))); | |
| if (!payload.email_verified) { | |
| showAlert('Your Google email is not verified. Please verify your Google account first.'); | |
| return; | |
| } | |
| verifyStudentInDB({ | |
| sub: payload.sub, | |
| email: payload.email, | |
| name: payload.name, | |
| picture: payload.picture | |
| }); | |
| } catch (err) { | |
| console.error('Token decode error:', err); | |
| showAlert('Authentication failed. Please try again.'); | |
| } | |
| } | |
| function initiateGoogleLogin() { | |
| if (APP_CONFIG.googleClientId === 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { | |
| showAlert(` | |
| <strong>Google OAuth Not Configured</strong><br> | |
| To enable Google login, a system administrator must configure the Google OAuth Client ID.<br> | |
| Please contact your institution's technical team. | |
| `, 'warning'); | |
| return; | |
| } | |
| if (typeof google !== 'undefined' && google.accounts) { | |
| google.accounts.id.prompt(); | |
| } else { | |
| showAlert('Google authentication service unavailable. Check your internet connection.', 'warning'); | |
| } | |
| } | |
| // Initialize Google Identity Services | |
| window.addEventListener('load', () => { | |
| if (APP_CONFIG.googleClientId !== 'YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com') { | |
| if (typeof google !== 'undefined') { | |
| google.accounts.id.initialize({ | |
| client_id: APP_CONFIG.googleClientId, | |
| callback: handleGoogleCredential, | |
| auto_select: false, | |
| cancel_on_tap_outside: true | |
| }); | |
| } | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |