@open-mercato/core 0.5.1-develop.2953.6647bb2c43 → 0.5.1-develop.2964.d5ac4a6ebb

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 (226) 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/backend/users/create/page.js +26 -26
  10. package/dist/modules/auth/backend/users/create/page.js.map +2 -2
  11. package/dist/modules/business_rules/components/ActionRow.js +36 -25
  12. package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
  13. package/dist/modules/business_rules/components/ConditionGroup.js +14 -5
  14. package/dist/modules/business_rules/components/ConditionGroup.js.map +2 -2
  15. package/dist/modules/business_rules/components/ConditionRow.js +19 -10
  16. package/dist/modules/business_rules/components/ConditionRow.js.map +2 -2
  17. package/dist/modules/business_rules/components/RuleSetMembers.js +16 -10
  18. package/dist/modules/business_rules/components/RuleSetMembers.js.map +2 -2
  19. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +30 -34
  20. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  21. package/dist/modules/catalog/backend/catalog/products/create/page.js +220 -223
  22. package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
  23. package/dist/modules/catalog/components/PriceKindSettings.js +20 -19
  24. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  25. package/dist/modules/catalog/components/products/ProductUomSection.js +42 -37
  26. package/dist/modules/catalog/components/products/ProductUomSection.js.map +2 -2
  27. package/dist/modules/catalog/components/products/VariantBuilder.js +22 -18
  28. package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
  29. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +18 -26
  30. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  31. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +4 -6
  32. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  33. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +5 -4
  34. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  35. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +19 -7
  36. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  37. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +24 -21
  38. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  39. package/dist/modules/customers/components/AddressEditor.js +24 -7
  40. package/dist/modules/customers/components/AddressEditor.js.map +2 -2
  41. package/dist/modules/customers/components/AddressFormatSettings.js +35 -25
  42. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  43. package/dist/modules/customers/components/detail/ActivityForm.js +20 -12
  44. package/dist/modules/customers/components/detail/ActivityForm.js.map +2 -2
  45. package/dist/modules/customers/components/detail/AnnualRevenueField.js +2 -2
  46. package/dist/modules/customers/components/detail/AnnualRevenueField.js.map +2 -2
  47. package/dist/modules/customers/components/detail/DealForm.js +19 -14
  48. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  49. package/dist/modules/customers/components/formConfig.js +16 -12
  50. package/dist/modules/customers/components/formConfig.js.map +2 -2
  51. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +3 -2
  52. package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
  53. package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js +18 -10
  54. package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js.map +2 -2
  55. package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js +3 -2
  56. package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js.map +2 -2
  57. package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js +3 -2
  58. package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js.map +2 -2
  59. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +27 -28
  60. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  61. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +14 -6
  62. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +2 -2
  63. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +14 -6
  64. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
  65. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +3 -2
  66. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
  67. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +3 -2
  68. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
  69. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +17 -8
  70. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
  71. package/dist/modules/data_sync/backend/data-sync/page.js +40 -23
  72. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  73. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +15 -6
  74. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  75. package/dist/modules/dictionaries/components/AppearanceSelector.js +4 -4
  76. package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
  77. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +4 -5
  78. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  79. package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +22 -14
  80. package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +2 -2
  81. package/dist/modules/dictionaries/fields/dictionary.js +18 -13
  82. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  83. package/dist/modules/entities/components/EncryptionManager.js +23 -19
  84. package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
  85. package/dist/modules/feature_toggles/components/formConfig.js +17 -9
  86. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  87. package/dist/modules/feature_toggles/components/overrideFormConfig.js +17 -9
  88. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  89. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +15 -8
  90. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  91. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +37 -22
  92. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  93. package/dist/modules/integrations/backend/integrations/[id]/page.js +22 -17
  94. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  95. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +12 -6
  96. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  97. package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -12
  98. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  99. package/dist/modules/resources/components/ResourceCrudForm.js +15 -10
  100. package/dist/modules/resources/components/ResourceCrudForm.js.map +3 -3
  101. package/dist/modules/sales/backend/sales/documents/[id]/page.js +15 -18
  102. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  103. package/dist/modules/sales/components/ProviderFieldInput.js +23 -20
  104. package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
  105. package/dist/modules/sales/components/ShippingMethodsSettings.js +25 -17
  106. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +3 -3
  107. package/dist/modules/sales/components/channels/ChannelOfferForm.js +35 -42
  108. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  109. package/dist/modules/sales/components/documents/AddressesSection.js +87 -90
  110. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  111. package/dist/modules/sales/components/documents/AdjustmentDialog.js +17 -6
  112. package/dist/modules/sales/components/documents/AdjustmentDialog.js.map +3 -3
  113. package/dist/modules/sales/components/documents/LineItemDialog.js +42 -25
  114. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  115. package/dist/modules/sales/components/documents/SalesDocumentForm.js +96 -87
  116. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  117. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +20 -11
  118. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
  119. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +20 -11
  120. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
  121. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +36 -22
  122. package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
  123. package/dist/modules/staff/components/TeamMemberForm.js +14 -9
  124. package/dist/modules/staff/components/TeamMemberForm.js.map +3 -3
  125. package/dist/modules/workflows/backend/tasks/[id]/page.js +42 -21
  126. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  127. package/dist/modules/workflows/components/ActivitiesEditor.js +14 -6
  128. package/dist/modules/workflows/components/ActivitiesEditor.js.map +3 -3
  129. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +25 -17
  130. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +3 -3
  131. package/dist/modules/workflows/components/EdgeEditDialog.js +48 -45
  132. package/dist/modules/workflows/components/EdgeEditDialog.js.map +2 -2
  133. package/dist/modules/workflows/components/NodeEditDialog.js +90 -90
  134. package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
  135. package/dist/modules/workflows/components/StepsEditor.js +14 -6
  136. package/dist/modules/workflows/components/StepsEditor.js.map +3 -3
  137. package/dist/modules/workflows/components/TransitionsEditor.js +31 -26
  138. package/dist/modules/workflows/components/TransitionsEditor.js.map +3 -3
  139. package/dist/modules/workflows/components/fields/ActivityArrayEditor.js +19 -11
  140. package/dist/modules/workflows/components/fields/ActivityArrayEditor.js.map +3 -3
  141. package/dist/modules/workflows/components/fields/BusinessRuleConditionsEditor.js +12 -14
  142. package/dist/modules/workflows/components/fields/BusinessRuleConditionsEditor.js.map +2 -2
  143. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +24 -16
  144. package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +3 -3
  145. package/dist/modules/workflows/components/fields/StartPreConditionsEditor.js +12 -13
  146. package/dist/modules/workflows/components/fields/StartPreConditionsEditor.js.map +2 -2
  147. package/dist/modules/workflows/components/mobile/MobileTaskForm.js +12 -8
  148. package/dist/modules/workflows/components/mobile/MobileTaskForm.js.map +2 -2
  149. package/dist/modules/workflows/frontend/checkout-demo/page.js +43 -46
  150. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  151. package/package.json +3 -3
  152. package/src/helpers/integration/salesUi.ts +40 -30
  153. package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +25 -19
  154. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +21 -11
  155. package/src/modules/attachments/fields/attachment.tsx +4 -6
  156. package/src/modules/auth/backend/users/create/page.tsx +16 -20
  157. package/src/modules/business_rules/components/ActionRow.tsx +51 -32
  158. package/src/modules/business_rules/components/ConditionGroup.tsx +20 -9
  159. package/src/modules/business_rules/components/ConditionRow.tsx +24 -15
  160. package/src/modules/business_rules/components/RuleSetMembers.tsx +23 -13
  161. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +47 -53
  162. package/src/modules/catalog/backend/catalog/products/create/page.tsx +84 -87
  163. package/src/modules/catalog/components/PriceKindSettings.tsx +9 -9
  164. package/src/modules/catalog/components/products/ProductUomSection.tsx +85 -83
  165. package/src/modules/catalog/components/products/VariantBuilder.tsx +49 -33
  166. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +12 -27
  167. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +4 -6
  168. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +5 -4
  169. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +28 -15
  170. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +37 -26
  171. package/src/modules/customers/components/AddressEditor.tsx +30 -16
  172. package/src/modules/customers/components/AddressFormatSettings.tsx +25 -19
  173. package/src/modules/customers/components/detail/ActivityForm.tsx +35 -23
  174. package/src/modules/customers/components/detail/AnnualRevenueField.tsx +2 -2
  175. package/src/modules/customers/components/detail/DealForm.tsx +33 -20
  176. package/src/modules/customers/components/formConfig.tsx +25 -17
  177. package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +3 -2
  178. package/src/modules/customers/widgets/dashboard/new-customers/widget.client.tsx +21 -11
  179. package/src/modules/customers/widgets/dashboard/new-deals/widget.client.tsx +3 -2
  180. package/src/modules/customers/widgets/dashboard/next-interactions/widget.client.tsx +3 -2
  181. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +17 -22
  182. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +17 -7
  183. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +20 -10
  184. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +3 -2
  185. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +3 -2
  186. package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +20 -9
  187. package/src/modules/data_sync/backend/data-sync/page.tsx +64 -38
  188. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +18 -7
  189. package/src/modules/dictionaries/components/AppearanceSelector.tsx +4 -4
  190. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +3 -4
  191. package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +27 -21
  192. package/src/modules/dictionaries/fields/dictionary.tsx +36 -23
  193. package/src/modules/entities/components/EncryptionManager.tsx +49 -33
  194. package/src/modules/feature_toggles/components/formConfig.tsx +20 -10
  195. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +20 -10
  196. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +19 -10
  197. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +49 -26
  198. package/src/modules/integrations/backend/integrations/[id]/page.tsx +20 -11
  199. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +19 -9
  200. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +34 -21
  201. package/src/modules/resources/components/ResourceCrudForm.tsx +24 -15
  202. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +12 -15
  203. package/src/modules/sales/components/ProviderFieldInput.tsx +26 -17
  204. package/src/modules/sales/components/ShippingMethodsSettings.tsx +28 -20
  205. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +51 -46
  206. package/src/modules/sales/components/documents/AddressesSection.tsx +78 -76
  207. package/src/modules/sales/components/documents/AdjustmentDialog.tsx +27 -15
  208. package/src/modules/sales/components/documents/LineItemDialog.tsx +69 -51
  209. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +98 -87
  210. package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +23 -12
  211. package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +23 -12
  212. package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +35 -19
  213. package/src/modules/staff/components/TeamMemberForm.tsx +23 -14
  214. package/src/modules/workflows/backend/tasks/[id]/page.tsx +51 -23
  215. package/src/modules/workflows/components/ActivitiesEditor.tsx +20 -10
  216. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +28 -18
  217. package/src/modules/workflows/components/EdgeEditDialog.tsx +51 -40
  218. package/src/modules/workflows/components/NodeEditDialog.tsx +81 -77
  219. package/src/modules/workflows/components/StepsEditor.tsx +20 -10
  220. package/src/modules/workflows/components/TransitionsEditor.tsx +61 -44
  221. package/src/modules/workflows/components/fields/ActivityArrayEditor.tsx +22 -12
  222. package/src/modules/workflows/components/fields/BusinessRuleConditionsEditor.tsx +9 -13
  223. package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +27 -17
  224. package/src/modules/workflows/components/fields/StartPreConditionsEditor.tsx +9 -12
  225. package/src/modules/workflows/components/mobile/MobileTaskForm.tsx +19 -11
  226. package/src/modules/workflows/frontend/checkout-demo/page.tsx +71 -60
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/components/AttachmentPartitionSettings.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n envKey: string\n createdAt: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n}\n\nconst OCR_MODEL_OPTIONS = [\n { value: '', label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport function AttachmentPartitionSettings() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n setSubmitting(true)\n setError(null)\n try {\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const call = await apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <select\n id=\"partition-ocr-model\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.ocrModel}\n onChange={(event) => setForm((prev) => ({ ...prev, ocrModel: event.target.value }))}\n >\n {OCR_MODEL_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {t(`attachments.partitions.form.ocrModelOptions.${option.value || 'default'}`, option.label)}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {error ? <p className=\"text-sm text-red-600\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n envKey: string\n createdAt: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n}\n\nconst OCR_MODEL_OPTIONS = [\n { value: '', label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport function AttachmentPartitionSettings() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n setSubmitting(true)\n setError(null)\n try {\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const call = await apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel || undefined}\n onValueChange={(value) => setForm((prev) => ({ ...prev, ocrModel: value ?? '' }))}\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(`attachments.partitions.form.ocrModelOptions.${option.value || 'default'}`, option.label)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {error ? <p className=\"text-sm text-red-600\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -4,6 +4,7 @@ import * as React from "react";
4
4
  import { FieldRegistry } from "@open-mercato/ui/backend/fields/registry";
5
5
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
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
  function humanSize(n) {
@@ -160,9 +161,8 @@ function AttachmentDefEditor({ def, onChange }) {
160
161
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
161
162
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Max file size (MB)" }),
162
163
  /* @__PURE__ */ jsx(
163
- "input",
164
+ Input,
164
165
  {
165
- className: "w-full rounded border px-2 py-1 text-sm",
166
166
  type: "number",
167
167
  min: 0,
168
168
  placeholder: "e.g., 10",
@@ -175,9 +175,8 @@ function AttachmentDefEditor({ def, onChange }) {
175
175
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
176
176
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Accepted extensions" }),
177
177
  /* @__PURE__ */ jsx(
178
- "input",
178
+ Input,
179
179
  {
180
- className: "w-full rounded border px-2 py-1 text-sm",
181
180
  placeholder: "e.g., pdf, jpg, png",
182
181
  value: exts,
183
182
  onChange: (e) => setExts(e.target.value),
@@ -189,9 +188,8 @@ function AttachmentDefEditor({ def, onChange }) {
189
188
  /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
190
189
  /* @__PURE__ */ jsx("label", { className: "text-xs font-medium", children: "Partition code" }),
191
190
  /* @__PURE__ */ jsx(
192
- "input",
191
+ Input,
193
192
  {
194
- className: "w-full rounded border px-2 py-1 text-sm",
195
193
  placeholder: "e.g., productsMedia",
196
194
  value: partition,
197
195
  onChange: (e) => setPartition(e.target.value),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/fields/attachment.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { FieldRegistry } from '@open-mercato/ui/backend/fields/registry'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Upload } from 'lucide-react'\n\nfunction humanSize(n: number): string {\n if (!Number.isFinite(n)) return String(n)\n const units = ['B','KB','MB','GB']\n let i = 0\n let x = n\n while (x >= 1024 && i < units.length - 1) { x /= 1024; i++ }\n return `${x.toFixed(i === 0 ? 0 : 1)} ${units[i]}`\n}\n\ntype AttachmentsResponse = {\n items?: Array<{ id: string; url: string; fileName: string; fileSize: number }>\n error?: string\n}\n\ntype AttachmentFieldDef = CustomFieldDefDto & {\n configJson?: {\n maxAttachmentSizeMb?: number\n acceptExtensions?: string[]\n partitionCode?: string\n }\n}\n\ntype AttachmentDefEditorPatch = {\n maxAttachmentSizeMb?: number\n acceptExtensions?: string[]\n partitionCode?: string\n}\n\nfunction buildAcceptAttribute(def?: AttachmentFieldDef): string | undefined {\n if (!Array.isArray(def?.acceptExtensions) || def.acceptExtensions.length === 0) return undefined\n const values = def.acceptExtensions\n .map((entry) => String(entry ?? '').trim().replace(/^\\./, ''))\n .filter((entry) => entry.length > 0)\n if (values.length === 0) return undefined\n return values.map((entry) => `.${entry}`).join(',')\n}\n\nexport const AttachmentInput = ({\n entityId,\n recordId,\n def,\n disabled,\n}: {\n entityId?: string\n recordId?: string\n def?: AttachmentFieldDef\n disabled?: boolean\n}) => {\n const t = useT()\n const [items, setItems] = React.useState<Array<{ id: string; url: string; fileName: string; fileSize: number }>>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [uploading, setUploading] = React.useState(false)\n const fileInputRef = React.useRef<HTMLInputElement | null>(null)\n const accept = React.useMemo(() => buildAcceptAttribute(def), [def])\n\n const load = React.useCallback(async () => {\n if (!entityId || !recordId) return\n try {\n setLoading(true)\n const call = await apiCall<AttachmentsResponse>(\n `/api/attachments?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.errors.load', 'Failed to load attachments.')\n throw new Error(message)\n }\n const j = call.result ?? { items: [] }\n setItems(Array.isArray(j.items) ? j.items : [])\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : t('attachments.library.errors.load', 'Failed to load attachments.'))\n } finally {\n setLoading(false)\n }\n }, [entityId, recordId, t])\n\n React.useEffect(() => { load() }, [load])\n\n const onUpload = async (files: FileList | null) => {\n if (!files || !entityId || !recordId) return\n setError(null)\n setUploading(true)\n try {\n for (const file of Array.from(files)) {\n const ext = (file.name || '').split('.').pop()?.toLowerCase() || ''\n const acceptExtensions = Array.isArray(def?.acceptExtensions) ? def.acceptExtensions : []\n if (acceptExtensions.length > 0) {\n const allowed = new Set(acceptExtensions.map((entry) => String(entry).toLowerCase().replace(/^\\./, '')))\n if (!allowed.has(ext)) { setError('File type not allowed'); continue }\n }\n const maxAttachmentSizeMb = typeof def?.maxAttachmentSizeMb === 'number' ? def.maxAttachmentSizeMb : undefined\n if (typeof maxAttachmentSizeMb === 'number' && maxAttachmentSizeMb > 0) {\n const maxBytes = Math.floor(maxAttachmentSizeMb * 1024 * 1024)\n if (file.size > maxBytes) { setError(`File exceeds ${maxAttachmentSizeMb} MB limit`); continue }\n }\n const fd = new FormData()\n fd.set('entityId', entityId)\n fd.set('recordId', recordId)\n if (def?.key) fd.set('fieldKey', String(def.key))\n fd.set('file', file)\n const call = await apiCall<{ error?: string }>(\n '/api/attachments',\n { method: 'POST', body: fd },\n { fallback: null },\n )\n if (!call.ok) {\n setError(call.result?.error || t('attachments.library.upload.failed', 'Upload failed.'))\n break\n }\n }\n await load()\n } finally {\n setUploading(false)\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n }\n\n return (\n <div className=\"space-y-3\">\n {!entityId || !recordId ? (\n <div className=\"rounded-md border border-dashed border-border/70 px-3 py-4 text-sm text-muted-foreground\">\n {t('attachments.library.upload.saveFirst', 'Save the record before uploading files.')}\n </div>\n ) : (\n <div className=\"rounded-md border border-dashed border-border/70 bg-muted/30 px-4 py-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => fileInputRef.current?.click()}\n disabled={disabled || uploading}\n >\n <Upload className=\"h-4 w-4\" />\n {uploading\n ? t('attachments.library.upload.submitting', 'Uploading\u2026')\n : t('attachments.library.upload.choose', 'Choose files')}\n </Button>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n accept={accept}\n disabled={disabled || uploading}\n onChange={(event) => { void onUpload(event.target.files) }}\n />\n </div>\n )}\n {error ? <div className=\"text-xs text-red-600\">{error}</div> : null}\n <div className=\"space-y-1\">\n {loading ? <div className=\"text-xs text-muted-foreground\">{t('attachments.library.loading', 'Loading attachments\u2026')}</div> : null}\n {items.map(it => (\n <div key={it.id} className=\"text-sm\">\n <a className=\"underline\" href={it.url} target=\"_blank\" rel=\"noreferrer\">{it.fileName}</a>\n <span className=\"text-xs text-muted-foreground\"> \u2022 {humanSize(it.fileSize)}</span>\n </div>\n ))}\n {!loading && items.length === 0 ? <div className=\"text-xs text-muted-foreground\">{t('attachments.library.table.empty', 'No attachments found.')}</div> : null}\n </div>\n </div>\n )\n}\n\n// Register with field registry under kind 'attachment'\nfunction AttachmentDefEditor({ def, onChange }: { def: AttachmentFieldDef; onChange: (patch: AttachmentDefEditorPatch) => void }) {\n const cfg = def?.configJson || {}\n const [maxMb, setMaxMb] = React.useState<number | ''>(typeof cfg.maxAttachmentSizeMb === 'number' ? cfg.maxAttachmentSizeMb : '')\n const [exts, setExts] = React.useState<string>((Array.isArray(cfg.acceptExtensions) ? cfg.acceptExtensions : []).join(', '))\n const [partition, setPartition] = React.useState<string>(typeof cfg.partitionCode === 'string' ? cfg.partitionCode : '')\n return (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <label className=\"text-xs font-medium\">Max file size (MB)</label>\n <input\n className=\"w-full rounded border px-2 py-1 text-sm\"\n type=\"number\"\n min={0}\n placeholder=\"e.g., 10\"\n value={maxMb}\n onChange={(e) => setMaxMb(e.target.value === '' ? '' : Number(e.target.value))}\n onBlur={() => onChange({ maxAttachmentSizeMb: maxMb === '' ? undefined : Number(maxMb) })}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-xs font-medium\">Accepted extensions</label>\n <input\n className=\"w-full rounded border px-2 py-1 text-sm\"\n placeholder=\"e.g., pdf, jpg, png\"\n value={exts}\n onChange={(e) => setExts(e.target.value)}\n onBlur={() => onChange({ acceptExtensions: exts.split(',').map((s) => s.trim()).filter(Boolean) })}\n />\n <div className=\"text-xs text-muted-foreground\">Leave blank to allow any.</div>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-xs font-medium\">Partition code</label>\n <input\n className=\"w-full rounded border px-2 py-1 text-sm\"\n placeholder=\"e.g., productsMedia\"\n value={partition}\n onChange={(e) => setPartition(e.target.value)}\n onBlur={() => onChange({ partitionCode: partition.trim() || undefined })}\n />\n <div className=\"text-xs text-muted-foreground\">\n Configure partitions under Settings \u2192 Attachments. Leave blank for default.\n </div>\n </div>\n </div>\n )\n}\n\nFieldRegistry.register('attachment', {\n input: (props) => <AttachmentInput entityId={props.entityId} recordId={props.recordId} def={props.def} disabled={props.disabled} />,\n defEditor: (p) => <AttachmentDefEditor {...p} />,\n})\n\nexport {}\n"],
5
- "mappings": ";AAqIQ,cAMI,YANJ;AApIR,YAAY,WAAW;AACvB,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AAExB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,cAAc;AAEvB,SAAS,UAAU,GAAmB;AACpC,MAAI,CAAC,OAAO,SAAS,CAAC,EAAG,QAAO,OAAO,CAAC;AACxC,QAAM,QAAQ,CAAC,KAAI,MAAK,MAAK,IAAI;AACjC,MAAI,IAAI;AACR,MAAI,IAAI;AACR,SAAO,KAAK,QAAQ,IAAI,MAAM,SAAS,GAAG;AAAE,SAAK;AAAM;AAAA,EAAI;AAC3D,SAAO,GAAG,EAAE,QAAQ,MAAM,IAAI,IAAI,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC;AAClD;AAqBA,SAAS,qBAAqB,KAA8C;AAC1E,MAAI,CAAC,MAAM,QAAQ,KAAK,gBAAgB,KAAK,IAAI,iBAAiB,WAAW,EAAG,QAAO;AACvF,QAAM,SAAS,IAAI,iBAChB,IAAI,CAAC,UAAU,OAAO,SAAS,EAAE,EAAE,KAAK,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC5D,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACrC,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,IAAI,CAAC,UAAU,IAAI,KAAK,EAAE,EAAE,KAAK,GAAG;AACpD;AAEO,MAAM,kBAAkB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAKM;AACJ,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAiF,CAAC,CAAC;AACnH,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,eAAe,MAAM,OAAgC,IAAI;AAC/D,QAAM,SAAS,MAAM,QAAQ,MAAM,qBAAqB,GAAG,GAAG,CAAC,GAAG,CAAC;AAEnE,QAAM,OAAO,MAAM,YAAY,YAAY;AACzC,QAAI,CAAC,YAAY,CAAC,SAAU;AAC5B,QAAI;AACF,iBAAW,IAAI;AACf,YAAM,OAAO,MAAM;AAAA,QACjB,6BAA6B,mBAAmB,QAAQ,CAAC,aAAa,mBAAmB,QAAQ,CAAC;AAAA,QAClG;AAAA,QACA,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,MAC5B;AACA,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,UAAU,KAAK,QAAQ,SAAS,EAAE,mCAAmC,6BAA6B;AACxG,cAAM,IAAI,MAAM,OAAO;AAAA,MACzB;AACA,YAAM,IAAI,KAAK,UAAU,EAAE,OAAO,CAAC,EAAE;AACrC,eAAS,MAAM,QAAQ,EAAE,KAAK,IAAI,EAAE,QAAQ,CAAC,CAAC;AAAA,IAChD,SAASA,QAAgB;AACvB,eAASA,kBAAiB,QAAQA,OAAM,UAAU,EAAE,mCAAmC,6BAA6B,CAAC;AAAA,IACvH,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,UAAU,UAAU,CAAC,CAAC;AAE1B,QAAM,UAAU,MAAM;AAAE,SAAK;AAAA,EAAE,GAAG,CAAC,IAAI,CAAC;AAExC,QAAM,WAAW,OAAO,UAA2B;AACjD,QAAI,CAAC,SAAS,CAAC,YAAY,CAAC,SAAU;AACtC,aAAS,IAAI;AACb,iBAAa,IAAI;AACjB,QAAI;AACF,iBAAW,QAAQ,MAAM,KAAK,KAAK,GAAG;AACpC,cAAM,OAAO,KAAK,QAAQ,IAAI,MAAM,GAAG,EAAE,IAAI,GAAG,YAAY,KAAK;AACjE,cAAM,mBAAmB,MAAM,QAAQ,KAAK,gBAAgB,IAAI,IAAI,mBAAmB,CAAC;AACxF,YAAI,iBAAiB,SAAS,GAAG;AAC/B,gBAAM,UAAU,IAAI,IAAI,iBAAiB,IAAI,CAAC,UAAU,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,OAAO,EAAE,CAAC,CAAC;AACvG,cAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AAAE,qBAAS,uBAAuB;AAAG;AAAA,UAAS;AAAA,QACvE;AACA,cAAM,sBAAsB,OAAO,KAAK,wBAAwB,WAAW,IAAI,sBAAsB;AACrG,YAAI,OAAO,wBAAwB,YAAY,sBAAsB,GAAG;AACtE,gBAAM,WAAW,KAAK,MAAM,sBAAsB,OAAO,IAAI;AAC7D,cAAI,KAAK,OAAO,UAAU;AAAE,qBAAS,gBAAgB,mBAAmB,WAAW;AAAG;AAAA,UAAS;AAAA,QACjG;AACA,cAAM,KAAK,IAAI,SAAS;AACxB,WAAG,IAAI,YAAY,QAAQ;AAC3B,WAAG,IAAI,YAAY,QAAQ;AAC3B,YAAI,KAAK,IAAK,IAAG,IAAI,YAAY,OAAO,IAAI,GAAG,CAAC;AAChD,WAAG,IAAI,QAAQ,IAAI;AACnB,cAAM,OAAO,MAAM;AAAA,UACjB;AAAA,UACA,EAAE,QAAQ,QAAQ,MAAM,GAAG;AAAA,UAC3B,EAAE,UAAU,KAAK;AAAA,QACnB;AACA,YAAI,CAAC,KAAK,IAAI;AACZ,mBAAS,KAAK,QAAQ,SAAS,EAAE,qCAAqC,gBAAgB,CAAC;AACvF;AAAA,QACF;AAAA,MACF;AACA,YAAM,KAAK;AAAA,IACb,UAAE;AACA,mBAAa,KAAK;AAClB,UAAI,aAAa,SAAS;AACxB,qBAAa,QAAQ,QAAQ;AAAA,MAC/B;AAAA,IACF;AAAA,EACF;AAEA,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,KAAC,YAAY,CAAC,WACb,oBAAC,SAAI,WAAU,4FACZ,YAAE,wCAAwC,yCAAyC,GACtF,IAEA,qBAAC,SAAI,WAAU,0EACb;AAAA,0BAAC,SAAI,WAAU,qCACb;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,SAAS,MAAM,aAAa,SAAS,MAAM;AAAA,UAC3C,UAAU,YAAY;AAAA,UAEtB;AAAA,gCAAC,UAAO,WAAU,WAAU;AAAA,YAC3B,YACG,EAAE,yCAAyC,iBAAY,IACvD,EAAE,qCAAqC,cAAc;AAAA;AAAA;AAAA,MAC3D,GACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,KAAK;AAAA,UACL,MAAK;AAAA,UACL,UAAQ;AAAA,UACR,WAAU;AAAA,UACV;AAAA,UACA,UAAU,YAAY;AAAA,UACtB,UAAU,CAAC,UAAU;AAAE,iBAAK,SAAS,MAAM,OAAO,KAAK;AAAA,UAAE;AAAA;AAAA,MAC3D;AAAA,OACF;AAAA,IAED,QAAQ,oBAAC,SAAI,WAAU,wBAAwB,iBAAM,IAAS;AAAA,IAC/D,qBAAC,SAAI,WAAU,aACZ;AAAA,gBAAU,oBAAC,SAAI,WAAU,iCAAiC,YAAE,+BAA+B,2BAAsB,GAAE,IAAS;AAAA,MAC5H,MAAM,IAAI,QACT,qBAAC,SAAgB,WAAU,WACzB;AAAA,4BAAC,OAAE,WAAU,aAAY,MAAM,GAAG,KAAK,QAAO,UAAS,KAAI,cAAc,aAAG,UAAS;AAAA,QACrF,qBAAC,UAAK,WAAU,iCAAgC;AAAA;AAAA,UAAI,UAAU,GAAG,QAAQ;AAAA,WAAE;AAAA,WAFnE,GAAG,EAGb,CACD;AAAA,MACA,CAAC,WAAW,MAAM,WAAW,IAAI,oBAAC,SAAI,WAAU,iCAAiC,YAAE,mCAAmC,uBAAuB,GAAE,IAAS;AAAA,OAC3J;AAAA,KACF;AAEJ;AAGA,SAAS,oBAAoB,EAAE,KAAK,SAAS,GAAqF;AAChI,QAAM,MAAM,KAAK,cAAc,CAAC;AAChC,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAsB,OAAO,IAAI,wBAAwB,WAAW,IAAI,sBAAsB,EAAE;AAChI,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,UAAkB,MAAM,QAAQ,IAAI,gBAAgB,IAAI,IAAI,mBAAmB,CAAC,GAAG,KAAK,IAAI,CAAC;AAC3H,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAiB,OAAO,IAAI,kBAAkB,WAAW,IAAI,gBAAgB,EAAE;AACvH,SACE,qBAAC,SAAI,WAAU,yCACb;AAAA,yBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAsB,gCAAkB;AAAA,MACzD;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,MAAK;AAAA,UACL,KAAK;AAAA,UACL,aAAY;AAAA,UACZ,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,UAAU,KAAK,KAAK,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,UAC7E,QAAQ,MAAM,SAAS,EAAE,qBAAqB,UAAU,KAAK,SAAY,OAAO,KAAK,EAAE,CAAC;AAAA;AAAA,MAC1F;AAAA,OACF;AAAA,IACA,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAsB,iCAAmB;AAAA,MAC1D;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,aAAY;AAAA,UACZ,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA,UACvC,QAAQ,MAAM,SAAS,EAAE,kBAAkB,KAAK,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,EAAE,CAAC;AAAA;AAAA,MACnG;AAAA,MACA,oBAAC,SAAI,WAAU,iCAAgC,uCAAyB;AAAA,OAC1E;AAAA,IACA,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,WAAM,WAAU,uBAAsB,4BAAc;AAAA,MACrD;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,aAAY;AAAA,UACZ,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,UAC5C,QAAQ,MAAM,SAAS,EAAE,eAAe,UAAU,KAAK,KAAK,OAAU,CAAC;AAAA;AAAA,MACzE;AAAA,MACA,oBAAC,SAAI,WAAU,iCAAgC,8FAE/C;AAAA,OACF;AAAA,KACF;AAEJ;AAEA,cAAc,SAAS,cAAc;AAAA,EACnC,OAAO,CAAC,UAAU,oBAAC,mBAAgB,UAAU,MAAM,UAAU,UAAU,MAAM,UAAU,KAAK,MAAM,KAAK,UAAU,MAAM,UAAU;AAAA,EACjI,WAAW,CAAC,MAAM,oBAAC,uBAAqB,GAAG,GAAG;AAChD,CAAC;",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { FieldRegistry } from '@open-mercato/ui/backend/fields/registry'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Upload } from 'lucide-react'\n\nfunction humanSize(n: number): string {\n if (!Number.isFinite(n)) return String(n)\n const units = ['B','KB','MB','GB']\n let i = 0\n let x = n\n while (x >= 1024 && i < units.length - 1) { x /= 1024; i++ }\n return `${x.toFixed(i === 0 ? 0 : 1)} ${units[i]}`\n}\n\ntype AttachmentsResponse = {\n items?: Array<{ id: string; url: string; fileName: string; fileSize: number }>\n error?: string\n}\n\ntype AttachmentFieldDef = CustomFieldDefDto & {\n configJson?: {\n maxAttachmentSizeMb?: number\n acceptExtensions?: string[]\n partitionCode?: string\n }\n}\n\ntype AttachmentDefEditorPatch = {\n maxAttachmentSizeMb?: number\n acceptExtensions?: string[]\n partitionCode?: string\n}\n\nfunction buildAcceptAttribute(def?: AttachmentFieldDef): string | undefined {\n if (!Array.isArray(def?.acceptExtensions) || def.acceptExtensions.length === 0) return undefined\n const values = def.acceptExtensions\n .map((entry) => String(entry ?? '').trim().replace(/^\\./, ''))\n .filter((entry) => entry.length > 0)\n if (values.length === 0) return undefined\n return values.map((entry) => `.${entry}`).join(',')\n}\n\nexport const AttachmentInput = ({\n entityId,\n recordId,\n def,\n disabled,\n}: {\n entityId?: string\n recordId?: string\n def?: AttachmentFieldDef\n disabled?: boolean\n}) => {\n const t = useT()\n const [items, setItems] = React.useState<Array<{ id: string; url: string; fileName: string; fileSize: number }>>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [uploading, setUploading] = React.useState(false)\n const fileInputRef = React.useRef<HTMLInputElement | null>(null)\n const accept = React.useMemo(() => buildAcceptAttribute(def), [def])\n\n const load = React.useCallback(async () => {\n if (!entityId || !recordId) return\n try {\n setLoading(true)\n const call = await apiCall<AttachmentsResponse>(\n `/api/attachments?entityId=${encodeURIComponent(entityId)}&recordId=${encodeURIComponent(recordId)}`,\n undefined,\n { fallback: { items: [] } },\n )\n if (!call.ok) {\n const message = call.result?.error || t('attachments.library.errors.load', 'Failed to load attachments.')\n throw new Error(message)\n }\n const j = call.result ?? { items: [] }\n setItems(Array.isArray(j.items) ? j.items : [])\n } catch (error: unknown) {\n setError(error instanceof Error ? error.message : t('attachments.library.errors.load', 'Failed to load attachments.'))\n } finally {\n setLoading(false)\n }\n }, [entityId, recordId, t])\n\n React.useEffect(() => { load() }, [load])\n\n const onUpload = async (files: FileList | null) => {\n if (!files || !entityId || !recordId) return\n setError(null)\n setUploading(true)\n try {\n for (const file of Array.from(files)) {\n const ext = (file.name || '').split('.').pop()?.toLowerCase() || ''\n const acceptExtensions = Array.isArray(def?.acceptExtensions) ? def.acceptExtensions : []\n if (acceptExtensions.length > 0) {\n const allowed = new Set(acceptExtensions.map((entry) => String(entry).toLowerCase().replace(/^\\./, '')))\n if (!allowed.has(ext)) { setError('File type not allowed'); continue }\n }\n const maxAttachmentSizeMb = typeof def?.maxAttachmentSizeMb === 'number' ? def.maxAttachmentSizeMb : undefined\n if (typeof maxAttachmentSizeMb === 'number' && maxAttachmentSizeMb > 0) {\n const maxBytes = Math.floor(maxAttachmentSizeMb * 1024 * 1024)\n if (file.size > maxBytes) { setError(`File exceeds ${maxAttachmentSizeMb} MB limit`); continue }\n }\n const fd = new FormData()\n fd.set('entityId', entityId)\n fd.set('recordId', recordId)\n if (def?.key) fd.set('fieldKey', String(def.key))\n fd.set('file', file)\n const call = await apiCall<{ error?: string }>(\n '/api/attachments',\n { method: 'POST', body: fd },\n { fallback: null },\n )\n if (!call.ok) {\n setError(call.result?.error || t('attachments.library.upload.failed', 'Upload failed.'))\n break\n }\n }\n await load()\n } finally {\n setUploading(false)\n if (fileInputRef.current) {\n fileInputRef.current.value = ''\n }\n }\n }\n\n return (\n <div className=\"space-y-3\">\n {!entityId || !recordId ? (\n <div className=\"rounded-md border border-dashed border-border/70 px-3 py-4 text-sm text-muted-foreground\">\n {t('attachments.library.upload.saveFirst', 'Save the record before uploading files.')}\n </div>\n ) : (\n <div className=\"rounded-md border border-dashed border-border/70 bg-muted/30 px-4 py-4\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => fileInputRef.current?.click()}\n disabled={disabled || uploading}\n >\n <Upload className=\"h-4 w-4\" />\n {uploading\n ? t('attachments.library.upload.submitting', 'Uploading\u2026')\n : t('attachments.library.upload.choose', 'Choose files')}\n </Button>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n multiple\n className=\"hidden\"\n accept={accept}\n disabled={disabled || uploading}\n onChange={(event) => { void onUpload(event.target.files) }}\n />\n </div>\n )}\n {error ? <div className=\"text-xs text-red-600\">{error}</div> : null}\n <div className=\"space-y-1\">\n {loading ? <div className=\"text-xs text-muted-foreground\">{t('attachments.library.loading', 'Loading attachments\u2026')}</div> : null}\n {items.map(it => (\n <div key={it.id} className=\"text-sm\">\n <a className=\"underline\" href={it.url} target=\"_blank\" rel=\"noreferrer\">{it.fileName}</a>\n <span className=\"text-xs text-muted-foreground\"> \u2022 {humanSize(it.fileSize)}</span>\n </div>\n ))}\n {!loading && items.length === 0 ? <div className=\"text-xs text-muted-foreground\">{t('attachments.library.table.empty', 'No attachments found.')}</div> : null}\n </div>\n </div>\n )\n}\n\n// Register with field registry under kind 'attachment'\nfunction AttachmentDefEditor({ def, onChange }: { def: AttachmentFieldDef; onChange: (patch: AttachmentDefEditorPatch) => void }) {\n const cfg = def?.configJson || {}\n const [maxMb, setMaxMb] = React.useState<number | ''>(typeof cfg.maxAttachmentSizeMb === 'number' ? cfg.maxAttachmentSizeMb : '')\n const [exts, setExts] = React.useState<string>((Array.isArray(cfg.acceptExtensions) ? cfg.acceptExtensions : []).join(', '))\n const [partition, setPartition] = React.useState<string>(typeof cfg.partitionCode === 'string' ? cfg.partitionCode : '')\n return (\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <label className=\"text-xs font-medium\">Max file size (MB)</label>\n <Input\n type=\"number\"\n min={0}\n placeholder=\"e.g., 10\"\n value={maxMb}\n onChange={(e) => setMaxMb(e.target.value === '' ? '' : Number(e.target.value))}\n onBlur={() => onChange({ maxAttachmentSizeMb: maxMb === '' ? undefined : Number(maxMb) })}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-xs font-medium\">Accepted extensions</label>\n <Input\n placeholder=\"e.g., pdf, jpg, png\"\n value={exts}\n onChange={(e) => setExts(e.target.value)}\n onBlur={() => onChange({ acceptExtensions: exts.split(',').map((s) => s.trim()).filter(Boolean) })}\n />\n <div className=\"text-xs text-muted-foreground\">Leave blank to allow any.</div>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-xs font-medium\">Partition code</label>\n <Input\n placeholder=\"e.g., productsMedia\"\n value={partition}\n onChange={(e) => setPartition(e.target.value)}\n onBlur={() => onChange({ partitionCode: partition.trim() || undefined })}\n />\n <div className=\"text-xs text-muted-foreground\">\n Configure partitions under Settings \u2192 Attachments. Leave blank for default.\n </div>\n </div>\n </div>\n )\n}\n\nFieldRegistry.register('attachment', {\n input: (props) => <AttachmentInput entityId={props.entityId} recordId={props.recordId} def={props.def} disabled={props.disabled} />,\n defEditor: (p) => <AttachmentDefEditor {...p} />,\n})\n\nexport {}\n"],
5
+ "mappings": "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",
6
6
  "names": ["error"]
7
7
  }
@@ -11,6 +11,8 @@ import { OrganizationSelect } from "@open-mercato/core/modules/directory/compone
11
11
  import { TenantSelect } from "@open-mercato/core/modules/directory/components/TenantSelect";
12
12
  import { fetchRoleOptions } from "@open-mercato/core/modules/auth/backend/users/roleOptions";
13
13
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
14
+ import { RadioGroup } from "@open-mercato/ui/primitives/radio";
15
+ import { RadioField } from "@open-mercato/ui/primitives/radio-field";
14
16
  import { useT } from "@open-mercato/shared/lib/i18n/context";
15
17
  import { formatPasswordRequirements, getPasswordPolicy } from "@open-mercato/shared/lib/auth/passwordPolicy";
16
18
  function TenantAwareOrganizationSelectInput({
@@ -329,32 +331,30 @@ function DashboardWidgetSelector({
329
331
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
330
332
  error && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: error }),
331
333
  !error && /* @__PURE__ */ jsxs(Fragment, { children: [
332
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-md border bg-muted/30 px-3 py-2", children: [
333
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
334
- /* @__PURE__ */ jsx(
335
- "input",
336
- {
337
- type: "radio",
338
- value: "inherit",
339
- checked: mode === "inherit",
340
- onChange: () => onModeChange("inherit")
341
- }
342
- ),
343
- t("auth.users.widgets.mode.inherit", "Inherit from roles")
344
- ] }),
345
- /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
346
- /* @__PURE__ */ jsx(
347
- "input",
348
- {
349
- type: "radio",
350
- value: "override",
351
- checked: mode === "override",
352
- onChange: () => onModeChange("override")
353
- }
354
- ),
355
- t("auth.users.widgets.mode.override", "Override for this user")
356
- ] })
357
- ] }),
334
+ /* @__PURE__ */ jsxs(
335
+ RadioGroup,
336
+ {
337
+ className: "flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2",
338
+ value: mode,
339
+ onValueChange: (next) => onModeChange(next),
340
+ children: [
341
+ /* @__PURE__ */ jsx(
342
+ RadioField,
343
+ {
344
+ value: "inherit",
345
+ label: t("auth.users.widgets.mode.inherit", "Inherit from roles")
346
+ }
347
+ ),
348
+ /* @__PURE__ */ jsx(
349
+ RadioField,
350
+ {
351
+ value: "override",
352
+ label: t("auth.users.widgets.mode.override", "Override for this user")
353
+ }
354
+ )
355
+ ]
356
+ }
357
+ ),
358
358
  mode === "override" && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: catalog.map((widget) => /* @__PURE__ */ jsxs("label", { className: "flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40", children: [
359
359
  /* @__PURE__ */ jsx(
360
360
  "input",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/auth/backend/users/create/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\ntype CreateUserFormValues = {\n email: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype UserListResponse = {\n isSuperAdmin?: boolean\n}\n\ntype WidgetCatalogResponse = {\n items?: Array<{ id?: string | null; title?: string | null; description?: string | null }>\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n tenantId={tenantId}\n includeInactiveIds={includeInactiveIds}\n />\n )\n}\n\nexport default function CreateUserPage() {\n const t = useT()\n const [widgetCatalog, setWidgetCatalog] = React.useState<Array<{ id: string; title: string; description: string | null }>>([])\n const [widgetLoading, setWidgetLoading] = React.useState(true)\n const [widgetError, setWidgetError] = React.useState<string | null>(null)\n const [widgetMode, setWidgetMode] = React.useState<'inherit' | 'override'>('inherit')\n const [selectedWidgets, setSelectedWidgets] = React.useState<string[]>([])\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const [sendInviteEmail, setSendInviteEmail] = React.useState(false)\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadCatalog() {\n setWidgetLoading(true)\n setWidgetError(null)\n try {\n const { ok, result } = await apiCall<WidgetCatalogResponse>('/api/dashboards/widgets/catalog')\n if (!ok) throw new Error('request_failed')\n if (!cancelled) {\n const rawItems: unknown[] = Array.isArray(result?.items) ? result?.items ?? [] : []\n const normalized = rawItems\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const idValue = entry.id\n const titleValue = entry.title\n const descriptionValue = entry.description\n const id = typeof idValue === 'string' ? idValue : null\n if (!id || !id.length) return null\n const title = typeof titleValue === 'string' && titleValue.length > 0 ? titleValue : id\n const description = typeof descriptionValue === 'string' && descriptionValue.length > 0 ? descriptionValue : null\n return { id, title, description }\n })\n .filter((item): item is { id: string; title: string; description: string | null } => item !== null)\n setWidgetCatalog(normalized)\n }\n } catch (err) {\n console.error('Failed to load dashboard widget catalog', err)\n if (!cancelled) {\n setWidgetError(t(\n 'auth.users.widgets.errors.load',\n 'Unable to load dashboard widgets. You can configure them later from the user page.',\n ))\n }\n } finally {\n if (!cancelled) setWidgetLoading(false)\n }\n }\n loadCatalog()\n return () => { cancelled = true }\n }, [t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<UserListResponse>('/api/auth/users?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch (err) {\n console.error('Failed to resolve actor super admin flag', err)\n } finally {\n if (!cancelled) setActorResolved(true)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const toggleWidget = React.useCallback((id: string) => {\n setSelectedWidgets((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n {\n id: 'sendInviteEmail',\n label: t('auth.users.form.field.sendInviteEmail', 'Send password setup link via email'),\n type: 'custom',\n component: () => (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n className=\"size-4\"\n checked={sendInviteEmail}\n onChange={(e) => setSendInviteEmail(e.target.checked)}\n />\n {t('auth.users.form.field.sendInviteEmailHint', 'Invite user to set their own password via a secure email link')}\n </label>\n ),\n },\n ...(!sendInviteEmail ? [{\n id: 'password',\n label: t('auth.users.form.field.password', 'Password'),\n type: 'password' as const,\n required: true,\n description: passwordDescription,\n }] : []),\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n required\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? value : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, selectedTenantId, sendInviteEmail, t])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = sendInviteEmail\n ? ['email', 'sendInviteEmail', 'organizationId', 'roles']\n : ['email', 'sendInviteEmail', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) {\n const orgIdx = base.indexOf('organizationId')\n base.splice(orgIdx, 0, 'tenantId')\n }\n return base\n }, [actorIsSuperAdmin, sendInviteEmail])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (\n <div className=\"text-sm text-muted-foreground\">\n {t('auth.users.form.aclHint', 'ACL can be edited after creating the user.')}\n </div>\n ),\n },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (\n <DashboardWidgetSelector\n catalog={widgetCatalog}\n loading={widgetLoading}\n error={widgetError}\n mode={widgetMode}\n onModeChange={setWidgetMode}\n selected={selectedWidgets}\n onToggle={toggleWidget}\n />\n ),\n },\n ], [detailFieldIds, t, widgetCatalog, widgetError, widgetLoading, widgetMode, selectedWidgets, toggleWidget])\n\n const initialValues = React.useMemo<Partial<CreateUserFormValues>>(\n () => ({\n email: '',\n password: '',\n tenantId: null,\n organizationId: null,\n roles: [],\n }),\n [],\n )\n\n return (\n <Page>\n <PageBody>\n <CrudForm<CreateUserFormValues>\n title={t('auth.users.form.title.create', 'Create User')}\n backHref=\"/backend/users\"\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n submitLabel={t('auth.users.form.action.create', 'Create')}\n cancelHref=\"/backend/users\"\n successRedirect={`/backend/users?flash=${encodeURIComponent(\n sendInviteEmail\n ? t('auth.users.flash.createdWithInvite', 'User created and invitation sent')\n : t('auth.users.flash.created', 'User created')\n )}&type=success`}\n onSubmit={async (values) => {\n const customFields = collectCustomFieldValues(values)\n const payload: Record<string, unknown> = {\n email: values.email,\n organizationId: values.organizationId ? values.organizationId : null,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n if (sendInviteEmail) {\n payload.sendInviteEmail = true\n } else {\n payload.password = values.password\n }\n if (actorIsSuperAdmin) {\n const rawTenant = typeof values.tenantId === 'string' ? values.tenantId.trim() : null\n payload.tenantId = rawTenant && rawTenant.length ? rawTenant : null\n }\n const { result: created } = await createCrud<{ id?: string; _warning?: string }>('auth/users', payload)\n const newUserId = typeof created?.id === 'string' ? created.id : null\n if (created?._warning === 'invite_email_failed') {\n const msg = t('auth.users.flash.createdEmailFailed', 'User created but invitation email could not be sent. You can resend it from the user page.')\n window.location.href = `/backend/users?flash=${encodeURIComponent(msg)}&type=warning`\n return\n }\n\n if (widgetMode === 'override' && newUserId) {\n await updateCrud('dashboards/users/widgets', {\n userId: newUserId,\n mode: 'override',\n widgetIds: selectedWidgets,\n organizationId: values.organizationId ? values.organizationId : null,\n tenantId: actorIsSuperAdmin\n ? (typeof values.tenantId === 'string' && values.tenantId.length ? values.tenantId : null)\n : null,\n }, {\n errorMessage: t('auth.users.form.errors.widgetsAssign', 'Failed to assign dashboard widgets to the new user'),\n })\n }\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction DashboardWidgetSelector({\n catalog,\n loading,\n error,\n mode,\n onModeChange,\n selected,\n onToggle,\n}: {\n catalog: Array<{ id: string; title: string; description: string | null }>\n loading: boolean\n error: string | null\n mode: 'inherit' | 'override'\n onModeChange: (mode: 'inherit' | 'override') => void\n selected: string[]\n onToggle: (id: string) => void\n}) {\n const t = useT()\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('auth.users.widgets.loading', 'Loading widgets\u2026')}\n </div>\n )\n }\n\n return (\n <div className=\"space-y-3\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n {!error && (\n <>\n <div className=\"flex items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\">\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n value=\"inherit\"\n checked={mode === 'inherit'}\n onChange={() => onModeChange('inherit')}\n />\n {t('auth.users.widgets.mode.inherit', 'Inherit from roles')}\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"radio\"\n value=\"override\"\n checked={mode === 'override'}\n onChange={() => onModeChange('override')}\n />\n {t('auth.users.widgets.mode.override', 'Override for this user')}\n </label>\n </div>\n {mode === 'override' && (\n <div className=\"space-y-2\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => onToggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{widget.title}</div>\n {widget.description ? <div className=\"text-xs text-muted-foreground\">{widget.description}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n {mode === 'inherit' && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('auth.users.widgets.mode.hint', 'New users inherit widgets from their assigned roles. Override to pick a custom set.')}\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { E } from '#generated/entities.ids.generated'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm, type CrudField, type CrudFormGroup, type CrudFieldOption } from '@open-mercato/ui/backend/CrudForm'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud, updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'\nimport { OrganizationSelect } from '@open-mercato/core/modules/directory/components/OrganizationSelect'\nimport { TenantSelect } from '@open-mercato/core/modules/directory/components/TenantSelect'\nimport { fetchRoleOptions } from '@open-mercato/core/modules/auth/backend/users/roleOptions'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup } from '@open-mercato/ui/primitives/radio'\nimport { RadioField } from '@open-mercato/ui/primitives/radio-field'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatPasswordRequirements, getPasswordPolicy } from '@open-mercato/shared/lib/auth/passwordPolicy'\n\ntype CreateUserFormValues = {\n email: string\n password: string\n tenantId: string | null\n organizationId: string | null\n roles: string[]\n} & Record<string, unknown>\n\ntype UserListResponse = {\n isSuperAdmin?: boolean\n}\n\ntype WidgetCatalogResponse = {\n items?: Array<{ id?: string | null; title?: string | null; description?: string | null }>\n}\n\ntype TenantAwareOrganizationSelectProps = {\n fieldId: string\n value: string | null\n setValue: (value: string | null) => void\n tenantId: string | null\n includeInactiveIds?: Iterable<string | null | undefined>\n}\n\nfunction TenantAwareOrganizationSelectInput({\n fieldId,\n value,\n setValue,\n tenantId,\n includeInactiveIds,\n}: TenantAwareOrganizationSelectProps) {\n const prevTenantRef = React.useRef<string | null>(tenantId)\n const hydratedRef = React.useRef(false)\n const handleChange = React.useCallback((next: string | null) => {\n setValue(next ?? null)\n }, [setValue])\n\n React.useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n prevTenantRef.current = tenantId\n return\n }\n if (prevTenantRef.current !== tenantId) {\n prevTenantRef.current = tenantId\n setValue(null)\n }\n }, [tenantId, setValue])\n\n return (\n <OrganizationSelect\n id={fieldId}\n value={value}\n onChange={handleChange}\n required\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n tenantId={tenantId}\n includeInactiveIds={includeInactiveIds}\n />\n )\n}\n\nexport default function CreateUserPage() {\n const t = useT()\n const [widgetCatalog, setWidgetCatalog] = React.useState<Array<{ id: string; title: string; description: string | null }>>([])\n const [widgetLoading, setWidgetLoading] = React.useState(true)\n const [widgetError, setWidgetError] = React.useState<string | null>(null)\n const [widgetMode, setWidgetMode] = React.useState<'inherit' | 'override'>('inherit')\n const [selectedWidgets, setSelectedWidgets] = React.useState<string[]>([])\n const [selectedTenantId, setSelectedTenantId] = React.useState<string | null>(null)\n const [actorIsSuperAdmin, setActorIsSuperAdmin] = React.useState(false)\n const [actorResolved, setActorResolved] = React.useState(false)\n const [sendInviteEmail, setSendInviteEmail] = React.useState(false)\n const passwordPolicy = React.useMemo(() => getPasswordPolicy(), [])\n const passwordRequirements = React.useMemo(\n () => formatPasswordRequirements(passwordPolicy, t),\n [passwordPolicy, t],\n )\n const passwordDescription = React.useMemo(() => (\n passwordRequirements\n ? t('auth.password.requirements.help', 'Password requirements: {requirements}', { requirements: passwordRequirements })\n : undefined\n ), [passwordRequirements, t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadCatalog() {\n setWidgetLoading(true)\n setWidgetError(null)\n try {\n const { ok, result } = await apiCall<WidgetCatalogResponse>('/api/dashboards/widgets/catalog')\n if (!ok) throw new Error('request_failed')\n if (!cancelled) {\n const rawItems: unknown[] = Array.isArray(result?.items) ? result?.items ?? [] : []\n const normalized = rawItems\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const entry = item as Record<string, unknown>\n const idValue = entry.id\n const titleValue = entry.title\n const descriptionValue = entry.description\n const id = typeof idValue === 'string' ? idValue : null\n if (!id || !id.length) return null\n const title = typeof titleValue === 'string' && titleValue.length > 0 ? titleValue : id\n const description = typeof descriptionValue === 'string' && descriptionValue.length > 0 ? descriptionValue : null\n return { id, title, description }\n })\n .filter((item): item is { id: string; title: string; description: string | null } => item !== null)\n setWidgetCatalog(normalized)\n }\n } catch (err) {\n console.error('Failed to load dashboard widget catalog', err)\n if (!cancelled) {\n setWidgetError(t(\n 'auth.users.widgets.errors.load',\n 'Unable to load dashboard widgets. You can configure them later from the user page.',\n ))\n }\n } finally {\n if (!cancelled) setWidgetLoading(false)\n }\n }\n loadCatalog()\n return () => { cancelled = true }\n }, [t])\n\n React.useEffect(() => {\n let cancelled = false\n async function loadActor() {\n try {\n const { ok, result } = await apiCall<UserListResponse>('/api/auth/users?page=1&pageSize=1')\n if (!cancelled && ok) setActorIsSuperAdmin(Boolean(result?.isSuperAdmin))\n } catch (err) {\n console.error('Failed to resolve actor super admin flag', err)\n } finally {\n if (!cancelled) setActorResolved(true)\n }\n }\n loadActor()\n return () => { cancelled = true }\n }, [])\n\n const toggleWidget = React.useCallback((id: string) => {\n setSelectedWidgets((prev) => (prev.includes(id) ? prev.filter((value) => value !== id) : [...prev, id]))\n }, [])\n\n // Block role loading until we know whether the actor is a super admin. Without this guard the\n // initial (non-super-admin) branch fires before the flag resolves and the server returns roles\n // from other tenants because the real caller is a super admin without tenantId scoping.\n const loadRoleOptions = React.useCallback(async (query?: string): Promise<CrudFieldOption[]> => {\n if (!actorResolved) return []\n if (actorIsSuperAdmin) {\n if (!selectedTenantId) return []\n return fetchRoleOptions(query, { tenantId: selectedTenantId })\n }\n return fetchRoleOptions(query)\n }, [actorIsSuperAdmin, actorResolved, selectedTenantId])\n\n const fields: CrudField[] = React.useMemo(() => {\n const items: CrudField[] = [\n { id: 'email', label: t('auth.users.form.field.email', 'Email'), type: 'text', required: true },\n {\n id: 'sendInviteEmail',\n label: t('auth.users.form.field.sendInviteEmail', 'Send password setup link via email'),\n type: 'custom',\n component: () => (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n className=\"size-4\"\n checked={sendInviteEmail}\n onChange={(e) => setSendInviteEmail(e.target.checked)}\n />\n {t('auth.users.form.field.sendInviteEmailHint', 'Invite user to set their own password via a secure email link')}\n </label>\n ),\n },\n ...(!sendInviteEmail ? [{\n id: 'password',\n label: t('auth.users.form.field.password', 'Password'),\n type: 'password' as const,\n required: true,\n description: passwordDescription,\n }] : []),\n ]\n if (actorIsSuperAdmin) {\n items.push({\n id: 'tenantId',\n label: t('auth.users.form.field.tenant', 'Tenant'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => {\n const normalizedValue = typeof value === 'string'\n ? value\n : (typeof selectedTenantId === 'string' ? selectedTenantId : null)\n return (\n <TenantSelect\n id=\"tenantId\"\n value={normalizedValue}\n onChange={(next) => {\n const resolved = next ?? null\n setValue(resolved)\n setSelectedTenantId(resolved)\n }}\n includeEmptyOption\n className=\"w-full h-9 rounded border px-2 text-sm\"\n required\n />\n )\n },\n })\n }\n items.push({\n id: 'organizationId',\n label: t('auth.users.form.field.organization', 'Organization'),\n type: 'custom',\n required: true,\n component: ({ id, value, setValue }) => {\n const normalizedValue = typeof value === 'string' ? value : null\n return (\n <TenantAwareOrganizationSelectInput\n fieldId={id}\n value={normalizedValue}\n setValue={(next) => setValue(next ?? null)}\n tenantId={selectedTenantId}\n />\n )\n },\n })\n items.push({ id: 'roles', label: t('auth.users.form.field.roles', 'Roles'), type: 'tags', loadOptions: loadRoleOptions })\n return items\n }, [actorIsSuperAdmin, loadRoleOptions, passwordDescription, selectedTenantId, sendInviteEmail, t])\n\n const detailFieldIds = React.useMemo(() => {\n const base: string[] = sendInviteEmail\n ? ['email', 'sendInviteEmail', 'organizationId', 'roles']\n : ['email', 'sendInviteEmail', 'password', 'organizationId', 'roles']\n if (actorIsSuperAdmin) {\n const orgIdx = base.indexOf('organizationId')\n base.splice(orgIdx, 0, 'tenantId')\n }\n return base\n }, [actorIsSuperAdmin, sendInviteEmail])\n\n const groups: CrudFormGroup[] = React.useMemo(() => [\n { id: 'details', title: t('auth.users.form.group.details', 'Details'), column: 1, fields: detailFieldIds },\n {\n id: 'acl',\n title: t('auth.users.form.group.access', 'Access'),\n column: 1,\n component: () => (\n <div className=\"text-sm text-muted-foreground\">\n {t('auth.users.form.aclHint', 'ACL can be edited after creating the user.')}\n </div>\n ),\n },\n { id: 'custom', title: t('auth.users.form.group.customFields', 'Custom Data'), column: 2, kind: 'customFields' },\n {\n id: 'dashboardWidgets',\n title: t('auth.users.form.group.widgets', 'Dashboard Widgets'),\n column: 2,\n component: () => (\n <DashboardWidgetSelector\n catalog={widgetCatalog}\n loading={widgetLoading}\n error={widgetError}\n mode={widgetMode}\n onModeChange={setWidgetMode}\n selected={selectedWidgets}\n onToggle={toggleWidget}\n />\n ),\n },\n ], [detailFieldIds, t, widgetCatalog, widgetError, widgetLoading, widgetMode, selectedWidgets, toggleWidget])\n\n const initialValues = React.useMemo<Partial<CreateUserFormValues>>(\n () => ({\n email: '',\n password: '',\n tenantId: null,\n organizationId: null,\n roles: [],\n }),\n [],\n )\n\n return (\n <Page>\n <PageBody>\n <CrudForm<CreateUserFormValues>\n title={t('auth.users.form.title.create', 'Create User')}\n backHref=\"/backend/users\"\n fields={fields}\n groups={groups}\n entityId={E.auth.user}\n initialValues={initialValues}\n submitLabel={t('auth.users.form.action.create', 'Create')}\n cancelHref=\"/backend/users\"\n successRedirect={`/backend/users?flash=${encodeURIComponent(\n sendInviteEmail\n ? t('auth.users.flash.createdWithInvite', 'User created and invitation sent')\n : t('auth.users.flash.created', 'User created')\n )}&type=success`}\n onSubmit={async (values) => {\n const customFields = collectCustomFieldValues(values)\n const payload: Record<string, unknown> = {\n email: values.email,\n organizationId: values.organizationId ? values.organizationId : null,\n roles: Array.isArray(values.roles) ? values.roles : [],\n ...(Object.keys(customFields).length ? { customFields } : {}),\n }\n if (sendInviteEmail) {\n payload.sendInviteEmail = true\n } else {\n payload.password = values.password\n }\n if (actorIsSuperAdmin) {\n const rawTenant = typeof values.tenantId === 'string' ? values.tenantId.trim() : null\n payload.tenantId = rawTenant && rawTenant.length ? rawTenant : null\n }\n const { result: created } = await createCrud<{ id?: string; _warning?: string }>('auth/users', payload)\n const newUserId = typeof created?.id === 'string' ? created.id : null\n if (created?._warning === 'invite_email_failed') {\n const msg = t('auth.users.flash.createdEmailFailed', 'User created but invitation email could not be sent. You can resend it from the user page.')\n window.location.href = `/backend/users?flash=${encodeURIComponent(msg)}&type=warning`\n return\n }\n\n if (widgetMode === 'override' && newUserId) {\n await updateCrud('dashboards/users/widgets', {\n userId: newUserId,\n mode: 'override',\n widgetIds: selectedWidgets,\n organizationId: values.organizationId ? values.organizationId : null,\n tenantId: actorIsSuperAdmin\n ? (typeof values.tenantId === 'string' && values.tenantId.length ? values.tenantId : null)\n : null,\n }, {\n errorMessage: t('auth.users.form.errors.widgetsAssign', 'Failed to assign dashboard widgets to the new user'),\n })\n }\n }}\n />\n </PageBody>\n </Page>\n )\n}\n\nfunction DashboardWidgetSelector({\n catalog,\n loading,\n error,\n mode,\n onModeChange,\n selected,\n onToggle,\n}: {\n catalog: Array<{ id: string; title: string; description: string | null }>\n loading: boolean\n error: string | null\n mode: 'inherit' | 'override'\n onModeChange: (mode: 'inherit' | 'override') => void\n selected: string[]\n onToggle: (id: string) => void\n}) {\n const t = useT()\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" /> {t('auth.users.widgets.loading', 'Loading widgets\u2026')}\n </div>\n )\n }\n\n return (\n <div className=\"space-y-3\">\n {error && (\n <div className=\"rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive\">{error}</div>\n )}\n {!error && (\n <>\n <RadioGroup\n className=\"flex flex-row items-center gap-3 rounded-md border bg-muted/30 px-3 py-2\"\n value={mode}\n onValueChange={(next) => onModeChange(next as 'inherit' | 'override')}\n >\n <RadioField\n value=\"inherit\"\n label={t('auth.users.widgets.mode.inherit', 'Inherit from roles')}\n />\n <RadioField\n value=\"override\"\n label={t('auth.users.widgets.mode.override', 'Override for this user')}\n />\n </RadioGroup>\n {mode === 'override' && (\n <div className=\"space-y-2\">\n {catalog.map((widget) => (\n <label key={widget.id} className=\"flex items-start gap-3 rounded-md border px-3 py-2 hover:border-primary/40\">\n <input\n type=\"checkbox\"\n className=\"mt-1 size-4\"\n checked={selected.includes(widget.id)}\n onChange={() => onToggle(widget.id)}\n />\n <div>\n <div className=\"text-sm font-medium leading-none\">{widget.title}</div>\n {widget.description ? <div className=\"text-xs text-muted-foreground\">{widget.description}</div> : null}\n </div>\n </label>\n ))}\n </div>\n )}\n {mode === 'inherit' && (\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n {t('auth.users.widgets.mode.hint', 'New users inherit widgets from their assigned roles. Override to pick a custom set.')}\n </div>\n )}\n </>\n )}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }