@open-mercato/core 0.4.7-develop-0a657b411f → 0.4.7-develop-11728c8558

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 (275) 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 +80 -0
  102. package/dist/modules/sales/data/enrichers.js.map +7 -0
  103. package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js +29 -0
  104. package/dist/modules/sales/widgets/injection/payment-gateway-config-field/widget.js.map +7 -0
  105. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js +23 -0
  106. package/dist/modules/sales/widgets/injection/payment-gateway-status-column/widget.js.map +7 -0
  107. package/dist/modules/sales/widgets/injection-table.js +13 -1
  108. package/dist/modules/sales/widgets/injection-table.js.map +2 -2
  109. package/dist/modules/shipping_carriers/acl.js +10 -0
  110. package/dist/modules/shipping_carriers/acl.js.map +7 -0
  111. package/dist/modules/shipping_carriers/api/cancel/route.js +55 -0
  112. package/dist/modules/shipping_carriers/api/cancel/route.js.map +7 -0
  113. package/dist/modules/shipping_carriers/api/interceptors.js +21 -0
  114. package/dist/modules/shipping_carriers/api/interceptors.js.map +7 -0
  115. package/dist/modules/shipping_carriers/api/openapi.js +5 -0
  116. package/dist/modules/shipping_carriers/api/openapi.js.map +7 -0
  117. package/dist/modules/shipping_carriers/api/rates/route.js +55 -0
  118. package/dist/modules/shipping_carriers/api/rates/route.js.map +7 -0
  119. package/dist/modules/shipping_carriers/api/shipments/route.js +61 -0
  120. package/dist/modules/shipping_carriers/api/shipments/route.js.map +7 -0
  121. package/dist/modules/shipping_carriers/api/tracking/route.js +58 -0
  122. package/dist/modules/shipping_carriers/api/tracking/route.js.map +7 -0
  123. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js +119 -0
  124. package/dist/modules/shipping_carriers/api/webhook/[provider]/route.js.map +7 -0
  125. package/dist/modules/shipping_carriers/data/enrichers.js +82 -0
  126. package/dist/modules/shipping_carriers/data/enrichers.js.map +7 -0
  127. package/dist/modules/shipping_carriers/data/entities.js +80 -0
  128. package/dist/modules/shipping_carriers/data/entities.js.map +7 -0
  129. package/dist/modules/shipping_carriers/data/validators.js +49 -0
  130. package/dist/modules/shipping_carriers/data/validators.js.map +7 -0
  131. package/dist/modules/shipping_carriers/di.js +15 -0
  132. package/dist/modules/shipping_carriers/di.js.map +7 -0
  133. package/dist/modules/shipping_carriers/events.js +19 -0
  134. package/dist/modules/shipping_carriers/events.js.map +7 -0
  135. package/dist/modules/shipping_carriers/i18n/en.js +11 -0
  136. package/dist/modules/shipping_carriers/i18n/en.js.map +7 -0
  137. package/dist/modules/shipping_carriers/i18n/pl.js +11 -0
  138. package/dist/modules/shipping_carriers/i18n/pl.js.map +7 -0
  139. package/dist/modules/shipping_carriers/index.js +9 -0
  140. package/dist/modules/shipping_carriers/index.js.map +7 -0
  141. package/dist/modules/shipping_carriers/lib/adapter-registry.js +29 -0
  142. package/dist/modules/shipping_carriers/lib/adapter-registry.js.map +7 -0
  143. package/dist/modules/shipping_carriers/lib/adapter.js +1 -0
  144. package/dist/modules/shipping_carriers/lib/adapter.js.map +7 -0
  145. package/dist/modules/shipping_carriers/lib/queue.js +17 -0
  146. package/dist/modules/shipping_carriers/lib/queue.js.map +7 -0
  147. package/dist/modules/shipping_carriers/lib/shipping-service.js +155 -0
  148. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +7 -0
  149. package/dist/modules/shipping_carriers/lib/status-sync.js +37 -0
  150. package/dist/modules/shipping_carriers/lib/status-sync.js.map +7 -0
  151. package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js +16 -0
  152. package/dist/modules/shipping_carriers/migrations/Migration20260305170000.js.map +7 -0
  153. package/dist/modules/shipping_carriers/setup.js +13 -0
  154. package/dist/modules/shipping_carriers/setup.js.map +7 -0
  155. package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js +25 -0
  156. package/dist/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.js.map +7 -0
  157. package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js +23 -0
  158. package/dist/modules/shipping_carriers/widgets/injection/tracking-column/widget.js.map +7 -0
  159. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js +40 -0
  160. package/dist/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.js.map +7 -0
  161. package/dist/modules/shipping_carriers/widgets/injection-table.js +24 -0
  162. package/dist/modules/shipping_carriers/widgets/injection-table.js.map +7 -0
  163. package/dist/modules/shipping_carriers/workers/status-poller.js +21 -0
  164. package/dist/modules/shipping_carriers/workers/status-poller.js.map +7 -0
  165. package/dist/modules/shipping_carriers/workers/webhook-processor.js +54 -0
  166. package/dist/modules/shipping_carriers/workers/webhook-processor.js.map +7 -0
  167. package/dist/modules/translations/api/get/locales.js +1 -0
  168. package/dist/modules/translations/api/get/locales.js.map +2 -2
  169. package/dist/modules/translations/api/put/locales.js +1 -0
  170. package/dist/modules/translations/api/put/locales.js.map +2 -2
  171. package/generated/entities/carrier_shipment/index.ts +17 -0
  172. package/generated/entities/gateway_transaction/index.ts +22 -0
  173. package/generated/entities/webhook_processed_event/index.ts +7 -0
  174. package/generated/entities.ids.generated.ts +10 -1
  175. package/generated/entity-fields-registry.ts +6 -0
  176. package/jest.config.cjs +1 -0
  177. package/package.json +5 -2
  178. package/src/modules/auth/i18n/de.json +1 -0
  179. package/src/modules/auth/i18n/en.json +1 -0
  180. package/src/modules/auth/i18n/es.json +1 -0
  181. package/src/modules/auth/i18n/pl.json +1 -0
  182. package/src/modules/data_sync/api/runs/[id]/cancel.ts +18 -5
  183. package/src/modules/data_sync/backend/data-sync/page.meta.ts +2 -2
  184. package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +50 -12
  185. package/src/modules/directory/api/get/tenants/lookup.ts +1 -0
  186. package/src/modules/integrations/AGENTS.md +31 -0
  187. package/src/modules/integrations/api/[id]/route.ts +38 -11
  188. package/src/modules/integrations/api/logs/route.ts +53 -27
  189. package/src/modules/integrations/api/route.ts +31 -1
  190. package/src/modules/integrations/api/umes-read.ts +177 -0
  191. package/src/modules/integrations/backend/integrations/[id]/page.tsx +902 -202
  192. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +43 -9
  193. package/src/modules/integrations/backend/integrations/detail-page-widgets.ts +74 -0
  194. package/src/modules/integrations/backend/integrations/page.meta.ts +2 -2
  195. package/src/modules/integrations/backend/integrations/page.tsx +65 -54
  196. package/src/modules/integrations/i18n/de.json +15 -0
  197. package/src/modules/integrations/i18n/en.json +15 -0
  198. package/src/modules/integrations/i18n/es.json +15 -0
  199. package/src/modules/integrations/i18n/pl.json +15 -0
  200. package/src/modules/integrations/setup.ts +2 -2
  201. package/src/modules/payment_gateways/acl.ts +8 -0
  202. package/src/modules/payment_gateways/api/cancel/route.ts +56 -0
  203. package/src/modules/payment_gateways/api/capture/route.ts +56 -0
  204. package/src/modules/payment_gateways/api/interceptors.ts +22 -0
  205. package/src/modules/payment_gateways/api/openapi.ts +1 -0
  206. package/src/modules/payment_gateways/api/refund/route.ts +57 -0
  207. package/src/modules/payment_gateways/api/sessions/route.ts +76 -0
  208. package/src/modules/payment_gateways/api/status/route.ts +69 -0
  209. package/src/modules/payment_gateways/api/transactions/[id]/route.ts +123 -0
  210. package/src/modules/payment_gateways/api/transactions/route.ts +120 -0
  211. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +161 -0
  212. package/src/modules/payment_gateways/backend/payment-gateways/page.meta.ts +19 -0
  213. package/src/modules/payment_gateways/backend/payment-gateways/page.tsx +660 -0
  214. package/src/modules/payment_gateways/data/enrichers.ts +8 -0
  215. package/src/modules/payment_gateways/data/entities.ts +106 -0
  216. package/src/modules/payment_gateways/data/validators.ts +67 -0
  217. package/src/modules/payment_gateways/di.ts +26 -0
  218. package/src/modules/payment_gateways/events.ts +17 -0
  219. package/src/modules/payment_gateways/i18n/de.json +77 -0
  220. package/src/modules/payment_gateways/i18n/en.json +77 -0
  221. package/src/modules/payment_gateways/i18n/en.ts +4 -0
  222. package/src/modules/payment_gateways/i18n/es.json +77 -0
  223. package/src/modules/payment_gateways/i18n/pl.json +77 -0
  224. package/src/modules/payment_gateways/i18n/pl.ts +4 -0
  225. package/src/modules/payment_gateways/index.ts +5 -0
  226. package/src/modules/payment_gateways/lib/gateway-service.ts +486 -0
  227. package/src/modules/payment_gateways/lib/queue.ts +19 -0
  228. package/src/modules/payment_gateways/lib/status-machine.ts +28 -0
  229. package/src/modules/payment_gateways/lib/webhook-processor.ts +133 -0
  230. package/src/modules/payment_gateways/lib/webhook-utils.ts +52 -0
  231. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +373 -0
  232. package/src/modules/payment_gateways/migrations/Migration20260305122155.ts +20 -0
  233. package/src/modules/payment_gateways/setup.ts +11 -0
  234. package/src/modules/payment_gateways/widgets/injection-table.ts +9 -0
  235. package/src/modules/payment_gateways/workers/status-poller.ts +58 -0
  236. package/src/modules/payment_gateways/workers/webhook-processor.ts +30 -0
  237. package/src/modules/sales/data/enrichers.ts +94 -0
  238. package/src/modules/sales/widgets/injection/payment-gateway-config-field/widget.ts +28 -0
  239. package/src/modules/sales/widgets/injection/payment-gateway-status-column/widget.ts +22 -0
  240. package/src/modules/sales/widgets/injection-table.ts +12 -0
  241. package/src/modules/shipping_carriers/acl.ts +6 -0
  242. package/src/modules/shipping_carriers/api/cancel/route.ts +53 -0
  243. package/src/modules/shipping_carriers/api/interceptors.ts +19 -0
  244. package/src/modules/shipping_carriers/api/openapi.ts +1 -0
  245. package/src/modules/shipping_carriers/api/rates/route.ts +53 -0
  246. package/src/modules/shipping_carriers/api/shipments/route.ts +59 -0
  247. package/src/modules/shipping_carriers/api/tracking/route.ts +56 -0
  248. package/src/modules/shipping_carriers/api/webhook/[provider]/route.ts +134 -0
  249. package/src/modules/shipping_carriers/data/enrichers.ts +89 -0
  250. package/src/modules/shipping_carriers/data/entities.ts +60 -0
  251. package/src/modules/shipping_carriers/data/validators.ts +48 -0
  252. package/src/modules/shipping_carriers/di.ts +20 -0
  253. package/src/modules/shipping_carriers/events.ts +16 -0
  254. package/src/modules/shipping_carriers/i18n/de.json +7 -0
  255. package/src/modules/shipping_carriers/i18n/en.json +7 -0
  256. package/src/modules/shipping_carriers/i18n/en.ts +7 -0
  257. package/src/modules/shipping_carriers/i18n/es.json +7 -0
  258. package/src/modules/shipping_carriers/i18n/pl.json +7 -0
  259. package/src/modules/shipping_carriers/i18n/pl.ts +7 -0
  260. package/src/modules/shipping_carriers/index.ts +5 -0
  261. package/src/modules/shipping_carriers/lib/adapter-registry.ts +33 -0
  262. package/src/modules/shipping_carriers/lib/adapter.ts +93 -0
  263. package/src/modules/shipping_carriers/lib/queue.ts +19 -0
  264. package/src/modules/shipping_carriers/lib/shipping-service.ts +204 -0
  265. package/src/modules/shipping_carriers/lib/status-sync.ts +38 -0
  266. package/src/modules/shipping_carriers/migrations/Migration20260305170000.ts +14 -0
  267. package/src/modules/shipping_carriers/setup.ts +11 -0
  268. package/src/modules/shipping_carriers/widgets/injection/create-shipment-button/widget.ts +24 -0
  269. package/src/modules/shipping_carriers/widgets/injection/tracking-column/widget.ts +22 -0
  270. package/src/modules/shipping_carriers/widgets/injection/tracking-status-badge/widget.tsx +44 -0
  271. package/src/modules/shipping_carriers/widgets/injection-table.ts +22 -0
  272. package/src/modules/shipping_carriers/workers/status-poller.ts +33 -0
  273. package/src/modules/shipping_carriers/workers/webhook-processor.ts +79 -0
  274. package/src/modules/translations/api/get/locales.ts +1 -0
  275. package/src/modules/translations/api/put/locales.ts +1 -0
@@ -1,9 +1,14 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import Link from "next/link";
5
- import { useParams } from "next/navigation";
4
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
5
+ import { z } from "zod";
6
6
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
+ import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
8
+ import { WebhookSetupGuide } from "@open-mercato/ui/backend/WebhookSetupGuide";
9
+ import { InjectionSpot, useInjectionWidgets } from "@open-mercato/ui/backend/injection/InjectionSpot";
10
+ import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
11
+ import { FormHeader } from "@open-mercato/ui/backend/forms";
7
12
  import { Card, CardHeader, CardTitle, CardContent } from "@open-mercato/ui/primitives/card";
8
13
  import { Badge } from "@open-mercato/ui/primitives/badge";
9
14
  import { Button } from "@open-mercato/ui/primitives/button";
@@ -11,11 +16,20 @@ import { Switch } from "@open-mercato/ui/primitives/switch";
11
16
  import { Input } from "@open-mercato/ui/primitives/input";
12
17
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
13
18
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
19
+ import { JsonDisplay } from "@open-mercato/ui/backend/JsonDisplay";
14
20
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
15
21
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
22
+ import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
16
23
  import { useT } from "@open-mercato/shared/lib/i18n/context";
17
- import { LoadingMessage } from "@open-mercato/ui/backend/detail";
18
- import { ErrorMessage } from "@open-mercato/ui/backend/detail";
24
+ import { LEGACY_INTEGRATION_DETAIL_TABS_SPOT_ID } from "@open-mercato/shared/modules/integrations/types";
25
+ import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
26
+ import { Bell, ChevronDown, ChevronRight, CreditCard, HardDrive, MessageSquare, RefreshCw, Truck, Webhook, Zap } from "lucide-react";
27
+ import {
28
+ buildIntegrationDetailInjectedTabs,
29
+ filterIntegrationDetailWidgetsByKind,
30
+ resolveIntegrationDetailWidgetSpotId,
31
+ resolveRequestedIntegrationDetailTab
32
+ } from "../detail-page-widgets.js";
19
33
  const UNSUPPORTED_CREDENTIAL_FIELD_TYPES = /* @__PURE__ */ new Set(["oauth", "ssh_keypair"]);
20
34
  function isEditableCredentialField(field) {
21
35
  return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type);
@@ -30,49 +44,190 @@ const HEALTH_STATUS_STYLES = {
30
44
  degraded: "bg-yellow-100 text-yellow-800",
31
45
  unhealthy: "bg-red-100 text-red-800"
32
46
  };
33
- function IntegrationDetailPage() {
34
- const params = useParams();
35
- const integrationId = params.id;
47
+ const CATEGORY_ICONS = {
48
+ payment: CreditCard,
49
+ shipping: Truck,
50
+ data_sync: RefreshCw,
51
+ communication: MessageSquare,
52
+ notification: Bell,
53
+ storage: HardDrive,
54
+ webhook: Webhook
55
+ };
56
+ function resolveRouteId(value) {
57
+ if (Array.isArray(value)) return value[0];
58
+ return value;
59
+ }
60
+ function resolvePathnameId(pathname) {
61
+ const parts = pathname.split("/").filter(Boolean);
62
+ const integrationId = parts.at(-1);
63
+ if (!integrationId || integrationId === "integrations" || integrationId === "bundle") return void 0;
64
+ return decodeURIComponent(integrationId);
65
+ }
66
+ function buildCredentialFields(credFields) {
67
+ return credFields.map((field) => {
68
+ const shared = {
69
+ id: field.key,
70
+ label: field.label,
71
+ description: field.helpDetails ? /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
72
+ field.helpText ? /* @__PURE__ */ jsx("div", { children: field.helpText }) : null,
73
+ /* @__PURE__ */ jsx(WebhookSetupGuide, { guide: field.helpDetails, buttonLabel: "Show details" })
74
+ ] }) : field.helpText,
75
+ placeholder: field.placeholder,
76
+ required: field.required
77
+ };
78
+ if (field.type === "secret") {
79
+ return {
80
+ ...shared,
81
+ type: "custom",
82
+ component: ({ id, value, setValue, disabled }) => /* @__PURE__ */ jsx(
83
+ Input,
84
+ {
85
+ id,
86
+ type: "password",
87
+ placeholder: field.placeholder,
88
+ value: typeof value === "string" ? value : "",
89
+ onChange: (event) => setValue(event.target.value),
90
+ disabled
91
+ }
92
+ )
93
+ };
94
+ }
95
+ if (field.type === "select" && field.options) {
96
+ return {
97
+ ...shared,
98
+ type: "select",
99
+ options: field.options
100
+ };
101
+ }
102
+ if (field.type === "boolean") {
103
+ return {
104
+ ...shared,
105
+ type: "checkbox"
106
+ };
107
+ }
108
+ return {
109
+ ...shared,
110
+ type: "text"
111
+ };
112
+ });
113
+ }
114
+ function isHealthLog(log) {
115
+ return log.message === "Health check passed" || log.message.startsWith("Health check:");
116
+ }
117
+ function extractHealthDetails(payload) {
118
+ if (!payload) return {};
119
+ return Object.fromEntries(
120
+ Object.entries(payload).filter(([key, value]) => key !== "status" && key !== "message" && value !== void 0 && value !== null)
121
+ );
122
+ }
123
+ function formatHealthValue(value) {
124
+ if (typeof value === "boolean") return value ? "Yes" : "No";
125
+ if (typeof value === "string") return value;
126
+ if (typeof value === "number") return String(value);
127
+ if (value instanceof Date) return value.toLocaleString();
128
+ return JSON.stringify(value);
129
+ }
130
+ function formatTypeLabel(value) {
131
+ return value.split("_").filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
132
+ }
133
+ function isPrimitiveLogValue(value) {
134
+ return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
135
+ }
136
+ function formatLogDetailLabel(key) {
137
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").split(" ").filter(Boolean).map((part) => part[0]?.toUpperCase() + part.slice(1)).join(" ");
138
+ }
139
+ function formatLogPrimitiveValue(value) {
140
+ if (value === null) return "None";
141
+ if (typeof value === "boolean") return value ? "Yes" : "No";
142
+ return String(value);
143
+ }
144
+ function splitLogPayload(payload) {
145
+ if (!payload) {
146
+ return {
147
+ inlineEntries: [],
148
+ nestedEntries: []
149
+ };
150
+ }
151
+ const inlineEntries = [];
152
+ const nestedEntries = [];
153
+ Object.entries(payload).forEach(([key, value]) => {
154
+ if (isPrimitiveLogValue(value)) {
155
+ inlineEntries.push([key, value]);
156
+ return;
157
+ }
158
+ nestedEntries.push([key, value]);
159
+ });
160
+ return { inlineEntries, nestedEntries };
161
+ }
162
+ function IntegrationDetailPage({ params }) {
163
+ const pathname = usePathname();
164
+ const router = useRouter();
165
+ const searchParams = useSearchParams();
166
+ const integrationId = resolveRouteId(params?.id) ?? resolvePathnameId(pathname);
36
167
  const t = useT();
37
168
  const [detail, setDetail] = React.useState(null);
38
169
  const [isLoading, setIsLoading] = React.useState(true);
39
170
  const [error, setError] = React.useState(null);
40
171
  const [credValues, setCredValues] = React.useState({});
41
- const [isSavingCreds, setIsSavingCreds] = React.useState(false);
172
+ const [credentialsFormKey, setCredentialsFormKey] = React.useState(0);
173
+ const [isSavingCredentials, setIsSavingCredentials] = React.useState(false);
42
174
  const [logs, setLogs] = React.useState([]);
43
175
  const [logLevel, setLogLevel] = React.useState("");
44
176
  const [isLoadingLogs, setIsLoadingLogs] = React.useState(false);
177
+ const [expandedLogId, setExpandedLogId] = React.useState(null);
45
178
  const [isCheckingHealth, setIsCheckingHealth] = React.useState(false);
46
179
  const [isTogglingState, setIsTogglingState] = React.useState(false);
180
+ const [latestHealthResult, setLatestHealthResult] = React.useState(null);
181
+ const [activeTab, setActiveTab] = React.useState("credentials");
182
+ const credentialsFormId = React.useId();
183
+ const resolveCurrentIntegrationId = React.useCallback(() => {
184
+ return integrationId ?? (typeof window !== "undefined" ? resolvePathnameId(window.location.pathname) : void 0);
185
+ }, [integrationId]);
47
186
  const loadDetail = React.useCallback(async () => {
48
- setIsLoading(true);
49
- setError(null);
50
- const call = await apiCall(
51
- `/api/integrations/${encodeURIComponent(integrationId)}`,
52
- void 0,
53
- { fallback: null }
54
- );
55
- if (!call.ok || !call.result) {
56
- setError(t("integrations.detail.loadError"));
187
+ const currentIntegrationId = resolveCurrentIntegrationId();
188
+ if (!currentIntegrationId) {
57
189
  setIsLoading(false);
190
+ setError(t("integrations.detail.loadError", "Failed to load integration"));
58
191
  return;
59
192
  }
60
- setDetail(call.result);
61
- setIsLoading(false);
62
- }, [integrationId, t]);
193
+ setError(null);
194
+ setIsLoading(true);
195
+ try {
196
+ const call = await apiCall(
197
+ `/api/integrations/${encodeURIComponent(currentIntegrationId)}`,
198
+ void 0,
199
+ { fallback: null }
200
+ );
201
+ if (!call.ok || !call.result) {
202
+ setError(t("integrations.detail.loadError", "Failed to load integration"));
203
+ setIsLoading(false);
204
+ return;
205
+ }
206
+ setDetail(call.result);
207
+ setIsLoading(false);
208
+ } catch {
209
+ setError(t("integrations.detail.loadError", "Failed to load integration"));
210
+ setIsLoading(false);
211
+ }
212
+ }, [resolveCurrentIntegrationId, t]);
63
213
  const loadCredentials = React.useCallback(async () => {
214
+ const currentIntegrationId = resolveCurrentIntegrationId();
215
+ if (!currentIntegrationId) return;
64
216
  const call = await apiCall(
65
- `/api/integrations/${encodeURIComponent(integrationId)}/credentials`,
217
+ `/api/integrations/${encodeURIComponent(currentIntegrationId)}/credentials`,
66
218
  void 0,
67
219
  { fallback: null }
68
220
  );
69
221
  if (call.ok && call.result?.credentials) {
70
222
  setCredValues(call.result.credentials);
223
+ setCredentialsFormKey((current) => current + 1);
71
224
  }
72
- }, [integrationId]);
225
+ }, [resolveCurrentIntegrationId]);
73
226
  const loadLogs = React.useCallback(async () => {
227
+ const currentIntegrationId = resolveCurrentIntegrationId();
228
+ if (!currentIntegrationId) return;
74
229
  setIsLoadingLogs(true);
75
- const params2 = new URLSearchParams({ integrationId, pageSize: "50" });
230
+ const params2 = new URLSearchParams({ integrationId: currentIntegrationId, pageSize: "50" });
76
231
  if (logLevel) params2.set("level", logLevel);
77
232
  const call = await apiCall(
78
233
  `/api/integrations/logs?${params2.toString()}`,
@@ -83,7 +238,101 @@ function IntegrationDetailPage() {
83
238
  setLogs(call.result.items);
84
239
  }
85
240
  setIsLoadingLogs(false);
86
- }, [integrationId, logLevel]);
241
+ }, [logLevel, resolveCurrentIntegrationId]);
242
+ const detailWidgetSpotId = React.useMemo(
243
+ () => resolveIntegrationDetailWidgetSpotId(detail?.integration ?? null, LEGACY_INTEGRATION_DETAIL_TABS_SPOT_ID),
244
+ [detail?.integration]
245
+ );
246
+ const mutationContextId = React.useMemo(
247
+ () => `integrations.detail:${integrationId ?? "unknown"}`,
248
+ [integrationId]
249
+ );
250
+ const { runMutation, retryLastMutation } = useGuardedMutation({
251
+ contextId: mutationContextId,
252
+ spotId: detailWidgetSpotId
253
+ });
254
+ const refreshDetail = React.useCallback(async () => {
255
+ await loadDetail();
256
+ await loadCredentials();
257
+ }, [loadCredentials, loadDetail]);
258
+ const refreshLogs = React.useCallback(async () => {
259
+ await loadLogs();
260
+ }, [loadLogs]);
261
+ const injectionContext = React.useMemo(
262
+ () => ({
263
+ formId: mutationContextId,
264
+ integrationDetailWidgetSpotId: detailWidgetSpotId,
265
+ resourceKind: "integrations.integration",
266
+ resourceId: integrationId ?? detail?.integration.id,
267
+ integrationId: integrationId ?? detail?.integration.id,
268
+ integration: detail?.integration ?? null,
269
+ detail,
270
+ state: detail?.state ?? null,
271
+ credentialValues: credValues,
272
+ latestHealthResult,
273
+ activeTab,
274
+ setActiveTab,
275
+ refreshDetail,
276
+ refreshLogs,
277
+ retryLastMutation
278
+ }),
279
+ [
280
+ activeTab,
281
+ credValues,
282
+ detail,
283
+ detailWidgetSpotId,
284
+ integrationId,
285
+ latestHealthResult,
286
+ mutationContextId,
287
+ refreshDetail,
288
+ refreshLogs,
289
+ retryLastMutation
290
+ ]
291
+ );
292
+ const { widgets: detailWidgets } = useInjectionWidgets(detailWidgetSpotId, {
293
+ context: injectionContext,
294
+ triggerOnLoad: true
295
+ });
296
+ const stackedDetailWidgets = React.useMemo(
297
+ () => filterIntegrationDetailWidgetsByKind(detailWidgets, "stack"),
298
+ [detailWidgets]
299
+ );
300
+ const groupedDetailWidgets = React.useMemo(
301
+ () => filterIntegrationDetailWidgetsByKind(detailWidgets, "group"),
302
+ [detailWidgets]
303
+ );
304
+ const injectedTabs = React.useMemo(
305
+ () => buildIntegrationDetailInjectedTabs(
306
+ detailWidgets,
307
+ (widget) => widget.placement?.groupLabel ? t(widget.placement.groupLabel, widget.module.metadata.title ?? widget.widgetId) : widget.module.metadata.title ?? widget.widgetId
308
+ ),
309
+ [detailWidgets, t]
310
+ );
311
+ const customTabIds = React.useMemo(
312
+ () => injectedTabs.map((tab) => tab.id),
313
+ [injectedTabs]
314
+ );
315
+ const runMutationWithContext = React.useCallback(
316
+ async ({
317
+ operation,
318
+ mutationPayload,
319
+ actionId,
320
+ tabId,
321
+ operationType = "update"
322
+ }) => {
323
+ return runMutation({
324
+ operation,
325
+ mutationPayload,
326
+ context: {
327
+ ...injectionContext,
328
+ operation: operationType,
329
+ actionId,
330
+ activeTab: tabId ?? activeTab
331
+ }
332
+ });
333
+ },
334
+ [activeTab, injectionContext, runMutation]
335
+ );
87
336
  React.useEffect(() => {
88
337
  void loadDetail();
89
338
  }, [loadDetail]);
@@ -93,181 +342,363 @@ function IntegrationDetailPage() {
93
342
  React.useEffect(() => {
94
343
  void loadLogs();
95
344
  }, [loadLogs]);
345
+ React.useEffect(() => {
346
+ setExpandedLogId((current) => current && logs.some((log) => log.id === current) ? current : null);
347
+ }, [logs]);
96
348
  const handleToggleState = React.useCallback(async (enabled) => {
349
+ const currentIntegrationId = resolveCurrentIntegrationId();
350
+ if (!currentIntegrationId) return;
97
351
  setIsTogglingState(true);
98
- const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {
99
- method: "PUT",
100
- headers: { "Content-Type": "application/json" },
101
- body: JSON.stringify({ isEnabled: enabled })
102
- }, { fallback: null });
103
- if (call.ok) {
104
- setDetail((prev) => prev ? { ...prev, state: { ...prev.state, isEnabled: enabled } } : prev);
105
- flash(t("integrations.detail.stateUpdated"), "success");
106
- } else {
352
+ try {
353
+ const call = await runMutationWithContext({
354
+ actionId: "toggle-state",
355
+ mutationPayload: { integrationId: currentIntegrationId, isEnabled: enabled },
356
+ operation: () => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/state`, {
357
+ method: "PUT",
358
+ headers: { "Content-Type": "application/json" },
359
+ body: JSON.stringify({ isEnabled: enabled })
360
+ }, { fallback: null })
361
+ });
362
+ if (call.ok) {
363
+ setDetail((prev) => prev ? { ...prev, state: { ...prev.state, isEnabled: enabled } } : prev);
364
+ flash(t("integrations.detail.stateUpdated"), "success");
365
+ } else {
366
+ flash(t("integrations.detail.stateError"), "error");
367
+ }
368
+ } catch {
107
369
  flash(t("integrations.detail.stateError"), "error");
370
+ } finally {
371
+ setIsTogglingState(false);
108
372
  }
109
- setIsTogglingState(false);
110
- }, [integrationId, t]);
111
- const handleSaveCredentials = React.useCallback(async () => {
112
- setIsSavingCreds(true);
113
- const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/credentials`, {
114
- method: "PUT",
115
- headers: { "Content-Type": "application/json" },
116
- body: JSON.stringify({ credentials: credValues })
117
- }, { fallback: null });
118
- if (call.ok) {
119
- flash(t("integrations.detail.credentials.saved"), "success");
120
- } else {
121
- flash(t("integrations.detail.credentials.saveError"), "error");
373
+ }, [resolveCurrentIntegrationId, runMutationWithContext, t]);
374
+ const handleSaveCredentials = React.useCallback(async (values) => {
375
+ const currentIntegrationId = resolveCurrentIntegrationId();
376
+ if (!currentIntegrationId) return;
377
+ setIsSavingCredentials(true);
378
+ try {
379
+ const call = await runMutationWithContext({
380
+ actionId: "save-credentials",
381
+ tabId: "credentials",
382
+ mutationPayload: { integrationId: currentIntegrationId, credentials: values },
383
+ operation: () => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/credentials`, {
384
+ method: "PUT",
385
+ headers: { "Content-Type": "application/json" },
386
+ body: JSON.stringify({ credentials: values })
387
+ }, { fallback: null })
388
+ });
389
+ if (call.ok) {
390
+ setCredValues(values);
391
+ setCredentialsFormKey((current) => current + 1);
392
+ flash(t("integrations.detail.credentials.saved"), "success");
393
+ return;
394
+ }
395
+ const result = call.result;
396
+ throw createCrudFormError(
397
+ result?.error ?? t("integrations.detail.credentials.saveError", "Failed to save credentials"),
398
+ result?.details?.fieldErrors,
399
+ { details: result?.details }
400
+ );
401
+ } finally {
402
+ setIsSavingCredentials(false);
122
403
  }
123
- setIsSavingCreds(false);
124
- }, [integrationId, credValues, t]);
404
+ }, [resolveCurrentIntegrationId, runMutationWithContext, t]);
125
405
  const handleVersionChange = React.useCallback(async (version) => {
126
- const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/version`, {
127
- method: "PUT",
128
- headers: { "Content-Type": "application/json" },
129
- body: JSON.stringify({ apiVersion: version })
130
- }, { fallback: null });
131
- if (call.ok) {
132
- setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev);
133
- flash(t("integrations.detail.version.saved"), "success");
134
- } else {
406
+ const currentIntegrationId = resolveCurrentIntegrationId();
407
+ if (!currentIntegrationId) return;
408
+ try {
409
+ const call = await runMutationWithContext({
410
+ actionId: "change-version",
411
+ tabId: "version",
412
+ mutationPayload: { integrationId: currentIntegrationId, apiVersion: version },
413
+ operation: () => apiCall(`/api/integrations/${encodeURIComponent(currentIntegrationId)}/version`, {
414
+ method: "PUT",
415
+ headers: { "Content-Type": "application/json" },
416
+ body: JSON.stringify({ apiVersion: version })
417
+ }, { fallback: null })
418
+ });
419
+ if (call.ok) {
420
+ setDetail((prev) => prev ? { ...prev, state: { ...prev.state, apiVersion: version } } : prev);
421
+ flash(t("integrations.detail.version.saved"), "success");
422
+ } else {
423
+ flash(t("integrations.detail.version.saveError"), "error");
424
+ }
425
+ } catch {
135
426
  flash(t("integrations.detail.version.saveError"), "error");
136
427
  }
137
- }, [integrationId, t]);
428
+ }, [resolveCurrentIntegrationId, runMutationWithContext, t]);
138
429
  const handleHealthCheck = React.useCallback(async () => {
430
+ const currentIntegrationId = resolveCurrentIntegrationId();
431
+ if (!currentIntegrationId) return;
139
432
  setIsCheckingHealth(true);
140
- const call = await apiCall(
141
- `/api/integrations/${encodeURIComponent(integrationId)}/health`,
142
- { method: "POST" },
143
- { fallback: null }
144
- );
145
- if (call.ok && call.result) {
146
- setDetail((prev) => prev ? {
147
- ...prev,
148
- state: {
149
- ...prev.state,
150
- lastHealthStatus: call.result.status,
151
- lastHealthCheckedAt: call.result.checkedAt
152
- }
153
- } : prev);
154
- } else {
433
+ try {
434
+ const call = await runMutationWithContext({
435
+ actionId: "run-health-check",
436
+ tabId: "health",
437
+ mutationPayload: { integrationId: currentIntegrationId },
438
+ operation: () => apiCall(
439
+ `/api/integrations/${encodeURIComponent(currentIntegrationId)}/health`,
440
+ { method: "POST" },
441
+ { fallback: null }
442
+ )
443
+ });
444
+ const result = call.result;
445
+ if (call.ok && result) {
446
+ setLatestHealthResult(result);
447
+ setDetail((prev) => prev ? {
448
+ ...prev,
449
+ state: {
450
+ ...prev.state,
451
+ lastHealthStatus: result.status,
452
+ lastHealthCheckedAt: result.checkedAt
453
+ }
454
+ } : prev);
455
+ void refreshLogs();
456
+ } else {
457
+ flash(t("integrations.detail.health.checkError"), "error");
458
+ }
459
+ } catch {
155
460
  flash(t("integrations.detail.health.checkError"), "error");
461
+ } finally {
462
+ setIsCheckingHealth(false);
156
463
  }
157
- setIsCheckingHealth(false);
158
- }, [integrationId, t]);
464
+ }, [refreshLogs, resolveCurrentIntegrationId, runMutationWithContext, t]);
465
+ const hasVersions = Boolean(detail?.integration.apiVersions?.length);
466
+ const integration = detail?.integration ?? null;
467
+ const state = detail?.state ?? null;
468
+ const editableCredentialFields = React.useMemo(
469
+ () => (detail?.integration.credentials?.fields ?? detail?.bundle?.credentials?.fields ?? []).filter(isEditableCredentialField),
470
+ [detail?.bundle?.credentials?.fields, detail?.integration.credentials?.fields]
471
+ );
472
+ const credentialFormFields = React.useMemo(
473
+ () => buildCredentialFields(editableCredentialFields),
474
+ [editableCredentialFields]
475
+ );
476
+ const credentialSchema = React.useMemo(() => z.object({}).passthrough().superRefine((rawValues, ctx) => {
477
+ const values = rawValues;
478
+ editableCredentialFields.forEach((field) => {
479
+ const value = values[field.key];
480
+ if (field.type === "boolean") {
481
+ if (value !== void 0 && value !== null && typeof value !== "boolean") {
482
+ ctx.addIssue({
483
+ code: z.ZodIssueCode.custom,
484
+ path: [field.key],
485
+ message: t("integrations.detail.credentials.validation.boolean", "Select a valid value.")
486
+ });
487
+ }
488
+ if (field.required && typeof value !== "boolean") {
489
+ ctx.addIssue({
490
+ code: z.ZodIssueCode.custom,
491
+ path: [field.key],
492
+ message: t("integrations.detail.credentials.validation.required", "{field} is required.", { field: field.label })
493
+ });
494
+ }
495
+ return;
496
+ }
497
+ if (value !== void 0 && value !== null && typeof value !== "string") {
498
+ ctx.addIssue({
499
+ code: z.ZodIssueCode.custom,
500
+ path: [field.key],
501
+ message: t("integrations.detail.credentials.validation.text", "Enter a valid value.")
502
+ });
503
+ return;
504
+ }
505
+ const normalizedValue = typeof value === "string" ? value : "";
506
+ if (field.required && normalizedValue.trim().length === 0) {
507
+ ctx.addIssue({
508
+ code: z.ZodIssueCode.custom,
509
+ path: [field.key],
510
+ message: t("integrations.detail.credentials.validation.required", "{field} is required.", { field: field.label })
511
+ });
512
+ }
513
+ if (normalizedValue.length > 2e4) {
514
+ ctx.addIssue({
515
+ code: z.ZodIssueCode.custom,
516
+ path: [field.key],
517
+ message: t("integrations.detail.credentials.validation.tooLong", "Value is too long.")
518
+ });
519
+ }
520
+ if (field.type === "select" && normalizedValue && field.options && !field.options.some((option) => option.value === normalizedValue)) {
521
+ ctx.addIssue({
522
+ code: z.ZodIssueCode.custom,
523
+ path: [field.key],
524
+ message: t("integrations.detail.credentials.validation.option", "Select one of the available options.")
525
+ });
526
+ }
527
+ });
528
+ }), [editableCredentialFields, t]);
529
+ const latestHealthLog = React.useMemo(() => logs.find(isHealthLog) ?? null, [logs]);
530
+ const healthMessage = latestHealthResult?.message ?? (typeof latestHealthLog?.payload?.message === "string" ? latestHealthLog.payload.message : null);
531
+ const healthDetailsSource = latestHealthResult?.details ?? extractHealthDetails(latestHealthLog?.payload);
532
+ const healthDetails = latestHealthLog?.code ? { ...healthDetailsSource, code: latestHealthLog.code } : healthDetailsSource;
533
+ const healthDetailEntries = Object.entries(healthDetails);
534
+ const healthStatusDescription = state?.lastHealthStatus ? t(
535
+ `integrations.detail.health.meaning.${state.lastHealthStatus}`,
536
+ state.lastHealthStatus === "healthy" ? "The provider responded successfully using the current credentials." : state.lastHealthStatus === "degraded" ? "The provider responded, but reported warnings or limited functionality." : integration?.id === "gateway_stripe" ? "Stripe rejected the last check. This usually means the secret key is invalid, missing required permissions, revoked, or Stripe was temporarily unavailable." : "The last check failed. This usually means invalid credentials, missing permissions, or a provider outage."
537
+ ) : null;
538
+ React.useEffect(() => {
539
+ setActiveTab(resolveRequestedIntegrationDetailTab(searchParams?.get("tab"), hasVersions, customTabIds));
540
+ }, [customTabIds, hasVersions, searchParams]);
541
+ const handleTabChange = React.useCallback((nextValue) => {
542
+ const currentIntegrationId = resolveCurrentIntegrationId();
543
+ const nextTab = resolveRequestedIntegrationDetailTab(nextValue, hasVersions, customTabIds);
544
+ setActiveTab(nextTab);
545
+ if (!currentIntegrationId) return;
546
+ const basePath = `/backend/integrations/${encodeURIComponent(currentIntegrationId)}`;
547
+ router.replace(nextTab === "credentials" ? basePath : `${basePath}?tab=${encodeURIComponent(nextTab)}`);
548
+ }, [customTabIds, hasVersions, resolveCurrentIntegrationId, router]);
159
549
  if (isLoading) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("integrations.detail.title") }) }) });
160
550
  if (error || !detail) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(ErrorMessage, { label: error ?? t("integrations.detail.loadError") }) }) });
161
- const { integration, state } = detail;
162
- const credFields = integration.credentials?.fields ?? detail.bundle?.credentials?.fields ?? [];
163
- const hasVersions = Boolean(integration.apiVersions?.length);
551
+ const resolvedIntegration = detail.integration;
552
+ const resolvedState = detail.state;
553
+ const CategoryIcon = resolvedIntegration.category ? CATEGORY_ICONS[resolvedIntegration.category] : null;
554
+ const showCredentialActions = activeTab === "credentials" && credentialFormFields.length > 0;
164
555
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { className: "space-y-6", children: [
165
- /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(Link, { href: "/backend/integrations", className: "text-sm text-muted-foreground hover:underline", children: t("integrations.detail.back") }) }),
166
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
167
- /* @__PURE__ */ jsxs("div", { children: [
168
- /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: integration.title }),
169
- integration.description && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1", children: integration.description }),
170
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 mt-2", children: [
171
- integration.category && /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: integration.category }),
172
- integration.hub && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: integration.hub })
173
- ] })
174
- ] }),
175
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
176
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: state.isEnabled ? t("integrations.detail.enable") : t("integrations.detail.disable") }),
177
- /* @__PURE__ */ jsx(
178
- Switch,
179
- {
180
- checked: state.isEnabled,
181
- disabled: isTogglingState,
182
- onCheckedChange: (checked) => void handleToggleState(checked)
183
- }
184
- )
556
+ /* @__PURE__ */ jsx(
557
+ FormHeader,
558
+ {
559
+ backHref: "/backend/integrations",
560
+ title: resolvedIntegration.title,
561
+ actions: {
562
+ cancelHref: showCredentialActions ? "/backend/integrations" : void 0,
563
+ submit: showCredentialActions ? {
564
+ formId: credentialsFormId,
565
+ pending: isSavingCredentials,
566
+ label: t("integrations.detail.credentials.save", "Save credentials"),
567
+ pendingLabel: t("ui.forms.status.saving", "Saving...")
568
+ } : void 0
569
+ }
570
+ }
571
+ ),
572
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
573
+ resolvedIntegration.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: resolvedIntegration.description }) : null,
574
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-4 text-sm text-muted-foreground", children: [
575
+ resolvedIntegration.category ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
576
+ CategoryIcon ? /* @__PURE__ */ jsx(CategoryIcon, { className: "h-4 w-4" }) : null,
577
+ /* @__PURE__ */ jsx("span", { children: formatTypeLabel(resolvedIntegration.category) })
578
+ ] }) : null,
579
+ resolvedIntegration.hub ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
580
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground/80", children: t("integrations.detail.hub.label", "Hub") }),
581
+ /* @__PURE__ */ jsx("span", { children: formatTypeLabel(resolvedIntegration.hub) })
582
+ ] }) : null
185
583
  ] })
186
584
  ] }),
187
- /* @__PURE__ */ jsxs(Tabs, { defaultValue: "credentials", children: [
585
+ /* @__PURE__ */ jsx("section", { className: "rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-4", children: [
586
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
587
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: t("integrations.detail.state.label", "State") }),
588
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: resolvedState.isEnabled ? t("integrations.detail.state.enabled", "Enabled") : t("integrations.detail.state.disabled", "Disabled") })
589
+ ] }),
590
+ /* @__PURE__ */ jsx(
591
+ Switch,
592
+ {
593
+ checked: resolvedState.isEnabled,
594
+ disabled: isTogglingState,
595
+ onCheckedChange: (checked) => void handleToggleState(checked)
596
+ }
597
+ )
598
+ ] }) }),
599
+ stackedDetailWidgets.length > 0 ? /* @__PURE__ */ jsx("section", { className: "space-y-4", children: /* @__PURE__ */ jsx(
600
+ InjectionSpot,
601
+ {
602
+ spotId: detailWidgetSpotId,
603
+ context: injectionContext,
604
+ data: detail,
605
+ onDataChange: (next) => setDetail(next),
606
+ widgetsOverride: stackedDetailWidgets
607
+ }
608
+ ) }) : null,
609
+ groupedDetailWidgets.length > 0 ? /* @__PURE__ */ jsx("section", { className: "grid gap-4 lg:grid-cols-2", children: groupedDetailWidgets.map((widget) => /* @__PURE__ */ jsxs(
610
+ Card,
611
+ {
612
+ className: widget.placement?.column === 2 ? "lg:col-start-2" : void 0,
613
+ children: [
614
+ /* @__PURE__ */ jsxs(CardHeader, { children: [
615
+ /* @__PURE__ */ jsx(CardTitle, { children: widget.placement?.groupLabel ? t(widget.placement.groupLabel, widget.module.metadata.title) : widget.module.metadata.title }),
616
+ widget.placement?.groupDescription ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: widget.placement.groupDescription }) : null
617
+ ] }),
618
+ /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(
619
+ widget.module.Widget,
620
+ {
621
+ context: injectionContext,
622
+ data: detail,
623
+ onDataChange: (next) => setDetail(next)
624
+ }
625
+ ) })
626
+ ]
627
+ },
628
+ widget.widgetId
629
+ )) }) : null,
630
+ /* @__PURE__ */ jsxs(Tabs, { value: activeTab, onValueChange: handleTabChange, className: "space-y-4", children: [
188
631
  /* @__PURE__ */ jsxs(TabsList, { children: [
189
632
  /* @__PURE__ */ jsx(TabsTrigger, { value: "credentials", children: t("integrations.detail.tabs.credentials") }),
190
- hasVersions && /* @__PURE__ */ jsx(TabsTrigger, { value: "version", children: t("integrations.detail.tabs.version") }),
633
+ hasVersions ? /* @__PURE__ */ jsx(TabsTrigger, { value: "version", children: t("integrations.detail.tabs.version") }) : null,
191
634
  /* @__PURE__ */ jsx(TabsTrigger, { value: "health", children: t("integrations.detail.tabs.health") }),
192
- /* @__PURE__ */ jsx(TabsTrigger, { value: "logs", children: t("integrations.detail.tabs.logs") })
635
+ /* @__PURE__ */ jsx(TabsTrigger, { value: "logs", children: t("integrations.detail.tabs.logs") }),
636
+ injectedTabs.map((tab) => /* @__PURE__ */ jsx(TabsTrigger, { value: tab.id, children: tab.label }, tab.id))
193
637
  ] }),
194
- /* @__PURE__ */ jsxs(TabsContent, { value: "credentials", className: "space-y-4 mt-4", children: [
195
- detail.bundle && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800", children: t("integrations.detail.credentials.bundleShared", { bundle: detail.bundle.title }) }),
196
- credFields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: t("integrations.detail.credentials.notConfigured") }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsxs(CardContent, { className: "pt-6 space-y-4", children: [
197
- credFields.filter(isEditableCredentialField).map((field) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
198
- /* @__PURE__ */ jsxs("label", { className: "text-sm font-medium", children: [
199
- field.label,
200
- field.required && /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-0.5", children: "*" })
201
- ] }),
202
- field.type === "select" && field.options ? /* @__PURE__ */ jsxs(
203
- "select",
204
- {
205
- className: "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm",
206
- value: credValues[field.key] ?? "",
207
- onChange: (e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value })),
208
- children: [
209
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
210
- field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
211
- ]
212
- }
213
- ) : field.type === "boolean" ? /* @__PURE__ */ jsx(
214
- Switch,
215
- {
216
- checked: Boolean(credValues[field.key]),
217
- onCheckedChange: (checked) => setCredValues((prev) => ({ ...prev, [field.key]: checked }))
218
- }
219
- ) : /* @__PURE__ */ jsx(
220
- Input,
221
- {
222
- type: field.type === "secret" ? "password" : "text",
223
- placeholder: field.placeholder,
224
- value: credValues[field.key] ?? "",
225
- onChange: (e) => setCredValues((prev) => ({ ...prev, [field.key]: e.target.value }))
226
- }
227
- )
228
- ] }, field.key)),
229
- /* @__PURE__ */ jsxs(Button, { type: "button", onClick: () => void handleSaveCredentials(), disabled: isSavingCreds, children: [
230
- isSavingCreds ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
231
- t("integrations.detail.credentials.save")
232
- ] })
233
- ] }) })
234
- ] }),
235
- hasVersions && /* @__PURE__ */ jsx(TabsContent, { value: "version", className: "space-y-4 mt-4", children: /* @__PURE__ */ jsxs(Card, { children: [
638
+ /* @__PURE__ */ jsx(TabsContent, { value: "credentials", className: "mt-0", children: /* @__PURE__ */ jsxs("section", { className: "space-y-4 rounded-lg border bg-card p-6", children: [
639
+ detail.bundle ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-blue-200 bg-blue-50 p-3 text-sm text-blue-800", children: t("integrations.detail.credentials.bundleShared", { bundle: detail.bundle.title }) }) : null,
640
+ credentialFormFields.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("integrations.detail.credentials.notConfigured") }) : /* @__PURE__ */ jsx(
641
+ CrudForm,
642
+ {
643
+ formId: credentialsFormId,
644
+ entityId: "integrations.integration",
645
+ schema: credentialSchema,
646
+ fields: credentialFormFields,
647
+ initialValues: credValues,
648
+ onSubmit: handleSaveCredentials,
649
+ embedded: true,
650
+ hideFooterActions: true
651
+ },
652
+ `${resolvedIntegration.id}:${credentialsFormKey}`
653
+ )
654
+ ] }) }),
655
+ hasVersions ? /* @__PURE__ */ jsx(TabsContent, { value: "version", className: "mt-0 space-y-4", children: /* @__PURE__ */ jsxs(Card, { children: [
236
656
  /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: t("integrations.detail.version.select") }) }),
237
- /* @__PURE__ */ jsx(CardContent, { className: "space-y-3", children: integration.apiVersions.map((v) => {
238
- const isSelected = (state.apiVersion ?? integration.apiVersions.find((x) => x.status === "stable")?.id) === v.id;
657
+ /* @__PURE__ */ jsx(CardContent, { className: "space-y-3", children: resolvedIntegration.apiVersions?.map((version) => {
658
+ const stableVersion = resolvedIntegration.apiVersions?.find((item) => item.status === "stable")?.id;
659
+ const isSelected = (resolvedState.apiVersion ?? stableVersion) === version.id;
239
660
  return /* @__PURE__ */ jsxs(
240
661
  "div",
241
662
  {
242
- className: `flex items-center justify-between rounded-lg border p-3 cursor-pointer transition-colors ${isSelected ? "border-primary bg-primary/5" : "hover:bg-muted/50"}`,
243
- onClick: () => void handleVersionChange(v.id),
663
+ className: `flex cursor-pointer items-center justify-between rounded-lg border p-3 transition-colors ${isSelected ? "border-primary bg-primary/5" : "hover:bg-muted/50"}`,
664
+ onClick: () => void handleVersionChange(version.id),
244
665
  children: [
245
666
  /* @__PURE__ */ jsxs("div", { children: [
246
- /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: v.label ?? v.id }),
667
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: version.label ?? version.id }),
247
668
  /* @__PURE__ */ jsx(
248
669
  Badge,
249
670
  {
250
- variant: v.status === "stable" ? "default" : v.status === "deprecated" ? "destructive" : "secondary",
671
+ variant: version.status === "stable" ? "default" : version.status === "deprecated" ? "destructive" : "secondary",
251
672
  className: "ml-2",
252
- children: t(`integrations.detail.version.${v.status}`)
673
+ children: t(`integrations.detail.version.${version.status}`)
253
674
  }
254
675
  ),
255
- v.status === "deprecated" && v.sunsetAt && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-2", children: t("integrations.detail.version.sunsetAt", { date: new Date(v.sunsetAt).toLocaleDateString() }) })
676
+ version.status === "deprecated" && version.sunsetAt ? /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: t("integrations.detail.version.sunsetAt", { date: new Date(version.sunsetAt).toLocaleDateString() }) }) : null
256
677
  ] }),
257
- isSelected && /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t("integrations.detail.version.current") })
678
+ isSelected ? /* @__PURE__ */ jsx(Badge, { variant: "outline", children: t("integrations.detail.version.current") }) : null
258
679
  ]
259
680
  },
260
- v.id
681
+ version.id
261
682
  );
262
683
  }) })
263
- ] }) }),
264
- /* @__PURE__ */ jsx(TabsContent, { value: "health", className: "space-y-4 mt-4", children: /* @__PURE__ */ jsxs(Card, { children: [
684
+ ] }) }) : null,
685
+ /* @__PURE__ */ jsx(TabsContent, { value: "health", className: "mt-0 space-y-4", children: /* @__PURE__ */ jsxs(Card, { children: [
265
686
  /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
266
687
  /* @__PURE__ */ jsx(CardTitle, { children: t("integrations.detail.health.title") }),
267
- /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleHealthCheck(), disabled: isCheckingHealth, children: [
268
- isCheckingHealth ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : null,
269
- isCheckingHealth ? t("integrations.detail.health.checking") : t("integrations.detail.health.check")
270
- ] })
688
+ /* @__PURE__ */ jsxs(
689
+ Button,
690
+ {
691
+ type: "button",
692
+ variant: "outline",
693
+ size: "sm",
694
+ onClick: () => void handleHealthCheck(),
695
+ disabled: isCheckingHealth,
696
+ children: [
697
+ isCheckingHealth ? /* @__PURE__ */ jsx(Spinner, { className: "mr-2 h-4 w-4" }) : /* @__PURE__ */ jsx(Zap, { className: "mr-2 h-4 w-4" }),
698
+ isCheckingHealth ? t("integrations.detail.health.checking") : t("integrations.detail.health.check")
699
+ ]
700
+ }
701
+ )
271
702
  ] }) }),
272
703
  /* @__PURE__ */ jsxs(CardContent, { className: "space-y-3", children: [
273
704
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
@@ -275,39 +706,140 @@ function IntegrationDetailPage() {
275
706
  t("integrations.detail.health.title"),
276
707
  ":"
277
708
  ] }),
278
- state.lastHealthStatus ? /* @__PURE__ */ jsx(Badge, { className: HEALTH_STATUS_STYLES[state.lastHealthStatus] ?? "", children: t(`integrations.detail.health.${state.lastHealthStatus}`) }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("integrations.detail.health.unknown") })
709
+ resolvedState.lastHealthStatus ? /* @__PURE__ */ jsx(Badge, { className: HEALTH_STATUS_STYLES[resolvedState.lastHealthStatus] ?? "", children: t(`integrations.detail.health.${resolvedState.lastHealthStatus}`) }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("integrations.detail.health.unknown") })
279
710
  ] }),
280
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: state.lastHealthCheckedAt ? t("integrations.detail.health.lastChecked", { date: new Date(state.lastHealthCheckedAt).toLocaleString() }) : t("integrations.detail.health.neverChecked") })
711
+ healthStatusDescription ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: healthStatusDescription }) : null,
712
+ healthMessage ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-muted/30 p-3", children: [
713
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("integrations.detail.health.lastResult", "Last result") }),
714
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm", children: healthMessage })
715
+ ] }) : null,
716
+ healthDetailEntries.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-3", children: [
717
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("integrations.detail.health.details", "Details") }),
718
+ /* @__PURE__ */ jsx("dl", { className: "mt-3 grid gap-3 sm:grid-cols-2", children: healthDetailEntries.map(([key, value]) => /* @__PURE__ */ jsxs("div", { children: [
719
+ /* @__PURE__ */ jsx("dt", { className: "text-xs font-medium text-muted-foreground", children: key }),
720
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 text-sm", children: formatHealthValue(value) })
721
+ ] }, key)) })
722
+ ] }) : null,
723
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolvedState.lastHealthCheckedAt ? t("integrations.detail.health.lastChecked", { date: new Date(resolvedState.lastHealthCheckedAt).toLocaleString() }) : t("integrations.detail.health.neverChecked") })
281
724
  ] })
282
725
  ] }) }),
283
- /* @__PURE__ */ jsxs(TabsContent, { value: "logs", className: "space-y-4 mt-4", children: [
284
- /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxs(
285
- "select",
286
- {
287
- className: "flex h-9 rounded-md border border-input bg-transparent px-3 py-1 text-sm",
288
- value: logLevel,
289
- onChange: (e) => setLogLevel(e.target.value),
290
- children: [
291
- /* @__PURE__ */ jsx("option", { value: "", children: t("integrations.detail.logs.level.all") }),
292
- /* @__PURE__ */ jsx("option", { value: "info", children: t("integrations.detail.logs.level.info") }),
293
- /* @__PURE__ */ jsx("option", { value: "warn", children: t("integrations.detail.logs.level.warn") }),
294
- /* @__PURE__ */ jsx("option", { value: "error", children: t("integrations.detail.logs.level.error") })
295
- ]
296
- }
297
- ) }),
298
- isLoadingLogs ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsx(Spinner, {}) }) : logs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm py-4", children: t("integrations.detail.logs.empty") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
726
+ /* @__PURE__ */ jsxs(TabsContent, { value: "logs", className: "mt-0 space-y-4", children: [
727
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ jsxs("div", { className: "relative inline-flex", children: [
728
+ /* @__PURE__ */ jsxs(
729
+ "select",
730
+ {
731
+ className: "h-11 min-w-40 appearance-none rounded-xl border border-border bg-card pl-4 pr-11 text-sm font-medium text-foreground shadow-sm transition-colors focus-visible:border-ring focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
732
+ value: logLevel,
733
+ onChange: (event) => setLogLevel(event.target.value),
734
+ children: [
735
+ /* @__PURE__ */ jsx("option", { value: "", children: t("integrations.detail.logs.level.all") }),
736
+ /* @__PURE__ */ jsx("option", { value: "info", children: t("integrations.detail.logs.level.info") }),
737
+ /* @__PURE__ */ jsx("option", { value: "warn", children: t("integrations.detail.logs.level.warn") }),
738
+ /* @__PURE__ */ jsx("option", { value: "error", children: t("integrations.detail.logs.level.error") })
739
+ ]
740
+ }
741
+ ),
742
+ /* @__PURE__ */ jsx(ChevronDown, { className: "pointer-events-none absolute right-4 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" })
743
+ ] }) }),
744
+ isLoadingLogs ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-8", children: /* @__PURE__ */ jsx(Spinner, {}) }) : logs.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-sm text-muted-foreground", children: t("integrations.detail.logs.empty") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
299
745
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
300
746
  /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: t("integrations.detail.logs.columns.time") }),
301
747
  /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: t("integrations.detail.logs.columns.level") }),
302
748
  /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left font-medium", children: t("integrations.detail.logs.columns.message") })
303
749
  ] }) }),
304
- /* @__PURE__ */ jsx("tbody", { children: logs.map((log) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
305
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2 text-muted-foreground whitespace-nowrap", children: new Date(log.createdAt).toLocaleString() }),
306
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: LOG_LEVEL_STYLES[log.level] ?? "", children: log.level }) }),
307
- /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: log.message })
308
- ] }, log.id)) })
750
+ /* @__PURE__ */ jsx("tbody", { children: logs.map((log) => {
751
+ const isExpanded = expandedLogId === log.id;
752
+ const metadataEntries = [
753
+ ["Time", new Date(log.createdAt).toLocaleString()],
754
+ ["Level", log.level],
755
+ ["Code", log.code ?? null],
756
+ ["Run ID", log.runId ?? null],
757
+ ["Entity Type", log.scopeEntityType ?? null],
758
+ ["Entity ID", log.scopeEntityId ?? null]
759
+ ].filter((entry) => typeof entry[1] === "string" && entry[1].trim().length > 0);
760
+ const { inlineEntries, nestedEntries } = splitLogPayload(log.payload);
761
+ return /* @__PURE__ */ jsxs(React.Fragment, { children: [
762
+ /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
763
+ /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-4 py-2 text-muted-foreground", children: new Date(log.createdAt).toLocaleString() }),
764
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: LOG_LEVEL_STYLES[log.level] ?? "", children: log.level }) }),
765
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2", children: /* @__PURE__ */ jsxs(
766
+ Button,
767
+ {
768
+ type: "button",
769
+ variant: "ghost",
770
+ size: "sm",
771
+ className: "h-auto w-full justify-start gap-2 px-0 py-0 text-left hover:bg-transparent",
772
+ onClick: () => setExpandedLogId((current) => current === log.id ? null : log.id),
773
+ children: [
774
+ isExpanded ? /* @__PURE__ */ jsx(ChevronDown, { className: "h-4 w-4 shrink-0" }) : /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 shrink-0" }),
775
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: log.message })
776
+ ]
777
+ }
778
+ ) })
779
+ ] }),
780
+ isExpanded ? /* @__PURE__ */ jsx("tr", { className: "border-b bg-muted/20 last:border-0", children: /* @__PURE__ */ jsx("td", { colSpan: 3, className: "px-4 py-4", children: /* @__PURE__ */ jsx("div", { className: "space-y-4 rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-[minmax(0,1.1fr)_minmax(0,0.9fr)]", children: [
781
+ /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
782
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
783
+ /* @__PURE__ */ jsxs("div", { children: [
784
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("integrations.detail.logs.details.summary", "Summary") }),
785
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm font-medium", children: log.message })
786
+ ] }),
787
+ metadataEntries.length > 0 ? /* @__PURE__ */ jsx("dl", { className: "grid gap-3 sm:grid-cols-2", children: metadataEntries.map(([label, value]) => /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [
788
+ /* @__PURE__ */ jsx("dt", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: label }),
789
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 break-all text-sm", children: value })
790
+ ] }, label)) }) : null
791
+ ] }),
792
+ inlineEntries.length > 0 ? /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
793
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("integrations.detail.logs.details.fields", "Fields") }),
794
+ /* @__PURE__ */ jsx("dl", { className: "grid gap-3 sm:grid-cols-2", children: inlineEntries.map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "rounded-md border bg-muted/30 px-3 py-2", children: [
795
+ /* @__PURE__ */ jsx("dt", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: formatLogDetailLabel(key) }),
796
+ /* @__PURE__ */ jsx("dd", { className: "mt-1 break-words text-sm", children: formatLogPrimitiveValue(value) })
797
+ ] }, key)) })
798
+ ] }) : null
799
+ ] }),
800
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
801
+ nestedEntries.map(([key, value]) => /* @__PURE__ */ jsx(
802
+ JsonDisplay,
803
+ {
804
+ data: value,
805
+ title: formatLogDetailLabel(key),
806
+ defaultExpanded: true,
807
+ maxInitialDepth: 1,
808
+ theme: "dark",
809
+ maxHeight: "16rem",
810
+ className: "p-4"
811
+ },
812
+ key
813
+ )),
814
+ log.payload && nestedEntries.length === 0 ? /* @__PURE__ */ jsx(
815
+ JsonDisplay,
816
+ {
817
+ data: log.payload,
818
+ title: t("integrations.detail.logs.details.payload", "Payload"),
819
+ defaultExpanded: true,
820
+ maxInitialDepth: 1,
821
+ theme: "dark",
822
+ maxHeight: "16rem",
823
+ className: "p-4"
824
+ }
825
+ ) : null,
826
+ !log.payload ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed px-4 py-6 text-sm text-muted-foreground", children: t("integrations.detail.logs.details.noPayload", "No structured payload was stored for this log entry.") }) : null
827
+ ] })
828
+ ] }) }) }) }) : null
829
+ ] }, log.id);
830
+ }) })
309
831
  ] }) })
310
- ] })
832
+ ] }),
833
+ injectedTabs.map((tab) => /* @__PURE__ */ jsx(TabsContent, { value: tab.id, className: "mt-0 space-y-4", children: /* @__PURE__ */ jsx(
834
+ InjectionSpot,
835
+ {
836
+ spotId: detailWidgetSpotId,
837
+ context: injectionContext,
838
+ data: detail,
839
+ onDataChange: (next) => setDetail(next),
840
+ widgetsOverride: tab.widgets
841
+ }
842
+ ) }, tab.id))
311
843
  ] })
312
844
  ] }) });
313
845
  }