diff --git a/apps/dashboard/src/routeTree.gen.ts b/apps/dashboard/src/routeTree.gen.ts index ef55fe8..a118f40 100644 --- a/apps/dashboard/src/routeTree.gen.ts +++ b/apps/dashboard/src/routeTree.gen.ts @@ -29,7 +29,7 @@ import { Route as ProtectedOwnerRepoIndexRouteImport } from './routes/_protected import { Route as ApiGithubAppCallbackRouteImport } from './routes/api/github/app/callback' import { Route as ApiGithubAppAuthorizeRouteImport } from './routes/api/github/app/authorize' import { Route as ProtectedOwnerRepoPullsRouteImport } from './routes/_protected/$owner/$repo/pulls' -import { Route as ProtectedOwnerRepoIssuesRouteImport } from './routes/_protected/$owner/$repo/issues' +import { Route as ProtectedOwnerRepoIssuesIndexRouteImport } from './routes/_protected/$owner/$repo/issues.index' import { Route as ProtectedOwnerRepoReviewPullIdRouteImport } from './routes/_protected/$owner/$repo/review.$pullId' import { Route as ProtectedOwnerRepoPullPullIdRouteImport } from './routes/_protected/$owner/$repo/pull.$pullId' import { Route as ProtectedOwnerRepoIssuesNewRouteImport } from './routes/_protected/$owner/$repo/issues.new' @@ -135,10 +135,10 @@ const ProtectedOwnerRepoPullsRoute = ProtectedOwnerRepoPullsRouteImport.update({ path: '/$owner/$repo/pulls', getParentRoute: () => ProtectedRoute, } as any) -const ProtectedOwnerRepoIssuesRoute = - ProtectedOwnerRepoIssuesRouteImport.update({ - id: '/$owner/$repo/issues', - path: '/$owner/$repo/issues', +const ProtectedOwnerRepoIssuesIndexRoute = + ProtectedOwnerRepoIssuesIndexRouteImport.update({ + id: '/$owner/$repo/issues/', + path: '/$owner/$repo/issues/', getParentRoute: () => ProtectedRoute, } as any) const ProtectedOwnerRepoReviewPullIdRoute = @@ -155,15 +155,15 @@ const ProtectedOwnerRepoPullPullIdRoute = } as any) const ProtectedOwnerRepoIssuesNewRoute = ProtectedOwnerRepoIssuesNewRouteImport.update({ - id: '/new', - path: '/new', - getParentRoute: () => ProtectedOwnerRepoIssuesRoute, + id: '/$owner/$repo/issues/new', + path: '/$owner/$repo/issues/new', + getParentRoute: () => ProtectedRoute, } as any) const ProtectedOwnerRepoIssuesIssueIdRoute = ProtectedOwnerRepoIssuesIssueIdRouteImport.update({ - id: '/$issueId', - path: '/$issueId', - getParentRoute: () => ProtectedOwnerRepoIssuesRoute, + id: '/$owner/$repo/issues/$issueId', + path: '/$owner/$repo/issues/$issueId', + getParentRoute: () => ProtectedRoute, } as any) export interface FileRoutesByFullPath { @@ -182,7 +182,6 @@ export interface FileRoutesByFullPath { '/api/webhooks/github': typeof ApiWebhooksGithubRoute '/$owner/': typeof ProtectedOwnerIndexRoute '/settings/': typeof ProtectedSettingsIndexRoute - '/$owner/$repo/issues': typeof ProtectedOwnerRepoIssuesRouteWithChildren '/$owner/$repo/pulls': typeof ProtectedOwnerRepoPullsRoute '/api/github/app/authorize': typeof ApiGithubAppAuthorizeRoute '/api/github/app/callback': typeof ApiGithubAppCallbackRoute @@ -191,6 +190,7 @@ export interface FileRoutesByFullPath { '/$owner/$repo/issues/new': typeof ProtectedOwnerRepoIssuesNewRoute '/$owner/$repo/pull/$pullId': typeof ProtectedOwnerRepoPullPullIdRoute '/$owner/$repo/review/$pullId': typeof ProtectedOwnerRepoReviewPullIdRoute + '/$owner/$repo/issues/': typeof ProtectedOwnerRepoIssuesIndexRoute } export interface FileRoutesByTo { '/$': typeof SplatRoute @@ -207,7 +207,6 @@ export interface FileRoutesByTo { '/api/webhooks/github': typeof ApiWebhooksGithubRoute '/$owner': typeof ProtectedOwnerIndexRoute '/settings': typeof ProtectedSettingsIndexRoute - '/$owner/$repo/issues': typeof ProtectedOwnerRepoIssuesRouteWithChildren '/$owner/$repo/pulls': typeof ProtectedOwnerRepoPullsRoute '/api/github/app/authorize': typeof ApiGithubAppAuthorizeRoute '/api/github/app/callback': typeof ApiGithubAppCallbackRoute @@ -216,6 +215,7 @@ export interface FileRoutesByTo { '/$owner/$repo/issues/new': typeof ProtectedOwnerRepoIssuesNewRoute '/$owner/$repo/pull/$pullId': typeof ProtectedOwnerRepoPullPullIdRoute '/$owner/$repo/review/$pullId': typeof ProtectedOwnerRepoReviewPullIdRoute + '/$owner/$repo/issues': typeof ProtectedOwnerRepoIssuesIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport @@ -235,7 +235,6 @@ export interface FileRoutesById { '/api/webhooks/github': typeof ApiWebhooksGithubRoute '/_protected/$owner/': typeof ProtectedOwnerIndexRoute '/_protected/settings/': typeof ProtectedSettingsIndexRoute - '/_protected/$owner/$repo/issues': typeof ProtectedOwnerRepoIssuesRouteWithChildren '/_protected/$owner/$repo/pulls': typeof ProtectedOwnerRepoPullsRoute '/api/github/app/authorize': typeof ApiGithubAppAuthorizeRoute '/api/github/app/callback': typeof ApiGithubAppCallbackRoute @@ -244,6 +243,7 @@ export interface FileRoutesById { '/_protected/$owner/$repo/issues/new': typeof ProtectedOwnerRepoIssuesNewRoute '/_protected/$owner/$repo/pull/$pullId': typeof ProtectedOwnerRepoPullPullIdRoute '/_protected/$owner/$repo/review/$pullId': typeof ProtectedOwnerRepoReviewPullIdRoute + '/_protected/$owner/$repo/issues/': typeof ProtectedOwnerRepoIssuesIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath @@ -263,7 +263,6 @@ export interface FileRouteTypes { | '/api/webhooks/github' | '/$owner/' | '/settings/' - | '/$owner/$repo/issues' | '/$owner/$repo/pulls' | '/api/github/app/authorize' | '/api/github/app/callback' @@ -272,6 +271,7 @@ export interface FileRouteTypes { | '/$owner/$repo/issues/new' | '/$owner/$repo/pull/$pullId' | '/$owner/$repo/review/$pullId' + | '/$owner/$repo/issues/' fileRoutesByTo: FileRoutesByTo to: | '/$' @@ -288,7 +288,6 @@ export interface FileRouteTypes { | '/api/webhooks/github' | '/$owner' | '/settings' - | '/$owner/$repo/issues' | '/$owner/$repo/pulls' | '/api/github/app/authorize' | '/api/github/app/callback' @@ -297,6 +296,7 @@ export interface FileRouteTypes { | '/$owner/$repo/issues/new' | '/$owner/$repo/pull/$pullId' | '/$owner/$repo/review/$pullId' + | '/$owner/$repo/issues' id: | '__root__' | '/$' @@ -315,7 +315,6 @@ export interface FileRouteTypes { | '/api/webhooks/github' | '/_protected/$owner/' | '/_protected/settings/' - | '/_protected/$owner/$repo/issues' | '/_protected/$owner/$repo/pulls' | '/api/github/app/authorize' | '/api/github/app/callback' @@ -324,6 +323,7 @@ export interface FileRouteTypes { | '/_protected/$owner/$repo/issues/new' | '/_protected/$owner/$repo/pull/$pullId' | '/_protected/$owner/$repo/review/$pullId' + | '/_protected/$owner/$repo/issues/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { @@ -481,11 +481,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ProtectedOwnerRepoPullsRouteImport parentRoute: typeof ProtectedRoute } - '/_protected/$owner/$repo/issues': { - id: '/_protected/$owner/$repo/issues' + '/_protected/$owner/$repo/issues/': { + id: '/_protected/$owner/$repo/issues/' path: '/$owner/$repo/issues' - fullPath: '/$owner/$repo/issues' - preLoaderRoute: typeof ProtectedOwnerRepoIssuesRouteImport + fullPath: '/$owner/$repo/issues/' + preLoaderRoute: typeof ProtectedOwnerRepoIssuesIndexRouteImport parentRoute: typeof ProtectedRoute } '/_protected/$owner/$repo/review/$pullId': { @@ -504,17 +504,17 @@ declare module '@tanstack/react-router' { } '/_protected/$owner/$repo/issues/new': { id: '/_protected/$owner/$repo/issues/new' - path: '/new' + path: '/$owner/$repo/issues/new' fullPath: '/$owner/$repo/issues/new' preLoaderRoute: typeof ProtectedOwnerRepoIssuesNewRouteImport - parentRoute: typeof ProtectedOwnerRepoIssuesRoute + parentRoute: typeof ProtectedRoute } '/_protected/$owner/$repo/issues/$issueId': { id: '/_protected/$owner/$repo/issues/$issueId' - path: '/$issueId' + path: '/$owner/$repo/issues/$issueId' fullPath: '/$owner/$repo/issues/$issueId' preLoaderRoute: typeof ProtectedOwnerRepoIssuesIssueIdRouteImport - parentRoute: typeof ProtectedOwnerRepoIssuesRoute + parentRoute: typeof ProtectedRoute } } } @@ -532,22 +532,6 @@ const ProtectedSettingsRouteChildren: ProtectedSettingsRouteChildren = { const ProtectedSettingsRouteWithChildren = ProtectedSettingsRoute._addFileChildren(ProtectedSettingsRouteChildren) -interface ProtectedOwnerRepoIssuesRouteChildren { - ProtectedOwnerRepoIssuesIssueIdRoute: typeof ProtectedOwnerRepoIssuesIssueIdRoute - ProtectedOwnerRepoIssuesNewRoute: typeof ProtectedOwnerRepoIssuesNewRoute -} - -const ProtectedOwnerRepoIssuesRouteChildren: ProtectedOwnerRepoIssuesRouteChildren = - { - ProtectedOwnerRepoIssuesIssueIdRoute: ProtectedOwnerRepoIssuesIssueIdRoute, - ProtectedOwnerRepoIssuesNewRoute: ProtectedOwnerRepoIssuesNewRoute, - } - -const ProtectedOwnerRepoIssuesRouteWithChildren = - ProtectedOwnerRepoIssuesRoute._addFileChildren( - ProtectedOwnerRepoIssuesRouteChildren, - ) - interface ProtectedRouteChildren { ProtectedIssuesRoute: typeof ProtectedIssuesRoute ProtectedPullsRoute: typeof ProtectedPullsRoute @@ -555,11 +539,13 @@ interface ProtectedRouteChildren { ProtectedSettingsRoute: typeof ProtectedSettingsRouteWithChildren ProtectedIndexRoute: typeof ProtectedIndexRoute ProtectedOwnerIndexRoute: typeof ProtectedOwnerIndexRoute - ProtectedOwnerRepoIssuesRoute: typeof ProtectedOwnerRepoIssuesRouteWithChildren ProtectedOwnerRepoPullsRoute: typeof ProtectedOwnerRepoPullsRoute ProtectedOwnerRepoIndexRoute: typeof ProtectedOwnerRepoIndexRoute + ProtectedOwnerRepoIssuesIssueIdRoute: typeof ProtectedOwnerRepoIssuesIssueIdRoute + ProtectedOwnerRepoIssuesNewRoute: typeof ProtectedOwnerRepoIssuesNewRoute ProtectedOwnerRepoPullPullIdRoute: typeof ProtectedOwnerRepoPullPullIdRoute ProtectedOwnerRepoReviewPullIdRoute: typeof ProtectedOwnerRepoReviewPullIdRoute + ProtectedOwnerRepoIssuesIndexRoute: typeof ProtectedOwnerRepoIssuesIndexRoute } const ProtectedRouteChildren: ProtectedRouteChildren = { @@ -569,11 +555,13 @@ const ProtectedRouteChildren: ProtectedRouteChildren = { ProtectedSettingsRoute: ProtectedSettingsRouteWithChildren, ProtectedIndexRoute: ProtectedIndexRoute, ProtectedOwnerIndexRoute: ProtectedOwnerIndexRoute, - ProtectedOwnerRepoIssuesRoute: ProtectedOwnerRepoIssuesRouteWithChildren, ProtectedOwnerRepoPullsRoute: ProtectedOwnerRepoPullsRoute, ProtectedOwnerRepoIndexRoute: ProtectedOwnerRepoIndexRoute, + ProtectedOwnerRepoIssuesIssueIdRoute: ProtectedOwnerRepoIssuesIssueIdRoute, + ProtectedOwnerRepoIssuesNewRoute: ProtectedOwnerRepoIssuesNewRoute, ProtectedOwnerRepoPullPullIdRoute: ProtectedOwnerRepoPullPullIdRoute, ProtectedOwnerRepoReviewPullIdRoute: ProtectedOwnerRepoReviewPullIdRoute, + ProtectedOwnerRepoIssuesIndexRoute: ProtectedOwnerRepoIssuesIndexRoute, } const ProtectedRouteWithChildren = ProtectedRoute._addFileChildren( @@ -595,12 +583,3 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' -declare module '@tanstack/react-start' { - interface Register { - ssr: true - router: Awaited> - } -} diff --git a/apps/dashboard/src/router.tsx b/apps/dashboard/src/router.tsx index c084434..9ef5e19 100644 --- a/apps/dashboard/src/router.tsx +++ b/apps/dashboard/src/router.tsx @@ -1,5 +1,6 @@ import { createRouter as createTanStackRouter } from "@tanstack/react-router"; import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query"; +import { DashboardContentLoading } from "#/components/layouts/dashboard-content-loading"; import { DashboardErrorScreen } from "#/components/layouts/dashboard-error-screen"; import { NotFoundScreen } from "#/components/layouts/not-found-screen"; import { @@ -19,6 +20,7 @@ export function getRouter() { defaultPreload: "intent", defaultPreloadStaleTime: 0, defaultPendingMs: 0, + defaultPendingComponent: DashboardContentLoading, defaultErrorComponent: DashboardErrorScreen, defaultNotFoundComponent: NotFoundScreen, Wrap: ({ children }) => ( diff --git a/apps/dashboard/src/routes/_protected.tsx b/apps/dashboard/src/routes/_protected.tsx index 826c818..6295aa2 100644 --- a/apps/dashboard/src/routes/_protected.tsx +++ b/apps/dashboard/src/routes/_protected.tsx @@ -5,9 +5,22 @@ import { getSession } from "#/lib/auth.functions"; import { checkSetupComplete } from "#/lib/github.functions"; import { buildSeo, formatPageTitle, PRIVATE_ROUTE_HEADERS } from "#/lib/seo"; +/** + * Cache the auth check so navigations within the dashboard are instant. + * The cache is cleared on full page reloads. If the session expires mid-use, + * API calls in child routes will 401 and the error boundary handles it. + */ +let cachedAuth: Awaited> | null = null; + export const Route = createFileRoute("/_protected")({ beforeLoad: async ({ location }) => { - const session = await getSession(); + if (cachedAuth) return cachedAuth; + + const [session, setupComplete] = await Promise.all([ + getSession(), + checkSetupComplete(), + ]); + if (!session) { throw redirect({ to: "/login", @@ -15,12 +28,12 @@ export const Route = createFileRoute("/_protected")({ }); } - const setupComplete = await checkSetupComplete(); if (!setupComplete) { throw redirect({ to: "/setup" }); } - return { user: session.user, session: session.session }; + cachedAuth = { user: session.user, session: session.session }; + return cachedAuth; }, headers: () => PRIVATE_ROUTE_HEADERS, head: ({ match }) => { diff --git a/apps/dashboard/src/routes/_protected/$owner/$repo/issues.tsx b/apps/dashboard/src/routes/_protected/$owner/$repo/issues.index.tsx similarity index 99% rename from apps/dashboard/src/routes/_protected/$owner/$repo/issues.tsx rename to apps/dashboard/src/routes/_protected/$owner/$repo/issues.index.tsx index 5e72deb..8b87f47 100644 --- a/apps/dashboard/src/routes/_protected/$owner/$repo/issues.tsx +++ b/apps/dashboard/src/routes/_protected/$owner/$repo/issues.index.tsx @@ -24,7 +24,7 @@ import { useHasMounted } from "#/lib/use-has-mounted"; const PER_PAGE = 30; -export const Route = createFileRoute("/_protected/$owner/$repo/issues")({ +export const Route = createFileRoute("/_protected/$owner/$repo/issues/")({ ssr: false, head: ({ match, params }) => buildSeo({