this repo has no description
1<script lang="ts"> 2 import { login, confirmSignup, resendVerification, getAuthState } from '../lib/auth.svelte' 3 import { navigate } from '../lib/router.svelte' 4 import { ApiError } from '../lib/api' 5 let identifier = $state('') 6 let password = $state('') 7 let submitting = $state(false) 8 let error = $state<string | null>(null) 9 let pendingVerification = $state<{ did: string } | null>(null) 10 let verificationCode = $state('') 11 let resendingCode = $state(false) 12 let resendMessage = $state<string | null>(null) 13 const auth = getAuthState() 14 $effect(() => { 15 if (auth.session) { 16 navigate('/dashboard') 17 } 18 }) 19 async function handleSubmit(e: Event) { 20 e.preventDefault() 21 if (!identifier || !password) return 22 submitting = true 23 error = null 24 pendingVerification = null 25 try { 26 await login(identifier, password) 27 navigate('/dashboard') 28 } catch (e: any) { 29 if (e instanceof ApiError && e.error === 'AccountNotVerified') { 30 if (e.did) { 31 pendingVerification = { did: e.did } 32 } else { 33 error = 'Account not verified. Please check your verification method for a code.' 34 } 35 } else { 36 error = e.message || 'Login failed' 37 } 38 } finally { 39 submitting = false 40 } 41 } 42 async function handleVerification(e: Event) { 43 e.preventDefault() 44 if (!pendingVerification || !verificationCode.trim()) return 45 submitting = true 46 error = null 47 try { 48 await confirmSignup(pendingVerification.did, verificationCode.trim()) 49 navigate('/dashboard') 50 } catch (e: any) { 51 error = e.message || 'Verification failed' 52 } finally { 53 submitting = false 54 } 55 } 56 async function handleResendCode() { 57 if (!pendingVerification || resendingCode) return 58 resendingCode = true 59 resendMessage = null 60 error = null 61 try { 62 await resendVerification(pendingVerification.did) 63 resendMessage = 'Verification code resent!' 64 } catch (e: any) { 65 error = e.message || 'Failed to resend code' 66 } finally { 67 resendingCode = false 68 } 69 } 70 function backToLogin() { 71 pendingVerification = null 72 verificationCode = '' 73 error = null 74 resendMessage = null 75 } 76</script> 77<div class="login-container"> 78 {#if error} 79 <div class="error">{error}</div> 80 {/if} 81 {#if pendingVerification} 82 <h1>Verify Your Account</h1> 83 <p class="subtitle"> 84 Your account needs verification. Enter the code sent to your verification method. 85 </p> 86 {#if resendMessage} 87 <div class="success">{resendMessage}</div> 88 {/if} 89 <form onsubmit={(e) => { e.preventDefault(); handleVerification(e); }}> 90 <div class="field"> 91 <label for="verification-code">Verification Code</label> 92 <input 93 id="verification-code" 94 type="text" 95 bind:value={verificationCode} 96 placeholder="Enter 6-digit code" 97 disabled={submitting} 98 required 99 maxlength="6" 100 pattern="[0-9]{6}" 101 autocomplete="one-time-code" 102 /> 103 </div> 104 <button type="submit" disabled={submitting || !verificationCode.trim()}> 105 {submitting ? 'Verifying...' : 'Verify Account'} 106 </button> 107 <button type="button" class="secondary" onclick={handleResendCode} disabled={resendingCode}> 108 {resendingCode ? 'Resending...' : 'Resend Code'} 109 </button> 110 <button type="button" class="tertiary" onclick={backToLogin}> 111 Back to Login 112 </button> 113 </form> 114 {:else} 115 <h1>Sign In</h1> 116 <p class="subtitle">Sign in to manage your PDS account</p> 117 <form onsubmit={(e) => { e.preventDefault(); handleSubmit(e); }}> 118 <div class="field"> 119 <label for="identifier">Handle or Email</label> 120 <input 121 id="identifier" 122 type="text" 123 bind:value={identifier} 124 placeholder="you.bsky.social or you@example.com" 125 disabled={submitting} 126 required 127 /> 128 </div> 129 <div class="field"> 130 <label for="password">Password</label> 131 <input 132 id="password" 133 type="password" 134 bind:value={password} 135 placeholder="Password" 136 disabled={submitting} 137 required 138 /> 139 </div> 140 <button type="submit" disabled={submitting || !identifier || !password}> 141 {submitting ? 'Signing in...' : 'Sign In'} 142 </button> 143 </form> 144 <p class="register-link"> 145 Don't have an account? <a href="#/register">Create one</a> 146 </p> 147 {/if} 148</div> 149<style> 150 .login-container { 151 max-width: 400px; 152 margin: 4rem auto; 153 padding: 2rem; 154 } 155 h1 { 156 margin: 0 0 0.5rem 0; 157 } 158 .subtitle { 159 color: var(--text-secondary); 160 margin: 0 0 2rem 0; 161 } 162 form { 163 display: flex; 164 flex-direction: column; 165 gap: 1rem; 166 } 167 .field { 168 display: flex; 169 flex-direction: column; 170 gap: 0.25rem; 171 } 172 label { 173 font-size: 0.875rem; 174 font-weight: 500; 175 } 176 input { 177 padding: 0.75rem; 178 border: 1px solid var(--border-color-light); 179 border-radius: 4px; 180 font-size: 1rem; 181 background: var(--bg-input); 182 color: var(--text-primary); 183 } 184 input:focus { 185 outline: none; 186 border-color: var(--accent); 187 } 188 button { 189 padding: 0.75rem; 190 background: var(--accent); 191 color: white; 192 border: none; 193 border-radius: 4px; 194 font-size: 1rem; 195 cursor: pointer; 196 margin-top: 0.5rem; 197 } 198 button:hover:not(:disabled) { 199 background: var(--accent-hover); 200 } 201 button:disabled { 202 opacity: 0.6; 203 cursor: not-allowed; 204 } 205 button.secondary { 206 background: transparent; 207 color: var(--accent); 208 border: 1px solid var(--accent); 209 } 210 button.secondary:hover:not(:disabled) { 211 background: var(--accent); 212 color: white; 213 } 214 button.tertiary { 215 background: transparent; 216 color: var(--text-secondary); 217 border: none; 218 } 219 button.tertiary:hover:not(:disabled) { 220 color: var(--text-primary); 221 } 222 .error { 223 padding: 0.75rem; 224 background: var(--error-bg); 225 border: 1px solid var(--error-border); 226 border-radius: 4px; 227 color: var(--error-text); 228 } 229 .success { 230 padding: 0.75rem; 231 background: var(--success-bg); 232 border: 1px solid var(--success-border); 233 border-radius: 4px; 234 color: var(--success-text); 235 } 236 .register-link { 237 text-align: center; 238 margin-top: 1.5rem; 239 color: var(--text-secondary); 240 } 241 .register-link a { 242 color: var(--accent); 243 } 244</style>