Attender / register.html
chualinwei3's picture
Update register.html
fc966ab verified
Raw
History Blame Contribute Delete
16 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register - Attendr</title>
<link rel="stylesheet" href="style.css">
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@700;800&display=swap"
rel="stylesheet">
</head>
<body>
<!-- Navigation -->
<!-- Navigation -->
{% include '_navbar.html' %}
<!-- Main Content -->
<section class="section">
<div class="container" style="max-width: 800px;">
<h1 class="text-center mb-3">Register Account</h1>
<p class="text-center text-muted mb-3">
Create your student or lecturer account
</p>
<!-- Global Status Messages -->
<div id="globalStatusMessage" class="mb-2"></div>
<!-- Step 1: Account Details -->
<div id="step1Card" class="glass-card">
<h2 class="mb-2">Step 1: Account Information</h2>
<div class="form-group">
<label class="form-label">I am a...</label>
<select id="roleSelect" class="form-select">
<option value="">Select your role</option>
<option value="student">Student</option>
<option value="lecturer">Lecturer</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Matric Number</label>
<input type="text" id="matricNo" class="form-input" placeholder="e.g., A20EC0001 or L001"
style="text-transform: uppercase;">
<small class="text-muted" id="matricHelp" style="font-size: 0.875rem;">
Select your role first
</small>
</div>
<div class="form-group">
<label class="form-label">Full Name</label>
<input type="text" id="fullName" class="form-input" placeholder="Your full name">
</div>
<div class="form-group">
<label class="form-label">Email (Optional)</label>
<input type="email" id="email" class="form-input" placeholder="your.email@graduate.utm.my">
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" id="password" class="form-input" placeholder="Create a strong password">
</div>
<div class="form-group">
<label class="form-label">Confirm Password</label>
<input type="password" id="confirmPassword" class="form-input" placeholder="Re-enter your password">
</div>
<div id="statusMessage" class="mt-2" style="display:none;"></div>
<button id="nextBtn" class="btn btn-primary mt-2" style="width: 100%;">
Next: Face Registration →
</button>
<div class="text-center mt-2">
<p class="text-muted">
Already have an account? <a href="/login" style="color: var(--color-primary);">Login here</a>
</p>
</div>
</div>
<!-- Step 2: Face Registration (Students Only) -->
<div id="step2Card" class="glass-card mt-2 hidden">
<h2 class="mb-2">Step 2: Face Registration</h2>
<p class="text-muted mb-2">Capture your face for biometric verification</p>
<div class="text-center">
<video id="video" width="400" height="300" autoplay
style="border-radius: 12px; border: 2px solid var(--color-primary);"></video>
<canvas id="canvas" width="400" height="300" style="display: none;"></canvas>
<div id="preview" class="mt-2 hidden">
<img id="capturedImage"
style="max-width: 400px; border-radius: 12px; border: 2px solid var(--color-success);">
</div>
</div>
<div id="faceStatusMessage" class="mt-2" style="display:none;"></div>
<div class="flex-center gap-2 mt-2">
<button id="startCameraBtn" class="btn btn-secondary">
📷 Start Camera
</button>
<button id="captureBtn" class="btn btn-primary hidden">
📸 Capture Face
</button>
<button id="retakeBtn" class="btn btn-outline hidden">
🔄 Retake
</button>
<button id="registerBtn" class="btn btn-success hidden">
✅ Complete Registration
</button>
</div>
<div class="text-center mt-2">
<button id="backBtn" class="btn btn-outline">
← Back to Account Details
</button>
</div>
</div>
<!-- Success Message -->
<div id="successCard" class="glass-card mt-2 hidden"
style="background: rgba(34, 197, 94, 0.1); border: 2px solid var(--color-success);">
<div class="text-center">
<div style="font-size: 4rem; margin-bottom: 1rem;"></div>
<h2 style="color: var(--color-success);">Registration Submitted!</h2>
<p class="text-muted mt-2" id="successMessage">Your account has been created successfully. Your application is now pending approval by an Admin. You will be able to login once approved.</p>
<div class="flex-center gap-2 mt-4">
<a href="/login" class="btn btn-primary">Go to Login Page</a>
</div>
</div>
</div>
</div>
</section>
<!-- Loading Overlay -->
<div id="loadingOverlay" class="loading-overlay hidden">
<div class="text-center">
<div class="spinner"></div>
<p class="mt-2" id="loadingText">Processing...</p>
</div>
</div>
<script>
let videoStream = null;
let capturedImageData = null;
let userRole = null;
// Update matric number help text based on role
document.getElementById('roleSelect').addEventListener('change', (e) => {
userRole = e.target.value;
const helpText = document.getElementById('matricHelp');
if (userRole === 'student') {
helpText.textContent = 'Student matric number must start with A or B (e.g., A20EC0001)';
helpText.style.color = 'var(--color-primary)';
} else if (userRole === 'lecturer') {
helpText.textContent = 'Lecturer matric number must start with L (e.g., L001)';
helpText.style.color = 'var(--color-primary)';
} else {
helpText.textContent = 'Select your role first';
helpText.style.color = 'var(--color-text-muted)';
}
});
// Next button - validate and move to face registration
document.getElementById('nextBtn').addEventListener('click', async () => {
const role = document.getElementById('roleSelect').value;
const matricNo = document.getElementById('matricNo').value.trim().toUpperCase();
const fullName = document.getElementById('fullName').value.trim();
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirmPassword').value;
// Validation
if (!role) {
showStatus('Please select your role', 'error');
return;
}
if (!matricNo || !fullName || !password) {
showStatus('Please fill in all required fields', 'error');
return;
}
if (password !== confirmPassword) {
showStatus('Passwords do not match', 'error');
return;
}
if (password.length < 6) {
showStatus('Password must be at least 6 characters long', 'error');
return;
}
// If lecturer, register directly (no face needed)
if (role === 'lecturer') {
await registerUser(false);
} else {
// Student - go to face registration
document.getElementById('step1Card').classList.add('hidden');
document.getElementById('step2Card').classList.remove('hidden');
}
});
// Back button
document.getElementById('backBtn').addEventListener('click', () => {
stopCamera();
document.getElementById('step2Card').classList.add('hidden');
document.getElementById('step1Card').classList.remove('hidden');
});
// Camera controls
document.getElementById('startCameraBtn').addEventListener('click', startCamera);
document.getElementById('captureBtn').addEventListener('click', captureFace);
document.getElementById('retakeBtn').addEventListener('click', retake);
document.getElementById('registerBtn').addEventListener('click', () => registerUser(true));
async function startCamera() {
try {
videoStream = await navigator.mediaDevices.getUserMedia({ video: true });
document.getElementById('video').srcObject = videoStream;
document.getElementById('startCameraBtn').classList.add('hidden');
document.getElementById('captureBtn').classList.remove('hidden');
showFaceStatus('Camera ready! Position your face in the frame', 'success');
} catch (error) {
showFaceStatus('Camera access denied. Please allow camera access.', 'error');
}
}
function stopCamera() {
if (videoStream) {
videoStream.getTracks().forEach(track => track.stop());
videoStream = null;
}
}
function captureFace() {
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.drawImage(video, 0, 0, 400, 300);
capturedImageData = canvas.toDataURL('image/jpeg');
document.getElementById('capturedImage').src = capturedImageData;
document.getElementById('preview').classList.remove('hidden');
document.getElementById('video').style.display = 'none';
document.getElementById('captureBtn').classList.add('hidden');
document.getElementById('retakeBtn').classList.remove('hidden');
document.getElementById('registerBtn').classList.remove('hidden');
stopCamera();
showFaceStatus('Face captured! Click "Complete Registration" to finish', 'success');
}
function retake() {
document.getElementById('preview').classList.add('hidden');
document.getElementById('video').style.display = 'block';
document.getElementById('retakeBtn').classList.add('hidden');
document.getElementById('registerBtn').classList.add('hidden');
capturedImageData = null;
startCamera();
}
async function registerUser(includeFace) {
const role = document.getElementById('roleSelect').value;
const matricNo = document.getElementById('matricNo').value.trim().toUpperCase();
const fullName = document.getElementById('fullName').value.trim();
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
if (includeFace && !capturedImageData) {
showFaceStatus('Please capture your face first', 'error');
return;
}
showLoading('Creating your account...');
try {
// Step 1: Create user account
const userResponse = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
role: role,
matric_no: matricNo,
name: fullName,
email: email || null,
password: password
})
});
const userData = await userResponse.json();
if (!userData.success && !userData.error.includes("already registered")) {
hideLoading();
showGlobalStatus(userData.error, 'error');
return;
}
// Step 2: Register face if student
if (includeFace) {
showLoading('Registering face data...');
const faceResponse = await fetch('/api/register_face', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
student_id: matricNo,
name: fullName,
email: email || null,
image: capturedImageData
})
});
const faceData = await faceResponse.json();
if (!faceData.success) {
hideLoading();
showGlobalStatus(`Face recognition failed: ${faceData.error}`, 'error');
return;
}
}
hideLoading();
console.log('Registration complete, showing success card');
// Show success
document.getElementById('step1Card').classList.add('hidden');
document.getElementById('step2Card').classList.add('hidden');
document.getElementById('successCard').classList.remove('hidden');
document.getElementById('globalStatusMessage').innerHTML = '';
} catch (error) {
hideLoading();
showStatus('Registration failed. Please try again.', 'error');
}
}
function showLoading(text) {
document.getElementById('loadingText').textContent = text;
document.getElementById('loadingOverlay').classList.remove('hidden');
}
function hideLoading() {
document.getElementById('loadingOverlay').classList.add('hidden');
}
function showStatus(message, type) {
showGlobalStatus(message, type);
}
function showFaceStatus(message, type) {
showGlobalStatus(message, type);
}
function showGlobalStatus(message, type) {
const statusDiv = document.getElementById('globalStatusMessage');
const alertClass = type === 'success' ? 'alert-success' : 'alert-error';
statusDiv.innerHTML = `<div class="alert ${alertClass}">${message}</div>`;
// Scroll to top to see error
window.scrollTo({ top: 0, behavior: 'smooth' });
if (type === 'error') {
setTimeout(() => {
statusDiv.innerHTML = '';
}, 8000);
}
}
</script>
</body>
</html>