+47
-100
Diff
round #0
-6
frontend/index.html
-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
+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
+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
+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
+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
+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
+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
+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
+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
+3
frontend/src/main.ts
+13
-10
frontend/src/styles/tokens.css
+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
+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
oyster.cafe
submitted
#0
1 commit
expand
collapse
feat(frontend): design tokens, app wiring, locales
expand 0 comments
pull request successfully merged