Skip to content
File

Blob: src/client/route-tree.tsx

typescript157 lines
1import { createRootRoute, createRoute, redirect, lazyRouteComponent } from "@tanstack/react-router";
2import { RootShell } from "@/client/components/root-shell";
3import { StandaloneLayout } from "@/client/components/layouts/standalone-layout";
4import { ShareLayout } from "@/client/components/share/layout";
5import { WorkspaceLayout } from "@/client/components/workspace/layout";
6import { RouteErrorFallback } from "@/client/components/route-error-fallback";
7import { useAuthStore, selectHasLocalSession, selectIsAuthenticated } from "@/client/stores/auth-store";
8 
9const rootRoute = createRootRoute({
10 component: RootShell,
11 errorComponent: RouteErrorFallback,
12});
13 
14// --- Bare route (no layout chrome) ---
15const indexRoute = createRoute({
16 getParentRoute: () => rootRoute,
17 path: "/",
18 component: lazyRouteComponent(() => import("@/client/components/index-gateway"), "IndexGateway"),
19});
20 
21// --- Standalone layout routes ---
22// Each standalone route uses StandaloneLayout as its component, with the
23// actual page as a child index route. This preserves the URL structure
24// without needing pathless id-based layout routes.
25 
26const loginWrapper = createRoute({
27 getParentRoute: () => rootRoute,
28 path: "/login",
29 component: StandaloneLayout,
30 validateSearch: (search: Record<string, unknown>): { redirect?: string; error?: string } => {
31 const out: { redirect?: string; error?: string } = {};
32 if (typeof search.redirect === "string") out.redirect = search.redirect;
33 if (typeof search.error === "string") out.error = search.error;
34 return out;
35 },
36 beforeLoad: ({ search }) => {
37 if (selectIsAuthenticated(useAuthStore.getState())) {
38 throw redirect({ to: search.redirect || "/" });
39 }
40 },
41});
42 
43const loginRoute = createRoute({
44 getParentRoute: () => loginWrapper,
45 path: "/",
46 component: lazyRouteComponent(() => import("@/client/components/auth/login-page"), "LoginPage"),
47});
48 
49const inviteWrapper = createRoute({
50 getParentRoute: () => rootRoute,
51 path: "/invite/$token",
52 component: StandaloneLayout,
53 validateSearch: (search: Record<string, unknown>) => ({
54 accept: search.accept === "1" || search.accept === 1 ? ("1" as const) : undefined,
55 }),
56});
57 
58const inviteRoute = createRoute({
59 getParentRoute: () => inviteWrapper,
60 path: "/",
61 component: lazyRouteComponent(() => import("@/client/components/auth/invite-page"), "InvitePage"),
62});
63 
64const profileWrapper = createRoute({
65 getParentRoute: () => rootRoute,
66 path: "/profile",
67 component: StandaloneLayout,
68 beforeLoad: async () => {
69 if (!selectHasLocalSession(useAuthStore.getState())) {
70 throw redirect({ to: "/login", search: { redirect: "/profile" } });
71 }
72 },
73});
74 
75const profileRoute = createRoute({
76 getParentRoute: () => profileWrapper,
77 path: "/",
78 component: lazyRouteComponent(() => import("@/client/components/profile-settings"), "ProfileSettings"),
79});
80 
81const sharedWithMeWrapper = createRoute({
82 getParentRoute: () => rootRoute,
83 path: "/shared-with-me",
84 component: StandaloneLayout,
85 beforeLoad: async () => {
86 if (!selectHasLocalSession(useAuthStore.getState())) {
87 throw redirect({ to: "/login", search: { redirect: "/shared-with-me" } });
88 }
89 },
90});
91 
92const sharedWithMeRoute = createRoute({
93 getParentRoute: () => sharedWithMeWrapper,
94 path: "/",
95 component: lazyRouteComponent(() => import("@/client/components/shared-with-me-view"), "SharedWithMeView"),
96});
97 
98const shareWrapper = createRoute({
99 getParentRoute: () => rootRoute,
100 path: "/s/$token",
101 component: ShareLayout,
102 validateSearch: (search: Record<string, unknown>) => ({
103 page: typeof search.page === "string" ? search.page : undefined,
104 }),
105});
106 
107const shareRoute = createRoute({
108 getParentRoute: () => shareWrapper,
109 path: "/",
110 component: lazyRouteComponent(() => import("@/client/routes/shared-page-route"), "SharedPageRoute"),
111});
112 
113const workspaceRoute = createRoute({
114 getParentRoute: () => rootRoute,
115 path: "/$workspaceSlug",
116 component: WorkspaceLayout,
117 beforeLoad: ({ location }) => {
118 if (!selectHasLocalSession(useAuthStore.getState())) {
119 throw redirect({ to: "/login", search: { redirect: location.pathname } });
120 }
121 },
122});
123 
124const workspaceIndexRoute = createRoute({
125 getParentRoute: () => workspaceRoute,
126 path: "/",
127 component: lazyRouteComponent(() => import("@/client/components/workspace/index"), "WorkspaceIndex"),
128});
129 
130const settingsRoute = createRoute({
131 getParentRoute: () => workspaceRoute,
132 path: "/settings",
133 component: lazyRouteComponent(() => import("@/client/components/workspace/settings"), "WorkspaceSettings"),
134});
135 
136const archiveRoute = createRoute({
137 getParentRoute: () => workspaceRoute,
138 path: "/archive",
139 component: lazyRouteComponent(() => import("@/client/components/workspace/archive"), "WorkspaceArchive"),
140});
141 
142const pageRoute = createRoute({
143 getParentRoute: () => workspaceRoute,
144 path: "/$pageId",
145 component: lazyRouteComponent(() => import("@/client/components/workspace/page-view"), "PageView"),
146});
147 
148export const routeTree = rootRoute.addChildren([
149 indexRoute,
150 loginWrapper.addChildren([loginRoute]),
151 inviteWrapper.addChildren([inviteRoute]),
152 profileWrapper.addChildren([profileRoute]),
153 sharedWithMeWrapper.addChildren([sharedWithMeRoute]),
154 shareWrapper.addChildren([shareRoute]),
155 workspaceRoute.addChildren([workspaceIndexRoute, settingsRoute, archiveRoute, pageRoute]),
156]);