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>