Our Personal Data Server from scratch!
at nix-fixes 213 lines 6.2 kB view raw
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>