@open-mercato/core 0.6.4-develop.3996.1.430e257cfc → 0.6.4-develop.4011.1.4f3ed9ae3e

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.
Files changed (65) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/features.js +24 -2
  3. package/dist/modules/auth/api/features.js.map +2 -2
  4. package/dist/modules/auth/backend/users/[id]/edit/page.js +70 -57
  5. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  6. package/dist/modules/auth/components/AclDependencyDiagnosticsPanel.js +135 -0
  7. package/dist/modules/auth/components/AclDependencyDiagnosticsPanel.js.map +7 -0
  8. package/dist/modules/auth/components/AclEditor.js +10 -0
  9. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  10. package/dist/modules/catalog/acl.js +30 -5
  11. package/dist/modules/catalog/acl.js.map +2 -2
  12. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +17 -5
  13. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  14. package/dist/modules/catalog/commands/offers.js +26 -7
  15. package/dist/modules/catalog/commands/offers.js.map +2 -2
  16. package/dist/modules/catalog/commands/prices.js +41 -26
  17. package/dist/modules/catalog/commands/prices.js.map +2 -2
  18. package/dist/modules/catalog/commands/productUnitConversions.js +7 -1
  19. package/dist/modules/catalog/commands/productUnitConversions.js.map +2 -2
  20. package/dist/modules/catalog/commands/products.js +2 -0
  21. package/dist/modules/catalog/commands/products.js.map +2 -2
  22. package/dist/modules/catalog/commands/shared.js +58 -11
  23. package/dist/modules/catalog/commands/shared.js.map +2 -2
  24. package/dist/modules/catalog/commands/variants.js +18 -5
  25. package/dist/modules/catalog/commands/variants.js.map +2 -2
  26. package/dist/modules/customers/acl.js +72 -12
  27. package/dist/modules/customers/acl.js.map +2 -2
  28. package/dist/modules/resources/backend/resources/resources/[id]/page.js +17 -2
  29. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  30. package/dist/modules/sales/acl.js +109 -16
  31. package/dist/modules/sales/acl.js.map +2 -2
  32. package/dist/modules/sales/backend/sales/documents/[id]/page.js +20 -1
  33. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  34. package/dist/modules/sales/setup.js +2 -0
  35. package/dist/modules/sales/setup.js.map +2 -2
  36. package/package.json +7 -7
  37. package/src/modules/auth/api/features.ts +34 -4
  38. package/src/modules/auth/backend/users/[id]/edit/page.tsx +28 -6
  39. package/src/modules/auth/components/AclDependencyDiagnosticsPanel.tsx +173 -0
  40. package/src/modules/auth/components/AclEditor.tsx +14 -4
  41. package/src/modules/auth/i18n/de.json +9 -0
  42. package/src/modules/auth/i18n/en.json +9 -0
  43. package/src/modules/auth/i18n/es.json +9 -0
  44. package/src/modules/auth/i18n/pl.json +9 -0
  45. package/src/modules/catalog/acl.ts +30 -5
  46. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +21 -5
  47. package/src/modules/catalog/commands/offers.ts +26 -7
  48. package/src/modules/catalog/commands/prices.ts +41 -26
  49. package/src/modules/catalog/commands/productUnitConversions.ts +7 -1
  50. package/src/modules/catalog/commands/products.ts +2 -0
  51. package/src/modules/catalog/commands/shared.ts +70 -6
  52. package/src/modules/catalog/commands/variants.ts +18 -5
  53. package/src/modules/catalog/i18n/de.json +1 -0
  54. package/src/modules/catalog/i18n/en.json +1 -0
  55. package/src/modules/catalog/i18n/es.json +1 -0
  56. package/src/modules/catalog/i18n/pl.json +1 -0
  57. package/src/modules/customers/acl.ts +72 -12
  58. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +21 -2
  59. package/src/modules/sales/acl.ts +109 -16
  60. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +28 -1
  61. package/src/modules/sales/i18n/de.json +3 -0
  62. package/src/modules/sales/i18n/en.json +3 -0
  63. package/src/modules/sales/i18n/es.json +3 -0
  64. package/src/modules/sales/i18n/pl.json +3 -0
  65. package/src/modules/sales/setup.ts +2 -0
@@ -1,4 +1,4 @@
1
- [build:core] found 2636 entry points
1
+ [build:core] found 2643 entry points
2
2
  [build:core] built successfully
3
3
  [build:core:generated] found 172 entry points
4
4
  [build:core:generated] built successfully
@@ -5,12 +5,33 @@ import { getModules } from "@open-mercato/shared/lib/i18n/server";
5
5
  const metadata = {
6
6
  GET: { requireAuth: true, requireFeatures: ["auth.acl.manage"] }
7
7
  };
8
+ function normalizeDependsOn(value) {
9
+ if (!Array.isArray(value)) return void 0;
10
+ const out = [];
11
+ for (const entry of value) {
12
+ if (typeof entry !== "string") continue;
13
+ const trimmed = entry.trim();
14
+ if (!trimmed) continue;
15
+ out.push(trimmed);
16
+ }
17
+ if (out.length === 0) return void 0;
18
+ return Array.from(new Set(out));
19
+ }
8
20
  async function GET(req) {
9
21
  const auth = await getAuthFromRequest(req);
10
22
  if (!auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
11
23
  const modules = getModules();
12
24
  const items = (modules || []).flatMap(
13
- (m) => (m.features || []).map((f) => ({ id: String(f.id), title: String(f.title || f.id), module: String(f.module || m.id) }))
25
+ (m) => (m.features || []).map((f) => {
26
+ const deps = normalizeDependsOn(f?.dependsOn);
27
+ const base = {
28
+ id: String(f.id),
29
+ title: String(f.title || f.id),
30
+ module: String(f.module || m.id)
31
+ };
32
+ if (deps) base.dependsOn = deps;
33
+ return base;
34
+ })
14
35
  );
15
36
  const byId = /* @__PURE__ */ new Map();
16
37
  for (const it of items) if (!byId.has(it.id)) byId.set(it.id, it);
@@ -26,7 +47,8 @@ async function GET(req) {
26
47
  const featureItemSchema = z.object({
27
48
  id: z.string(),
28
49
  title: z.string(),
29
- module: z.string()
50
+ module: z.string(),
51
+ dependsOn: z.array(z.string()).optional()
30
52
  });
31
53
  const featureModuleSchema = z.object({
32
54
  id: z.string(),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/auth/api/features.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const modules = getModules()\n const items = (modules || []).flatMap((m: any) =>\n (m.features || []).map((f: any) => ({ id: String(f.id), title: String(f.title || f.id), module: String(f.module || m.id) }))\n )\n // Deduplicate by id\n const byId = new Map<string, { id: string; title: string; module: string }>()\n for (const it of items) if (!byId.has(it.id)) byId.set(it.id, it)\n const list = Array.from(byId.values()).sort((a, b) => a.module.localeCompare(b.module) || a.id.localeCompare(b.id))\n\n // Build module info map\n const moduleInfo = new Map<string, { id: string; title: string }>()\n for (const m of modules) {\n if (m.id) {\n moduleInfo.set(m.id, { id: m.id, title: (m.info as any)?.title || m.id })\n }\n }\n\n return NextResponse.json({ items: list, modules: Array.from(moduleInfo.values()) })\n}\n\nconst featureItemSchema = z.object({\n id: z.string(),\n title: z.string(),\n module: z.string(),\n})\n\nconst featureModuleSchema = z.object({\n id: z.string(),\n title: z.string(),\n})\n\nconst featuresResponseSchema = z.object({\n items: z.array(featureItemSchema),\n modules: z.array(featureModuleSchema),\n})\n\nconst featuresMethodDoc: OpenApiMethodDoc = {\n summary: 'List declared feature flags',\n description: 'Returns all static features contributed by the enabled modules along with their module source.',\n tags: ['Authentication & Accounts'],\n responses: [\n {\n status: 200,\n description: 'Aggregated feature catalog',\n schema: featuresResponseSchema,\n },\n ],\n errors: [\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'List declared feature flags',\n methods: {\n GET: featuresMethodDoc,\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,kBAAkB;AAEpB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E,QAAM,UAAU,WAAW;AAC3B,QAAM,SAAS,WAAW,CAAC,GAAG;AAAA,IAAQ,CAAC,OACpC,EAAE,YAAY,CAAC,GAAG,IAAI,CAAC,OAAY,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,OAAO,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,QAAQ,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE;AAAA,EAC7H;AAEA,QAAM,OAAO,oBAAI,IAA2D;AAC5E,aAAW,MAAM,MAAO,KAAI,CAAC,KAAK,IAAI,GAAG,EAAE,EAAG,MAAK,IAAI,GAAG,IAAI,EAAE;AAChE,QAAM,OAAO,MAAM,KAAK,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,KAAK,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AAGlH,QAAM,aAAa,oBAAI,IAA2C;AAClE,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,IAAI;AACR,iBAAW,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,OAAQ,EAAE,MAAc,SAAS,EAAE,GAAG,CAAC;AAAA,IAC1E;AAAA,EACF;AAEA,SAAO,aAAa,KAAK,EAAE,OAAO,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,CAAC;AACpF;AAEA,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,OAAO,EAAE,OAAO;AAAA,EAChB,QAAQ,EAAE,OAAO;AACnB,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,IAAI,EAAE,OAAO;AAAA,EACb,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,OAAO,EAAE,MAAM,iBAAiB;AAAA,EAChC,SAAS,EAAE,MAAM,mBAAmB;AACtC,CAAC;AAED,MAAM,oBAAsC;AAAA,EAC1C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,2BAA2B;AAAA,EAClC,WAAW;AAAA,IACT;AAAA,MACE,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN;AAAA,MACE,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,IACxC;AAAA,EACF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { getModules } from '@open-mercato/shared/lib/i18n/server'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.acl.manage'] },\n}\n\ntype FeatureItem = {\n id: string\n title: string\n module: string\n dependsOn?: string[]\n}\n\nfunction normalizeDependsOn(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: string[] = []\n for (const entry of value) {\n if (typeof entry !== 'string') continue\n const trimmed = entry.trim()\n if (!trimmed) continue\n out.push(trimmed)\n }\n if (out.length === 0) return undefined\n return Array.from(new Set(out))\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const modules = getModules()\n const items: FeatureItem[] = (modules || []).flatMap((m: any) =>\n (m.features || []).map((f: any) => {\n const deps = normalizeDependsOn(f?.dependsOn)\n const base: FeatureItem = {\n id: String(f.id),\n title: String(f.title || f.id),\n module: String(f.module || m.id),\n }\n if (deps) base.dependsOn = deps\n return base\n })\n )\n // Deduplicate by id (keep first occurrence)\n const byId = new Map<string, FeatureItem>()\n for (const it of items) if (!byId.has(it.id)) byId.set(it.id, it)\n const list = Array.from(byId.values()).sort((a, b) => a.module.localeCompare(b.module) || a.id.localeCompare(b.id))\n\n // Build module info map\n const moduleInfo = new Map<string, { id: string; title: string }>()\n for (const m of modules) {\n if (m.id) {\n moduleInfo.set(m.id, { id: m.id, title: (m.info as any)?.title || m.id })\n }\n }\n\n return NextResponse.json({ items: list, modules: Array.from(moduleInfo.values()) })\n}\n\nconst featureItemSchema = z.object({\n id: z.string(),\n title: z.string(),\n module: z.string(),\n dependsOn: z.array(z.string()).optional(),\n})\n\nconst featureModuleSchema = z.object({\n id: z.string(),\n title: z.string(),\n})\n\nconst featuresResponseSchema = z.object({\n items: z.array(featureItemSchema),\n modules: z.array(featureModuleSchema),\n})\n\nconst featuresMethodDoc: OpenApiMethodDoc = {\n summary: 'List declared feature flags',\n description: 'Returns all static features contributed by the enabled modules along with their module source.',\n tags: ['Authentication & Accounts'],\n responses: [\n {\n status: 200,\n description: 'Aggregated feature catalog',\n schema: featuresResponseSchema,\n },\n ],\n errors: [\n {\n status: 401,\n description: 'Missing authentication',\n schema: z.object({ error: z.string() }),\n },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'List declared feature flags',\n methods: {\n GET: featuresMethodDoc,\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,kBAAkB;AAEpB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AASA,SAAS,mBAAmB,OAAsC;AAChE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,OAAO;AACzB,QAAI,OAAO,UAAU,SAAU;AAC/B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS;AACd,QAAI,KAAK,OAAO;AAAA,EAClB;AACA,MAAI,IAAI,WAAW,EAAG,QAAO;AAC7B,SAAO,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC;AAChC;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC9E,QAAM,UAAU,WAAW;AAC3B,QAAM,SAAwB,WAAW,CAAC,GAAG;AAAA,IAAQ,CAAC,OACnD,EAAE,YAAY,CAAC,GAAG,IAAI,CAAC,MAAW;AACjC,YAAM,OAAO,mBAAmB,GAAG,SAAS;AAC5C,YAAM,OAAoB;AAAA,QACxB,IAAI,OAAO,EAAE,EAAE;AAAA,QACf,OAAO,OAAO,EAAE,SAAS,EAAE,EAAE;AAAA,QAC7B,QAAQ,OAAO,EAAE,UAAU,EAAE,EAAE;AAAA,MACjC;AACA,UAAI,KAAM,MAAK,YAAY;AAC3B,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,OAAO,oBAAI,IAAyB;AAC1C,aAAW,MAAM,MAAO,KAAI,CAAC,KAAK,IAAI,GAAG,EAAE,EAAG,MAAK,IAAI,GAAG,IAAI,EAAE;AAChE,QAAM,OAAO,MAAM,KAAK,KAAK,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,KAAK,EAAE,GAAG,cAAc,EAAE,EAAE,CAAC;AAGlH,QAAM,aAAa,oBAAI,IAA2C;AAClE,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,IAAI;AACR,iBAAW,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,OAAQ,EAAE,MAAc,SAAS,EAAE,GAAG,CAAC;AAAA,IAC1E;AAAA,EACF;AAEA,SAAO,aAAa,KAAK,EAAE,OAAO,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,CAAC,EAAE,CAAC;AACpF;AAEA,MAAM,oBAAoB,EAAE,OAAO;AAAA,EACjC,IAAI,EAAE,OAAO;AAAA,EACb,OAAO,EAAE,OAAO;AAAA,EAChB,QAAQ,EAAE,OAAO;AAAA,EACjB,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAC1C,CAAC;AAED,MAAM,sBAAsB,EAAE,OAAO;AAAA,EACnC,IAAI,EAAE,OAAO;AAAA,EACb,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,OAAO,EAAE,MAAM,iBAAiB;AAAA,EAChC,SAAS,EAAE,MAAM,mBAAmB;AACtC,CAAC;AAED,MAAM,oBAAsC;AAAA,EAC1C,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,2BAA2B;AAAA,EAClC,WAAW;AAAA,IACT;AAAA,MACE,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN;AAAA,MACE,QAAQ;AAAA,MACR,aAAa;AAAA,MACb,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAAA,IACxC;AAAA,EACF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { jsx } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { E } from "../../../../../../generated/entities.ids.generated.js";
5
5
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
@@ -18,6 +18,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
18
18
  import { extractCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields-client";
19
19
  import { formatPasswordRequirements, getPasswordPolicy } from "@open-mercato/shared/lib/auth/passwordPolicy";
20
20
  import { UserConsentsPanel } from "@open-mercato/core/modules/auth/components/UserConsentsPanel";
21
+ import { RecordNotFoundState, ErrorMessage } from "@open-mercato/ui/backend/detail";
21
22
  import { normalizeDisplayNameInput } from "@open-mercato/core/modules/auth/lib/displayName";
22
23
  function TenantAwareOrganizationSelectInput({
23
24
  fieldId,
@@ -65,6 +66,7 @@ function EditUserPage({ params }) {
65
66
  const [selectedTenantId, setSelectedTenantId] = React.useState(null);
66
67
  const [loading, setLoading] = React.useState(true);
67
68
  const [error, setError] = React.useState(null);
69
+ const [isNotFound, setIsNotFound] = React.useState(false);
68
70
  const [canEditOrgs, setCanEditOrgs] = React.useState(false);
69
71
  const [aclData, setAclData] = React.useState({ isSuperAdmin: false, features: [], organizations: null });
70
72
  const [customFieldValues, setCustomFieldValues] = React.useState({});
@@ -111,6 +113,7 @@ function EditUserPage({ params }) {
111
113
  async function load() {
112
114
  setLoading(true);
113
115
  setError(null);
116
+ setIsNotFound(false);
114
117
  setCustomFieldValues({});
115
118
  try {
116
119
  const { ok, result } = await apiCall(
@@ -122,7 +125,7 @@ function EditUserPage({ params }) {
122
125
  setActorIsSuperAdmin(Boolean(result?.isSuperAdmin));
123
126
  setActorResolved(true);
124
127
  if (!item) {
125
- setError(tRef.current("auth.users.form.errors.notFound", "User not found"));
128
+ setIsNotFound(true);
126
129
  setCustomFieldValues({});
127
130
  setInitialUser(null);
128
131
  setSelectedTenantId(null);
@@ -322,64 +325,74 @@ function EditUserPage({ params }) {
322
325
  ...customFieldValues
323
326
  };
324
327
  }, [initialUser, customFieldValues, selectedTenantId]);
325
- return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { children: [
326
- error && /* @__PURE__ */ jsx("div", { className: "p-4 mb-4 bg-red-50 border border-red-200 rounded text-red-800", children: error }),
327
- /* @__PURE__ */ jsx(
328
- CrudForm,
328
+ if (isNotFound) {
329
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
330
+ RecordNotFoundState,
329
331
  {
330
- title: t("auth.users.form.title.edit", "Edit User"),
332
+ label: t("auth.users.form.errors.notFound", "User not found"),
331
333
  backHref: "/backend/users",
332
- versionHistory: { resourceKind: "auth.user", resourceId: id ? String(id) : "" },
333
- fields,
334
- groups,
335
- entityId: E.auth.user,
336
- initialValues,
337
- isLoading: loading,
338
- loadingMessage: t("auth.users.form.loading", "Loading user data..."),
339
- submitLabel: t("auth.users.form.action.save", "Save"),
340
- cancelHref: "/backend/users",
341
- extraActions: id && !userHasPassword ? /* @__PURE__ */ jsx(
342
- Button,
343
- {
344
- type: "button",
345
- variant: "outline",
346
- disabled: resendingInvite,
347
- onClick: handleResendInvite,
348
- children: resendingInvite ? t("auth.users.form.action.resendingInvite", "Sending...") : t("auth.users.form.action.resendInvite", "Resend Invite")
349
- }
350
- ) : void 0,
351
- successRedirect: `/backend/users?flash=${encodeURIComponent(t("auth.users.flash.updated", "User saved"))}&type=success`,
352
- onSubmit: async (values) => {
353
- if (!id) return;
354
- const customFields = collectCustomFieldValues(values);
355
- const payload = {
356
- id: id ? String(id) : "",
357
- email: values.email,
358
- name: normalizeDisplayNameInput(values.name),
359
- password: values.password && values.password.trim() ? values.password : void 0,
360
- organizationId: values.organizationId ? values.organizationId : void 0,
361
- roles: Array.isArray(values.roles) ? values.roles : [],
362
- ...Object.keys(customFields).length ? { customFields } : {}
363
- };
364
- await updateCrud("auth/users", payload);
365
- await updateCrud("auth/users/acl", { userId: id, ...aclData }, {
366
- errorMessage: t("auth.users.form.errors.aclUpdate", "Failed to update user access control")
367
- });
368
- await widgetEditorRef.current?.save();
369
- try {
370
- window.dispatchEvent(new Event("om:refresh-sidebar"));
371
- } catch {
372
- }
373
- },
374
- onDelete: async () => {
375
- await deleteCrud("auth/users", String(id), {
376
- errorMessage: t("auth.users.form.errors.delete", "Failed to delete user")
377
- });
378
- },
379
- deleteRedirect: `/backend/users?flash=${encodeURIComponent(t("auth.users.flash.deleted", "User deleted"))}&type=success`
334
+ backLabel: t("auth.users.form.actions.backToList", "Back to users")
380
335
  }
381
- )
382
- ] }) });
336
+ ) }) });
337
+ }
338
+ if (error && !loading) {
339
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error }) }) });
340
+ }
341
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
342
+ CrudForm,
343
+ {
344
+ title: t("auth.users.form.title.edit", "Edit User"),
345
+ backHref: "/backend/users",
346
+ versionHistory: { resourceKind: "auth.user", resourceId: id ? String(id) : "" },
347
+ fields,
348
+ groups,
349
+ entityId: E.auth.user,
350
+ initialValues,
351
+ isLoading: loading,
352
+ loadingMessage: t("auth.users.form.loading", "Loading user data..."),
353
+ submitLabel: t("auth.users.form.action.save", "Save"),
354
+ cancelHref: "/backend/users",
355
+ extraActions: id && !userHasPassword ? /* @__PURE__ */ jsx(
356
+ Button,
357
+ {
358
+ type: "button",
359
+ variant: "outline",
360
+ disabled: resendingInvite,
361
+ onClick: handleResendInvite,
362
+ children: resendingInvite ? t("auth.users.form.action.resendingInvite", "Sending...") : t("auth.users.form.action.resendInvite", "Resend Invite")
363
+ }
364
+ ) : void 0,
365
+ successRedirect: `/backend/users?flash=${encodeURIComponent(t("auth.users.flash.updated", "User saved"))}&type=success`,
366
+ onSubmit: async (values) => {
367
+ if (!id) return;
368
+ const customFields = collectCustomFieldValues(values);
369
+ const payload = {
370
+ id: id ? String(id) : "",
371
+ email: values.email,
372
+ name: normalizeDisplayNameInput(values.name),
373
+ password: values.password && values.password.trim() ? values.password : void 0,
374
+ organizationId: values.organizationId ? values.organizationId : void 0,
375
+ roles: Array.isArray(values.roles) ? values.roles : [],
376
+ ...Object.keys(customFields).length ? { customFields } : {}
377
+ };
378
+ await updateCrud("auth/users", payload);
379
+ await updateCrud("auth/users/acl", { userId: id, ...aclData }, {
380
+ errorMessage: t("auth.users.form.errors.aclUpdate", "Failed to update user access control")
381
+ });
382
+ await widgetEditorRef.current?.save();
383
+ try {
384
+ window.dispatchEvent(new Event("om:refresh-sidebar"));
385
+ } catch {
386
+ }
387
+ },
388
+ onDelete: async () => {
389
+ await deleteCrud("auth/users", String(id), {
390
+ errorMessage: t("auth.users.form.errors.delete", "Failed to delete user")
391
+ });
392
+ },
393
+ deleteRedirect: `/backend/users?flash=${encodeURIComponent(t("auth.users.flash.deleted", "User deleted"))}&type=success`
394
+ }
395
+ ) }) });
383
396
  }
384
397
  export {
385
398
  EditUserPage as default
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/auth/backend/users/%5Bid%5D/edit/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { AclEditor, type AclData } from '@open-mercato/core/modules/auth/components/AclEditor'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { WidgetVisibilityEditor, type WidgetVisibilityEditorHandle } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\n\ntype EditUserFormValues = {\n email: string\n name: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype LoadedUser = {\n id: string\n email: string\n name: string | null\n organizationId: string | null\n tenantId: string | null\n tenantName: string | null\n organizationName: string | null\n roles: string[]\n roleIds: string[]\n hasPassword: boolean\n}\n\ntype UserApiItem = {\n id?: string | null\n email?: string | null\n name?: string | null\n organizationId?: string | null\n tenantId?: string | null\n tenantName?: string | null\n organizationName?: string | null\n roles?: unknown\n roleIds?: unknown\n hasPassword?: boolean\n}\n\ntype UserListResponse = {\n items?: UserApiItem[]\n isSuperAdmin?: boolean\n}\n\ntype FeatureCheckResponse = {\n ok?: boolean\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n includeInactiveIds={includeInactiveIds}\n tenantId={tenantId}\n />\n )\n}\n\nexport default function EditUserPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n const [initialUser, setInitialUser] = React.useState<LoadedUser | null>(null)\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [canEditOrgs, setCanEditOrgs] = React.useState(false)\n const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })\n const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const widgetEditorRef = React.useRef<WidgetVisibilityEditorHandle | null>(null)\n const [resendingInvite, setResendingInvite] = React.useState(false)\n\n const handleResendInvite = React.useCallback(async () => {\n if (!id) return\n setResendingInvite(true)\n try {\n const { ok, result } = await apiCall<{ ok?: boolean; warning?: string }>('/api/auth/users/resend-invite', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n })\n if (ok) {\n if (result?.warning === 'invite_email_failed') {\n flash(tRef.current('auth.users.flash.inviteEmailFailed', 'Invite token created but the email could not be sent. Please check your email provider configuration.'), 'warning')\n } else {\n flash(tRef.current('auth.users.flash.inviteSent', 'Invitation email sent'), 'success')\n }\n }\n } catch (err) {\n console.error('Failed to resend invite:', err)\n flash(tRef.current('auth.users.form.errors.inviteResend', 'Failed to send invitation email'), 'error')\n } finally {\n setResendingInvite(false)\n }\n }, [id])\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n if (!id) {\n setLoading(false)\n setError(tRef.current('auth.users.form.errors.noId', 'No user ID provided'))\n return\n }\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n setCustomFieldValues({})\n try {\n const { ok, result } = await apiCall<UserListResponse>(\n `/api/auth/users?id=${encodeURIComponent(String(id))}&page=1&pageSize=1`,\n )\n if (!ok) throw new Error(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))\n const item = Array.isArray(result?.items) ? result?.items?.[0] : undefined\n if (!cancelled) {\n setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n setActorResolved(true)\n if (!item) {\n setError(tRef.current('auth.users.form.errors.notFound', 'User not found'))\n setCustomFieldValues({})\n setInitialUser(null)\n setSelectedTenantId(null)\n } else {\n const roleNames = Array.isArray(item.roles)\n ? item.roles\n .map((role) => (typeof role === 'string' ? role : role == null ? '' : String(role)))\n .filter((role) => role.trim().length > 0)\n : []\n const roleIds = Array.isArray(item.roleIds)\n ? (item.roleIds as string[]).filter((rid) => typeof rid === 'string' && rid.trim().length > 0)\n : []\n setInitialUser({\n id: item.id ? String(item.id) : String(id),\n email: item.email ? String(item.email) : '',\n name: item.name ? String(item.name) : null,\n organizationId: item.organizationId ? String(item.organizationId) : null,\n tenantId: item.tenantId ? String(item.tenantId) : null,\n tenantName: item.tenantName ? String(item.tenantName) : null,\n organizationName: item.organizationName ? String(item.organizationName) : null,\n roles: roleNames,\n roleIds: roleIds.length > 0 ? roleIds : roleNames,\n hasPassword: item.hasPassword !== false,\n })\n setSelectedTenantId(item.tenantId ? String(item.tenantId) : null)\n const custom = extractCustomFieldEntries(item as Record<string, unknown>)\n setCustomFieldValues(custom)\n }\n }\n } catch (err) {\n console.error('Failed to load user:', err)\n if (!cancelled) setError(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))\n if (!cancelled) setCustomFieldValues({})\n if (!cancelled) setActorResolved(true)\n }\n if (!cancelled) setLoading(false)\n try {\n const featureCheck = await apiCall<FeatureCheckResponse>(\n '/api/auth/feature-check',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['directory.organizations.view'] }),\n },\n { fallback: { ok: false } },\n )\n if (!cancelled) setCanEditOrgs(Boolean(featureCheck.result?.ok))\n } catch (err) {\n console.error('Failed to check features:', err)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id])\n\n const selectedOrgId = initialUser?.organizationId ? String(initialUser.organizationId) : null\n const preloadedTenants = React.useMemo(() => {\n if (!selectedTenantId) return null\n const name = initialUser?.tenantId === selectedTenantId\n ? (initialUser?.tenantName ?? selectedTenantId)\n : selectedTenantId\n return [{ id: selectedTenantId, name, isActive: true }]\n }, [initialUser, selectedTenantId])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId, includeSuperAdmin: true })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const userHasPassword = initialUser?.hasPassword !== false\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n { id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },\n {\n id: 'password',\n label: userHasPassword\n ? t('auth.users.form.field.newPassword', 'New Password')\n : t('auth.users.form.field.setPassword', 'Set Password'),\n type: 'password' as const,\n description: [\n userHasPassword\n ? t('auth.users.form.field.passwordChangeHint', 'Leave blank to keep current password')\n : t('auth.users.form.field.passwordInviteHint', 'Optionally set a password for this user (they were invited via email)'),\n passwordDescription,\n ].filter(Boolean).join('. '),\n },\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n setAclData({ isSuperAdmin: false, features: [], organizations: null })\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n required\n tenants={preloadedTenants}\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? (value.length > 0 ? value : null) : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n includeInactiveIds={selectedOrgId ? [selectedOrgId] : undefined}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t, userHasPassword])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) base.splice(2, 0, 'tenantId')\n return base\n }, [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (id\n ? (\n <AclEditor\n kind=\"user\"\n targetId={String(id)}\n canEditOrganizations={canEditOrgs}\n value={aclData}\n onChange={setAclData}\n userRoles={initialUser?.roles || []}\n currentUserIsSuperAdmin={actorIsSuperAdmin}\n tenantId={selectedTenantId ?? null}\n />\n )\n : null),\n },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (id && initialUser\n ? (\n <WidgetVisibilityEditor\n kind=\"user\"\n targetId={String(id)}\n tenantId={selectedTenantId ?? null}\n organizationId={initialUser?.organizationId ?? null}\n ref={widgetEditorRef}\n />\n ) : null\n ),\n },\n {\n id: 'consents',\n title: t('auth.users.form.group.consents', 'Consents'),\n column: 2,\n component: () => (id ? <UserConsentsPanel userId={String(id)} /> : null),\n },\n ], [aclData, actorIsSuperAdmin, canEditOrgs, detailFieldIds, id, initialUser, selectedTenantId, t])\n\n const initialValues = React.useMemo(() => {\n if (initialUser) {\n return {\n email: initialUser.email,\n name: initialUser.name ?? '',\n password: '',\n tenantId: initialUser.tenantId,\n organizationId: initialUser.organizationId,\n roles: initialUser.roleIds,\n ...customFieldValues,\n }\n }\n return {\n email: '',\n name: '',\n password: '',\n tenantId: selectedTenantId ?? null,\n organizationId: null,\n roles: [],\n ...customFieldValues,\n }\n }, [initialUser, customFieldValues, selectedTenantId])\n\n return (\n <Page>\n <PageBody>\n {error && (\n <div className=\"p-4 mb-4 bg-red-50 border border-red-200 rounded text-red-800\">\n {error}\n </div>\n )}\n <CrudForm<EditUserFormValues>\n title={t('auth.users.form.title.edit', 'Edit User')}\n backHref=\"/backend/users\"\n versionHistory={{ resourceKind: 'auth.user', resourceId: id ? String(id) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('auth.users.form.loading', 'Loading user data...')}\n submitLabel={t('auth.users.form.action.save', 'Save')}\n cancelHref=\"/backend/users\"\n extraActions={id && !userHasPassword ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={resendingInvite}\n onClick={handleResendInvite}\n >\n {resendingInvite\n ? t('auth.users.form.action.resendingInvite', 'Sending...')\n : t('auth.users.form.action.resendInvite', 'Resend Invite')}\n </Button>\n ) : undefined}\n successRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.updated', 'User saved'))}&type=success`}\n onSubmit={async (values) => {\n if (!id) return\n const customFields = collectCustomFieldValues(values)\n const payload = {\n id: id ? String(id) : '',\n email: values.email,\n name: normalizeDisplayNameInput(values.name),\n password: values.password && values.password.trim() ? values.password : undefined,\n organizationId: values.organizationId ? values.organizationId : undefined,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n await updateCrud('auth/users', payload)\n await updateCrud('auth/users/acl', { userId: id, ...aclData }, {\n errorMessage: t('auth.users.form.errors.aclUpdate', 'Failed to update user access control'),\n })\n await widgetEditorRef.current?.save()\n try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}\n }}\n onDelete={async () => {\n await deleteCrud('auth/users', String(id), {\n errorMessage: t('auth.users.form.errors.delete', 'Failed to delete user'),\n })\n }}\n deleteRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.deleted', 'User deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { deleteCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { AclEditor, type AclData } from '@open-mercato/core/modules/auth/components/AclEditor'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { WidgetVisibilityEditor, type WidgetVisibilityEditorHandle } from '@open-mercato/core/modules/dashboards/components/WidgetVisibilityEditor'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { UserConsentsPanel } from '@open-mercato/core/modules/auth/components/UserConsentsPanel'\nimport { RecordNotFoundState, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\n\ntype EditUserFormValues = {\n email: string\n name: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype LoadedUser = {\n id: string\n email: string\n name: string | null\n organizationId: string | null\n tenantId: string | null\n tenantName: string | null\n organizationName: string | null\n roles: string[]\n roleIds: string[]\n hasPassword: boolean\n}\n\ntype UserApiItem = {\n id?: string | null\n email?: string | null\n name?: string | null\n organizationId?: string | null\n tenantId?: string | null\n tenantName?: string | null\n organizationName?: string | null\n roles?: unknown\n roleIds?: unknown\n hasPassword?: boolean\n}\n\ntype UserListResponse = {\n items?: UserApiItem[]\n isSuperAdmin?: boolean\n}\n\ntype FeatureCheckResponse = {\n ok?: boolean\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n includeInactiveIds={includeInactiveIds}\n tenantId={tenantId}\n />\n )\n}\n\nexport default function EditUserPage({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const tRef = React.useRef(t)\n tRef.current = t\n const [initialUser, setInitialUser] = React.useState<LoadedUser | null>(null)\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n const [canEditOrgs, setCanEditOrgs] = React.useState(false)\n const [aclData, setAclData] = React.useState<AclData>({ isSuperAdmin: false, features: [], organizations: null })\n const [customFieldValues, setCustomFieldValues] = React.useState<Record<string, unknown>>({})\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const widgetEditorRef = React.useRef<WidgetVisibilityEditorHandle | null>(null)\n const [resendingInvite, setResendingInvite] = React.useState(false)\n\n const handleResendInvite = React.useCallback(async () => {\n if (!id) return\n setResendingInvite(true)\n try {\n const { ok, result } = await apiCall<{ ok?: boolean; warning?: string }>('/api/auth/users/resend-invite', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id }),\n })\n if (ok) {\n if (result?.warning === 'invite_email_failed') {\n flash(tRef.current('auth.users.flash.inviteEmailFailed', 'Invite token created but the email could not be sent. Please check your email provider configuration.'), 'warning')\n } else {\n flash(tRef.current('auth.users.flash.inviteSent', 'Invitation email sent'), 'success')\n }\n }\n } catch (err) {\n console.error('Failed to resend invite:', err)\n flash(tRef.current('auth.users.form.errors.inviteResend', 'Failed to send invitation email'), 'error')\n } finally {\n setResendingInvite(false)\n }\n }, [id])\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n if (!id) {\n setLoading(false)\n setError(tRef.current('auth.users.form.errors.noId', 'No user ID provided'))\n return\n }\n let cancelled = false\n async function load() {\n setLoading(true)\n setError(null)\n setIsNotFound(false)\n setCustomFieldValues({})\n try {\n const { ok, result } = await apiCall<UserListResponse>(\n `/api/auth/users?id=${encodeURIComponent(String(id))}&page=1&pageSize=1`,\n )\n if (!ok) throw new Error(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))\n const item = Array.isArray(result?.items) ? result?.items?.[0] : undefined\n if (!cancelled) {\n setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n setActorResolved(true)\n if (!item) {\n setIsNotFound(true)\n setCustomFieldValues({})\n setInitialUser(null)\n setSelectedTenantId(null)\n } else {\n const roleNames = Array.isArray(item.roles)\n ? item.roles\n .map((role) => (typeof role === 'string' ? role : role == null ? '' : String(role)))\n .filter((role) => role.trim().length > 0)\n : []\n const roleIds = Array.isArray(item.roleIds)\n ? (item.roleIds as string[]).filter((rid) => typeof rid === 'string' && rid.trim().length > 0)\n : []\n setInitialUser({\n id: item.id ? String(item.id) : String(id),\n email: item.email ? String(item.email) : '',\n name: item.name ? String(item.name) : null,\n organizationId: item.organizationId ? String(item.organizationId) : null,\n tenantId: item.tenantId ? String(item.tenantId) : null,\n tenantName: item.tenantName ? String(item.tenantName) : null,\n organizationName: item.organizationName ? String(item.organizationName) : null,\n roles: roleNames,\n roleIds: roleIds.length > 0 ? roleIds : roleNames,\n hasPassword: item.hasPassword !== false,\n })\n setSelectedTenantId(item.tenantId ? String(item.tenantId) : null)\n const custom = extractCustomFieldEntries(item as Record<string, unknown>)\n setCustomFieldValues(custom)\n }\n }\n } catch (err) {\n console.error('Failed to load user:', err)\n if (!cancelled) setError(tRef.current('auth.users.form.errors.load', 'Failed to load user data'))\n if (!cancelled) setCustomFieldValues({})\n if (!cancelled) setActorResolved(true)\n }\n if (!cancelled) setLoading(false)\n try {\n const featureCheck = await apiCall<FeatureCheckResponse>(\n '/api/auth/feature-check',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['directory.organizations.view'] }),\n },\n { fallback: { ok: false } },\n )\n if (!cancelled) setCanEditOrgs(Boolean(featureCheck.result?.ok))\n } catch (err) {\n console.error('Failed to check features:', err)\n }\n }\n load()\n return () => { cancelled = true }\n }, [id])\n\n const selectedOrgId = initialUser?.organizationId ? String(initialUser.organizationId) : null\n const preloadedTenants = React.useMemo(() => {\n if (!selectedTenantId) return null\n const name = initialUser?.tenantId === selectedTenantId\n ? (initialUser?.tenantName ?? selectedTenantId)\n : selectedTenantId\n return [{ id: selectedTenantId, name, isActive: true }]\n }, [initialUser, selectedTenantId])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId, includeSuperAdmin: true })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const userHasPassword = initialUser?.hasPassword !== false\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n { id: 'name', label: t('auth.users.form.field.name', 'Display name'), type: 'text' },\n {\n id: 'password',\n label: userHasPassword\n ? t('auth.users.form.field.newPassword', 'New Password')\n : t('auth.users.form.field.setPassword', 'Set Password'),\n type: 'password' as const,\n description: [\n userHasPassword\n ? t('auth.users.form.field.passwordChangeHint', 'Leave blank to keep current password')\n : t('auth.users.form.field.passwordInviteHint', 'Optionally set a password for this user (they were invited via email)'),\n passwordDescription,\n ].filter(Boolean).join('. '),\n },\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n setAclData({ isSuperAdmin: false, features: [], organizations: null })\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border pl-2 pr-7 text-sm truncate\"\n required\n tenants={preloadedTenants}\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? (value.length > 0 ? value : null) : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n includeInactiveIds={selectedOrgId ? [selectedOrgId] : undefined}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, preloadedTenants, selectedOrgId, selectedTenantId, t, userHasPassword])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = ['email', 'name', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) base.splice(2, 0, 'tenantId')\n return base\n }, [actorIsSuperAdmin])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (id\n ? (\n <AclEditor\n kind=\"user\"\n targetId={String(id)}\n canEditOrganizations={canEditOrgs}\n value={aclData}\n onChange={setAclData}\n userRoles={initialUser?.roles || []}\n currentUserIsSuperAdmin={actorIsSuperAdmin}\n tenantId={selectedTenantId ?? null}\n />\n )\n : null),\n },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (id && initialUser\n ? (\n <WidgetVisibilityEditor\n kind=\"user\"\n targetId={String(id)}\n tenantId={selectedTenantId ?? null}\n organizationId={initialUser?.organizationId ?? null}\n ref={widgetEditorRef}\n />\n ) : null\n ),\n },\n {\n id: 'consents',\n title: t('auth.users.form.group.consents', 'Consents'),\n column: 2,\n component: () => (id ? <UserConsentsPanel userId={String(id)} /> : null),\n },\n ], [aclData, actorIsSuperAdmin, canEditOrgs, detailFieldIds, id, initialUser, selectedTenantId, t])\n\n const initialValues = React.useMemo(() => {\n if (initialUser) {\n return {\n email: initialUser.email,\n name: initialUser.name ?? '',\n password: '',\n tenantId: initialUser.tenantId,\n organizationId: initialUser.organizationId,\n roles: initialUser.roleIds,\n ...customFieldValues,\n }\n }\n return {\n email: '',\n name: '',\n password: '',\n tenantId: selectedTenantId ?? null,\n organizationId: null,\n roles: [],\n ...customFieldValues,\n }\n }, [initialUser, customFieldValues, selectedTenantId])\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('auth.users.form.errors.notFound', 'User not found')}\n backHref=\"/backend/users\"\n backLabel={t('auth.users.form.actions.backToList', 'Back to users')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error && !loading) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage label={error} />\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <CrudForm<EditUserFormValues>\n title={t('auth.users.form.title.edit', 'Edit User')}\n backHref=\"/backend/users\"\n versionHistory={{ resourceKind: 'auth.user', resourceId: id ? String(id) : '' }}\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n isLoading={loading}\n loadingMessage={t('auth.users.form.loading', 'Loading user data...')}\n submitLabel={t('auth.users.form.action.save', 'Save')}\n cancelHref=\"/backend/users\"\n extraActions={id && !userHasPassword ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={resendingInvite}\n onClick={handleResendInvite}\n >\n {resendingInvite\n ? t('auth.users.form.action.resendingInvite', 'Sending...')\n : t('auth.users.form.action.resendInvite', 'Resend Invite')}\n </Button>\n ) : undefined}\n successRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.updated', 'User saved'))}&type=success`}\n onSubmit={async (values) => {\n if (!id) return\n const customFields = collectCustomFieldValues(values)\n const payload = {\n id: id ? String(id) : '',\n email: values.email,\n name: normalizeDisplayNameInput(values.name),\n password: values.password && values.password.trim() ? values.password : undefined,\n organizationId: values.organizationId ? values.organizationId : undefined,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n await updateCrud('auth/users', payload)\n await updateCrud('auth/users/acl', { userId: id, ...aclData }, {\n errorMessage: t('auth.users.form.errors.aclUpdate', 'Failed to update user access control'),\n })\n await widgetEditorRef.current?.save()\n try { window.dispatchEvent(new Event('om:refresh-sidebar')) } catch {}\n }}\n onDelete={async () => {\n await deleteCrud('auth/users', String(id), {\n errorMessage: t('auth.users.form.errors.delete', 'Failed to delete user'),\n })\n }}\n deleteRedirect={`/backend/users?flash=${encodeURIComponent(t('auth.users.flash.deleted', 'User deleted'))}&type=success`}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["id"]
7
7
  }
@@ -0,0 +1,135 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Button } from "@open-mercato/ui/primitives/button";
5
+ import { Alert } from "@open-mercato/ui/primitives/alert";
6
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
7
+ import {
8
+ applyAddMissingDependency,
9
+ applyRemoveDependents,
10
+ applyRestoreDependency,
11
+ resolveAclDependencyDiagnostics
12
+ } from "@open-mercato/shared/security/aclDependencies";
13
+ function AclDependencyDiagnosticsPanel({
14
+ granted,
15
+ catalog,
16
+ onGrantedChange,
17
+ hideUnknownReferences
18
+ }) {
19
+ const t = useT();
20
+ const diagnostics = React.useMemo(
21
+ () => resolveAclDependencyDiagnostics(granted, catalog),
22
+ [granted, catalog]
23
+ );
24
+ const titleById = React.useMemo(() => {
25
+ const map = /* @__PURE__ */ new Map();
26
+ for (const entry of catalog) {
27
+ if (entry?.title && !map.has(entry.id)) map.set(entry.id, entry.title);
28
+ }
29
+ return map;
30
+ }, [catalog]);
31
+ const featureLabel = React.useCallback((id) => titleById.get(id) ?? id, [titleById]);
32
+ const hasMissing = diagnostics.missingDependencies.length > 0;
33
+ const hasOrphaned = diagnostics.orphanedDependents.length > 0;
34
+ const showUnknown = !hideUnknownReferences && diagnostics.unknownReferences.length > 0;
35
+ if (!hasMissing && !hasOrphaned && !showUnknown) return null;
36
+ const handleAdd = (dep) => onGrantedChange((prev) => applyAddMissingDependency(prev, dep));
37
+ const handleRestore = (dep) => onGrantedChange((prev) => applyRestoreDependency(prev, dep));
38
+ const handleDropDependents = (dependents) => onGrantedChange((prev) => applyRemoveDependents(prev, dependents));
39
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": "acl-dependency-diagnostics", children: [
40
+ hasMissing && /* @__PURE__ */ jsx(Alert, { status: "warning", style: "lighter", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
41
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: t(
42
+ "auth.acl.deps.missing.title",
43
+ "Some granted permissions need other permissions to work:"
44
+ ) }),
45
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-sm", children: diagnostics.missingDependencies.map((row) => /* @__PURE__ */ jsxs(
46
+ "li",
47
+ {
48
+ className: "flex flex-wrap items-center gap-x-2 gap-y-1",
49
+ "data-testid": `missing-${row.feature}`,
50
+ children: [
51
+ /* @__PURE__ */ jsx("span", { children: t("auth.acl.deps.missing.item", '"{feature}" needs:', {
52
+ feature: featureLabel(row.feature)
53
+ }) }),
54
+ row.missing.map((dep) => /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
55
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: featureLabel(dep) }),
56
+ /* @__PURE__ */ jsx(
57
+ Button,
58
+ {
59
+ type: "button",
60
+ size: "sm",
61
+ variant: "outline",
62
+ onClick: () => handleAdd(dep),
63
+ "data-testid": `add-missing-${row.feature}-${dep}`,
64
+ children: t("auth.acl.deps.missing.add", 'Add "{dep}"', {
65
+ dep: featureLabel(dep)
66
+ })
67
+ }
68
+ )
69
+ ] }, dep))
70
+ ]
71
+ },
72
+ row.feature
73
+ )) })
74
+ ] }) }),
75
+ hasOrphaned && /* @__PURE__ */ jsx(Alert, { status: "warning", style: "lighter", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
76
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: t(
77
+ "auth.acl.deps.orphaned.title",
78
+ "Removing a permission that other granted permissions need:"
79
+ ) }),
80
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-sm", children: diagnostics.orphanedDependents.map((row) => /* @__PURE__ */ jsxs(
81
+ "li",
82
+ {
83
+ className: "flex flex-wrap items-center gap-x-2 gap-y-1",
84
+ "data-testid": `orphaned-${row.dependency}`,
85
+ children: [
86
+ /* @__PURE__ */ jsx("span", { children: t("auth.acl.deps.orphaned.item", '"{dependency}" is required by:', {
87
+ dependency: featureLabel(row.dependency)
88
+ }) }),
89
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: row.dependents.map((dependent) => featureLabel(dependent)).join(", ") }),
90
+ /* @__PURE__ */ jsx(
91
+ Button,
92
+ {
93
+ type: "button",
94
+ size: "sm",
95
+ variant: "outline",
96
+ onClick: () => handleRestore(row.dependency),
97
+ "data-testid": `restore-${row.dependency}`,
98
+ children: t("auth.acl.deps.orphaned.restore", 'Restore "{dependency}"', {
99
+ dependency: featureLabel(row.dependency)
100
+ })
101
+ }
102
+ ),
103
+ /* @__PURE__ */ jsx(
104
+ Button,
105
+ {
106
+ type: "button",
107
+ size: "sm",
108
+ variant: "outline",
109
+ onClick: () => handleDropDependents(row.dependents),
110
+ "data-testid": `drop-dependents-${row.dependency}`,
111
+ children: t("auth.acl.deps.orphaned.drop", "Drop dependents")
112
+ }
113
+ )
114
+ ]
115
+ },
116
+ row.dependency
117
+ )) })
118
+ ] }) }),
119
+ showUnknown && /* @__PURE__ */ jsx(Alert, { status: "warning", style: "lighter", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
120
+ /* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: t(
121
+ "auth.acl.deps.unknown.title",
122
+ "Some declared dependencies do not match any known permission:"
123
+ ) }),
124
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-sm font-mono text-xs text-muted-foreground", children: diagnostics.unknownReferences.map((row) => /* @__PURE__ */ jsxs("li", { "data-testid": `unknown-${row.feature}`, children: [
125
+ row.feature,
126
+ " \u2192 ",
127
+ row.missing.join(", ")
128
+ ] }, row.feature)) })
129
+ ] }) })
130
+ ] });
131
+ }
132
+ export {
133
+ AclDependencyDiagnosticsPanel
134
+ };
135
+ //# sourceMappingURL=AclDependencyDiagnosticsPanel.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/auth/components/AclDependencyDiagnosticsPanel.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n applyAddMissingDependency,\n applyRemoveDependents,\n applyRestoreDependency,\n resolveAclDependencyDiagnostics,\n type FeatureDescriptor,\n} from '@open-mercato/shared/security/aclDependencies'\n\nexport type AclDependencyDiagnosticsPanelProps = {\n granted: readonly string[]\n catalog: readonly FeatureDescriptor[]\n onGrantedChange: (updater: (prev: string[]) => string[]) => void\n hideUnknownReferences?: boolean\n}\n\nexport function AclDependencyDiagnosticsPanel({\n granted,\n catalog,\n onGrantedChange,\n hideUnknownReferences,\n}: AclDependencyDiagnosticsPanelProps) {\n const t = useT()\n const diagnostics = React.useMemo(\n () => resolveAclDependencyDiagnostics(granted, catalog),\n [granted, catalog],\n )\n const titleById = React.useMemo(() => {\n const map = new Map<string, string>()\n for (const entry of catalog) {\n if (entry?.title && !map.has(entry.id)) map.set(entry.id, entry.title)\n }\n return map\n }, [catalog])\n const featureLabel = React.useCallback((id: string) => titleById.get(id) ?? id, [titleById])\n\n const hasMissing = diagnostics.missingDependencies.length > 0\n const hasOrphaned = diagnostics.orphanedDependents.length > 0\n const showUnknown = !hideUnknownReferences && diagnostics.unknownReferences.length > 0\n if (!hasMissing && !hasOrphaned && !showUnknown) return null\n\n const handleAdd = (dep: string) => onGrantedChange((prev) => applyAddMissingDependency(prev, dep))\n const handleRestore = (dep: string) => onGrantedChange((prev) => applyRestoreDependency(prev, dep))\n const handleDropDependents = (dependents: readonly string[]) =>\n onGrantedChange((prev) => applyRemoveDependents(prev, dependents))\n\n return (\n <div className=\"space-y-3\" data-testid=\"acl-dependency-diagnostics\">\n {hasMissing && (\n <Alert status=\"warning\" style=\"lighter\">\n <div className=\"space-y-2\">\n <div className=\"text-sm font-medium\">\n {t(\n 'auth.acl.deps.missing.title',\n 'Some granted permissions need other permissions to work:',\n )}\n </div>\n <ul className=\"space-y-1 text-sm\">\n {diagnostics.missingDependencies.map((row) => (\n <li\n key={row.feature}\n className=\"flex flex-wrap items-center gap-x-2 gap-y-1\"\n data-testid={`missing-${row.feature}`}\n >\n <span>\n {t('auth.acl.deps.missing.item', '\"{feature}\" needs:', {\n feature: featureLabel(row.feature),\n })}\n </span>\n {row.missing.map((dep) => (\n <span key={dep} className=\"inline-flex items-center gap-1\">\n <span className=\"font-mono text-xs text-muted-foreground\">\n {featureLabel(dep)}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleAdd(dep)}\n data-testid={`add-missing-${row.feature}-${dep}`}\n >\n {t('auth.acl.deps.missing.add', 'Add \"{dep}\"', {\n dep: featureLabel(dep),\n })}\n </Button>\n </span>\n ))}\n </li>\n ))}\n </ul>\n </div>\n </Alert>\n )}\n\n {hasOrphaned && (\n <Alert status=\"warning\" style=\"lighter\">\n <div className=\"space-y-2\">\n <div className=\"text-sm font-medium\">\n {t(\n 'auth.acl.deps.orphaned.title',\n 'Removing a permission that other granted permissions need:',\n )}\n </div>\n <ul className=\"space-y-1 text-sm\">\n {diagnostics.orphanedDependents.map((row) => (\n <li\n key={row.dependency}\n className=\"flex flex-wrap items-center gap-x-2 gap-y-1\"\n data-testid={`orphaned-${row.dependency}`}\n >\n <span>\n {t('auth.acl.deps.orphaned.item', '\"{dependency}\" is required by:', {\n dependency: featureLabel(row.dependency),\n })}\n </span>\n <span className=\"font-mono text-xs text-muted-foreground\">\n {row.dependents\n .map((dependent) => featureLabel(dependent))\n .join(', ')}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleRestore(row.dependency)}\n data-testid={`restore-${row.dependency}`}\n >\n {t('auth.acl.deps.orphaned.restore', 'Restore \"{dependency}\"', {\n dependency: featureLabel(row.dependency),\n })}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => handleDropDependents(row.dependents)}\n data-testid={`drop-dependents-${row.dependency}`}\n >\n {t('auth.acl.deps.orphaned.drop', 'Drop dependents')}\n </Button>\n </li>\n ))}\n </ul>\n </div>\n </Alert>\n )}\n\n {showUnknown && (\n <Alert status=\"warning\" style=\"lighter\">\n <div className=\"space-y-1\">\n <div className=\"text-sm font-medium\">\n {t(\n 'auth.acl.deps.unknown.title',\n 'Some declared dependencies do not match any known permission:',\n )}\n </div>\n <ul className=\"space-y-1 text-sm font-mono text-xs text-muted-foreground\">\n {diagnostics.unknownReferences.map((row) => (\n <li key={row.feature} data-testid={`unknown-${row.feature}`}>\n {row.feature} \u2192 {row.missing.join(', ')}\n </li>\n ))}\n </ul>\n </div>\n </Alert>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -6,6 +6,7 @@ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
6
6
  import Link from "next/link";
7
7
  import { hasFeature, matchFeature } from "@open-mercato/shared/security/features";
8
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
9
+ import { AclDependencyDiagnosticsPanel } from "./AclDependencyDiagnosticsPanel.js";
9
10
  function toTitleCase(value) {
10
11
  return value.replace(/[-_.]/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
11
12
  }
@@ -313,6 +314,15 @@ function AclEditor({
313
314
  }
314
315
  )
315
316
  ] }),
317
+ !hasGlobalWildcard && /* @__PURE__ */ jsx(
318
+ AclDependencyDiagnosticsPanel,
319
+ {
320
+ granted,
321
+ catalog: features,
322
+ onGrantedChange: updateGranted,
323
+ hideUnknownReferences: process.env.NODE_ENV === "production"
324
+ }
325
+ ),
316
326
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: grouped.map((group) => {
317
327
  const moduleWildcard = isModuleWildcardEnabled(group.moduleId);
318
328
  const nestedWildcards = Array.from(