@open-mercato/core 0.4.7-develop-0a657b411f → 0.4.7-develop-e249d3e7d0

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 (278) hide show
  1. package/dist/generated/entities/carrier_shipment/index.js +37 -0
  2. package/dist/generated/entities/carrier_shipment/index.js.map +7 -0
  3. package/dist/generated/entities/gateway_transaction/index.js +47 -0
  4. package/dist/generated/entities/gateway_transaction/index.js.map +7 -0
  5. package/dist/generated/entities/webhook_processed_event/index.js +17 -0
  6. package/dist/generated/entities/webhook_processed_event/index.js.map +7 -0
  7. package/dist/generated/entities.ids.generated.js +10 -1
  8. package/dist/generated/entities.ids.generated.js.map +2 -2
  9. package/dist/generated/entity-fields-registry.js +6 -0
  10. package/dist/generated/entity-fields-registry.js.map +2 -2
  11. package/dist/modules/data_sync/api/runs/[id]/cancel.js +14 -5
  12. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  13. package/dist/modules/data_sync/backend/data-sync/page.meta.js +2 -2
  14. package/dist/modules/data_sync/backend/data-sync/page.meta.js.map +1 -1
  15. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +37 -12
  16. package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
  17. package/dist/modules/directory/api/get/tenants/lookup.js +1 -0
  18. package/dist/modules/directory/api/get/tenants/lookup.js.map +2 -2
  19. package/dist/modules/integrations/api/[id]/route.js +38 -11
  20. package/dist/modules/integrations/api/[id]/route.js.map +2 -2
  21. package/dist/modules/integrations/api/logs/route.js +52 -26
  22. package/dist/modules/integrations/api/logs/route.js.map +2 -2
  23. package/dist/modules/integrations/api/route.js +37 -7
  24. package/dist/modules/integrations/api/route.js.map +2 -2
  25. package/dist/modules/integrations/api/umes-read.js +121 -0
  26. package/dist/modules/integrations/api/umes-read.js.map +7 -0
  27. package/dist/modules/integrations/backend/integrations/[id]/page.js +715 -183
  28. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  29. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +30 -9
  30. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  31. package/dist/modules/integrations/backend/integrations/detail-page-widgets.js +46 -0
  32. package/dist/modules/integrations/backend/integrations/detail-page-widgets.js.map +7 -0
  33. package/dist/modules/integrations/backend/integrations/page.js +78 -62
  34. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  35. package/dist/modules/integrations/backend/integrations/page.meta.js +2 -2
  36. package/dist/modules/integrations/backend/integrations/page.meta.js.map +1 -1
  37. package/dist/modules/integrations/setup.js +2 -2
  38. package/dist/modules/integrations/setup.js.map +2 -2
  39. package/dist/modules/payment_gateways/acl.js +12 -0
  40. package/dist/modules/payment_gateways/acl.js.map +7 -0
  41. package/dist/modules/payment_gateways/api/cancel/route.js +55 -0
  42. package/dist/modules/payment_gateways/api/cancel/route.js.map +7 -0
  43. package/dist/modules/payment_gateways/api/capture/route.js +55 -0
  44. package/dist/modules/payment_gateways/api/capture/route.js.map +7 -0
  45. package/dist/modules/payment_gateways/api/interceptors.js +24 -0
  46. package/dist/modules/payment_gateways/api/interceptors.js.map +7 -0
  47. package/dist/modules/payment_gateways/api/openapi.js +5 -0
  48. package/dist/modules/payment_gateways/api/openapi.js.map +7 -0
  49. package/dist/modules/payment_gateways/api/refund/route.js +56 -0
  50. package/dist/modules/payment_gateways/api/refund/route.js.map +7 -0
  51. package/dist/modules/payment_gateways/api/sessions/route.js +74 -0
  52. package/dist/modules/payment_gateways/api/sessions/route.js.map +7 -0
  53. package/dist/modules/payment_gateways/api/status/route.js +66 -0
  54. package/dist/modules/payment_gateways/api/status/route.js.map +7 -0
  55. package/dist/modules/payment_gateways/api/transactions/[id]/route.js +118 -0
  56. package/dist/modules/payment_gateways/api/transactions/[id]/route.js.map +7 -0
  57. package/dist/modules/payment_gateways/api/transactions/route.js +113 -0
  58. package/dist/modules/payment_gateways/api/transactions/route.js.map +7 -0
  59. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +136 -0
  60. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +7 -0
  61. package/dist/modules/payment_gateways/backend/payment-gateways/page.js +496 -0
  62. package/dist/modules/payment_gateways/backend/payment-gateways/page.js.map +7 -0
  63. package/dist/modules/payment_gateways/backend/payment-gateways/page.meta.js +23 -0
  64. package/dist/modules/payment_gateways/backend/payment-gateways/page.meta.js.map +7 -0
  65. package/dist/modules/payment_gateways/data/enrichers.js +5 -0
  66. package/dist/modules/payment_gateways/data/enrichers.js.map +7 -0
  67. package/dist/modules/payment_gateways/data/entities.js +131 -0
  68. package/dist/modules/payment_gateways/data/entities.js.map +7 -0
  69. package/dist/modules/payment_gateways/data/validators.js +57 -0
  70. package/dist/modules/payment_gateways/data/validators.js.map +7 -0
  71. package/dist/modules/payment_gateways/di.js +16 -0
  72. package/dist/modules/payment_gateways/di.js.map +7 -0
  73. package/dist/modules/payment_gateways/events.js +21 -0
  74. package/dist/modules/payment_gateways/events.js.map +7 -0
  75. package/dist/modules/payment_gateways/i18n/en.js +6 -0
  76. package/dist/modules/payment_gateways/i18n/en.js.map +7 -0
  77. package/dist/modules/payment_gateways/i18n/pl.js +6 -0
  78. package/dist/modules/payment_gateways/i18n/pl.js.map +7 -0
  79. package/dist/modules/payment_gateways/index.js +9 -0
  80. package/dist/modules/payment_gateways/index.js.map +7 -0
  81. package/dist/modules/payment_gateways/lib/gateway-service.js +378 -0
  82. package/dist/modules/payment_gateways/lib/gateway-service.js.map +7 -0
  83. package/dist/modules/payment_gateways/lib/queue.js +17 -0
  84. package/dist/modules/payment_gateways/lib/queue.js.map +7 -0
  85. package/dist/modules/payment_gateways/lib/status-machine.js +29 -0
  86. package/dist/modules/payment_gateways/lib/status-machine.js.map +7 -0
  87. package/dist/modules/payment_gateways/lib/webhook-processor.js +88 -0
  88. package/dist/modules/payment_gateways/lib/webhook-processor.js.map +7 -0
  89. package/dist/modules/payment_gateways/lib/webhook-utils.js +42 -0
  90. package/dist/modules/payment_gateways/lib/webhook-utils.js.map +7 -0
  91. package/dist/modules/payment_gateways/migrations/Migration20260305122155.js +19 -0
  92. package/dist/modules/payment_gateways/migrations/Migration20260305122155.js.map +7 -0
  93. package/dist/modules/payment_gateways/setup.js +13 -0
  94. package/dist/modules/payment_gateways/setup.js.map +7 -0
  95. package/dist/modules/payment_gateways/widgets/injection-table.js +7 -0
  96. package/dist/modules/payment_gateways/widgets/injection-table.js.map +7 -0
  97. package/dist/modules/payment_gateways/workers/status-poller.js +44 -0
  98. package/dist/modules/payment_gateways/workers/status-poller.js.map +7 -0
  99. package/dist/modules/payment_gateways/workers/webhook-processor.js +20 -0
  100. package/dist/modules/payment_gateways/workers/webhook-processor.js.map +7 -0
  101. package/dist/modules/sales/data/enrichers.js +72 -0
  102. package/dist/modules/sales/data/enrichers.js.map +7 -0
  103. package/dist/modules/sales/lib/makeSalesLineRoute.js +3 -0
  104. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
  105. package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js +29 -0
  106. package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js.map +7 -0
  107. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +23 -0
  108. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +7 -0
  109. package/dist/modules/sales/widgets/injection-table.js +13 -1
  110. package/dist/modules/sales/widgets/injection-table.js.map +2 -2
  111. package/dist/modules/shipping_carriers/acl.js +10 -0
  112. package/dist/modules/shipping_carriers/acl.js.map +7 -0
  113. package/dist/modules/shipping_carriers/api/cancel/route.js +55 -0
  114. package/dist/modules/shipping_carriers/api/cancel/route.js.map +7 -0
  115. package/dist/modules/shipping_carriers/api/interceptors.js +21 -0
  116. package/dist/modules/shipping_carriers/api/interceptors.js.map +7 -0
  117. package/dist/modules/shipping_carriers/api/openapi.js +5 -0
  118. package/dist/modules/shipping_carriers/api/openapi.js.map +7 -0
  119. package/dist/modules/shipping_carriers/api/rates/route.js +55 -0
  120. package/dist/modules/shipping_carriers/api/rates/route.js.map +7 -0
  121. package/dist/modules/shipping_carriers/api/shipments/route.js +61 -0
  122. package/dist/modules/shipping_carriers/api/shipments/route.js.map +7 -0
  123. package/dist/modules/shipping_carriers/api/tracking/route.js +58 -0
  124. package/dist/modules/shipping_carriers/api/tracking/route.js.map +7 -0
  125. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +119 -0
  126. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +7 -0
  127. package/dist/modules/shipping_carriers/data/enrichers.js +82 -0
  128. package/dist/modules/shipping_carriers/data/enrichers.js.map +7 -0
  129. package/dist/modules/shipping_carriers/data/entities.js +80 -0
  130. package/dist/modules/shipping_carriers/data/entities.js.map +7 -0
  131. package/dist/modules/shipping_carriers/data/validators.js +49 -0
  132. package/dist/modules/shipping_carriers/data/validators.js.map +7 -0
  133. package/dist/modules/shipping_carriers/di.js +15 -0
  134. package/dist/modules/shipping_carriers/di.js.map +7 -0
  135. package/dist/modules/shipping_carriers/events.js +19 -0
  136. package/dist/modules/shipping_carriers/events.js.map +7 -0
  137. package/dist/modules/shipping_carriers/i18n/en.js +11 -0
  138. package/dist/modules/shipping_carriers/i18n/en.js.map +7 -0
  139. package/dist/modules/shipping_carriers/i18n/pl.js +11 -0
  140. package/dist/modules/shipping_carriers/i18n/pl.js.map +7 -0
  141. package/dist/modules/shipping_carriers/index.js +9 -0
  142. package/dist/modules/shipping_carriers/index.js.map +7 -0
  143. package/dist/modules/shipping_carriers/lib/adapter-registry.js +29 -0
  144. package/dist/modules/shipping_carriers/lib/adapter-registry.js.map +7 -0
  145. package/dist/modules/shipping_carriers/lib/adapter.js +1 -0
  146. package/dist/modules/shipping_carriers/lib/adapter.js.map +7 -0
  147. package/dist/modules/shipping_carriers/lib/queue.js +17 -0
  148. package/dist/modules/shipping_carriers/lib/queue.js.map +7 -0
  149. package/dist/modules/shipping_carriers/lib/shipping-service.js +155 -0
  150. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +7 -0
  151. package/dist/modules/shipping_carriers/lib/status-sync.js +37 -0
  152. package/dist/modules/shipping_carriers/lib/status-sync.js.map +7 -0
  153. package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js +16 -0
  154. package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js.map +7 -0
  155. package/dist/modules/shipping_carriers/setup.js +13 -0
  156. package/dist/modules/shipping_carriers/setup.js.map +7 -0
  157. package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js +25 -0
  158. package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js.map +7 -0
  159. package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js +23 -0
  160. package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js.map +7 -0
  161. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +40 -0
  162. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +7 -0
  163. package/dist/modules/shipping_carriers/widgets/injection-table.js +24 -0
  164. package/dist/modules/shipping_carriers/widgets/injection-table.js.map +7 -0
  165. package/dist/modules/shipping_carriers/workers/status-poller.js +21 -0
  166. package/dist/modules/shipping_carriers/workers/status-poller.js.map +7 -0
  167. package/dist/modules/shipping_carriers/workers/webhook-processor.js +54 -0
  168. package/dist/modules/shipping_carriers/workers/webhook-processor.js.map +7 -0
  169. package/dist/modules/translations/api/get/locales.js +1 -0
  170. package/dist/modules/translations/api/get/locales.js.map +2 -2
  171. package/dist/modules/translations/api/put/locales.js +1 -0
  172. package/dist/modules/translations/api/put/locales.js.map +2 -2
  173. package/generated/entities/carrier_shipment/index.ts +17 -0
  174. package/generated/entities/gateway_transaction/index.ts +22 -0
  175. package/generated/entities/webhook_processed_event/index.ts +7 -0
  176. package/generated/entities.ids.generated.ts +10 -1
  177. package/generated/entity-fields-registry.ts +6 -0
  178. package/jest.config.cjs +1 -0
  179. package/package.json +5 -2
  180. package/src/modules/auth/i18n/de.json +1 -0
  181. package/src/modules/auth/i18n/en.json +1 -0
  182. package/src/modules/auth/i18n/es.json +1 -0
  183. package/src/modules/auth/i18n/pl.json +1 -0
  184. package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -5
  185. package/src/modules/data_sync/backend/data-sync/page.meta.ts +2 -2
  186. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +50 -12
  187. package/src/modules/directory/api/get/tenants/lookup.ts +1 -0
  188. package/src/modules/integrations/AGENTS.md +31 -0
  189. package/src/modules/integrations/api/[id]/route.ts +38 -11
  190. package/src/modules/integrations/api/logs/route.ts +53 -27
  191. package/src/modules/integrations/api/route.ts +31 -1
  192. package/src/modules/integrations/api/umes-read.ts +177 -0
  193. package/src/modules/integrations/backend/integrations/[id]/page.tsx +902 -202
  194. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +43 -9
  195. package/src/modules/integrations/backend/integrations/detail-page-widgets.ts +74 -0
  196. package/src/modules/integrations/backend/integrations/page.meta.ts +2 -2
  197. package/src/modules/integrations/backend/integrations/page.tsx +65 -54
  198. package/src/modules/integrations/i18n/de.json +15 -0
  199. package/src/modules/integrations/i18n/en.json +15 -0
  200. package/src/modules/integrations/i18n/es.json +15 -0
  201. package/src/modules/integrations/i18n/pl.json +15 -0
  202. package/src/modules/integrations/setup.ts +2 -2
  203. package/src/modules/payment_gateways/acl.ts +8 -0
  204. package/src/modules/payment_gateways/api/cancel/route.ts +56 -0
  205. package/src/modules/payment_gateways/api/capture/route.ts +56 -0
  206. package/src/modules/payment_gateways/api/interceptors.ts +22 -0
  207. package/src/modules/payment_gateways/api/openapi.ts +1 -0
  208. package/src/modules/payment_gateways/api/refund/route.ts +57 -0
  209. package/src/modules/payment_gateways/api/sessions/route.ts +76 -0
  210. package/src/modules/payment_gateways/api/status/route.ts +69 -0
  211. package/src/modules/payment_gateways/api/transactions/[id]/route.ts +123 -0
  212. package/src/modules/payment_gateways/api/transactions/route.ts +120 -0
  213. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +161 -0
  214. package/src/modules/payment_gateways/backend/payment-gateways/page.meta.ts +19 -0
  215. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +660 -0
  216. package/src/modules/payment_gateways/data/enrichers.ts +8 -0
  217. package/src/modules/payment_gateways/data/entities.ts +106 -0
  218. package/src/modules/payment_gateways/data/validators.ts +67 -0
  219. package/src/modules/payment_gateways/di.ts +26 -0
  220. package/src/modules/payment_gateways/events.ts +17 -0
  221. package/src/modules/payment_gateways/i18n/de.json +77 -0
  222. package/src/modules/payment_gateways/i18n/en.json +77 -0
  223. package/src/modules/payment_gateways/i18n/en.ts +4 -0
  224. package/src/modules/payment_gateways/i18n/es.json +77 -0
  225. package/src/modules/payment_gateways/i18n/pl.json +77 -0
  226. package/src/modules/payment_gateways/i18n/pl.ts +4 -0
  227. package/src/modules/payment_gateways/index.ts +5 -0
  228. package/src/modules/payment_gateways/lib/gateway-service.ts +486 -0
  229. package/src/modules/payment_gateways/lib/queue.ts +19 -0
  230. package/src/modules/payment_gateways/lib/status-machine.ts +28 -0
  231. package/src/modules/payment_gateways/lib/webhook-processor.ts +133 -0
  232. package/src/modules/payment_gateways/lib/webhook-utils.ts +52 -0
  233. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +373 -0
  234. package/src/modules/payment_gateways/migrations/Migration20260305122155.ts +20 -0
  235. package/src/modules/payment_gateways/setup.ts +11 -0
  236. package/src/modules/payment_gateways/widgets/injection-table.ts +9 -0
  237. package/src/modules/payment_gateways/workers/status-poller.ts +58 -0
  238. package/src/modules/payment_gateways/workers/webhook-processor.ts +30 -0
  239. package/src/modules/sales/data/enrichers.ts +120 -0
  240. package/src/modules/sales/lib/makeSalesLineRoute.ts +3 -0
  241. package/src/modules/sales/widgets/injection/payment-gateway-config-field/widget.ts +28 -0
  242. package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +22 -0
  243. package/src/modules/sales/widgets/injection-table.ts +12 -0
  244. package/src/modules/shipping_carriers/acl.ts +6 -0
  245. package/src/modules/shipping_carriers/api/cancel/route.ts +53 -0
  246. package/src/modules/shipping_carriers/api/interceptors.ts +19 -0
  247. package/src/modules/shipping_carriers/api/openapi.ts +1 -0
  248. package/src/modules/shipping_carriers/api/rates/route.ts +53 -0
  249. package/src/modules/shipping_carriers/api/shipments/route.ts +59 -0
  250. package/src/modules/shipping_carriers/api/tracking/route.ts +56 -0
  251. package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +134 -0
  252. package/src/modules/shipping_carriers/data/enrichers.ts +89 -0
  253. package/src/modules/shipping_carriers/data/entities.ts +60 -0
  254. package/src/modules/shipping_carriers/data/validators.ts +48 -0
  255. package/src/modules/shipping_carriers/di.ts +20 -0
  256. package/src/modules/shipping_carriers/events.ts +16 -0
  257. package/src/modules/shipping_carriers/i18n/de.json +7 -0
  258. package/src/modules/shipping_carriers/i18n/en.json +7 -0
  259. package/src/modules/shipping_carriers/i18n/en.ts +7 -0
  260. package/src/modules/shipping_carriers/i18n/es.json +7 -0
  261. package/src/modules/shipping_carriers/i18n/pl.json +7 -0
  262. package/src/modules/shipping_carriers/i18n/pl.ts +7 -0
  263. package/src/modules/shipping_carriers/index.ts +5 -0
  264. package/src/modules/shipping_carriers/lib/adapter-registry.ts +33 -0
  265. package/src/modules/shipping_carriers/lib/adapter.ts +93 -0
  266. package/src/modules/shipping_carriers/lib/queue.ts +19 -0
  267. package/src/modules/shipping_carriers/lib/shipping-service.ts +204 -0
  268. package/src/modules/shipping_carriers/lib/status-sync.ts +38 -0
  269. package/src/modules/shipping_carriers/migrations/Migration20260305170000.ts +14 -0
  270. package/src/modules/shipping_carriers/setup.ts +11 -0
  271. package/src/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.ts +24 -0
  272. package/src/modules/shipping_carriers/widgets/injection/tracking-column/widget.ts +22 -0
  273. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +44 -0
  274. package/src/modules/shipping_carriers/widgets/injection-table.ts +22 -0
  275. package/src/modules/shipping_carriers/workers/status-poller.ts +33 -0
  276. package/src/modules/shipping_carriers/workers/webhook-processor.ts +79 -0
  277. package/src/modules/translations/api/get/locales.ts +1 -0
  278. package/src/modules/translations/api/put/locales.ts +1 -0
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
  import * as React from 'react'
3
3
  import Link from 'next/link'
4
- import { useParams } from 'next/navigation'
4
+ import { usePathname } from 'next/navigation'
5
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
6
  import { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'
7
7
  import { Badge } from '@open-mercato/ui/primitives/badge'
@@ -50,9 +50,27 @@ type BundleDetail = {
50
50
  hasCredentials: boolean
51
51
  }
52
52
 
53
- export default function BundleConfigPage() {
54
- const params = useParams<{ id: string }>()
55
- const bundleId = params.id
53
+ type BundleConfigPageProps = {
54
+ params?: {
55
+ id?: string | string[]
56
+ }
57
+ }
58
+
59
+ function resolveRouteId(value: string | string[] | undefined): string | undefined {
60
+ if (Array.isArray(value)) return value[0]
61
+ return value
62
+ }
63
+
64
+ function resolvePathnameId(pathname: string): string | undefined {
65
+ const parts = pathname.split('/').filter(Boolean)
66
+ const bundleId = parts.at(-1)
67
+ if (!bundleId || bundleId === 'bundle' || bundleId === 'integrations') return undefined
68
+ return decodeURIComponent(bundleId)
69
+ }
70
+
71
+ export default function BundleConfigPage({ params }: BundleConfigPageProps) {
72
+ const pathname = usePathname()
73
+ const bundleId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname)
56
74
  const t = useT()
57
75
 
58
76
  const [detail, setDetail] = React.useState<BundleDetail | null>(null)
@@ -62,11 +80,25 @@ export default function BundleConfigPage() {
62
80
  const [isSavingCreds, setIsSavingCreds] = React.useState(false)
63
81
  const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())
64
82
 
83
+ const resolveCurrentBundleId = React.useCallback(() => {
84
+ return bundleId ?? (
85
+ typeof window !== 'undefined'
86
+ ? resolvePathnameId(window.location.pathname)
87
+ : undefined
88
+ )
89
+ }, [bundleId])
90
+
65
91
  const load = React.useCallback(async () => {
92
+ const currentBundleId = resolveCurrentBundleId()
93
+ if (!currentBundleId) {
94
+ setError(t('integrations.detail.loadError'))
95
+ setIsLoading(false)
96
+ return
97
+ }
66
98
  setIsLoading(true)
67
99
  setError(null)
68
100
  const call = await apiCall<BundleDetail>(
69
- `/api/integrations/${encodeURIComponent(bundleId)}`,
101
+ `/api/integrations/${encodeURIComponent(currentBundleId)}`,
70
102
  undefined,
71
103
  { fallback: null },
72
104
  )
@@ -78,7 +110,7 @@ export default function BundleConfigPage() {
78
110
  setDetail(call.result)
79
111
 
80
112
  const credCall = await apiCall<{ credentials: Record<string, unknown> }>(
81
- `/api/integrations/${encodeURIComponent(bundleId)}/credentials`,
113
+ `/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`,
82
114
  undefined,
83
115
  { fallback: null },
84
116
  )
@@ -86,13 +118,15 @@ export default function BundleConfigPage() {
86
118
  setCredValues(credCall.result.credentials)
87
119
  }
88
120
  setIsLoading(false)
89
- }, [bundleId, t])
121
+ }, [resolveCurrentBundleId, t])
90
122
 
91
123
  React.useEffect(() => { void load() }, [load])
92
124
 
93
125
  const handleSaveCredentials = React.useCallback(async () => {
126
+ const currentBundleId = resolveCurrentBundleId()
127
+ if (!currentBundleId) return
94
128
  setIsSavingCreds(true)
95
- const call = await apiCall(`/api/integrations/${encodeURIComponent(bundleId)}/credentials`, {
129
+ const call = await apiCall(`/api/integrations/${encodeURIComponent(currentBundleId)}/credentials`, {
96
130
  method: 'PUT',
97
131
  headers: { 'Content-Type': 'application/json' },
98
132
  body: JSON.stringify({ credentials: credValues }),
@@ -103,7 +137,7 @@ export default function BundleConfigPage() {
103
137
  flash(t('integrations.detail.credentials.saveError'), 'error')
104
138
  }
105
139
  setIsSavingCreds(false)
106
- }, [bundleId, credValues, t])
140
+ }, [resolveCurrentBundleId, credValues, t])
107
141
 
108
142
  const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {
109
143
  setTogglingIds((prev) => new Set(prev).add(integrationId))
@@ -0,0 +1,74 @@
1
+ import { type IntegrationDefinition, LEGACY_INTEGRATION_DETAIL_TABS_SPOT_ID } from '@open-mercato/shared/modules/integrations/types'
2
+ import type { LoadedInjectionSpotWidget } from '@open-mercato/ui/backend/injection/InjectionSpot'
3
+
4
+ type IntegrationDetailPlacementKind = 'tab' | 'group' | 'stack'
5
+
6
+ export type IntegrationDetailInjectedTab = {
7
+ id: string
8
+ label: string
9
+ priority: number
10
+ widgets: LoadedInjectionSpotWidget[]
11
+ }
12
+
13
+ export function resolveIntegrationDetailWidgetSpotId(
14
+ integration: Pick<IntegrationDefinition, 'detailPage'> | null | undefined,
15
+ legacySpotId: typeof LEGACY_INTEGRATION_DETAIL_TABS_SPOT_ID,
16
+ ): string {
17
+ const widgetSpotId = integration?.detailPage?.widgetSpotId?.trim()
18
+ return widgetSpotId && widgetSpotId.length > 0 ? widgetSpotId : legacySpotId
19
+ }
20
+
21
+ export function resolveRequestedIntegrationDetailTab(
22
+ value: string | null | undefined,
23
+ hasVersions: boolean,
24
+ customTabIds: readonly string[],
25
+ ): string {
26
+ if (value && customTabIds.includes(value)) return value
27
+ if (value === 'health' || value === 'logs') return value
28
+ if (value === 'version' && hasVersions) return 'version'
29
+ return 'credentials'
30
+ }
31
+
32
+ export function filterIntegrationDetailWidgetsByKind(
33
+ widgets: readonly LoadedInjectionSpotWidget[],
34
+ kind: IntegrationDetailPlacementKind,
35
+ ): LoadedInjectionSpotWidget[] {
36
+ return widgets
37
+ .filter((widget) => (widget.placement?.kind ?? 'stack') === kind)
38
+ .sort((left, right) => {
39
+ const leftPriority = typeof left.placement?.priority === 'number' ? left.placement.priority : 0
40
+ const rightPriority = typeof right.placement?.priority === 'number' ? right.placement.priority : 0
41
+ return rightPriority - leftPriority
42
+ })
43
+ }
44
+
45
+ export function buildIntegrationDetailInjectedTabs(
46
+ widgets: readonly LoadedInjectionSpotWidget[],
47
+ resolveLabel: (widget: LoadedInjectionSpotWidget) => string,
48
+ ): IntegrationDetailInjectedTab[] {
49
+ const groupedTabs = new Map<string, IntegrationDetailInjectedTab>()
50
+
51
+ for (const widget of widgets) {
52
+ if ((widget.placement?.kind ?? 'stack') !== 'tab') continue
53
+
54
+ const id = widget.placement?.groupId ?? widget.widgetId
55
+ const label = resolveLabel(widget)
56
+ const priority = typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0
57
+ const existing = groupedTabs.get(id)
58
+
59
+ if (existing) {
60
+ existing.widgets.push(widget)
61
+ existing.priority = Math.max(existing.priority, priority)
62
+ continue
63
+ }
64
+
65
+ groupedTabs.set(id, {
66
+ id,
67
+ label,
68
+ priority,
69
+ widgets: [widget],
70
+ })
71
+ }
72
+
73
+ return Array.from(groupedTabs.values()).sort((left, right) => right.priority - left.priority)
74
+ }
@@ -9,8 +9,8 @@ export const metadata = {
9
9
  requireFeatures: ['integrations.view'],
10
10
  pageTitle: 'Integrations',
11
11
  pageTitleKey: 'integrations.nav.title',
12
- pageGroup: 'Settings',
13
- pageGroupKey: 'settings.sections.general',
12
+ pageGroup: 'External systems',
13
+ pageGroupKey: 'backend.nav.externalSystems',
14
14
  pageOrder: 50,
15
15
  icon: puzzleIcon,
16
16
  pageContext: 'settings' as const,
@@ -3,7 +3,6 @@ import * as React from 'react'
3
3
  import Link from 'next/link'
4
4
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
5
5
  import { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'
6
- import { Badge } from '@open-mercato/ui/primitives/badge'
7
6
  import { Button } from '@open-mercato/ui/primitives/button'
8
7
  import { Switch } from '@open-mercato/ui/primitives/switch'
9
8
  import { Input } from '@open-mercato/ui/primitives/input'
@@ -13,7 +12,7 @@ import { flash } from '@open-mercato/ui/backend/FlashMessages'
13
12
  import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
14
13
  import { useT } from '@open-mercato/shared/lib/i18n/context'
15
14
  import { FilterBar, type FilterValues } from '@open-mercato/ui/backend/FilterBar'
16
- import { Bell, CreditCard, HardDrive, LayoutGrid, MessageSquare, RefreshCw, Truck, Webhook } from 'lucide-react'
15
+ import { Bell, Cog, CreditCard, HardDrive, LayoutGrid, MessageSquare, RefreshCw, Search, Truck, Webhook } from 'lucide-react'
17
16
  import {
18
17
  buildIntegrationMarketplaceFilterDefs,
19
18
  getIntegrationMarketplaceCategory,
@@ -28,6 +27,9 @@ type IntegrationItem = {
28
27
  category?: string
29
28
  icon?: string
30
29
  bundleId?: string
30
+ author?: string
31
+ company?: string
32
+ version?: string
31
33
  isEnabled: boolean
32
34
  hasCredentials: boolean
33
35
  }
@@ -57,11 +59,6 @@ const CATEGORY_ICONS: Record<string, React.ElementType> = {
57
59
  webhook: Webhook,
58
60
  }
59
61
 
60
- function categoryBadgeVariant(category: string | undefined): 'default' | 'secondary' | 'outline' {
61
- if (!category) return 'outline'
62
- return 'secondary'
63
- }
64
-
65
62
  export default function IntegrationsMarketplacePage() {
66
63
  const [data, setData] = React.useState<ListResponse | null>(null)
67
64
  const [isLoading, setIsLoading] = React.useState(true)
@@ -144,6 +141,13 @@ export default function IntegrationsMarketplacePage() {
144
141
  return { bundles, standalone }
145
142
  }, [data, search, selectedCategory])
146
143
 
144
+ const renderCategoryIcon = React.useCallback((category: string | undefined, className: string) => {
145
+ if (!category) return null
146
+ const Icon = CATEGORY_ICONS[category]
147
+ if (!Icon) return null
148
+ return <Icon className={className} />
149
+ }, [])
150
+
147
151
  if (isLoading) {
148
152
  return (
149
153
  <Page>
@@ -160,13 +164,22 @@ export default function IntegrationsMarketplacePage() {
160
164
  <Page>
161
165
  <PageBody className="space-y-6">
162
166
  <section className="space-y-6 rounded-lg border bg-background p-6">
163
- <header className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
164
- <div className="space-y-1">
167
+ <header className="flex items-center justify-between gap-4">
168
+ <div className="space-y-0.5">
165
169
  <h2 className="text-lg font-semibold">{t('integrations.marketplace.title')}</h2>
166
170
  <p className="text-sm text-muted-foreground">
167
171
  {t('integrations.marketplace.description')}
168
172
  </p>
169
173
  </div>
174
+ <div className="relative w-64 shrink-0 hidden lg:block">
175
+ <Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground pointer-events-none" />
176
+ <Input
177
+ placeholder={t('integrations.marketplace.search')}
178
+ value={search}
179
+ onChange={(e) => setSearch(e.target.value)}
180
+ className="pl-8"
181
+ />
182
+ </div>
170
183
  </header>
171
184
 
172
185
  <div className="lg:hidden">
@@ -182,32 +195,22 @@ export default function IntegrationsMarketplacePage() {
182
195
  />
183
196
  </div>
184
197
 
185
- <div className="hidden lg:flex lg:flex-col gap-4">
186
- <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
187
- <Input
188
- placeholder={t('integrations.marketplace.search')}
189
- value={search}
190
- onChange={(e) => setSearch(e.target.value)}
191
- className="max-w-sm"
192
- />
193
- <div className="flex flex-wrap gap-1.5">
194
- {INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => {
195
- const Icon = CATEGORY_ICONS[category]
196
- return (
197
- <Button
198
- key={category}
199
- type="button"
200
- variant={selectedCategory === category ? 'default' : 'outline'}
201
- size="sm"
202
- onClick={() => setFilterValues(normalizeIntegrationMarketplaceFilterValues({ category }))}
203
- >
204
- {Icon ? <Icon className="mr-1.5 h-3.5 w-3.5" /> : null}
205
- {t(`integrations.marketplace.categories.${category}`)}
206
- </Button>
207
- )
208
- })}
209
- </div>
210
- </div>
198
+ <div className="hidden lg:flex flex-wrap gap-1.5">
199
+ {INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => {
200
+ const Icon = CATEGORY_ICONS[category]
201
+ return (
202
+ <Button
203
+ key={category}
204
+ type="button"
205
+ variant={selectedCategory === category ? 'default' : 'outline'}
206
+ size="sm"
207
+ onClick={() => setFilterValues(normalizeIntegrationMarketplaceFilterValues({ category }))}
208
+ >
209
+ {Icon ? <Icon className="mr-1.5 h-3.5 w-3.5" /> : null}
210
+ {t(`integrations.marketplace.categories.${category}`)}
211
+ </Button>
212
+ )
213
+ })}
211
214
  </div>
212
215
 
213
216
  {filteredItems.bundles.map((bundle) => (
@@ -225,6 +228,7 @@ export default function IntegrationsMarketplacePage() {
225
228
  </div>
226
229
  <Button asChild variant="outline" size="sm">
227
230
  <Link href={`/backend/integrations/bundle/${encodeURIComponent(bundle.id)}`}>
231
+ <Cog className="mr-1.5 h-4 w-4" />
228
232
  {t('integrations.marketplace.configure')}
229
233
  </Link>
230
234
  </Button>
@@ -237,18 +241,21 @@ export default function IntegrationsMarketplacePage() {
237
241
  key={item.id}
238
242
  className="flex items-center justify-between rounded-lg border p-3"
239
243
  >
240
- <div className="min-w-0">
241
- <Link
242
- href={`/backend/integrations/${encodeURIComponent(item.id)}`}
243
- className="text-sm font-medium hover:underline"
244
- >
245
- {item.title}
246
- </Link>
247
- {item.category && (
248
- <Badge variant={categoryBadgeVariant(item.category)} className="ml-2 text-xs">
249
- {item.category}
250
- </Badge>
251
- )}
244
+ <div className="min-w-0 space-y-1">
245
+ <div className="flex items-center gap-2">
246
+ {renderCategoryIcon(item.category, 'h-4 w-4 text-muted-foreground')}
247
+ <Link
248
+ href={`/backend/integrations/${encodeURIComponent(item.id)}`}
249
+ className="truncate text-sm font-medium hover:underline"
250
+ >
251
+ {item.title}
252
+ </Link>
253
+ </div>
254
+ {(item.company || item.author || item.version) ? (
255
+ <p className="text-xs text-muted-foreground">
256
+ {[item.company || item.author, item.version ? `v${item.version}` : null].filter(Boolean).join(' · ')}
257
+ </p>
258
+ ) : null}
252
259
  </div>
253
260
  <Switch
254
261
  checked={item.isEnabled}
@@ -268,27 +275,31 @@ export default function IntegrationsMarketplacePage() {
268
275
  <Card key={item.id} className="flex flex-col">
269
276
  <CardHeader>
270
277
  <div className="flex items-center justify-between">
271
- <CardTitle className="text-base">{item.title}</CardTitle>
278
+ <div className="flex items-center gap-2">
279
+ {renderCategoryIcon(item.category, 'h-4 w-4 text-muted-foreground')}
280
+ <CardTitle className="text-base">{item.title}</CardTitle>
281
+ </div>
272
282
  <Switch
273
283
  checked={item.isEnabled}
274
284
  disabled={togglingIds.has(item.id)}
275
285
  onCheckedChange={(checked) => void handleToggle(item.id, checked)}
276
286
  />
277
287
  </div>
278
- {item.category && (
279
- <Badge variant={categoryBadgeVariant(item.category)} className="w-fit text-xs">
280
- {item.category}
281
- </Badge>
282
- )}
283
288
  </CardHeader>
284
- <CardContent className="flex-1">
289
+ <CardContent className="flex-1 space-y-2">
285
290
  {item.description && (
286
291
  <p className="text-muted-foreground text-sm">{item.description}</p>
287
292
  )}
293
+ {(item.company || item.author || item.version) && (
294
+ <p className="text-muted-foreground text-xs">
295
+ {[item.company || item.author, item.version ? `v${item.version}` : null].filter(Boolean).join(' · ')}
296
+ </p>
297
+ )}
288
298
  </CardContent>
289
299
  <div className="px-6 pb-4">
290
300
  <Button asChild variant="outline" size="sm" className="w-full">
291
301
  <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>
302
+ <Cog className="mr-1.5 h-4 w-4" />
292
303
  {t('integrations.marketplace.configure')}
293
304
  </Link>
294
305
  </Button>
@@ -85,28 +85,43 @@
85
85
  "integrations.detail.credentials.save": "Zugangsdaten speichern",
86
86
  "integrations.detail.credentials.saveError": "Zugangsdaten konnten nicht gespeichert werden",
87
87
  "integrations.detail.credentials.saved": "Zugangsdaten gespeichert",
88
+ "integrations.detail.credentials.validation.boolean": "Select a valid value.",
89
+ "integrations.detail.credentials.validation.option": "Select one of the available options.",
90
+ "integrations.detail.credentials.validation.required": "{field} is required.",
91
+ "integrations.detail.credentials.validation.text": "Enter a valid value.",
92
+ "integrations.detail.credentials.validation.tooLong": "Value is too long.",
88
93
  "integrations.detail.disable": "Deaktivieren",
89
94
  "integrations.detail.enable": "Aktivieren",
90
95
  "integrations.detail.health.check": "Prüfung ausführen",
91
96
  "integrations.detail.health.checkError": "Statusprüfung fehlgeschlagen",
92
97
  "integrations.detail.health.checking": "Prüfe…",
93
98
  "integrations.detail.health.degraded": "Beeinträchtigt",
99
+ "integrations.detail.health.details": "Details",
94
100
  "integrations.detail.health.healthy": "Gesund",
95
101
  "integrations.detail.health.lastChecked": "Zuletzt geprüft: {date}",
102
+ "integrations.detail.health.lastResult": "Last result",
96
103
  "integrations.detail.health.neverChecked": "Nie geprüft",
97
104
  "integrations.detail.health.title": "Gesundheitsstatus",
98
105
  "integrations.detail.health.unhealthy": "Nicht gesund",
99
106
  "integrations.detail.health.unknown": "Unbekannt",
107
+ "integrations.detail.hub.label": "Hub",
100
108
  "integrations.detail.loadError": "Integration konnte nicht geladen werden",
101
109
  "integrations.detail.logs.columns.level": "Stufe",
102
110
  "integrations.detail.logs.columns.message": "Nachricht",
103
111
  "integrations.detail.logs.columns.time": "Zeit",
112
+ "integrations.detail.logs.details.fields": "Fields",
113
+ "integrations.detail.logs.details.noPayload": "No structured payload was stored for this log entry.",
114
+ "integrations.detail.logs.details.payload": "Payload",
115
+ "integrations.detail.logs.details.summary": "Summary",
104
116
  "integrations.detail.logs.empty": "Noch keine Protokolleinträge",
105
117
  "integrations.detail.logs.level.all": "Alle Stufen",
106
118
  "integrations.detail.logs.level.error": "Fehler",
107
119
  "integrations.detail.logs.level.info": "Info",
108
120
  "integrations.detail.logs.level.warn": "Warnung",
109
121
  "integrations.detail.logs.title": "Betriebsprotokolle",
122
+ "integrations.detail.state.disabled": "Disabled",
123
+ "integrations.detail.state.enabled": "Enabled",
124
+ "integrations.detail.state.label": "State",
110
125
  "integrations.detail.stateError": "Status konnte nicht aktualisiert werden",
111
126
  "integrations.detail.stateUpdated": "Integrationsstatus aktualisiert",
112
127
  "integrations.detail.tabs.credentials": "Zugangsdaten",
@@ -85,28 +85,43 @@
85
85
  "integrations.detail.credentials.save": "Save Credentials",
86
86
  "integrations.detail.credentials.saveError": "Failed to save credentials",
87
87
  "integrations.detail.credentials.saved": "Credentials saved",
88
+ "integrations.detail.credentials.validation.boolean": "Select a valid value.",
89
+ "integrations.detail.credentials.validation.option": "Select one of the available options.",
90
+ "integrations.detail.credentials.validation.required": "{field} is required.",
91
+ "integrations.detail.credentials.validation.text": "Enter a valid value.",
92
+ "integrations.detail.credentials.validation.tooLong": "Value is too long.",
88
93
  "integrations.detail.disable": "Disable",
89
94
  "integrations.detail.enable": "Enable",
90
95
  "integrations.detail.health.check": "Run Check",
91
96
  "integrations.detail.health.checkError": "Health check failed",
92
97
  "integrations.detail.health.checking": "Checking…",
93
98
  "integrations.detail.health.degraded": "Degraded",
99
+ "integrations.detail.health.details": "Details",
94
100
  "integrations.detail.health.healthy": "Healthy",
95
101
  "integrations.detail.health.lastChecked": "Last checked: {date}",
102
+ "integrations.detail.health.lastResult": "Last result",
96
103
  "integrations.detail.health.neverChecked": "Never checked",
97
104
  "integrations.detail.health.title": "Health Status",
98
105
  "integrations.detail.health.unhealthy": "Unhealthy",
99
106
  "integrations.detail.health.unknown": "Unknown",
107
+ "integrations.detail.hub.label": "Hub",
100
108
  "integrations.detail.loadError": "Failed to load integration",
101
109
  "integrations.detail.logs.columns.level": "Level",
102
110
  "integrations.detail.logs.columns.message": "Message",
103
111
  "integrations.detail.logs.columns.time": "Time",
112
+ "integrations.detail.logs.details.fields": "Fields",
113
+ "integrations.detail.logs.details.noPayload": "No structured payload was stored for this log entry.",
114
+ "integrations.detail.logs.details.payload": "Payload",
115
+ "integrations.detail.logs.details.summary": "Summary",
104
116
  "integrations.detail.logs.empty": "No log entries yet",
105
117
  "integrations.detail.logs.level.all": "All Levels",
106
118
  "integrations.detail.logs.level.error": "Error",
107
119
  "integrations.detail.logs.level.info": "Info",
108
120
  "integrations.detail.logs.level.warn": "Warning",
109
121
  "integrations.detail.logs.title": "Operation Logs",
122
+ "integrations.detail.state.disabled": "Disabled",
123
+ "integrations.detail.state.enabled": "Enabled",
124
+ "integrations.detail.state.label": "State",
110
125
  "integrations.detail.stateError": "Failed to update state",
111
126
  "integrations.detail.stateUpdated": "Integration state updated",
112
127
  "integrations.detail.tabs.credentials": "Credentials",
@@ -85,28 +85,43 @@
85
85
  "integrations.detail.credentials.save": "Guardar credenciales",
86
86
  "integrations.detail.credentials.saveError": "No se pudieron guardar las credenciales",
87
87
  "integrations.detail.credentials.saved": "Credenciales guardadas",
88
+ "integrations.detail.credentials.validation.boolean": "Select a valid value.",
89
+ "integrations.detail.credentials.validation.option": "Select one of the available options.",
90
+ "integrations.detail.credentials.validation.required": "{field} is required.",
91
+ "integrations.detail.credentials.validation.text": "Enter a valid value.",
92
+ "integrations.detail.credentials.validation.tooLong": "Value is too long.",
88
93
  "integrations.detail.disable": "Desactivar",
89
94
  "integrations.detail.enable": "Activar",
90
95
  "integrations.detail.health.check": "Ejecutar verificación",
91
96
  "integrations.detail.health.checkError": "La verificación de estado falló",
92
97
  "integrations.detail.health.checking": "Verificando…",
93
98
  "integrations.detail.health.degraded": "Degradado",
99
+ "integrations.detail.health.details": "Details",
94
100
  "integrations.detail.health.healthy": "Saludable",
95
101
  "integrations.detail.health.lastChecked": "Última verificación: {date}",
102
+ "integrations.detail.health.lastResult": "Last result",
96
103
  "integrations.detail.health.neverChecked": "Nunca verificado",
97
104
  "integrations.detail.health.title": "Estado de salud",
98
105
  "integrations.detail.health.unhealthy": "No saludable",
99
106
  "integrations.detail.health.unknown": "Desconocido",
107
+ "integrations.detail.hub.label": "Hub",
100
108
  "integrations.detail.loadError": "No se pudo cargar la integración",
101
109
  "integrations.detail.logs.columns.level": "Nivel",
102
110
  "integrations.detail.logs.columns.message": "Mensaje",
103
111
  "integrations.detail.logs.columns.time": "Hora",
112
+ "integrations.detail.logs.details.fields": "Fields",
113
+ "integrations.detail.logs.details.noPayload": "No structured payload was stored for this log entry.",
114
+ "integrations.detail.logs.details.payload": "Payload",
115
+ "integrations.detail.logs.details.summary": "Summary",
104
116
  "integrations.detail.logs.empty": "Sin entradas de registro",
105
117
  "integrations.detail.logs.level.all": "Todos los niveles",
106
118
  "integrations.detail.logs.level.error": "Error",
107
119
  "integrations.detail.logs.level.info": "Info",
108
120
  "integrations.detail.logs.level.warn": "Advertencia",
109
121
  "integrations.detail.logs.title": "Registros de operación",
122
+ "integrations.detail.state.disabled": "Disabled",
123
+ "integrations.detail.state.enabled": "Enabled",
124
+ "integrations.detail.state.label": "State",
110
125
  "integrations.detail.stateError": "No se pudo actualizar el estado",
111
126
  "integrations.detail.stateUpdated": "Estado de integración actualizado",
112
127
  "integrations.detail.tabs.credentials": "Credenciales",
@@ -85,28 +85,43 @@
85
85
  "integrations.detail.credentials.save": "Zapisz dane",
86
86
  "integrations.detail.credentials.saveError": "Nie udało się zapisać danych",
87
87
  "integrations.detail.credentials.saved": "Dane zapisane",
88
+ "integrations.detail.credentials.validation.boolean": "Select a valid value.",
89
+ "integrations.detail.credentials.validation.option": "Select one of the available options.",
90
+ "integrations.detail.credentials.validation.required": "{field} is required.",
91
+ "integrations.detail.credentials.validation.text": "Enter a valid value.",
92
+ "integrations.detail.credentials.validation.tooLong": "Value is too long.",
88
93
  "integrations.detail.disable": "Wyłącz",
89
94
  "integrations.detail.enable": "Włącz",
90
95
  "integrations.detail.health.check": "Sprawdź",
91
96
  "integrations.detail.health.checkError": "Sprawdzenie stanu nie powiodło się",
92
97
  "integrations.detail.health.checking": "Sprawdzanie…",
93
98
  "integrations.detail.health.degraded": "Pogorszony",
99
+ "integrations.detail.health.details": "Details",
94
100
  "integrations.detail.health.healthy": "Zdrowy",
95
101
  "integrations.detail.health.lastChecked": "Ostatnie sprawdzenie: {date}",
102
+ "integrations.detail.health.lastResult": "Last result",
96
103
  "integrations.detail.health.neverChecked": "Nigdy nie sprawdzano",
97
104
  "integrations.detail.health.title": "Stan zdrowia",
98
105
  "integrations.detail.health.unhealthy": "Niezdrowy",
99
106
  "integrations.detail.health.unknown": "Nieznany",
107
+ "integrations.detail.hub.label": "Hub",
100
108
  "integrations.detail.loadError": "Nie udało się załadować integracji",
101
109
  "integrations.detail.logs.columns.level": "Poziom",
102
110
  "integrations.detail.logs.columns.message": "Wiadomość",
103
111
  "integrations.detail.logs.columns.time": "Czas",
112
+ "integrations.detail.logs.details.fields": "Fields",
113
+ "integrations.detail.logs.details.noPayload": "No structured payload was stored for this log entry.",
114
+ "integrations.detail.logs.details.payload": "Payload",
115
+ "integrations.detail.logs.details.summary": "Summary",
104
116
  "integrations.detail.logs.empty": "Brak wpisów w logach",
105
117
  "integrations.detail.logs.level.all": "Wszystkie poziomy",
106
118
  "integrations.detail.logs.level.error": "Błąd",
107
119
  "integrations.detail.logs.level.info": "Info",
108
120
  "integrations.detail.logs.level.warn": "Ostrzeżenie",
109
121
  "integrations.detail.logs.title": "Logi operacji",
122
+ "integrations.detail.state.disabled": "Disabled",
123
+ "integrations.detail.state.enabled": "Enabled",
124
+ "integrations.detail.state.label": "State",
110
125
  "integrations.detail.stateError": "Nie udało się zaktualizować stanu",
111
126
  "integrations.detail.stateUpdated": "Stan integracji zaktualizowany",
112
127
  "integrations.detail.tabs.credentials": "Dane uwierzytelniające",
@@ -2,8 +2,8 @@ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
2
 
3
3
  export const setup: ModuleSetupConfig = {
4
4
  defaultRoleFeatures: {
5
- superadmin: ['integrations.view', 'integrations.manage', 'integrations.credentials.manage'],
6
- admin: ['integrations.view', 'integrations.manage', 'integrations.credentials.manage'],
5
+ superadmin: ['integrations.*', 'integrations.view', 'integrations.manage', 'integrations.credentials.manage'],
6
+ admin: ['integrations.*', 'integrations.view', 'integrations.manage', 'integrations.credentials.manage'],
7
7
  employee: ['integrations.view'],
8
8
  },
9
9
  }
@@ -0,0 +1,8 @@
1
+ export const features = [
2
+ { id: 'payment_gateways.view', title: 'View payment gateway transactions', module: 'payment_gateways' },
3
+ { id: 'payment_gateways.manage', title: 'Create and manage payment sessions', module: 'payment_gateways' },
4
+ { id: 'payment_gateways.capture', title: 'Capture authorized payments', module: 'payment_gateways' },
5
+ { id: 'payment_gateways.refund', title: 'Refund captured payments', module: 'payment_gateways' },
6
+ ]
7
+
8
+ export default features
@@ -0,0 +1,56 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
+ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
5
+ import { cancelSchema } from '../../data/validators'
6
+ import type { PaymentGatewayService } from '../../lib/gateway-service'
7
+ import { paymentGatewaysTag } from '../openapi'
8
+
9
+ export const metadata = {
10
+ path: '/payment_gateways/cancel',
11
+ POST: { requireAuth: true, requireFeatures: ['payment_gateways.manage'] },
12
+ }
13
+
14
+ export async function POST(req: Request) {
15
+ const auth = await getAuthFromRequest(req)
16
+ if (!auth?.tenantId || !auth.orgId) {
17
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
18
+ }
19
+
20
+ const payload = await readJsonSafe<unknown>(req)
21
+ const parsed = cancelSchema.safeParse(payload)
22
+ if (!parsed.success) {
23
+ return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 422 })
24
+ }
25
+
26
+ const container = await createRequestContainer()
27
+ const service = container.resolve('paymentGatewayService') as PaymentGatewayService
28
+
29
+ try {
30
+ const result = await service.cancelPayment(
31
+ parsed.data.transactionId,
32
+ parsed.data.reason,
33
+ { organizationId: auth.orgId as string, tenantId: auth.tenantId },
34
+ )
35
+ return NextResponse.json(result)
36
+ } catch (err: unknown) {
37
+ const message = err instanceof Error ? err.message : 'Cancel failed'
38
+ return NextResponse.json({ error: message }, { status: 502 })
39
+ }
40
+ }
41
+
42
+ export const openApi = {
43
+ tags: [paymentGatewaysTag],
44
+ summary: 'Cancel/void an authorized payment',
45
+ methods: {
46
+ POST: {
47
+ summary: 'Cancel payment',
48
+ tags: [paymentGatewaysTag],
49
+ responses: [
50
+ { status: 200, description: 'Payment cancelled' },
51
+ { status: 422, description: 'Invalid payload' },
52
+ { status: 502, description: 'Gateway provider error' },
53
+ ],
54
+ },
55
+ },
56
+ }