Nguyendat92929 commited on
Commit
b94eb74
·
verified ·
1 Parent(s): c89e395

Update templates/register.html

Browse files
Files changed (1) hide show
  1. templates/register.html +526 -440
templates/register.html CHANGED
@@ -1,441 +1,527 @@
1
- <!DOCTYPE html>
2
- <html lang="vi" data-theme="dark">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <meta name="description" content="Đăng ký tài khoản một cách dễ dàng và an toàn">
8
- <title>Đăng Ký</title>
9
- <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
11
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
12
- <link href="https://cdnjs.cloudflare.com/ajax/libs/jarallax/2.2.1/jarallax.min.css" rel="stylesheet">
13
- <style>
14
- :root {
15
- --primary-color: #6366F1;
16
- --error-color: #dc3545;
17
- --success-color: #28a745;
18
- --text-muted: #adb5bd;
19
- --input-text: #000000;
20
- }
21
-
22
- [data-theme="dark"] {
23
- --card-bg: rgba(13, 27, 42, 0.95);
24
- --input-bg: #2c3e50;
25
- --input-border: #3b4a5e;
26
- --input-focus-bg: #34495e;
27
- --alert-bg: #1b263b;
28
- --input-text: #ffffff;
29
- }
30
-
31
- [data-theme="light"] {
32
- --card-bg: rgba(255, 255, 255, 0.95);
33
- --input-bg: #ffffff;
34
- --input-border: #ced4da;
35
- --input-focus-bg: #f8f9fa;
36
- --alert-bg: #e9ecef;
37
- --text-muted: #6c757d;
38
- --input-text: #000000;
39
- }
40
-
41
- body {
42
- background-color: #000000;
43
- min-height: 100vh;
44
- margin: 0;
45
- display: flex;
46
- justify-content: center;
47
- align-items: center;
48
- font-family: 'Inter', sans-serif;
49
- }
50
-
51
- .jarallax {
52
- position: relative;
53
- z-index: 0;
54
- background-color: #000000;
55
- min-height: 100vh;
56
- width: 100%;
57
- display: flex;
58
- justify-content: center;
59
- align-items: center;
60
- }
61
-
62
- .jarallax-img {
63
- position: absolute;
64
- object-fit: cover;
65
- top: 0;
66
- left: 0;
67
- width: 100%;
68
- height: 100%;
69
- z-index: -1;
70
- }
71
-
72
- .login-container {
73
- background: var(--card-bg);
74
- padding: 2.5rem;
75
- border-radius: 12px;
76
- width: 100%;
77
- max-width: 450px;
78
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
79
- backdrop-filter: blur(5px);
80
- transition: transform 0.3s ease;
81
- position: relative;
82
- z-index: 1;
83
- }
84
-
85
- .login-container:hover {
86
- transform: translateY(-2px);
87
- }
88
-
89
- .login-container h2 {
90
- font-size: 1.75rem;
91
- font-weight: 600;
92
- margin-bottom: 1rem;
93
- text-align: center;
94
- color: #fff;
95
- }
96
-
97
- .description {
98
- font-size: 0.9rem;
99
- text-align: center;
100
- margin-bottom: 2rem;
101
- line-height: 1.5;
102
- color: white;
103
- }
104
-
105
- .form-label {
106
- color: var(--text-muted);
107
- font-size: 0.85rem;
108
- font-weight: 500;
109
- margin-bottom: 0.5rem;
110
- }
111
-
112
- .form-control {
113
- background-color: var(--input-bg);
114
- border: 1px solid var(--input-border);
115
- color: var(--input-text);
116
- border-radius: 6px;
117
- padding: 0.75rem;
118
- transition: all 0.2s ease;
119
- }
120
-
121
- .form-control:focus {
122
- background-color: var(--input-focus-bg);
123
- border-color: var(--primary-color);
124
- box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.2);
125
- outline: none;
126
- }
127
-
128
- .form-control::placeholder {
129
- color: var(--input-text);
130
- opacity: 0.7;
131
- }
132
-
133
- .form-control.is-invalid {
134
- border-color: var(--error-color);
135
- background-image: none;
136
- }
137
-
138
- .invalid-feedback {
139
- font-size: 0.8rem;
140
- color: var(--error-color);
141
- }
142
-
143
- .alert {
144
- background-color: var(--alert-bg);
145
- color: var(--text-muted);
146
- border: none;
147
- margin-bottom: 1rem;
148
- font-size: 0.9rem;
149
- border-radius: 6px;
150
- }
151
-
152
- .alert-success {
153
- background-color: var(--success-color);
154
- color: #fff;
155
- }
156
-
157
- .alert-danger {
158
- background-color: var(--error-color);
159
- color: #fff;
160
- }
161
-
162
- .btn-login {
163
- background: var(--primary-color);
164
- color: #fff;
165
- border: none;
166
- padding: 0.85rem;
167
- border-radius: 6px;
168
- width: 100%;
169
- font-weight: 500;
170
- transition: all 0.2s ease;
171
- position: relative;
172
- display: flex;
173
- align-items: center;
174
- justify-content: center;
175
- }
176
-
177
- .btn-login:hover:not(:disabled) {
178
- background: #4f46e5;
179
- transform: translateY(-1px);
180
- }
181
-
182
- .btn-login:disabled {
183
- opacity: 0.6;
184
- cursor: not-allowed;
185
- }
186
-
187
- .loading-spinner {
188
- display: none;
189
- border: 3px solid #f3f3f3;
190
- border-top: 3px solid var(--primary-color);
191
- border-radius: 50%;
192
- width: 20px;
193
- height: 20px;
194
- animation: spin 1s linear infinite;
195
- position: absolute;
196
- right: 10px;
197
- top: 50%;
198
- transform: translateY(-50%);
199
- }
200
-
201
- @keyframes spin {
202
- 0% {
203
- transform: translateY(-50%) rotate(0deg);
204
- }
205
-
206
- 100% {
207
- transform: translateY(-50%) rotate(360deg);
208
- }
209
- }
210
-
211
- .theme-toggle-btn {
212
- position: absolute;
213
- top: 1rem;
214
- right: 1rem;
215
- background: transparent;
216
- border: none;
217
- color: var(--text-muted);
218
- font-size: 1.2rem;
219
- cursor: pointer;
220
- transition: color 0.2s ease;
221
- }
222
-
223
- .theme-toggle-btn:hover {
224
- color: var(--primary-color);
225
- }
226
-
227
- @media (max-width: 576px) {
228
- .login-container {
229
- margin: 1.5rem;
230
- padding: 1.5rem;
231
- }
232
-
233
- .login-container h2 {
234
- font-size: 1.5rem;
235
- }
236
-
237
- .description {
238
- font-size: 0.85rem;
239
- }
240
-
241
- .btn-login {
242
- padding: 0.75rem;
243
- }
244
- }
245
-
246
- .sr-only {
247
- position: absolute;
248
- width: 1px;
249
- height: 1px;
250
- padding: 0;
251
- margin: -1px;
252
- overflow: hidden;
253
- clip: rect(0, 0, 0, 0);
254
- border: 0;
255
- }
256
- </style>
257
- </head>
258
-
259
- <body>
260
- <div class="jarallax" data-jarallax data-speed="0.2">
261
- <img class="jarallax-img" src="https://silicon.createx.studio/assets/img/landing/saas-5/hero-bg-pattern.png"
262
- alt="Background">
263
- <div class="login-container">
264
- <button class="theme-toggle-btn" onclick="toggleTheme()">
265
- <i class="fas fa-sun"></i>
266
- <span class="sr-only">Chuyển đổi giao diện</span>
267
- </button>
268
- <h2>Đăng Ký Tài Khoản</h2>
269
- <p class="description">Tạo tài khoản để bắt đầu sử dụng dịch vụ của chúng tôi</p>
270
- <div id="register-message"></div>
271
- <form id="register-form" onsubmit="event.preventDefault(); register();">
272
- <div class="mb-3">
273
- <label for="username" class="form-label">Tên người dùng</label>
274
- <input type="text" class="form-control" id="username" placeholder="Nhập tên người dùng" required>
275
- <div class="invalid-feedback" id="username-error"></div>
276
- </div>
277
- <div class="mb-3">
278
- <label for="email" class="form-label">Email</label>
279
- <input type="email" class="form-control" id="email" placeholder="Nhập email của bạn" required>
280
- <div class="invalid-feedback" id="email-error"></div>
281
- </div>
282
- <div class="mb-3">
283
- <label for="password" class="form-label">Mật khẩu</label>
284
- <input type="password" class="form-control" id="password" placeholder="Nhập mật khẩu" required>
285
- <div class="invalid-feedback" id="password-error"></div>
286
- </div>
287
- <div class="mb-3">
288
- <label for="phone" class="form-label">Số điện thoại</label>
289
- <input type="tel" class="form-control" id="phone" placeholder="+84xxxxxxxxx" pattern="\+84[0-9]{9}|^0[0-9]{9}" required>
290
- <div class="invalid-feedback" id="phone-error"></div>
291
- </div>
292
- <button type="submit" class="btn btn-login" id="registerBtn">
293
- Đăng ký ngay
294
- <span class="loading-spinner" id="register-spinner"></span>
295
- </button>
296
- </form>
297
- <p class="text-center text-muted mt-3">
298
- Bạn đã có tài khoản? <a href="/login" style="color: #6366F1;">Đăng nhập ngay</a> hoặc
299
- <a href="/" style="color: #6366F1;">Trang chủ</a>
300
- </p>
301
- </div>
302
- </div>
303
-
304
- <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
305
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jarallax/2.2.1/jarallax.min.js"></script>
306
- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.5/socket.io.min.js"></script>
307
- <script>
308
- // Theme toggle functionality
309
- function toggleTheme() {
310
- const html = document.documentElement;
311
- const currentTheme = html.getAttribute('data-theme');
312
- const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
313
- html.setAttribute('data-theme', newTheme);
314
- document.querySelector('.theme-toggle-btn i').className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
315
- localStorage.setItem('theme', newTheme);
316
- }
317
-
318
- // Load saved theme
319
- document.addEventListener('DOMContentLoaded', () => {
320
- const savedTheme = localStorage.getItem('theme') || 'dark';
321
- document.documentElement.setAttribute('data-theme', savedTheme);
322
- document.querySelector('.theme-toggle-btn i').className = savedTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
323
- });
324
-
325
- // Show message
326
- function showMessage(elementId, message, type = 'success') {
327
- const messageDiv = document.getElementById(elementId);
328
- messageDiv.innerHTML = `<div class="alert alert-${type}">${message}</div>`;
329
- setTimeout(() => messageDiv.innerHTML = '', 5000);
330
- }
331
-
332
- // Client-side validation
333
- function validateForm() {
334
- const username = document.getElementById('username');
335
- const email = document.getElementById('email');
336
- const password = document.getElementById('password');
337
- const phone = document.getElementById('phone');
338
- let isValid = true;
339
-
340
- // Reset error states
341
- [username, email, password, phone].forEach(input => {
342
- input.classList.remove('is-invalid');
343
- document.getElementById(`${input.id}-error`).textContent = '';
344
- });
345
-
346
- // Username validation
347
- if (!username.value.trim()) {
348
- username.classList.add('is-invalid');
349
- document.getElementById('username-error').textContent = 'Tên người dùng không được để trống';
350
- isValid = false;
351
- }
352
-
353
- // Email validation
354
- const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
355
- if (!emailPattern.test(email.value)) {
356
- email.classList.add('is-invalid');
357
- document.getElementById('email-error').textContent = 'Vui lòng nhập email hợp lệ';
358
- isValid = false;
359
- }
360
-
361
- // Password validation
362
- if (password.value.length < 6) {
363
- password.classList.add('is-invalid');
364
- document.getElementById('password-error').textContent = 'Mật khẩu phải có ít nhất 6 ký tự';
365
- isValid = false;
366
- }
367
-
368
- // Phone validation
369
- const phonePattern = /^\+84[0-9]{9}$|^0[0-9]{9}$/;
370
- if (!phonePattern.test(phone.value)) {
371
- phone.classList.add('is-invalid');
372
- document.getElementById('phone-error').textContent = 'Số điện thoại phải bắt đầu bằng +84 hoặc 0 và có 10 số';
373
- isValid = false;
374
- }
375
-
376
- return isValid;
377
- }
378
-
379
- // Register function
380
- async function register() {
381
- if (!validateForm()) return;
382
-
383
- const username = document.getElementById('username').value;
384
- const email = document.getElementById('email').value;
385
- const password = document.getElementById('password').value;
386
- const phone = document.getElementById('phone').value;
387
- const registerBtn = document.getElementById('registerBtn');
388
- const spinner = document.getElementById('register-spinner');
389
-
390
- // Show loading state
391
- registerBtn.disabled = true;
392
- spinner.style.display = 'block';
393
-
394
- try {
395
- const response = await fetch('/register', {
396
- method: 'POST',
397
- headers: { 'Content-Type': 'application/json' },
398
- body: JSON.stringify({ username, email, password, phone, account_type: 'limited' })
399
- });
400
- const data = await response.json();
401
-
402
- if (data.error) {
403
- showMessage('register-message', data.error, 'danger');
404
- } else {
405
- showMessage('register-message', data.message, 'success');
406
- setTimeout(() => {
407
- window.location.href = `/verify_otp?user_id=${data.user_id}`;
408
- }, 2000);
409
- }
410
- } catch (error) {
411
- showMessage('register-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger');
412
- console.error('Error:', error);
413
- } finally {
414
- registerBtn.disabled = false;
415
- spinner.style.display = 'none';
416
- }
417
- }
418
-
419
- // SocketIO connection (optional, for future real-time updates)
420
- const socket = io('https://nguyendat92929-legalmind.hf.space/', {
421
- reconnection: true,
422
- reconnectionAttempts: 5,
423
- reconnectionDelay: 1000
424
- });
425
-
426
- socket.on('connect', () => {
427
- console.log('Connected to SocketIO server');
428
- });
429
-
430
- socket.on('query_update', (data) => {
431
- console.log('Query update:', data);
432
- // Handle query updates if needed (e.g., display query count)
433
- });
434
-
435
- socket.on('disconnect', () => {
436
- console.log('Disconnected from SocketIO server');
437
- });
438
- </script>
439
- </body>
440
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi" data-theme="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description" content="Đăng ký tài khoản một cách dễ dàng và an toàn">
8
+ <title>Đăng Ký</title>
9
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css">
12
+ <style>
13
+ :root {
14
+ --primary-color: #6366F1;
15
+ --primary-dark: #4f46e5;
16
+ --primary-light: #818cf8;
17
+ --error-color: #ef4444;
18
+ --success-color: #10b981;
19
+ --text-muted: #9ca3af;
20
+ --input-text: #000000;
21
+ --border-radius: 10px;
22
+ --transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
23
+ }
24
+
25
+ [data-theme="dark"] {
26
+ --card-bg: rgba(15, 23, 42, 0.85);
27
+ --input-bg: #1e293b;
28
+ --input-border: #334155;
29
+ --input-focus-bg: #0f172a;
30
+ --alert-bg: #0f172a;
31
+ --input-text: #f1f5f9;
32
+ --text-secondary: #cbd5e1;
33
+ }
34
+
35
+ [data-theme="light"] {
36
+ --card-bg: rgba(255, 255, 255, 0.95);
37
+ --input-bg: #f8fafc;
38
+ --input-border: #e2e8f0;
39
+ --input-focus-bg: #f1f5f9;
40
+ --alert-bg: #f0f9ff;
41
+ --text-muted: #64748b;
42
+ --input-text: #000000;
43
+ --text-secondary: #475569;
44
+ }
45
+
46
+ * {
47
+ margin: 0;
48
+ padding: 0;
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ body {
53
+ background: linear-gradient(135deg, #000000 0%, #0f172a 100%);
54
+ min-height: 100vh;
55
+ font-family: 'Inter', sans-serif;
56
+ position: relative;
57
+ overflow-x: hidden;
58
+ }
59
+
60
+ body::before {
61
+ content: '';
62
+ position: fixed;
63
+ top: -50%;
64
+ right: -50%;
65
+ width: 200%;
66
+ height: 200%;
67
+ background: radial-gradient(circle, rgba(99, 102, 241, 0.1) 0%, transparent 70%);
68
+ animation: float 30s ease-in-out infinite;
69
+ z-index: 0;
70
+ }
71
+
72
+ @keyframes float {
73
+
74
+ 0%,
75
+ 100% {
76
+ transform: translate(0, 0);
77
+ }
78
+
79
+ 50% {
80
+ transform: translate(30px, 30px);
81
+ }
82
+ }
83
+
84
+ .wrapper {
85
+ position: relative;
86
+ z-index: 1;
87
+ display: flex;
88
+ justify-content: center;
89
+ align-items: center;
90
+ min-height: 100vh;
91
+ padding: 1rem;
92
+ }
93
+
94
+ .login-container {
95
+ background: var(--card-bg);
96
+ padding: 2rem 1.75rem;
97
+ border-radius: var(--border-radius);
98
+ width: 100%;
99
+ max-width: 500px;
100
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4), 0 0 1px rgba(99, 102, 241, 0.3);
101
+ backdrop-filter: blur(10px);
102
+ border: 1px solid rgba(99, 102, 241, 0.15);
103
+ transition: var(--transition);
104
+ position: relative;
105
+ }
106
+
107
+ .login-container:hover {
108
+ transform: translateY(-4px);
109
+ box-shadow: 0 25px 80px rgba(0, 0, 0, 0.5), 0 0 2px rgba(99, 102, 241, 0.4);
110
+ border-color: rgba(99, 102, 241, 0.25);
111
+ }
112
+
113
+ .login-container h2 {
114
+ font-size: 1.5rem;
115
+ font-weight: 700;
116
+ margin-bottom: 0.3rem;
117
+ text-align: center;
118
+ background: linear-gradient(135deg, #6366F1 0%, #818cf8 100%);
119
+ -webkit-background-clip: text;
120
+ -webkit-text-fill-color: transparent;
121
+ background-clip: text;
122
+ }
123
+
124
+ .description {
125
+ font-size: 0.85rem;
126
+ text-align: center;
127
+ margin-bottom: 1.75rem;
128
+ line-height: 1.5;
129
+ color: var(--text-secondary);
130
+ }
131
+
132
+ .form-label {
133
+ color: var(--text-secondary);
134
+ font-size: 0.875rem;
135
+ font-weight: 600;
136
+ margin-bottom: 0.75rem;
137
+ text-transform: uppercase;
138
+ letter-spacing: 0.5px;
139
+ }
140
+
141
+ .form-control {
142
+ background-color: var(--input-bg);
143
+ border: 2px solid var(--input-border);
144
+ color: var(--input-text);
145
+ border-radius: 8px;
146
+ padding: 0.875rem 1rem;
147
+ transition: var(--transition);
148
+ font-size: 0.95rem;
149
+ }
150
+
151
+ .form-control:focus {
152
+ background-color: var(--input-focus-bg);
153
+ border-color: var(--primary-color);
154
+ box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1), 0 0 0 2px rgba(99, 102, 241, 0.2);
155
+ outline: none;
156
+ transform: translateY(-2px);
157
+ }
158
+
159
+ .form-control::placeholder {
160
+ color: var(--text-muted);
161
+ opacity: 0.8;
162
+ }
163
+
164
+ .form-control.is-invalid {
165
+ border-color: var(--error-color);
166
+ background-image: none;
167
+ }
168
+
169
+ .form-control.is-invalid:focus {
170
+ border-color: var(--error-color);
171
+ box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1), 0 0 0 2px rgba(239, 68, 68, 0.2);
172
+ }
173
+
174
+ .invalid-feedback {
175
+ font-size: 0.8rem;
176
+ color: var(--error-color);
177
+ margin-top: 0.5rem;
178
+ display: block;
179
+ font-weight: 500;
180
+ }
181
+
182
+ .mb-3 {
183
+ margin-bottom: 1.5rem;
184
+ }
185
+
186
+ .alert {
187
+ background-color: var(--alert-bg);
188
+ color: var(--text-secondary);
189
+ border: 1px solid;
190
+ margin-bottom: 1.5rem;
191
+ font-size: 0.9rem;
192
+ border-radius: 8px;
193
+ padding: 1rem 1.25rem;
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 0.75rem;
197
+ animation: slideDown 0.3s ease-out;
198
+ }
199
+
200
+ @keyframes slideDown {
201
+ from {
202
+ opacity: 0;
203
+ transform: translateY(-10px);
204
+ }
205
+
206
+ to {
207
+ opacity: 1;
208
+ transform: translateY(0);
209
+ }
210
+ }
211
+
212
+ .alert-success {
213
+ background-color: rgba(16, 185, 129, 0.1);
214
+ color: var(--success-color);
215
+ border-color: rgba(16, 185, 129, 0.3);
216
+ }
217
+
218
+ .alert-danger {
219
+ background-color: rgba(239, 68, 68, 0.1);
220
+ color: var(--error-color);
221
+ border-color: rgba(239, 68, 68, 0.3);
222
+ }
223
+
224
+ .btn-login {
225
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
226
+ color: #fff;
227
+ border: none;
228
+ padding: 1rem;
229
+ border-radius: 8px;
230
+ width: 100%;
231
+ font-weight: 600;
232
+ transition: var(--transition);
233
+ position: relative;
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ gap: 0.5rem;
238
+ font-size: 0.95rem;
239
+ letter-spacing: 0.3px;
240
+ cursor: pointer;
241
+ margin-top: 0.5rem;
242
+ }
243
+
244
+ .btn-login:hover:not(:disabled) {
245
+ background: linear-gradient(135deg, var(--primary-dark) 0%, #4338ca 100%);
246
+ transform: translateY(-3px);
247
+ box-shadow: 0 10px 30px rgba(99, 102, 241, 0.3);
248
+ }
249
+
250
+ .btn-login:active:not(:disabled) {
251
+ transform: translateY(-1px);
252
+ }
253
+
254
+ .btn-login:disabled {
255
+ opacity: 0.7;
256
+ cursor: not-allowed;
257
+ }
258
+
259
+ .loading-spinner {
260
+ display: none;
261
+ border: 2px solid rgba(255, 255, 255, 0.2);
262
+ border-top: 2px solid #fff;
263
+ border-radius: 50%;
264
+ width: 16px;
265
+ height: 16px;
266
+ animation: spin 0.8s linear infinite;
267
+ }
268
+
269
+ @keyframes spin {
270
+ 0% {
271
+ transform: rotate(0deg);
272
+ }
273
+
274
+ 100% {
275
+ transform: rotate(360deg);
276
+ }
277
+ }
278
+
279
+ .theme-toggle-btn {
280
+ position: absolute;
281
+ top: 1.5rem;
282
+ right: 1.5rem;
283
+ background: transparent;
284
+ border: 2px solid var(--input-border);
285
+ color: var(--text-secondary);
286
+ width: 40px;
287
+ height: 40px;
288
+ border-radius: 8px;
289
+ cursor: pointer;
290
+ transition: var(--transition);
291
+ display: flex;
292
+ align-items: center;
293
+ justify-content: center;
294
+ font-size: 1.1rem;
295
+ }
296
+
297
+ .theme-toggle-btn:hover {
298
+ color: var(--primary-color);
299
+ border-color: var(--primary-color);
300
+ background: rgba(99, 102, 241, 0.1);
301
+ transform: rotate(20deg);
302
+ }
303
+
304
+ .form-footer {
305
+ text-align: center;
306
+ margin-top: 2rem;
307
+ padding-top: 1.5rem;
308
+ border-top: 1px solid rgba(99, 102, 241, 0.1);
309
+ }
310
+
311
+ .form-footer p {
312
+ color: var(--text-secondary);
313
+ font-size: 0.9rem;
314
+ margin: 0;
315
+ }
316
+
317
+ .form-footer a {
318
+ color: var(--primary-color);
319
+ text-decoration: none;
320
+ font-weight: 600;
321
+ transition: var(--transition);
322
+ display: inline-block;
323
+ }
324
+
325
+ .form-footer a:hover {
326
+ color: var(--primary-light);
327
+ transform: translateX(2px);
328
+ }
329
+
330
+ .sr-only {
331
+ position: absolute;
332
+ width: 1px;
333
+ height: 1px;
334
+ padding: 0;
335
+ margin: -1px;
336
+ overflow: hidden;
337
+ clip: rect(0, 0, 0, 0);
338
+ border: 0;
339
+ }
340
+
341
+ @media (max-width: 576px) {
342
+ .login-container {
343
+ margin: 1rem;
344
+ padding: 2rem 1.5rem;
345
+ }
346
+
347
+ .login-container h2 {
348
+ font-size: 1.5rem;
349
+ }
350
+
351
+ .description {
352
+ font-size: 0.9rem;
353
+ margin-bottom: 2rem;
354
+ }
355
+
356
+ .btn-login {
357
+ padding: 0.875rem;
358
+ font-size: 0.9rem;
359
+ }
360
+
361
+ .form-label {
362
+ font-size: 0.8rem;
363
+ }
364
+ }
365
+ </style>
366
+ </head>
367
+
368
+ <body>
369
+ <div class="wrapper">
370
+ <div class="login-container">
371
+ <button class="theme-toggle-btn" onclick="toggleTheme()" title="Chuyển đổi giao diện">
372
+ <i class="fas fa-sun"></i>
373
+ <span class="sr-only">Chuyển đổi giao diện</span>
374
+ </button>
375
+ <h2>Đăng Ký Tài Khoản</h2>
376
+ <p class="description">Tạo tài khoản để bắt đầu sử dụng dịch vụ của chúng tôi</p>
377
+ <div id="register-message"></div>
378
+ <form id="register-form" onsubmit="event.preventDefault(); register();">
379
+ <div class="row">
380
+ <div class="col">
381
+ <div class="mb-3">
382
+ <label for="username" class="form-label"><i class="fas fa-user"></i> Tên người dùng</label>
383
+ <input type="text" class="form-control" id="username"
384
+ placeholder="Nhập tên người dùng của bạn" required>
385
+ <div class="invalid-feedback" id="username-error"></div>
386
+ </div>
387
+ <div class="mb-3">
388
+ <label for="email" class="form-label"><i class="fas fa-envelope"></i> Email</label>
389
+ <input type="email" class="form-control" id="email" placeholder="[email protected]" required>
390
+ <div class="invalid-feedback" id="email-error"></div>
391
+ </div>
392
+ </div>
393
+ <div class="col">
394
+ <div class="mb-3">
395
+ <label for="password" class="form-label"><i class="fas fa-lock"></i> Mật khẩu</label>
396
+ <input type="password" class="form-control" id="password"
397
+ placeholder="Nhập mật khẩu (tối thiểu 6 ký tự)" required>
398
+ <div class="invalid-feedback" id="password-error"></div>
399
+ </div>
400
+ <div class="mb-3">
401
+ <label for="phone" class="form-label"><i class="fas fa-phone"></i> Số điện thoại</label>
402
+ <input type="tel" class="form-control" id="phone" placeholder="+84xxxxxxxxx"
403
+ pattern="\+84[0-9]{9}|^0[0-9]{9}" required>
404
+ <div class="invalid-feedback" id="phone-error"></div>
405
+ </div>
406
+ </div>
407
+ </div>
408
+
409
+
410
+ <button type="submit" class="btn btn-login" id="registerBtn">
411
+ <i class="fas fa-user-plus"></i>
412
+ Đăng ký ngay
413
+ <span class="loading-spinner" id="register-spinner"></span>
414
+ </button>
415
+ </form>
416
+ <div class="form-footer">
417
+ <p>Bạn đã có tài khoản? <a href="/login">Đăng nhập ngay</a> hoặc <a href="/">Trang chủ</a></p>
418
+ </div>
419
+ </div>
420
+ </div>
421
+
422
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
423
+ <script>
424
+ function toggleTheme() {
425
+ const html = document.documentElement;
426
+ const currentTheme = html.getAttribute('data-theme');
427
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
428
+ html.setAttribute('data-theme', newTheme);
429
+ document.querySelector('.theme-toggle-btn i').className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
430
+ localStorage.setItem('theme', newTheme);
431
+ }
432
+
433
+ document.addEventListener('DOMContentLoaded', () => {
434
+ const savedTheme = localStorage.getItem('theme') || 'dark';
435
+ document.documentElement.setAttribute('data-theme', savedTheme);
436
+ document.querySelector('.theme-toggle-btn i').className = savedTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
437
+ });
438
+
439
+ function showMessage(elementId, message, type = 'success') {
440
+ const messageDiv = document.getElementById(elementId);
441
+ const icon = type === 'success' ? '<i class="fas fa-check-circle"></i>' : '<i class="fas fa-exclamation-circle"></i>';
442
+ messageDiv.innerHTML = `<div class="alert alert-${type}">${icon} ${message}</div>`;
443
+ setTimeout(() => messageDiv.innerHTML = '', 5000);
444
+ }
445
+
446
+ function validateForm() {
447
+ const username = document.getElementById('username');
448
+ const email = document.getElementById('email');
449
+ const password = document.getElementById('password');
450
+ const phone = document.getElementById('phone');
451
+ let isValid = true;
452
+
453
+ [username, email, password, phone].forEach(input => {
454
+ input.classList.remove('is-invalid');
455
+ document.getElementById(`${input.id}-error`).textContent = '';
456
+ });
457
+
458
+ if (!username.value.trim()) {
459
+ username.classList.add('is-invalid');
460
+ document.getElementById('username-error').textContent = 'Tên người dùng không được để trống';
461
+ isValid = false;
462
+ }
463
+
464
+ const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
465
+ if (!emailPattern.test(email.value)) {
466
+ email.classList.add('is-invalid');
467
+ document.getElementById('email-error').textContent = 'Vui lòng nhập email hợp lệ';
468
+ isValid = false;
469
+ }
470
+
471
+ if (password.value.length < 6) {
472
+ password.classList.add('is-invalid');
473
+ document.getElementById('password-error').textContent = 'Mật khẩu phải có ít nhất 6 ký tự';
474
+ isValid = false;
475
+ }
476
+
477
+ const phonePattern = /^\+84[0-9]{9}$|^0[0-9]{9}$/;
478
+ if (!phonePattern.test(phone.value)) {
479
+ phone.classList.add('is-invalid');
480
+ document.getElementById('phone-error').textContent = 'Số điện thoại phải bắt đầu bằng +84 hoặc 0 và có 10 số';
481
+ isValid = false;
482
+ }
483
+
484
+ return isValid;
485
+ }
486
+
487
+ async function register() {
488
+ if (!validateForm()) return;
489
+
490
+ const username = document.getElementById('username').value;
491
+ const email = document.getElementById('email').value;
492
+ const password = document.getElementById('password').value;
493
+ const phone = document.getElementById('phone').value;
494
+ const registerBtn = document.getElementById('registerBtn');
495
+ const spinner = document.getElementById('register-spinner');
496
+
497
+ registerBtn.disabled = true;
498
+ spinner.style.display = 'block';
499
+
500
+ try {
501
+ const response = await fetch('/register', {
502
+ method: 'POST',
503
+ headers: { 'Content-Type': 'application/json' },
504
+ body: JSON.stringify({ username, email, password, phone, account_type: 'limited' })
505
+ });
506
+ const data = await response.json();
507
+
508
+ if (data.error) {
509
+ showMessage('register-message', data.error, 'danger');
510
+ } else {
511
+ showMessage('register-message', data.message, 'success');
512
+ setTimeout(() => {
513
+ window.location.href = `/verify_otp?user_id=${data.user_id}`;
514
+ }, 2000);
515
+ }
516
+ } catch (error) {
517
+ showMessage('register-message', 'Lỗi hệ thống, vui lòng thử lại', 'danger');
518
+ console.error('Error:', error);
519
+ } finally {
520
+ registerBtn.disabled = false;
521
+ spinner.style.display = 'none';
522
+ }
523
+ }
524
+ </script>
525
+ </body>
526
+
527
  </html>