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>