@endora-commerce/mod-megamenu 0.100.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/LICENSE +21 -0
- package/README.md +59 -0
- package/dist/admin/api/megamenu-client.d.ts +28 -0
- package/dist/admin/api/megamenu-client.d.ts.map +1 -0
- package/dist/admin/api/megamenu-client.js +43 -0
- package/dist/admin/api/megamenu-client.js.map +1 -0
- package/dist/admin/components/BindingsPanel.d.ts +10 -0
- package/dist/admin/components/BindingsPanel.d.ts.map +1 -0
- package/dist/admin/components/BindingsPanel.js +120 -0
- package/dist/admin/components/BindingsPanel.js.map +1 -0
- package/dist/admin/components/MenuItemConfigPanel.d.ts +22 -0
- package/dist/admin/components/MenuItemConfigPanel.d.ts.map +1 -0
- package/dist/admin/components/MenuItemConfigPanel.js +77 -0
- package/dist/admin/components/MenuItemConfigPanel.js.map +1 -0
- package/dist/admin/components/MenuItemTree.d.ts +13 -0
- package/dist/admin/components/MenuItemTree.d.ts.map +1 -0
- package/dist/admin/components/MenuItemTree.js +46 -0
- package/dist/admin/components/MenuItemTree.js.map +1 -0
- package/dist/admin/index.d.ts +31 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +47 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/MegamenuEditor.d.ts +4 -0
- package/dist/admin/pages/MegamenuEditor.d.ts.map +1 -0
- package/dist/admin/pages/MegamenuEditor.js +151 -0
- package/dist/admin/pages/MegamenuEditor.js.map +1 -0
- package/dist/admin/pages/MegamenuListPage.d.ts +4 -0
- package/dist/admin/pages/MegamenuListPage.d.ts.map +1 -0
- package/dist/admin/pages/MegamenuListPage.js +51 -0
- package/dist/admin/pages/MegamenuListPage.js.map +1 -0
- package/dist/backend/entities/megamenu-binding.entity.d.ts +18 -0
- package/dist/backend/entities/megamenu-binding.entity.d.ts.map +1 -0
- package/dist/backend/entities/megamenu-binding.entity.js +61 -0
- package/dist/backend/entities/megamenu-binding.entity.js.map +1 -0
- package/dist/backend/entities/megamenu-item.entity.d.ts +27 -0
- package/dist/backend/entities/megamenu-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/megamenu-item.entity.js +83 -0
- package/dist/backend/entities/megamenu-item.entity.js.map +1 -0
- package/dist/backend/entities/megamenu.entity.d.ts +17 -0
- package/dist/backend/entities/megamenu.entity.d.ts.map +1 -0
- package/dist/backend/entities/megamenu.entity.js +57 -0
- package/dist/backend/entities/megamenu.entity.js.map +1 -0
- package/dist/backend/index.d.ts +116 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +147 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +10 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +50 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.storefront.d.ts +6 -0
- package/dist/backend/routes.storefront.d.ts.map +1 -0
- package/dist/backend/routes.storefront.js +26 -0
- package/dist/backend/routes.storefront.js.map +1 -0
- package/dist/backend/services/asset-references.d.ts +4 -0
- package/dist/backend/services/asset-references.d.ts.map +1 -0
- package/dist/backend/services/asset-references.js +36 -0
- package/dist/backend/services/asset-references.js.map +1 -0
- package/dist/backend/services/cms-references.d.ts +6 -0
- package/dist/backend/services/cms-references.d.ts.map +1 -0
- package/dist/backend/services/cms-references.js +39 -0
- package/dist/backend/services/cms-references.js.map +1 -0
- package/dist/backend/services/cross-module-ports.d.ts +112 -0
- package/dist/backend/services/cross-module-ports.d.ts.map +1 -0
- package/dist/backend/services/cross-module-ports.js +88 -0
- package/dist/backend/services/cross-module-ports.js.map +1 -0
- package/dist/backend/services/megamenu-cache.d.ts +36 -0
- package/dist/backend/services/megamenu-cache.d.ts.map +1 -0
- package/dist/backend/services/megamenu-cache.js +70 -0
- package/dist/backend/services/megamenu-cache.js.map +1 -0
- package/dist/backend/services/megamenu-item-service.d.ts +39 -0
- package/dist/backend/services/megamenu-item-service.d.ts.map +1 -0
- package/dist/backend/services/megamenu-item-service.js +151 -0
- package/dist/backend/services/megamenu-item-service.js.map +1 -0
- package/dist/backend/services/megamenu-language-reference.d.ts +12 -0
- package/dist/backend/services/megamenu-language-reference.d.ts.map +1 -0
- package/dist/backend/services/megamenu-language-reference.js +24 -0
- package/dist/backend/services/megamenu-language-reference.js.map +1 -0
- package/dist/backend/services/megamenu-reference-registry.d.ts +24 -0
- package/dist/backend/services/megamenu-reference-registry.d.ts.map +1 -0
- package/dist/backend/services/megamenu-reference-registry.js +69 -0
- package/dist/backend/services/megamenu-reference-registry.js.map +1 -0
- package/dist/backend/services/megamenu-service.d.ts +70 -0
- package/dist/backend/services/megamenu-service.d.ts.map +1 -0
- package/dist/backend/services/megamenu-service.js +299 -0
- package/dist/backend/services/megamenu-service.js.map +1 -0
- package/dist/backend/services/storefront-resolver.d.ts +46 -0
- package/dist/backend/services/storefront-resolver.d.ts.map +1 -0
- package/dist/backend/services/storefront-resolver.js +162 -0
- package/dist/backend/services/storefront-resolver.js.map +1 -0
- package/dist/backend/services/target-validator.d.ts +24 -0
- package/dist/backend/services/target-validator.d.ts.map +1 -0
- package/dist/backend/services/target-validator.js +105 -0
- package/dist/backend/services/target-validator.js.map +1 -0
- package/dist/manifest.d.ts +167 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +170 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260505T193836_megamenu_init.d.ts +23 -0
- package/dist/migrations/20260505T193836_megamenu_init.d.ts.map +1 -0
- package/dist/migrations/20260505T193836_megamenu_init.js +91 -0
- package/dist/migrations/20260505T193836_megamenu_init.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/megamenu/index.md +159 -0
- package/i18n/en.json +96 -0
- package/i18n/pl.json +96 -0
- package/package.json +99 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Megamenu
|
|
3
|
+
sidebar_position: 1
|
|
4
|
+
description: Configurable navigation tree with per-channel + per-language bindings
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Megamenu
|
|
8
|
+
|
|
9
|
+
The Megamenu module owns the storefront's primary navigation. It introduces a configurable tree of menu items authored in the admin panel, scoped to `(sales channel × language)` pairs, and rendered server-side on Next.js with a hover-revealed full-width drop-down panel on desktop and a stacked drill-down drawer on mobile.
|
|
10
|
+
|
|
11
|
+
## Entities and item kinds
|
|
12
|
+
|
|
13
|
+
| Entity | Identifier | Notes |
|
|
14
|
+
| ------------------- | ----------------------------------- | ------------------------------------------------------ |
|
|
15
|
+
| **Megamenu** | `id` (uuid) | Configuration row — name, description, version. |
|
|
16
|
+
| **MegamenuItem** | `id` (uuid) + `parentId` (self-FK) | Adjacency-list tree node with kind-specific `target`. |
|
|
17
|
+
| **MegamenuBinding** | `(megamenuId, salesChannelId, language)` | Per-scope `active` flag enforced by a partial unique index. |
|
|
18
|
+
|
|
19
|
+
The closed set of item kinds:
|
|
20
|
+
|
|
21
|
+
| `kind` | Target shape |
|
|
22
|
+
| ----------------- | --------------------------------------------------------------------- |
|
|
23
|
+
| `category-link` | `{ categoryId, iconAssetId?, iconPosition? }` |
|
|
24
|
+
| `cms-page-link` | `{ pageId, iconAssetId?, iconPosition? }` |
|
|
25
|
+
| `external-link` | `{ url, iconAssetId?, iconPosition? }` (URL: `http\|https\|tel\|mailto`) |
|
|
26
|
+
| `button` | `{ url, variant: 'primary'\|'secondary'\|'ghost' }` |
|
|
27
|
+
| `asset` | `{ assetId, kind: 'image'\|'video' }` |
|
|
28
|
+
| `cms-block-embed` | `{ blockId, embedSide: 'left'\|'right' }` |
|
|
29
|
+
|
|
30
|
+
The optional `iconAssetId` accepts only `image`-kind Library assets; the validator refuses non-image targets.
|
|
31
|
+
|
|
32
|
+
## Activation contract
|
|
33
|
+
|
|
34
|
+
At most one megamenu may be `active` per `(sales channel, language)` pair at any time. The DB enforces this with:
|
|
35
|
+
|
|
36
|
+
```sql
|
|
37
|
+
CREATE UNIQUE INDEX megamenu_bindings_active_uniq
|
|
38
|
+
ON megamenu_bindings (sales_channel_id, language)
|
|
39
|
+
WHERE active = true;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The activate transaction:
|
|
43
|
+
|
|
44
|
+
1. `UPDATE megamenu_bindings SET active = false WHERE sales_channel_id = ? AND language = ? AND active = true;` — deactivates the prior holder (if any).
|
|
45
|
+
2. `UPDATE megamenu_bindings SET active = true WHERE megamenu_id = ? AND sales_channel_id = ? AND language = ?;` — activates the requested binding.
|
|
46
|
+
|
|
47
|
+
Both run in a single transaction, so the partial unique index never sees a dual-active state. The activate endpoint surfaces the prior holder in `previouslyActive` so the admin's confirmation dialog can read "switched from `<name>`".
|
|
48
|
+
|
|
49
|
+
Activate refuses on an empty tree with `400 MEGAMENU_EMPTY_TREE`. Configuration delete refuses while at least one binding has `active = true` with `409 MEGAMENU_HAS_ACTIVE_BINDINGS` (admins must deactivate first).
|
|
50
|
+
|
|
51
|
+
## Sales-channel + language scoping
|
|
52
|
+
|
|
53
|
+
A configuration may carry many `(salesChannelId, language)` bindings. The binding endpoint refuses a language outside the channel's configured language set with `400 MEGAMENU_LANGUAGE_NOT_IN_CHANNEL_SCOPE`. Removing a binding falls back to "no megamenu" for that scope until another configuration activates.
|
|
54
|
+
|
|
55
|
+
Per-language label fallback: when an item's label is missing in the requested language, the storefront resolver falls back to the channel's default language. Items with no label in either is silently omitted from the resolved tree (admin sees the warning in the editor).
|
|
56
|
+
|
|
57
|
+
## Tree depth
|
|
58
|
+
|
|
59
|
+
Tree depth is a UX guideline, not a hard constraint. The data model imposes no cap. The admin form surfaces a non-blocking warning past 4 levels:
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
meta.warnings: [{ code: "MEGAMENU_DEPTH_EXCEEDED", message: "..." }]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Save still succeeds; every level renders on the storefront.
|
|
66
|
+
|
|
67
|
+
## Reference protection
|
|
68
|
+
|
|
69
|
+
Megamenu items hold soft references to upstream entities (Categories, CMS Pages, CMS Blocks, Library Assets). Deletion of any of those is refused while a megamenu still references them:
|
|
70
|
+
|
|
71
|
+
| Upstream entity | Registry / module | Match path on megamenu_items.target |
|
|
72
|
+
| --------------- | --------------------------------------- | ------------------------------------ |
|
|
73
|
+
| Library Asset | `AssetReferenceRegistry` | `assetId` OR `iconAssetId` |
|
|
74
|
+
| CMS Page | `CmsReferenceRegistry` | `kind='cms-page-link' AND pageId = ?` |
|
|
75
|
+
| CMS Block | `CmsReferenceRegistry` | `kind='cms-block-embed' AND blockId = ?` |
|
|
76
|
+
| Category | (catalog category-reference registry) | `kind='category-link' AND categoryId = ?` (planned) |
|
|
77
|
+
|
|
78
|
+
Each registration is a one-line surface change wired in `composition.ts`. The CMS module's existing `findBlockReferences` / `findTemplateReferences` were extended to consult external scanners; the megamenu module's `registerMegamenuCmsReferences` registers its scanner there.
|
|
79
|
+
|
|
80
|
+
The category-reference registry surface is planned for the catalog module; until it lands, category-delete protection from megamenu references is enforced at the validator boundary (a megamenu cannot save an item pointing at a deleted category — the category check returns `false` and the admin sees a clear error).
|
|
81
|
+
|
|
82
|
+
## Storefront resolution + Redis cache
|
|
83
|
+
|
|
84
|
+
Single read endpoint:
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
GET /api/v1/megamenu/by-channel?language=<bcp-47>
|
|
88
|
+
X-Sales-Channel: <channel-code>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The resolver:
|
|
92
|
+
|
|
93
|
+
1. Looks up the active binding via the partial unique index (1 query).
|
|
94
|
+
2. Reads every item under that megamenu in `(parent_id, position)` order (1 query).
|
|
95
|
+
3. Walks the tree, dispatching by `kind`: resolves Category / CMS-page URLs from their slugs, signs Asset URLs through the Assets Library, inlines CMS Blocks via the CMS module's storefront resolver, populates icons.
|
|
96
|
+
4. Returns the recursive `ResolvedMegamenu` payload.
|
|
97
|
+
|
|
98
|
+
Resolved payloads are cached in Redis under `megamenu:v1:<channel>:<language>` with a 5-minute TTL. Invalidation:
|
|
99
|
+
|
|
100
|
+
- `POST /menus`, `PATCH /menus/:id`, `PUT /menus/:id/items`, `DELETE /menus/:id` → coarse drop of `megamenu:v1:*` (one menu may serve N bindings).
|
|
101
|
+
- `POST /menus/:id/bindings` / `DELETE /menus/:id/bindings/:channel/:language` → drop the affected scope.
|
|
102
|
+
- `POST /menus/:id/activate` / `POST /menus/:id/deactivate` → drop the activated/deactivated scope.
|
|
103
|
+
|
|
104
|
+
The cache implementation mirrors the CMS module's `CmsCache` exactly (same prefix scheme, same TTL, same SCAN-based invalidation).
|
|
105
|
+
|
|
106
|
+
## Storefront rendering
|
|
107
|
+
|
|
108
|
+
### Desktop
|
|
109
|
+
|
|
110
|
+
Reproduces the Industria design's `.mega` panel: a full-width drop-down beneath the nav bar, opened by hover (`onMouseEnter`) and closed by `onMouseLeave`. The panel uses a 3-column grid `220px 1fr 280px` with `gap: 32px` and `padding: 28px 0`. The third column is filled by the first `cms-block-embed` child whose `embedSide === 'right'` (a `left`-side embed appears in column 1 instead, shifting the link grid right).
|
|
111
|
+
|
|
112
|
+
### Mobile
|
|
113
|
+
|
|
114
|
+
Burger trigger renders below `768px`. Tapping opens a stacked drill-down drawer; tapping a parent slides to the next level via component state with a "Back" affordance. Embedded CMS Blocks, Buttons, and Assets render stacked inline (no off-canvas side-by-side layout). Browser-native scroll restoration covers the "preserves scroll on parent levels" requirement.
|
|
115
|
+
|
|
116
|
+
## HTTP surface
|
|
117
|
+
|
|
118
|
+
### Admin (`/api/v1/admin/megamenu`)
|
|
119
|
+
|
|
120
|
+
| Method | Path | Purpose |
|
|
121
|
+
| ------- | ----------------------------------------------------------- | ---------------------------------------------------- |
|
|
122
|
+
| GET | `/menus` | List configurations with binding counts. |
|
|
123
|
+
| POST | `/menus` | Create a Megamenu (no items, no bindings). |
|
|
124
|
+
| GET | `/menus/:id` | Detail with tree + bindings. |
|
|
125
|
+
| PATCH | `/menus/:id` | Edit metadata. Honours `If-Match` via `version`. |
|
|
126
|
+
| DELETE | `/menus/:id` | Refused while any binding has `active = true`. |
|
|
127
|
+
| PUT | `/menus/:id/items` | Save the entire item tree (full overwrite). |
|
|
128
|
+
| GET | `/menus/:id/bindings` | List every binding for a configuration. |
|
|
129
|
+
| POST | `/menus/:id/bindings` | Add a `(channel, language)` binding (always staged). |
|
|
130
|
+
| DELETE | `/menus/:id/bindings/:salesChannelId/:language` | Remove a binding (idempotent). |
|
|
131
|
+
| POST | `/menus/:id/activate` | Atomic swap activation. |
|
|
132
|
+
| POST | `/menus/:id/deactivate` | Deactivate a binding. |
|
|
133
|
+
|
|
134
|
+
### Storefront (`/api/v1/megamenu`)
|
|
135
|
+
|
|
136
|
+
| Method | Path | Returns |
|
|
137
|
+
| ------ | --------------------------------- | ----------------------------------------------------------------------- |
|
|
138
|
+
| GET | `/by-channel?language=…` | Resolved megamenu for the requested scope; `404 MEGAMENU_NOT_FOUND` when no active binding. |
|
|
139
|
+
|
|
140
|
+
## Migration from no-megamenu
|
|
141
|
+
|
|
142
|
+
The migration `036_megamenu_init.ts` adds three new tables and the partial unique index. There are no seeded rows; first-time admins create a configuration through the admin UI.
|
|
143
|
+
|
|
144
|
+
Mounted in the storefront's root layout between `<Header>` and the existing `header.bottom` Hook. The CMS Hooks integration is unaffected.
|
|
145
|
+
|
|
146
|
+
## Error codes
|
|
147
|
+
|
|
148
|
+
`MEGAMENU_NOT_FOUND`, `MEGAMENU_HAS_ACTIVE_BINDINGS`, `MEGAMENU_EMPTY_TREE`, `MEGAMENU_BINDING_NOT_FOUND`, `MEGAMENU_BINDING_ALREADY_EXISTS`, `MEGAMENU_LANGUAGE_NOT_IN_CHANNEL_SCOPE`, `MEGAMENU_TARGET_OUT_OF_SCOPE`, `MEGAMENU_ASSET_KIND_MISMATCH`, `MEGAMENU_REFERENCED`, `MEGAMENU_DEPTH_EXCEEDED`.
|
|
149
|
+
|
|
150
|
+
All envelopes follow the platform-wide error contract in `packages/contracts/src/errors.ts`.
|
|
151
|
+
|
|
152
|
+
## Out of scope (v1)
|
|
153
|
+
|
|
154
|
+
- Per-customer-group / per-organization megamenu variants.
|
|
155
|
+
- A/B testing of menu variants.
|
|
156
|
+
- An admin "preview without publishing" surface.
|
|
157
|
+
- Item URL targets that point directly at product PDPs (use a Category link or an External link).
|
|
158
|
+
- Free-URL asset items: a server-side `importFromUrl` on the Assets Library is the natural home for fetching, validating, and storing externally-sourced bytes; the Megamenu module's validator refuses free-URL asset items until that surface lands. Admins upload the asset via the Library first and reference it by id.
|
|
159
|
+
- Drag-and-drop tree authoring in the admin: the v1 editor uses up/down/delete/add-child arrow controls. `@dnd-kit` is not bundled in the admin app and the brief's emphasis is on tree editing, not gesture sophistication.
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"assetKind.image": "Image",
|
|
3
|
+
"assetKind.video": "Video",
|
|
4
|
+
"bindings.activate": "Activate",
|
|
5
|
+
"bindings.activatedWithPrevious": "Activated. Previously active megamenu in this scope: \"{name}\" (deactivated automatically).",
|
|
6
|
+
"bindings.add": "Add binding",
|
|
7
|
+
"bindings.deactivate": "Deactivate",
|
|
8
|
+
"bindings.empty": "No bindings yet - add one to publish this megamenu to a storefront scope.",
|
|
9
|
+
"bindings.languagePlaceholder": "Language...",
|
|
10
|
+
"bindings.salesChannelPlaceholder": "Sales channel...",
|
|
11
|
+
"columns.actions": "Actions",
|
|
12
|
+
"columns.active": "Active",
|
|
13
|
+
"columns.bindings": "Bindings",
|
|
14
|
+
"columns.channel": "Channel",
|
|
15
|
+
"columns.language": "Language",
|
|
16
|
+
"columns.name": "Name",
|
|
17
|
+
"columns.version": "Version",
|
|
18
|
+
"common.back": "Back",
|
|
19
|
+
"common.create": "Create",
|
|
20
|
+
"common.loading": "Loading...",
|
|
21
|
+
"common.moveDown": "Move down",
|
|
22
|
+
"common.moveUp": "Move up",
|
|
23
|
+
"common.saveMetadata": "Save metadata",
|
|
24
|
+
"common.saveTree": "Save tree",
|
|
25
|
+
"editor.description": "Author the navigation tree, scope it to (channel, language), activate it.",
|
|
26
|
+
"editor.fallbackTitle": "Megamenu",
|
|
27
|
+
"editor.newItemLabel": "New item",
|
|
28
|
+
"fields.assetId": "Asset id",
|
|
29
|
+
"fields.categoryId": "Category id",
|
|
30
|
+
"fields.cmsBlockId": "CMS block id",
|
|
31
|
+
"fields.cmsPageId": "CMS page id",
|
|
32
|
+
"fields.description": "Description",
|
|
33
|
+
"fields.embedSide": "Embed side",
|
|
34
|
+
"fields.kind": "Kind",
|
|
35
|
+
"fields.labelsPerLanguage": "Labels (per language)",
|
|
36
|
+
"fields.linkTarget": "Link target",
|
|
37
|
+
"fields.name": "Name",
|
|
38
|
+
"fields.url": "URL",
|
|
39
|
+
"fields.variant": "Variant",
|
|
40
|
+
"itemConfig.addBindingFirst": "Add a binding first - the tree's labels are scoped to the languages of the megamenu's bindings.",
|
|
41
|
+
"itemConfig.empty": "Select a menu item from the tree (or add one) to edit its fields.",
|
|
42
|
+
"itemConfig.labelPlaceholder": "Label in {language}",
|
|
43
|
+
"itemKind.asset": "Asset",
|
|
44
|
+
"itemKind.button": "Button",
|
|
45
|
+
"itemKind.categoryLink": "Category link",
|
|
46
|
+
"itemKind.cmsBlockEmbed": "CMS block embed",
|
|
47
|
+
"itemKind.cmsPageLink": "CMS page link",
|
|
48
|
+
"itemKind.externalLink": "External link",
|
|
49
|
+
"list.activeScopes": "{count} scopes",
|
|
50
|
+
"list.cardTitle": "Configurations",
|
|
51
|
+
"list.description": "Storefront navigation configurations. One active megamenu per (sales channel x language) at a time.",
|
|
52
|
+
"list.empty": "No megamenus yet.",
|
|
53
|
+
"list.newNamePlaceholder": "New megamenu name",
|
|
54
|
+
"list.title": "Megamenu",
|
|
55
|
+
"placeholders.assetUuid": "Library asset UUID",
|
|
56
|
+
"placeholders.categoryUuid": "Category UUID",
|
|
57
|
+
"placeholders.cmsBlockUuid": "CMS block UUID",
|
|
58
|
+
"placeholders.cmsPageUuid": "CMS page UUID",
|
|
59
|
+
"placeholders.externalUrl": "https://example.com / tel:+48... / mailto:...",
|
|
60
|
+
"sections.bindings": "Bindings",
|
|
61
|
+
"sections.item": "Item",
|
|
62
|
+
"sections.items": "Items",
|
|
63
|
+
"sections.metadata": "Metadata",
|
|
64
|
+
"side.left": "Left",
|
|
65
|
+
"side.right": "Right",
|
|
66
|
+
"state.active": "active",
|
|
67
|
+
"state.staged": "staged",
|
|
68
|
+
"tree.addChild": "Add child item",
|
|
69
|
+
"tree.addTopLevel": "Add top-level item",
|
|
70
|
+
"tree.deleteItem": "Delete item",
|
|
71
|
+
"tree.empty": "No items yet - add one above.",
|
|
72
|
+
"tree.noLabel": "(no label)",
|
|
73
|
+
"tree.title": "Tree",
|
|
74
|
+
"treeKind.asset": "Asset",
|
|
75
|
+
"treeKind.button": "Button",
|
|
76
|
+
"treeKind.category-link": "Category",
|
|
77
|
+
"treeKind.cms-block-embed": "Block embed",
|
|
78
|
+
"treeKind.cms-page-link": "CMS Page",
|
|
79
|
+
"treeKind.external-link": "External link",
|
|
80
|
+
"variant.ghost": "Ghost",
|
|
81
|
+
"variant.primary": "Primary",
|
|
82
|
+
"variant.secondary": "Secondary",
|
|
83
|
+
"actions.editMegamenu.label": "Edit megamenu",
|
|
84
|
+
"actions.editMegamenu.description": "Configure the storefront navigation menu",
|
|
85
|
+
"errors.MEGAMENU_NOT_FOUND": "Megamenu Not Found.",
|
|
86
|
+
"errors.MEGAMENU_HAS_ACTIVE_BINDINGS": "Megamenu Has Active Bindings.",
|
|
87
|
+
"errors.MEGAMENU_EMPTY_TREE": "Megamenu Empty Tree.",
|
|
88
|
+
"errors.MEGAMENU_BINDING_NOT_FOUND": "Megamenu Binding Not Found.",
|
|
89
|
+
"errors.MEGAMENU_BINDING_ALREADY_EXISTS": "Megamenu Binding Already Exists.",
|
|
90
|
+
"errors.MEGAMENU_LANGUAGE_NOT_IN_CHANNEL_SCOPE": "Megamenu Language Not In Channel Scope.",
|
|
91
|
+
"errors.MEGAMENU_TARGET_OUT_OF_SCOPE": "Megamenu Target Out Of Scope.",
|
|
92
|
+
"errors.MEGAMENU_ASSET_KIND_MISMATCH": "Megamenu Asset Kind Mismatch.",
|
|
93
|
+
"errors.MEGAMENU_REFERENCED": "Megamenu Referenced.",
|
|
94
|
+
"errors.MEGAMENU_DEPTH_EXCEEDED": "Megamenu Depth Exceeded.",
|
|
95
|
+
"nav.megamenu.label": "Megamenu"
|
|
96
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"assetKind.image": "Obraz",
|
|
3
|
+
"assetKind.video": "Wideo",
|
|
4
|
+
"bindings.activate": "Aktywuj",
|
|
5
|
+
"bindings.activatedWithPrevious": "Aktywowano. Poprzednio aktywne megamenu w tym zakresie: \"{name}\" (automatycznie dezaktywowane).",
|
|
6
|
+
"bindings.add": "Dodaj powiązanie",
|
|
7
|
+
"bindings.deactivate": "Dezaktywuj",
|
|
8
|
+
"bindings.empty": "Brak powiązań - dodaj jedno, aby opublikować megamenu w zakresie sklepu.",
|
|
9
|
+
"bindings.languagePlaceholder": "Język...",
|
|
10
|
+
"bindings.salesChannelPlaceholder": "Kanał sprzedaży...",
|
|
11
|
+
"columns.actions": "Akcje",
|
|
12
|
+
"columns.active": "Aktywne",
|
|
13
|
+
"columns.bindings": "Powiązania",
|
|
14
|
+
"columns.channel": "Kanał",
|
|
15
|
+
"columns.language": "Język",
|
|
16
|
+
"columns.name": "Nazwa",
|
|
17
|
+
"columns.version": "Wersja",
|
|
18
|
+
"common.back": "Wróć",
|
|
19
|
+
"common.create": "Utwórz",
|
|
20
|
+
"common.loading": "Ładowanie...",
|
|
21
|
+
"common.moveDown": "Przesuń w dół",
|
|
22
|
+
"common.moveUp": "Przesuń w górę",
|
|
23
|
+
"common.saveMetadata": "Zapisz metadane",
|
|
24
|
+
"common.saveTree": "Zapisz drzewo",
|
|
25
|
+
"editor.description": "Twórz drzewo nawigacji, przypisz je do zakresu (kanał, język) i aktywuj.",
|
|
26
|
+
"editor.fallbackTitle": "Megamenu",
|
|
27
|
+
"editor.newItemLabel": "Nowy element",
|
|
28
|
+
"fields.assetId": "ID zasobu",
|
|
29
|
+
"fields.categoryId": "ID kategorii",
|
|
30
|
+
"fields.cmsBlockId": "ID bloku CMS",
|
|
31
|
+
"fields.cmsPageId": "ID strony CMS",
|
|
32
|
+
"fields.description": "Opis",
|
|
33
|
+
"fields.embedSide": "Strona osadzenia",
|
|
34
|
+
"fields.kind": "Typ",
|
|
35
|
+
"fields.labelsPerLanguage": "Etykiety (na język)",
|
|
36
|
+
"fields.linkTarget": "Cel linku",
|
|
37
|
+
"fields.name": "Nazwa",
|
|
38
|
+
"fields.url": "URL",
|
|
39
|
+
"fields.variant": "Wariant",
|
|
40
|
+
"itemConfig.addBindingFirst": "Najpierw dodaj powiązanie - etykiety drzewa są ograniczone do języków powiązań megamenu.",
|
|
41
|
+
"itemConfig.empty": "Wybierz element menu z drzewa (albo dodaj nowy), aby edytować jego pola.",
|
|
42
|
+
"itemConfig.labelPlaceholder": "Etykieta w {language}",
|
|
43
|
+
"itemKind.asset": "Zasób",
|
|
44
|
+
"itemKind.button": "Przycisk",
|
|
45
|
+
"itemKind.categoryLink": "Link do kategorii",
|
|
46
|
+
"itemKind.cmsBlockEmbed": "Osadzony blok CMS",
|
|
47
|
+
"itemKind.cmsPageLink": "Link do strony CMS",
|
|
48
|
+
"itemKind.externalLink": "Link zewnętrzny",
|
|
49
|
+
"list.activeScopes": "{count} zakresów",
|
|
50
|
+
"list.cardTitle": "Konfiguracje",
|
|
51
|
+
"list.description": "Konfiguracje nawigacji sklepu. Jedno aktywne megamenu na raz dla pary (kanał sprzedaży x język).",
|
|
52
|
+
"list.empty": "Brak megamenu.",
|
|
53
|
+
"list.newNamePlaceholder": "Nazwa nowego megamenu",
|
|
54
|
+
"list.title": "Megamenu",
|
|
55
|
+
"placeholders.assetUuid": "UUID zasobu z biblioteki",
|
|
56
|
+
"placeholders.categoryUuid": "UUID kategorii",
|
|
57
|
+
"placeholders.cmsBlockUuid": "UUID bloku CMS",
|
|
58
|
+
"placeholders.cmsPageUuid": "UUID strony CMS",
|
|
59
|
+
"placeholders.externalUrl": "https://example.com / tel:+48... / mailto:...",
|
|
60
|
+
"sections.bindings": "Powiązania",
|
|
61
|
+
"sections.item": "Element",
|
|
62
|
+
"sections.items": "Elementy",
|
|
63
|
+
"sections.metadata": "Metadane",
|
|
64
|
+
"side.left": "Lewa",
|
|
65
|
+
"side.right": "Prawa",
|
|
66
|
+
"state.active": "aktywne",
|
|
67
|
+
"state.staged": "robocze",
|
|
68
|
+
"tree.addChild": "Dodaj element podrzędny",
|
|
69
|
+
"tree.addTopLevel": "Dodaj element główny",
|
|
70
|
+
"tree.deleteItem": "Usuń element",
|
|
71
|
+
"tree.empty": "Brak elementów - dodaj jeden powyżej.",
|
|
72
|
+
"tree.noLabel": "(brak etykiety)",
|
|
73
|
+
"tree.title": "Drzewo",
|
|
74
|
+
"treeKind.asset": "Zasób",
|
|
75
|
+
"treeKind.button": "Przycisk",
|
|
76
|
+
"treeKind.category-link": "Kategoria",
|
|
77
|
+
"treeKind.cms-block-embed": "Osadzony blok",
|
|
78
|
+
"treeKind.cms-page-link": "Strona CMS",
|
|
79
|
+
"treeKind.external-link": "Link zewnętrzny",
|
|
80
|
+
"variant.ghost": "Ghost",
|
|
81
|
+
"variant.primary": "Primary",
|
|
82
|
+
"variant.secondary": "Secondary",
|
|
83
|
+
"actions.editMegamenu.label": "Edytuj megamenu",
|
|
84
|
+
"actions.editMegamenu.description": "Skonfiguruj menu nawigacji witryny",
|
|
85
|
+
"errors.MEGAMENU_NOT_FOUND": "Błąd: megamenu not found.",
|
|
86
|
+
"errors.MEGAMENU_HAS_ACTIVE_BINDINGS": "Błąd: megamenu has active bindings.",
|
|
87
|
+
"errors.MEGAMENU_EMPTY_TREE": "Błąd: megamenu empty tree.",
|
|
88
|
+
"errors.MEGAMENU_BINDING_NOT_FOUND": "Błąd: megamenu binding not found.",
|
|
89
|
+
"errors.MEGAMENU_BINDING_ALREADY_EXISTS": "Błąd: megamenu binding already exists.",
|
|
90
|
+
"errors.MEGAMENU_LANGUAGE_NOT_IN_CHANNEL_SCOPE": "Błąd: megamenu language not in channel scope.",
|
|
91
|
+
"errors.MEGAMENU_TARGET_OUT_OF_SCOPE": "Błąd: megamenu target out of scope.",
|
|
92
|
+
"errors.MEGAMENU_ASSET_KIND_MISMATCH": "Błąd: megamenu asset kind mismatch.",
|
|
93
|
+
"errors.MEGAMENU_REFERENCED": "Błąd: megamenu referenced.",
|
|
94
|
+
"errors.MEGAMENU_DEPTH_EXCEEDED": "Błąd: megamenu depth exceeded.",
|
|
95
|
+
"nav.megamenu.label": "Megamenu"
|
|
96
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endora-commerce/mod-megamenu",
|
|
3
|
+
"version": "0.100.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Multi-column dropdown navigation builder.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"endora": {
|
|
9
|
+
"type": "module",
|
|
10
|
+
"id": "megamenu"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/endora-commerce/endora-commerce.git",
|
|
15
|
+
"directory": "packages/modules/megamenu"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/manifest.d.ts",
|
|
23
|
+
"default": "./dist/manifest.js"
|
|
24
|
+
},
|
|
25
|
+
"./backend": {
|
|
26
|
+
"types": "./dist/backend/index.d.ts",
|
|
27
|
+
"default": "./dist/backend/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./migrations": {
|
|
30
|
+
"types": "./dist/migrations/index.d.ts",
|
|
31
|
+
"default": "./dist/migrations/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./admin": {
|
|
34
|
+
"types": "./dist/admin/index.d.ts",
|
|
35
|
+
"default": "./dist/admin/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./tailwind.css": "./tailwind.css",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"i18n",
|
|
43
|
+
"docs",
|
|
44
|
+
"tailwind.css"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.18.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@mikro-orm/core": "^6",
|
|
51
|
+
"@mikro-orm/migrations": "^6",
|
|
52
|
+
"@mikro-orm/postgresql": "^6",
|
|
53
|
+
"fastify": "^5",
|
|
54
|
+
"ioredis": "^5",
|
|
55
|
+
"lucide-react": "^1",
|
|
56
|
+
"react": "^19",
|
|
57
|
+
"react-router-dom": "^7",
|
|
58
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
59
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
60
|
+
"@endora-commerce/platform": "0.100.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@endora-commerce/admin-kit": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"lucide-react": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"react": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"react-router-dom": {
|
|
73
|
+
"optional": true
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"devDependencies": {
|
|
77
|
+
"@mikro-orm/core": "^6.6.13",
|
|
78
|
+
"@mikro-orm/migrations": "^6.6.13",
|
|
79
|
+
"@mikro-orm/postgresql": "^6.6.13",
|
|
80
|
+
"@types/node": "^22.9.0",
|
|
81
|
+
"@types/react": "^19.2.14",
|
|
82
|
+
"fastify": "^5.12.5",
|
|
83
|
+
"ioredis": "^5.10.1",
|
|
84
|
+
"lucide-react": "^1.11.0",
|
|
85
|
+
"react": "^19.2.5",
|
|
86
|
+
"react-router-dom": "^7.18.2",
|
|
87
|
+
"typescript": "^5.9.3",
|
|
88
|
+
"vitest": "^4.1.11",
|
|
89
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
90
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
91
|
+
"@endora-commerce/platform": "0.100.0"
|
|
92
|
+
},
|
|
93
|
+
"scripts": {
|
|
94
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
|
|
95
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
|
|
96
|
+
"lint": "eslint src",
|
|
97
|
+
"test": "vitest run"
|
|
98
|
+
}
|
|
99
|
+
}
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @endora-commerce/mod-megamenu — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
|
|
2
|
+
*
|
|
3
|
+
* The `@source` directives this package asks its host to scan
|
|
4
|
+
* (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
|
|
5
|
+
* They resolve relative to **this file**, so they hold wherever the package is
|
|
6
|
+
* installed — a workspace link here, `node_modules` in a client's instance.
|
|
7
|
+
*
|
|
8
|
+
* The `dist` line is what a published tarball ships and is what an instance
|
|
9
|
+
* scans; the `src` line is inert there and is what keeps `pnpm --filter admin
|
|
10
|
+
* run dev` reading source in this repository. Do not edit: run
|
|
11
|
+
* `pnpm --filter backend run manifests:generate`.
|
|
12
|
+
*/
|
|
13
|
+
@source "./dist/admin";
|
|
14
|
+
@source "./src/admin";
|