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 { getAuthState } from '../lib/auth.svelte'
5 import { _ } from '../lib/i18n'
6
7 const auth = getAuthState()
8
9 let email = $state('')
10 let token = $state('')
11 let newPassword = $state('')
12 let confirmPassword = $state('')
13 let submitting = $state(false)
14 let error = $state<string | null>(null)
15 let success = $state<string | null>(null)
16 let tokenSent = $state(false)
17
18 $effect(() => {
19 if (auth.session) {
20 navigate('/dashboard')
21 }
22 })
23
24 async function handleRequestReset(e: Event) {
25 e.preventDefault()
26 if (!email) return
27 submitting = true
28 error = null
29 success = null
30 try {
31 await api.requestPasswordReset(email)
32 tokenSent = true
33 success = $_('resetPassword.codeSent')
34 } catch (e) {
35 error = e instanceof ApiError ? e.message : 'Failed to send reset code'
36 } finally {
37 submitting = false
38 }
39 }
40
41 async function handleReset(e: Event) {
42 e.preventDefault()
43 if (!token || !newPassword || !confirmPassword) return
44 if (newPassword !== confirmPassword) {
45 error = $_('resetPassword.passwordsMismatch')
46 return
47 }
48 if (newPassword.length < 8) {
49 error = $_('resetPassword.passwordLength')
50 return
51 }
52 submitting = true
53 error = null
54 success = null
55 try {
56 await api.resetPassword(token, newPassword)
57 success = $_('resetPassword.success')
58 setTimeout(() => navigate('/login'), 2000)
59 } catch (e) {
60 error = e instanceof ApiError ? e.message : 'Failed to reset password'
61 } finally {
62 submitting = false
63 }
64 }
65</script>
66
67<div class="reset-page">
68 {#if error}
69 <div class="message error">{error}</div>
70 {/if}
71 {#if success}
72 <div class="message success">{success}</div>
73 {/if}
74
75 {#if tokenSent}
76 <h1>{$_('resetPassword.title')}</h1>
77 <p class="subtitle">{$_('resetPassword.subtitle')}</p>
78
79 <form onsubmit={handleReset}>
80 <div class="field">
81 <label for="token">{$_('resetPassword.code')}</label>
82 <input
83 id="token"
84 type="text"
85 bind:value={token}
86 placeholder={$_('resetPassword.codePlaceholder')}
87 disabled={submitting}
88 required
89 />
90 </div>
91 <div class="field">
92 <label for="new-password">{$_('resetPassword.newPassword')}</label>
93 <input
94 id="new-password"
95 type="password"
96 bind:value={newPassword}
97 placeholder={$_('resetPassword.newPasswordPlaceholder')}
98 disabled={submitting}
99 required
100 minlength="8"
101 />
102 </div>
103 <div class="field">
104 <label for="confirm-password">{$_('resetPassword.confirmPassword')}</label>
105 <input
106 id="confirm-password"
107 type="password"
108 bind:value={confirmPassword}
109 placeholder={$_('resetPassword.confirmPasswordPlaceholder')}
110 disabled={submitting}
111 required
112 />
113 </div>
114 <button type="submit" disabled={submitting || !token || !newPassword || !confirmPassword}>
115 {submitting ? $_('resetPassword.resetting') : $_('resetPassword.resetButton')}
116 </button>
117 <button type="button" class="secondary" onclick={() => { tokenSent = false; token = ''; newPassword = ''; confirmPassword = '' }}>
118 {$_('resetPassword.requestNewCode')}
119 </button>
120 </form>
121 {:else}
122 <h1>{$_('resetPassword.forgotTitle')}</h1>
123 <p class="subtitle">{$_('resetPassword.forgotSubtitle')}</p>
124
125 <form onsubmit={handleRequestReset}>
126 <div class="field">
127 <label for="email">{$_('resetPassword.handleOrEmail')}</label>
128 <input
129 id="email"
130 type="text"
131 bind:value={email}
132 placeholder={$_('resetPassword.emailPlaceholder')}
133 disabled={submitting}
134 required
135 />
136 </div>
137 <button type="submit" disabled={submitting || !email}>
138 {submitting ? $_('resetPassword.sending') : $_('resetPassword.sendCode')}
139 </button>
140 </form>
141 {/if}
142
143 <p class="link-text">
144 <a href="#/login">{$_('resetPassword.backToLogin')}</a>
145 </p>
146</div>
147
148<style>
149 .reset-page {
150 max-width: var(--width-sm);
151 margin: var(--space-9) auto;
152 padding: var(--space-7);
153 }
154
155 h1 {
156 margin: 0 0 var(--space-3) 0;
157 }
158
159 .subtitle {
160 color: var(--text-secondary);
161 margin: 0 0 var(--space-7) 0;
162 }
163
164 form {
165 display: flex;
166 flex-direction: column;
167 gap: var(--space-4);
168 }
169
170 .link-text {
171 text-align: center;
172 margin-top: var(--space-6);
173 color: var(--text-secondary);
174 }
175
176 .link-text a {
177 color: var(--accent);
178 }
179</style>