@igstack/app-catalog-frontend-core 6.6.0 → 6.7.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/dist/esm/App.d.ts +29 -3
- package/dist/esm/App.js +4 -2
- package/dist/esm/App.js.map +1 -1
- package/dist/esm/__tests__/modules/appCatalog/ui/components/SubResourcesSection.adminColumn.test.d.ts +0 -0
- package/dist/esm/__tests__/modules/appCatalog/ui/components/SubResourcesSection.rowMemo.test.d.ts +0 -0
- package/dist/esm/__tests__/modules/dismiss/DismissStack.test.d.ts +0 -0
- package/dist/esm/__tests__/modules/extensions/slots.test.d.ts +0 -0
- package/dist/esm/__tests__/publicEntryResolves.test.d.ts +1 -0
- package/dist/esm/appPropsFactory.d.ts +19 -1
- package/dist/esm/appPropsFactory.js +4 -3
- package/dist/esm/appPropsFactory.js.map +1 -1
- package/dist/esm/index.d.ts +15 -2
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal.d.ts +7 -1
- package/dist/esm/internal.js +0 -2
- package/dist/esm/internal.js.map +1 -1
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.d.ts +17 -0
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.js +7 -16
- package/dist/esm/modules/appCatalog/context/AppCatalogContext.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.js +20 -11
- package/dist/esm/modules/appCatalog/ui/catalog/AppCatalogGrid.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js +39 -17
- package/dist/esm/modules/appCatalog/ui/catalog/AppDetailPanel.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/PersonBadge.js +0 -14
- package/dist/esm/modules/appCatalog/ui/components/PersonBadge.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/ScreenshotGallery.js +5 -18
- package/dist/esm/modules/appCatalog/ui/components/ScreenshotGallery.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourceDetailPanel.d.ts +12 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourceDetailPanel.js +3 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourceDetailPanel.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.d.ts +17 -1
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js +212 -79
- package/dist/esm/modules/appCatalog/ui/components/SubResourcesSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/components/TierVariantsSection.js +0 -14
- package/dist/esm/modules/appCatalog/ui/components/TierVariantsSection.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/detail/AppDetails.d.ts +1 -2
- package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js +31 -23
- package/dist/esm/modules/appCatalog/ui/detail/AppDetails.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js +5 -0
- package/dist/esm/modules/appCatalog/ui/detail/DetailTabs.js.map +1 -1
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js +5 -3
- package/dist/esm/modules/appCatalog/ui/pages/AppCatalogPage.js.map +1 -1
- package/dist/esm/modules/auth/AuthContext.d.ts +12 -0
- package/dist/esm/modules/auth/AuthContext.js +5 -0
- package/dist/esm/modules/auth/AuthContext.js.map +1 -1
- package/dist/esm/modules/auth/index.d.ts +1 -1
- package/dist/esm/modules/dismiss/DismissStack.d.ts +102 -0
- package/dist/esm/modules/dismiss/DismissStack.js +82 -0
- package/dist/esm/modules/dismiss/DismissStack.js.map +1 -0
- package/dist/esm/modules/extensions/ExtensionsContext.d.ts +17 -0
- package/dist/esm/modules/extensions/ExtensionsContext.js +15 -0
- package/dist/esm/modules/extensions/ExtensionsContext.js.map +1 -0
- package/dist/esm/modules/extensions/factories.d.ts +18 -0
- package/dist/esm/modules/extensions/factories.js +12 -0
- package/dist/esm/modules/extensions/factories.js.map +1 -0
- package/dist/esm/modules/extensions/features/resourceDetail.d.ts +92 -0
- package/dist/esm/modules/extensions/features/resourceDetail.js +14 -0
- package/dist/esm/modules/extensions/features/resourceDetail.js.map +1 -0
- package/dist/esm/modules/extensions/index.d.ts +79 -0
- package/dist/esm/modules/extensions/index.js +26 -0
- package/dist/esm/modules/extensions/index.js.map +1 -0
- package/dist/esm/modules/extensions/makeSlot.d.ts +46 -0
- package/dist/esm/modules/extensions/makeSlot.js +62 -0
- package/dist/esm/modules/extensions/makeSlot.js.map +1 -0
- package/dist/esm/modules/extensions/types.d.ts +46 -0
- package/dist/esm/modules/gallery/Gallery.js +6 -14
- package/dist/esm/modules/gallery/Gallery.js.map +1 -1
- package/dist/esm/routeTree.gen.d.ts +34 -34
- package/dist/esm/routeTree.gen.js +14 -14
- package/dist/esm/routeTree.gen.js.map +1 -1
- package/dist/esm/router.d.ts +23 -0
- package/dist/esm/router.js +5 -0
- package/dist/esm/router.js.map +1 -0
- package/dist/esm/routes/_layout/app.$slug.d.ts +1 -1
- package/dist/esm/routes/_layout/app.$slug_.sub.$subSlug.d.ts +1 -1
- package/dist/esm/routes/_layout/catalog.apps.index.d.ts +2 -2
- package/dist/esm/routes/_layout/index.d.ts +1 -1
- package/dist/esm/routes/_layout/login.d.ts +1 -1
- package/dist/esm/routes/_layout/mcp.d.ts +1 -1
- package/dist/esm/routes/_layout/service-desks.d.ts +1 -1
- package/dist/esm/routes/_layout.d.ts +1 -1
- package/dist/esm/routes/_layout.js +13 -2
- package/dist/esm/routes/_layout.js.map +1 -1
- package/dist/esm/routes/auth.callback.d.ts +1 -1
- package/dist/esm/ui/components/error/DefaultErrorComponent.js +9 -2
- package/dist/esm/ui/components/error/DefaultErrorComponent.js.map +1 -1
- package/dist/esm/ui/components/header/Header.js +0 -1
- package/dist/esm/ui/components/header/Header.js.map +1 -1
- package/dist/esm/ui/dialog.d.ts +1 -1
- package/dist/esm/ui/dialog.js +9 -0
- package/dist/esm/ui/dialog.js.map +1 -1
- package/dist/esm/ui/table.d.ts +25 -1
- package/dist/esm/ui/table.js +20 -3
- package/dist/esm/ui/table.js.map +1 -1
- package/dist/esm/util/createAcRouter.d.ts +1 -1
- package/package.json +19 -11
- package/src/App.tsx +34 -3
- package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.appSwitch.test.tsx +4 -1
- package/src/__tests__/modules/appCatalog/ui/catalog/AppDetailPanel.focus.test.tsx +1 -1
- package/src/__tests__/modules/appCatalog/ui/components/SubResourcesSection.adminColumn.test.tsx +115 -0
- package/src/__tests__/modules/appCatalog/ui/components/SubResourcesSection.rowMemo.test.tsx +120 -0
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.background.test.tsx +3 -5
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.doorAndTabs.test.tsx +9 -9
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.escape.test.tsx +31 -5
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.sectionOrder.test.tsx +3 -3
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.subResourceTab.test.tsx +48 -6
- package/src/__tests__/modules/appCatalog/ui/detail/AppDetails.tags.test.tsx +1 -7
- package/src/__tests__/modules/dismiss/DismissStack.test.tsx +330 -0
- package/src/__tests__/modules/extensions/slots.test.tsx +252 -0
- package/src/__tests__/publicEntryResolves.test.ts +63 -0
- package/src/appPropsFactory.ts +23 -3
- package/src/index.tsx +32 -2
- package/src/internal.ts +7 -1
- package/src/modules/appCatalog/context/AppCatalogContext.tsx +21 -0
- package/src/modules/appCatalog/ui/catalog/AppCatalogGrid.tsx +52 -12
- package/src/modules/appCatalog/ui/catalog/AppDetailPanel.tsx +59 -18
- package/src/modules/appCatalog/ui/components/ScreenshotGallery.tsx +12 -20
- package/src/modules/appCatalog/ui/components/SubResourceDetailPanel.tsx +19 -0
- package/src/modules/appCatalog/ui/components/SubResourcesSection.tsx +396 -111
- package/src/modules/appCatalog/ui/detail/AppDetails.tsx +46 -26
- package/src/modules/appCatalog/ui/detail/DetailTabs.tsx +20 -0
- package/src/modules/appCatalog/ui/pages/AppCatalogPage.tsx +17 -3
- package/src/modules/auth/AuthContext.tsx +15 -0
- package/src/modules/auth/index.ts +1 -0
- package/src/modules/dismiss/DismissStack.tsx +224 -0
- package/src/modules/extensions/ExtensionsContext.tsx +36 -0
- package/src/modules/extensions/factories.ts +19 -0
- package/src/modules/extensions/features/resourceDetail.ts +74 -0
- package/src/modules/extensions/index.ts +121 -0
- package/src/modules/extensions/makeSlot.tsx +123 -0
- package/src/modules/extensions/types.ts +51 -0
- package/src/modules/gallery/Gallery.tsx +13 -17
- package/src/routeTree.gen.ts +31 -31
- package/src/router.ts +23 -0
- package/src/routes/_layout.tsx +51 -1
- package/src/ui/components/error/DefaultErrorComponent.tsx +22 -4
- package/src/ui/dialog.tsx +26 -0
- package/src/ui/table.tsx +46 -3
|
@@ -5,6 +5,12 @@ import { AppCatalogPage } from "../modules/appCatalog/ui/pages/AppCatalogPage.js
|
|
|
5
5
|
import { Route as Route$1 } from "./_layout/app._slug.js";
|
|
6
6
|
import { Route as Route$2 } from "./_layout/app._slug_.sub._subSlug.js";
|
|
7
7
|
import { Route as Route$3 } from "./_layout/index.js";
|
|
8
|
+
function wantsCatalogLayout(staticData) {
|
|
9
|
+
return typeof staticData === "object" && staticData !== null && "acCatalogLayout" in staticData && staticData.acCatalogLayout === true;
|
|
10
|
+
}
|
|
11
|
+
function hasSlug(params) {
|
|
12
|
+
return typeof params === "object" && params !== null && "slug" in params && typeof params.slug === "string";
|
|
13
|
+
}
|
|
8
14
|
const Route = createFileRoute("/_layout")({
|
|
9
15
|
component: LayoutComponent
|
|
10
16
|
});
|
|
@@ -18,9 +24,14 @@ function LayoutComponent() {
|
|
|
18
24
|
(m) => m.routeId === Route$2.id
|
|
19
25
|
);
|
|
20
26
|
const params = (_a = detailMatch ?? subMatch) == null ? void 0 : _a.params;
|
|
27
|
+
const optedIn = s.matches.some(
|
|
28
|
+
(m) => wantsCatalogLayout(m.staticData) === true
|
|
29
|
+
);
|
|
30
|
+
const slugMatch = [...s.matches].reverse().find((m) => hasSlug(m.params));
|
|
31
|
+
const consumerSlug = hasSlug(slugMatch == null ? void 0 : slugMatch.params) ? slugMatch.params.slug : void 0;
|
|
21
32
|
return {
|
|
22
|
-
isCatalogRoute: detailMatch !== void 0 || subMatch !== void 0 || s.matches.some((m) => m.routeId === Route$3.id),
|
|
23
|
-
selectedSlug: params == null ? void 0 : params.slug,
|
|
33
|
+
isCatalogRoute: detailMatch !== void 0 || subMatch !== void 0 || optedIn || s.matches.some((m) => m.routeId === Route$3.id),
|
|
34
|
+
selectedSlug: (params == null ? void 0 : params.slug) ?? consumerSlug,
|
|
24
35
|
selectedSubSlug: params == null ? void 0 : params.subSlug
|
|
25
36
|
};
|
|
26
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_layout.js","sources":["../../../src/routes/_layout.tsx"],"sourcesContent":["import { Outlet, createFileRoute, useRouterState } from '@tanstack/react-router'\nimport { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'\nimport { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'\n// Route ids are read off the route objects instead of being spelled out as\n// string literals: renaming/moving either file is then a compile error here\n// rather than a silently non-matching id at runtime. `Route.id` is only\n// populated once the router builds the tree, so it must be read at render\n// time (inside the selector), never at module scope.\nimport { Route as AppDetailRoute } from './_layout/app.$slug'\nimport { Route as SubResourceDetailRoute } from './_layout/app.$slug_.sub.$subSlug'\nimport { Route as CatalogIndexRoute } from './_layout/index'\n\nexport const Route = createFileRoute('/_layout')({\n component: LayoutComponent,\n})\n\nfunction LayoutComponent() {\n // All hooks must be called unconditionally (rules of hooks).\n const { queryClient, trpcClient } = Route.useRouteContext()\n\n // The catalog routes (list + detail) share a single AppCatalogPage instance\n // so the component never unmounts between them, preserving scroll position\n // when opening/closing the app detail overlay.\n //\n // The selector returns only the two derived values, so this component\n // re-renders when the catalog branch or the open app changes -- not on every\n // router state update. `useStore` shallow-compares the result, so returning\n // an object is still referentially stable.\n const { isCatalogRoute, selectedSlug, selectedSubSlug } = useRouterState({\n select: (s) => {\n const detailMatch = s.matches.find((m) => m.routeId === AppDetailRoute.id)\n const subMatch = s.matches.find(\n (m) => m.routeId === SubResourceDetailRoute.id,\n )\n const params = (detailMatch ?? subMatch)?.params as\n | { slug?: string; subSlug?: string }\n | undefined\n return {\n isCatalogRoute:\n detailMatch !== undefined ||\n subMatch !== undefined ||\n s.matches.some((m) => m.routeId === CatalogIndexRoute.id),\n selectedSlug: params?.slug,\n selectedSubSlug: params?.subSlug,\n }\n },\n })\n\n if (!isCatalogRoute) {\n return <Outlet />\n }\n\n return (\n <AppCatalogLayout queryClient={queryClient} trpcClient={trpcClient}>\n <AppCatalogPage\n selectedSlug={selectedSlug}\n selectedSubSlug={selectedSubSlug}\n />\n {/*\n The catalog child routes render `null` -- the page above is theirs. The\n Outlet is still rendered so those matches stay mounted: that is what\n lets a child loader error / notFound propagate to the single app-wide\n error boundary (`errorComponent: RootErrorPage` in __root.tsx) and to\n `defaultNotFoundComponent`. Drop it and those states are swallowed with\n no error surface at all.\n */}\n <Outlet />\n </AppCatalogLayout>\n )\n}\n"],"names":["AppDetailRoute","SubResourceDetailRoute","CatalogIndexRoute"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"_layout.js","sources":["../../../src/routes/_layout.tsx"],"sourcesContent":["import { Outlet, createFileRoute, useRouterState } from '@tanstack/react-router'\nimport { AppCatalogLayout } from '~/modules/appCatalog/ui/layout/AppCatalogLayout'\nimport { AppCatalogPage } from '~/modules/appCatalog/ui/pages/AppCatalogPage'\n// Route ids are read off the route objects instead of being spelled out as\n// string literals: renaming/moving either file is then a compile error here\n// rather than a silently non-matching id at runtime. `Route.id` is only\n// populated once the router builds the tree, so it must be read at render\n// time (inside the selector), never at module scope.\nimport { Route as AppDetailRoute } from './_layout/app.$slug'\nimport { Route as SubResourceDetailRoute } from './_layout/app.$slug_.sub.$subSlug'\nimport { Route as CatalogIndexRoute } from './_layout/index'\n\n/**\n * The opt-in a deployment's own route sets to get the catalog layout:\n *\n * createFileRoute('/_layout/app/$slug_/whatever')({\n * staticData: { acCatalogLayout: true },\n * component: MyRoute,\n * })\n *\n * Narrowed rather than asserted: `staticData` is a loose bag that a consumer\n * fills, so an unexpected shape must read as \"not opted in\" instead of\n * throwing inside a router-state selector.\n */\nfunction wantsCatalogLayout(staticData: unknown): boolean {\n return (\n typeof staticData === 'object' &&\n staticData !== null &&\n 'acCatalogLayout' in staticData &&\n staticData.acCatalogLayout === true\n )\n}\n\nfunction hasSlug(params: unknown): params is { slug: string } {\n return (\n typeof params === 'object' &&\n params !== null &&\n 'slug' in params &&\n typeof params.slug === 'string'\n )\n}\n\nexport const Route = createFileRoute('/_layout')({\n component: LayoutComponent,\n})\n\nfunction LayoutComponent() {\n // All hooks must be called unconditionally (rules of hooks).\n const { queryClient, trpcClient } = Route.useRouteContext()\n\n // The catalog routes (list + detail) share a single AppCatalogPage instance\n // so the component never unmounts between them, preserving scroll position\n // when opening/closing the app detail overlay.\n //\n // The selector returns only the two derived values, so this component\n // re-renders when the catalog branch or the open app changes -- not on every\n // router state update. `useStore` shallow-compares the result, so returning\n // an object is still referentially stable.\n const { isCatalogRoute, selectedSlug, selectedSubSlug } = useRouterState({\n select: (s) => {\n const detailMatch = s.matches.find((m) => m.routeId === AppDetailRoute.id)\n const subMatch = s.matches.find(\n (m) => m.routeId === SubResourceDetailRoute.id,\n )\n const params = (detailMatch ?? subMatch)?.params as\n | { slug?: string; subSlug?: string }\n | undefined\n // A deployment composing its own route tree can put a route inside this\n // layout, and it will not be one of the ids above — so without an opt-in\n // it silently renders outside AppCatalogLayout with no providers, and\n // the first hook that needs the catalog throws. `staticData` is the\n // opt-in: the route declares that it wants the catalog layout, and the\n // core needs to know nothing about it.\n const optedIn = s.matches.some(\n (m) => wantsCatalogLayout(m.staticData) === true,\n )\n // The slug from whichever match carries one, so a deployment's own route\n // under /app/$slug/... opens the card it belongs to rather than dropping\n // the user on the catalog index.\n const slugMatch = [...s.matches].reverse().find((m) => hasSlug(m.params))\n // One narrowing is enough: the predicate on `find` already guarantees the\n // match it returned carries a string slug.\n const consumerSlug = hasSlug(slugMatch?.params)\n ? slugMatch.params.slug\n : undefined\n\n return {\n isCatalogRoute:\n detailMatch !== undefined ||\n subMatch !== undefined ||\n optedIn ||\n s.matches.some((m) => m.routeId === CatalogIndexRoute.id),\n selectedSlug: params?.slug ?? consumerSlug,\n selectedSubSlug: params?.subSlug,\n }\n },\n })\n\n if (!isCatalogRoute) {\n return <Outlet />\n }\n\n return (\n <AppCatalogLayout queryClient={queryClient} trpcClient={trpcClient}>\n <AppCatalogPage\n selectedSlug={selectedSlug}\n selectedSubSlug={selectedSubSlug}\n />\n {/*\n The catalog child routes render `null` -- the page above is theirs. The\n Outlet is still rendered so those matches stay mounted: that is what\n lets a child loader error / notFound propagate to the single app-wide\n error boundary (`errorComponent: RootErrorPage` in __root.tsx) and to\n `defaultNotFoundComponent`. Drop it and those states are swallowed with\n no error surface at all.\n */}\n <Outlet />\n </AppCatalogLayout>\n )\n}\n"],"names":["AppDetailRoute","SubResourceDetailRoute","CatalogIndexRoute"],"mappings":";;;;;;;AAwBA,SAAS,mBAAmB,YAA8B;AACxD,SACE,OAAO,eAAe,YACtB,eAAe,QACf,qBAAqB,cACrB,WAAW,oBAAoB;AAEnC;AAEA,SAAS,QAAQ,QAA6C;AAC5D,SACE,OAAO,WAAW,YAClB,WAAW,QACX,UAAU,UACV,OAAO,OAAO,SAAS;AAE3B;AAEO,MAAM,QAAQ,gBAAgB,UAAU,EAAE;AAAA,EAC/C,WAAW;AACb,CAAC;AAED,SAAS,kBAAkB;AAEzB,QAAM,EAAE,aAAa,eAAe,MAAM,gBAAA;AAU1C,QAAM,EAAE,gBAAgB,cAAc,gBAAA,IAAoB,eAAe;AAAA,IACvE,QAAQ,CAAC,MAAM;;AACb,YAAM,cAAc,EAAE,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAYA,QAAe,EAAE;AACzE,YAAM,WAAW,EAAE,QAAQ;AAAA,QACzB,CAAC,MAAM,EAAE,YAAYC,QAAuB;AAAA,MAAA;AAE9C,YAAM,UAAU,oBAAe,aAAf,mBAA0B;AAS1C,YAAM,UAAU,EAAE,QAAQ;AAAA,QACxB,CAAC,MAAM,mBAAmB,EAAE,UAAU,MAAM;AAAA,MAAA;AAK9C,YAAM,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,QAAA,EAAU,KAAK,CAAC,MAAM,QAAQ,EAAE,MAAM,CAAC;AAGxE,YAAM,eAAe,QAAQ,uCAAW,MAAM,IAC1C,UAAU,OAAO,OACjB;AAEJ,aAAO;AAAA,QACL,gBACE,gBAAgB,UAChB,aAAa,UACb,WACA,EAAE,QAAQ,KAAK,CAAC,MAAM,EAAE,YAAYC,QAAkB,EAAE;AAAA,QAC1D,eAAc,iCAAQ,SAAQ;AAAA,QAC9B,iBAAiB,iCAAQ;AAAA,MAAA;AAAA,IAE7B;AAAA,EAAA,CACD;AAED,MAAI,CAAC,gBAAgB;AACnB,+BAAQ,QAAA,EAAO;AAAA,EACjB;AAEA,SACE,qBAAC,kBAAA,EAAiB,aAA0B,YAC1C,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA;AAAA,MAAA;AAAA,IAAA;AAAA,wBAUD,QAAA,CAAA,CAAO;AAAA,EAAA,GACV;AAEJ;"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const Route: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('
|
|
1
|
+
export declare const Route: import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, import('@tanstack/react-router').RootRoute<import('@tanstack/react-router').Register, undefined, import('..').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>, "/auth/callback", "/auth/callback", "/auth/callback", "/auth/callback", (search: Record<string, unknown>) => {
|
|
2
2
|
error: string | undefined;
|
|
3
3
|
code: string | undefined;
|
|
4
4
|
}, import('@tanstack/router-core').ResolveParams<"/auth/callback">, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, unknown, unknown, unknown, unknown, undefined>;
|
|
@@ -62,14 +62,21 @@ function Treatment({ error, reset }) {
|
|
|
62
62
|
"Try reset error"
|
|
63
63
|
] }) });
|
|
64
64
|
}
|
|
65
|
+
function describeThrown(error) {
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
return { message: error.message, stack: error.stack };
|
|
68
|
+
}
|
|
69
|
+
return { message: String(error), stack: void 0 };
|
|
70
|
+
}
|
|
65
71
|
function DefaultErrorComponent({ error, reset }) {
|
|
72
|
+
const { message, stack } = describeThrown(error);
|
|
66
73
|
return /* @__PURE__ */ jsx(BaseErrorPage, { children: /* @__PURE__ */ jsxs(Empty, { role: "alert", children: [
|
|
67
74
|
/* @__PURE__ */ jsxs(EmptyHeader, { children: [
|
|
68
75
|
/* @__PURE__ */ jsx(EmptyMedia, { variant: "icon", children: /* @__PURE__ */ jsx(BugIcon, {}) }),
|
|
69
76
|
/* @__PURE__ */ jsx(EmptyTitle, { children: "Ooops!" }),
|
|
70
77
|
/* @__PURE__ */ jsxs(EmptyDescription, { children: [
|
|
71
78
|
"Error inside app-catalog occured: ",
|
|
72
|
-
/* @__PURE__ */ jsx("i", { children:
|
|
79
|
+
/* @__PURE__ */ jsx("i", { children: message })
|
|
73
80
|
] })
|
|
74
81
|
] }),
|
|
75
82
|
/* @__PURE__ */ jsxs(EmptyContent, { children: [
|
|
@@ -78,7 +85,7 @@ function DefaultErrorComponent({ error, reset }) {
|
|
|
78
85
|
"div",
|
|
79
86
|
{
|
|
80
87
|
className: "mt-8 text-center max-w-[90vw] max-h-[80vh] overflow-auto",
|
|
81
|
-
children: /* @__PURE__ */ jsx("pre", { className: "text-left mt-8 text-sm", children: /* @__PURE__ */ jsx("i", { children:
|
|
88
|
+
children: /* @__PURE__ */ jsx("pre", { className: "text-left mt-8 text-sm", children: /* @__PURE__ */ jsx("i", { children: stack }) })
|
|
82
89
|
}
|
|
83
90
|
)
|
|
84
91
|
] })
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultErrorComponent.js","sources":["../../../../../src/ui/components/error/DefaultErrorComponent.tsx"],"sourcesContent":["import type { ErrorComponentProps } from '@tanstack/react-router'\nimport { BugIcon, RefreshCcwIcon } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { Button } from '~/ui/button'\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from '~/ui/empty'\nimport { useDb } from '~/userDb/DbContext'\nimport { usePwaAutoUpdate } from '~/modules/pwa'\nimport { isDexieError, isDexieMigrationError } from '~/util/error-utils'\nimport { BaseErrorPage } from './BaseErrorPage'\n\nexport function Treatment({ error, reset }: ErrorComponentProps) {\n const db = useDb()\n const pwaAutoUpdate = usePwaAutoUpdate()\n const [isResetting, setIsResetting] = useState(false)\n const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)\n\n useEffect(() => {\n if (!isDexieError(error) && pwaAutoUpdate) {\n setIsCheckingUpdate(true)\n pwaAutoUpdate.triggerUpdateOnError()\n const timer = setTimeout(() => setIsCheckingUpdate(false), 2000)\n return () => clearTimeout(timer)\n }\n return undefined\n }, [error, pwaAutoUpdate])\n\n async function dexieResetDb() {\n setIsResetting(true)\n try {\n await db.resetDatabase()\n console.log('Database deleted and recreated successfully')\n // Reload the page after successful reset\n window.location.reload()\n } catch (resetError) {\n console.error('Failed to reset database:', resetError)\n // Still reload the page even if reset fails, as it might help\n window.location.reload()\n } finally {\n setIsResetting(false)\n }\n }\n\n if (isDexieError(error)) {\n const isMigrationError = isDexieMigrationError(error)\n const buttonText = isMigrationError\n ? isResetting\n ? 'Clearing database...'\n : 'Clear database and reload'\n : isResetting\n ? 'Resetting...'\n : 'Try reset local settings'\n\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => dexieResetDb()}\n disabled={isResetting}\n >\n <RefreshCcwIcon className={isResetting ? 'animate-spin' : ''} />\n {buttonText}\n </Button>\n )\n }\n\n if (isCheckingUpdate) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <RefreshCcwIcon className=\"h-4 w-4 animate-spin\" />\n Checking for updates...\n </div>\n )\n }\n\n return (\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => reset()}>\n <RefreshCcwIcon />\n Try reset error\n </Button>\n {/* <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => window.location.reload()}\n >\n <RefreshCcwIcon />\n Try refresh page\n </Button> */}\n </div>\n )\n}\n\nexport function DefaultErrorComponent({ error, reset }: ErrorComponentProps) {\n return (\n <BaseErrorPage>\n <Empty role=\"alert\">\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <BugIcon />\n </EmptyMedia>\n <EmptyTitle>Ooops!</EmptyTitle>\n <EmptyDescription>\n Error inside app-catalog occured: {<i>{
|
|
1
|
+
{"version":3,"file":"DefaultErrorComponent.js","sources":["../../../../../src/ui/components/error/DefaultErrorComponent.tsx"],"sourcesContent":["import type { ErrorComponentProps } from '@tanstack/react-router'\nimport { BugIcon, RefreshCcwIcon } from 'lucide-react'\nimport { useEffect, useState } from 'react'\nimport { Button } from '~/ui/button'\nimport {\n Empty,\n EmptyContent,\n EmptyDescription,\n EmptyHeader,\n EmptyMedia,\n EmptyTitle,\n} from '~/ui/empty'\nimport { useDb } from '~/userDb/DbContext'\nimport { usePwaAutoUpdate } from '~/modules/pwa'\nimport { isDexieError, isDexieMigrationError } from '~/util/error-utils'\nimport { BaseErrorPage } from './BaseErrorPage'\n\nexport function Treatment({ error, reset }: ErrorComponentProps) {\n const db = useDb()\n const pwaAutoUpdate = usePwaAutoUpdate()\n const [isResetting, setIsResetting] = useState(false)\n const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)\n\n useEffect(() => {\n if (!isDexieError(error) && pwaAutoUpdate) {\n setIsCheckingUpdate(true)\n pwaAutoUpdate.triggerUpdateOnError()\n const timer = setTimeout(() => setIsCheckingUpdate(false), 2000)\n return () => clearTimeout(timer)\n }\n return undefined\n }, [error, pwaAutoUpdate])\n\n async function dexieResetDb() {\n setIsResetting(true)\n try {\n await db.resetDatabase()\n console.log('Database deleted and recreated successfully')\n // Reload the page after successful reset\n window.location.reload()\n } catch (resetError) {\n console.error('Failed to reset database:', resetError)\n // Still reload the page even if reset fails, as it might help\n window.location.reload()\n } finally {\n setIsResetting(false)\n }\n }\n\n if (isDexieError(error)) {\n const isMigrationError = isDexieMigrationError(error)\n const buttonText = isMigrationError\n ? isResetting\n ? 'Clearing database...'\n : 'Clear database and reload'\n : isResetting\n ? 'Resetting...'\n : 'Try reset local settings'\n\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => dexieResetDb()}\n disabled={isResetting}\n >\n <RefreshCcwIcon className={isResetting ? 'animate-spin' : ''} />\n {buttonText}\n </Button>\n )\n }\n\n if (isCheckingUpdate) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <RefreshCcwIcon className=\"h-4 w-4 animate-spin\" />\n Checking for updates...\n </div>\n )\n }\n\n return (\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => reset()}>\n <RefreshCcwIcon />\n Try reset error\n </Button>\n {/* <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => window.location.reload()}\n >\n <RefreshCcwIcon />\n Try refresh page\n </Button> */}\n </div>\n )\n}\n\n/**\n * What a thrown value can be relied on to have.\n *\n * `ErrorComponentProps.error` is `unknown`, and rightly so — `throw` accepts\n * any value, so a route can be handed a string or a plain object. Narrowing\n * here rather than asserting `Error` means a non-Error throw renders a useful\n * message instead of \"Cannot read properties of undefined\".\n */\nfunction describeThrown(error: unknown): {\n message: string\n stack: string | undefined\n} {\n if (error instanceof Error) {\n return { message: error.message, stack: error.stack }\n }\n return { message: String(error), stack: undefined }\n}\n\nexport function DefaultErrorComponent({ error, reset }: ErrorComponentProps) {\n const { message, stack } = describeThrown(error)\n\n return (\n <BaseErrorPage>\n <Empty role=\"alert\">\n <EmptyHeader>\n <EmptyMedia variant=\"icon\">\n <BugIcon />\n </EmptyMedia>\n <EmptyTitle>Ooops!</EmptyTitle>\n <EmptyDescription>\n Error inside app-catalog occured: {<i>{message}</i>}\n </EmptyDescription>\n </EmptyHeader>\n <EmptyContent>\n <Treatment error={error} reset={reset} />\n\n <div\n className={\n 'mt-8 text-center max-w-[90vw] max-h-[80vh] overflow-auto'\n }\n >\n <pre className={'text-left mt-8 text-sm'}>{<i>{stack}</i>}</pre>\n </div>\n </EmptyContent>\n </Empty>\n </BaseErrorPage>\n )\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -10,7 +10,6 @@ import { AppCatalogContext } from "../../../modules/appCatalog/context/AppCatalo
|
|
|
10
10
|
import { AttributionFooter } from "../../../modules/appCatalog/ui/catalog/AttributionFooter.js";
|
|
11
11
|
import { useAuth, useIsAuthenticated, useUser } from "../../../modules/auth/AuthContext.js";
|
|
12
12
|
import { Button } from "../../button.js";
|
|
13
|
-
import "../../../modules/auth/AuthModalContext.js";
|
|
14
13
|
import { useAuthActions } from "../../../modules/auth/useAuthActions.js";
|
|
15
14
|
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuSeparator, DropdownMenuItem } from "../../dropdown-menu.js";
|
|
16
15
|
import { Popover, PopoverTrigger, PopoverContent, PopoverClose } from "../../popover.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Header.js","sources":["../../../../../src/ui/components/header/Header.tsx"],"sourcesContent":["import type {\n AppVersionInfo,\n VersionInfo,\n} from '@igstack/app-catalog-backend-core'\nimport { Link } from '@tanstack/react-router'\nimport { useQuery } from '@tanstack/react-query'\nimport { LogOut, X } from 'lucide-react'\nimport React, { use } from 'react'\nimport AppCatalogLogo from '~/assets/app-catalog.svg?react'\nimport { useTRPC } from '~/api/infra/trpc'\nimport { ThemeSwitcher } from '~/components/ThemeSwitcher'\nimport { AppCatalogContext } from '~/modules/appCatalog/context/AppCatalogContext'\nimport { AttributionFooter } from '~/modules/appCatalog/ui/catalog/AttributionFooter'\nimport {\n useAuth,\n useAuthActions,\n useIsAuthenticated,\n useUser,\n} from '~/modules/auth'\nimport { Button } from '~/ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '~/ui/dropdown-menu'\nimport {\n Popover,\n PopoverClose,\n PopoverContent,\n PopoverTrigger,\n} from '~/ui/popover'\nimport { Skeleton } from '~/ui/skeleton'\n\nfunction ShaSuffix({ version }: { version: VersionInfo }) {\n if (!version.sha) return null\n if (version.shaUrl) {\n return (\n <>\n {' '}\n <a\n href={version.shaUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-muted-foreground hover:text-primary\"\n onClick={(e) => e.stopPropagation()}\n >\n ({version.sha})\n </a>\n </>\n )\n }\n return <span className=\"text-muted-foreground\"> ({version.sha})</span>\n}\n\nfunction VersionItem({\n label,\n version,\n}: {\n label: string\n version: VersionInfo\n}) {\n return (\n <div className=\"flex items-center justify-between gap-4 py-1\">\n <span className=\"text-xs text-muted-foreground\">{label}</span>\n <span className=\"text-sm font-medium text-right\">\n {version.url ? (\n <a\n href={version.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:text-primary/80 transition-colors\"\n onClick={(e) => e.stopPropagation()}\n >\n {version.displayName}\n </a>\n ) : (\n version.displayName\n )}\n <ShaSuffix version={version} />\n </span>\n </div>\n )\n}\n\nexport function VersionDisplay({ versions }: { versions: AppVersionInfo }) {\n const isLocal =\n versions.backend?.displayName === 'local' && !versions.coreVersion\n\n if (isLocal) {\n return (\n <span className=\"text-xs text-muted-foreground\" title=\"Local\">\n local\n </span>\n )\n }\n\n return (\n <div className=\"flex flex-col divide-y\">\n {versions.backend && (\n <VersionItem label=\"Pipeline\" version={versions.backend} />\n )}\n {versions.coreVersion && (\n <VersionItem label=\"Core\" version={versions.coreVersion} />\n )}\n {versions.frontend && (\n <VersionItem label=\"FE\" version={versions.frontend} />\n )}\n </div>\n )\n}\n\n/**\n * Version detail moved behind an on-demand popover (same click-to-reveal\n * pattern as PersonBadge's approver chips) instead of always-visible text —\n * keeps the header compact. Content is unchanged: same Pipeline/Core/FE\n * lines, links, and SHA suffix as before, just revealed on click.\n */\nexport function VersionPopover({ versions }: { versions: AppVersionInfo }) {\n const isLocal =\n versions.backend?.displayName === 'local' && !versions.coreVersion\n\n if (isLocal) {\n return (\n <span className=\"text-xs text-muted-foreground\" title=\"Local\">\n local\n </span>\n )\n }\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-mono leading-none text-muted-foreground hover:bg-primary/15 hover:text-primary transition-colors\"\n aria-label=\"Show version details\"\n title=\"Version details\"\n data-testid=\"version-info-trigger\"\n >\n {versions.backend?.displayName ?? '?'}\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-72 p-0 overflow-hidden\">\n <div className=\"flex items-center justify-between px-3 py-2 border-b bg-muted/40\">\n <span className=\"text-sm font-semibold\">Version details</span>\n <PopoverClose\n className=\"rounded p-1 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\n aria-label=\"Close\"\n >\n <X className=\"size-3.5\" />\n </PopoverClose>\n </div>\n <div className=\"px-3 py-1\">\n <VersionDisplay versions={versions} />\n </div>\n <AttributionFooter variant=\"popover\" />\n </PopoverContent>\n </Popover>\n )\n}\n\nexport interface HeaderProps {\n middle?: React.ReactNode\n}\n\nexport function Header({ middle }: HeaderProps) {\n const { isLoading } = useAuth()\n const isAuthenticated = useIsAuthenticated()\n const user = useUser()\n const { logout, devLogin } = useAuthActions()\n const appCatalogContextMaybe = use(AppCatalogContext)\n const trpc = useTRPC()\n const { data: providersData } = useQuery(\n trpc.auth.getProviders.queryOptions(),\n )\n\n const handleLogout = async () => {\n try {\n await logout()\n } catch (error) {\n console.error('Logout failed:', error)\n }\n }\n\n return (\n <div className=\"flex items-center mb-4 pb-4 gap-4 border-b\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <Link to=\"/\">\n <AppCatalogLogo className=\"h-16 w-16\" />\n </Link>\n <div className=\"flex flex-col items-start\">\n <Link to=\"/\" className=\"font-serif text-lg font-semibold\">\n App Catalog\n </Link>\n {appCatalogContextMaybe?.versions && (\n <VersionPopover versions={appCatalogContextMaybe.versions} />\n )}\n </div>\n </div>\n </div>\n {middle && <div className=\"flex-1\">{middle}</div>}\n <div className=\"flex items-center gap-3 ml-auto\">\n <ThemeSwitcher />\n {isLoading ? (\n <Skeleton className=\"w-8 h-8 rounded-full\" />\n ) : !isAuthenticated ? (\n // No Login button: the catalog is fully browsable without signing in,\n // so it mostly advertised a flow most visitors have no reason to\n // enter. Auth itself is untouched -- the modal, the login route, the\n // client and session handling all still work, so anyone who reaches\n // the flow by URL or by calling `useAuthModal().open()` can sign in,\n // and this branch flips to the user menu below once they do. `git\n // revert` of the commit that removed it restores the button as-is.\n // DEV Login stays: it is gated behind `devLoginEnabled`, so it never\n // reaches a real deployment.\n providersData?.devLoginEnabled && (\n <button\n type=\"button\"\n onClick={devLogin}\n className=\"text-xs font-medium px-2 py-1 rounded bg-amber-500/20 text-amber-600 dark:text-amber-400 hover:bg-amber-500/30 border border-amber-500/30 cursor-pointer\"\n >\n DEV Login\n </button>\n )\n ) : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-8 h-8 rounded-full p-0 hover:bg-accent hover:text-accent-foreground cursor-pointer\"\n aria-label={`User menu for ${user?.name || 'User'}`}\n data-testid=\"user-avatar-button\"\n >\n {user?.image ? (\n <img\n src={user.image}\n alt={user.name || 'User'}\n className=\"w-full h-full rounded-full object-cover\"\n />\n ) : (\n <div className=\"flex items-center justify-center w-full h-full rounded-full bg-primary text-primary-foreground text-xs font-semibold\">\n {(user?.name || user?.email || '?').charAt(0).toUpperCase()}\n </div>\n )}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <div className=\"px-2 py-3 flex items-center gap-3\">\n {user?.image ? (\n <img\n src={user.image}\n alt={user.name || 'User'}\n className=\"w-10 h-10 rounded-full object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex items-center justify-center w-10 h-10 rounded-full bg-primary text-primary-foreground text-sm font-semibold shrink-0\">\n {(user?.name || user?.email || '?').charAt(0).toUpperCase()}\n </div>\n )}\n <div className=\"flex flex-col min-w-0\">\n <p className=\"text-xs text-muted-foreground truncate\">\n @{user?.email?.split('@')[0] || 'user'}\n </p>\n <p className=\"text-sm font-medium truncate\">\n {user?.name || 'User'}\n </p>\n </div>\n </div>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={handleLogout}>\n <LogOut className=\"h-4 w-4 mr-2\" />\n <span>Sign out</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n )\n}\n"],"names":["AppCatalogLogo"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Header.js","sources":["../../../../../src/ui/components/header/Header.tsx"],"sourcesContent":["import type {\n AppVersionInfo,\n VersionInfo,\n} from '@igstack/app-catalog-backend-core'\nimport { Link } from '@tanstack/react-router'\nimport { useQuery } from '@tanstack/react-query'\nimport { LogOut, X } from 'lucide-react'\nimport React, { use } from 'react'\nimport AppCatalogLogo from '~/assets/app-catalog.svg?react'\nimport { useTRPC } from '~/api/infra/trpc'\nimport { ThemeSwitcher } from '~/components/ThemeSwitcher'\nimport { AppCatalogContext } from '~/modules/appCatalog/context/AppCatalogContext'\nimport { AttributionFooter } from '~/modules/appCatalog/ui/catalog/AttributionFooter'\nimport {\n useAuth,\n useAuthActions,\n useIsAuthenticated,\n useUser,\n} from '~/modules/auth'\nimport { Button } from '~/ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '~/ui/dropdown-menu'\nimport {\n Popover,\n PopoverClose,\n PopoverContent,\n PopoverTrigger,\n} from '~/ui/popover'\nimport { Skeleton } from '~/ui/skeleton'\n\nfunction ShaSuffix({ version }: { version: VersionInfo }) {\n if (!version.sha) return null\n if (version.shaUrl) {\n return (\n <>\n {' '}\n <a\n href={version.shaUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-muted-foreground hover:text-primary\"\n onClick={(e) => e.stopPropagation()}\n >\n ({version.sha})\n </a>\n </>\n )\n }\n return <span className=\"text-muted-foreground\"> ({version.sha})</span>\n}\n\nfunction VersionItem({\n label,\n version,\n}: {\n label: string\n version: VersionInfo\n}) {\n return (\n <div className=\"flex items-center justify-between gap-4 py-1\">\n <span className=\"text-xs text-muted-foreground\">{label}</span>\n <span className=\"text-sm font-medium text-right\">\n {version.url ? (\n <a\n href={version.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:text-primary/80 transition-colors\"\n onClick={(e) => e.stopPropagation()}\n >\n {version.displayName}\n </a>\n ) : (\n version.displayName\n )}\n <ShaSuffix version={version} />\n </span>\n </div>\n )\n}\n\nexport function VersionDisplay({ versions }: { versions: AppVersionInfo }) {\n const isLocal =\n versions.backend?.displayName === 'local' && !versions.coreVersion\n\n if (isLocal) {\n return (\n <span className=\"text-xs text-muted-foreground\" title=\"Local\">\n local\n </span>\n )\n }\n\n return (\n <div className=\"flex flex-col divide-y\">\n {versions.backend && (\n <VersionItem label=\"Pipeline\" version={versions.backend} />\n )}\n {versions.coreVersion && (\n <VersionItem label=\"Core\" version={versions.coreVersion} />\n )}\n {versions.frontend && (\n <VersionItem label=\"FE\" version={versions.frontend} />\n )}\n </div>\n )\n}\n\n/**\n * Version detail moved behind an on-demand popover (same click-to-reveal\n * pattern as PersonBadge's approver chips) instead of always-visible text —\n * keeps the header compact. Content is unchanged: same Pipeline/Core/FE\n * lines, links, and SHA suffix as before, just revealed on click.\n */\nexport function VersionPopover({ versions }: { versions: AppVersionInfo }) {\n const isLocal =\n versions.backend?.displayName === 'local' && !versions.coreVersion\n\n if (isLocal) {\n return (\n <span className=\"text-xs text-muted-foreground\" title=\"Local\">\n local\n </span>\n )\n }\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-mono leading-none text-muted-foreground hover:bg-primary/15 hover:text-primary transition-colors\"\n aria-label=\"Show version details\"\n title=\"Version details\"\n data-testid=\"version-info-trigger\"\n >\n {versions.backend?.displayName ?? '?'}\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-72 p-0 overflow-hidden\">\n <div className=\"flex items-center justify-between px-3 py-2 border-b bg-muted/40\">\n <span className=\"text-sm font-semibold\">Version details</span>\n <PopoverClose\n className=\"rounded p-1 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors\"\n aria-label=\"Close\"\n >\n <X className=\"size-3.5\" />\n </PopoverClose>\n </div>\n <div className=\"px-3 py-1\">\n <VersionDisplay versions={versions} />\n </div>\n <AttributionFooter variant=\"popover\" />\n </PopoverContent>\n </Popover>\n )\n}\n\nexport interface HeaderProps {\n middle?: React.ReactNode\n}\n\nexport function Header({ middle }: HeaderProps) {\n const { isLoading } = useAuth()\n const isAuthenticated = useIsAuthenticated()\n const user = useUser()\n const { logout, devLogin } = useAuthActions()\n const appCatalogContextMaybe = use(AppCatalogContext)\n const trpc = useTRPC()\n const { data: providersData } = useQuery(\n trpc.auth.getProviders.queryOptions(),\n )\n\n const handleLogout = async () => {\n try {\n await logout()\n } catch (error) {\n console.error('Logout failed:', error)\n }\n }\n\n return (\n <div className=\"flex items-center mb-4 pb-4 gap-4 border-b\">\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <Link to=\"/\">\n <AppCatalogLogo className=\"h-16 w-16\" />\n </Link>\n <div className=\"flex flex-col items-start\">\n <Link to=\"/\" className=\"font-serif text-lg font-semibold\">\n App Catalog\n </Link>\n {appCatalogContextMaybe?.versions && (\n <VersionPopover versions={appCatalogContextMaybe.versions} />\n )}\n </div>\n </div>\n </div>\n {middle && <div className=\"flex-1\">{middle}</div>}\n <div className=\"flex items-center gap-3 ml-auto\">\n <ThemeSwitcher />\n {isLoading ? (\n <Skeleton className=\"w-8 h-8 rounded-full\" />\n ) : !isAuthenticated ? (\n // No Login button: the catalog is fully browsable without signing in,\n // so it mostly advertised a flow most visitors have no reason to\n // enter. Auth itself is untouched -- the modal, the login route, the\n // client and session handling all still work, so anyone who reaches\n // the flow by URL or by calling `useAuthModal().open()` can sign in,\n // and this branch flips to the user menu below once they do. `git\n // revert` of the commit that removed it restores the button as-is.\n // DEV Login stays: it is gated behind `devLoginEnabled`, so it never\n // reaches a real deployment.\n providersData?.devLoginEnabled && (\n <button\n type=\"button\"\n onClick={devLogin}\n className=\"text-xs font-medium px-2 py-1 rounded bg-amber-500/20 text-amber-600 dark:text-amber-400 hover:bg-amber-500/30 border border-amber-500/30 cursor-pointer\"\n >\n DEV Login\n </button>\n )\n ) : (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-8 h-8 rounded-full p-0 hover:bg-accent hover:text-accent-foreground cursor-pointer\"\n aria-label={`User menu for ${user?.name || 'User'}`}\n data-testid=\"user-avatar-button\"\n >\n {user?.image ? (\n <img\n src={user.image}\n alt={user.name || 'User'}\n className=\"w-full h-full rounded-full object-cover\"\n />\n ) : (\n <div className=\"flex items-center justify-center w-full h-full rounded-full bg-primary text-primary-foreground text-xs font-semibold\">\n {(user?.name || user?.email || '?').charAt(0).toUpperCase()}\n </div>\n )}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <div className=\"px-2 py-3 flex items-center gap-3\">\n {user?.image ? (\n <img\n src={user.image}\n alt={user.name || 'User'}\n className=\"w-10 h-10 rounded-full object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex items-center justify-center w-10 h-10 rounded-full bg-primary text-primary-foreground text-sm font-semibold shrink-0\">\n {(user?.name || user?.email || '?').charAt(0).toUpperCase()}\n </div>\n )}\n <div className=\"flex flex-col min-w-0\">\n <p className=\"text-xs text-muted-foreground truncate\">\n @{user?.email?.split('@')[0] || 'user'}\n </p>\n <p className=\"text-sm font-medium truncate\">\n {user?.name || 'User'}\n </p>\n </div>\n </div>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={handleLogout}>\n <LogOut className=\"h-4 w-4 mr-2\" />\n <span>Sign out</span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n )\n}\n"],"names":["AppCatalogLogo"],"mappings":"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"}
|
package/dist/esm/ui/dialog.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ declare function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogP
|
|
|
5
5
|
declare function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>): import("react/jsx-runtime").JSX.Element;
|
|
6
6
|
declare function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
declare function DialogOverlay({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Overlay>): import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
declare function DialogContent({ className, children, showCloseButton, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
8
|
+
declare function DialogContent({ className, children, showCloseButton, onEscapeKeyDown, ...props }: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
9
9
|
showCloseButton?: boolean;
|
|
10
10
|
}): import("react/jsx-runtime").JSX.Element;
|
|
11
11
|
declare function DialogHeader({ className, ...props }: React.ComponentProps<'div'>): import("react/jsx-runtime").JSX.Element;
|
package/dist/esm/ui/dialog.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
3
|
+
import { useDismissStack } from "../modules/dismiss/DismissStack.js";
|
|
3
4
|
import { XIcon } from "lucide-react";
|
|
4
5
|
import { cn } from "../lib/utils.js";
|
|
5
6
|
function Dialog({
|
|
@@ -32,14 +33,22 @@ function DialogContent({
|
|
|
32
33
|
className,
|
|
33
34
|
children,
|
|
34
35
|
showCloseButton = true,
|
|
36
|
+
onEscapeKeyDown,
|
|
35
37
|
...props
|
|
36
38
|
}) {
|
|
39
|
+
const stack = useDismissStack();
|
|
40
|
+
const handleEscapeKeyDown = (event) => {
|
|
41
|
+
onEscapeKeyDown == null ? void 0 : onEscapeKeyDown(event);
|
|
42
|
+
if (event.defaultPrevented) return;
|
|
43
|
+
if (stack.dismissTop()) event.preventDefault();
|
|
44
|
+
};
|
|
37
45
|
return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
38
46
|
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
39
47
|
/* @__PURE__ */ jsxs(
|
|
40
48
|
DialogPrimitive.Content,
|
|
41
49
|
{
|
|
42
50
|
"data-slot": "dialog-content",
|
|
51
|
+
onEscapeKeyDown: handleEscapeKeyDown,
|
|
43
52
|
className: cn(
|
|
44
53
|
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
45
54
|
className
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog.js","sources":["../../../src/ui/dialog.tsx"],"sourcesContent":["import * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { XIcon } from 'lucide-react'\n\nimport { cn } from '~/lib/utils'\n\nfunction Dialog({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />\n}\n\nfunction DialogTrigger({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />\n}\n\nfunction DialogPortal({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />\n}\n\nfunction DialogClose({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean\n}) {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n className={cn(\n 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn('flex flex-col gap-2 text-center sm:text-left', className)}\n {...props}\n />\n )\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction DialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn('text-lg leading-none font-semibold', className)}\n {...props}\n />\n )\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn('text-muted-foreground text-sm', className)}\n {...props}\n />\n )\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dialog.js","sources":["../../../src/ui/dialog.tsx"],"sourcesContent":["import * as React from 'react'\nimport * as DialogPrimitive from '@radix-ui/react-dialog'\nimport { useDismissStack } from '~/modules/dismiss/DismissStack'\nimport { XIcon } from 'lucide-react'\n\nimport { cn } from '~/lib/utils'\n\nfunction Dialog({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />\n}\n\nfunction DialogTrigger({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />\n}\n\nfunction DialogPortal({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />\n}\n\nfunction DialogClose({\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n data-slot=\"dialog-overlay\"\n className={cn(\n 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction DialogContent({\n className,\n children,\n showCloseButton = true,\n onEscapeKeyDown,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean\n}) {\n const stack = useDismissStack()\n\n /**\n * Bridges Radix's Escape handling into the dismiss stack.\n *\n * Radix listens for Escape with `capture: true`, so it runs before the\n * stack's own document listener and before every component handler. Left\n * alone, a Radix dialog therefore closes itself even when something else is\n * on top of it — which is the bug this whole module exists to fix, just\n * from the other direction.\n *\n * So: ask the stack first. If it had a layer to dismiss, that layer was the\n * topmost one and Radix must stand down, which `preventDefault` tells it.\n * If the stack was empty, nothing registered an opinion and Radix closing\n * this dialog is exactly right — which keeps every existing dialog working\n * whether or not anyone has wired it into the stack.\n */\n const handleEscapeKeyDown = (event: KeyboardEvent) => {\n onEscapeKeyDown?.(event)\n if (event.defaultPrevented) return\n if (stack.dismissTop()) event.preventDefault()\n }\n\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Content\n data-slot=\"dialog-content\"\n onEscapeKeyDown={handleEscapeKeyDown}\n className={cn(\n 'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n data-slot=\"dialog-close\"\n className=\"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n )\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-header\"\n className={cn('flex flex-col gap-2 text-center sm:text-left', className)}\n {...props}\n />\n )\n}\n\nfunction DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"dialog-footer\"\n className={cn(\n 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction DialogTitle({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n data-slot=\"dialog-title\"\n className={cn('text-lg leading-none font-semibold', className)}\n {...props}\n />\n )\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn('text-muted-foreground text-sm', className)}\n {...props}\n />\n )\n}\n\nexport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogOverlay,\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n}\n"],"names":[],"mappings":";;;;;AAOA,SAAS,OAAO;AAAA,EACd,GAAG;AACL,GAAsD;AACpD,6BAAQ,gBAAgB,MAAhB,EAAqB,aAAU,UAAU,GAAG,OAAO;AAC7D;AAQA,SAAS,aAAa;AAAA,EACpB,GAAG;AACL,GAAwD;AACtD,6BAAQ,gBAAgB,QAAhB,EAAuB,aAAU,iBAAiB,GAAG,OAAO;AACtE;AAQA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA,GAAG;AACL,GAAyD;AACvD,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAEA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB;AAAA,EACA,GAAG;AACL,GAEG;AACD,QAAM,QAAQ,gBAAA;AAiBd,QAAM,sBAAsB,CAAC,UAAyB;AACpD,uDAAkB;AAClB,QAAI,MAAM,iBAAkB;AAC5B,QAAI,MAAM,aAAc,OAAM,eAAA;AAAA,EAChC;AAEA,SACE,qBAAC,cAAA,EAAa,aAAU,iBACtB,UAAA;AAAA,IAAA,oBAAC,eAAA,EAAc;AAAA,IACf;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,aAAU;AAAA,QACV,iBAAiB;AAAA,QACjB,WAAW;AAAA,UACT;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAEH,UAAA;AAAA,UAAA;AAAA,UACA,mBACC;AAAA,YAAC,gBAAgB;AAAA,YAAhB;AAAA,cACC,aAAU;AAAA,cACV,WAAU;AAAA,cAEV,UAAA;AAAA,gBAAA,oBAAC,OAAA,EAAM;AAAA,gBACP,oBAAC,QAAA,EAAK,WAAU,WAAU,UAAA,QAAA,CAAK;AAAA,cAAA;AAAA,YAAA;AAAA,UAAA;AAAA,QACjC;AAAA,MAAA;AAAA,IAAA;AAAA,EAEJ,GACF;AAEJ;AAEA,SAAS,aAAa,EAAE,WAAW,GAAG,SAAsC;AAC1E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,gDAAgD,SAAS;AAAA,MACtE,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAeA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA,GAAG;AACL,GAAuD;AACrD,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,sCAAsC,SAAS;AAAA,MAC5D,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAEA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA,GAAG;AACL,GAA6D;AAC3D,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
|
package/dist/esm/ui/table.d.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
|
|
2
|
+
interface TableProps extends React.ComponentProps<'table'> {
|
|
3
|
+
/**
|
|
4
|
+
* Keep the header visible while the body scrolls.
|
|
5
|
+
*
|
|
6
|
+
* The scroll has to be on the container BELOW, not on an ancestor, and that is
|
|
7
|
+
* the whole difficulty: this container sets `overflow-x: auto`, which per spec
|
|
8
|
+
* makes `overflow-y: visible` compute to `auto` as well. So it is already a
|
|
9
|
+
* scrollport on both axes, and `position: sticky` on the header resolves
|
|
10
|
+
* against it rather than against whatever ancestor has the height limit.
|
|
11
|
+
* Putting the height limit anywhere above leaves the header pinned to an
|
|
12
|
+
* element that never scrolls vertically — it scrolls away exactly as if
|
|
13
|
+
* nothing had been set.
|
|
14
|
+
*
|
|
15
|
+
* Pass the max height as `className` (e.g. `max-h-[400px]`); it lands on this
|
|
16
|
+
* container, which then both clips and scrolls.
|
|
17
|
+
*/
|
|
18
|
+
stickyHeader?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* The scrolling container, for a caller that virtualizes the body: the
|
|
21
|
+
* scrollport is this element, not the `<table>`, so a ref on the table
|
|
22
|
+
* would measure the wrong thing.
|
|
23
|
+
*/
|
|
24
|
+
containerRef?: React.Ref<HTMLDivElement>;
|
|
25
|
+
}
|
|
26
|
+
declare function Table({ className, stickyHeader, containerRef, ...props }: TableProps): import("react/jsx-runtime").JSX.Element;
|
|
3
27
|
declare function TableHeader({ className, ...props }: React.ComponentProps<'thead'>): import("react/jsx-runtime").JSX.Element;
|
|
4
28
|
declare function TableBody({ className, ...props }: React.ComponentProps<'tbody'>): import("react/jsx-runtime").JSX.Element;
|
|
5
29
|
declare function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>): import("react/jsx-runtime").JSX.Element;
|
package/dist/esm/ui/table.js
CHANGED
|
@@ -1,16 +1,33 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { cn } from "../lib/utils.js";
|
|
3
|
-
function Table({
|
|
3
|
+
function Table({
|
|
4
|
+
className,
|
|
5
|
+
stickyHeader,
|
|
6
|
+
containerRef,
|
|
7
|
+
...props
|
|
8
|
+
}) {
|
|
4
9
|
return /* @__PURE__ */ jsx(
|
|
5
10
|
"div",
|
|
6
11
|
{
|
|
12
|
+
ref: containerRef,
|
|
7
13
|
"data-slot": "table-container",
|
|
8
|
-
className:
|
|
14
|
+
className: cn(
|
|
15
|
+
"relative w-full overflow-x-auto",
|
|
16
|
+
stickyHeader && "overflow-y-auto",
|
|
17
|
+
stickyHeader && className
|
|
18
|
+
),
|
|
9
19
|
children: /* @__PURE__ */ jsx(
|
|
10
20
|
"table",
|
|
11
21
|
{
|
|
12
22
|
"data-slot": "table",
|
|
13
|
-
className: cn(
|
|
23
|
+
className: cn(
|
|
24
|
+
"w-full caption-bottom text-sm",
|
|
25
|
+
// The header's own bottom border does not render reliably once the
|
|
26
|
+
// row is sticky, so the separating line comes from an inset shadow on
|
|
27
|
+
// the cells instead. Opaque background, or rows show through it.
|
|
28
|
+
stickyHeader && "[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10 [&>thead>tr]:border-b-0 [&>thead_th]:bg-background [&>thead_th]:shadow-[inset_0_-1px_0_var(--border)]",
|
|
29
|
+
!stickyHeader && className
|
|
30
|
+
),
|
|
14
31
|
...props
|
|
15
32
|
}
|
|
16
33
|
)
|
package/dist/esm/ui/table.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sources":["../../../src/ui/table.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport { cn } from '~/lib/utils'\n\
|
|
1
|
+
{"version":3,"file":"table.js","sources":["../../../src/ui/table.tsx"],"sourcesContent":["import * as React from 'react'\n\nimport { cn } from '~/lib/utils'\n\ninterface TableProps extends React.ComponentProps<'table'> {\n /**\n * Keep the header visible while the body scrolls.\n *\n * The scroll has to be on the container BELOW, not on an ancestor, and that is\n * the whole difficulty: this container sets `overflow-x: auto`, which per spec\n * makes `overflow-y: visible` compute to `auto` as well. So it is already a\n * scrollport on both axes, and `position: sticky` on the header resolves\n * against it rather than against whatever ancestor has the height limit.\n * Putting the height limit anywhere above leaves the header pinned to an\n * element that never scrolls vertically — it scrolls away exactly as if\n * nothing had been set.\n *\n * Pass the max height as `className` (e.g. `max-h-[400px]`); it lands on this\n * container, which then both clips and scrolls.\n */\n stickyHeader?: boolean\n /**\n * The scrolling container, for a caller that virtualizes the body: the\n * scrollport is this element, not the `<table>`, so a ref on the table\n * would measure the wrong thing.\n */\n containerRef?: React.Ref<HTMLDivElement>\n}\n\nfunction Table({\n className,\n stickyHeader,\n containerRef,\n ...props\n}: TableProps) {\n return (\n <div\n ref={containerRef}\n data-slot=\"table-container\"\n className={cn(\n 'relative w-full overflow-x-auto',\n stickyHeader && 'overflow-y-auto',\n stickyHeader && className,\n )}\n >\n <table\n data-slot=\"table\"\n className={cn(\n 'w-full caption-bottom text-sm',\n // The header's own bottom border does not render reliably once the\n // row is sticky, so the separating line comes from an inset shadow on\n // the cells instead. Opaque background, or rows show through it.\n stickyHeader &&\n '[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10 [&>thead>tr]:border-b-0 [&>thead_th]:bg-background [&>thead_th]:shadow-[inset_0_-1px_0_var(--border)]',\n !stickyHeader && className,\n )}\n {...props}\n />\n </div>\n )\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n return (\n <thead\n data-slot=\"table-header\"\n className={cn('[&_tr]:border-b', className)}\n {...props}\n />\n )\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn('[&_tr:last-child]:border-0', className)}\n {...props}\n />\n )\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\n 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n 'text-foreground h-10 px-2 text-left align-middle font-medium [&:has([role=checkbox])]:pr-0',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n 'p-2 align-middle [&:has([role=checkbox])]:pr-0',\n className,\n )}\n {...props}\n />\n )\n}\n\nfunction TableCaption({\n className,\n ...props\n}: React.ComponentProps<'caption'>) {\n return (\n <caption\n data-slot=\"table-caption\"\n className={cn('text-muted-foreground mt-4 text-sm', className)}\n {...props}\n />\n )\n}\n\nexport {\n Table,\n TableBody,\n TableCaption,\n TableCell,\n TableFooter,\n TableHead,\n TableHeader,\n TableRow,\n}\n"],"names":[],"mappings":";;AA6BA,SAAS,MAAM;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAe;AACb,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK;AAAA,MACL,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA,gBAAgB;AAAA,QAChB,gBAAgB;AAAA,MAAA;AAAA,MAGlB,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,aAAU;AAAA,UACV,WAAW;AAAA,YACT;AAAA;AAAA;AAAA;AAAA,YAIA,gBACE;AAAA,YACF,CAAC,gBAAgB;AAAA,UAAA;AAAA,UAElB,GAAG;AAAA,QAAA;AAAA,MAAA;AAAA,IACN;AAAA,EAAA;AAGN;AAEA,SAAS,YAAY,EAAE,WAAW,GAAG,SAAwC;AAC3E,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,mBAAmB,SAAS;AAAA,MACzC,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,SAAwC;AACzE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,8BAA8B,SAAS;AAAA,MACpD,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAeA,SAAS,SAAS,EAAE,WAAW,GAAG,SAAqC;AACrE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,SAAqC;AACtE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;AAEA,SAAS,UAAU,EAAE,WAAW,GAAG,SAAqC;AACtE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAG;AAAA,IAAA;AAAA,EAAA;AAGV;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AcRouterInitParams } from '../types/types.js';
|
|
2
|
-
export declare function createAcRouter({ context, history }: AcRouterInitParams): import('@tanstack/router-core').RouterCore<import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, any, "/", "/", string, "__root__", undefined, {}, import('
|
|
2
|
+
export declare function createAcRouter({ context, history }: AcRouterInitParams): import('@tanstack/router-core').RouterCore<import('@tanstack/router-core').Route<import('@tanstack/react-router').Register, any, "/", "/", string, "__root__", undefined, {}, import('..').AcRouterContext, import('@tanstack/router-core').AnyContext, import('@tanstack/router-core').AnyContext, {}, undefined, import('../routeTree.gen').RootRouteChildren, import('../routeTree.gen').FileRouteTypes, unknown, unknown, undefined>, "never", false, import('@tanstack/history').RouterHistory, Record<string, any>>;
|
|
3
3
|
declare module '@tanstack/react-router' {
|
|
4
4
|
interface Register {
|
|
5
5
|
router: ReturnType<typeof createAcRouter>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igstack/app-catalog-frontend-core",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.7.0",
|
|
4
4
|
"description": "Frontend core library for App Catalog",
|
|
5
5
|
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
"default": "./dist/esm/internal.js"
|
|
28
28
|
}
|
|
29
29
|
},
|
|
30
|
+
"./router": {
|
|
31
|
+
"my-custom-condition": "./src/router.ts",
|
|
32
|
+
"import": {
|
|
33
|
+
"types": "./dist/esm/router.d.ts",
|
|
34
|
+
"default": "./dist/esm/router.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
30
37
|
"./public": {
|
|
31
38
|
"my-custom-condition": "./public",
|
|
32
39
|
"default": "./dist/public"
|
|
@@ -65,10 +72,9 @@
|
|
|
65
72
|
"@tanstack/query-sync-storage-persister": "^5.80.6",
|
|
66
73
|
"@tanstack/react-query": "^5.90.12",
|
|
67
74
|
"@tanstack/react-query-devtools": "^5.91.1",
|
|
68
|
-
"@tanstack/react-router": "^1.151.6",
|
|
69
75
|
"@tanstack/react-table": "^8.21.3",
|
|
70
76
|
"@tanstack/react-virtual": "3.13.13",
|
|
71
|
-
"@tanstack/router-plugin": "
|
|
77
|
+
"@tanstack/router-plugin": "1.168.42",
|
|
72
78
|
"@tiptap/extension-link": "^2.10.0",
|
|
73
79
|
"@tiptap/extension-placeholder": "^2.10.0",
|
|
74
80
|
"@tiptap/react": "^2.10.0",
|
|
@@ -112,19 +118,20 @@
|
|
|
112
118
|
"tailwindcss": "^4.1.7",
|
|
113
119
|
"tiptap-markdown": "^0.8.0",
|
|
114
120
|
"tslib": "^2.3.0",
|
|
115
|
-
"@igstack/app-catalog-shared-core": "6.
|
|
121
|
+
"@igstack/app-catalog-shared-core": "6.7.0"
|
|
116
122
|
},
|
|
117
123
|
"devDependencies": {
|
|
118
124
|
"@tailwindcss/vite": "^4.0.5",
|
|
119
|
-
"@tanstack/react-router
|
|
120
|
-
"@tanstack/router-devtools": "
|
|
121
|
-
"@tanstack/router-
|
|
125
|
+
"@tanstack/react-router": "1.170.41",
|
|
126
|
+
"@tanstack/react-router-devtools": "1.167.2",
|
|
127
|
+
"@tanstack/router-devtools": "1.167.2",
|
|
128
|
+
"@tanstack/router-plugin": "1.168.42",
|
|
122
129
|
"@tanstack/vite-config": "^0.4.3",
|
|
123
130
|
"@testing-library/jest-dom": "6.6.3",
|
|
124
131
|
"@testing-library/react": "^16.2.0",
|
|
125
132
|
"@testing-library/user-event": "^14.6.1",
|
|
126
|
-
"@types/react": "
|
|
127
|
-
"@types/react-dom": "
|
|
133
|
+
"@types/react": "19.2.9",
|
|
134
|
+
"@types/react-dom": "19.2.7",
|
|
128
135
|
"@vitejs/plugin-react": "^4.3.4",
|
|
129
136
|
"@vitest/ui": "^4.1.2",
|
|
130
137
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
@@ -139,16 +146,17 @@
|
|
|
139
146
|
"vite-plugin-static-copy": "^3.1.4",
|
|
140
147
|
"vite-plugin-svgr": "^4.2.0",
|
|
141
148
|
"vitest": "^4.1.2",
|
|
142
|
-
"@igstack/app-catalog-backend-core": "6.
|
|
149
|
+
"@igstack/app-catalog-backend-core": "6.7.0"
|
|
143
150
|
},
|
|
144
151
|
"peerDependencies": {
|
|
152
|
+
"@tanstack/react-router": "1.170.41",
|
|
145
153
|
"react": "19.1.2",
|
|
146
154
|
"react-dom": "19.1.2",
|
|
147
155
|
"tailwindcss": "^4.1.7",
|
|
148
156
|
"vite": "^6.3.5",
|
|
149
157
|
"vite-plugin-svgr": "^4.2.0"
|
|
150
158
|
},
|
|
151
|
-
"gitHead": "
|
|
159
|
+
"gitHead": "4d75acde50ae71295fee00fc17f87134becc949f",
|
|
152
160
|
"scripts": {
|
|
153
161
|
"build": "vite build",
|
|
154
162
|
"build:lenient": "vite build --mode lenient",
|
package/src/App.tsx
CHANGED
|
@@ -5,17 +5,44 @@ import type { QueryClient } from '@tanstack/react-query'
|
|
|
5
5
|
import type { TRPCRouter } from '@igstack/app-catalog-backend-core'
|
|
6
6
|
import type { TRPCClient } from '@trpc/client'
|
|
7
7
|
import type { AcDb } from './userDb/AcDb'
|
|
8
|
-
import type {
|
|
8
|
+
import type { RegisteredRouter } from '@tanstack/react-router'
|
|
9
9
|
import { TRPCProvider } from './api/infra/trpc'
|
|
10
10
|
import type { UiSettings } from './types/uiSettings'
|
|
11
11
|
import { UiSettingsContext } from './context/UiSettingsContext'
|
|
12
|
+
import { ExtensionsContext } from './modules/extensions'
|
|
13
|
+
import type { AcPlugin } from './modules/extensions'
|
|
12
14
|
|
|
13
15
|
export interface AppProps {
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* `RegisteredRouter`, not `ReturnType<typeof createAcRouter>`.
|
|
18
|
+
*
|
|
19
|
+
* That was a type-only import, and a type-only import still pulls the
|
|
20
|
+
* referenced file's `declare module` into the program — so naming
|
|
21
|
+
* `createAcRouter` here put the core's `Register` augmentation into every
|
|
22
|
+
* consumer, and a deployment composing its own route tree could not declare
|
|
23
|
+
* its own without `TS2717`.
|
|
24
|
+
*
|
|
25
|
+
* This widens nothing: `RegisteredRouter` resolves per program against
|
|
26
|
+
* whatever `Register` that program declared, so the core's own program gets
|
|
27
|
+
* the core's router and a deployment gets its own, each exactly typed. A
|
|
28
|
+
* router built from a different route tree is still rejected here.
|
|
29
|
+
*
|
|
30
|
+
* The one blind spot, guarded by a type test on the consumer side: with NO
|
|
31
|
+
* `Register` anywhere in a program this silently degrades to `AnyRouter`.
|
|
32
|
+
*/
|
|
33
|
+
router: RegisteredRouter
|
|
15
34
|
queryClient: QueryClient
|
|
16
35
|
trpcClient: TRPCClient<TRPCRouter>
|
|
17
36
|
db: AcDb
|
|
18
37
|
uiSettings?: UiSettings
|
|
38
|
+
/**
|
|
39
|
+
* Deployment-specific UI, rendered at the slots the core declares. Optional:
|
|
40
|
+
* with none registered every slot renders nothing and the UI is identical to
|
|
41
|
+
* one with no slots at all.
|
|
42
|
+
*
|
|
43
|
+
* Must be a module-level constant — see `AcPlugin`.
|
|
44
|
+
*/
|
|
45
|
+
extensions?: readonly AcPlugin[]
|
|
19
46
|
}
|
|
20
47
|
|
|
21
48
|
export function App({
|
|
@@ -24,13 +51,17 @@ export function App({
|
|
|
24
51
|
trpcClient,
|
|
25
52
|
db,
|
|
26
53
|
uiSettings,
|
|
54
|
+
extensions,
|
|
27
55
|
}: AppProps) {
|
|
28
56
|
return (
|
|
29
57
|
<QueryClientProvider client={queryClient}>
|
|
30
58
|
<TRPCProvider queryClient={queryClient} trpcClient={trpcClient}>
|
|
31
59
|
<DbProvider db={db}>
|
|
32
60
|
<UiSettingsContext value={uiSettings}>
|
|
33
|
-
|
|
61
|
+
{/* Above the router, so a slot anywhere in the tree can read it. */}
|
|
62
|
+
<ExtensionsContext value={extensions}>
|
|
63
|
+
<RouterProvider router={router} />
|
|
64
|
+
</ExtensionsContext>
|
|
34
65
|
</UiSettingsContext>
|
|
35
66
|
</DbProvider>
|
|
36
67
|
</TRPCProvider>
|
|
@@ -32,7 +32,10 @@ vi.mock('~/modules/appCatalog/ui/context/AppCatalogFiltersContext', () => ({
|
|
|
32
32
|
actions: {},
|
|
33
33
|
})),
|
|
34
34
|
}))
|
|
35
|
-
vi.mock('~/modules/auth', () => ({
|
|
35
|
+
vi.mock('~/modules/auth', () => ({
|
|
36
|
+
useUser: vi.fn(() => null),
|
|
37
|
+
useOptionalUser: vi.fn(() => null),
|
|
38
|
+
}))
|
|
36
39
|
vi.mock('~/modules/appCatalog/ui/detail/FeedbackSection', () => ({
|
|
37
40
|
FeedbackSection: () => <div />,
|
|
38
41
|
}))
|
|
@@ -17,7 +17,7 @@ vi.mock('~/modules/appCatalog/ui/components/SubResourceDetailPanel', () => ({
|
|
|
17
17
|
SubResourceDetailPanel: () => null,
|
|
18
18
|
}))
|
|
19
19
|
vi.mock('~/modules/appCatalog/context/AppCatalogContext', () => ({
|
|
20
|
-
useAppCatalogContext: vi.fn(() => ({ approvalMethods: [] })),
|
|
20
|
+
useAppCatalogContext: vi.fn(() => ({ approvalMethods: [], resources: [] })),
|
|
21
21
|
}))
|
|
22
22
|
|
|
23
23
|
const { AppDetailPanel } =
|