this repo has no description
1<script lang="ts"> 2 import { confirmSignup, resendVerification, getAuthState } from '../lib/auth.svelte' 3 import { navigate } from '../lib/router.svelte' 4 import { _ } from '../lib/i18n' 5 6 const STORAGE_KEY = 'tranquil_pds_pending_verification' 7 8 interface PendingVerification { 9 did: string 10 handle: string 11 channel: string 12 } 13 14 let pendingVerification = $state<PendingVerification | null>(null) 15 let verificationCode = $state('') 16 let submitting = $state(false) 17 let resendingCode = $state(false) 18 let error = $state<string | null>(null) 19 let resendMessage = $state<string | null>(null) 20 21 const auth = getAuthState() 22 23 $effect(() => { 24 if (auth.session) { 25 clearPendingVerification() 26 navigate('/dashboard') 27 } 28 }) 29 30 $effect(() => { 31 const stored = localStorage.getItem(STORAGE_KEY) 32 if (stored) { 33 try { 34 pendingVerification = JSON.parse(stored) 35 } catch { 36 pendingVerification = null 37 } 38 } 39 }) 40 41 function clearPendingVerification() { 42 localStorage.removeItem(STORAGE_KEY) 43 pendingVerification = null 44 } 45 46 async function handleVerification(e: Event) { 47 e.preventDefault() 48 if (!pendingVerification || !verificationCode.trim()) return 49 50 submitting = true 51 error = null 52 53 try { 54 await confirmSignup(pendingVerification.did, verificationCode.trim()) 55 clearPendingVerification() 56 navigate('/dashboard') 57 } catch (e: any) { 58 error = e.message || 'Verification failed' 59 } finally { 60 submitting = false 61 } 62 } 63 64 async function handleResendCode() { 65 if (!pendingVerification || resendingCode) return 66 67 resendingCode = true 68 resendMessage = null 69 error = null 70 71 try { 72 await resendVerification(pendingVerification.did) 73 resendMessage = 'Verification code resent!' 74 } catch (e: any) { 75 error = e.message || 'Failed to resend code' 76 } finally { 77 resendingCode = false 78 } 79 } 80 81 function channelLabel(ch: string): string { 82 switch (ch) { 83 case 'email': return $_('register.email') 84 case 'discord': return $_('register.discord') 85 case 'telegram': return $_('register.telegram') 86 case 'signal': return $_('register.signal') 87 default: return ch 88 } 89 } 90</script> 91 92<div class="verify-page"> 93 {#if error} 94 <div class="message error">{error}</div> 95 {/if} 96 97 {#if pendingVerification} 98 <h1>{$_('verify.title')}</h1> 99 <p class="subtitle"> 100 {$_('verify.subtitle', { values: { channel: channelLabel(pendingVerification.channel) } })} 101 </p> 102 <p class="handle-info">{$_('verify.verifyingAccount', { values: { handle: pendingVerification.handle } })}</p> 103 104 {#if resendMessage} 105 <div class="message success">{resendMessage}</div> 106 {/if} 107 108 <form onsubmit={(e) => { e.preventDefault(); handleVerification(e); }}> 109 <div class="field"> 110 <label for="verification-code">{$_('verify.codeLabel')}</label> 111 <input 112 id="verification-code" 113 type="text" 114 bind:value={verificationCode} 115 placeholder={$_('verify.codePlaceholder')} 116 disabled={submitting} 117 required 118 maxlength="6" 119 inputmode="numeric" 120 autocomplete="one-time-code" 121 /> 122 </div> 123 124 <button type="submit" disabled={submitting || !verificationCode.trim()}> 125 {submitting ? $_('verify.verifying') : $_('verify.verifyButton')} 126 </button> 127 128 <button type="button" class="secondary" onclick={handleResendCode} disabled={resendingCode}> 129 {resendingCode ? $_('verify.resending') : $_('verify.resendCode')} 130 </button> 131 </form> 132 133 <p class="link-text"> 134 <a href="#/register" onclick={() => clearPendingVerification()}>{$_('verify.startOver')}</a> 135 </p> 136 {:else} 137 <h1>{$_('verify.title')}</h1> 138 <p class="subtitle">{$_('verify.noPending')}</p> 139 <p class="info-text">{$_('verify.noPendingInfo')}</p> 140 141 <div class="actions"> 142 <a href="#/register" class="btn">{$_('verify.createAccount')}</a> 143 <a href="#/login" class="btn secondary">{$_('verify.signIn')}</a> 144 </div> 145 {/if} 146</div> 147 148<style> 149 .verify-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-4) 0; 162 } 163 164 .handle-info { 165 font-size: var(--text-sm); 166 color: var(--text-secondary); 167 margin: 0 0 var(--space-6) 0; 168 } 169 170 .info-text { 171 color: var(--text-secondary); 172 margin: var(--space-4) 0 var(--space-6) 0; 173 } 174 175 form { 176 display: flex; 177 flex-direction: column; 178 gap: var(--space-4); 179 } 180 181 .link-text { 182 text-align: center; 183 margin-top: var(--space-6); 184 font-size: var(--text-sm); 185 } 186 187 .link-text a { 188 color: var(--text-secondary); 189 } 190 191 .actions { 192 display: flex; 193 gap: var(--space-4); 194 } 195 196 .btn { 197 flex: 1; 198 display: inline-block; 199 padding: var(--space-4); 200 background: var(--accent); 201 color: var(--text-inverse); 202 border: none; 203 border-radius: var(--radius-md); 204 font-size: var(--text-base); 205 font-weight: var(--font-medium); 206 cursor: pointer; 207 text-decoration: none; 208 text-align: center; 209 } 210 211 .btn:hover { 212 background: var(--accent-hover); 213 text-decoration: none; 214 } 215 216 .btn.secondary { 217 background: transparent; 218 color: var(--accent); 219 border: 1px solid var(--accent); 220 } 221 222 .btn.secondary:hover { 223 background: var(--accent); 224 color: var(--text-inverse); 225 } 226</style>