create-tinny-backend 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1173 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="light">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
+ <title>Change Password | Admin</title>
7
+ <link rel="shortcut icon" href="/imgs/logo.png" type="image/x-icon">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
10
+
11
+ <style>
12
+ :root {
13
+ --bg: #f4f6fa;
14
+ --text: #1e293b;
15
+ --text-secondary: #475569;
16
+ --text-muted: #64748b;
17
+ --border: #e2e8f0;
18
+ --card-bg: #ffffff;
19
+ --card-border: #e9eef3;
20
+ --input-bg: #f8fafc;
21
+ --input-border: #e2e8f0;
22
+ --input-focus: #2563eb;
23
+ --btn-bg: #2563eb;
24
+ --btn-hover: #1d4ed8;
25
+ --btn-text: #ffffff;
26
+ --heading: #0f172a;
27
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
28
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.08);
29
+ --shadow-lg: 0 12px 40px rgba(0,0,0,0.1);
30
+ --toggle-bg: #f1f5f9;
31
+ --badge-bg: #dbeafe;
32
+ --badge-text: #1e40af;
33
+ --success-bg: #dcfce7;
34
+ --success-text: #166534;
35
+ --error-bg: #fee2e2;
36
+ --error-text: #991b1b;
37
+ --details-bg: #f9fafc;
38
+ --icon-bg: #f8fafc;
39
+ }
40
+
41
+ [data-theme="dark"] {
42
+ --bg: #1a1a1a;
43
+ --text: #d4d4d4;
44
+ --text-secondary: #a0a0a0;
45
+ --text-muted: #808080;
46
+ --border: #3a3a3a;
47
+ --card-bg: #2a2a2a;
48
+ --card-border: #3a3a3a;
49
+ --input-bg: #1e1e1e;
50
+ --input-border: #3a3a3a;
51
+ --input-focus: #60a5fa;
52
+ --btn-bg: #3b82f6;
53
+ --btn-hover: #2563eb;
54
+ --btn-text: #ffffff;
55
+ --heading: #e0e0e0;
56
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
57
+ --shadow-md: 0 4px 12px rgba(0,0,0,0.4);
58
+ --shadow-lg: 0 12px 40px rgba(0,0,0,0.5);
59
+ --toggle-bg: #333333;
60
+ --badge-bg: #2a2a2a;
61
+ --badge-text: #a0a0a0;
62
+ --success-bg: #1a3a2a;
63
+ --success-text: #48bb78;
64
+ --error-bg: #3a1a1a;
65
+ --error-text: #fc8181;
66
+ --details-bg: #2a2a2a;
67
+ --icon-bg: #222222;
68
+ }
69
+
70
+ * {
71
+ margin: 0;
72
+ padding: 0;
73
+ box-sizing: border-box;
74
+ }
75
+
76
+ body {
77
+ background: var(--bg);
78
+ font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
79
+ min-height: 100vh;
80
+ display: flex;
81
+ justify-content: center;
82
+ align-items: center;
83
+ padding: 2rem;
84
+ color: var(--text);
85
+ transition: background 0.3s ease, color 0.3s ease;
86
+ position: relative;
87
+ }
88
+
89
+ /* Background decoration */
90
+ .bg-decoration {
91
+ position: fixed;
92
+ inset: 0;
93
+ pointer-events: none;
94
+ overflow: hidden;
95
+ z-index: 0;
96
+ }
97
+
98
+ .bg-decoration .circle {
99
+ position: absolute;
100
+ border-radius: 50%;
101
+ background: radial-gradient(circle, rgba(37, 99, 235, 0.05) 0%, transparent 70%);
102
+ }
103
+
104
+ .bg-decoration .circle:nth-child(1) {
105
+ width: 400px;
106
+ height: 400px;
107
+ top: -100px;
108
+ right: -100px;
109
+ }
110
+
111
+ .bg-decoration .circle:nth-child(2) {
112
+ width: 300px;
113
+ height: 300px;
114
+ bottom: -50px;
115
+ left: -50px;
116
+ }
117
+
118
+ .container {
119
+ width: 100%;
120
+ max-width: 480px;
121
+ animation: fadeIn 0.6s ease;
122
+ position: relative;
123
+ z-index: 1;
124
+ }
125
+
126
+ @keyframes fadeIn {
127
+ from {
128
+ opacity: 0;
129
+ transform: translateY(20px);
130
+ }
131
+ to {
132
+ opacity: 1;
133
+ transform: translateY(0);
134
+ }
135
+ }
136
+
137
+ /* Card */
138
+ .password-card {
139
+ background: var(--card-bg);
140
+ border: 1px solid var(--card-border);
141
+ border-radius: 1.3rem;
142
+ padding: 2.5rem 2rem;
143
+ box-shadow: var(--shadow-lg);
144
+ transition: all 0.3s ease;
145
+ }
146
+
147
+ .password-card:hover {
148
+ border-color: var(--input-focus);
149
+ box-shadow: var(--shadow-lg), 0 0 0 1px var(--input-focus);
150
+ }
151
+
152
+ /* Header with theme toggle */
153
+ .card-header {
154
+ display: flex;
155
+ justify-content: space-between;
156
+ align-items: center;
157
+ margin-bottom: 1.5rem;
158
+ }
159
+
160
+ .theme-toggle {
161
+ background: var(--toggle-bg);
162
+ border: 1px solid var(--border);
163
+ color: var(--text);
164
+ width: 36px;
165
+ height: 36px;
166
+ border-radius: 0.6rem;
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: center;
170
+ cursor: pointer;
171
+ font-size: 1rem;
172
+ transition: all 0.2s ease;
173
+ }
174
+
175
+ .theme-toggle:hover {
176
+ background: var(--input-focus);
177
+ color: white;
178
+ border-color: var(--input-focus);
179
+ }
180
+
181
+ /* Icon */
182
+ .icon-wrapper {
183
+ width: 80px;
184
+ height: 80px;
185
+ margin: 0 auto 1.2rem;
186
+ border-radius: 50%;
187
+ background: var(--icon-bg);
188
+ border: 2px solid var(--border);
189
+ display: flex;
190
+ align-items: center;
191
+ justify-content: center;
192
+ transition: all 0.3s ease;
193
+ }
194
+
195
+ .icon-wrapper:hover {
196
+ border-color: var(--input-focus);
197
+ box-shadow: 0 0 30px rgba(37, 99, 235, 0.1);
198
+ }
199
+
200
+ .icon-wrapper i {
201
+ font-size: 2.5rem;
202
+ color: var(--input-focus);
203
+ }
204
+
205
+ .title {
206
+ text-align: center;
207
+ font-size: 1.8rem;
208
+ font-weight: 700;
209
+ color: var(--heading);
210
+ margin-bottom: 0.3rem;
211
+ }
212
+
213
+ .subtitle {
214
+ text-align: center;
215
+ color: var(--text-secondary);
216
+ font-size: 0.9rem;
217
+ margin-bottom: 2rem;
218
+ }
219
+
220
+ .subtitle i {
221
+ color: var(--input-focus);
222
+ margin-right: 0.3rem;
223
+ }
224
+
225
+ /* Steps indicator */
226
+ .steps {
227
+ display: flex;
228
+ justify-content: center;
229
+ align-items: center;
230
+ gap: 0.5rem;
231
+ margin-bottom: 2rem;
232
+ }
233
+
234
+ .step {
235
+ display: flex;
236
+ align-items: center;
237
+ gap: 0.5rem;
238
+ font-size: 0.75rem;
239
+ color: var(--text-muted);
240
+ font-weight: 500;
241
+ }
242
+
243
+ .step .step-number {
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ width: 24px;
248
+ height: 24px;
249
+ border-radius: 50%;
250
+ background: var(--toggle-bg);
251
+ border: 1px solid var(--border);
252
+ font-size: 0.7rem;
253
+ font-weight: 700;
254
+ transition: all 0.3s ease;
255
+ }
256
+
257
+ .step.active .step-number {
258
+ background: var(--input-focus);
259
+ color: white;
260
+ border-color: var(--input-focus);
261
+ }
262
+
263
+ .step.active {
264
+ color: var(--text);
265
+ }
266
+
267
+ .step-line {
268
+ width: 30px;
269
+ height: 2px;
270
+ background: var(--border);
271
+ }
272
+
273
+ .step-line.active {
274
+ background: var(--input-focus);
275
+ }
276
+
277
+ /* Form */
278
+ .form-group {
279
+ margin-bottom: 1.2rem;
280
+ }
281
+
282
+ .form-group label {
283
+ display: block;
284
+ margin-bottom: 0.5rem;
285
+ font-size: 0.85rem;
286
+ font-weight: 600;
287
+ color: var(--text-secondary);
288
+ letter-spacing: 0.3px;
289
+ }
290
+
291
+ .form-group label i {
292
+ margin-right: 0.5rem;
293
+ color: var(--input-focus);
294
+ font-size: 0.8rem;
295
+ }
296
+
297
+ .input-wrapper {
298
+ position: relative;
299
+ }
300
+
301
+ .input-wrapper .input-icon {
302
+ position: absolute;
303
+ left: 1rem;
304
+ top: 50%;
305
+ transform: translateY(-50%);
306
+ color: var(--text-muted);
307
+ font-size: 0.9rem;
308
+ transition: color 0.3s ease;
309
+ pointer-events: none;
310
+ }
311
+
312
+ .input-wrapper input {
313
+ width: 100%;
314
+ padding: 0.9rem 1rem 0.9rem 2.8rem;
315
+ background: var(--input-bg);
316
+ color: var(--text);
317
+ border: 1px solid var(--input-border);
318
+ border-radius: 0.8rem;
319
+ font-size: 0.95rem;
320
+ font-family: 'Inter', sans-serif;
321
+ transition: all 0.3s ease;
322
+ }
323
+
324
+ .input-wrapper input::placeholder {
325
+ color: var(--text-muted);
326
+ }
327
+
328
+ .input-wrapper input:focus {
329
+ outline: none;
330
+ border-color: var(--input-focus);
331
+ background: var(--card-bg);
332
+ box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1);
333
+ }
334
+
335
+ .input-wrapper input:focus ~ .input-icon {
336
+ color: var(--input-focus);
337
+ }
338
+
339
+ .input-wrapper input:disabled {
340
+ opacity: 0.5;
341
+ cursor: not-allowed;
342
+ }
343
+
344
+ .password-toggle {
345
+ position: absolute;
346
+ right: 1rem;
347
+ top: 50%;
348
+ transform: translateY(-50%);
349
+ background: none;
350
+ border: none;
351
+ color: var(--text-muted);
352
+ cursor: pointer;
353
+ font-size: 0.9rem;
354
+ transition: color 0.3s ease;
355
+ padding: 0.2rem;
356
+ }
357
+
358
+ .password-toggle:hover {
359
+ color: var(--text);
360
+ }
361
+
362
+ /* Password strength indicator */
363
+ .password-strength {
364
+ margin-top: 0.5rem;
365
+ display: flex;
366
+ gap: 0.3rem;
367
+ align-items: center;
368
+ }
369
+
370
+ .strength-bar {
371
+ flex: 1;
372
+ height: 4px;
373
+ border-radius: 2px;
374
+ background: var(--border);
375
+ transition: background 0.3s ease;
376
+ }
377
+
378
+ .strength-bar.weak { background: #fc8181; }
379
+ .strength-bar.medium { background: #ecc94b; }
380
+ .strength-bar.strong { background: #48bb78; }
381
+
382
+ .strength-text {
383
+ font-size: 0.7rem;
384
+ color: var(--text-muted);
385
+ min-width: 50px;
386
+ text-align: right;
387
+ }
388
+
389
+ /* Buttons */
390
+ .btn {
391
+ width: 100%;
392
+ padding: 0.9rem;
393
+ border: none;
394
+ border-radius: 0.8rem;
395
+ font-size: 0.95rem;
396
+ font-weight: 600;
397
+ font-family: 'Inter', sans-serif;
398
+ cursor: pointer;
399
+ transition: all 0.3s ease;
400
+ display: flex;
401
+ align-items: center;
402
+ justify-content: center;
403
+ gap: 0.6rem;
404
+ margin-top: 0.5rem;
405
+ }
406
+
407
+ .btn-primary {
408
+ background: var(--btn-bg);
409
+ color: var(--btn-text);
410
+ }
411
+
412
+ .btn-primary:hover:not(:disabled) {
413
+ background: var(--btn-hover);
414
+ transform: translateY(-2px);
415
+ box-shadow: 0 8px 24px rgba(37, 99, 235, 0.3);
416
+ }
417
+
418
+ .btn-primary:active:not(:disabled) {
419
+ transform: translateY(0);
420
+ }
421
+
422
+ .btn-primary:disabled {
423
+ opacity: 0.6;
424
+ cursor: not-allowed;
425
+ transform: none;
426
+ }
427
+
428
+ .btn-secondary {
429
+ background: var(--toggle-bg);
430
+ color: var(--text);
431
+ border: 1px solid var(--border);
432
+ }
433
+
434
+ .btn-secondary:hover {
435
+ background: var(--border);
436
+ transform: translateY(-2px);
437
+ }
438
+
439
+ .btn i {
440
+ font-size: 1rem;
441
+ }
442
+
443
+ /* Message */
444
+ .message {
445
+ margin-top: 1rem;
446
+ padding: 0.8rem 1rem;
447
+ border-radius: 0.8rem;
448
+ text-align: center;
449
+ font-size: 0.85rem;
450
+ display: none;
451
+ animation: slideDown 0.3s ease;
452
+ }
453
+
454
+ .message.show {
455
+ display: block;
456
+ }
457
+
458
+ @keyframes slideDown {
459
+ from {
460
+ opacity: 0;
461
+ transform: translateY(-10px);
462
+ }
463
+ to {
464
+ opacity: 1;
465
+ transform: translateY(0);
466
+ }
467
+ }
468
+
469
+ .message.success {
470
+ background: var(--success-bg);
471
+ color: var(--success-text);
472
+ border: 1px solid var(--success-text);
473
+ }
474
+
475
+ .message.success i {
476
+ margin-right: 0.5rem;
477
+ }
478
+
479
+ .message.error {
480
+ background: var(--error-bg);
481
+ color: var(--error-text);
482
+ border: 1px solid var(--error-text);
483
+ }
484
+
485
+ .message.error i {
486
+ margin-right: 0.5rem;
487
+ }
488
+
489
+ .message.info {
490
+ background: var(--badge-bg);
491
+ color: var(--badge-text);
492
+ border: 1px solid var(--badge-text);
493
+ }
494
+
495
+ .message.info i {
496
+ margin-right: 0.5rem;
497
+ }
498
+
499
+ /* Spinner */
500
+ .spinner {
501
+ display: inline-block;
502
+ width: 16px;
503
+ height: 16px;
504
+ border: 2px solid rgba(255, 255, 255, 0.3);
505
+ border-top-color: #fff;
506
+ border-radius: 50%;
507
+ animation: spin 0.6s linear infinite;
508
+ }
509
+
510
+ @keyframes spin {
511
+ to {
512
+ transform: rotate(360deg);
513
+ }
514
+ }
515
+
516
+ /* Back link */
517
+ .back-link {
518
+ display: flex;
519
+ align-items: center;
520
+ justify-content: center;
521
+ gap: 0.5rem;
522
+ margin-top: 1.5rem;
523
+ color: var(--text-muted);
524
+ font-size: 0.85rem;
525
+ text-decoration: none;
526
+ transition: color 0.3s ease;
527
+ }
528
+
529
+ .back-link:hover {
530
+ color: var(--input-focus);
531
+ }
532
+
533
+ .back-link i {
534
+ font-size: 0.8rem;
535
+ }
536
+
537
+ /* Username display */
538
+ .username-display {
539
+ text-align: center;
540
+ padding: 0.5rem 1rem;
541
+ background: var(--toggle-bg);
542
+ border-radius: 2rem;
543
+ display: inline-block;
544
+ margin: 0 auto 1.5rem;
545
+ font-size: 0.85rem;
546
+ color: var(--text-secondary);
547
+ border: 1px solid var(--border);
548
+ }
549
+
550
+ .username-display strong {
551
+ color: var(--text);
552
+ }
553
+
554
+ .username-display i {
555
+ color: var(--input-focus);
556
+ margin-right: 0.3rem;
557
+ }
558
+
559
+ .text-center {
560
+ text-align: center;
561
+ }
562
+
563
+ /* Shake animation */
564
+ @keyframes shake {
565
+ 0%, 100% { transform: translateX(0); }
566
+ 10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
567
+ 20%, 40%, 60%, 80% { transform: translateX(5px); }
568
+ }
569
+
570
+ .shake {
571
+ animation: shake 0.5s ease;
572
+ }
573
+
574
+ /* Responsive */
575
+ @media (max-width: 480px) {
576
+ body {
577
+ padding: 1rem;
578
+ }
579
+
580
+ .password-card {
581
+ padding: 1.8rem 1.2rem;
582
+ }
583
+
584
+ .title {
585
+ font-size: 1.5rem;
586
+ }
587
+
588
+ .icon-wrapper {
589
+ width: 64px;
590
+ height: 64px;
591
+ }
592
+
593
+ .icon-wrapper i {
594
+ font-size: 2rem;
595
+ }
596
+
597
+ .input-wrapper input {
598
+ padding: 0.8rem 1rem 0.8rem 2.5rem;
599
+ font-size: 0.9rem;
600
+ }
601
+
602
+ .steps {
603
+ flex-wrap: wrap;
604
+ gap: 0.3rem;
605
+ }
606
+
607
+ .step {
608
+ font-size: 0.7rem;
609
+ }
610
+
611
+ .step-line {
612
+ width: 15px;
613
+ }
614
+ }
615
+ </style>
616
+ </head>
617
+ <body>
618
+
619
+ <!-- Background Decoration -->
620
+ <div class="bg-decoration">
621
+ <div class="circle"></div>
622
+ <div class="circle"></div>
623
+ </div>
624
+
625
+ <div class="container">
626
+ <div class="password-card">
627
+
628
+ <!-- Header with Theme Toggle -->
629
+ <div class="card-header">
630
+ <div></div>
631
+ <button class="theme-toggle" id="themeToggle" title="Toggle dark/light mode">
632
+ <i class="fas fa-moon"></i>
633
+ </button>
634
+ </div>
635
+
636
+ <!-- Icon -->
637
+ <div class="icon-wrapper">
638
+ <i class="fas fa-key"></i>
639
+ </div>
640
+
641
+ <!-- Title -->
642
+ <h1 class="title">Change Password</h1>
643
+ <p class="subtitle">
644
+ <i class="fas fa-shield-alt"></i>
645
+ Secure your account with a new password
646
+ </p>
647
+
648
+ <!-- Steps Indicator -->
649
+ <div class="steps" id="steps">
650
+ <div class="step active" id="step1">
651
+ <span class="step-number">1</span>
652
+ <span>Verify</span>
653
+ </div>
654
+ <div class="step-line active" id="line1"></div>
655
+ <div class="step" id="step2">
656
+ <span class="step-number">2</span>
657
+ <span>Change</span>
658
+ </div>
659
+ <div class="step-line" id="line2"></div>
660
+ <div class="step" id="step3">
661
+ <span class="step-number">3</span>
662
+ <span>Done</span>
663
+ </div>
664
+ </div>
665
+
666
+ <!-- Step 1: Username Verification -->
667
+ <div id="step1-content">
668
+ <form id="verifyForm">
669
+ <div class="form-group">
670
+ <label for="username">
671
+ <i class="fas fa-user"></i> Username
672
+ </label>
673
+ <div class="input-wrapper">
674
+ <input
675
+ type="text"
676
+ id="username"
677
+ placeholder="Enter your username"
678
+ required
679
+ autocomplete="username"
680
+ >
681
+ <i class="fas fa-user input-icon"></i>
682
+ </div>
683
+ </div>
684
+
685
+ <button type="submit" class="btn btn-primary" id="verifyBtn">
686
+ <i class="fas fa-search"></i>
687
+ <span>Verify User</span>
688
+ </button>
689
+
690
+ <div id="verifyMessage" class="message"></div>
691
+ </form>
692
+ </div>
693
+
694
+ <!-- Step 2: Password Change (hidden initially) -->
695
+ <div id="step2-content" style="display: none;">
696
+ <div class="text-center">
697
+ <div class="username-display">
698
+ <i class="fas fa-user-check"></i>
699
+ User: <strong id="displayUsername">—</strong>
700
+ </div>
701
+ </div>
702
+
703
+ <form id="changeForm">
704
+ <div class="form-group">
705
+ <label for="oldPassword">
706
+ <i class="fas fa-lock"></i> Current Password
707
+ </label>
708
+ <div class="input-wrapper">
709
+ <input
710
+ type="password"
711
+ id="oldPassword"
712
+ placeholder="Enter your current password"
713
+ required
714
+ autocomplete="current-password"
715
+ >
716
+ <i class="fas fa-key input-icon"></i>
717
+ <button type="button" class="password-toggle" id="toggleOldPassword" aria-label="Toggle password visibility">
718
+ <i class="fas fa-eye"></i>
719
+ </button>
720
+ </div>
721
+ </div>
722
+
723
+ <div class="form-group">
724
+ <label for="newPassword">
725
+ <i class="fas fa-lock"></i> New Password
726
+ </label>
727
+ <div class="input-wrapper">
728
+ <input
729
+ type="password"
730
+ id="newPassword"
731
+ placeholder="Enter your new password (min 8 chars)"
732
+ required
733
+ autocomplete="new-password"
734
+ minlength="8"
735
+ >
736
+ <i class="fas fa-key input-icon"></i>
737
+ <button type="button" class="password-toggle" id="toggleNewPassword" aria-label="Toggle password visibility">
738
+ <i class="fas fa-eye"></i>
739
+ </button>
740
+ </div>
741
+ <div class="password-strength" id="strengthIndicator">
742
+ <div class="strength-bar" id="strength1"></div>
743
+ <div class="strength-bar" id="strength2"></div>
744
+ <div class="strength-bar" id="strength3"></div>
745
+ <span class="strength-text" id="strengthText">Weak</span>
746
+ </div>
747
+ </div>
748
+
749
+ <div class="form-group">
750
+ <label for="confirmPassword">
751
+ <i class="fas fa-check-circle"></i> Confirm New Password
752
+ </label>
753
+ <div class="input-wrapper">
754
+ <input
755
+ type="password"
756
+ id="confirmPassword"
757
+ placeholder="Confirm your new password"
758
+ required
759
+ autocomplete="new-password"
760
+ >
761
+ <i class="fas fa-check input-icon"></i>
762
+ <button type="button" class="password-toggle" id="toggleConfirmPassword" aria-label="Toggle password visibility">
763
+ <i class="fas fa-eye"></i>
764
+ </button>
765
+ </div>
766
+ </div>
767
+
768
+ <div style="display: flex; gap: 0.8rem; margin-top: 0.5rem;">
769
+ <button type="button" class="btn btn-secondary" id="backBtn" style="flex: 0.4;">
770
+ <i class="fas fa-arrow-left"></i> Back
771
+ </button>
772
+ <button type="submit" class="btn btn-primary" id="changeBtn" style="flex: 0.6;">
773
+ <i class="fas fa-save"></i>
774
+ <span>Change Password</span>
775
+ </button>
776
+ </div>
777
+
778
+ <div id="changeMessage" class="message"></div>
779
+ </form>
780
+ </div>
781
+
782
+ <!-- Step 3: Success (hidden initially) -->
783
+ <div id="step3-content" style="display: none;">
784
+ <div class="text-center">
785
+ <div style="font-size: 4rem; color: #48bb78; margin-bottom: 1rem;">
786
+ <i class="fas fa-check-circle"></i>
787
+ </div>
788
+ <h2 style="color: var(--heading); margin-bottom: 0.5rem;">Password Changed!</h2>
789
+ <p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
790
+ Your password has been updated successfully.
791
+ You will be redirected to the login page in a few seconds.
792
+ </p>
793
+ <div class="spinner" style="margin: 0 auto; border-color: var(--border); border-top-color: var(--input-focus);"></div>
794
+ <p style="color: var(--text-muted); font-size: 0.8rem; margin-top: 1rem;">
795
+ Redirecting in <span id="countdown">5</span> seconds...
796
+ </p>
797
+ <a href="/" class="btn btn-primary" style="margin-top: 1rem; text-decoration: none;">
798
+ <i class="fas fa-sign-in-alt"></i> Go to Login
799
+ </a>
800
+ </div>
801
+ </div>
802
+
803
+ <!-- Back to Dashboard -->
804
+ <a href="/admin/" class="back-link">
805
+ <i class="fas fa-arrow-left"></i>
806
+ Back to Dashboard
807
+ </a>
808
+
809
+ </div>
810
+ </div>
811
+
812
+ <script>
813
+ (function() {
814
+ // ===== THEME TOGGLE =====
815
+ const html = document.documentElement;
816
+ const themeToggle = document.getElementById('themeToggle');
817
+ const savedTheme = localStorage.getItem('tinnybackend-theme');
818
+
819
+ if (savedTheme) {
820
+ html.setAttribute('data-theme', savedTheme);
821
+ } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
822
+ html.setAttribute('data-theme', 'dark');
823
+ }
824
+
825
+ function updateThemeUI(theme) {
826
+ const icon = themeToggle.querySelector('i');
827
+ if (theme === 'dark') {
828
+ icon.classList.remove('fa-moon');
829
+ icon.classList.add('fa-sun');
830
+ } else {
831
+ icon.classList.remove('fa-sun');
832
+ icon.classList.add('fa-moon');
833
+ }
834
+ }
835
+
836
+ updateThemeUI(html.getAttribute('data-theme'));
837
+
838
+ themeToggle.addEventListener('click', () => {
839
+ const currentTheme = html.getAttribute('data-theme');
840
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
841
+ html.setAttribute('data-theme', newTheme);
842
+ localStorage.setItem('tinnybackend-theme', newTheme);
843
+ updateThemeUI(newTheme);
844
+ });
845
+
846
+ if (window.matchMedia) {
847
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
848
+ if (!localStorage.getItem('tinnybackend-theme')) {
849
+ const newTheme = e.matches ? 'dark' : 'light';
850
+ html.setAttribute('data-theme', newTheme);
851
+ updateThemeUI(newTheme);
852
+ }
853
+ });
854
+ }
855
+
856
+ // ===== PASSWORD CHANGE LOGIC =====
857
+ const verifyForm = document.getElementById('verifyForm');
858
+ const changeForm = document.getElementById('changeForm');
859
+ const usernameInput = document.getElementById('username');
860
+ const oldPasswordInput = document.getElementById('oldPassword');
861
+ const newPasswordInput = document.getElementById('newPassword');
862
+ const confirmPasswordInput = document.getElementById('confirmPassword');
863
+ const verifyBtn = document.getElementById('verifyBtn');
864
+ const changeBtn = document.getElementById('changeBtn');
865
+ const backBtn = document.getElementById('backBtn');
866
+ const verifyMessage = document.getElementById('verifyMessage');
867
+ const changeMessage = document.getElementById('changeMessage');
868
+ const displayUsername = document.getElementById('displayUsername');
869
+ const step1 = document.getElementById('step1');
870
+ const step2 = document.getElementById('step2');
871
+ const step3 = document.getElementById('step3');
872
+ const line1 = document.getElementById('line1');
873
+ const line2 = document.getElementById('line2');
874
+ const step1Content = document.getElementById('step1-content');
875
+ const step2Content = document.getElementById('step2-content');
876
+ const step3Content = document.getElementById('step3-content');
877
+ const countdown = document.getElementById('countdown');
878
+
879
+ let verifiedUsername = '';
880
+
881
+ // Password visibility toggles
882
+ document.getElementById('toggleOldPassword').addEventListener('click', function() {
883
+ togglePasswordVisibility(oldPasswordInput, this);
884
+ });
885
+
886
+ document.getElementById('toggleNewPassword').addEventListener('click', function() {
887
+ togglePasswordVisibility(newPasswordInput, this);
888
+ });
889
+
890
+ document.getElementById('toggleConfirmPassword').addEventListener('click', function() {
891
+ togglePasswordVisibility(confirmPasswordInput, this);
892
+ });
893
+
894
+ function togglePasswordVisibility(input, button) {
895
+ const type = input.getAttribute('type') === 'password' ? 'text' : 'password';
896
+ input.setAttribute('type', type);
897
+ button.querySelector('i').classList.toggle('fa-eye');
898
+ button.querySelector('i').classList.toggle('fa-eye-slash');
899
+ }
900
+
901
+ // Password strength indicator
902
+ newPasswordInput.addEventListener('input', function() {
903
+ const password = this.value;
904
+ const strength = checkPasswordStrength(password);
905
+ updateStrengthIndicator(strength);
906
+ });
907
+
908
+ function checkPasswordStrength(password) {
909
+ let score = 0;
910
+ if (password.length >= 8) score++;
911
+ if (password.length >= 12) score++;
912
+ if (/[a-z]/.test(password) && /[A-Z]/.test(password)) score++;
913
+ if (/\d/.test(password)) score++;
914
+ if (/[!@#$%^&*(),.?":{}|<>]/.test(password)) score++;
915
+ return score;
916
+ }
917
+
918
+ function updateStrengthIndicator(score) {
919
+ const bars = [
920
+ document.getElementById('strength1'),
921
+ document.getElementById('strength2'),
922
+ document.getElementById('strength3')
923
+ ];
924
+ const text = document.getElementById('strengthText');
925
+
926
+ // Reset bars
927
+ bars.forEach(bar => bar.className = 'strength-bar');
928
+
929
+ if (score === 0) {
930
+ text.textContent = 'Weak';
931
+ return;
932
+ }
933
+
934
+ if (score <= 2) {
935
+ bars[0].classList.add('weak');
936
+ text.textContent = 'Weak';
937
+ } else if (score <= 3) {
938
+ bars[0].classList.add('medium');
939
+ bars[1].classList.add('medium');
940
+ text.textContent = 'Medium';
941
+ } else {
942
+ bars[0].classList.add('strong');
943
+ bars[1].classList.add('strong');
944
+ bars[2].classList.add('strong');
945
+ text.textContent = 'Strong';
946
+ }
947
+ }
948
+
949
+ // Show message function
950
+ function showMessage(element, text, type = 'error') {
951
+ element.textContent = '';
952
+ element.className = 'message show ' + type;
953
+
954
+ const icon = document.createElement('i');
955
+ if (type === 'success') icon.className = 'fas fa-check-circle';
956
+ else if (type === 'info') icon.className = 'fas fa-info-circle';
957
+ else icon.className = 'fas fa-exclamation-circle';
958
+
959
+ element.appendChild(icon);
960
+ element.appendChild(document.createTextNode(' ' + text));
961
+
962
+ if (type === 'error') {
963
+ element.classList.add('shake');
964
+ setTimeout(() => {
965
+ element.classList.remove('shake');
966
+ }, 500);
967
+ }
968
+ }
969
+
970
+ function hideMessage(element) {
971
+ element.className = 'message';
972
+ element.textContent = '';
973
+ }
974
+
975
+ function setLoading(button, isLoading) {
976
+ if (isLoading) {
977
+ button.disabled = true;
978
+ button.innerHTML = '<span class="spinner"></span> Processing...';
979
+ } else {
980
+ button.disabled = false;
981
+ const originalText = button.querySelector('span')?.textContent || 'Submit';
982
+ const originalIcon = button.querySelector('i')?.className || '';
983
+ button.innerHTML = `<i class="${originalIcon}"></i> <span>${originalText}</span>`;
984
+ }
985
+ }
986
+
987
+ // Update steps
988
+ function updateStep(step) {
989
+ [step1, step2, step3].forEach((s, index) => {
990
+ s.classList.toggle('active', index < step);
991
+ });
992
+ [line1, line2].forEach((line, index) => {
993
+ if (line) line.classList.toggle('active', index < step - 1);
994
+ });
995
+ }
996
+
997
+ // Step 1: Verify username
998
+ verifyForm.addEventListener('submit', async (e) => {
999
+ e.preventDefault();
1000
+ hideMessage(verifyMessage);
1001
+
1002
+ const username = usernameInput.value.trim();
1003
+
1004
+ if (!username) {
1005
+ showMessage(verifyMessage, 'Please enter your username', 'error');
1006
+ usernameInput.focus();
1007
+ return;
1008
+ }
1009
+
1010
+ setLoading(verifyBtn, true);
1011
+
1012
+ try {
1013
+ const response = await fetch(`/api/passwd/${username}`, {
1014
+ method: 'GET',
1015
+ headers: {
1016
+ 'Content-Type': 'application/json',
1017
+ },
1018
+ credentials: 'same-origin'
1019
+ });
1020
+
1021
+ const data = await response.json();
1022
+
1023
+ if (response.ok && data.status === 'found') {
1024
+ verifiedUsername = data.user;
1025
+ showMessage(verifyMessage, `User ${data.user} verified!`, 'success');
1026
+
1027
+ // Update UI for step 2
1028
+ displayUsername.textContent = data.user;
1029
+ step1Content.style.display = 'none';
1030
+ step2Content.style.display = 'block';
1031
+ updateStep(2);
1032
+
1033
+ // Focus on old password
1034
+ oldPasswordInput.focus();
1035
+ } else {
1036
+ showMessage(verifyMessage, 'User not found. Please check the username.', 'error');
1037
+ usernameInput.value = '';
1038
+ usernameInput.focus();
1039
+ }
1040
+ } catch (error) {
1041
+ console.error('Verification error:', error);
1042
+ showMessage(verifyMessage, 'Network error. Please try again.', 'error');
1043
+ } finally {
1044
+ setLoading(verifyBtn, false);
1045
+ }
1046
+ });
1047
+
1048
+ // Step 2: Change password
1049
+ changeForm.addEventListener('submit', async (e) => {
1050
+ e.preventDefault();
1051
+ hideMessage(changeMessage);
1052
+
1053
+ const oldPassword = oldPasswordInput.value.trim();
1054
+ const newPassword = newPasswordInput.value.trim();
1055
+ const confirmPassword = confirmPasswordInput.value.trim();
1056
+
1057
+ if (!oldPassword || !newPassword || !confirmPassword) {
1058
+ showMessage(changeMessage, 'Please fill in all fields', 'error');
1059
+ return;
1060
+ }
1061
+
1062
+ if (newPassword.length < 8) {
1063
+ showMessage(changeMessage, 'Password must be at least 8 characters long', 'error');
1064
+ newPasswordInput.focus();
1065
+ return;
1066
+ }
1067
+
1068
+ if (newPassword !== confirmPassword) {
1069
+ showMessage(changeMessage, 'Passwords do not match', 'error');
1070
+ confirmPasswordInput.focus();
1071
+ return;
1072
+ }
1073
+
1074
+ if (oldPassword === newPassword) {
1075
+ showMessage(changeMessage, 'New password must be different from current password', 'error');
1076
+ newPasswordInput.focus();
1077
+ return;
1078
+ }
1079
+
1080
+ setLoading(changeBtn, true);
1081
+
1082
+ try {
1083
+ const response = await fetch('/api/passwd/change', {
1084
+ method: 'POST',
1085
+ headers: {
1086
+ 'Content-Type': 'application/json',
1087
+ },
1088
+ body: JSON.stringify({
1089
+ "userName": verifiedUsername,
1090
+ oldpwd: oldPassword,
1091
+ newpwd: newPassword
1092
+ }),
1093
+ credentials: 'same-origin'
1094
+ });
1095
+
1096
+ const data = await response.json();
1097
+
1098
+ if (response.ok && data.status === 'password changed') {
1099
+ // Success - show step 3
1100
+ step2Content.style.display = 'none';
1101
+ step3Content.style.display = 'block';
1102
+ updateStep(3);
1103
+
1104
+ // Start countdown
1105
+ let seconds = 5;
1106
+ countdown.textContent = seconds;
1107
+ const interval = setInterval(() => {
1108
+ seconds--;
1109
+ countdown.textContent = seconds;
1110
+ if (seconds <= 0) {
1111
+ clearInterval(interval);
1112
+ window.location.href = '/';
1113
+ }
1114
+ }, 1000);
1115
+ } else if (data.status === 'password not corect') {
1116
+ showMessage(changeMessage, 'Current password is incorrect', 'error');
1117
+ oldPasswordInput.value = '';
1118
+ oldPasswordInput.focus();
1119
+ } else if (data.status === 'password must be 8 char +') {
1120
+ showMessage(changeMessage, 'Password must be at least 8 characters long', 'error');
1121
+ newPasswordInput.value = '';
1122
+ confirmPasswordInput.value = '';
1123
+ newPasswordInput.focus();
1124
+ } else {
1125
+ showMessage(changeMessage, data.message || 'Failed to change password', 'error');
1126
+ }
1127
+ } catch (error) {
1128
+ console.error('Password change error:', error);
1129
+ showMessage(changeMessage, 'Network error. Please try again.', 'error');
1130
+ } finally {
1131
+ setLoading(changeBtn, false);
1132
+ }
1133
+ });
1134
+
1135
+ // Back button
1136
+ backBtn.addEventListener('click', () => {
1137
+ hideMessage(changeMessage);
1138
+ step2Content.style.display = 'none';
1139
+ step1Content.style.display = 'block';
1140
+ updateStep(1);
1141
+ usernameInput.focus();
1142
+ });
1143
+
1144
+ // Enter key support for forms
1145
+ oldPasswordInput.addEventListener('keydown', (e) => {
1146
+ if (e.key === 'Enter') {
1147
+ e.preventDefault();
1148
+ newPasswordInput.focus();
1149
+ }
1150
+ });
1151
+
1152
+ newPasswordInput.addEventListener('keydown', (e) => {
1153
+ if (e.key === 'Enter') {
1154
+ e.preventDefault();
1155
+ confirmPasswordInput.focus();
1156
+ }
1157
+ });
1158
+
1159
+ confirmPasswordInput.addEventListener('keydown', (e) => {
1160
+ if (e.key === 'Enter') {
1161
+ e.preventDefault();
1162
+ changeForm.dispatchEvent(new Event('submit'));
1163
+ }
1164
+ });
1165
+
1166
+ // Auto-focus username on load
1167
+ usernameInput.focus();
1168
+
1169
+ })();
1170
+ </script>
1171
+
1172
+ </body>
1173
+ </html>