this repo has no description
1<script lang="ts">
2 import { navigate } from '../lib/router.svelte'
3 import { api, ApiError } from '../lib/api'
4 import { _ } from '../lib/i18n'
5
6 let identifier = $state('')
7 let submitting = $state(false)
8 let error = $state<string | null>(null)
9 let success = $state(false)
10
11 async function handleSubmit(e: Event) {
12 e.preventDefault()
13 submitting = true
14 error = null
15
16 try {
17 await api.requestPasskeyRecovery(identifier)
18 success = true
19 } catch (err) {
20 if (err instanceof ApiError) {
21 error = err.message || 'Failed to send recovery link'
22 } else if (err instanceof Error) {
23 error = err.message || 'Failed to send recovery link'
24 } else {
25 error = 'Failed to send recovery link'
26 }
27 } finally {
28 submitting = false
29 }
30 }
31</script>
32
33<div class="recovery-page">
34 {#if success}
35 <div class="success-content">
36 <h1>{$_('requestPasskeyRecovery.successTitle')}</h1>
37 <p class="subtitle">{$_('requestPasskeyRecovery.successMessage')}</p>
38 <p class="info-text">{$_('requestPasskeyRecovery.successInfo')}</p>
39 <button onclick={() => navigate('/login')}>{$_('requestPasskeyRecovery.backToLogin')}</button>
40 </div>
41 {:else}
42 <h1>{$_('requestPasskeyRecovery.title')}</h1>
43 <p class="subtitle">{$_('requestPasskeyRecovery.subtitle')}</p>
44
45 {#if error}
46 <div class="message error">{error}</div>
47 {/if}
48
49 <form onsubmit={handleSubmit}>
50 <div class="field">
51 <label for="identifier">{$_('requestPasskeyRecovery.handleOrEmail')}</label>
52 <input
53 id="identifier"
54 type="text"
55 bind:value={identifier}
56 placeholder={$_('requestPasskeyRecovery.emailPlaceholder')}
57 disabled={submitting}
58 required
59 />
60 </div>
61
62 <div class="info-box">
63 <strong>{$_('requestPasskeyRecovery.howItWorks')}</strong>
64 <p>{$_('requestPasskeyRecovery.howItWorksDetail')}</p>
65 </div>
66
67 <button type="submit" disabled={submitting || !identifier.trim()}>
68 {submitting ? $_('requestPasskeyRecovery.sending') : $_('requestPasskeyRecovery.sendRecoveryLink')}
69 </button>
70 </form>
71 {/if}
72
73 <p class="link-text">
74 <a href="#/login">{$_('requestPasskeyRecovery.backToLogin')}</a>
75 </p>
76</div>
77
78<style>
79 .recovery-page {
80 max-width: var(--width-sm);
81 margin: var(--space-9) auto;
82 padding: var(--space-7);
83 }
84
85 h1 {
86 margin: 0 0 var(--space-3) 0;
87 }
88
89 .subtitle {
90 color: var(--text-secondary);
91 margin: 0 0 var(--space-7) 0;
92 }
93
94 form {
95 display: flex;
96 flex-direction: column;
97 gap: var(--space-4);
98 }
99
100 .info-box {
101 background: var(--bg-secondary);
102 border: 1px solid var(--border-color);
103 border-radius: var(--radius-lg);
104 padding: var(--space-5);
105 font-size: var(--text-sm);
106 }
107
108 .info-box strong {
109 display: block;
110 margin-bottom: var(--space-3);
111 }
112
113 .info-box p {
114 margin: 0;
115 color: var(--text-secondary);
116 }
117
118 .success-content {
119 text-align: center;
120 }
121
122 .info-text {
123 color: var(--text-secondary);
124 font-size: var(--text-sm);
125 margin-bottom: var(--space-6);
126 }
127
128 .link-text {
129 text-align: center;
130 margin-top: var(--space-7);
131 }
132
133 .link-text a {
134 color: var(--accent);
135 }
136</style>