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 5 const STORAGE_KEY = 'bspds_pending_verification' 6 7 interface PendingVerification { 8 did: string 9 handle: string 10 channel: string 11 } 12 13 let pendingVerification = $state<PendingVerification | null>(null) 14 let verificationCode = $state('') 15 let submitting = $state(false) 16 let resendingCode = $state(false) 17 let error = $state<string | null>(null) 18 let resendMessage = $state<string | null>(null) 19 20 const auth = getAuthState() 21 22 $effect(() => { 23 if (auth.session) { 24 clearPendingVerification() 25 navigate('/dashboard') 26 } 27 }) 28 29 $effect(() => { 30 const stored = localStorage.getItem(STORAGE_KEY) 31 if (stored) { 32 try { 33 pendingVerification = JSON.parse(stored) 34 } catch { 35 pendingVerification = null 36 } 37 } 38 }) 39 40 function clearPendingVerification() { 41 localStorage.removeItem(STORAGE_KEY) 42 pendingVerification = null 43 } 44 45 async function handleVerification(e: Event) { 46 e.preventDefault() 47 if (!pendingVerification || !verificationCode.trim()) return 48 49 submitting = true 50 error = null 51 52 try { 53 await confirmSignup(pendingVerification.did, verificationCode.trim()) 54 clearPendingVerification() 55 navigate('/dashboard') 56 } catch (e: any) { 57 error = e.message || 'Verification failed' 58 } finally { 59 submitting = false 60 } 61 } 62 63 async function handleResendCode() { 64 if (!pendingVerification || resendingCode) return 65 66 resendingCode = true 67 resendMessage = null 68 error = null 69 70 try { 71 await resendVerification(pendingVerification.did) 72 resendMessage = 'Verification code resent!' 73 } catch (e: any) { 74 error = e.message || 'Failed to resend code' 75 } finally { 76 resendingCode = false 77 } 78 } 79 80 function channelLabel(ch: string): string { 81 switch (ch) { 82 case 'email': return 'Email' 83 case 'discord': return 'Discord' 84 case 'telegram': return 'Telegram' 85 case 'signal': return 'Signal' 86 default: return ch 87 } 88 } 89</script> 90 91<div class="verify-container"> 92 {#if error} 93 <div class="error">{error}</div> 94 {/if} 95 96 {#if pendingVerification} 97 <h1>Verify Your Account</h1> 98 <p class="subtitle"> 99 We've sent a verification code to your {channelLabel(pendingVerification.channel)}. 100 Enter it below to complete registration. 101 </p> 102 <p class="handle-info">Verifying account: <strong>@{pendingVerification.handle}</strong></p> 103 104 {#if resendMessage} 105 <div class="success">{resendMessage}</div> 106 {/if} 107 108 <form onsubmit={(e) => { e.preventDefault(); handleVerification(e); }}> 109 <div class="field"> 110 <label for="verification-code">Verification Code</label> 111 <input 112 id="verification-code" 113 type="text" 114 bind:value={verificationCode} 115 placeholder="Enter 6-digit code" 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 ? 'Verifying...' : 'Verify Account'} 126 </button> 127 128 <button type="button" class="secondary" onclick={handleResendCode} disabled={resendingCode}> 129 {resendingCode ? 'Resending...' : 'Resend Code'} 130 </button> 131 </form> 132 133 <p class="cancel-link"> 134 <a href="#/register" onclick={() => clearPendingVerification()}>Start over with a different account</a> 135 </p> 136 {:else} 137 <h1>Account Verification</h1> 138 <p class="subtitle">No pending verification found.</p> 139 <p class="no-pending-info"> 140 If you recently created an account and need to verify it, you may need to create a new account. 141 If you already verified your account, you can sign in. 142 </p> 143 <div class="actions"> 144 <a href="#/register" class="btn">Create Account</a> 145 <a href="#/login" class="btn secondary">Sign In</a> 146 </div> 147 {/if} 148</div> 149 150<style> 151 .verify-container { 152 max-width: 400px; 153 margin: 4rem auto; 154 padding: 2rem; 155 } 156 157 h1 { 158 margin: 0 0 0.5rem 0; 159 } 160 161 .subtitle { 162 color: var(--text-secondary); 163 margin: 0 0 1rem 0; 164 } 165 166 .handle-info { 167 font-size: 0.9rem; 168 color: var(--text-secondary); 169 margin: 0 0 1.5rem 0; 170 } 171 172 .no-pending-info { 173 color: var(--text-secondary); 174 margin: 1rem 0 1.5rem 0; 175 } 176 177 form { 178 display: flex; 179 flex-direction: column; 180 gap: 1rem; 181 } 182 183 .field { 184 display: flex; 185 flex-direction: column; 186 gap: 0.25rem; 187 } 188 189 label { 190 font-size: 0.875rem; 191 font-weight: 500; 192 } 193 194 input { 195 padding: 0.75rem; 196 border: 1px solid var(--border-color-light); 197 border-radius: 4px; 198 font-size: 1rem; 199 background: var(--bg-input); 200 color: var(--text-primary); 201 } 202 203 input:focus { 204 outline: none; 205 border-color: var(--accent); 206 } 207 208 button, .btn { 209 padding: 0.75rem; 210 background: var(--accent); 211 color: white; 212 border: none; 213 border-radius: 4px; 214 font-size: 1rem; 215 cursor: pointer; 216 text-decoration: none; 217 text-align: center; 218 display: inline-block; 219 } 220 221 button:hover:not(:disabled), .btn:hover { 222 background: var(--accent-hover); 223 } 224 225 button:disabled { 226 opacity: 0.6; 227 cursor: not-allowed; 228 } 229 230 button.secondary, .btn.secondary { 231 background: transparent; 232 color: var(--accent); 233 border: 1px solid var(--accent); 234 } 235 236 button.secondary:hover:not(:disabled), .btn.secondary:hover { 237 background: var(--accent); 238 color: white; 239 } 240 241 .error { 242 padding: 0.75rem; 243 background: var(--error-bg); 244 border: 1px solid var(--error-border); 245 border-radius: 4px; 246 color: var(--error-text); 247 margin-bottom: 1rem; 248 } 249 250 .success { 251 padding: 0.75rem; 252 background: var(--success-bg); 253 border: 1px solid var(--success-border); 254 border-radius: 4px; 255 color: var(--success-text); 256 margin-bottom: 1rem; 257 } 258 259 .cancel-link { 260 text-align: center; 261 margin-top: 1.5rem; 262 font-size: 0.875rem; 263 } 264 265 .cancel-link a { 266 color: var(--text-secondary); 267 } 268 269 .actions { 270 display: flex; 271 gap: 1rem; 272 } 273 274 .actions .btn { 275 flex: 1; 276 } 277</style>