@xeplr/ui-account 1.0.5 → 1.0.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeplr/ui-account",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Account UI: auth, profile, RBAC admin, tenant management — React controller hooks and designs",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -33,5 +33,8 @@
33
33
  "peerDependencies": {
34
34
  "react": "^18.0.0 || ^19.0.0",
35
35
  "react-router-dom": "^6.0.0 || ^7.0.0"
36
+ },
37
+ "scripts": {
38
+ "test": "node --test test/*.test.js"
36
39
  }
37
40
  }
package/src/adminApi.js CHANGED
@@ -37,3 +37,24 @@ export function toggleModuleRole({ module, action, roleId, assign }) {
37
37
  body: JSON.stringify({ module, action, roleId, assign }),
38
38
  });
39
39
  }
40
+
41
+ // ── Menu items: key, label, order, visibility (Super Admin) ──────────────
42
+
43
+ /** Every menu item, hidden ones included: [{ name, label, shown, sortOrder, isHidden, isPublic }]. */
44
+ export function listMenuItems() {
45
+ return authFetch('/auth/api/admin/menu-items');
46
+ }
47
+
48
+ /** Rename / reorder / hide by key: [{ name, label?, sortOrder?, isHidden? }]. */
49
+ export function saveMenuItems(items) {
50
+ return authFetch('/auth/api/admin/menu-items', { method: 'POST', body: JSON.stringify({ items }) });
51
+ }
52
+
53
+ /** A new item — e.g. a form added to the menu: { name: key, label }. */
54
+ export function addMenuItem(item) {
55
+ return authFetch('/auth/api/admin/menu-items/add', { method: 'POST', body: JSON.stringify(item) });
56
+ }
57
+
58
+ export function removeMenuItem(name) {
59
+ return authFetch('/auth/api/admin/menu-items/remove', { method: 'POST', body: JSON.stringify({ name }) });
60
+ }
@@ -52,8 +52,8 @@ function AccountMenu({
52
52
 
53
53
  {(accountItems || []).map(function(item) {
54
54
  return (
55
- <Link key={item.name} to={item.path} className="xeplr-nav-account-item" role="menuitem" onClick={closeAccount}>
56
- {item.name}
55
+ <Link key={item.key || item.name} to={item.path} className="xeplr-nav-account-item" role="menuitem" onClick={closeAccount}>
56
+ {item.label || item.name}
57
57
  </Link>
58
58
  );
59
59
  })}
@@ -55,7 +55,7 @@ function NavDrawer({
55
55
  var visibleItems = useMemo(function() {
56
56
  if (!drawerOpen || !query.trim()) return drawerItems;
57
57
  var q = query.trim().toLowerCase();
58
- return drawerItems.filter(function(item) { return item.name.toLowerCase().indexOf(q) !== -1; });
58
+ return drawerItems.filter(function(item) { return String(item.label || item.name).toLowerCase().indexOf(q) !== -1; });
59
59
  }, [drawerItems, query, drawerOpen]);
60
60
 
61
61
  var buckets = useMemo(function() { return bucketItems(visibleItems); }, [visibleItems]);
@@ -74,13 +74,13 @@ function NavDrawer({
74
74
  var badge = item.badge === 0 || item.badge == null || item.badge === '' ? null : item.badge;
75
75
  return (
76
76
  <button
77
- key={item.name}
77
+ key={item.key || item.name}
78
78
  type="button"
79
79
  className={'xeplr-nav-drawer-link' + (badge ? ' xeplr-nav-drawer-link-badged' : '')}
80
80
  onClick={item.clickHandler}
81
81
  // The count belongs in the tooltip too — the collapsed dot says
82
82
  // "something", and the hover has to say how many.
83
- title={badge ? item.name + ' (' + badge + ')' : item.name}
83
+ title={badge ? (item.label || item.name) + ' (' + badge + ')' : (item.label || item.name)}
84
84
  >
85
85
  {item.icon && (
86
86
  <span className="xeplr-nav-drawer-icon">
@@ -88,7 +88,7 @@ function NavDrawer({
88
88
  {badge && !drawerOpen && <span className="xeplr-nav-drawer-dot" aria-hidden="true" />}
89
89
  </span>
90
90
  )}
91
- {drawerOpen && <span className="xeplr-nav-drawer-label">{item.name}</span>}
91
+ {drawerOpen && <span className="xeplr-nav-drawer-label">{item.label || item.name}</span>}
92
92
  {drawerOpen && badge && <span className="xeplr-nav-drawer-badge">{badge}</span>}
93
93
  </button>
94
94
  );
package/src/index.js CHANGED
@@ -41,7 +41,8 @@ export { ProtectedRoute } from './ProtectedRoute.jsx';
41
41
  export { AccessGuard } from './AccessGuard.jsx';
42
42
 
43
43
  // Admin API (model layer for RBAC management)
44
- export { getUsers, getRoles, getAccessItems, toggleUserRole, toggleAccessRole, toggleModuleRole } from './adminApi.js';
44
+ export { getUsers, getRoles, getAccessItems, toggleUserRole, toggleAccessRole, toggleModuleRole, listMenuItems, saveMenuItems, addMenuItem, removeMenuItem } from './adminApi.js';
45
+ export { labelMenuItems } from './menuLabels.js';
45
46
  export { getMasterItems, saveMasterItem, deleteMasterItem, MASTER_TYPES } from './masterApi.js';
46
47
 
47
48
  // Design validation (use when building custom designs)
@@ -0,0 +1,40 @@
1
+ // MENU KEYS AND LABELS — no React.
2
+ //
3
+ // An app lists its menu items by KEY (drawerItems: [{ key: 'Tasks', icon, … }]).
4
+ // The server says which keys this person may see, what each is CALLED, and in
5
+ // what order (access.menuItems — renamed from the app without touching code).
6
+ //
7
+ // `name` is still accepted as the key, so drawer catalogs written before keys
8
+ // existed keep working; with no label from the server, the item's own `label`
9
+ // or its key is shown.
10
+
11
+ /**
12
+ * @param catalog the app's items: [{ key | name, label?, icon?, … }]
13
+ * @param access { menus: [keys], menuItems?: [{ name, label, sortOrder }] }
14
+ * @returns the items this person may see, each with `key` and `label`, in the server's order
15
+ */
16
+ export function labelMenuItems(catalog, access) {
17
+ var allowed = (access && access.menus) || [];
18
+ var fromServer = {};
19
+ var position = {};
20
+ ((access && access.menuItems) || []).forEach(function(m, i) {
21
+ if (!m || !m.name) return;
22
+ fromServer[m.name] = m.label;
23
+ position[m.name] = i;
24
+ });
25
+
26
+ return (catalog || [])
27
+ .map(function(item, i) {
28
+ var key = item.key || item.name;
29
+ return { item: item, key: key, index: i };
30
+ })
31
+ .filter(function(x) { return x.key && allowed.indexOf(x.key) !== -1; })
32
+ .sort(function(a, b) {
33
+ var pa = position[a.key] === undefined ? Infinity : position[a.key];
34
+ var pb = position[b.key] === undefined ? Infinity : position[b.key];
35
+ return pa !== pb ? pa - pb : a.index - b.index;
36
+ })
37
+ .map(function(x) {
38
+ return Object.assign({}, x.item, { key: x.key, label: fromServer[x.key] || x.item.label || x.key });
39
+ });
40
+ }
@@ -1,4 +1,5 @@
1
1
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
+ import { labelMenuItems } from './menuLabels.js';
2
3
  import { useAccessStrict } from './AccessContext.jsx';
3
4
  import { authPath } from './authRoutes.jsx';
4
5
 
@@ -180,21 +181,20 @@ export function useNavController(props) {
180
181
  // Settings dropdown, in render order: app overrides first, then the fixed
181
182
  // builtin section — role-filtered the same way for both.
182
183
  var accountItems = useMemo(function() {
183
- var overrides = (props.settingsOverrides || [])
184
- .filter(function(item) { return allowedMenus.indexOf(item.name) !== -1; });
184
+ var overrides = labelMenuItems(props.settingsOverrides, access);
185
185
  var builtin = BUILTIN_SETTINGS_ITEMS
186
186
  .filter(function(item) { return allowedMenus.indexOf(item.menuName) !== -1; })
187
187
  .map(function(item) { return { name: item.menuName, path: authPath(item.authKey) }; });
188
188
  return overrides.concat(builtin);
189
- }, [allowedMenus, props.settingsOverrides]);
189
+ }, [allowedMenus, access, props.settingsOverrides]);
190
190
 
191
191
  var notificationsAllowed = allowedMenus.indexOf(NOTIFICATIONS_MENU_NAME) !== -1;
192
192
 
193
- // Role-filter the app's own drawer catalog down to what this user can actually see.
193
+ // Role-filter the app's own drawer catalog down to what this user can see —
194
+ // matched by KEY, shown by the LABEL the server holds, in the server's order.
194
195
  var drawerItems = useMemo(function() {
195
- var catalog = props.drawerItems || [];
196
- return catalog.filter(function(m) { return allowedMenus.indexOf(m.name) !== -1; });
197
- }, [props.drawerItems, allowedMenus]);
196
+ return labelMenuItems(props.drawerItems, access);
197
+ }, [props.drawerItems, access]);
198
198
 
199
199
  return {
200
200
  user: accessCtx.user,