@open-mercato/core 0.6.6 → 0.6.7-develop.6552.1.5208a19369

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 (268) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/gateway_session_initialization/index.js +23 -0
  3. package/dist/generated/entities/gateway_session_initialization/index.js.map +7 -0
  4. package/dist/generated/entities.ids.generated.js +1 -0
  5. package/dist/generated/entities.ids.generated.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +12 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/api_keys/api/keys/route.js +30 -13
  9. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  10. package/dist/modules/api_keys/cli.js +2 -1
  11. package/dist/modules/api_keys/cli.js.map +2 -2
  12. package/dist/modules/attachments/api/file/[id]/route.js +9 -7
  13. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  14. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
  15. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  16. package/dist/modules/attachments/api/route.js +21 -17
  17. package/dist/modules/attachments/api/route.js.map +2 -2
  18. package/dist/modules/attachments/components/AttachmentContentPreview.js +6 -35
  19. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  20. package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
  21. package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
  22. package/dist/modules/attachments/lib/requestScope.js +10 -0
  23. package/dist/modules/attachments/lib/requestScope.js.map +7 -0
  24. package/dist/modules/audit_logs/backend/audit-logs/page.js +14 -24
  25. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  26. package/dist/modules/auth/api/autologin.js +85 -0
  27. package/dist/modules/auth/api/autologin.js.map +7 -0
  28. package/dist/modules/auth/api/users/route.js +6 -2
  29. package/dist/modules/auth/api/users/route.js.map +2 -2
  30. package/dist/modules/auth/cli.js +3 -2
  31. package/dist/modules/auth/cli.js.map +2 -2
  32. package/dist/modules/auth/lib/autologin.js +19 -0
  33. package/dist/modules/auth/lib/autologin.js.map +7 -0
  34. package/dist/modules/configs/lib/upgrade-actions.js +22 -0
  35. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  36. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +1 -0
  37. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/roles/[id].js +17 -3
  39. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  40. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +6 -1
  41. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users-invite.js +6 -1
  43. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  44. package/dist/modules/customer_accounts/services/customerInvitationService.js +1 -0
  45. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  46. package/dist/modules/customer_accounts/services/customerRbacService.js +6 -1
  47. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  48. package/dist/modules/customer_accounts/widgets/injection-table.js +0 -2
  49. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  50. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -1
  51. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  52. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  53. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  54. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +37 -43
  55. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  56. package/dist/modules/customers/components/detail/DealDetailTabs.js +23 -35
  57. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  58. package/dist/modules/customers/components/detail/DetailTabsLayout.js +6 -21
  59. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  60. package/dist/modules/customers/components/detail/MobilePersonDetail.js +26 -32
  61. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  62. package/dist/modules/customers/components/detail/PersonDetailTabs.js +29 -39
  63. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  64. package/dist/modules/customers/components/detail/useDealsAccess.js +16 -0
  65. package/dist/modules/customers/components/detail/useDealsAccess.js.map +7 -0
  66. package/dist/modules/dashboards/api/roles/widgets/route.js +22 -3
  67. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  68. package/dist/modules/dashboards/api/users/widgets/route.js +22 -3
  69. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  70. package/dist/modules/dashboards/cli.js +4 -3
  71. package/dist/modules/dashboards/cli.js.map +2 -2
  72. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +9 -1
  73. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  74. package/dist/modules/dictionaries/commands/entries.js +7 -14
  75. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  76. package/dist/modules/dictionaries/commands/entry-operations.js +7 -16
  77. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  78. package/dist/modules/dictionaries/commands/factory.js +9 -3
  79. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  80. package/dist/modules/dictionaries/fields/dictionary.js +56 -41
  81. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  82. package/dist/modules/directory/utils/organizationScope.js +10 -3
  83. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  84. package/dist/modules/entities/api/records.js +5 -4
  85. package/dist/modules/entities/api/records.js.map +2 -2
  86. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +1 -0
  87. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  88. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  89. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  90. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +1 -0
  91. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  92. package/dist/modules/entities/cli.js +2 -1
  93. package/dist/modules/entities/cli.js.map +2 -2
  94. package/dist/modules/feature_toggles/api/check/boolean/route.js +0 -14
  95. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  96. package/dist/modules/feature_toggles/defaults.json +8 -0
  97. package/dist/modules/integrations/backend/integrations/[id]/page.js +9 -80
  98. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  99. package/dist/modules/payment_gateways/data/entities.js +45 -0
  100. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  101. package/dist/modules/payment_gateways/lib/gateway-service.js +173 -38
  102. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  103. package/dist/modules/payment_gateways/lib/session-idempotency.js +104 -0
  104. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +7 -0
  105. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +11 -0
  106. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +7 -0
  107. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +16 -14
  108. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  109. package/dist/modules/planner/components/AvailabilityRulesEditor.js +18 -30
  110. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  111. package/dist/modules/query_index/api/status.js +2 -8
  112. package/dist/modules/query_index/api/status.js.map +2 -2
  113. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  114. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  115. package/dist/modules/sales/backend/sales/channels/create/page.js +6 -0
  116. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  117. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +2 -0
  118. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  119. package/dist/modules/sales/backend/sales/channels/offers/page.js +6 -0
  120. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  121. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +2 -0
  122. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  123. package/dist/modules/sales/backend/sales/channels/page.js +6 -0
  124. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  125. package/dist/modules/sales/backend/sales/channels/page.meta.js +2 -0
  126. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  127. package/dist/modules/sales/backend/sales/documents/[id]/page.js +6 -4
  128. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  129. package/dist/modules/sales/commands/documents.js +44 -0
  130. package/dist/modules/sales/commands/documents.js.map +2 -2
  131. package/dist/modules/sales/commands/returns.js +2 -27
  132. package/dist/modules/sales/commands/returns.js.map +2 -2
  133. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +15 -0
  134. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +7 -0
  135. package/dist/modules/sales/components/documents/AddressesSection.js +6 -2
  136. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  137. package/dist/modules/sales/components/documents/ItemsSection.js +1 -0
  138. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  139. package/dist/modules/sales/components/documents/LineItemDialog.js +9 -0
  140. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  141. package/dist/modules/sales/components/documents/SalesDocumentForm.js +51 -3
  142. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  143. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -8
  144. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  145. package/dist/modules/sales/components/documents/ShipmentDialog.js +59 -38
  146. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  147. package/dist/modules/sales/components/useSalesChannelsEnabled.js +44 -0
  148. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +7 -0
  149. package/dist/modules/sales/lib/salesChannelsToggle.js +18 -0
  150. package/dist/modules/sales/lib/salesChannelsToggle.js.map +7 -0
  151. package/dist/modules/sales/lib/salesChannelsToggleId.js +5 -0
  152. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +7 -0
  153. package/dist/modules/sales/lib/shipments/snapshots.js +27 -1
  154. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  155. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +22 -25
  156. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  157. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +12 -14
  158. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  159. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +10 -29
  160. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  161. package/dist/modules/sync_excel/lib/adapters/customers.js +19 -20
  162. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  163. package/dist/modules/sync_excel/lib/parser.js +185 -0
  164. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  165. package/dist/modules/sync_excel/lib/upload-storage.js +20 -1
  166. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  167. package/generated/entities/gateway_session_initialization/index.ts +10 -0
  168. package/generated/entities.ids.generated.ts +1 -0
  169. package/generated/entity-fields-registry.ts +12 -0
  170. package/package.json +9 -8
  171. package/src/modules/api_keys/api/keys/route.ts +36 -13
  172. package/src/modules/api_keys/cli.ts +2 -3
  173. package/src/modules/attachments/api/file/[id]/route.ts +11 -7
  174. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
  175. package/src/modules/attachments/api/route.ts +26 -21
  176. package/src/modules/attachments/components/AttachmentContentPreview.tsx +9 -44
  177. package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
  178. package/src/modules/attachments/lib/requestScope.ts +27 -0
  179. package/src/modules/audit_logs/backend/audit-logs/page.tsx +12 -22
  180. package/src/modules/auth/api/autologin.ts +100 -0
  181. package/src/modules/auth/api/users/route.ts +11 -2
  182. package/src/modules/auth/cli.ts +3 -2
  183. package/src/modules/auth/lib/autologin.ts +36 -0
  184. package/src/modules/configs/i18n/de.json +5 -1
  185. package/src/modules/configs/i18n/en.json +5 -1
  186. package/src/modules/configs/i18n/es.json +5 -1
  187. package/src/modules/configs/i18n/pl.json +5 -1
  188. package/src/modules/configs/lib/upgrade-actions.ts +24 -0
  189. package/src/modules/customer_accounts/AGENTS.md +8 -6
  190. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +1 -0
  191. package/src/modules/customer_accounts/api/admin/roles/[id].ts +17 -3
  192. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +6 -1
  193. package/src/modules/customer_accounts/api/portal/users-invite.ts +6 -1
  194. package/src/modules/customer_accounts/services/customerInvitationService.ts +1 -0
  195. package/src/modules/customer_accounts/services/customerRbacService.ts +9 -2
  196. package/src/modules/customer_accounts/widgets/injection-table.ts +0 -2
  197. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +11 -1
  198. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +6 -2
  199. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +39 -55
  200. package/src/modules/customers/components/detail/DealDetailTabs.tsx +24 -49
  201. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +13 -24
  202. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +25 -31
  203. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +29 -50
  204. package/src/modules/customers/components/detail/useDealsAccess.ts +19 -0
  205. package/src/modules/dashboards/api/roles/widgets/route.ts +22 -3
  206. package/src/modules/dashboards/api/users/widgets/route.ts +22 -3
  207. package/src/modules/dashboards/cli.ts +4 -9
  208. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +18 -0
  209. package/src/modules/dictionaries/commands/entries.ts +7 -16
  210. package/src/modules/dictionaries/commands/entry-operations.ts +8 -18
  211. package/src/modules/dictionaries/commands/factory.ts +8 -2
  212. package/src/modules/dictionaries/fields/dictionary.tsx +55 -32
  213. package/src/modules/dictionaries/i18n/de.json +2 -0
  214. package/src/modules/dictionaries/i18n/en.json +2 -0
  215. package/src/modules/dictionaries/i18n/es.json +2 -0
  216. package/src/modules/dictionaries/i18n/pl.json +2 -0
  217. package/src/modules/directory/utils/organizationScope.ts +36 -2
  218. package/src/modules/entities/api/records.ts +5 -7
  219. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +1 -0
  220. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +1 -0
  221. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +51 -49
  222. package/src/modules/entities/cli.ts +2 -1
  223. package/src/modules/entities/i18n/de.json +34 -0
  224. package/src/modules/entities/i18n/en.json +34 -0
  225. package/src/modules/entities/i18n/es.json +34 -0
  226. package/src/modules/entities/i18n/pl.json +34 -0
  227. package/src/modules/feature_toggles/api/check/boolean/route.ts +0 -15
  228. package/src/modules/feature_toggles/defaults.json +8 -0
  229. package/src/modules/integrations/backend/integrations/[id]/page.tsx +16 -59
  230. package/src/modules/payment_gateways/data/entities.ts +39 -0
  231. package/src/modules/payment_gateways/lib/gateway-service.ts +211 -40
  232. package/src/modules/payment_gateways/lib/session-idempotency.ts +142 -0
  233. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +236 -0
  234. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +10 -0
  235. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +14 -20
  236. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +16 -34
  237. package/src/modules/query_index/api/status.ts +2 -8
  238. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +27 -37
  239. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +3 -0
  240. package/src/modules/sales/backend/sales/channels/create/page.tsx +7 -0
  241. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +3 -0
  242. package/src/modules/sales/backend/sales/channels/offers/page.tsx +7 -0
  243. package/src/modules/sales/backend/sales/channels/page.meta.ts +4 -0
  244. package/src/modules/sales/backend/sales/channels/page.tsx +7 -0
  245. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +7 -5
  246. package/src/modules/sales/commands/documents.ts +65 -0
  247. package/src/modules/sales/commands/returns.ts +2 -32
  248. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +21 -0
  249. package/src/modules/sales/components/documents/AddressesSection.tsx +6 -2
  250. package/src/modules/sales/components/documents/ItemsSection.tsx +5 -0
  251. package/src/modules/sales/components/documents/LineItemDialog.tsx +10 -0
  252. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +53 -3
  253. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +10 -8
  254. package/src/modules/sales/components/documents/ShipmentDialog.tsx +65 -40
  255. package/src/modules/sales/components/useSalesChannelsEnabled.ts +59 -0
  256. package/src/modules/sales/i18n/de.json +5 -0
  257. package/src/modules/sales/i18n/en.json +5 -0
  258. package/src/modules/sales/i18n/es.json +5 -0
  259. package/src/modules/sales/i18n/pl.json +5 -0
  260. package/src/modules/sales/lib/salesChannelsToggle.ts +26 -0
  261. package/src/modules/sales/lib/salesChannelsToggleId.ts +1 -0
  262. package/src/modules/sales/lib/shipments/snapshots.ts +30 -0
  263. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +25 -34
  264. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +11 -17
  265. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +11 -28
  266. package/src/modules/sync_excel/lib/adapters/customers.ts +20 -22
  267. package/src/modules/sync_excel/lib/parser.ts +245 -0
  268. package/src/modules/sync_excel/lib/upload-storage.ts +23 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/customer_accounts/api/portal/users/%5Bid%5D/roles.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUser, CustomerUserRole, CustomerRole } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { assignRolesSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n\n try {\n await requireCustomerFeature(auth, ['portal.users.roles.manage'], customerRbacService)\n } catch (response) {\n return response as NextResponse\n }\n\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = assignRolesSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n // Verify target user belongs to same company\n const targetUser = await findOneWithDecryption(em, CustomerUser, {\n id: params.id,\n customerEntityId: auth.customerEntityId,\n tenantId: auth.tenantId,\n deletedAt: null,\n }, undefined, { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!targetUser) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n // Validate all roles are customer_assignable and collect for assignment\n const requestedRoleIds = parsed.data.roleIds\n const fetchedRoles = requestedRoleIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerRole,\n { id: { $in: requestedRoleIds }, tenantId: auth.tenantId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n : []\n const fetchedRolesById = new Map(fetchedRoles.map((role) => [role.id, role]))\n const validatedRoles: InstanceType<typeof CustomerRole>[] = []\n for (const roleId of requestedRoleIds) {\n const role = fetchedRolesById.get(roleId)\n if (!role || !role.customerAssignable) {\n return NextResponse.json({ ok: false, error: 'Role not found or not assignable' }, { status: 400 })\n }\n validatedRoles.push(role)\n }\n\n // Replace the role set atomically: deleting the old roles and inserting the new\n // ones in one transaction prevents a flush failure from leaving the user with\n // zero roles (portal lockout / privilege loss) (#2337).\n await em.transactional(async (tx) => {\n await tx.nativeDelete(CustomerUserRole, { user: targetUser.id as any })\n for (const role of validatedRoles) {\n const userRole = tx.create(CustomerUserRole, {\n user: targetUser,\n role,\n createdAt: new Date(),\n } as any)\n tx.persist(userRole)\n }\n })\n\n await customerRbacService.invalidateUserCache(targetUser.id)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Update portal user roles',\n description: 'Assigns new roles to a company portal user.',\n tags: ['Customer Portal'],\n requestBody: { schema: assignRolesSchema },\n responses: [{ status: 200, description: 'Roles updated', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Update portal user roles',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: { PUT: methodDoc },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerUser, CustomerUserRole, CustomerRole } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { assignRolesSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n\n try {\n await requireCustomerFeature(auth, ['portal.users.roles.manage'], customerRbacService)\n } catch (response) {\n return response as NextResponse\n }\n\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = assignRolesSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed' }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n // Verify target user belongs to same company\n const targetUser = await findOneWithDecryption(em, CustomerUser, {\n id: params.id,\n customerEntityId: auth.customerEntityId,\n tenantId: auth.tenantId,\n deletedAt: null,\n }, undefined, { tenantId: auth.tenantId, organizationId: auth.orgId })\n if (!targetUser) {\n return NextResponse.json({ ok: false, error: 'User not found' }, { status: 404 })\n }\n\n // Validate all roles are customer_assignable and collect for assignment\n const requestedRoleIds = parsed.data.roleIds\n const fetchedRoles = requestedRoleIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerRole,\n {\n id: { $in: requestedRoleIds },\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n : []\n const fetchedRolesById = new Map(fetchedRoles.map((role) => [role.id, role]))\n const validatedRoles: InstanceType<typeof CustomerRole>[] = []\n for (const roleId of requestedRoleIds) {\n const role = fetchedRolesById.get(roleId)\n if (!role || !role.customerAssignable) {\n return NextResponse.json({ ok: false, error: 'Role not found or not assignable' }, { status: 400 })\n }\n validatedRoles.push(role)\n }\n\n // Replace the role set atomically: deleting the old roles and inserting the new\n // ones in one transaction prevents a flush failure from leaving the user with\n // zero roles (portal lockout / privilege loss) (#2337).\n await em.transactional(async (tx) => {\n await tx.nativeDelete(CustomerUserRole, { user: targetUser.id as any })\n for (const role of validatedRoles) {\n const userRole = tx.create(CustomerUserRole, {\n user: targetUser,\n role,\n createdAt: new Date(),\n } as any)\n tx.persist(userRole)\n }\n })\n\n await customerRbacService.invalidateUserCache(targetUser.id)\n\n return NextResponse.json({ ok: true })\n}\n\nconst successSchema = z.object({ ok: z.literal(true) })\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Update portal user roles',\n description: 'Assigns new roles to a company portal user.',\n tags: ['Customer Portal'],\n requestBody: { schema: assignRolesSchema },\n responses: [{ status: 200, description: 'Roles updated', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions', schema: errorSchema },\n { status: 404, description: 'User not found', schema: errorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Update portal user roles',\n pathParams: z.object({ id: z.string().uuid() }),\n methods: { PUT: methodDoc },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -52,7 +52,12 @@ async function POST(req) {
52
52
  const roles = requestedRoleIds.length > 0 ? await findWithDecryption(
53
53
  em,
54
54
  CustomerRole,
55
- { id: { $in: requestedRoleIds }, tenantId: auth.tenantId, deletedAt: null },
55
+ {
56
+ id: { $in: requestedRoleIds },
57
+ tenantId: auth.tenantId,
58
+ organizationId: auth.orgId,
59
+ deletedAt: null
60
+ },
56
61
  void 0,
57
62
  { tenantId: auth.tenantId, organizationId: auth.orgId }
58
63
  ) : [];
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customer_accounts/api/portal/users-invite.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerInvitationService } from '@open-mercato/core/modules/customer_accounts/services/customerInvitationService'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { CustomerRole } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { inviteUserSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerInviteRateLimitConfig,\n customerInviteIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const rateLimitEmail = await readNormalizedEmailFromJsonRequest(req)\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerInviteIpRateLimitConfig,\n compoundConfig: customerInviteRateLimitConfig,\n compoundIdentifier: rateLimitEmail,\n })\n if (rateLimitError) return rateLimitError\n\n const container = await createRequestContainer()\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n\n try {\n await requireCustomerFeature(auth, ['portal.users.manage'], customerRbacService)\n } catch (response) {\n return response as NextResponse\n }\n\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = inviteUserSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n // Validate all roles are customer_assignable\n const requestedRoleIds = parsed.data.roleIds\n const roles = requestedRoleIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerRole,\n { id: { $in: requestedRoleIds }, tenantId: auth.tenantId, deletedAt: null } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n : []\n const rolesById = new Map(roles.map((role) => [role.id, role]))\n for (const roleId of requestedRoleIds) {\n const role = rolesById.get(roleId)\n if (!role) {\n return NextResponse.json({ ok: false, error: `Role ${roleId} not found` }, { status: 400 })\n }\n if (!role.customerAssignable) {\n return NextResponse.json({ ok: false, error: `Role \"${role.name}\" cannot be assigned by portal users` }, { status: 403 })\n }\n }\n\n const customerInvitationService = container.resolve('customerInvitationService') as CustomerInvitationService\n\n const { invitation } = await customerInvitationService.createInvitation(\n parsed.data.email,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n {\n customerEntityId: auth.customerEntityId,\n roleIds: parsed.data.roleIds,\n invitedByCustomerUserId: auth.sub,\n displayName: parsed.data.displayName || null,\n },\n )\n\n void emitCustomerAccountsEvent('customer_accounts.user.invited', {\n invitationId: invitation.id,\n email: invitation.email,\n customerEntityId: invitation.customerEntityId || null,\n invitedByType: 'portal',\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }).catch(() => undefined)\n\n return NextResponse.json({\n ok: true,\n invitation: {\n id: invitation.id,\n email: invitation.email,\n expiresAt: invitation.expiresAt,\n },\n }, { status: 201 })\n}\n\nconst successSchema = z.object({\n ok: z.literal(true),\n invitation: z.object({\n id: z.string().uuid(),\n email: z.string(),\n expiresAt: z.string().datetime(),\n }),\n})\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Invite a user to the company portal',\n description: 'Creates an invitation for a new user to join the company portal.',\n tags: ['Customer Portal'],\n requestBody: { schema: inviteUserSchema },\n responses: [{ status: 201, description: 'Invitation created', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions or non-assignable role', schema: errorSchema },\n { status: 429, description: 'Too many invitation requests', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Invite portal user',\n methods: { POST: methodDoc },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi'\nimport { getCustomerAuthFromRequest, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { CustomerInvitationService } from '@open-mercato/core/modules/customer_accounts/services/customerInvitationService'\nimport { emitCustomerAccountsEvent } from '@open-mercato/core/modules/customer_accounts/events'\nimport { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'\nimport { CustomerRole } from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { inviteUserSchema } from '@open-mercato/core/modules/customer_accounts/data/validators'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { rateLimitErrorSchema } from '@open-mercato/shared/lib/ratelimit/helpers'\nimport {\n checkAuthRateLimit,\n customerInviteRateLimitConfig,\n customerInviteIpRateLimitConfig,\n} from '@open-mercato/core/modules/customer_accounts/lib/rateLimiter'\nimport { readNormalizedEmailFromJsonRequest } from '@open-mercato/core/modules/customer_accounts/lib/rateLimitIdentifier'\n\nexport const metadata: { path?: string; requireAuth?: boolean } = { requireAuth: false }\n\nexport async function POST(req: Request) {\n const auth = await getCustomerAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ ok: false, error: 'Authentication required' }, { status: 401 })\n }\n\n const rateLimitEmail = await readNormalizedEmailFromJsonRequest(req)\n const { error: rateLimitError } = await checkAuthRateLimit({\n req,\n ipConfig: customerInviteIpRateLimitConfig,\n compoundConfig: customerInviteRateLimitConfig,\n compoundIdentifier: rateLimitEmail,\n })\n if (rateLimitError) return rateLimitError\n\n const container = await createRequestContainer()\n const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService\n\n try {\n await requireCustomerFeature(auth, ['portal.users.manage'], customerRbacService)\n } catch (response) {\n return response as NextResponse\n }\n\n if (!auth.customerEntityId) {\n return NextResponse.json({ ok: false, error: 'No company association' }, { status: 403 })\n }\n\n let body: unknown\n try {\n body = await req.json()\n } catch {\n return NextResponse.json({ ok: false, error: 'Invalid request body' }, { status: 400 })\n }\n\n const parsed = inviteUserSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Validation failed', details: parsed.error.flatten().fieldErrors }, { status: 400 })\n }\n\n const em = container.resolve('em') as import('@mikro-orm/postgresql').EntityManager\n\n // Validate all roles are customer_assignable\n const requestedRoleIds = parsed.data.roleIds\n const roles = requestedRoleIds.length > 0\n ? await findWithDecryption(\n em,\n CustomerRole,\n {\n id: { $in: requestedRoleIds },\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n )\n : []\n const rolesById = new Map(roles.map((role) => [role.id, role]))\n for (const roleId of requestedRoleIds) {\n const role = rolesById.get(roleId)\n if (!role) {\n return NextResponse.json({ ok: false, error: `Role ${roleId} not found` }, { status: 400 })\n }\n if (!role.customerAssignable) {\n return NextResponse.json({ ok: false, error: `Role \"${role.name}\" cannot be assigned by portal users` }, { status: 403 })\n }\n }\n\n const customerInvitationService = container.resolve('customerInvitationService') as CustomerInvitationService\n\n const { invitation } = await customerInvitationService.createInvitation(\n parsed.data.email,\n { tenantId: auth.tenantId, organizationId: auth.orgId },\n {\n customerEntityId: auth.customerEntityId,\n roleIds: parsed.data.roleIds,\n invitedByCustomerUserId: auth.sub,\n displayName: parsed.data.displayName || null,\n },\n )\n\n void emitCustomerAccountsEvent('customer_accounts.user.invited', {\n invitationId: invitation.id,\n email: invitation.email,\n customerEntityId: invitation.customerEntityId || null,\n invitedByType: 'portal',\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n }).catch(() => undefined)\n\n return NextResponse.json({\n ok: true,\n invitation: {\n id: invitation.id,\n email: invitation.email,\n expiresAt: invitation.expiresAt,\n },\n }, { status: 201 })\n}\n\nconst successSchema = z.object({\n ok: z.literal(true),\n invitation: z.object({\n id: z.string().uuid(),\n email: z.string(),\n expiresAt: z.string().datetime(),\n }),\n})\nconst errorSchema = z.object({ ok: z.literal(false), error: z.string() })\n\nconst methodDoc: OpenApiMethodDoc = {\n summary: 'Invite a user to the company portal',\n description: 'Creates an invitation for a new user to join the company portal.',\n tags: ['Customer Portal'],\n requestBody: { schema: inviteUserSchema },\n responses: [{ status: 201, description: 'Invitation created', schema: successSchema }],\n errors: [\n { status: 400, description: 'Validation failed', schema: errorSchema },\n { status: 401, description: 'Not authenticated', schema: errorSchema },\n { status: 403, description: 'Insufficient permissions or non-assignable role', schema: errorSchema },\n { status: 429, description: 'Too many invitation requests', schema: rateLimitErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n summary: 'Invite portal user',\n methods: { POST: methodDoc },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -103,6 +103,7 @@ class CustomerInvitationService {
103
103
  {
104
104
  id: { $in: roleIds },
105
105
  tenantId: invitation.tenantId,
106
+ organizationId: invitation.organizationId,
106
107
  deletedAt: null
107
108
  },
108
109
  void 0,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_accounts/services/customerInvitationService.ts"],
4
- "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { hash } from 'bcryptjs'\nimport {\n CustomerUser,\n CustomerUserInvitation,\n CustomerUserRole,\n CustomerRole,\n} from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { hashForLookup } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nconst BCRYPT_COST = 10\nconst INVITATION_TTL_MS = 72 * 60 * 60 * 1000 // 72 hours\n\nexport class CustomerInvitationService {\n constructor(private em: EntityManager) {}\n\n async createInvitation(\n email: string,\n scope: { tenantId: string; organizationId: string },\n options: {\n customerEntityId?: string | null\n roleIds: string[]\n invitedByUserId?: string | null\n invitedByCustomerUserId?: string | null\n displayName?: string | null\n },\n ): Promise<{ invitation: CustomerUserInvitation; rawToken: string }> {\n const token = generateSecureToken()\n const emailHash = hashForLookup(email)\n const normalizedEmail = email.toLowerCase().trim()\n const expiresAt = new Date(Date.now() + INVITATION_TTL_MS)\n const tokenHashed = hashToken(token)\n\n // Dedupe: reuse an existing pending (not accepted, not cancelled, unexpired)\n // invitation for the same recipient instead of inserting a new row. This caps\n // row/token growth and keeps a single live token per concurrently-pending\n // (tenant, organization, email) tuple.\n const existing = await findOneWithDecryption(\n this.em,\n CustomerUserInvitation,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n emailHash,\n acceptedAt: null,\n cancelledAt: null,\n expiresAt: { $gt: new Date() },\n } as any,\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n\n if (existing) {\n existing.email = normalizedEmail\n existing.token = tokenHashed\n existing.customerEntityId = options.customerEntityId || null\n existing.roleIdsJson = options.roleIds\n existing.invitedByUserId = options.invitedByUserId || null\n existing.invitedByCustomerUserId = options.invitedByCustomerUserId || null\n existing.displayName = options.displayName || null\n existing.expiresAt = expiresAt\n await this.em.flush()\n return { invitation: existing, rawToken: token }\n }\n\n const invitation = this.em.create(CustomerUserInvitation, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n email: normalizedEmail,\n emailHash,\n token: tokenHashed,\n customerEntityId: options.customerEntityId || null,\n roleIdsJson: options.roleIds,\n invitedByUserId: options.invitedByUserId || null,\n invitedByCustomerUserId: options.invitedByCustomerUserId || null,\n displayName: options.displayName || null,\n expiresAt,\n createdAt: new Date(),\n } as any) as CustomerUserInvitation\n await this.em.persist(invitation).flush()\n return { invitation, rawToken: token }\n }\n\n async findByToken(token: string): Promise<CustomerUserInvitation | null> {\n const tokenHashed = hashToken(token)\n const invitation = await findOneWithDecryption(\n this.em,\n CustomerUserInvitation,\n { token: tokenHashed } as any,\n )\n if (!invitation) return null\n if (invitation.acceptedAt) return null\n if (invitation.cancelledAt) return null\n if (invitation.expiresAt.getTime() < Date.now()) return null\n return invitation\n }\n\n async acceptInvitation(\n token: string,\n password: string,\n displayName: string,\n ): Promise<{ user: CustomerUser; invitation: CustomerUserInvitation } | null> {\n const invitation = await this.findByToken(token)\n if (!invitation) return null\n\n const passwordHash = await hash(password, BCRYPT_COST)\n const emailHash = hashForLookup(invitation.email)\n\n // Create user\n const user = this.em.create(CustomerUser, {\n email: invitation.email,\n emailHash,\n passwordHash,\n displayName: displayName || invitation.displayName || invitation.email,\n tenantId: invitation.tenantId,\n organizationId: invitation.organizationId,\n customerEntityId: invitation.customerEntityId || null,\n isActive: true,\n emailVerifiedAt: new Date(), // Invitation implicitly verifies email\n failedLoginAttempts: 0,\n createdAt: new Date(),\n } as any) as CustomerUser\n this.em.persist(user)\n\n // Assign roles\n const roleIds = Array.isArray(invitation.roleIdsJson) ? invitation.roleIdsJson : []\n const roles = roleIds.length > 0\n ? await findWithDecryption(\n this.em,\n CustomerRole,\n {\n id: { $in: roleIds } as any,\n tenantId: invitation.tenantId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: invitation.tenantId, organizationId: invitation.organizationId },\n )\n : []\n for (const role of roles) {\n const userRole = this.em.create(CustomerUserRole, {\n user,\n role,\n createdAt: new Date(),\n } as any)\n this.em.persist(userRole)\n }\n\n // Mark invitation as accepted\n invitation.acceptedAt = new Date()\n\n await this.em.flush()\n return { user, invitation }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport { hash } from 'bcryptjs'\nimport {\n CustomerUser,\n CustomerUserInvitation,\n CustomerUserRole,\n CustomerRole,\n} from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { generateSecureToken, hashToken } from '@open-mercato/core/modules/customer_accounts/lib/tokenGenerator'\nimport { hashForLookup } from '@open-mercato/shared/lib/encryption/aes'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nconst BCRYPT_COST = 10\nconst INVITATION_TTL_MS = 72 * 60 * 60 * 1000 // 72 hours\n\nexport class CustomerInvitationService {\n constructor(private em: EntityManager) {}\n\n async createInvitation(\n email: string,\n scope: { tenantId: string; organizationId: string },\n options: {\n customerEntityId?: string | null\n roleIds: string[]\n invitedByUserId?: string | null\n invitedByCustomerUserId?: string | null\n displayName?: string | null\n },\n ): Promise<{ invitation: CustomerUserInvitation; rawToken: string }> {\n const token = generateSecureToken()\n const emailHash = hashForLookup(email)\n const normalizedEmail = email.toLowerCase().trim()\n const expiresAt = new Date(Date.now() + INVITATION_TTL_MS)\n const tokenHashed = hashToken(token)\n\n // Dedupe: reuse an existing pending (not accepted, not cancelled, unexpired)\n // invitation for the same recipient instead of inserting a new row. This caps\n // row/token growth and keeps a single live token per concurrently-pending\n // (tenant, organization, email) tuple.\n const existing = await findOneWithDecryption(\n this.em,\n CustomerUserInvitation,\n {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n emailHash,\n acceptedAt: null,\n cancelledAt: null,\n expiresAt: { $gt: new Date() },\n } as any,\n undefined,\n { tenantId: scope.tenantId, organizationId: scope.organizationId },\n )\n\n if (existing) {\n existing.email = normalizedEmail\n existing.token = tokenHashed\n existing.customerEntityId = options.customerEntityId || null\n existing.roleIdsJson = options.roleIds\n existing.invitedByUserId = options.invitedByUserId || null\n existing.invitedByCustomerUserId = options.invitedByCustomerUserId || null\n existing.displayName = options.displayName || null\n existing.expiresAt = expiresAt\n await this.em.flush()\n return { invitation: existing, rawToken: token }\n }\n\n const invitation = this.em.create(CustomerUserInvitation, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n email: normalizedEmail,\n emailHash,\n token: tokenHashed,\n customerEntityId: options.customerEntityId || null,\n roleIdsJson: options.roleIds,\n invitedByUserId: options.invitedByUserId || null,\n invitedByCustomerUserId: options.invitedByCustomerUserId || null,\n displayName: options.displayName || null,\n expiresAt,\n createdAt: new Date(),\n } as any) as CustomerUserInvitation\n await this.em.persist(invitation).flush()\n return { invitation, rawToken: token }\n }\n\n async findByToken(token: string): Promise<CustomerUserInvitation | null> {\n const tokenHashed = hashToken(token)\n const invitation = await findOneWithDecryption(\n this.em,\n CustomerUserInvitation,\n { token: tokenHashed } as any,\n )\n if (!invitation) return null\n if (invitation.acceptedAt) return null\n if (invitation.cancelledAt) return null\n if (invitation.expiresAt.getTime() < Date.now()) return null\n return invitation\n }\n\n async acceptInvitation(\n token: string,\n password: string,\n displayName: string,\n ): Promise<{ user: CustomerUser; invitation: CustomerUserInvitation } | null> {\n const invitation = await this.findByToken(token)\n if (!invitation) return null\n\n const passwordHash = await hash(password, BCRYPT_COST)\n const emailHash = hashForLookup(invitation.email)\n\n // Create user\n const user = this.em.create(CustomerUser, {\n email: invitation.email,\n emailHash,\n passwordHash,\n displayName: displayName || invitation.displayName || invitation.email,\n tenantId: invitation.tenantId,\n organizationId: invitation.organizationId,\n customerEntityId: invitation.customerEntityId || null,\n isActive: true,\n emailVerifiedAt: new Date(), // Invitation implicitly verifies email\n failedLoginAttempts: 0,\n createdAt: new Date(),\n } as any) as CustomerUser\n this.em.persist(user)\n\n // Assign roles\n const roleIds = Array.isArray(invitation.roleIdsJson) ? invitation.roleIdsJson : []\n const roles = roleIds.length > 0\n ? await findWithDecryption(\n this.em,\n CustomerRole,\n {\n id: { $in: roleIds } as any,\n tenantId: invitation.tenantId,\n organizationId: invitation.organizationId,\n deletedAt: null,\n } as any,\n undefined,\n { tenantId: invitation.tenantId, organizationId: invitation.organizationId },\n )\n : []\n for (const role of roles) {\n const userRole = this.em.create(CustomerUserRole, {\n user,\n role,\n createdAt: new Date(),\n } as any)\n this.em.persist(userRole)\n }\n\n // Mark invitation as accepted\n invitation.acceptedAt = new Date()\n\n await this.em.flush()\n return { user, invitation }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -19,7 +19,7 @@ class CustomerRbacService {
19
19
  this.cache = cache || null;
20
20
  }
21
21
  getCacheKey(userId, scope) {
22
- return `customer_rbac:${userId}:${scope.tenantId}:${scope.organizationId}`;
22
+ return `customer_rbac:v2:${userId}:${scope.tenantId}:${scope.organizationId}`;
23
23
  }
24
24
  getUserTag(userId) {
25
25
  return `customer_rbac:user:${userId}`;
@@ -61,6 +61,11 @@ class CustomerRbacService {
61
61
  }
62
62
  const links = await em.find(CustomerUserRole, {
63
63
  user: userId,
64
+ role: {
65
+ tenantId: scope.tenantId,
66
+ organizationId: scope.organizationId,
67
+ deletedAt: null
68
+ },
64
69
  deletedAt: null
65
70
  }, { populate: ["role"] });
66
71
  const roleIds = links.map((l) => l.role?.id).filter(Boolean);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_accounts/services/customerRbacService.ts"],
4
- "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport {\n CustomerUserAcl,\n CustomerRoleAcl,\n CustomerUserRole,\n} from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'\n\ninterface CustomerAclData {\n isPortalAdmin: boolean\n features: string[]\n}\n\nfunction isCustomerAclData(value: unknown): value is CustomerAclData {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<CustomerAclData>\n if (typeof record.isPortalAdmin !== 'boolean') return false\n if (!Array.isArray(record.features) || record.features.some((f) => typeof f !== 'string')) return false\n return true\n}\n\nexport class CustomerRbacService {\n private cacheTtlMs: number = 5 * 60 * 1000\n private cache: CacheStrategy | null = null\n\n constructor(private em: EntityManager, cache?: CacheStrategy) {\n this.cache = cache || null\n }\n\n private getCacheKey(userId: string, scope: { tenantId: string; organizationId: string }): string {\n return `customer_rbac:${userId}:${scope.tenantId}:${scope.organizationId}`\n }\n\n private getUserTag(userId: string): string {\n return `customer_rbac:user:${userId}`\n }\n\n private getTenantTag(tenantId: string): string {\n return `customer_rbac:tenant:${tenantId}`\n }\n\n private async getFromCache(cacheKey: string): Promise<CustomerAclData | null> {\n if (!this.cache) return null\n const cached = await this.cache.get(cacheKey)\n if (!cached) return null\n return isCustomerAclData(cached) ? cached : null\n }\n\n private async setCache(\n cacheKey: string,\n data: CustomerAclData,\n userId: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<void> {\n if (!this.cache) return\n const tags = [\n this.getUserTag(userId),\n this.getTenantTag(scope.tenantId),\n 'customer_rbac:all',\n ]\n await this.cache.set(cacheKey, data, { ttl: this.cacheTtlMs, tags })\n }\n\n async loadAcl(\n userId: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<CustomerAclData> {\n const cacheKey = this.getCacheKey(userId, scope)\n const cached = await this.getFromCache(cacheKey)\n if (cached) return cached\n\n const em = this.em.fork()\n\n // Per-user ACL first\n const userAcl = await em.findOne(CustomerUserAcl, {\n user: userId as any,\n tenantId: scope.tenantId,\n })\n if (userAcl) {\n const result: CustomerAclData = {\n isPortalAdmin: !!userAcl.isPortalAdmin,\n features: Array.isArray(userAcl.featuresJson) ? userAcl.featuresJson : [],\n }\n await this.setCache(cacheKey, result, userId, scope)\n return result\n }\n\n // Aggregate role ACLs\n const links = await em.find(CustomerUserRole, {\n user: userId as any,\n deletedAt: null,\n }, { populate: ['role'] })\n const roleIds = links.map((l) => (l.role as any)?.id).filter(Boolean)\n\n let isPortalAdmin = false\n const features: string[] = []\n if (roleIds.length) {\n const roleAcls = await em.find(CustomerRoleAcl, {\n tenantId: scope.tenantId,\n role: { $in: roleIds as any },\n } as any)\n for (const acl of roleAcls) {\n isPortalAdmin = isPortalAdmin || !!acl.isPortalAdmin\n if (Array.isArray(acl.featuresJson)) {\n for (const f of acl.featuresJson) {\n if (!features.includes(f)) features.push(f)\n }\n }\n }\n }\n\n const result: CustomerAclData = { isPortalAdmin, features }\n await this.setCache(cacheKey, result, userId, scope)\n return result\n }\n\n async userHasAllFeatures(\n userId: string,\n required: string[],\n scope: { tenantId: string; organizationId: string },\n ): Promise<boolean> {\n if (!required.length) return true\n const acl = await this.loadAcl(userId, scope)\n if (acl.isPortalAdmin) return true\n return hasAllFeatures(required, acl.features)\n }\n\n async invalidateUserCache(userId: string): Promise<void> {\n if (!this.cache) return\n await this.cache.deleteByTags([this.getUserTag(userId)])\n }\n\n async invalidateRoleCache(roleId: string): Promise<void> {\n if (!this.cache) return\n // When a role changes, invalidate all customer RBAC caches since we don't track role\u2192user mappings\n await this.cache.deleteByTags(['customer_rbac:all'])\n }\n\n async invalidateTenantCache(tenantId: string): Promise<void> {\n if (!this.cache) return\n await this.cache.deleteByTags([this.getTenantTag(tenantId)])\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { EntityManager } from '@mikro-orm/postgresql'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport {\n CustomerUserAcl,\n CustomerRoleAcl,\n CustomerUserRole,\n} from '@open-mercato/core/modules/customer_accounts/data/entities'\nimport { hasAllFeatures } from '@open-mercato/shared/lib/auth/featureMatch'\n\ninterface CustomerAclData {\n isPortalAdmin: boolean\n features: string[]\n}\n\nfunction isCustomerAclData(value: unknown): value is CustomerAclData {\n if (typeof value !== 'object' || value === null) return false\n const record = value as Partial<CustomerAclData>\n if (typeof record.isPortalAdmin !== 'boolean') return false\n if (!Array.isArray(record.features) || record.features.some((f) => typeof f !== 'string')) return false\n return true\n}\n\nexport class CustomerRbacService {\n private cacheTtlMs: number = 5 * 60 * 1000\n private cache: CacheStrategy | null = null\n\n constructor(private em: EntityManager, cache?: CacheStrategy) {\n this.cache = cache || null\n }\n\n private getCacheKey(userId: string, scope: { tenantId: string; organizationId: string }): string {\n // Keep the authorization query versioned in the key so cache entries created\n // before scope hardening cannot preserve grants that are no longer valid.\n return `customer_rbac:v2:${userId}:${scope.tenantId}:${scope.organizationId}`\n }\n\n private getUserTag(userId: string): string {\n return `customer_rbac:user:${userId}`\n }\n\n private getTenantTag(tenantId: string): string {\n return `customer_rbac:tenant:${tenantId}`\n }\n\n private async getFromCache(cacheKey: string): Promise<CustomerAclData | null> {\n if (!this.cache) return null\n const cached = await this.cache.get(cacheKey)\n if (!cached) return null\n return isCustomerAclData(cached) ? cached : null\n }\n\n private async setCache(\n cacheKey: string,\n data: CustomerAclData,\n userId: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<void> {\n if (!this.cache) return\n const tags = [\n this.getUserTag(userId),\n this.getTenantTag(scope.tenantId),\n 'customer_rbac:all',\n ]\n await this.cache.set(cacheKey, data, { ttl: this.cacheTtlMs, tags })\n }\n\n async loadAcl(\n userId: string,\n scope: { tenantId: string; organizationId: string },\n ): Promise<CustomerAclData> {\n const cacheKey = this.getCacheKey(userId, scope)\n const cached = await this.getFromCache(cacheKey)\n if (cached) return cached\n\n const em = this.em.fork()\n\n // Per-user ACL first\n const userAcl = await em.findOne(CustomerUserAcl, {\n user: userId as any,\n tenantId: scope.tenantId,\n })\n if (userAcl) {\n const result: CustomerAclData = {\n isPortalAdmin: !!userAcl.isPortalAdmin,\n features: Array.isArray(userAcl.featuresJson) ? userAcl.featuresJson : [],\n }\n await this.setCache(cacheKey, result, userId, scope)\n return result\n }\n\n // Aggregate role ACLs\n const links = await em.find(CustomerUserRole, {\n user: userId as any,\n role: {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n },\n deletedAt: null,\n } as any, { populate: ['role'] })\n const roleIds = links.map((l) => (l.role as any)?.id).filter(Boolean)\n\n let isPortalAdmin = false\n const features: string[] = []\n if (roleIds.length) {\n const roleAcls = await em.find(CustomerRoleAcl, {\n tenantId: scope.tenantId,\n role: { $in: roleIds as any },\n } as any)\n for (const acl of roleAcls) {\n isPortalAdmin = isPortalAdmin || !!acl.isPortalAdmin\n if (Array.isArray(acl.featuresJson)) {\n for (const f of acl.featuresJson) {\n if (!features.includes(f)) features.push(f)\n }\n }\n }\n }\n\n const result: CustomerAclData = { isPortalAdmin, features }\n await this.setCache(cacheKey, result, userId, scope)\n return result\n }\n\n async userHasAllFeatures(\n userId: string,\n required: string[],\n scope: { tenantId: string; organizationId: string },\n ): Promise<boolean> {\n if (!required.length) return true\n const acl = await this.loadAcl(userId, scope)\n if (acl.isPortalAdmin) return true\n return hasAllFeatures(required, acl.features)\n }\n\n async invalidateUserCache(userId: string): Promise<void> {\n if (!this.cache) return\n await this.cache.deleteByTags([this.getUserTag(userId)])\n }\n\n async invalidateRoleCache(roleId: string): Promise<void> {\n if (!this.cache) return\n // When a role changes, invalidate all customer RBAC caches since we don't track role\u2192user mappings\n await this.cache.deleteByTags(['customer_rbac:all'])\n }\n\n async invalidateTenantCache(tenantId: string): Promise<void> {\n if (!this.cache) return\n await this.cache.deleteByTags([this.getTenantTag(tenantId)])\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["result"]
7
7
  }
@@ -15,10 +15,8 @@ const companyUsersWidget = {
15
15
  const injectionTable = {
16
16
  "customers.person": [accountStatusWidget],
17
17
  "crud-form:customers.person": [accountStatusWidget],
18
- "crud-form:customers:customer_person_profile:fields": [accountStatusWidget],
19
18
  "customers.company": [companyUsersWidget],
20
19
  "crud-form:customers.company": [companyUsersWidget],
21
- "crud-form:customers:customer_company_profile:fields": [companyUsersWidget],
22
20
  // Step 4.10 — Portal AiChat injection example.
23
21
  // Mapped to the portal profile page's `pageAfter('profile')` spot;
24
22
  // third-party modules targeting other portal pages can copy this entry.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customer_accounts/widgets/injection-table.ts"],
4
- "sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\nconst accountStatusWidget = {\n widgetId: 'customer_accounts.injection.account-status',\n kind: 'group',\n column: 2,\n groupLabel: 'customer_accounts.widgets.accountStatus',\n priority: 200,\n} as const\n\nconst companyUsersWidget = {\n widgetId: 'customer_accounts.injection.company-users',\n kind: 'group',\n column: 2,\n groupLabel: 'customer_accounts.widgets.portalUsers',\n priority: 200,\n} as const\n\nexport const injectionTable: ModuleInjectionTable = {\n 'customers.person': [accountStatusWidget],\n 'crud-form:customers.person': [accountStatusWidget],\n 'crud-form:customers:customer_person_profile:fields': [accountStatusWidget],\n 'customers.company': [companyUsersWidget],\n 'crud-form:customers.company': [companyUsersWidget],\n 'crud-form:customers:customer_company_profile:fields': [companyUsersWidget],\n // Step 4.10 \u2014 Portal AiChat injection example.\n // Mapped to the portal profile page's `pageAfter('profile')` spot;\n // third-party modules targeting other portal pages can copy this entry.\n 'portal:profile:after': [\n {\n widgetId: 'customer_accounts.injection.portal-ai-assistant-trigger',\n priority: 100,\n },\n ],\n}\n\nexport default injectionTable\n"],
5
- "mappings": "AAEA,MAAM,sBAAsB;AAAA,EAC1B,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AACZ;AAEA,MAAM,qBAAqB;AAAA,EACzB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AACZ;AAEO,MAAM,iBAAuC;AAAA,EAClD,oBAAoB,CAAC,mBAAmB;AAAA,EACxC,8BAA8B,CAAC,mBAAmB;AAAA,EAClD,sDAAsD,CAAC,mBAAmB;AAAA,EAC1E,qBAAqB,CAAC,kBAAkB;AAAA,EACxC,+BAA+B,CAAC,kBAAkB;AAAA,EAClD,uDAAuD,CAAC,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAI1E,wBAAwB;AAAA,IACtB;AAAA,MACE,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
4
+ "sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\nconst accountStatusWidget = {\n widgetId: 'customer_accounts.injection.account-status',\n kind: 'group',\n column: 2,\n groupLabel: 'customer_accounts.widgets.accountStatus',\n priority: 200,\n} as const\n\nconst companyUsersWidget = {\n widgetId: 'customer_accounts.injection.company-users',\n kind: 'group',\n column: 2,\n groupLabel: 'customer_accounts.widgets.portalUsers',\n priority: 200,\n} as const\n\nexport const injectionTable: ModuleInjectionTable = {\n 'customers.person': [accountStatusWidget],\n 'crud-form:customers.person': [accountStatusWidget],\n 'customers.company': [companyUsersWidget],\n 'crud-form:customers.company': [companyUsersWidget],\n // Step 4.10 \u2014 Portal AiChat injection example.\n // Mapped to the portal profile page's `pageAfter('profile')` spot;\n // third-party modules targeting other portal pages can copy this entry.\n 'portal:profile:after': [\n {\n widgetId: 'customer_accounts.injection.portal-ai-assistant-trigger',\n priority: 100,\n },\n ],\n}\n\nexport default injectionTable\n"],
5
+ "mappings": "AAEA,MAAM,sBAAsB;AAAA,EAC1B,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AACZ;AAEA,MAAM,qBAAqB;AAAA,EACzB,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AACZ;AAEO,MAAM,iBAAuC;AAAA,EAClD,oBAAoB,CAAC,mBAAmB;AAAA,EACxC,8BAA8B,CAAC,mBAAmB;AAAA,EAClD,qBAAqB,CAAC,kBAAkB;AAAA,EACxC,+BAA+B,CAAC,kBAAkB;AAAA;AAAA;AAAA;AAAA,EAIlD,wBAAwB;AAAA,IACtB;AAAA,MACE,UAAU;AAAA,MACV,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
6
6
  "names": []
7
7
  }
@@ -29,6 +29,7 @@ import { CompanyPeopleSection } from "../../../../components/detail/CompanyPeopl
29
29
  import { coerceDisplayName } from "../../../../lib/displayName.js";
30
30
  import { CompanyDetailHeader } from "../../../../components/detail/CompanyDetailHeader.js";
31
31
  import { CompanyDetailTabs, resolveLegacyTab } from "../../../../components/detail/CompanyDetailTabs.js";
32
+ import { useDealsAccess } from "../../../../components/detail/useDealsAccess.js";
32
33
  import { CompanyKpiBar } from "../../../../components/detail/CompanyKpiBar.js";
33
34
  import { ScheduleActivityDialog } from "../../../../components/detail/ScheduleActivityDialog.js";
34
35
  import { ChangelogTab } from "../../../../components/detail/ChangelogTab.js";
@@ -59,6 +60,7 @@ function CompanyDetailV2Page({ params }) {
59
60
  }, [searchParams]);
60
61
  const [activeTab, setActiveTab] = React.useState(initialTab);
61
62
  const [sectionAction, setSectionAction] = React.useState(null);
63
+ const { canViewDeals, isReady: isDealsAccessReady } = useDealsAccess();
62
64
  const [isDirty, setIsDirty] = React.useState(false);
63
65
  const [isSaving, setIsSaving] = React.useState(false);
64
66
  const formWrapperRef = React.useRef(null);
@@ -260,6 +262,10 @@ function CompanyDetailV2Page({ params }) {
260
262
  React.useEffect(() => {
261
263
  setSectionAction(null);
262
264
  }, [activeTab]);
265
+ React.useEffect(() => {
266
+ if (!isDealsAccessReady || canViewDeals) return;
267
+ setActiveTab((current) => current === "deals" ? "people" : current);
268
+ }, [isDealsAccessReady, canViewDeals]);
263
269
  const dealsScope = React.useMemo(
264
270
  () => currentCompanyId ? { kind: "company", entityId: currentCompanyId } : null,
265
271
  [currentCompanyId]
@@ -437,7 +443,7 @@ function CompanyDetailV2Page({ params }) {
437
443
  }
438
444
  }
439
445
  ),
440
- activeTab === "deals" && /* @__PURE__ */ jsx(
446
+ activeTab === "deals" && canViewDeals && /* @__PURE__ */ jsx(
441
447
  DealsSection,
442
448
  {
443
449
  scope: dealsScope,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/customers/backend/customers/companies-v2/%5Bid%5D/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams, usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Building2, Hash, Users, BarChart3, StickyNote } from 'lucide-react'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState, type SectionAction } from '@open-mercato/ui/backend/detail'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { E } from '#generated/entities.ids.generated'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DealsSection } from '../../../../components/detail/DealsSection'\nimport { ActivityLogTab } from '../../../../components/detail/ActivityLogTab'\nimport { CompanyPeopleSection, type CompanyPersonSummary } from '../../../../components/detail/CompanyPeopleSection'\nimport type { TagSummary } from '../../../../components/detail/types'\nimport type { TagsSectionController } from '@open-mercato/ui/backend/detail'\nimport { coerceDisplayName } from '../../../../lib/displayName'\nimport { CompanyDetailHeader } from '../../../../components/detail/CompanyDetailHeader'\nimport { CompanyDetailTabs, resolveLegacyTab, type CompanyTabId } from '../../../../components/detail/CompanyDetailTabs'\nimport { CompanyKpiBar } from '../../../../components/detail/CompanyKpiBar'\nimport { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'\nimport { ChangelogTab } from '../../../../components/detail/ChangelogTab'\nimport { useInteractionMutations } from '../../../../components/detail/hooks/useInteractionMutations'\nimport {\n buildCompanyEditPayload,\n createCompanyEditFields,\n createCompanyDaneFiremyGroups,\n createCompanyEditSchema,\n mapCompanyOverviewToFormValues,\n type CompanyEditFormValues,\n type CompanyOverview,\n} from '../../../../components/formConfig'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport default function CompanyDetailV2Page({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const pathname = usePathname()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])\n\n const [data, setData] = React.useState<CompanyOverview | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n\n // Tab state\n const initialTab = React.useMemo(() => {\n return resolveLegacyTab(searchParams?.get('tab'))\n }, [searchParams])\n const [activeTab, setActiveTab] = React.useState<CompanyTabId>(initialTab)\n const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)\n\n // Form state\n const [isDirty, setIsDirty] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const formWrapperRef = React.useRef<HTMLDivElement>(null)\n const { organizationId } = useOrganizationScopeDetail()\n const formSchema = React.useMemo(() => createCompanyEditSchema(), [])\n const formFields = React.useMemo(() => createCompanyEditFields(t), [t])\n const formGroups = React.useMemo(() => createCompanyDaneFiremyGroups(t), [t])\n const initialValues = React.useMemo(\n () => (data ? mapCompanyOverviewToFormValues(data) : undefined),\n [data],\n )\n const zoneSections = React.useMemo<ZoneSectionDescriptor[]>(() => [\n { id: 'identity', icon: Building2, label: t('customers.companies.form.sections.identity', 'Identity') },\n { id: 'contact', icon: Hash, label: t('customers.companies.form.sections.contact', 'Contact') },\n { id: 'classification', icon: Users, label: t('customers.companies.form.sections.classification', 'Classification') },\n { id: 'businessProfile', icon: BarChart3, label: t('customers.companies.form.sections.businessProfile', 'Business profile') },\n { id: 'notes', icon: StickyNote, label: t('customers.companies.form.groups.notes', 'Notes') },\n { id: 'customFields', icon: Hash, label: t('customers.companies.form.groups.customAttributes', 'Custom attributes') },\n ], [t])\n const [scheduleDialogOpen, setScheduleDialogOpen] = React.useState(false)\n const [scheduleEditData, setScheduleEditData] = React.useState<ScheduleActivityEditData | null>(null)\n const [activityRefreshKey, setActivityRefreshKey] = React.useState(0)\n const [dealCount, setDealCount] = React.useState(0)\n\n const currentCompanyId = data?.company?.id ?? null\n const mutationContextId = React.useMemo(\n () => (currentCompanyId ? `customer-company:${currentCompanyId}` : `customer-company:${id ?? 'pending'}`),\n [currentCompanyId, id],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n companyId?: string | null\n resourceKind: string\n resourceId?: string\n data: CompanyOverview | null\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const companyDisplayName = coerceDisplayName(data?.company?.displayName)\n const companyName = companyDisplayName.trim().length\n ? companyDisplayName\n : t('customers.companies.list.deleteFallbackName', 'this company')\n\n // Data loading\n const initialLoadDoneRef = React.useRef(false)\n const loadData = React.useCallback(async () => {\n if (!id) {\n setIsNotFound(true)\n setIsLoading(false)\n return\n }\n if (!initialLoadDoneRef.current) {\n setIsLoading(true)\n }\n setError(null)\n setIsNotFound(false)\n try {\n const payload = await readApiResultOrThrow<CompanyOverview>(\n `/api/customers/companies/${encodeURIComponent(id)}`,\n undefined,\n { errorMessage: t('customers.companies.detail.error.load', 'Failed to load company.') },\n )\n setData(payload as CompanyOverview)\n } catch (err) {\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('customers.companies.detail.error.load', 'Failed to load company.')\n setError(message)\n }\n if (!initialLoadDoneRef.current) setData(null)\n } finally {\n setIsLoading(false)\n initialLoadDoneRef.current = true\n }\n }, [id, t])\n\n React.useEffect(() => {\n loadData().catch((err) => logger.warn('loadData failed', { component: 'companies-v2', err }))\n }, [loadData])\n\n React.useEffect(() => {\n setDealCount(data?.counts?.deals ?? 0)\n }, [data?.counts?.deals])\n\n const handleActivityCreated = React.useCallback(() => {\n setActivityRefreshKey((k) => k + 1)\n loadData().catch((err) => logger.warn('reload after activity failed', { component: 'companies-v2', err }))\n }, [loadData])\n\n // Planned activities for the activity-log tab\n const plannedActivities = React.useMemo(() => {\n return data?.plannedActivitiesPreview ?? []\n }, [data?.plannedActivitiesPreview])\n\n // Injection context for UMES\n const injectionContext = React.useMemo(\n () => ({\n formId: mutationContextId,\n companyId: currentCompanyId,\n resourceKind: 'customers.company',\n resourceId: currentCompanyId ?? (id ?? undefined),\n data,\n retryLastMutation,\n }),\n [currentCompanyId, data, id, mutationContextId, retryLastMutation],\n )\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: injectionContext,\n })\n },\n [injectionContext, runMutation],\n )\n\n const { completeInteraction: handleMarkDone, cancelInteraction: handleCancelActivity } = useInteractionMutations({\n runMutationWithContext,\n onAfterChange: handleActivityCreated,\n logContext: 'customers.companies-v2',\n })\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `customers.company` + id\n // explicitly (the hardcoded path allowlist misses the `companies-v2` route).\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'customers.company',\n resourceId: currentCompanyId,\n updatedAt: (data?.company as { updatedAt?: string | null; updated_at?: string | null } | undefined)?.updatedAt\n ?? (data?.company as { updated_at?: string | null } | undefined)?.updated_at\n ?? null,\n data: data as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n const handleEditActivity = React.useCallback((activity: { id: string; interactionType?: string; title?: string | null; body?: string | null; scheduledAt?: string | null; occurredAt?: string | null; [key: string]: unknown }) => {\n const raw = activity as Record<string, unknown>\n const durationValue = typeof raw.duration === 'number'\n ? raw.duration\n : typeof raw.durationMinutes === 'number'\n ? raw.durationMinutes as number\n : null\n // Forward `customValues` so per-type chip state (callPhoneNumber, callDirection,\n // taskPriority, \u2026) round-trips on edit (#1808 phone persistence).\n // Forward `occurredAt` so historical activity edits prefill from the original\n // moment instead of \"today\" (#1807 prefill).\n const editPayload = {\n id: activity.id,\n updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt as string : typeof raw.updated_at === 'string' ? raw.updated_at as string : null,\n interactionType: typeof activity.interactionType === 'string' ? activity.interactionType : undefined,\n title: typeof activity.title === 'string' ? activity.title : null,\n body: typeof activity.body === 'string' ? activity.body : null,\n scheduledAt: typeof activity.scheduledAt === 'string' ? activity.scheduledAt : null,\n occurredAt: typeof activity.occurredAt === 'string' ? activity.occurredAt : null,\n durationMinutes: durationValue,\n location: typeof raw.location === 'string' ? raw.location as string : null,\n allDay: typeof raw.allDay === 'boolean' ? raw.allDay as boolean : null,\n recurrenceRule: typeof raw.recurrenceRule === 'string' ? raw.recurrenceRule as string : null,\n recurrenceEnd: typeof raw.recurrenceEnd === 'string' ? raw.recurrenceEnd as string : null,\n participants: Array.isArray(raw.participants) ? raw.participants as ScheduleActivityEditData['participants'] : null,\n reminderMinutes: typeof raw.reminderMinutes === 'number' ? raw.reminderMinutes as number : null,\n visibility: typeof raw.visibility === 'string' ? raw.visibility as string : null,\n linkedEntities: Array.isArray(raw.linkedEntities) ? raw.linkedEntities as ScheduleActivityEditData['linkedEntities'] : null,\n guestPermissions: raw.guestPermissions && typeof raw.guestPermissions === 'object'\n ? raw.guestPermissions as ScheduleActivityEditData['guestPermissions']\n : null,\n customValues: raw.customValues && typeof raw.customValues === 'object'\n ? raw.customValues as Record<string, unknown>\n : null,\n phoneNumber: typeof raw.phoneNumber === 'string' ? raw.phoneNumber as string : null,\n } as ScheduleActivityEditData & { customValues?: Record<string, unknown> | null; phoneNumber?: string | null }\n setScheduleEditData(editPayload)\n setScheduleDialogOpen(true)\n }, [])\n\n const openNewScheduleDialog = React.useCallback(() => {\n setScheduleEditData(null)\n setScheduleDialogOpen(true)\n }, [])\n\n const handleAddActivity = React.useCallback((kind: 'meeting' | 'call' | 'task' | 'email') => {\n setScheduleEditData({\n id: '',\n interactionType: kind,\n title: null,\n body: null,\n scheduledAt: null,\n durationMinutes: null,\n location: null,\n allDay: null,\n recurrenceRule: null,\n recurrenceEnd: null,\n participants: null,\n reminderMinutes: null,\n visibility: null,\n linkedEntities: null,\n guestPermissions: null,\n })\n setScheduleDialogOpen(true)\n }, [])\n\n // Injected tabs from UMES\n const { widgets: injectedTabWidgets } = useInjectionWidgets('detail:customers.company:tabs', {\n context: injectionContext,\n triggerOnLoad: true,\n })\n\n const injectedTabs = React.useMemo(\n () =>\n (injectedTabWidgets ?? [])\n .filter((widget) => (widget.placement?.kind ?? 'tab') === 'tab')\n .map((widget) => {\n const tabId = widget.placement?.groupId ?? widget.widgetId\n const label = widget.placement?.groupLabel ?? widget.module.metadata.title ?? tabId\n const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0\n const render = () => (\n <widget.module.Widget\n context={injectionContext}\n data={data}\n onDataChange={(next: unknown) => setData(next as CompanyOverview)}\n />\n )\n return { id: tabId, label, priority, render }\n })\n .sort((a, b) => b.priority - a.priority),\n [data, injectedTabWidgets, injectionContext],\n )\n\n const injectedTabMap = React.useMemo(() => new Map(injectedTabs.map((tab) => [tab.id, tab.render])), [injectedTabs])\n\n // Tags\n const handleTagsChange = React.useCallback((nextTags: TagSummary[]) => {\n setData((prev) => (prev ? { ...prev, tags: nextTags } : prev))\n }, [])\n const tagsSectionControllerRef = React.useRef<TagsSectionController | null>(null)\n\n // Section action (for tabs that expose add/create buttons)\n const handleSectionActionChange = React.useCallback((action: SectionAction | null) => {\n setSectionAction((prev) => (action !== null ? action : prev))\n }, [])\n\n const handleSectionAction = React.useCallback(() => {\n if (!sectionAction || sectionAction.disabled) return\n sectionAction.onClick()\n }, [sectionAction])\n\n React.useEffect(() => {\n setSectionAction(null)\n }, [activeTab])\n\n // Deals scope\n const dealsScope = React.useMemo(\n () => (currentCompanyId ? ({ kind: 'company', entityId: currentCompanyId } as const) : null),\n [currentCompanyId],\n )\n\n // Delete handler (shared between header and form)\n const handleDelete = React.useCallback(async () => {\n const companyId = data?.company?.id ?? ''\n if (!companyId) return\n const approved = await confirm({\n title: t('customers.companies.detail.deleteConfirmTitle', 'Delete company?'),\n description: t('customers.companies.detail.deleteConfirmDescription', 'This action cannot be undone.'),\n confirmText: t('customers.companies.detail.actions.delete', 'Delete company'),\n cancelText: t('customers.companies.detail.actions.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!approved) return\n try {\n await runMutationWithContext(\n () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader((data?.company as { updatedAt?: string } | undefined)?.updatedAt),\n () => deleteCrud('customers/companies', { id: companyId }),\n ),\n { id: companyId, operation: 'deleteCompany' },\n )\n } catch (err) {\n // The guarded mutation already routes a 409 to the unified conflict bar;\n // surface any other server error (e.g. \"Cannot delete company: linked\n // deals\u2026\") as a flash instead of letting it crash the page.\n if (!surfaceRecordConflict(err, t)) {\n flash(\n err instanceof Error && err.message.trim().length > 0\n ? err.message\n : t('customers.companies.detail.deleteError', 'Failed to delete company.'),\n 'error',\n )\n }\n return\n }\n flash(t('customers.companies.list.deleteSuccess', 'Company deleted.'), 'success')\n router.push('/backend/customers/companies')\n }, [confirm, data?.company?.id, router, runMutationWithContext, t])\n\n // Form submit handler (lifted from CompanyDataTab)\n const handleFormSubmit = React.useCallback(\n async (values: CompanyEditFormValues) => {\n setIsSaving(true)\n try {\n let payload: Record<string, unknown>\n try {\n payload = buildCompanyEditPayload(values, organizationId)\n } catch (err) {\n if (err instanceof Error) {\n if (err.message === 'DISPLAY_NAME_REQUIRED') {\n const message = t('customers.companies.form.displayName.error')\n throw createCrudFormError(message, { displayName: message })\n }\n if (err.message === 'ANNUAL_REVENUE_INVALID') {\n const message = t('customers.companies.form.annualRevenue.error')\n throw createCrudFormError(message, { annualRevenue: message })\n }\n }\n throw err\n }\n await updateCrud('customers/companies', payload)\n flash(t('customers.companies.form.updateSuccess', 'Company updated.'), 'success')\n await loadData()\n } finally {\n setIsSaving(false)\n }\n },\n [loadData, organizationId, t],\n )\n\n // Save handler (triggers form submit via ref)\n const handleHeaderSave = React.useCallback(() => {\n const form = formWrapperRef.current?.querySelector('form')\n if (form) form.requestSubmit()\n }, [])\n\n // Loading / error states\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('customers.companies.detail.loading', 'Loading company\u2026')} />\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('customers.companies.detail.error.notFound', 'Company not found.')}\n backHref=\"/backend/customers/companies\"\n backLabel={t('customers.companies.detail.actions.backToList', 'Back to companies')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data?.company?.id) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={error ?? t('customers.companies.detail.error.load', 'Failed to load company.')}\n action={(\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/customers/companies\">\n {t('customers.companies.detail.actions.backToList', 'Back to companies')}\n </Link>\n </Button>\n )}\n />\n </PageBody>\n </Page>\n )\n }\n\n const companyId = data.company.id\n const useCanonicalInteractions = data.interactionMode === 'canonical'\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-4\">\n {/* UMES header injection */}\n <InjectionSpot spotId=\"detail:customers.company:header\" context={injectionContext} data={data} />\n <InjectionSpot spotId=\"detail:customers.company:status-badges\" context={injectionContext} data={data} />\n\n {/* Persistent company header */}\n <CompanyDetailHeader\n data={data}\n onTagsChange={handleTagsChange}\n tagsSectionControllerRef={tagsSectionControllerRef}\n onSave={handleHeaderSave}\n onDelete={handleDelete}\n isDirty={isDirty}\n isSaving={isSaving}\n onDataReload={() => { loadData().catch((err) => logger.warn('onDataReload failed', { component: 'companies-v2', err })) }}\n />\n\n {/* KPI bar \u2014 always visible above zones */}\n <CompanyKpiBar data={data} />\n\n {/* Two-zone layout: zone1 = form, zone2 = tabs */}\n <CollapsibleZoneLayout\n pageType=\"company-v2\"\n entityName={companyName}\n isDirty={isDirty}\n sections={zoneSections}\n zone1={\n <div ref={formWrapperRef}>\n <CrudForm<CompanyEditFormValues>\n embedded\n trackDirtyWhenEmbedded\n injectionSpotId=\"crud-form:customers.company\"\n entityIds={[E.customers.customer_entity, E.customers.customer_company_profile]}\n schema={formSchema}\n fields={formFields}\n groups={formGroups}\n initialValues={initialValues}\n onSubmit={handleFormSubmit}\n onDelete={handleDelete}\n optimisticLockUpdatedAt={(data?.company as { updatedAt?: string } | undefined)?.updatedAt}\n hideFooterActions\n collapsibleGroups={{ pageType: 'company-v2', chevronPosition: 'right' }}\n sortableGroups={{ pageType: 'company-v2' }}\n onDirtyChange={setIsDirty}\n />\n </div>\n }\n zone2={\n <CompanyDetailTabs\n activeTab={activeTab}\n onTabChange={setActiveTab}\n injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}\n peopleCount={data.counts?.people ?? 0}\n dealsCount={dealCount}\n activitiesCount={data.counts?.activities ?? 0}\n sectionAction={sectionAction}\n >\n {activeTab === 'people' && (\n <CompanyPeopleSection\n companyId={companyId}\n companyName={companyDisplayName}\n initialPeople={[]}\n addActionLabel={t('customers.companies.detail.people.add', 'Add person')}\n emptyLabel={t('customers.companies.detail.people.empty', 'No people linked to this company yet.')}\n emptyState={{\n title: t('customers.companies.detail.emptyState.people.title', 'Build the account team'),\n actionLabel: t('customers.companies.detail.emptyState.people.action', 'Create person'),\n }}\n onActionChange={handleSectionActionChange}\n translator={detailTranslator}\n runGuardedMutation={runMutationWithContext}\n onPeopleChange={(next) => {\n setData((prev) => {\n if (!prev) return prev\n const nextCount = next.length\n return {\n ...prev,\n people: next,\n counts: prev.counts ? { ...prev.counts, people: nextCount } : prev.counts,\n }\n })\n }}\n />\n )}\n\n {activeTab === 'deals' && (\n <DealsSection\n scope={dealsScope}\n emptyLabel={t('customers.companies.detail.empty.deals', 'No deals linked to this company.')}\n addActionLabel={t('customers.companies.detail.actions.addDeal', 'Add deal')}\n emptyState={{\n title: t('customers.companies.detail.emptyState.deals.title', 'No deals yet'),\n actionLabel: t('customers.companies.detail.emptyState.deals.action', 'Create a deal'),\n }}\n onActionChange={handleSectionActionChange}\n translator={detailTranslator}\n runGuardedMutation={runMutationWithContext}\n onCountDelta={(delta) => setDealCount((current) => Math.max(0, current + delta))}\n />\n )}\n\n {activeTab === 'activity-log' && (\n <ActivityLogTab\n entityId={companyId}\n plannedActivities={plannedActivities}\n onActivityCreated={handleActivityCreated}\n onScheduleRequested={openNewScheduleDialog}\n onAddActivity={handleAddActivity}\n onMarkDone={handleMarkDone}\n onEditActivity={handleEditActivity}\n onCancelActivity={handleCancelActivity}\n runGuardedMutation={runMutationWithContext}\n refreshKey={activityRefreshKey}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n )}\n\n {activeTab === 'changelog' && companyId && (\n <ChangelogTab entityId={companyId} entityType=\"company\" />\n )}\n\n {activeTab === 'files' && (\n <AttachmentsSection\n entityId={E.customers.customer_entity}\n recordId={companyId}\n title={t('customers.companies.detail.tabs.files', 'Files')}\n description={t('customers.companies.detail.files.subtitle', 'Upload and manage files linked to this company.')}\n />\n )}\n\n {/* Injected tabs from UMES */}\n {injectedTabMap.has(activeTab) && injectedTabMap.get(activeTab)!()}\n </CompanyDetailTabs>\n }\n />\n\n {/* UMES footer injection */}\n <InjectionSpot spotId=\"detail:customers.company:footer\" context={injectionContext} data={data} />\n\n {/* Schedule Activity Dialog */}\n <ScheduleActivityDialog\n open={scheduleDialogOpen}\n onClose={() => { setScheduleDialogOpen(false); setScheduleEditData(null) }}\n entityId={companyId}\n entityName={companyName}\n entityType=\"company\"\n onActivityCreated={handleActivityCreated}\n editData={scheduleEditData}\n />\n {ConfirmDialogElement}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useRouter, useSearchParams, usePathname } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Building2, Hash, Users, BarChart3, StickyNote } from 'lucide-react'\nimport { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { AttachmentsSection, ErrorMessage, LoadingMessage, RecordNotFoundState, type SectionAction } from '@open-mercato/ui/backend/detail'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { InjectionSpot, useInjectionWidgets } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { buildRecordInjectionContext, useSetCurrentRecordInjectionContext } from '@open-mercato/ui/backend/injection/recordContext'\nimport { createTranslatorWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { CollapsibleZoneLayout, type ZoneSectionDescriptor } from '@open-mercato/ui/backend/crud/CollapsibleZoneLayout'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { E } from '#generated/entities.ids.generated'\nimport { useOrganizationScopeDetail } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { DealsSection } from '../../../../components/detail/DealsSection'\nimport { ActivityLogTab } from '../../../../components/detail/ActivityLogTab'\nimport { CompanyPeopleSection, type CompanyPersonSummary } from '../../../../components/detail/CompanyPeopleSection'\nimport type { TagSummary } from '../../../../components/detail/types'\nimport type { TagsSectionController } from '@open-mercato/ui/backend/detail'\nimport { coerceDisplayName } from '../../../../lib/displayName'\nimport { CompanyDetailHeader } from '../../../../components/detail/CompanyDetailHeader'\nimport { CompanyDetailTabs, resolveLegacyTab, type CompanyTabId } from '../../../../components/detail/CompanyDetailTabs'\nimport { useDealsAccess } from '../../../../components/detail/useDealsAccess'\nimport { CompanyKpiBar } from '../../../../components/detail/CompanyKpiBar'\nimport { ScheduleActivityDialog, type ScheduleActivityEditData } from '../../../../components/detail/ScheduleActivityDialog'\nimport { ChangelogTab } from '../../../../components/detail/ChangelogTab'\nimport { useInteractionMutations } from '../../../../components/detail/hooks/useInteractionMutations'\nimport {\n buildCompanyEditPayload,\n createCompanyEditFields,\n createCompanyDaneFiremyGroups,\n createCompanyEditSchema,\n mapCompanyOverviewToFormValues,\n type CompanyEditFormValues,\n type CompanyOverview,\n} from '../../../../components/formConfig'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\nexport default function CompanyDetailV2Page({ params }: { params?: { id?: string } }) {\n const id = params?.id\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n const pathname = usePathname()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const detailTranslator = React.useMemo(() => createTranslatorWithFallback(t), [t])\n\n const [data, setData] = React.useState<CompanyOverview | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [isNotFound, setIsNotFound] = React.useState(false)\n\n // Tab state\n const initialTab = React.useMemo(() => {\n return resolveLegacyTab(searchParams?.get('tab'))\n }, [searchParams])\n const [activeTab, setActiveTab] = React.useState<CompanyTabId>(initialTab)\n const [sectionAction, setSectionAction] = React.useState<SectionAction | null>(null)\n const { canViewDeals, isReady: isDealsAccessReady } = useDealsAccess()\n\n // Form state\n const [isDirty, setIsDirty] = React.useState(false)\n const [isSaving, setIsSaving] = React.useState(false)\n const formWrapperRef = React.useRef<HTMLDivElement>(null)\n const { organizationId } = useOrganizationScopeDetail()\n const formSchema = React.useMemo(() => createCompanyEditSchema(), [])\n const formFields = React.useMemo(() => createCompanyEditFields(t), [t])\n const formGroups = React.useMemo(() => createCompanyDaneFiremyGroups(t), [t])\n const initialValues = React.useMemo(\n () => (data ? mapCompanyOverviewToFormValues(data) : undefined),\n [data],\n )\n const zoneSections = React.useMemo<ZoneSectionDescriptor[]>(() => [\n { id: 'identity', icon: Building2, label: t('customers.companies.form.sections.identity', 'Identity') },\n { id: 'contact', icon: Hash, label: t('customers.companies.form.sections.contact', 'Contact') },\n { id: 'classification', icon: Users, label: t('customers.companies.form.sections.classification', 'Classification') },\n { id: 'businessProfile', icon: BarChart3, label: t('customers.companies.form.sections.businessProfile', 'Business profile') },\n { id: 'notes', icon: StickyNote, label: t('customers.companies.form.groups.notes', 'Notes') },\n { id: 'customFields', icon: Hash, label: t('customers.companies.form.groups.customAttributes', 'Custom attributes') },\n ], [t])\n const [scheduleDialogOpen, setScheduleDialogOpen] = React.useState(false)\n const [scheduleEditData, setScheduleEditData] = React.useState<ScheduleActivityEditData | null>(null)\n const [activityRefreshKey, setActivityRefreshKey] = React.useState(0)\n const [dealCount, setDealCount] = React.useState(0)\n\n const currentCompanyId = data?.company?.id ?? null\n const mutationContextId = React.useMemo(\n () => (currentCompanyId ? `customer-company:${currentCompanyId}` : `customer-company:${id ?? 'pending'}`),\n [currentCompanyId, id],\n )\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n companyId?: string | null\n resourceKind: string\n resourceId?: string\n data: CompanyOverview | null\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: mutationContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const companyDisplayName = coerceDisplayName(data?.company?.displayName)\n const companyName = companyDisplayName.trim().length\n ? companyDisplayName\n : t('customers.companies.list.deleteFallbackName', 'this company')\n\n // Data loading\n const initialLoadDoneRef = React.useRef(false)\n const loadData = React.useCallback(async () => {\n if (!id) {\n setIsNotFound(true)\n setIsLoading(false)\n return\n }\n if (!initialLoadDoneRef.current) {\n setIsLoading(true)\n }\n setError(null)\n setIsNotFound(false)\n try {\n const payload = await readApiResultOrThrow<CompanyOverview>(\n `/api/customers/companies/${encodeURIComponent(id)}`,\n undefined,\n { errorMessage: t('customers.companies.detail.error.load', 'Failed to load company.') },\n )\n setData(payload as CompanyOverview)\n } catch (err) {\n if ((err as { status?: number }).status === 404) {\n setIsNotFound(true)\n } else {\n const message = err instanceof Error ? err.message : t('customers.companies.detail.error.load', 'Failed to load company.')\n setError(message)\n }\n if (!initialLoadDoneRef.current) setData(null)\n } finally {\n setIsLoading(false)\n initialLoadDoneRef.current = true\n }\n }, [id, t])\n\n React.useEffect(() => {\n loadData().catch((err) => logger.warn('loadData failed', { component: 'companies-v2', err }))\n }, [loadData])\n\n React.useEffect(() => {\n setDealCount(data?.counts?.deals ?? 0)\n }, [data?.counts?.deals])\n\n const handleActivityCreated = React.useCallback(() => {\n setActivityRefreshKey((k) => k + 1)\n loadData().catch((err) => logger.warn('reload after activity failed', { component: 'companies-v2', err }))\n }, [loadData])\n\n // Planned activities for the activity-log tab\n const plannedActivities = React.useMemo(() => {\n return data?.plannedActivitiesPreview ?? []\n }, [data?.plannedActivitiesPreview])\n\n // Injection context for UMES\n const injectionContext = React.useMemo(\n () => ({\n formId: mutationContextId,\n companyId: currentCompanyId,\n resourceKind: 'customers.company',\n resourceId: currentCompanyId ?? (id ?? undefined),\n data,\n retryLastMutation,\n }),\n [currentCompanyId, data, id, mutationContextId, retryLastMutation],\n )\n const runMutationWithContext = React.useCallback(\n async <T,>(operation: () => Promise<T>, mutationPayload?: Record<string, unknown>): Promise<T> => {\n return runMutation({\n operation,\n mutationPayload,\n context: injectionContext,\n })\n },\n [injectionContext, runMutation],\n )\n\n const { completeInteraction: handleMarkDone, cancelInteraction: handleCancelActivity } = useInteractionMutations({\n runMutationWithContext,\n onAfterChange: handleActivityCreated,\n logContext: 'customers.companies-v2',\n })\n\n // Publish page-load record context to the AppShell-owned `backend:record:current`\n // mount so the enterprise record_locks widget resolves `customers.company` + id\n // explicitly (the hardcoded path allowlist misses the `companies-v2` route).\n useSetCurrentRecordInjectionContext(\n buildRecordInjectionContext({\n resourceKind: 'customers.company',\n resourceId: currentCompanyId,\n updatedAt: (data?.company as { updatedAt?: string | null; updated_at?: string | null } | undefined)?.updatedAt\n ?? (data?.company as { updated_at?: string | null } | undefined)?.updated_at\n ?? null,\n data: data as Record<string, unknown> | null,\n path: pathname,\n }),\n )\n\n const handleEditActivity = React.useCallback((activity: { id: string; interactionType?: string; title?: string | null; body?: string | null; scheduledAt?: string | null; occurredAt?: string | null; [key: string]: unknown }) => {\n const raw = activity as Record<string, unknown>\n const durationValue = typeof raw.duration === 'number'\n ? raw.duration\n : typeof raw.durationMinutes === 'number'\n ? raw.durationMinutes as number\n : null\n // Forward `customValues` so per-type chip state (callPhoneNumber, callDirection,\n // taskPriority, \u2026) round-trips on edit (#1808 phone persistence).\n // Forward `occurredAt` so historical activity edits prefill from the original\n // moment instead of \"today\" (#1807 prefill).\n const editPayload = {\n id: activity.id,\n updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt as string : typeof raw.updated_at === 'string' ? raw.updated_at as string : null,\n interactionType: typeof activity.interactionType === 'string' ? activity.interactionType : undefined,\n title: typeof activity.title === 'string' ? activity.title : null,\n body: typeof activity.body === 'string' ? activity.body : null,\n scheduledAt: typeof activity.scheduledAt === 'string' ? activity.scheduledAt : null,\n occurredAt: typeof activity.occurredAt === 'string' ? activity.occurredAt : null,\n durationMinutes: durationValue,\n location: typeof raw.location === 'string' ? raw.location as string : null,\n allDay: typeof raw.allDay === 'boolean' ? raw.allDay as boolean : null,\n recurrenceRule: typeof raw.recurrenceRule === 'string' ? raw.recurrenceRule as string : null,\n recurrenceEnd: typeof raw.recurrenceEnd === 'string' ? raw.recurrenceEnd as string : null,\n participants: Array.isArray(raw.participants) ? raw.participants as ScheduleActivityEditData['participants'] : null,\n reminderMinutes: typeof raw.reminderMinutes === 'number' ? raw.reminderMinutes as number : null,\n visibility: typeof raw.visibility === 'string' ? raw.visibility as string : null,\n linkedEntities: Array.isArray(raw.linkedEntities) ? raw.linkedEntities as ScheduleActivityEditData['linkedEntities'] : null,\n guestPermissions: raw.guestPermissions && typeof raw.guestPermissions === 'object'\n ? raw.guestPermissions as ScheduleActivityEditData['guestPermissions']\n : null,\n customValues: raw.customValues && typeof raw.customValues === 'object'\n ? raw.customValues as Record<string, unknown>\n : null,\n phoneNumber: typeof raw.phoneNumber === 'string' ? raw.phoneNumber as string : null,\n } as ScheduleActivityEditData & { customValues?: Record<string, unknown> | null; phoneNumber?: string | null }\n setScheduleEditData(editPayload)\n setScheduleDialogOpen(true)\n }, [])\n\n const openNewScheduleDialog = React.useCallback(() => {\n setScheduleEditData(null)\n setScheduleDialogOpen(true)\n }, [])\n\n const handleAddActivity = React.useCallback((kind: 'meeting' | 'call' | 'task' | 'email') => {\n setScheduleEditData({\n id: '',\n interactionType: kind,\n title: null,\n body: null,\n scheduledAt: null,\n durationMinutes: null,\n location: null,\n allDay: null,\n recurrenceRule: null,\n recurrenceEnd: null,\n participants: null,\n reminderMinutes: null,\n visibility: null,\n linkedEntities: null,\n guestPermissions: null,\n })\n setScheduleDialogOpen(true)\n }, [])\n\n // Injected tabs from UMES\n const { widgets: injectedTabWidgets } = useInjectionWidgets('detail:customers.company:tabs', {\n context: injectionContext,\n triggerOnLoad: true,\n })\n\n const injectedTabs = React.useMemo(\n () =>\n (injectedTabWidgets ?? [])\n .filter((widget) => (widget.placement?.kind ?? 'tab') === 'tab')\n .map((widget) => {\n const tabId = widget.placement?.groupId ?? widget.widgetId\n const label = widget.placement?.groupLabel ?? widget.module.metadata.title ?? tabId\n const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0\n const render = () => (\n <widget.module.Widget\n context={injectionContext}\n data={data}\n onDataChange={(next: unknown) => setData(next as CompanyOverview)}\n />\n )\n return { id: tabId, label, priority, render }\n })\n .sort((a, b) => b.priority - a.priority),\n [data, injectedTabWidgets, injectionContext],\n )\n\n const injectedTabMap = React.useMemo(() => new Map(injectedTabs.map((tab) => [tab.id, tab.render])), [injectedTabs])\n\n // Tags\n const handleTagsChange = React.useCallback((nextTags: TagSummary[]) => {\n setData((prev) => (prev ? { ...prev, tags: nextTags } : prev))\n }, [])\n const tagsSectionControllerRef = React.useRef<TagsSectionController | null>(null)\n\n // Section action (for tabs that expose add/create buttons)\n const handleSectionActionChange = React.useCallback((action: SectionAction | null) => {\n setSectionAction((prev) => (action !== null ? action : prev))\n }, [])\n\n const handleSectionAction = React.useCallback(() => {\n if (!sectionAction || sectionAction.disabled) return\n sectionAction.onClick()\n }, [sectionAction])\n\n React.useEffect(() => {\n setSectionAction(null)\n }, [activeTab])\n\n // A `?tab=deals` deep link must not strand users without `customers.deals.view`\n // on a tab that no longer exists for them. Wait for the granted features to load\n // so a permitted user is never bounced off the tab mid-fetch.\n React.useEffect(() => {\n if (!isDealsAccessReady || canViewDeals) return\n setActiveTab((current) => (current === 'deals' ? 'people' : current))\n }, [isDealsAccessReady, canViewDeals])\n\n // Deals scope\n const dealsScope = React.useMemo(\n () => (currentCompanyId ? ({ kind: 'company', entityId: currentCompanyId } as const) : null),\n [currentCompanyId],\n )\n\n // Delete handler (shared between header and form)\n const handleDelete = React.useCallback(async () => {\n const companyId = data?.company?.id ?? ''\n if (!companyId) return\n const approved = await confirm({\n title: t('customers.companies.detail.deleteConfirmTitle', 'Delete company?'),\n description: t('customers.companies.detail.deleteConfirmDescription', 'This action cannot be undone.'),\n confirmText: t('customers.companies.detail.actions.delete', 'Delete company'),\n cancelText: t('customers.companies.detail.actions.cancel', 'Cancel'),\n variant: 'destructive',\n })\n if (!approved) return\n try {\n await runMutationWithContext(\n () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader((data?.company as { updatedAt?: string } | undefined)?.updatedAt),\n () => deleteCrud('customers/companies', { id: companyId }),\n ),\n { id: companyId, operation: 'deleteCompany' },\n )\n } catch (err) {\n // The guarded mutation already routes a 409 to the unified conflict bar;\n // surface any other server error (e.g. \"Cannot delete company: linked\n // deals\u2026\") as a flash instead of letting it crash the page.\n if (!surfaceRecordConflict(err, t)) {\n flash(\n err instanceof Error && err.message.trim().length > 0\n ? err.message\n : t('customers.companies.detail.deleteError', 'Failed to delete company.'),\n 'error',\n )\n }\n return\n }\n flash(t('customers.companies.list.deleteSuccess', 'Company deleted.'), 'success')\n router.push('/backend/customers/companies')\n }, [confirm, data?.company?.id, router, runMutationWithContext, t])\n\n // Form submit handler (lifted from CompanyDataTab)\n const handleFormSubmit = React.useCallback(\n async (values: CompanyEditFormValues) => {\n setIsSaving(true)\n try {\n let payload: Record<string, unknown>\n try {\n payload = buildCompanyEditPayload(values, organizationId)\n } catch (err) {\n if (err instanceof Error) {\n if (err.message === 'DISPLAY_NAME_REQUIRED') {\n const message = t('customers.companies.form.displayName.error')\n throw createCrudFormError(message, { displayName: message })\n }\n if (err.message === 'ANNUAL_REVENUE_INVALID') {\n const message = t('customers.companies.form.annualRevenue.error')\n throw createCrudFormError(message, { annualRevenue: message })\n }\n }\n throw err\n }\n await updateCrud('customers/companies', payload)\n flash(t('customers.companies.form.updateSuccess', 'Company updated.'), 'success')\n await loadData()\n } finally {\n setIsSaving(false)\n }\n },\n [loadData, organizationId, t],\n )\n\n // Save handler (triggers form submit via ref)\n const handleHeaderSave = React.useCallback(() => {\n const form = formWrapperRef.current?.querySelector('form')\n if (form) form.requestSubmit()\n }, [])\n\n // Loading / error states\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <LoadingMessage label={t('customers.companies.detail.loading', 'Loading company\u2026')} />\n </PageBody>\n </Page>\n )\n }\n\n if (isNotFound) {\n return (\n <Page>\n <PageBody>\n <RecordNotFoundState\n label={t('customers.companies.detail.error.notFound', 'Company not found.')}\n backHref=\"/backend/customers/companies\"\n backLabel={t('customers.companies.detail.actions.backToList', 'Back to companies')}\n />\n </PageBody>\n </Page>\n )\n }\n\n if (error || !data?.company?.id) {\n return (\n <Page>\n <PageBody>\n <ErrorMessage\n label={error ?? t('customers.companies.detail.error.load', 'Failed to load company.')}\n action={(\n <Button asChild variant=\"outline\">\n <Link href=\"/backend/customers/companies\">\n {t('customers.companies.detail.actions.backToList', 'Back to companies')}\n </Link>\n </Button>\n )}\n />\n </PageBody>\n </Page>\n )\n }\n\n const companyId = data.company.id\n const useCanonicalInteractions = data.interactionMode === 'canonical'\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-4\">\n {/* UMES header injection */}\n <InjectionSpot spotId=\"detail:customers.company:header\" context={injectionContext} data={data} />\n <InjectionSpot spotId=\"detail:customers.company:status-badges\" context={injectionContext} data={data} />\n\n {/* Persistent company header */}\n <CompanyDetailHeader\n data={data}\n onTagsChange={handleTagsChange}\n tagsSectionControllerRef={tagsSectionControllerRef}\n onSave={handleHeaderSave}\n onDelete={handleDelete}\n isDirty={isDirty}\n isSaving={isSaving}\n onDataReload={() => { loadData().catch((err) => logger.warn('onDataReload failed', { component: 'companies-v2', err })) }}\n />\n\n {/* KPI bar \u2014 always visible above zones */}\n <CompanyKpiBar data={data} />\n\n {/* Two-zone layout: zone1 = form, zone2 = tabs */}\n <CollapsibleZoneLayout\n pageType=\"company-v2\"\n entityName={companyName}\n isDirty={isDirty}\n sections={zoneSections}\n zone1={\n <div ref={formWrapperRef}>\n <CrudForm<CompanyEditFormValues>\n embedded\n trackDirtyWhenEmbedded\n injectionSpotId=\"crud-form:customers.company\"\n entityIds={[E.customers.customer_entity, E.customers.customer_company_profile]}\n schema={formSchema}\n fields={formFields}\n groups={formGroups}\n initialValues={initialValues}\n onSubmit={handleFormSubmit}\n onDelete={handleDelete}\n optimisticLockUpdatedAt={(data?.company as { updatedAt?: string } | undefined)?.updatedAt}\n hideFooterActions\n collapsibleGroups={{ pageType: 'company-v2', chevronPosition: 'right' }}\n sortableGroups={{ pageType: 'company-v2' }}\n onDirtyChange={setIsDirty}\n />\n </div>\n }\n zone2={\n <CompanyDetailTabs\n activeTab={activeTab}\n onTabChange={setActiveTab}\n injectedTabs={injectedTabs.map((tab) => ({ id: tab.id, label: tab.label }))}\n peopleCount={data.counts?.people ?? 0}\n dealsCount={dealCount}\n activitiesCount={data.counts?.activities ?? 0}\n sectionAction={sectionAction}\n >\n {activeTab === 'people' && (\n <CompanyPeopleSection\n companyId={companyId}\n companyName={companyDisplayName}\n initialPeople={[]}\n addActionLabel={t('customers.companies.detail.people.add', 'Add person')}\n emptyLabel={t('customers.companies.detail.people.empty', 'No people linked to this company yet.')}\n emptyState={{\n title: t('customers.companies.detail.emptyState.people.title', 'Build the account team'),\n actionLabel: t('customers.companies.detail.emptyState.people.action', 'Create person'),\n }}\n onActionChange={handleSectionActionChange}\n translator={detailTranslator}\n runGuardedMutation={runMutationWithContext}\n onPeopleChange={(next) => {\n setData((prev) => {\n if (!prev) return prev\n const nextCount = next.length\n return {\n ...prev,\n people: next,\n counts: prev.counts ? { ...prev.counts, people: nextCount } : prev.counts,\n }\n })\n }}\n />\n )}\n\n {activeTab === 'deals' && canViewDeals && (\n <DealsSection\n scope={dealsScope}\n emptyLabel={t('customers.companies.detail.empty.deals', 'No deals linked to this company.')}\n addActionLabel={t('customers.companies.detail.actions.addDeal', 'Add deal')}\n emptyState={{\n title: t('customers.companies.detail.emptyState.deals.title', 'No deals yet'),\n actionLabel: t('customers.companies.detail.emptyState.deals.action', 'Create a deal'),\n }}\n onActionChange={handleSectionActionChange}\n translator={detailTranslator}\n runGuardedMutation={runMutationWithContext}\n onCountDelta={(delta) => setDealCount((current) => Math.max(0, current + delta))}\n />\n )}\n\n {activeTab === 'activity-log' && (\n <ActivityLogTab\n entityId={companyId}\n plannedActivities={plannedActivities}\n onActivityCreated={handleActivityCreated}\n onScheduleRequested={openNewScheduleDialog}\n onAddActivity={handleAddActivity}\n onMarkDone={handleMarkDone}\n onEditActivity={handleEditActivity}\n onCancelActivity={handleCancelActivity}\n runGuardedMutation={runMutationWithContext}\n refreshKey={activityRefreshKey}\n useCanonicalInteractions={useCanonicalInteractions}\n />\n )}\n\n {activeTab === 'changelog' && companyId && (\n <ChangelogTab entityId={companyId} entityType=\"company\" />\n )}\n\n {activeTab === 'files' && (\n <AttachmentsSection\n entityId={E.customers.customer_entity}\n recordId={companyId}\n title={t('customers.companies.detail.tabs.files', 'Files')}\n description={t('customers.companies.detail.files.subtitle', 'Upload and manage files linked to this company.')}\n />\n )}\n\n {/* Injected tabs from UMES */}\n {injectedTabMap.has(activeTab) && injectedTabMap.get(activeTab)!()}\n </CompanyDetailTabs>\n }\n />\n\n {/* UMES footer injection */}\n <InjectionSpot spotId=\"detail:customers.company:footer\" context={injectionContext} data={data} />\n\n {/* Schedule Activity Dialog */}\n <ScheduleActivityDialog\n open={scheduleDialogOpen}\n onClose={() => { setScheduleDialogOpen(false); setScheduleEditData(null) }}\n entityId={companyId}\n entityName={companyName}\n entityType=\"company\"\n onActivityCreated={handleActivityCreated}\n editData={scheduleEditData}\n />\n {ConfirmDialogElement}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["companyId"]
7
7
  }
@@ -13,8 +13,8 @@ function ViewTabsRow({ active, className }) {
13
13
  list: translateWithFallback(t, "customers.deals.kanban.view.list", "List"),
14
14
  map: translateWithFallback(t, "customers.deals.kanban.view.map", "Map")
15
15
  };
16
- const baseTab = "inline-flex items-center px-3.5 py-2.5 text-sm leading-normal transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2";
17
- const activeTab = "border-b-2 border-foreground font-semibold text-foreground";
16
+ const baseTab = "inline-flex items-center px-3.5 py-2.5 text-sm leading-normal transition-colors focus-visible:outline-none focus-visible:shadow-focus";
17
+ const activeTab = "border-b-2 border-accent-indigo font-semibold text-foreground";
18
18
  const inactiveTab = "border-b-2 border-transparent font-normal text-muted-foreground hover:text-foreground";
19
19
  const isKanbanActive = active === "kanban";
20
20
  const isListActive = active === "list";
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\n\nexport type KanbanView = 'kanban' | 'list' | 'map'\n\ntype ViewTabsRowProps = {\n active: KanbanView\n className?: string\n}\n\nconst VIEW_KANBAN_HREF = '/backend/customers/deals/pipeline'\nconst VIEW_LIST_HREF = '/backend/customers/deals'\nconst VIEW_MAP_HREF = '/backend/customers/deals/map'\n\nexport function ViewTabsRow({ active, className }: ViewTabsRowProps): React.ReactElement {\n const t = useT()\n const labels = {\n kanban: translateWithFallback(t, 'customers.deals.kanban.view.kanban', 'Kanban'),\n list: translateWithFallback(t, 'customers.deals.kanban.view.list', 'List'),\n map: translateWithFallback(t, 'customers.deals.kanban.view.map', 'Map'),\n }\n\n const baseTab =\n 'inline-flex items-center px-3.5 py-2.5 text-sm leading-normal transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2'\n const activeTab = 'border-b-2 border-foreground font-semibold text-foreground'\n const inactiveTab = 'border-b-2 border-transparent font-normal text-muted-foreground hover:text-foreground'\n\n // Renders the active tab as a non-navigating `<span>` and the inactive tab as a `<Link>`,\n // so the user can always round-trip between kanban and list from either page. Previously\n // the kanban tab was a hardcoded `<span>` because the row was only rendered on the kanban\n // page \u2014 adding it to the list page (item 7 of the SPEC-048 UX review) requires it to\n // navigate back to /pipeline.\n const isKanbanActive = active === 'kanban'\n const isListActive = active === 'list'\n const isMapActive = active === 'map'\n\n return (\n <div\n role=\"tablist\"\n aria-label={translateWithFallback(t, 'customers.deals.kanban.view.tablistLabel', 'Deals views')}\n className={`flex items-end gap-1 border-b border-border ${className ?? ''}`.trim()}\n >\n {isKanbanActive ? (\n <span\n role=\"tab\"\n aria-selected={true}\n className={`${baseTab} ${activeTab}`}\n >\n {labels.kanban}\n </span>\n ) : (\n <Link\n href={VIEW_KANBAN_HREF}\n role=\"tab\"\n aria-selected={false}\n className={`${baseTab} ${inactiveTab}`}\n >\n {labels.kanban}\n </Link>\n )}\n {isListActive ? (\n <span\n role=\"tab\"\n aria-selected={true}\n className={`${baseTab} ${activeTab}`}\n >\n {labels.list}\n </span>\n ) : (\n <Link\n href={VIEW_LIST_HREF}\n role=\"tab\"\n aria-selected={false}\n className={`${baseTab} ${inactiveTab}`}\n >\n {labels.list}\n </Link>\n )}\n {isMapActive ? (\n <span\n role=\"tab\"\n aria-selected={true}\n className={`${baseTab} ${activeTab}`}\n >\n {labels.map}\n </span>\n ) : (\n <Link\n href={VIEW_MAP_HREF}\n role=\"tab\"\n aria-selected={false}\n className={`${baseTab} ${inactiveTab}`}\n >\n {labels.map}\n </Link>\n )}\n </div>\n )\n}\n\nexport default ViewTabsRow\n"],
5
- "mappings": ";AAyCI,SAMI,KANJ;AAtCJ,OAAO,UAAU;AACjB,SAAS,YAAY;AACrB,SAAS,6BAA6B;AAStC,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AACvB,MAAM,gBAAgB;AAEf,SAAS,YAAY,EAAE,QAAQ,UAAU,GAAyC;AACvF,QAAM,IAAI,KAAK;AACf,QAAM,SAAS;AAAA,IACb,QAAQ,sBAAsB,GAAG,sCAAsC,QAAQ;AAAA,IAC/E,MAAM,sBAAsB,GAAG,oCAAoC,MAAM;AAAA,IACzE,KAAK,sBAAsB,GAAG,mCAAmC,KAAK;AAAA,EACxE;AAEA,QAAM,UACJ;AACF,QAAM,YAAY;AAClB,QAAM,cAAc;AAOpB,QAAM,iBAAiB,WAAW;AAClC,QAAM,eAAe,WAAW;AAChC,QAAM,cAAc,WAAW;AAE/B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY,sBAAsB,GAAG,4CAA4C,aAAa;AAAA,MAC9F,WAAW,+CAA+C,aAAa,EAAE,GAAG,KAAK;AAAA,MAEhF;AAAA,yBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,SAAS;AAAA,YAEjC,iBAAO;AAAA;AAAA,QACV,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,WAAW;AAAA,YAEnC,iBAAO;AAAA;AAAA,QACV;AAAA,QAED,eACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,SAAS;AAAA,YAEjC,iBAAO;AAAA;AAAA,QACV,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,WAAW;AAAA,YAEnC,iBAAO;AAAA;AAAA,QACV;AAAA,QAED,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,SAAS;AAAA,YAEjC,iBAAO;AAAA;AAAA,QACV,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,WAAW;AAAA,YAEnC,iBAAO;AAAA;AAAA,QACV;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,IAAO,sBAAQ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\n\nexport type KanbanView = 'kanban' | 'list' | 'map'\n\ntype ViewTabsRowProps = {\n active: KanbanView\n className?: string\n}\n\nconst VIEW_KANBAN_HREF = '/backend/customers/deals/pipeline'\nconst VIEW_LIST_HREF = '/backend/customers/deals'\nconst VIEW_MAP_HREF = '/backend/customers/deals/map'\n\nexport function ViewTabsRow({ active, className }: ViewTabsRowProps): React.ReactElement {\n const t = useT()\n const labels = {\n kanban: translateWithFallback(t, 'customers.deals.kanban.view.kanban', 'Kanban'),\n list: translateWithFallback(t, 'customers.deals.kanban.view.list', 'List'),\n map: translateWithFallback(t, 'customers.deals.kanban.view.map', 'Map'),\n }\n\n // Link-based tab row (two routes), so the Tabs primitive (state-driven,\n // onValueChange) does not fit \u2014 real <Link> semantics must stay. Classes\n // mirror the Tabs underline variant: accent-indigo active border,\n // shadow-focus halo.\n const baseTab =\n 'inline-flex items-center px-3.5 py-2.5 text-sm leading-normal transition-colors focus-visible:outline-none focus-visible:shadow-focus'\n const activeTab = 'border-b-2 border-accent-indigo font-semibold text-foreground'\n const inactiveTab = 'border-b-2 border-transparent font-normal text-muted-foreground hover:text-foreground'\n\n // Renders the active tab as a non-navigating `<span>` and the inactive tab as a `<Link>`,\n // so the user can always round-trip between kanban and list from either page. Previously\n // the kanban tab was a hardcoded `<span>` because the row was only rendered on the kanban\n // page \u2014 adding it to the list page (item 7 of the SPEC-048 UX review) requires it to\n // navigate back to /pipeline.\n const isKanbanActive = active === 'kanban'\n const isListActive = active === 'list'\n const isMapActive = active === 'map'\n\n return (\n <div\n role=\"tablist\"\n aria-label={translateWithFallback(t, 'customers.deals.kanban.view.tablistLabel', 'Deals views')}\n className={`flex items-end gap-1 border-b border-border ${className ?? ''}`.trim()}\n >\n {isKanbanActive ? (\n <span\n role=\"tab\"\n aria-selected={true}\n className={`${baseTab} ${activeTab}`}\n >\n {labels.kanban}\n </span>\n ) : (\n <Link\n href={VIEW_KANBAN_HREF}\n role=\"tab\"\n aria-selected={false}\n className={`${baseTab} ${inactiveTab}`}\n >\n {labels.kanban}\n </Link>\n )}\n {isListActive ? (\n <span\n role=\"tab\"\n aria-selected={true}\n className={`${baseTab} ${activeTab}`}\n >\n {labels.list}\n </span>\n ) : (\n <Link\n href={VIEW_LIST_HREF}\n role=\"tab\"\n aria-selected={false}\n className={`${baseTab} ${inactiveTab}`}\n >\n {labels.list}\n </Link>\n )}\n {isMapActive ? (\n <span\n role=\"tab\"\n aria-selected={true}\n className={`${baseTab} ${activeTab}`}\n >\n {labels.map}\n </span>\n ) : (\n <Link\n href={VIEW_MAP_HREF}\n role=\"tab\"\n aria-selected={false}\n className={`${baseTab} ${inactiveTab}`}\n >\n {labels.map}\n </Link>\n )}\n </div>\n )\n}\n\nexport default ViewTabsRow\n"],
5
+ "mappings": ";AA6CI,SAMI,KANJ;AA1CJ,OAAO,UAAU;AACjB,SAAS,YAAY;AACrB,SAAS,6BAA6B;AAStC,MAAM,mBAAmB;AACzB,MAAM,iBAAiB;AACvB,MAAM,gBAAgB;AAEf,SAAS,YAAY,EAAE,QAAQ,UAAU,GAAyC;AACvF,QAAM,IAAI,KAAK;AACf,QAAM,SAAS;AAAA,IACb,QAAQ,sBAAsB,GAAG,sCAAsC,QAAQ;AAAA,IAC/E,MAAM,sBAAsB,GAAG,oCAAoC,MAAM;AAAA,IACzE,KAAK,sBAAsB,GAAG,mCAAmC,KAAK;AAAA,EACxE;AAMA,QAAM,UACJ;AACF,QAAM,YAAY;AAClB,QAAM,cAAc;AAOpB,QAAM,iBAAiB,WAAW;AAClC,QAAM,eAAe,WAAW;AAChC,QAAM,cAAc,WAAW;AAE/B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY,sBAAsB,GAAG,4CAA4C,aAAa;AAAA,MAC9F,WAAW,+CAA+C,aAAa,EAAE,GAAG,KAAK;AAAA,MAEhF;AAAA,yBACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,SAAS;AAAA,YAEjC,iBAAO;AAAA;AAAA,QACV,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,WAAW;AAAA,YAEnC,iBAAO;AAAA;AAAA,QACV;AAAA,QAED,eACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,SAAS;AAAA,YAEjC,iBAAO;AAAA;AAAA,QACV,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,WAAW;AAAA,YAEnC,iBAAO;AAAA;AAAA,QACV;AAAA,QAED,cACC;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,SAAS;AAAA,YAEjC,iBAAO;AAAA;AAAA,QACV,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM;AAAA,YACN,MAAK;AAAA,YACL,iBAAe;AAAA,YACf,WAAW,GAAG,OAAO,IAAI,WAAW;AAAA,YAEnC,iBAAO;AAAA;AAAA,QACV;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAEA,IAAO,sBAAQ;",
6
6
  "names": []
7
7
  }