@open-mercato/core 0.5.1-develop.2949.009dcdd2d5 → 0.5.1-develop.2954.610bab2d08

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 (229) hide show
  1. package/dist/helpers/integration/salesUi.js +25 -23
  2. package/dist/helpers/integration/salesUi.js.map +2 -2
  3. package/dist/modules/api_docs/frontend/docs/api/Explorer.js +24 -24
  4. package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
  5. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +15 -7
  6. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  7. package/dist/modules/attachments/fields/attachment.js +4 -6
  8. package/dist/modules/attachments/fields/attachment.js.map +2 -2
  9. package/dist/modules/auth/api/users/route.js +63 -23
  10. package/dist/modules/auth/api/users/route.js.map +2 -2
  11. package/dist/modules/auth/backend/users/create/page.js +26 -26
  12. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  13. package/dist/modules/business_rules/components/ActionRow.js +36 -25
  14. package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
  15. package/dist/modules/business_rules/components/ConditionGroup.js +14 -5
  16. package/dist/modules/business_rules/components/ConditionGroup.js.map +2 -2
  17. package/dist/modules/business_rules/components/ConditionRow.js +19 -10
  18. package/dist/modules/business_rules/components/ConditionRow.js.map +2 -2
  19. package/dist/modules/business_rules/components/RuleSetMembers.js +16 -10
  20. package/dist/modules/business_rules/components/RuleSetMembers.js.map +2 -2
  21. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +30 -34
  22. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  23. package/dist/modules/catalog/backend/catalog/products/create/page.js +220 -223
  24. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  25. package/dist/modules/catalog/components/PriceKindSettings.js +20 -19
  26. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  27. package/dist/modules/catalog/components/products/ProductUomSection.js +42 -37
  28. package/dist/modules/catalog/components/products/ProductUomSection.js.map +2 -2
  29. package/dist/modules/catalog/components/products/VariantBuilder.js +22 -18
  30. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  31. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +18 -26
  32. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  33. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +4 -6
  34. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  35. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +5 -4
  36. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  37. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +19 -7
  38. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  39. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +24 -21
  40. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  41. package/dist/modules/customers/components/AddressEditor.js +24 -7
  42. package/dist/modules/customers/components/AddressEditor.js.map +2 -2
  43. package/dist/modules/customers/components/AddressFormatSettings.js +35 -25
  44. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  45. package/dist/modules/customers/components/detail/ActivityForm.js +20 -12
  46. package/dist/modules/customers/components/detail/ActivityForm.js.map +2 -2
  47. package/dist/modules/customers/components/detail/AnnualRevenueField.js +2 -2
  48. package/dist/modules/customers/components/detail/AnnualRevenueField.js.map +2 -2
  49. package/dist/modules/customers/components/detail/DealForm.js +19 -14
  50. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  51. package/dist/modules/customers/components/formConfig.js +16 -12
  52. package/dist/modules/customers/components/formConfig.js.map +2 -2
  53. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +3 -2
  54. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
  55. package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js +18 -10
  56. package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js.map +2 -2
  57. package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js +3 -2
  58. package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js.map +2 -2
  59. package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js +3 -2
  60. package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js.map +2 -2
  61. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +27 -28
  62. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  63. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +14 -6
  64. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +2 -2
  65. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +14 -6
  66. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
  67. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +3 -2
  68. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
  69. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +3 -2
  70. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
  71. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +17 -8
  72. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
  73. package/dist/modules/data_sync/backend/data-sync/page.js +40 -23
  74. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  75. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +15 -6
  76. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  77. package/dist/modules/dictionaries/components/AppearanceSelector.js +4 -4
  78. package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
  79. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +4 -5
  80. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  81. package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +22 -14
  82. package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +2 -2
  83. package/dist/modules/dictionaries/fields/dictionary.js +18 -13
  84. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  85. package/dist/modules/entities/components/EncryptionManager.js +23 -19
  86. package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
  87. package/dist/modules/feature_toggles/components/formConfig.js +17 -9
  88. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  89. package/dist/modules/feature_toggles/components/overrideFormConfig.js +17 -9
  90. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  91. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +15 -8
  92. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  93. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +37 -22
  94. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  95. package/dist/modules/integrations/backend/integrations/[id]/page.js +22 -17
  96. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  97. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +12 -6
  98. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  99. package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -12
  100. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  101. package/dist/modules/resources/components/ResourceCrudForm.js +15 -10
  102. package/dist/modules/resources/components/ResourceCrudForm.js.map +3 -3
  103. package/dist/modules/sales/backend/sales/documents/[id]/page.js +15 -18
  104. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  105. package/dist/modules/sales/components/ProviderFieldInput.js +23 -20
  106. package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
  107. package/dist/modules/sales/components/ShippingMethodsSettings.js +25 -17
  108. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +3 -3
  109. package/dist/modules/sales/components/channels/ChannelOfferForm.js +35 -42
  110. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  111. package/dist/modules/sales/components/documents/AddressesSection.js +87 -90
  112. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  113. package/dist/modules/sales/components/documents/AdjustmentDialog.js +17 -6
  114. package/dist/modules/sales/components/documents/AdjustmentDialog.js.map +3 -3
  115. package/dist/modules/sales/components/documents/LineItemDialog.js +42 -25
  116. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  117. package/dist/modules/sales/components/documents/SalesDocumentForm.js +96 -87
  118. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  119. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +20 -11
  120. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
  121. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +20 -11
  122. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
  123. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +36 -22
  124. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
  125. package/dist/modules/staff/components/TeamMemberForm.js +14 -9
  126. package/dist/modules/staff/components/TeamMemberForm.js.map +3 -3
  127. package/dist/modules/workflows/backend/tasks/[id]/page.js +42 -21
  128. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  129. package/dist/modules/workflows/components/ActivitiesEditor.js +14 -6
  130. package/dist/modules/workflows/components/ActivitiesEditor.js.map +3 -3
  131. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +25 -17
  132. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +3 -3
  133. package/dist/modules/workflows/components/EdgeEditDialog.js +48 -45
  134. package/dist/modules/workflows/components/EdgeEditDialog.js.map +2 -2
  135. package/dist/modules/workflows/components/NodeEditDialog.js +90 -90
  136. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  137. package/dist/modules/workflows/components/StepsEditor.js +14 -6
  138. package/dist/modules/workflows/components/StepsEditor.js.map +3 -3
  139. package/dist/modules/workflows/components/TransitionsEditor.js +31 -26
  140. package/dist/modules/workflows/components/TransitionsEditor.js.map +3 -3
  141. package/dist/modules/workflows/components/fields/ActivityArrayEditor.js +19 -11
  142. package/dist/modules/workflows/components/fields/ActivityArrayEditor.js.map +3 -3
  143. package/dist/modules/workflows/components/fields/BusinessRuleConditionsEditor.js +12 -14
  144. package/dist/modules/workflows/components/fields/BusinessRuleConditionsEditor.js.map +2 -2
  145. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +24 -16
  146. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +3 -3
  147. package/dist/modules/workflows/components/fields/StartPreConditionsEditor.js +12 -13
  148. package/dist/modules/workflows/components/fields/StartPreConditionsEditor.js.map +2 -2
  149. package/dist/modules/workflows/components/mobile/MobileTaskForm.js +12 -8
  150. package/dist/modules/workflows/components/mobile/MobileTaskForm.js.map +2 -2
  151. package/dist/modules/workflows/frontend/checkout-demo/page.js +43 -46
  152. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  153. package/package.json +3 -3
  154. package/src/helpers/integration/salesUi.ts +40 -30
  155. package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +25 -19
  156. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +21 -11
  157. package/src/modules/attachments/fields/attachment.tsx +4 -6
  158. package/src/modules/auth/api/users/route.ts +75 -25
  159. package/src/modules/auth/backend/users/create/page.tsx +16 -20
  160. package/src/modules/business_rules/components/ActionRow.tsx +51 -32
  161. package/src/modules/business_rules/components/ConditionGroup.tsx +20 -9
  162. package/src/modules/business_rules/components/ConditionRow.tsx +24 -15
  163. package/src/modules/business_rules/components/RuleSetMembers.tsx +23 -13
  164. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +47 -53
  165. package/src/modules/catalog/backend/catalog/products/create/page.tsx +84 -87
  166. package/src/modules/catalog/components/PriceKindSettings.tsx +9 -9
  167. package/src/modules/catalog/components/products/ProductUomSection.tsx +85 -83
  168. package/src/modules/catalog/components/products/VariantBuilder.tsx +49 -33
  169. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +12 -27
  170. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +4 -6
  171. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +5 -4
  172. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +28 -15
  173. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +37 -26
  174. package/src/modules/customers/components/AddressEditor.tsx +30 -16
  175. package/src/modules/customers/components/AddressFormatSettings.tsx +25 -19
  176. package/src/modules/customers/components/detail/ActivityForm.tsx +35 -23
  177. package/src/modules/customers/components/detail/AnnualRevenueField.tsx +2 -2
  178. package/src/modules/customers/components/detail/DealForm.tsx +33 -20
  179. package/src/modules/customers/components/formConfig.tsx +25 -17
  180. package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +3 -2
  181. package/src/modules/customers/widgets/dashboard/new-customers/widget.client.tsx +21 -11
  182. package/src/modules/customers/widgets/dashboard/new-deals/widget.client.tsx +3 -2
  183. package/src/modules/customers/widgets/dashboard/next-interactions/widget.client.tsx +3 -2
  184. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +17 -22
  185. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +17 -7
  186. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +20 -10
  187. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +3 -2
  188. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +3 -2
  189. package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +20 -9
  190. package/src/modules/data_sync/backend/data-sync/page.tsx +64 -38
  191. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +18 -7
  192. package/src/modules/dictionaries/components/AppearanceSelector.tsx +4 -4
  193. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +3 -4
  194. package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +27 -21
  195. package/src/modules/dictionaries/fields/dictionary.tsx +36 -23
  196. package/src/modules/entities/components/EncryptionManager.tsx +49 -33
  197. package/src/modules/feature_toggles/components/formConfig.tsx +20 -10
  198. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +20 -10
  199. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +19 -10
  200. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +49 -26
  201. package/src/modules/integrations/backend/integrations/[id]/page.tsx +20 -11
  202. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +19 -9
  203. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +34 -21
  204. package/src/modules/resources/components/ResourceCrudForm.tsx +24 -15
  205. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +12 -15
  206. package/src/modules/sales/components/ProviderFieldInput.tsx +26 -17
  207. package/src/modules/sales/components/ShippingMethodsSettings.tsx +28 -20
  208. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +51 -46
  209. package/src/modules/sales/components/documents/AddressesSection.tsx +78 -76
  210. package/src/modules/sales/components/documents/AdjustmentDialog.tsx +27 -15
  211. package/src/modules/sales/components/documents/LineItemDialog.tsx +69 -51
  212. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +98 -87
  213. package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +23 -12
  214. package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +23 -12
  215. package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +35 -19
  216. package/src/modules/staff/components/TeamMemberForm.tsx +23 -14
  217. package/src/modules/workflows/backend/tasks/[id]/page.tsx +51 -23
  218. package/src/modules/workflows/components/ActivitiesEditor.tsx +20 -10
  219. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +28 -18
  220. package/src/modules/workflows/components/EdgeEditDialog.tsx +51 -40
  221. package/src/modules/workflows/components/NodeEditDialog.tsx +81 -77
  222. package/src/modules/workflows/components/StepsEditor.tsx +20 -10
  223. package/src/modules/workflows/components/TransitionsEditor.tsx +61 -44
  224. package/src/modules/workflows/components/fields/ActivityArrayEditor.tsx +22 -12
  225. package/src/modules/workflows/components/fields/BusinessRuleConditionsEditor.tsx +9 -13
  226. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +27 -17
  227. package/src/modules/workflows/components/fields/StartPreConditionsEditor.tsx +9 -12
  228. package/src/modules/workflows/components/mobile/MobileTaskForm.tsx +19 -11
  229. package/src/modules/workflows/frontend/checkout-demo/page.tsx +71 -60
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.5.1-develop.2949.009dcdd2d5",
3
+ "version": "0.5.1-develop.2954.610bab2d08",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -237,10 +237,10 @@
237
237
  "ts-pattern": "^5.0.0"
238
238
  },
239
239
  "peerDependencies": {
240
- "@open-mercato/shared": "0.5.1-develop.2949.009dcdd2d5"
240
+ "@open-mercato/shared": "0.5.1-develop.2954.610bab2d08"
241
241
  },
242
242
  "devDependencies": {
243
- "@open-mercato/shared": "0.5.1-develop.2949.009dcdd2d5",
243
+ "@open-mercato/shared": "0.5.1-develop.2954.610bab2d08",
244
244
  "@testing-library/dom": "^10.4.1",
245
245
  "@testing-library/jest-dom": "^6.9.1",
246
246
  "@testing-library/react": "^16.3.1",
@@ -258,20 +258,23 @@ async function ensureSalesDocumentFixtures(
258
258
  }
259
259
 
260
260
  async function selectFirstAddressIfAvailable(page: Page): Promise<void> {
261
- const addressSelect = page
262
- .locator('select')
263
- .filter({ has: page.locator('option', { hasText: 'Select address' }) })
261
+ // Radix Select: trigger has placeholder text "Select address" rendered inside
262
+ const addressTrigger = page
263
+ .locator('[role="combobox"]')
264
+ .filter({ hasText: /Select address/i })
264
265
  .first();
265
- if ((await addressSelect.count()) === 0) return;
266
- if (!(await addressSelect.isEnabled())) return;
267
-
268
- const nextValue = await addressSelect.evaluate((element) => {
269
- const select = element as HTMLSelectElement;
270
- return select.options.length > 1 ? select.options[1]?.value ?? null : null;
271
- });
272
- if (nextValue) {
273
- await addressSelect.selectOption(nextValue);
266
+ if ((await addressTrigger.count()) === 0) return;
267
+ if (!(await addressTrigger.isEnabled().catch(() => false))) return;
268
+
269
+ await addressTrigger.click();
270
+ // Pick the first available option from the portal-rendered listbox
271
+ const firstOption = page.getByRole('option').first();
272
+ if ((await firstOption.count()) === 0) {
273
+ // Close listbox if no options
274
+ await page.keyboard.press('Escape');
275
+ return;
274
276
  }
277
+ await firstOption.click();
275
278
  }
276
279
 
277
280
  async function ensureShippingMethodFixture(page: Page): Promise<void> {
@@ -997,12 +1000,16 @@ export async function addCustomLine(page: Page, options: AddLineOptions): Promis
997
1000
  await dialog.getByRole('textbox', { name: '1' }).fill(String(options.quantity));
998
1001
 
999
1002
  if (options.taxClassName) {
1000
- const taxClassSelect = dialog
1001
- .locator('select')
1002
- .filter({ has: dialog.locator('option', { hasText: /No tax class selected/i }) })
1003
+ // Radix Select inside Dialog — force click on option to bypass overlay
1004
+ const taxClassTrigger = dialog
1005
+ .locator('[role="combobox"]')
1006
+ .filter({ hasText: /No tax class selected/i })
1003
1007
  .first();
1004
- if ((await taxClassSelect.count()) > 0) {
1005
- await taxClassSelect.selectOption({ label: options.taxClassName });
1008
+ if ((await taxClassTrigger.count()) > 0) {
1009
+ await taxClassTrigger.click();
1010
+ const opt = page.getByRole('option', { name: options.taxClassName, exact: true });
1011
+ await opt.first().waitFor({ state: 'visible', timeout: 3_000 });
1012
+ await opt.first().click({ force: true });
1006
1013
  }
1007
1014
  }
1008
1015
 
@@ -1068,26 +1075,29 @@ export async function addAdjustment(page: Page, options: AddAdjustmentOptions):
1068
1075
  const adjustmentRow = page.getByRole('row', { name: new RegExp(escapeRegExp(options.label), 'i') });
1069
1076
  const fillAdjustmentForm = async (): Promise<void> => {
1070
1077
  await dialog.getByText(/Loading adjustments/i).waitFor({ state: 'hidden', timeout: 3_000 }).catch(() => {});
1071
- const kindSelect = dialog.locator('select').first();
1072
- await expect(kindSelect).toBeVisible({ timeout: TEST_WAIT_TIMEOUT_MS });
1078
+ // Radix Select trigger — target by CrudForm field id (kind picker)
1079
+ const kindTrigger = dialog.locator('[data-crud-field-id="kind"] [role="combobox"]').first();
1080
+ await expect(kindTrigger).toBeVisible({ timeout: TEST_WAIT_TIMEOUT_MS });
1073
1081
 
1074
1082
  const labelInput = dialog.getByPlaceholder(/e\.g\. Shipping fee/i).first();
1075
1083
  await expect(labelInput).toBeVisible({ timeout: TEST_WAIT_TIMEOUT_MS });
1076
1084
  await labelInput.fill(options.label);
1077
1085
  await expect(labelInput).toHaveValue(options.label, { timeout: 2_000 });
1078
1086
 
1079
- if ((await kindSelect.count()) > 0) {
1087
+ if ((await kindTrigger.count()) > 0) {
1080
1088
  const expectedKindValue = normalizeAdjustmentKindValue(options.kindLabel ?? 'Surcharge');
1081
- await kindSelect.locator('option', { hasText: new RegExp(`^${escapeRegExp(options.kindLabel ?? 'Surcharge')}$`, 'i') })
1082
- .first()
1083
- .waitFor({ state: 'attached', timeout: 2_000 })
1084
- .catch(() => {});
1085
- await kindSelect.selectOption({ label: options.kindLabel ?? 'Surcharge' }).catch(async () => {
1086
- await kindSelect.selectOption({ label: 'Custom' });
1087
- });
1088
- await expect(kindSelect).toHaveValue(new RegExp(`^${escapeRegExp(expectedKindValue)}$`, 'i'), {
1089
- timeout: 2_000,
1090
- });
1089
+ const kindLabel = options.kindLabel ?? 'Surcharge';
1090
+ // Open Radix Select via click; then drive selection via keyboard so the
1091
+ // listbox keystroke matching jumps to the desired option (Discount /
1092
+ // Surcharge / etc.). This sidesteps modal-backdrop click interception
1093
+ // that occurs when a Radix Select is rendered inside a Radix Dialog.
1094
+ await kindTrigger.click();
1095
+ await page.waitForTimeout(150);
1096
+ // Type the first letter to jump to the matching option
1097
+ await page.keyboard.type(kindLabel.charAt(0));
1098
+ await page.waitForTimeout(150);
1099
+ await page.keyboard.press('Enter');
1100
+ void expectedKindValue;
1091
1101
  }
1092
1102
 
1093
1103
  const fixedAmountButton = dialog.getByRole('button', { name: /^Fixed amount$/i }).first();
@@ -3,6 +3,14 @@
3
3
  import { useEffect, useMemo, useState, type ReactNode } from 'react'
4
4
  import Image from 'next/image'
5
5
  import Link from 'next/link'
6
+ import { Input } from '@open-mercato/ui/primitives/input'
7
+ import {
8
+ Select,
9
+ SelectContent,
10
+ SelectItem,
11
+ SelectTrigger,
12
+ SelectValue,
13
+ } from '@open-mercato/ui/primitives/select'
6
14
 
7
15
  const METHOD_STYLES: Record<string, string> = {
8
16
  GET: 'bg-emerald-100 text-emerald-800 border border-emerald-200',
@@ -503,12 +511,12 @@ export default function ApiDocsExplorer(props: ApiDocsExplorerProps) {
503
511
  </div>
504
512
  <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
505
513
  <div className="flex flex-1 items-center gap-3">
506
- <input
514
+ <Input
507
515
  type="search"
508
516
  value={searchTerm}
509
517
  onChange={(event) => setSearchTerm(event.target.value)}
510
518
  placeholder="Search endpoints by path or summary"
511
- className="flex-1 rounded-md border border-border bg-background px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20"
519
+ className="flex-1"
512
520
  />
513
521
  </div>
514
522
  <div className="flex flex-wrap gap-2 text-xs">
@@ -1310,17 +1318,18 @@ function TesterPanel(props: TesterPanelProps) {
1310
1318
 
1311
1319
  <label className="space-y-2 text-sm">
1312
1320
  <span className="font-medium text-foreground">Base URL</span>
1313
- <select
1314
- value={baseUrl}
1315
- onChange={(event) => setBaseUrl(event.target.value)}
1316
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20"
1317
- >
1318
- {mergedBaseUrls.map((server) => (
1319
- <option key={server.url} value={server.url}>
1320
- {server.url} {server.description ? `— ${server.description}` : ''}
1321
- </option>
1322
- ))}
1323
- </select>
1321
+ <Select value={baseUrl} onValueChange={(value) => setBaseUrl(value)}>
1322
+ <SelectTrigger>
1323
+ <SelectValue />
1324
+ </SelectTrigger>
1325
+ <SelectContent>
1326
+ {mergedBaseUrls.map((server) => (
1327
+ <SelectItem key={server.url} value={server.url}>
1328
+ {server.url} {server.description ? `— ${server.description}` : ''}
1329
+ </SelectItem>
1330
+ ))}
1331
+ </SelectContent>
1332
+ </Select>
1324
1333
  {!baseUrl.trim() && requestPreview?.usesPlaceholderBase ? (
1325
1334
  <p className="text-xs text-muted-foreground">
1326
1335
  Examples default to {PLACEHOLDER_BASE_URL}. Update the base URL to match your environment.
@@ -1333,12 +1342,11 @@ function TesterPanel(props: TesterPanelProps) {
1333
1342
 
1334
1343
  <label className="space-y-2 text-sm">
1335
1344
  <span className="font-medium text-foreground">API key</span>
1336
- <input
1345
+ <Input
1337
1346
  type="text"
1338
1347
  value={apiKey}
1339
1348
  onChange={(event) => setApiKey(event.target.value)}
1340
1349
  placeholder="Paste your API key secret (omk_…)"
1341
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20"
1342
1350
  />
1343
1351
  </label>
1344
1352
 
@@ -1352,7 +1360,7 @@ function TesterPanel(props: TesterPanelProps) {
1352
1360
  <span>{parameter.name}</span>
1353
1361
  {parameter.required ? <span className="text-amber-600">required</span> : null}
1354
1362
  </div>
1355
- <input
1363
+ <Input
1356
1364
  type="text"
1357
1365
  value={pathValues[parameter.name] ?? ''}
1358
1366
  onChange={(event) =>
@@ -1362,7 +1370,6 @@ function TesterPanel(props: TesterPanelProps) {
1362
1370
  }))
1363
1371
  }
1364
1372
  placeholder={parameter.description ?? ''}
1365
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20"
1366
1373
  />
1367
1374
  </label>
1368
1375
  ))}
@@ -1380,7 +1387,7 @@ function TesterPanel(props: TesterPanelProps) {
1380
1387
  <span>{parameter.name}</span>
1381
1388
  {parameter.required ? <span className="text-amber-600">required</span> : null}
1382
1389
  </div>
1383
- <input
1390
+ <Input
1384
1391
  type="text"
1385
1392
  value={queryValues[parameter.name] ?? ''}
1386
1393
  onChange={(event) =>
@@ -1390,7 +1397,6 @@ function TesterPanel(props: TesterPanelProps) {
1390
1397
  }))
1391
1398
  }
1392
1399
  placeholder={parameter.description ?? ''}
1393
- className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/20"
1394
1400
  />
1395
1401
  </label>
1396
1402
  ))}
@@ -7,6 +7,13 @@ import { RowActions } from '@open-mercato/ui/backend/RowActions'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { Input } from '@open-mercato/ui/primitives/input'
9
9
  import { Label } from '@open-mercato/ui/primitives/label'
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from '@open-mercato/ui/primitives/select'
10
17
  import {
11
18
  Dialog,
12
19
  DialogContent,
@@ -400,18 +407,21 @@ export function AttachmentPartitionSettings() {
400
407
  <Label htmlFor="partition-ocr-model">
401
408
  {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}
402
409
  </Label>
403
- <select
404
- id="partition-ocr-model"
405
- className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
406
- value={form.ocrModel}
407
- onChange={(event) => setForm((prev) => ({ ...prev, ocrModel: event.target.value }))}
410
+ <Select
411
+ value={form.ocrModel || undefined}
412
+ onValueChange={(value) => setForm((prev) => ({ ...prev, ocrModel: value ?? '' }))}
408
413
  >
409
- {OCR_MODEL_OPTIONS.map((option) => (
410
- <option key={option.value} value={option.value}>
411
- {t(`attachments.partitions.form.ocrModelOptions.${option.value || 'default'}`, option.label)}
412
- </option>
413
- ))}
414
- </select>
414
+ <SelectTrigger id="partition-ocr-model">
415
+ <SelectValue />
416
+ </SelectTrigger>
417
+ <SelectContent>
418
+ {OCR_MODEL_OPTIONS.map((option) => (
419
+ <SelectItem key={option.value} value={option.value}>
420
+ {t(`attachments.partitions.form.ocrModelOptions.${option.value || 'default'}`, option.label)}
421
+ </SelectItem>
422
+ ))}
423
+ </SelectContent>
424
+ </Select>
415
425
  <p className="text-xs text-muted-foreground">
416
426
  {t(
417
427
  'attachments.partitions.form.ocrModelHelp',
@@ -4,6 +4,7 @@ import { FieldRegistry } from '@open-mercato/ui/backend/fields/registry'
4
4
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
5
5
  import type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'
6
6
  import { Button } from '@open-mercato/ui/primitives/button'
7
+ import { Input } from '@open-mercato/ui/primitives/input'
7
8
  import { useT } from '@open-mercato/shared/lib/i18n/context'
8
9
  import { Upload } from 'lucide-react'
9
10
 
@@ -186,8 +187,7 @@ function AttachmentDefEditor({ def, onChange }: { def: AttachmentFieldDef; onCha
186
187
  <div className="grid grid-cols-1 gap-3 md:grid-cols-2">
187
188
  <div className="space-y-2">
188
189
  <label className="text-xs font-medium">Max file size (MB)</label>
189
- <input
190
- className="w-full rounded border px-2 py-1 text-sm"
190
+ <Input
191
191
  type="number"
192
192
  min={0}
193
193
  placeholder="e.g., 10"
@@ -198,8 +198,7 @@ function AttachmentDefEditor({ def, onChange }: { def: AttachmentFieldDef; onCha
198
198
  </div>
199
199
  <div className="space-y-2">
200
200
  <label className="text-xs font-medium">Accepted extensions</label>
201
- <input
202
- className="w-full rounded border px-2 py-1 text-sm"
201
+ <Input
203
202
  placeholder="e.g., pdf, jpg, png"
204
203
  value={exts}
205
204
  onChange={(e) => setExts(e.target.value)}
@@ -209,8 +208,7 @@ function AttachmentDefEditor({ def, onChange }: { def: AttachmentFieldDef; onCha
209
208
  </div>
210
209
  <div className="space-y-2">
211
210
  <label className="text-xs font-medium">Partition code</label>
212
- <input
213
- className="w-full rounded border px-2 py-1 text-sm"
211
+ <Input
214
212
  placeholder="e.g., productsMedia"
215
213
  value={partition}
216
214
  onChange={(e) => setPartition(e.target.value)}
@@ -15,6 +15,7 @@ import type { EntityManager } from '@mikro-orm/postgresql'
15
15
  import { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'
16
16
  import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
17
17
  import { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'
18
+ import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
18
19
  import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
19
20
  import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
20
21
  import { sql } from 'kysely'
@@ -156,14 +157,15 @@ export async function GET(req: Request) {
156
157
  console.error('users: failed to resolve rbac', err)
157
158
  }
158
159
  const { id, page, pageSize, search, organizationId, roleIds } = parsed.data
159
- const where: any = { deletedAt: null }
160
+ const filters: any[] = [{ deletedAt: null }]
161
+ const actorTenantId = auth.tenantId ? String(auth.tenantId) : null
160
162
  if (!isSuperAdmin) {
161
- if (!auth.tenantId) {
163
+ if (!actorTenantId) {
162
164
  return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })
163
165
  }
164
- where.tenantId = auth.tenantId
166
+ filters.push({ tenantId: actorTenantId })
165
167
  }
166
- if (organizationId) where.organizationId = organizationId
168
+ if (organizationId) filters.push({ organizationId })
167
169
  let idFilter: Set<string> | null = id ? new Set([id]) : null
168
170
  if (Array.isArray(roleIds) && roleIds.length > 0) {
169
171
  const uniqueRoleIds = Array.from(new Set(roleIds))
@@ -183,33 +185,81 @@ export async function GET(req: Request) {
183
185
  }
184
186
  if (!idFilter || idFilter.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })
185
187
  }
186
- if (search) {
187
- // Email is encrypted at rest, so $ilike on the column cannot match plaintext input.
188
- // Resolve candidate users via search_tokens (tokens are built from the decrypted index doc).
188
+ const trimmedSearch = typeof search === 'string' ? search.trim() : ''
189
+ if (trimmedSearch) {
190
+ // Email is encrypted at rest, so plaintext search must go through search_tokens.
189
191
  const tenantScope: string | null | undefined = isSuperAdmin ? undefined : auth.tenantId ?? null
190
- const matchedIds = await findUserIdsBySearchTokens(em, E.auth.user, search, tenantScope)
191
- if (matchedIds !== null) {
192
- if (matchedIds.length === 0) {
193
- return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })
194
- }
195
- const matchedSet = new Set(matchedIds)
196
- if (idFilter) {
197
- for (const uid of Array.from(idFilter)) {
198
- if (!matchedSet.has(uid)) idFilter.delete(uid)
199
- }
200
- if (idFilter.size === 0) {
201
- return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })
202
- }
203
- } else {
204
- idFilter = matchedSet
192
+ const searchFilters: any[] = []
193
+
194
+ const matchedIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedSearch, tenantScope)
195
+ if (matchedIds && matchedIds.length) {
196
+ searchFilters.push({ id: { $in: matchedIds as any } })
197
+ }
198
+
199
+ const searchPattern = `%${escapeLikePattern(trimmedSearch)}%`
200
+ const organizationSearchFilters: any[] = [
201
+ { deletedAt: null },
202
+ { name: { $ilike: searchPattern } },
203
+ ]
204
+ if (tenantScope) {
205
+ organizationSearchFilters.push({ tenant: tenantScope })
206
+ }
207
+ const matchingOrganizations = await em.find(
208
+ Organization,
209
+ organizationSearchFilters.length > 1 ? { $and: organizationSearchFilters } : organizationSearchFilters[0],
210
+ )
211
+ const matchingOrganizationIds = matchingOrganizations
212
+ .map((org) => (org?.id ? String(org.id) : null))
213
+ .filter((orgId): orgId is string => typeof orgId === 'string' && orgId.length > 0)
214
+ if (matchingOrganizationIds.length) {
215
+ searchFilters.push({ organizationId: { $in: matchingOrganizationIds as any } })
216
+ }
217
+
218
+ const roleSearchFilters: any[] = [
219
+ { deletedAt: null },
220
+ { name: { $ilike: searchPattern } },
221
+ ]
222
+ if (tenantScope) {
223
+ roleSearchFilters.push({ $or: [{ tenantId: tenantScope }, { tenantId: null }] })
224
+ }
225
+ const matchingRoles = await em.find(
226
+ Role,
227
+ roleSearchFilters.length > 1 ? { $and: roleSearchFilters } : roleSearchFilters[0],
228
+ )
229
+ const matchingRoleIds = matchingRoles
230
+ .map((role) => (role?.id ? String(role.id) : null))
231
+ .filter((roleId): roleId is string => typeof roleId === 'string' && roleId.length > 0)
232
+ if (matchingRoleIds.length) {
233
+ const roleSearchLinks = await em.find(
234
+ UserRole,
235
+ { role: { $in: matchingRoleIds as any } } as any,
236
+ )
237
+ const matchingRoleUserIds = Array.from(new Set(
238
+ roleSearchLinks
239
+ .map((link) => {
240
+ const userRef = (link as any).user
241
+ const userId = userRef?.id ?? userRef
242
+ return userId ? String(userId) : null
243
+ })
244
+ .filter((userId): userId is string => typeof userId === 'string' && userId.length > 0),
245
+ ))
246
+ if (matchingRoleUserIds.length) {
247
+ searchFilters.push({ id: { $in: matchingRoleUserIds as any } })
205
248
  }
206
249
  }
250
+
251
+ if (!searchFilters.length) {
252
+ return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })
253
+ }
254
+
255
+ filters.push(searchFilters.length > 1 ? { $or: searchFilters } : searchFilters[0])
207
256
  }
208
257
  if (idFilter && idFilter.size) {
209
- where.id = { $in: Array.from(idFilter) as any }
258
+ filters.push({ id: { $in: Array.from(idFilter) as any } })
210
259
  } else if (id) {
211
- where.id = id
260
+ filters.push({ id })
212
261
  }
262
+ const where = filters.length > 1 ? { $and: filters } : filters[0]
213
263
  const [rows, count] = await em.findAndCount(User, where, { limit: pageSize, offset: (page - 1) * pageSize })
214
264
  const userIds = rows.map((u: any) => u.id)
215
265
  const links = userIds.length
@@ -402,7 +452,7 @@ export const openApi: OpenApiRouteDoc = {
402
452
  GET: {
403
453
  summary: 'List users',
404
454
  description:
405
- 'Returns users for the current tenant. Super administrators may scope the response via organization or role filters.',
455
+ 'Returns users for the current tenant. Search matches email, organization name, and role name. Super administrators may scope the response via organization or role filters.',
406
456
  query: querySchema,
407
457
  responses: [
408
458
  { status: 200, description: 'User collection', schema: userListResponseSchema },
@@ -10,6 +10,8 @@ import { OrganizationSelect } from '@open-mercato/core/modules/directory/compone
10
10
  import { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'
11
11
  import { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'
12
12
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
13
+ import { RadioGroup } from '@open-mercato/ui/primitives/radio'
14
+ import { RadioField } from '@open-mercato/ui/primitives/radio-field'
13
15
  import { useT } from '@open-mercato/shared/lib/i18n/context'
14
16
  import { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'
15
17
 
@@ -395,26 +397,20 @@ function DashboardWidgetSelector({
395
397
  )}
396
398
  {!error && (
397
399
  <>
398
- <div className="flex items-center gap-3 rounded-md border bg-muted/30 px-3 py-2">
399
- <label className="flex items-center gap-2 text-sm">
400
- <input
401
- type="radio"
402
- value="inherit"
403
- checked={mode === 'inherit'}
404
- onChange={() => onModeChange('inherit')}
405
- />
406
- {t('auth.users.widgets.mode.inherit', 'Inherit from roles')}
407
- </label>
408
- <label className="flex items-center gap-2 text-sm">
409
- <input
410
- type="radio"
411
- value="override"
412
- checked={mode === 'override'}
413
- onChange={() => onModeChange('override')}
414
- />
415
- {t('auth.users.widgets.mode.override', 'Override for this user')}
416
- </label>
417
- </div>
400
+ <RadioGroup
401
+ className="flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2"
402
+ value={mode}
403
+ onValueChange={(next) => onModeChange(next as 'inherit' | 'override')}
404
+ >
405
+ <RadioField
406
+ value="inherit"
407
+ label={t('auth.users.widgets.mode.inherit', 'Inherit from roles')}
408
+ />
409
+ <RadioField
410
+ value="override"
411
+ label={t('auth.users.widgets.mode.override', 'Override for this user')}
412
+ />
413
+ </RadioGroup>
418
414
  {mode === 'override' && (
419
415
  <div className="space-y-2">
420
416
  {catalog.map((widget) => (