this repo has no description
1<script lang="ts"> 2 import { getAuthState, logout, switchAccount } from '../lib/auth.svelte' 3 import { navigate } from '../lib/router.svelte' 4 const auth = getAuthState() 5 let dropdownOpen = $state(false) 6 let switching = $state(false) 7 $effect(() => { 8 if (!auth.loading && !auth.session) { 9 navigate('/login') 10 } 11 }) 12 async function handleLogout() { 13 await logout() 14 navigate('/login') 15 } 16 async function handleSwitchAccount(did: string) { 17 switching = true 18 dropdownOpen = false 19 try { 20 await switchAccount(did) 21 } catch { 22 navigate('/login') 23 } finally { 24 switching = false 25 } 26 } 27 function toggleDropdown() { 28 dropdownOpen = !dropdownOpen 29 } 30 function closeDropdown(e: MouseEvent) { 31 const target = e.target as HTMLElement 32 if (!target.closest('.account-dropdown')) { 33 dropdownOpen = false 34 } 35 } 36 $effect(() => { 37 if (dropdownOpen) { 38 document.addEventListener('click', closeDropdown) 39 return () => document.removeEventListener('click', closeDropdown) 40 } 41 }) 42 let otherAccounts = $derived( 43 auth.savedAccounts.filter(a => a.did !== auth.session?.did) 44 ) 45</script> 46{#if auth.session} 47 <div class="dashboard"> 48 <header> 49 <h1>Dashboard</h1> 50 <div class="account-dropdown"> 51 <button class="account-trigger" onclick={toggleDropdown} disabled={switching}> 52 <span class="account-handle">@{auth.session.handle}</span> 53 <span class="dropdown-arrow">{dropdownOpen ? '▲' : '▼'}</span> 54 </button> 55 {#if dropdownOpen} 56 <div class="dropdown-menu"> 57 {#if otherAccounts.length > 0} 58 <div class="dropdown-section"> 59 <span class="dropdown-label">Switch Account</span> 60 {#each otherAccounts as account} 61 <button 62 type="button" 63 class="dropdown-item" 64 onclick={() => handleSwitchAccount(account.did)} 65 > 66 @{account.handle} 67 </button> 68 {/each} 69 </div> 70 <div class="dropdown-divider"></div> 71 {/if} 72 <button 73 type="button" 74 class="dropdown-item" 75 onclick={() => { dropdownOpen = false; navigate('/login') }} 76 > 77 Add another account 78 </button> 79 <div class="dropdown-divider"></div> 80 <button type="button" class="dropdown-item logout-item" onclick={handleLogout}> 81 Sign out @{auth.session.handle} 82 </button> 83 </div> 84 {/if} 85 </div> 86 </header> 87 <section class="account-overview"> 88 <h2>Account Overview</h2> 89 <dl> 90 <dt>Handle</dt> 91 <dd> 92 @{auth.session.handle} 93 {#if auth.session.isAdmin} 94 <span class="badge admin">Admin</span> 95 {/if} 96 </dd> 97 <dt>DID</dt> 98 <dd class="mono">{auth.session.did}</dd> 99 {#if auth.session.preferredChannel} 100 <dt>Primary Contact</dt> 101 <dd> 102 {#if auth.session.preferredChannel === 'email'} 103 {auth.session.email || 'Email'} 104 {:else if auth.session.preferredChannel === 'discord'} 105 Discord 106 {:else if auth.session.preferredChannel === 'telegram'} 107 Telegram 108 {:else if auth.session.preferredChannel === 'signal'} 109 Signal 110 {:else} 111 {auth.session.preferredChannel} 112 {/if} 113 {#if auth.session.preferredChannelVerified} 114 <span class="badge success">Verified</span> 115 {:else} 116 <span class="badge warning">Unverified</span> 117 {/if} 118 </dd> 119 {:else if auth.session.email} 120 <dt>Email</dt> 121 <dd> 122 {auth.session.email} 123 {#if auth.session.emailConfirmed} 124 <span class="badge success">Verified</span> 125 {:else} 126 <span class="badge warning">Unverified</span> 127 {/if} 128 </dd> 129 {/if} 130 </dl> 131 </section> 132 <nav class="nav-grid"> 133 <a href="#/app-passwords" class="nav-card"> 134 <h3>App Passwords</h3> 135 <p>Manage passwords for third-party apps</p> 136 </a> 137 <a href="#/sessions" class="nav-card"> 138 <h3>Active Sessions</h3> 139 <p>View and manage your login sessions</p> 140 </a> 141 <a href="#/invite-codes" class="nav-card"> 142 <h3>Invite Codes</h3> 143 <p>View and create invite codes</p> 144 </a> 145 <a href="#/settings" class="nav-card"> 146 <h3>Account Settings</h3> 147 <p>Email, password, handle, and more</p> 148 </a> 149 <a href="#/notifications" class="nav-card"> 150 <h3>Notification Preferences</h3> 151 <p>Discord, Telegram, Signal channels</p> 152 </a> 153 <a href="#/repo" class="nav-card"> 154 <h3>Repository Explorer</h3> 155 <p>Browse and manage raw AT Protocol records</p> 156 </a> 157 {#if auth.session.isAdmin} 158 <a href="#/admin" class="nav-card admin-card"> 159 <h3>Admin Panel</h3> 160 <p>Server stats and admin operations</p> 161 </a> 162 {/if} 163 </nav> 164 </div> 165{:else if auth.loading} 166 <div class="loading">Loading...</div> 167{/if} 168<style> 169 .dashboard { 170 max-width: 800px; 171 margin: 0 auto; 172 padding: 2rem; 173 } 174 header { 175 display: flex; 176 justify-content: space-between; 177 align-items: center; 178 margin-bottom: 2rem; 179 } 180 header h1 { 181 margin: 0; 182 } 183 .account-dropdown { 184 position: relative; 185 } 186 .account-trigger { 187 display: flex; 188 align-items: center; 189 gap: 0.5rem; 190 padding: 0.5rem 1rem; 191 background: transparent; 192 border: 1px solid var(--border-color-light); 193 border-radius: 4px; 194 cursor: pointer; 195 color: var(--text-primary); 196 } 197 .account-trigger:hover:not(:disabled) { 198 background: var(--bg-secondary); 199 } 200 .account-trigger:disabled { 201 opacity: 0.6; 202 cursor: not-allowed; 203 } 204 .account-trigger .account-handle { 205 font-weight: 500; 206 } 207 .dropdown-arrow { 208 font-size: 0.625rem; 209 color: var(--text-secondary); 210 } 211 .dropdown-menu { 212 position: absolute; 213 top: 100%; 214 right: 0; 215 margin-top: 0.25rem; 216 min-width: 200px; 217 background: var(--bg-card); 218 border: 1px solid var(--border-color); 219 border-radius: 8px; 220 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 221 z-index: 100; 222 overflow: hidden; 223 } 224 .dropdown-section { 225 padding: 0.5rem 0; 226 } 227 .dropdown-label { 228 display: block; 229 padding: 0.25rem 1rem; 230 font-size: 0.75rem; 231 color: var(--text-muted); 232 text-transform: uppercase; 233 letter-spacing: 0.05em; 234 } 235 .dropdown-item { 236 display: block; 237 width: 100%; 238 padding: 0.75rem 1rem; 239 background: transparent; 240 border: none; 241 text-align: left; 242 cursor: pointer; 243 color: var(--text-primary); 244 font-size: 0.875rem; 245 } 246 .dropdown-item:hover { 247 background: var(--bg-secondary); 248 } 249 .dropdown-item.logout-item { 250 color: var(--error-text); 251 } 252 .dropdown-divider { 253 height: 1px; 254 background: var(--border-color); 255 margin: 0; 256 } 257 section { 258 background: var(--bg-secondary); 259 padding: 1.5rem; 260 border-radius: 8px; 261 margin-bottom: 2rem; 262 } 263 section h2 { 264 margin: 0 0 1rem 0; 265 font-size: 1.25rem; 266 } 267 dl { 268 display: grid; 269 grid-template-columns: auto 1fr; 270 gap: 0.5rem 1rem; 271 margin: 0; 272 } 273 dt { 274 font-weight: 500; 275 color: var(--text-secondary); 276 } 277 dd { 278 margin: 0; 279 } 280 .mono { 281 font-family: monospace; 282 font-size: 0.875rem; 283 word-break: break-all; 284 } 285 .badge { 286 display: inline-block; 287 padding: 0.125rem 0.5rem; 288 border-radius: 4px; 289 font-size: 0.75rem; 290 margin-left: 0.5rem; 291 } 292 .badge.success { 293 background: var(--success-bg); 294 color: var(--success-text); 295 } 296 .badge.warning { 297 background: var(--warning-bg); 298 color: var(--warning-text); 299 } 300 .badge.admin { 301 background: var(--accent); 302 color: white; 303 } 304 .nav-grid { 305 display: grid; 306 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); 307 gap: 1rem; 308 } 309 .nav-card { 310 display: block; 311 padding: 1.5rem; 312 background: var(--bg-card); 313 border: 1px solid var(--border-color); 314 border-radius: 8px; 315 text-decoration: none; 316 color: inherit; 317 transition: border-color 0.15s, box-shadow 0.15s; 318 } 319 .nav-card:hover { 320 border-color: var(--accent); 321 box-shadow: 0 2px 8px rgba(77, 166, 255, 0.15); 322 } 323 .nav-card h3 { 324 margin: 0 0 0.5rem 0; 325 color: var(--accent); 326 } 327 .nav-card p { 328 margin: 0; 329 color: var(--text-secondary); 330 font-size: 0.875rem; 331 } 332 .nav-card.admin-card { 333 border-color: var(--accent); 334 background: linear-gradient(135deg, var(--bg-card) 0%, rgba(77, 166, 255, 0.05) 100%); 335 } 336 .nav-card.admin-card:hover { 337 box-shadow: 0 2px 12px rgba(77, 166, 255, 0.25); 338 } 339 .loading { 340 text-align: center; 341 padding: 4rem; 342 color: var(--text-secondary); 343 } 344</style>