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