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