@shipfox/client-shell 22.0.0 → 22.0.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"main-layout.d.ts","sourceRoot":"","sources":["../../src/components/main-layout.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAMxD,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,qBAAqB;QAC7B,KAAK,CAAC,EAAE,UAAU,CAAC;KACpB;CACF;AAQD,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,qBAA6B,GAC9B,EAAE;IACD,UAAU,EAAE,SAAS,WAAW,EAAE,CAAC;IACnC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,+BA4BA"}
1
+ {"version":3,"file":"main-layout.d.ts","sourceRoot":"","sources":["../../src/components/main-layout.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,cAAc,CAAC;AAG9C,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,yBAAyB,CAAC;AAOxD,OAAO,QAAQ,wBAAwB,CAAC;IACtC,UAAU,qBAAqB;QAC7B,KAAK,CAAC,EAAE,UAAU,CAAC;KACpB;CACF;AAQD,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,qBAA6B,GAC9B,EAAE;IACD,UAAU,EAAE,SAAS,WAAW,EAAE,CAAC;IACnC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC,+BAoCA"}
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { FullPageLoader } from '@shipfox/react-ui/loader';
3
- import { Outlet, useMatches } from '@tanstack/react-router';
3
+ import { Navigate, Outlet, useLocation, useMatches } from '@tanstack/react-router';
4
4
  import { useMaybeActiveWorkspace } from '#runtime/active-workspace.js';
5
+ import { useAuthState } from '#runtime/auth.js';
5
6
  import { parseWorkspaceProjectParams, useRouteParams } from '#runtime/route-inputs.js';
7
+ import { WorkspaceUnavailablePage } from '#runtime/workspace-setup.js';
6
8
  import { FOCUSED_FRAME_CONTENT_CLASS_NAME } from './focused-frame.js';
7
9
  import { NavBar } from './nav-bar.js';
8
10
  import { NavTabs } from './nav-tabs.js';
@@ -12,10 +14,22 @@ const frameClassNames = {
12
14
  focused: `${FOCUSED_FRAME_CONTENT_CLASS_NAME} px-frame py-frame`
13
15
  };
14
16
  export function MainLayout({ navigation, hideProjectNavigation = false }) {
17
+ const auth = useAuthState();
15
18
  const workspace = useMaybeActiveWorkspace();
19
+ const location = useLocation();
16
20
  const { projectSlug } = useRouteParams(parseWorkspaceProjectParams);
17
21
  const matches = useMatches();
18
- if (!workspace) return /*#__PURE__*/ _jsx(FullPageLoader, {});
22
+ if (auth.isLoading) return /*#__PURE__*/ _jsx(FullPageLoader, {});
23
+ if (!auth.isAuthenticated) {
24
+ return /*#__PURE__*/ _jsx(Navigate, {
25
+ to: '/auth/login',
26
+ search: {
27
+ redirect: location.href
28
+ },
29
+ replace: true
30
+ });
31
+ }
32
+ if (!workspace) return /*#__PURE__*/ _jsx(WorkspaceUnavailablePage, {});
19
33
  const frame = matches.reduce((current, match)=>match.staticData.frame ?? current, 'content');
20
34
  const appContentHeight = hideProjectNavigation ? '[--app-content-h:calc(100dvh_-_56px)]' : '[--app-content-h:calc(100dvh_-_96px)]';
21
35
  const isFullBleedFrame = frame === 'data';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/main-layout.tsx"],"sourcesContent":["import {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Outlet, useMatches} from '@tanstack/react-router';\nimport type {NavTabEntry} from '#contract.js';\nimport {useMaybeActiveWorkspace} from '#runtime/active-workspace.js';\nimport type {RouteFrame} from '#runtime/route-frame.js';\nimport {parseWorkspaceProjectParams, useRouteParams} from '#runtime/route-inputs.js';\nimport {FOCUSED_FRAME_CONTENT_CLASS_NAME} from './focused-frame.js';\nimport {NavBar} from './nav-bar.js';\nimport {NavTabs} from './nav-tabs.js';\n\ndeclare module '@tanstack/react-router' {\n interface StaticDataRouteOption {\n frame?: RouteFrame;\n }\n}\n\nconst frameClassNames: Record<RouteFrame, string> = {\n content: 'mx-auto w-full max-w-[1120px] px-frame py-frame',\n data: 'flex min-h-0 w-full flex-1 flex-col px-frame py-frame',\n focused: `${FOCUSED_FRAME_CONTENT_CLASS_NAME} px-frame py-frame`,\n};\n\nexport function MainLayout({\n navigation,\n hideProjectNavigation = false,\n}: {\n navigation: readonly NavTabEntry[];\n hideProjectNavigation?: boolean;\n}) {\n const workspace = useMaybeActiveWorkspace();\n const {projectSlug} = useRouteParams(parseWorkspaceProjectParams);\n const matches = useMatches();\n if (!workspace) return <FullPageLoader />;\n const frame = matches.reduce<RouteFrame>(\n (current, match) => match.staticData.frame ?? current,\n 'content',\n );\n const appContentHeight = hideProjectNavigation\n ? '[--app-content-h:calc(100dvh_-_56px)]'\n : '[--app-content-h:calc(100dvh_-_96px)]';\n const isFullBleedFrame = frame === 'data';\n return (\n <div className=\"h-screen w-full flex flex-col bg-background-subtle-base\">\n <NavBar hideProjectNavigation={hideProjectNavigation} />\n {hideProjectNavigation ? undefined : (\n <NavTabs entries={navigation} scope={projectSlug ? 'project' : 'workspace'} />\n )}\n <main\n className={`${isFullBleedFrame ? 'flex min-h-0 flex-1 flex-col overflow-hidden' : 'flex-1 overflow-auto'} ${appContentHeight}`}\n >\n <div className={frameClassNames[frame]}>\n <Outlet />\n </div>\n </main>\n </div>\n );\n}\n"],"names":["FullPageLoader","Outlet","useMatches","useMaybeActiveWorkspace","parseWorkspaceProjectParams","useRouteParams","FOCUSED_FRAME_CONTENT_CLASS_NAME","NavBar","NavTabs","frameClassNames","content","data","focused","MainLayout","navigation","hideProjectNavigation","workspace","projectSlug","matches","frame","reduce","current","match","staticData","appContentHeight","isFullBleedFrame","div","className","undefined","entries","scope","main"],"mappings":";AAAA,SAAQA,cAAc,QAAO,2BAA2B;AACxD,SAAQC,MAAM,EAAEC,UAAU,QAAO,yBAAyB;AAE1D,SAAQC,uBAAuB,QAAO,+BAA+B;AAErE,SAAQC,2BAA2B,EAAEC,cAAc,QAAO,2BAA2B;AACrF,SAAQC,gCAAgC,QAAO,qBAAqB;AACpE,SAAQC,MAAM,QAAO,eAAe;AACpC,SAAQC,OAAO,QAAO,gBAAgB;AAQtC,MAAMC,kBAA8C;IAClDC,SAAS;IACTC,MAAM;IACNC,SAAS,GAAGN,iCAAiC,kBAAkB,CAAC;AAClE;AAEA,OAAO,SAASO,WAAW,EACzBC,UAAU,EACVC,wBAAwB,KAAK,EAI9B;IACC,MAAMC,YAAYb;IAClB,MAAM,EAACc,WAAW,EAAC,GAAGZ,eAAeD;IACrC,MAAMc,UAAUhB;IAChB,IAAI,CAACc,WAAW,qBAAO,KAAChB;IACxB,MAAMmB,QAAQD,QAAQE,MAAM,CAC1B,CAACC,SAASC,QAAUA,MAAMC,UAAU,CAACJ,KAAK,IAAIE,SAC9C;IAEF,MAAMG,mBAAmBT,wBACrB,0CACA;IACJ,MAAMU,mBAAmBN,UAAU;IACnC,qBACE,MAACO;QAAIC,WAAU;;0BACb,KAACpB;gBAAOQ,uBAAuBA;;YAC9BA,wBAAwBa,0BACvB,KAACpB;gBAAQqB,SAASf;gBAAYgB,OAAOb,cAAc,YAAY;;0BAEjE,KAACc;gBACCJ,WAAW,GAAGF,mBAAmB,iDAAiD,uBAAuB,CAAC,EAAED,kBAAkB;0BAE9H,cAAA,KAACE;oBAAIC,WAAWlB,eAAe,CAACU,MAAM;8BACpC,cAAA,KAAClB;;;;;AAKX"}
1
+ {"version":3,"sources":["../../src/components/main-layout.tsx"],"sourcesContent":["import {FullPageLoader} from '@shipfox/react-ui/loader';\nimport {Navigate, Outlet, useLocation, useMatches} from '@tanstack/react-router';\nimport type {NavTabEntry} from '#contract.js';\nimport {useMaybeActiveWorkspace} from '#runtime/active-workspace.js';\nimport {useAuthState} from '#runtime/auth.js';\nimport type {RouteFrame} from '#runtime/route-frame.js';\nimport {parseWorkspaceProjectParams, useRouteParams} from '#runtime/route-inputs.js';\nimport {WorkspaceUnavailablePage} from '#runtime/workspace-setup.js';\nimport {FOCUSED_FRAME_CONTENT_CLASS_NAME} from './focused-frame.js';\nimport {NavBar} from './nav-bar.js';\nimport {NavTabs} from './nav-tabs.js';\n\ndeclare module '@tanstack/react-router' {\n interface StaticDataRouteOption {\n frame?: RouteFrame;\n }\n}\n\nconst frameClassNames: Record<RouteFrame, string> = {\n content: 'mx-auto w-full max-w-[1120px] px-frame py-frame',\n data: 'flex min-h-0 w-full flex-1 flex-col px-frame py-frame',\n focused: `${FOCUSED_FRAME_CONTENT_CLASS_NAME} px-frame py-frame`,\n};\n\nexport function MainLayout({\n navigation,\n hideProjectNavigation = false,\n}: {\n navigation: readonly NavTabEntry[];\n hideProjectNavigation?: boolean;\n}) {\n const auth = useAuthState();\n const workspace = useMaybeActiveWorkspace();\n const location = useLocation();\n const {projectSlug} = useRouteParams(parseWorkspaceProjectParams);\n const matches = useMatches();\n if (auth.isLoading) return <FullPageLoader />;\n if (!auth.isAuthenticated) {\n return (\n <Navigate to={'/auth/login' as never} search={{redirect: location.href} as never} replace />\n );\n }\n if (!workspace) return <WorkspaceUnavailablePage />;\n const frame = matches.reduce<RouteFrame>(\n (current, match) => match.staticData.frame ?? current,\n 'content',\n );\n const appContentHeight = hideProjectNavigation\n ? '[--app-content-h:calc(100dvh_-_56px)]'\n : '[--app-content-h:calc(100dvh_-_96px)]';\n const isFullBleedFrame = frame === 'data';\n return (\n <div className=\"h-screen w-full flex flex-col bg-background-subtle-base\">\n <NavBar hideProjectNavigation={hideProjectNavigation} />\n {hideProjectNavigation ? undefined : (\n <NavTabs entries={navigation} scope={projectSlug ? 'project' : 'workspace'} />\n )}\n <main\n className={`${isFullBleedFrame ? 'flex min-h-0 flex-1 flex-col overflow-hidden' : 'flex-1 overflow-auto'} ${appContentHeight}`}\n >\n <div className={frameClassNames[frame]}>\n <Outlet />\n </div>\n </main>\n </div>\n );\n}\n"],"names":["FullPageLoader","Navigate","Outlet","useLocation","useMatches","useMaybeActiveWorkspace","useAuthState","parseWorkspaceProjectParams","useRouteParams","WorkspaceUnavailablePage","FOCUSED_FRAME_CONTENT_CLASS_NAME","NavBar","NavTabs","frameClassNames","content","data","focused","MainLayout","navigation","hideProjectNavigation","auth","workspace","location","projectSlug","matches","isLoading","isAuthenticated","to","search","redirect","href","replace","frame","reduce","current","match","staticData","appContentHeight","isFullBleedFrame","div","className","undefined","entries","scope","main"],"mappings":";AAAA,SAAQA,cAAc,QAAO,2BAA2B;AACxD,SAAQC,QAAQ,EAAEC,MAAM,EAAEC,WAAW,EAAEC,UAAU,QAAO,yBAAyB;AAEjF,SAAQC,uBAAuB,QAAO,+BAA+B;AACrE,SAAQC,YAAY,QAAO,mBAAmB;AAE9C,SAAQC,2BAA2B,EAAEC,cAAc,QAAO,2BAA2B;AACrF,SAAQC,wBAAwB,QAAO,8BAA8B;AACrE,SAAQC,gCAAgC,QAAO,qBAAqB;AACpE,SAAQC,MAAM,QAAO,eAAe;AACpC,SAAQC,OAAO,QAAO,gBAAgB;AAQtC,MAAMC,kBAA8C;IAClDC,SAAS;IACTC,MAAM;IACNC,SAAS,GAAGN,iCAAiC,kBAAkB,CAAC;AAClE;AAEA,OAAO,SAASO,WAAW,EACzBC,UAAU,EACVC,wBAAwB,KAAK,EAI9B;IACC,MAAMC,OAAOd;IACb,MAAMe,YAAYhB;IAClB,MAAMiB,WAAWnB;IACjB,MAAM,EAACoB,WAAW,EAAC,GAAGf,eAAeD;IACrC,MAAMiB,UAAUpB;IAChB,IAAIgB,KAAKK,SAAS,EAAE,qBAAO,KAACzB;IAC5B,IAAI,CAACoB,KAAKM,eAAe,EAAE;QACzB,qBACE,KAACzB;YAAS0B,IAAI;YAAwBC,QAAQ;gBAACC,UAAUP,SAASQ,IAAI;YAAA;YAAYC,OAAO;;IAE7F;IACA,IAAI,CAACV,WAAW,qBAAO,KAACZ;IACxB,MAAMuB,QAAQR,QAAQS,MAAM,CAC1B,CAACC,SAASC,QAAUA,MAAMC,UAAU,CAACJ,KAAK,IAAIE,SAC9C;IAEF,MAAMG,mBAAmBlB,wBACrB,0CACA;IACJ,MAAMmB,mBAAmBN,UAAU;IACnC,qBACE,MAACO;QAAIC,WAAU;;0BACb,KAAC7B;gBAAOQ,uBAAuBA;;YAC9BA,wBAAwBsB,0BACvB,KAAC7B;gBAAQ8B,SAASxB;gBAAYyB,OAAOpB,cAAc,YAAY;;0BAEjE,KAACqB;gBACCJ,WAAW,GAAGF,mBAAmB,iDAAiD,uBAAuB,CAAC,EAAED,kBAAkB;0BAE9H,cAAA,KAACE;oBAAIC,WAAW3B,eAAe,CAACmB,MAAM;8BACpC,cAAA,KAAC9B;;;;;AAKX"}
@@ -1 +1 @@
1
- {"version":3,"file":"settings-nav.d.ts","sourceRoot":"","sources":["../../src/components/settings-nav.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAOvD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,GACN,EAAE;IACD,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC,sCA4CA"}
1
+ {"version":3,"file":"settings-nav.d.ts","sourceRoot":"","sources":["../../src/components/settings-nav.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,cAAc,CAAC;AAOvD,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,GACN,EAAE;IACD,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACzC,KAAK,EAAE,WAAW,GAAG,SAAS,CAAC;CAChC,sCAoDA"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button } from '@shipfox/react-ui/button';
3
2
  import { Icon } from '@shipfox/react-ui/icon';
3
+ import { cn } from '@shipfox/react-ui/utils';
4
4
  import { Link, useMatchRoute } from '@tanstack/react-router';
5
5
  import { parseWorkspaceParams, parseWorkspaceProjectParams, useRouteParams } from '#runtime/route-inputs.js';
6
6
  export function SettingsNav({ entries, scope }) {
@@ -17,30 +17,38 @@ export function SettingsNav({ entries, scope }) {
17
17
  };
18
18
  return /*#__PURE__*/ _jsx("nav", {
19
19
  "aria-label": `${scope === 'workspace' ? 'Workspace' : 'Project'} settings`,
20
- className: "flex flex-col gap-tight",
21
- children: scopedEntries.map((entry)=>{
22
- const to = `${settingsPath}/${entry.pathSegment}`;
23
- const active = Boolean(matchRoute({
24
- to: to,
25
- params: paramsForLink
26
- }));
27
- return /*#__PURE__*/ _jsx(Button, {
28
- asChild: true,
29
- variant: active ? 'secondary' : 'transparent',
30
- className: "w-full justify-start",
31
- children: /*#__PURE__*/ _jsxs(Link, {
20
+ className: "flex min-w-0 flex-col",
21
+ children: /*#__PURE__*/ _jsx("ul", {
22
+ className: "divide-y divide-border-neutral-base",
23
+ children: scopedEntries.map((entry)=>{
24
+ const to = `${settingsPath}/${entry.pathSegment}`;
25
+ const active = Boolean(matchRoute({
32
26
  to: to,
33
- params: paramsForLink,
34
- "aria-current": active ? 'page' : undefined,
35
- children: [
36
- /*#__PURE__*/ _jsx(Icon, {
37
- name: entry.icon,
38
- className: "size-16"
39
- }),
40
- entry.label
41
- ]
42
- })
43
- }, entry.id);
27
+ params: paramsForLink
28
+ }));
29
+ return /*#__PURE__*/ _jsx("li", {
30
+ className: "py-tight first:pt-0 last:pb-0",
31
+ children: /*#__PURE__*/ _jsxs(Link, {
32
+ to: to,
33
+ params: paramsForLink,
34
+ "aria-current": active ? 'page' : undefined,
35
+ className: cn('relative flex min-h-32 w-full items-center justify-start gap-inline rounded-4 px-tight text-left text-sm font-medium text-foreground-neutral-base outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active', active && 'bg-background-neutral-hover'),
36
+ children: [
37
+ active ? /*#__PURE__*/ _jsx("span", {
38
+ "aria-hidden": "true",
39
+ "data-settings-active-bar": true,
40
+ className: "absolute inset-y-0 left-0 w-2 rounded-l-4 bg-border-highlights-interactive"
41
+ }) : null,
42
+ /*#__PURE__*/ _jsx(Icon, {
43
+ name: entry.icon,
44
+ className: "size-16",
45
+ "aria-hidden": "true"
46
+ }),
47
+ entry.label
48
+ ]
49
+ })
50
+ }, entry.id);
51
+ })
44
52
  })
45
53
  });
46
54
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/settings-nav.tsx"],"sourcesContent":["import {Button} from '@shipfox/react-ui/button';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Link, useMatchRoute} from '@tanstack/react-router';\nimport type {SettingsSectionEntry} from '#contract.js';\nimport {\n parseWorkspaceParams,\n parseWorkspaceProjectParams,\n useRouteParams,\n} from '#runtime/route-inputs.js';\n\nexport function SettingsNav({\n entries,\n scope,\n}: {\n entries: readonly SettingsSectionEntry[];\n scope: 'workspace' | 'project';\n}) {\n const params = useRouteParams((input): {workspaceSlug?: string; projectSlug?: string} =>\n scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input),\n );\n const matchRoute = useMatchRoute();\n if (!params.workspaceSlug || (scope === 'project' && !params.projectSlug)) return null;\n const scopedEntries = entries.filter((entry) => (entry.scope ?? 'workspace') === scope);\n const settingsPath =\n scope === 'workspace'\n ? '/w/$workspaceSlug/settings'\n : '/w/$workspaceSlug/p/$projectSlug/settings';\n const paramsForLink =\n scope === 'workspace'\n ? {workspaceSlug: params.workspaceSlug}\n : {workspaceSlug: params.workspaceSlug, projectSlug: params.projectSlug};\n\n return (\n <nav\n aria-label={`${scope === 'workspace' ? 'Workspace' : 'Project'} settings`}\n className=\"flex flex-col gap-tight\"\n >\n {scopedEntries.map((entry) => {\n const to = `${settingsPath}/${entry.pathSegment}`;\n const active = Boolean(matchRoute({to: to as never, params: paramsForLink as never}));\n return (\n <Button\n key={entry.id}\n asChild\n variant={active ? 'secondary' : 'transparent'}\n className=\"w-full justify-start\"\n >\n <Link\n to={to as never}\n params={paramsForLink as never}\n aria-current={active ? 'page' : undefined}\n >\n <Icon name={entry.icon} className=\"size-16\" />\n {entry.label}\n </Link>\n </Button>\n );\n })}\n </nav>\n );\n}\n"],"names":["Button","Icon","Link","useMatchRoute","parseWorkspaceParams","parseWorkspaceProjectParams","useRouteParams","SettingsNav","entries","scope","params","input","matchRoute","workspaceSlug","projectSlug","scopedEntries","filter","entry","settingsPath","paramsForLink","nav","aria-label","className","map","to","pathSegment","active","Boolean","asChild","variant","aria-current","undefined","name","icon","label","id"],"mappings":";AAAA,SAAQA,MAAM,QAAO,2BAA2B;AAChD,SAAQC,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,IAAI,EAAEC,aAAa,QAAO,yBAAyB;AAE3D,SACEC,oBAAoB,EACpBC,2BAA2B,EAC3BC,cAAc,QACT,2BAA2B;AAElC,OAAO,SAASC,YAAY,EAC1BC,OAAO,EACPC,KAAK,EAIN;IACC,MAAMC,SAASJ,eAAe,CAACK,QAC7BF,UAAU,cAAcL,qBAAqBO,SAASN,4BAA4BM;IAEpF,MAAMC,aAAaT;IACnB,IAAI,CAACO,OAAOG,aAAa,IAAKJ,UAAU,aAAa,CAACC,OAAOI,WAAW,EAAG,OAAO;IAClF,MAAMC,gBAAgBP,QAAQQ,MAAM,CAAC,CAACC,QAAU,AAACA,CAAAA,MAAMR,KAAK,IAAI,WAAU,MAAOA;IACjF,MAAMS,eACJT,UAAU,cACN,+BACA;IACN,MAAMU,gBACJV,UAAU,cACN;QAACI,eAAeH,OAAOG,aAAa;IAAA,IACpC;QAACA,eAAeH,OAAOG,aAAa;QAAEC,aAAaJ,OAAOI,WAAW;IAAA;IAE3E,qBACE,KAACM;QACCC,cAAY,GAAGZ,UAAU,cAAc,cAAc,UAAU,SAAS,CAAC;QACzEa,WAAU;kBAETP,cAAcQ,GAAG,CAAC,CAACN;YAClB,MAAMO,KAAK,GAAGN,aAAa,CAAC,EAAED,MAAMQ,WAAW,EAAE;YACjD,MAAMC,SAASC,QAAQf,WAAW;gBAACY,IAAIA;gBAAad,QAAQS;YAAsB;YAClF,qBACE,KAACnB;gBAEC4B,OAAO;gBACPC,SAASH,SAAS,cAAc;gBAChCJ,WAAU;0BAEV,cAAA,MAACpB;oBACCsB,IAAIA;oBACJd,QAAQS;oBACRW,gBAAcJ,SAAS,SAASK;;sCAEhC,KAAC9B;4BAAK+B,MAAMf,MAAMgB,IAAI;4BAAEX,WAAU;;wBACjCL,MAAMiB,KAAK;;;eAXTjB,MAAMkB,EAAE;QAenB;;AAGN"}
1
+ {"version":3,"sources":["../../src/components/settings-nav.tsx"],"sourcesContent":["import {Icon} from '@shipfox/react-ui/icon';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {Link, useMatchRoute} from '@tanstack/react-router';\nimport type {SettingsSectionEntry} from '#contract.js';\nimport {\n parseWorkspaceParams,\n parseWorkspaceProjectParams,\n useRouteParams,\n} from '#runtime/route-inputs.js';\n\nexport function SettingsNav({\n entries,\n scope,\n}: {\n entries: readonly SettingsSectionEntry[];\n scope: 'workspace' | 'project';\n}) {\n const params = useRouteParams((input): {workspaceSlug?: string; projectSlug?: string} =>\n scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input),\n );\n const matchRoute = useMatchRoute();\n if (!params.workspaceSlug || (scope === 'project' && !params.projectSlug)) return null;\n const scopedEntries = entries.filter((entry) => (entry.scope ?? 'workspace') === scope);\n const settingsPath =\n scope === 'workspace'\n ? '/w/$workspaceSlug/settings'\n : '/w/$workspaceSlug/p/$projectSlug/settings';\n const paramsForLink =\n scope === 'workspace'\n ? {workspaceSlug: params.workspaceSlug}\n : {workspaceSlug: params.workspaceSlug, projectSlug: params.projectSlug};\n\n return (\n <nav\n aria-label={`${scope === 'workspace' ? 'Workspace' : 'Project'} settings`}\n className=\"flex min-w-0 flex-col\"\n >\n <ul className=\"divide-y divide-border-neutral-base\">\n {scopedEntries.map((entry) => {\n const to = `${settingsPath}/${entry.pathSegment}`;\n const active = Boolean(matchRoute({to: to as never, params: paramsForLink as never}));\n return (\n <li key={entry.id} className=\"py-tight first:pt-0 last:pb-0\">\n <Link\n to={to as never}\n params={paramsForLink as never}\n aria-current={active ? 'page' : undefined}\n className={cn(\n 'relative flex min-h-32 w-full items-center justify-start gap-inline rounded-4 px-tight text-left text-sm font-medium text-foreground-neutral-base outline-none transition-colors hover:bg-background-neutral-hover focus-visible:shadow-border-interactive-with-active',\n active && 'bg-background-neutral-hover',\n )}\n >\n {active ? (\n <span\n aria-hidden=\"true\"\n data-settings-active-bar\n className=\"absolute inset-y-0 left-0 w-2 rounded-l-4 bg-border-highlights-interactive\"\n />\n ) : null}\n <Icon name={entry.icon} className=\"size-16\" aria-hidden=\"true\" />\n {entry.label}\n </Link>\n </li>\n );\n })}\n </ul>\n </nav>\n );\n}\n"],"names":["Icon","cn","Link","useMatchRoute","parseWorkspaceParams","parseWorkspaceProjectParams","useRouteParams","SettingsNav","entries","scope","params","input","matchRoute","workspaceSlug","projectSlug","scopedEntries","filter","entry","settingsPath","paramsForLink","nav","aria-label","className","ul","map","to","pathSegment","active","Boolean","li","aria-current","undefined","span","aria-hidden","data-settings-active-bar","name","icon","label","id"],"mappings":";AAAA,SAAQA,IAAI,QAAO,yBAAyB;AAC5C,SAAQC,EAAE,QAAO,0BAA0B;AAC3C,SAAQC,IAAI,EAAEC,aAAa,QAAO,yBAAyB;AAE3D,SACEC,oBAAoB,EACpBC,2BAA2B,EAC3BC,cAAc,QACT,2BAA2B;AAElC,OAAO,SAASC,YAAY,EAC1BC,OAAO,EACPC,KAAK,EAIN;IACC,MAAMC,SAASJ,eAAe,CAACK,QAC7BF,UAAU,cAAcL,qBAAqBO,SAASN,4BAA4BM;IAEpF,MAAMC,aAAaT;IACnB,IAAI,CAACO,OAAOG,aAAa,IAAKJ,UAAU,aAAa,CAACC,OAAOI,WAAW,EAAG,OAAO;IAClF,MAAMC,gBAAgBP,QAAQQ,MAAM,CAAC,CAACC,QAAU,AAACA,CAAAA,MAAMR,KAAK,IAAI,WAAU,MAAOA;IACjF,MAAMS,eACJT,UAAU,cACN,+BACA;IACN,MAAMU,gBACJV,UAAU,cACN;QAACI,eAAeH,OAAOG,aAAa;IAAA,IACpC;QAACA,eAAeH,OAAOG,aAAa;QAAEC,aAAaJ,OAAOI,WAAW;IAAA;IAE3E,qBACE,KAACM;QACCC,cAAY,GAAGZ,UAAU,cAAc,cAAc,UAAU,SAAS,CAAC;QACzEa,WAAU;kBAEV,cAAA,KAACC;YAAGD,WAAU;sBACXP,cAAcS,GAAG,CAAC,CAACP;gBAClB,MAAMQ,KAAK,GAAGP,aAAa,CAAC,EAAED,MAAMS,WAAW,EAAE;gBACjD,MAAMC,SAASC,QAAQhB,WAAW;oBAACa,IAAIA;oBAAaf,QAAQS;gBAAsB;gBAClF,qBACE,KAACU;oBAAkBP,WAAU;8BAC3B,cAAA,MAACpB;wBACCuB,IAAIA;wBACJf,QAAQS;wBACRW,gBAAcH,SAAS,SAASI;wBAChCT,WAAWrB,GACT,0QACA0B,UAAU;;4BAGXA,uBACC,KAACK;gCACCC,eAAY;gCACZC,0BAAwB;gCACxBZ,WAAU;iCAEV;0CACJ,KAACtB;gCAAKmC,MAAMlB,MAAMmB,IAAI;gCAAEd,WAAU;gCAAUW,eAAY;;4BACvDhB,MAAMoB,KAAK;;;mBAlBPpB,MAAMqB,EAAE;YAsBrB;;;AAIR"}
@@ -1 +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;AACvD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAO9D,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;;;;;;;;;;;;;EAwHA"}
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;AACvD,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAO9D,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;;;;;;;;;;;;;EAgHA"}
@@ -5,7 +5,7 @@ import { createRootRouteWithContext, createRoute, Outlet, redirect } from '@tans
5
5
  import { MainLayout } from '#components/main-layout.js';
6
6
  import { NotFoundPage } from '#components/not-found-page.js';
7
7
  import { SettingsNav } from '#components/settings-nav.js';
8
- import { useActiveWorkspace, useMaybeActiveWorkspace } from './active-workspace.js';
8
+ import { useMaybeActiveWorkspace } from './active-workspace.js';
9
9
  import { anchorPaths } from './anchor-paths.js';
10
10
  import { rememberLastWorkspaceId } from './last-workspace.js';
11
11
  import { parseWorkspaceParams, parseWorkspaceProjectParams, useRouteParams } from './route-inputs.js';
@@ -116,11 +116,8 @@ export function buildAnchorSkeleton({ navigation, settingsSections }) {
116
116
  getParentRoute: ()=>workspaceLayout,
117
117
  path: '/settings',
118
118
  component: ()=>{
119
- const workspace = useActiveWorkspace();
120
119
  return /*#__PURE__*/ _jsx(SettingsAnchorLayout, {
121
120
  scope: "workspace",
122
- title: "Workspace settings",
123
- description: `Configure ${workspace.name}.`,
124
121
  settingsSections: settingsSections
125
122
  });
126
123
  }
@@ -144,22 +141,22 @@ function SettingsAnchorLayout({ scope, title, description, settingsSections }) {
144
141
  const params = useRouteParams((input)=>scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input));
145
142
  if (!params.workspaceSlug || scope === 'project' && !params.projectSlug) return null;
146
143
  return /*#__PURE__*/ _jsxs("div", {
147
- className: "flex w-full flex-col gap-section",
144
+ className: title ? 'flex w-full flex-col gap-section' : 'w-full',
148
145
  children: [
149
- /*#__PURE__*/ _jsxs("header", {
146
+ title ? /*#__PURE__*/ _jsxs("header", {
150
147
  className: "flex flex-col gap-inline",
151
148
  children: [
152
149
  /*#__PURE__*/ _jsx(Header, {
153
150
  variant: "h2",
154
151
  children: title
155
152
  }),
156
- /*#__PURE__*/ _jsx(Text, {
153
+ description ? /*#__PURE__*/ _jsx(Text, {
157
154
  size: "sm",
158
155
  className: "text-foreground-neutral-muted",
159
156
  children: description
160
- })
157
+ }) : null
161
158
  ]
162
- }),
159
+ }) : null,
163
160
  /*#__PURE__*/ _jsxs("div", {
164
161
  className: "grid grid-cols-[180px_minmax(0,1fr)] gap-region max-[760px]:grid-cols-1",
165
162
  children: [
@@ -1 +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, useMaybeActiveWorkspace} from './active-workspace.js';\nimport {anchorPaths} from './anchor-paths.js';\nimport {rememberLastWorkspaceId} from './last-workspace.js';\nimport {parseWorkspaceParams, parseWorkspaceProjectParams, useRouteParams} from './route-inputs.js';\nimport type {RouterContext} from './router-context.js';\nimport type {WorkspaceSetupState} from './workspace-setup.js';\nimport {\n WorkspaceLayoutErrorRoute,\n WorkspaceSetupPending,\n WorkspaceUnavailablePage,\n} 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 staticData: {frame: 'content'},\n beforeLoad: async ({context, params, location}) => {\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 const workspace = auth.workspaces.find(\n (candidate) => candidate.slug === params.workspaceSlug,\n );\n if (!workspace) throw redirect({to: '/'});\n try {\n if (auth.user?.id) rememberLastWorkspaceId(auth.user.id, workspace.id);\n } catch {\n // Local storage is best effort.\n }\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: workspace.id,\n workspaceSlug: params.workspaceSlug,\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 {\n hideProjectNavigation?: boolean;\n unavailable?: boolean;\n };\n const workspace = useMaybeActiveWorkspace();\n return setupState.hideProjectNavigation === undefined ? (\n <WorkspaceSetupPending />\n ) : setupState.unavailable ? (\n <WorkspaceUnavailablePage workspaceName={workspace?.name} />\n ) : (\n <MainLayout\n navigation={navigation}\n hideProjectNavigation={setupState.hideProjectNavigation}\n />\n );\n },\n });\n const projectLayout = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/p/$projectSlug',\n staticData: {frame: 'content'},\n beforeLoad: async ({context, params}) => {\n const auth = context.auth;\n if (!auth || auth.isLoading || !context.queryClient) return;\n if ((context as RouterContext & Partial<WorkspaceSetupState>).unavailable) return;\n const workspace = auth.workspaces.find(\n (candidate) => candidate.slug === params.workspaceSlug,\n );\n if (!workspace) throw redirect({to: '/'});\n if (!context.projectSlugResolver) {\n throw new Error('Client composition includes project routes but no project slug resolver.');\n }\n const projectId = await context.projectSlugResolver({\n queryClient: context.queryClient,\n workspaceId: workspace.id,\n projectSlug: params.projectSlug,\n });\n if (!projectId) {\n throw redirect({\n to: '/w/$workspaceSlug',\n params: {workspaceSlug: params.workspaceSlug},\n });\n }\n },\n component: Outlet,\n });\n const projectSettings = createRoute({\n getParentRoute: () => projectLayout,\n path: '/settings',\n component: () => (\n <SettingsAnchorLayout\n scope=\"project\"\n title=\"Project settings\"\n description=\"Configure this project.\"\n settingsSections={settingsSections}\n />\n ),\n });\n const workspaceSettings = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/settings',\n component: () => {\n const workspace = useActiveWorkspace();\n return (\n <SettingsAnchorLayout\n scope=\"workspace\"\n title=\"Workspace settings\"\n description={`Configure ${workspace.name}.`}\n settingsSections={settingsSections}\n />\n );\n },\n });\n return {\n anchors: {root: rootRoute, workspaceLayout, projectLayout, workspaceSettings, projectSettings},\n rootRoute,\n workspaceLayout,\n projectLayout,\n workspaceSettings,\n projectSettings,\n };\n}\n\nfunction SettingsAnchorLayout({\n scope,\n title,\n description,\n settingsSections,\n}: {\n scope: 'workspace' | 'project';\n title: string;\n description: string;\n settingsSections: readonly SettingsSectionEntry[];\n}) {\n const params = useRouteParams((input): {workspaceSlug?: string; projectSlug?: string} =>\n scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input),\n );\n if (!params.workspaceSlug || (scope === 'project' && !params.projectSlug)) return null;\n\n return (\n <div className=\"flex w-full flex-col gap-section\">\n <header className=\"flex flex-col gap-inline\">\n <Header variant=\"h2\">{title}</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {description}\n </Text>\n </header>\n\n <div className=\"grid grid-cols-[180px_minmax(0,1fr)] gap-region max-[760px]:grid-cols-1\">\n <SettingsNav entries={settingsSections} scope={scope} />\n <Outlet />\n </div>\n </div>\n );\n}\n"],"names":["ShipfoxLoader","Header","Text","createRootRouteWithContext","createRoute","Outlet","redirect","MainLayout","NotFoundPage","SettingsNav","useActiveWorkspace","useMaybeActiveWorkspace","anchorPaths","rememberLastWorkspaceId","parseWorkspaceParams","parseWorkspaceProjectParams","useRouteParams","WorkspaceLayoutErrorRoute","WorkspaceSetupPending","WorkspaceUnavailablePage","routePathForAnchor","buildAnchorSkeleton","navigation","settingsSections","rootRoute","component","notFoundComponent","workspaceLayout","getParentRoute","path","staticData","frame","beforeLoad","context","params","location","auth","isLoading","queryClient","isAuthenticated","to","search","href","workspace","workspaces","find","candidate","slug","workspaceSlug","user","id","workspaceSetup","Error","workspaceId","pathname","pendingComponent","div","className","size","animation","color","background","errorComponent","setupState","useRouteContext","hideProjectNavigation","undefined","unavailable","workspaceName","name","projectLayout","projectSlugResolver","projectId","projectSlug","projectSettings","SettingsAnchorLayout","scope","title","description","workspaceSettings","anchors","root","input","header","variant","entries"],"mappings":"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"}
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 {useMaybeActiveWorkspace} from './active-workspace.js';\nimport {anchorPaths} from './anchor-paths.js';\nimport {rememberLastWorkspaceId} from './last-workspace.js';\nimport {parseWorkspaceParams, parseWorkspaceProjectParams, useRouteParams} from './route-inputs.js';\nimport type {RouterContext} from './router-context.js';\nimport type {WorkspaceSetupState} from './workspace-setup.js';\nimport {\n WorkspaceLayoutErrorRoute,\n WorkspaceSetupPending,\n WorkspaceUnavailablePage,\n} 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 staticData: {frame: 'content'},\n beforeLoad: async ({context, params, location}) => {\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 const workspace = auth.workspaces.find(\n (candidate) => candidate.slug === params.workspaceSlug,\n );\n if (!workspace) throw redirect({to: '/'});\n try {\n if (auth.user?.id) rememberLastWorkspaceId(auth.user.id, workspace.id);\n } catch {\n // Local storage is best effort.\n }\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: workspace.id,\n workspaceSlug: params.workspaceSlug,\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 {\n hideProjectNavigation?: boolean;\n unavailable?: boolean;\n };\n const workspace = useMaybeActiveWorkspace();\n return setupState.hideProjectNavigation === undefined ? (\n <WorkspaceSetupPending />\n ) : setupState.unavailable ? (\n <WorkspaceUnavailablePage workspaceName={workspace?.name} />\n ) : (\n <MainLayout\n navigation={navigation}\n hideProjectNavigation={setupState.hideProjectNavigation}\n />\n );\n },\n });\n const projectLayout = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/p/$projectSlug',\n staticData: {frame: 'content'},\n beforeLoad: async ({context, params}) => {\n const auth = context.auth;\n if (!auth || auth.isLoading || !context.queryClient) return;\n if ((context as RouterContext & Partial<WorkspaceSetupState>).unavailable) return;\n const workspace = auth.workspaces.find(\n (candidate) => candidate.slug === params.workspaceSlug,\n );\n if (!workspace) throw redirect({to: '/'});\n if (!context.projectSlugResolver) {\n throw new Error('Client composition includes project routes but no project slug resolver.');\n }\n const projectId = await context.projectSlugResolver({\n queryClient: context.queryClient,\n workspaceId: workspace.id,\n projectSlug: params.projectSlug,\n });\n if (!projectId) {\n throw redirect({\n to: '/w/$workspaceSlug',\n params: {workspaceSlug: params.workspaceSlug},\n });\n }\n },\n component: Outlet,\n });\n const projectSettings = createRoute({\n getParentRoute: () => projectLayout,\n path: '/settings',\n component: () => (\n <SettingsAnchorLayout\n scope=\"project\"\n title=\"Project settings\"\n description=\"Configure this project.\"\n settingsSections={settingsSections}\n />\n ),\n });\n const workspaceSettings = createRoute({\n getParentRoute: () => workspaceLayout,\n path: '/settings',\n component: () => {\n return <SettingsAnchorLayout scope=\"workspace\" settingsSections={settingsSections} />;\n },\n });\n return {\n anchors: {root: rootRoute, workspaceLayout, projectLayout, workspaceSettings, projectSettings},\n rootRoute,\n workspaceLayout,\n projectLayout,\n workspaceSettings,\n projectSettings,\n };\n}\n\nfunction SettingsAnchorLayout({\n scope,\n title,\n description,\n settingsSections,\n}: {\n scope: 'workspace' | 'project';\n title?: string;\n description?: string;\n settingsSections: readonly SettingsSectionEntry[];\n}) {\n const params = useRouteParams((input): {workspaceSlug?: string; projectSlug?: string} =>\n scope === 'workspace' ? parseWorkspaceParams(input) : parseWorkspaceProjectParams(input),\n );\n if (!params.workspaceSlug || (scope === 'project' && !params.projectSlug)) return null;\n\n return (\n <div className={title ? 'flex w-full flex-col gap-section' : 'w-full'}>\n {title ? (\n <header className=\"flex flex-col gap-inline\">\n <Header variant=\"h2\">{title}</Header>\n {description ? (\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n {description}\n </Text>\n ) : null}\n </header>\n ) : null}\n\n <div className=\"grid grid-cols-[180px_minmax(0,1fr)] gap-region max-[760px]:grid-cols-1\">\n <SettingsNav entries={settingsSections} scope={scope} />\n <Outlet />\n </div>\n </div>\n );\n}\n"],"names":["ShipfoxLoader","Header","Text","createRootRouteWithContext","createRoute","Outlet","redirect","MainLayout","NotFoundPage","SettingsNav","useMaybeActiveWorkspace","anchorPaths","rememberLastWorkspaceId","parseWorkspaceParams","parseWorkspaceProjectParams","useRouteParams","WorkspaceLayoutErrorRoute","WorkspaceSetupPending","WorkspaceUnavailablePage","routePathForAnchor","buildAnchorSkeleton","navigation","settingsSections","rootRoute","component","notFoundComponent","workspaceLayout","getParentRoute","path","staticData","frame","beforeLoad","context","params","location","auth","isLoading","queryClient","isAuthenticated","to","search","href","workspace","workspaces","find","candidate","slug","workspaceSlug","user","id","workspaceSetup","Error","workspaceId","pathname","pendingComponent","div","className","size","animation","color","background","errorComponent","setupState","useRouteContext","hideProjectNavigation","undefined","unavailable","workspaceName","name","projectLayout","projectSlugResolver","projectId","projectSlug","projectSettings","SettingsAnchorLayout","scope","title","description","workspaceSettings","anchors","root","input","header","variant","entries"],"mappings":"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"}