Our Personal Data Server from scratch! tranquil.farm
atproto pds rust postgresql fun oauth

refactor(frontend): delete RegisterPassword and UiTest routes #71

merged opened by oyster.cafe targeting main from refactor/frontend-deletions
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mhg53l5v2d22
-1084
Diff #2
-550
frontend/src/routes/RegisterPassword.svelte
··· 1 - <script lang="ts"> 2 - import { navigate, routes, getFullUrl } from '../lib/router.svelte' 3 - import { api, ApiError } from '../lib/api' 4 - import { _ } from '../lib/i18n' 5 - import { 6 - createRegistrationFlow, 7 - restoreRegistrationFlow, 8 - VerificationStep, 9 - KeyChoiceStep, 10 - DidDocStep, 11 - } from '../lib/registration' 12 - import AccountTypeSwitcher from '../components/AccountTypeSwitcher.svelte' 13 - import HandleInput from '../components/HandleInput.svelte' 14 - import { ensureRequestUri, getRequestUriFromUrl } from '../lib/oauth' 15 - 16 - let serverInfo = $state<{ 17 - availableUserDomains: string[] 18 - inviteCodeRequired: boolean 19 - availableCommsChannels?: string[] 20 - selfHostedDidWebEnabled?: boolean 21 - } | null>(null) 22 - let loadingServerInfo = $state(true) 23 - let serverInfoLoaded = false 24 - let ssoAvailable = $state(false) 25 - 26 - let flow = $state<ReturnType<typeof createRegistrationFlow> | null>(null) 27 - let confirmPassword = $state('') 28 - let clientName = $state<string | null>(null) 29 - let selectedDomain = $state('') 30 - let checkHandleTimeout: ReturnType<typeof setTimeout> | null = null 31 - 32 - $effect(() => { 33 - if (!flow) return 34 - const handle = flow.info.handle 35 - if (checkHandleTimeout) { 36 - clearTimeout(checkHandleTimeout) 37 - } 38 - if (handle.length >= 3 && !handle.includes('.')) { 39 - checkHandleTimeout = setTimeout(() => flow?.checkHandleAvailability(handle), 400) 40 - } 41 - }) 42 - 43 - $effect(() => { 44 - if (!serverInfoLoaded) { 45 - serverInfoLoaded = true 46 - ensureRequestUri().then((requestUri) => { 47 - if (!requestUri) return 48 - loadServerInfo() 49 - checkSsoAvailable() 50 - fetchClientName() 51 - }).catch((err) => { 52 - console.error('Failed to ensure OAuth request URI:', err) 53 - }) 54 - } 55 - }) 56 - 57 - async function fetchClientName() { 58 - const requestUri = getRequestUriFromUrl() 59 - if (!requestUri) return 60 - 61 - try { 62 - const response = await fetch(`/oauth/authorize?request_uri=${encodeURIComponent(requestUri)}`, { 63 - headers: { 'Accept': 'application/json' } 64 - }) 65 - if (response.ok) { 66 - const data = await response.json() 67 - clientName = data.client_name || null 68 - } 69 - } catch { 70 - clientName = null 71 - } 72 - } 73 - 74 - async function checkSsoAvailable() { 75 - try { 76 - const response = await fetch('/oauth/sso/providers') 77 - if (response.ok) { 78 - const data = await response.json() 79 - ssoAvailable = (data.providers?.length ?? 0) > 0 80 - } 81 - } catch { 82 - ssoAvailable = false 83 - } 84 - } 85 - 86 - $effect(() => { 87 - if (flow?.state.step === 'redirect-to-dashboard') { 88 - completeOAuthRegistration() 89 - } 90 - }) 91 - 92 - let creatingStarted = false 93 - $effect(() => { 94 - if (flow?.state.step === 'creating' && !creatingStarted) { 95 - creatingStarted = true 96 - flow.createPasswordAccount() 97 - } 98 - }) 99 - 100 - async function loadServerInfo() { 101 - try { 102 - const restored = restoreRegistrationFlow() 103 - if (restored && restored.state.mode === 'password') { 104 - flow = restored 105 - serverInfo = await api.describeServer() 106 - } else { 107 - serverInfo = await api.describeServer() 108 - const hostname = serverInfo?.availableUserDomains?.[0] || window.location.hostname 109 - flow = createRegistrationFlow('password', hostname) 110 - } 111 - selectedDomain = serverInfo?.availableUserDomains?.[0] || window.location.hostname 112 - if (flow) flow.setSelectedDomain(selectedDomain) 113 - } catch (e) { 114 - console.error('Failed to load server info:', e) 115 - } finally { 116 - loadingServerInfo = false 117 - } 118 - } 119 - 120 - function validateInfoStep(): string | null { 121 - if (!flow) return 'Flow not initialized' 122 - const info = flow.info 123 - if (!info.handle.trim()) return $_('register.validation.handleRequired') 124 - if (info.handle.includes('.')) return $_('register.validation.handleNoDots') 125 - if (!info.password) return $_('register.validation.passwordRequired') 126 - if (info.password.length < 8) return $_('register.validation.passwordLength') 127 - if (info.password !== confirmPassword) return $_('register.validation.passwordsMismatch') 128 - if (serverInfo?.inviteCodeRequired && !info.inviteCode?.trim()) { 129 - return $_('register.validation.inviteCodeRequired') 130 - } 131 - if (info.didType === 'web-external') { 132 - if (!info.externalDid?.trim()) return $_('register.validation.externalDidRequired') 133 - if (!info.externalDid.trim().startsWith('did:web:')) return $_('register.validation.externalDidFormat') 134 - } 135 - switch (info.verificationChannel) { 136 - case 'email': 137 - if (!info.email.trim()) return $_('register.validation.emailRequired') 138 - break 139 - case 'discord': 140 - if (!info.discordUsername?.trim()) return $_('register.validation.discordUsernameRequired') 141 - break 142 - case 'telegram': 143 - if (!info.telegramUsername?.trim()) return $_('register.validation.telegramRequired') 144 - break 145 - case 'signal': 146 - if (!info.signalUsername?.trim()) return $_('register.validation.signalRequired') 147 - break 148 - } 149 - return null 150 - } 151 - 152 - async function handleInfoSubmit(e: Event) { 153 - e.preventDefault() 154 - if (!flow) return 155 - 156 - const validationError = validateInfoStep() 157 - if (validationError) { 158 - flow.setError(validationError) 159 - return 160 - } 161 - 162 - flow.clearError() 163 - flow.proceedFromInfo() 164 - } 165 - 166 - async function handleCreateAccount() { 167 - if (!flow) return 168 - await flow.createPasswordAccount() 169 - } 170 - 171 - async function handleComplete() { 172 - if (flow) { 173 - await flow.finalizeSession() 174 - } 175 - navigate(routes.dashboard) 176 - } 177 - 178 - async function completeOAuthRegistration() { 179 - const requestUri = getRequestUriFromUrl() 180 - if (!requestUri || !flow?.account) { 181 - navigate(routes.dashboard) 182 - return 183 - } 184 - 185 - try { 186 - const response = await fetch('/oauth/register/complete', { 187 - method: 'POST', 188 - headers: { 189 - 'Content-Type': 'application/json', 190 - 'Accept': 'application/json', 191 - }, 192 - body: JSON.stringify({ 193 - request_uri: requestUri, 194 - did: flow.account.did, 195 - app_password: flow.account.appPassword || flow.info.password, 196 - }), 197 - }) 198 - 199 - const data = await response.json() 200 - 201 - if (!response.ok) { 202 - flow.setError(data.error_description || data.error || $_('common.error')) 203 - return 204 - } 205 - 206 - if (data.redirect_uri) { 207 - window.location.href = data.redirect_uri 208 - return 209 - } 210 - 211 - navigate(routes.dashboard) 212 - } catch (err) { 213 - console.error('OAuth registration completion failed:', err) 214 - flow.setError(err instanceof Error ? err.message : $_('common.error')) 215 - } 216 - } 217 - 218 - function isChannelAvailable(ch: string): boolean { 219 - const available = serverInfo?.availableCommsChannels ?? ['email'] 220 - return available.includes(ch) 221 - } 222 - 223 - function channelLabel(ch: string): string { 224 - switch (ch) { 225 - case 'email': return $_('register.email') 226 - case 'discord': return $_('register.discord') 227 - case 'telegram': return $_('register.telegram') 228 - case 'signal': return $_('register.signal') 229 - default: return ch 230 - } 231 - } 232 - 233 - let fullHandle = $derived(() => { 234 - if (!flow?.info.handle.trim()) return '' 235 - if (flow.info.handle.includes('.')) return flow.info.handle.trim() 236 - return selectedDomain ? `${flow.info.handle.trim()}.${selectedDomain}` : flow.info.handle.trim() 237 - }) 238 - 239 - function extractDomain(did: string): string { 240 - return did.replace('did:web:', '').replace(/%3A/g, ':') 241 - } 242 - 243 - async function handleCancel() { 244 - const requestUri = getRequestUriFromUrl() 245 - if (!requestUri) { 246 - window.history.back() 247 - return 248 - } 249 - 250 - try { 251 - const response = await fetch('/oauth/authorize/deny', { 252 - method: 'POST', 253 - headers: { 254 - 'Content-Type': 'application/json', 255 - 'Accept': 'application/json' 256 - }, 257 - body: JSON.stringify({ request_uri: requestUri }) 258 - }) 259 - 260 - if (!response.ok) { 261 - window.history.back() 262 - return 263 - } 264 - 265 - const data = await response.json() 266 - if (data.redirect_uri) { 267 - window.location.href = data.redirect_uri 268 - } else { 269 - window.history.back() 270 - } 271 - } catch { 272 - window.history.back() 273 - } 274 - } 275 - </script> 276 - 277 - <div class="page"> 278 - <header class="page-header"> 279 - <h1>{$_('register.title')}</h1> 280 - {#if clientName} 281 - <p class="subtitle">{$_('oauth.register.subtitle')} <strong>{clientName}</strong></p> 282 - {/if} 283 - </header> 284 - 285 - {#if flow?.state.error} 286 - <div class="message error">{flow.state.error}</div> 287 - {/if} 288 - 289 - {#if loadingServerInfo || !flow} 290 - <div class="loading"></div> 291 - {:else if flow.state.step === 'info'} 292 - <div class="migrate-callout"> 293 - <div class="migrate-icon">↗</div> 294 - <div class="migrate-content"> 295 - <strong>{$_('register.migrateTitle')}</strong> 296 - <p>{$_('register.migrateDescription')}</p> 297 - <a href={getFullUrl(routes.migrate)} class="migrate-link"> 298 - {$_('register.migrateLink')} → 299 - </a> 300 - </div> 301 - </div> 302 - 303 - <AccountTypeSwitcher active="password" {ssoAvailable} oauthRequestUri={getRequestUriFromUrl()} /> 304 - 305 - <form class="register-form" onsubmit={handleInfoSubmit}> 306 - <div> 307 - <label for="handle">{$_('register.handle')}</label> 308 - <HandleInput 309 - value={flow.info.handle} 310 - domains={serverInfo?.availableUserDomains ?? []} 311 - {selectedDomain} 312 - placeholder={$_('register.handlePlaceholder')} 313 - disabled={flow.state.submitting} 314 - onInput={(v) => { flow!.info.handle = v }} 315 - onDomainChange={(d) => { selectedDomain = d; flow!.setSelectedDomain(d) }} 316 - /> 317 - {#if flow.info.handle.includes('.')} 318 - <p class="hint warning">{$_('register.handleDotWarning')}</p> 319 - {:else if flow.state.checkingHandle} 320 - <p class="hint">{$_('common.checking')}</p> 321 - {:else if flow.state.handleAvailable === false} 322 - <p class="hint warning">{$_('register.handleTaken')}</p> 323 - {:else if flow.state.handleAvailable === true && fullHandle()} 324 - <p class="hint success">{$_('register.handleHint', { values: { handle: fullHandle() } })}</p> 325 - {:else if fullHandle()} 326 - <p class="hint">{$_('register.handleHint', { values: { handle: fullHandle() } })}</p> 327 - {/if} 328 - </div> 329 - 330 - <div> 331 - <label for="password">{$_('register.password')}</label> 332 - <input 333 - id="password" 334 - type="password" 335 - bind:value={flow.info.password} 336 - placeholder={$_('register.passwordPlaceholder')} 337 - disabled={flow.state.submitting} 338 - required 339 - minlength="8" 340 - /> 341 - </div> 342 - 343 - <div> 344 - <label for="confirm-password">{$_('register.confirmPassword')}</label> 345 - <input 346 - id="confirm-password" 347 - type="password" 348 - bind:value={confirmPassword} 349 - placeholder={$_('register.confirmPasswordPlaceholder')} 350 - disabled={flow.state.submitting} 351 - required 352 - /> 353 - </div> 354 - 355 - <div> 356 - <label for="verification-channel">{$_('register.verificationMethod')}</label> 357 - <select id="verification-channel" bind:value={flow.info.verificationChannel} disabled={flow.state.submitting}> 358 - <option value="email">{$_('register.email')}</option> 359 - {#if isChannelAvailable('discord')} 360 - <option value="discord">{$_('register.discord')}</option> 361 - {/if} 362 - {#if isChannelAvailable('telegram')} 363 - <option value="telegram">{$_('register.telegram')}</option> 364 - {/if} 365 - {#if isChannelAvailable('signal')} 366 - <option value="signal">{$_('register.signal')}</option> 367 - {/if} 368 - </select> 369 - </div> 370 - 371 - {#if flow.info.verificationChannel === 'email'} 372 - <div> 373 - <label for="email">{$_('register.emailAddress')}</label> 374 - <input 375 - id="email" 376 - type="email" 377 - bind:value={flow.info.email} 378 - placeholder={$_('register.emailPlaceholder')} 379 - disabled={flow.state.submitting} 380 - required 381 - /> 382 - </div> 383 - {:else if flow.info.verificationChannel === 'discord'} 384 - <div> 385 - <label for="discord-username">{$_('register.discordUsername')}</label> 386 - <input 387 - id="discord-username" 388 - type="text" 389 - bind:value={flow.info.discordUsername} 390 - onblur={() => flow?.checkCommsChannelInUse('discord', flow.info.discordUsername ?? '')} 391 - placeholder={$_('register.discordUsernamePlaceholder')} 392 - disabled={flow.state.submitting} 393 - required 394 - /> 395 - {#if flow.state.discordInUse} 396 - <p class="hint warning">{$_('register.discordInUseWarning')}</p> 397 - {/if} 398 - </div> 399 - {:else if flow.info.verificationChannel === 'telegram'} 400 - <div> 401 - <label for="telegram-username">{$_('register.telegramUsername')}</label> 402 - <input 403 - id="telegram-username" 404 - type="text" 405 - bind:value={flow.info.telegramUsername} 406 - onblur={() => flow?.checkCommsChannelInUse('telegram', flow.info.telegramUsername ?? '')} 407 - placeholder={$_('register.telegramUsernamePlaceholder')} 408 - disabled={flow.state.submitting} 409 - required 410 - /> 411 - {#if flow.state.telegramInUse} 412 - <p class="hint warning">{$_('register.telegramInUseWarning')}</p> 413 - {/if} 414 - </div> 415 - {:else if flow.info.verificationChannel === 'signal'} 416 - <div> 417 - <label for="signal-number">{$_('register.signalUsername')}</label> 418 - <input 419 - id="signal-number" 420 - type="tel" 421 - bind:value={flow.info.signalUsername} 422 - onblur={() => flow?.checkCommsChannelInUse('signal', flow.info.signalUsername ?? '')} 423 - placeholder={$_('register.signalUsernamePlaceholder')} 424 - disabled={flow.state.submitting} 425 - required 426 - /> 427 - <p class="hint">{$_('register.signalUsernameHint')}</p> 428 - {#if flow.state.signalInUse} 429 - <p class="hint warning">{$_('register.signalInUseWarning')}</p> 430 - {/if} 431 - </div> 432 - {/if} 433 - 434 - <fieldset class="identity-section"> 435 - <legend>{$_('register.identityType')}</legend> 436 - <div class="radio-group"> 437 - <label class="radio-label"> 438 - <input type="radio" name="didType" value="plc" bind:group={flow.info.didType} disabled={flow.state.submitting} /> 439 - <span class="radio-content"> 440 - <strong>{$_('register.didPlc')}</strong> 441 - <span class="radio-hint">{$_('register.didPlcHint')}</span> 442 - </span> 443 - </label> 444 - 445 - <label class="radio-label" class:disabled={serverInfo?.selfHostedDidWebEnabled === false}> 446 - <input type="radio" name="didType" value="web" bind:group={flow.info.didType} disabled={flow.state.submitting || serverInfo?.selfHostedDidWebEnabled === false} /> 447 - <span class="radio-content"> 448 - <strong>{$_('register.didWeb')}</strong> 449 - {#if serverInfo?.selfHostedDidWebEnabled === false} 450 - <span class="radio-hint disabled-hint">{$_('register.didWebDisabledHint')}</span> 451 - {:else} 452 - <span class="radio-hint">{$_('register.didWebHint')}</span> 453 - {/if} 454 - </span> 455 - </label> 456 - 457 - <label class="radio-label"> 458 - <input type="radio" name="didType" value="web-external" bind:group={flow.info.didType} disabled={flow.state.submitting} /> 459 - <span class="radio-content"> 460 - <strong>{$_('register.didWebBYOD')}</strong> 461 - <span class="radio-hint">{$_('register.didWebBYODHint')}</span> 462 - </span> 463 - </label> 464 - </div> 465 - </fieldset> 466 - 467 - {#if flow.info.didType === 'web'} 468 - <div class="warning-box"> 469 - <strong>{$_('register.didWebWarningTitle')}</strong> 470 - <ul> 471 - <li><strong>{$_('register.didWebWarning1')}</strong> {$_('register.didWebWarning1Detail', { values: { did: `did:web:yourhandle.${serverInfo?.availableUserDomains?.[0] || 'this-pds.com'}` } })}</li> 472 - <li><strong>{$_('register.didWebWarning2')}</strong> {$_('register.didWebWarning2Detail')}</li> 473 - {#if $_('register.didWebWarning3')} 474 - <li><strong>{$_('register.didWebWarning3')}</strong> {$_('register.didWebWarning3Detail')}</li> 475 - {/if} 476 - </ul> 477 - </div> 478 - {/if} 479 - 480 - {#if flow.info.didType === 'web-external'} 481 - <div> 482 - <label for="external-did">{$_('register.externalDid')}</label> 483 - <input 484 - id="external-did" 485 - type="text" 486 - bind:value={flow.info.externalDid} 487 - placeholder={$_('register.externalDidPlaceholder')} 488 - disabled={flow.state.submitting} 489 - required 490 - /> 491 - <p class="hint">{$_('register.externalDidHint')}</p> 492 - </div> 493 - {/if} 494 - 495 - {#if serverInfo?.inviteCodeRequired} 496 - <div> 497 - <label for="invite-code">{$_('register.inviteCode')}</label> 498 - <input 499 - id="invite-code" 500 - type="text" 501 - bind:value={flow.info.inviteCode} 502 - placeholder={$_('register.inviteCodePlaceholder')} 503 - disabled={flow.state.submitting} 504 - required 505 - /> 506 - </div> 507 - {/if} 508 - 509 - <div class="form-actions"> 510 - <button type="button" class="secondary" onclick={handleCancel} disabled={flow.state.submitting}> 511 - {$_('common.cancel')} 512 - </button> 513 - <button type="submit" class="primary" disabled={flow.state.submitting || flow.state.handleAvailable === false || flow.state.checkingHandle}> 514 - {flow.state.submitting ? $_('common.loading') : $_('common.continue')} 515 - </button> 516 - </div> 517 - </form> 518 - 519 - {:else if flow.state.step === 'key-choice'} 520 - <KeyChoiceStep {flow} /> 521 - 522 - {:else if flow.state.step === 'initial-did-doc'} 523 - <DidDocStep 524 - {flow} 525 - type="initial" 526 - onConfirm={handleCreateAccount} 527 - onBack={() => flow?.goBack()} 528 - /> 529 - 530 - {:else if flow.state.step === 'creating'} 531 - <div class="loading"> 532 - <p>{$_('common.creating')}</p> 533 - </div> 534 - 535 - {:else if flow.state.step === 'verify'} 536 - <VerificationStep {flow} /> 537 - 538 - {:else if flow.state.step === 'updated-did-doc'} 539 - <DidDocStep 540 - {flow} 541 - type="updated" 542 - onConfirm={() => flow?.activateAccount()} 543 - /> 544 - 545 - {:else if flow.state.step === 'redirect-to-dashboard'} 546 - <div class="loading"> 547 - <p>{$_('register.redirecting')}</p> 548 - </div> 549 - {/if} 550 - </div>
-534
frontend/src/routes/UiTest.svelte
··· 1 - <script lang="ts"> 2 - import { Button, Card, Input, Message, Page, Section } from '../components/ui' 3 - import Skeleton from '../components/Skeleton.svelte' 4 - import { toast } from '../lib/toast.svelte' 5 - import { getServerConfigState } from '../lib/serverConfig.svelte' 6 - import { _, locale, getSupportedLocales, localeNames, type SupportedLocale } from '../lib/i18n' 7 - 8 - let inputValue = $state('') 9 - let inputError = $state('') 10 - let inputDisabled = $state('') 11 - 12 - const serverConfig = getServerConfigState() 13 - 14 - const LIGHT_ACCENT_DEFAULT = '#1a1d1d' 15 - const DARK_ACCENT_DEFAULT = '#e6e8e8' 16 - const LIGHT_SECONDARY_DEFAULT = '#1a1d1d' 17 - const DARK_SECONDARY_DEFAULT = '#e6e8e8' 18 - 19 - let accentLight = $state(LIGHT_ACCENT_DEFAULT) 20 - let accentDark = $state(DARK_ACCENT_DEFAULT) 21 - let secondaryLight = $state(LIGHT_SECONDARY_DEFAULT) 22 - let secondaryDark = $state(DARK_SECONDARY_DEFAULT) 23 - 24 - $effect(() => { 25 - accentLight = serverConfig.primaryColor || LIGHT_ACCENT_DEFAULT 26 - accentDark = serverConfig.primaryColorDark || DARK_ACCENT_DEFAULT 27 - secondaryLight = serverConfig.secondaryColor || LIGHT_SECONDARY_DEFAULT 28 - secondaryDark = serverConfig.secondaryColorDark || DARK_SECONDARY_DEFAULT 29 - }) 30 - 31 - const isDark = $derived( 32 - typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches 33 - ) 34 - 35 - function applyColor(prop: string, value: string): void { 36 - document.documentElement.style.setProperty(prop, value) 37 - } 38 - 39 - $effect(() => { 40 - applyColor('--accent', isDark ? accentDark : accentLight) 41 - }) 42 - 43 - $effect(() => { 44 - applyColor('--secondary', isDark ? secondaryDark : secondaryLight) 45 - }) 46 - </script> 47 - 48 - <Page title="UI Test" size="lg"> 49 - <Section title="Theme"> 50 - <div class="form-row"> 51 - <div class="field"> 52 - <label for="accent-light">Accent (light)</label> 53 - <div class="color-pair"> 54 - <input type="color" bind:value={accentLight} /> 55 - <input id="accent-light" type="text" class="mono" bind:value={accentLight} /> 56 - </div> 57 - </div> 58 - <div class="field"> 59 - <label for="accent-dark">Accent (dark)</label> 60 - <div class="color-pair"> 61 - <input type="color" bind:value={accentDark} /> 62 - <input id="accent-dark" type="text" class="mono" bind:value={accentDark} /> 63 - </div> 64 - </div> 65 - <div class="field"> 66 - <label for="secondary-light">Secondary (light)</label> 67 - <div class="color-pair"> 68 - <input type="color" bind:value={secondaryLight} /> 69 - <input id="secondary-light" type="text" class="mono" bind:value={secondaryLight} /> 70 - </div> 71 - </div> 72 - <div class="field"> 73 - <label for="secondary-dark">Secondary (dark)</label> 74 - <div class="color-pair"> 75 - <input type="color" bind:value={secondaryDark} /> 76 - <input id="secondary-dark" type="text" class="mono" bind:value={secondaryDark} /> 77 - </div> 78 - </div> 79 - <div class="field"> 80 - <label for="locale-picker">Locale</label> 81 - <select id="locale-picker" value={$locale} onchange={(e) => locale.set(e.currentTarget.value)}> 82 - {#each getSupportedLocales() as loc} 83 - <option value={loc}>{localeNames[loc]} ({loc})</option> 84 - {/each} 85 - </select> 86 - </div> 87 - </div> 88 - </Section> 89 - 90 - <Section title="Typography"> 91 - <p style="font-size: var(--text-4xl)">4xl (2.5rem)</p> 92 - <p style="font-size: var(--text-3xl)">3xl (2rem)</p> 93 - <p style="font-size: var(--text-2xl)">2xl (1.5rem)</p> 94 - <p style="font-size: var(--text-xl)">xl (1.25rem)</p> 95 - <p style="font-size: var(--text-lg)">lg (1.125rem)</p> 96 - <p style="font-size: var(--text-base)">base (1rem)</p> 97 - <p style="font-size: var(--text-sm)">sm (0.875rem)</p> 98 - <p style="font-size: var(--text-xs)">xs (0.75rem)</p> 99 - <hr /> 100 - <p style="font-weight: var(--font-normal)">Normal (400)</p> 101 - <p style="font-weight: var(--font-medium)">Medium (500)</p> 102 - <p style="font-weight: var(--font-semibold)">Semibold (600)</p> 103 - <p style="font-weight: var(--font-bold)">Bold (700)</p> 104 - <hr /> 105 - <code>Monospace text</code> 106 - <pre>Pre block 107 - indented</pre> 108 - </Section> 109 - 110 - <Section title="Colors"> 111 - <div class="form-row"> 112 - <div> 113 - <h4>Backgrounds</h4> 114 - <div class="swatch" style="background: var(--bg-primary)">bg-primary</div> 115 - <div class="swatch" style="background: var(--bg-secondary)">bg-secondary</div> 116 - <div class="swatch" style="background: var(--bg-tertiary)">bg-tertiary</div> 117 - <div class="swatch" style="background: var(--bg-card)">bg-card</div> 118 - <div class="swatch" style="background: var(--bg-input)">bg-input</div> 119 - </div> 120 - <div> 121 - <h4>Text</h4> 122 - <p style="color: var(--text-primary)">text-primary</p> 123 - <p style="color: var(--text-secondary)">text-secondary</p> 124 - <p style="color: var(--text-muted)">text-muted</p> 125 - <div class="swatch" style="background: var(--accent); color: var(--text-inverse)">text-inverse</div> 126 - </div> 127 - <div> 128 - <h4>Accent</h4> 129 - <div class="swatch" style="background: var(--accent); color: var(--text-inverse)">accent</div> 130 - <div class="swatch" style="background: var(--accent-hover); color: var(--text-inverse)">accent-hover</div> 131 - <div class="swatch" style="background: var(--accent-muted)">accent-muted</div> 132 - </div> 133 - <div> 134 - <h4>Status</h4> 135 - <div class="swatch" style="background: var(--success-bg); color: var(--success-text)">success</div> 136 - <div class="swatch" style="background: var(--error-bg); color: var(--error-text)">error</div> 137 - <div class="swatch" style="background: var(--warning-bg); color: var(--warning-text)">warning</div> 138 - </div> 139 - </div> 140 - </Section> 141 - 142 - <Section title="Spacing"> 143 - <div class="spacing-row"> 144 - {#each [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] as i} 145 - <div class="spacing-item"> 146 - <div class="spacing-box" style="width: var(--space-{i}); height: var(--space-{i})"></div> 147 - <span class="text-xs text-muted">--space-{i}</span> 148 - </div> 149 - {/each} 150 - </div> 151 - </Section> 152 - 153 - <Section title="Buttons"> 154 - <p> 155 - <Button variant="primary">{$_('common.save')}</Button> 156 - <Button variant="secondary">{$_('common.cancel')}</Button> 157 - <Button variant="tertiary">{$_('common.back')}</Button> 158 - <Button variant="danger">{$_('common.delete')}</Button> 159 - <Button variant="ghost">{$_('common.refresh')}</Button> 160 - </p> 161 - <p class="mt-5"> 162 - <Button size="sm">{$_('common.verify')}</Button> 163 - <Button size="md">{$_('common.continue')}</Button> 164 - <Button size="lg">{$_('common.signIn')}</Button> 165 - </p> 166 - <p class="mt-5"> 167 - <Button disabled>{$_('common.save')}</Button> 168 - <Button loading>{$_('common.saving')}</Button> 169 - <Button variant="secondary" disabled>{$_('common.cancel')}</Button> 170 - <Button variant="danger" loading>{$_('common.delete')}</Button> 171 - </p> 172 - <p class="mt-5"> 173 - <button class="danger-outline">{$_('common.revoke')}</button> 174 - <button class="link">{$_('login.forgotPassword')}</button> 175 - <button class="sm">{$_('common.done')}</button> 176 - </p> 177 - <div class="mt-5"> 178 - <Button fullWidth>{$_('common.signIn')}</Button> 179 - </div> 180 - </Section> 181 - 182 - <Section title="Inputs"> 183 - <div class="form-row"> 184 - <div class="field"> 185 - <Input label={$_('settings.newEmail')} placeholder={$_('settings.newEmailPlaceholder')} bind:value={inputValue} /> 186 - </div> 187 - <div class="field"> 188 - <Input label={$_('security.passkeyName')} placeholder={$_('security.passkeyNamePlaceholder')} hint={$_('appPasswords.createdMessage')} /> 189 - </div> 190 - </div> 191 - <div class="form-row"> 192 - <div class="field"> 193 - <Input label={$_('verification.codeLabel')} placeholder={$_('verification.codePlaceholder')} error={$_('common.error')} bind:value={inputError} /> 194 - </div> 195 - <div class="field"> 196 - <Input label={$_('settings.yourDomain')} placeholder={$_('settings.yourDomainPlaceholder')} disabled bind:value={inputDisabled} /> 197 - </div> 198 - </div> 199 - <div class="form-row mt-5"> 200 - <div class="field"> 201 - <label for="demo-select">{$_('settings.language')}</label> 202 - <select id="demo-select"> 203 - {#each getSupportedLocales() as loc} 204 - <option>{localeNames[loc]}</option> 205 - {/each} 206 - </select> 207 - </div> 208 - <div class="field"> 209 - <label for="demo-textarea">{$_('settings.exportData')}</label> 210 - <textarea id="demo-textarea" rows="3"></textarea> 211 - </div> 212 - </div> 213 - </Section> 214 - 215 - <Section title="Cards"> 216 - <Card> 217 - <h4>{$_('settings.exportData')}</h4> 218 - <p class="text-secondary text-sm">{$_('settings.downloadRepo')}</p> 219 - </Card> 220 - <div class="mt-4"> 221 - <Card variant="interactive"> 222 - <h4>{$_('sessions.session')}</h4> 223 - <p class="text-secondary text-sm">{$_('sessions.current')}</p> 224 - </Card> 225 - </div> 226 - <div class="mt-4"> 227 - <Card variant="danger"> 228 - <h4>{$_('security.removePassword')}</h4> 229 - <p class="text-secondary text-sm">{$_('security.removePasswordWarning')}</p> 230 - </Card> 231 - </div> 232 - </Section> 233 - 234 - <Section title="Sections"> 235 - <Section title="Default section" description="With a description"> 236 - <p>Section content</p> 237 - </Section> 238 - <div class="mt-5"> 239 - <Section title="Danger section" variant="danger"> 240 - <p>Destructive operations</p> 241 - </Section> 242 - </div> 243 - </Section> 244 - 245 - <Section title="Messages"> 246 - <Message variant="success">{$_('appPasswords.deleted')}</Message> 247 - <div class="mt-4"><Message variant="error">{$_('appPasswords.createFailed')}</Message></div> 248 - <div class="mt-4"><Message variant="warning">{$_('security.legacyLoginWarning')}</Message></div> 249 - <div class="mt-4"><Message variant="info">{$_('appPasswords.createdMessage')}</Message></div> 250 - </Section> 251 - 252 - <Section title="Badges"> 253 - <p> 254 - <span class="badge success">{$_('inviteCodes.available')}</span> 255 - <span class="badge warning">{$_('inviteCodes.spent')}</span> 256 - <span class="badge error">{$_('inviteCodes.disabled')}</span> 257 - <span class="badge accent">{$_('sessions.current')}</span> 258 - </p> 259 - </Section> 260 - 261 - <Section title="Toasts"> 262 - <p> 263 - <Button variant="secondary" onclick={() => toast.success($_('appPasswords.deleted'))}>{$_('appPasswords.deleted')}</Button> 264 - <Button variant="secondary" onclick={() => toast.error($_('appPasswords.createFailed'))}>{$_('appPasswords.createFailed')}</Button> 265 - <Button variant="secondary" onclick={() => toast.warning($_('security.disableTotpWarning'))}>{$_('security.disableTotpWarning')}</Button> 266 - <Button variant="secondary" onclick={() => toast.info($_('appPasswords.createdMessage'))}>{$_('appPasswords.createdMessage')}</Button> 267 - </p> 268 - </Section> 269 - 270 - <Section title="Skeleton loading"> 271 - <Skeleton variant="line" size="full" /> 272 - <Skeleton variant="line" size="medium" /> 273 - <Skeleton variant="line" size="short" /> 274 - <Skeleton variant="line" size="tiny" /> 275 - <div class="mt-5"> 276 - <Skeleton variant="line" lines={3} /> 277 - </div> 278 - <div class="mt-5"> 279 - <Skeleton variant="card" lines={2} /> 280 - </div> 281 - </Section> 282 - 283 - <Section title="Fieldset"> 284 - <fieldset> 285 - <legend>Account settings</legend> 286 - <div class="field"> 287 - <label for="demo-display-name">Display name</label> 288 - <input id="demo-display-name" type="text" placeholder="Name" /> 289 - </div> 290 - </fieldset> 291 - </Section> 292 - 293 - <Section title="Form layouts"> 294 - <h4>Two column</h4> 295 - <div class="form-row"> 296 - <div class="field"> 297 - <label for="demo-fname">First name</label> 298 - <input id="demo-fname" type="text" /> 299 - </div> 300 - <div class="field"> 301 - <label for="demo-lname">Last name</label> 302 - <input id="demo-lname" type="text" /> 303 - </div> 304 - </div> 305 - <h4 class="mt-5">Three column</h4> 306 - <div class="form-row thirds"> 307 - <div class="field"> 308 - <label for="demo-city">City</label> 309 - <input id="demo-city" type="text" /> 310 - </div> 311 - <div class="field"> 312 - <label for="demo-state">State</label> 313 - <input id="demo-state" type="text" /> 314 - </div> 315 - <div class="field"> 316 - <label for="demo-zip">ZIP</label> 317 - <input id="demo-zip" type="text" /> 318 - </div> 319 - </div> 320 - <h4 class="mt-5">Full width in row</h4> 321 - <div class="form-row"> 322 - <div class="field"> 323 - <label for="demo-handle">Handle</label> 324 - <input id="demo-handle" type="text" /> 325 - </div> 326 - <div class="field"> 327 - <label for="demo-domain">Domain</label> 328 - <select id="demo-domain"><option>example.com</option></select> 329 - </div> 330 - <div class="field full-width"> 331 - <label for="demo-bio">Bio</label> 332 - <textarea id="demo-bio" rows="2"></textarea> 333 - </div> 334 - </div> 335 - </Section> 336 - 337 - <Section title="Hints"> 338 - <div class="field"> 339 - <label for="demo-hint">With hints</label> 340 - <input id="demo-hint" type="text" /> 341 - <span class="hint">Default hint</span> 342 - </div> 343 - <div class="field"> 344 - <input type="text" /> 345 - <span class="hint warning">Warning hint</span> 346 - </div> 347 - <div class="field"> 348 - <input type="text" /> 349 - <span class="hint error">Error hint</span> 350 - </div> 351 - <div class="field"> 352 - <input type="text" /> 353 - <span class="hint success">Success hint</span> 354 - </div> 355 - </Section> 356 - 357 - <Section title="Radio group"> 358 - <div class="radio-group"> 359 - <label class="radio-label"> 360 - <input type="radio" name="demo-radio" checked /> 361 - <div class="radio-content"> 362 - <span>Option A</span> 363 - <span class="radio-hint">First choice</span> 364 - </div> 365 - </label> 366 - <label class="radio-label"> 367 - <input type="radio" name="demo-radio" /> 368 - <div class="radio-content"> 369 - <span>Option B</span> 370 - <span class="radio-hint">Second choice</span> 371 - </div> 372 - </label> 373 - <label class="radio-label disabled"> 374 - <input type="radio" name="demo-radio" disabled /> 375 - <div class="radio-content"> 376 - <span>Option C</span> 377 - <span class="radio-hint disabled-hint">Unavailable</span> 378 - </div> 379 - </label> 380 - </div> 381 - </Section> 382 - 383 - <Section title="Checkbox"> 384 - <label class="checkbox-label"> 385 - <input type="checkbox" /> 386 - <span>{$_('appPasswords.acknowledgeLabel')}</span> 387 - </label> 388 - </Section> 389 - 390 - <Section title="Warning box"> 391 - <div class="warning-box"> 392 - <strong>{$_('appPasswords.saveWarningTitle')}</strong> 393 - <p>{$_('appPasswords.saveWarningMessage')}</p> 394 - </div> 395 - </Section> 396 - 397 - <Section title="Split layout"> 398 - <div class="split-layout"> 399 - <Card> 400 - <h4>Main content</h4> 401 - <p class="text-secondary">Primary area with a form or data</p> 402 - <div class="field mt-5"> 403 - <label for="demo-example">Example field</label> 404 - <input id="demo-example" type="text" placeholder="Value" /> 405 - </div> 406 - </Card> 407 - <div class="info-panel"> 408 - <h3>Sidebar</h3> 409 - <p>Supplementary information placed alongside the main content area.</p> 410 - <ul class="info-list"> 411 - <li>Supports DID methods: did:web, did:plc</li> 412 - <li>Maximum blob size: 10MB</li> 413 - <li>Rate limit: 100 requests per minute</li> 414 - </ul> 415 - </div> 416 - </div> 417 - </Section> 418 - 419 - <Section title="Composite: card with form"> 420 - <Card> 421 - <h4>{$_('appPasswords.create')}</h4> 422 - <p class="text-secondary text-sm mb-5">{$_('appPasswords.permissions')}</p> 423 - <div class="field"> 424 - <Input label={$_('appPasswords.name')} placeholder={$_('appPasswords.namePlaceholder')} /> 425 - </div> 426 - <div class="mt-5" style="display: flex; gap: var(--space-3); justify-content: flex-end"> 427 - <Button variant="tertiary">{$_('common.cancel')}</Button> 428 - <Button>{$_('appPasswords.create')}</Button> 429 - </div> 430 - </Card> 431 - </Section> 432 - 433 - <Section title="Composite: section with actions"> 434 - <Section title={$_('security.passkeys')}> 435 - <div style="display: flex; justify-content: space-between; align-items: center"> 436 - <div> 437 - <strong>{$_('security.totp')}</strong> 438 - <p class="text-sm text-secondary">{$_('security.totpEnabled')}</p> 439 - </div> 440 - <Button variant="secondary" size="sm">{$_('security.disableTotp')}</Button> 441 - </div> 442 - <hr /> 443 - <div style="display: flex; justify-content: space-between; align-items: center"> 444 - <div> 445 - <strong>{$_('security.passkeys')}</strong> 446 - <p class="text-sm text-secondary">{$_('security.noPasskeys')}</p> 447 - </div> 448 - <Button variant="secondary" size="sm">{$_('security.addPasskey')}</Button> 449 - </div> 450 - </Section> 451 - </Section> 452 - 453 - <Section title="Composite: error state"> 454 - <div class="error-container"> 455 - <div class="error-icon">!</div> 456 - <h2>Authorization failed</h2> 457 - <p>The requested scope exceeds the granted permissions.</p> 458 - <Button variant="secondary">Back</Button> 459 - </div> 460 - </Section> 461 - 462 - <Section title="Item list"> 463 - <div class="item"> 464 - <div class="item-info"> 465 - <strong>Primary passkey</strong> 466 - <span class="text-sm text-secondary">Created 2024-01-15</span> 467 - </div> 468 - <div class="item-actions"> 469 - <button class="sm">Rename</button> 470 - <button class="sm danger-outline">Revoke</button> 471 - </div> 472 - </div> 473 - <div class="item"> 474 - <div class="item-info"> 475 - <strong>Backup passkey</strong> 476 - <span class="text-sm text-secondary">Created 2024-03-20</span> 477 - </div> 478 - <div class="item-actions"> 479 - <button class="sm">Rename</button> 480 - <button class="sm danger-outline">Revoke</button> 481 - </div> 482 - </div> 483 - <div class="item"> 484 - <div class="item-info"> 485 - <strong>Work laptop</strong> 486 - <span class="text-sm text-secondary">Created 2024-06-01</span> 487 - </div> 488 - <div class="item-actions"> 489 - <button class="sm danger-outline">Revoke</button> 490 - </div> 491 - </div> 492 - </Section> 493 - 494 - <Section title="Definition list"> 495 - <dl class="definition-list"> 496 - <dt>Handle</dt> 497 - <dd>@alice.example.com</dd> 498 - <dt>DID</dt> 499 - <dd class="mono">did:web:alice.example.com</dd> 500 - <dt>Email</dt> 501 - <dd>alice@example.com</dd> 502 - <dt>Created</dt> 503 - <dd>2024-01-15</dd> 504 - <dt>Status</dt> 505 - <dd><span class="badge success">Verified</span></dd> 506 - </dl> 507 - </Section> 508 - 509 - <Section title="Tabs"> 510 - <div class="tabs"> 511 - <button class="tab active">PDS handle</button> 512 - <button class="tab">Custom domain</button> 513 - </div> 514 - <p class="text-secondary">Tab content appears here</p> 515 - </Section> 516 - 517 - <Section title="Inline form"> 518 - <div class="inline-form"> 519 - <h4>Change password</h4> 520 - <div class="field"> 521 - <label for="demo-current-pw">Current password</label> 522 - <input id="demo-current-pw" type="password" /> 523 - </div> 524 - <div class="field"> 525 - <label for="demo-new-pw">New password</label> 526 - <input id="demo-new-pw" type="password" /> 527 - </div> 528 - <div style="display: flex; gap: var(--space-3); justify-content: flex-end"> 529 - <Button variant="secondary">Cancel</Button> 530 - <Button>Save</Button> 531 - </div> 532 - </div> 533 - </Section> 534 - </Page>

History

3 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
refactor(frontend): delete RegisterPassword and UiTest routes
expand 0 comments
pull request successfully merged
1 commit
expand
refactor(frontend): delete RegisterPassword and UiTest routes
expand 0 comments
1 commit
expand
refactor(frontend): delete RegisterPassword and UiTest routes
expand 0 comments