@shipfox/client-shell 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +26 -0
- package/LICENSE +21 -0
- package/dist/components/main-layout.d.ts +11 -0
- package/dist/components/main-layout.d.ts.map +1 -0
- package/dist/components/main-layout.js +39 -0
- package/dist/components/main-layout.js.map +1 -0
- package/dist/components/nav-bar.d.ts +4 -0
- package/dist/components/nav-bar.d.ts.map +1 -0
- package/dist/components/nav-bar.js +62 -0
- package/dist/components/nav-bar.js.map +1 -0
- package/dist/components/nav-tabs.d.ts +6 -0
- package/dist/components/nav-tabs.d.ts.map +1 -0
- package/dist/components/nav-tabs.js +38 -0
- package/dist/components/nav-tabs.js.map +1 -0
- package/dist/components/not-found-page.d.ts +2 -0
- package/dist/components/not-found-page.d.ts.map +1 -0
- package/dist/components/not-found-page.js +25 -0
- package/dist/components/not-found-page.js.map +1 -0
- package/dist/components/settings-nav.d.ts +5 -0
- package/dist/components/settings-nav.d.ts.map +1 -0
- package/dist/components/settings-nav.js +45 -0
- package/dist/components/settings-nav.js.map +1 -0
- package/dist/components/user-menu.d.ts +2 -0
- package/dist/components/user-menu.d.ts.map +1 -0
- package/dist/components/user-menu.js +76 -0
- package/dist/components/user-menu.js.map +1 -0
- package/dist/components/workspace-crumb.d.ts +7 -0
- package/dist/components/workspace-crumb.d.ts.map +1 -0
- package/dist/components/workspace-crumb.js +54 -0
- package/dist/components/workspace-crumb.js.map +1 -0
- package/dist/components/workspace-switcher.d.ts +5 -0
- package/dist/components/workspace-switcher.d.ts.map +1 -0
- package/dist/components/workspace-switcher.js +90 -0
- package/dist/components/workspace-switcher.js.map +1 -0
- package/dist/compose/compose-routes.d.ts +6 -0
- package/dist/compose/compose-routes.d.ts.map +1 -0
- package/dist/compose/compose-routes.js +53 -0
- package/dist/compose/compose-routes.js.map +1 -0
- package/dist/compose/errors.d.ts +26 -0
- package/dist/compose/errors.d.ts.map +1 -0
- package/dist/compose/errors.js +39 -0
- package/dist/compose/errors.js.map +1 -0
- package/dist/compose/merge-config.d.ts +4 -0
- package/dist/compose/merge-config.d.ts.map +1 -0
- package/dist/compose/merge-config.js +28 -0
- package/dist/compose/merge-config.js.map +1 -0
- package/dist/compose/normalize-route-path.d.ts +2 -0
- package/dist/compose/normalize-route-path.d.ts.map +1 -0
- package/dist/compose/normalize-route-path.js +7 -0
- package/dist/compose/normalize-route-path.js.map +1 -0
- package/dist/compose/validate-providers.d.ts +3 -0
- package/dist/compose/validate-providers.d.ts.map +1 -0
- package/dist/compose/validate-providers.js +32 -0
- package/dist/compose/validate-providers.js.map +1 -0
- package/dist/compose/validate-registries.d.ts +4 -0
- package/dist/compose/validate-registries.d.ts.map +1 -0
- package/dist/compose/validate-registries.js +50 -0
- package/dist/compose/validate-registries.js.map +1 -0
- package/dist/config/shell-config.d.ts +10 -0
- package/dist/config/shell-config.d.ts.map +1 -0
- package/dist/config/shell-config.js +12 -0
- package/dist/config/shell-config.js.map +1 -0
- package/dist/contract.d.ts +39 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +5 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/active-workspace.d.ts +4 -0
- package/dist/runtime/active-workspace.d.ts.map +1 -0
- package/dist/runtime/active-workspace.js +15 -0
- package/dist/runtime/active-workspace.js.map +1 -0
- package/dist/runtime/anchor-paths.d.ts +9 -0
- package/dist/runtime/anchor-paths.d.ts.map +1 -0
- package/dist/runtime/anchor-paths.js +18 -0
- package/dist/runtime/anchor-paths.js.map +1 -0
- package/dist/runtime/anchors.d.ts +19 -0
- package/dist/runtime/anchors.d.ts.map +1 -0
- package/dist/runtime/anchors.js +125 -0
- package/dist/runtime/anchors.js.map +1 -0
- package/dist/runtime/assemble-route-tree.d.ts +10 -0
- package/dist/runtime/assemble-route-tree.d.ts.map +1 -0
- package/dist/runtime/assemble-route-tree.js +30 -0
- package/dist/runtime/assemble-route-tree.js.map +1 -0
- package/dist/runtime/auth.d.ts +50 -0
- package/dist/runtime/auth.d.ts.map +1 -0
- package/dist/runtime/auth.js +194 -0
- package/dist/runtime/auth.js.map +1 -0
- package/dist/runtime/chrome-context.d.ts +10 -0
- package/dist/runtime/chrome-context.d.ts.map +1 -0
- package/dist/runtime/chrome-context.js +16 -0
- package/dist/runtime/chrome-context.js.map +1 -0
- package/dist/runtime/compose-client-app.d.ts +13 -0
- package/dist/runtime/compose-client-app.d.ts.map +1 -0
- package/dist/runtime/compose-client-app.js +93 -0
- package/dist/runtime/compose-client-app.js.map +1 -0
- package/dist/runtime/define-route.d.ts +19 -0
- package/dist/runtime/define-route.d.ts.map +1 -0
- package/dist/runtime/define-route.js +12 -0
- package/dist/runtime/define-route.js.map +1 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +21 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/last-workspace.d.ts +4 -0
- package/dist/runtime/last-workspace.d.ts.map +1 -0
- package/dist/runtime/last-workspace.js +20 -0
- package/dist/runtime/last-workspace.js.map +1 -0
- package/dist/runtime/nav-order.d.ts +7 -0
- package/dist/runtime/nav-order.d.ts.map +1 -0
- package/dist/runtime/nav-order.js +8 -0
- package/dist/runtime/nav-order.js.map +1 -0
- package/dist/runtime/provider-stack.d.ts +14 -0
- package/dist/runtime/provider-stack.d.ts.map +1 -0
- package/dist/runtime/provider-stack.js +28 -0
- package/dist/runtime/provider-stack.js.map +1 -0
- package/dist/runtime/registries.d.ts +4 -0
- package/dist/runtime/registries.d.ts.map +1 -0
- package/dist/runtime/registries.js +23 -0
- package/dist/runtime/registries.js.map +1 -0
- package/dist/runtime/router-context.d.ts +9 -0
- package/dist/runtime/router-context.d.ts.map +1 -0
- package/dist/runtime/router-context.js +3 -0
- package/dist/runtime/router-context.js.map +1 -0
- package/dist/runtime/workspace-setup.d.ts +18 -0
- package/dist/runtime/workspace-setup.d.ts.map +1 -0
- package/dist/runtime/workspace-setup.js +64 -0
- package/dist/runtime/workspace-setup.js.map +1 -0
- package/dist/testing/index.d.ts +17 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +35 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/dist/vite/evaluate-features.d.ts +8 -0
- package/dist/vite/evaluate-features.d.ts.map +1 -0
- package/dist/vite/evaluate-features.js +80 -0
- package/dist/vite/evaluate-features.js.map +1 -0
- package/dist/vite/generate.d.ts +9 -0
- package/dist/vite/generate.d.ts.map +1 -0
- package/dist/vite/generate.js +80 -0
- package/dist/vite/generate.js.map +1 -0
- package/dist/vite/index.d.ts +2 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +3 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vite/plugin.d.ts +7 -0
- package/dist/vite/plugin.d.ts.map +1 -0
- package/dist/vite/plugin.js +77 -0
- package/dist/vite/plugin.js.map +1 -0
- package/package.json +113 -0
- package/src/components/main-layout.tsx +45 -0
- package/src/components/nav-bar.tsx +41 -0
- package/src/components/nav-tabs.test.tsx +60 -0
- package/src/components/nav-tabs.tsx +47 -0
- package/src/components/not-found-page.tsx +14 -0
- package/src/components/settings-nav.tsx +35 -0
- package/src/components/user-menu.tsx +59 -0
- package/src/components/workspace-crumb.tsx +43 -0
- package/src/components/workspace-switcher.tsx +75 -0
- package/src/compose/compose-routes.test.ts +112 -0
- package/src/compose/compose-routes.ts +60 -0
- package/src/compose/errors.ts +54 -0
- package/src/compose/merge-config.test.ts +26 -0
- package/src/compose/merge-config.ts +27 -0
- package/src/compose/normalize-route-path.ts +6 -0
- package/src/compose/validate-providers.test.ts +31 -0
- package/src/compose/validate-providers.ts +36 -0
- package/src/compose/validate-registries.test.ts +92 -0
- package/src/compose/validate-registries.ts +61 -0
- package/src/config/config-screen.test.tsx +37 -0
- package/src/config/shell-config.ts +10 -0
- package/src/contract.ts +47 -0
- package/src/index.ts +1 -0
- package/src/runtime/active-workspace.ts +13 -0
- package/src/runtime/anchor-paths.ts +18 -0
- package/src/runtime/anchors.tsx +106 -0
- package/src/runtime/assemble-route-tree.ts +42 -0
- package/src/runtime/auth.tsx +213 -0
- package/src/runtime/chrome-context.tsx +22 -0
- package/src/runtime/compose-client-app.tsx +122 -0
- package/src/runtime/define-route.test.ts +15 -0
- package/src/runtime/define-route.ts +33 -0
- package/src/runtime/index.ts +19 -0
- package/src/runtime/last-workspace.ts +22 -0
- package/src/runtime/nav-order.ts +1 -0
- package/src/runtime/provider-stack.tsx +39 -0
- package/src/runtime/providers.test.tsx +51 -0
- package/src/runtime/registries.test.tsx +100 -0
- package/src/runtime/registries.ts +42 -0
- package/src/runtime/router-context.ts +9 -0
- package/src/runtime/routes.test.tsx +48 -0
- package/src/runtime/workspace-setup.tsx +61 -0
- package/src/testing/index.test.tsx +57 -0
- package/src/testing/index.tsx +51 -0
- package/src/vite/evaluate-features.test.ts +34 -0
- package/src/vite/evaluate-features.ts +93 -0
- package/src/vite/generate.test.ts +22 -0
- package/src/vite/generate.ts +99 -0
- package/src/vite/generated-router.test.tsx +58 -0
- package/src/vite/index.ts +1 -0
- package/src/vite/plugin.test.ts +181 -0
- package/src/vite/plugin.ts +114 -0
- package/src/vite/validate.test.ts +166 -0
- package/test/default-route-impl.tsx +3 -0
- package/test/external/FINDINGS.md +53 -0
- package/test/external/README.md +39 -0
- package/test/external/fixture/index.html +12 -0
- package/test/external/fixture/package.json +32 -0
- package/test/external/fixture/src/app.fixture.tsx +72 -0
- package/test/external/fixture/src/features/override-impl.tsx +12 -0
- package/test/external/fixture/src/features/override.tsx +20 -0
- package/test/external/fixture/src/features.collision.ts +15 -0
- package/test/external/fixture/src/features.ts +4 -0
- package/test/external/fixture/src/link-typecheck.tsx +10 -0
- package/test/external/fixture/src/main.tsx +4 -0
- package/test/external/fixture/tsconfig.json +15 -0
- package/test/external/fixture/vite.config.ts +10 -0
- package/test/external/fixture/vitest.config.ts +15 -0
- package/test/external/toy-feature/CHANGELOG.md +15 -0
- package/test/external/toy-feature/node_modules/.bin/shipfox-biome-check +43 -0
- package/test/external/toy-feature/node_modules/.bin/shipfox-biome-format +43 -0
- package/test/external/toy-feature/node_modules/.bin/shipfox-biome-lint +43 -0
- package/test/external/toy-feature/node_modules/.bin/shipfox-swc +43 -0
- package/test/external/toy-feature/node_modules/.bin/shipfox-tsc-check +43 -0
- package/test/external/toy-feature/node_modules/.bin/shipfox-tsc-emit +43 -0
- package/test/external/toy-feature/package.json +57 -0
- package/test/external/toy-feature/src/config.ts +5 -0
- package/test/external/toy-feature/src/index.ts +64 -0
- package/test/external/toy-feature/src/provider.tsx +33 -0
- package/test/external/toy-feature/src/routes/insights.tsx +7 -0
- package/test/external/toy-feature/src/routes/settings.tsx +7 -0
- package/test/external/toy-feature/tsconfig.build.json +5 -0
- package/test/external/toy-feature/tsconfig.json +1 -0
- package/test/external/verify.mjs +318 -0
- package/test/fixtures/features.ts +56 -0
- package/test/named-route-impl.tsx +3 -0
- package/test/not-route-impl.ts +1 -0
- package/test/render.tsx +67 -0
- package/test/search-route-impl.tsx +7 -0
- package/test/setup.ts +3 -0
- package/test/typecheck/shipfox-app.gen.ts +85 -0
- package/test/typecheck/types.tsx +18 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +1 -0
- package/tsconfig.test.json +12 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const anchorPaths = {
|
|
2
|
+
root: '/',
|
|
3
|
+
workspaceLayout: '/workspaces/$wid',
|
|
4
|
+
projectLayout: '/workspaces/$wid/projects/$pid',
|
|
5
|
+
workspaceSettings: '/workspaces/$wid/settings'
|
|
6
|
+
};
|
|
7
|
+
export { anchorPaths };
|
|
8
|
+
export function routePathForAnchor(anchor, fullPath) {
|
|
9
|
+
const anchorPath = anchorPaths[anchor];
|
|
10
|
+
if (anchor === 'root') return fullPath;
|
|
11
|
+
if (fullPath === anchorPath) return '/';
|
|
12
|
+
if (!fullPath.startsWith(`${anchorPath}/`)) {
|
|
13
|
+
throw new Error(`Route "${fullPath}" must be nested under anchor "${anchor}" (${anchorPath}).`);
|
|
14
|
+
}
|
|
15
|
+
return fullPath.slice(anchorPath.length);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=anchor-paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/anchor-paths.ts"],"sourcesContent":["const anchorPaths = {\n root: '/',\n workspaceLayout: '/workspaces/$wid',\n projectLayout: '/workspaces/$wid/projects/$pid',\n workspaceSettings: '/workspaces/$wid/settings',\n} as const;\n\nexport {anchorPaths};\n\nexport function routePathForAnchor(anchor: keyof typeof anchorPaths, fullPath: string): string {\n const anchorPath = anchorPaths[anchor];\n if (anchor === 'root') return fullPath;\n if (fullPath === anchorPath) return '/';\n if (!fullPath.startsWith(`${anchorPath}/`)) {\n throw new Error(`Route \"${fullPath}\" must be nested under anchor \"${anchor}\" (${anchorPath}).`);\n }\n return fullPath.slice(anchorPath.length);\n}\n"],"names":["anchorPaths","root","workspaceLayout","projectLayout","workspaceSettings","routePathForAnchor","anchor","fullPath","anchorPath","startsWith","Error","slice","length"],"mappings":"AAAA,MAAMA,cAAc;IAClBC,MAAM;IACNC,iBAAiB;IACjBC,eAAe;IACfC,mBAAmB;AACrB;AAEA,SAAQJ,WAAW,GAAE;AAErB,OAAO,SAASK,mBAAmBC,MAAgC,EAAEC,QAAgB;IACnF,MAAMC,aAAaR,WAAW,CAACM,OAAO;IACtC,IAAIA,WAAW,QAAQ,OAAOC;IAC9B,IAAIA,aAAaC,YAAY,OAAO;IACpC,IAAI,CAACD,SAASE,UAAU,CAAC,GAAGD,WAAW,CAAC,CAAC,GAAG;QAC1C,MAAM,IAAIE,MAAM,CAAC,OAAO,EAAEH,SAAS,+BAA+B,EAAED,OAAO,GAAG,EAAEE,WAAW,EAAE,CAAC;IAChG;IACA,OAAOD,SAASI,KAAK,CAACH,WAAWI,MAAM;AACzC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NavTabEntry, SettingsSectionEntry } from '#contract.js';
|
|
2
|
+
import type { RouterContext } from './router-context.js';
|
|
3
|
+
export { routePathForAnchor } from './anchor-paths.js';
|
|
4
|
+
export declare function buildAnchorSkeleton({ navigation, settingsSections, }: {
|
|
5
|
+
navigation: readonly NavTabEntry[];
|
|
6
|
+
settingsSections: readonly SettingsSectionEntry[];
|
|
7
|
+
}): {
|
|
8
|
+
anchors: {
|
|
9
|
+
root: import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
10
|
+
workspaceLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
11
|
+
projectLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/projects/$pid", "/workspaces/$wid/projects/$pid", string, "/workspaces/$wid/projects/$pid", undefined, import("@tanstack/router-core").ResolveParams<"/projects/$pid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
12
|
+
workspaceSettings: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/settings", "/workspaces/$wid/settings", string, "/workspaces/$wid/settings", undefined, import("@tanstack/router-core").ResolveParams<"/settings">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
13
|
+
};
|
|
14
|
+
rootRoute: import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
15
|
+
workspaceLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
16
|
+
projectLayout: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/projects/$pid", "/workspaces/$wid/projects/$pid", string, "/workspaces/$wid/projects/$pid", undefined, import("@tanstack/router-core").ResolveParams<"/projects/$pid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
17
|
+
workspaceSettings: import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup.js").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/settings", "/workspaces/$wid/settings", string, "/workspaces/$wid/settings", undefined, import("@tanstack/router-core").ResolveParams<"/settings">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=anchors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchors.d.ts","sourceRoot":"","sources":["../../src/runtime/anchors.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAE,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAKpE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAGvD,OAAO,EAAC,kBAAkB,EAAC,MAAM,mBAAmB,CAAC;AAErD,wBAAgB,mBAAmB,CAAC,EAClC,UAAU,EACV,gBAAgB,GACjB,EAAE;IACD,UAAU,EAAE,SAAS,WAAW,EAAE,CAAC;IACnC,gBAAgB,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACnD;;;;;;;;;;;EAmFA"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ShipfoxLoader } from '@shipfox/react-ui/loader';
|
|
3
|
+
import { Header, Text } from '@shipfox/react-ui/typography';
|
|
4
|
+
import { createRootRouteWithContext, createRoute, Outlet, redirect } from '@tanstack/react-router';
|
|
5
|
+
import { MainLayout } from '#components/main-layout.js';
|
|
6
|
+
import { NotFoundPage } from '#components/not-found-page.js';
|
|
7
|
+
import { SettingsNav } from '#components/settings-nav.js';
|
|
8
|
+
import { useActiveWorkspace } from './active-workspace.js';
|
|
9
|
+
import { anchorPaths } from './anchor-paths.js';
|
|
10
|
+
import { useChrome } from './chrome-context.js';
|
|
11
|
+
import { rememberLastWorkspaceId } from './last-workspace.js';
|
|
12
|
+
import { WorkspaceLayoutErrorRoute, WorkspaceSetupPending } from './workspace-setup.js';
|
|
13
|
+
export { routePathForAnchor } from './anchor-paths.js';
|
|
14
|
+
export function buildAnchorSkeleton({ navigation, settingsSections }) {
|
|
15
|
+
const rootRoute = createRootRouteWithContext()({
|
|
16
|
+
component: Outlet,
|
|
17
|
+
notFoundComponent: NotFoundPage
|
|
18
|
+
});
|
|
19
|
+
const workspaceLayout = createRoute({
|
|
20
|
+
getParentRoute: ()=>rootRoute,
|
|
21
|
+
path: anchorPaths.workspaceLayout,
|
|
22
|
+
beforeLoad: async ({ context, params, location })=>{
|
|
23
|
+
try {
|
|
24
|
+
rememberLastWorkspaceId(params.wid);
|
|
25
|
+
} catch {
|
|
26
|
+
// Local storage is best effort.
|
|
27
|
+
}
|
|
28
|
+
const auth = context.auth;
|
|
29
|
+
if (!auth || auth.isLoading || !context.queryClient) return;
|
|
30
|
+
if (!auth.isAuthenticated) throw redirect({
|
|
31
|
+
to: '/auth/login',
|
|
32
|
+
search: {
|
|
33
|
+
redirect: location.href
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (!auth.workspaces.some((workspace)=>workspace.id === params.wid)) throw redirect({
|
|
37
|
+
to: '/'
|
|
38
|
+
});
|
|
39
|
+
if (!context.workspaceSetup) throw new Error('Client composition includes workspace routes but no workspaceSetup gate.');
|
|
40
|
+
return await context.workspaceSetup({
|
|
41
|
+
queryClient: context.queryClient,
|
|
42
|
+
workspaceId: params.wid,
|
|
43
|
+
pathname: location.pathname
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
pendingComponent: ()=>/*#__PURE__*/ _jsx("div", {
|
|
47
|
+
className: "flex h-screen items-center justify-center",
|
|
48
|
+
children: /*#__PURE__*/ _jsx(ShipfoxLoader, {
|
|
49
|
+
size: 64,
|
|
50
|
+
animation: "circular",
|
|
51
|
+
color: "orange",
|
|
52
|
+
background: "dark"
|
|
53
|
+
})
|
|
54
|
+
}),
|
|
55
|
+
errorComponent: WorkspaceLayoutErrorRoute,
|
|
56
|
+
component: ()=>{
|
|
57
|
+
const setupState = workspaceLayout.useRouteContext();
|
|
58
|
+
return setupState.hideProjectNavigation === undefined ? /*#__PURE__*/ _jsx(WorkspaceSetupPending, {}) : /*#__PURE__*/ _jsx(MainLayout, {
|
|
59
|
+
navigation: navigation,
|
|
60
|
+
hideProjectNavigation: setupState.hideProjectNavigation
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const projectLayout = createRoute({
|
|
65
|
+
getParentRoute: ()=>workspaceLayout,
|
|
66
|
+
path: '/projects/$pid',
|
|
67
|
+
component: ()=>{
|
|
68
|
+
const { ProjectLayoutGuard } = useChrome();
|
|
69
|
+
return /*#__PURE__*/ _jsx(ProjectLayoutGuard, {});
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const workspaceSettings = createRoute({
|
|
73
|
+
getParentRoute: ()=>workspaceLayout,
|
|
74
|
+
path: '/settings',
|
|
75
|
+
component: ()=>{
|
|
76
|
+
const workspace = useActiveWorkspace();
|
|
77
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
78
|
+
className: "flex w-full flex-col gap-24",
|
|
79
|
+
children: [
|
|
80
|
+
/*#__PURE__*/ _jsxs("header", {
|
|
81
|
+
className: "flex flex-col gap-6",
|
|
82
|
+
children: [
|
|
83
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
84
|
+
variant: "h2",
|
|
85
|
+
children: "Workspace settings"
|
|
86
|
+
}),
|
|
87
|
+
/*#__PURE__*/ _jsxs(Text, {
|
|
88
|
+
size: "sm",
|
|
89
|
+
className: "text-foreground-neutral-muted",
|
|
90
|
+
children: [
|
|
91
|
+
"Configure ",
|
|
92
|
+
workspace.name,
|
|
93
|
+
"."
|
|
94
|
+
]
|
|
95
|
+
})
|
|
96
|
+
]
|
|
97
|
+
}),
|
|
98
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
99
|
+
className: "grid grid-cols-[180px_minmax(0,1fr)] gap-32 max-[760px]:grid-cols-1",
|
|
100
|
+
children: [
|
|
101
|
+
/*#__PURE__*/ _jsx(SettingsNav, {
|
|
102
|
+
entries: settingsSections
|
|
103
|
+
}),
|
|
104
|
+
/*#__PURE__*/ _jsx(Outlet, {})
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
anchors: {
|
|
113
|
+
root: rootRoute,
|
|
114
|
+
workspaceLayout,
|
|
115
|
+
projectLayout,
|
|
116
|
+
workspaceSettings
|
|
117
|
+
},
|
|
118
|
+
rootRoute,
|
|
119
|
+
workspaceLayout,
|
|
120
|
+
projectLayout,
|
|
121
|
+
workspaceSettings
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=anchors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/anchors.tsx"],"sourcesContent":["import {ShipfoxLoader} from '@shipfox/react-ui/loader';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {createRootRouteWithContext, createRoute, Outlet, redirect} from '@tanstack/react-router';\nimport {MainLayout} from '#components/main-layout.js';\nimport {NotFoundPage} from '#components/not-found-page.js';\nimport {SettingsNav} from '#components/settings-nav.js';\nimport type {NavTabEntry, SettingsSectionEntry} from '#contract.js';\nimport {useActiveWorkspace} from './active-workspace.js';\nimport {anchorPaths} from './anchor-paths.js';\nimport {useChrome} from './chrome-context.js';\nimport {rememberLastWorkspaceId} from './last-workspace.js';\nimport type {RouterContext} from './router-context.js';\nimport {WorkspaceLayoutErrorRoute, WorkspaceSetupPending} from './workspace-setup.js';\n\nexport {routePathForAnchor} from './anchor-paths.js';\n\nexport function buildAnchorSkeleton({\n navigation,\n settingsSections,\n}: {\n navigation: readonly NavTabEntry[];\n settingsSections: readonly SettingsSectionEntry[];\n}) {\n const rootRoute = createRootRouteWithContext<RouterContext>()({\n component: Outlet,\n notFoundComponent: NotFoundPage,\n });\n const workspaceLayout = createRoute({\n getParentRoute: () => rootRoute,\n path: anchorPaths.workspaceLayout,\n beforeLoad: async ({context, params, location}) => {\n try {\n rememberLastWorkspaceId(params.wid);\n } catch {\n // Local storage is best effort.\n }\n const auth = context.auth;\n if (!auth || auth.isLoading || !context.queryClient) return;\n if (!auth.isAuthenticated)\n throw redirect({to: '/auth/login' as never, search: {redirect: location.href} as never});\n if (!auth.workspaces.some((workspace) => workspace.id === params.wid))\n throw redirect({to: '/'});\n if (!context.workspaceSetup)\n throw new Error('Client composition includes workspace routes but no workspaceSetup gate.');\n return await context.workspaceSetup({\n queryClient: context.queryClient,\n workspaceId: params.wid,\n pathname: location.pathname,\n });\n },\n pendingComponent: () => (\n <div className=\"flex h-screen items-center justify-center\">\n <ShipfoxLoader size={64} animation=\"circular\" color=\"orange\" background=\"dark\" />\n </div>\n ),\n errorComponent: WorkspaceLayoutErrorRoute,\n component: () => {\n const setupState = workspaceLayout.useRouteContext() as {hideProjectNavigation?: boolean};\n return setupState.hideProjectNavigation === undefined ? (\n <WorkspaceSetupPending />\n ) : (\n <MainLayout\n navigation={navigation}\n hideProjectNavigation={setupState.hideProjectNavigation}\n />\n );\n },\n });\n const projectLayout = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/projects/$pid',\n component: () => {\n const {ProjectLayoutGuard} = useChrome();\n return <ProjectLayoutGuard />;\n },\n });\n const workspaceSettings = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/settings',\n component: () => {\n const workspace = useActiveWorkspace();\n return (\n <div className=\"flex w-full flex-col gap-24\">\n <header className=\"flex flex-col gap-6\">\n <Header variant=\"h2\">Workspace settings</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Configure {workspace.name}.\n </Text>\n </header>\n\n <div className=\"grid grid-cols-[180px_minmax(0,1fr)] gap-32 max-[760px]:grid-cols-1\">\n <SettingsNav entries={settingsSections} />\n <Outlet />\n </div>\n </div>\n );\n },\n });\n return {\n anchors: {root: rootRoute, workspaceLayout, projectLayout, workspaceSettings},\n rootRoute,\n workspaceLayout,\n projectLayout,\n workspaceSettings,\n };\n}\n"],"names":["ShipfoxLoader","Header","Text","createRootRouteWithContext","createRoute","Outlet","redirect","MainLayout","NotFoundPage","SettingsNav","useActiveWorkspace","anchorPaths","useChrome","rememberLastWorkspaceId","WorkspaceLayoutErrorRoute","WorkspaceSetupPending","routePathForAnchor","buildAnchorSkeleton","navigation","settingsSections","rootRoute","component","notFoundComponent","workspaceLayout","getParentRoute","path","beforeLoad","context","params","location","wid","auth","isLoading","queryClient","isAuthenticated","to","search","href","workspaces","some","workspace","id","workspaceSetup","Error","workspaceId","pathname","pendingComponent","div","className","size","animation","color","background","errorComponent","setupState","useRouteContext","hideProjectNavigation","undefined","projectLayout","ProjectLayoutGuard","workspaceSettings","header","variant","name","entries","anchors","root"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComposedRoute } from '#compose/compose-routes.js';
|
|
2
|
+
import { buildAnchorSkeleton } from '#runtime/anchors.js';
|
|
3
|
+
import type { RouteImpl } from '#runtime/define-route.js';
|
|
4
|
+
export type ResolveRouteImpl = (specifier: string) => RouteImpl | Promise<RouteImpl>;
|
|
5
|
+
export declare function assembleRouteTree(routes: readonly ComposedRoute[], options: {
|
|
6
|
+
resolveImpl: ResolveRouteImpl;
|
|
7
|
+
navigation: Parameters<typeof buildAnchorSkeleton>[0]['navigation'];
|
|
8
|
+
settingsSections: Parameters<typeof buildAnchorSkeleton>[0]['settingsSections'];
|
|
9
|
+
}): Promise<import("@tanstack/router-core").Route<import("@tanstack/router-core").Register, any, "/", "/", string, "__root__", undefined, {}, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, readonly [...import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], import("@tanstack/router-core").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup").WorkspaceSetupState | undefined>, {}, undefined, readonly [...import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], import("@tanstack/router-core").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/projects/$pid", "/workspaces/$wid/projects/$pid", string, "/workspaces/$wid/projects/$pid", undefined, import("@tanstack/router-core").ResolveParams<"/projects/$pid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], unknown, unknown, unknown, undefined>, import("@tanstack/router-core").Route<unknown, import("@tanstack/react-router").Route<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/workspaces/$wid", "/workspaces/$wid", string, "/workspaces/$wid", undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, ({ context, params, location }: import("@tanstack/router-core").BeforeLoadContextOptions<unknown, import("@tanstack/react-router").RootRoute<import("@tanstack/router-core").Register, undefined, import("./router-context").RouterContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, undefined, import("@tanstack/router-core").ResolveParams<"/workspaces/$wid">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, "/workspaces/$wid", unknown, undefined>) => Promise<import("./workspace-setup").WorkspaceSetupState | undefined>, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/settings", "/workspaces/$wid/settings", string, "/workspaces/$wid/settings", undefined, import("@tanstack/router-core").ResolveParams<"/settings">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, import("@tanstack/react-router").Route<unknown, import("@tanstack/router-core").AnyRoute, "/", "/" | `/${any}/`, string, "/" | `/${any}/`, undefined, import("@tanstack/router-core").ResolveParams<"/">, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, import("@tanstack/router-core").AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>[], unknown, unknown, unknown, undefined>], unknown, unknown, unknown, undefined>], unknown, unknown, unknown, undefined>>;
|
|
10
|
+
//# sourceMappingURL=assemble-route-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble-route-tree.d.ts","sourceRoot":"","sources":["../../src/runtime/assemble-route-tree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAC,mBAAmB,EAAqB,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD,MAAM,MAAM,gBAAgB,GAAG,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAErF,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,OAAO,EAAE;IACP,WAAW,EAAE,gBAAgB,CAAC;IAC9B,UAAU,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IACpE,gBAAgB,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACjF,o+MA4BF"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createRoute } from '@tanstack/react-router';
|
|
2
|
+
import { buildAnchorSkeleton, routePathForAnchor } from '#runtime/anchors.js';
|
|
3
|
+
export async function assembleRouteTree(routes, options) {
|
|
4
|
+
const skeleton = buildAnchorSkeleton(options);
|
|
5
|
+
const children = await Promise.all(routes.map(async (route)=>{
|
|
6
|
+
const impl = await options.resolveImpl(route.impl);
|
|
7
|
+
return {
|
|
8
|
+
parent: route.parent,
|
|
9
|
+
route: createRoute({
|
|
10
|
+
getParentRoute: ()=>skeleton.anchors[route.parent],
|
|
11
|
+
path: routePathForAnchor(route.parent, route.path),
|
|
12
|
+
...impl.options
|
|
13
|
+
})
|
|
14
|
+
};
|
|
15
|
+
}));
|
|
16
|
+
const childrenFor = (anchor)=>children.filter((child)=>child.parent === anchor).map((child)=>child.route);
|
|
17
|
+
const projectLayout = skeleton.projectLayout.addChildren(childrenFor('projectLayout'));
|
|
18
|
+
const workspaceSettings = skeleton.workspaceSettings.addChildren(childrenFor('workspaceSettings'));
|
|
19
|
+
const workspaceLayout = skeleton.workspaceLayout.addChildren([
|
|
20
|
+
...childrenFor('workspaceLayout'),
|
|
21
|
+
projectLayout,
|
|
22
|
+
workspaceSettings
|
|
23
|
+
]);
|
|
24
|
+
return skeleton.rootRoute.addChildren([
|
|
25
|
+
...childrenFor('root'),
|
|
26
|
+
workspaceLayout
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//# sourceMappingURL=assemble-route-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/assemble-route-tree.ts"],"sourcesContent":["import {createRoute} from '@tanstack/react-router';\nimport type {ComposedRoute} from '#compose/compose-routes.js';\nimport {buildAnchorSkeleton, routePathForAnchor} from '#runtime/anchors.js';\nimport type {RouteImpl} from '#runtime/define-route.js';\n\nexport type ResolveRouteImpl = (specifier: string) => RouteImpl | Promise<RouteImpl>;\n\nexport async function assembleRouteTree(\n routes: readonly ComposedRoute[],\n options: {\n resolveImpl: ResolveRouteImpl;\n navigation: Parameters<typeof buildAnchorSkeleton>[0]['navigation'];\n settingsSections: Parameters<typeof buildAnchorSkeleton>[0]['settingsSections'];\n },\n) {\n const skeleton = buildAnchorSkeleton(options);\n const children = await Promise.all(\n routes.map(async (route) => {\n const impl = await options.resolveImpl(route.impl);\n return {\n parent: route.parent,\n route: createRoute({\n getParentRoute: () => skeleton.anchors[route.parent] as never,\n path: routePathForAnchor(route.parent, route.path),\n ...impl.options,\n } as never),\n };\n }),\n );\n const childrenFor = (anchor: keyof typeof skeleton.anchors) =>\n children.filter((child) => child.parent === anchor).map((child) => child.route);\n const projectLayout = skeleton.projectLayout.addChildren(childrenFor('projectLayout'));\n const workspaceSettings = skeleton.workspaceSettings.addChildren(\n childrenFor('workspaceSettings'),\n );\n const workspaceLayout = skeleton.workspaceLayout.addChildren([\n ...childrenFor('workspaceLayout'),\n projectLayout,\n workspaceSettings,\n ]);\n return skeleton.rootRoute.addChildren([...childrenFor('root'), workspaceLayout]);\n}\n"],"names":["createRoute","buildAnchorSkeleton","routePathForAnchor","assembleRouteTree","routes","options","skeleton","children","Promise","all","map","route","impl","resolveImpl","parent","getParentRoute","anchors","path","childrenFor","anchor","filter","child","projectLayout","addChildren","workspaceSettings","workspaceLayout","rootRoute"],"mappings":"AAAA,SAAQA,WAAW,QAAO,yBAAyB;AAEnD,SAAQC,mBAAmB,EAAEC,kBAAkB,QAAO,sBAAsB;AAK5E,OAAO,eAAeC,kBACpBC,MAAgC,EAChCC,OAIC;IAED,MAAMC,WAAWL,oBAAoBI;IACrC,MAAME,WAAW,MAAMC,QAAQC,GAAG,CAChCL,OAAOM,GAAG,CAAC,OAAOC;QAChB,MAAMC,OAAO,MAAMP,QAAQQ,WAAW,CAACF,MAAMC,IAAI;QACjD,OAAO;YACLE,QAAQH,MAAMG,MAAM;YACpBH,OAAOX,YAAY;gBACjBe,gBAAgB,IAAMT,SAASU,OAAO,CAACL,MAAMG,MAAM,CAAC;gBACpDG,MAAMf,mBAAmBS,MAAMG,MAAM,EAAEH,MAAMM,IAAI;gBACjD,GAAGL,KAAKP,OAAO;YACjB;QACF;IACF;IAEF,MAAMa,cAAc,CAACC,SACnBZ,SAASa,MAAM,CAAC,CAACC,QAAUA,MAAMP,MAAM,KAAKK,QAAQT,GAAG,CAAC,CAACW,QAAUA,MAAMV,KAAK;IAChF,MAAMW,gBAAgBhB,SAASgB,aAAa,CAACC,WAAW,CAACL,YAAY;IACrE,MAAMM,oBAAoBlB,SAASkB,iBAAiB,CAACD,WAAW,CAC9DL,YAAY;IAEd,MAAMO,kBAAkBnB,SAASmB,eAAe,CAACF,WAAW,CAAC;WACxDL,YAAY;QACfI;QACAE;KACD;IACD,OAAOlB,SAASoB,SAAS,CAACH,WAAW,CAAC;WAAIL,YAAY;QAASO;KAAgB;AACjF"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { LoginResponseDto, UserDto } from '@shipfox/api-auth-dto';
|
|
2
|
+
import type { MembershipWithWorkspaceDto } from '@shipfox/api-workspaces-dto';
|
|
3
|
+
import type { PropsWithChildren } from 'react';
|
|
4
|
+
export type AuthStatus = 'loading' | 'authenticated' | 'guest';
|
|
5
|
+
export interface Workspace {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
membershipId: string;
|
|
9
|
+
}
|
|
10
|
+
export interface AuthState {
|
|
11
|
+
status: AuthStatus;
|
|
12
|
+
token?: string;
|
|
13
|
+
user?: UserDto;
|
|
14
|
+
workspaces?: Workspace[];
|
|
15
|
+
}
|
|
16
|
+
export interface AuthStateValue extends AuthState {
|
|
17
|
+
isLoading: boolean;
|
|
18
|
+
isAuthenticated: boolean;
|
|
19
|
+
workspaces: Workspace[];
|
|
20
|
+
hasWorkspace: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare const initialAuthState: AuthState;
|
|
23
|
+
export declare const authStateAtom: import("jotai").PrimitiveAtom<AuthState> & {
|
|
24
|
+
init: AuthState;
|
|
25
|
+
};
|
|
26
|
+
export declare const authRefreshQueryKey: readonly ["auth", "refresh"];
|
|
27
|
+
export declare const userWorkspacesQueryKey: readonly ["workspaces", "mine"];
|
|
28
|
+
export declare function toAuthenticatedState(result: LoginResponseDto, memberships?: MembershipWithWorkspaceDto[]): AuthState;
|
|
29
|
+
export declare function useAuthState(): AuthStateValue;
|
|
30
|
+
export declare function listUserWorkspaces(token?: string): Promise<{
|
|
31
|
+
memberships: MembershipWithWorkspaceDto[];
|
|
32
|
+
}>;
|
|
33
|
+
export declare function getAuthRefreshDelayMs(token: string, nowMs?: number): number | undefined;
|
|
34
|
+
export declare function useRefreshAuth(): () => Promise<{
|
|
35
|
+
token: string;
|
|
36
|
+
user: {
|
|
37
|
+
id: string;
|
|
38
|
+
email: string;
|
|
39
|
+
name: string | null;
|
|
40
|
+
email_verified_at: string | null;
|
|
41
|
+
status: "active" | "suspended" | "deleted";
|
|
42
|
+
created_at: string;
|
|
43
|
+
updated_at: string;
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
46
|
+
export interface AuthRuntimeProps extends PropsWithChildren {
|
|
47
|
+
effects?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function AuthRuntime({ children, effects }: AuthRuntimeProps): import("react").ReactNode;
|
|
50
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/runtime/auth.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,gBAAgB,EAAE,OAAO,EAAC,MAAM,uBAAuB,CAAC;AACrE,OAAO,KAAK,EAAC,0BAA0B,EAAC,MAAM,6BAA6B,CAAC;AAI5E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAO7C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,eAAe,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,UAAU,CAAC,EAAE,SAAS,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAe,SAAQ,SAAS;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,eAAO,MAAM,gBAAgB,EAAE,SAA+B,CAAC;AAC/D,eAAO,MAAM,aAAa;;CAAoC,CAAC;AAC/D,eAAO,MAAM,mBAAmB,8BAA+B,CAAC;AAChE,eAAO,MAAM,sBAAsB,iCAAkC,CAAC;AAEtE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,EACxB,WAAW,GAAE,0BAA0B,EAAO,GAC7C,SAAS,CAWX;AAED,wBAAgB,YAAY,IAAI,cAAc,CAY7C;AAED,wBAAsB,kBAAkB,CAAC,KAAK,CAAC,EAAE,MAAM;iBACf,0BAA0B,EAAE;GAInE;AAwBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,MAAM,GAAG,SAAS,CAG3F;AAED,wBAAgB,cAAc;;;;;;;;;;;GA8B7B;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,EAAC,QAAQ,EAAE,OAAc,EAAC,EAAE,gBAAgB,6BAyEvE"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { ApiError, apiRequest, configureApiClient } from '@shipfox/client-api';
|
|
2
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { atom, useAtomValue, useSetAtom, useStore } from 'jotai';
|
|
4
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
5
|
+
const REFRESH_EARLY_MS = 5 * 60 * 1000;
|
|
6
|
+
const REFRESH_RETRY_DELAY_MS = 60_000;
|
|
7
|
+
const BASE64_URL_REPLACEMENTS = {
|
|
8
|
+
dash: /-/g,
|
|
9
|
+
underscore: /_/g
|
|
10
|
+
};
|
|
11
|
+
export const initialAuthState = {
|
|
12
|
+
status: 'loading'
|
|
13
|
+
};
|
|
14
|
+
export const authStateAtom = atom(initialAuthState);
|
|
15
|
+
export const authRefreshQueryKey = [
|
|
16
|
+
'auth',
|
|
17
|
+
'refresh'
|
|
18
|
+
];
|
|
19
|
+
export const userWorkspacesQueryKey = [
|
|
20
|
+
'workspaces',
|
|
21
|
+
'mine'
|
|
22
|
+
];
|
|
23
|
+
export function toAuthenticatedState(result, memberships = []) {
|
|
24
|
+
return {
|
|
25
|
+
status: 'authenticated',
|
|
26
|
+
token: result.token,
|
|
27
|
+
user: result.user,
|
|
28
|
+
workspaces: memberships.map((membership)=>({
|
|
29
|
+
id: membership.workspace_id,
|
|
30
|
+
name: membership.workspace_name,
|
|
31
|
+
membershipId: membership.id
|
|
32
|
+
}))
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function useAuthState() {
|
|
36
|
+
const state = useAtomValue(authStateAtom);
|
|
37
|
+
return useMemo(()=>({
|
|
38
|
+
...state,
|
|
39
|
+
workspaces: state.workspaces ?? [],
|
|
40
|
+
isLoading: state.status === 'loading',
|
|
41
|
+
isAuthenticated: state.status === 'authenticated',
|
|
42
|
+
hasWorkspace: (state.workspaces ?? []).length > 0
|
|
43
|
+
}), [
|
|
44
|
+
state
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
export async function listUserWorkspaces(token) {
|
|
48
|
+
return await apiRequest('/workspaces', token ? {
|
|
49
|
+
headers: {
|
|
50
|
+
authorization: `Bearer ${token}`
|
|
51
|
+
}
|
|
52
|
+
} : {});
|
|
53
|
+
}
|
|
54
|
+
async function refreshAuthRequest() {
|
|
55
|
+
return await apiRequest('/auth/refresh', {
|
|
56
|
+
method: 'POST'
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function decodeBase64Url(value) {
|
|
60
|
+
const base64 = value.replace(BASE64_URL_REPLACEMENTS.dash, '+').replace(BASE64_URL_REPLACEMENTS.underscore, '/');
|
|
61
|
+
return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));
|
|
62
|
+
}
|
|
63
|
+
function readJwtExp(token) {
|
|
64
|
+
const [, payload] = token.split('.');
|
|
65
|
+
if (!payload) return undefined;
|
|
66
|
+
try {
|
|
67
|
+
const parsed = JSON.parse(decodeBase64Url(payload));
|
|
68
|
+
return typeof parsed.exp === 'number' && Number.isFinite(parsed.exp) ? parsed.exp : undefined;
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export function getAuthRefreshDelayMs(token, nowMs = Date.now()) {
|
|
74
|
+
const exp = readJwtExp(token);
|
|
75
|
+
return exp === undefined ? undefined : exp * 1000 - nowMs - REFRESH_EARLY_MS;
|
|
76
|
+
}
|
|
77
|
+
export function useRefreshAuth() {
|
|
78
|
+
const queryClient = useQueryClient();
|
|
79
|
+
const setState = useSetAtom(authStateAtom);
|
|
80
|
+
return useCallback(async ()=>{
|
|
81
|
+
try {
|
|
82
|
+
const result = await queryClient.fetchQuery({
|
|
83
|
+
queryKey: authRefreshQueryKey,
|
|
84
|
+
queryFn: refreshAuthRequest,
|
|
85
|
+
retry: false,
|
|
86
|
+
staleTime: 0
|
|
87
|
+
});
|
|
88
|
+
let memberships = [];
|
|
89
|
+
try {
|
|
90
|
+
const workspaces = await queryClient.fetchQuery({
|
|
91
|
+
queryKey: userWorkspacesQueryKey,
|
|
92
|
+
queryFn: ()=>listUserWorkspaces(result.token),
|
|
93
|
+
retry: false,
|
|
94
|
+
staleTime: 0
|
|
95
|
+
});
|
|
96
|
+
memberships = workspaces.memberships;
|
|
97
|
+
} catch {
|
|
98
|
+
// A valid session remains usable while workspace hydration retries on the next route load.
|
|
99
|
+
}
|
|
100
|
+
setState(toAuthenticatedState(result, memberships));
|
|
101
|
+
return result;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error instanceof ApiError && error.status === 401) setState({
|
|
104
|
+
status: 'guest'
|
|
105
|
+
});
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}, [
|
|
109
|
+
queryClient,
|
|
110
|
+
setState
|
|
111
|
+
]);
|
|
112
|
+
}
|
|
113
|
+
export function AuthRuntime({ children, effects = true }) {
|
|
114
|
+
const store = useStore();
|
|
115
|
+
const authState = useAtomValue(authStateAtom);
|
|
116
|
+
const refreshAuth = useRefreshAuth();
|
|
117
|
+
useEffect(()=>{
|
|
118
|
+
if (!effects) return;
|
|
119
|
+
configureApiClient({
|
|
120
|
+
getAccessToken: ()=>store.get(authStateAtom).token,
|
|
121
|
+
refreshAccessToken: async ()=>(await refreshAuth()).token
|
|
122
|
+
});
|
|
123
|
+
}, [
|
|
124
|
+
effects,
|
|
125
|
+
refreshAuth,
|
|
126
|
+
store
|
|
127
|
+
]);
|
|
128
|
+
useEffect(()=>{
|
|
129
|
+
if (!effects) return;
|
|
130
|
+
refreshAuth().catch(()=>undefined);
|
|
131
|
+
}, [
|
|
132
|
+
effects,
|
|
133
|
+
refreshAuth
|
|
134
|
+
]);
|
|
135
|
+
useEffect(()=>{
|
|
136
|
+
if (!effects || authState.status !== 'authenticated' || !authState.token) return;
|
|
137
|
+
let timeout;
|
|
138
|
+
let disposed = false;
|
|
139
|
+
let refreshing = false;
|
|
140
|
+
const clearRefreshTimer = ()=>{
|
|
141
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
142
|
+
timeout = undefined;
|
|
143
|
+
};
|
|
144
|
+
const scheduleRefresh = (delayMs)=>{
|
|
145
|
+
clearRefreshTimer();
|
|
146
|
+
timeout = setTimeout(runRefresh, Math.max(0, delayMs));
|
|
147
|
+
};
|
|
148
|
+
const retryIfStillDue = ()=>{
|
|
149
|
+
const current = store.get(authStateAtom);
|
|
150
|
+
if (current.status !== 'authenticated' || !current.token) return;
|
|
151
|
+
const delay = getAuthRefreshDelayMs(current.token);
|
|
152
|
+
if (delay !== undefined && delay <= 0) scheduleRefresh(REFRESH_RETRY_DELAY_MS);
|
|
153
|
+
};
|
|
154
|
+
function runRefresh() {
|
|
155
|
+
if (disposed || refreshing) return;
|
|
156
|
+
refreshing = true;
|
|
157
|
+
clearRefreshTimer();
|
|
158
|
+
refreshAuth().catch(()=>undefined).finally(()=>{
|
|
159
|
+
refreshing = false;
|
|
160
|
+
if (!disposed) retryIfStillDue();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
const refreshIfDue = ()=>{
|
|
164
|
+
const current = store.get(authStateAtom);
|
|
165
|
+
if (current.status !== 'authenticated' || !current.token) return;
|
|
166
|
+
const delay = getAuthRefreshDelayMs(current.token);
|
|
167
|
+
if (delay !== undefined && delay <= 0) runRefresh();
|
|
168
|
+
};
|
|
169
|
+
const refreshIfVisible = ()=>{
|
|
170
|
+
if (document.visibilityState === 'visible') refreshIfDue();
|
|
171
|
+
};
|
|
172
|
+
const delay = getAuthRefreshDelayMs(authState.token);
|
|
173
|
+
if (delay !== undefined) scheduleRefresh(delay);
|
|
174
|
+
window.addEventListener('focus', refreshIfDue);
|
|
175
|
+
window.addEventListener('online', refreshIfDue);
|
|
176
|
+
document.addEventListener('visibilitychange', refreshIfVisible);
|
|
177
|
+
return ()=>{
|
|
178
|
+
disposed = true;
|
|
179
|
+
clearRefreshTimer();
|
|
180
|
+
window.removeEventListener('focus', refreshIfDue);
|
|
181
|
+
window.removeEventListener('online', refreshIfDue);
|
|
182
|
+
document.removeEventListener('visibilitychange', refreshIfVisible);
|
|
183
|
+
};
|
|
184
|
+
}, [
|
|
185
|
+
authState.status,
|
|
186
|
+
authState.token,
|
|
187
|
+
effects,
|
|
188
|
+
refreshAuth,
|
|
189
|
+
store
|
|
190
|
+
]);
|
|
191
|
+
return children;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/auth.tsx"],"sourcesContent":["import type {LoginResponseDto, UserDto} from '@shipfox/api-auth-dto';\nimport type {MembershipWithWorkspaceDto} from '@shipfox/api-workspaces-dto';\nimport {ApiError, apiRequest, configureApiClient} from '@shipfox/client-api';\nimport {useQueryClient} from '@tanstack/react-query';\nimport {atom, useAtomValue, useSetAtom, useStore} from 'jotai';\nimport type {PropsWithChildren} from 'react';\nimport {useCallback, useEffect, useMemo} from 'react';\n\nconst REFRESH_EARLY_MS = 5 * 60 * 1000;\nconst REFRESH_RETRY_DELAY_MS = 60_000;\nconst BASE64_URL_REPLACEMENTS = {dash: /-/g, underscore: /_/g} as const;\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'guest';\n\nexport interface Workspace {\n id: string;\n name: string;\n membershipId: string;\n}\n\nexport interface AuthState {\n status: AuthStatus;\n token?: string;\n user?: UserDto;\n workspaces?: Workspace[];\n}\n\nexport interface AuthStateValue extends AuthState {\n isLoading: boolean;\n isAuthenticated: boolean;\n workspaces: Workspace[];\n hasWorkspace: boolean;\n}\n\nexport const initialAuthState: AuthState = {status: 'loading'};\nexport const authStateAtom = atom<AuthState>(initialAuthState);\nexport const authRefreshQueryKey = ['auth', 'refresh'] as const;\nexport const userWorkspacesQueryKey = ['workspaces', 'mine'] as const;\n\nexport function toAuthenticatedState(\n result: LoginResponseDto,\n memberships: MembershipWithWorkspaceDto[] = [],\n): AuthState {\n return {\n status: 'authenticated',\n token: result.token,\n user: result.user,\n workspaces: memberships.map((membership) => ({\n id: membership.workspace_id,\n name: membership.workspace_name,\n membershipId: membership.id,\n })),\n };\n}\n\nexport function useAuthState(): AuthStateValue {\n const state = useAtomValue(authStateAtom);\n return useMemo(\n () => ({\n ...state,\n workspaces: state.workspaces ?? [],\n isLoading: state.status === 'loading',\n isAuthenticated: state.status === 'authenticated',\n hasWorkspace: (state.workspaces ?? []).length > 0,\n }),\n [state],\n );\n}\n\nexport async function listUserWorkspaces(token?: string) {\n return await apiRequest<{memberships: MembershipWithWorkspaceDto[]}>(\n '/workspaces',\n token ? {headers: {authorization: `Bearer ${token}`}} : {},\n );\n}\n\nasync function refreshAuthRequest() {\n return await apiRequest<LoginResponseDto>('/auth/refresh', {method: 'POST'});\n}\n\nfunction decodeBase64Url(value: string): string {\n const base64 = value\n .replace(BASE64_URL_REPLACEMENTS.dash, '+')\n .replace(BASE64_URL_REPLACEMENTS.underscore, '/');\n return atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, '='));\n}\n\nfunction readJwtExp(token: string): number | undefined {\n const [, payload] = token.split('.');\n if (!payload) return undefined;\n try {\n const parsed = JSON.parse(decodeBase64Url(payload)) as {exp?: unknown};\n return typeof parsed.exp === 'number' && Number.isFinite(parsed.exp) ? parsed.exp : undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function getAuthRefreshDelayMs(token: string, nowMs = Date.now()): number | undefined {\n const exp = readJwtExp(token);\n return exp === undefined ? undefined : exp * 1000 - nowMs - REFRESH_EARLY_MS;\n}\n\nexport function useRefreshAuth() {\n const queryClient = useQueryClient();\n const setState = useSetAtom(authStateAtom);\n return useCallback(async () => {\n try {\n const result = await queryClient.fetchQuery({\n queryKey: authRefreshQueryKey,\n queryFn: refreshAuthRequest,\n retry: false,\n staleTime: 0,\n });\n let memberships: MembershipWithWorkspaceDto[] = [];\n try {\n const workspaces = await queryClient.fetchQuery({\n queryKey: userWorkspacesQueryKey,\n queryFn: () => listUserWorkspaces(result.token),\n retry: false,\n staleTime: 0,\n });\n memberships = workspaces.memberships;\n } catch {\n // A valid session remains usable while workspace hydration retries on the next route load.\n }\n setState(toAuthenticatedState(result, memberships));\n return result;\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) setState({status: 'guest'});\n throw error;\n }\n }, [queryClient, setState]);\n}\n\nexport interface AuthRuntimeProps extends PropsWithChildren {\n effects?: boolean;\n}\n\nexport function AuthRuntime({children, effects = true}: AuthRuntimeProps) {\n const store = useStore();\n const authState = useAtomValue(authStateAtom);\n const refreshAuth = useRefreshAuth();\n\n useEffect(() => {\n if (!effects) return;\n configureApiClient({\n getAccessToken: () => store.get(authStateAtom).token,\n refreshAccessToken: async () => (await refreshAuth()).token,\n });\n }, [effects, refreshAuth, store]);\n\n useEffect(() => {\n if (!effects) return;\n refreshAuth().catch(() => undefined);\n }, [effects, refreshAuth]);\n\n useEffect(() => {\n if (!effects || authState.status !== 'authenticated' || !authState.token) return;\n\n let timeout: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n let refreshing = false;\n const clearRefreshTimer = () => {\n if (timeout !== undefined) clearTimeout(timeout);\n timeout = undefined;\n };\n const scheduleRefresh = (delayMs: number) => {\n clearRefreshTimer();\n timeout = setTimeout(runRefresh, Math.max(0, delayMs));\n };\n const retryIfStillDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) scheduleRefresh(REFRESH_RETRY_DELAY_MS);\n };\n function runRefresh() {\n if (disposed || refreshing) return;\n refreshing = true;\n clearRefreshTimer();\n refreshAuth()\n .catch(() => undefined)\n .finally(() => {\n refreshing = false;\n if (!disposed) retryIfStillDue();\n });\n }\n const refreshIfDue = () => {\n const current = store.get(authStateAtom);\n if (current.status !== 'authenticated' || !current.token) return;\n const delay = getAuthRefreshDelayMs(current.token);\n if (delay !== undefined && delay <= 0) runRefresh();\n };\n const refreshIfVisible = () => {\n if (document.visibilityState === 'visible') refreshIfDue();\n };\n const delay = getAuthRefreshDelayMs(authState.token);\n if (delay !== undefined) scheduleRefresh(delay);\n window.addEventListener('focus', refreshIfDue);\n window.addEventListener('online', refreshIfDue);\n document.addEventListener('visibilitychange', refreshIfVisible);\n return () => {\n disposed = true;\n clearRefreshTimer();\n window.removeEventListener('focus', refreshIfDue);\n window.removeEventListener('online', refreshIfDue);\n document.removeEventListener('visibilitychange', refreshIfVisible);\n };\n }, [authState.status, authState.token, effects, refreshAuth, store]);\n\n return children;\n}\n"],"names":["ApiError","apiRequest","configureApiClient","useQueryClient","atom","useAtomValue","useSetAtom","useStore","useCallback","useEffect","useMemo","REFRESH_EARLY_MS","REFRESH_RETRY_DELAY_MS","BASE64_URL_REPLACEMENTS","dash","underscore","initialAuthState","status","authStateAtom","authRefreshQueryKey","userWorkspacesQueryKey","toAuthenticatedState","result","memberships","token","user","workspaces","map","membership","id","workspace_id","name","workspace_name","membershipId","useAuthState","state","isLoading","isAuthenticated","hasWorkspace","length","listUserWorkspaces","headers","authorization","refreshAuthRequest","method","decodeBase64Url","value","base64","replace","atob","padEnd","Math","ceil","readJwtExp","payload","split","undefined","parsed","JSON","parse","exp","Number","isFinite","getAuthRefreshDelayMs","nowMs","Date","now","useRefreshAuth","queryClient","setState","fetchQuery","queryKey","queryFn","retry","staleTime","error","AuthRuntime","children","effects","store","authState","refreshAuth","getAccessToken","get","refreshAccessToken","catch","timeout","disposed","refreshing","clearRefreshTimer","clearTimeout","scheduleRefresh","delayMs","setTimeout","runRefresh","max","retryIfStillDue","current","delay","finally","refreshIfDue","refreshIfVisible","document","visibilityState","window","addEventListener","removeEventListener"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentType, PropsWithChildren } from 'react';
|
|
2
|
+
export interface ChromeSlots {
|
|
3
|
+
ProjectBreadcrumb: ComponentType;
|
|
4
|
+
ProjectLayoutGuard: ComponentType;
|
|
5
|
+
}
|
|
6
|
+
export declare function ChromeProvider({ chrome, children, }: PropsWithChildren<{
|
|
7
|
+
chrome: ChromeSlots | undefined;
|
|
8
|
+
}>): import("react").JSX.Element;
|
|
9
|
+
export declare function useChrome(): ChromeSlots;
|
|
10
|
+
//# sourceMappingURL=chrome-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome-context.d.ts","sourceRoot":"","sources":["../../src/runtime/chrome-context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,aAAa,EAAE,iBAAiB,EAAC,MAAM,OAAO,CAAC;AAG5D,MAAM,WAAW,WAAW;IAC1B,iBAAiB,EAAE,aAAa,CAAC;IACjC,kBAAkB,EAAE,aAAa,CAAC;CACnC;AAID,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,QAAQ,GACT,EAAE,iBAAiB,CAAC;IAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAAA;CAAC,CAAC,+BAEtD;AAED,wBAAgB,SAAS,IAAI,WAAW,CAIvC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
const ChromeContext = /*#__PURE__*/ createContext(undefined);
|
|
4
|
+
export function ChromeProvider({ chrome, children }) {
|
|
5
|
+
return /*#__PURE__*/ _jsx(ChromeContext.Provider, {
|
|
6
|
+
value: chrome,
|
|
7
|
+
children: children
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function useChrome() {
|
|
11
|
+
const chrome = useContext(ChromeContext);
|
|
12
|
+
if (!chrome) throw new Error('Client composition must provide browser chrome slots.');
|
|
13
|
+
return chrome;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=chrome-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/runtime/chrome-context.tsx"],"sourcesContent":["import type {ComponentType, PropsWithChildren} from 'react';\nimport {createContext, useContext} from 'react';\n\nexport interface ChromeSlots {\n ProjectBreadcrumb: ComponentType;\n ProjectLayoutGuard: ComponentType;\n}\n\nconst ChromeContext = createContext<ChromeSlots | undefined>(undefined);\n\nexport function ChromeProvider({\n chrome,\n children,\n}: PropsWithChildren<{chrome: ChromeSlots | undefined}>) {\n return <ChromeContext.Provider value={chrome}>{children}</ChromeContext.Provider>;\n}\n\nexport function useChrome(): ChromeSlots {\n const chrome = useContext(ChromeContext);\n if (!chrome) throw new Error('Client composition must provide browser chrome slots.');\n return chrome;\n}\n"],"names":["createContext","useContext","ChromeContext","undefined","ChromeProvider","chrome","children","Provider","value","useChrome","Error"],"mappings":";AACA,SAAQA,aAAa,EAAEC,UAAU,QAAO,QAAQ;AAOhD,MAAMC,8BAAgBF,cAAuCG;AAE7D,OAAO,SAASC,eAAe,EAC7BC,MAAM,EACNC,QAAQ,EAC6C;IACrD,qBAAO,KAACJ,cAAcK,QAAQ;QAACC,OAAOH;kBAASC;;AACjD;AAEA,OAAO,SAASG;IACd,MAAMJ,SAASJ,WAAWC;IAC1B,IAAI,CAACG,QAAQ,MAAM,IAAIK,MAAM;IAC7B,OAAOL;AACT"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnyRouter } from '@tanstack/react-router';
|
|
2
|
+
import type { ClientFeature } from '#contract.js';
|
|
3
|
+
import { type ChromeSlots } from './chrome-context.js';
|
|
4
|
+
import type { WorkspaceSetupGate } from './workspace-setup.js';
|
|
5
|
+
export declare function composeClientApp({ features, router, chrome, workspaceSetup, }: {
|
|
6
|
+
features: readonly ClientFeature[];
|
|
7
|
+
router: AnyRouter;
|
|
8
|
+
chrome?: ChromeSlots;
|
|
9
|
+
workspaceSetup?: WorkspaceSetupGate;
|
|
10
|
+
}): {
|
|
11
|
+
mount(element: HTMLElement): void;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=compose-client-app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-client-app.d.ts","sourceRoot":"","sources":["../../src/runtime/compose-client-app.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAC,KAAK,SAAS,EAAiB,MAAM,wBAAwB,CAAC;AAQtE,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,cAAc,CAAC;AAEhD,OAAO,EAAiB,KAAK,WAAW,EAAC,MAAM,qBAAqB,CAAC;AAErE,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,sBAAsB,CAAC;AAE7D,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,MAAM,EACN,MAAM,EACN,cAAc,GACf,EAAE;IACD,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,cAAc,CAAC,EAAE,kBAAkB,CAAC;CACrC;mBAkBkB,WAAW,GAAG,IAAI;EAiCpC"}
|