@stapel/categories-react 0.1.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/CHANGELOG.md +1 -0
- package/MODULE.md +194 -0
- package/README.md +191 -0
- package/dist/api/categoriesApi.d.ts +121 -0
- package/dist/api/categoriesApi.d.ts.map +1 -0
- package/dist/api/categoriesApi.js +48 -0
- package/dist/api/categoriesApi.js.map +1 -0
- package/dist/api/generated/schema.d.ts +2321 -0
- package/dist/api/generated/schema.d.ts.map +1 -0
- package/dist/api/generated/schema.js +2 -0
- package/dist/api/generated/schema.js.map +1 -0
- package/dist/api/types.d.ts +92 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/catalog/labels.d.ts +103 -0
- package/dist/catalog/labels.d.ts.map +1 -0
- package/dist/catalog/labels.js +56 -0
- package/dist/catalog/labels.js.map +1 -0
- package/dist/catalog/pks.d.ts +25 -0
- package/dist/catalog/pks.d.ts.map +1 -0
- package/dist/catalog/pks.js +38 -0
- package/dist/catalog/pks.js.map +1 -0
- package/dist/catalog/sync.d.ts +103 -0
- package/dist/catalog/sync.d.ts.map +1 -0
- package/dist/catalog/sync.js +114 -0
- package/dist/catalog/sync.js.map +1 -0
- package/dist/catalog/tree.d.ts +119 -0
- package/dist/catalog/tree.d.ts.map +1 -0
- package/dist/catalog/tree.js +166 -0
- package/dist/catalog/tree.js.map +1 -0
- package/dist/default/CatalogPage.d.ts +16 -0
- package/dist/default/CatalogPage.d.ts.map +1 -0
- package/dist/default/CatalogPage.js +13 -0
- package/dist/default/CatalogPage.js.map +1 -0
- package/dist/default/CategoryBreadcrumbsBar.d.ts +20 -0
- package/dist/default/CategoryBreadcrumbsBar.d.ts.map +1 -0
- package/dist/default/CategoryBreadcrumbsBar.js +35 -0
- package/dist/default/CategoryBreadcrumbsBar.js.map +1 -0
- package/dist/default/CategoryCarouselStrip.d.ts +22 -0
- package/dist/default/CategoryCarouselStrip.d.ts.map +1 -0
- package/dist/default/CategoryCarouselStrip.js +24 -0
- package/dist/default/CategoryCarouselStrip.js.map +1 -0
- package/dist/default/CategoryFeatureList.d.ts +23 -0
- package/dist/default/CategoryFeatureList.d.ts.map +1 -0
- package/dist/default/CategoryFeatureList.js +28 -0
- package/dist/default/CategoryFeatureList.js.map +1 -0
- package/dist/default/CategoryPage.d.ts +43 -0
- package/dist/default/CategoryPage.d.ts.map +1 -0
- package/dist/default/CategoryPage.js +21 -0
- package/dist/default/CategoryPage.js.map +1 -0
- package/dist/default/CategoryPickerField.d.ts +26 -0
- package/dist/default/CategoryPickerField.d.ts.map +1 -0
- package/dist/default/CategoryPickerField.js +40 -0
- package/dist/default/CategoryPickerField.js.map +1 -0
- package/dist/default/CategoryTreePane.d.ts +28 -0
- package/dist/default/CategoryTreePane.d.ts.map +1 -0
- package/dist/default/CategoryTreePane.js +25 -0
- package/dist/default/CategoryTreePane.js.map +1 -0
- package/dist/default/ErrorAlert.d.ts +9 -0
- package/dist/default/ErrorAlert.d.ts.map +1 -0
- package/dist/default/ErrorAlert.js +27 -0
- package/dist/default/ErrorAlert.js.map +1 -0
- package/dist/default/index.d.ts +37 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +28 -0
- package/dist/default/index.js.map +1 -0
- package/dist/default/theme.d.ts +25 -0
- package/dist/default/theme.d.ts.map +1 -0
- package/dist/default/theme.js +40 -0
- package/dist/default/theme.js.map +1 -0
- package/dist/default/types.d.ts +12 -0
- package/dist/default/types.d.ts.map +1 -0
- package/dist/default/types.js +2 -0
- package/dist/default/types.js.map +1 -0
- package/dist/headless/CategoriesProvider.d.ts +19 -0
- package/dist/headless/CategoriesProvider.d.ts.map +1 -0
- package/dist/headless/CategoriesProvider.js +15 -0
- package/dist/headless/CategoriesProvider.js.map +1 -0
- package/dist/headless/CategoryBreadcrumbs.d.ts +40 -0
- package/dist/headless/CategoryBreadcrumbs.d.ts.map +1 -0
- package/dist/headless/CategoryBreadcrumbs.js +42 -0
- package/dist/headless/CategoryBreadcrumbs.js.map +1 -0
- package/dist/headless/CategoryCarousel.d.ts +48 -0
- package/dist/headless/CategoryCarousel.d.ts.map +1 -0
- package/dist/headless/CategoryCarousel.js +34 -0
- package/dist/headless/CategoryCarousel.js.map +1 -0
- package/dist/headless/CategoryFeatures.d.ts +67 -0
- package/dist/headless/CategoryFeatures.d.ts.map +1 -0
- package/dist/headless/CategoryFeatures.js +46 -0
- package/dist/headless/CategoryFeatures.js.map +1 -0
- package/dist/headless/CategoryPicker.d.ts +69 -0
- package/dist/headless/CategoryPicker.d.ts.map +1 -0
- package/dist/headless/CategoryPicker.js +86 -0
- package/dist/headless/CategoryPicker.js.map +1 -0
- package/dist/headless/CategoryTree.d.ts +55 -0
- package/dist/headless/CategoryTree.d.ts.map +1 -0
- package/dist/headless/CategoryTree.js +51 -0
- package/dist/headless/CategoryTree.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +22 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +31 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +25 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +74 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.es.gen.js +58 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +407 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +207 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +58 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +89 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +134 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +41 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +90 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/model/catalogStore.d.ts +23 -0
- package/dist/model/catalogStore.d.ts.map +1 -0
- package/dist/model/catalogStore.js +85 -0
- package/dist/model/catalogStore.js.map +1 -0
- package/dist/model/catalogSync.d.ts +49 -0
- package/dist/model/catalogSync.d.ts.map +1 -0
- package/dist/model/catalogSync.js +64 -0
- package/dist/model/catalogSync.js.map +1 -0
- package/dist/model/context.d.ts +11 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +16 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/queries.d.ts +124 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +147 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +30 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +16 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/runtime.d.ts +31 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +6 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/nav/manifest.d.ts +31 -0
- package/dist/nav/manifest.d.ts.map +1 -0
- package/dist/nav/manifest.js +37 -0
- package/dist/nav/manifest.js.map +1 -0
- package/llms.txt +125 -0
- package/manifest.json +918 -0
- package/nav-manifest.json +44 -0
- package/package.json +119 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/categoriesApi.ts +179 -0
- package/src/api/generated/schema.ts +2323 -0
- package/src/api/types.ts +99 -0
- package/src/catalog/labels.ts +129 -0
- package/src/catalog/pks.ts +35 -0
- package/src/catalog/sync.ts +169 -0
- package/src/catalog/tree.ts +242 -0
- package/src/default/CatalogPage.tsx +40 -0
- package/src/default/CategoryBreadcrumbsBar.tsx +93 -0
- package/src/default/CategoryCarouselStrip.tsx +102 -0
- package/src/default/CategoryFeatureList.tsx +125 -0
- package/src/default/CategoryPage.tsx +114 -0
- package/src/default/CategoryPickerField.tsx +173 -0
- package/src/default/CategoryTreePane.tsx +134 -0
- package/src/default/ErrorAlert.tsx +46 -0
- package/src/default/index.ts +36 -0
- package/src/default/theme.tsx +67 -0
- package/src/default/types.ts +12 -0
- package/src/headless/CategoriesProvider.tsx +20 -0
- package/src/headless/CategoryBreadcrumbs.tsx +78 -0
- package/src/headless/CategoryCarousel.tsx +80 -0
- package/src/headless/CategoryFeatures.tsx +100 -0
- package/src/headless/CategoryPicker.tsx +173 -0
- package/src/headless/CategoryTree.tsx +115 -0
- package/src/i18n/errorsMap.ts +46 -0
- package/src/i18n/es.ts +92 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +238 -0
- package/src/i18n/generated/errors.json +495 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +156 -0
- package/src/i18n/ru.ts +110 -0
- package/src/index.ts +191 -0
- package/src/model/catalogStore.ts +102 -0
- package/src/model/catalogSync.ts +115 -0
- package/src/model/context.tsx +28 -0
- package/src/model/queries.ts +226 -0
- package/src/model/queryKeys.ts +50 -0
- package/src/model/runtime.ts +39 -0
- package/src/nav/manifest.ts +66 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
2
|
+
import { categoryBreadcrumbs, resolveCategorySlug } from "../catalog/tree.js";
|
|
3
|
+
import { categoryLabel } from "../catalog/labels.js";
|
|
4
|
+
import { useCategoryCatalog } from "../model/queries.js";
|
|
5
|
+
/**
|
|
6
|
+
* The ancestry of the current category, as data.
|
|
7
|
+
*
|
|
8
|
+
* Walks the BUILT index rather than `tn_ancestors_pks`: a crumb must name a
|
|
9
|
+
* category the visitor can actually open, and the built index is what the
|
|
10
|
+
* visitor is being shown. The server's own column is exposed separately
|
|
11
|
+
* (`categoryAncestorIds`) and the two are cross-checked in the suite, so a
|
|
12
|
+
* divergence is a red test rather than a link to nowhere.
|
|
13
|
+
*/
|
|
14
|
+
export function CategoryBreadcrumbs(props) {
|
|
15
|
+
const { slug, categoryId, children, ...catalogOptions } = props;
|
|
16
|
+
const query = useCategoryCatalog(catalogOptions);
|
|
17
|
+
const catalog = loadStateFromQuery(query);
|
|
18
|
+
const index = catalog.status === "ready" ? catalog.data.index : null;
|
|
19
|
+
let current = null;
|
|
20
|
+
if (index !== null) {
|
|
21
|
+
if (slug !== undefined)
|
|
22
|
+
current = resolveCategorySlug(index, slug) ?? null;
|
|
23
|
+
else if (categoryId !== null && categoryId !== undefined) {
|
|
24
|
+
current = index.byId.get(categoryId) ?? null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return children({
|
|
28
|
+
state: mapLoad(catalog, (data) => {
|
|
29
|
+
const path = categoryBreadcrumbs(data.index, current?.id);
|
|
30
|
+
return path.map((node, i) => ({
|
|
31
|
+
node,
|
|
32
|
+
label: categoryLabel(node.category),
|
|
33
|
+
isCurrent: i === path.length - 1,
|
|
34
|
+
}));
|
|
35
|
+
}),
|
|
36
|
+
unknownSlug: catalog.status === "ready" && slug !== undefined && current === null,
|
|
37
|
+
refetch: () => {
|
|
38
|
+
void query.refetch();
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=CategoryBreadcrumbs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryBreadcrumbs.js","sourceRoot":"","sources":["../../src/headless/CategoryBreadcrumbs.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AA8BzD;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAA+B;IAE/B,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,cAAc,EAAE,GAAG,KAAK,CAAC;IAChE,MAAM,KAAK,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAErE,IAAI,OAAO,GAAwB,IAAI,CAAC;IACxC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,GAAG,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;aACtE,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YACzD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;QACd,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YAC/B,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;YAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC5B,IAAI;gBACJ,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC;gBACnC,SAAS,EAAE,CAAC,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC;aACjC,CAAC,CAAC,CAAC;QACN,CAAC,CAAC;QACF,WAAW,EACT,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;QACtE,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { Category } from "../api/types.js";
|
|
4
|
+
import type { CategoryLabel } from "../catalog/labels.js";
|
|
5
|
+
/** One carousel tile. */
|
|
6
|
+
export interface CarouselEntry {
|
|
7
|
+
readonly category: Category;
|
|
8
|
+
readonly label: CategoryLabel;
|
|
9
|
+
/**
|
|
10
|
+
* The tile's icon reference — `carousel_icon`, falling back to
|
|
11
|
+
* `catalog_icon`. Both are OPAQUE STRINGS, deliberately decoupled from
|
|
12
|
+
* stapel-cdn ("an opaque string, resolved by the host if at all",
|
|
13
|
+
* `models.py`). This pair does not build a URL out of one: the host knows
|
|
14
|
+
* its CDN base, this library does not, and a guessed URL is a broken image
|
|
15
|
+
* on every deployment that guessed differently.
|
|
16
|
+
*/
|
|
17
|
+
readonly icon: string | null;
|
|
18
|
+
/** The storefront path for this tile. Slug-based, because that is the
|
|
19
|
+
* route the spec defines — and the reason the client resolves slugs. */
|
|
20
|
+
readonly href: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CategoryCarouselBag {
|
|
23
|
+
/**
|
|
24
|
+
* The tiles. `empty` means the catalogue has no `carousel_enabled`
|
|
25
|
+
* categories — a real configuration, and a landing page that says nothing
|
|
26
|
+
* rather than showing a spinner forever.
|
|
27
|
+
*/
|
|
28
|
+
readonly state: LoadState<readonly CarouselEntry[]>;
|
|
29
|
+
readonly isFetching: boolean;
|
|
30
|
+
refetch(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface CategoryCarouselProps {
|
|
33
|
+
/** Path prefix for a tile's link. Default `/c` — the spec's `/c/:slug`. */
|
|
34
|
+
basePath?: string;
|
|
35
|
+
enabled?: boolean;
|
|
36
|
+
children: (bag: CategoryCarouselBag) => ReactNode;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The landing page's category strip.
|
|
40
|
+
*
|
|
41
|
+
* `GET /categories/carousel/` is the one endpoint that arrives ready to
|
|
42
|
+
* render: the server filters `active` AND `carousel_enabled`, orders by
|
|
43
|
+
* `tn_priority` descending, caches the serialization and sends
|
|
44
|
+
* `Cache-Control: public, max-age`. So this component does no filtering of its
|
|
45
|
+
* own — it maps rows to tiles and says which strings still need translating.
|
|
46
|
+
*/
|
|
47
|
+
export declare function CategoryCarousel(props: CategoryCarouselProps): ReactNode;
|
|
48
|
+
//# sourceMappingURL=CategoryCarousel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryCarousel.d.ts","sourceRoot":"","sources":["../../src/headless/CategoryCarousel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,yBAAyB;AACzB,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;4EACwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;IACpD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,SAAS,CAAC;CACnD;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,SAAS,CA0BxE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
2
|
+
import { categoryLabel } from "../catalog/labels.js";
|
|
3
|
+
import { useCategoryCarousel } from "../model/queries.js";
|
|
4
|
+
/**
|
|
5
|
+
* The landing page's category strip.
|
|
6
|
+
*
|
|
7
|
+
* `GET /categories/carousel/` is the one endpoint that arrives ready to
|
|
8
|
+
* render: the server filters `active` AND `carousel_enabled`, orders by
|
|
9
|
+
* `tn_priority` descending, caches the serialization and sends
|
|
10
|
+
* `Cache-Control: public, max-age`. So this component does no filtering of its
|
|
11
|
+
* own — it maps rows to tiles and says which strings still need translating.
|
|
12
|
+
*/
|
|
13
|
+
export function CategoryCarousel(props) {
|
|
14
|
+
const base = props.basePath ?? "/c";
|
|
15
|
+
const query = useCategoryCarousel(props.enabled !== undefined ? { enabled: props.enabled } : {});
|
|
16
|
+
const state = loadStateFromQuery(query);
|
|
17
|
+
return props.children({
|
|
18
|
+
state: mapLoad(state, (rows) => rows.map((category) => ({
|
|
19
|
+
category,
|
|
20
|
+
label: categoryLabel(category),
|
|
21
|
+
icon: category.carousel_icon !== undefined && category.carousel_icon !== ""
|
|
22
|
+
? category.carousel_icon
|
|
23
|
+
: category.catalog_icon !== undefined && category.catalog_icon !== ""
|
|
24
|
+
? category.catalog_icon
|
|
25
|
+
: null,
|
|
26
|
+
href: `${base}/${category.slug}`,
|
|
27
|
+
}))),
|
|
28
|
+
isFetching: query.isFetching,
|
|
29
|
+
refetch: () => {
|
|
30
|
+
void query.refetch();
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=CategoryCarousel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryCarousel.js","sourceRoot":"","sources":["../../src/headless/CategoryCarousel.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAsC1D;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,IAAI,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC;IACpC,MAAM,KAAK,GAAG,mBAAmB,CAC/B,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAC9D,CAAC;IACF,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAExC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAC7B,IAAI,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;YACtB,QAAQ;YACR,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC;YAC9B,IAAI,EACF,QAAQ,CAAC,aAAa,KAAK,SAAS,IAAI,QAAQ,CAAC,aAAa,KAAK,EAAE;gBACnE,CAAC,CAAC,QAAQ,CAAC,aAAa;gBACxB,CAAC,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS,IAAI,QAAQ,CAAC,YAAY,KAAK,EAAE;oBACnE,CAAC,CAAC,QAAQ,CAAC,YAAY;oBACvB,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,GAAG,IAAI,IAAI,QAAQ,CAAC,IAAI,EAAE;SACjC,CAAC,CAAC,CACJ;QACD,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { CategoryFeature } from "../api/types.js";
|
|
4
|
+
import type { CategoryLabel } from "../catalog/labels.js";
|
|
5
|
+
/** One feature of a category, with everything a renderer needs decided. */
|
|
6
|
+
export interface CategoryFeatureEntry {
|
|
7
|
+
readonly feature: CategoryFeature;
|
|
8
|
+
/** `kind: "key"` → translate. `translate: "none"` makes it a literal. */
|
|
9
|
+
readonly label: CategoryLabel;
|
|
10
|
+
/** `config.type`, or `undefined` when the row carries none. An undefined
|
|
11
|
+
* type is an UNSUPPORTED type as far as any renderer is concerned —
|
|
12
|
+
* `@stapel/attributes-react` says so loudly rather than skipping the row. */
|
|
13
|
+
readonly type: string | undefined;
|
|
14
|
+
readonly mandatory: boolean;
|
|
15
|
+
/** Are this feature's option labels translation keys? Only under
|
|
16
|
+
* `translate: "all"` and while `translatable_options` is not false. */
|
|
17
|
+
readonly optionsAreKeys: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface CategoryFeaturesBag {
|
|
20
|
+
/**
|
|
21
|
+
* The feature schema. `empty` means the category declares no features and
|
|
22
|
+
* inherits none — common for a root category, and a compose form that says
|
|
23
|
+
* "no extra details for this category" rather than spinning.
|
|
24
|
+
*/
|
|
25
|
+
readonly state: LoadState<readonly CategoryFeatureEntry[]>;
|
|
26
|
+
/**
|
|
27
|
+
* The raw `FeatureDef[]`, ready to hand straight to
|
|
28
|
+
* `@stapel/attributes-react`'s `<FeatureFields>`, `unsupportedTypeGate`,
|
|
29
|
+
* `mirrorValidate` or `formatFeatureValue`. Kept beside the decorated list
|
|
30
|
+
* because those functions take the wire shape, and re-deriving it from
|
|
31
|
+
* `entries` would be a second mapping to keep in step.
|
|
32
|
+
*/
|
|
33
|
+
readonly features: readonly CategoryFeature[];
|
|
34
|
+
/** Only the features marked `show_as_badge` — the card's summary line. */
|
|
35
|
+
readonly badges: readonly CategoryFeature[];
|
|
36
|
+
/** Only the features marked `show_at_title` — the generated title parts. */
|
|
37
|
+
readonly titleParts: readonly CategoryFeature[];
|
|
38
|
+
readonly isFetching: boolean;
|
|
39
|
+
refetch(): void;
|
|
40
|
+
}
|
|
41
|
+
export interface CategoryFeaturesProps {
|
|
42
|
+
categoryId: number | null | undefined;
|
|
43
|
+
enabled?: boolean;
|
|
44
|
+
children: (bag: CategoryFeaturesBag) => ReactNode;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* A category's feature schema — the bridge to `@stapel/attributes-react`.
|
|
48
|
+
*
|
|
49
|
+
* `GET /categories/{id}/features/` resolves inheritance and order server-side
|
|
50
|
+
* and returns `FeatureCompact` rows whose `config` is the polymorphic
|
|
51
|
+
* attributes config, VERBATIM: `FeatureCompactSerializer.get_config` returns
|
|
52
|
+
* `obj.config`, not `get_config_with_defaults()`. So an absent config key
|
|
53
|
+
* means "the type's default", never "off", and this pair does not restate
|
|
54
|
+
* those defaults — attributes-react owns them (its §13.2 note 1).
|
|
55
|
+
*
|
|
56
|
+
* The two consumers this exists for:
|
|
57
|
+
*
|
|
58
|
+
* - the **compose form** (`listings-react`), which draws the features with
|
|
59
|
+
* attributes-react's editor registry and blocks submit on a type this
|
|
60
|
+
* build cannot draw; and
|
|
61
|
+
* - the **facet panel** (`search-react`), whose `categoryFeatures` slot takes
|
|
62
|
+
* exactly `features` below and uses it to caption facet values.
|
|
63
|
+
*
|
|
64
|
+
* Neither of them re-fetches: a host mounts this once per category page.
|
|
65
|
+
*/
|
|
66
|
+
export declare function CategoryFeatures(props: CategoryFeaturesProps): ReactNode;
|
|
67
|
+
//# sourceMappingURL=CategoryFeatures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryFeatures.d.ts","sourceRoot":"","sources":["../../src/headless/CategoryFeatures.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAG1D,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAClC,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B;;iFAE6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;2EACuE;IACvE,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,oBAAoB,EAAE,CAAC,CAAC;IAC3D;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;IAC9C,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IAC5C,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,SAAS,CAAC;CACnD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,SAAS,CA0BxE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
2
|
+
import { featureType } from "@stapel/attributes-react";
|
|
3
|
+
import { featureLabel, featureOptionsAreKeys } from "../catalog/labels.js";
|
|
4
|
+
import { useCategoryFeatures } from "../model/queries.js";
|
|
5
|
+
/**
|
|
6
|
+
* A category's feature schema — the bridge to `@stapel/attributes-react`.
|
|
7
|
+
*
|
|
8
|
+
* `GET /categories/{id}/features/` resolves inheritance and order server-side
|
|
9
|
+
* and returns `FeatureCompact` rows whose `config` is the polymorphic
|
|
10
|
+
* attributes config, VERBATIM: `FeatureCompactSerializer.get_config` returns
|
|
11
|
+
* `obj.config`, not `get_config_with_defaults()`. So an absent config key
|
|
12
|
+
* means "the type's default", never "off", and this pair does not restate
|
|
13
|
+
* those defaults — attributes-react owns them (its §13.2 note 1).
|
|
14
|
+
*
|
|
15
|
+
* The two consumers this exists for:
|
|
16
|
+
*
|
|
17
|
+
* - the **compose form** (`listings-react`), which draws the features with
|
|
18
|
+
* attributes-react's editor registry and blocks submit on a type this
|
|
19
|
+
* build cannot draw; and
|
|
20
|
+
* - the **facet panel** (`search-react`), whose `categoryFeatures` slot takes
|
|
21
|
+
* exactly `features` below and uses it to caption facet values.
|
|
22
|
+
*
|
|
23
|
+
* Neither of them re-fetches: a host mounts this once per category page.
|
|
24
|
+
*/
|
|
25
|
+
export function CategoryFeatures(props) {
|
|
26
|
+
const query = useCategoryFeatures(props.categoryId, props.enabled !== undefined ? { enabled: props.enabled } : {});
|
|
27
|
+
const state = loadStateFromQuery(query);
|
|
28
|
+
const features = state.status === "ready" ? state.data : [];
|
|
29
|
+
return props.children({
|
|
30
|
+
state: mapLoad(state, (rows) => rows.map((feature) => ({
|
|
31
|
+
feature,
|
|
32
|
+
label: featureLabel(feature),
|
|
33
|
+
type: featureType(feature),
|
|
34
|
+
mandatory: feature.mandatory === true,
|
|
35
|
+
optionsAreKeys: featureOptionsAreKeys(feature),
|
|
36
|
+
}))),
|
|
37
|
+
features,
|
|
38
|
+
badges: features.filter((f) => f.show_as_badge === true),
|
|
39
|
+
titleParts: features.filter((f) => f.show_at_title === true),
|
|
40
|
+
isFetching: query.isFetching,
|
|
41
|
+
refetch: () => {
|
|
42
|
+
void query.refetch();
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=CategoryFeatures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryFeatures.js","sourceRoot":"","sources":["../../src/headless/CategoryFeatures.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAE3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AA8C1D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAA4B;IAC3D,MAAM,KAAK,GAAG,mBAAmB,CAC/B,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAC9D,CAAC;IACF,MAAM,KAAK,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IAE5D,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAC7B,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACrB,OAAO;YACP,KAAK,EAAE,YAAY,CAAC,OAAO,CAAC;YAC5B,IAAI,EAAE,WAAW,CAAC,OAAO,CAAC;YAC1B,SAAS,EAAE,OAAO,CAAC,SAAS,KAAK,IAAI;YACrC,cAAc,EAAE,qBAAqB,CAAC,OAAO,CAAC;SAC/C,CAAC,CAAC,CACJ;QACD,QAAQ;QACR,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,IAAI,CAAC;QACxD,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,KAAK,IAAI,CAAC;QAC5D,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { CategoryNode } from "../catalog/tree.js";
|
|
4
|
+
import type { CategoryLabel } from "../catalog/labels.js";
|
|
5
|
+
import type { UseCategoryCatalogOptions } from "../model/queries.js";
|
|
6
|
+
/** One selectable option in the picker. */
|
|
7
|
+
export interface CategoryOption {
|
|
8
|
+
readonly node: CategoryNode;
|
|
9
|
+
readonly label: CategoryLabel;
|
|
10
|
+
/** Root → this node, for a skin that shows the full path in the option. */
|
|
11
|
+
readonly path: readonly CategoryLabel[];
|
|
12
|
+
/** `true` when this node has no children — usually the only kind a listing
|
|
13
|
+
* may be filed under. See `leavesOnly`. */
|
|
14
|
+
readonly isLeaf: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface CategoryPickerBag {
|
|
17
|
+
/**
|
|
18
|
+
* The options at the current drill level, or the search matches while a
|
|
19
|
+
* query is typed. `empty` is a real answer here — a leaf category has no
|
|
20
|
+
* children, and a search can match nothing.
|
|
21
|
+
*/
|
|
22
|
+
readonly state: LoadState<readonly CategoryOption[]>;
|
|
23
|
+
/** Where the drill-down currently is: root → open node. */
|
|
24
|
+
readonly path: readonly CategoryNode[];
|
|
25
|
+
/** The selected node, or `null`. */
|
|
26
|
+
readonly selected: CategoryNode | null;
|
|
27
|
+
/** Blocked when nothing is selected, or when the selection is not a leaf
|
|
28
|
+
* and `leavesOnly` is on. Carries the REASON — a disabled control that does
|
|
29
|
+
* not say why is the thing `ActionAvailability` exists to prevent. */
|
|
30
|
+
readonly submitBlockedReason: CategoryPickerBlockedReason | null;
|
|
31
|
+
readonly query: string;
|
|
32
|
+
setQuery(value: string): void;
|
|
33
|
+
/** Descend into a node. A leaf selects instead of descending. */
|
|
34
|
+
open(node: CategoryNode): void;
|
|
35
|
+
/** Back up one level. */
|
|
36
|
+
up(): void;
|
|
37
|
+
select(node: CategoryNode | null): void;
|
|
38
|
+
refetch(): void;
|
|
39
|
+
}
|
|
40
|
+
/** Why the picker will not hand a value back yet. */
|
|
41
|
+
export type CategoryPickerBlockedReason = "nothing_selected" | "not_a_leaf";
|
|
42
|
+
export interface CategoryPickerProps extends UseCategoryCatalogOptions {
|
|
43
|
+
/** Controlled selection. */
|
|
44
|
+
value?: number | null;
|
|
45
|
+
onChange?: (id: number | null, node: CategoryNode | null) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Only leaf categories are a valid answer. Default `true`: a listing filed
|
|
48
|
+
* under "Electronics" instead of "Electronics › Phones › Used" inherits the
|
|
49
|
+
* wrong feature set, and the compose form then asks the wrong questions.
|
|
50
|
+
*/
|
|
51
|
+
leavesOnly?: boolean;
|
|
52
|
+
/** Resolve a translation key to a caption, so SEARCH matches what the
|
|
53
|
+
* person can read. Without it, search matches the key. */
|
|
54
|
+
translate?: (key: string) => string;
|
|
55
|
+
children: (bag: CategoryPickerBag) => ReactNode;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The category chooser of the compose form, headless.
|
|
59
|
+
*
|
|
60
|
+
* Two modes over the SAME synced tree: drill-down (roots → children → …) and
|
|
61
|
+
* a flat search. The search matches the RENDERED caption when a `translate` is
|
|
62
|
+
* supplied and the raw key otherwise, which is the only honest behaviour for a
|
|
63
|
+
* catalogue whose names are translation keys (`catalog/labels.ts`): a person
|
|
64
|
+
* typing "phones" is typing what they see, not `category.phones`.
|
|
65
|
+
*
|
|
66
|
+
* Nothing here requests anything — the catalogue is already in memory.
|
|
67
|
+
*/
|
|
68
|
+
export declare function CategoryPicker(props: CategoryPickerProps): ReactNode;
|
|
69
|
+
//# sourceMappingURL=CategoryPicker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryPicker.d.ts","sourceRoot":"","sources":["../../src/headless/CategoryPicker.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAErE,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,EAAE,SAAS,aAAa,EAAE,CAAC;IACxC;+CAC2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,cAAc,EAAE,CAAC,CAAC;IACrD,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,SAAS,YAAY,EAAE,CAAC;IACvC,oCAAoC;IACpC,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IACvC;;0EAEsE;IACtE,QAAQ,CAAC,mBAAmB,EAAE,2BAA2B,GAAG,IAAI,CAAC;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,iEAAiE;IACjE,IAAI,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,yBAAyB;IACzB,EAAE,IAAI,IAAI,CAAC;IACX,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC;IACxC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,qDAAqD;AACrD,MAAM,MAAM,2BAA2B,GAAG,kBAAkB,GAAG,YAAY,CAAC;AAE5E,MAAM,WAAW,mBAAoB,SAAQ,yBAAyB;IACpE,4BAA4B;IAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;IAClE;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;8DAC0D;IAC1D,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,QAAQ,EAAE,CAAC,GAAG,EAAE,iBAAiB,KAAK,SAAS,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,SAAS,CA4FpE"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
3
|
+
import { categoryBreadcrumbs, flattenCategoryNodes, } from "../catalog/tree.js";
|
|
4
|
+
import { categoryLabel, renderCategoryLabel } from "../catalog/labels.js";
|
|
5
|
+
import { useCategoryCatalog } from "../model/queries.js";
|
|
6
|
+
/**
|
|
7
|
+
* The category chooser of the compose form, headless.
|
|
8
|
+
*
|
|
9
|
+
* Two modes over the SAME synced tree: drill-down (roots → children → …) and
|
|
10
|
+
* a flat search. The search matches the RENDERED caption when a `translate` is
|
|
11
|
+
* supplied and the raw key otherwise, which is the only honest behaviour for a
|
|
12
|
+
* catalogue whose names are translation keys (`catalog/labels.ts`): a person
|
|
13
|
+
* typing "phones" is typing what they see, not `category.phones`.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here requests anything — the catalogue is already in memory.
|
|
16
|
+
*/
|
|
17
|
+
export function CategoryPicker(props) {
|
|
18
|
+
const { value, onChange, leavesOnly, translate, children, ...catalogOptions } = props;
|
|
19
|
+
const requireLeaf = leavesOnly ?? true;
|
|
20
|
+
const query = useCategoryCatalog(catalogOptions);
|
|
21
|
+
const catalog = loadStateFromQuery(query);
|
|
22
|
+
const index = catalog.status === "ready" ? catalog.data.index : null;
|
|
23
|
+
const [openId, setOpenId] = useState(null);
|
|
24
|
+
const [text, setText] = useState("");
|
|
25
|
+
const selected = index !== null && value !== null && value !== undefined
|
|
26
|
+
? (index.byId.get(value) ?? null)
|
|
27
|
+
: null;
|
|
28
|
+
const toOption = useMemo(() => (node) => ({
|
|
29
|
+
node,
|
|
30
|
+
label: categoryLabel(node.category),
|
|
31
|
+
path: (index === null ? [] : categoryBreadcrumbs(index, node.id)).map((crumb) => categoryLabel(crumb.category)),
|
|
32
|
+
isLeaf: node.children.length === 0,
|
|
33
|
+
}), [index]);
|
|
34
|
+
const options = useMemo(() => {
|
|
35
|
+
if (index === null)
|
|
36
|
+
return [];
|
|
37
|
+
const needle = text.trim().toLocaleLowerCase();
|
|
38
|
+
if (needle !== "") {
|
|
39
|
+
const caption = (node) => {
|
|
40
|
+
const label = categoryLabel(node.category);
|
|
41
|
+
return translate === undefined
|
|
42
|
+
? label.value
|
|
43
|
+
: renderCategoryLabel(label, translate);
|
|
44
|
+
};
|
|
45
|
+
return flattenCategoryNodes(index.roots)
|
|
46
|
+
.filter((node) => caption(node).toLocaleLowerCase().includes(needle) ||
|
|
47
|
+
node.category.slug.toLocaleLowerCase().includes(needle))
|
|
48
|
+
.filter((node) => !requireLeaf || node.children.length === 0)
|
|
49
|
+
.map(toOption);
|
|
50
|
+
}
|
|
51
|
+
const level = openId === null ? index.roots : (index.byId.get(openId)?.children ?? []);
|
|
52
|
+
return level.map(toOption);
|
|
53
|
+
}, [index, text, openId, requireLeaf, translate, toOption]);
|
|
54
|
+
const blocked = selected === null
|
|
55
|
+
? "nothing_selected"
|
|
56
|
+
: requireLeaf && selected.children.length > 0
|
|
57
|
+
? "not_a_leaf"
|
|
58
|
+
: null;
|
|
59
|
+
return children({
|
|
60
|
+
state: mapLoad(catalog, () => options),
|
|
61
|
+
path: index === null ? [] : categoryBreadcrumbs(index, openId),
|
|
62
|
+
selected,
|
|
63
|
+
submitBlockedReason: blocked,
|
|
64
|
+
query: text,
|
|
65
|
+
setQuery: setText,
|
|
66
|
+
open: (node) => {
|
|
67
|
+
if (node.children.length === 0) {
|
|
68
|
+
onChange?.(node.id, node);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
setOpenId(node.id);
|
|
72
|
+
},
|
|
73
|
+
up: () => {
|
|
74
|
+
if (index === null || openId === null)
|
|
75
|
+
return;
|
|
76
|
+
setOpenId(index.byId.get(openId)?.category.tn_parent ?? null);
|
|
77
|
+
},
|
|
78
|
+
select: (node) => {
|
|
79
|
+
onChange?.(node?.id ?? null, node);
|
|
80
|
+
},
|
|
81
|
+
refetch: () => {
|
|
82
|
+
void query.refetch();
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=CategoryPicker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryPicker.js","sourceRoot":"","sources":["../../src/headless/CategoryPicker.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { CategoryNode } from "../catalog/tree.js";
|
|
4
|
+
import type { CategoryCatalog, UseCategoryCatalogOptions } from "../model/queries.js";
|
|
5
|
+
/** The bag `<CategoryTree>` hands its render prop. */
|
|
6
|
+
export interface CategoryTreeBag {
|
|
7
|
+
/**
|
|
8
|
+
* The nodes to render at this level. `empty` means the catalogue really has
|
|
9
|
+
* no categories here — reachable only from a sync that succeeded, which is
|
|
10
|
+
* the whole reason it is a separate arm from `failed`.
|
|
11
|
+
*/
|
|
12
|
+
readonly state: LoadState<readonly CategoryNode[]>;
|
|
13
|
+
/** The full built catalogue, for a skin that wants more than one level. */
|
|
14
|
+
readonly catalog: LoadState<CategoryCatalog>;
|
|
15
|
+
/** The node `slug`/`categoryId` selected, once the catalogue has loaded. */
|
|
16
|
+
readonly current: CategoryNode | null;
|
|
17
|
+
/** Root → current, inclusive. `[]` when nothing is selected. */
|
|
18
|
+
readonly breadcrumbs: readonly CategoryNode[];
|
|
19
|
+
/**
|
|
20
|
+
* A slug that is not in the loaded catalogue. `true` only once the
|
|
21
|
+
* catalogue is `ready` — before that the honest answer is "we do not know
|
|
22
|
+
* yet", and a skin that renders "category not found" during a sync is
|
|
23
|
+
* showing a 404 for a page that exists.
|
|
24
|
+
*/
|
|
25
|
+
readonly unknownSlug: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The page budget stopped the sync walk: this tree is INCOMPLETE. Distinct
|
|
28
|
+
* from `empty` in the same way `failed` is — a branch missing here was not
|
|
29
|
+
* read, not absent.
|
|
30
|
+
*/
|
|
31
|
+
readonly truncated: boolean;
|
|
32
|
+
readonly isFetching: boolean;
|
|
33
|
+
refetch(): void;
|
|
34
|
+
}
|
|
35
|
+
export interface CategoryTreeProps extends UseCategoryCatalogOptions {
|
|
36
|
+
/** Render the children of this category instead of the roots. */
|
|
37
|
+
parentId?: number | null;
|
|
38
|
+
/** Select a category by slug — the `/c/:slug` route's job. Resolved against
|
|
39
|
+
* the synced tree because the server has no slug lookup. */
|
|
40
|
+
slug?: string;
|
|
41
|
+
/** Select a category by id. Ignored when `slug` is given. */
|
|
42
|
+
categoryId?: number | null;
|
|
43
|
+
children: (bag: CategoryTreeBag) => ReactNode;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Headless category navigation over the delta-synced catalogue.
|
|
47
|
+
*
|
|
48
|
+
* One hook mounted anywhere in the tree serves every level: the roots on the
|
|
49
|
+
* catalogue page, one category's children on `/c/:slug`, the breadcrumb above
|
|
50
|
+
* either. Nothing here issues a request per level — the tree is already in
|
|
51
|
+
* memory (see `useCategoryCatalog`), which is precisely the property the spec
|
|
52
|
+
* asked for in §4.3.
|
|
53
|
+
*/
|
|
54
|
+
export declare function CategoryTree(props: CategoryTreeProps): ReactNode;
|
|
55
|
+
//# sourceMappingURL=CategoryTree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryTree.d.ts","sourceRoot":"","sources":["../../src/headless/CategoryTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,KAAK,EACV,eAAe,EACf,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,sDAAsD;AACtD,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IACnD,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC;IAC7C,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAC;IACtC,gEAAgE;IAChE,QAAQ,CAAC,WAAW,EAAE,SAAS,YAAY,EAAE,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,yBAAyB;IAClE,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;gEAC4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,SAAS,CAAC;CAC/C;AAkBD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,SAAS,CAoChE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
2
|
+
import { categoryBreadcrumbs, resolveCategorySlug } from "../catalog/tree.js";
|
|
3
|
+
import { useCategoryCatalog } from "../model/queries.js";
|
|
4
|
+
function levelOf(index, parentId, current) {
|
|
5
|
+
if (parentId !== null && parentId !== undefined) {
|
|
6
|
+
return index.byId.get(parentId)?.children ?? [];
|
|
7
|
+
}
|
|
8
|
+
// With a category selected and no explicit parent, "the level" is that
|
|
9
|
+
// category's own sub-categories — which is what a category page shows. A
|
|
10
|
+
// LEAF category has none, and that empty is a true empty: the skin below it
|
|
11
|
+
// renders listings, not a menu.
|
|
12
|
+
if (current !== null)
|
|
13
|
+
return current.children;
|
|
14
|
+
return index.roots;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Headless category navigation over the delta-synced catalogue.
|
|
18
|
+
*
|
|
19
|
+
* One hook mounted anywhere in the tree serves every level: the roots on the
|
|
20
|
+
* catalogue page, one category's children on `/c/:slug`, the breadcrumb above
|
|
21
|
+
* either. Nothing here issues a request per level — the tree is already in
|
|
22
|
+
* memory (see `useCategoryCatalog`), which is precisely the property the spec
|
|
23
|
+
* asked for in §4.3.
|
|
24
|
+
*/
|
|
25
|
+
export function CategoryTree(props) {
|
|
26
|
+
const { parentId, slug, categoryId, children, ...catalogOptions } = props;
|
|
27
|
+
const query = useCategoryCatalog(catalogOptions);
|
|
28
|
+
const catalog = loadStateFromQuery(query);
|
|
29
|
+
const index = catalog.status === "ready" ? catalog.data.index : null;
|
|
30
|
+
let current = null;
|
|
31
|
+
if (index !== null) {
|
|
32
|
+
if (slug !== undefined)
|
|
33
|
+
current = resolveCategorySlug(index, slug) ?? null;
|
|
34
|
+
else if (categoryId !== null && categoryId !== undefined) {
|
|
35
|
+
current = index.byId.get(categoryId) ?? null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return children({
|
|
39
|
+
state: mapLoad(catalog, (data) => levelOf(data.index, parentId, current)),
|
|
40
|
+
catalog,
|
|
41
|
+
current,
|
|
42
|
+
breadcrumbs: index === null ? [] : categoryBreadcrumbs(index, current?.id),
|
|
43
|
+
unknownSlug: catalog.status === "ready" && slug !== undefined && current === null,
|
|
44
|
+
truncated: catalog.status === "ready" && catalog.data.truncated,
|
|
45
|
+
isFetching: query.isFetching,
|
|
46
|
+
refetch: () => {
|
|
47
|
+
void query.refetch();
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=CategoryTree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CategoryTree.js","sourceRoot":"","sources":["../../src/headless/CategoryTree.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAgDzD,SAAS,OAAO,CACd,KAAoB,EACpB,QAAmC,EACnC,OAA4B;IAE5B,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAC;IAClD,CAAC;IACD,uEAAuE;IACvE,yEAAyE;IACzE,4EAA4E;IAC5E,gCAAgC;IAChC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC;IAC9C,OAAO,KAAK,CAAC,KAAK,CAAC;AACrB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,KAAwB;IACnD,MAAM,EACJ,QAAQ,EACR,IAAI,EACJ,UAAU,EACV,QAAQ,EACR,GAAG,cAAc,EAClB,GAAG,KAAK,CAAC;IACV,MAAM,KAAK,GAAG,kBAAkB,CAAC,cAAc,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IAErE,IAAI,OAAO,GAAwB,IAAI,CAAC;IACxC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,GAAG,mBAAmB,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;aACtE,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YACzD,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;QACd,KAAK,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAC/B,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CACvC;QACD,OAAO;QACP,OAAO;QACP,WAAW,EACT,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC;QAC/D,WAAW,EACT,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,KAAK,SAAS,IAAI,OAAO,KAAK,IAAI;QACtE,SAAS,EAAE,OAAO,CAAC,MAAM,KAAK,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS;QAC/D,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Remediation } from "./generated/errors.gen.js";
|
|
2
|
+
export { CATEGORIES_ERRORS, CATEGORIES_ERROR_CODES, categoriesErrorBundleEn, } from "./generated/errors.gen.js";
|
|
3
|
+
export type { CategoriesErrorCode, CategoriesErrorSpec, Remediation, } from "./generated/errors.gen.js";
|
|
4
|
+
/**
|
|
5
|
+
* The optimistic-concurrency refusal of the feature editor, named because it
|
|
6
|
+
* is the one 409 in the registry and it means something specific: somebody
|
|
7
|
+
* else changed this category's schema while you were editing it, and
|
|
8
|
+
* `params.expected` / `params.actual` say which revisions.
|
|
9
|
+
*
|
|
10
|
+
* Not raised by anything on this pair's surface (the feature editor is a staff
|
|
11
|
+
* screen — see `api/categoriesApi.ts`), and exported anyway because the
|
|
12
|
+
* catalogue admin a host builds on top of this client WILL meet it, and a
|
|
13
|
+
* constant beats a string retyped at each call site.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CATEGORIES_FEATURE_EDITOR_CONFLICT = "error.409.categories_feature_editor_conflict";
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a backend error code to its remediation hint, or `undefined` for a
|
|
18
|
+
* code this module doesn't know (e.g. a cross-cutting `stapel.http.*`
|
|
19
|
+
* fallback). Zero guessing at runtime — a static lookup over the generated map.
|
|
20
|
+
*/
|
|
21
|
+
export declare function explainCategoriesError(code: string): Remediation | undefined;
|
|
22
|
+
//# sourceMappingURL=errorsMap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorsMap.d.ts","sourceRoot":"","sources":["../../src/i18n/errorsMap.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE7D,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,GACZ,MAAM,2BAA2B,CAAC;AAEnC;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kCAAkC,iDACC,CAAC;AAEjD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAI5E"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pair's error map (frontend-standard §4 checklist #7, frontend-core
|
|
3
|
+
* §2.5): the generated `code → { status, params, remediation, en }` catalog
|
|
4
|
+
* plus a tiny `explain()` lookup. Backs the manifest `errors` block and gives
|
|
5
|
+
* hosts a mechanical UX branch beside `t(code, params)`. The map itself is
|
|
6
|
+
* generated from the backend registry (`pnpm gen:errors`); this file only adds
|
|
7
|
+
* the lookup helper and re-exports the public surface.
|
|
8
|
+
*/
|
|
9
|
+
import { CATEGORIES_ERRORS } from "./generated/errors.gen.js";
|
|
10
|
+
export { CATEGORIES_ERRORS, CATEGORIES_ERROR_CODES, categoriesErrorBundleEn, } from "./generated/errors.gen.js";
|
|
11
|
+
/**
|
|
12
|
+
* The optimistic-concurrency refusal of the feature editor, named because it
|
|
13
|
+
* is the one 409 in the registry and it means something specific: somebody
|
|
14
|
+
* else changed this category's schema while you were editing it, and
|
|
15
|
+
* `params.expected` / `params.actual` say which revisions.
|
|
16
|
+
*
|
|
17
|
+
* Not raised by anything on this pair's surface (the feature editor is a staff
|
|
18
|
+
* screen — see `api/categoriesApi.ts`), and exported anyway because the
|
|
19
|
+
* catalogue admin a host builds on top of this client WILL meet it, and a
|
|
20
|
+
* constant beats a string retyped at each call site.
|
|
21
|
+
*/
|
|
22
|
+
export const CATEGORIES_FEATURE_EDITOR_CONFLICT = "error.409.categories_feature_editor_conflict";
|
|
23
|
+
/**
|
|
24
|
+
* Resolve a backend error code to its remediation hint, or `undefined` for a
|
|
25
|
+
* code this module doesn't know (e.g. a cross-cutting `stapel.http.*`
|
|
26
|
+
* fallback). Zero guessing at runtime — a static lookup over the generated map.
|
|
27
|
+
*/
|
|
28
|
+
export function explainCategoriesError(code) {
|
|
29
|
+
return CATEGORIES_ERRORS[code]?.remediation;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=errorsMap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorsMap.js","sourceRoot":"","sources":["../../src/i18n/errorsMap.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,2BAA2B,CAAC;AAOnC;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,kCAAkC,GAC7C,8CAA8C,CAAC;AAEjD;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAQ,iBAAkE,CACxE,IAAI,CACL,EAAE,WAAW,CAAC;AACjB,CAAC"}
|