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>