@hartl-services/medusa-base 0.4.0 → 1.1.0

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 (258) hide show
  1. package/.medusa/server/src/admin/index.js +1657 -111
  2. package/.medusa/server/src/admin/index.mjs +1647 -101
  3. package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +24 -0
  4. package/.medusa/server/src/api/admin/email/messages/[id]/route.js +12 -0
  5. package/.medusa/server/src/api/admin/email/messages/route.js +12 -0
  6. package/.medusa/server/src/api/admin/email/terms-versions/[id]/download/route.js +16 -0
  7. package/.medusa/server/src/api/admin/email/terms-versions/route.js +47 -0
  8. package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/download/route.js +21 -0
  9. package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/regenerate/route.js +38 -0
  10. package/.medusa/server/src/api/admin/orders/[id]/documents/regenerate/route.js +32 -0
  11. package/.medusa/server/src/api/admin/orders/[id]/documents/route.js +36 -0
  12. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/download/route.js +24 -0
  13. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/retry/route.js +44 -0
  14. package/.medusa/server/src/api/admin/orders/[id]/packing-lists/route.js +82 -0
  15. package/.medusa/server/src/api/document-download-response.js +16 -0
  16. package/.medusa/server/src/api/document-response.js +23 -0
  17. package/.medusa/server/src/api/files/[asset]/route.js +24 -0
  18. package/.medusa/server/src/api/files/file-response.js +43 -0
  19. package/.medusa/server/src/api/files/gateway-options.js +31 -0
  20. package/.medusa/server/src/api/files/private/[token]/route.js +29 -0
  21. package/.medusa/server/src/api/files/upload/[token]/route.js +66 -0
  22. package/.medusa/server/src/api/middlewares.js +168 -1
  23. package/.medusa/server/src/api/packing-list-response.js +22 -0
  24. package/.medusa/server/src/api/route.js +27 -0
  25. package/.medusa/server/src/api/store/documents/[id]/download/route.js +21 -0
  26. package/.medusa/server/src/api/store/documents/[id]/route.js +15 -0
  27. package/.medusa/server/src/api/store/documents/route.js +11 -0
  28. package/.medusa/server/src/api/terms-download-response.js +12 -0
  29. package/.medusa/server/src/documents/config/index.js +18 -0
  30. package/.medusa/server/src/documents/paths/documents.js +6 -0
  31. package/.medusa/server/src/documents/paths/index.js +18 -0
  32. package/.medusa/server/src/documents/plugin-features.js +38 -0
  33. package/.medusa/server/src/documents/schemas/documents.js +18 -0
  34. package/.medusa/server/src/documents/schemas/index.js +18 -0
  35. package/.medusa/server/src/documents/scripts/verify-documents-file-storage.js +55 -0
  36. package/.medusa/server/src/documents/templates/financial-document.js +160 -0
  37. package/.medusa/server/src/documents/templates/format.js +73 -0
  38. package/.medusa/server/src/documents/templates/html-factory.js +42 -0
  39. package/.medusa/server/src/documents/templates/html-utils.js +23 -0
  40. package/.medusa/server/src/documents/templates/layout.js +142 -0
  41. package/.medusa/server/src/documents/templates/packing-list.js +52 -0
  42. package/.medusa/server/src/documents/types/admin-api.js +3 -0
  43. package/.medusa/server/src/documents/types/document-summary.js +3 -0
  44. package/.medusa/server/src/documents/types/documents.js +3 -0
  45. package/.medusa/server/src/documents/types/html-factory.js +6 -0
  46. package/.medusa/server/src/documents/types/index.js +22 -0
  47. package/.medusa/server/src/documents/types/store-api.js +3 -0
  48. package/.medusa/server/src/documents/utils/document-merchant.js +22 -0
  49. package/.medusa/server/src/documents/utils/document-render-settings.js +59 -0
  50. package/.medusa/server/src/documents/utils/html-to-pdf.js +61 -0
  51. package/.medusa/server/src/documents/utils/private-file-storage.js +64 -0
  52. package/.medusa/server/src/documents/utils/resolve-services.js +7 -0
  53. package/.medusa/server/src/email/application/handle-invite-event.js +318 -0
  54. package/.medusa/server/src/email/application/handle-password-reset-event.js +366 -0
  55. package/.medusa/server/src/email/config/index.js +84 -0
  56. package/.medusa/server/src/email/config/notification-module.js +20 -0
  57. package/.medusa/server/src/email/domain/business-keys.js +50 -0
  58. package/.medusa/server/src/email/domain/documents-fact-fingerprint.js +7 -0
  59. package/.medusa/server/src/email/domain/event-schemas.js +36 -0
  60. package/.medusa/server/src/email/domain/failure-classification.js +53 -0
  61. package/.medusa/server/src/email/domain/order-summary.js +103 -0
  62. package/.medusa/server/src/email/domain/refund-summary.js +43 -0
  63. package/.medusa/server/src/email/domain/return-summary.js +118 -0
  64. package/.medusa/server/src/email/domain/shipment-summary.js +74 -0
  65. package/.medusa/server/src/email/plugin-features.js +39 -0
  66. package/.medusa/server/src/email/rendering/default-templates.js +719 -0
  67. package/.medusa/server/src/email/rendering/load-email-shop-details.js +36 -0
  68. package/.medusa/server/src/email/rendering/render-email.js +50 -0
  69. package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +67 -0
  70. package/.medusa/server/src/email/types/email-kinds.js +18 -0
  71. package/.medusa/server/src/email/types/index.js +21 -0
  72. package/.medusa/server/src/email/types/shop-configuration.js +3 -0
  73. package/.medusa/server/src/email/types/smtp-configuration.js +3 -0
  74. package/.medusa/server/src/email/types/template-data.js +232 -0
  75. package/.medusa/server/src/files/config/index.js +256 -0
  76. package/.medusa/server/src/files/gateway/index.js +115 -0
  77. package/.medusa/server/src/files/types/index.js +6 -0
  78. package/.medusa/server/src/files/types/public.js +6 -0
  79. package/.medusa/server/src/jobs/document-fact-publication-reconciliation.js +18 -0
  80. package/.medusa/server/src/jobs/document-resolution-reconciliation.js +20 -0
  81. package/.medusa/server/src/jobs/packing-list-reconciliation.js +49 -0
  82. package/.medusa/server/src/jobs/reconcile-email-attempts.js +63 -0
  83. package/.medusa/server/src/jobs/reconcile-pending-email-messages.js +41 -0
  84. package/.medusa/server/src/jobs/retry-email-messages.js +27 -0
  85. package/.medusa/server/src/migration-scripts/dismiss-orphaned-cart-promotion-links.js +26 -0
  86. package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +97 -0
  87. package/.medusa/server/src/modules/documents/domain/document-number.js +36 -0
  88. package/.medusa/server/src/modules/documents/domain/document-resolution.js +24 -0
  89. package/.medusa/server/src/modules/documents/domain/issuance-rules.js +37 -0
  90. package/.medusa/server/src/modules/documents/domain/merchant.js +86 -0
  91. package/.medusa/server/src/modules/documents/domain/options.js +40 -0
  92. package/.medusa/server/src/modules/documents/domain/packing-list.js +90 -0
  93. package/.medusa/server/src/modules/documents/domain/snapshot-normalization.js +17 -0
  94. package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +66 -0
  95. package/.medusa/server/src/modules/documents/index.js +15 -0
  96. package/.medusa/server/src/modules/documents/loaders/assert-private-file-storage.js +20 -0
  97. package/.medusa/server/src/modules/documents/migrations/Migration20260827061749.js +75 -0
  98. package/.medusa/server/src/modules/documents/migrations/Migration20260830111207.js +28 -0
  99. package/.medusa/server/src/modules/documents/models/document-artifact.js +25 -0
  100. package/.medusa/server/src/modules/documents/models/document-number-sequence.js +29 -0
  101. package/.medusa/server/src/modules/documents/models/document.js +82 -0
  102. package/.medusa/server/src/modules/documents/models/order-cancellation-resolution.js +51 -0
  103. package/.medusa/server/src/modules/documents/models/packing-list-revision.js +36 -0
  104. package/.medusa/server/src/modules/documents/models/packing-list.js +27 -0
  105. package/.medusa/server/src/modules/documents/models/refund-document-resolution.js +66 -0
  106. package/.medusa/server/src/modules/documents/models/shipment-invoice-assignment.js +51 -0
  107. package/.medusa/server/src/modules/documents/models/shipment-invoice-coordinator.js +44 -0
  108. package/.medusa/server/src/modules/documents/service.js +1701 -0
  109. package/.medusa/server/src/modules/email/domain/attachment.js +25 -0
  110. package/.medusa/server/src/modules/email/domain/message-state.js +69 -0
  111. package/.medusa/server/src/modules/email/index.js +13 -0
  112. package/.medusa/server/src/modules/email/migrations/Migration20260826173422.js +32 -0
  113. package/.medusa/server/src/modules/email/migrations/Migration20260827172945.js +16 -0
  114. package/.medusa/server/src/modules/email/migrations/Migration20260827194804.js +22 -0
  115. package/.medusa/server/src/modules/email/migrations/Migration20260906195205.js +16 -0
  116. package/.medusa/server/src/modules/email/models/email-attempt.js +56 -0
  117. package/.medusa/server/src/modules/email/models/email-message.js +71 -0
  118. package/.medusa/server/src/modules/email/models/email-terms-version.js +37 -0
  119. package/.medusa/server/src/modules/email/service.js +1285 -0
  120. package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +4 -3
  121. package/.medusa/server/src/providers/s3-cache/cache/atomic-write.js +25 -0
  122. package/.medusa/server/src/providers/s3-cache/cache/cache-manager.js +115 -0
  123. package/.medusa/server/src/providers/s3-cache/cache/keyed-lock.js +28 -0
  124. package/.medusa/server/src/providers/s3-cache/index.js +8 -0
  125. package/.medusa/server/src/providers/s3-cache/object-key.js +53 -0
  126. package/.medusa/server/src/providers/s3-cache/s3-cache-file.js +229 -0
  127. package/.medusa/server/src/providers/s3-cache/s3-object-store.js +86 -0
  128. package/.medusa/server/src/providers/smtp/index.js +8 -0
  129. package/.medusa/server/src/providers/smtp/service.js +236 -0
  130. package/.medusa/server/src/providers/smtp/types.js +11 -0
  131. package/.medusa/server/src/public/config.js +186 -0
  132. package/.medusa/server/src/public/descriptor.js +3 -1
  133. package/.medusa/server/src/public/gateway.js +19 -0
  134. package/.medusa/server/src/public/instrumentation.js +56 -0
  135. package/.medusa/server/src/public/shop-config.js +12 -0
  136. package/.medusa/server/src/public/shop-master-data.js +2 -2
  137. package/.medusa/server/src/public/shop-types.js +24 -0
  138. package/.medusa/server/src/shop-config/build-food-shop-config.js +252 -0
  139. package/.medusa/server/src/shop-config/options.js +75 -0
  140. package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +26 -0
  141. package/.medusa/server/src/subscribers/documents-order-canceled.js +24 -0
  142. package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +26 -0
  143. package/.medusa/server/src/subscribers/documents-refund-ready.js +24 -0
  144. package/.medusa/server/src/subscribers/documents-shipment-created.js +36 -0
  145. package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +26 -0
  146. package/.medusa/server/src/subscribers/email-order-canceled.js +28 -0
  147. package/.medusa/server/src/subscribers/email-shipment-created.js +32 -0
  148. package/.medusa/server/src/subscribers/order-placed.js +27 -0
  149. package/.medusa/server/src/subscribers/packing-list-allocation-revised.js +32 -0
  150. package/.medusa/server/src/subscribers/packing-list-fulfillment-canceled.js +54 -0
  151. package/.medusa/server/src/subscribers/packing-list-fulfillment-created.js +31 -0
  152. package/.medusa/server/src/subscribers/password-reset.js +23 -0
  153. package/.medusa/server/src/subscribers/payment-refunded.js +38 -0
  154. package/.medusa/server/src/subscribers/return-received.js +27 -0
  155. package/.medusa/server/src/subscribers/return-requested.js +27 -0
  156. package/.medusa/server/src/subscribers/user-invite.js +23 -0
  157. package/.medusa/server/src/utils/cart-promotion-links.js +101 -0
  158. package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +246 -0
  159. package/.medusa/server/src/workflows/documents/issue-document.js +165 -0
  160. package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +521 -0
  161. package/.medusa/server/src/workflows/documents/issue-packing-list.js +390 -0
  162. package/.medusa/server/src/workflows/documents/publish-document-facts.js +53 -0
  163. package/.medusa/server/src/workflows/documents/reconcile-document-resolutions.js +48 -0
  164. package/.medusa/server/src/workflows/documents/reconcile-order-cancellation.js +92 -0
  165. package/.medusa/server/src/workflows/documents/regenerate-issued-document.js +61 -0
  166. package/.medusa/server/src/workflows/email/dispatch-email-message.js +301 -0
  167. package/.medusa/server/src/workflows/email/documents-required-facts.js +30 -0
  168. package/.medusa/server/src/workflows/email/prepare-affiliate-reminder-email.js +58 -0
  169. package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +102 -0
  170. package/.medusa/server/src/workflows/email/prepare-order-received-email.js +82 -0
  171. package/.medusa/server/src/workflows/email/prepare-refund-email.js +71 -0
  172. package/.medusa/server/src/workflows/email/prepare-return-email.js +136 -0
  173. package/.medusa/server/src/workflows/email/prepare-shipment-email.js +115 -0
  174. package/.medusa/server/src/workflows/email/retry-email-messages.js +31 -0
  175. package/.medusa/server/src/workflows/hooks/dismiss-deleted-promotion-cart-links.js +23 -0
  176. package/README.md +1777 -8
  177. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.d.ts +15 -0
  178. package/dist/admin-components/features/plugin-features/components/ImageSettingInput.js +42 -0
  179. package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.js +4 -2
  180. package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +1 -1
  181. package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +4 -4
  182. package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +1 -1
  183. package/dist/admin-components/features/plugin-features/lib/image-upload.d.ts +6 -0
  184. package/dist/admin-components/features/plugin-features/lib/image-upload.js +17 -0
  185. package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +4 -4
  186. package/dist/admin-components/i18n/texts.d.ts +6 -0
  187. package/dist/admin-components/i18n/texts.js +6 -0
  188. package/dist/documents/config/index.d.ts +11 -0
  189. package/dist/documents/config/index.js +17 -0
  190. package/dist/documents/templates/financial-document.d.ts +3 -0
  191. package/dist/documents/templates/financial-document.js +159 -0
  192. package/dist/documents/templates/format.d.ts +30 -0
  193. package/dist/documents/templates/format.js +72 -0
  194. package/dist/documents/templates/html-factory.d.ts +15 -0
  195. package/dist/documents/templates/html-factory.js +41 -0
  196. package/dist/documents/templates/html-utils.d.ts +8 -0
  197. package/dist/documents/templates/html-utils.js +22 -0
  198. package/dist/documents/templates/layout.d.ts +35 -0
  199. package/dist/documents/templates/layout.js +141 -0
  200. package/dist/documents/templates/packing-list.d.ts +3 -0
  201. package/dist/documents/templates/packing-list.js +51 -0
  202. package/dist/documents/types/admin-api.d.ts +1 -0
  203. package/dist/documents/types/admin-api.js +2 -0
  204. package/dist/documents/types/document-summary.d.ts +1 -0
  205. package/dist/documents/types/document-summary.js +2 -0
  206. package/dist/documents/types/documents.d.ts +191 -0
  207. package/dist/documents/types/documents.js +2 -0
  208. package/dist/documents/types/html-factory.d.ts +8 -0
  209. package/dist/documents/types/html-factory.js +5 -0
  210. package/dist/documents/types/index.d.ts +5 -0
  211. package/dist/documents/types/index.js +21 -0
  212. package/dist/documents/types/store-api.d.ts +1 -0
  213. package/dist/documents/types/store-api.js +2 -0
  214. package/dist/email/config/index.d.ts +17 -0
  215. package/dist/email/config/index.js +83 -0
  216. package/dist/email/config/notification-module.d.ts +22 -0
  217. package/dist/email/config/notification-module.js +19 -0
  218. package/dist/email/types/email-kinds.d.ts +15 -0
  219. package/dist/email/types/email-kinds.js +17 -0
  220. package/dist/email/types/index.d.ts +4 -0
  221. package/dist/email/types/index.js +20 -0
  222. package/dist/email/types/shop-configuration.d.ts +68 -0
  223. package/dist/email/types/shop-configuration.js +2 -0
  224. package/dist/email/types/smtp-configuration.d.ts +9 -0
  225. package/dist/email/types/smtp-configuration.js +2 -0
  226. package/dist/email/types/template-data.d.ts +609 -0
  227. package/dist/email/types/template-data.js +231 -0
  228. package/dist/files/config/index.d.ts +32 -0
  229. package/dist/files/config/index.js +255 -0
  230. package/dist/files/gateway/index.d.ts +30 -0
  231. package/dist/files/gateway/index.js +114 -0
  232. package/dist/files/types/index.d.ts +2 -0
  233. package/dist/files/types/index.js +5 -0
  234. package/dist/files/types/public.d.ts +90 -0
  235. package/dist/files/types/public.js +5 -0
  236. package/dist/modules/documents/domain/merchant.d.ts +22 -0
  237. package/dist/modules/documents/domain/merchant.js +85 -0
  238. package/dist/modules/documents/domain/options.d.ts +34 -0
  239. package/dist/modules/documents/domain/options.js +39 -0
  240. package/dist/public/config.d.ts +59 -0
  241. package/dist/public/config.js +185 -0
  242. package/dist/public/descriptor.js +2 -0
  243. package/dist/public/gateway.d.ts +2 -0
  244. package/dist/public/gateway.js +18 -0
  245. package/dist/public/instrumentation.d.ts +12 -0
  246. package/dist/public/instrumentation.js +55 -0
  247. package/dist/public/shop-config.d.ts +8 -0
  248. package/dist/public/shop-config.js +11 -0
  249. package/dist/public/shop-master-data.d.ts +1 -1
  250. package/dist/public/shop-master-data.js +1 -1
  251. package/dist/public/shop-types.d.ts +7 -0
  252. package/dist/public/shop-types.js +23 -0
  253. package/dist/public/types.d.ts +19 -2
  254. package/dist/shop-config/build-food-shop-config.d.ts +44 -0
  255. package/dist/shop-config/build-food-shop-config.js +251 -0
  256. package/dist/shop-config/options.d.ts +48 -0
  257. package/dist/shop-config/options.js +74 -0
  258. package/package.json +48 -5
@@ -1,12 +1,15 @@
1
1
  "use strict";
2
- const adminSdk = require("@medusajs/admin-sdk");
3
2
  const jsxRuntime = require("react/jsx-runtime");
3
+ const react = require("react");
4
+ const adminSdk = require("@medusajs/admin-sdk");
4
5
  const ui = require("@medusajs/ui");
5
6
  const reactQuery = require("@tanstack/react-query");
6
- const pluginFeatures = require("@hartl-services/medusa-food-supplements-contracts/paths/plugin-features");
7
+ const documents = require("@hartl-services/medusa-food-supplements-contracts/paths/documents");
7
8
  const Medusa = require("@medusajs/js-sdk");
8
9
  const reactRouterDom = require("react-router-dom");
9
- const react = require("react");
10
+ const pluginFeatures = require("@hartl-services/medusa-food-supplements-contracts/paths/plugin-features");
11
+ const email = require("@hartl-services/medusa-food-supplements-contracts/paths/email");
12
+ const email$1 = require("@hartl-services/medusa-food-supplements-contracts/schemas/email");
10
13
  const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
11
14
  const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
12
15
  const texts = {
@@ -48,6 +51,11 @@ const texts = {
48
51
  parentDisabled: (parentLabel) => `Inaktiv, solange „${parentLabel}“ ausgeschaltet ist.`,
49
52
  saving: "Wird gespeichert…"
50
53
  },
54
+ gate: {
55
+ disabled: "Diese Funktion ist unter Einstellungen → Hartl Services Plugins ausgeschaltet.",
56
+ disabledNamed: (label) => `„${label}“ ist unter Einstellungen → Hartl Services Plugins ausgeschaltet.`,
57
+ openHub: "Zu Hartl Services Plugins"
58
+ },
51
59
  settings: {
52
60
  missing: (labels) => `Nicht konfiguriert: ${labels}`,
53
61
  save: "Einstellungen speichern",
@@ -65,6 +73,12 @@ const texts = {
65
73
  numberMax: (max) => `Höchstens ${max}.`,
66
74
  numberRange: (min, max) => `Zwischen ${min} und ${max}.`,
67
75
  selectPlaceholder: "Bitte auswählen",
76
+ imageUpload: "Bild hochladen",
77
+ imageReplace: "Anderes Bild hochladen",
78
+ imageRemove: "Entfernen",
79
+ imageNone: "Kein Bild hinterlegt",
80
+ imageUploadFailed: "Das Bild konnte nicht hochgeladen werden.",
81
+ imagePreviewAlt: (label) => `Vorschau ${label}`,
68
82
  clearHint: "Ein leeres Feld speichern entfernt den gespeicherten Wert (dann gilt Konfiguration oder Standard).",
69
83
  source: {
70
84
  stored: "gespeichert",
@@ -375,6 +389,106 @@ function usePluginSettingsForm(plugin2) {
375
389
  mutation
376
390
  };
377
391
  }
392
+ const SETTING_IMAGE_MAX_BYTES = 2 * 1024 * 1024;
393
+ const IMAGE_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg"]);
394
+ const validateSettingImage = (file) => {
395
+ if (!IMAGE_TYPES.has(file.type)) {
396
+ return "Bitte eine PNG- oder JPG-Datei auswählen.";
397
+ }
398
+ if (file.size === 0) {
399
+ return "Die Bilddatei ist leer.";
400
+ }
401
+ if (file.size > SETTING_IMAGE_MAX_BYTES) {
402
+ return "Die Bilddatei darf maximal 2 MiB groß sein.";
403
+ }
404
+ return null;
405
+ };
406
+ const ImageSettingInput = ({
407
+ id,
408
+ hintId,
409
+ label,
410
+ value,
411
+ disabled,
412
+ onChange
413
+ }) => {
414
+ const fileInput = react.useRef(null);
415
+ const [error, setError] = react.useState(null);
416
+ const upload = reactQuery.useMutation({
417
+ mutationFn: async (file) => {
418
+ const { files } = await sdk.admin.upload.create({ files: [file] });
419
+ const url = files[0]?.url;
420
+ if (!url) {
421
+ throw new Error("upload returned no file");
422
+ }
423
+ return url;
424
+ },
425
+ onSuccess: (url) => onChange(url),
426
+ onError: () => setError(texts.settings.imageUploadFailed)
427
+ });
428
+ const busy = disabled || upload.isPending;
429
+ const selectFile = (file) => {
430
+ if (!file) {
431
+ return;
432
+ }
433
+ const invalid = validateSettingImage(file);
434
+ setError(invalid);
435
+ if (!invalid) {
436
+ upload.mutate(file);
437
+ }
438
+ };
439
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
440
+ value && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-ui-bg-subtle flex items-center justify-center rounded-md border p-4", children: /* @__PURE__ */ jsxRuntime.jsx(
441
+ "img",
442
+ {
443
+ src: value,
444
+ alt: texts.settings.imagePreviewAlt(label),
445
+ className: "max-h-24 max-w-full object-contain"
446
+ }
447
+ ) }),
448
+ /* @__PURE__ */ jsxRuntime.jsx(
449
+ "input",
450
+ {
451
+ ref: fileInput,
452
+ id,
453
+ "aria-describedby": hintId,
454
+ type: "file",
455
+ accept: "image/png,image/jpeg",
456
+ className: "hidden",
457
+ onChange: (event) => {
458
+ selectFile(event.target.files?.[0]);
459
+ event.target.value = "";
460
+ }
461
+ }
462
+ ),
463
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
464
+ /* @__PURE__ */ jsxRuntime.jsx(
465
+ ui.Button,
466
+ {
467
+ type: "button",
468
+ size: "small",
469
+ variant: "secondary",
470
+ isLoading: upload.isPending,
471
+ disabled: busy,
472
+ onClick: () => fileInput.current?.click(),
473
+ children: value ? texts.settings.imageReplace : texts.settings.imageUpload
474
+ }
475
+ ),
476
+ value && /* @__PURE__ */ jsxRuntime.jsx(
477
+ ui.Button,
478
+ {
479
+ type: "button",
480
+ size: "small",
481
+ variant: "transparent",
482
+ disabled: busy,
483
+ onClick: () => onChange(""),
484
+ children: texts.settings.imageRemove
485
+ }
486
+ ),
487
+ !value && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.imageNone })
488
+ ] }),
489
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Hint, { variant: "error", children: error })
490
+ ] });
491
+ };
378
492
  const fieldId = (pluginKey, key) => `plugin-setting-${pluginKey}-${key}`;
379
493
  const missingLabels = (plugin2) => plugin2.missing.map(
380
494
  (key) => plugin2.settings.find((setting) => setting.key === key)?.label ?? key
@@ -455,6 +569,16 @@ const SettingField = ({
455
569
  /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: setting.options.map((option) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: option.value, children: option.label }, option.value)) })
456
570
  ]
457
571
  }
572
+ ) : setting.type === "image" ? /* @__PURE__ */ jsxRuntime.jsx(
573
+ ImageSettingInput,
574
+ {
575
+ id,
576
+ hintId,
577
+ label: setting.label,
578
+ value: typeof value === "string" ? value : "",
579
+ disabled,
580
+ onChange: (url) => onChange(url)
581
+ }
458
582
  ) : setting.type === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
459
583
  ui.Input,
460
584
  {
@@ -494,7 +618,7 @@ const PluginSettingsForm = ({ plugin: plugin2 }) => {
494
618
  const form = usePluginSettingsForm(plugin2);
495
619
  const pending = form.mutation.isPending;
496
620
  const hasTextFields = plugin2.settings.some(
497
- (setting) => setting.type !== "boolean" && setting.type !== "secret" && setting.type !== "select"
621
+ (setting) => setting.type !== "boolean" && setting.type !== "secret" && setting.type !== "select" && setting.type !== "image"
498
622
  );
499
623
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", "aria-busy": pending, children: [
500
624
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
@@ -533,62 +657,30 @@ const PluginStatusChips = ({ plugin: plugin2 }) => /* @__PURE__ */ jsxRuntime.js
533
657
  plugin2.switchable && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: plugin2.enabled ? "green" : "grey", children: plugin2.enabled ? texts.chips.enabled : texts.chips.disabled }),
534
658
  plugin2.settings.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: plugin2.configured ? "green" : "orange", children: plugin2.configured ? texts.chips.configured : texts.chips.notConfigured(missingLabels(plugin2)) })
535
659
  ] });
536
- const PluginHubCard = ({ plugin: plugin2 }) => {
537
- const mutation = useUpdatePluginFeatures();
538
- const pending = mutation.isPending;
539
- const toggle = (enabled) => {
540
- mutation.mutate({
541
- plugin_key: plugin2.plugin_key,
542
- expected_version: plugin2.version,
543
- enabled,
544
- features: Object.fromEntries(
545
- plugin2.features.map((feature) => [feature.key, feature.enabled])
546
- )
547
- });
548
- };
549
- return /* @__PURE__ */ jsxRuntime.jsxs(
550
- ui.Container,
551
- {
552
- className: "flex flex-wrap items-start justify-between gap-4 px-6 py-4",
553
- "aria-busy": pending,
554
- children: [
555
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
556
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: plugin2.label }),
557
- plugin2.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: plugin2.description }),
558
- /* @__PURE__ */ jsxRuntime.jsx(PluginStatusChips, { plugin: plugin2 }),
559
- pending && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted mt-1", children: texts.hints.saving })
560
- ] }),
561
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
562
- plugin2.switchable && /* @__PURE__ */ jsxRuntime.jsx(
563
- ui.Switch,
564
- {
565
- "aria-label": texts.plugin.switchLabel(plugin2.label),
566
- checked: plugin2.enabled,
567
- disabled: pending,
568
- onCheckedChange: (checked) => toggle(checked === true)
569
- }
570
- ),
571
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
572
- reactRouterDom.Link,
573
- {
574
- to: plugin2.admin_page ?? pluginDetailPath(plugin2.plugin_key),
575
- "aria-label": texts.plugin.openLabel(plugin2.label),
576
- children: texts.plugin.open
577
- }
578
- ) })
579
- ] })
580
- ]
581
- }
582
- );
583
- };
584
- const PluginFeaturesSettingsPage = () => {
585
- const query = usePluginFeatures();
660
+ const isNotFound = (error) => typeof error === "object" && error !== null && error.status === 404;
661
+ const PluginPageShell = ({ pluginKey, children }) => {
662
+ const query = usePluginFeature(pluginKey);
663
+ const plugin2 = query.data?.plugin;
586
664
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
587
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "px-6 py-4", children: [
588
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.page.title }),
589
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.page.description })
590
- ] }),
591
- query.isPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) }) : query.isError ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-wrap items-center justify-between gap-3", children: [
665
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(
666
+ reactRouterDom.Link,
667
+ {
668
+ to: PLUGIN_HUB_PATH,
669
+ className: "text-ui-fg-subtle hover:text-ui-fg-base txt-compact-small-plus",
670
+ children: [
671
+ "← ",
672
+ texts.detail.back
673
+ ]
674
+ }
675
+ ) }),
676
+ plugin2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
677
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "px-6 py-4", children: [
678
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: plugin2.label }),
679
+ plugin2.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: plugin2.description }),
680
+ /* @__PURE__ */ jsxRuntime.jsx(PluginStatusChips, { plugin: plugin2 })
681
+ ] }),
682
+ children
683
+ ] }) : query.isPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) }) : isNotFound(query.error) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.notFound }) }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-wrap items-center justify-between gap-3", children: [
592
684
  /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: texts.states.error }),
593
685
  /* @__PURE__ */ jsxRuntime.jsx(
594
686
  ui.Button,
@@ -600,15 +692,9 @@ const PluginFeaturesSettingsPage = () => {
600
692
  children: texts.states.retry
601
693
  }
602
694
  )
603
- ] }) : query.data.plugins.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.empty }) }) : [
604
- ...query.data.plugins.filter((plugin2) => !plugin2.switchable),
605
- ...query.data.plugins.filter((plugin2) => plugin2.switchable)
606
- ].map((plugin2) => /* @__PURE__ */ jsxRuntime.jsx(PluginHubCard, { plugin: plugin2 }, plugin2.plugin_key))
695
+ ] })
607
696
  ] });
608
697
  };
609
- const config = adminSdk.defineRouteConfig({
610
- label: texts.menu.label
611
- });
612
698
  const SUMMARY_MAX_LENGTH = 200;
613
699
  const cut = (text) => text.length > SUMMARY_MAX_LENGTH ? `${text.slice(0, SUMMARY_MAX_LENGTH - 1)}…` : text;
614
700
  const isPrimitive = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
@@ -884,55 +970,1515 @@ const PluginFeatureSections = ({ pluginKey }) => {
884
970
  plugin2.actions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(PluginActionsSection, { plugin: plugin2 })
885
971
  ] });
886
972
  };
887
- const isNotFound = (error) => typeof error === "object" && error !== null && error.status === 404;
888
- const PluginPageShell = ({ pluginKey, children }) => {
889
- const query = usePluginFeature(pluginKey);
890
- const plugin2 = query.data?.plugin;
891
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
892
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsxs(
893
- reactRouterDom.Link,
894
- {
895
- to: PLUGIN_HUB_PATH,
896
- className: "text-ui-fg-subtle hover:text-ui-fg-base txt-compact-small-plus",
897
- children: [
898
- "← ",
899
- texts.detail.back
900
- ]
901
- }
902
- ) }),
903
- plugin2 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
904
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "px-6 py-4", children: [
905
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: plugin2.label }),
906
- plugin2.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: plugin2.description }),
907
- /* @__PURE__ */ jsxRuntime.jsx(PluginStatusChips, { plugin: plugin2 })
908
- ] }),
909
- children
910
- ] }) : query.isPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) }) : isNotFound(query.error) ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.notFound }) }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-wrap items-center justify-between gap-3", children: [
911
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: texts.states.error }),
912
- /* @__PURE__ */ jsxRuntime.jsx(
913
- ui.Button,
973
+ const PLUGIN_FEATURE_ENABLED_STALE_TIME = 3e4;
974
+ const isPluginFeatureEnabledIn = (data, pluginKey, featureKeys) => {
975
+ const plugin2 = data.plugins.find((entry) => entry.plugin_key === pluginKey);
976
+ if (!plugin2?.enabled) {
977
+ return false;
978
+ }
979
+ return featureKeys.every(
980
+ (key) => plugin2.features.find((feature) => feature.key === key)?.effective_enabled === true
981
+ );
982
+ };
983
+ const pluginFeatureEnabledQueryOptions = (pluginKey, featureKeys) => ({
984
+ queryKey: pluginFeatureKeys.list(),
985
+ queryFn: listPluginFeatures,
986
+ staleTime: PLUGIN_FEATURE_ENABLED_STALE_TIME,
987
+ select: (data) => isPluginFeatureEnabledIn(data, pluginKey, featureKeys)
988
+ });
989
+ const toPluginFeatureEnabledState = (result) => ({
990
+ enabled: !result.isPending && !result.isError && result.data === true,
991
+ isPending: result.isPending,
992
+ isError: result.isError
993
+ });
994
+ function usePluginFeatureEnabled(pluginKey, ...featureKeys) {
995
+ return toPluginFeatureEnabledState(
996
+ reactQuery.useQuery(pluginFeatureEnabledQueryOptions(pluginKey, featureKeys))
997
+ );
998
+ }
999
+ const PluginFeatureGate = ({
1000
+ pluginKey,
1001
+ featureKeys = [],
1002
+ fallback = null,
1003
+ children
1004
+ }) => {
1005
+ const { enabled } = usePluginFeatureEnabled(pluginKey, ...featureKeys);
1006
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: enabled ? children : fallback });
1007
+ };
1008
+ const PluginFeatureDisabledNotice = ({
1009
+ pluginKey,
1010
+ featureLabel
1011
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(
1012
+ ui.Container,
1013
+ {
1014
+ className: "flex flex-col gap-2 px-6 py-4",
1015
+ "data-plugin-key": pluginKey,
1016
+ children: [
1017
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: featureLabel ? texts.gate.disabledNamed(featureLabel) : texts.gate.disabled }),
1018
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(
1019
+ reactRouterDom.Link,
914
1020
  {
915
- size: "small",
916
- variant: "secondary",
917
- isLoading: query.isFetching,
918
- onClick: () => void query.refetch(),
919
- children: texts.states.retry
1021
+ to: PLUGIN_HUB_PATH,
1022
+ className: "text-ui-fg-interactive hover:text-ui-fg-interactive-hover txt-compact-small-plus",
1023
+ children: texts.gate.openHub
920
1024
  }
921
- )
922
- ] })
923
- ] });
1025
+ ) })
1026
+ ]
1027
+ }
1028
+ );
1029
+ const listOrderDocuments = (orderId) => sdk.client.fetch(
1030
+ documents.documentsPaths.admin.get.listForOrder(orderId)
1031
+ );
1032
+ const issueOrderInvoice = (orderId) => sdk.client.fetch(
1033
+ documents.documentsPaths.admin.post.issueInvoice(orderId),
1034
+ { method: "POST" }
1035
+ );
1036
+ const regenerateOrderDocument = (orderId, documentId) => sdk.client.fetch(
1037
+ documents.documentsPaths.admin.post.regenerateDocument(orderId, documentId),
1038
+ { method: "POST", body: {} }
1039
+ );
1040
+ const listOrderPackingLists = (orderId) => sdk.client.fetch(
1041
+ documents.packingListsPaths.admin.get.listForOrder(orderId)
1042
+ );
1043
+ const retryOrderPackingList = (orderId, fulfillmentId) => sdk.client.fetch(
1044
+ documents.packingListsPaths.admin.post.retry(orderId, fulfillmentId),
1045
+ { method: "POST", body: {} }
1046
+ );
1047
+ const retrieveOrderDocumentPreview = async (orderId, documentId) => {
1048
+ const response = await sdk.client.fetch(
1049
+ documents.documentsPaths.admin.get.download(orderId, documentId),
1050
+ { headers: { accept: "application/pdf" } }
1051
+ );
1052
+ const pdf = await response.blob();
1053
+ return URL.createObjectURL(pdf);
924
1054
  };
925
- const PluginDetailPage = () => {
926
- const { plugin_key: pluginKey = "" } = reactRouterDom.useParams();
927
- return /* @__PURE__ */ jsxRuntime.jsx(PluginPageShell, { pluginKey, children: /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureSections, { pluginKey }) });
1055
+ const retrieveOrderPackingListPreview = async (orderId, fulfillmentId) => {
1056
+ const response = await sdk.client.fetch(
1057
+ documents.packingListsPaths.admin.get.download(orderId, fulfillmentId),
1058
+ { headers: { accept: "application/pdf" } }
1059
+ );
1060
+ const pdf = await response.blob();
1061
+ return URL.createObjectURL(pdf);
928
1062
  };
929
- const widgetModule = { widgets: [] };
930
- const routeModule = {
931
- routes: [
932
- {
933
- Component: PluginFeaturesSettingsPage,
934
- path: "/settings/hartl-services",
935
- handle: { label: config.label, translationNs: config.translationNs }
1063
+ const formatDocumentKind = (kind) => {
1064
+ switch (kind) {
1065
+ case "invoice":
1066
+ return "Rechnung";
1067
+ case "cancellation":
1068
+ return "Stornorechnung";
1069
+ case "credit_note":
1070
+ return "Gutschrift";
1071
+ }
1072
+ };
1073
+ const formatAmount = (document2) => {
1074
+ const amount = Number(document2.total.amount);
1075
+ if (!Number.isFinite(amount)) {
1076
+ return `${document2.total.amount} ${document2.total.currency_code.toUpperCase()}`;
1077
+ }
1078
+ return new Intl.NumberFormat("de-AT", {
1079
+ style: "currency",
1080
+ currency: document2.total.currency_code.toUpperCase()
1081
+ }).format(amount);
1082
+ };
1083
+ const formatIssuedAt = (issuedAt) => new Intl.DateTimeFormat("de-AT", { dateStyle: "medium" }).format(
1084
+ new Date(issuedAt)
1085
+ );
1086
+ const OrderDocumentsWidgetContent = ({
1087
+ data
1088
+ }) => {
1089
+ const queryClient = reactQuery.useQueryClient();
1090
+ const [preview, setPreview] = react.useState(null);
1091
+ const previewRequest = react.useRef(0);
1092
+ const documentsQueryKey = ["documents", "order", data.id];
1093
+ const documentsQuery = reactQuery.useQuery({
1094
+ queryKey: documentsQueryKey,
1095
+ queryFn: () => listOrderDocuments(data.id)
1096
+ });
1097
+ const documents2 = documentsQuery.data?.documents ?? [];
1098
+ const invoice = documents2.find((document2) => document2.kind === "invoice");
1099
+ const issueInvoice = reactQuery.useMutation({
1100
+ mutationFn: () => issueOrderInvoice(data.id),
1101
+ onSuccess: async ({ created }) => {
1102
+ ui.toast.success(
1103
+ created ? "Die Rechnung wurde ausgestellt." : "Für diese Bestellung existiert bereits eine Rechnung."
1104
+ );
1105
+ await queryClient.invalidateQueries({ queryKey: documentsQueryKey });
1106
+ },
1107
+ onError: (error) => {
1108
+ const message = error instanceof Error ? error.message : null;
1109
+ ui.toast.error("Die Rechnung konnte nicht ausgestellt werden.", {
1110
+ description: message ?? "Die Bestellung muss vollständig versendet und darf nicht storniert sein."
1111
+ });
1112
+ }
1113
+ });
1114
+ const regenerateDocument = reactQuery.useMutation({
1115
+ mutationFn: (document2) => regenerateOrderDocument(data.id, document2.id),
1116
+ onSuccess: async () => {
1117
+ ui.toast.success("Das Dokument wurde neu ausgestellt.");
1118
+ await queryClient.invalidateQueries({ queryKey: documentsQueryKey });
1119
+ },
1120
+ onError: (error) => {
1121
+ const message = error instanceof Error ? error.message : null;
1122
+ ui.toast.error("Das Dokument konnte nicht regeneriert werden.", {
1123
+ description: message ?? "Das ausgestellte Dokument ist nicht verfügbar."
1124
+ });
1125
+ }
1126
+ });
1127
+ const closePreview = () => {
1128
+ previewRequest.current += 1;
1129
+ if (preview?.url) {
1130
+ URL.revokeObjectURL(preview.url);
1131
+ }
1132
+ setPreview(null);
1133
+ };
1134
+ const previewDocument = async (document2) => {
1135
+ if (preview?.url) {
1136
+ URL.revokeObjectURL(preview.url);
1137
+ }
1138
+ const requestId = previewRequest.current + 1;
1139
+ previewRequest.current = requestId;
1140
+ setPreview({ document: document2, url: null, error: null });
1141
+ try {
1142
+ const url = await retrieveOrderDocumentPreview(data.id, document2.id);
1143
+ if (previewRequest.current !== requestId) {
1144
+ URL.revokeObjectURL(url);
1145
+ return;
1146
+ }
1147
+ setPreview({ document: document2, url, error: null });
1148
+ } catch {
1149
+ if (previewRequest.current !== requestId) {
1150
+ return;
1151
+ }
1152
+ setPreview({
1153
+ document: document2,
1154
+ url: null,
1155
+ error: "Das Dokument konnte nicht geladen werden."
1156
+ });
1157
+ }
1158
+ };
1159
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1160
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1161
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-4 px-6 py-4", children: [
1162
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1163
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Dokumente" }),
1164
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Ausgestellte Belege zu dieser Bestellung." })
1165
+ ] }),
1166
+ !invoice ? /* @__PURE__ */ jsxRuntime.jsx(
1167
+ ui.Button,
1168
+ {
1169
+ disabled: issueInvoice.isPending,
1170
+ isLoading: issueInvoice.isPending,
1171
+ onClick: () => issueInvoice.mutate(),
1172
+ size: "small",
1173
+ variant: "secondary",
1174
+ children: "Rechnung ausstellen"
1175
+ }
1176
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1177
+ ui.Button,
1178
+ {
1179
+ disabled: regenerateDocument.isPending,
1180
+ isLoading: regenerateDocument.isPending,
1181
+ onClick: () => regenerateDocument.mutate(invoice),
1182
+ size: "small",
1183
+ variant: "secondary",
1184
+ children: "Rechnung regenerieren"
1185
+ }
1186
+ )
1187
+ ] }),
1188
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4", children: documentsQuery.isPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Dokumente werden geladen." }) : documentsQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: "Dokumente konnten nicht geladen werden." }) : documents2.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: documents2.map((document2) => /* @__PURE__ */ jsxRuntime.jsxs(
1189
+ "div",
1190
+ {
1191
+ className: "flex items-center justify-between gap-4",
1192
+ children: [
1193
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1194
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "font-medium", children: [
1195
+ formatDocumentKind(document2.kind),
1196
+ " ",
1197
+ document2.number
1198
+ ] }),
1199
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle", children: [
1200
+ formatIssuedAt(document2.issued_at),
1201
+ " ·",
1202
+ " ",
1203
+ formatAmount(document2)
1204
+ ] })
1205
+ ] }),
1206
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1207
+ /* @__PURE__ */ jsxRuntime.jsx(
1208
+ ui.Button,
1209
+ {
1210
+ onClick: () => previewDocument(document2),
1211
+ size: "small",
1212
+ variant: "secondary",
1213
+ children: "Ansehen"
1214
+ }
1215
+ ),
1216
+ document2.kind !== "invoice" ? /* @__PURE__ */ jsxRuntime.jsx(
1217
+ ui.Button,
1218
+ {
1219
+ disabled: regenerateDocument.isPending,
1220
+ isLoading: regenerateDocument.isPending,
1221
+ onClick: () => regenerateDocument.mutate(document2),
1222
+ size: "small",
1223
+ variant: "secondary",
1224
+ children: "Regenerieren"
1225
+ }
1226
+ ) : null
1227
+ ] })
1228
+ ]
1229
+ },
1230
+ document2.id
1231
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Für diese Bestellung wurden noch keine Dokumente ausgestellt." }) })
1232
+ ] }),
1233
+ /* @__PURE__ */ jsxRuntime.jsx(
1234
+ ui.FocusModal,
1235
+ {
1236
+ onOpenChange: (open) => {
1237
+ if (!open) {
1238
+ closePreview();
1239
+ }
1240
+ },
1241
+ open: preview !== null,
1242
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
1243
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Title, { children: preview ? `${formatDocumentKind(preview.document.kind)} ${preview.document.number}` : "Dokument" }) }),
1244
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { className: "h-[75vh] p-0", children: preview?.url ? /* @__PURE__ */ jsxRuntime.jsx(
1245
+ "iframe",
1246
+ {
1247
+ className: "h-full w-full border-0",
1248
+ src: preview.url,
1249
+ title: `Vorschau ${preview.document.number}`
1250
+ }
1251
+ ) : preview?.error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "p-6 text-ui-fg-error", children: preview.error }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "p-6 text-ui-fg-subtle", children: "Dokument wird geladen." }) }),
1252
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Footer, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: closePreview, variant: "secondary", children: "Schließen" }) }) })
1253
+ ] })
1254
+ }
1255
+ )
1256
+ ] });
1257
+ };
1258
+ const OrderDocumentsWidget = (props) => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureGate, { pluginKey: "documents", featureKeys: ["invoices"], children: /* @__PURE__ */ jsxRuntime.jsx(OrderDocumentsWidgetContent, { ...props }) });
1259
+ adminSdk.defineWidgetConfig({
1260
+ zone: "order.details.side.after"
1261
+ });
1262
+ const formatStatus = (status) => {
1263
+ switch (status) {
1264
+ case "missing":
1265
+ return "Nicht erstellt";
1266
+ case "pending":
1267
+ return "Ausstehend";
1268
+ case "processing":
1269
+ return "Wird erstellt";
1270
+ case "ready":
1271
+ return "Bereit";
1272
+ case "failed":
1273
+ return "Fehlgeschlagen";
1274
+ case "canceled":
1275
+ return "Storniert";
1276
+ }
1277
+ };
1278
+ const formatGeneratedAt = (generatedAt) => new Intl.DateTimeFormat("de-AT", {
1279
+ dateStyle: "medium",
1280
+ timeStyle: "short"
1281
+ }).format(new Date(generatedAt));
1282
+ const formatDetails = (packingList) => [
1283
+ packingList.number ? `Nummer: ${packingList.number}` : null,
1284
+ packingList.source_revision !== null ? `Revision: ${packingList.source_revision}` : null,
1285
+ packingList.generated_at ? `Datum: ${formatGeneratedAt(packingList.generated_at)}` : null
1286
+ ].filter((detail) => detail !== null).join(" · ");
1287
+ const formatFailureReason = (failureReason) => {
1288
+ const description = failureReason?.replace(/\s+/g, " ").trim();
1289
+ if (!description) {
1290
+ return null;
1291
+ }
1292
+ return description.length > 140 ? `${description.slice(0, 137)}...` : description;
1293
+ };
1294
+ const retryLabel = (status) => {
1295
+ if (status === "missing") {
1296
+ return "Erstellen";
1297
+ }
1298
+ if (status === "pending" || status === "failed") {
1299
+ return "Erneut versuchen";
1300
+ }
1301
+ return null;
1302
+ };
1303
+ const OrderPackingListsWidgetContent = ({
1304
+ data
1305
+ }) => {
1306
+ const queryClient = reactQuery.useQueryClient();
1307
+ const [preview, setPreview] = react.useState(null);
1308
+ const previewRequest = react.useRef(0);
1309
+ const previewUrl = react.useRef(null);
1310
+ const packingListsQueryKey = ["packing-lists", "order", data.id];
1311
+ const packingListsQuery = reactQuery.useQuery({
1312
+ queryKey: packingListsQueryKey,
1313
+ queryFn: () => listOrderPackingLists(data.id),
1314
+ refetchInterval: (query) => {
1315
+ const packingLists2 = query.state.data?.packing_lists;
1316
+ return packingLists2?.some(
1317
+ ({ status }) => status === "missing" || status === "pending" || status === "processing"
1318
+ ) ? 2e3 : false;
1319
+ }
1320
+ });
1321
+ const packingLists = packingListsQuery.data?.packing_lists ?? [];
1322
+ const retryPackingList = reactQuery.useMutation({
1323
+ mutationFn: (packingList) => retryOrderPackingList(data.id, packingList.fulfillment_id),
1324
+ onSuccess: async () => {
1325
+ ui.toast.success("Die Packliste wird erstellt.");
1326
+ await queryClient.invalidateQueries({ queryKey: packingListsQueryKey });
1327
+ },
1328
+ onError: (error) => {
1329
+ ui.toast.error("Die Packliste konnte nicht erstellt werden.", {
1330
+ description: error instanceof Error ? error.message : "Bitte versuchen Sie es erneut."
1331
+ });
1332
+ }
1333
+ });
1334
+ react.useEffect(
1335
+ () => () => {
1336
+ previewRequest.current += 1;
1337
+ if (previewUrl.current) {
1338
+ URL.revokeObjectURL(previewUrl.current);
1339
+ previewUrl.current = null;
1340
+ }
1341
+ },
1342
+ []
1343
+ );
1344
+ const revokePreviewUrl = () => {
1345
+ if (previewUrl.current) {
1346
+ URL.revokeObjectURL(previewUrl.current);
1347
+ previewUrl.current = null;
1348
+ }
1349
+ };
1350
+ const closePreview = () => {
1351
+ previewRequest.current += 1;
1352
+ revokePreviewUrl();
1353
+ setPreview(null);
1354
+ };
1355
+ const previewPackingList = async (packingList, position) => {
1356
+ revokePreviewUrl();
1357
+ const requestId = previewRequest.current + 1;
1358
+ previewRequest.current = requestId;
1359
+ setPreview({ packingList, position, url: null, error: null });
1360
+ try {
1361
+ const url = await retrieveOrderPackingListPreview(
1362
+ data.id,
1363
+ packingList.fulfillment_id
1364
+ );
1365
+ if (previewRequest.current !== requestId) {
1366
+ URL.revokeObjectURL(url);
1367
+ return;
1368
+ }
1369
+ previewUrl.current = url;
1370
+ setPreview({ packingList, position, url, error: null });
1371
+ } catch {
1372
+ if (previewRequest.current !== requestId) {
1373
+ return;
1374
+ }
1375
+ setPreview({
1376
+ packingList,
1377
+ position,
1378
+ url: null,
1379
+ error: "Die Packliste konnte nicht geladen werden."
1380
+ });
1381
+ }
1382
+ };
1383
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1384
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1385
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
1386
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Packlisten" }),
1387
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Packlisten zu den Versandvorgängen dieser Bestellung." })
1388
+ ] }),
1389
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 px-6 py-4", children: packingListsQuery.isPending ? /* @__PURE__ */ jsxRuntime.jsx(
1390
+ ui.Text,
1391
+ {
1392
+ "aria-atomic": "true",
1393
+ "aria-live": "polite",
1394
+ className: "text-ui-fg-subtle",
1395
+ role: "status",
1396
+ children: "Packlisten werden geladen."
1397
+ }
1398
+ ) : packingListsQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx(
1399
+ ui.Text,
1400
+ {
1401
+ "aria-atomic": "true",
1402
+ "aria-live": "assertive",
1403
+ className: "text-ui-fg-error",
1404
+ role: "alert",
1405
+ children: "Packlisten konnten nicht geladen werden."
1406
+ }
1407
+ ) : packingLists.length ? packingLists.map((packingList, index) => {
1408
+ const actionLabel = retryLabel(packingList.status);
1409
+ const details = formatDetails(packingList);
1410
+ const failureReason = formatFailureReason(
1411
+ packingList.failure_reason
1412
+ );
1413
+ const canPreview = packingList.generated_at !== null && (packingList.status === "ready" || packingList.status === "canceled");
1414
+ const isRetrying = retryPackingList.isPending && retryPackingList.variables?.fulfillment_id === packingList.fulfillment_id;
1415
+ const accessiblePackingListName = packingList.number ? `Packliste ${packingList.number} für Fulfillment ${packingList.fulfillment_id}` : `Packliste ${index + 1} für Fulfillment ${packingList.fulfillment_id}`;
1416
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1417
+ "div",
1418
+ {
1419
+ className: "border-ui-border-base rounded-md border p-3",
1420
+ children: [
1421
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1422
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
1423
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "font-medium", children: [
1424
+ "Packliste ",
1425
+ index + 1
1426
+ ] }),
1427
+ /* @__PURE__ */ jsxRuntime.jsxs(
1428
+ ui.Text,
1429
+ {
1430
+ "aria-atomic": "true",
1431
+ "aria-live": "polite",
1432
+ className: packingList.status === "failed" ? "text-ui-fg-error text-xs" : "text-ui-fg-subtle text-xs",
1433
+ role: "status",
1434
+ children: [
1435
+ "Status: ",
1436
+ formatStatus(packingList.status)
1437
+ ]
1438
+ }
1439
+ )
1440
+ ] }),
1441
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 gap-2", children: [
1442
+ canPreview ? /* @__PURE__ */ jsxRuntime.jsx(
1443
+ ui.Button,
1444
+ {
1445
+ "aria-label": `Ansehen: ${accessiblePackingListName}`,
1446
+ onClick: () => previewPackingList(packingList, index + 1),
1447
+ size: "small",
1448
+ variant: "secondary",
1449
+ children: "Ansehen"
1450
+ }
1451
+ ) : null,
1452
+ actionLabel ? /* @__PURE__ */ jsxRuntime.jsx(
1453
+ ui.Button,
1454
+ {
1455
+ "aria-label": `${actionLabel}: ${accessiblePackingListName}`,
1456
+ disabled: retryPackingList.isPending,
1457
+ isLoading: isRetrying,
1458
+ onClick: () => retryPackingList.mutate(packingList),
1459
+ size: "small",
1460
+ variant: "secondary",
1461
+ children: actionLabel
1462
+ }
1463
+ ) : null
1464
+ ] })
1465
+ ] }),
1466
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 grid gap-1", children: [
1467
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-xs", children: [
1468
+ "Lagerstandort:",
1469
+ " ",
1470
+ packingList.location_name ?? "Nicht verfügbar"
1471
+ ] }),
1472
+ details ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: details }) : null,
1473
+ failureReason ? /* @__PURE__ */ jsxRuntime.jsxs(
1474
+ ui.Text,
1475
+ {
1476
+ "aria-atomic": "true",
1477
+ "aria-live": "assertive",
1478
+ className: "text-ui-fg-error text-xs",
1479
+ role: "alert",
1480
+ children: [
1481
+ "Fehler: ",
1482
+ failureReason
1483
+ ]
1484
+ }
1485
+ ) : null
1486
+ ] })
1487
+ ]
1488
+ },
1489
+ packingList.fulfillment_id
1490
+ );
1491
+ }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Für diese Bestellung sind keine Packlisten vorhanden." }) })
1492
+ ] }),
1493
+ /* @__PURE__ */ jsxRuntime.jsx(
1494
+ ui.FocusModal,
1495
+ {
1496
+ onOpenChange: (open) => {
1497
+ if (!open) {
1498
+ closePreview();
1499
+ }
1500
+ },
1501
+ open: preview !== null,
1502
+ children: /* @__PURE__ */ jsxRuntime.jsxs(ui.FocusModal.Content, { children: [
1503
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Title, { children: preview ? `Packliste ${preview.position}${preview.packingList.number ? ` · ${preview.packingList.number}` : ""}` : "Packliste" }) }),
1504
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Body, { className: "h-[75vh] p-0", children: preview?.url ? /* @__PURE__ */ jsxRuntime.jsx(
1505
+ "iframe",
1506
+ {
1507
+ className: "h-full w-full border-0",
1508
+ src: preview.url,
1509
+ title: `Vorschau Packliste ${preview.position}`
1510
+ }
1511
+ ) : preview?.error ? /* @__PURE__ */ jsxRuntime.jsx(
1512
+ ui.Text,
1513
+ {
1514
+ "aria-atomic": "true",
1515
+ "aria-live": "assertive",
1516
+ className: "p-6 text-ui-fg-error",
1517
+ role: "alert",
1518
+ children: preview.error
1519
+ }
1520
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1521
+ ui.Text,
1522
+ {
1523
+ "aria-atomic": "true",
1524
+ "aria-live": "polite",
1525
+ className: "p-6 text-ui-fg-subtle",
1526
+ role: "status",
1527
+ children: "Packliste wird geladen."
1528
+ }
1529
+ ) }),
1530
+ /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Footer, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.FocusModal.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: closePreview, variant: "secondary", children: "Schließen" }) }) })
1531
+ ] })
1532
+ }
1533
+ )
1534
+ ] });
1535
+ };
1536
+ const OrderPackingListsWidget = (props) => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureGate, { pluginKey: "documents", featureKeys: ["packing_lists"], children: /* @__PURE__ */ jsxRuntime.jsx(OrderPackingListsWidgetContent, { ...props }) });
1537
+ adminSdk.defineWidgetConfig({
1538
+ zone: "order.details.side.after"
1539
+ });
1540
+ const PluginHubCard = ({ plugin: plugin2 }) => {
1541
+ const mutation = useUpdatePluginFeatures();
1542
+ const pending = mutation.isPending;
1543
+ const toggle = (enabled) => {
1544
+ mutation.mutate({
1545
+ plugin_key: plugin2.plugin_key,
1546
+ expected_version: plugin2.version,
1547
+ enabled,
1548
+ features: Object.fromEntries(
1549
+ plugin2.features.map((feature) => [feature.key, feature.enabled])
1550
+ )
1551
+ });
1552
+ };
1553
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1554
+ ui.Container,
1555
+ {
1556
+ className: "flex flex-wrap items-start justify-between gap-4 px-6 py-4",
1557
+ "aria-busy": pending,
1558
+ children: [
1559
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
1560
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: plugin2.label }),
1561
+ plugin2.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: plugin2.description }),
1562
+ /* @__PURE__ */ jsxRuntime.jsx(PluginStatusChips, { plugin: plugin2 }),
1563
+ pending && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted mt-1", children: texts.hints.saving })
1564
+ ] }),
1565
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
1566
+ plugin2.switchable && /* @__PURE__ */ jsxRuntime.jsx(
1567
+ ui.Switch,
1568
+ {
1569
+ "aria-label": texts.plugin.switchLabel(plugin2.label),
1570
+ checked: plugin2.enabled,
1571
+ disabled: pending,
1572
+ onCheckedChange: (checked) => toggle(checked === true)
1573
+ }
1574
+ ),
1575
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1576
+ reactRouterDom.Link,
1577
+ {
1578
+ to: plugin2.admin_page ?? pluginDetailPath(plugin2.plugin_key),
1579
+ "aria-label": texts.plugin.openLabel(plugin2.label),
1580
+ children: texts.plugin.open
1581
+ }
1582
+ ) })
1583
+ ] })
1584
+ ]
1585
+ }
1586
+ );
1587
+ };
1588
+ const PluginFeaturesSettingsPage = () => {
1589
+ const query = usePluginFeatures();
1590
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
1591
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "px-6 py-4", children: [
1592
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: texts.page.title }),
1593
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.page.description })
1594
+ ] }),
1595
+ query.isPending ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) }) : query.isError ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-wrap items-center justify-between gap-3", children: [
1596
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", children: texts.states.error }),
1597
+ /* @__PURE__ */ jsxRuntime.jsx(
1598
+ ui.Button,
1599
+ {
1600
+ size: "small",
1601
+ variant: "secondary",
1602
+ isLoading: query.isFetching,
1603
+ onClick: () => void query.refetch(),
1604
+ children: texts.states.retry
1605
+ }
1606
+ )
1607
+ ] }) : query.data.plugins.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: texts.states.empty }) }) : [
1608
+ ...query.data.plugins.filter((plugin2) => !plugin2.switchable),
1609
+ ...query.data.plugins.filter((plugin2) => plugin2.switchable)
1610
+ ].map((plugin2) => /* @__PURE__ */ jsxRuntime.jsx(PluginHubCard, { plugin: plugin2 }, plugin2.plugin_key))
1611
+ ] });
1612
+ };
1613
+ const config = adminSdk.defineRouteConfig({
1614
+ label: texts.menu.label
1615
+ });
1616
+ const withPagination = (params) => new URLSearchParams({
1617
+ limit: String(params.limit),
1618
+ offset: String(params.offset)
1619
+ });
1620
+ const withMessageQuery = (path, params) => {
1621
+ const query = withPagination(params);
1622
+ if (params.kind) {
1623
+ query.set("kind", params.kind);
1624
+ }
1625
+ if (params.status) {
1626
+ query.set("status", params.status);
1627
+ }
1628
+ return `${path}?${query.toString()}`;
1629
+ };
1630
+ const listEmailMessages = (params) => sdk.client.fetch(
1631
+ withMessageQuery(email.emailAdminPaths.get.list, params)
1632
+ );
1633
+ const retrieveEmailMessage = (messageId) => sdk.client.fetch(
1634
+ email.emailAdminPaths.get.detail(messageId)
1635
+ );
1636
+ const resendEmailMessage = (messageId, body) => sdk.client.fetch(
1637
+ email.emailAdminPaths.post.resend(messageId),
1638
+ { method: "POST", body }
1639
+ );
1640
+ const listEmailTermsVersions = (params) => {
1641
+ const query = withPagination(params);
1642
+ return sdk.client.fetch(
1643
+ `${email.emailAdminPaths.get.termsVersions}?${query.toString()}`
1644
+ );
1645
+ };
1646
+ const createEmailTermsVersion = (body) => sdk.client.fetch(
1647
+ email.emailAdminPaths.post.createTermsVersion,
1648
+ { method: "POST", body }
1649
+ );
1650
+ const downloadEmailTermsVersion = async (termsVersionId) => {
1651
+ const response = await sdk.client.fetch(
1652
+ email.emailAdminPaths.get.downloadTermsVersion(termsVersionId),
1653
+ { headers: { accept: "application/pdf" } }
1654
+ );
1655
+ return response.blob();
1656
+ };
1657
+ const PAGE_SIZE$1 = 20;
1658
+ const MAX_PAGE_OFFSET$1 = 1e4;
1659
+ const KINDS = [
1660
+ "order.received",
1661
+ "order.received.admin",
1662
+ "order.shipped",
1663
+ "order.canceled",
1664
+ "order.return_requested",
1665
+ "order.return_received",
1666
+ "order.refunded",
1667
+ "auth.password_reset",
1668
+ "affiliate.consignment_report_due"
1669
+ ];
1670
+ const STATUSES = [
1671
+ "pending",
1672
+ "ready",
1673
+ "sending",
1674
+ "retry_pending",
1675
+ "sent",
1676
+ "failed",
1677
+ "unknown",
1678
+ "suppressed"
1679
+ ];
1680
+ const KIND_LABELS = {
1681
+ "order.received": "Bestellbestätigung (Kunde)",
1682
+ "order.received.admin": "Neue Bestellung (Shop)",
1683
+ "order.shipped": "Versandbestätigung",
1684
+ "order.canceled": "Stornierung",
1685
+ "order.return_requested": "Retourenanfrage",
1686
+ "order.return_received": "Retoureneingang",
1687
+ "order.refunded": "Rückerstattung",
1688
+ "auth.password_reset": "Passwort zurücksetzen",
1689
+ "affiliate.consignment_report_due": "Affiliate-Meldung"
1690
+ };
1691
+ const STATUS_LABELS = {
1692
+ pending: "Wartet auf Fakten",
1693
+ ready: "Bereit",
1694
+ sending: "Wird versendet",
1695
+ retry_pending: "Erneuter Versuch geplant",
1696
+ sent: "Versendet",
1697
+ failed: "Fehlgeschlagen",
1698
+ unknown: "Ausgang unbekannt",
1699
+ suppressed: "Unterdrückt"
1700
+ };
1701
+ const ATTEMPT_STATUS_LABELS = {
1702
+ processing: "In Bearbeitung",
1703
+ sent: "Versendet",
1704
+ failed: "Fehlgeschlagen",
1705
+ unknown: "Ausgang unbekannt"
1706
+ };
1707
+ const isKindFilter = (value) => value === "all" || KINDS.includes(value);
1708
+ const isStatusFilter = (value) => value === "all" || STATUSES.includes(value);
1709
+ const canResend = (message) => message.kind !== "auth.password_reset" && (message.status === "sent" || message.status === "unknown" || message.status === "failed");
1710
+ const formatDateTime$1 = (value) => {
1711
+ if (!value) {
1712
+ return "-";
1713
+ }
1714
+ const date = new Date(value);
1715
+ if (Number.isNaN(date.getTime())) {
1716
+ return "-";
1717
+ }
1718
+ return new Intl.DateTimeFormat("de-AT", {
1719
+ dateStyle: "medium",
1720
+ timeStyle: "short"
1721
+ }).format(date);
1722
+ };
1723
+ const formatResource = (message) => message.resource_id ? `${message.resource_type} · ${message.resource_id}` : message.resource_type;
1724
+ const Status = ({ message }) => /* @__PURE__ */ jsxRuntime.jsx(
1725
+ "span",
1726
+ {
1727
+ className: message.status === "sent" ? "text-ui-fg-base" : message.status === "failed" || message.status === "unknown" ? "text-ui-fg-error" : "text-ui-fg-subtle",
1728
+ children: STATUS_LABELS[message.status]
1729
+ }
1730
+ );
1731
+ const MessageCards = ({
1732
+ messages,
1733
+ selectedId,
1734
+ onSelect
1735
+ }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 px-4 py-4 md:hidden", children: messages.map((message) => /* @__PURE__ */ jsxRuntime.jsxs(
1736
+ "div",
1737
+ {
1738
+ className: `border-ui-border-base rounded-md border p-4 ${selectedId === message.id ? "bg-ui-bg-base-hover" : ""}`,
1739
+ children: [
1740
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-3", children: [
1741
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
1742
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-medium", children: KIND_LABELS[message.kind] }),
1743
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle break-all text-xs", children: formatResource(message) })
1744
+ ] }),
1745
+ /* @__PURE__ */ jsxRuntime.jsx(
1746
+ ui.Button,
1747
+ {
1748
+ "aria-pressed": selectedId === message.id,
1749
+ onClick: () => onSelect(message.id),
1750
+ size: "small",
1751
+ variant: "secondary",
1752
+ children: "Details"
1753
+ }
1754
+ )
1755
+ ] }),
1756
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 flex items-center justify-between gap-3 text-sm", children: [
1757
+ /* @__PURE__ */ jsxRuntime.jsx(Status, { message }),
1758
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: formatDateTime$1(message.created_at) })
1759
+ ] })
1760
+ ]
1761
+ },
1762
+ message.id
1763
+ )) });
1764
+ const MessageTable = ({
1765
+ messages,
1766
+ selectedId,
1767
+ onSelect
1768
+ }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden overflow-x-auto md:block", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
1769
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
1770
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Art" }),
1771
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Status" }),
1772
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Ressource" }),
1773
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Versuche" }),
1774
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Erstellt" }),
1775
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: "Aktion" })
1776
+ ] }) }),
1777
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: messages.map((message) => /* @__PURE__ */ jsxRuntime.jsxs(
1778
+ "tr",
1779
+ {
1780
+ className: `border-ui-border-base border-b ${selectedId === message.id ? "bg-ui-bg-base-hover" : ""}`,
1781
+ children: [
1782
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 font-medium", children: KIND_LABELS[message.kind] }),
1783
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3", children: /* @__PURE__ */ jsxRuntime.jsx(Status, { message }) }),
1784
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "max-w-64 truncate px-6 py-3 font-mono text-xs", children: formatResource(message) }),
1785
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 tabular-nums", children: message.attempt_count }),
1786
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "whitespace-nowrap px-6 py-3", children: formatDateTime$1(message.created_at) }),
1787
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsx(
1788
+ ui.Button,
1789
+ {
1790
+ "aria-pressed": selectedId === message.id,
1791
+ onClick: () => onSelect(message.id),
1792
+ size: "small",
1793
+ variant: "secondary",
1794
+ children: "Details"
1795
+ }
1796
+ ) })
1797
+ ]
1798
+ },
1799
+ message.id
1800
+ )) })
1801
+ ] }) });
1802
+ const DetailTimestamp = ({
1803
+ label,
1804
+ value
1805
+ }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1806
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: label }),
1807
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: formatDateTime$1(value) })
1808
+ ] });
1809
+ const EmailStatusPage = () => {
1810
+ const queryClient = reactQuery.useQueryClient();
1811
+ const [kind, setKind] = react.useState("all");
1812
+ const [status, setStatus] = react.useState("all");
1813
+ const [offset, setOffset] = react.useState(0);
1814
+ const [selectedId, setSelectedId] = react.useState(null);
1815
+ const [confirmationOpen, setConfirmationOpen] = react.useState(false);
1816
+ const [retryDecisions, setRetryDecisions] = react.useState({});
1817
+ const listQueryKey = [
1818
+ "email",
1819
+ "message-list",
1820
+ { kind, status, offset, limit: PAGE_SIZE$1 }
1821
+ ];
1822
+ const messagesQuery = reactQuery.useQuery({
1823
+ queryKey: listQueryKey,
1824
+ queryFn: () => listEmailMessages({
1825
+ kind: kind === "all" ? void 0 : kind,
1826
+ status: status === "all" ? void 0 : status,
1827
+ limit: PAGE_SIZE$1,
1828
+ offset
1829
+ })
1830
+ });
1831
+ const detailQueryKey = ["email", "message-detail", selectedId];
1832
+ const detailQuery = reactQuery.useQuery({
1833
+ queryKey: detailQueryKey,
1834
+ queryFn: () => retrieveEmailMessage(selectedId),
1835
+ enabled: selectedId !== null
1836
+ });
1837
+ const resend = reactQuery.useMutation({
1838
+ mutationFn: (decision) => resendEmailMessage(decision.messageId, {
1839
+ request_id: decision.requestId
1840
+ }),
1841
+ onSuccess: async (_, decision) => {
1842
+ setRetryDecisions((current) => {
1843
+ const next = { ...current };
1844
+ delete next[decision.messageId];
1845
+ return next;
1846
+ });
1847
+ ui.toast.success("Der erneute Versand wurde angenommen.");
1848
+ await Promise.all([
1849
+ queryClient.invalidateQueries({ queryKey: ["email", "message-list"] }),
1850
+ queryClient.invalidateQueries({
1851
+ queryKey: ["email", "message-detail", decision.messageId]
1852
+ })
1853
+ ]);
1854
+ },
1855
+ onError: (_, decision) => {
1856
+ setRetryDecisions((current) => ({
1857
+ ...current,
1858
+ [decision.messageId]: decision
1859
+ }));
1860
+ ui.toast.error("Die Anfrage zum erneuten Versand ist fehlgeschlagen.", {
1861
+ description: "Dieselbe Anfrage kann mit unveränderter UUID sicher wiederholt werden."
1862
+ });
1863
+ }
1864
+ });
1865
+ const messages = messagesQuery.data?.messages ?? [];
1866
+ const count = messagesQuery.data?.count ?? 0;
1867
+ const detail = detailQuery.data?.message;
1868
+ const attempts = detailQuery.data?.attempts ?? [];
1869
+ const retryDecision = selectedId ? retryDecisions[selectedId] : void 0;
1870
+ const selectMessage = (messageId) => {
1871
+ setSelectedId(messageId);
1872
+ };
1873
+ const changePage = (nextOffset) => {
1874
+ setSelectedId(null);
1875
+ setOffset(nextOffset);
1876
+ };
1877
+ const confirmResend = () => {
1878
+ if (!detail || !canResend(detail)) {
1879
+ setConfirmationOpen(false);
1880
+ return;
1881
+ }
1882
+ setConfirmationOpen(false);
1883
+ resend.mutate({ messageId: detail.id, requestId: crypto.randomUUID() });
1884
+ };
1885
+ const renderMessages = () => {
1886
+ if (messagesQuery.isPending) {
1887
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", role: "status", children: "E-Mails werden geladen." }) });
1888
+ }
1889
+ if (messagesQuery.isError) {
1890
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", role: "alert", children: "E-Mails konnten nicht geladen werden." }) });
1891
+ }
1892
+ if (!messages.length) {
1893
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Für diese Filter wurden keine E-Mails gefunden." }) });
1894
+ }
1895
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1896
+ /* @__PURE__ */ jsxRuntime.jsx(
1897
+ MessageTable,
1898
+ {
1899
+ messages,
1900
+ onSelect: selectMessage,
1901
+ selectedId
1902
+ }
1903
+ ),
1904
+ /* @__PURE__ */ jsxRuntime.jsx(
1905
+ MessageCards,
1906
+ {
1907
+ messages,
1908
+ onSelect: selectMessage,
1909
+ selectedId
1910
+ }
1911
+ )
1912
+ ] });
1913
+ };
1914
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
1915
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1916
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 px-6 py-4 lg:flex-row lg:items-center lg:justify-between", children: [
1917
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1918
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "E-Mail-Protokoll" }),
1919
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Sicherer Status und Zustellversuche ohne Empfänger- oder Inhaltsdaten." })
1920
+ ] }),
1921
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
1922
+ /* @__PURE__ */ jsxRuntime.jsxs(
1923
+ ui.Select,
1924
+ {
1925
+ value: kind,
1926
+ onValueChange: (value) => {
1927
+ if (isKindFilter(value)) {
1928
+ setKind(value);
1929
+ setOffset(0);
1930
+ setSelectedId(null);
1931
+ }
1932
+ },
1933
+ children: [
1934
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { "aria-label": "E-Mail-Art", className: "sm:w-56", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
1935
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
1936
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "all", children: "Alle Arten" }),
1937
+ KINDS.map((value) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value, children: KIND_LABELS[value] }, value))
1938
+ ] })
1939
+ ]
1940
+ }
1941
+ ),
1942
+ /* @__PURE__ */ jsxRuntime.jsxs(
1943
+ ui.Select,
1944
+ {
1945
+ value: status,
1946
+ onValueChange: (value) => {
1947
+ if (isStatusFilter(value)) {
1948
+ setStatus(value);
1949
+ setOffset(0);
1950
+ setSelectedId(null);
1951
+ }
1952
+ },
1953
+ children: [
1954
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { "aria-label": "E-Mail-Status", className: "sm:w-52", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, {}) }),
1955
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
1956
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "all", children: "Alle Status" }),
1957
+ STATUSES.map((value) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value, children: STATUS_LABELS[value] }, value))
1958
+ ] })
1959
+ ]
1960
+ }
1961
+ )
1962
+ ] })
1963
+ ] }),
1964
+ renderMessages(),
1965
+ !messagesQuery.isPending && !messagesQuery.isError && count > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-center sm:justify-between", children: [
1966
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-sm", children: [
1967
+ offset + 1,
1968
+ "-",
1969
+ offset + messages.length,
1970
+ " von ",
1971
+ count
1972
+ ] }),
1973
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
1974
+ /* @__PURE__ */ jsxRuntime.jsx(
1975
+ ui.Button,
1976
+ {
1977
+ disabled: offset === 0,
1978
+ onClick: () => changePage(Math.max(0, offset - PAGE_SIZE$1)),
1979
+ size: "small",
1980
+ variant: "secondary",
1981
+ children: "Zurück"
1982
+ }
1983
+ ),
1984
+ /* @__PURE__ */ jsxRuntime.jsx(
1985
+ ui.Button,
1986
+ {
1987
+ disabled: offset >= MAX_PAGE_OFFSET$1 || offset + messages.length >= count,
1988
+ onClick: () => changePage(Math.min(MAX_PAGE_OFFSET$1, offset + PAGE_SIZE$1)),
1989
+ size: "small",
1990
+ variant: "secondary",
1991
+ children: "Weiter"
1992
+ }
1993
+ )
1994
+ ] })
1995
+ ] }) : null
1996
+ ] }),
1997
+ selectedId ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
1998
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-start sm:justify-between", children: [
1999
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2000
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Zustellstatus" }),
2001
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Autoritativer Status und sichere Versuchsdaten." })
2002
+ ] }),
2003
+ detail && canResend(detail) ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 sm:items-end", children: [
2004
+ /* @__PURE__ */ jsxRuntime.jsx(
2005
+ ui.Button,
2006
+ {
2007
+ disabled: resend.isPending,
2008
+ onClick: () => setConfirmationOpen(true),
2009
+ size: "small",
2010
+ variant: "secondary",
2011
+ children: "Erneut senden"
2012
+ }
2013
+ ),
2014
+ retryDecision ? /* @__PURE__ */ jsxRuntime.jsx(
2015
+ ui.Button,
2016
+ {
2017
+ disabled: resend.isPending,
2018
+ onClick: () => resend.mutate(retryDecision),
2019
+ size: "small",
2020
+ variant: "transparent",
2021
+ children: "Gleiche Anfrage wiederholen"
2022
+ }
2023
+ ) : null
2024
+ ] }) : null
2025
+ ] }),
2026
+ detailQuery.isPending ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", role: "status", children: "Zustellversuche werden geladen." }) }) : detailQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", role: "alert", children: "Details konnten nicht geladen werden." }) }) : detail ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2027
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 px-6 py-5 sm:grid-cols-2 lg:grid-cols-4", children: [
2028
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2029
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Art" }),
2030
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: KIND_LABELS[detail.kind] })
2031
+ ] }),
2032
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2033
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Status" }),
2034
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: /* @__PURE__ */ jsxRuntime.jsx(Status, { message: detail }) })
2035
+ ] }),
2036
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sm:col-span-2", children: [
2037
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Ressource" }),
2038
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "break-all font-mono text-xs", children: formatResource(detail) })
2039
+ ] }),
2040
+ /* @__PURE__ */ jsxRuntime.jsx(DetailTimestamp, { label: "Erstellt", value: detail.created_at }),
2041
+ /* @__PURE__ */ jsxRuntime.jsx(
2042
+ DetailTimestamp,
2043
+ {
2044
+ label: "Aktualisiert",
2045
+ value: detail.updated_at
2046
+ }
2047
+ ),
2048
+ /* @__PURE__ */ jsxRuntime.jsx(DetailTimestamp, { label: "Versendet", value: detail.sent_at }),
2049
+ /* @__PURE__ */ jsxRuntime.jsx(
2050
+ DetailTimestamp,
2051
+ {
2052
+ label: "Fehlgeschlagen",
2053
+ value: detail.failed_at
2054
+ }
2055
+ ),
2056
+ /* @__PURE__ */ jsxRuntime.jsx(
2057
+ DetailTimestamp,
2058
+ {
2059
+ label: "Nächster Versuch",
2060
+ value: detail.next_attempt_at
2061
+ }
2062
+ ),
2063
+ detail.terminal_code ? /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2064
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Statuscode" }),
2065
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "font-mono text-xs", children: detail.terminal_code })
2066
+ ] }) : null
2067
+ ] }),
2068
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-5", children: [
2069
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Zustellversuche" }),
2070
+ attempts.length ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 grid gap-3", children: attempts.map((attempt) => /* @__PURE__ */ jsxRuntime.jsxs(
2071
+ "div",
2072
+ {
2073
+ className: "border-ui-border-base grid gap-3 rounded-md border p-4 sm:grid-cols-[auto_1fr_auto] sm:items-center",
2074
+ children: [
2075
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "font-medium", children: [
2076
+ "Versuch ",
2077
+ attempt.attempt_number
2078
+ ] }),
2079
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2080
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: ATTEMPT_STATUS_LABELS[attempt.status] }),
2081
+ attempt.failure_code ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle font-mono text-xs", children: attempt.failure_code }) : null
2082
+ ] }),
2083
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-xs sm:text-right", children: [
2084
+ formatDateTime$1(attempt.started_at),
2085
+ attempt.finished_at ? ` · ${formatDateTime$1(attempt.finished_at)}` : ""
2086
+ ] })
2087
+ ]
2088
+ },
2089
+ attempt.id
2090
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mt-3", children: "Es gibt noch keinen Zustellversuch." })
2091
+ ] })
2092
+ ] }) : null
2093
+ ] }) : null,
2094
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt, { open: confirmationOpen, onOpenChange: setConfirmationOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Content, { children: [
2095
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Header, { children: [
2096
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Title, { children: "E-Mail erneut senden?" }),
2097
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Description, { children: "Diese bewusste Entscheidung erzeugt einen neuen Versand mit einer neuen Request-UUID. Der Server prüft Status und E-Mail-Art erneut." })
2098
+ ] }),
2099
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Footer, { children: [
2100
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Cancel, { children: "Abbrechen" }),
2101
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Action, { onClick: confirmResend, children: "Erneut senden" })
2102
+ ] })
2103
+ ] }) })
2104
+ ] });
2105
+ };
2106
+ const validateTermsPdf = (file) => {
2107
+ if (!file.name.toLowerCase().endsWith(".pdf") || file.type !== "" && file.type !== "application/pdf") {
2108
+ return "Bitte eine PDF-Datei auswählen.";
2109
+ }
2110
+ if (file.size === 0) {
2111
+ return "Die PDF-Datei darf nicht leer sein.";
2112
+ }
2113
+ if (file.size > email$1.EMAIL_TERMS_MAX_PDF_BYTES) {
2114
+ return "Die PDF-Datei darf maximal 5 MiB groß sein.";
2115
+ }
2116
+ return null;
2117
+ };
2118
+ const encodeCanonicalBase64 = (buffer) => {
2119
+ const bytes = new Uint8Array(buffer);
2120
+ const chunkSize = 32768;
2121
+ let binary = "";
2122
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
2123
+ binary += String.fromCharCode(
2124
+ ...bytes.subarray(offset, offset + chunkSize)
2125
+ );
2126
+ }
2127
+ return btoa(binary);
2128
+ };
2129
+ const readPdfAsCanonicalBase64 = async (file) => encodeCanonicalBase64(await file.arrayBuffer());
2130
+ const PAGE_SIZE = 20;
2131
+ const MAX_PAGE_OFFSET = 1e4;
2132
+ const formatDateTime = (value) => {
2133
+ const date = new Date(value);
2134
+ if (Number.isNaN(date.getTime())) {
2135
+ return "-";
2136
+ }
2137
+ return new Intl.DateTimeFormat("de-AT", {
2138
+ dateStyle: "medium",
2139
+ timeStyle: "short"
2140
+ }).format(date);
2141
+ };
2142
+ const formatFileSize = (bytes) => {
2143
+ if (bytes < 1024) {
2144
+ return `${bytes} B`;
2145
+ }
2146
+ const divisor = bytes < 1024 * 1024 ? 1024 : 1024 * 1024;
2147
+ const unit = bytes < 1024 * 1024 ? "KiB" : "MiB";
2148
+ return `${new Intl.NumberFormat("de-AT", {
2149
+ maximumFractionDigits: 1
2150
+ }).format(bytes / divisor)} ${unit}`;
2151
+ };
2152
+ const newestFirst = (versions) => [...versions].sort(
2153
+ (left, right) => right.effective_at.localeCompare(left.effective_at) || right.created_at.localeCompare(left.created_at)
2154
+ );
2155
+ const EmailTermsVersionsSection = () => {
2156
+ const queryClient = reactQuery.useQueryClient();
2157
+ const fileInputRef = react.useRef(null);
2158
+ const [effectiveAt, setEffectiveAt] = react.useState("");
2159
+ const [file, setFile] = react.useState(null);
2160
+ const [formError, setFormError] = react.useState(null);
2161
+ const [offset, setOffset] = react.useState(0);
2162
+ const termsQueryKey = [
2163
+ "email",
2164
+ "terms-versions",
2165
+ { limit: PAGE_SIZE, offset }
2166
+ ];
2167
+ const termsQuery = reactQuery.useQuery({
2168
+ queryKey: termsQueryKey,
2169
+ queryFn: () => listEmailTermsVersions({ limit: PAGE_SIZE, offset })
2170
+ });
2171
+ const createTermsVersion = reactQuery.useMutation({
2172
+ mutationFn: async ({
2173
+ effectiveAt: effectiveAtValue,
2174
+ file: selectedFile,
2175
+ requestId
2176
+ }) => {
2177
+ const body = {
2178
+ request_id: requestId,
2179
+ filename: selectedFile.name,
2180
+ effective_at: effectiveAtValue,
2181
+ content_base64: await readPdfAsCanonicalBase64(selectedFile)
2182
+ };
2183
+ return createEmailTermsVersion(body);
2184
+ },
2185
+ onSuccess: async ({ created }) => {
2186
+ setFile(null);
2187
+ setEffectiveAt("");
2188
+ setFormError(null);
2189
+ setOffset(0);
2190
+ if (fileInputRef.current) {
2191
+ fileInputRef.current.value = "";
2192
+ }
2193
+ ui.toast.success(
2194
+ created ? "Die AGB-Version wurde hochgeladen." : "Diese AGB-Version war bereits vorhanden."
2195
+ );
2196
+ await queryClient.invalidateQueries({
2197
+ queryKey: ["email", "terms-versions"]
2198
+ });
2199
+ },
2200
+ onError: () => {
2201
+ ui.toast.error("Die AGB-Version konnte nicht hochgeladen werden.");
2202
+ }
2203
+ });
2204
+ const downloadTermsVersion = reactQuery.useMutation({
2205
+ mutationFn: async (version) => ({
2206
+ blob: await downloadEmailTermsVersion(version.id),
2207
+ version
2208
+ }),
2209
+ onSuccess: ({ blob, version }) => {
2210
+ const url = URL.createObjectURL(blob);
2211
+ const link = document.createElement("a");
2212
+ link.href = url;
2213
+ link.download = version.filename;
2214
+ document.body.appendChild(link);
2215
+ link.click();
2216
+ link.remove();
2217
+ window.setTimeout(() => URL.revokeObjectURL(url), 0);
2218
+ },
2219
+ onError: () => {
2220
+ ui.toast.error("Die AGB-PDF konnte nicht heruntergeladen werden.");
2221
+ }
2222
+ });
2223
+ const versions = newestFirst(termsQuery.data?.terms_versions ?? []);
2224
+ const count = termsQuery.data?.count ?? 0;
2225
+ const submit = (event) => {
2226
+ event.preventDefault();
2227
+ if (!file) {
2228
+ setFormError("Bitte eine PDF-Datei auswählen.");
2229
+ return;
2230
+ }
2231
+ const fileError = validateTermsPdf(file);
2232
+ if (fileError) {
2233
+ setFormError(fileError);
2234
+ return;
2235
+ }
2236
+ const date = new Date(effectiveAt);
2237
+ if (!effectiveAt || Number.isNaN(date.getTime())) {
2238
+ setFormError("Bitte einen gültigen Wirksamkeitszeitpunkt angeben.");
2239
+ return;
2240
+ }
2241
+ setFormError(null);
2242
+ createTermsVersion.mutate({
2243
+ effectiveAt: date.toISOString(),
2244
+ file,
2245
+ requestId: crypto.randomUUID()
2246
+ });
2247
+ };
2248
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2249
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
2250
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "AGB/PDF-Versionen" }),
2251
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Globale, unveränderliche AGB-Versionen für den E-Mail-Versand verwalten." })
2252
+ ] }),
2253
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { className: "grid gap-4 px-6 py-5", onSubmit: submit, children: [
2254
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(15rem,0.6fr)_auto] md:items-end", children: [
2255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2256
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "email-terms-pdf", children: "AGB als PDF" }),
2257
+ /* @__PURE__ */ jsxRuntime.jsx(
2258
+ ui.Input,
2259
+ {
2260
+ accept: "application/pdf,.pdf",
2261
+ "aria-describedby": `email-terms-pdf-help${formError ? " email-terms-form-error" : ""}`,
2262
+ disabled: createTermsVersion.isPending,
2263
+ id: "email-terms-pdf",
2264
+ onChange: (event) => {
2265
+ const selectedFile = event.target.files?.[0] ?? null;
2266
+ setFile(selectedFile);
2267
+ setFormError(
2268
+ selectedFile ? validateTermsPdf(selectedFile) : null
2269
+ );
2270
+ },
2271
+ ref: fileInputRef,
2272
+ type: "file"
2273
+ }
2274
+ )
2275
+ ] }),
2276
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2", children: [
2277
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "email-terms-effective-at", children: "Wirksam ab" }),
2278
+ /* @__PURE__ */ jsxRuntime.jsx(
2279
+ ui.Input,
2280
+ {
2281
+ "aria-describedby": `email-terms-effective-at-help${formError ? " email-terms-form-error" : ""}`,
2282
+ disabled: createTermsVersion.isPending,
2283
+ id: "email-terms-effective-at",
2284
+ onChange: (event) => setEffectiveAt(event.target.value),
2285
+ type: "datetime-local",
2286
+ value: effectiveAt
2287
+ }
2288
+ )
2289
+ ] }),
2290
+ /* @__PURE__ */ jsxRuntime.jsx(
2291
+ ui.Button,
2292
+ {
2293
+ disabled: createTermsVersion.isPending,
2294
+ isLoading: createTermsVersion.isPending,
2295
+ type: "submit",
2296
+ children: "Version hochladen"
2297
+ }
2298
+ )
2299
+ ] }),
2300
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-1", children: [
2301
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-xs", id: "email-terms-pdf-help", children: [
2302
+ "Nur PDF, maximal ",
2303
+ formatFileSize(email$1.EMAIL_TERMS_MAX_PDF_BYTES),
2304
+ "."
2305
+ ] }),
2306
+ /* @__PURE__ */ jsxRuntime.jsx(
2307
+ ui.Text,
2308
+ {
2309
+ className: "text-ui-fg-subtle text-xs",
2310
+ id: "email-terms-effective-at-help",
2311
+ children: "Der Zeitpunkt wird aus der lokalen Zeitzone in UTC übernommen."
2312
+ }
2313
+ ),
2314
+ formError ? /* @__PURE__ */ jsxRuntime.jsx(
2315
+ ui.Text,
2316
+ {
2317
+ className: "text-ui-fg-error text-sm",
2318
+ id: "email-terms-form-error",
2319
+ role: "alert",
2320
+ children: formError
2321
+ }
2322
+ ) : null
2323
+ ] })
2324
+ ] }),
2325
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2326
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
2327
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Hinterlegte Versionen" }),
2328
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Neueste Wirksamkeit zuerst. Downloads erfolgen authentifiziert." })
2329
+ ] }),
2330
+ termsQuery.isPending ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pb-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", role: "status", children: "AGB-Versionen werden geladen." }) }) : termsQuery.isError ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pb-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-error", role: "alert", children: "AGB-Versionen konnten nicht geladen werden." }) }) : versions.length ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2331
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden overflow-x-auto md:block", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full text-left text-sm", children: [
2332
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "border-ui-border-base border-y", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "text-ui-fg-subtle", children: [
2333
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Dateiname" }),
2334
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Wirksam ab" }),
2335
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Größe" }),
2336
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 font-medium", children: "Erstellt" }),
2337
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-6 py-3 text-right font-medium", children: "Aktion" })
2338
+ ] }) }),
2339
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: versions.map((version) => {
2340
+ const isDownloading = downloadTermsVersion.isPending && downloadTermsVersion.variables?.id === version.id;
2341
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2342
+ "tr",
2343
+ {
2344
+ className: "border-ui-border-base border-b",
2345
+ children: [
2346
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "max-w-80 break-all px-6 py-3 font-medium", children: version.filename }),
2347
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "whitespace-nowrap px-6 py-3", children: formatDateTime(version.effective_at) }),
2348
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "whitespace-nowrap px-6 py-3 tabular-nums", children: formatFileSize(version.byte_size) }),
2349
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "whitespace-nowrap px-6 py-3", children: formatDateTime(version.created_at) }),
2350
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsx(
2351
+ ui.Button,
2352
+ {
2353
+ "aria-label": `${version.filename} herunterladen`,
2354
+ disabled: downloadTermsVersion.isPending,
2355
+ isLoading: isDownloading,
2356
+ onClick: () => downloadTermsVersion.mutate(version),
2357
+ size: "small",
2358
+ variant: "secondary",
2359
+ children: "Herunterladen"
2360
+ }
2361
+ ) })
2362
+ ]
2363
+ },
2364
+ version.id
2365
+ );
2366
+ }) })
2367
+ ] }) }),
2368
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-3 px-4 py-4 md:hidden", children: versions.map((version) => {
2369
+ const isDownloading = downloadTermsVersion.isPending && downloadTermsVersion.variables?.id === version.id;
2370
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2371
+ "div",
2372
+ {
2373
+ className: "border-ui-border-base grid gap-3 rounded-md border p-4",
2374
+ children: [
2375
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "break-all font-medium", children: version.filename }),
2376
+ /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "grid grid-cols-2 gap-3 text-sm", children: [
2377
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2378
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle text-xs", children: "Wirksam ab" }),
2379
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: formatDateTime(version.effective_at) })
2380
+ ] }),
2381
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2382
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle text-xs", children: "Größe" }),
2383
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: formatFileSize(version.byte_size) })
2384
+ ] }),
2385
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-2", children: [
2386
+ /* @__PURE__ */ jsxRuntime.jsx("dt", { className: "text-ui-fg-subtle text-xs", children: "Erstellt" }),
2387
+ /* @__PURE__ */ jsxRuntime.jsx("dd", { children: formatDateTime(version.created_at) })
2388
+ ] })
2389
+ ] }),
2390
+ /* @__PURE__ */ jsxRuntime.jsx(
2391
+ ui.Button,
2392
+ {
2393
+ "aria-label": `${version.filename} herunterladen`,
2394
+ disabled: downloadTermsVersion.isPending,
2395
+ isLoading: isDownloading,
2396
+ onClick: () => downloadTermsVersion.mutate(version),
2397
+ size: "small",
2398
+ variant: "secondary",
2399
+ children: "Herunterladen"
2400
+ }
2401
+ )
2402
+ ]
2403
+ },
2404
+ version.id
2405
+ );
2406
+ }) })
2407
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pb-6", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle", children: "Es wurde noch keine AGB-Version hinterlegt." }) }),
2408
+ !termsQuery.isPending && !termsQuery.isError && count > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-center sm:justify-between", children: [
2409
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-sm", children: [
2410
+ offset + 1,
2411
+ "-",
2412
+ offset + versions.length,
2413
+ " von ",
2414
+ count
2415
+ ] }),
2416
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2417
+ /* @__PURE__ */ jsxRuntime.jsx(
2418
+ ui.Button,
2419
+ {
2420
+ disabled: offset === 0,
2421
+ onClick: () => setOffset(Math.max(0, offset - PAGE_SIZE)),
2422
+ size: "small",
2423
+ variant: "secondary",
2424
+ children: "Neuere"
2425
+ }
2426
+ ),
2427
+ /* @__PURE__ */ jsxRuntime.jsx(
2428
+ ui.Button,
2429
+ {
2430
+ disabled: offset >= MAX_PAGE_OFFSET || offset + versions.length >= count,
2431
+ onClick: () => setOffset(Math.min(MAX_PAGE_OFFSET, offset + PAGE_SIZE)),
2432
+ size: "small",
2433
+ variant: "secondary",
2434
+ children: "Ältere"
2435
+ }
2436
+ )
2437
+ ] })
2438
+ ] }) : null
2439
+ ] })
2440
+ ] });
2441
+ };
2442
+ const EmailPluginContent = () => {
2443
+ const { enabled, isPending, isError } = usePluginFeatureEnabled("email");
2444
+ if (isPending || isError) {
2445
+ return null;
2446
+ }
2447
+ return enabled ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2448
+ /* @__PURE__ */ jsxRuntime.jsx(EmailTermsVersionsSection, {}),
2449
+ /* @__PURE__ */ jsxRuntime.jsx(EmailStatusPage, {})
2450
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureDisabledNotice, { pluginKey: "email" });
2451
+ };
2452
+ const EmailPluginRoute = () => /* @__PURE__ */ jsxRuntime.jsxs(PluginPageShell, { pluginKey: "email", children: [
2453
+ /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureSections, { pluginKey: "email" }),
2454
+ /* @__PURE__ */ jsxRuntime.jsx(EmailPluginContent, {})
2455
+ ] });
2456
+ const PluginDetailPage = () => {
2457
+ const { plugin_key: pluginKey = "" } = reactRouterDom.useParams();
2458
+ return /* @__PURE__ */ jsxRuntime.jsx(PluginPageShell, { pluginKey, children: /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureSections, { pluginKey }) });
2459
+ };
2460
+ const widgetModule = { widgets: [
2461
+ {
2462
+ Component: OrderDocumentsWidget,
2463
+ zone: ["order.details.side.after"],
2464
+ widgetId: "Widget-93c9"
2465
+ },
2466
+ {
2467
+ Component: OrderPackingListsWidget,
2468
+ zone: ["order.details.side.after"],
2469
+ widgetId: "Widget-381c"
2470
+ }
2471
+ ] };
2472
+ const routeModule = {
2473
+ routes: [
2474
+ {
2475
+ Component: PluginFeaturesSettingsPage,
2476
+ path: "/settings/hartl-services",
2477
+ handle: { label: config.label, translationNs: config.translationNs }
2478
+ },
2479
+ {
2480
+ Component: EmailPluginRoute,
2481
+ path: "/settings/hartl-services/email"
936
2482
  },
937
2483
  {
938
2484
  Component: PluginDetailPage,