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