@open-mercato/core 0.4.8-develop-28cee031d6 → 0.4.8-develop-15259be22b

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 (333) hide show
  1. package/agentic/standalone-guide.md +235 -0
  2. package/dist/generated/entities/customer_role/index.js +27 -0
  3. package/dist/generated/entities/customer_role/index.js.map +7 -0
  4. package/dist/generated/entities/customer_role_acl/index.js +19 -0
  5. package/dist/generated/entities/customer_role_acl/index.js.map +7 -0
  6. package/dist/generated/entities/customer_user/index.js +37 -0
  7. package/dist/generated/entities/customer_user/index.js.map +7 -0
  8. package/dist/generated/entities/customer_user_acl/index.js +19 -0
  9. package/dist/generated/entities/customer_user_acl/index.js.map +7 -0
  10. package/dist/generated/entities/customer_user_email_verification/index.js +17 -0
  11. package/dist/generated/entities/customer_user_email_verification/index.js.map +7 -0
  12. package/dist/generated/entities/customer_user_invitation/index.js +33 -0
  13. package/dist/generated/entities/customer_user_invitation/index.js.map +7 -0
  14. package/dist/generated/entities/customer_user_password_reset/index.js +15 -0
  15. package/dist/generated/entities/customer_user_password_reset/index.js.map +7 -0
  16. package/dist/generated/entities/customer_user_role/index.js +13 -0
  17. package/dist/generated/entities/customer_user_role/index.js.map +7 -0
  18. package/dist/generated/entities/customer_user_session/index.js +21 -0
  19. package/dist/generated/entities/customer_user_session/index.js.map +7 -0
  20. package/dist/generated/entities/organization/index.js +2 -0
  21. package/dist/generated/entities/organization/index.js.map +2 -2
  22. package/dist/generated/entities.ids.generated.js +14 -1
  23. package/dist/generated/entities.ids.generated.js.map +2 -2
  24. package/dist/generated/entity-fields-registry.js +18 -0
  25. package/dist/generated/entity-fields-registry.js.map +2 -2
  26. package/dist/modules/auth/services/rbacService.js +3 -9
  27. package/dist/modules/auth/services/rbacService.js.map +2 -2
  28. package/dist/modules/customer_accounts/acl.js +12 -0
  29. package/dist/modules/customer_accounts/acl.js.map +7 -0
  30. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +87 -0
  31. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +7 -0
  32. package/dist/modules/customer_accounts/api/admin/roles/[id].js +216 -0
  33. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +7 -0
  34. package/dist/modules/customer_accounts/api/admin/roles.js +189 -0
  35. package/dist/modules/customer_accounts/api/admin/roles.js.map +7 -0
  36. package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js +69 -0
  37. package/dist/modules/customer_accounts/api/admin/users/[id]/reset-password.js.map +7 -0
  38. package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js +64 -0
  39. package/dist/modules/customer_accounts/api/admin/users/[id]/verify-email.js.map +7 -0
  40. package/dist/modules/customer_accounts/api/admin/users/[id].js +253 -0
  41. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +7 -0
  42. package/dist/modules/customer_accounts/api/admin/users-invite.js +78 -0
  43. package/dist/modules/customer_accounts/api/admin/users-invite.js.map +7 -0
  44. package/dist/modules/customer_accounts/api/admin/users.js +251 -0
  45. package/dist/modules/customer_accounts/api/admin/users.js.map +7 -0
  46. package/dist/modules/customer_accounts/api/email/verify.js +59 -0
  47. package/dist/modules/customer_accounts/api/email/verify.js.map +7 -0
  48. package/dist/modules/customer_accounts/api/interceptors.js +5 -0
  49. package/dist/modules/customer_accounts/api/interceptors.js.map +7 -0
  50. package/dist/modules/customer_accounts/api/invitations/accept.js +114 -0
  51. package/dist/modules/customer_accounts/api/invitations/accept.js.map +7 -0
  52. package/dist/modules/customer_accounts/api/login.js +143 -0
  53. package/dist/modules/customer_accounts/api/login.js.map +7 -0
  54. package/dist/modules/customer_accounts/api/magic-link/request.js +78 -0
  55. package/dist/modules/customer_accounts/api/magic-link/request.js.map +7 -0
  56. package/dist/modules/customer_accounts/api/magic-link/verify.js +114 -0
  57. package/dist/modules/customer_accounts/api/magic-link/verify.js.map +7 -0
  58. package/dist/modules/customer_accounts/api/password/reset-confirm.js +59 -0
  59. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +7 -0
  60. package/dist/modules/customer_accounts/api/password/reset-request.js +77 -0
  61. package/dist/modules/customer_accounts/api/password/reset-request.js.map +7 -0
  62. package/dist/modules/customer_accounts/api/portal/events/stream.js +163 -0
  63. package/dist/modules/customer_accounts/api/portal/events/stream.js.map +7 -0
  64. package/dist/modules/customer_accounts/api/portal/feature-check.js +57 -0
  65. package/dist/modules/customer_accounts/api/portal/feature-check.js.map +7 -0
  66. package/dist/modules/customer_accounts/api/portal/logout.js +64 -0
  67. package/dist/modules/customer_accounts/api/portal/logout.js.map +7 -0
  68. package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js +49 -0
  69. package/dist/modules/customer_accounts/api/portal/notifications/[id]/dismiss.js.map +7 -0
  70. package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js +49 -0
  71. package/dist/modules/customer_accounts/api/portal/notifications/[id]/read.js.map +7 -0
  72. package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js +46 -0
  73. package/dist/modules/customer_accounts/api/portal/notifications/mark-all-read.js.map +7 -0
  74. package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js +42 -0
  75. package/dist/modules/customer_accounts/api/portal/notifications/unread-count.js.map +7 -0
  76. package/dist/modules/customer_accounts/api/portal/notifications.js +105 -0
  77. package/dist/modules/customer_accounts/api/portal/notifications.js.map +7 -0
  78. package/dist/modules/customer_accounts/api/portal/password-change.js +57 -0
  79. package/dist/modules/customer_accounts/api/portal/password-change.js.map +7 -0
  80. package/dist/modules/customer_accounts/api/portal/profile.js +135 -0
  81. package/dist/modules/customer_accounts/api/portal/profile.js.map +7 -0
  82. package/dist/modules/customer_accounts/api/portal/sessions/[id].js +62 -0
  83. package/dist/modules/customer_accounts/api/portal/sessions/[id].js.map +7 -0
  84. package/dist/modules/customer_accounts/api/portal/sessions-refresh.js +75 -0
  85. package/dist/modules/customer_accounts/api/portal/sessions-refresh.js.map +7 -0
  86. package/dist/modules/customer_accounts/api/portal/sessions.js +77 -0
  87. package/dist/modules/customer_accounts/api/portal/sessions.js.map +7 -0
  88. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +90 -0
  89. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +7 -0
  90. package/dist/modules/customer_accounts/api/portal/users/[id].js +71 -0
  91. package/dist/modules/customer_accounts/api/portal/users/[id].js.map +7 -0
  92. package/dist/modules/customer_accounts/api/portal/users-invite.js +92 -0
  93. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +7 -0
  94. package/dist/modules/customer_accounts/api/portal/users.js +79 -0
  95. package/dist/modules/customer_accounts/api/portal/users.js.map +7 -0
  96. package/dist/modules/customer_accounts/api/signup.js +121 -0
  97. package/dist/modules/customer_accounts/api/signup.js.map +7 -0
  98. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js +491 -0
  99. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.js.map +7 -0
  100. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js +15 -0
  101. package/dist/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.js.map +7 -0
  102. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +343 -0
  103. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +7 -0
  104. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js +16 -0
  105. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.js.map +7 -0
  106. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js +180 -0
  107. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.js.map +7 -0
  108. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js +16 -0
  109. package/dist/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.js.map +7 -0
  110. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +176 -0
  111. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +7 -0
  112. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js +33 -0
  113. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.meta.js.map +7 -0
  114. package/dist/modules/customer_accounts/backend/page.js +466 -0
  115. package/dist/modules/customer_accounts/backend/page.js.map +7 -0
  116. package/dist/modules/customer_accounts/backend/page.meta.js +35 -0
  117. package/dist/modules/customer_accounts/backend/page.meta.js.map +7 -0
  118. package/dist/modules/customer_accounts/ce.js +26 -0
  119. package/dist/modules/customer_accounts/ce.js.map +7 -0
  120. package/dist/modules/customer_accounts/data/enrichers.js +85 -0
  121. package/dist/modules/customer_accounts/data/enrichers.js.map +7 -0
  122. package/dist/modules/customer_accounts/data/entities.js +377 -0
  123. package/dist/modules/customer_accounts/data/entities.js.map +7 -0
  124. package/dist/modules/customer_accounts/data/extensions.js +8 -0
  125. package/dist/modules/customer_accounts/data/extensions.js.map +7 -0
  126. package/dist/modules/customer_accounts/data/validators.js +111 -0
  127. package/dist/modules/customer_accounts/data/validators.js.map +7 -0
  128. package/dist/modules/customer_accounts/di.js +17 -0
  129. package/dist/modules/customer_accounts/di.js.map +7 -0
  130. package/dist/modules/customer_accounts/events.js +28 -0
  131. package/dist/modules/customer_accounts/events.js.map +7 -0
  132. package/dist/modules/customer_accounts/index.js +15 -0
  133. package/dist/modules/customer_accounts/index.js.map +7 -0
  134. package/dist/modules/customer_accounts/lib/customerAuth.js +71 -0
  135. package/dist/modules/customer_accounts/lib/customerAuth.js.map +7 -0
  136. package/dist/modules/customer_accounts/lib/customerAuthServer.js +29 -0
  137. package/dist/modules/customer_accounts/lib/customerAuthServer.js.map +7 -0
  138. package/dist/modules/customer_accounts/lib/rateLimiter.js +63 -0
  139. package/dist/modules/customer_accounts/lib/rateLimiter.js.map +7 -0
  140. package/dist/modules/customer_accounts/lib/tokenGenerator.js +12 -0
  141. package/dist/modules/customer_accounts/lib/tokenGenerator.js.map +7 -0
  142. package/dist/modules/customer_accounts/migrations/Migration20260313222043.js +49 -0
  143. package/dist/modules/customer_accounts/migrations/Migration20260313222043.js.map +7 -0
  144. package/dist/modules/customer_accounts/notifications.client.js +47 -0
  145. package/dist/modules/customer_accounts/notifications.client.js.map +7 -0
  146. package/dist/modules/customer_accounts/notifications.js +46 -0
  147. package/dist/modules/customer_accounts/notifications.js.map +7 -0
  148. package/dist/modules/customer_accounts/search.js +120 -0
  149. package/dist/modules/customer_accounts/search.js.map +7 -0
  150. package/dist/modules/customer_accounts/services/customerInvitationService.js +87 -0
  151. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +7 -0
  152. package/dist/modules/customer_accounts/services/customerRbacService.js +109 -0
  153. package/dist/modules/customer_accounts/services/customerRbacService.js.map +7 -0
  154. package/dist/modules/customer_accounts/services/customerSessionService.js +75 -0
  155. package/dist/modules/customer_accounts/services/customerSessionService.js.map +7 -0
  156. package/dist/modules/customer_accounts/services/customerTokenService.js +91 -0
  157. package/dist/modules/customer_accounts/services/customerTokenService.js.map +7 -0
  158. package/dist/modules/customer_accounts/services/customerUserService.js +92 -0
  159. package/dist/modules/customer_accounts/services/customerUserService.js.map +7 -0
  160. package/dist/modules/customer_accounts/setup.js +179 -0
  161. package/dist/modules/customer_accounts/setup.js.map +7 -0
  162. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js +54 -0
  163. package/dist/modules/customer_accounts/subscribers/autoLinkCrm.js.map +7 -0
  164. package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js +68 -0
  165. package/dist/modules/customer_accounts/subscribers/autoLinkCrmReverse.js.map +7 -0
  166. package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js +29 -0
  167. package/dist/modules/customer_accounts/subscribers/notifyStaffOnSignup.js.map +7 -0
  168. package/dist/modules/customer_accounts/translations.js +9 -0
  169. package/dist/modules/customer_accounts/translations.js.map +7 -0
  170. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +63 -0
  171. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +7 -0
  172. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js +17 -0
  173. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.js.map +7 -0
  174. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js +55 -0
  175. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.client.js.map +7 -0
  176. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js +17 -0
  177. package/dist/modules/customer_accounts/widgets/injection/company-users/widget.js.map +7 -0
  178. package/dist/modules/customer_accounts/widgets/injection-table.js +26 -0
  179. package/dist/modules/customer_accounts/widgets/injection-table.js.map +7 -0
  180. package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js +23 -0
  181. package/dist/modules/customer_accounts/workers/cleanupExpiredSessions.js.map +7 -0
  182. package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js +38 -0
  183. package/dist/modules/customer_accounts/workers/cleanupExpiredTokens.js.map +7 -0
  184. package/dist/modules/customers/components/AddressTiles.js +1 -1
  185. package/dist/modules/customers/components/AddressTiles.js.map +2 -2
  186. package/dist/modules/directory/api/get/organizations/lookup.js +83 -0
  187. package/dist/modules/directory/api/get/organizations/lookup.js.map +7 -0
  188. package/dist/modules/directory/commands/organizations.js +32 -1
  189. package/dist/modules/directory/commands/organizations.js.map +2 -2
  190. package/dist/modules/directory/data/entities.js +6 -2
  191. package/dist/modules/directory/data/entities.js.map +2 -2
  192. package/dist/modules/directory/data/validators.js +3 -0
  193. package/dist/modules/directory/data/validators.js.map +2 -2
  194. package/dist/modules/directory/migrations/Migration20260314143323.js +15 -0
  195. package/dist/modules/directory/migrations/Migration20260314143323.js.map +7 -0
  196. package/dist/modules/directory/setup.js +36 -0
  197. package/dist/modules/directory/setup.js.map +2 -2
  198. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js +15 -0
  199. package/dist/modules/payment_gateways/migrations/Migration20260313222043.js.map +7 -0
  200. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +131 -0
  201. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +7 -0
  202. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +96 -0
  203. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +7 -0
  204. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +94 -0
  205. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +7 -0
  206. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +89 -0
  207. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +7 -0
  208. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js +104 -0
  209. package/dist/modules/portal/frontend/[orgSlug]/portal/signup/page.js.map +7 -0
  210. package/dist/modules/portal/index.js +11 -0
  211. package/dist/modules/portal/index.js.map +7 -0
  212. package/dist/modules/portal/setup.js +23 -0
  213. package/dist/modules/portal/setup.js.map +7 -0
  214. package/generated/entities/customer_role/index.ts +12 -0
  215. package/generated/entities/customer_role_acl/index.ts +8 -0
  216. package/generated/entities/customer_user/index.ts +17 -0
  217. package/generated/entities/customer_user_acl/index.ts +8 -0
  218. package/generated/entities/customer_user_email_verification/index.ts +7 -0
  219. package/generated/entities/customer_user_invitation/index.ts +15 -0
  220. package/generated/entities/customer_user_password_reset/index.ts +6 -0
  221. package/generated/entities/customer_user_role/index.ts +5 -0
  222. package/generated/entities/customer_user_session/index.ts +9 -0
  223. package/generated/entities/organization/index.ts +1 -0
  224. package/generated/entities.ids.generated.ts +14 -1
  225. package/generated/entity-fields-registry.ts +18 -0
  226. package/package.json +3 -3
  227. package/src/modules/auth/services/rbacService.ts +3 -9
  228. package/src/modules/customer_accounts/AGENTS.md +377 -0
  229. package/src/modules/customer_accounts/acl.ts +8 -0
  230. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +98 -0
  231. package/src/modules/customer_accounts/api/admin/roles/[id].ts +246 -0
  232. package/src/modules/customer_accounts/api/admin/roles.ts +212 -0
  233. package/src/modules/customer_accounts/api/admin/users/[id]/reset-password.ts +78 -0
  234. package/src/modules/customer_accounts/api/admin/users/[id]/verify-email.ts +72 -0
  235. package/src/modules/customer_accounts/api/admin/users/[id].ts +289 -0
  236. package/src/modules/customer_accounts/api/admin/users-invite.ts +86 -0
  237. package/src/modules/customer_accounts/api/admin/users.ts +280 -0
  238. package/src/modules/customer_accounts/api/email/verify.ts +66 -0
  239. package/src/modules/customer_accounts/api/interceptors.ts +3 -0
  240. package/src/modules/customer_accounts/api/invitations/accept.ts +128 -0
  241. package/src/modules/customer_accounts/api/login.ts +163 -0
  242. package/src/modules/customer_accounts/api/magic-link/request.ts +87 -0
  243. package/src/modules/customer_accounts/api/magic-link/verify.ts +132 -0
  244. package/src/modules/customer_accounts/api/password/reset-confirm.ts +69 -0
  245. package/src/modules/customer_accounts/api/password/reset-request.ts +87 -0
  246. package/src/modules/customer_accounts/api/portal/events/stream.ts +209 -0
  247. package/src/modules/customer_accounts/api/portal/feature-check.ts +60 -0
  248. package/src/modules/customer_accounts/api/portal/logout.ts +71 -0
  249. package/src/modules/customer_accounts/api/portal/notifications/[id]/dismiss.ts +54 -0
  250. package/src/modules/customer_accounts/api/portal/notifications/[id]/read.ts +54 -0
  251. package/src/modules/customer_accounts/api/portal/notifications/mark-all-read.ts +49 -0
  252. package/src/modules/customer_accounts/api/portal/notifications/unread-count.ts +45 -0
  253. package/src/modules/customer_accounts/api/portal/notifications.ts +115 -0
  254. package/src/modules/customer_accounts/api/portal/password-change.ts +65 -0
  255. package/src/modules/customer_accounts/api/portal/profile.ts +151 -0
  256. package/src/modules/customer_accounts/api/portal/sessions/[id].ts +70 -0
  257. package/src/modules/customer_accounts/api/portal/sessions-refresh.ts +87 -0
  258. package/src/modules/customer_accounts/api/portal/sessions.ts +84 -0
  259. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +106 -0
  260. package/src/modules/customer_accounts/api/portal/users/[id].ts +81 -0
  261. package/src/modules/customer_accounts/api/portal/users-invite.ts +103 -0
  262. package/src/modules/customer_accounts/api/portal/users.ts +86 -0
  263. package/src/modules/customer_accounts/api/signup.ts +136 -0
  264. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.meta.ts +11 -0
  265. package/src/modules/customer_accounts/backend/customer_accounts/[id]/page.tsx +607 -0
  266. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.meta.ts +12 -0
  267. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +385 -0
  268. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.meta.ts +12 -0
  269. package/src/modules/customer_accounts/backend/customer_accounts/roles/create/page.tsx +203 -0
  270. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.meta.ts +31 -0
  271. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +217 -0
  272. package/src/modules/customer_accounts/backend/page.meta.ts +33 -0
  273. package/src/modules/customer_accounts/backend/page.tsx +535 -0
  274. package/src/modules/customer_accounts/ce.ts +22 -0
  275. package/src/modules/customer_accounts/data/enrichers.ts +117 -0
  276. package/src/modules/customer_accounts/data/entities.ts +302 -0
  277. package/src/modules/customer_accounts/data/extensions.ts +4 -0
  278. package/src/modules/customer_accounts/data/validators.ts +128 -0
  279. package/src/modules/customer_accounts/di.ts +15 -0
  280. package/src/modules/customer_accounts/events.ts +28 -0
  281. package/src/modules/customer_accounts/i18n/de.json +176 -0
  282. package/src/modules/customer_accounts/i18n/en.json +176 -0
  283. package/src/modules/customer_accounts/i18n/es.json +176 -0
  284. package/src/modules/customer_accounts/i18n/pl.json +176 -0
  285. package/src/modules/customer_accounts/index.ts +13 -0
  286. package/src/modules/customer_accounts/lib/customerAuth.ts +85 -0
  287. package/src/modules/customer_accounts/lib/customerAuthServer.ts +54 -0
  288. package/src/modules/customer_accounts/lib/rateLimiter.ts +36 -0
  289. package/src/modules/customer_accounts/lib/tokenGenerator.ts +9 -0
  290. package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +1255 -0
  291. package/src/modules/customer_accounts/migrations/Migration20260313222043.ts +62 -0
  292. package/src/modules/customer_accounts/notifications.client.ts +46 -0
  293. package/src/modules/customer_accounts/notifications.ts +44 -0
  294. package/src/modules/customer_accounts/search.ts +134 -0
  295. package/src/modules/customer_accounts/services/customerInvitationService.ts +109 -0
  296. package/src/modules/customer_accounts/services/customerRbacService.ts +144 -0
  297. package/src/modules/customer_accounts/services/customerSessionService.ts +90 -0
  298. package/src/modules/customer_accounts/services/customerTokenService.ts +98 -0
  299. package/src/modules/customer_accounts/services/customerUserService.ts +105 -0
  300. package/src/modules/customer_accounts/setup.ts +212 -0
  301. package/src/modules/customer_accounts/subscribers/autoLinkCrm.ts +65 -0
  302. package/src/modules/customer_accounts/subscribers/autoLinkCrmReverse.ts +78 -0
  303. package/src/modules/customer_accounts/subscribers/notifyStaffOnSignup.ts +32 -0
  304. package/src/modules/customer_accounts/translations.ts +5 -0
  305. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +89 -0
  306. package/src/modules/customer_accounts/widgets/injection/account-status/widget.ts +16 -0
  307. package/src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx +78 -0
  308. package/src/modules/customer_accounts/widgets/injection/company-users/widget.ts +16 -0
  309. package/src/modules/customer_accounts/widgets/injection-table.ts +24 -0
  310. package/src/modules/customer_accounts/workers/cleanupExpiredSessions.ts +33 -0
  311. package/src/modules/customer_accounts/workers/cleanupExpiredTokens.ts +51 -0
  312. package/src/modules/customers/components/AddressTiles.tsx +1 -1
  313. package/src/modules/directory/api/get/organizations/lookup.ts +92 -0
  314. package/src/modules/directory/commands/organizations.ts +34 -1
  315. package/src/modules/directory/data/entities.ts +5 -1
  316. package/src/modules/directory/data/validators.ts +4 -0
  317. package/src/modules/directory/migrations/.snapshot-open-mercato.json +20 -1
  318. package/src/modules/directory/migrations/Migration20260314143323.ts +15 -0
  319. package/src/modules/directory/setup.ts +41 -0
  320. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +4 -1
  321. package/src/modules/payment_gateways/migrations/Migration20260313222043.ts +17 -0
  322. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +158 -0
  323. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +120 -0
  324. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +118 -0
  325. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +112 -0
  326. package/src/modules/portal/frontend/[orgSlug]/portal/signup/page.tsx +138 -0
  327. package/src/modules/portal/i18n/de.json +93 -0
  328. package/src/modules/portal/i18n/en.json +93 -0
  329. package/src/modules/portal/i18n/es.json +93 -0
  330. package/src/modules/portal/i18n/pl.json +93 -0
  331. package/src/modules/portal/index.ts +9 -0
  332. package/src/modules/portal/setup.ts +23 -0
  333. package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +226 -0
@@ -0,0 +1,55 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ function CompanyUsersWidget({ context }) {
7
+ const t = useT();
8
+ const customerEntityId = context?.recordId;
9
+ const { data, isLoading } = useQuery({
10
+ queryKey: ["customer-company-users", customerEntityId],
11
+ queryFn: async () => {
12
+ if (!customerEntityId) return [];
13
+ const result = await apiCall(`/api/customer_accounts/admin/users?customerEntityId=${customerEntityId}&pageSize=50`);
14
+ if (!result.ok) return [];
15
+ const json = result.result;
16
+ return json?.items || [];
17
+ },
18
+ enabled: !!customerEntityId
19
+ });
20
+ if (isLoading) {
21
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("common.loading", "Loading...") });
22
+ }
23
+ const users = data || [];
24
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-md border p-3", children: [
25
+ /* @__PURE__ */ jsxs("div", { className: "text-sm font-medium mb-2", children: [
26
+ t("customer_accounts.widgets.portalUsers", "Portal Users"),
27
+ users.length > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-muted-foreground", children: [
28
+ "(",
29
+ users.length,
30
+ ")"
31
+ ] })
32
+ ] }),
33
+ users.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("customer_accounts.widgets.noUsers", "No portal users for this company") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: users.map((user) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm", children: [
34
+ /* @__PURE__ */ jsxs("div", { children: [
35
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: user.displayName }),
36
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: user.email })
37
+ ] }),
38
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
39
+ /* @__PURE__ */ jsx("span", { className: `text-xs ${user.isActive ? "text-green-600" : "text-red-600"}`, children: user.isActive ? t("common.active", "Active") : t("common.inactive", "Inactive") }),
40
+ /* @__PURE__ */ jsx(
41
+ "a",
42
+ {
43
+ href: `/backend/customer_accounts/${user.id}`,
44
+ className: "text-xs text-primary hover:underline",
45
+ children: t("common.view", "View")
46
+ }
47
+ )
48
+ ] })
49
+ ] }, user.id)) })
50
+ ] });
51
+ }
52
+ export {
53
+ CompanyUsersWidget as default
54
+ };
55
+ //# sourceMappingURL=widget.client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/customer_accounts/widgets/injection/company-users/widget.client.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport { useQuery } from '@tanstack/react-query'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\ninterface CompanyUser {\n id: string\n displayName: string\n email: string\n isActive: boolean\n}\n\ninterface CompanyUsersProps {\n context?: {\n entityId?: string\n recordId?: string\n }\n}\n\nexport default function CompanyUsersWidget({ context }: CompanyUsersProps) {\n const t = useT()\n const customerEntityId = context?.recordId\n\n const { data, isLoading } = useQuery({\n queryKey: ['customer-company-users', customerEntityId],\n queryFn: async (): Promise<CompanyUser[]> => {\n if (!customerEntityId) return []\n const result = await apiCall(`/api/customer_accounts/admin/users?customerEntityId=${customerEntityId}&pageSize=50`)\n if (!result.ok) return []\n const json = result.result as Record<string, unknown> | null\n return (json?.items as CompanyUser[]) || []\n },\n enabled: !!customerEntityId,\n })\n\n if (isLoading) {\n return <div className=\"text-sm text-muted-foreground\">{t('common.loading', 'Loading...')}</div>\n }\n\n const users = data || []\n\n return (\n <div className=\"rounded-md border p-3\">\n <div className=\"text-sm font-medium mb-2\">\n {t('customer_accounts.widgets.portalUsers', 'Portal Users')}\n {users.length > 0 && <span className=\"ml-1 text-muted-foreground\">({users.length})</span>}\n </div>\n {users.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">\n {t('customer_accounts.widgets.noUsers', 'No portal users for this company')}\n </div>\n ) : (\n <div className=\"space-y-2\">\n {users.map((user) => (\n <div key={user.id} className=\"flex items-center justify-between text-sm\">\n <div>\n <div className=\"font-medium\">{user.displayName}</div>\n <div className=\"text-xs text-muted-foreground\">{user.email}</div>\n </div>\n <div className=\"flex items-center gap-2\">\n <span className={`text-xs ${user.isActive ? 'text-green-600' : 'text-red-600'}`}>\n {user.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}\n </span>\n <a\n href={`/backend/customer_accounts/${user.id}`}\n className=\"text-xs text-primary hover:underline\"\n >\n {t('common.view', 'View')}\n </a>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAqCW,cASkB,YATlB;AAnCX,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,YAAY;AAgBN,SAAR,mBAAoC,EAAE,QAAQ,GAAsB;AACzE,QAAM,IAAI,KAAK;AACf,QAAM,mBAAmB,SAAS;AAElC,QAAM,EAAE,MAAM,UAAU,IAAI,SAAS;AAAA,IACnC,UAAU,CAAC,0BAA0B,gBAAgB;AAAA,IACrD,SAAS,YAAoC;AAC3C,UAAI,CAAC,iBAAkB,QAAO,CAAC;AAC/B,YAAM,SAAS,MAAM,QAAQ,uDAAuD,gBAAgB,cAAc;AAClH,UAAI,CAAC,OAAO,GAAI,QAAO,CAAC;AACxB,YAAM,OAAO,OAAO;AACpB,aAAQ,MAAM,SAA2B,CAAC;AAAA,IAC5C;AAAA,IACA,SAAS,CAAC,CAAC;AAAA,EACb,CAAC;AAED,MAAI,WAAW;AACb,WAAO,oBAAC,SAAI,WAAU,iCAAiC,YAAE,kBAAkB,YAAY,GAAE;AAAA,EAC3F;AAEA,QAAM,QAAQ,QAAQ,CAAC;AAEvB,SACE,qBAAC,SAAI,WAAU,yBACb;AAAA,yBAAC,SAAI,WAAU,4BACZ;AAAA,QAAE,yCAAyC,cAAc;AAAA,MACzD,MAAM,SAAS,KAAK,qBAAC,UAAK,WAAU,8BAA6B;AAAA;AAAA,QAAE,MAAM;AAAA,QAAO;AAAA,SAAC;AAAA,OACpF;AAAA,IACC,MAAM,WAAW,IAChB,oBAAC,SAAI,WAAU,iCACZ,YAAE,qCAAqC,kCAAkC,GAC5E,IAEA,oBAAC,SAAI,WAAU,aACZ,gBAAM,IAAI,CAAC,SACV,qBAAC,SAAkB,WAAU,6CAC3B;AAAA,2BAAC,SACC;AAAA,4BAAC,SAAI,WAAU,eAAe,eAAK,aAAY;AAAA,QAC/C,oBAAC,SAAI,WAAU,iCAAiC,eAAK,OAAM;AAAA,SAC7D;AAAA,MACA,qBAAC,SAAI,WAAU,2BACb;AAAA,4BAAC,UAAK,WAAW,WAAW,KAAK,WAAW,mBAAmB,cAAc,IAC1E,eAAK,WAAW,EAAE,iBAAiB,QAAQ,IAAI,EAAE,mBAAmB,UAAU,GACjF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,MAAM,8BAA8B,KAAK,EAAE;AAAA,YAC3C,WAAU;AAAA,YAET,YAAE,eAAe,MAAM;AAAA;AAAA,QAC1B;AAAA,SACF;AAAA,SAfQ,KAAK,EAgBf,CACD,GACH;AAAA,KAEJ;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import CompanyUsersWidget from "./widget.client.js";
2
+ const widget = {
3
+ metadata: {
4
+ id: "customer_accounts.injection.company-users",
5
+ title: "Company Portal Users",
6
+ description: "Shows portal users associated with a CRM company",
7
+ features: ["customer_accounts.view"],
8
+ priority: 100,
9
+ enabled: true
10
+ },
11
+ Widget: CompanyUsersWidget
12
+ };
13
+ var widget_default = widget;
14
+ export {
15
+ widget_default as default
16
+ };
17
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/customer_accounts/widgets/injection/company-users/widget.ts"],
4
+ "sourcesContent": ["import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'\nimport CompanyUsersWidget from './widget.client'\n\nconst widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {\n metadata: {\n id: 'customer_accounts.injection.company-users',\n title: 'Company Portal Users',\n description: 'Shows portal users associated with a CRM company',\n features: ['customer_accounts.view'],\n priority: 100,\n enabled: true,\n },\n Widget: CompanyUsersWidget,\n}\n\nexport default widget\n"],
5
+ "mappings": "AACA,OAAO,wBAAwB;AAE/B,MAAM,SAAkF;AAAA,EACtF,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,wBAAwB;AAAA,IACnC,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AACV;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,26 @@
1
+ const injectionTable = {
2
+ "crud-form:customers:customer_person_profile:fields": [
3
+ {
4
+ widgetId: "customer_accounts.injection.account-status",
5
+ kind: "group",
6
+ column: 2,
7
+ groupLabel: "customer_accounts.widgets.accountStatus",
8
+ priority: 200
9
+ }
10
+ ],
11
+ "crud-form:customers:customer_company_profile:fields": [
12
+ {
13
+ widgetId: "customer_accounts.injection.company-users",
14
+ kind: "group",
15
+ column: 2,
16
+ groupLabel: "customer_accounts.widgets.portalUsers",
17
+ priority: 200
18
+ }
19
+ ]
20
+ };
21
+ var injection_table_default = injectionTable;
22
+ export {
23
+ injection_table_default as default,
24
+ injectionTable
25
+ };
26
+ //# sourceMappingURL=injection-table.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customer_accounts/widgets/injection-table.ts"],
4
+ "sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\nexport const injectionTable: ModuleInjectionTable = {\n 'crud-form:customers:customer_person_profile:fields': [\n {\n widgetId: 'customer_accounts.injection.account-status',\n kind: 'group',\n column: 2,\n groupLabel: 'customer_accounts.widgets.accountStatus',\n priority: 200,\n },\n ],\n 'crud-form:customers:customer_company_profile:fields': [\n {\n widgetId: 'customer_accounts.injection.company-users',\n kind: 'group',\n column: 2,\n groupLabel: 'customer_accounts.widgets.portalUsers',\n priority: 200,\n },\n ],\n}\n\nexport default injectionTable\n"],
5
+ "mappings": "AAEO,MAAM,iBAAuC;AAAA,EAClD,sDAAsD;AAAA,IACpD;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,uDAAuD;AAAA,IACrD;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,YAAY;AAAA,MACZ,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ import { CustomerUserSession } from "@open-mercato/core/modules/customer_accounts/data/entities";
2
+ const metadata = {
3
+ queue: "customer-accounts-cleanup-sessions",
4
+ id: "customer_accounts:cleanup-expired-sessions",
5
+ concurrency: 1
6
+ };
7
+ async function handle(job, ctx) {
8
+ const em = ctx.resolve("em");
9
+ const batchSize = job.payload?.batchSize || 1e3;
10
+ const deleted = await em.nativeDelete(CustomerUserSession, {
11
+ $or: [
12
+ { expiresAt: { $lt: /* @__PURE__ */ new Date() } },
13
+ { deletedAt: { $ne: null } }
14
+ ]
15
+ });
16
+ if (typeof deleted === "number" && deleted > 0) {
17
+ }
18
+ }
19
+ export {
20
+ handle as default,
21
+ metadata
22
+ };
23
+ //# sourceMappingURL=cleanupExpiredSessions.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customer_accounts/workers/cleanupExpiredSessions.ts"],
4
+ "sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CustomerUserSession } from '@open-mercato/core/modules/customer_accounts/data/entities'\n\ntype CleanupPayload = {\n batchSize?: number\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'customer-accounts-cleanup-sessions',\n id: 'customer_accounts:cleanup-expired-sessions',\n concurrency: 1,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(job: QueuedJob<CleanupPayload>, ctx: HandlerContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n const batchSize = job.payload?.batchSize || 1000\n\n const deleted = await em.nativeDelete(CustomerUserSession, {\n $or: [\n { expiresAt: { $lt: new Date() } },\n { deletedAt: { $ne: null } },\n ],\n } as any)\n\n if (typeof deleted === 'number' && deleted > 0) {\n // Batch processed; if more remain, the scheduler will enqueue again\n }\n}\n"],
5
+ "mappings": "AAEA,SAAS,2BAA2B;AAM7B,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAMA,eAAO,OAA8B,KAAgC,KAAoC;AACvG,QAAM,KAAK,IAAI,QAAuB,IAAI;AAC1C,QAAM,YAAY,IAAI,SAAS,aAAa;AAE5C,QAAM,UAAU,MAAM,GAAG,aAAa,qBAAqB;AAAA,IACzD,KAAK;AAAA,MACH,EAAE,WAAW,EAAE,KAAK,oBAAI,KAAK,EAAE,EAAE;AAAA,MACjC,EAAE,WAAW,EAAE,KAAK,KAAK,EAAE;AAAA,IAC7B;AAAA,EACF,CAAQ;AAER,MAAI,OAAO,YAAY,YAAY,UAAU,GAAG;AAAA,EAEhD;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ CustomerUserEmailVerification,
3
+ CustomerUserPasswordReset,
4
+ CustomerUserInvitation
5
+ } from "@open-mercato/core/modules/customer_accounts/data/entities";
6
+ const metadata = {
7
+ queue: "customer-accounts-cleanup-tokens",
8
+ id: "customer_accounts:cleanup-expired-tokens",
9
+ concurrency: 1
10
+ };
11
+ async function handle(job, ctx) {
12
+ const em = ctx.resolve("em");
13
+ const now = /* @__PURE__ */ new Date();
14
+ await em.nativeDelete(CustomerUserEmailVerification, {
15
+ $or: [
16
+ { expiresAt: { $lt: now }, usedAt: null },
17
+ { usedAt: { $ne: null } }
18
+ ]
19
+ });
20
+ await em.nativeDelete(CustomerUserPasswordReset, {
21
+ $or: [
22
+ { expiresAt: { $lt: now }, usedAt: null },
23
+ { usedAt: { $ne: null } }
24
+ ]
25
+ });
26
+ await em.nativeDelete(CustomerUserInvitation, {
27
+ $or: [
28
+ { expiresAt: { $lt: now }, acceptedAt: null, cancelledAt: null },
29
+ { acceptedAt: { $ne: null } },
30
+ { cancelledAt: { $ne: null } }
31
+ ]
32
+ });
33
+ }
34
+ export {
35
+ handle as default,
36
+ metadata
37
+ };
38
+ //# sourceMappingURL=cleanupExpiredTokens.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customer_accounts/workers/cleanupExpiredTokens.ts"],
4
+ "sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n CustomerUserEmailVerification,\n CustomerUserPasswordReset,\n CustomerUserInvitation,\n} from '@open-mercato/core/modules/customer_accounts/data/entities'\n\ntype CleanupPayload = {\n batchSize?: number\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'customer-accounts-cleanup-tokens',\n id: 'customer_accounts:cleanup-expired-tokens',\n concurrency: 1,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(job: QueuedJob<CleanupPayload>, ctx: HandlerContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n const now = new Date()\n\n // Cleanup expired email verifications\n await em.nativeDelete(CustomerUserEmailVerification, {\n $or: [\n { expiresAt: { $lt: now }, usedAt: null },\n { usedAt: { $ne: null } },\n ],\n } as any)\n\n // Cleanup expired password resets\n await em.nativeDelete(CustomerUserPasswordReset, {\n $or: [\n { expiresAt: { $lt: now }, usedAt: null },\n { usedAt: { $ne: null } },\n ],\n } as any)\n\n // Cleanup expired/accepted/cancelled invitations\n await em.nativeDelete(CustomerUserInvitation, {\n $or: [\n { expiresAt: { $lt: now }, acceptedAt: null, cancelledAt: null },\n { acceptedAt: { $ne: null } },\n { cancelledAt: { $ne: null } },\n ],\n } as any)\n}\n"],
5
+ "mappings": "AAEA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAMA,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAMA,eAAO,OAA8B,KAAgC,KAAoC;AACvG,QAAM,KAAK,IAAI,QAAuB,IAAI;AAC1C,QAAM,MAAM,oBAAI,KAAK;AAGrB,QAAM,GAAG,aAAa,+BAA+B;AAAA,IACnD,KAAK;AAAA,MACH,EAAE,WAAW,EAAE,KAAK,IAAI,GAAG,QAAQ,KAAK;AAAA,MACxC,EAAE,QAAQ,EAAE,KAAK,KAAK,EAAE;AAAA,IAC1B;AAAA,EACF,CAAQ;AAGR,QAAM,GAAG,aAAa,2BAA2B;AAAA,IAC/C,KAAK;AAAA,MACH,EAAE,WAAW,EAAE,KAAK,IAAI,GAAG,QAAQ,KAAK;AAAA,MACxC,EAAE,QAAQ,EAAE,KAAK,KAAK,EAAE;AAAA,IAC1B;AAAA,EACF,CAAQ;AAGR,QAAM,GAAG,aAAa,wBAAwB;AAAA,IAC5C,KAAK;AAAA,MACH,EAAE,WAAW,EAAE,KAAK,IAAI,GAAG,YAAY,MAAM,aAAa,KAAK;AAAA,MAC/D,EAAE,YAAY,EAAE,KAAK,KAAK,EAAE;AAAA,MAC5B,EAAE,aAAa,EAAE,KAAK,KAAK,EAAE;AAAA,IAC/B;AAAA,EACF,CAAQ;AACV;",
6
+ "names": []
7
+ }
@@ -366,7 +366,7 @@ function CustomerAddressTiles({
366
366
  ]
367
367
  );
368
368
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
369
- !hideAddButton ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
369
+ !hideAddButton && hasAddresses ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs(
370
370
  Button,
371
371
  {
372
372
  type: "button",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/customers/components/AddressTiles.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { AddressView, formatAddressJson, formatAddressString, type AddressFormatStrategy } from '../utils/addressFormat'\nimport AddressEditor from './AddressEditor'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { ensureCustomerDictionary, invalidateCustomerDictionary } from './detail/hooks/useCustomerDictionary'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type CustomerAddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n isPrimary?: boolean\n}\n\nexport type CustomerAddressValue = CustomerAddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n}\n\ntype CustomerAddressTilesProps = {\n addresses: CustomerAddressValue[]\n onCreate: (payload: CustomerAddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: CustomerAddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator): string {\n switch (detail.code) {\n case 'invalid_type':\n return t('customers.people.detail.addresses.validation.invalid', undefined, { field: fieldLabel })\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return t('customers.people.detail.addresses.validation.required', undefined, { field: fieldLabel })\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return t(\n 'customers.people.detail.addresses.validation.tooLong',\n undefined,\n {\n field: fieldLabel,\n max: detail.maximum,\n }\n )\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n default:\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n }\n}\n\nexport function CustomerAddressTiles({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid gap-4 min-[480px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n}: CustomerAddressTilesProps) {\n const scopeVersion = useOrganizationScopeVersion()\n const queryClient = useQueryClient()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\n const { map: addressTypeMap } = useAddressTypes(t)\n\n const fieldLabels = React.useMemo(\n () => ({\n name: t('customers.people.detail.addresses.fields.label'),\n purpose: t('customers.people.detail.addresses.fields.type'),\n companyName: t('customers.people.detail.addresses.fields.companyName', 'Company name'),\n addressLine1: t('customers.people.detail.addresses.fields.line1'),\n addressLine2: t('customers.people.detail.addresses.fields.line2'),\n street: t('customers.people.detail.addresses.fields.street', 'Street'),\n buildingNumber: t('customers.people.detail.addresses.fields.buildingNumber', 'Building number'),\n flatNumber: t('customers.people.detail.addresses.fields.flatNumber', 'Flat number'),\n city: t('customers.people.detail.addresses.fields.city'),\n region: t('customers.people.detail.addresses.fields.region'),\n postalCode: t('customers.people.detail.addresses.fields.postalCode'),\n country: t('customers.people.detail.addresses.fields.country'),\n isPrimary: t('customers.people.detail.addresses.fields.primary'),\n }),\n [t]\n )\n\n\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormat() {\n setFormatLoading(true)\n try {\n const call = await apiCall<{ addressFormat?: string; error?: string }>(\n '/api/customers/settings/address-format',\n )\n const payload = (call.result ?? {}) as Record<string, unknown>\n if (!call.ok) {\n if (!cancelled) {\n const message =\n typeof (payload as Record<string, unknown>)?.error === 'string'\n ? (payload as Record<string, unknown>).error as string\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n return\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n loadFormat().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [scopeVersion, t])\n\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const openEditForm = React.useCallback(\n (address: CustomerAddressValue) => {\n setDraft({\n name: address.name ?? '',\n purpose: address.purpose ?? '',\n companyName: address.companyName ?? '',\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? '',\n buildingNumber: address.buildingNumber ?? '',\n flatNumber: address.flatNumber ?? '',\n city: address.city ?? '',\n region: address.region ?? '',\n postalCode: address.postalCode ?? '',\n country: address.country ? address.country.toUpperCase() : '',\n isPrimary: address.isPrimary ?? false,\n })\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(address.id)\n setIsFormOpen(true)\n },\n []\n )\n\n const handleCancel = React.useCallback(() => {\n setIsFormOpen(false)\n resetForm()\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n const trimmedLine1 = draft.addressLine1.trim()\n if (!trimmedLine1.length) {\n const message = t(\n 'customers.people.detail.addresses.validation.required',\n undefined,\n { field: fieldLabels.addressLine1 }\n )\n setFieldErrors((prev) => ({ ...prev, addressLine1: message }))\n setGeneralError(message)\n return\n }\n\n const payload: CustomerAddressInput = {\n addressLine1: trimmedLine1,\n isPrimary: draft.isPrimary,\n }\n\n const purpose = normalizeOptional(draft.purpose)\n if (purpose !== undefined) payload.purpose = purpose\n const name = normalizeOptional(draft.name)\n if (name !== undefined) payload.name = name\n const companyName = normalizeOptional(draft.companyName)\n if (companyName !== undefined) payload.companyName = companyName\n const line2 = normalizeOptional(draft.addressLine2)\n if (line2 !== undefined) payload.addressLine2 = line2\n const buildingNumber = normalizeOptional(draft.buildingNumber)\n if (buildingNumber !== undefined) payload.buildingNumber = buildingNumber\n const flatNumber = normalizeOptional(draft.flatNumber)\n if (flatNumber !== undefined) payload.flatNumber = flatNumber\n const city = normalizeOptional(draft.city)\n if (city !== undefined) payload.city = city\n const region = normalizeOptional(draft.region)\n if (region !== undefined) payload.region = region\n const postal = normalizeOptional(draft.postalCode)\n if (postal !== undefined) payload.postalCode = postal\n const country = normalizeOptional(draft.country)\n if (country !== undefined) payload.country = country.toUpperCase()\n\n setSaving(true)\n setGeneralError(null)\n setFieldErrors({})\n try {\n if (editingId && onUpdate) await onUpdate(editingId, payload)\n else await onCreate(payload)\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n for (const detail of details) {\n const path = Array.isArray(detail.path) ? detail.path : []\n const targetKey = path.length ? serverFieldMap[String(path[0])] : undefined\n if (!targetKey) continue\n const message = resolveFieldMessage(detail, fieldLabels[targetKey], t)\n if (message) nextErrors[targetKey] = message\n }\n if (Object.keys(nextErrors).length) {\n setFieldErrors(nextErrors)\n setGeneralError(Object.values(nextErrors)[0] ?? null)\n setSaving(false)\n return\n }\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, fieldLabels, onCreate, onUpdate, resetForm, t, editingId])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, onDelete, resetForm, t]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? t('customers.people.detail.addresses.add')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/20 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? t('customers.people.detail.addresses.editTitle')\n : t('customers.people.detail.addresses.addTitle')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.detail.addresses.formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft(next)\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n />\n {generalError ? <p className=\"text-xs text-red-600\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {t('customers.people.detail.addresses.cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? t('customers.people.detail.addresses.updating')\n : t('customers.people.detail.addresses.saving')}\n </>\n ) : editingId ? (\n t('customers.people.detail.addresses.update')\n ) : (\n t('customers.people.detail.addresses.save')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n saving,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('customers.people.detail.addresses.add')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(address.id)\n }\n const formattedJson = formatAddressJson(address, format)\n const formattedString = formatAddressString(address, format)\n\n return (\n <div\n key={address.id}\n className=\"rounded-lg border bg-background p-4 text-sm shadow-sm\"\n title={formattedString}\n data-address-json={JSON.stringify(formattedJson)}\n data-address-string={formattedString}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <span className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {address.name ||\n (address.purpose ? addressTypeMap.get(address.purpose) ?? address.purpose : null) ||\n t('customers.people.detail.address')}\n </span>\n {address.isPrimary ? (\n <span className=\"mt-1 inline-flex w-fit rounded bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700\">\n {t('customers.people.detail.primary')}\n </span>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => openEditForm(address)}\n disabled={disableActions}\n aria-label={t('customers.people.detail.addresses.editAction')}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-destructive hover:text-destructive focus-visible:text-destructive\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions || !onDelete}\n aria-label={t('customers.people.detail.addresses.deleteAction')}\n >\n {deletingId === address.id ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n <div className=\"mt-2 space-y-1\">\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {addressTypeMap.get(address.purpose) ?? address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"space-y-1\" lineClassName=\"text-sm\" />\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('__new') : null}\n </div>\n ) : isFormOpen && !editingId ? (\n <div className={gridClassName}>{renderFormTile('__new')}</div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Loader2, Pencil, Plus, Trash2, X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { AddressView, formatAddressJson, formatAddressString, type AddressFormatStrategy } from '../utils/addressFormat'\nimport AddressEditor from './AddressEditor'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { ensureCustomerDictionary, invalidateCustomerDictionary } from './detail/hooks/useCustomerDictionary'\n\nexport type Translator = (\n key: string,\n fallback?: string,\n params?: Record<string, string | number>,\n) => string\n\nexport type CustomerAddressInput = {\n name?: string\n purpose?: string\n companyName?: string\n addressLine1: string\n addressLine2?: string\n buildingNumber?: string\n flatNumber?: string\n city?: string\n region?: string\n postalCode?: string\n country?: string\n isPrimary?: boolean\n}\n\nexport type CustomerAddressValue = CustomerAddressInput & {\n id: string\n purpose?: string | null\n companyName?: string | null\n}\n\ntype CustomerAddressTilesProps = {\n addresses: CustomerAddressValue[]\n onCreate: (payload: CustomerAddressInput) => Promise<void> | void\n onUpdate?: (id: string, payload: CustomerAddressInput) => Promise<void> | void\n onDelete?: (id: string) => Promise<void> | void\n t: Translator\n emptyLabel: string\n isSubmitting?: boolean\n gridClassName?: string\n hideAddButton?: boolean\n onAddActionChange?: (action: { openCreateForm: () => void; addDisabled: boolean } | null) => void\n emptyStateTitle?: string\n emptyStateActionLabel?: string\n}\n\ntype DraftAddressState = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\ntype DraftFieldKey = keyof DraftAddressState\n\ntype AddressValidationDetail = {\n path?: Array<string | number>\n code?: string\n message?: string\n minimum?: number\n maximum?: number\n type?: string\n}\n\nconst defaultDraft: DraftAddressState = {\n name: '',\n purpose: '',\n companyName: '',\n addressLine1: '',\n addressLine2: '',\n buildingNumber: '',\n flatNumber: '',\n city: '',\n region: '',\n postalCode: '',\n country: '',\n isPrimary: false,\n}\n\nconst serverFieldMap: Record<string, DraftFieldKey> = {\n name: 'name',\n purpose: 'purpose',\n companyName: 'companyName',\n addressLine1: 'addressLine1',\n addressLine2: 'addressLine2',\n buildingNumber: 'buildingNumber',\n flatNumber: 'flatNumber',\n city: 'city',\n region: 'region',\n postalCode: 'postalCode',\n country: 'country',\n isPrimary: 'isPrimary',\n}\n\nfunction normalizeOptional(value: string): string | undefined {\n const trimmed = value.trim()\n return trimmed.length ? trimmed : undefined\n}\n\nfunction extractValidationDetails(error: unknown): AddressValidationDetail[] {\n if (!error || typeof error !== 'object') return []\n const candidate = (error as { details?: unknown }).details\n if (!Array.isArray(candidate)) return []\n return candidate\n .map((entry) => (entry && typeof entry === 'object' ? (entry as AddressValidationDetail) : null))\n .filter((entry): entry is AddressValidationDetail => entry !== null)\n}\n\nfunction resolveFieldMessage(detail: AddressValidationDetail, fieldLabel: string, t: Translator): string {\n switch (detail.code) {\n case 'invalid_type':\n return t('customers.people.detail.addresses.validation.invalid', undefined, { field: fieldLabel })\n case 'too_small':\n if (detail.minimum === 1 && detail.type === 'string') {\n return t('customers.people.detail.addresses.validation.required', undefined, { field: fieldLabel })\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n case 'too_big':\n if (typeof detail.maximum === 'number') {\n return t(\n 'customers.people.detail.addresses.validation.tooLong',\n undefined,\n {\n field: fieldLabel,\n max: detail.maximum,\n }\n )\n }\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n default:\n return t('customers.people.detail.addresses.validation.generic', undefined, { field: fieldLabel })\n }\n}\n\nexport function CustomerAddressTiles({\n addresses,\n onCreate,\n onUpdate,\n onDelete,\n t,\n emptyLabel,\n isSubmitting = false,\n gridClassName = 'grid gap-4 min-[480px]:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4',\n hideAddButton = false,\n onAddActionChange,\n emptyStateTitle,\n emptyStateActionLabel,\n}: CustomerAddressTilesProps) {\n const scopeVersion = useOrganizationScopeVersion()\n const queryClient = useQueryClient()\n const [isFormOpen, setIsFormOpen] = React.useState(false)\n const [editingId, setEditingId] = React.useState<string | null>(null)\n const [draft, setDraft] = React.useState<DraftAddressState>(defaultDraft)\n const [saving, setSaving] = React.useState(false)\n const [deletingId, setDeletingId] = React.useState<string | null>(null)\n const [generalError, setGeneralError] = React.useState<string | null>(null)\n const [fieldErrors, setFieldErrors] = React.useState<Partial<Record<DraftFieldKey, string>>>({})\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [formatLoading, setFormatLoading] = React.useState(false)\n const { map: addressTypeMap } = useAddressTypes(t)\n\n const fieldLabels = React.useMemo(\n () => ({\n name: t('customers.people.detail.addresses.fields.label'),\n purpose: t('customers.people.detail.addresses.fields.type'),\n companyName: t('customers.people.detail.addresses.fields.companyName', 'Company name'),\n addressLine1: t('customers.people.detail.addresses.fields.line1'),\n addressLine2: t('customers.people.detail.addresses.fields.line2'),\n street: t('customers.people.detail.addresses.fields.street', 'Street'),\n buildingNumber: t('customers.people.detail.addresses.fields.buildingNumber', 'Building number'),\n flatNumber: t('customers.people.detail.addresses.fields.flatNumber', 'Flat number'),\n city: t('customers.people.detail.addresses.fields.city'),\n region: t('customers.people.detail.addresses.fields.region'),\n postalCode: t('customers.people.detail.addresses.fields.postalCode'),\n country: t('customers.people.detail.addresses.fields.country'),\n isPrimary: t('customers.people.detail.addresses.fields.primary'),\n }),\n [t]\n )\n\n\n\n const resetForm = React.useCallback(() => {\n setDraft(defaultDraft)\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(null)\n }, [])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadFormat() {\n setFormatLoading(true)\n try {\n const call = await apiCall<{ addressFormat?: string; error?: string }>(\n '/api/customers/settings/address-format',\n )\n const payload = (call.result ?? {}) as Record<string, unknown>\n if (!call.ok) {\n if (!cancelled) {\n const message =\n typeof (payload as Record<string, unknown>)?.error === 'string'\n ? (payload as Record<string, unknown>).error as string\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n return\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (!cancelled && (value === 'street_first' || value === 'line_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.formatLoadError', 'Failed to load address configuration')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setFormatLoading(false)\n }\n }\n loadFormat().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [scopeVersion, t])\n\n\n const openCreateForm = React.useCallback(() => {\n resetForm()\n setIsFormOpen(true)\n }, [resetForm])\n\n const openEditForm = React.useCallback(\n (address: CustomerAddressValue) => {\n setDraft({\n name: address.name ?? '',\n purpose: address.purpose ?? '',\n companyName: address.companyName ?? '',\n addressLine1: address.addressLine1,\n addressLine2: address.addressLine2 ?? '',\n buildingNumber: address.buildingNumber ?? '',\n flatNumber: address.flatNumber ?? '',\n city: address.city ?? '',\n region: address.region ?? '',\n postalCode: address.postalCode ?? '',\n country: address.country ? address.country.toUpperCase() : '',\n isPrimary: address.isPrimary ?? false,\n })\n setFieldErrors({})\n setGeneralError(null)\n setEditingId(address.id)\n setIsFormOpen(true)\n },\n []\n )\n\n const handleCancel = React.useCallback(() => {\n setIsFormOpen(false)\n resetForm()\n }, [resetForm])\n\n const handleSave = React.useCallback(async () => {\n const trimmedLine1 = draft.addressLine1.trim()\n if (!trimmedLine1.length) {\n const message = t(\n 'customers.people.detail.addresses.validation.required',\n undefined,\n { field: fieldLabels.addressLine1 }\n )\n setFieldErrors((prev) => ({ ...prev, addressLine1: message }))\n setGeneralError(message)\n return\n }\n\n const payload: CustomerAddressInput = {\n addressLine1: trimmedLine1,\n isPrimary: draft.isPrimary,\n }\n\n const purpose = normalizeOptional(draft.purpose)\n if (purpose !== undefined) payload.purpose = purpose\n const name = normalizeOptional(draft.name)\n if (name !== undefined) payload.name = name\n const companyName = normalizeOptional(draft.companyName)\n if (companyName !== undefined) payload.companyName = companyName\n const line2 = normalizeOptional(draft.addressLine2)\n if (line2 !== undefined) payload.addressLine2 = line2\n const buildingNumber = normalizeOptional(draft.buildingNumber)\n if (buildingNumber !== undefined) payload.buildingNumber = buildingNumber\n const flatNumber = normalizeOptional(draft.flatNumber)\n if (flatNumber !== undefined) payload.flatNumber = flatNumber\n const city = normalizeOptional(draft.city)\n if (city !== undefined) payload.city = city\n const region = normalizeOptional(draft.region)\n if (region !== undefined) payload.region = region\n const postal = normalizeOptional(draft.postalCode)\n if (postal !== undefined) payload.postalCode = postal\n const country = normalizeOptional(draft.country)\n if (country !== undefined) payload.country = country.toUpperCase()\n\n setSaving(true)\n setGeneralError(null)\n setFieldErrors({})\n try {\n if (editingId && onUpdate) await onUpdate(editingId, payload)\n else await onCreate(payload)\n resetForm()\n setIsFormOpen(false)\n } catch (err) {\n const details = extractValidationDetails(err)\n if (details.length) {\n const nextErrors: Partial<Record<DraftFieldKey, string>> = {}\n for (const detail of details) {\n const path = Array.isArray(detail.path) ? detail.path : []\n const targetKey = path.length ? serverFieldMap[String(path[0])] : undefined\n if (!targetKey) continue\n const message = resolveFieldMessage(detail, fieldLabels[targetKey], t)\n if (message) nextErrors[targetKey] = message\n }\n if (Object.keys(nextErrors).length) {\n setFieldErrors(nextErrors)\n setGeneralError(Object.values(nextErrors)[0] ?? null)\n setSaving(false)\n return\n }\n }\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n setGeneralError(message)\n flash(message, 'error')\n } finally {\n setSaving(false)\n }\n }, [draft, fieldLabels, onCreate, onUpdate, resetForm, t, editingId])\n\n const handleDelete = React.useCallback(\n async (id: string) => {\n if (!onDelete) return\n setDeletingId(id)\n try {\n await onDelete(id)\n if (editingId === id) {\n resetForm()\n setIsFormOpen(false)\n }\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.people.detail.addresses.error')\n flash(message, 'error')\n } finally {\n setDeletingId(null)\n }\n },\n [editingId, onDelete, resetForm, t]\n )\n\n const disableActions = saving || isSubmitting || deletingId !== null\n const isEditing = editingId !== null\n const addDisabled = disableActions || isEditing\n const hasAddresses = addresses.length > 0\n const emptyTitle = emptyStateTitle ?? emptyLabel\n const emptyActionLabel = emptyStateActionLabel ?? t('customers.people.detail.addresses.add')\n\n React.useEffect(() => {\n if (!onAddActionChange) return\n onAddActionChange({ openCreateForm, addDisabled })\n }, [onAddActionChange, openCreateForm, addDisabled])\n\n React.useEffect(\n () => () => {\n if (onAddActionChange) onAddActionChange(null)\n },\n [onAddActionChange]\n )\n\n const renderFormTile = React.useCallback(\n (key: string) => (\n <div\n key={key}\n className=\"rounded-lg border-2 border-dashed border-muted-foreground/50 bg-muted/20 p-4 text-sm\"\n onKeyDown={(event) => {\n if (!(event.metaKey || event.ctrlKey)) return\n if (event.key !== 'Enter') return\n event.preventDefault()\n if (disableActions) return\n void handleSave()\n }}\n >\n <div className=\"flex items-center justify-between text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <span>\n {editingId\n ? t('customers.people.detail.addresses.editTitle')\n : t('customers.people.detail.addresses.addTitle')}\n </span>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" onClick={handleCancel} disabled={disableActions}>\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n <div className=\"mt-3 space-y-3\">\n {formatLoading ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.people.detail.addresses.formatLoading', 'Loading address preferences\u2026')}\n </p>\n ) : null}\n <AddressEditor\n value={draft}\n onChange={(next) => {\n setDraft(next)\n if (Object.keys(fieldErrors).length) {\n const nextErrors = { ...fieldErrors }\n ;(Object.keys(nextErrors) as DraftFieldKey[]).forEach((key) => {\n const candidate = (next as Record<string, unknown>)[key]\n if (candidate !== undefined && candidate !== null && `${candidate}`.length) {\n delete nextErrors[key]\n }\n })\n setFieldErrors(nextErrors)\n }\n }}\n format={format}\n t={t}\n disabled={disableActions}\n errors={fieldErrors}\n showFormatHint={!formatLoading}\n />\n {generalError ? <p className=\"text-xs text-red-600\">{generalError}</p> : null}\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={disableActions}>\n {t('customers.people.detail.addresses.cancel')}\n </Button>\n <Button type=\"button\" onClick={handleSave} disabled={disableActions}>\n {saving ? (\n <>\n <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />\n {editingId\n ? t('customers.people.detail.addresses.updating')\n : t('customers.people.detail.addresses.saving')}\n </>\n ) : editingId ? (\n t('customers.people.detail.addresses.update')\n ) : (\n t('customers.people.detail.addresses.save')\n )}\n </Button>\n </div>\n </div>\n </div>\n ),\n [\n disableActions,\n draft,\n editingId,\n fieldErrors,\n format,\n formatLoading,\n handleCancel,\n handleSave,\n generalError,\n saving,\n t,\n ]\n )\n\n return (\n <div className=\"space-y-4\">\n {!hideAddButton && hasAddresses ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={openCreateForm}\n disabled={addDisabled}\n >\n <Plus className=\"mr-2 h-4 w-4\" />\n {t('customers.people.detail.addresses.add')}\n </Button>\n </div>\n ) : null}\n {hasAddresses ? (\n <div className={gridClassName}>\n {addresses.map((address) => {\n if (isFormOpen && editingId === address.id) {\n return renderFormTile(address.id)\n }\n const formattedJson = formatAddressJson(address, format)\n const formattedString = formatAddressString(address, format)\n\n return (\n <div\n key={address.id}\n className=\"rounded-lg border bg-background p-4 text-sm shadow-sm\"\n title={formattedString}\n data-address-json={JSON.stringify(formattedJson)}\n data-address-string={formattedString}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div>\n <span className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {address.name ||\n (address.purpose ? addressTypeMap.get(address.purpose) ?? address.purpose : null) ||\n t('customers.people.detail.address')}\n </span>\n {address.isPrimary ? (\n <span className=\"mt-1 inline-flex w-fit rounded bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700\">\n {t('customers.people.detail.primary')}\n </span>\n ) : null}\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n onClick={() => openEditForm(address)}\n disabled={disableActions}\n aria-label={t('customers.people.detail.addresses.editAction')}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-destructive hover:text-destructive focus-visible:text-destructive\"\n onClick={() => handleDelete(address.id)}\n disabled={disableActions || !onDelete}\n aria-label={t('customers.people.detail.addresses.deleteAction')}\n >\n {deletingId === address.id ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trash2 className=\"h-4 w-4\" />\n )}\n </Button>\n </div>\n </div>\n <div className=\"mt-2 space-y-1\">\n {address.purpose ? (\n <p className=\"text-xs text-muted-foreground\">\n {addressTypeMap.get(address.purpose) ?? address.purpose}\n </p>\n ) : null}\n <AddressView address={address} format={format} className=\"space-y-1\" lineClassName=\"text-sm\" />\n </div>\n </div>\n )\n })}\n {isFormOpen && !editingId ? renderFormTile('__new') : null}\n </div>\n ) : isFormOpen && !editingId ? (\n <div className={gridClassName}>{renderFormTile('__new')}</div>\n ) : (\n <TabEmptyState\n title={emptyTitle}\n action={{\n label: emptyActionLabel,\n onClick: openCreateForm,\n disabled: addDisabled,\n }}\n />\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["message", "key"]
7
7
  }
@@ -0,0 +1,83 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { Organization } from "@open-mercato/core/modules/directory/data/entities";
5
+ const metadata = {
6
+ path: "/directory/organizations/lookup",
7
+ GET: {
8
+ requireAuth: false
9
+ }
10
+ };
11
+ const orgLookupQuerySchema = z.object({
12
+ slug: z.string().min(1).max(150)
13
+ });
14
+ async function GET(req) {
15
+ const url = new URL(req.url);
16
+ const slug = url.searchParams.get("slug") || "";
17
+ const parsed = orgLookupQuerySchema.safeParse({ slug });
18
+ if (!parsed.success) {
19
+ return NextResponse.json({ ok: false, error: "Invalid slug." }, { status: 400 });
20
+ }
21
+ const container = await createRequestContainer();
22
+ const em = container.resolve("em");
23
+ const organization = await em.findOne(
24
+ Organization,
25
+ { slug: parsed.data.slug, deletedAt: null },
26
+ { populate: ["tenant"] }
27
+ );
28
+ if (!organization) {
29
+ return NextResponse.json({ ok: false, error: "Organization not found." }, { status: 404 });
30
+ }
31
+ const tenantId = typeof organization.tenant === "string" ? organization.tenant : organization.tenant?.id ? String(organization.tenant.id) : null;
32
+ return NextResponse.json({
33
+ ok: true,
34
+ organization: {
35
+ id: String(organization.id),
36
+ name: organization.name,
37
+ slug: organization.slug,
38
+ tenantId
39
+ }
40
+ });
41
+ }
42
+ const lookupTag = "Directory";
43
+ const orgLookupSuccessSchema = z.object({
44
+ ok: z.literal(true),
45
+ organization: z.object({
46
+ id: z.string().uuid(),
47
+ name: z.string(),
48
+ slug: z.string(),
49
+ tenantId: z.string().uuid().nullable()
50
+ })
51
+ });
52
+ const orgLookupErrorSchema = z.object({
53
+ ok: z.literal(false),
54
+ error: z.string()
55
+ });
56
+ const orgLookupDoc = {
57
+ summary: "Public organization lookup by slug",
58
+ description: "Resolves organization metadata for portal flows. No authentication required.",
59
+ tags: [lookupTag],
60
+ query: orgLookupQuerySchema,
61
+ responses: [
62
+ { status: 200, description: "Organization resolved.", schema: orgLookupSuccessSchema }
63
+ ],
64
+ errors: [
65
+ { status: 400, description: "Invalid slug", schema: orgLookupErrorSchema },
66
+ { status: 404, description: "Organization not found", schema: orgLookupErrorSchema }
67
+ ]
68
+ };
69
+ const openApi = {
70
+ tag: lookupTag,
71
+ summary: "Public organization lookup by slug",
72
+ methods: {
73
+ GET: orgLookupDoc
74
+ }
75
+ };
76
+ var lookup_default = GET;
77
+ export {
78
+ GET,
79
+ lookup_default as default,
80
+ metadata,
81
+ openApi
82
+ };
83
+ //# sourceMappingURL=lookup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/directory/api/get/organizations/lookup.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { Organization } from '@open-mercato/core/modules/directory/data/entities'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nexport const metadata = {\n path: '/directory/organizations/lookup',\n GET: {\n requireAuth: false,\n },\n}\n\nconst orgLookupQuerySchema = z.object({\n slug: z.string().min(1).max(150),\n})\n\nexport async function GET(req: Request) {\n const url = new URL(req.url)\n const slug = url.searchParams.get('slug') || ''\n const parsed = orgLookupQuerySchema.safeParse({ slug })\n if (!parsed.success) {\n return NextResponse.json({ ok: false, error: 'Invalid slug.' }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const organization = await em.findOne(\n Organization,\n { slug: parsed.data.slug, deletedAt: null },\n { populate: ['tenant'] },\n )\n if (!organization) {\n return NextResponse.json({ ok: false, error: 'Organization not found.' }, { status: 404 })\n }\n const tenantId = typeof organization.tenant === 'string'\n ? organization.tenant\n : organization.tenant?.id\n ? String(organization.tenant.id)\n : null\n return NextResponse.json({\n ok: true,\n organization: {\n id: String(organization.id),\n name: organization.name,\n slug: organization.slug,\n tenantId,\n },\n })\n}\n\nconst lookupTag = 'Directory'\n\nconst orgLookupSuccessSchema = z.object({\n ok: z.literal(true),\n organization: z.object({\n id: z.string().uuid(),\n name: z.string(),\n slug: z.string(),\n tenantId: z.string().uuid().nullable(),\n }),\n})\n\nconst orgLookupErrorSchema = z.object({\n ok: z.literal(false),\n error: z.string(),\n})\n\nconst orgLookupDoc: OpenApiMethodDoc = {\n summary: 'Public organization lookup by slug',\n description: 'Resolves organization metadata for portal flows. No authentication required.',\n tags: [lookupTag],\n query: orgLookupQuerySchema,\n responses: [\n { status: 200, description: 'Organization resolved.', schema: orgLookupSuccessSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid slug', schema: orgLookupErrorSchema },\n { status: 404, description: 'Organization not found', schema: orgLookupErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: lookupTag,\n summary: 'Public organization lookup by slug',\n methods: {\n GET: orgLookupDoc,\n },\n}\n\nexport default GET\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,MAAM;AAAA,EACN,KAAK;AAAA,IACH,aAAa;AAAA,EACf;AACF;AAEA,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AACjC,CAAC;AAED,eAAsB,IAAI,KAAc;AACtC,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,OAAO,IAAI,aAAa,IAAI,MAAM,KAAK;AAC7C,QAAM,SAAS,qBAAqB,UAAU,EAAE,KAAK,CAAC;AACtD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,gBAAgB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAM,eAAe,MAAM,GAAG;AAAA,IAC5B;AAAA,IACA,EAAE,MAAM,OAAO,KAAK,MAAM,WAAW,KAAK;AAAA,IAC1C,EAAE,UAAU,CAAC,QAAQ,EAAE;AAAA,EACzB;AACA,MAAI,CAAC,cAAc;AACjB,WAAO,aAAa,KAAK,EAAE,IAAI,OAAO,OAAO,0BAA0B,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC3F;AACA,QAAM,WAAW,OAAO,aAAa,WAAW,WAC5C,aAAa,SACb,aAAa,QAAQ,KACnB,OAAO,aAAa,OAAO,EAAE,IAC7B;AACN,SAAO,aAAa,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,cAAc;AAAA,MACZ,IAAI,OAAO,aAAa,EAAE;AAAA,MAC1B,MAAM,aAAa;AAAA,MACnB,MAAM,aAAa;AAAA,MACnB;AAAA,IACF;AAAA,EACF,CAAC;AACH;AAEA,MAAM,YAAY;AAElB,MAAM,yBAAyB,EAAE,OAAO;AAAA,EACtC,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,cAAc,EAAE,OAAO;AAAA,IACrB,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,IACpB,MAAM,EAAE,OAAO;AAAA,IACf,MAAM,EAAE,OAAO;AAAA,IACf,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACvC,CAAC;AACH,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,IAAI,EAAE,QAAQ,KAAK;AAAA,EACnB,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,eAAiC;AAAA,EACrC,SAAS;AAAA,EACT,aAAa;AAAA,EACb,MAAM,CAAC,SAAS;AAAA,EAChB,OAAO;AAAA,EACP,WAAW;AAAA,IACT,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,uBAAuB;AAAA,EACvF;AAAA,EACA,QAAQ;AAAA,IACN,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,qBAAqB;AAAA,IACzE,EAAE,QAAQ,KAAK,aAAa,0BAA0B,QAAQ,qBAAqB;AAAA,EACrF;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,EACP;AACF;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -21,6 +21,7 @@ import {
21
21
  } from "@open-mercato/shared/lib/commands/helpers";
22
22
  import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
23
23
  import { toOptionalString } from "@open-mercato/shared/lib/string/coerce";
24
+ import { slugify } from "@open-mercato/shared/lib/slugify";
24
25
  const organizationCrudEvents = {
25
26
  module: "directory",
26
27
  entity: "organization",
@@ -78,6 +79,7 @@ function serializeOrganization(entity, custom) {
78
79
  id: String(entity.id),
79
80
  tenantId: resolveTenantIdFromEntity(entity),
80
81
  name: entity.name,
82
+ slug: entity.slug ?? null,
81
83
  isActive: !!entity.isActive,
82
84
  parentId: entity.parentId ?? null,
83
85
  ancestorIds: Array.isArray(entity.ancestorIds) ? [...entity.ancestorIds] : [],
@@ -96,6 +98,7 @@ function captureOrganizationSnapshots(entity, childParents, custom) {
96
98
  id: String(entity.id),
97
99
  tenantId,
98
100
  name: entity.name,
101
+ slug: entity.slug ?? null,
99
102
  isActive: !!entity.isActive,
100
103
  parentId: entity.parentId ?? null,
101
104
  childParents: (childParents ?? []).map((entry) => ({
@@ -186,6 +189,23 @@ async function clearRemovedChildren(em, tenantId, recordId, desiredChildIds) {
186
189
  for (const child of toPersist) child.parentId = null;
187
190
  await em.persistAndFlush(toPersist);
188
191
  }
192
+ async function resolveUniqueSlug(em, tenantId, baseSlug, excludeId) {
193
+ let candidate = baseSlug;
194
+ let suffix = 0;
195
+ const maxAttempts = 50;
196
+ while (suffix < maxAttempts) {
197
+ const filter = {
198
+ tenant: tenantId,
199
+ slug: candidate,
200
+ deletedAt: null
201
+ };
202
+ const existing = await em.findOne(Organization, filter);
203
+ if (!existing || excludeId && String(existing.id) === excludeId) return candidate;
204
+ suffix += 1;
205
+ candidate = `${baseSlug}-${suffix}`;
206
+ }
207
+ return `${baseSlug}-${Date.now()}`;
208
+ }
189
209
  const createOrganizationCommand = {
190
210
  id: "directory.organizations.create",
191
211
  async execute(rawInput, ctx) {
@@ -202,12 +222,15 @@ const createOrganizationCommand = {
202
222
  await ensureChildrenValid(em, tenantId, childIds);
203
223
  const childParentsBefore = await loadChildParentSnapshots(em, tenantId, childIds);
204
224
  const tenantRef = em.getReference(Tenant, tenantId);
225
+ const baseSlug = parsed.slug ? parsed.slug : slugify(parsed.name);
226
+ const slug = baseSlug ? await resolveUniqueSlug(em, tenantId, baseSlug) : null;
205
227
  const de = ctx.container.resolve("dataEngine");
206
228
  const organization = await de.createOrmEntity({
207
229
  entity: Organization,
208
230
  data: {
209
231
  tenant: tenantRef,
210
232
  name: parsed.name,
233
+ slug,
211
234
  isActive: parsed.isActive ?? true,
212
235
  parentId
213
236
  }
@@ -367,12 +390,17 @@ const updateOrganizationCommand = {
367
390
  ...Array.isArray(existing.childIds) ? existing.childIds.map(String) : []
368
391
  ]);
369
392
  const childParentsBefore = await loadChildParentSnapshots(em, tenantId, combinedChildIds);
393
+ let resolvedSlug;
394
+ if (parsed.slug !== void 0) {
395
+ resolvedSlug = parsed.slug ? await resolveUniqueSlug(em, tenantId, parsed.slug, parsed.id) : parsed.slug;
396
+ }
370
397
  const de = ctx.container.resolve("dataEngine");
371
398
  const organization = await de.updateOrmEntity({
372
399
  entity: Organization,
373
400
  where: { id: parsed.id, deletedAt: null },
374
401
  apply: (entity) => {
375
402
  if (parsed.name !== void 0) entity.name = parsed.name;
403
+ if (resolvedSlug !== void 0) entity.slug = resolvedSlug;
376
404
  if (parsed.isActive !== void 0) entity.isActive = parsed.isActive;
377
405
  entity.parentId = parentId;
378
406
  }
@@ -430,7 +458,7 @@ const updateOrganizationCommand = {
430
458
  organizationId: String(result.id)
431
459
  });
432
460
  const after = serializeOrganization(result, custom);
433
- const changes = buildChanges(beforeRecord, after, ["name", "isActive", "parentId"]);
461
+ const changes = buildChanges(beforeRecord, after, ["name", "slug", "isActive", "parentId"]);
434
462
  const customDiff = diffCustomFieldChanges(beforeRecord?.custom, custom);
435
463
  for (const [key, diff] of Object.entries(customDiff)) {
436
464
  changes[`cf_${key}`] = diff;
@@ -463,6 +491,7 @@ const updateOrganizationCommand = {
463
491
  where: { id: before.id },
464
492
  apply: (entity) => {
465
493
  entity.name = before.name;
494
+ if (before.slug !== void 0) entity.slug = before.slug;
466
495
  entity.isActive = before.isActive;
467
496
  entity.parentId = before.parentId;
468
497
  }
@@ -596,6 +625,7 @@ const deleteOrganizationCommand = {
596
625
  organization.deletedAt = null;
597
626
  organization.isActive = before.isActive;
598
627
  organization.name = before.name;
628
+ if (before.slug !== void 0) organization.slug = before.slug;
599
629
  organization.parentId = before.parentId;
600
630
  await em.flush();
601
631
  if (tenantId) setInternalTenantId(organization, tenantId);
@@ -605,6 +635,7 @@ const deleteOrganizationCommand = {
605
635
  data: {
606
636
  id: before.id,
607
637
  name: before.name,
638
+ slug: before.slug ?? null,
608
639
  tenant: tenantId ? em.getReference(Tenant, tenantId) : void 0,
609
640
  isActive: before.isActive,
610
641
  parentId: before.parentId