this repo has no description
1<script lang="ts"> 2 import { navigate } from '../lib/router.svelte' 3 4 interface AccountInfo { 5 did: string 6 handle: string 7 email: string 8 } 9 10 let loading = $state(true) 11 let error = $state<string | null>(null) 12 let submitting = $state(false) 13 let accounts = $state<AccountInfo[]>([]) 14 15 function getRequestUri(): string | null { 16 const params = new URLSearchParams(window.location.hash.split('?')[1] || '') 17 return params.get('request_uri') 18 } 19 20 async function fetchAccounts() { 21 const requestUri = getRequestUri() 22 if (!requestUri) { 23 error = 'Missing request_uri parameter' 24 loading = false 25 return 26 } 27 28 try { 29 const response = await fetch(`/oauth/authorize/accounts?request_uri=${encodeURIComponent(requestUri)}`) 30 if (!response.ok) { 31 const data = await response.json() 32 error = data.error_description || data.error || 'Failed to load accounts' 33 loading = false 34 return 35 } 36 const data = await response.json() 37 accounts = data.accounts || [] 38 } catch { 39 error = 'Failed to connect to server' 40 } finally { 41 loading = false 42 } 43 } 44 45 async function handleSelectAccount(did: string) { 46 const requestUri = getRequestUri() 47 if (!requestUri) { 48 error = 'Missing request_uri parameter' 49 return 50 } 51 52 submitting = true 53 error = null 54 55 try { 56 const response = await fetch('/oauth/authorize/select', { 57 method: 'POST', 58 headers: { 59 'Content-Type': 'application/json', 60 'Accept': 'application/json' 61 }, 62 body: JSON.stringify({ 63 request_uri: requestUri, 64 did 65 }) 66 }) 67 68 const data = await response.json() 69 70 if (!response.ok) { 71 error = data.error_description || data.error || 'Selection failed' 72 submitting = false 73 return 74 } 75 76 if (data.needs_2fa) { 77 navigate(`/oauth/2fa?request_uri=${encodeURIComponent(requestUri)}&channel=${encodeURIComponent(data.channel || '')}`) 78 return 79 } 80 81 if (data.redirect_uri) { 82 window.location.href = data.redirect_uri 83 return 84 } 85 86 error = 'Unexpected response from server' 87 submitting = false 88 } catch { 89 error = 'Failed to connect to server' 90 submitting = false 91 } 92 } 93 94 function handleDifferentAccount() { 95 const requestUri = getRequestUri() 96 if (requestUri) { 97 navigate(`/oauth/login?request_uri=${encodeURIComponent(requestUri)}`) 98 } else { 99 navigate('/oauth/login') 100 } 101 } 102 103 $effect(() => { 104 fetchAccounts() 105 }) 106</script> 107 108<div class="oauth-accounts-container"> 109 {#if loading} 110 <div class="loading"> 111 <p>Loading accounts...</p> 112 </div> 113 {:else if error} 114 <div class="error-container"> 115 <h1>Error</h1> 116 <div class="error">{error}</div> 117 <button type="button" onclick={handleDifferentAccount}> 118 Sign in with different account 119 </button> 120 </div> 121 {:else} 122 <h1>Choose an Account</h1> 123 <p class="subtitle">Select an account to continue</p> 124 125 <div class="accounts-list"> 126 {#each accounts as account} 127 <button 128 type="button" 129 class="account-item" 130 class:disabled={submitting} 131 onclick={() => !submitting && handleSelectAccount(account.did)} 132 > 133 <div class="account-info"> 134 <span class="account-handle">@{account.handle}</span> 135 <span class="account-email">{account.email}</span> 136 </div> 137 </button> 138 {/each} 139 </div> 140 141 <button type="button" class="secondary different-account" onclick={handleDifferentAccount}> 142 Sign in to different account 143 </button> 144 {/if} 145</div> 146 147<style> 148 .oauth-accounts-container { 149 max-width: 400px; 150 margin: 4rem auto; 151 padding: 2rem; 152 } 153 154 h1 { 155 margin: 0 0 0.5rem 0; 156 } 157 158 .subtitle { 159 color: var(--text-secondary); 160 margin: 0 0 2rem 0; 161 } 162 163 .loading { 164 display: flex; 165 align-items: center; 166 justify-content: center; 167 min-height: 200px; 168 color: var(--text-secondary); 169 } 170 171 .error-container { 172 text-align: center; 173 } 174 175 .error { 176 padding: 0.75rem; 177 background: var(--error-bg); 178 border: 1px solid var(--error-border); 179 border-radius: 4px; 180 color: var(--error-text); 181 margin-bottom: 1rem; 182 } 183 184 .accounts-list { 185 display: flex; 186 flex-direction: column; 187 gap: 0.5rem; 188 margin-bottom: 1rem; 189 } 190 191 .account-item { 192 display: flex; 193 align-items: center; 194 padding: 1rem; 195 background: var(--bg-card); 196 border: 1px solid var(--border-color); 197 border-radius: 8px; 198 cursor: pointer; 199 text-align: left; 200 width: 100%; 201 transition: border-color 0.15s, box-shadow 0.15s; 202 } 203 204 .account-item:hover:not(.disabled) { 205 border-color: var(--accent); 206 box-shadow: 0 2px 8px rgba(77, 166, 255, 0.15); 207 } 208 209 .account-item.disabled { 210 opacity: 0.6; 211 cursor: not-allowed; 212 } 213 214 .account-info { 215 display: flex; 216 flex-direction: column; 217 gap: 0.25rem; 218 } 219 220 .account-handle { 221 font-weight: 500; 222 color: var(--text-primary); 223 } 224 225 .account-email { 226 font-size: 0.875rem; 227 color: var(--text-secondary); 228 } 229 230 button { 231 padding: 0.75rem; 232 background: var(--accent); 233 color: white; 234 border: none; 235 border-radius: 4px; 236 font-size: 1rem; 237 cursor: pointer; 238 } 239 240 button:hover:not(:disabled) { 241 background: var(--accent-hover); 242 } 243 244 button:disabled { 245 opacity: 0.6; 246 cursor: not-allowed; 247 } 248 249 button.secondary { 250 background: transparent; 251 color: var(--accent); 252 border: 1px solid var(--accent); 253 width: 100%; 254 } 255 256 button.secondary:hover:not(:disabled) { 257 background: var(--accent); 258 color: white; 259 } 260 261 .different-account { 262 margin-top: 1rem; 263 } 264</style>