Alsmwal commited on
Commit
ca2ca26
·
verified ·
1 Parent(s): 2fcdb07

Update reset-password.html

Browse files
Files changed (1) hide show
  1. reset-password.html +416 -416
reset-password.html CHANGED
@@ -1,417 +1,417 @@
1
- <!DOCTYPE html>
2
- <html lang="en" dir="ltr">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Reset Password - University AI</title>
7
- <style>
8
- * {
9
- margin: 0;
10
- padding: 0;
11
- box-sizing: border-box;
12
- }
13
-
14
- :root {
15
- --olive-light: #3A662A;
16
- --bg-light: #FFFFFF;
17
- --bg-dark: #1A1A1A;
18
- --text-light: #2C2C2C;
19
- --text-dark: #F5F5F5;
20
- --card-light: #F8F9FA;
21
- --card-dark: #2D2D2D;
22
- }
23
-
24
- body {
25
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
26
- min-height: 100vh;
27
- display: flex;
28
- align-items: center;
29
- justify-content: center;
30
- transition: all 0.3s ease;
31
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
32
- color: var(--text-light);
33
- }
34
-
35
- .container {
36
- width: 100%;
37
- max-width: 450px;
38
- padding: 20px;
39
- }
40
-
41
- .card {
42
- background: white;
43
- padding: 40px;
44
- border-radius: 15px;
45
- box-shadow: 0 10px 40px rgba(0,0,0,0.1);
46
- }
47
-
48
- .header {
49
- text-align: center;
50
- margin-bottom: 30px;
51
- }
52
-
53
- .logo {
54
- font-size: 48px;
55
- margin-bottom: 10px;
56
- }
57
-
58
- h1 {
59
- font-size: 28px;
60
- margin-bottom: 10px;
61
- color: var(--olive-light);
62
- }
63
-
64
- .subtitle {
65
- opacity: 0.7;
66
- font-size: 14px;
67
- }
68
-
69
- .form-group {
70
- margin-bottom: 20px;
71
- }
72
-
73
- label {
74
- display: block;
75
- margin-bottom: 8px;
76
- font-weight: 500;
77
- }
78
-
79
- input {
80
- width: 100%;
81
- padding: 12px 15px;
82
- border-radius: 8px;
83
- border: 2px solid #e0e0e0;
84
- font-size: 16px;
85
- transition: all 0.3s ease;
86
- }
87
-
88
- input:focus {
89
- outline: none;
90
- border-color: var(--olive-light);
91
- }
92
-
93
- .btn {
94
- width: 100%;
95
- padding: 14px;
96
- border: none;
97
- border-radius: 8px;
98
- font-size: 16px;
99
- font-weight: 600;
100
- cursor: pointer;
101
- transition: all 0.3s ease;
102
- background: var(--olive-light);
103
- color: white;
104
- }
105
-
106
- .btn:hover {
107
- transform: translateY(-2px);
108
- box-shadow: 0 5px 15px rgba(58, 102, 42, 0.4);
109
- }
110
-
111
- .btn:disabled {
112
- opacity: 0.6;
113
- cursor: not-allowed;
114
- transform: none;
115
- }
116
-
117
- .alert {
118
- padding: 12px;
119
- border-radius: 8px;
120
- margin-bottom: 20px;
121
- display: none;
122
- }
123
-
124
- .alert.error {
125
- background: #fee;
126
- color: #c33;
127
- border: 1px solid #fcc;
128
- }
129
-
130
- .alert.success {
131
- background: #efe;
132
- color: #3c3;
133
- border: 1px solid #cfc;
134
- }
135
-
136
- .alert.show {
137
- display: block;
138
- }
139
-
140
- .links {
141
- text-align: center;
142
- margin-top: 20px;
143
- font-size: 14px;
144
- }
145
-
146
- .links a {
147
- color: var(--olive-light);
148
- text-decoration: none;
149
- font-weight: 500;
150
- }
151
-
152
- .links a:hover {
153
- text-decoration: underline;
154
- }
155
-
156
- .loading {
157
- display: none;
158
- text-align: center;
159
- margin-top: 10px;
160
- }
161
-
162
- .loading.show {
163
- display: block;
164
- }
165
-
166
- .password-strength {
167
- margin-top: 5px;
168
- font-size: 12px;
169
- }
170
-
171
- .strength-weak { color: #c33; }
172
- .strength-medium { color: #f90; }
173
- .strength-strong { color: #3c3; }
174
- </style>
175
- </head>
176
- <body>
177
- <div class="container">
178
- <div class="card">
179
- <div class="header">
180
- <div class="logo">🔐</div>
181
- <h1>Reset Password</h1>
182
- <p class="subtitle">Enter the code sent to your email</p>
183
- </div>
184
-
185
- <div id="alert" class="alert"></div>
186
-
187
- <div id="resetForm">
188
- <input type="hidden" id="email">
189
- <div class="form-group">
190
- <label for="code">Verification Code</label>
191
- <input
192
- type="text"
193
- id="code"
194
- required
195
- maxlength="6"
196
- placeholder="123456"
197
- style="letter-spacing: 2px; font-weight: bold;"
198
- >
199
- <div style="text-align: right; margin-top: 8px; font-size: 14px;">
200
- <a href="#" id="resendBtn" style="color: var(--olive-light); text-decoration: none;">Resend Code</a>
201
- <span id="timerContainer" style="display: none; color: #666;">
202
- (Wait <span id="timer">60</span>s)
203
- </span>
204
- </div>
205
- </div>
206
-
207
- <div class="form-group">
208
- <label for="newPassword">New Password</label>
209
- <input
210
- type="password"
211
- id="newPassword"
212
- name="newPassword"
213
- required
214
- placeholder="••••••••"
215
- minlength="6"
216
- >
217
- <div id="passwordStrength" class="password-strength"></div>
218
- </div>
219
-
220
- <div class="form-group">
221
- <label for="confirmPassword">Confirm Password</label>
222
- <input
223
- type="password"
224
- id="confirmPassword"
225
- name="confirmPassword"
226
- required
227
- placeholder="••••••••"
228
- >
229
- </div>
230
-
231
- <button type="button" class="btn" id="resetBtn" onclick="handleResetPassword()">
232
- Reset Password
233
- </button>
234
-
235
- <div class="loading" id="loading">
236
- <p>Resetting password...</p>
237
- </div>
238
- </div>
239
-
240
- <div class="links">
241
- <p>Remember your password? <a href="login.html">Sign In</a></p>
242
- </div>
243
- </div>
244
- </div>
245
-
246
- <script>
247
- const API_URL = '';
248
-
249
- // Get email from URL if present
250
- const urlParams = new URLSearchParams(window.location.search);
251
- const emailParam = urlParams.get('email');
252
- if (emailParam) {
253
- document.getElementById('email').value = emailParam;
254
- } else {
255
- showAlert('Email not found in URL. Please start the process again.', 'error');
256
- }
257
- // عرض رسالة
258
- function showAlert(message, type = 'error') {
259
- const alert = document.getElementById('alert');
260
- alert.textContent = message;
261
- alert.className = `alert ${type} show`;
262
-
263
- setTimeout(() => {
264
- alert.classList.remove('show');
265
- }, 5000);
266
- }
267
-
268
- // فحص قوة كلمة المرور
269
- document.getElementById('newPassword').addEventListener('input', (e) => {
270
- const password = e.target.value;
271
- const strengthDiv = document.getElementById('passwordStrength');
272
-
273
- let strength = 0;
274
- if (password.length >= 8) strength++;
275
- if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
276
- if (/\d/.test(password)) strength++;
277
- if (/[^a-zA-Z\d]/.test(password)) strength++;
278
-
279
- if (password.length === 0) {
280
- strengthDiv.textContent = '';
281
- } else if (strength <= 1) {
282
- strengthDiv.textContent = '⚠️ Weak password';
283
- strengthDiv.className = 'password-strength strength-weak';
284
- } else if (strength === 2) {
285
- strengthDiv.textContent = '⚡ Medium password';
286
- strengthDiv.className = 'password-strength strength-medium';
287
- } else {
288
- strengthDiv.textContent = '✅ Strong password';
289
- strengthDiv.className = 'password-strength strength-strong';
290
- }
291
- });
292
-
293
- // Restrict code input to numbers only
294
- document.getElementById('code').addEventListener('input', function(e) {
295
- this.value = this.value.replace(/[^0-9]/g, '');
296
- });
297
-
298
- // Timer Logic
299
- function startResendTimer(duration = 60) {
300
- const btn = document.getElementById('resendBtn');
301
- const container = document.getElementById('timerContainer');
302
- const timerSpan = document.getElementById('timer');
303
-
304
- let timeLeft = duration;
305
-
306
- // Disable button
307
- btn.style.pointerEvents = 'none';
308
- btn.style.opacity = '0.5';
309
- btn.style.textDecoration = 'none';
310
-
311
- // Show timer
312
- container.style.display = 'inline';
313
- timerSpan.textContent = timeLeft;
314
-
315
- const interval = setInterval(() => {
316
- timeLeft--;
317
- timerSpan.textContent = timeLeft;
318
-
319
- if (timeLeft <= 0) {
320
- clearInterval(interval);
321
- btn.style.pointerEvents = 'auto';
322
- btn.style.opacity = '1';
323
- btn.style.textDecoration = 'underline';
324
- container.style.display = 'none';
325
- }
326
- }, 1000);
327
- }
328
-
329
- // Start timer on load
330
- startResendTimer();
331
-
332
- // Resend Logic
333
- document.getElementById('resendBtn').addEventListener('click', async (e) => {
334
- e.preventDefault();
335
- const email = document.getElementById('email').value;
336
- if(!email) return;
337
-
338
- try {
339
- // Reuse forgot-password endpoint to generate a new OTP
340
- const response = await fetch(`${API_URL}/auth/forgot-password`, {
341
- method: 'POST',
342
- headers: { 'Content-Type': 'application/json' },
343
- body: JSON.stringify({ email })
344
- });
345
-
346
- if (response.ok) {
347
- showAlert('New code sent!', 'success');
348
- startResendTimer();
349
- } else {
350
- const data = await response.json();
351
- showAlert(data.detail || 'Failed to resend', 'error');
352
- }
353
- } catch (err) {
354
- console.error('Resend error:', err);
355
- showAlert('Connection error', 'error');
356
- }
357
- });
358
-
359
- async function handleResetPassword() {
360
- const email = document.getElementById('email').value;
361
- const code = document.getElementById('code').value.trim();
362
- const newPassword = document.getElementById('newPassword').value;
363
- const confirmPassword = document.getElementById('confirmPassword').value;
364
- const resetBtn = document.getElementById('resetBtn');
365
- const loading = document.getElementById('loading');
366
-
367
- // التحقق من تطابق كلمات المرور
368
- if (newPassword !== confirmPassword) {
369
- showAlert('Passwords do not match!');
370
- return;
371
- }
372
-
373
- if (!/^\d{6}$/.test(code)) {
374
- showAlert('Code must be exactly 6 digits', 'error');
375
- return;
376
- }
377
-
378
- if (newPassword.length < 6) {
379
- showAlert('Password must be at least 6 characters');
380
- return;
381
- }
382
-
383
- resetBtn.disabled = true;
384
- loading.classList.add('show');
385
-
386
- try {
387
- const response = await fetch(`${API_URL}/auth/reset-password`, {
388
- method: 'POST',
389
- headers: { 'Content-Type': 'application/json' },
390
- body: JSON.stringify({
391
- email: email,
392
- token: code,
393
- new_password: newPassword
394
- })
395
- });
396
-
397
- const data = await response.json();
398
-
399
- if (response.ok) {
400
- showAlert('✅ Password reset successfully! redirecting...', 'success');
401
- setTimeout(() => {
402
- window.location.replace('login.html');
403
- }, 5000);
404
- } else {
405
- showAlert(data.detail || 'Invalid code or email.', 'error');
406
- }
407
- } catch (error) {
408
- console.error('Reset error:', error);
409
- showAlert('Connection error.', 'error');
410
- } finally {
411
- resetBtn.disabled = false;
412
- loading.classList.remove('show');
413
- }
414
- }
415
- </script>
416
- </body>
417
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" dir="ltr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Reset Password - University AI</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ :root {
15
+ --olive-light: #3A662A;
16
+ --bg-light: #FFFFFF;
17
+ --bg-dark: #1A1A1A;
18
+ --text-light: #2C2C2C;
19
+ --text-dark: #F5F5F5;
20
+ --card-light: #F8F9FA;
21
+ --card-dark: #2D2D2D;
22
+ }
23
+
24
+ body {
25
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
26
+ min-height: 100vh;
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: center;
30
+ transition: all 0.3s ease;
31
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
32
+ color: var(--text-light);
33
+ }
34
+
35
+ .container {
36
+ width: 100%;
37
+ max-width: 450px;
38
+ padding: 20px;
39
+ }
40
+
41
+ .card {
42
+ background: white;
43
+ padding: 40px;
44
+ border-radius: 15px;
45
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1);
46
+ }
47
+
48
+ .header {
49
+ text-align: center;
50
+ margin-bottom: 30px;
51
+ }
52
+
53
+ .logo {
54
+ font-size: 48px;
55
+ margin-bottom: 10px;
56
+ }
57
+
58
+ h1 {
59
+ font-size: 28px;
60
+ margin-bottom: 10px;
61
+ color: var(--olive-light);
62
+ }
63
+
64
+ .subtitle {
65
+ opacity: 0.7;
66
+ font-size: 14px;
67
+ }
68
+
69
+ .form-group {
70
+ margin-bottom: 20px;
71
+ }
72
+
73
+ label {
74
+ display: block;
75
+ margin-bottom: 8px;
76
+ font-weight: 500;
77
+ }
78
+
79
+ input {
80
+ width: 100%;
81
+ padding: 12px 15px;
82
+ border-radius: 8px;
83
+ border: 2px solid #e0e0e0;
84
+ font-size: 16px;
85
+ transition: all 0.3s ease;
86
+ }
87
+
88
+ input:focus {
89
+ outline: none;
90
+ border-color: var(--olive-light);
91
+ }
92
+
93
+ .btn {
94
+ width: 100%;
95
+ padding: 14px;
96
+ border: none;
97
+ border-radius: 8px;
98
+ font-size: 16px;
99
+ font-weight: 600;
100
+ cursor: pointer;
101
+ transition: all 0.3s ease;
102
+ background: var(--olive-light);
103
+ color: white;
104
+ }
105
+
106
+ .btn:hover {
107
+ transform: translateY(-2px);
108
+ box-shadow: 0 5px 15px rgba(58, 102, 42, 0.4);
109
+ }
110
+
111
+ .btn:disabled {
112
+ opacity: 0.6;
113
+ cursor: not-allowed;
114
+ transform: none;
115
+ }
116
+
117
+ .alert {
118
+ padding: 12px;
119
+ border-radius: 8px;
120
+ margin-bottom: 20px;
121
+ display: none;
122
+ }
123
+
124
+ .alert.error {
125
+ background: #fee;
126
+ color: #c33;
127
+ border: 1px solid #fcc;
128
+ }
129
+
130
+ .alert.success {
131
+ background: #efe;
132
+ color: #3c3;
133
+ border: 1px solid #cfc;
134
+ }
135
+
136
+ .alert.show {
137
+ display: block;
138
+ }
139
+
140
+ .links {
141
+ text-align: center;
142
+ margin-top: 20px;
143
+ font-size: 14px;
144
+ }
145
+
146
+ .links a {
147
+ color: var(--olive-light);
148
+ text-decoration: none;
149
+ font-weight: 500;
150
+ }
151
+
152
+ .links a:hover {
153
+ text-decoration: underline;
154
+ }
155
+
156
+ .loading {
157
+ display: none;
158
+ text-align: center;
159
+ margin-top: 10px;
160
+ }
161
+
162
+ .loading.show {
163
+ display: block;
164
+ }
165
+
166
+ .password-strength {
167
+ margin-top: 5px;
168
+ font-size: 12px;
169
+ }
170
+
171
+ .strength-weak { color: #c33; }
172
+ .strength-medium { color: #f90; }
173
+ .strength-strong { color: #3c3; }
174
+ </style>
175
+ </head>
176
+ <body>
177
+ <div class="container">
178
+ <div class="card">
179
+ <div class="header">
180
+ <div class="logo">🔐</div>
181
+ <h1>Reset Password</h1>
182
+ <p class="subtitle">Enter the code sent to your email</p>
183
+ </div>
184
+
185
+ <div id="alert" class="alert"></div>
186
+
187
+ <div id="resetForm">
188
+ <input type="hidden" id="email">
189
+ <div class="form-group">
190
+ <label for="code">Verification Code</label>
191
+ <input
192
+ type="text"
193
+ id="code"
194
+ required
195
+ maxlength="6"
196
+ placeholder="123456"
197
+ style="letter-spacing: 2px; font-weight: bold;"
198
+ >
199
+ <div style="text-align: right; margin-top: 8px; font-size: 14px;">
200
+ <a href="#" id="resendBtn" style="color: var(--olive-light); text-decoration: none;">Resend Code</a>
201
+ <span id="timerContainer" style="display: none; color: #666;">
202
+ (Wait <span id="timer">60</span>s)
203
+ </span>
204
+ </div>
205
+ </div>
206
+
207
+ <div class="form-group">
208
+ <label for="newPassword">New Password</label>
209
+ <input
210
+ type="password"
211
+ id="newPassword"
212
+ name="newPassword"
213
+ required
214
+ placeholder="••••••••"
215
+ minlength="6"
216
+ >
217
+ <div id="passwordStrength" class="password-strength"></div>
218
+ </div>
219
+
220
+ <div class="form-group">
221
+ <label for="confirmPassword">Confirm Password</label>
222
+ <input
223
+ type="password"
224
+ id="confirmPassword"
225
+ name="confirmPassword"
226
+ required
227
+ placeholder="••••••••"
228
+ >
229
+ </div>
230
+
231
+ <button type="button" class="btn" id="resetBtn" onclick="handleResetPassword()">
232
+ Reset Password
233
+ </button>
234
+
235
+ <div class="loading" id="loading">
236
+ <p>Resetting password...</p>
237
+ </div>
238
+ </div>
239
+
240
+ <div class="links">
241
+ <p>Remember your password? <a href="login.html">Sign In</a></p>
242
+ </div>
243
+ </div>
244
+ </div>
245
+
246
+ <script>
247
+ const API_URL = '';
248
+
249
+ // Get email from URL if present
250
+ const urlParams = new URLSearchParams(window.location.search);
251
+ const emailParam = urlParams.get('email');
252
+ if (emailParam) {
253
+ document.getElementById('email').value = emailParam;
254
+ } else {
255
+ showAlert('Email not found in URL. Please start the process again.', 'error');
256
+ }
257
+ // عرض رسالة
258
+ function showAlert(message, type = 'error') {
259
+ const alert = document.getElementById('alert');
260
+ alert.textContent = message;
261
+ alert.className = `alert ${type} show`;
262
+
263
+ setTimeout(() => {
264
+ alert.classList.remove('show');
265
+ }, 5000);
266
+ }
267
+
268
+ // فحص قوة كلمة المرور
269
+ document.getElementById('newPassword').addEventListener('input', (e) => {
270
+ const password = e.target.value;
271
+ const strengthDiv = document.getElementById('passwordStrength');
272
+
273
+ let strength = 0;
274
+ if (password.length >= 8) strength++;
275
+ if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
276
+ if (/\d/.test(password)) strength++;
277
+ if (/[^a-zA-Z\d]/.test(password)) strength++;
278
+
279
+ if (password.length === 0) {
280
+ strengthDiv.textContent = '';
281
+ } else if (strength <= 1) {
282
+ strengthDiv.textContent = '⚠️ Weak password';
283
+ strengthDiv.className = 'password-strength strength-weak';
284
+ } else if (strength === 2) {
285
+ strengthDiv.textContent = '⚡ Medium password';
286
+ strengthDiv.className = 'password-strength strength-medium';
287
+ } else {
288
+ strengthDiv.textContent = '✅ Strong password';
289
+ strengthDiv.className = 'password-strength strength-strong';
290
+ }
291
+ });
292
+
293
+ // Restrict code input to numbers only
294
+ document.getElementById('code').addEventListener('input', function(e) {
295
+ this.value = this.value.replace(/[^0-9]/g, '');
296
+ });
297
+
298
+ // Timer Logic
299
+ function startResendTimer(duration = 60) {
300
+ const btn = document.getElementById('resendBtn');
301
+ const container = document.getElementById('timerContainer');
302
+ const timerSpan = document.getElementById('timer');
303
+
304
+ let timeLeft = duration;
305
+
306
+ // Disable button
307
+ btn.style.pointerEvents = 'none';
308
+ btn.style.opacity = '0.5';
309
+ btn.style.textDecoration = 'none';
310
+
311
+ // Show timer
312
+ container.style.display = 'inline';
313
+ timerSpan.textContent = timeLeft;
314
+
315
+ const interval = setInterval(() => {
316
+ timeLeft--;
317
+ timerSpan.textContent = timeLeft;
318
+
319
+ if (timeLeft <= 0) {
320
+ clearInterval(interval);
321
+ btn.style.pointerEvents = 'auto';
322
+ btn.style.opacity = '1';
323
+ btn.style.textDecoration = 'underline';
324
+ container.style.display = 'none';
325
+ }
326
+ }, 1000);
327
+ }
328
+
329
+ // Start timer on load
330
+ startResendTimer();
331
+
332
+ // Resend Logic
333
+ document.getElementById('resendBtn').addEventListener('click', async (e) => {
334
+ e.preventDefault();
335
+ const email = document.getElementById('email').value;
336
+ if(!email) return;
337
+
338
+ try {
339
+ // Reuse forgot-password endpoint to generate a new OTP
340
+ const response = await fetch(`${API_URL}/auth/forgot-password`, {
341
+ method: 'POST',
342
+ headers: { 'Content-Type': 'application/json' },
343
+ body: JSON.stringify({ email })
344
+ });
345
+
346
+ if (response.ok) {
347
+ showAlert('New code sent!', 'success');
348
+ startResendTimer();
349
+ } else {
350
+ const data = await response.json();
351
+ showAlert(data.detail || 'Failed to resend', 'error');
352
+ }
353
+ } catch (err) {
354
+ console.error('Resend error:', err);
355
+ showAlert('Connection error', 'error');
356
+ }
357
+ });
358
+
359
+ async function handleResetPassword() {
360
+ const email = document.getElementById('email').value;
361
+ const code = document.getElementById('code').value.trim();
362
+ const newPassword = document.getElementById('newPassword').value;
363
+ const confirmPassword = document.getElementById('confirmPassword').value;
364
+ const resetBtn = document.getElementById('resetBtn');
365
+ const loading = document.getElementById('loading');
366
+
367
+ // التحقق من تطابق كلمات المرور
368
+ if (newPassword !== confirmPassword) {
369
+ showAlert('Passwords do not match!');
370
+ return;
371
+ }
372
+
373
+ if (!/^\d{6}$/.test(code)) {
374
+ showAlert('Code must be exactly 6 digits', 'error');
375
+ return;
376
+ }
377
+
378
+ if (newPassword.length < 6) {
379
+ showAlert('Password must be at least 6 characters');
380
+ return;
381
+ }
382
+
383
+ resetBtn.disabled = true;
384
+ loading.classList.add('show');
385
+
386
+ try {
387
+ const response = await fetch(`${API_URL}/auth/reset-password`, {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify({
391
+ email: email,
392
+ token: code,
393
+ new_password: newPassword
394
+ })
395
+ });
396
+
397
+ const data = await response.json();
398
+
399
+ if (response.ok) {
400
+ showAlert('✅ Password reset successfully! redirecting...', 'success');
401
+ setTimeout(() => {
402
+ window.location.replace('login.html');
403
+ }, 5000);
404
+ } else {
405
+ showAlert(data.detail || 'Invalid code or email.', 'error');
406
+ }
407
+ } catch (error) {
408
+ console.error('Reset error:', error);
409
+ showAlert('Connection error.', 'error');
410
+ } finally {
411
+ resetBtn.disabled = false;
412
+ loading.classList.remove('show');
413
+ }
414
+ }
415
+ </script>
416
+ </body>
417
  </html>