forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
1<script lang="ts">
2 import { getCurrentPath, navigate } from './lib/router.svelte'
3 import { initAuth, getAuthState } from './lib/auth.svelte'
4 import { initServerConfig } from './lib/serverConfig.svelte'
5 import { initI18n } from './lib/i18n'
6 import { isLoading as i18nLoading } from 'svelte-i18n'
7 import Toast from './components/Toast.svelte'
8 import Login from './routes/Login.svelte'
9 import Register from './routes/Register.svelte'
10 import RegisterPasskey from './routes/RegisterPasskey.svelte'
11 import Verify from './routes/Verify.svelte'
12 import ResetPassword from './routes/ResetPassword.svelte'
13 import RecoverPasskey from './routes/RecoverPasskey.svelte'
14 import RequestPasskeyRecovery from './routes/RequestPasskeyRecovery.svelte'
15 import Dashboard from './routes/Dashboard.svelte'
16 import AppPasswords from './routes/AppPasswords.svelte'
17 import InviteCodes from './routes/InviteCodes.svelte'
18 import Settings from './routes/Settings.svelte'
19 import Sessions from './routes/Sessions.svelte'
20 import Comms from './routes/Comms.svelte'
21 import RepoExplorer from './routes/RepoExplorer.svelte'
22 import Admin from './routes/Admin.svelte'
23 import OAuthConsent from './routes/OAuthConsent.svelte'
24 import OAuthLogin from './routes/OAuthLogin.svelte'
25 import OAuthAccounts from './routes/OAuthAccounts.svelte'
26 import OAuth2FA from './routes/OAuth2FA.svelte'
27 import OAuthTotp from './routes/OAuthTotp.svelte'
28 import OAuthPasskey from './routes/OAuthPasskey.svelte'
29 import OAuthDelegation from './routes/OAuthDelegation.svelte'
30 import OAuthError from './routes/OAuthError.svelte'
31 import Security from './routes/Security.svelte'
32 import TrustedDevices from './routes/TrustedDevices.svelte'
33 import Controllers from './routes/Controllers.svelte'
34 import DelegationAudit from './routes/DelegationAudit.svelte'
35 import ActAs from './routes/ActAs.svelte'
36 import Migration from './routes/Migration.svelte'
37 import DidDocumentEditor from './routes/DidDocumentEditor.svelte'
38 import { _ } from './lib/i18n'
39 initI18n()
40
41 const auth = $derived(getAuthState())
42
43 let oauthCallbackPending = $state(hasOAuthCallback())
44 let showSpinner = $state(false)
45 let loadingTimer: ReturnType<typeof setTimeout> | null = null
46
47 function hasOAuthCallback(): boolean {
48 if (window.location.pathname === '/app/migrate') {
49 return false
50 }
51 const params = new URLSearchParams(window.location.search)
52 return !!(params.get('code') && params.get('state'))
53 }
54
55 $effect(() => {
56 loadingTimer = setTimeout(() => {
57 showSpinner = true
58 }, 5000)
59
60 initServerConfig()
61 initAuth().then(({ oauthLoginCompleted }) => {
62 if (oauthLoginCompleted) {
63 navigate('/dashboard', { replace: true })
64 }
65 oauthCallbackPending = false
66 if (loadingTimer) {
67 clearTimeout(loadingTimer)
68 loadingTimer = null
69 }
70 })
71
72 return () => {
73 if (loadingTimer) {
74 clearTimeout(loadingTimer)
75 }
76 }
77 })
78
79 const isLoading = $derived(
80 auth.kind === 'loading' || $i18nLoading || oauthCallbackPending
81 )
82
83 $effect(() => {
84 if (auth.kind === 'loading') return
85 const path = getCurrentPath()
86 if (path === '/') {
87 if (auth.kind === 'authenticated') {
88 navigate('/dashboard', { replace: true })
89 } else {
90 navigate('/login', { replace: true })
91 }
92 }
93 })
94
95 function getComponent(path: string) {
96 switch (path) {
97 case '/login':
98 return Login
99 case '/register':
100 return RegisterPasskey
101 case '/register-password':
102 return Register
103 case '/verify':
104 return Verify
105 case '/reset-password':
106 return ResetPassword
107 case '/recover-passkey':
108 return RecoverPasskey
109 case '/request-passkey-recovery':
110 return RequestPasskeyRecovery
111 case '/dashboard':
112 return Dashboard
113 case '/app-passwords':
114 return AppPasswords
115 case '/invite-codes':
116 return InviteCodes
117 case '/settings':
118 return Settings
119 case '/sessions':
120 return Sessions
121 case '/comms':
122 return Comms
123 case '/repo':
124 return RepoExplorer
125 case '/admin':
126 return Admin
127 case '/oauth/consent':
128 return OAuthConsent
129 case '/oauth/login':
130 return OAuthLogin
131 case '/oauth/accounts':
132 return OAuthAccounts
133 case '/oauth/2fa':
134 return OAuth2FA
135 case '/oauth/totp':
136 return OAuthTotp
137 case '/oauth/passkey':
138 return OAuthPasskey
139 case '/oauth/delegation':
140 return OAuthDelegation
141 case '/oauth/error':
142 return OAuthError
143 case '/security':
144 return Security
145 case '/trusted-devices':
146 return TrustedDevices
147 case '/controllers':
148 return Controllers
149 case '/delegation-audit':
150 return DelegationAudit
151 case '/act-as':
152 return ActAs
153 case '/migrate':
154 return Migration
155 case '/did-document':
156 return DidDocumentEditor
157 default:
158 return Login
159 }
160 }
161
162 let currentPath = $derived(getCurrentPath())
163 let CurrentComponent = $derived(getComponent(currentPath))
164</script>
165
166<main>
167 {#if isLoading}
168 <div class="loading">
169 {#if showSpinner}
170 <div class="loading-content">
171 <div class="spinner"></div>
172 <p>{$_('common.loading')}</p>
173 </div>
174 {/if}
175 </div>
176 {:else}
177 <CurrentComponent />
178 {/if}
179</main>
180<Toast />
181
182<style>
183 main {
184 min-height: 100vh;
185 }
186
187 .loading {
188 min-height: 100vh;
189 display: flex;
190 align-items: center;
191 justify-content: center;
192 }
193
194 .loading-content {
195 display: flex;
196 flex-direction: column;
197 align-items: center;
198 gap: var(--space-4);
199 }
200
201 .loading-content p {
202 margin: 0;
203 color: var(--text-secondary);
204 }
205</style>