|
1 | | -import { createSignal, createEffect, onMount, Show, type JSX } from "solid-js"; |
| 1 | +import { createSignal, createEffect, onMount, Show, ErrorBoundary, Suspense, lazy, type JSX } from "solid-js"; |
2 | 2 | import { Router, Route, Navigate, useNavigate } from "@solidjs/router"; |
3 | | -import { isAuthenticated, validateToken } from "./stores/auth"; |
| 3 | +import { isAuthenticated, validateToken, AUTH_STORAGE_KEY } from "./stores/auth"; |
4 | 4 | import { config, initConfigPersistence, resolveTheme } from "./stores/config"; |
5 | 5 | import { initViewPersistence } from "./stores/view"; |
6 | 6 | import { evictStaleEntries } from "./stores/cache"; |
7 | 7 | import { initClientWatcher } from "./services/github"; |
8 | 8 | import LoginPage from "./pages/LoginPage"; |
9 | 9 | import OAuthCallback from "./pages/OAuthCallback"; |
10 | | -import DashboardPage from "./components/dashboard/DashboardPage"; |
11 | | -import OnboardingWizard from "./components/onboarding/OnboardingWizard"; |
12 | | -import SettingsPage from "./components/settings/SettingsPage"; |
13 | 10 | import PrivacyPage from "./pages/PrivacyPage"; |
14 | 11 |
|
| 12 | +const DashboardPage = lazy(() => import("./components/dashboard/DashboardPage")); |
| 13 | +const OnboardingWizard = lazy(() => import("./components/onboarding/OnboardingWizard")); |
| 14 | +const SettingsPage = lazy(() => import("./components/settings/SettingsPage")); |
| 15 | + |
| 16 | +function handleRouteError(err: unknown) { |
| 17 | + console.error("[app] Route render failed:", err); |
| 18 | + return <ChunkErrorFallback />; |
| 19 | +} |
| 20 | + |
| 21 | +function ChunkErrorFallback() { |
| 22 | + return ( |
| 23 | + <div class="min-h-screen flex items-center justify-center bg-base-200"> |
| 24 | + <div class="card bg-base-100 shadow-md p-8 flex flex-col items-center gap-4 max-w-sm"> |
| 25 | + <p class="text-error font-medium">Failed to load page</p> |
| 26 | + <p class="text-sm text-base-content/60 text-center"> |
| 27 | + A new version may have been deployed. Reloading should fix this. |
| 28 | + </p> |
| 29 | + <button |
| 30 | + type="button" |
| 31 | + class="btn btn-neutral" |
| 32 | + onClick={() => window.location.reload()} |
| 33 | + > |
| 34 | + Reload page |
| 35 | + </button> |
| 36 | + </div> |
| 37 | + </div> |
| 38 | + ); |
| 39 | +} |
| 40 | + |
15 | 41 | // Auth guard: redirects unauthenticated users to /login. |
16 | 42 | // On page load, validates the localStorage token with GitHub API. |
17 | 43 | function AuthGuard(props: { children: JSX.Element }) { |
@@ -138,17 +164,33 @@ export default function App() { |
138 | 164 | evictStaleEntries(24 * 60 * 60 * 1000).catch(() => { |
139 | 165 | // Non-fatal — stale eviction failure is acceptable |
140 | 166 | }); |
| 167 | + |
| 168 | + // Preload dashboard chunk in parallel with token validation to avoid |
| 169 | + // a sequential waterfall (validateToken → chunk fetch) |
| 170 | + if (localStorage.getItem?.(AUTH_STORAGE_KEY)) { |
| 171 | + import("./components/dashboard/DashboardPage").catch(() => { |
| 172 | + console.warn("[app] Dashboard chunk preload failed"); |
| 173 | + }); |
| 174 | + } |
141 | 175 | }); |
142 | 176 |
|
143 | 177 | return ( |
144 | | - <Router> |
145 | | - <Route path="/" component={RootRedirect} /> |
146 | | - <Route path="/login" component={LoginPage} /> |
147 | | - <Route path="/oauth/callback" component={OAuthCallback} /> |
148 | | - <Route path="/onboarding" component={() => <AuthGuard><OnboardingWizard /></AuthGuard>} /> |
149 | | - <Route path="/dashboard" component={() => <AuthGuard><DashboardPage /></AuthGuard>} /> |
150 | | - <Route path="/settings" component={() => <AuthGuard><SettingsPage /></AuthGuard>} /> |
151 | | - <Route path="/privacy" component={PrivacyPage} /> |
152 | | - </Router> |
| 178 | + <ErrorBoundary fallback={handleRouteError}> |
| 179 | + <Suspense fallback={ |
| 180 | + <div class="min-h-screen flex items-center justify-center bg-base-200"> |
| 181 | + <span class="loading loading-spinner loading-lg" aria-label="Loading" /> |
| 182 | + </div> |
| 183 | + }> |
| 184 | + <Router> |
| 185 | + <Route path="/" component={RootRedirect} /> |
| 186 | + <Route path="/login" component={LoginPage} /> |
| 187 | + <Route path="/oauth/callback" component={OAuthCallback} /> |
| 188 | + <Route path="/onboarding" component={() => <AuthGuard><OnboardingWizard /></AuthGuard>} /> |
| 189 | + <Route path="/dashboard" component={() => <AuthGuard><DashboardPage /></AuthGuard>} /> |
| 190 | + <Route path="/settings" component={() => <AuthGuard><SettingsPage /></AuthGuard>} /> |
| 191 | + <Route path="/privacy" component={PrivacyPage} /> |
| 192 | + </Router> |
| 193 | + </Suspense> |
| 194 | + </ErrorBoundary> |
153 | 195 | ); |
154 | 196 | } |
0 commit comments