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

feat(frontend): design tokens, app wiring, locales #55

merged opened by oyster.cafe targeting main from refactor/extract-scoped-styles
Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:3fwecdnvtcscjnrx2p4n7alz/sh.tangled.repo.pull/3mhdc5fbtbh22
+47 -100
Diff #0
-6
frontend/index.html
··· 4 4 <meta charset="UTF-8" /> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 6 <title>Tranquil PDS</title> 7 - <link rel="preconnect" href="https://fonts.googleapis.com"> 8 - <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 9 - <link 10 - href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" 11 - rel="stylesheet" 12 - > 13 7 <style> 14 8 html { 15 9 background: #f9fafa;
+4 -49
frontend/src/App.svelte
··· 25 25 import RegisterPassword from './routes/RegisterPassword.svelte' 26 26 import ActAs from './routes/ActAs.svelte' 27 27 import Migration from './routes/Migration.svelte' 28 + import UiTest from './routes/UiTest.svelte' 28 29 import { _ } from './lib/i18n' 29 30 initI18n() 30 31 31 32 const auth = $derived(getAuthState()) 32 33 33 34 let oauthCallbackPending = $state(hasOAuthCallback()) 34 - let showSpinner = $state(false) 35 - let loadingTimer: ReturnType<typeof setTimeout> | null = null 36 35 37 36 function hasOAuthCallback(): boolean { 38 37 if (window.location.pathname === '/app/migrate') { ··· 43 42 } 44 43 45 44 $effect(() => { 46 - loadingTimer = setTimeout(() => { 47 - showSpinner = true 48 - }, 5000) 49 - 50 45 initServerConfig() 51 46 initAuth().then(({ oauthLoginCompleted }) => { 52 47 if (oauthLoginCompleted) { 53 48 navigate('/dashboard', { replace: true }) 54 49 } 55 50 oauthCallbackPending = false 56 - if (loadingTimer) { 57 - clearTimeout(loadingTimer) 58 - loadingTimer = null 59 - } 60 51 }) 61 - 62 - return () => { 63 - if (loadingTimer) { 64 - clearTimeout(loadingTimer) 65 - } 66 - } 67 52 }) 68 53 69 54 const isLoading = $derived( ··· 142 127 return ActAs 143 128 case '/migrate': 144 129 return Migration 130 + case '/ui-test': 131 + return UiTest 145 132 default: 146 133 return Login 147 134 } ··· 154 141 155 142 <main> 156 143 {#if isLoading} 157 - <div class="loading"> 158 - {#if showSpinner} 159 - <div class="loading-content"> 160 - <div class="spinner"></div> 161 - <p>{$_('common.loading')}</p> 162 - </div> 163 - {/if} 164 - </div> 144 + <div class="loading"></div> 165 145 {:else} 166 146 <CurrentComponent /> 167 147 {/if} 168 148 </main> 169 149 <Toast /> 170 - 171 - <style> 172 - main { 173 - min-height: 100vh; 174 - } 175 - 176 - .loading { 177 - min-height: 100vh; 178 - display: flex; 179 - align-items: center; 180 - justify-content: center; 181 - } 182 - 183 - .loading-content { 184 - display: flex; 185 - flex-direction: column; 186 - align-items: center; 187 - gap: var(--space-4); 188 - } 189 - 190 - .loading-content p { 191 - margin: 0; 192 - color: var(--text-secondary); 193 - } 194 - </style>
+1 -9
frontend/src/lib/toast.svelte.ts
··· 5 5 type: ToastType; 6 6 message: string; 7 7 duration: number; 8 - dismissing?: boolean; 9 8 } 10 9 11 10 let nextId = 0; ··· 33 32 } 34 33 35 34 export function dismissToast(id: number): void { 36 - const toast = toasts.find((t) => t.id === id); 37 - if (!toast || toast.dismissing) return; 38 - 39 - toasts = toasts.map((t) => t.id === id ? { ...t, dismissing: true } : t); 40 - 41 - setTimeout(() => { 42 - toasts = toasts.filter((t) => t.id !== id); 43 - }, 150); 35 + toasts = toasts.filter((t) => t.id !== id); 44 36 } 45 37 46 38 export function clearAllToasts(): void {
+4 -4
frontend/src/locales/en.json
··· 266 266 "appPasswords": { 267 267 "create": "Create", 268 268 "name": "Name", 269 - "namePlaceholder": "App name (eg., Graysky)", 269 + "namePlaceholder": "App name (eg. Witchsky)", 270 270 "created": "App Password Created", 271 271 "createdMessage": "Copy this password now. You won't be able to see it again.", 272 272 "noPasswords": "No app passwords yet", ··· 334 334 "adding": "Adding...", 335 335 "noPasskeys": "No passkeys registered", 336 336 "passkeyName": "Passkey name", 337 - "passkeyNamePlaceholder": "eg., MacBook Pro, iPhone", 337 + "passkeyNamePlaceholder": "eg. yubikey, phone", 338 338 "rename": "Rename", 339 339 "deletePasskey": "Delete", 340 340 "deletePasskeyConfirm": "Delete passkey \"{name}\"? You won't be able to use it to sign in anymore.", ··· 762 762 "externalDidPlaceholder": "did:web:yourdomain.com", 763 763 "externalDidHint": "Serve DID document at", 764 764 "passkeyName": "Passkey Name", 765 - "passkeyNamePlaceholder": "MacBook Touch ID", 765 + "passkeyNamePlaceholder": "eg. yubikey, phone", 766 766 "passkeyNameHint": "Optional identifier", 767 767 "setupPasskey": "Create Passkey", 768 768 "passkeyDescription": "Register a passkey for this account", ··· 1038 1038 "title": "Set Up Your Passkey", 1039 1039 "desc": "Your email has been verified. Now set up your passkey for secure, passwordless login.", 1040 1040 "nameLabel": "Passkey Name (optional)", 1041 - "namePlaceholder": "eg., MacBook Pro, iPhone", 1041 + "namePlaceholder": "eg. yubikey, phone", 1042 1042 "nameHint": "A friendly name to identify this passkey", 1043 1043 "instructions": "Click the button below to register your passkey. Your device will prompt you to use biometrics (fingerprint, Face ID) or a security key.", 1044 1044 "register": "Register Passkey",
+4 -4
frontend/src/locales/fi.json
··· 278 278 "byController": "Hallinnoijan luoma", 279 279 "create": "Luo", 280 280 "name": "Nimi", 281 - "namePlaceholder": "Sovelluksen nimi (esim. Graysky)", 281 + "namePlaceholder": "Sovelluksen nimi (esim. Witchsky)", 282 282 "deleteConfirm": "Peruuta sovelluksen salasana \"{name}\"?", 283 283 "deleted": "Sovelluksen salasana peruutettu", 284 284 "loadFailed": "Sovellusten salasanojen lataus epäonnistui", ··· 334 334 "adding": "Lisätään...", 335 335 "noPasskeys": "Ei rekisteröityjä pääsyavaimia", 336 336 "passkeyName": "Pääsyavaimen nimi", 337 - "passkeyNamePlaceholder": "esim. MacBook Pro, iPhone", 337 + "passkeyNamePlaceholder": "esim. yubikey, puhelin", 338 338 "rename": "Nimeä uudelleen", 339 339 "deletePasskey": "Poista", 340 340 "deletePasskeyConfirm": "Poista pääsyavain \"{name}\"? Et voi enää käyttää sitä kirjautumiseen.", ··· 758 758 }, 759 759 "identityType": "Identiteettityyppi", 760 760 "identityTypeHint": "Valitse, miten hajautettua identiteettiäsi hallitaan.", 761 - "passkeyNamePlaceholder": "esim. MacBook Touch ID", 761 + "passkeyNamePlaceholder": "esim. yubikey, puhelin", 762 762 "passkeyNameHint": "Ystävällinen nimi tämän pääsyavaimen tunnistamiseksi", 763 763 "createPasskey": "Luo pääsyavain", 764 764 "didPlcRecommended": "did:plc (Suositeltava)", ··· 1037 1037 "title": "Määritä pääsyavaimesi", 1038 1038 "desc": "Sähköpostisi on vahvistettu. Määritä nyt pääsyavaimesi turvallista, salasanatonta kirjautumista varten.", 1039 1039 "nameLabel": "Pääsyavaimen nimi (valinnainen)", 1040 - "namePlaceholder": "esim. MacBook Pro, iPhone", 1040 + "namePlaceholder": "esim. yubikey, puhelin", 1041 1041 "nameHint": "Kutsumanimi tämän pääsyavaimen tunnistamiseen", 1042 1042 "instructions": "Klikkaa alla olevaa painiketta rekisteröidäksesi pääsyavaimesi. Laitteesi pyytää käyttämään biometriikkaa (sormenjälki, Face ID) tai suojausavainta.", 1043 1043 "register": "Rekisteröi pääsyavain",
+4 -4
frontend/src/locales/ja.json
··· 278 278 "byController": "管理者作成", 279 279 "create": "作成", 280 280 "name": "名前", 281 - "namePlaceholder": "アプリ名(例:Graysky)", 281 + "namePlaceholder": "アプリ名(例:Witchsky)", 282 282 "deleteConfirm": "アプリパスワード「{name}」を取り消しますか?", 283 283 "deleted": "アプリパスワードを取り消しました", 284 284 "loadFailed": "アプリパスワードの読み込みに失敗しました", ··· 334 334 "adding": "追加中...", 335 335 "noPasskeys": "登録されたパスキーはありません", 336 336 "passkeyName": "パスキー名", 337 - "passkeyNamePlaceholder": "例: MacBook Pro、iPhone", 337 + "passkeyNamePlaceholder": "例: yubikey、スマホ", 338 338 "rename": "名前変更", 339 339 "deletePasskey": "削除", 340 340 "deletePasskeyConfirm": "パスキー「{name}」を削除しますか?サインインに使用できなくなります。", ··· 758 758 }, 759 759 "identityType": "アイデンティティタイプ", 760 760 "identityTypeHint": "分散型アイデンティティの管理方法を選択してください。", 761 - "passkeyNamePlaceholder": "例:MacBook Touch ID", 761 + "passkeyNamePlaceholder": "例: yubikey、スマホ", 762 762 "passkeyNameHint": "このパスキーを識別するための名前", 763 763 "createPasskey": "パスキーを作成", 764 764 "didPlcRecommended": "did:plc(推奨)", ··· 1037 1037 "title": "パスキーを設定", 1038 1038 "desc": "メールが確認されました。安全なパスワードレスログインのためにパスキーを設定してください。", 1039 1039 "nameLabel": "パスキー名(任意)", 1040 - "namePlaceholder": "例:MacBook Pro、iPhone", 1040 + "namePlaceholder": "例: yubikey、スマホ", 1041 1041 "nameHint": "このパスキーを識別するためのわかりやすい名前", 1042 1042 "instructions": "下のボタンをクリックしてパスキーを登録してください。デバイスが生体認証(指紋、Face ID)またはセキュリティキーの使用を促します。", 1043 1043 "register": "パスキーを登録",
+4 -4
frontend/src/locales/ko.json
··· 278 278 "byController": "컨트롤러 생성", 279 279 "create": "생성", 280 280 "name": "이름", 281 - "namePlaceholder": "앱 이름 (예: Graysky)", 281 + "namePlaceholder": "앱 이름 (예: Witchsky)", 282 282 "deleteConfirm": "앱 비밀번호 \"{name}\"을(를) 취소하시겠습니까?", 283 283 "deleted": "앱 비밀번호가 취소되었습니다", 284 284 "loadFailed": "앱 비밀번호 로딩 실패", ··· 334 334 "adding": "추가 중...", 335 335 "noPasskeys": "등록된 패스키가 없습니다", 336 336 "passkeyName": "패스키 이름", 337 - "passkeyNamePlaceholder": "예: MacBook Pro, iPhone", 337 + "passkeyNamePlaceholder": "예: yubikey, 휴대폰", 338 338 "rename": "이름 변경", 339 339 "deletePasskey": "삭제", 340 340 "deletePasskeyConfirm": "패스키 \"{name}\"을(를) 삭제하시겠습니까? 더 이상 로그인에 사용할 수 없습니다.", ··· 758 758 }, 759 759 "identityType": "아이덴티티 유형", 760 760 "identityTypeHint": "분산 아이덴티티 관리 방법을 선택하세요.", 761 - "passkeyNamePlaceholder": "예: MacBook Touch ID", 761 + "passkeyNamePlaceholder": "예: yubikey, 휴대폰", 762 762 "passkeyNameHint": "이 패스키를 식별할 수 있는 이름", 763 763 "createPasskey": "패스키 생성", 764 764 "didPlcRecommended": "did:plc (권장)", ··· 1037 1037 "title": "패스키 설정", 1038 1038 "desc": "이메일이 인증되었습니다. 안전한 비밀번호 없는 로그인을 위해 패스키를 설정하세요.", 1039 1039 "nameLabel": "패스키 이름 (선택사항)", 1040 - "namePlaceholder": "예: MacBook Pro, iPhone", 1040 + "namePlaceholder": "예: yubikey, 휴대폰", 1041 1041 "nameHint": "이 패스키를 식별하기 위한 이름", 1042 1042 "instructions": "아래 버튼을 클릭하여 패스키를 등록하세요. 기기에서 생체 인식(지문, Face ID) 또는 보안 키 사용을 요청합니다.", 1043 1043 "register": "패스키 등록",
+4 -4
frontend/src/locales/sv.json
··· 278 278 "byController": "Av controller", 279 279 "create": "Skapa", 280 280 "name": "Namn", 281 - "namePlaceholder": "Appnamn (t.ex. Graysky)", 281 + "namePlaceholder": "Appnamn (t.ex. Witchsky)", 282 282 "deleteConfirm": "Återkalla applösenord \"{name}\"?", 283 283 "deleted": "Applösenord återkallat", 284 284 "loadFailed": "Kunde inte ladda applösenord", ··· 334 334 "adding": "Lägger till...", 335 335 "noPasskeys": "Inga nycklar registrerade", 336 336 "passkeyName": "Nyckelnamn", 337 - "passkeyNamePlaceholder": "t.ex. MacBook Pro, iPhone", 337 + "passkeyNamePlaceholder": "t.ex. yubikey, telefon", 338 338 "rename": "Byt namn", 339 339 "deletePasskey": "Radera", 340 340 "deletePasskeyConfirm": "Radera nyckel \"{name}\"? Du kommer inte att kunna använda den för att logga in längre.", ··· 758 758 }, 759 759 "identityType": "Identitetstyp", 760 760 "identityTypeHint": "Välj hur din decentraliserade identitet ska hanteras.", 761 - "passkeyNamePlaceholder": "t.ex. MacBook Touch ID", 761 + "passkeyNamePlaceholder": "t.ex. yubikey, telefon", 762 762 "passkeyNameHint": "Ett vänligt namn för att identifiera denna nyckel", 763 763 "createPasskey": "Skapa nyckel", 764 764 "didPlcRecommended": "did:plc (Rekommenderas)", ··· 1037 1037 "title": "Konfigurera din passkey", 1038 1038 "desc": "Din e-post har verifierats. Konfigurera nu din passkey för säker, lösenordslös inloggning.", 1039 1039 "nameLabel": "Passkey-namn (valfritt)", 1040 - "namePlaceholder": "t.ex. MacBook Pro, iPhone", 1040 + "namePlaceholder": "t.ex. yubikey, telefon", 1041 1041 "nameHint": "Ett vänligt namn för att identifiera denna passkey", 1042 1042 "instructions": "Klicka på knappen nedan för att registrera din passkey. Din enhet kommer att uppmana dig att använda biometri (fingeravtryck, Face ID) eller en säkerhetsnyckel.", 1043 1043 "register": "Registrera passkey",
+4 -4
frontend/src/locales/zh.json
··· 278 278 "byController": "由控制者创建", 279 279 "create": "创建", 280 280 "name": "名称", 281 - "namePlaceholder": "应用名称(如 Graysky)", 281 + "namePlaceholder": "应用名称(如 Witchsky)", 282 282 "deleteConfirm": "撤销应用专用密码「{name}」?", 283 283 "deleted": "应用专用密码已撤销", 284 284 "loadFailed": "加载应用专用密码失败", ··· 334 334 "adding": "添加中...", 335 335 "noPasskeys": "未注册通行密钥", 336 336 "passkeyName": "通行密钥名称", 337 - "passkeyNamePlaceholder": "如 MacBook Pro、iPhone", 337 + "passkeyNamePlaceholder": "如 yubikey、手机", 338 338 "rename": "重命名", 339 339 "deletePasskey": "删除", 340 340 "deletePasskeyConfirm": "删除通行密钥「{name}」?您将无法再使用它登录。", ··· 761 761 "externalDidPlaceholder": "did:web:yourdomain.com", 762 762 "externalDidHint": "您需要在以下地址提供 DID 文档", 763 763 "passkeyName": "通行密钥名称", 764 - "passkeyNamePlaceholder": "MacBook Touch ID", 764 + "passkeyNamePlaceholder": "如 yubikey、手机", 765 765 "passkeyNameHint": "可选标识", 766 766 "createPasskey": "创建通行密钥", 767 767 "errors": { ··· 1037 1037 "title": "设置您的通行密钥", 1038 1038 "desc": "您的邮箱已验证。现在设置通行密钥以实现安全的无密码登录。", 1039 1039 "nameLabel": "通行密钥名称(可选)", 1040 - "namePlaceholder": "例如:MacBook Pro、iPhone", 1040 + "namePlaceholder": "如 yubikey、手机", 1041 1041 "nameHint": "用于识别此通行密钥的友好名称", 1042 1042 "instructions": "点击下方按钮注册您的通行密钥。您的设备将提示您使用生物识别(指纹、面容ID)或安全密钥。", 1043 1043 "register": "注册通行密钥",
+3
frontend/src/main.ts
··· 1 1 import "./styles/base.css"; 2 + import "./styles/pages.css"; 3 + import "./styles/dashboard.css"; 4 + import "./styles/migration.css"; 2 5 import App from "./App.svelte"; 3 6 import { mount } from "svelte"; 4 7
+13 -10
frontend/src/styles/tokens.css
··· 28 28 --leading-normal: 1.5; 29 29 --leading-relaxed: 1.75; 30 30 31 - --radius-sm: 3px; 32 - --radius-md: 4px; 33 - --radius-lg: 6px; 34 - --radius-xl: 8px; 35 - 36 31 --width-xs: 360px; 37 32 --width-sm: 480px; 38 33 --width-md: 760px; ··· 42 37 --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); 43 38 --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.1); 44 39 --shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15); 45 - --shadow-focus: 0 0 0 2px var(--accent-muted); 46 - 47 - --transition-fast: 0.1s ease; 48 - --transition-normal: 0.15s ease; 49 - --transition-slow: 0.25s ease; 40 + --shadow-focus: 0 0 0 2px var(--secondary-muted); 50 41 51 42 --z-modal: 1000; 52 43 --overlay-bg: rgba(0, 0, 0, 0.5); ··· 91 82 --warning-border: #d4a03c; 92 83 --warning-text: #856404; 93 84 85 + --danger-bg: #c00; 86 + --danger-bg-hover: #a00; 87 + 88 + --info-bg: #e0f2fe; 89 + --info-text: #0369a1; 90 + 94 91 --border-color-light: var(--border-dark); 95 92 } 96 93 ··· 133 130 --warning-bg: #1f1a0f; 134 131 --warning-border: #3d351a; 135 132 --warning-text: #c6b87b; 133 + 134 + --danger-bg: #ff8a8a; 135 + --danger-bg-hover: #ff6b6b; 136 + 137 + --info-bg: #0c2d48; 138 + --info-text: #7bc6f0; 136 139 } 137 140 }
+2 -2
frontend/src/tests/oauth-registration.test.ts
··· 149 149 150 150 const Register = (await import("../routes/Register.svelte")) 151 151 .default; 152 - render(Register); 152 + const { container } = render(Register); 153 153 154 154 await waitFor(() => { 155 - expect(screen.getByText(/loading/i)).toBeInTheDocument(); 155 + expect(container.querySelector(".loading")).toBeInTheDocument(); 156 156 }); 157 157 }); 158 158

History

1 round 0 comments
sign up or login to add to the discussion
oyster.cafe submitted #0
1 commit
expand
feat(frontend): design tokens, app wiring, locales
expand 0 comments
pull request successfully merged