+16
-892
Diff
round #0
+3
-397
frontend/src/routes/OAuthConsent.svelte
+3
-397
frontend/src/routes/OAuthConsent.svelte
···
49
49
}
50
50
51
51
let loading = $state(true)
52
-
let showSpinner = $state(false)
53
-
let loadingTimer: ReturnType<typeof setTimeout> | null = null
54
52
let error = $state<string | null>(null)
55
53
let submitting = $state(false)
56
54
let consentData = $state<ConsentData | null>(null)
···
140
138
error = $_('oauth.error.genericError')
141
139
} finally {
142
140
loading = false
143
-
showSpinner = false
144
-
if (loadingTimer) {
145
-
clearTimeout(loadingTimer)
146
-
loadingTimer = null
147
-
}
148
141
}
149
142
}
150
143
···
252
245
}
253
246
254
247
$effect(() => {
255
-
loadingTimer = setTimeout(() => {
256
-
if (loading) {
257
-
showSpinner = true
258
-
}
259
-
}, 5000)
260
248
fetchConsentData()
261
-
return () => {
262
-
if (loadingTimer) {
263
-
clearTimeout(loadingTimer)
264
-
}
265
-
}
266
249
})
267
250
268
251
let scopeGroups = $derived(consentData ? groupScopesByCategory(consentData.scopes) : [])
···
297
280
298
281
<div class="consent-container">
299
282
{#if loading}
300
-
<div class="loading">
301
-
{#if showSpinner}
302
-
<div class="loading-content">
303
-
<div class="spinner"></div>
304
-
<p>{$_('common.loading')}</p>
305
-
</div>
306
-
{:else}
307
-
<p style="color: var(--text-muted); font-size: 0.875rem;">Loading consent data...</p>
308
-
{/if}
309
-
</div>
283
+
<div class="loading"></div>
310
284
{:else if error}
311
285
<div class="error-container">
312
286
<h1>{$_('oauth.error.title')}</h1>
···
421
395
</div>
422
396
423
397
<div class="actions">
424
-
<button type="button" class="deny-btn" onclick={handleDeny} disabled={submitting}>
398
+
<button type="button" class="cancel" onclick={handleDeny} disabled={submitting}>
425
399
{$_('oauth.consent.deny')}
426
400
</button>
427
-
<button type="button" class="approve-btn" onclick={submitConsent} disabled={submitting}>
401
+
<button type="button" onclick={submitConsent} disabled={submitting}>
428
402
{submitting ? $_('oauth.consent.authorizing') : $_('oauth.consent.authorize')}
429
403
</button>
430
404
</div>
···
443
417
</div>
444
418
{/if}
445
419
</div>
446
-
447
-
<style>
448
-
.consent-container {
449
-
max-width: var(--width-lg);
450
-
margin: var(--space-7) auto;
451
-
padding: var(--space-7);
452
-
}
453
-
454
-
.loading {
455
-
display: flex;
456
-
align-items: center;
457
-
justify-content: center;
458
-
min-height: 200px;
459
-
color: var(--text-secondary);
460
-
}
461
-
462
-
.loading-content {
463
-
display: flex;
464
-
flex-direction: column;
465
-
align-items: center;
466
-
gap: var(--space-4);
467
-
}
468
-
469
-
.loading-content p {
470
-
margin: 0;
471
-
color: var(--text-secondary);
472
-
}
473
-
474
-
.error-container {
475
-
text-align: center;
476
-
max-width: var(--width-sm);
477
-
margin: 0 auto;
478
-
}
479
-
480
-
.error {
481
-
padding: var(--space-3);
482
-
background: var(--error-bg);
483
-
border: 1px solid var(--error-border);
484
-
border-radius: var(--radius-md);
485
-
color: var(--error-text);
486
-
margin-bottom: var(--space-4);
487
-
}
488
-
489
-
.client-panel {
490
-
display: flex;
491
-
flex-direction: column;
492
-
gap: var(--space-5);
493
-
}
494
-
495
-
.permissions-panel {
496
-
min-width: 0;
497
-
}
498
-
499
-
.client-info {
500
-
text-align: center;
501
-
padding: var(--space-6);
502
-
background: var(--bg-secondary);
503
-
border-radius: var(--radius-xl);
504
-
}
505
-
506
-
@media (min-width: 800px) {
507
-
.client-info {
508
-
text-align: left;
509
-
}
510
-
}
511
-
512
-
.client-logo {
513
-
width: 64px;
514
-
height: 64px;
515
-
border-radius: var(--radius-xl);
516
-
margin-bottom: var(--space-4);
517
-
}
518
-
519
-
.client-info h1 {
520
-
margin: 0 0 var(--space-1) 0;
521
-
font-size: var(--text-xl);
522
-
}
523
-
524
-
.subtitle {
525
-
color: var(--text-secondary);
526
-
margin: 0;
527
-
}
528
-
529
-
.client-link {
530
-
display: inline-block;
531
-
margin-top: var(--space-2);
532
-
font-size: var(--text-sm);
533
-
color: var(--accent);
534
-
text-decoration: none;
535
-
}
536
-
537
-
.client-link:hover {
538
-
text-decoration: underline;
539
-
}
540
-
541
-
.account-info {
542
-
display: flex;
543
-
flex-direction: column;
544
-
gap: var(--space-1);
545
-
padding: var(--space-4);
546
-
background: var(--bg-secondary);
547
-
border-radius: var(--radius-xl);
548
-
margin-bottom: var(--space-6);
549
-
}
550
-
551
-
.account-info .label {
552
-
font-size: var(--text-xs);
553
-
color: var(--text-muted);
554
-
text-transform: uppercase;
555
-
letter-spacing: 0.05em;
556
-
}
557
-
558
-
.account-info .did {
559
-
font-family: var(--font-mono);
560
-
font-size: var(--text-sm);
561
-
color: var(--text-secondary);
562
-
word-break: break-all;
563
-
}
564
-
565
-
.account-info .handle {
566
-
font-size: var(--text-base);
567
-
font-weight: var(--font-medium);
568
-
color: var(--text-primary);
569
-
}
570
-
571
-
.delegation-badge {
572
-
display: inline-block;
573
-
padding: var(--space-1) var(--space-2);
574
-
background: var(--accent);
575
-
color: var(--text-inverse);
576
-
border-radius: var(--radius-md);
577
-
font-size: var(--text-xs);
578
-
font-weight: var(--font-semibold);
579
-
text-transform: uppercase;
580
-
letter-spacing: 0.05em;
581
-
margin-bottom: var(--space-3);
582
-
}
583
-
584
-
.delegation-info {
585
-
display: flex;
586
-
flex-direction: column;
587
-
gap: var(--space-2);
588
-
}
589
-
590
-
.delegation-info .info-row {
591
-
display: flex;
592
-
flex-direction: column;
593
-
gap: 2px;
594
-
}
595
-
596
-
.delegation-info .handle {
597
-
font-weight: var(--font-medium);
598
-
color: var(--text-primary);
599
-
}
600
-
601
-
.level-badge {
602
-
display: inline-block;
603
-
padding: 2px var(--space-2);
604
-
background: var(--bg-tertiary);
605
-
color: var(--text-primary);
606
-
border-radius: var(--radius-sm);
607
-
font-size: var(--text-sm);
608
-
font-weight: var(--font-medium);
609
-
}
610
-
611
-
.level-badge.level-owner {
612
-
background: var(--success-bg);
613
-
color: var(--success-text);
614
-
}
615
-
616
-
.level-badge.level-admin {
617
-
background: var(--accent);
618
-
color: var(--text-inverse);
619
-
}
620
-
621
-
.level-badge.level-editor {
622
-
background: var(--warning-bg);
623
-
color: var(--warning-text);
624
-
}
625
-
626
-
.level-badge.level-viewer {
627
-
background: var(--bg-tertiary);
628
-
color: var(--text-secondary);
629
-
}
630
-
631
-
.permissions-notice {
632
-
margin-top: var(--space-3);
633
-
padding: var(--space-3);
634
-
background: var(--warning-bg);
635
-
border: 1px solid var(--warning-border);
636
-
border-radius: var(--radius-md);
637
-
}
638
-
639
-
.notice-header {
640
-
display: flex;
641
-
align-items: center;
642
-
gap: var(--space-2);
643
-
font-weight: var(--font-semibold);
644
-
color: var(--warning-text);
645
-
margin-bottom: var(--space-2);
646
-
}
647
-
648
-
.notice-header svg {
649
-
flex-shrink: 0;
650
-
}
651
-
652
-
.notice-text {
653
-
margin: 0;
654
-
font-size: var(--text-sm);
655
-
color: var(--warning-text);
656
-
line-height: 1.5;
657
-
}
658
-
659
-
.scopes-section {
660
-
margin-bottom: var(--space-6);
661
-
}
662
-
663
-
.scopes-section h2 {
664
-
font-size: var(--text-base);
665
-
margin: 0 0 var(--space-4) 0;
666
-
color: var(--text-secondary);
667
-
}
668
-
669
-
.scope-group {
670
-
margin-bottom: var(--space-4);
671
-
}
672
-
673
-
.category-title {
674
-
font-size: var(--text-sm);
675
-
font-weight: var(--font-semibold);
676
-
color: var(--text-primary);
677
-
margin: 0 0 var(--space-2) 0;
678
-
padding-bottom: var(--space-1);
679
-
border-bottom: 1px solid var(--border-color);
680
-
}
681
-
682
-
.scope-item {
683
-
display: flex;
684
-
gap: var(--space-3);
685
-
padding: var(--space-3);
686
-
background: var(--bg-card);
687
-
border: 1px solid var(--border-color);
688
-
border-radius: var(--radius-lg);
689
-
margin-bottom: var(--space-2);
690
-
cursor: pointer;
691
-
transition: border-color var(--transition-fast);
692
-
overflow: hidden;
693
-
}
694
-
695
-
.scope-item:hover:not(.required) {
696
-
border-color: var(--accent);
697
-
}
698
-
699
-
.scope-item.required {
700
-
background: var(--bg-secondary);
701
-
}
702
-
703
-
.scope-item.read-only {
704
-
background: var(--bg-secondary);
705
-
border-style: dashed;
706
-
}
707
-
708
-
.scope-item input[type="checkbox"] {
709
-
flex-shrink: 0;
710
-
width: 18px;
711
-
height: 18px;
712
-
margin-top: 2px;
713
-
}
714
-
715
-
.scope-info {
716
-
flex: 1;
717
-
min-width: 0;
718
-
display: flex;
719
-
flex-direction: column;
720
-
gap: 2px;
721
-
overflow: hidden;
722
-
}
723
-
724
-
.scope-name {
725
-
font-weight: var(--font-medium);
726
-
color: var(--text-primary);
727
-
word-break: break-all;
728
-
}
729
-
730
-
.scope-description {
731
-
font-size: var(--text-sm);
732
-
color: var(--text-secondary);
733
-
word-break: break-all;
734
-
}
735
-
736
-
.required-badge {
737
-
display: inline-block;
738
-
font-size: 0.625rem;
739
-
padding: 2px var(--space-2);
740
-
background: var(--warning-bg);
741
-
color: var(--warning-text);
742
-
border-radius: var(--radius-sm);
743
-
text-transform: uppercase;
744
-
letter-spacing: 0.05em;
745
-
margin-top: var(--space-1);
746
-
width: fit-content;
747
-
}
748
-
749
-
.remember-choice {
750
-
display: flex;
751
-
align-items: center;
752
-
gap: var(--space-2);
753
-
margin-top: var(--space-5);
754
-
cursor: pointer;
755
-
color: var(--text-secondary);
756
-
font-size: var(--text-sm);
757
-
}
758
-
759
-
.remember-choice input {
760
-
width: 16px;
761
-
height: 16px;
762
-
}
763
-
764
-
.actions {
765
-
display: flex;
766
-
gap: var(--space-4);
767
-
margin-top: var(--space-6);
768
-
}
769
-
770
-
@media (min-width: 800px) {
771
-
.actions {
772
-
max-width: 400px;
773
-
margin-left: auto;
774
-
}
775
-
}
776
-
777
-
.actions button {
778
-
flex: 1;
779
-
padding: var(--space-3);
780
-
border: none;
781
-
border-radius: var(--radius-lg);
782
-
font-size: var(--text-base);
783
-
font-weight: var(--font-medium);
784
-
cursor: pointer;
785
-
transition: background-color var(--transition-fast);
786
-
}
787
-
788
-
.actions button:disabled {
789
-
opacity: 0.6;
790
-
cursor: not-allowed;
791
-
}
792
-
793
-
.deny-btn {
794
-
background: var(--bg-secondary);
795
-
color: var(--text-primary);
796
-
border: 1px solid var(--border-color);
797
-
}
798
-
799
-
.deny-btn:hover:not(:disabled) {
800
-
background: var(--error-bg);
801
-
border-color: var(--error-border);
802
-
color: var(--error-text);
803
-
}
804
-
805
-
.approve-btn {
806
-
background: var(--accent);
807
-
color: var(--text-inverse);
808
-
}
809
-
810
-
.approve-btn:hover:not(:disabled) {
811
-
background: var(--accent-hover);
812
-
}
813
-
</style>
+8
-291
frontend/src/routes/OAuthLogin.svelte
+8
-291
frontend/src/routes/OAuthLogin.svelte
···
401
401
{/if}
402
402
403
403
<form onsubmit={handleSubmit}>
404
-
<div class="field">
404
+
<div>
405
405
<label for="username">{$_('register.handle')}</label>
406
406
<input
407
407
id="username"
···
425
425
disabled={submitting || ssoLoading !== null}
426
426
>
427
427
{#if ssoLoading === provider.provider}
428
-
<span class="spinner sm"></span>
428
+
<span>{$_('common.loading')}</span>
429
429
{:else}
430
430
<SsoIcon provider={provider.icon} size={20} />
431
431
{/if}
···
445
445
<h3>{$_('oauth.login.signInWithPasskey')}</h3>
446
446
<button
447
447
type="button"
448
-
class="passkey-btn"
448
+
style="width: 100%"
449
449
class:passkey-unavailable={!hasPasskeys || checkingSecurityStatus || !securityStatusChecked}
450
450
onclick={handlePasskeyLogin}
451
451
disabled={submitting || !hasPasskeys || !username || checkingSecurityStatus || !securityStatusChecked}
···
494
494
<span>{$_('oauth.login.rememberDevice')}</span>
495
495
</label>
496
496
497
-
<button type="submit" class="submit-btn" disabled={submitting || !username || !password}>
497
+
<button type="submit" disabled={submitting || !username || !password}>
498
498
{submitting ? $_('oauth.login.signingIn') : $_('oauth.login.title')}
499
499
</button>
500
500
</div>
···
502
502
</div>
503
503
504
504
<div class="cancel-row">
505
-
<button type="button" class="cancel-btn-subtle" onclick={handleCancel} disabled={submitting}>
505
+
<button type="button" class="ghost sm" onclick={handleCancel} disabled={submitting}>
506
506
{$_('common.cancel')}
507
507
</button>
508
508
</div>
509
509
{:else}
510
510
{#if hasPassword || !securityStatusChecked}
511
-
<div class="field">
511
+
<div>
512
512
<label for="password">{$_('oauth.login.password')}</label>
513
513
<input
514
514
id="password"
···
526
526
</label>
527
527
528
528
<div class="actions">
529
-
<button type="submit" class="submit-btn" disabled={submitting || !username || !password}>
529
+
<button type="submit" disabled={submitting || !username || !password}>
530
530
{submitting ? $_('oauth.login.signingIn') : $_('oauth.login.title')}
531
531
</button>
532
532
</div>
533
533
{/if}
534
534
535
535
<div class="cancel-row">
536
-
<button type="button" class="cancel-btn-subtle" onclick={handleCancel} disabled={submitting}>
536
+
<button type="button" class="ghost sm" onclick={handleCancel} disabled={submitting}>
537
537
{$_('common.cancel')}
538
538
</button>
539
539
</div>
···
544
544
<a href={getFullUrl(routes.resetPassword)}>{$_('login.forgotPassword')}</a> · <a href={getFullUrl(routes.requestPasskeyRecovery)}>{$_('login.lostPasskey')}</a>
545
545
</p>
546
546
</div>
547
-
548
-
<style>
549
-
.help-links {
550
-
text-align: center;
551
-
margin-top: var(--space-4);
552
-
font-size: var(--text-sm);
553
-
}
554
-
555
-
.help-links a {
556
-
color: var(--accent);
557
-
text-decoration: none;
558
-
}
559
-
560
-
.help-links a:hover {
561
-
text-decoration: underline;
562
-
}
563
-
564
-
form {
565
-
display: flex;
566
-
flex-direction: column;
567
-
gap: var(--space-4);
568
-
}
569
-
570
-
.auth-methods {
571
-
display: grid;
572
-
grid-template-columns: 1fr;
573
-
gap: var(--space-5);
574
-
margin-top: var(--space-4);
575
-
}
576
-
577
-
@media (min-width: 600px) {
578
-
.auth-methods {
579
-
grid-template-columns: 1fr auto 1fr;
580
-
align-items: start;
581
-
}
582
-
}
583
-
584
-
.auth-methods.single-method {
585
-
grid-template-columns: 1fr;
586
-
}
587
-
588
-
@media (min-width: 600px) {
589
-
.auth-methods.single-method {
590
-
grid-template-columns: 1fr;
591
-
max-width: 400px;
592
-
margin: var(--space-4) auto 0;
593
-
}
594
-
}
595
-
596
-
.passkey-method,
597
-
.password-method {
598
-
display: flex;
599
-
flex-direction: column;
600
-
gap: var(--space-4);
601
-
padding: var(--space-5);
602
-
background: var(--bg-secondary);
603
-
border-radius: var(--radius-xl);
604
-
}
605
-
606
-
.passkey-method h3,
607
-
.password-method h3 {
608
-
margin: 0;
609
-
font-size: var(--text-sm);
610
-
font-weight: var(--font-semibold);
611
-
color: var(--text-secondary);
612
-
text-transform: uppercase;
613
-
letter-spacing: 0.05em;
614
-
}
615
-
616
-
617
-
.method-divider {
618
-
display: flex;
619
-
align-items: center;
620
-
justify-content: center;
621
-
color: var(--text-muted);
622
-
font-size: var(--text-sm);
623
-
}
624
-
625
-
@media (min-width: 600px) {
626
-
.method-divider {
627
-
flex-direction: column;
628
-
padding: 0 var(--space-3);
629
-
}
630
-
631
-
.method-divider::before,
632
-
.method-divider::after {
633
-
content: '';
634
-
width: 1px;
635
-
height: var(--space-6);
636
-
background: var(--border-color);
637
-
}
638
-
639
-
.method-divider span {
640
-
writing-mode: vertical-rl;
641
-
text-orientation: mixed;
642
-
transform: rotate(180deg);
643
-
padding: var(--space-2) 0;
644
-
}
645
-
}
646
-
647
-
@media (max-width: 599px) {
648
-
.method-divider {
649
-
gap: var(--space-4);
650
-
}
651
-
652
-
.method-divider::before,
653
-
.method-divider::after {
654
-
content: '';
655
-
flex: 1;
656
-
height: 1px;
657
-
background: var(--border-color);
658
-
}
659
-
}
660
-
661
-
.remember-device {
662
-
display: flex;
663
-
align-items: center;
664
-
gap: var(--space-2);
665
-
cursor: pointer;
666
-
color: var(--text-secondary);
667
-
font-size: var(--text-sm);
668
-
}
669
-
670
-
.remember-device input {
671
-
width: 16px;
672
-
height: 16px;
673
-
}
674
-
675
-
.actions {
676
-
display: flex;
677
-
gap: var(--space-4);
678
-
margin-top: var(--space-2);
679
-
}
680
-
681
-
.actions button {
682
-
flex: 1;
683
-
}
684
-
685
-
.cancel-row {
686
-
display: flex;
687
-
justify-content: center;
688
-
margin-top: var(--space-4);
689
-
}
690
-
691
-
.cancel-btn-subtle {
692
-
padding: var(--space-2) var(--space-4);
693
-
background: transparent;
694
-
color: var(--text-muted);
695
-
border: none;
696
-
border-radius: var(--radius-md);
697
-
font-size: var(--text-sm);
698
-
cursor: pointer;
699
-
transition: color var(--transition-fast);
700
-
}
701
-
702
-
.cancel-btn-subtle:hover:not(:disabled) {
703
-
color: var(--text-secondary);
704
-
}
705
-
706
-
.cancel-btn-subtle:disabled {
707
-
opacity: 0.6;
708
-
cursor: not-allowed;
709
-
}
710
-
711
-
.submit-btn {
712
-
background: var(--accent);
713
-
color: var(--text-inverse);
714
-
}
715
-
716
-
.submit-btn:hover:not(:disabled) {
717
-
background: var(--accent-hover);
718
-
}
719
-
720
-
.passkey-btn {
721
-
display: flex;
722
-
align-items: center;
723
-
justify-content: center;
724
-
gap: var(--space-2);
725
-
width: 100%;
726
-
padding: var(--space-3);
727
-
background: var(--accent);
728
-
color: var(--text-inverse);
729
-
border: 1px solid var(--accent);
730
-
border-radius: var(--radius-md);
731
-
font-size: var(--text-base);
732
-
cursor: pointer;
733
-
transition: background-color var(--transition-fast), border-color var(--transition-fast), opacity var(--transition-fast);
734
-
}
735
-
736
-
.passkey-btn:hover:not(:disabled) {
737
-
background: var(--accent-hover);
738
-
border-color: var(--accent-hover);
739
-
}
740
-
741
-
.passkey-btn:disabled {
742
-
opacity: 0.6;
743
-
cursor: not-allowed;
744
-
}
745
-
746
-
.passkey-btn.passkey-unavailable {
747
-
background: var(--bg-secondary);
748
-
color: var(--text-secondary);
749
-
border-color: var(--border-color);
750
-
}
751
-
752
-
.passkey-icon {
753
-
width: 20px;
754
-
height: 20px;
755
-
}
756
-
757
-
.passkey-text {
758
-
flex: 1;
759
-
text-align: left;
760
-
}
761
-
762
-
.sso-section {
763
-
margin-top: var(--space-6);
764
-
}
765
-
766
-
.sso-section-top {
767
-
margin-top: var(--space-4);
768
-
margin-bottom: 0;
769
-
}
770
-
771
-
.sso-section-top .sso-divider {
772
-
margin-top: var(--space-5);
773
-
margin-bottom: 0;
774
-
}
775
-
776
-
.sso-divider {
777
-
display: flex;
778
-
align-items: center;
779
-
gap: var(--space-4);
780
-
margin-bottom: var(--space-4);
781
-
color: var(--text-muted);
782
-
font-size: var(--text-sm);
783
-
}
784
-
785
-
.sso-divider::before,
786
-
.sso-divider::after {
787
-
content: '';
788
-
flex: 1;
789
-
height: 1px;
790
-
background: var(--border-color);
791
-
}
792
-
793
-
.sso-buttons {
794
-
display: flex;
795
-
flex-wrap: wrap;
796
-
gap: var(--space-3);
797
-
justify-content: center;
798
-
}
799
-
800
-
.sso-btn {
801
-
display: flex;
802
-
align-items: center;
803
-
gap: var(--space-2);
804
-
padding: var(--space-2) var(--space-4);
805
-
background: var(--bg-secondary);
806
-
color: var(--text-primary);
807
-
border: 1px solid var(--border-color);
808
-
border-radius: var(--radius-md);
809
-
font-size: var(--text-sm);
810
-
cursor: pointer;
811
-
transition: background-color var(--transition-fast), border-color var(--transition-fast);
812
-
}
813
-
814
-
.sso-btn-prominent {
815
-
padding: var(--space-3) var(--space-5);
816
-
font-size: var(--text-base);
817
-
font-weight: var(--font-medium);
818
-
}
819
-
820
-
.sso-btn:hover:not(:disabled) {
821
-
background: var(--bg-tertiary);
822
-
border-color: var(--accent);
823
-
}
824
-
825
-
.sso-btn:disabled {
826
-
opacity: 0.6;
827
-
cursor: not-allowed;
828
-
}
829
-
</style>
+5
-204
frontend/src/routes/OAuthRegister.svelte
+5
-204
frontend/src/routes/OAuthRegister.svelte
···
308
308
309
309
<div class="oauth-register-container">
310
310
{#if loadingServerInfo}
311
-
<div class="loading">
312
-
<div class="spinner"></div>
313
-
<p>{$_('common.loading')}</p>
314
-
</div>
311
+
<div class="loading"></div>
315
312
{:else if flow}
316
313
<header class="page-header">
317
314
<h1>{$_('oauth.register.title')}</h1>
···
345
342
<div class="split-layout">
346
343
<div class="form-section">
347
344
<form onsubmit={handleInfoSubmit}>
348
-
<div class="field">
345
+
<div>
349
346
<label for="handle">{$_('register.handle')}</label>
350
347
<HandleInput
351
348
value={flow.info.handle}
···
484
481
</fieldset>
485
482
486
483
{#if serverInfo?.inviteCodeRequired}
487
-
<div class="field">
484
+
<div>
488
485
<label for="invite-code">{$_('register.inviteCode')} <span class="required">*</span></label>
489
486
<input
490
487
id="invite-code"
···
504
501
</div>
505
502
506
503
<div class="secondary-actions">
507
-
<button type="button" class="link-btn" onclick={goToLogin}>
504
+
<button type="button" class="link" onclick={goToLogin}>
508
505
{$_('oauth.register.haveAccount')}
509
506
</button>
510
-
<button type="button" class="link-btn" onclick={handleCancel}>
507
+
<button type="button" class="link" onclick={handleCancel}>
511
508
{$_('common.cancel')}
512
509
</button>
513
510
</div>
···
540
537
541
538
{:else if flow.state.step === 'creating'}
542
539
<div class="creating">
543
-
<div class="spinner"></div>
544
540
<p>{$_('registerPasskey.creatingAccount')}</p>
545
541
</div>
546
542
···
582
578
583
579
{:else if flow.state.step === 'activating'}
584
580
<div class="creating">
585
-
<div class="spinner"></div>
586
581
<p>{$_('registerPasskey.activatingAccount')}</p>
587
582
</div>
588
583
{/if}
589
584
{/if}
590
585
</div>
591
-
592
-
<style>
593
-
.oauth-register-container {
594
-
max-width: var(--width-lg);
595
-
margin: var(--space-9) auto;
596
-
padding: var(--space-7);
597
-
}
598
-
599
-
.loading, .creating {
600
-
display: flex;
601
-
flex-direction: column;
602
-
align-items: center;
603
-
gap: var(--space-4);
604
-
padding: var(--space-8);
605
-
}
606
-
607
-
.loading p, .creating p {
608
-
color: var(--text-secondary);
609
-
}
610
-
611
-
.page-header {
612
-
margin-bottom: var(--space-6);
613
-
}
614
-
615
-
.page-header h1 {
616
-
margin: 0 0 var(--space-2) 0;
617
-
}
618
-
619
-
.subtitle {
620
-
color: var(--text-secondary);
621
-
margin: 0;
622
-
}
623
-
624
-
.form-section {
625
-
min-width: 0;
626
-
}
627
-
628
-
.form-links {
629
-
margin-top: var(--space-6);
630
-
}
631
-
632
-
.link-text {
633
-
text-align: center;
634
-
color: var(--text-secondary);
635
-
}
636
-
637
-
.link-text a {
638
-
color: var(--accent);
639
-
}
640
-
641
-
form {
642
-
display: flex;
643
-
flex-direction: column;
644
-
gap: var(--space-5);
645
-
}
646
-
647
-
.field {
648
-
display: flex;
649
-
flex-direction: column;
650
-
gap: var(--space-1);
651
-
}
652
-
653
-
label {
654
-
font-size: var(--text-sm);
655
-
font-weight: var(--font-medium);
656
-
}
657
-
658
-
input, select {
659
-
padding: var(--space-3);
660
-
border: 1px solid var(--border-color);
661
-
border-radius: var(--radius-md);
662
-
font-size: var(--text-base);
663
-
background: var(--bg-input);
664
-
color: var(--text-primary);
665
-
}
666
-
667
-
input:focus, select:focus {
668
-
outline: none;
669
-
border-color: var(--accent);
670
-
}
671
-
672
-
.hint {
673
-
font-size: var(--text-xs);
674
-
color: var(--text-muted);
675
-
margin: var(--space-1) 0 0 0;
676
-
}
677
-
678
-
.error {
679
-
padding: var(--space-3);
680
-
background: var(--error-bg);
681
-
border: 1px solid var(--error-border);
682
-
border-radius: var(--radius-md);
683
-
color: var(--error-text);
684
-
margin-bottom: var(--space-4);
685
-
}
686
-
687
-
.actions {
688
-
display: flex;
689
-
gap: var(--space-4);
690
-
margin-top: var(--space-2);
691
-
}
692
-
693
-
button.primary {
694
-
flex: 1;
695
-
padding: var(--space-3);
696
-
background: var(--accent);
697
-
color: var(--text-inverse);
698
-
border: none;
699
-
border-radius: var(--radius-md);
700
-
font-size: var(--text-base);
701
-
cursor: pointer;
702
-
transition: background-color var(--transition-fast);
703
-
}
704
-
705
-
button.primary:hover:not(:disabled) {
706
-
background: var(--accent-hover);
707
-
}
708
-
709
-
button.primary:disabled {
710
-
opacity: 0.6;
711
-
cursor: not-allowed;
712
-
}
713
-
714
-
.secondary-actions {
715
-
display: flex;
716
-
justify-content: center;
717
-
gap: var(--space-4);
718
-
margin-top: var(--space-4);
719
-
}
720
-
721
-
.link-btn {
722
-
background: none;
723
-
border: none;
724
-
color: var(--accent);
725
-
cursor: pointer;
726
-
font-size: var(--text-sm);
727
-
padding: var(--space-2);
728
-
}
729
-
730
-
.link-btn:hover {
731
-
text-decoration: underline;
732
-
}
733
-
734
-
.contact-fields {
735
-
display: flex;
736
-
flex-direction: column;
737
-
gap: var(--space-4);
738
-
}
739
-
740
-
.required {
741
-
color: var(--error-text);
742
-
}
743
-
744
-
.passkey-step {
745
-
display: flex;
746
-
flex-direction: column;
747
-
gap: var(--space-4);
748
-
}
749
-
750
-
.passkey-step h2 {
751
-
margin: 0;
752
-
}
753
-
754
-
.passkey-step p {
755
-
color: var(--text-secondary);
756
-
margin: 0;
757
-
}
758
-
759
-
fieldset {
760
-
border: 1px solid var(--border-color);
761
-
border-radius: var(--radius-md);
762
-
padding: var(--space-4);
763
-
}
764
-
765
-
legend {
766
-
padding: 0 var(--space-2);
767
-
font-weight: var(--font-medium);
768
-
}
769
-
770
-
.spinner {
771
-
width: 32px;
772
-
height: 32px;
773
-
border: 3px solid var(--border-color);
774
-
border-top-color: var(--accent);
775
-
border-radius: 50%;
776
-
animation: spin 1s linear infinite;
777
-
}
778
-
779
-
@keyframes spin {
780
-
to {
781
-
transform: rotate(360deg);
782
-
}
783
-
}
784
-
</style>
History
1 round
0 comments
oyster.cafe
submitted
#0
1 commit
expand
collapse
refactor(frontend): extract oauth primary flow page styles
expand 0 comments
pull request successfully merged