this repo has no description
at main 3.4 kB view raw
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>