@open-mercato/core 0.4.6-develop-f7d3079656 → 0.4.6-develop-0861f05ea9

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 (107) hide show
  1. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +17 -154
  2. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +3 -3
  3. package/dist/modules/currencies/backend/exchange-rates/create/page.js +14 -152
  4. package/dist/modules/currencies/backend/exchange-rates/create/page.js.map +2 -2
  5. package/dist/modules/currencies/lib/exchangeRateFormConfig.js +167 -0
  6. package/dist/modules/currencies/lib/exchangeRateFormConfig.js.map +7 -0
  7. package/dist/modules/customers/api/dashboard/widgets/utils.js +1 -34
  8. package/dist/modules/customers/api/dashboard/widgets/utils.js.map +2 -2
  9. package/dist/modules/customers/commands/activities.js +3 -8
  10. package/dist/modules/customers/commands/activities.js.map +2 -2
  11. package/dist/modules/customers/commands/comments.js +2 -8
  12. package/dist/modules/customers/commands/comments.js.map +2 -2
  13. package/dist/modules/dashboards/lib/widgetScope.js +38 -0
  14. package/dist/modules/dashboards/lib/widgetScope.js.map +7 -0
  15. package/dist/modules/entities/lib/makeActivityRoute.js +265 -0
  16. package/dist/modules/entities/lib/makeActivityRoute.js.map +7 -0
  17. package/dist/modules/resources/api/activities.js +24 -232
  18. package/dist/modules/resources/api/activities.js.map +2 -2
  19. package/dist/modules/resources/commands/activities.js +3 -8
  20. package/dist/modules/resources/commands/activities.js.map +2 -2
  21. package/dist/modules/resources/commands/comments.js +2 -8
  22. package/dist/modules/resources/commands/comments.js.map +2 -2
  23. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +27 -182
  24. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +2 -2
  25. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +28 -183
  26. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
  27. package/dist/modules/sales/api/order-line-statuses/route.js +15 -194
  28. package/dist/modules/sales/api/order-line-statuses/route.js.map +2 -2
  29. package/dist/modules/sales/api/order-lines/route.js +15 -281
  30. package/dist/modules/sales/api/order-lines/route.js.map +2 -2
  31. package/dist/modules/sales/api/order-statuses/route.js +15 -194
  32. package/dist/modules/sales/api/order-statuses/route.js.map +2 -2
  33. package/dist/modules/sales/api/payment-statuses/route.js +15 -194
  34. package/dist/modules/sales/api/payment-statuses/route.js.map +2 -2
  35. package/dist/modules/sales/api/quote-lines/route.js +15 -279
  36. package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
  37. package/dist/modules/sales/api/shipment-statuses/route.js +15 -194
  38. package/dist/modules/sales/api/shipment-statuses/route.js.map +2 -2
  39. package/dist/modules/sales/components/PaymentMethodsSettings.js +3 -84
  40. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  41. package/dist/modules/sales/components/ProviderFieldInput.js +86 -0
  42. package/dist/modules/sales/components/ProviderFieldInput.js.map +7 -0
  43. package/dist/modules/sales/components/ShippingMethodsSettings.js +3 -82
  44. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  45. package/dist/modules/sales/lib/makeSalesLineRoute.js +308 -0
  46. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +7 -0
  47. package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +206 -0
  48. package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +7 -0
  49. package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js +178 -0
  50. package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js.map +7 -0
  51. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +1 -39
  52. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
  53. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +1 -39
  54. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
  55. package/dist/modules/sales/widgets/dashboard/shared.js +46 -0
  56. package/dist/modules/sales/widgets/dashboard/shared.js.map +7 -0
  57. package/dist/modules/staff/api/activities.js +24 -232
  58. package/dist/modules/staff/api/activities.js.map +2 -2
  59. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +14 -34
  60. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  61. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +15 -34
  62. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  63. package/dist/modules/staff/commands/activities.js +3 -8
  64. package/dist/modules/staff/commands/activities.js.map +2 -2
  65. package/dist/modules/staff/commands/comments.js +2 -8
  66. package/dist/modules/staff/commands/comments.js.map +2 -2
  67. package/dist/modules/staff/lib/leaveRequestHelpers.js +41 -0
  68. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +7 -0
  69. package/package.json +2 -2
  70. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -180
  71. package/src/modules/currencies/backend/exchange-rates/create/page.tsx +16 -175
  72. package/src/modules/currencies/lib/exchangeRateFormConfig.ts +200 -0
  73. package/src/modules/customers/api/dashboard/widgets/utils.ts +1 -53
  74. package/src/modules/customers/commands/activities.ts +2 -8
  75. package/src/modules/customers/commands/comments.ts +2 -8
  76. package/src/modules/dashboards/i18n/de.json +3 -0
  77. package/src/modules/dashboards/i18n/en.json +3 -0
  78. package/src/modules/dashboards/i18n/es.json +3 -0
  79. package/src/modules/dashboards/i18n/pl.json +3 -0
  80. package/src/modules/dashboards/lib/widgetScope.ts +53 -0
  81. package/src/modules/entities/lib/makeActivityRoute.ts +327 -0
  82. package/src/modules/resources/api/activities.ts +25 -269
  83. package/src/modules/resources/commands/activities.ts +2 -7
  84. package/src/modules/resources/commands/comments.ts +2 -8
  85. package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +29 -244
  86. package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +30 -245
  87. package/src/modules/sales/api/order-line-statuses/route.ts +16 -209
  88. package/src/modules/sales/api/order-lines/route.ts +16 -300
  89. package/src/modules/sales/api/order-statuses/route.ts +16 -209
  90. package/src/modules/sales/api/payment-statuses/route.ts +16 -209
  91. package/src/modules/sales/api/quote-lines/route.ts +16 -298
  92. package/src/modules/sales/api/shipment-statuses/route.ts +16 -209
  93. package/src/modules/sales/components/PaymentMethodsSettings.tsx +3 -88
  94. package/src/modules/sales/components/ProviderFieldInput.tsx +85 -0
  95. package/src/modules/sales/components/ShippingMethodsSettings.tsx +3 -86
  96. package/src/modules/sales/lib/makeSalesLineRoute.ts +345 -0
  97. package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +229 -0
  98. package/src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts +247 -0
  99. package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +7 -50
  100. package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +7 -49
  101. package/src/modules/sales/widgets/dashboard/shared.ts +44 -0
  102. package/src/modules/staff/api/activities.ts +25 -269
  103. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +15 -69
  104. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +16 -65
  105. package/src/modules/staff/commands/activities.ts +2 -7
  106. package/src/modules/staff/commands/comments.ts +2 -8
  107. package/src/modules/staff/lib/leaveRequestHelpers.ts +78 -0
@@ -1,214 +1,21 @@
1
- import { z } from 'zod'
2
- import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
3
- import type { EntityManager } from '@mikro-orm/postgresql'
4
- import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
- import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
- import { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'
7
1
  import { E } from '#generated/entities.ids.generated'
8
2
  import * as F from '#generated/entities/dictionary_entry'
9
- import { statusDictionaryCreateSchema, statusDictionaryUpdateSchema } from '../../data/validators'
10
- import { getSalesDictionaryDefinition, ensureSalesDictionary, type SalesDictionaryKind } from '../../lib/dictionaries'
11
- import { parseScopedCommandInput, resolveCrudRecordId } from '../utils'
12
- import {
13
- createPagedListResponseSchema,
14
- createSalesCrudOpenApi,
15
- defaultDeleteRequestSchema,
16
- } from '../openapi'
17
- import { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'
18
-
19
- const rawBodySchema = z.object({}).passthrough()
20
-
21
- const listSchema = z
22
- .object({
23
- page: z.coerce.number().min(1).default(1),
24
- pageSize: z.coerce.number().min(1).max(100).default(50),
25
- search: z.string().optional(),
26
- sortField: z.string().optional(),
27
- sortDir: z.enum(['asc', 'desc']).optional(),
28
- })
29
- .passthrough()
30
-
31
- const kind: SalesDictionaryKind = 'shipment-status'
32
- const definition = getSalesDictionaryDefinition(kind)
33
-
34
- export const metadata = {
35
- GET: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
36
- POST: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
37
- PUT: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
38
- DELETE: { requireAuth: true, requireFeatures: ['sales.settings.manage'] },
39
- }
40
-
41
- const dictionaryItemSchema = z.object({
42
- id: z.string().uuid(),
43
- value: z.string(),
44
- label: z.string().nullable(),
45
- color: z.string().nullable(),
46
- icon: z.string().nullable(),
47
- organizationId: z.string().uuid().nullable(),
48
- tenantId: z.string().uuid().nullable(),
49
- createdAt: z.string(),
50
- updatedAt: z.string(),
51
- })
52
-
53
- const dictionaryListResponseSchema = createPagedListResponseSchema(dictionaryItemSchema)
54
-
55
- const normalizeId = (value: unknown): string | null => {
56
- if (typeof value !== 'string') return null
57
- const trimmed = value.trim()
58
- return trimmed.length > 0 ? trimmed : null
59
- }
60
-
61
- async function resolveDictionaryContext(ctx: any): Promise<{ dictionaryId: string; organizationId: string | null }> {
62
- if (!ctx.auth || !ctx.auth.tenantId) {
63
- throw new CrudHttpError(401, { error: 'Tenant context is required.' })
64
- }
65
- const em = ctx.container.resolve('em') as EntityManager
66
- const tenantId: string = ctx.auth.tenantId
67
- const candidateOrgIds = new Set<string>()
68
- const pushCandidate = (value: unknown) => {
69
- const normalized = normalizeId(value)
70
- if (normalized) candidateOrgIds.add(normalized)
71
- }
72
- pushCandidate(ctx.selectedOrganizationId)
73
- pushCandidate(ctx.auth.orgId ?? null)
74
- const scope = ctx.organizationScope
75
- if (scope) {
76
- if (Array.isArray(scope.filterIds)) {
77
- for (const id of scope.filterIds) pushCandidate(id)
78
- }
79
- if (Array.isArray(scope.allowedIds)) {
80
- for (const id of scope.allowedIds) pushCandidate(id)
81
- }
82
- }
83
-
84
- for (const orgId of candidateOrgIds) {
85
- const dictionary = await ensureSalesDictionary({
86
- em,
87
- tenantId,
88
- organizationId: orgId,
89
- kind,
90
- })
91
- if (dictionary) {
92
- return { dictionaryId: dictionary.id, organizationId: orgId }
93
- }
94
- }
95
-
96
- const fallback = await em.findOne(
97
- Dictionary,
98
- {
99
- tenantId,
100
- key: definition.key,
101
- deletedAt: null,
102
- },
103
- { orderBy: { createdAt: 'asc' } },
104
- )
105
- if (fallback) {
106
- return { dictionaryId: fallback.id, organizationId: fallback.organizationId }
107
- }
108
- throw new CrudHttpError(400, { error: 'Organization context is required.' })
109
- }
110
-
111
- const crud = makeCrudRoute({
112
- metadata,
113
- orm: {
114
- entity: DictionaryEntry,
115
- idField: 'id',
116
- orgField: 'organizationId',
117
- tenantField: 'tenantId',
118
- softDeleteField: null,
3
+ import { makeStatusDictionaryRoute } from '../../lib/makeStatusDictionaryRoute'
4
+
5
+ const route = makeStatusDictionaryRoute({
6
+ kind: 'shipment-status',
7
+ entityId: E.dictionaries.dictionary_entry,
8
+ fieldConstants: F,
9
+ openApi: {
10
+ resourceName: 'Shipment status',
11
+ pluralName: 'Shipment statuses',
12
+ description: 'Manage the lifecycle states available for shipments.',
119
13
  },
120
- list: {
121
- schema: listSchema,
122
- entityId: E.dictionaries.dictionary_entry,
123
- fields: [
124
- F.id,
125
- F.value,
126
- F.label,
127
- F.color,
128
- F.icon,
129
- F.organization_id,
130
- F.tenant_id,
131
- F.created_at,
132
- F.updated_at,
133
- ],
134
- sortFieldMap: {
135
- id: F.id,
136
- value: F.value,
137
- label: F.label,
138
- createdAt: F.created_at,
139
- updatedAt: F.updated_at,
140
- },
141
- buildFilters: async (query, ctx) => {
142
- const { dictionaryId } = await resolveDictionaryContext(ctx)
143
- const filters: Record<string, unknown> = {
144
- dictionary_id: dictionaryId,
145
- }
146
- if (query.search && query.search.trim().length > 0) {
147
- const term = `%${escapeLikePattern(query.search.trim())}%`
148
- filters.$or = [
149
- { [F.value]: { $ilike: term } },
150
- { [F.label]: { $ilike: term } },
151
- ]
152
- }
153
- return filters
154
- },
155
- transformItem: (item: any) => ({
156
- id: item.id,
157
- value: item.value,
158
- label: item.label,
159
- color: item.color ?? null,
160
- icon: item.icon ?? null,
161
- organizationId: item.organization_id ?? null,
162
- tenantId: item.tenant_id ?? null,
163
- createdAt: item.created_at,
164
- updatedAt: item.updated_at,
165
- }),
166
- },
167
- actions: {
168
- create: {
169
- commandId: `${definition.commandPrefix}.create`,
170
- schema: rawBodySchema,
171
- mapInput: async ({ raw, ctx }) => {
172
- const { translate } = await resolveTranslations()
173
- return parseScopedCommandInput(statusDictionaryCreateSchema, raw ?? {}, ctx, translate)
174
- },
175
- response: ({ result }) => ({ id: result?.entryId ?? null }),
176
- status: 201,
177
- },
178
- update: {
179
- commandId: `${definition.commandPrefix}.update`,
180
- schema: rawBodySchema,
181
- mapInput: async ({ raw, ctx }) => {
182
- const { translate } = await resolveTranslations()
183
- return parseScopedCommandInput(statusDictionaryUpdateSchema, raw ?? {}, ctx, translate)
184
- },
185
- response: () => ({ ok: true }),
186
- },
187
- delete: {
188
- commandId: `${definition.commandPrefix}.delete`,
189
- schema: rawBodySchema,
190
- mapInput: async ({ parsed, ctx }) => {
191
- const { translate } = await resolveTranslations()
192
- const id = resolveCrudRecordId(parsed, ctx, translate)
193
- return { id }
194
- },
195
- response: () => ({ ok: true }),
196
- },
197
- },
198
- })
199
-
200
- export const openApi = createSalesCrudOpenApi({
201
- resourceName: 'Shipment status',
202
- pluralName: 'Shipment statuses',
203
- description: 'Manage the lifecycle states available for shipments.',
204
- querySchema: listSchema,
205
- listResponseSchema: dictionaryListResponseSchema,
206
- create: { schema: statusDictionaryCreateSchema },
207
- update: { schema: statusDictionaryUpdateSchema },
208
- del: { schema: defaultDeleteRequestSchema },
209
14
  })
210
15
 
211
- export const GET = crud.GET
212
- export const POST = crud.POST
213
- export const PUT = crud.PUT
214
- export const DELETE = crud.DELETE
16
+ export const metadata = route.metadata
17
+ export const openApi = route.openApi
18
+ export const GET = route.GET
19
+ export const POST = route.POST
20
+ export const PUT = route.PUT
21
+ export const DELETE = route.DELETE
@@ -12,10 +12,7 @@ import {
12
12
  DialogHeader,
13
13
  DialogTitle,
14
14
  } from '@open-mercato/ui/primitives/dialog'
15
- import { Input } from '@open-mercato/ui/primitives/input'
16
15
  import { Label } from '@open-mercato/ui/primitives/label'
17
- import { Switch } from '@open-mercato/ui/primitives/switch'
18
- import { Textarea } from '@open-mercato/ui/primitives/textarea'
19
16
  import { CrudForm, type CrudField, type CrudCustomFieldRenderProps } from '@open-mercato/ui/backend/CrudForm'
20
17
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
21
18
  import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
@@ -26,8 +23,9 @@ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
26
23
  import {
27
24
  listPaymentProviders,
28
25
  type PaymentProvider,
29
- type ProviderSettingField,
30
26
  } from '../lib/providers'
27
+ import { isRecord } from '@open-mercato/shared/lib/utils'
28
+ import { renderProviderFieldInput } from './ProviderFieldInput'
31
29
 
32
30
  type PaymentMethodRow = {
33
31
  id: string
@@ -68,89 +66,6 @@ const DEFAULT_FORM: PaymentFormValues = {
68
66
  providerSettings: {},
69
67
  }
70
68
 
71
- function isRecord(value: unknown): value is Record<string, unknown> {
72
- return !!value && typeof value === 'object' && !Array.isArray(value)
73
- }
74
-
75
- function renderFieldInput(opts: {
76
- field: ProviderSettingField
77
- value: unknown
78
- onChange: (next: unknown) => void
79
- }) {
80
- const { field, value, onChange } = opts
81
- const common = { id: field.key, 'data-provider-setting': field.key }
82
- switch (field.type) {
83
- case 'textarea':
84
- return (
85
- <Textarea
86
- {...common}
87
- value={typeof value === 'string' ? value : ''}
88
- onChange={(evt) => onChange(evt.target.value)}
89
- placeholder={field.placeholder}
90
- />
91
- )
92
- case 'number':
93
- return (
94
- <Input
95
- {...common}
96
- type="number"
97
- value={typeof value === 'number' || typeof value === 'string' ? String(value) : ''}
98
- onChange={(evt) => onChange(evt.target.value === '' ? '' : Number(evt.target.value))}
99
- placeholder={field.placeholder}
100
- />
101
- )
102
- case 'boolean':
103
- return (
104
- <div className="flex items-center gap-2 py-1">
105
- <Switch
106
- id={field.key}
107
- checked={Boolean(value)}
108
- onCheckedChange={(checked) => onChange(checked)}
109
- />
110
- <Label htmlFor={field.key}>{field.placeholder ?? ''}</Label>
111
- </div>
112
- )
113
- case 'select':
114
- return (
115
- <select
116
- {...common}
117
- className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
118
- value={typeof value === 'string' ? value : ''}
119
- onChange={(evt) => onChange(evt.target.value)}
120
- >
121
- <option value="">—</option>
122
- {(field.options ?? []).map((opt) => (
123
- <option key={opt.value} value={opt.value}>
124
- {opt.label}
125
- </option>
126
- ))}
127
- </select>
128
- )
129
- case 'secret':
130
- return (
131
- <Input
132
- {...common}
133
- type="password"
134
- value={typeof value === 'string' ? value : ''}
135
- onChange={(evt) => onChange(evt.target.value)}
136
- placeholder={field.placeholder}
137
- />
138
- )
139
- case 'url':
140
- case 'text':
141
- default:
142
- return (
143
- <Input
144
- {...common}
145
- type={field.type === 'url' ? 'url' : 'text'}
146
- value={typeof value === 'string' ? value : ''}
147
- onChange={(evt) => onChange(evt.target.value)}
148
- placeholder={field.placeholder}
149
- />
150
- )
151
- }
152
- }
153
-
154
69
  function createPaymentProviderSettingsRenderer(params: {
155
70
  providers: PaymentProvider[]
156
71
  selectPrompt: string
@@ -200,7 +115,7 @@ function createPaymentProviderSettingsRenderer(params: {
200
115
  {field.description ? (
201
116
  <p className="text-xs text-muted-foreground">{field.description}</p>
202
117
  ) : null}
203
- {renderFieldInput({
118
+ {renderProviderFieldInput({
204
119
  field,
205
120
  value: fieldValue,
206
121
  onChange: (next) => setValue({ ...settings, [field.key]: next }),
@@ -0,0 +1,85 @@
1
+ import { Input } from '@open-mercato/ui/primitives/input'
2
+ import { Label } from '@open-mercato/ui/primitives/label'
3
+ import { Switch } from '@open-mercato/ui/primitives/switch'
4
+ import { Textarea } from '@open-mercato/ui/primitives/textarea'
5
+ import { isRecord } from '@open-mercato/shared/lib/utils'
6
+ import type { ProviderSettingField } from '../lib/providers'
7
+
8
+ export function renderProviderFieldInput(opts: {
9
+ field: ProviderSettingField
10
+ value: unknown
11
+ onChange: (next: unknown) => void
12
+ }) {
13
+ const { field, value, onChange } = opts
14
+ const common = { id: field.key, 'data-provider-setting': field.key }
15
+ switch (field.type) {
16
+ case 'textarea':
17
+ return (
18
+ <Textarea
19
+ {...common}
20
+ value={typeof value === 'string' ? value : ''}
21
+ onChange={(evt) => onChange(evt.target.value)}
22
+ placeholder={field.placeholder}
23
+ />
24
+ )
25
+ case 'number':
26
+ return (
27
+ <Input
28
+ {...common}
29
+ type="number"
30
+ value={typeof value === 'number' || typeof value === 'string' ? String(value) : ''}
31
+ onChange={(evt) => onChange(evt.target.value === '' ? '' : Number(evt.target.value))}
32
+ placeholder={field.placeholder}
33
+ />
34
+ )
35
+ case 'boolean':
36
+ return (
37
+ <div className="flex items-center gap-2 py-1">
38
+ <Switch
39
+ id={field.key}
40
+ checked={Boolean(value)}
41
+ onCheckedChange={(checked) => onChange(checked)}
42
+ />
43
+ <Label htmlFor={field.key}>{field.placeholder ?? ''}</Label>
44
+ </div>
45
+ )
46
+ case 'select':
47
+ return (
48
+ <select
49
+ {...common}
50
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
51
+ value={typeof value === 'string' ? value : ''}
52
+ onChange={(evt) => onChange(evt.target.value)}
53
+ >
54
+ <option value="">—</option>
55
+ {(field.options ?? []).map((opt) => (
56
+ <option key={opt.value} value={opt.value}>
57
+ {opt.label}
58
+ </option>
59
+ ))}
60
+ </select>
61
+ )
62
+ case 'secret':
63
+ return (
64
+ <Input
65
+ {...common}
66
+ type="password"
67
+ value={typeof value === 'string' ? value : ''}
68
+ onChange={(evt) => onChange(evt.target.value)}
69
+ placeholder={field.placeholder}
70
+ />
71
+ )
72
+ case 'url':
73
+ case 'text':
74
+ default:
75
+ return (
76
+ <Input
77
+ {...common}
78
+ type={field.type === 'url' ? 'url' : 'text'}
79
+ value={typeof value === 'string' ? value : ''}
80
+ onChange={(evt) => onChange(evt.target.value)}
81
+ placeholder={field.placeholder}
82
+ />
83
+ )
84
+ }
85
+ }
@@ -15,7 +15,6 @@ import {
15
15
  import { Input } from '@open-mercato/ui/primitives/input'
16
16
  import { Label } from '@open-mercato/ui/primitives/label'
17
17
  import { Switch } from '@open-mercato/ui/primitives/switch'
18
- import { Textarea } from '@open-mercato/ui/primitives/textarea'
19
18
  import { CrudForm, type CrudCustomFieldRenderProps, type CrudField } from '@open-mercato/ui/backend/CrudForm'
20
19
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
21
20
  import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
@@ -25,9 +24,10 @@ import { useT } from '@open-mercato/shared/lib/i18n/context'
25
24
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
26
25
  import {
27
26
  listShippingProviders,
28
- type ProviderSettingField,
29
27
  type ShippingProvider,
30
28
  } from '../lib/providers'
29
+ import { isRecord } from '@open-mercato/shared/lib/utils'
30
+ import { renderProviderFieldInput } from './ProviderFieldInput'
31
31
 
32
32
  type ShippingMethodRow = {
33
33
  id: string
@@ -83,89 +83,6 @@ const DEFAULT_FORM: ShippingFormValues = {
83
83
  providerSettings: {},
84
84
  }
85
85
 
86
- function isRecord(value: unknown): value is Record<string, unknown> {
87
- return !!value && typeof value === 'object' && !Array.isArray(value)
88
- }
89
-
90
- function renderFieldInput(opts: {
91
- field: ProviderSettingField
92
- value: unknown
93
- onChange: (next: unknown) => void
94
- }) {
95
- const { field, value, onChange } = opts
96
- const common = { id: field.key, 'data-provider-setting': field.key }
97
- switch (field.type) {
98
- case 'textarea':
99
- return (
100
- <Textarea
101
- {...common}
102
- value={typeof value === 'string' ? value : ''}
103
- onChange={(evt) => onChange(evt.target.value)}
104
- placeholder={field.placeholder}
105
- />
106
- )
107
- case 'number':
108
- return (
109
- <Input
110
- {...common}
111
- type="number"
112
- value={typeof value === 'number' || typeof value === 'string' ? String(value) : ''}
113
- onChange={(evt) => onChange(evt.target.value === '' ? '' : Number(evt.target.value))}
114
- placeholder={field.placeholder}
115
- />
116
- )
117
- case 'boolean':
118
- return (
119
- <div className="flex items-center gap-2 py-1">
120
- <Switch
121
- id={field.key}
122
- checked={Boolean(value)}
123
- onCheckedChange={(checked) => onChange(checked)}
124
- />
125
- <Label htmlFor={field.key}>{field.placeholder ?? ''}</Label>
126
- </div>
127
- )
128
- case 'select':
129
- return (
130
- <select
131
- {...common}
132
- className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
133
- value={typeof value === 'string' ? value : ''}
134
- onChange={(evt) => onChange(evt.target.value)}
135
- >
136
- <option value="">—</option>
137
- {(field.options ?? []).map((opt) => (
138
- <option key={opt.value} value={opt.value}>
139
- {opt.label}
140
- </option>
141
- ))}
142
- </select>
143
- )
144
- case 'secret':
145
- return (
146
- <Input
147
- {...common}
148
- type="password"
149
- value={typeof value === 'string' ? value : ''}
150
- onChange={(evt) => onChange(evt.target.value)}
151
- placeholder={field.placeholder}
152
- />
153
- )
154
- case 'url':
155
- case 'text':
156
- default:
157
- return (
158
- <Input
159
- {...common}
160
- type={field.type === 'url' ? 'url' : 'text'}
161
- value={typeof value === 'string' ? value : ''}
162
- onChange={(evt) => onChange(evt.target.value)}
163
- placeholder={field.placeholder}
164
- />
165
- )
166
- }
167
- }
168
-
169
86
  type RateRule = {
170
87
  id?: string
171
88
  name?: string
@@ -395,7 +312,7 @@ function createShippingProviderSettingsRenderer(params: {
395
312
  {field.description ? (
396
313
  <p className="text-xs text-muted-foreground">{field.description}</p>
397
314
  ) : null}
398
- {renderFieldInput({
315
+ {renderProviderFieldInput({
399
316
  field,
400
317
  value: fieldValue,
401
318
  onChange: (next) => setValue({ ...settings, [field.key]: next }),