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

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