mirror of
https://tangled.org/tranquil.farm/tranquil-pds
synced 2026-08-21 08:46:04 +00:00
feat(frontend): design tokens, app wiring, locales
This commit is contained in:
@@ -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
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "パスキーを登録",
|
||||
|
||||
@@ -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": "패스키 등록",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "注册通行密钥",
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user