@hartl-services/medusa-base 0.3.0 → 1.0.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.
- package/.medusa/server/src/admin/index.js +2164 -29
- package/.medusa/server/src/admin/index.mjs +2166 -31
- package/.medusa/server/src/api/admin/email/messages/[id]/resends/route.js +24 -0
- package/.medusa/server/src/api/admin/email/messages/[id]/route.js +12 -0
- package/.medusa/server/src/api/admin/email/messages/route.js +12 -0
- package/.medusa/server/src/api/admin/email/terms-versions/[id]/download/route.js +16 -0
- package/.medusa/server/src/api/admin/email/terms-versions/route.js +47 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/download/route.js +21 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/[document_id]/regenerate/route.js +38 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/regenerate/route.js +32 -0
- package/.medusa/server/src/api/admin/orders/[id]/documents/route.js +36 -0
- package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/download/route.js +24 -0
- package/.medusa/server/src/api/admin/orders/[id]/packing-lists/[fulfillment_id]/retry/route.js +44 -0
- package/.medusa/server/src/api/admin/orders/[id]/packing-lists/route.js +82 -0
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/route.js +31 -0
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/actions/[action_key]/runs/route.js +16 -0
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/route.js +14 -0
- package/.medusa/server/src/api/admin/plugin-features/[plugin_key]/settings/route.js +22 -0
- package/.medusa/server/src/api/admin/plugin-features/route.js +6 -4
- package/.medusa/server/src/api/document-download-response.js +16 -0
- package/.medusa/server/src/api/document-response.js +23 -0
- package/.medusa/server/src/api/files/[asset]/route.js +24 -0
- package/.medusa/server/src/api/files/file-response.js +43 -0
- package/.medusa/server/src/api/files/gateway-options.js +31 -0
- package/.medusa/server/src/api/files/private/[token]/route.js +29 -0
- package/.medusa/server/src/api/files/upload/[token]/route.js +66 -0
- package/.medusa/server/src/api/middlewares.js +186 -5
- package/.medusa/server/src/api/packing-list-response.js +22 -0
- package/.medusa/server/src/api/plugin-actions.js +221 -0
- package/.medusa/server/src/api/plugin-features-http.js +67 -8
- package/.medusa/server/src/api/route.js +27 -0
- package/.medusa/server/src/api/store/documents/[id]/download/route.js +21 -0
- package/.medusa/server/src/api/store/documents/[id]/route.js +15 -0
- package/.medusa/server/src/api/store/documents/route.js +11 -0
- package/.medusa/server/src/api/terms-download-response.js +12 -0
- package/.medusa/server/src/documents/config/index.js +18 -0
- package/.medusa/server/src/documents/paths/documents.js +6 -0
- package/.medusa/server/src/documents/paths/index.js +18 -0
- package/.medusa/server/src/documents/plugin-features.js +24 -0
- package/.medusa/server/src/documents/schemas/documents.js +18 -0
- package/.medusa/server/src/documents/schemas/index.js +18 -0
- package/.medusa/server/src/documents/scripts/verify-documents-file-storage.js +55 -0
- package/.medusa/server/src/documents/templates/financial-document.js +142 -0
- package/.medusa/server/src/documents/templates/format.js +31 -0
- package/.medusa/server/src/documents/templates/html-factory.js +40 -0
- package/.medusa/server/src/documents/templates/html-utils.js +13 -0
- package/.medusa/server/src/documents/templates/layout.js +97 -0
- package/.medusa/server/src/documents/templates/packing-list.js +50 -0
- package/.medusa/server/src/documents/types/admin-api.js +3 -0
- package/.medusa/server/src/documents/types/document-summary.js +3 -0
- package/.medusa/server/src/documents/types/documents.js +3 -0
- package/.medusa/server/src/documents/types/html-factory.js +6 -0
- package/.medusa/server/src/documents/types/index.js +22 -0
- package/.medusa/server/src/documents/types/store-api.js +3 -0
- package/.medusa/server/src/documents/utils/document-merchant.js +22 -0
- package/.medusa/server/src/documents/utils/html-to-pdf.js +61 -0
- package/.medusa/server/src/documents/utils/private-file-storage.js +64 -0
- package/.medusa/server/src/documents/utils/resolve-services.js +7 -0
- package/.medusa/server/src/email/application/handle-invite-event.js +312 -0
- package/.medusa/server/src/email/application/handle-password-reset-event.js +354 -0
- package/.medusa/server/src/email/config/index.js +84 -0
- package/.medusa/server/src/email/config/notification-module.js +20 -0
- package/.medusa/server/src/email/domain/business-keys.js +50 -0
- package/.medusa/server/src/email/domain/documents-fact-fingerprint.js +7 -0
- package/.medusa/server/src/email/domain/event-schemas.js +36 -0
- package/.medusa/server/src/email/domain/failure-classification.js +53 -0
- package/.medusa/server/src/email/plugin-features.js +31 -0
- package/.medusa/server/src/email/rendering/default-templates.js +91 -0
- package/.medusa/server/src/email/rendering/render-email.js +37 -0
- package/.medusa/server/src/email/rendering/resolve-shop-configuration.js +48 -0
- package/.medusa/server/src/email/types/email-kinds.js +18 -0
- package/.medusa/server/src/email/types/index.js +21 -0
- package/.medusa/server/src/email/types/shop-configuration.js +3 -0
- package/.medusa/server/src/email/types/smtp-configuration.js +3 -0
- package/.medusa/server/src/email/types/template-data.js +79 -0
- package/.medusa/server/src/files/config/index.js +256 -0
- package/.medusa/server/src/files/gateway/index.js +115 -0
- package/.medusa/server/src/files/types/index.js +6 -0
- package/.medusa/server/src/files/types/public.js +6 -0
- package/.medusa/server/src/jobs/document-fact-publication-reconciliation.js +18 -0
- package/.medusa/server/src/jobs/document-resolution-reconciliation.js +20 -0
- package/.medusa/server/src/jobs/packing-list-reconciliation.js +49 -0
- package/.medusa/server/src/jobs/reconcile-email-attempts.js +63 -0
- package/.medusa/server/src/jobs/reconcile-pending-email-messages.js +41 -0
- package/.medusa/server/src/jobs/retry-email-messages.js +27 -0
- package/.medusa/server/src/migration-scripts/dismiss-orphaned-cart-promotion-links.js +26 -0
- package/.medusa/server/src/modules/documents/domain/cancellation-snapshot.js +97 -0
- package/.medusa/server/src/modules/documents/domain/document-number.js +36 -0
- package/.medusa/server/src/modules/documents/domain/document-resolution.js +24 -0
- package/.medusa/server/src/modules/documents/domain/issuance-rules.js +37 -0
- package/.medusa/server/src/modules/documents/domain/merchant.js +86 -0
- package/.medusa/server/src/modules/documents/domain/options.js +40 -0
- package/.medusa/server/src/modules/documents/domain/packing-list.js +90 -0
- package/.medusa/server/src/modules/documents/domain/snapshot-normalization.js +17 -0
- package/.medusa/server/src/modules/documents/domain/tax-breakdown.js +66 -0
- package/.medusa/server/src/modules/documents/index.js +15 -0
- package/.medusa/server/src/modules/documents/loaders/assert-private-file-storage.js +20 -0
- package/.medusa/server/src/modules/documents/migrations/Migration20260827061749.js +75 -0
- package/.medusa/server/src/modules/documents/migrations/Migration20260830111207.js +28 -0
- package/.medusa/server/src/modules/documents/models/document-artifact.js +25 -0
- package/.medusa/server/src/modules/documents/models/document-number-sequence.js +29 -0
- package/.medusa/server/src/modules/documents/models/document.js +82 -0
- package/.medusa/server/src/modules/documents/models/order-cancellation-resolution.js +51 -0
- package/.medusa/server/src/modules/documents/models/packing-list-revision.js +36 -0
- package/.medusa/server/src/modules/documents/models/packing-list.js +27 -0
- package/.medusa/server/src/modules/documents/models/refund-document-resolution.js +66 -0
- package/.medusa/server/src/modules/documents/models/shipment-invoice-assignment.js +51 -0
- package/.medusa/server/src/modules/documents/models/shipment-invoice-coordinator.js +44 -0
- package/.medusa/server/src/modules/documents/service.js +1701 -0
- package/.medusa/server/src/modules/email/domain/attachment.js +25 -0
- package/.medusa/server/src/modules/email/domain/message-state.js +69 -0
- package/.medusa/server/src/modules/email/index.js +13 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260826173422.js +32 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260827172945.js +16 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260827194804.js +22 -0
- package/.medusa/server/src/modules/email/migrations/Migration20260906195205.js +16 -0
- package/.medusa/server/src/modules/email/models/email-attempt.js +56 -0
- package/.medusa/server/src/modules/email/models/email-message.js +71 -0
- package/.medusa/server/src/modules/email/models/email-terms-version.js +37 -0
- package/.medusa/server/src/modules/email/service.js +1285 -0
- package/.medusa/server/src/modules/plugin-features/domain/plugin-action-runs.js +92 -0
- package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +213 -0
- package/.medusa/server/src/modules/plugin-features/domain/secret-box.js +56 -0
- package/.medusa/server/src/modules/plugin-features/migrations/Migration20260928205031.js +16 -0
- package/.medusa/server/src/modules/plugin-features/migrations/Migration20260928215428.js +17 -0
- package/.medusa/server/src/modules/plugin-features/models/plugin-action-run.js +43 -0
- package/.medusa/server/src/modules/plugin-features/models/plugin-setting-state.js +32 -0
- package/.medusa/server/src/modules/plugin-features/service.js +419 -17
- package/.medusa/server/src/providers/s3-cache/cache/atomic-write.js +25 -0
- package/.medusa/server/src/providers/s3-cache/cache/cache-manager.js +115 -0
- package/.medusa/server/src/providers/s3-cache/cache/keyed-lock.js +28 -0
- package/.medusa/server/src/providers/s3-cache/index.js +8 -0
- package/.medusa/server/src/providers/s3-cache/object-key.js +53 -0
- package/.medusa/server/src/providers/s3-cache/s3-cache-file.js +229 -0
- package/.medusa/server/src/providers/s3-cache/s3-object-store.js +86 -0
- package/.medusa/server/src/providers/smtp/index.js +8 -0
- package/.medusa/server/src/providers/smtp/service.js +236 -0
- package/.medusa/server/src/providers/smtp/types.js +11 -0
- package/.medusa/server/src/public/action-progress.js +67 -0
- package/.medusa/server/src/public/config.js +186 -0
- package/.medusa/server/src/public/descriptor.js +199 -2
- package/.medusa/server/src/public/effective-features.js +9 -5
- package/.medusa/server/src/public/enforcement.js +18 -8
- package/.medusa/server/src/public/gateway.js +19 -0
- package/.medusa/server/src/public/index.js +10 -2
- package/.medusa/server/src/public/instrumentation.js +56 -0
- package/.medusa/server/src/public/module-access.js +14 -0
- package/.medusa/server/src/public/settings.js +17 -0
- package/.medusa/server/src/public/shop-config.js +12 -0
- package/.medusa/server/src/public/shop-master-data.js +95 -0
- package/.medusa/server/src/public/shop-types.js +24 -0
- package/.medusa/server/src/shop-config/build-food-shop-config.js +248 -0
- package/.medusa/server/src/shop-config/options.js +71 -0
- package/.medusa/server/src/subscribers/affiliate-consignment-reminder-due.js +26 -0
- package/.medusa/server/src/subscribers/documents-order-canceled.js +24 -0
- package/.medusa/server/src/subscribers/documents-order-cancellation-ready.js +26 -0
- package/.medusa/server/src/subscribers/documents-refund-ready.js +24 -0
- package/.medusa/server/src/subscribers/documents-shipment-created.js +36 -0
- package/.medusa/server/src/subscribers/documents-shipment-invoice-assignment-decided.js +26 -0
- package/.medusa/server/src/subscribers/email-order-canceled.js +28 -0
- package/.medusa/server/src/subscribers/email-shipment-created.js +32 -0
- package/.medusa/server/src/subscribers/order-placed.js +27 -0
- package/.medusa/server/src/subscribers/packing-list-allocation-revised.js +32 -0
- package/.medusa/server/src/subscribers/packing-list-fulfillment-canceled.js +54 -0
- package/.medusa/server/src/subscribers/packing-list-fulfillment-created.js +31 -0
- package/.medusa/server/src/subscribers/password-reset.js +23 -0
- package/.medusa/server/src/subscribers/payment-refunded.js +38 -0
- package/.medusa/server/src/subscribers/return-received.js +27 -0
- package/.medusa/server/src/subscribers/return-requested.js +27 -0
- package/.medusa/server/src/subscribers/user-invite.js +23 -0
- package/.medusa/server/src/utils/cart-promotion-links.js +101 -0
- package/.medusa/server/src/utils/discover-complete-cart-participants.js +9 -2
- package/.medusa/server/src/utils/discover-plugin-features.js +47 -9
- package/.medusa/server/src/workflows/documents/issue-cancellation-after-refund.js +246 -0
- package/.medusa/server/src/workflows/documents/issue-document.js +163 -0
- package/.medusa/server/src/workflows/documents/issue-invoice-after-shipment.js +521 -0
- package/.medusa/server/src/workflows/documents/issue-packing-list.js +384 -0
- package/.medusa/server/src/workflows/documents/publish-document-facts.js +53 -0
- package/.medusa/server/src/workflows/documents/reconcile-document-resolutions.js +48 -0
- package/.medusa/server/src/workflows/documents/reconcile-order-cancellation.js +92 -0
- package/.medusa/server/src/workflows/documents/regenerate-issued-document.js +61 -0
- package/.medusa/server/src/workflows/email/dispatch-email-message.js +299 -0
- package/.medusa/server/src/workflows/email/documents-required-facts.js +30 -0
- package/.medusa/server/src/workflows/email/prepare-affiliate-reminder-email.js +58 -0
- package/.medusa/server/src/workflows/email/prepare-order-canceled-email.js +64 -0
- package/.medusa/server/src/workflows/email/prepare-order-received-email.js +56 -0
- package/.medusa/server/src/workflows/email/prepare-refund-email.js +47 -0
- package/.medusa/server/src/workflows/email/prepare-return-email.js +75 -0
- package/.medusa/server/src/workflows/email/prepare-shipment-email.js +82 -0
- package/.medusa/server/src/workflows/email/retry-email-messages.js +31 -0
- package/.medusa/server/src/workflows/hooks/complete-cart-participants.js +28 -16
- package/.medusa/server/src/workflows/hooks/dismiss-deleted-promotion-cart-links.js +23 -0
- package/README.md +2004 -11
- package/dist/admin-components/features/plugin-features/components/PluginActionsSection.d.ts +6 -0
- package/dist/admin-components/features/plugin-features/components/PluginActionsSection.js +66 -0
- package/dist/admin-components/features/plugin-features/components/PluginDetailPage.d.ts +2 -0
- package/dist/admin-components/features/plugin-features/components/PluginDetailPage.js +9 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeatureGate.d.ts +22 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeatureGate.js +18 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeatureSections.d.ts +11 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeatureSections.js +19 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeaturesCard.d.ts +12 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeaturesCard.js +34 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.d.ts +5 -0
- package/dist/admin-components/features/plugin-features/components/PluginFeaturesSettingsPage.js +16 -0
- package/dist/admin-components/features/plugin-features/components/PluginHubCard.d.ts +7 -0
- package/dist/admin-components/features/plugin-features/components/PluginHubCard.js +21 -0
- package/dist/admin-components/features/plugin-features/components/PluginPageShell.d.ts +13 -0
- package/dist/admin-components/features/plugin-features/components/PluginPageShell.js +20 -0
- package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.d.ts +7 -0
- package/dist/admin-components/features/plugin-features/components/PluginSettingsForm.js +39 -0
- package/dist/admin-components/features/plugin-features/components/PluginStatusChips.d.ts +5 -0
- package/dist/admin-components/features/plugin-features/components/PluginStatusChips.js +8 -0
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +114 -0
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.js +40 -0
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +392 -0
- package/dist/admin-components/features/plugin-features/hooks/usePluginFeatures.js +128 -0
- package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +93 -0
- package/dist/admin-components/features/plugin-features/hooks/usePluginSettingsForm.js +44 -0
- package/dist/admin-components/features/plugin-features/lib/action-runs.d.ts +19 -0
- package/dist/admin-components/features/plugin-features/lib/action-runs.js +74 -0
- package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +363 -0
- package/dist/admin-components/features/plugin-features/lib/plugin-features-admin-client.js +9 -0
- package/dist/admin-components/features/plugin-features/lib/plugin-paths.d.ts +4 -0
- package/dist/admin-components/features/plugin-features/lib/plugin-paths.js +4 -0
- package/dist/admin-components/features/plugin-features/lib/settings-draft.d.ts +28 -0
- package/dist/admin-components/features/plugin-features/lib/settings-draft.js +86 -0
- package/dist/admin-components/features/plugin-features/queries/pluginFeatureKeys.d.ts +7 -0
- package/dist/admin-components/features/plugin-features/queries/pluginFeatureKeys.js +7 -0
- package/dist/admin-components/i18n/texts.d.ts +90 -0
- package/dist/admin-components/i18n/texts.js +90 -0
- package/dist/admin-components/index.d.ts +14 -0
- package/dist/admin-components/index.js +14 -0
- package/dist/admin-components/lib/errors.d.ts +6 -0
- package/dist/admin-components/lib/errors.js +27 -0
- package/dist/admin-components/lib/sdk.d.ts +2 -0
- package/dist/admin-components/lib/sdk.js +13 -0
- package/dist/documents/config/index.d.ts +11 -0
- package/dist/documents/config/index.js +17 -0
- package/dist/documents/templates/financial-document.d.ts +3 -0
- package/dist/documents/templates/financial-document.js +141 -0
- package/dist/documents/templates/format.d.ts +16 -0
- package/dist/documents/templates/format.js +30 -0
- package/dist/documents/templates/html-factory.d.ts +11 -0
- package/dist/documents/templates/html-factory.js +39 -0
- package/dist/documents/templates/html-utils.d.ts +2 -0
- package/dist/documents/templates/html-utils.js +12 -0
- package/dist/documents/templates/layout.d.ts +17 -0
- package/dist/documents/templates/layout.js +96 -0
- package/dist/documents/templates/packing-list.d.ts +3 -0
- package/dist/documents/templates/packing-list.js +49 -0
- package/dist/documents/types/admin-api.d.ts +1 -0
- package/dist/documents/types/admin-api.js +2 -0
- package/dist/documents/types/document-summary.d.ts +1 -0
- package/dist/documents/types/document-summary.js +2 -0
- package/dist/documents/types/documents.d.ts +174 -0
- package/dist/documents/types/documents.js +2 -0
- package/dist/documents/types/html-factory.d.ts +7 -0
- package/dist/documents/types/html-factory.js +5 -0
- package/dist/documents/types/index.d.ts +5 -0
- package/dist/documents/types/index.js +21 -0
- package/dist/documents/types/store-api.d.ts +1 -0
- package/dist/documents/types/store-api.js +2 -0
- package/dist/email/config/index.d.ts +17 -0
- package/dist/email/config/index.js +83 -0
- package/dist/email/config/notification-module.d.ts +22 -0
- package/dist/email/config/notification-module.js +19 -0
- package/dist/email/types/email-kinds.d.ts +15 -0
- package/dist/email/types/email-kinds.js +17 -0
- package/dist/email/types/index.d.ts +4 -0
- package/dist/email/types/index.js +20 -0
- package/dist/email/types/shop-configuration.d.ts +43 -0
- package/dist/email/types/shop-configuration.js +2 -0
- package/dist/email/types/smtp-configuration.d.ts +9 -0
- package/dist/email/types/smtp-configuration.js +2 -0
- package/dist/email/types/template-data.d.ts +100 -0
- package/dist/email/types/template-data.js +78 -0
- package/dist/files/config/index.d.ts +32 -0
- package/dist/files/config/index.js +255 -0
- package/dist/files/gateway/index.d.ts +30 -0
- package/dist/files/gateway/index.js +114 -0
- package/dist/files/types/index.d.ts +2 -0
- package/dist/files/types/index.js +5 -0
- package/dist/files/types/public.d.ts +90 -0
- package/dist/files/types/public.js +5 -0
- package/dist/modules/documents/domain/merchant.d.ts +22 -0
- package/dist/modules/documents/domain/merchant.js +85 -0
- package/dist/modules/documents/domain/options.d.ts +34 -0
- package/dist/modules/documents/domain/options.js +39 -0
- package/dist/public/action-progress.d.ts +25 -0
- package/dist/public/action-progress.js +66 -0
- package/dist/public/config.d.ts +59 -0
- package/dist/public/config.js +185 -0
- package/dist/public/descriptor.d.ts +3 -2
- package/dist/public/descriptor.js +198 -1
- package/dist/public/effective-features.d.ts +2 -1
- package/dist/public/effective-features.js +8 -4
- package/dist/public/enforcement.d.ts +7 -0
- package/dist/public/enforcement.js +17 -7
- package/dist/public/gateway.d.ts +2 -0
- package/dist/public/gateway.js +18 -0
- package/dist/public/index.d.ts +5 -2
- package/dist/public/index.js +9 -1
- package/dist/public/instrumentation.d.ts +12 -0
- package/dist/public/instrumentation.js +55 -0
- package/dist/public/module-access.d.ts +3 -0
- package/dist/public/module-access.js +13 -0
- package/dist/public/settings.d.ts +11 -0
- package/dist/public/settings.js +16 -0
- package/dist/public/shop-config.d.ts +8 -0
- package/dist/public/shop-config.js +11 -0
- package/dist/public/shop-master-data.d.ts +25 -0
- package/dist/public/shop-master-data.js +94 -0
- package/dist/public/shop-types.d.ts +7 -0
- package/dist/public/shop-types.js +23 -0
- package/dist/public/types.d.ts +113 -1
- package/dist/shop-config/build-food-shop-config.d.ts +44 -0
- package/dist/shop-config/build-food-shop-config.js +247 -0
- package/dist/shop-config/options.d.ts +41 -0
- package/dist/shop-config/options.js +70 -0
- package/dist/utils/discover-plugin-features.d.ts +28 -0
- package/dist/utils/discover-plugin-features.js +96 -0
- package/package.json +53 -6
|
@@ -1,35 +1,99 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
|
|
4
|
+
import { toast, Container, Heading, Text, Alert, Button, Label, Switch, Input, Select, Hint, StatusBadge, usePrompt, FocusModal, Prompt } from "@medusajs/ui";
|
|
4
5
|
import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
|
|
5
|
-
import {
|
|
6
|
+
import { documentsPaths, packingListsPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/documents";
|
|
6
7
|
import Medusa from "@medusajs/js-sdk";
|
|
8
|
+
import { Link, useParams } from "react-router-dom";
|
|
9
|
+
import { pluginFeaturesAdminPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/plugin-features";
|
|
10
|
+
import { emailAdminPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/email";
|
|
11
|
+
import { EMAIL_TERMS_MAX_PDF_BYTES } from "@hartl-services/medusa-food-supplements-contracts/schemas/email";
|
|
7
12
|
const texts = {
|
|
8
13
|
menu: {
|
|
9
14
|
label: "Hartl Services Plugins"
|
|
10
15
|
},
|
|
11
16
|
page: {
|
|
12
17
|
title: "Hartl Services Plugins",
|
|
13
|
-
description: "Plugins und
|
|
18
|
+
description: "Plugins ein- oder ausschalten und öffnen, um Funktionen, Einstellungen und Aktionen zu verwalten. Änderungen gelten sofort; abgeschaltete Funktionen sind im Shop nicht erreichbar."
|
|
14
19
|
},
|
|
15
20
|
states: {
|
|
16
|
-
loading: "
|
|
17
|
-
error: "
|
|
21
|
+
loading: "Plugins werden geladen…",
|
|
22
|
+
error: "Plugins konnten nicht geladen werden.",
|
|
18
23
|
empty: "Keine Plugins mit Funktionsschaltern installiert.",
|
|
19
|
-
retry: "Erneut laden"
|
|
24
|
+
retry: "Erneut laden",
|
|
25
|
+
notFound: "Dieses Plugin ist nicht installiert."
|
|
20
26
|
},
|
|
21
27
|
plugin: {
|
|
22
|
-
switchLabel: (label) => `${label} aktiv
|
|
28
|
+
switchLabel: (label) => `${label} aktiv`,
|
|
29
|
+
open: "Öffnen",
|
|
30
|
+
openLabel: (label) => `${label} öffnen`
|
|
31
|
+
},
|
|
32
|
+
chips: {
|
|
33
|
+
enabled: "aktiv",
|
|
34
|
+
disabled: "aus",
|
|
35
|
+
configured: "konfiguriert",
|
|
36
|
+
notConfigured: (labels) => `nicht konfiguriert: ${labels}`
|
|
37
|
+
},
|
|
38
|
+
detail: {
|
|
39
|
+
back: "Zurück zu Hartl Services Plugins"
|
|
40
|
+
},
|
|
41
|
+
sections: {
|
|
42
|
+
features: "Funktionen",
|
|
43
|
+
settings: "Einstellungen",
|
|
44
|
+
actions: "Aktionen"
|
|
23
45
|
},
|
|
24
46
|
hints: {
|
|
25
47
|
pluginDisabled: "Das Plugin ist ausgeschaltet. Alle Funktionen sind inaktiv, ihre Einstellungen bleiben erhalten.",
|
|
26
48
|
parentDisabled: (parentLabel) => `Inaktiv, solange „${parentLabel}“ ausgeschaltet ist.`,
|
|
27
49
|
saving: "Wird gespeichert…"
|
|
28
50
|
},
|
|
51
|
+
gate: {
|
|
52
|
+
disabled: "Diese Funktion ist unter Einstellungen → Hartl Services Plugins ausgeschaltet.",
|
|
53
|
+
disabledNamed: (label) => `„${label}“ ist unter Einstellungen → Hartl Services Plugins ausgeschaltet.`,
|
|
54
|
+
openHub: "Zu Hartl Services Plugins"
|
|
55
|
+
},
|
|
56
|
+
settings: {
|
|
57
|
+
missing: (labels) => `Nicht konfiguriert: ${labels}`,
|
|
58
|
+
save: "Einstellungen speichern",
|
|
59
|
+
secretPlaceholder: "gesetzt",
|
|
60
|
+
secretClear: "Löschen",
|
|
61
|
+
secretClearPending: "Wird beim Speichern gelöscht; danach gilt der Wert aus der Konfiguration, falls vorhanden.",
|
|
62
|
+
secretClearUndo: "Nicht löschen",
|
|
63
|
+
secretClearLabel: (label) => `${label} löschen`,
|
|
64
|
+
secretClearUndoLabel: (label) => `${label} nicht löschen`,
|
|
65
|
+
invalidates: (labels) => `Beim Ändern muss ${labels} neu eingegeben werden.`,
|
|
66
|
+
required: "Pflichtfeld",
|
|
67
|
+
invalidNumber: "Bitte eine gültige Zahl eingeben.",
|
|
68
|
+
outOfRange: "Der Wert liegt außerhalb des erlaubten Bereichs.",
|
|
69
|
+
numberMin: (min) => `Mindestens ${min}.`,
|
|
70
|
+
numberMax: (max) => `Höchstens ${max}.`,
|
|
71
|
+
numberRange: (min, max) => `Zwischen ${min} und ${max}.`,
|
|
72
|
+
selectPlaceholder: "Bitte auswählen",
|
|
73
|
+
clearHint: "Ein leeres Feld speichern entfernt den gespeicherten Wert (dann gilt Konfiguration oder Standard).",
|
|
74
|
+
source: {
|
|
75
|
+
stored: "gespeichert",
|
|
76
|
+
config: "aus Konfiguration",
|
|
77
|
+
default: "Standard",
|
|
78
|
+
none: "nicht gesetzt"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
actions: {
|
|
82
|
+
confirmTitle: (label) => `${label}?`,
|
|
83
|
+
confirm: "Ausführen",
|
|
84
|
+
cancel: "Abbrechen",
|
|
85
|
+
runningSince: (time) => `läuft seit ${time}`,
|
|
86
|
+
lastCompleted: (time) => `Zuletzt erfolgreich: ${time}`,
|
|
87
|
+
lastFailed: (time) => `Zuletzt fehlgeschlagen: ${time}`
|
|
88
|
+
},
|
|
29
89
|
toasts: {
|
|
30
90
|
saved: "Einstellung wurde gespeichert.",
|
|
31
91
|
saveFailed: "Einstellung konnte nicht gespeichert werden.",
|
|
32
|
-
conflict: "Die Einstellungen wurden zwischenzeitlich geändert und neu geladen."
|
|
92
|
+
conflict: "Die Einstellungen wurden zwischenzeitlich geändert und neu geladen.",
|
|
93
|
+
actionCompleted: (label) => `„${label}“ wurde ausgeführt.`,
|
|
94
|
+
actionStarted: (label) => `„${label}“ wurde gestartet und läuft im Hintergrund.`,
|
|
95
|
+
actionFailed: (label) => `„${label}“ ist fehlgeschlagen.`,
|
|
96
|
+
actionAlreadyRunning: (label) => `„${label}“ läuft bereits; der Fortschritt wird angezeigt.`
|
|
33
97
|
},
|
|
34
98
|
errors: {
|
|
35
99
|
unknown: "Unbekannter Fehler"
|
|
@@ -58,62 +122,641 @@ const getErrorMessage = (error) => {
|
|
|
58
122
|
return void 0;
|
|
59
123
|
};
|
|
60
124
|
const sdk = new Medusa({
|
|
61
|
-
baseUrl: __BACKEND_URL__ ?? "/",
|
|
125
|
+
baseUrl: (typeof __BACKEND_URL__ === "undefined" ? void 0 : __BACKEND_URL__) ?? "/",
|
|
62
126
|
auth: {
|
|
63
|
-
type: __AUTH_TYPE__ ?? "session",
|
|
64
|
-
jwtTokenStorageKey: __JWT_TOKEN_STORAGE_KEY__ || void 0
|
|
127
|
+
type: (typeof __AUTH_TYPE__ === "undefined" ? void 0 : __AUTH_TYPE__) ?? "session",
|
|
128
|
+
jwtTokenStorageKey: (typeof __JWT_TOKEN_STORAGE_KEY__ === "undefined" ? void 0 : __JWT_TOKEN_STORAGE_KEY__) || void 0
|
|
65
129
|
}
|
|
66
130
|
});
|
|
67
131
|
const listPluginFeatures = () => sdk.client.fetch(
|
|
68
132
|
pluginFeaturesAdminPaths.get.list,
|
|
69
133
|
{ method: "GET" }
|
|
70
134
|
);
|
|
135
|
+
const retrievePluginFeatures = (pluginKey) => sdk.client.fetch(
|
|
136
|
+
pluginFeaturesAdminPaths.get.retrieve(pluginKey),
|
|
137
|
+
{ method: "GET" }
|
|
138
|
+
);
|
|
71
139
|
const updatePluginFeatures = (payload) => sdk.client.fetch(
|
|
72
140
|
pluginFeaturesAdminPaths.post.update,
|
|
73
141
|
{ method: "POST", body: payload }
|
|
74
142
|
);
|
|
143
|
+
const updatePluginSettings = (pluginKey, payload) => sdk.client.fetch(
|
|
144
|
+
pluginFeaturesAdminPaths.post.updateSettings(pluginKey),
|
|
145
|
+
{ method: "POST", body: payload }
|
|
146
|
+
);
|
|
147
|
+
const runPluginAction = (pluginKey, actionKey) => sdk.client.fetch(
|
|
148
|
+
pluginFeaturesAdminPaths.post.runAction(pluginKey, actionKey),
|
|
149
|
+
{ method: "POST", body: {} }
|
|
150
|
+
);
|
|
151
|
+
const listPluginActionRuns = (pluginKey, actionKey, limit = 1) => sdk.client.fetch(
|
|
152
|
+
pluginFeaturesAdminPaths.get.actionRuns(pluginKey, actionKey),
|
|
153
|
+
{ method: "GET", query: { limit } }
|
|
154
|
+
);
|
|
75
155
|
const pluginFeatureKeys = {
|
|
76
156
|
all: ["plugin-features"],
|
|
77
|
-
list: () => [...pluginFeatureKeys.all, "list"]
|
|
157
|
+
list: () => [...pluginFeatureKeys.all, "list"],
|
|
158
|
+
details: () => [...pluginFeatureKeys.all, "detail"],
|
|
159
|
+
detail: (pluginKey) => [...pluginFeatureKeys.details(), pluginKey],
|
|
160
|
+
actionRuns: (pluginKey, actionKey) => [...pluginFeatureKeys.all, "action-runs", pluginKey, actionKey]
|
|
78
161
|
};
|
|
79
162
|
const isConflict = (error) => typeof error === "object" && error !== null && "status" in error && error.status === 409;
|
|
163
|
+
const storePlugin = (queryClient, plugin2) => {
|
|
164
|
+
queryClient.setQueryData(
|
|
165
|
+
pluginFeatureKeys.list(),
|
|
166
|
+
(current) => current && {
|
|
167
|
+
plugins: current.plugins.map(
|
|
168
|
+
(entry) => entry.plugin_key === plugin2.plugin_key ? plugin2 : entry
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
queryClient.setQueryData(
|
|
173
|
+
pluginFeatureKeys.detail(plugin2.plugin_key),
|
|
174
|
+
{ plugin: plugin2 }
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
const onSaveError = async (queryClient, error) => {
|
|
178
|
+
if (isConflict(error)) {
|
|
179
|
+
toast.error(texts.toasts.conflict);
|
|
180
|
+
} else {
|
|
181
|
+
toast.error(texts.toasts.saveFailed, {
|
|
182
|
+
description: getErrorMessage(error) ?? texts.errors.unknown
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
await queryClient.invalidateQueries({ queryKey: pluginFeatureKeys.all });
|
|
186
|
+
};
|
|
80
187
|
function usePluginFeatures() {
|
|
81
188
|
return useQuery({
|
|
82
189
|
queryKey: pluginFeatureKeys.list(),
|
|
83
190
|
queryFn: listPluginFeatures
|
|
84
191
|
});
|
|
85
192
|
}
|
|
193
|
+
function usePluginFeature(pluginKey) {
|
|
194
|
+
const queryClient = useQueryClient();
|
|
195
|
+
return useQuery({
|
|
196
|
+
queryKey: pluginFeatureKeys.detail(pluginKey ?? ""),
|
|
197
|
+
queryFn: () => retrievePluginFeatures(pluginKey),
|
|
198
|
+
enabled: Boolean(pluginKey),
|
|
199
|
+
placeholderData: () => {
|
|
200
|
+
const plugin2 = queryClient.getQueryData(pluginFeatureKeys.list())?.plugins.find((entry) => entry.plugin_key === pluginKey);
|
|
201
|
+
return plugin2 ? { plugin: plugin2 } : void 0;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
86
205
|
function useUpdatePluginFeatures() {
|
|
87
206
|
const queryClient = useQueryClient();
|
|
88
207
|
return useMutation({
|
|
89
208
|
mutationFn: updatePluginFeatures,
|
|
90
209
|
onSuccess: ({ plugin: plugin2 }) => {
|
|
210
|
+
storePlugin(queryClient, plugin2);
|
|
211
|
+
toast.success(texts.toasts.saved);
|
|
212
|
+
},
|
|
213
|
+
onError: (error) => onSaveError(queryClient, error)
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
function useUpdatePluginSettings(pluginKey) {
|
|
217
|
+
const queryClient = useQueryClient();
|
|
218
|
+
return useMutation({
|
|
219
|
+
mutationFn: (payload) => updatePluginSettings(pluginKey, payload),
|
|
220
|
+
onSuccess: ({ plugin: plugin2 }) => {
|
|
221
|
+
storePlugin(queryClient, plugin2);
|
|
222
|
+
toast.success(texts.toasts.saved);
|
|
223
|
+
},
|
|
224
|
+
onError: (error) => onSaveError(queryClient, error)
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const PLUGIN_ACTION_RUN_POLL_MS = 2e3;
|
|
228
|
+
function useLatestPluginActionRun(pluginKey, actionKey) {
|
|
229
|
+
return useQuery({
|
|
230
|
+
queryKey: pluginFeatureKeys.actionRuns(pluginKey, actionKey),
|
|
231
|
+
queryFn: () => listPluginActionRuns(pluginKey, actionKey, 1),
|
|
232
|
+
select: (data) => data.runs[0],
|
|
233
|
+
refetchInterval: (query) => query.state.data?.runs[0]?.status === "running" ? PLUGIN_ACTION_RUN_POLL_MS : false
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function useRunPluginAction(pluginKey) {
|
|
237
|
+
const queryClient = useQueryClient();
|
|
238
|
+
return useMutation({
|
|
239
|
+
mutationFn: (action) => runPluginAction(pluginKey, action.key),
|
|
240
|
+
onSuccess: ({ action: run }, action) => {
|
|
91
241
|
queryClient.setQueryData(
|
|
92
|
-
pluginFeatureKeys.
|
|
93
|
-
|
|
94
|
-
plugins: current.plugins.map(
|
|
95
|
-
(entry) => entry.plugin_key === plugin2.plugin_key ? plugin2 : entry
|
|
96
|
-
)
|
|
97
|
-
}
|
|
242
|
+
pluginFeatureKeys.actionRuns(pluginKey, action.key),
|
|
243
|
+
{ runs: [run.run] }
|
|
98
244
|
);
|
|
99
|
-
|
|
245
|
+
if (run.status === "started") {
|
|
246
|
+
toast.info(texts.toasts.actionStarted(action.label));
|
|
247
|
+
} else {
|
|
248
|
+
toast.success(texts.toasts.actionCompleted(action.label));
|
|
249
|
+
}
|
|
100
250
|
},
|
|
101
|
-
onError: async (error) => {
|
|
251
|
+
onError: async (error, action) => {
|
|
102
252
|
if (isConflict(error)) {
|
|
103
|
-
toast.
|
|
253
|
+
toast.info(texts.toasts.actionAlreadyRunning(action.label));
|
|
104
254
|
} else {
|
|
105
|
-
toast.error(texts.toasts.
|
|
255
|
+
toast.error(texts.toasts.actionFailed(action.label), {
|
|
106
256
|
description: getErrorMessage(error) ?? texts.errors.unknown
|
|
107
257
|
});
|
|
108
258
|
}
|
|
109
|
-
await queryClient.invalidateQueries({
|
|
259
|
+
await queryClient.invalidateQueries({
|
|
260
|
+
queryKey: pluginFeatureKeys.actionRuns(pluginKey, action.key)
|
|
261
|
+
});
|
|
110
262
|
}
|
|
111
263
|
});
|
|
112
264
|
}
|
|
265
|
+
const PLUGIN_HUB_PATH = "/settings/hartl-services";
|
|
266
|
+
const pluginDetailPath = (pluginKey) => `${PLUGIN_HUB_PATH}/plugin/${encodeURIComponent(pluginKey)}`;
|
|
267
|
+
const initialEntry = (setting) => {
|
|
268
|
+
if (setting.type === "secret") {
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
if (setting.type === "boolean") {
|
|
272
|
+
return setting.value === true;
|
|
273
|
+
}
|
|
274
|
+
return setting.value === null ? "" : String(setting.value);
|
|
275
|
+
};
|
|
276
|
+
const initialSettingsDraft = (settings) => Object.fromEntries(
|
|
277
|
+
settings.map((setting) => [setting.key, initialEntry(setting)])
|
|
278
|
+
);
|
|
279
|
+
const outsideBounds = (setting, value) => setting.min !== null && value < setting.min || setting.max !== null && value > setting.max;
|
|
280
|
+
const numberEntryValue = (entry) => {
|
|
281
|
+
const value = Number(entry.trim());
|
|
282
|
+
return Number.isFinite(value) ? value : null;
|
|
283
|
+
};
|
|
284
|
+
const invalidDraftKeys = (settings, draft, unparsable = []) => settings.filter((setting) => {
|
|
285
|
+
if (setting.type !== "number") {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
if (unparsable.includes(setting.key)) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
const entry = draft[setting.key];
|
|
292
|
+
if (typeof entry !== "string" || entry.trim() === "") {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
const value = numberEntryValue(entry);
|
|
296
|
+
return value === null || outsideBounds(setting, value);
|
|
297
|
+
}).map((setting) => setting.key);
|
|
298
|
+
const changedSettingValues = (settings, draft, unparsable = []) => {
|
|
299
|
+
const initial = initialSettingsDraft(settings);
|
|
300
|
+
const values = {};
|
|
301
|
+
for (const setting of settings) {
|
|
302
|
+
const entry = draft[setting.key];
|
|
303
|
+
if (entry === void 0 || entry === initial[setting.key]) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (setting.type === "secret") {
|
|
307
|
+
if (entry === null) {
|
|
308
|
+
if (setting.source === "stored") {
|
|
309
|
+
values[setting.key] = null;
|
|
310
|
+
}
|
|
311
|
+
} else if (typeof entry === "string" && entry !== "") {
|
|
312
|
+
values[setting.key] = entry;
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (setting.type === "boolean") {
|
|
317
|
+
values[setting.key] = entry === true;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if (unparsable.includes(setting.key)) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
const text = typeof entry === "string" ? entry.trim() : "";
|
|
324
|
+
if (text === "") {
|
|
325
|
+
if (setting.source === "stored") {
|
|
326
|
+
values[setting.key] = null;
|
|
327
|
+
}
|
|
328
|
+
} else if (setting.type === "number") {
|
|
329
|
+
const value = numberEntryValue(text);
|
|
330
|
+
if (value !== null) {
|
|
331
|
+
values[setting.key] = value;
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
values[setting.key] = text;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return values;
|
|
338
|
+
};
|
|
339
|
+
function usePluginSettingsForm(plugin2) {
|
|
340
|
+
const mutation = useUpdatePluginSettings(plugin2.plugin_key);
|
|
341
|
+
const [draft, setDraft] = useState(
|
|
342
|
+
() => initialSettingsDraft(plugin2.settings)
|
|
343
|
+
);
|
|
344
|
+
const [unparsable, setUnparsable] = useState([]);
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
setDraft(initialSettingsDraft(plugin2.settings));
|
|
347
|
+
setUnparsable([]);
|
|
348
|
+
}, [plugin2.settings, plugin2.settings_version]);
|
|
349
|
+
const changes = useMemo(
|
|
350
|
+
() => changedSettingValues(plugin2.settings, draft, unparsable),
|
|
351
|
+
[plugin2.settings, draft, unparsable]
|
|
352
|
+
);
|
|
353
|
+
const invalid = useMemo(
|
|
354
|
+
() => invalidDraftKeys(plugin2.settings, draft, unparsable),
|
|
355
|
+
[plugin2.settings, draft, unparsable]
|
|
356
|
+
);
|
|
357
|
+
const dirty = Object.keys(changes).length > 0;
|
|
358
|
+
const setValue = (key, value, badInput = false) => {
|
|
359
|
+
setDraft((current) => ({ ...current, [key]: value }));
|
|
360
|
+
setUnparsable(
|
|
361
|
+
(current) => badInput ? current.includes(key) ? current : [...current, key] : current.filter((entry) => entry !== key)
|
|
362
|
+
);
|
|
363
|
+
};
|
|
364
|
+
const submit = (event) => {
|
|
365
|
+
event.preventDefault();
|
|
366
|
+
if (!dirty || invalid.length > 0 || mutation.isPending) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
mutation.mutate({
|
|
370
|
+
expected_version: plugin2.settings_version,
|
|
371
|
+
values: changes
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
return {
|
|
375
|
+
draft,
|
|
376
|
+
setValue,
|
|
377
|
+
dirty,
|
|
378
|
+
invalid,
|
|
379
|
+
submit,
|
|
380
|
+
mutation
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const fieldId = (pluginKey, key) => `plugin-setting-${pluginKey}-${key}`;
|
|
384
|
+
const missingLabels = (plugin2) => plugin2.missing.map(
|
|
385
|
+
(key) => plugin2.settings.find((setting) => setting.key === key)?.label ?? key
|
|
386
|
+
).join(", ");
|
|
387
|
+
const invalidatesLabels = (plugin2, setting) => setting.invalidates.map(
|
|
388
|
+
(key) => plugin2.settings.find((candidate) => candidate.key === key)?.label ?? key
|
|
389
|
+
).join(", ");
|
|
390
|
+
const boundsText = (setting) => setting.min !== null && setting.max !== null ? texts.settings.numberRange(setting.min, setting.max) : setting.min !== null ? texts.settings.numberMin(setting.min) : setting.max !== null ? texts.settings.numberMax(setting.max) : void 0;
|
|
391
|
+
const SettingField = ({
|
|
392
|
+
pluginKey,
|
|
393
|
+
setting,
|
|
394
|
+
invalidatesLabels: invalidatesLabels2,
|
|
395
|
+
value,
|
|
396
|
+
invalid,
|
|
397
|
+
disabled,
|
|
398
|
+
onChange
|
|
399
|
+
}) => {
|
|
400
|
+
const id = fieldId(pluginKey, setting.key);
|
|
401
|
+
const hintId = `${id}-hint`;
|
|
402
|
+
const clearPending = setting.type === "secret" && value === null;
|
|
403
|
+
const bounds = setting.type === "number" ? boundsText(setting) : void 0;
|
|
404
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
405
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
|
|
406
|
+
/* @__PURE__ */ jsxs(Label, { htmlFor: id, size: "small", weight: "plus", children: [
|
|
407
|
+
setting.label,
|
|
408
|
+
setting.required && /* @__PURE__ */ jsxs("span", { className: "text-ui-fg-muted", children: [
|
|
409
|
+
" (",
|
|
410
|
+
texts.settings.required,
|
|
411
|
+
")"
|
|
412
|
+
] })
|
|
413
|
+
] }),
|
|
414
|
+
/* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-muted shrink-0", children: texts.settings.source[setting.source] })
|
|
415
|
+
] }),
|
|
416
|
+
setting.type === "boolean" ? /* @__PURE__ */ jsx(
|
|
417
|
+
Switch,
|
|
418
|
+
{
|
|
419
|
+
id,
|
|
420
|
+
"aria-describedby": hintId,
|
|
421
|
+
checked: value === true,
|
|
422
|
+
disabled,
|
|
423
|
+
onCheckedChange: (checked) => onChange(checked === true)
|
|
424
|
+
}
|
|
425
|
+
) : setting.type === "secret" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
426
|
+
/* @__PURE__ */ jsx(
|
|
427
|
+
Input,
|
|
428
|
+
{
|
|
429
|
+
id,
|
|
430
|
+
"aria-describedby": hintId,
|
|
431
|
+
className: "flex-1",
|
|
432
|
+
type: "password",
|
|
433
|
+
autoComplete: "new-password",
|
|
434
|
+
placeholder: setting.is_set ? texts.settings.secretPlaceholder : "",
|
|
435
|
+
value: typeof value === "string" ? value : "",
|
|
436
|
+
disabled: disabled || clearPending,
|
|
437
|
+
onChange: (event) => onChange(event.target.value)
|
|
438
|
+
}
|
|
439
|
+
),
|
|
440
|
+
setting.source === "stored" && /* @__PURE__ */ jsx(
|
|
441
|
+
Button,
|
|
442
|
+
{
|
|
443
|
+
type: "button",
|
|
444
|
+
size: "small",
|
|
445
|
+
variant: "secondary",
|
|
446
|
+
disabled,
|
|
447
|
+
"aria-label": clearPending ? texts.settings.secretClearUndoLabel(setting.label) : texts.settings.secretClearLabel(setting.label),
|
|
448
|
+
onClick: () => onChange(clearPending ? "" : null),
|
|
449
|
+
children: clearPending ? texts.settings.secretClearUndo : texts.settings.secretClear
|
|
450
|
+
}
|
|
451
|
+
)
|
|
452
|
+
] }) : setting.type === "select" ? /* @__PURE__ */ jsxs(
|
|
453
|
+
Select,
|
|
454
|
+
{
|
|
455
|
+
value: typeof value === "string" ? value : "",
|
|
456
|
+
disabled,
|
|
457
|
+
onValueChange: (next) => onChange(next),
|
|
458
|
+
children: [
|
|
459
|
+
/* @__PURE__ */ jsx(Select.Trigger, { id, "aria-describedby": hintId, children: /* @__PURE__ */ jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }),
|
|
460
|
+
/* @__PURE__ */ jsx(Select.Content, { children: setting.options.map((option) => /* @__PURE__ */ jsx(Select.Item, { value: option.value, children: option.label }, option.value)) })
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
) : setting.type === "number" ? /* @__PURE__ */ jsx(
|
|
464
|
+
Input,
|
|
465
|
+
{
|
|
466
|
+
id,
|
|
467
|
+
"aria-describedby": hintId,
|
|
468
|
+
"aria-invalid": invalid,
|
|
469
|
+
type: "number",
|
|
470
|
+
step: "any",
|
|
471
|
+
min: setting.min ?? void 0,
|
|
472
|
+
max: setting.max ?? void 0,
|
|
473
|
+
value: typeof value === "string" ? value : "",
|
|
474
|
+
disabled,
|
|
475
|
+
onChange: (event) => onChange(event.target.value, event.target.validity.badInput)
|
|
476
|
+
}
|
|
477
|
+
) : /* @__PURE__ */ jsx(
|
|
478
|
+
Input,
|
|
479
|
+
{
|
|
480
|
+
id,
|
|
481
|
+
"aria-describedby": hintId,
|
|
482
|
+
"aria-invalid": invalid,
|
|
483
|
+
type: setting.type === "url" ? "url" : "text",
|
|
484
|
+
value: typeof value === "string" ? value : "",
|
|
485
|
+
disabled,
|
|
486
|
+
onChange: (event) => onChange(event.target.value)
|
|
487
|
+
}
|
|
488
|
+
),
|
|
489
|
+
/* @__PURE__ */ jsxs("div", { id: hintId, className: "grid gap-1", children: [
|
|
490
|
+
setting.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: setting.description }),
|
|
491
|
+
clearPending && /* @__PURE__ */ jsx(Hint, { variant: "info", children: texts.settings.secretClearPending }),
|
|
492
|
+
invalidatesLabels2 && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.settings.invalidates(invalidatesLabels2) }),
|
|
493
|
+
bounds && !invalid && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds }),
|
|
494
|
+
invalid && /* @__PURE__ */ jsx(Hint, { variant: "error", children: bounds ?? texts.settings.invalidNumber })
|
|
495
|
+
] })
|
|
496
|
+
] });
|
|
497
|
+
};
|
|
498
|
+
const PluginSettingsForm = ({ plugin: plugin2 }) => {
|
|
499
|
+
const form = usePluginSettingsForm(plugin2);
|
|
500
|
+
const pending = form.mutation.isPending;
|
|
501
|
+
const hasTextFields = plugin2.settings.some(
|
|
502
|
+
(setting) => setting.type !== "boolean" && setting.type !== "secret" && setting.type !== "select"
|
|
503
|
+
);
|
|
504
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", "aria-busy": pending, children: [
|
|
505
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
506
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: texts.sections.settings }),
|
|
507
|
+
hasTextFields && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.clearHint })
|
|
508
|
+
] }),
|
|
509
|
+
/* @__PURE__ */ jsxs("form", { onSubmit: form.submit, className: "grid gap-5 px-6 py-5", children: [
|
|
510
|
+
!plugin2.configured && /* @__PURE__ */ jsx(Alert, { variant: "warning", children: texts.settings.missing(missingLabels(plugin2)) }),
|
|
511
|
+
plugin2.settings.map((setting) => /* @__PURE__ */ jsx(
|
|
512
|
+
SettingField,
|
|
513
|
+
{
|
|
514
|
+
pluginKey: plugin2.plugin_key,
|
|
515
|
+
setting,
|
|
516
|
+
invalidatesLabels: invalidatesLabels(plugin2, setting),
|
|
517
|
+
value: form.draft[setting.key],
|
|
518
|
+
invalid: form.invalid.includes(setting.key),
|
|
519
|
+
disabled: pending,
|
|
520
|
+
onChange: (value, badInput) => form.setValue(setting.key, value, badInput)
|
|
521
|
+
},
|
|
522
|
+
setting.key
|
|
523
|
+
)),
|
|
524
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
|
|
525
|
+
Button,
|
|
526
|
+
{
|
|
527
|
+
type: "submit",
|
|
528
|
+
size: "small",
|
|
529
|
+
isLoading: pending,
|
|
530
|
+
disabled: !form.dirty || form.invalid.length > 0 || pending,
|
|
531
|
+
children: texts.settings.save
|
|
532
|
+
}
|
|
533
|
+
) })
|
|
534
|
+
] })
|
|
535
|
+
] });
|
|
536
|
+
};
|
|
537
|
+
const PluginStatusChips = ({ plugin: plugin2 }) => /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap gap-2", children: [
|
|
538
|
+
plugin2.switchable && /* @__PURE__ */ jsx(StatusBadge, { color: plugin2.enabled ? "green" : "grey", children: plugin2.enabled ? texts.chips.enabled : texts.chips.disabled }),
|
|
539
|
+
plugin2.settings.length > 0 && /* @__PURE__ */ jsx(StatusBadge, { color: plugin2.configured ? "green" : "orange", children: plugin2.configured ? texts.chips.configured : texts.chips.notConfigured(missingLabels(plugin2)) })
|
|
540
|
+
] });
|
|
541
|
+
const isNotFound = (error) => typeof error === "object" && error !== null && error.status === 404;
|
|
542
|
+
const PluginPageShell = ({ pluginKey, children }) => {
|
|
543
|
+
const query = usePluginFeature(pluginKey);
|
|
544
|
+
const plugin2 = query.data?.plugin;
|
|
545
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3", children: [
|
|
546
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
|
|
547
|
+
Link,
|
|
548
|
+
{
|
|
549
|
+
to: PLUGIN_HUB_PATH,
|
|
550
|
+
className: "text-ui-fg-subtle hover:text-ui-fg-base txt-compact-small-plus",
|
|
551
|
+
children: [
|
|
552
|
+
"← ",
|
|
553
|
+
texts.detail.back
|
|
554
|
+
]
|
|
555
|
+
}
|
|
556
|
+
) }),
|
|
557
|
+
plugin2 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
558
|
+
/* @__PURE__ */ jsxs(Container, { className: "px-6 py-4", children: [
|
|
559
|
+
/* @__PURE__ */ jsx(Heading, { level: "h1", children: plugin2.label }),
|
|
560
|
+
plugin2.description && /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: plugin2.description }),
|
|
561
|
+
/* @__PURE__ */ jsx(PluginStatusChips, { plugin: plugin2 })
|
|
562
|
+
] }),
|
|
563
|
+
children
|
|
564
|
+
] }) : query.isPending ? /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: texts.states.loading }) }) : isNotFound(query.error) ? /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: texts.states.notFound }) }) : /* @__PURE__ */ jsxs(Container, { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
565
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", children: texts.states.error }),
|
|
566
|
+
/* @__PURE__ */ jsx(
|
|
567
|
+
Button,
|
|
568
|
+
{
|
|
569
|
+
size: "small",
|
|
570
|
+
variant: "secondary",
|
|
571
|
+
isLoading: query.isFetching,
|
|
572
|
+
onClick: () => void query.refetch(),
|
|
573
|
+
children: texts.states.retry
|
|
574
|
+
}
|
|
575
|
+
)
|
|
576
|
+
] })
|
|
577
|
+
] });
|
|
578
|
+
};
|
|
579
|
+
const SUMMARY_MAX_LENGTH = 200;
|
|
580
|
+
const cut = (text) => text.length > SUMMARY_MAX_LENGTH ? `${text.slice(0, SUMMARY_MAX_LENGTH - 1)}…` : text;
|
|
581
|
+
const isPrimitive = (value) => typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
582
|
+
const summarizeActionResult = (result) => {
|
|
583
|
+
if (result === null || result === void 0) {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
if (isPrimitive(result)) {
|
|
587
|
+
return cut(String(result));
|
|
588
|
+
}
|
|
589
|
+
if (typeof result !== "object" || Array.isArray(result)) {
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
const record = result;
|
|
593
|
+
for (const key of ["summary", "message"]) {
|
|
594
|
+
const value = record[key];
|
|
595
|
+
if (typeof value === "string" && value.trim()) {
|
|
596
|
+
return cut(value.trim());
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const parts = Object.entries(record).filter(
|
|
600
|
+
(entry) => isPrimitive(entry[1])
|
|
601
|
+
).map(([key, value]) => `${key}: ${String(value)}`);
|
|
602
|
+
return parts.length > 0 ? cut(parts.join(", ")) : null;
|
|
603
|
+
};
|
|
604
|
+
const progressPercent = (progress) => {
|
|
605
|
+
if (!progress || progress.total === null || progress.total <= 0) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
return Math.max(
|
|
609
|
+
0,
|
|
610
|
+
Math.min(100, Math.round(progress.current / progress.total * 100))
|
|
611
|
+
);
|
|
612
|
+
};
|
|
613
|
+
const progressCount = (progress) => {
|
|
614
|
+
if (!progress) {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
return progress.total === null ? String(progress.current) : `${progress.current} / ${progress.total}`;
|
|
618
|
+
};
|
|
619
|
+
const formatRunTime = (iso) => {
|
|
620
|
+
if (!iso) {
|
|
621
|
+
return "";
|
|
622
|
+
}
|
|
623
|
+
const date = new Date(iso);
|
|
624
|
+
return Number.isNaN(date.getTime()) ? iso : date.toLocaleString("de-AT", { dateStyle: "short", timeStyle: "short" });
|
|
625
|
+
};
|
|
626
|
+
const runTransition = (previous, current) => {
|
|
627
|
+
if (!previous || !current || previous.id !== current.id || previous.status !== "running" || current.status === "running") {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
return current.status;
|
|
631
|
+
};
|
|
632
|
+
const RunProgress = ({ run }) => {
|
|
633
|
+
const percent = progressPercent(run.progress);
|
|
634
|
+
const count = progressCount(run.progress);
|
|
635
|
+
const label = run.progress?.label;
|
|
636
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-col gap-y-1", role: "status", children: [
|
|
637
|
+
(label || count) && /* @__PURE__ */ jsx(Text, { size: "small", children: [label, count].filter(Boolean).join(" · ") }),
|
|
638
|
+
percent !== null && /* @__PURE__ */ jsx(
|
|
639
|
+
"div",
|
|
640
|
+
{
|
|
641
|
+
className: "bg-ui-border-base w-full overflow-hidden rounded-full",
|
|
642
|
+
style: { height: 6 },
|
|
643
|
+
role: "progressbar",
|
|
644
|
+
"aria-valuemin": 0,
|
|
645
|
+
"aria-valuemax": 100,
|
|
646
|
+
"aria-valuenow": percent,
|
|
647
|
+
children: /* @__PURE__ */ jsx(
|
|
648
|
+
"div",
|
|
649
|
+
{
|
|
650
|
+
className: "bg-ui-fg-interactive h-full rounded-full",
|
|
651
|
+
style: { width: `${percent}%`, height: "100%" }
|
|
652
|
+
}
|
|
653
|
+
)
|
|
654
|
+
}
|
|
655
|
+
),
|
|
656
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted", children: texts.actions.runningSince(formatRunTime(run.started_at)) })
|
|
657
|
+
] });
|
|
658
|
+
};
|
|
659
|
+
const RunStatusLine = ({ run }) => {
|
|
660
|
+
if (run.status === "completed") {
|
|
661
|
+
const summary = summarizeActionResult(run.result);
|
|
662
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
|
|
663
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.actions.lastCompleted(
|
|
664
|
+
formatRunTime(run.finished_at ?? run.started_at)
|
|
665
|
+
) }),
|
|
666
|
+
summary && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: summary })
|
|
667
|
+
] });
|
|
668
|
+
}
|
|
669
|
+
return /* @__PURE__ */ jsxs("div", { className: "mt-1", children: [
|
|
670
|
+
/* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-error", children: texts.actions.lastFailed(
|
|
671
|
+
formatRunTime(run.finished_at ?? run.started_at)
|
|
672
|
+
) }),
|
|
673
|
+
run.message && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-error", children: run.message })
|
|
674
|
+
] });
|
|
675
|
+
};
|
|
676
|
+
const PluginActionRow = ({
|
|
677
|
+
pluginKey,
|
|
678
|
+
action,
|
|
679
|
+
starting,
|
|
680
|
+
blocked,
|
|
681
|
+
onRun
|
|
682
|
+
}) => {
|
|
683
|
+
const { data: run } = useLatestPluginActionRun(pluginKey, action.key);
|
|
684
|
+
const previous = useRef(void 0);
|
|
685
|
+
const running = run?.status === "running";
|
|
686
|
+
useEffect(() => {
|
|
687
|
+
const transition = runTransition(previous.current, run);
|
|
688
|
+
previous.current = run;
|
|
689
|
+
if (transition === "completed") {
|
|
690
|
+
toast.success(texts.toasts.actionCompleted(action.label), {
|
|
691
|
+
description: summarizeActionResult(run?.result) ?? void 0
|
|
692
|
+
});
|
|
693
|
+
} else if (transition === "failed") {
|
|
694
|
+
toast.error(texts.toasts.actionFailed(action.label), {
|
|
695
|
+
description: run?.message ?? texts.errors.unknown
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}, [run, action.label]);
|
|
699
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 px-6 py-4", children: [
|
|
700
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
701
|
+
/* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: action.label }),
|
|
702
|
+
action.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: action.description }),
|
|
703
|
+
run && (running ? /* @__PURE__ */ jsx(RunProgress, { run }) : /* @__PURE__ */ jsx(RunStatusLine, { run }))
|
|
704
|
+
] }),
|
|
705
|
+
/* @__PURE__ */ jsx(
|
|
706
|
+
Button,
|
|
707
|
+
{
|
|
708
|
+
size: "small",
|
|
709
|
+
variant: "secondary",
|
|
710
|
+
className: "shrink-0",
|
|
711
|
+
isLoading: starting || running,
|
|
712
|
+
disabled: blocked || starting || running,
|
|
713
|
+
onClick: () => onRun(action),
|
|
714
|
+
children: action.label
|
|
715
|
+
}
|
|
716
|
+
)
|
|
717
|
+
] });
|
|
718
|
+
};
|
|
719
|
+
const PluginActionsSection = ({ plugin: plugin2 }) => {
|
|
720
|
+
const mutation = useRunPluginAction(plugin2.plugin_key);
|
|
721
|
+
const prompt = usePrompt();
|
|
722
|
+
const startingKey = mutation.isPending ? mutation.variables?.key : void 0;
|
|
723
|
+
const run = async (action) => {
|
|
724
|
+
if (mutation.isPending) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (action.confirm) {
|
|
728
|
+
const confirmed = await prompt({
|
|
729
|
+
title: texts.actions.confirmTitle(action.label),
|
|
730
|
+
description: action.confirm,
|
|
731
|
+
confirmText: texts.actions.confirm,
|
|
732
|
+
cancelText: texts.actions.cancel,
|
|
733
|
+
variant: "confirmation"
|
|
734
|
+
});
|
|
735
|
+
if (!confirmed) {
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
mutation.mutate(action);
|
|
740
|
+
};
|
|
741
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
742
|
+
/* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Heading, { level: "h2", children: texts.sections.actions }) }),
|
|
743
|
+
plugin2.actions.map((action) => /* @__PURE__ */ jsx(
|
|
744
|
+
PluginActionRow,
|
|
745
|
+
{
|
|
746
|
+
pluginKey: plugin2.plugin_key,
|
|
747
|
+
action,
|
|
748
|
+
starting: startingKey === action.key,
|
|
749
|
+
blocked: mutation.isPending && startingKey !== action.key,
|
|
750
|
+
onRun: (entry) => void run(entry)
|
|
751
|
+
},
|
|
752
|
+
action.key
|
|
753
|
+
))
|
|
754
|
+
] });
|
|
755
|
+
};
|
|
113
756
|
const configuredFeatures = (plugin2) => Object.fromEntries(
|
|
114
757
|
plugin2.features.map((feature) => [feature.key, feature.enabled])
|
|
115
758
|
);
|
|
116
|
-
const PluginFeaturesCard = ({ plugin: plugin2 }) => {
|
|
759
|
+
const PluginFeaturesCard = ({ plugin: plugin2, title }) => {
|
|
117
760
|
const mutation = useUpdatePluginFeatures();
|
|
118
761
|
const pending = mutation.isPending;
|
|
119
762
|
const labels = new Map(
|
|
@@ -132,12 +775,12 @@ const PluginFeaturesCard = ({ plugin: plugin2 }) => {
|
|
|
132
775
|
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", "aria-busy": pending, children: [
|
|
133
776
|
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 px-6 py-4", children: [
|
|
134
777
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
135
|
-
/* @__PURE__ */ jsx(Heading, { level: "h2", children: plugin2.label }),
|
|
136
|
-
plugin2.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: plugin2.description }),
|
|
778
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: title ?? plugin2.label }),
|
|
779
|
+
title === void 0 && plugin2.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: plugin2.description }),
|
|
137
780
|
!plugin2.enabled && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted mt-1", children: texts.hints.pluginDisabled }),
|
|
138
781
|
pending && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted mt-1", children: texts.hints.saving })
|
|
139
782
|
] }),
|
|
140
|
-
/* @__PURE__ */ jsx(
|
|
783
|
+
plugin2.switchable && /* @__PURE__ */ jsx(
|
|
141
784
|
Switch,
|
|
142
785
|
{
|
|
143
786
|
id: pluginSwitchId,
|
|
@@ -197,6 +840,632 @@ const PluginFeaturesCard = ({ plugin: plugin2 }) => {
|
|
|
197
840
|
})
|
|
198
841
|
] });
|
|
199
842
|
};
|
|
843
|
+
const PluginFeatureSections = ({ pluginKey }) => {
|
|
844
|
+
const plugin2 = usePluginFeature(pluginKey).data?.plugin;
|
|
845
|
+
if (!plugin2) {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
849
|
+
(plugin2.switchable || plugin2.features.length > 0) && /* @__PURE__ */ jsx(PluginFeaturesCard, { plugin: plugin2, title: texts.sections.features }),
|
|
850
|
+
plugin2.settings.length > 0 && /* @__PURE__ */ jsx(PluginSettingsForm, { plugin: plugin2 }),
|
|
851
|
+
plugin2.actions.length > 0 && /* @__PURE__ */ jsx(PluginActionsSection, { plugin: plugin2 })
|
|
852
|
+
] });
|
|
853
|
+
};
|
|
854
|
+
const PLUGIN_FEATURE_ENABLED_STALE_TIME = 3e4;
|
|
855
|
+
const isPluginFeatureEnabledIn = (data, pluginKey, featureKeys) => {
|
|
856
|
+
const plugin2 = data.plugins.find((entry) => entry.plugin_key === pluginKey);
|
|
857
|
+
if (!plugin2?.enabled) {
|
|
858
|
+
return false;
|
|
859
|
+
}
|
|
860
|
+
return featureKeys.every(
|
|
861
|
+
(key) => plugin2.features.find((feature) => feature.key === key)?.effective_enabled === true
|
|
862
|
+
);
|
|
863
|
+
};
|
|
864
|
+
const pluginFeatureEnabledQueryOptions = (pluginKey, featureKeys) => ({
|
|
865
|
+
queryKey: pluginFeatureKeys.list(),
|
|
866
|
+
queryFn: listPluginFeatures,
|
|
867
|
+
staleTime: PLUGIN_FEATURE_ENABLED_STALE_TIME,
|
|
868
|
+
select: (data) => isPluginFeatureEnabledIn(data, pluginKey, featureKeys)
|
|
869
|
+
});
|
|
870
|
+
const toPluginFeatureEnabledState = (result) => ({
|
|
871
|
+
enabled: !result.isPending && !result.isError && result.data === true,
|
|
872
|
+
isPending: result.isPending,
|
|
873
|
+
isError: result.isError
|
|
874
|
+
});
|
|
875
|
+
function usePluginFeatureEnabled(pluginKey, ...featureKeys) {
|
|
876
|
+
return toPluginFeatureEnabledState(
|
|
877
|
+
useQuery(pluginFeatureEnabledQueryOptions(pluginKey, featureKeys))
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
const PluginFeatureGate = ({
|
|
881
|
+
pluginKey,
|
|
882
|
+
featureKeys = [],
|
|
883
|
+
fallback = null,
|
|
884
|
+
children
|
|
885
|
+
}) => {
|
|
886
|
+
const { enabled } = usePluginFeatureEnabled(pluginKey, ...featureKeys);
|
|
887
|
+
return /* @__PURE__ */ jsx(Fragment, { children: enabled ? children : fallback });
|
|
888
|
+
};
|
|
889
|
+
const PluginFeatureDisabledNotice = ({
|
|
890
|
+
pluginKey,
|
|
891
|
+
featureLabel
|
|
892
|
+
}) => /* @__PURE__ */ jsxs(
|
|
893
|
+
Container,
|
|
894
|
+
{
|
|
895
|
+
className: "flex flex-col gap-2 px-6 py-4",
|
|
896
|
+
"data-plugin-key": pluginKey,
|
|
897
|
+
children: [
|
|
898
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: featureLabel ? texts.gate.disabledNamed(featureLabel) : texts.gate.disabled }),
|
|
899
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
900
|
+
Link,
|
|
901
|
+
{
|
|
902
|
+
to: PLUGIN_HUB_PATH,
|
|
903
|
+
className: "text-ui-fg-interactive hover:text-ui-fg-interactive-hover txt-compact-small-plus",
|
|
904
|
+
children: texts.gate.openHub
|
|
905
|
+
}
|
|
906
|
+
) })
|
|
907
|
+
]
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
const listOrderDocuments = (orderId) => sdk.client.fetch(
|
|
911
|
+
documentsPaths.admin.get.listForOrder(orderId)
|
|
912
|
+
);
|
|
913
|
+
const issueOrderInvoice = (orderId) => sdk.client.fetch(
|
|
914
|
+
documentsPaths.admin.post.issueInvoice(orderId),
|
|
915
|
+
{ method: "POST" }
|
|
916
|
+
);
|
|
917
|
+
const regenerateOrderDocument = (orderId, documentId) => sdk.client.fetch(
|
|
918
|
+
documentsPaths.admin.post.regenerateDocument(orderId, documentId),
|
|
919
|
+
{ method: "POST", body: {} }
|
|
920
|
+
);
|
|
921
|
+
const listOrderPackingLists = (orderId) => sdk.client.fetch(
|
|
922
|
+
packingListsPaths.admin.get.listForOrder(orderId)
|
|
923
|
+
);
|
|
924
|
+
const retryOrderPackingList = (orderId, fulfillmentId) => sdk.client.fetch(
|
|
925
|
+
packingListsPaths.admin.post.retry(orderId, fulfillmentId),
|
|
926
|
+
{ method: "POST", body: {} }
|
|
927
|
+
);
|
|
928
|
+
const retrieveOrderDocumentPreview = async (orderId, documentId) => {
|
|
929
|
+
const response = await sdk.client.fetch(
|
|
930
|
+
documentsPaths.admin.get.download(orderId, documentId),
|
|
931
|
+
{ headers: { accept: "application/pdf" } }
|
|
932
|
+
);
|
|
933
|
+
const pdf = await response.blob();
|
|
934
|
+
return URL.createObjectURL(pdf);
|
|
935
|
+
};
|
|
936
|
+
const retrieveOrderPackingListPreview = async (orderId, fulfillmentId) => {
|
|
937
|
+
const response = await sdk.client.fetch(
|
|
938
|
+
packingListsPaths.admin.get.download(orderId, fulfillmentId),
|
|
939
|
+
{ headers: { accept: "application/pdf" } }
|
|
940
|
+
);
|
|
941
|
+
const pdf = await response.blob();
|
|
942
|
+
return URL.createObjectURL(pdf);
|
|
943
|
+
};
|
|
944
|
+
const formatDocumentKind = (kind) => {
|
|
945
|
+
switch (kind) {
|
|
946
|
+
case "invoice":
|
|
947
|
+
return "Rechnung";
|
|
948
|
+
case "cancellation":
|
|
949
|
+
return "Stornorechnung";
|
|
950
|
+
case "credit_note":
|
|
951
|
+
return "Gutschrift";
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
const formatAmount = (document2) => {
|
|
955
|
+
const amount = Number(document2.total.amount);
|
|
956
|
+
if (!Number.isFinite(amount)) {
|
|
957
|
+
return `${document2.total.amount} ${document2.total.currency_code.toUpperCase()}`;
|
|
958
|
+
}
|
|
959
|
+
return new Intl.NumberFormat("de-AT", {
|
|
960
|
+
style: "currency",
|
|
961
|
+
currency: document2.total.currency_code.toUpperCase()
|
|
962
|
+
}).format(amount);
|
|
963
|
+
};
|
|
964
|
+
const formatIssuedAt = (issuedAt) => new Intl.DateTimeFormat("de-AT", { dateStyle: "medium" }).format(
|
|
965
|
+
new Date(issuedAt)
|
|
966
|
+
);
|
|
967
|
+
const OrderDocumentsWidgetContent = ({
|
|
968
|
+
data
|
|
969
|
+
}) => {
|
|
970
|
+
const queryClient = useQueryClient();
|
|
971
|
+
const [preview, setPreview] = useState(null);
|
|
972
|
+
const previewRequest = useRef(0);
|
|
973
|
+
const documentsQueryKey = ["documents", "order", data.id];
|
|
974
|
+
const documentsQuery = useQuery({
|
|
975
|
+
queryKey: documentsQueryKey,
|
|
976
|
+
queryFn: () => listOrderDocuments(data.id)
|
|
977
|
+
});
|
|
978
|
+
const documents = documentsQuery.data?.documents ?? [];
|
|
979
|
+
const invoice = documents.find((document2) => document2.kind === "invoice");
|
|
980
|
+
const issueInvoice = useMutation({
|
|
981
|
+
mutationFn: () => issueOrderInvoice(data.id),
|
|
982
|
+
onSuccess: async ({ created }) => {
|
|
983
|
+
toast.success(
|
|
984
|
+
created ? "Die Rechnung wurde ausgestellt." : "Für diese Bestellung existiert bereits eine Rechnung."
|
|
985
|
+
);
|
|
986
|
+
await queryClient.invalidateQueries({ queryKey: documentsQueryKey });
|
|
987
|
+
},
|
|
988
|
+
onError: (error) => {
|
|
989
|
+
const message = error instanceof Error ? error.message : null;
|
|
990
|
+
toast.error("Die Rechnung konnte nicht ausgestellt werden.", {
|
|
991
|
+
description: message ?? "Die Bestellung muss vollständig versendet und darf nicht storniert sein."
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
});
|
|
995
|
+
const regenerateDocument = useMutation({
|
|
996
|
+
mutationFn: (document2) => regenerateOrderDocument(data.id, document2.id),
|
|
997
|
+
onSuccess: async () => {
|
|
998
|
+
toast.success("Das Dokument wurde neu ausgestellt.");
|
|
999
|
+
await queryClient.invalidateQueries({ queryKey: documentsQueryKey });
|
|
1000
|
+
},
|
|
1001
|
+
onError: (error) => {
|
|
1002
|
+
const message = error instanceof Error ? error.message : null;
|
|
1003
|
+
toast.error("Das Dokument konnte nicht regeneriert werden.", {
|
|
1004
|
+
description: message ?? "Das ausgestellte Dokument ist nicht verfügbar."
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
const closePreview = () => {
|
|
1009
|
+
previewRequest.current += 1;
|
|
1010
|
+
if (preview?.url) {
|
|
1011
|
+
URL.revokeObjectURL(preview.url);
|
|
1012
|
+
}
|
|
1013
|
+
setPreview(null);
|
|
1014
|
+
};
|
|
1015
|
+
const previewDocument = async (document2) => {
|
|
1016
|
+
if (preview?.url) {
|
|
1017
|
+
URL.revokeObjectURL(preview.url);
|
|
1018
|
+
}
|
|
1019
|
+
const requestId = previewRequest.current + 1;
|
|
1020
|
+
previewRequest.current = requestId;
|
|
1021
|
+
setPreview({ document: document2, url: null, error: null });
|
|
1022
|
+
try {
|
|
1023
|
+
const url = await retrieveOrderDocumentPreview(data.id, document2.id);
|
|
1024
|
+
if (previewRequest.current !== requestId) {
|
|
1025
|
+
URL.revokeObjectURL(url);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
setPreview({ document: document2, url, error: null });
|
|
1029
|
+
} catch {
|
|
1030
|
+
if (previewRequest.current !== requestId) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
setPreview({
|
|
1034
|
+
document: document2,
|
|
1035
|
+
url: null,
|
|
1036
|
+
error: "Das Dokument konnte nicht geladen werden."
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1041
|
+
/* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4 px-6 py-4", children: [
|
|
1043
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1044
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Dokumente" }),
|
|
1045
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Ausgestellte Belege zu dieser Bestellung." })
|
|
1046
|
+
] }),
|
|
1047
|
+
!invoice ? /* @__PURE__ */ jsx(
|
|
1048
|
+
Button,
|
|
1049
|
+
{
|
|
1050
|
+
disabled: issueInvoice.isPending,
|
|
1051
|
+
isLoading: issueInvoice.isPending,
|
|
1052
|
+
onClick: () => issueInvoice.mutate(),
|
|
1053
|
+
size: "small",
|
|
1054
|
+
variant: "secondary",
|
|
1055
|
+
children: "Rechnung ausstellen"
|
|
1056
|
+
}
|
|
1057
|
+
) : /* @__PURE__ */ jsx(
|
|
1058
|
+
Button,
|
|
1059
|
+
{
|
|
1060
|
+
disabled: regenerateDocument.isPending,
|
|
1061
|
+
isLoading: regenerateDocument.isPending,
|
|
1062
|
+
onClick: () => regenerateDocument.mutate(invoice),
|
|
1063
|
+
size: "small",
|
|
1064
|
+
variant: "secondary",
|
|
1065
|
+
children: "Rechnung regenerieren"
|
|
1066
|
+
}
|
|
1067
|
+
)
|
|
1068
|
+
] }),
|
|
1069
|
+
/* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: documentsQuery.isPending ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Dokumente werden geladen." }) : documentsQuery.isError ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", children: "Dokumente konnten nicht geladen werden." }) : documents.length ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: documents.map((document2) => /* @__PURE__ */ jsxs(
|
|
1070
|
+
"div",
|
|
1071
|
+
{
|
|
1072
|
+
className: "flex items-center justify-between gap-4",
|
|
1073
|
+
children: [
|
|
1074
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1075
|
+
/* @__PURE__ */ jsxs(Text, { className: "font-medium", children: [
|
|
1076
|
+
formatDocumentKind(document2.kind),
|
|
1077
|
+
" ",
|
|
1078
|
+
document2.number
|
|
1079
|
+
] }),
|
|
1080
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle", children: [
|
|
1081
|
+
formatIssuedAt(document2.issued_at),
|
|
1082
|
+
" ·",
|
|
1083
|
+
" ",
|
|
1084
|
+
formatAmount(document2)
|
|
1085
|
+
] })
|
|
1086
|
+
] }),
|
|
1087
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1088
|
+
/* @__PURE__ */ jsx(
|
|
1089
|
+
Button,
|
|
1090
|
+
{
|
|
1091
|
+
onClick: () => previewDocument(document2),
|
|
1092
|
+
size: "small",
|
|
1093
|
+
variant: "secondary",
|
|
1094
|
+
children: "Ansehen"
|
|
1095
|
+
}
|
|
1096
|
+
),
|
|
1097
|
+
document2.kind !== "invoice" ? /* @__PURE__ */ jsx(
|
|
1098
|
+
Button,
|
|
1099
|
+
{
|
|
1100
|
+
disabled: regenerateDocument.isPending,
|
|
1101
|
+
isLoading: regenerateDocument.isPending,
|
|
1102
|
+
onClick: () => regenerateDocument.mutate(document2),
|
|
1103
|
+
size: "small",
|
|
1104
|
+
variant: "secondary",
|
|
1105
|
+
children: "Regenerieren"
|
|
1106
|
+
}
|
|
1107
|
+
) : null
|
|
1108
|
+
] })
|
|
1109
|
+
]
|
|
1110
|
+
},
|
|
1111
|
+
document2.id
|
|
1112
|
+
)) }) : /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Für diese Bestellung wurden noch keine Dokumente ausgestellt." }) })
|
|
1113
|
+
] }),
|
|
1114
|
+
/* @__PURE__ */ jsx(
|
|
1115
|
+
FocusModal,
|
|
1116
|
+
{
|
|
1117
|
+
onOpenChange: (open) => {
|
|
1118
|
+
if (!open) {
|
|
1119
|
+
closePreview();
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
1122
|
+
open: preview !== null,
|
|
1123
|
+
children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
|
|
1124
|
+
/* @__PURE__ */ jsx(FocusModal.Header, { children: /* @__PURE__ */ jsx(FocusModal.Title, { children: preview ? `${formatDocumentKind(preview.document.kind)} ${preview.document.number}` : "Dokument" }) }),
|
|
1125
|
+
/* @__PURE__ */ jsx(FocusModal.Body, { className: "h-[75vh] p-0", children: preview?.url ? /* @__PURE__ */ jsx(
|
|
1126
|
+
"iframe",
|
|
1127
|
+
{
|
|
1128
|
+
className: "h-full w-full border-0",
|
|
1129
|
+
src: preview.url,
|
|
1130
|
+
title: `Vorschau ${preview.document.number}`
|
|
1131
|
+
}
|
|
1132
|
+
) : preview?.error ? /* @__PURE__ */ jsx(Text, { className: "p-6 text-ui-fg-error", children: preview.error }) : /* @__PURE__ */ jsx(Text, { className: "p-6 text-ui-fg-subtle", children: "Dokument wird geladen." }) }),
|
|
1133
|
+
/* @__PURE__ */ jsx(FocusModal.Footer, { children: /* @__PURE__ */ jsx(FocusModal.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { onClick: closePreview, variant: "secondary", children: "Schließen" }) }) })
|
|
1134
|
+
] })
|
|
1135
|
+
}
|
|
1136
|
+
)
|
|
1137
|
+
] });
|
|
1138
|
+
};
|
|
1139
|
+
const OrderDocumentsWidget = (props) => /* @__PURE__ */ jsx(PluginFeatureGate, { pluginKey: "documents", featureKeys: ["invoices"], children: /* @__PURE__ */ jsx(OrderDocumentsWidgetContent, { ...props }) });
|
|
1140
|
+
defineWidgetConfig({
|
|
1141
|
+
zone: "order.details.side.after"
|
|
1142
|
+
});
|
|
1143
|
+
const formatStatus = (status) => {
|
|
1144
|
+
switch (status) {
|
|
1145
|
+
case "missing":
|
|
1146
|
+
return "Nicht erstellt";
|
|
1147
|
+
case "pending":
|
|
1148
|
+
return "Ausstehend";
|
|
1149
|
+
case "processing":
|
|
1150
|
+
return "Wird erstellt";
|
|
1151
|
+
case "ready":
|
|
1152
|
+
return "Bereit";
|
|
1153
|
+
case "failed":
|
|
1154
|
+
return "Fehlgeschlagen";
|
|
1155
|
+
case "canceled":
|
|
1156
|
+
return "Storniert";
|
|
1157
|
+
}
|
|
1158
|
+
};
|
|
1159
|
+
const formatGeneratedAt = (generatedAt) => new Intl.DateTimeFormat("de-AT", {
|
|
1160
|
+
dateStyle: "medium",
|
|
1161
|
+
timeStyle: "short"
|
|
1162
|
+
}).format(new Date(generatedAt));
|
|
1163
|
+
const formatDetails = (packingList) => [
|
|
1164
|
+
packingList.number ? `Nummer: ${packingList.number}` : null,
|
|
1165
|
+
packingList.source_revision !== null ? `Revision: ${packingList.source_revision}` : null,
|
|
1166
|
+
packingList.generated_at ? `Datum: ${formatGeneratedAt(packingList.generated_at)}` : null
|
|
1167
|
+
].filter((detail) => detail !== null).join(" · ");
|
|
1168
|
+
const formatFailureReason = (failureReason) => {
|
|
1169
|
+
const description = failureReason?.replace(/\s+/g, " ").trim();
|
|
1170
|
+
if (!description) {
|
|
1171
|
+
return null;
|
|
1172
|
+
}
|
|
1173
|
+
return description.length > 140 ? `${description.slice(0, 137)}...` : description;
|
|
1174
|
+
};
|
|
1175
|
+
const retryLabel = (status) => {
|
|
1176
|
+
if (status === "missing") {
|
|
1177
|
+
return "Erstellen";
|
|
1178
|
+
}
|
|
1179
|
+
if (status === "pending" || status === "failed") {
|
|
1180
|
+
return "Erneut versuchen";
|
|
1181
|
+
}
|
|
1182
|
+
return null;
|
|
1183
|
+
};
|
|
1184
|
+
const OrderPackingListsWidgetContent = ({
|
|
1185
|
+
data
|
|
1186
|
+
}) => {
|
|
1187
|
+
const queryClient = useQueryClient();
|
|
1188
|
+
const [preview, setPreview] = useState(null);
|
|
1189
|
+
const previewRequest = useRef(0);
|
|
1190
|
+
const previewUrl = useRef(null);
|
|
1191
|
+
const packingListsQueryKey = ["packing-lists", "order", data.id];
|
|
1192
|
+
const packingListsQuery = useQuery({
|
|
1193
|
+
queryKey: packingListsQueryKey,
|
|
1194
|
+
queryFn: () => listOrderPackingLists(data.id),
|
|
1195
|
+
refetchInterval: (query) => {
|
|
1196
|
+
const packingLists2 = query.state.data?.packing_lists;
|
|
1197
|
+
return packingLists2?.some(
|
|
1198
|
+
({ status }) => status === "missing" || status === "pending" || status === "processing"
|
|
1199
|
+
) ? 2e3 : false;
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
const packingLists = packingListsQuery.data?.packing_lists ?? [];
|
|
1203
|
+
const retryPackingList = useMutation({
|
|
1204
|
+
mutationFn: (packingList) => retryOrderPackingList(data.id, packingList.fulfillment_id),
|
|
1205
|
+
onSuccess: async () => {
|
|
1206
|
+
toast.success("Die Packliste wird erstellt.");
|
|
1207
|
+
await queryClient.invalidateQueries({ queryKey: packingListsQueryKey });
|
|
1208
|
+
},
|
|
1209
|
+
onError: (error) => {
|
|
1210
|
+
toast.error("Die Packliste konnte nicht erstellt werden.", {
|
|
1211
|
+
description: error instanceof Error ? error.message : "Bitte versuchen Sie es erneut."
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
useEffect(
|
|
1216
|
+
() => () => {
|
|
1217
|
+
previewRequest.current += 1;
|
|
1218
|
+
if (previewUrl.current) {
|
|
1219
|
+
URL.revokeObjectURL(previewUrl.current);
|
|
1220
|
+
previewUrl.current = null;
|
|
1221
|
+
}
|
|
1222
|
+
},
|
|
1223
|
+
[]
|
|
1224
|
+
);
|
|
1225
|
+
const revokePreviewUrl = () => {
|
|
1226
|
+
if (previewUrl.current) {
|
|
1227
|
+
URL.revokeObjectURL(previewUrl.current);
|
|
1228
|
+
previewUrl.current = null;
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
const closePreview = () => {
|
|
1232
|
+
previewRequest.current += 1;
|
|
1233
|
+
revokePreviewUrl();
|
|
1234
|
+
setPreview(null);
|
|
1235
|
+
};
|
|
1236
|
+
const previewPackingList = async (packingList, position) => {
|
|
1237
|
+
revokePreviewUrl();
|
|
1238
|
+
const requestId = previewRequest.current + 1;
|
|
1239
|
+
previewRequest.current = requestId;
|
|
1240
|
+
setPreview({ packingList, position, url: null, error: null });
|
|
1241
|
+
try {
|
|
1242
|
+
const url = await retrieveOrderPackingListPreview(
|
|
1243
|
+
data.id,
|
|
1244
|
+
packingList.fulfillment_id
|
|
1245
|
+
);
|
|
1246
|
+
if (previewRequest.current !== requestId) {
|
|
1247
|
+
URL.revokeObjectURL(url);
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
previewUrl.current = url;
|
|
1251
|
+
setPreview({ packingList, position, url, error: null });
|
|
1252
|
+
} catch {
|
|
1253
|
+
if (previewRequest.current !== requestId) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
setPreview({
|
|
1257
|
+
packingList,
|
|
1258
|
+
position,
|
|
1259
|
+
url: null,
|
|
1260
|
+
error: "Die Packliste konnte nicht geladen werden."
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1265
|
+
/* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
1267
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Packlisten" }),
|
|
1268
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Packlisten zu den Versandvorgängen dieser Bestellung." })
|
|
1269
|
+
] }),
|
|
1270
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-3 px-6 py-4", children: packingListsQuery.isPending ? /* @__PURE__ */ jsx(
|
|
1271
|
+
Text,
|
|
1272
|
+
{
|
|
1273
|
+
"aria-atomic": "true",
|
|
1274
|
+
"aria-live": "polite",
|
|
1275
|
+
className: "text-ui-fg-subtle",
|
|
1276
|
+
role: "status",
|
|
1277
|
+
children: "Packlisten werden geladen."
|
|
1278
|
+
}
|
|
1279
|
+
) : packingListsQuery.isError ? /* @__PURE__ */ jsx(
|
|
1280
|
+
Text,
|
|
1281
|
+
{
|
|
1282
|
+
"aria-atomic": "true",
|
|
1283
|
+
"aria-live": "assertive",
|
|
1284
|
+
className: "text-ui-fg-error",
|
|
1285
|
+
role: "alert",
|
|
1286
|
+
children: "Packlisten konnten nicht geladen werden."
|
|
1287
|
+
}
|
|
1288
|
+
) : packingLists.length ? packingLists.map((packingList, index) => {
|
|
1289
|
+
const actionLabel = retryLabel(packingList.status);
|
|
1290
|
+
const details = formatDetails(packingList);
|
|
1291
|
+
const failureReason = formatFailureReason(
|
|
1292
|
+
packingList.failure_reason
|
|
1293
|
+
);
|
|
1294
|
+
const canPreview = packingList.generated_at !== null && (packingList.status === "ready" || packingList.status === "canceled");
|
|
1295
|
+
const isRetrying = retryPackingList.isPending && retryPackingList.variables?.fulfillment_id === packingList.fulfillment_id;
|
|
1296
|
+
const accessiblePackingListName = packingList.number ? `Packliste ${packingList.number} für Fulfillment ${packingList.fulfillment_id}` : `Packliste ${index + 1} für Fulfillment ${packingList.fulfillment_id}`;
|
|
1297
|
+
return /* @__PURE__ */ jsxs(
|
|
1298
|
+
"div",
|
|
1299
|
+
{
|
|
1300
|
+
className: "border-ui-border-base rounded-md border p-3",
|
|
1301
|
+
children: [
|
|
1302
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
1303
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1304
|
+
/* @__PURE__ */ jsxs(Text, { className: "font-medium", children: [
|
|
1305
|
+
"Packliste ",
|
|
1306
|
+
index + 1
|
|
1307
|
+
] }),
|
|
1308
|
+
/* @__PURE__ */ jsxs(
|
|
1309
|
+
Text,
|
|
1310
|
+
{
|
|
1311
|
+
"aria-atomic": "true",
|
|
1312
|
+
"aria-live": "polite",
|
|
1313
|
+
className: packingList.status === "failed" ? "text-ui-fg-error text-xs" : "text-ui-fg-subtle text-xs",
|
|
1314
|
+
role: "status",
|
|
1315
|
+
children: [
|
|
1316
|
+
"Status: ",
|
|
1317
|
+
formatStatus(packingList.status)
|
|
1318
|
+
]
|
|
1319
|
+
}
|
|
1320
|
+
)
|
|
1321
|
+
] }),
|
|
1322
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 gap-2", children: [
|
|
1323
|
+
canPreview ? /* @__PURE__ */ jsx(
|
|
1324
|
+
Button,
|
|
1325
|
+
{
|
|
1326
|
+
"aria-label": `Ansehen: ${accessiblePackingListName}`,
|
|
1327
|
+
onClick: () => previewPackingList(packingList, index + 1),
|
|
1328
|
+
size: "small",
|
|
1329
|
+
variant: "secondary",
|
|
1330
|
+
children: "Ansehen"
|
|
1331
|
+
}
|
|
1332
|
+
) : null,
|
|
1333
|
+
actionLabel ? /* @__PURE__ */ jsx(
|
|
1334
|
+
Button,
|
|
1335
|
+
{
|
|
1336
|
+
"aria-label": `${actionLabel}: ${accessiblePackingListName}`,
|
|
1337
|
+
disabled: retryPackingList.isPending,
|
|
1338
|
+
isLoading: isRetrying,
|
|
1339
|
+
onClick: () => retryPackingList.mutate(packingList),
|
|
1340
|
+
size: "small",
|
|
1341
|
+
variant: "secondary",
|
|
1342
|
+
children: actionLabel
|
|
1343
|
+
}
|
|
1344
|
+
) : null
|
|
1345
|
+
] })
|
|
1346
|
+
] }),
|
|
1347
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 grid gap-1", children: [
|
|
1348
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-xs", children: [
|
|
1349
|
+
"Lagerstandort:",
|
|
1350
|
+
" ",
|
|
1351
|
+
packingList.location_name ?? "Nicht verfügbar"
|
|
1352
|
+
] }),
|
|
1353
|
+
details ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: details }) : null,
|
|
1354
|
+
failureReason ? /* @__PURE__ */ jsxs(
|
|
1355
|
+
Text,
|
|
1356
|
+
{
|
|
1357
|
+
"aria-atomic": "true",
|
|
1358
|
+
"aria-live": "assertive",
|
|
1359
|
+
className: "text-ui-fg-error text-xs",
|
|
1360
|
+
role: "alert",
|
|
1361
|
+
children: [
|
|
1362
|
+
"Fehler: ",
|
|
1363
|
+
failureReason
|
|
1364
|
+
]
|
|
1365
|
+
}
|
|
1366
|
+
) : null
|
|
1367
|
+
] })
|
|
1368
|
+
]
|
|
1369
|
+
},
|
|
1370
|
+
packingList.fulfillment_id
|
|
1371
|
+
);
|
|
1372
|
+
}) : /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Für diese Bestellung sind keine Packlisten vorhanden." }) })
|
|
1373
|
+
] }),
|
|
1374
|
+
/* @__PURE__ */ jsx(
|
|
1375
|
+
FocusModal,
|
|
1376
|
+
{
|
|
1377
|
+
onOpenChange: (open) => {
|
|
1378
|
+
if (!open) {
|
|
1379
|
+
closePreview();
|
|
1380
|
+
}
|
|
1381
|
+
},
|
|
1382
|
+
open: preview !== null,
|
|
1383
|
+
children: /* @__PURE__ */ jsxs(FocusModal.Content, { children: [
|
|
1384
|
+
/* @__PURE__ */ jsx(FocusModal.Header, { children: /* @__PURE__ */ jsx(FocusModal.Title, { children: preview ? `Packliste ${preview.position}${preview.packingList.number ? ` · ${preview.packingList.number}` : ""}` : "Packliste" }) }),
|
|
1385
|
+
/* @__PURE__ */ jsx(FocusModal.Body, { className: "h-[75vh] p-0", children: preview?.url ? /* @__PURE__ */ jsx(
|
|
1386
|
+
"iframe",
|
|
1387
|
+
{
|
|
1388
|
+
className: "h-full w-full border-0",
|
|
1389
|
+
src: preview.url,
|
|
1390
|
+
title: `Vorschau Packliste ${preview.position}`
|
|
1391
|
+
}
|
|
1392
|
+
) : preview?.error ? /* @__PURE__ */ jsx(
|
|
1393
|
+
Text,
|
|
1394
|
+
{
|
|
1395
|
+
"aria-atomic": "true",
|
|
1396
|
+
"aria-live": "assertive",
|
|
1397
|
+
className: "p-6 text-ui-fg-error",
|
|
1398
|
+
role: "alert",
|
|
1399
|
+
children: preview.error
|
|
1400
|
+
}
|
|
1401
|
+
) : /* @__PURE__ */ jsx(
|
|
1402
|
+
Text,
|
|
1403
|
+
{
|
|
1404
|
+
"aria-atomic": "true",
|
|
1405
|
+
"aria-live": "polite",
|
|
1406
|
+
className: "p-6 text-ui-fg-subtle",
|
|
1407
|
+
role: "status",
|
|
1408
|
+
children: "Packliste wird geladen."
|
|
1409
|
+
}
|
|
1410
|
+
) }),
|
|
1411
|
+
/* @__PURE__ */ jsx(FocusModal.Footer, { children: /* @__PURE__ */ jsx(FocusModal.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { onClick: closePreview, variant: "secondary", children: "Schließen" }) }) })
|
|
1412
|
+
] })
|
|
1413
|
+
}
|
|
1414
|
+
)
|
|
1415
|
+
] });
|
|
1416
|
+
};
|
|
1417
|
+
const OrderPackingListsWidget = (props) => /* @__PURE__ */ jsx(PluginFeatureGate, { pluginKey: "documents", featureKeys: ["packing_lists"], children: /* @__PURE__ */ jsx(OrderPackingListsWidgetContent, { ...props }) });
|
|
1418
|
+
defineWidgetConfig({
|
|
1419
|
+
zone: "order.details.side.after"
|
|
1420
|
+
});
|
|
1421
|
+
const PluginHubCard = ({ plugin: plugin2 }) => {
|
|
1422
|
+
const mutation = useUpdatePluginFeatures();
|
|
1423
|
+
const pending = mutation.isPending;
|
|
1424
|
+
const toggle = (enabled) => {
|
|
1425
|
+
mutation.mutate({
|
|
1426
|
+
plugin_key: plugin2.plugin_key,
|
|
1427
|
+
expected_version: plugin2.version,
|
|
1428
|
+
enabled,
|
|
1429
|
+
features: Object.fromEntries(
|
|
1430
|
+
plugin2.features.map((feature) => [feature.key, feature.enabled])
|
|
1431
|
+
)
|
|
1432
|
+
});
|
|
1433
|
+
};
|
|
1434
|
+
return /* @__PURE__ */ jsxs(
|
|
1435
|
+
Container,
|
|
1436
|
+
{
|
|
1437
|
+
className: "flex flex-wrap items-start justify-between gap-4 px-6 py-4",
|
|
1438
|
+
"aria-busy": pending,
|
|
1439
|
+
children: [
|
|
1440
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1441
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: plugin2.label }),
|
|
1442
|
+
plugin2.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: plugin2.description }),
|
|
1443
|
+
/* @__PURE__ */ jsx(PluginStatusChips, { plugin: plugin2 }),
|
|
1444
|
+
pending && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-muted mt-1", children: texts.hints.saving })
|
|
1445
|
+
] }),
|
|
1446
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3", children: [
|
|
1447
|
+
plugin2.switchable && /* @__PURE__ */ jsx(
|
|
1448
|
+
Switch,
|
|
1449
|
+
{
|
|
1450
|
+
"aria-label": texts.plugin.switchLabel(plugin2.label),
|
|
1451
|
+
checked: plugin2.enabled,
|
|
1452
|
+
disabled: pending,
|
|
1453
|
+
onCheckedChange: (checked) => toggle(checked === true)
|
|
1454
|
+
}
|
|
1455
|
+
),
|
|
1456
|
+
/* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsx(
|
|
1457
|
+
Link,
|
|
1458
|
+
{
|
|
1459
|
+
to: plugin2.admin_page ?? pluginDetailPath(plugin2.plugin_key),
|
|
1460
|
+
"aria-label": texts.plugin.openLabel(plugin2.label),
|
|
1461
|
+
children: texts.plugin.open
|
|
1462
|
+
}
|
|
1463
|
+
) })
|
|
1464
|
+
] })
|
|
1465
|
+
]
|
|
1466
|
+
}
|
|
1467
|
+
);
|
|
1468
|
+
};
|
|
200
1469
|
const PluginFeaturesSettingsPage = () => {
|
|
201
1470
|
const query = usePluginFeatures();
|
|
202
1471
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3", children: [
|
|
@@ -216,19 +1485,885 @@ const PluginFeaturesSettingsPage = () => {
|
|
|
216
1485
|
children: texts.states.retry
|
|
217
1486
|
}
|
|
218
1487
|
)
|
|
219
|
-
] }) : query.data.plugins.length === 0 ? /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: texts.states.empty }) }) :
|
|
1488
|
+
] }) : query.data.plugins.length === 0 ? /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: texts.states.empty }) }) : [
|
|
1489
|
+
...query.data.plugins.filter((plugin2) => !plugin2.switchable),
|
|
1490
|
+
...query.data.plugins.filter((plugin2) => plugin2.switchable)
|
|
1491
|
+
].map((plugin2) => /* @__PURE__ */ jsx(PluginHubCard, { plugin: plugin2 }, plugin2.plugin_key))
|
|
220
1492
|
] });
|
|
221
1493
|
};
|
|
222
1494
|
const config = defineRouteConfig({
|
|
223
1495
|
label: texts.menu.label
|
|
224
1496
|
});
|
|
225
|
-
const
|
|
1497
|
+
const withPagination = (params) => new URLSearchParams({
|
|
1498
|
+
limit: String(params.limit),
|
|
1499
|
+
offset: String(params.offset)
|
|
1500
|
+
});
|
|
1501
|
+
const withMessageQuery = (path, params) => {
|
|
1502
|
+
const query = withPagination(params);
|
|
1503
|
+
if (params.kind) {
|
|
1504
|
+
query.set("kind", params.kind);
|
|
1505
|
+
}
|
|
1506
|
+
if (params.status) {
|
|
1507
|
+
query.set("status", params.status);
|
|
1508
|
+
}
|
|
1509
|
+
return `${path}?${query.toString()}`;
|
|
1510
|
+
};
|
|
1511
|
+
const listEmailMessages = (params) => sdk.client.fetch(
|
|
1512
|
+
withMessageQuery(emailAdminPaths.get.list, params)
|
|
1513
|
+
);
|
|
1514
|
+
const retrieveEmailMessage = (messageId) => sdk.client.fetch(
|
|
1515
|
+
emailAdminPaths.get.detail(messageId)
|
|
1516
|
+
);
|
|
1517
|
+
const resendEmailMessage = (messageId, body) => sdk.client.fetch(
|
|
1518
|
+
emailAdminPaths.post.resend(messageId),
|
|
1519
|
+
{ method: "POST", body }
|
|
1520
|
+
);
|
|
1521
|
+
const listEmailTermsVersions = (params) => {
|
|
1522
|
+
const query = withPagination(params);
|
|
1523
|
+
return sdk.client.fetch(
|
|
1524
|
+
`${emailAdminPaths.get.termsVersions}?${query.toString()}`
|
|
1525
|
+
);
|
|
1526
|
+
};
|
|
1527
|
+
const createEmailTermsVersion = (body) => sdk.client.fetch(
|
|
1528
|
+
emailAdminPaths.post.createTermsVersion,
|
|
1529
|
+
{ method: "POST", body }
|
|
1530
|
+
);
|
|
1531
|
+
const downloadEmailTermsVersion = async (termsVersionId) => {
|
|
1532
|
+
const response = await sdk.client.fetch(
|
|
1533
|
+
emailAdminPaths.get.downloadTermsVersion(termsVersionId),
|
|
1534
|
+
{ headers: { accept: "application/pdf" } }
|
|
1535
|
+
);
|
|
1536
|
+
return response.blob();
|
|
1537
|
+
};
|
|
1538
|
+
const PAGE_SIZE$1 = 20;
|
|
1539
|
+
const MAX_PAGE_OFFSET$1 = 1e4;
|
|
1540
|
+
const KINDS = [
|
|
1541
|
+
"order.received",
|
|
1542
|
+
"order.received.admin",
|
|
1543
|
+
"order.shipped",
|
|
1544
|
+
"order.canceled",
|
|
1545
|
+
"order.return_requested",
|
|
1546
|
+
"order.return_received",
|
|
1547
|
+
"order.refunded",
|
|
1548
|
+
"auth.password_reset",
|
|
1549
|
+
"affiliate.consignment_report_due"
|
|
1550
|
+
];
|
|
1551
|
+
const STATUSES = [
|
|
1552
|
+
"pending",
|
|
1553
|
+
"ready",
|
|
1554
|
+
"sending",
|
|
1555
|
+
"retry_pending",
|
|
1556
|
+
"sent",
|
|
1557
|
+
"failed",
|
|
1558
|
+
"unknown",
|
|
1559
|
+
"suppressed"
|
|
1560
|
+
];
|
|
1561
|
+
const KIND_LABELS = {
|
|
1562
|
+
"order.received": "Bestellbestätigung (Kunde)",
|
|
1563
|
+
"order.received.admin": "Neue Bestellung (Shop)",
|
|
1564
|
+
"order.shipped": "Versandbestätigung",
|
|
1565
|
+
"order.canceled": "Stornierung",
|
|
1566
|
+
"order.return_requested": "Retourenanfrage",
|
|
1567
|
+
"order.return_received": "Retoureneingang",
|
|
1568
|
+
"order.refunded": "Rückerstattung",
|
|
1569
|
+
"auth.password_reset": "Passwort zurücksetzen",
|
|
1570
|
+
"affiliate.consignment_report_due": "Affiliate-Meldung"
|
|
1571
|
+
};
|
|
1572
|
+
const STATUS_LABELS = {
|
|
1573
|
+
pending: "Wartet auf Fakten",
|
|
1574
|
+
ready: "Bereit",
|
|
1575
|
+
sending: "Wird versendet",
|
|
1576
|
+
retry_pending: "Erneuter Versuch geplant",
|
|
1577
|
+
sent: "Versendet",
|
|
1578
|
+
failed: "Fehlgeschlagen",
|
|
1579
|
+
unknown: "Ausgang unbekannt",
|
|
1580
|
+
suppressed: "Unterdrückt"
|
|
1581
|
+
};
|
|
1582
|
+
const ATTEMPT_STATUS_LABELS = {
|
|
1583
|
+
processing: "In Bearbeitung",
|
|
1584
|
+
sent: "Versendet",
|
|
1585
|
+
failed: "Fehlgeschlagen",
|
|
1586
|
+
unknown: "Ausgang unbekannt"
|
|
1587
|
+
};
|
|
1588
|
+
const isKindFilter = (value) => value === "all" || KINDS.includes(value);
|
|
1589
|
+
const isStatusFilter = (value) => value === "all" || STATUSES.includes(value);
|
|
1590
|
+
const canResend = (message) => message.kind !== "auth.password_reset" && (message.status === "sent" || message.status === "unknown" || message.status === "failed");
|
|
1591
|
+
const formatDateTime$1 = (value) => {
|
|
1592
|
+
if (!value) {
|
|
1593
|
+
return "-";
|
|
1594
|
+
}
|
|
1595
|
+
const date = new Date(value);
|
|
1596
|
+
if (Number.isNaN(date.getTime())) {
|
|
1597
|
+
return "-";
|
|
1598
|
+
}
|
|
1599
|
+
return new Intl.DateTimeFormat("de-AT", {
|
|
1600
|
+
dateStyle: "medium",
|
|
1601
|
+
timeStyle: "short"
|
|
1602
|
+
}).format(date);
|
|
1603
|
+
};
|
|
1604
|
+
const formatResource = (message) => message.resource_id ? `${message.resource_type} · ${message.resource_id}` : message.resource_type;
|
|
1605
|
+
const Status = ({ message }) => /* @__PURE__ */ jsx(
|
|
1606
|
+
"span",
|
|
1607
|
+
{
|
|
1608
|
+
className: message.status === "sent" ? "text-ui-fg-base" : message.status === "failed" || message.status === "unknown" ? "text-ui-fg-error" : "text-ui-fg-subtle",
|
|
1609
|
+
children: STATUS_LABELS[message.status]
|
|
1610
|
+
}
|
|
1611
|
+
);
|
|
1612
|
+
const MessageCards = ({
|
|
1613
|
+
messages,
|
|
1614
|
+
selectedId,
|
|
1615
|
+
onSelect
|
|
1616
|
+
}) => /* @__PURE__ */ jsx("div", { className: "grid gap-3 px-4 py-4 md:hidden", children: messages.map((message) => /* @__PURE__ */ jsxs(
|
|
1617
|
+
"div",
|
|
1618
|
+
{
|
|
1619
|
+
className: `border-ui-border-base rounded-md border p-4 ${selectedId === message.id ? "bg-ui-bg-base-hover" : ""}`,
|
|
1620
|
+
children: [
|
|
1621
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
1622
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1623
|
+
/* @__PURE__ */ jsx(Text, { className: "font-medium", children: KIND_LABELS[message.kind] }),
|
|
1624
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle break-all text-xs", children: formatResource(message) })
|
|
1625
|
+
] }),
|
|
1626
|
+
/* @__PURE__ */ jsx(
|
|
1627
|
+
Button,
|
|
1628
|
+
{
|
|
1629
|
+
"aria-pressed": selectedId === message.id,
|
|
1630
|
+
onClick: () => onSelect(message.id),
|
|
1631
|
+
size: "small",
|
|
1632
|
+
variant: "secondary",
|
|
1633
|
+
children: "Details"
|
|
1634
|
+
}
|
|
1635
|
+
)
|
|
1636
|
+
] }),
|
|
1637
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 flex items-center justify-between gap-3 text-sm", children: [
|
|
1638
|
+
/* @__PURE__ */ jsx(Status, { message }),
|
|
1639
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: formatDateTime$1(message.created_at) })
|
|
1640
|
+
] })
|
|
1641
|
+
]
|
|
1642
|
+
},
|
|
1643
|
+
message.id
|
|
1644
|
+
)) });
|
|
1645
|
+
const MessageTable = ({
|
|
1646
|
+
messages,
|
|
1647
|
+
selectedId,
|
|
1648
|
+
onSelect
|
|
1649
|
+
}) => /* @__PURE__ */ jsx("div", { className: "hidden overflow-x-auto md:block", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
|
|
1650
|
+
/* @__PURE__ */ jsx("thead", { className: "border-ui-border-base border-b", children: /* @__PURE__ */ jsxs("tr", { className: "text-ui-fg-subtle", children: [
|
|
1651
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Art" }),
|
|
1652
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Status" }),
|
|
1653
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Ressource" }),
|
|
1654
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Versuche" }),
|
|
1655
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Erstellt" }),
|
|
1656
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-right font-medium", children: "Aktion" })
|
|
1657
|
+
] }) }),
|
|
1658
|
+
/* @__PURE__ */ jsx("tbody", { children: messages.map((message) => /* @__PURE__ */ jsxs(
|
|
1659
|
+
"tr",
|
|
1660
|
+
{
|
|
1661
|
+
className: `border-ui-border-base border-b ${selectedId === message.id ? "bg-ui-bg-base-hover" : ""}`,
|
|
1662
|
+
children: [
|
|
1663
|
+
/* @__PURE__ */ jsx("td", { className: "px-6 py-3 font-medium", children: KIND_LABELS[message.kind] }),
|
|
1664
|
+
/* @__PURE__ */ jsx("td", { className: "px-6 py-3", children: /* @__PURE__ */ jsx(Status, { message }) }),
|
|
1665
|
+
/* @__PURE__ */ jsx("td", { className: "max-w-64 truncate px-6 py-3 font-mono text-xs", children: formatResource(message) }),
|
|
1666
|
+
/* @__PURE__ */ jsx("td", { className: "px-6 py-3 tabular-nums", children: message.attempt_count }),
|
|
1667
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-6 py-3", children: formatDateTime$1(message.created_at) }),
|
|
1668
|
+
/* @__PURE__ */ jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsx(
|
|
1669
|
+
Button,
|
|
1670
|
+
{
|
|
1671
|
+
"aria-pressed": selectedId === message.id,
|
|
1672
|
+
onClick: () => onSelect(message.id),
|
|
1673
|
+
size: "small",
|
|
1674
|
+
variant: "secondary",
|
|
1675
|
+
children: "Details"
|
|
1676
|
+
}
|
|
1677
|
+
) })
|
|
1678
|
+
]
|
|
1679
|
+
},
|
|
1680
|
+
message.id
|
|
1681
|
+
)) })
|
|
1682
|
+
] }) });
|
|
1683
|
+
const DetailTimestamp = ({
|
|
1684
|
+
label,
|
|
1685
|
+
value
|
|
1686
|
+
}) => /* @__PURE__ */ jsxs("div", { children: [
|
|
1687
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: label }),
|
|
1688
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm", children: formatDateTime$1(value) })
|
|
1689
|
+
] });
|
|
1690
|
+
const EmailStatusPage = () => {
|
|
1691
|
+
const queryClient = useQueryClient();
|
|
1692
|
+
const [kind, setKind] = useState("all");
|
|
1693
|
+
const [status, setStatus] = useState("all");
|
|
1694
|
+
const [offset, setOffset] = useState(0);
|
|
1695
|
+
const [selectedId, setSelectedId] = useState(null);
|
|
1696
|
+
const [confirmationOpen, setConfirmationOpen] = useState(false);
|
|
1697
|
+
const [retryDecisions, setRetryDecisions] = useState({});
|
|
1698
|
+
const listQueryKey = [
|
|
1699
|
+
"email",
|
|
1700
|
+
"message-list",
|
|
1701
|
+
{ kind, status, offset, limit: PAGE_SIZE$1 }
|
|
1702
|
+
];
|
|
1703
|
+
const messagesQuery = useQuery({
|
|
1704
|
+
queryKey: listQueryKey,
|
|
1705
|
+
queryFn: () => listEmailMessages({
|
|
1706
|
+
kind: kind === "all" ? void 0 : kind,
|
|
1707
|
+
status: status === "all" ? void 0 : status,
|
|
1708
|
+
limit: PAGE_SIZE$1,
|
|
1709
|
+
offset
|
|
1710
|
+
})
|
|
1711
|
+
});
|
|
1712
|
+
const detailQueryKey = ["email", "message-detail", selectedId];
|
|
1713
|
+
const detailQuery = useQuery({
|
|
1714
|
+
queryKey: detailQueryKey,
|
|
1715
|
+
queryFn: () => retrieveEmailMessage(selectedId),
|
|
1716
|
+
enabled: selectedId !== null
|
|
1717
|
+
});
|
|
1718
|
+
const resend = useMutation({
|
|
1719
|
+
mutationFn: (decision) => resendEmailMessage(decision.messageId, {
|
|
1720
|
+
request_id: decision.requestId
|
|
1721
|
+
}),
|
|
1722
|
+
onSuccess: async (_, decision) => {
|
|
1723
|
+
setRetryDecisions((current) => {
|
|
1724
|
+
const next = { ...current };
|
|
1725
|
+
delete next[decision.messageId];
|
|
1726
|
+
return next;
|
|
1727
|
+
});
|
|
1728
|
+
toast.success("Der erneute Versand wurde angenommen.");
|
|
1729
|
+
await Promise.all([
|
|
1730
|
+
queryClient.invalidateQueries({ queryKey: ["email", "message-list"] }),
|
|
1731
|
+
queryClient.invalidateQueries({
|
|
1732
|
+
queryKey: ["email", "message-detail", decision.messageId]
|
|
1733
|
+
})
|
|
1734
|
+
]);
|
|
1735
|
+
},
|
|
1736
|
+
onError: (_, decision) => {
|
|
1737
|
+
setRetryDecisions((current) => ({
|
|
1738
|
+
...current,
|
|
1739
|
+
[decision.messageId]: decision
|
|
1740
|
+
}));
|
|
1741
|
+
toast.error("Die Anfrage zum erneuten Versand ist fehlgeschlagen.", {
|
|
1742
|
+
description: "Dieselbe Anfrage kann mit unveränderter UUID sicher wiederholt werden."
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
const messages = messagesQuery.data?.messages ?? [];
|
|
1747
|
+
const count = messagesQuery.data?.count ?? 0;
|
|
1748
|
+
const detail = detailQuery.data?.message;
|
|
1749
|
+
const attempts = detailQuery.data?.attempts ?? [];
|
|
1750
|
+
const retryDecision = selectedId ? retryDecisions[selectedId] : void 0;
|
|
1751
|
+
const selectMessage = (messageId) => {
|
|
1752
|
+
setSelectedId(messageId);
|
|
1753
|
+
};
|
|
1754
|
+
const changePage = (nextOffset) => {
|
|
1755
|
+
setSelectedId(null);
|
|
1756
|
+
setOffset(nextOffset);
|
|
1757
|
+
};
|
|
1758
|
+
const confirmResend = () => {
|
|
1759
|
+
if (!detail || !canResend(detail)) {
|
|
1760
|
+
setConfirmationOpen(false);
|
|
1761
|
+
return;
|
|
1762
|
+
}
|
|
1763
|
+
setConfirmationOpen(false);
|
|
1764
|
+
resend.mutate({ messageId: detail.id, requestId: crypto.randomUUID() });
|
|
1765
|
+
};
|
|
1766
|
+
const renderMessages = () => {
|
|
1767
|
+
if (messagesQuery.isPending) {
|
|
1768
|
+
return /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", role: "status", children: "E-Mails werden geladen." }) });
|
|
1769
|
+
}
|
|
1770
|
+
if (messagesQuery.isError) {
|
|
1771
|
+
return /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", role: "alert", children: "E-Mails konnten nicht geladen werden." }) });
|
|
1772
|
+
}
|
|
1773
|
+
if (!messages.length) {
|
|
1774
|
+
return /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Für diese Filter wurden keine E-Mails gefunden." }) });
|
|
1775
|
+
}
|
|
1776
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1777
|
+
/* @__PURE__ */ jsx(
|
|
1778
|
+
MessageTable,
|
|
1779
|
+
{
|
|
1780
|
+
messages,
|
|
1781
|
+
onSelect: selectMessage,
|
|
1782
|
+
selectedId
|
|
1783
|
+
}
|
|
1784
|
+
),
|
|
1785
|
+
/* @__PURE__ */ jsx(
|
|
1786
|
+
MessageCards,
|
|
1787
|
+
{
|
|
1788
|
+
messages,
|
|
1789
|
+
onSelect: selectMessage,
|
|
1790
|
+
selectedId
|
|
1791
|
+
}
|
|
1792
|
+
)
|
|
1793
|
+
] });
|
|
1794
|
+
};
|
|
1795
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3", children: [
|
|
1796
|
+
/* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
1797
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 px-6 py-4 lg:flex-row lg:items-center lg:justify-between", children: [
|
|
1798
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1799
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "E-Mail-Protokoll" }),
|
|
1800
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Sicherer Status und Zustellversuche ohne Empfänger- oder Inhaltsdaten." })
|
|
1801
|
+
] }),
|
|
1802
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
1803
|
+
/* @__PURE__ */ jsxs(
|
|
1804
|
+
Select,
|
|
1805
|
+
{
|
|
1806
|
+
value: kind,
|
|
1807
|
+
onValueChange: (value) => {
|
|
1808
|
+
if (isKindFilter(value)) {
|
|
1809
|
+
setKind(value);
|
|
1810
|
+
setOffset(0);
|
|
1811
|
+
setSelectedId(null);
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
children: [
|
|
1815
|
+
/* @__PURE__ */ jsx(Select.Trigger, { "aria-label": "E-Mail-Art", className: "sm:w-56", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
|
|
1816
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
1817
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "all", children: "Alle Arten" }),
|
|
1818
|
+
KINDS.map((value) => /* @__PURE__ */ jsx(Select.Item, { value, children: KIND_LABELS[value] }, value))
|
|
1819
|
+
] })
|
|
1820
|
+
]
|
|
1821
|
+
}
|
|
1822
|
+
),
|
|
1823
|
+
/* @__PURE__ */ jsxs(
|
|
1824
|
+
Select,
|
|
1825
|
+
{
|
|
1826
|
+
value: status,
|
|
1827
|
+
onValueChange: (value) => {
|
|
1828
|
+
if (isStatusFilter(value)) {
|
|
1829
|
+
setStatus(value);
|
|
1830
|
+
setOffset(0);
|
|
1831
|
+
setSelectedId(null);
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
children: [
|
|
1835
|
+
/* @__PURE__ */ jsx(Select.Trigger, { "aria-label": "E-Mail-Status", className: "sm:w-52", children: /* @__PURE__ */ jsx(Select.Value, {}) }),
|
|
1836
|
+
/* @__PURE__ */ jsxs(Select.Content, { children: [
|
|
1837
|
+
/* @__PURE__ */ jsx(Select.Item, { value: "all", children: "Alle Status" }),
|
|
1838
|
+
STATUSES.map((value) => /* @__PURE__ */ jsx(Select.Item, { value, children: STATUS_LABELS[value] }, value))
|
|
1839
|
+
] })
|
|
1840
|
+
]
|
|
1841
|
+
}
|
|
1842
|
+
)
|
|
1843
|
+
] })
|
|
1844
|
+
] }),
|
|
1845
|
+
renderMessages(),
|
|
1846
|
+
!messagesQuery.isPending && !messagesQuery.isError && count > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
1847
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-sm", children: [
|
|
1848
|
+
offset + 1,
|
|
1849
|
+
"-",
|
|
1850
|
+
offset + messages.length,
|
|
1851
|
+
" von ",
|
|
1852
|
+
count
|
|
1853
|
+
] }),
|
|
1854
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1855
|
+
/* @__PURE__ */ jsx(
|
|
1856
|
+
Button,
|
|
1857
|
+
{
|
|
1858
|
+
disabled: offset === 0,
|
|
1859
|
+
onClick: () => changePage(Math.max(0, offset - PAGE_SIZE$1)),
|
|
1860
|
+
size: "small",
|
|
1861
|
+
variant: "secondary",
|
|
1862
|
+
children: "Zurück"
|
|
1863
|
+
}
|
|
1864
|
+
),
|
|
1865
|
+
/* @__PURE__ */ jsx(
|
|
1866
|
+
Button,
|
|
1867
|
+
{
|
|
1868
|
+
disabled: offset >= MAX_PAGE_OFFSET$1 || offset + messages.length >= count,
|
|
1869
|
+
onClick: () => changePage(Math.min(MAX_PAGE_OFFSET$1, offset + PAGE_SIZE$1)),
|
|
1870
|
+
size: "small",
|
|
1871
|
+
variant: "secondary",
|
|
1872
|
+
children: "Weiter"
|
|
1873
|
+
}
|
|
1874
|
+
)
|
|
1875
|
+
] })
|
|
1876
|
+
] }) : null
|
|
1877
|
+
] }),
|
|
1878
|
+
selectedId ? /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
1879
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-start sm:justify-between", children: [
|
|
1880
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1881
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "Zustellstatus" }),
|
|
1882
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Autoritativer Status und sichere Versuchsdaten." })
|
|
1883
|
+
] }),
|
|
1884
|
+
detail && canResend(detail) ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 sm:items-end", children: [
|
|
1885
|
+
/* @__PURE__ */ jsx(
|
|
1886
|
+
Button,
|
|
1887
|
+
{
|
|
1888
|
+
disabled: resend.isPending,
|
|
1889
|
+
onClick: () => setConfirmationOpen(true),
|
|
1890
|
+
size: "small",
|
|
1891
|
+
variant: "secondary",
|
|
1892
|
+
children: "Erneut senden"
|
|
1893
|
+
}
|
|
1894
|
+
),
|
|
1895
|
+
retryDecision ? /* @__PURE__ */ jsx(
|
|
1896
|
+
Button,
|
|
1897
|
+
{
|
|
1898
|
+
disabled: resend.isPending,
|
|
1899
|
+
onClick: () => resend.mutate(retryDecision),
|
|
1900
|
+
size: "small",
|
|
1901
|
+
variant: "transparent",
|
|
1902
|
+
children: "Gleiche Anfrage wiederholen"
|
|
1903
|
+
}
|
|
1904
|
+
) : null
|
|
1905
|
+
] }) : null
|
|
1906
|
+
] }),
|
|
1907
|
+
detailQuery.isPending ? /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", role: "status", children: "Zustellversuche werden geladen." }) }) : detailQuery.isError ? /* @__PURE__ */ jsx("div", { className: "px-6 py-8", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", role: "alert", children: "Details konnten nicht geladen werden." }) }) : detail ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1908
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 px-6 py-5 sm:grid-cols-2 lg:grid-cols-4", children: [
|
|
1909
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1910
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Art" }),
|
|
1911
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm", children: KIND_LABELS[detail.kind] })
|
|
1912
|
+
] }),
|
|
1913
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1914
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Status" }),
|
|
1915
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm", children: /* @__PURE__ */ jsx(Status, { message: detail }) })
|
|
1916
|
+
] }),
|
|
1917
|
+
/* @__PURE__ */ jsxs("div", { className: "sm:col-span-2", children: [
|
|
1918
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Ressource" }),
|
|
1919
|
+
/* @__PURE__ */ jsx(Text, { className: "break-all font-mono text-xs", children: formatResource(detail) })
|
|
1920
|
+
] }),
|
|
1921
|
+
/* @__PURE__ */ jsx(DetailTimestamp, { label: "Erstellt", value: detail.created_at }),
|
|
1922
|
+
/* @__PURE__ */ jsx(
|
|
1923
|
+
DetailTimestamp,
|
|
1924
|
+
{
|
|
1925
|
+
label: "Aktualisiert",
|
|
1926
|
+
value: detail.updated_at
|
|
1927
|
+
}
|
|
1928
|
+
),
|
|
1929
|
+
/* @__PURE__ */ jsx(DetailTimestamp, { label: "Versendet", value: detail.sent_at }),
|
|
1930
|
+
/* @__PURE__ */ jsx(
|
|
1931
|
+
DetailTimestamp,
|
|
1932
|
+
{
|
|
1933
|
+
label: "Fehlgeschlagen",
|
|
1934
|
+
value: detail.failed_at
|
|
1935
|
+
}
|
|
1936
|
+
),
|
|
1937
|
+
/* @__PURE__ */ jsx(
|
|
1938
|
+
DetailTimestamp,
|
|
1939
|
+
{
|
|
1940
|
+
label: "Nächster Versuch",
|
|
1941
|
+
value: detail.next_attempt_at
|
|
1942
|
+
}
|
|
1943
|
+
),
|
|
1944
|
+
detail.terminal_code ? /* @__PURE__ */ jsxs("div", { children: [
|
|
1945
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle text-xs", children: "Statuscode" }),
|
|
1946
|
+
/* @__PURE__ */ jsx(Text, { className: "font-mono text-xs", children: detail.terminal_code })
|
|
1947
|
+
] }) : null
|
|
1948
|
+
] }),
|
|
1949
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-5", children: [
|
|
1950
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Zustellversuche" }),
|
|
1951
|
+
attempts.length ? /* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-3", children: attempts.map((attempt) => /* @__PURE__ */ jsxs(
|
|
1952
|
+
"div",
|
|
1953
|
+
{
|
|
1954
|
+
className: "border-ui-border-base grid gap-3 rounded-md border p-4 sm:grid-cols-[auto_1fr_auto] sm:items-center",
|
|
1955
|
+
children: [
|
|
1956
|
+
/* @__PURE__ */ jsxs(Text, { className: "font-medium", children: [
|
|
1957
|
+
"Versuch ",
|
|
1958
|
+
attempt.attempt_number
|
|
1959
|
+
] }),
|
|
1960
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1961
|
+
/* @__PURE__ */ jsx(Text, { className: "text-sm", children: ATTEMPT_STATUS_LABELS[attempt.status] }),
|
|
1962
|
+
attempt.failure_code ? /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle font-mono text-xs", children: attempt.failure_code }) : null
|
|
1963
|
+
] }),
|
|
1964
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-xs sm:text-right", children: [
|
|
1965
|
+
formatDateTime$1(attempt.started_at),
|
|
1966
|
+
attempt.finished_at ? ` · ${formatDateTime$1(attempt.finished_at)}` : ""
|
|
1967
|
+
] })
|
|
1968
|
+
]
|
|
1969
|
+
},
|
|
1970
|
+
attempt.id
|
|
1971
|
+
)) }) : /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle mt-3", children: "Es gibt noch keinen Zustellversuch." })
|
|
1972
|
+
] })
|
|
1973
|
+
] }) : null
|
|
1974
|
+
] }) : null,
|
|
1975
|
+
/* @__PURE__ */ jsx(Prompt, { open: confirmationOpen, onOpenChange: setConfirmationOpen, children: /* @__PURE__ */ jsxs(Prompt.Content, { children: [
|
|
1976
|
+
/* @__PURE__ */ jsxs(Prompt.Header, { children: [
|
|
1977
|
+
/* @__PURE__ */ jsx(Prompt.Title, { children: "E-Mail erneut senden?" }),
|
|
1978
|
+
/* @__PURE__ */ jsx(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." })
|
|
1979
|
+
] }),
|
|
1980
|
+
/* @__PURE__ */ jsxs(Prompt.Footer, { children: [
|
|
1981
|
+
/* @__PURE__ */ jsx(Prompt.Cancel, { children: "Abbrechen" }),
|
|
1982
|
+
/* @__PURE__ */ jsx(Prompt.Action, { onClick: confirmResend, children: "Erneut senden" })
|
|
1983
|
+
] })
|
|
1984
|
+
] }) })
|
|
1985
|
+
] });
|
|
1986
|
+
};
|
|
1987
|
+
const validateTermsPdf = (file) => {
|
|
1988
|
+
if (!file.name.toLowerCase().endsWith(".pdf") || file.type !== "" && file.type !== "application/pdf") {
|
|
1989
|
+
return "Bitte eine PDF-Datei auswählen.";
|
|
1990
|
+
}
|
|
1991
|
+
if (file.size === 0) {
|
|
1992
|
+
return "Die PDF-Datei darf nicht leer sein.";
|
|
1993
|
+
}
|
|
1994
|
+
if (file.size > EMAIL_TERMS_MAX_PDF_BYTES) {
|
|
1995
|
+
return "Die PDF-Datei darf maximal 5 MiB groß sein.";
|
|
1996
|
+
}
|
|
1997
|
+
return null;
|
|
1998
|
+
};
|
|
1999
|
+
const encodeCanonicalBase64 = (buffer) => {
|
|
2000
|
+
const bytes = new Uint8Array(buffer);
|
|
2001
|
+
const chunkSize = 32768;
|
|
2002
|
+
let binary = "";
|
|
2003
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) {
|
|
2004
|
+
binary += String.fromCharCode(
|
|
2005
|
+
...bytes.subarray(offset, offset + chunkSize)
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
return btoa(binary);
|
|
2009
|
+
};
|
|
2010
|
+
const readPdfAsCanonicalBase64 = async (file) => encodeCanonicalBase64(await file.arrayBuffer());
|
|
2011
|
+
const PAGE_SIZE = 20;
|
|
2012
|
+
const MAX_PAGE_OFFSET = 1e4;
|
|
2013
|
+
const formatDateTime = (value) => {
|
|
2014
|
+
const date = new Date(value);
|
|
2015
|
+
if (Number.isNaN(date.getTime())) {
|
|
2016
|
+
return "-";
|
|
2017
|
+
}
|
|
2018
|
+
return new Intl.DateTimeFormat("de-AT", {
|
|
2019
|
+
dateStyle: "medium",
|
|
2020
|
+
timeStyle: "short"
|
|
2021
|
+
}).format(date);
|
|
2022
|
+
};
|
|
2023
|
+
const formatFileSize = (bytes) => {
|
|
2024
|
+
if (bytes < 1024) {
|
|
2025
|
+
return `${bytes} B`;
|
|
2026
|
+
}
|
|
2027
|
+
const divisor = bytes < 1024 * 1024 ? 1024 : 1024 * 1024;
|
|
2028
|
+
const unit = bytes < 1024 * 1024 ? "KiB" : "MiB";
|
|
2029
|
+
return `${new Intl.NumberFormat("de-AT", {
|
|
2030
|
+
maximumFractionDigits: 1
|
|
2031
|
+
}).format(bytes / divisor)} ${unit}`;
|
|
2032
|
+
};
|
|
2033
|
+
const newestFirst = (versions) => [...versions].sort(
|
|
2034
|
+
(left, right) => right.effective_at.localeCompare(left.effective_at) || right.created_at.localeCompare(left.created_at)
|
|
2035
|
+
);
|
|
2036
|
+
const EmailTermsVersionsSection = () => {
|
|
2037
|
+
const queryClient = useQueryClient();
|
|
2038
|
+
const fileInputRef = useRef(null);
|
|
2039
|
+
const [effectiveAt, setEffectiveAt] = useState("");
|
|
2040
|
+
const [file, setFile] = useState(null);
|
|
2041
|
+
const [formError, setFormError] = useState(null);
|
|
2042
|
+
const [offset, setOffset] = useState(0);
|
|
2043
|
+
const termsQueryKey = [
|
|
2044
|
+
"email",
|
|
2045
|
+
"terms-versions",
|
|
2046
|
+
{ limit: PAGE_SIZE, offset }
|
|
2047
|
+
];
|
|
2048
|
+
const termsQuery = useQuery({
|
|
2049
|
+
queryKey: termsQueryKey,
|
|
2050
|
+
queryFn: () => listEmailTermsVersions({ limit: PAGE_SIZE, offset })
|
|
2051
|
+
});
|
|
2052
|
+
const createTermsVersion = useMutation({
|
|
2053
|
+
mutationFn: async ({
|
|
2054
|
+
effectiveAt: effectiveAtValue,
|
|
2055
|
+
file: selectedFile,
|
|
2056
|
+
requestId
|
|
2057
|
+
}) => {
|
|
2058
|
+
const body = {
|
|
2059
|
+
request_id: requestId,
|
|
2060
|
+
filename: selectedFile.name,
|
|
2061
|
+
effective_at: effectiveAtValue,
|
|
2062
|
+
content_base64: await readPdfAsCanonicalBase64(selectedFile)
|
|
2063
|
+
};
|
|
2064
|
+
return createEmailTermsVersion(body);
|
|
2065
|
+
},
|
|
2066
|
+
onSuccess: async ({ created }) => {
|
|
2067
|
+
setFile(null);
|
|
2068
|
+
setEffectiveAt("");
|
|
2069
|
+
setFormError(null);
|
|
2070
|
+
setOffset(0);
|
|
2071
|
+
if (fileInputRef.current) {
|
|
2072
|
+
fileInputRef.current.value = "";
|
|
2073
|
+
}
|
|
2074
|
+
toast.success(
|
|
2075
|
+
created ? "Die AGB-Version wurde hochgeladen." : "Diese AGB-Version war bereits vorhanden."
|
|
2076
|
+
);
|
|
2077
|
+
await queryClient.invalidateQueries({
|
|
2078
|
+
queryKey: ["email", "terms-versions"]
|
|
2079
|
+
});
|
|
2080
|
+
},
|
|
2081
|
+
onError: () => {
|
|
2082
|
+
toast.error("Die AGB-Version konnte nicht hochgeladen werden.");
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
const downloadTermsVersion = useMutation({
|
|
2086
|
+
mutationFn: async (version) => ({
|
|
2087
|
+
blob: await downloadEmailTermsVersion(version.id),
|
|
2088
|
+
version
|
|
2089
|
+
}),
|
|
2090
|
+
onSuccess: ({ blob, version }) => {
|
|
2091
|
+
const url = URL.createObjectURL(blob);
|
|
2092
|
+
const link = document.createElement("a");
|
|
2093
|
+
link.href = url;
|
|
2094
|
+
link.download = version.filename;
|
|
2095
|
+
document.body.appendChild(link);
|
|
2096
|
+
link.click();
|
|
2097
|
+
link.remove();
|
|
2098
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
2099
|
+
},
|
|
2100
|
+
onError: () => {
|
|
2101
|
+
toast.error("Die AGB-PDF konnte nicht heruntergeladen werden.");
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
const versions = newestFirst(termsQuery.data?.terms_versions ?? []);
|
|
2105
|
+
const count = termsQuery.data?.count ?? 0;
|
|
2106
|
+
const submit = (event) => {
|
|
2107
|
+
event.preventDefault();
|
|
2108
|
+
if (!file) {
|
|
2109
|
+
setFormError("Bitte eine PDF-Datei auswählen.");
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
const fileError = validateTermsPdf(file);
|
|
2113
|
+
if (fileError) {
|
|
2114
|
+
setFormError(fileError);
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
const date = new Date(effectiveAt);
|
|
2118
|
+
if (!effectiveAt || Number.isNaN(date.getTime())) {
|
|
2119
|
+
setFormError("Bitte einen gültigen Wirksamkeitszeitpunkt angeben.");
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
setFormError(null);
|
|
2123
|
+
createTermsVersion.mutate({
|
|
2124
|
+
effectiveAt: date.toISOString(),
|
|
2125
|
+
file,
|
|
2126
|
+
requestId: crypto.randomUUID()
|
|
2127
|
+
});
|
|
2128
|
+
};
|
|
2129
|
+
return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
|
|
2130
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
2131
|
+
/* @__PURE__ */ jsx(Heading, { level: "h2", children: "AGB/PDF-Versionen" }),
|
|
2132
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Globale, unveränderliche AGB-Versionen für den E-Mail-Versand verwalten." })
|
|
2133
|
+
] }),
|
|
2134
|
+
/* @__PURE__ */ jsxs("form", { className: "grid gap-4 px-6 py-5", onSubmit: submit, children: [
|
|
2135
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 md:grid-cols-[minmax(0,1fr)_minmax(15rem,0.6fr)_auto] md:items-end", children: [
|
|
2136
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2137
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "email-terms-pdf", children: "AGB als PDF" }),
|
|
2138
|
+
/* @__PURE__ */ jsx(
|
|
2139
|
+
Input,
|
|
2140
|
+
{
|
|
2141
|
+
accept: "application/pdf,.pdf",
|
|
2142
|
+
"aria-describedby": `email-terms-pdf-help${formError ? " email-terms-form-error" : ""}`,
|
|
2143
|
+
disabled: createTermsVersion.isPending,
|
|
2144
|
+
id: "email-terms-pdf",
|
|
2145
|
+
onChange: (event) => {
|
|
2146
|
+
const selectedFile = event.target.files?.[0] ?? null;
|
|
2147
|
+
setFile(selectedFile);
|
|
2148
|
+
setFormError(
|
|
2149
|
+
selectedFile ? validateTermsPdf(selectedFile) : null
|
|
2150
|
+
);
|
|
2151
|
+
},
|
|
2152
|
+
ref: fileInputRef,
|
|
2153
|
+
type: "file"
|
|
2154
|
+
}
|
|
2155
|
+
)
|
|
2156
|
+
] }),
|
|
2157
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2", children: [
|
|
2158
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "email-terms-effective-at", children: "Wirksam ab" }),
|
|
2159
|
+
/* @__PURE__ */ jsx(
|
|
2160
|
+
Input,
|
|
2161
|
+
{
|
|
2162
|
+
"aria-describedby": `email-terms-effective-at-help${formError ? " email-terms-form-error" : ""}`,
|
|
2163
|
+
disabled: createTermsVersion.isPending,
|
|
2164
|
+
id: "email-terms-effective-at",
|
|
2165
|
+
onChange: (event) => setEffectiveAt(event.target.value),
|
|
2166
|
+
type: "datetime-local",
|
|
2167
|
+
value: effectiveAt
|
|
2168
|
+
}
|
|
2169
|
+
)
|
|
2170
|
+
] }),
|
|
2171
|
+
/* @__PURE__ */ jsx(
|
|
2172
|
+
Button,
|
|
2173
|
+
{
|
|
2174
|
+
disabled: createTermsVersion.isPending,
|
|
2175
|
+
isLoading: createTermsVersion.isPending,
|
|
2176
|
+
type: "submit",
|
|
2177
|
+
children: "Version hochladen"
|
|
2178
|
+
}
|
|
2179
|
+
)
|
|
2180
|
+
] }),
|
|
2181
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-1", children: [
|
|
2182
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-xs", id: "email-terms-pdf-help", children: [
|
|
2183
|
+
"Nur PDF, maximal ",
|
|
2184
|
+
formatFileSize(EMAIL_TERMS_MAX_PDF_BYTES),
|
|
2185
|
+
"."
|
|
2186
|
+
] }),
|
|
2187
|
+
/* @__PURE__ */ jsx(
|
|
2188
|
+
Text,
|
|
2189
|
+
{
|
|
2190
|
+
className: "text-ui-fg-subtle text-xs",
|
|
2191
|
+
id: "email-terms-effective-at-help",
|
|
2192
|
+
children: "Der Zeitpunkt wird aus der lokalen Zeitzone in UTC übernommen."
|
|
2193
|
+
}
|
|
2194
|
+
),
|
|
2195
|
+
formError ? /* @__PURE__ */ jsx(
|
|
2196
|
+
Text,
|
|
2197
|
+
{
|
|
2198
|
+
className: "text-ui-fg-error text-sm",
|
|
2199
|
+
id: "email-terms-form-error",
|
|
2200
|
+
role: "alert",
|
|
2201
|
+
children: formError
|
|
2202
|
+
}
|
|
2203
|
+
) : null
|
|
2204
|
+
] })
|
|
2205
|
+
] }),
|
|
2206
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2207
|
+
/* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
|
|
2208
|
+
/* @__PURE__ */ jsx(Heading, { level: "h3", children: "Hinterlegte Versionen" }),
|
|
2209
|
+
/* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Neueste Wirksamkeit zuerst. Downloads erfolgen authentifiziert." })
|
|
2210
|
+
] }),
|
|
2211
|
+
termsQuery.isPending ? /* @__PURE__ */ jsx("div", { className: "px-6 pb-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", role: "status", children: "AGB-Versionen werden geladen." }) }) : termsQuery.isError ? /* @__PURE__ */ jsx("div", { className: "px-6 pb-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-error", role: "alert", children: "AGB-Versionen konnten nicht geladen werden." }) }) : versions.length ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2212
|
+
/* @__PURE__ */ jsx("div", { className: "hidden overflow-x-auto md:block", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
|
|
2213
|
+
/* @__PURE__ */ jsx("thead", { className: "border-ui-border-base border-y", children: /* @__PURE__ */ jsxs("tr", { className: "text-ui-fg-subtle", children: [
|
|
2214
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Dateiname" }),
|
|
2215
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Wirksam ab" }),
|
|
2216
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Größe" }),
|
|
2217
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 font-medium", children: "Erstellt" }),
|
|
2218
|
+
/* @__PURE__ */ jsx("th", { className: "px-6 py-3 text-right font-medium", children: "Aktion" })
|
|
2219
|
+
] }) }),
|
|
2220
|
+
/* @__PURE__ */ jsx("tbody", { children: versions.map((version) => {
|
|
2221
|
+
const isDownloading = downloadTermsVersion.isPending && downloadTermsVersion.variables?.id === version.id;
|
|
2222
|
+
return /* @__PURE__ */ jsxs(
|
|
2223
|
+
"tr",
|
|
2224
|
+
{
|
|
2225
|
+
className: "border-ui-border-base border-b",
|
|
2226
|
+
children: [
|
|
2227
|
+
/* @__PURE__ */ jsx("td", { className: "max-w-80 break-all px-6 py-3 font-medium", children: version.filename }),
|
|
2228
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-6 py-3", children: formatDateTime(version.effective_at) }),
|
|
2229
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-6 py-3 tabular-nums", children: formatFileSize(version.byte_size) }),
|
|
2230
|
+
/* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-6 py-3", children: formatDateTime(version.created_at) }),
|
|
2231
|
+
/* @__PURE__ */ jsx("td", { className: "px-6 py-3 text-right", children: /* @__PURE__ */ jsx(
|
|
2232
|
+
Button,
|
|
2233
|
+
{
|
|
2234
|
+
"aria-label": `${version.filename} herunterladen`,
|
|
2235
|
+
disabled: downloadTermsVersion.isPending,
|
|
2236
|
+
isLoading: isDownloading,
|
|
2237
|
+
onClick: () => downloadTermsVersion.mutate(version),
|
|
2238
|
+
size: "small",
|
|
2239
|
+
variant: "secondary",
|
|
2240
|
+
children: "Herunterladen"
|
|
2241
|
+
}
|
|
2242
|
+
) })
|
|
2243
|
+
]
|
|
2244
|
+
},
|
|
2245
|
+
version.id
|
|
2246
|
+
);
|
|
2247
|
+
}) })
|
|
2248
|
+
] }) }),
|
|
2249
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-3 px-4 py-4 md:hidden", children: versions.map((version) => {
|
|
2250
|
+
const isDownloading = downloadTermsVersion.isPending && downloadTermsVersion.variables?.id === version.id;
|
|
2251
|
+
return /* @__PURE__ */ jsxs(
|
|
2252
|
+
"div",
|
|
2253
|
+
{
|
|
2254
|
+
className: "border-ui-border-base grid gap-3 rounded-md border p-4",
|
|
2255
|
+
children: [
|
|
2256
|
+
/* @__PURE__ */ jsx(Text, { className: "break-all font-medium", children: version.filename }),
|
|
2257
|
+
/* @__PURE__ */ jsxs("dl", { className: "grid grid-cols-2 gap-3 text-sm", children: [
|
|
2258
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2259
|
+
/* @__PURE__ */ jsx("dt", { className: "text-ui-fg-subtle text-xs", children: "Wirksam ab" }),
|
|
2260
|
+
/* @__PURE__ */ jsx("dd", { children: formatDateTime(version.effective_at) })
|
|
2261
|
+
] }),
|
|
2262
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2263
|
+
/* @__PURE__ */ jsx("dt", { className: "text-ui-fg-subtle text-xs", children: "Größe" }),
|
|
2264
|
+
/* @__PURE__ */ jsx("dd", { children: formatFileSize(version.byte_size) })
|
|
2265
|
+
] }),
|
|
2266
|
+
/* @__PURE__ */ jsxs("div", { className: "col-span-2", children: [
|
|
2267
|
+
/* @__PURE__ */ jsx("dt", { className: "text-ui-fg-subtle text-xs", children: "Erstellt" }),
|
|
2268
|
+
/* @__PURE__ */ jsx("dd", { children: formatDateTime(version.created_at) })
|
|
2269
|
+
] })
|
|
2270
|
+
] }),
|
|
2271
|
+
/* @__PURE__ */ jsx(
|
|
2272
|
+
Button,
|
|
2273
|
+
{
|
|
2274
|
+
"aria-label": `${version.filename} herunterladen`,
|
|
2275
|
+
disabled: downloadTermsVersion.isPending,
|
|
2276
|
+
isLoading: isDownloading,
|
|
2277
|
+
onClick: () => downloadTermsVersion.mutate(version),
|
|
2278
|
+
size: "small",
|
|
2279
|
+
variant: "secondary",
|
|
2280
|
+
children: "Herunterladen"
|
|
2281
|
+
}
|
|
2282
|
+
)
|
|
2283
|
+
]
|
|
2284
|
+
},
|
|
2285
|
+
version.id
|
|
2286
|
+
);
|
|
2287
|
+
}) })
|
|
2288
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "px-6 pb-6", children: /* @__PURE__ */ jsx(Text, { className: "text-ui-fg-subtle", children: "Es wurde noch keine AGB-Version hinterlegt." }) }),
|
|
2289
|
+
!termsQuery.isPending && !termsQuery.isError && count > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-center sm:justify-between", children: [
|
|
2290
|
+
/* @__PURE__ */ jsxs(Text, { className: "text-ui-fg-subtle text-sm", children: [
|
|
2291
|
+
offset + 1,
|
|
2292
|
+
"-",
|
|
2293
|
+
offset + versions.length,
|
|
2294
|
+
" von ",
|
|
2295
|
+
count
|
|
2296
|
+
] }),
|
|
2297
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2298
|
+
/* @__PURE__ */ jsx(
|
|
2299
|
+
Button,
|
|
2300
|
+
{
|
|
2301
|
+
disabled: offset === 0,
|
|
2302
|
+
onClick: () => setOffset(Math.max(0, offset - PAGE_SIZE)),
|
|
2303
|
+
size: "small",
|
|
2304
|
+
variant: "secondary",
|
|
2305
|
+
children: "Neuere"
|
|
2306
|
+
}
|
|
2307
|
+
),
|
|
2308
|
+
/* @__PURE__ */ jsx(
|
|
2309
|
+
Button,
|
|
2310
|
+
{
|
|
2311
|
+
disabled: offset >= MAX_PAGE_OFFSET || offset + versions.length >= count,
|
|
2312
|
+
onClick: () => setOffset(Math.min(MAX_PAGE_OFFSET, offset + PAGE_SIZE)),
|
|
2313
|
+
size: "small",
|
|
2314
|
+
variant: "secondary",
|
|
2315
|
+
children: "Ältere"
|
|
2316
|
+
}
|
|
2317
|
+
)
|
|
2318
|
+
] })
|
|
2319
|
+
] }) : null
|
|
2320
|
+
] })
|
|
2321
|
+
] });
|
|
2322
|
+
};
|
|
2323
|
+
const EmailPluginContent = () => {
|
|
2324
|
+
const { enabled, isPending, isError } = usePluginFeatureEnabled("email");
|
|
2325
|
+
if (isPending || isError) {
|
|
2326
|
+
return null;
|
|
2327
|
+
}
|
|
2328
|
+
return enabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2329
|
+
/* @__PURE__ */ jsx(EmailTermsVersionsSection, {}),
|
|
2330
|
+
/* @__PURE__ */ jsx(EmailStatusPage, {})
|
|
2331
|
+
] }) : /* @__PURE__ */ jsx(PluginFeatureDisabledNotice, { pluginKey: "email" });
|
|
2332
|
+
};
|
|
2333
|
+
const EmailPluginRoute = () => /* @__PURE__ */ jsxs(PluginPageShell, { pluginKey: "email", children: [
|
|
2334
|
+
/* @__PURE__ */ jsx(PluginFeatureSections, { pluginKey: "email" }),
|
|
2335
|
+
/* @__PURE__ */ jsx(EmailPluginContent, {})
|
|
2336
|
+
] });
|
|
2337
|
+
const PluginDetailPage = () => {
|
|
2338
|
+
const { plugin_key: pluginKey = "" } = useParams();
|
|
2339
|
+
return /* @__PURE__ */ jsx(PluginPageShell, { pluginKey, children: /* @__PURE__ */ jsx(PluginFeatureSections, { pluginKey }) });
|
|
2340
|
+
};
|
|
2341
|
+
const widgetModule = { widgets: [
|
|
2342
|
+
{
|
|
2343
|
+
Component: OrderDocumentsWidget,
|
|
2344
|
+
zone: ["order.details.side.after"],
|
|
2345
|
+
widgetId: "Widget-93c9"
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
Component: OrderPackingListsWidget,
|
|
2349
|
+
zone: ["order.details.side.after"],
|
|
2350
|
+
widgetId: "Widget-381c"
|
|
2351
|
+
}
|
|
2352
|
+
] };
|
|
226
2353
|
const routeModule = {
|
|
227
2354
|
routes: [
|
|
228
2355
|
{
|
|
229
2356
|
Component: PluginFeaturesSettingsPage,
|
|
230
2357
|
path: "/settings/hartl-services",
|
|
231
2358
|
handle: { label: config.label, translationNs: config.translationNs }
|
|
2359
|
+
},
|
|
2360
|
+
{
|
|
2361
|
+
Component: EmailPluginRoute,
|
|
2362
|
+
path: "/settings/hartl-services/email"
|
|
2363
|
+
},
|
|
2364
|
+
{
|
|
2365
|
+
Component: PluginDetailPage,
|
|
2366
|
+
path: "/settings/hartl-services/plugin/:plugin_key"
|
|
232
2367
|
}
|
|
233
2368
|
]
|
|
234
2369
|
};
|