@open-mercato/core 0.8.1-develop.7275.1.f772b944d4 → 0.8.1-develop.7295.1.d0e0e33014

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 (261) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  3. package/dist/modules/auth/components/AclEditor.js +21 -3
  4. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/companies/page.js +6 -4
  6. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  7. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
  8. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/deals/page.js +6 -4
  10. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
  12. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
  14. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/people/page.js +6 -4
  16. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  17. package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
  18. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  19. package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
  20. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  21. package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
  22. package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
  23. package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
  24. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  25. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
  26. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  27. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
  28. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  29. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
  30. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  31. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
  32. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  33. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
  34. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  35. package/dist/modules/customers/components/detail/schedule/index.js +3 -1
  36. package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
  37. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
  38. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  39. package/dist/modules/customers/components/formConfig.js +17 -9
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +12 -7
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/assignableStaff.js +103 -22
  44. package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
  45. package/dist/modules/customers/setup.js +10 -2
  46. package/dist/modules/customers/setup.js.map +2 -2
  47. package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
  48. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  49. package/dist/modules/data_sync/api/schedules/route.js +5 -0
  50. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  51. package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
  52. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  53. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
  54. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  55. package/dist/modules/data_sync/data/validators.js +13 -2
  56. package/dist/modules/data_sync/data/validators.js.map +2 -2
  57. package/dist/modules/data_sync/lib/schedule-value.js +30 -0
  58. package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
  59. package/dist/modules/eudr/data/validators.js.map +2 -2
  60. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
  61. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  62. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
  63. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
  64. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
  65. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  66. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
  67. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
  68. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
  69. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
  70. package/dist/modules/portal/lib/navigation.js +3 -4
  71. package/dist/modules/portal/lib/navigation.js.map +2 -2
  72. package/dist/modules/query_index/lib/engine.js +155 -15
  73. package/dist/modules/query_index/lib/engine.js.map +2 -2
  74. package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
  75. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
  76. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
  77. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  78. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
  79. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
  80. package/dist/modules/staff/extension-points.js +16 -0
  81. package/dist/modules/staff/extension-points.js.map +2 -2
  82. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
  83. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
  84. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
  85. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
  86. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
  87. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
  88. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
  89. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
  90. package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
  91. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  92. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
  93. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
  94. package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
  95. package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
  96. package/dist/modules/warranty_claims/api/assign/route.js +3 -3
  97. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  98. package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
  99. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  100. package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
  101. package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
  102. package/dist/modules/warranty_claims/api/events/route.js +4 -4
  103. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  104. package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
  105. package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
  106. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
  107. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  108. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
  109. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  110. package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
  111. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  112. package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
  113. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  114. package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
  115. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  116. package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
  117. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  118. package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
  119. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  120. package/dist/modules/warranty_claims/api/risk/route.js +2 -2
  121. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  122. package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
  123. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  124. package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
  125. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  126. package/dist/modules/warranty_claims/api/stats/route.js +2 -2
  127. package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
  128. package/dist/modules/warranty_claims/api/submit/route.js +3 -3
  129. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  130. package/dist/modules/warranty_claims/api/transition/route.js +3 -3
  131. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  132. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
  133. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  134. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
  135. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  136. package/dist/modules/warranty_claims/backend/page.js +4 -2
  137. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  138. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
  139. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
  140. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
  141. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
  142. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
  143. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  144. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
  145. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
  146. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
  147. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  148. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
  149. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  150. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
  151. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
  152. package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
  153. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  154. package/package.json +7 -7
  155. package/src/modules/auth/api/users/acl/route.ts +4 -3
  156. package/src/modules/auth/components/AclEditor.tsx +38 -2
  157. package/src/modules/auth/i18n/de.json +5 -1
  158. package/src/modules/auth/i18n/en.json +5 -1
  159. package/src/modules/auth/i18n/es.json +5 -1
  160. package/src/modules/auth/i18n/ko.json +5 -1
  161. package/src/modules/auth/i18n/pl.json +5 -1
  162. package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
  163. package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
  164. package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
  165. package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
  166. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
  167. package/src/modules/customers/backend/customers/people/page.tsx +6 -4
  168. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
  169. package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
  170. package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
  171. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
  172. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
  173. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
  174. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
  175. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
  176. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
  177. package/src/modules/customers/components/detail/schedule/index.ts +1 -1
  178. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
  179. package/src/modules/customers/components/formConfig.tsx +35 -12
  180. package/src/modules/customers/data/validators.ts +14 -8
  181. package/src/modules/customers/i18n/de.json +1 -0
  182. package/src/modules/customers/i18n/en.json +1 -0
  183. package/src/modules/customers/i18n/es.json +1 -0
  184. package/src/modules/customers/i18n/ko.json +1 -0
  185. package/src/modules/customers/i18n/pl.json +1 -0
  186. package/src/modules/customers/lib/assignableStaff.ts +172 -34
  187. package/src/modules/customers/setup.ts +8 -0
  188. package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
  189. package/src/modules/data_sync/api/schedules/route.ts +5 -0
  190. package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
  191. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
  192. package/src/modules/data_sync/data/validators.ts +26 -2
  193. package/src/modules/data_sync/i18n/de.json +2 -0
  194. package/src/modules/data_sync/i18n/en.json +2 -0
  195. package/src/modules/data_sync/i18n/es.json +2 -0
  196. package/src/modules/data_sync/i18n/ko.json +2 -0
  197. package/src/modules/data_sync/i18n/pl.json +2 -0
  198. package/src/modules/data_sync/lib/schedule-value.ts +45 -0
  199. package/src/modules/eudr/data/validators.ts +3 -4
  200. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
  201. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
  202. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
  203. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
  204. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
  205. package/src/modules/portal/i18n/de.json +1 -0
  206. package/src/modules/portal/i18n/en.json +1 -0
  207. package/src/modules/portal/i18n/es.json +1 -0
  208. package/src/modules/portal/i18n/ko.json +1 -0
  209. package/src/modules/portal/i18n/pl.json +1 -0
  210. package/src/modules/portal/lib/navigation.ts +8 -3
  211. package/src/modules/query_index/lib/engine.ts +238 -17
  212. package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
  213. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
  214. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
  215. package/src/modules/staff/extension-points.ts +16 -0
  216. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
  217. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
  218. package/src/modules/staff/i18n/de.json +3 -0
  219. package/src/modules/staff/i18n/en.json +3 -0
  220. package/src/modules/staff/i18n/es.json +3 -0
  221. package/src/modules/staff/i18n/ko.json +3 -0
  222. package/src/modules/staff/i18n/pl.json +3 -0
  223. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
  224. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
  225. package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
  226. package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
  227. package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
  228. package/src/modules/warranty_claims/api/assign/route.ts +3 -3
  229. package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
  230. package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
  231. package/src/modules/warranty_claims/api/events/route.ts +4 -4
  232. package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
  233. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
  234. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
  235. package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
  236. package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
  237. package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
  238. package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
  239. package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
  240. package/src/modules/warranty_claims/api/risk/route.ts +2 -2
  241. package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
  242. package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
  243. package/src/modules/warranty_claims/api/stats/route.ts +2 -2
  244. package/src/modules/warranty_claims/api/submit/route.ts +3 -3
  245. package/src/modules/warranty_claims/api/transition/route.ts +3 -3
  246. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
  247. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
  248. package/src/modules/warranty_claims/backend/page.tsx +4 -2
  249. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
  250. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
  251. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
  252. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
  253. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
  254. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
  255. package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
  256. package/src/modules/workflows/i18n/de.json +1 -1
  257. package/src/modules/workflows/i18n/en.json +1 -1
  258. package/src/modules/workflows/i18n/es.json +1 -1
  259. package/src/modules/workflows/i18n/ko.json +1 -1
  260. package/src/modules/workflows/i18n/pl.json +1 -1
  261. package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/invite/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState, useEffect } from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { PortalInjectionSpots } from '@open-mercato/ui/backend/injection/spotIds'\nimport { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalInvitePage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n const searchParams = useSearchParams()\n\n const [displayName, setDisplayName] = useState('')\n const [password, setPassword] = useState('')\n const [confirmPassword, setConfirmPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [token, setToken] = useState<string | null>(null)\n\n useEffect(() => {\n const tokenParam = searchParams.get('token')\n if (!tokenParam) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n } else {\n setToken(tokenParam)\n }\n }, [searchParams, t])\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!token) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n return\n }\n\n if (!displayName.trim()) {\n setError(t('portal.invite.error.displayNameRequired', 'Display name is required.'))\n return\n }\n\n if (password !== confirmPassword) {\n setError(t('portal.invite.error.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n if (password.length < 8) {\n setError(t('portal.invite.error.passwordTooShort', 'Password must be at least 8 characters long.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/invitations/accept', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token, password, displayName: displayName.trim() }),\n })\n\n if (result.ok && result.result?.ok) {\n navigateWithPageReload(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 400) {\n setError(t('portal.invite.error.invalidToken', 'Invalid or expired invitation.'))\n } else {\n setError(result.result?.error || t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n }\n } catch {\n setError(t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [token, password, confirmPassword, displayName, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.invite.title', 'Accept Invitation')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.invite.description', 'Create your portal account to accept this invitation.')}</p>\n </div>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageBefore('invite')} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-display-name\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.displayName', 'Display Name')}</Label>\n <Input id=\"invite-display-name\" autoComplete=\"name\" required value={displayName} onChange={(e) => setDisplayName(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.password', 'Password')}</Label>\n <PasswordInput id=\"invite-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.password.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-confirm-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.confirmPassword', 'Confirm Password')}</Label>\n <PasswordInput id=\"invite-confirm-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.confirmPassword.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting || !token} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.invite.submitting', 'Accepting invitation...') : t('portal.invite.submit', 'Accept Invitation')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.invite.backToLogin', 'Already have an account?')}{' '}\n <Link href={`/${orgSlug}/portal/login`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.invite.loginLink', 'Sign in')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageAfter('invite')} context={injectionContext} />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState, useEffect } from 'react'\nimport { useSearchParams } from 'next/navigation'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { PortalInjectionSpots } from '@open-mercato/ui/backend/injection/spotIds'\nimport { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalInvitePage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n const searchParams = useSearchParams()\n\n const [displayName, setDisplayName] = useState('')\n const [password, setPassword] = useState('')\n const [confirmPassword, setConfirmPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n const [token, setToken] = useState<string | null>(null)\n\n useEffect(() => {\n const tokenParam = searchParams.get('token')\n if (!tokenParam) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n } else {\n setToken(tokenParam)\n }\n }, [searchParams, t])\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!token) {\n setError(t('portal.invite.error.noToken', 'Invalid or missing invitation token.'))\n return\n }\n\n if (!displayName.trim()) {\n setError(t('portal.invite.error.displayNameRequired', 'Display name is required.'))\n return\n }\n\n if (password !== confirmPassword) {\n setError(t('portal.invite.error.passwordMismatch', 'Passwords do not match.'))\n return\n }\n\n if (password.length < 8) {\n setError(t('portal.invite.error.passwordTooShort', 'Password must be at least 8 characters long.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/invitations/accept', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ token, password, displayName: displayName.trim() }),\n })\n\n if (result.ok && result.result?.ok) {\n navigateWithPageReload(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 409) {\n setError(t('portal.invite.error.accountExists', 'This email address already has a portal account. Sign in below instead of accepting the invitation.'))\n } else if (result.status === 400) {\n setError(t('portal.invite.error.invalidToken', 'Invalid or expired invitation.'))\n } else {\n setError(result.result?.error || t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n }\n } catch {\n setError(t('portal.invite.error.generic', 'Invitation acceptance failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [token, password, confirmPassword, displayName, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.invite.title', 'Accept Invitation')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.invite.description', 'Create your portal account to accept this invitation.')}</p>\n </div>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageBefore('invite')} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-display-name\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.displayName', 'Display Name')}</Label>\n <Input id=\"invite-display-name\" autoComplete=\"name\" required value={displayName} onChange={(e) => setDisplayName(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.password', 'Password')}</Label>\n <PasswordInput id=\"invite-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.password.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"invite-confirm-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.invite.confirmPassword', 'Confirm Password')}</Label>\n <PasswordInput id=\"invite-confirm-password\" autoComplete=\"new-password\" required placeholder={t('portal.invite.confirmPassword.placeholder', '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022')} value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} disabled={submitting || !token} className=\"rounded-lg\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting || !token} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.invite.submitting', 'Accepting invitation...') : t('portal.invite.submit', 'Accept Invitation')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.invite.backToLogin', 'Already have an account?')}{' '}\n <Link href={`/${orgSlug}/portal/login`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.invite.loginLink', 'Sign in')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={PortalInjectionSpots.pageAfter('invite')} context={injectionContext} />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,7 @@ import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
13
13
  import { SearchX } from "lucide-react";
14
14
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
15
15
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
16
+ import { navigateWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
16
17
  import { usePortalContext } from "@open-mercato/ui/portal/PortalContext";
17
18
  import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
18
19
  function PortalLoginPage({ params }) {
@@ -40,7 +41,7 @@ function PortalLoginPage({ params }) {
40
41
  body: JSON.stringify({ email, password, organizationId: tenant.organizationId })
41
42
  });
42
43
  if (result.ok && result.result?.ok) {
43
- window.location.assign(`/${orgSlug}/portal/dashboard`);
44
+ navigateWithPageReload(`/${orgSlug}/portal/dashboard`);
44
45
  return;
45
46
  }
46
47
  if (result.status === 401) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/login/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLoginPage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!tenant.organizationId) {\n setError(t('portal.org.invalid', 'Organization not found.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ email, password, organizationId: tenant.organizationId }),\n })\n\n if (result.ok && result.result?.ok) {\n window.location.assign(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 401) {\n // The login API intentionally returns a single generic 401 for unknown, inactive,\n // locked, and unverified accounts so account existence cannot be enumerated.\n // Lockout/deactivation guidance is delivered out-of-band (e.g. email), never here.\n setError(t('portal.login.error.invalidCredentials', 'Invalid email or password.'))\n } else {\n setError(result.result?.error || t('portal.login.error.generic', 'Login failed. Please try again.'))\n }\n } catch {\n setError(t('portal.login.error.generic', 'Login failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [email, password, tenant.organizationId, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.login.title', 'Sign In')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.login.description', 'Enter your credentials to access the portal.')}</p>\n </div>\n\n <InjectionSpot spotId={extensionPoints.hosts.loginBefore.spotId} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"login-email\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.login.email', 'Email')}</Label>\n <EmailInput id=\"login-email\" required placeholder={t('portal.login.email.placeholder', 'you@example.com')} value={email} onChange={(e) => setEmail(e.target.value)} disabled={submitting} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"login-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.login.password', 'Password')}</Label>\n <PasswordInput id=\"login-password\" autoComplete=\"current-password\" required placeholder={t('portal.login.password.placeholder', '\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting} className=\"rounded-lg\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.login.submitting', 'Signing in...') : t('portal.login.submit', 'Sign In')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.login.noAccount', \"Don't have an account?\")}{' '}\n <Link href={`/${orgSlug}/portal/signup`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.login.signupLink', 'Sign up')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={extensionPoints.hosts.loginAfter.spotId} context={injectionContext} />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useCallback, useMemo, useState } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { EmailInput } from '@open-mercato/ui/primitives/email-input'\nimport { PasswordInput } from '@open-mercato/ui/primitives/password-input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX } from 'lucide-react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { navigateWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLoginPage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { tenant } = usePortalContext()\n\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [error, setError] = useState<string | null>(null)\n const [submitting, setSubmitting] = useState(false)\n\n const handleSubmit = useCallback(\n async (event: React.FormEvent) => {\n event.preventDefault()\n setError(null)\n\n if (!tenant.organizationId) {\n setError(t('portal.org.invalid', 'Organization not found.'))\n return\n }\n\n setSubmitting(true)\n try {\n const result = await apiCall<{ ok: boolean; error?: string }>('/api/customer_accounts/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n credentials: 'include',\n body: JSON.stringify({ email, password, organizationId: tenant.organizationId }),\n })\n\n if (result.ok && result.result?.ok) {\n navigateWithPageReload(`/${orgSlug}/portal/dashboard`)\n return\n }\n\n if (result.status === 401) {\n // The login API intentionally returns a single generic 401 for unknown, inactive,\n // locked, and unverified accounts so account existence cannot be enumerated.\n // Lockout/deactivation guidance is delivered out-of-band (e.g. email), never here.\n setError(t('portal.login.error.invalidCredentials', 'Invalid email or password.'))\n } else {\n setError(result.result?.error || t('portal.login.error.generic', 'Login failed. Please try again.'))\n }\n } catch {\n setError(t('portal.login.error.generic', 'Login failed. Please try again.'))\n } finally {\n setSubmitting(false)\n }\n },\n [email, password, tenant.organizationId, orgSlug, t],\n )\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n return (\n <div className=\"mx-auto w-full max-w-sm\">\n <div className=\"mb-8 text-center\">\n <h1 className=\"text-2xl font-bold tracking-tight\">{t('portal.login.title', 'Sign In')}</h1>\n <p className=\"mt-1.5 text-sm text-muted-foreground\">{t('portal.login.description', 'Enter your credentials to access the portal.')}</p>\n </div>\n\n <InjectionSpot spotId={extensionPoints.hosts.loginBefore.spotId} context={injectionContext} />\n\n <form onSubmit={handleSubmit} className=\"flex flex-col gap-4\">\n {error ? (\n <Alert status=\"error\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n ) : null}\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"login-email\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.login.email', 'Email')}</Label>\n <EmailInput id=\"login-email\" required placeholder={t('portal.login.email.placeholder', 'you@example.com')} value={email} onChange={(e) => setEmail(e.target.value)} disabled={submitting} className=\"rounded-lg\" />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"login-password\" className=\"text-overline font-semibold uppercase tracking-wider text-muted-foreground/70\">{t('portal.login.password', 'Password')}</Label>\n <PasswordInput id=\"login-password\" autoComplete=\"current-password\" required placeholder={t('portal.login.password.placeholder', '\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022')} value={password} onChange={(e) => setPassword(e.target.value)} disabled={submitting} className=\"rounded-lg\" />\n </div>\n\n <Button type=\"submit\" disabled={submitting} className=\"mt-1 w-full rounded-lg\">\n {submitting ? t('portal.login.submitting', 'Signing in...') : t('portal.login.submit', 'Sign In')}\n </Button>\n\n <p className=\"text-center text-sm text-muted-foreground\">\n {t('portal.login.noAccount', \"Don't have an account?\")}{' '}\n <Link href={`/${orgSlug}/portal/signup`} className=\"font-medium text-foreground underline underline-offset-4 hover:opacity-80\">\n {t('portal.login.signupLink', 'Sign up')}\n </Link>\n </p>\n </form>\n\n <InjectionSpot spotId={extensionPoints.hosts.loginAfter.spotId} context={injectionContext} />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo } from "react";
4
4
  import { extensionPoints } from "@open-mercato/core/modules/portal/extension-points";
5
5
  import Link from "next/link";
6
- import { useRouter } from "next/navigation";
6
+ import { replaceWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
7
7
  import { useT } from "@open-mercato/shared/lib/i18n/context";
8
8
  import { Button } from "@open-mercato/ui/primitives/button";
9
9
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
@@ -14,14 +14,13 @@ import { PortalFeatureCard } from "@open-mercato/ui/portal/components/PortalFeat
14
14
  import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
15
15
  function PortalLandingPage({ params }) {
16
16
  const t = useT();
17
- const router = useRouter();
18
17
  const orgSlug = params.orgSlug;
19
18
  const { auth, tenant } = usePortalContext();
20
19
  useEffect(() => {
21
20
  if (!auth.loading && auth.user) {
22
- router.replace(`/${orgSlug}/portal/dashboard`);
21
+ replaceWithPageReload(`/${orgSlug}/portal/dashboard`);
23
22
  }
24
- }, [auth.loading, auth.user, router, orgSlug]);
23
+ }, [auth.loading, auth.user, orgSlug]);
25
24
  const injectionContext = useMemo(
26
25
  () => ({ orgSlug }),
27
26
  [orgSlug]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX, ShoppingBag, User, Shield } from 'lucide-react'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalFeatureCard } from '@open-mercato/ui/portal/components/PortalFeatureCard'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLandingPage({ params }: Props) {\n const t = useT()\n const router = useRouter()\n const orgSlug = params.orgSlug\n const { auth, tenant } = usePortalContext()\n\n // Redirect authenticated users to dashboard\n useEffect(() => {\n if (!auth.loading && auth.user) {\n router.replace(`/${orgSlug}/portal/dashboard`)\n }\n }, [auth.loading, auth.user, router, orgSlug])\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (auth.loading || tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n // Authenticated user \u2014 redirect is in progress\n if (auth.user) return null\n\n return (\n <>\n <InjectionSpot spotId={extensionPoints.hosts.homeBefore.spotId} context={injectionContext} />\n\n <section className=\"flex flex-col items-center gap-5 py-8 text-center sm:py-16\">\n <p className=\"text-overline font-semibold uppercase tracking-widest text-muted-foreground/60\">\n {t('portal.nav.home', 'Customer Portal')}\n </p>\n <h1 className=\"max-w-2xl text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl\">\n {t('portal.landing.hero.title', 'Welcome to your portal')}\n </h1>\n <p className=\"max-w-lg text-base leading-relaxed text-muted-foreground sm:text-lg\">\n {t('portal.landing.hero.description', 'Access your account, manage orders, and stay up to date.')}\n </p>\n <div className=\"mt-2 flex items-center gap-3\">\n <Button asChild size=\"lg\" className=\"rounded-lg px-6 text-sm\">\n <Link href={`/${orgSlug}/portal/login`}>{t('portal.landing.cta.login', 'Sign In')}</Link>\n </Button>\n <Button asChild variant=\"outline\" size=\"lg\" className=\"rounded-lg px-6 text-sm\">\n <Link href={`/${orgSlug}/portal/signup`}>{t('portal.landing.cta.signup', 'Create Account')}</Link>\n </Button>\n </div>\n </section>\n\n <section className=\"grid gap-4 sm:grid-cols-3\">\n <PortalFeatureCard\n icon={<ShoppingBag className=\"size-5\" />}\n title={t('portal.landing.feature.orders', 'Orders & Invoices')}\n description={t('portal.landing.feature.orders.description', 'Track your orders, download invoices, and view delivery status in real time.')}\n />\n <PortalFeatureCard\n icon={<User className=\"size-5\" />}\n title={t('portal.landing.feature.account', 'Account Management')}\n description={t('portal.landing.feature.account.description', 'Update your profile, manage team members, and configure your preferences.')}\n />\n <PortalFeatureCard\n icon={<Shield className=\"size-5\" />}\n title={t('portal.landing.feature.security', 'Secure Access')}\n description={t('portal.landing.feature.security.description', 'Role-based permissions, session management, and full audit trail.')}\n />\n </section>\n\n <InjectionSpot spotId={extensionPoints.hosts.homeAfter.spotId} context={injectionContext} />\n </>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport Link from 'next/link'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { SearchX, ShoppingBag, User, Shield } from 'lucide-react'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalFeatureCard } from '@open-mercato/ui/portal/components/PortalFeatureCard'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalLandingPage({ params }: Props) {\n const t = useT()\n const orgSlug = params.orgSlug\n const { auth, tenant } = usePortalContext()\n\n // Redirect authenticated users to dashboard. This crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n useEffect(() => {\n if (!auth.loading && auth.user) {\n replaceWithPageReload(`/${orgSlug}/portal/dashboard`)\n }\n }, [auth.loading, auth.user, orgSlug])\n\n const injectionContext = useMemo(\n () => ({ orgSlug }),\n [orgSlug],\n )\n\n if (auth.loading || tenant.loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (tenant.error) {\n return (\n <div className=\"mx-auto w-full max-w-md py-12\">\n <EmptyState\n variant=\"subtle\"\n size=\"lg\"\n icon={<SearchX className=\"h-6 w-6\" aria-hidden />}\n title={t('portal.org.invalid', 'Organization not found.')}\n />\n </div>\n )\n }\n\n // Authenticated user \u2014 redirect is in progress\n if (auth.user) return null\n\n return (\n <>\n <InjectionSpot spotId={extensionPoints.hosts.homeBefore.spotId} context={injectionContext} />\n\n <section className=\"flex flex-col items-center gap-5 py-8 text-center sm:py-16\">\n <p className=\"text-overline font-semibold uppercase tracking-widest text-muted-foreground/60\">\n {t('portal.nav.home', 'Customer Portal')}\n </p>\n <h1 className=\"max-w-2xl text-4xl font-bold tracking-tight text-foreground sm:text-5xl lg:text-6xl\">\n {t('portal.landing.hero.title', 'Welcome to your portal')}\n </h1>\n <p className=\"max-w-lg text-base leading-relaxed text-muted-foreground sm:text-lg\">\n {t('portal.landing.hero.description', 'Access your account, manage orders, and stay up to date.')}\n </p>\n <div className=\"mt-2 flex items-center gap-3\">\n <Button asChild size=\"lg\" className=\"rounded-lg px-6 text-sm\">\n <Link href={`/${orgSlug}/portal/login`}>{t('portal.landing.cta.login', 'Sign In')}</Link>\n </Button>\n <Button asChild variant=\"outline\" size=\"lg\" className=\"rounded-lg px-6 text-sm\">\n <Link href={`/${orgSlug}/portal/signup`}>{t('portal.landing.cta.signup', 'Create Account')}</Link>\n </Button>\n </div>\n </section>\n\n <section className=\"grid gap-4 sm:grid-cols-3\">\n <PortalFeatureCard\n icon={<ShoppingBag className=\"size-5\" />}\n title={t('portal.landing.feature.orders', 'Orders & Invoices')}\n description={t('portal.landing.feature.orders.description', 'Track your orders, download invoices, and view delivery status in real time.')}\n />\n <PortalFeatureCard\n icon={<User className=\"size-5\" />}\n title={t('portal.landing.feature.account', 'Account Management')}\n description={t('portal.landing.feature.account.description', 'Update your profile, manage team members, and configure your preferences.')}\n />\n <PortalFeatureCard\n icon={<Shield className=\"size-5\" />}\n title={t('portal.landing.feature.security', 'Secure Access')}\n description={t('portal.landing.feature.security.description', 'Role-based permissions, session management, and full audit trail.')}\n />\n </section>\n\n <InjectionSpot spotId={extensionPoints.hosts.homeAfter.spotId} context={injectionContext} />\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo } from "react";
4
4
  import { extensionPoints } from "@open-mercato/core/modules/portal/extension-points";
5
- import { useRouter } from "next/navigation";
5
+ import { replaceWithPageReload } from "@open-mercato/core/modules/portal/lib/navigation";
6
6
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
7
  import { Badge } from "@open-mercato/ui/primitives/badge";
8
8
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
@@ -12,14 +12,13 @@ import { PortalCard, PortalCardHeader, PortalStatRow, PortalCardDivider } from "
12
12
  import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
13
13
  function PortalProfilePage({ params }) {
14
14
  const t = useT();
15
- const router = useRouter();
16
15
  const { auth } = usePortalContext();
17
16
  const { user, roles, resolvedFeatures, isPortalAdmin, loading } = auth;
18
17
  useEffect(() => {
19
18
  if (!loading && !user) {
20
- router.replace(`/${params.orgSlug}/portal/login`);
19
+ replaceWithPageReload(`/${params.orgSlug}/portal/login`);
21
20
  }
22
- }, [loading, user, router, params.orgSlug]);
21
+ }, [loading, user, params.orgSlug]);
23
22
  const injectionContext = useMemo(
24
23
  () => ({ orgSlug: params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin }),
25
24
  [params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/portal/frontend/%5BorgSlug%5D/portal/profile/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'\nimport { PortalCard, PortalCardHeader, PortalStatRow, PortalCardDivider } from '@open-mercato/ui/portal/components/PortalCard'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalProfilePage({ params }: Props) {\n const t = useT()\n const router = useRouter()\n const { auth } = usePortalContext()\n const { user, roles, resolvedFeatures, isPortalAdmin, loading } = auth\n\n useEffect(() => {\n if (!loading && !user) {\n router.replace(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, router, params.orgSlug])\n\n const injectionContext = useMemo(\n () => ({ orgSlug: params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin }),\n [params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin],\n )\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n const formatDate = (dateString: string | null) => {\n if (!dateString) return t('portal.dashboard.never', 'Never')\n return new Date(dateString).toLocaleDateString(undefined, {\n year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',\n })\n }\n\n return (\n <div className=\"flex flex-col gap-8\">\n <PortalPageHeader\n label={t('portal.profile.label', 'Account')}\n title={t('portal.profile.title', 'Profile')}\n />\n\n <InjectionSpot spotId={extensionPoints.hosts.profileBefore.spotId} context={injectionContext} />\n\n <div className=\"grid gap-5 md:grid-cols-2\">\n <PortalCard>\n <PortalCardHeader label={t('portal.dashboard.profile', 'Profile')} title={user.displayName} />\n <div className=\"flex flex-col\">\n <PortalStatRow\n label={t('portal.dashboard.email', 'Email')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate\">{user.email}</span>\n <Badge variant={user.emailVerified ? 'default' : 'outline'} className=\"shrink-0 text-overline\">\n {user.emailVerified ? t('portal.dashboard.emailVerified', 'Verified') : t('portal.dashboard.emailNotVerified', 'Unverified')}\n </Badge>\n </span>\n }\n />\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.lastLogin', 'Last login')} value={formatDate(user.lastLoginAt)} />\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.memberSince', 'Member since')} value={formatDate(user.createdAt)} />\n {isPortalAdmin ? (\n <>\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.roles', 'Role')} value={<Badge className=\"text-overline\">{t('portal.dashboard.portalAdmin', 'Portal Admin')}</Badge>} />\n </>\n ) : null}\n </div>\n </PortalCard>\n\n <PortalCard>\n <PortalCardHeader label={t('portal.dashboard.roles', 'Roles')} title={`${roles.length} assigned`} />\n {roles.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {roles.map((role) => (\n <span key={role.id} className=\"inline-flex items-center rounded-lg border px-3 py-1.5 text-xs font-medium\">{role.name}</span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('portal.dashboard.noRoles', 'No roles assigned')}</p>\n )}\n </PortalCard>\n\n <PortalCard className=\"md:col-span-2\">\n <PortalCardHeader label={t('portal.dashboard.permissions', 'Permissions')} title={`${resolvedFeatures.length} features`} />\n {resolvedFeatures.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {resolvedFeatures.map((feature) => (\n <span key={feature} className=\"inline-flex items-center rounded-md border bg-muted/50 px-2 py-1 font-mono text-overline text-muted-foreground\">{feature}</span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('portal.dashboard.noPermissions', 'No permissions')}</p>\n )}\n </PortalCard>\n </div>\n\n <InjectionSpot spotId={extensionPoints.hosts.profileAfter.spotId} context={injectionContext} />\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useEffect, useMemo } from 'react'\nimport { extensionPoints } from '@open-mercato/core/modules/portal/extension-points'\nimport { replaceWithPageReload } from '@open-mercato/core/modules/portal/lib/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { usePortalContext } from '@open-mercato/ui/portal/PortalContext'\nimport { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'\nimport { PortalCard, PortalCardHeader, PortalStatRow, PortalCardDivider } from '@open-mercato/ui/portal/components/PortalCard'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\n\ntype Props = { params: { orgSlug: string } }\n\nexport default function PortalProfilePage({ params }: Props) {\n const t = useT()\n const { auth } = usePortalContext()\n const { user, roles, resolvedFeatures, isPortalAdmin, loading } = auth\n\n // Leaving an authenticated page for the login page crosses the public/authenticated\n // boundary, so it must be a full page load \u2014 see `replaceWithPageReload`.\n useEffect(() => {\n if (!loading && !user) {\n replaceWithPageReload(`/${params.orgSlug}/portal/login`)\n }\n }, [loading, user, params.orgSlug])\n\n const injectionContext = useMemo(\n () => ({ orgSlug: params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin }),\n [params.orgSlug, user, roles, resolvedFeatures, isPortalAdmin],\n )\n\n if (loading) {\n return <div className=\"flex items-center justify-center py-20\"><Spinner /></div>\n }\n\n if (!user) return null\n\n const formatDate = (dateString: string | null) => {\n if (!dateString) return t('portal.dashboard.never', 'Never')\n return new Date(dateString).toLocaleDateString(undefined, {\n year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',\n })\n }\n\n return (\n <div className=\"flex flex-col gap-8\">\n <PortalPageHeader\n label={t('portal.profile.label', 'Account')}\n title={t('portal.profile.title', 'Profile')}\n />\n\n <InjectionSpot spotId={extensionPoints.hosts.profileBefore.spotId} context={injectionContext} />\n\n <div className=\"grid gap-5 md:grid-cols-2\">\n <PortalCard>\n <PortalCardHeader label={t('portal.dashboard.profile', 'Profile')} title={user.displayName} />\n <div className=\"flex flex-col\">\n <PortalStatRow\n label={t('portal.dashboard.email', 'Email')}\n value={\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate\">{user.email}</span>\n <Badge variant={user.emailVerified ? 'default' : 'outline'} className=\"shrink-0 text-overline\">\n {user.emailVerified ? t('portal.dashboard.emailVerified', 'Verified') : t('portal.dashboard.emailNotVerified', 'Unverified')}\n </Badge>\n </span>\n }\n />\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.lastLogin', 'Last login')} value={formatDate(user.lastLoginAt)} />\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.memberSince', 'Member since')} value={formatDate(user.createdAt)} />\n {isPortalAdmin ? (\n <>\n <PortalCardDivider />\n <PortalStatRow label={t('portal.dashboard.roles', 'Role')} value={<Badge className=\"text-overline\">{t('portal.dashboard.portalAdmin', 'Portal Admin')}</Badge>} />\n </>\n ) : null}\n </div>\n </PortalCard>\n\n <PortalCard>\n <PortalCardHeader label={t('portal.dashboard.roles', 'Roles')} title={`${roles.length} assigned`} />\n {roles.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {roles.map((role) => (\n <span key={role.id} className=\"inline-flex items-center rounded-lg border px-3 py-1.5 text-xs font-medium\">{role.name}</span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('portal.dashboard.noRoles', 'No roles assigned')}</p>\n )}\n </PortalCard>\n\n <PortalCard className=\"md:col-span-2\">\n <PortalCardHeader label={t('portal.dashboard.permissions', 'Permissions')} title={`${resolvedFeatures.length} features`} />\n {resolvedFeatures.length > 0 ? (\n <div className=\"flex flex-wrap gap-2\">\n {resolvedFeatures.map((feature) => (\n <span key={feature} className=\"inline-flex items-center rounded-md border bg-muted/50 px-2 py-1 font-mono text-overline text-muted-foreground\">{feature}</span>\n ))}\n </div>\n ) : (\n <p className=\"text-sm text-muted-foreground\">{t('portal.dashboard.noPermissions', 'No permissions')}</p>\n )}\n </PortalCard>\n </div>\n\n <InjectionSpot spotId={extensionPoints.hosts.profileAfter.spotId} context={injectionContext} />\n </div>\n )\n}\n"],
5
+ "mappings": ";AAiCmE,SAyCrD,UAzCqD,KA4BnD,YA5BmD;AAhCnE,SAAS,WAAW,eAAe;AACnC,SAAS,uBAAuB;AAChC,SAAS,6BAA6B;AACtC,SAAS,YAAY;AACrB,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AACjC,SAAS,YAAY,kBAAkB,eAAe,yBAAyB;AAC/E,SAAS,qBAAqB;AAIf,SAAR,kBAAmC,EAAE,OAAO,GAAU;AAC3D,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,KAAK,IAAI,iBAAiB;AAClC,QAAM,EAAE,MAAM,OAAO,kBAAkB,eAAe,QAAQ,IAAI;AAIlE,YAAU,MAAM;AACd,QAAI,CAAC,WAAW,CAAC,MAAM;AACrB,4BAAsB,IAAI,OAAO,OAAO,eAAe;AAAA,IACzD;AAAA,EACF,GAAG,CAAC,SAAS,MAAM,OAAO,OAAO,CAAC;AAElC,QAAM,mBAAmB;AAAA,IACvB,OAAO,EAAE,SAAS,OAAO,SAAS,MAAM,OAAO,kBAAkB,cAAc;AAAA,IAC/E,CAAC,OAAO,SAAS,MAAM,OAAO,kBAAkB,aAAa;AAAA,EAC/D;AAEA,MAAI,SAAS;AACX,WAAO,oBAAC,SAAI,WAAU,0CAAyC,8BAAC,WAAQ,GAAE;AAAA,EAC5E;AAEA,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,aAAa,CAAC,eAA8B;AAChD,QAAI,CAAC,WAAY,QAAO,EAAE,0BAA0B,OAAO;AAC3D,WAAO,IAAI,KAAK,UAAU,EAAE,mBAAmB,QAAW;AAAA,MACxD,MAAM;AAAA,MAAW,OAAO;AAAA,MAAS,KAAK;AAAA,MAAW,MAAM;AAAA,MAAW,QAAQ;AAAA,IAC5E,CAAC;AAAA,EACH;AAEA,SACE,qBAAC,SAAI,WAAU,uBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wBAAwB,SAAS;AAAA,QAC1C,OAAO,EAAE,wBAAwB,SAAS;AAAA;AAAA,IAC5C;AAAA,IAEA,oBAAC,iBAAc,QAAQ,gBAAgB,MAAM,cAAc,QAAQ,SAAS,kBAAkB;AAAA,IAE9F,qBAAC,SAAI,WAAU,6BACb;AAAA,2BAAC,cACC;AAAA,4BAAC,oBAAiB,OAAO,EAAE,4BAA4B,SAAS,GAAG,OAAO,KAAK,aAAa;AAAA,QAC5F,qBAAC,SAAI,WAAU,iBACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,EAAE,0BAA0B,OAAO;AAAA,cAC1C,OACE,qBAAC,UAAK,WAAU,2BACd;AAAA,oCAAC,UAAK,WAAU,YAAY,eAAK,OAAM;AAAA,gBACvC,oBAAC,SAAM,SAAS,KAAK,gBAAgB,YAAY,WAAW,WAAU,0BACnE,eAAK,gBAAgB,EAAE,kCAAkC,UAAU,IAAI,EAAE,qCAAqC,YAAY,GAC7H;AAAA,iBACF;AAAA;AAAA,UAEJ;AAAA,UACA,oBAAC,qBAAkB;AAAA,UACnB,oBAAC,iBAAc,OAAO,EAAE,8BAA8B,YAAY,GAAG,OAAO,WAAW,KAAK,WAAW,GAAG;AAAA,UAC1G,oBAAC,qBAAkB;AAAA,UACnB,oBAAC,iBAAc,OAAO,EAAE,gCAAgC,cAAc,GAAG,OAAO,WAAW,KAAK,SAAS,GAAG;AAAA,UAC3G,gBACC,iCACE;AAAA,gCAAC,qBAAkB;AAAA,YACnB,oBAAC,iBAAc,OAAO,EAAE,0BAA0B,MAAM,GAAG,OAAO,oBAAC,SAAM,WAAU,iBAAiB,YAAE,gCAAgC,cAAc,GAAE,GAAU;AAAA,aAClK,IACE;AAAA,WACN;AAAA,SACF;AAAA,MAEA,qBAAC,cACC;AAAA,4BAAC,oBAAiB,OAAO,EAAE,0BAA0B,OAAO,GAAG,OAAO,GAAG,MAAM,MAAM,aAAa;AAAA,QACjG,MAAM,SAAS,IACd,oBAAC,SAAI,WAAU,wBACZ,gBAAM,IAAI,CAAC,SACV,oBAAC,UAAmB,WAAU,8EAA8E,eAAK,QAAtG,KAAK,EAAsG,CACvH,GACH,IAEA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,4BAA4B,mBAAmB,GAAE;AAAA,SAErG;AAAA,MAEA,qBAAC,cAAW,WAAU,iBACpB;AAAA,4BAAC,oBAAiB,OAAO,EAAE,gCAAgC,aAAa,GAAG,OAAO,GAAG,iBAAiB,MAAM,aAAa;AAAA,QACxH,iBAAiB,SAAS,IACzB,oBAAC,SAAI,WAAU,wBACZ,2BAAiB,IAAI,CAAC,YACrB,oBAAC,UAAmB,WAAU,kHAAkH,qBAArI,OAA6I,CACzJ,GACH,IAEA,oBAAC,OAAE,WAAU,iCAAiC,YAAE,kCAAkC,gBAAgB,GAAE;AAAA,SAExG;AAAA,OACF;AAAA,IAEA,oBAAC,iBAAc,QAAQ,gBAAgB,MAAM,aAAa,QAAQ,SAAS,kBAAkB;AAAA,KAC/F;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,6 @@
1
- function navigateWithPageReload(path) {
2
- window.location.assign(path);
3
- }
1
+ import { navigateWithPageReload, replaceWithPageReload } from "@open-mercato/shared/lib/navigation/pageReload";
4
2
  export {
5
- navigateWithPageReload
3
+ navigateWithPageReload,
4
+ replaceWithPageReload
6
5
  };
7
6
  //# sourceMappingURL=navigation.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/portal/lib/navigation.ts"],
4
- "sourcesContent": ["export function navigateWithPageReload(path: string): void {\n window.location.assign(path)\n}\n"],
5
- "mappings": "AAAO,SAAS,uBAAuB,MAAoB;AACzD,SAAO,SAAS,OAAO,IAAI;AAC7B;",
4
+ "sourcesContent": ["/**\n * Portal-local import path for the boundary-crossing navigation helpers.\n *\n * The implementation lives in `@open-mercato/shared/lib/navigation/pageReload` so `@open-mercato/ui`\n * \u2014 which does not depend on `@open-mercato/core` \u2014 can reach it from the portal shell and hooks.\n * This re-export keeps the published import path stable for existing callers.\n */\nexport { navigateWithPageReload, replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'\n"],
5
+ "mappings": "AAOA,SAAS,wBAAwB,6BAA6B;",
6
6
  "names": []
7
7
  }
@@ -5,6 +5,12 @@ import { sql } from "kysely";
5
5
  import { readCoverageSnapshot, refreshCoverageSnapshot } from "./coverage.js";
6
6
  import { createProfiler, shouldEnableProfiler } from "@open-mercato/shared/lib/profiler";
7
7
  import { decryptIndexDocCustomFields } from "@open-mercato/shared/lib/encryption/indexDoc";
8
+ import {
9
+ buildCustomFieldKindIndex,
10
+ createCustomFieldKindMapResolver,
11
+ emptyCustomFieldKindIndex,
12
+ mergeCustomFieldKindIndexes
13
+ } from "@open-mercato/shared/lib/custom-fields/kinds";
8
14
  import { parseBooleanToken, parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
9
15
  import {
10
16
  applyJoinFilters,
@@ -21,10 +27,12 @@ import {
21
27
  hasSearchFilter
22
28
  } from "@open-mercato/shared/lib/search/availability";
23
29
  import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
30
+ import { buildContainmentPatterns } from "@open-mercato/shared/lib/search/containment";
24
31
  import { runBeforeQueryPipeline, runAfterQueryPipeline } from "@open-mercato/shared/lib/query/query-extension-runner";
25
32
  import { warnOnCiphertextLikeFallback } from "@open-mercato/shared/lib/query/ciphertext-search-warning";
26
33
  import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
27
34
  import { resolveListCountCap } from "@open-mercato/shared/lib/query/count-cap";
35
+ import { resolveCfDefIndexOrgCandidates } from "@open-mercato/shared/lib/crud/custom-field-definition-index";
28
36
  import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decrypt";
29
37
  import { createLogger } from "@open-mercato/shared/lib/logger";
30
38
  import { parseNumberWithDefault } from "@open-mercato/shared/lib/number";
@@ -43,6 +51,7 @@ const CF_FILTER_SUPPORTED_OPS = /* @__PURE__ */ new Set([
43
51
  "lt",
44
52
  "lte"
45
53
  ]);
54
+ const NUMERIC_CF_SORT_KINDS = /* @__PURE__ */ new Set(["integer", "float"]);
46
55
  const DECRYPT_CONCURRENCY = 8;
47
56
  const AUTO_REINDEX_DEBOUNCE_DEFAULT_MS = 3e4;
48
57
  const AUTO_REINDEX_DEBOUNCE_MAX_SCOPES = 1e4;
@@ -138,7 +147,8 @@ class HybridQueryEngine {
138
147
  this.eventBusResolver = eventBusResolver;
139
148
  this.vectorServiceResolver = vectorServiceResolver;
140
149
  this.encryptionResolver = encryptionResolver;
141
- this.customFieldKeysCache = /* @__PURE__ */ new Map();
150
+ this.customFieldDefsCache = /* @__PURE__ */ new Map();
151
+ this.customFieldKindCache = /* @__PURE__ */ new Map();
142
152
  this.columnCache = /* @__PURE__ */ new Map();
143
153
  this.customEntityCache = /* @__PURE__ */ new Map();
144
154
  this.debugVerbosity = null;
@@ -584,6 +594,15 @@ class HybridQueryEngine {
584
594
  fallbackOrgId
585
595
  );
586
596
  const requiresPlaintextSort = encryptedSortFields.size > 0;
597
+ const cfSortKeys = Array.from(new Set(
598
+ resolvedSorts.filter((sort) => sort.field.startsWith("cf:")).map((sort) => sort.field.slice(3))
599
+ ));
600
+ const cfSortKinds = cfSortKeys.length ? await this.resolveCustomFieldSortKinds(
601
+ indexSources.map((source) => String(source.entityId)),
602
+ opts.tenantId ?? null,
603
+ resolveCfDefIndexOrgCandidates(opts.organizationIds, fallbackOrgId),
604
+ cfSortKeys
605
+ ) : /* @__PURE__ */ new Map();
587
606
  const applyBaseScope = (q) => {
588
607
  let next = q;
589
608
  if (orgScope && hasOrganizationColumn) {
@@ -929,7 +948,13 @@ class HybridQueryEngine {
929
948
  const textExpr = this.buildCfTextExprSql(fieldName, indexSources);
930
949
  if (textExpr) {
931
950
  const direction = sql.raw(coerceSortDirection(s.dir));
932
- next = next.orderBy(sql`${textExpr} ${direction}`);
951
+ const kind = cfSortKinds.get(fieldName.slice(3));
952
+ let sortExpr = textExpr;
953
+ if (kind && NUMERIC_CF_SORT_KINDS.has(kind)) {
954
+ const jsonExpr = this.buildCfJsonExprSql(fieldName, indexSources);
955
+ sortExpr = jsonExpr ? sql`CASE WHEN jsonb_typeof(${jsonExpr}) = 'number' THEN (${textExpr})::numeric END` : textExpr;
956
+ }
957
+ next = next.orderBy(sql`${sortExpr} ${direction} NULLS LAST`);
933
958
  }
934
959
  } else {
935
960
  const baseField = resolveBaseColumn(fieldName);
@@ -937,6 +962,10 @@ class HybridQueryEngine {
937
962
  next = next.orderBy(qualify(baseField), s.dir ?? SortDir.Asc);
938
963
  }
939
964
  }
965
+ const lastSortField = resolvedSorts.length ? String(resolvedSorts[resolvedSorts.length - 1].field) : null;
966
+ if (resolvedSorts.length && lastSortField !== "id") {
967
+ next = next.orderBy(qualify("id"), SortDir.Asc);
968
+ }
940
969
  return next;
941
970
  };
942
971
  const page = opts.page?.page ?? 1;
@@ -968,6 +997,16 @@ class HybridQueryEngine {
968
997
  const total = counted.total;
969
998
  const listCountCapWarning = counted.warning;
970
999
  const dekKeyCache = /* @__PURE__ */ new Map();
1000
+ let customFieldKinds = null;
1001
+ const resolveCustomFieldKinds = () => {
1002
+ if (!customFieldKinds) {
1003
+ customFieldKinds = this.resolveCustomFieldKindIndex(
1004
+ Array.from(new Set(indexSources.map((src) => String(src.entityId)))),
1005
+ opts.tenantId ?? null
1006
+ ).then((index) => createCustomFieldKindMapResolver(index));
1007
+ }
1008
+ return customFieldKinds;
1009
+ };
971
1010
  const decryptRow = async (item) => {
972
1011
  let next = item;
973
1012
  if (encSvc?.decryptEntityPayload) {
@@ -986,14 +1025,17 @@ class HybridQueryEngine {
986
1025
  }
987
1026
  if (encSvc) {
988
1027
  try {
1028
+ const cfScope = {
1029
+ tenantId: next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
1030
+ organizationId: next?.organization_id ?? next?.organizationId ?? fallbackOrgId ?? null
1031
+ };
1032
+ const resolveKinds = await resolveCustomFieldKinds();
989
1033
  next = await decryptIndexDocCustomFields(
990
1034
  next,
991
- {
992
- tenantId: next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
993
- organizationId: next?.organization_id ?? next?.organizationId ?? null
994
- },
1035
+ cfScope,
995
1036
  encSvc,
996
- dekKeyCache
1037
+ dekKeyCache,
1038
+ resolveKinds(cfScope.organizationId, cfScope.tenantId)
997
1039
  );
998
1040
  } catch {
999
1041
  }
@@ -1535,6 +1577,12 @@ class HybridQueryEngine {
1535
1577
  }
1536
1578
  return searchRuntime?.encryptedFields != null && isEncryptedLikeField(searchRuntime.encryptedFields, fieldName) ? sql`false` : sql`true`;
1537
1579
  }
1580
+ if ((filter.op === "like" || filter.op === "ilike") && typeof filter.value === "string" && searchRuntime?.enabled && searchRuntime.encryptedFields != null) {
1581
+ const patterns = buildContainmentPatterns(filter.value);
1582
+ if (patterns.length > 1) {
1583
+ return eb.and(patterns.map((pattern) => eb(qualify(baseField), filter.op, pattern)));
1584
+ }
1585
+ }
1538
1586
  return this.buildColumnFilterExpression(eb, qualify(baseField), filter.op, filter.value);
1539
1587
  }
1540
1588
  buildColumnFilterExpression(eb, column, op, value) {
@@ -1738,29 +1786,113 @@ class HybridQueryEngine {
1738
1786
  if (present) baseTableExistsCache.set(table, true);
1739
1787
  return present;
1740
1788
  }
1741
- async resolveAvailableCustomFieldKeys(entityIds, tenantId) {
1789
+ async resolveCustomFieldDefs(entityIds, tenantId) {
1742
1790
  if (!entityIds.length) return [];
1743
1791
  const cacheKey = this.customFieldKeysCacheKey(entityIds, tenantId);
1744
1792
  const now = Date.now();
1745
- const cached = this.customFieldKeysCache.get(cacheKey);
1793
+ const cached = this.customFieldDefsCache.get(cacheKey);
1746
1794
  if (cached && cached.expiresAt > now) return cached.value.slice();
1747
1795
  const db = this.getDb();
1748
- const rows = await db.selectFrom("custom_field_defs").select("key").where("entity_id", "in", entityIds).where("is_active", "=", true).where((eb) => eb.or([
1796
+ const rows = await db.selectFrom("custom_field_defs").select(["entity_id", "key", "kind", "organization_id", "tenant_id", "config_json", "updated_at"]).where("entity_id", "in", entityIds).where("is_active", "=", true).where((eb) => eb.or([
1749
1797
  eb("tenant_id", "=", tenantId),
1750
1798
  eb("tenant_id", "is", null)
1751
1799
  ])).execute();
1752
- const keys = /* @__PURE__ */ new Set();
1800
+ const result = [];
1753
1801
  for (const row of rows) {
1754
1802
  const key = row.key;
1755
- if (typeof key === "string" && key.trim().length) keys.add(key.trim());
1756
- else if (key != null) keys.add(String(key));
1803
+ const normalized = typeof key === "string" ? key.trim() : key == null ? "" : String(key);
1804
+ if (!normalized.length) continue;
1805
+ const entityId = typeof row.entity_id === "string" ? row.entity_id : String(row.entity_id ?? "");
1806
+ result.push({
1807
+ entityId,
1808
+ key: normalized,
1809
+ kind: row.kind,
1810
+ organizationId: typeof row.organization_id === "string" ? row.organization_id : null,
1811
+ tenantId: typeof row.tenant_id === "string" ? row.tenant_id : null,
1812
+ configJson: row.config_json ?? null,
1813
+ updatedAt: row.updated_at ?? null
1814
+ });
1757
1815
  }
1758
- const result = Array.from(keys);
1759
1816
  if (this.customFieldKeysTtlMs > 0) {
1760
- this.customFieldKeysCache.set(cacheKey, { expiresAt: now + this.customFieldKeysTtlMs, value: result });
1817
+ this.customFieldDefsCache.set(cacheKey, { expiresAt: now + this.customFieldKeysTtlMs, value: result });
1761
1818
  }
1762
1819
  return result.slice();
1763
1820
  }
1821
+ /**
1822
+ * Resolve the declared `kind` for a set of `cf:` sort keys so `applySort` can
1823
+ * cast numeric kinds instead of ordering by jsonb text (#5674). A same-org
1824
+ * definition wins over a tenant-wide one, which wins over a global one, for
1825
+ * the same key — a same-key definition scoped to a *different* organization
1826
+ * in the tenant never gets to decide the kind (#5674 review).
1827
+ */
1828
+ async resolveCustomFieldSortKinds(entityIds, tenantId, organizationIds, keys) {
1829
+ if (!entityIds.length || !keys.length) return /* @__PURE__ */ new Map();
1830
+ const sortedOrgIds = organizationIds.slice().sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1831
+ const cacheKey = `${this.customFieldKeysCacheKey(entityIds, tenantId)}|org:${sortedOrgIds.join(",")}|${keys.slice().sort((a, b) => a < b ? -1 : a > b ? 1 : 0).join(",")}`;
1832
+ const now = Date.now();
1833
+ const cached = this.customFieldKindCache.get(cacheKey);
1834
+ if (cached && cached.expiresAt > now) return new Map(cached.value);
1835
+ const db = this.getDb();
1836
+ const rows = await db.selectFrom("custom_field_defs").select(["key", "kind", "organization_id", "tenant_id"]).where("entity_id", "in", entityIds).where("key", "in", keys).where("is_active", "=", true).where((eb) => eb.or([
1837
+ eb("tenant_id", "=", tenantId),
1838
+ eb("tenant_id", "is", null)
1839
+ ])).where((eb) => sortedOrgIds.length ? eb.or([
1840
+ eb("organization_id", "is", null),
1841
+ eb("organization_id", "in", sortedOrgIds)
1842
+ ]) : eb("organization_id", "is", null)).execute();
1843
+ const winners = /* @__PURE__ */ new Map();
1844
+ for (const row of rows) {
1845
+ const key = typeof row.key === "string" ? row.key : String(row.key);
1846
+ const kind = typeof row.kind === "string" ? row.kind : null;
1847
+ if (!kind) continue;
1848
+ const specificity = row.organization_id != null ? 2 : row.tenant_id != null ? 1 : 0;
1849
+ const existing = winners.get(key);
1850
+ if (!existing || specificity >= existing.specificity) winners.set(key, { kind, specificity });
1851
+ }
1852
+ const result = /* @__PURE__ */ new Map();
1853
+ for (const [key, entry] of winners) result.set(key, entry.kind);
1854
+ if (this.customFieldKeysTtlMs > 0) {
1855
+ this.customFieldKindCache.set(cacheKey, { expiresAt: now + this.customFieldKeysTtlMs, value: new Map(result) });
1856
+ }
1857
+ return result;
1858
+ }
1859
+ async resolveAvailableCustomFieldKeys(entityIds, tenantId) {
1860
+ const defs = await this.resolveCustomFieldDefs(entityIds, tenantId);
1861
+ return Array.from(new Set(defs.map((def) => def.key)));
1862
+ }
1863
+ /**
1864
+ * Kind lookup for the `cf:`/`cf_` keys of an index document, so encrypted string-typed
1865
+ * custom fields keep their type on read instead of being `JSON.parse`d back into numbers
1866
+ * or booleans (issue #5968). Backed by the same TTL cache as the key lookup, so this adds
1867
+ * no round trip on a warm cache and fails open to an empty index on a cold-cache error.
1868
+ *
1869
+ * The index keeps every scope's definition per key rather than collapsing to one kind:
1870
+ * the caller resolves the winner against each row's own `organization_id`, the same scope
1871
+ * the row is decrypted with, so an organization's override never types another
1872
+ * organization's value.
1873
+ *
1874
+ * `entityIds` MUST arrive in `indexSources` order: `buildCfJsonExprSql` reads a `cf` value
1875
+ * with `coalesce(source0, source1, …)`, so a key defined by several sources has to be typed
1876
+ * by the same source the value came from — hence the priority-ordered merge rather than
1877
+ * whatever order the rows happen to come back in.
1878
+ */
1879
+ async resolveCustomFieldKindIndex(entityIds, tenantId) {
1880
+ try {
1881
+ const defs = await this.resolveCustomFieldDefs(entityIds, tenantId);
1882
+ const byEntityId = /* @__PURE__ */ new Map();
1883
+ for (const def of defs) {
1884
+ const group = byEntityId.get(def.entityId) ?? [];
1885
+ group.push(def);
1886
+ byEntityId.set(def.entityId, group);
1887
+ }
1888
+ return mergeCustomFieldKindIndexes(
1889
+ entityIds.map((entityId) => buildCustomFieldKindIndex(byEntityId.get(entityId) ?? []))
1890
+ );
1891
+ } catch (err) {
1892
+ logger.warn("Failed to resolve custom field kinds", { entityIds, err });
1893
+ return emptyCustomFieldKindIndex();
1894
+ }
1895
+ }
1764
1896
  async entityHasActiveCustomFields(entityId, tenantId) {
1765
1897
  try {
1766
1898
  const keys = await this.resolveAvailableCustomFieldKeys([entityId], tenantId);
@@ -2135,6 +2267,14 @@ class HybridQueryEngine {
2135
2267
  return q;
2136
2268
  }
2137
2269
  const col = column;
2270
+ if ((filter.op === "like" || filter.op === "ilike") && typeof filter.value === "string" && search?.enabled && search.encryptedFields != null) {
2271
+ const patterns = buildContainmentPatterns(filter.value);
2272
+ if (patterns.length > 1) {
2273
+ let next = q;
2274
+ for (const pattern of patterns) next = next.where(col, filter.op, pattern);
2275
+ return next;
2276
+ }
2277
+ }
2138
2278
  switch (filter.op) {
2139
2279
  case "eq":
2140
2280
  return filter.value === null ? q.where(col, "is", null) : q.where(col, "=", filter.value);