Spaces:
Runtime error
Runtime error
| <html lang="en" dir="ltr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Reset Password - University AI</title> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| :root { | |
| --olive-light: #3A662A; | |
| --bg-light: #FFFFFF; | |
| --bg-dark: #1A1A1A; | |
| --text-light: #2C2C2C; | |
| --text-dark: #F5F5F5; | |
| --card-light: #F8F9FA; | |
| --card-dark: #2D2D2D; | |
| } | |
| body { | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| min-height: 100vh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| color: var(--text-light); | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 450px; | |
| padding: 20px; | |
| } | |
| .card { | |
| background: white; | |
| padding: 40px; | |
| border-radius: 15px; | |
| box-shadow: 0 10px 40px rgba(0,0,0,0.1); | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 30px; | |
| } | |
| .logo { | |
| font-size: 48px; | |
| margin-bottom: 10px; | |
| } | |
| h1 { | |
| font-size: 28px; | |
| margin-bottom: 10px; | |
| color: var(--olive-light); | |
| } | |
| .subtitle { | |
| opacity: 0.7; | |
| font-size: 14px; | |
| } | |
| .form-group { | |
| margin-bottom: 20px; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 500; | |
| } | |
| input { | |
| width: 100%; | |
| padding: 12px 15px; | |
| border-radius: 8px; | |
| border: 2px solid #e0e0e0; | |
| font-size: 16px; | |
| transition: all 0.3s ease; | |
| } | |
| input:focus { | |
| outline: none; | |
| border-color: var(--olive-light); | |
| } | |
| .btn { | |
| width: 100%; | |
| padding: 14px; | |
| border: none; | |
| border-radius: 8px; | |
| font-size: 16px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| background: var(--olive-light); | |
| color: white; | |
| } | |
| .btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 5px 15px rgba(58, 102, 42, 0.4); | |
| } | |
| .btn:disabled { | |
| opacity: 0.6; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .alert { | |
| padding: 12px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| display: none; | |
| } | |
| .alert.error { | |
| background: #fee; | |
| color: #c33; | |
| border: 1px solid #fcc; | |
| } | |
| .alert.success { | |
| background: #efe; | |
| color: #3c3; | |
| border: 1px solid #cfc; | |
| } | |
| .alert.show { | |
| display: block; | |
| } | |
| .links { | |
| text-align: center; | |
| margin-top: 20px; | |
| font-size: 14px; | |
| } | |
| .links a { | |
| color: var(--olive-light); | |
| text-decoration: none; | |
| font-weight: 500; | |
| } | |
| .links a:hover { | |
| text-decoration: underline; | |
| } | |
| .loading { | |
| display: none; | |
| text-align: center; | |
| margin-top: 10px; | |
| } | |
| .loading.show { | |
| display: block; | |
| } | |
| .password-strength { | |
| margin-top: 5px; | |
| font-size: 12px; | |
| } | |
| .strength-weak { color: #c33; } | |
| .strength-medium { color: #f90; } | |
| .strength-strong { color: #3c3; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="card"> | |
| <div class="header"> | |
| <div class="logo">🔐</div> | |
| <h1>Reset Password</h1> | |
| <p class="subtitle">Enter the code sent to your email</p> | |
| </div> | |
| <div id="alert" class="alert"></div> | |
| <div id="resetForm"> | |
| <input type="hidden" id="email"> | |
| <div class="form-group"> | |
| <label for="code">Verification Code</label> | |
| <input | |
| type="text" | |
| id="code" | |
| required | |
| maxlength="6" | |
| placeholder="123456" | |
| style="letter-spacing: 2px; font-weight: bold;" | |
| > | |
| <div style="text-align: right; margin-top: 8px; font-size: 14px;"> | |
| <a href="#" id="resendBtn" style="color: var(--olive-light); text-decoration: none;">Resend Code</a> | |
| <span id="timerContainer" style="display: none; color: #666;"> | |
| (Wait <span id="timer">60</span>s) | |
| </span> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="newPassword">New Password</label> | |
| <input | |
| type="password" | |
| id="newPassword" | |
| name="newPassword" | |
| required | |
| placeholder="••••••••" | |
| minlength="6" | |
| > | |
| <div id="passwordStrength" class="password-strength"></div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="confirmPassword">Confirm Password</label> | |
| <input | |
| type="password" | |
| id="confirmPassword" | |
| name="confirmPassword" | |
| required | |
| placeholder="••••••••" | |
| > | |
| </div> | |
| <button type="button" class="btn" id="resetBtn" onclick="handleResetPassword()"> | |
| Reset Password | |
| </button> | |
| <div class="loading" id="loading"> | |
| <p>Resetting password...</p> | |
| </div> | |
| </div> | |
| <div class="links"> | |
| <p>Remember your password? <a href="login.html">Sign In</a></p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| const API_URL = ''; | |
| // Get email from URL if present | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const emailParam = urlParams.get('email'); | |
| if (emailParam) { | |
| document.getElementById('email').value = emailParam; | |
| } else { | |
| showAlert('Email not found in URL. Please start the process again.', 'error'); | |
| } | |
| // عرض رسالة | |
| function showAlert(message, type = 'error') { | |
| const alert = document.getElementById('alert'); | |
| alert.textContent = message; | |
| alert.className = `alert ${type} show`; | |
| setTimeout(() => { | |
| alert.classList.remove('show'); | |
| }, 5000); | |
| } | |
| // فحص قوة كلمة المرور | |
| document.getElementById('newPassword').addEventListener('input', (e) => { | |
| const password = e.target.value; | |
| const strengthDiv = document.getElementById('passwordStrength'); | |
| let strength = 0; | |
| if (password.length >= 8) strength++; | |
| if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++; | |
| if (/\d/.test(password)) strength++; | |
| if (/[^a-zA-Z\d]/.test(password)) strength++; | |
| if (password.length === 0) { | |
| strengthDiv.textContent = ''; | |
| } else if (strength <= 1) { | |
| strengthDiv.textContent = '⚠️ Weak password'; | |
| strengthDiv.className = 'password-strength strength-weak'; | |
| } else if (strength === 2) { | |
| strengthDiv.textContent = '⚡ Medium password'; | |
| strengthDiv.className = 'password-strength strength-medium'; | |
| } else { | |
| strengthDiv.textContent = '✅ Strong password'; | |
| strengthDiv.className = 'password-strength strength-strong'; | |
| } | |
| }); | |
| // Restrict code input to numbers only | |
| document.getElementById('code').addEventListener('input', function(e) { | |
| this.value = this.value.replace(/[^0-9]/g, ''); | |
| }); | |
| // Timer Logic | |
| function startResendTimer(duration = 60) { | |
| const btn = document.getElementById('resendBtn'); | |
| const container = document.getElementById('timerContainer'); | |
| const timerSpan = document.getElementById('timer'); | |
| let timeLeft = duration; | |
| // Disable button | |
| btn.style.pointerEvents = 'none'; | |
| btn.style.opacity = '0.5'; | |
| btn.style.textDecoration = 'none'; | |
| // Show timer | |
| container.style.display = 'inline'; | |
| timerSpan.textContent = timeLeft; | |
| const interval = setInterval(() => { | |
| timeLeft--; | |
| timerSpan.textContent = timeLeft; | |
| if (timeLeft <= 0) { | |
| clearInterval(interval); | |
| btn.style.pointerEvents = 'auto'; | |
| btn.style.opacity = '1'; | |
| btn.style.textDecoration = 'underline'; | |
| container.style.display = 'none'; | |
| } | |
| }, 1000); | |
| } | |
| // Start timer on load | |
| startResendTimer(); | |
| // Resend Logic | |
| document.getElementById('resendBtn').addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| const email = document.getElementById('email').value; | |
| if(!email) return; | |
| try { | |
| // Reuse forgot-password endpoint to generate a new OTP | |
| const response = await fetch(`${API_URL}/auth/forgot-password`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ email }) | |
| }); | |
| if (response.ok) { | |
| showAlert('New code sent!', 'success'); | |
| startResendTimer(); | |
| } else { | |
| const data = await response.json(); | |
| showAlert(data.detail || 'Failed to resend', 'error'); | |
| } | |
| } catch (err) { | |
| console.error('Resend error:', err); | |
| showAlert('Connection error', 'error'); | |
| } | |
| }); | |
| async function handleResetPassword() { | |
| const email = document.getElementById('email').value; | |
| const code = document.getElementById('code').value.trim(); | |
| const newPassword = document.getElementById('newPassword').value; | |
| const confirmPassword = document.getElementById('confirmPassword').value; | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const loading = document.getElementById('loading'); | |
| // التحقق من تطابق كلمات المرور | |
| if (newPassword !== confirmPassword) { | |
| showAlert('Passwords do not match!'); | |
| return; | |
| } | |
| if (!/^\d{6}$/.test(code)) { | |
| showAlert('Code must be exactly 6 digits', 'error'); | |
| return; | |
| } | |
| if (newPassword.length < 6) { | |
| showAlert('Password must be at least 6 characters'); | |
| return; | |
| } | |
| resetBtn.disabled = true; | |
| loading.classList.add('show'); | |
| try { | |
| const response = await fetch(`${API_URL}/auth/reset-password`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ | |
| email: email, | |
| token: code, | |
| new_password: newPassword | |
| }) | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| showAlert('✅ Password reset successfully! redirecting...', 'success'); | |
| setTimeout(() => { | |
| window.location.replace('login.html'); | |
| }, 5000); | |
| } else { | |
| showAlert(data.detail || 'Invalid code or email.', 'error'); | |
| } | |
| } catch (error) { | |
| console.error('Reset error:', error); | |
| showAlert('Connection error.', 'error'); | |
| } finally { | |
| resetBtn.disabled = false; | |
| loading.classList.remove('show'); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |