@hartl-services/medusa-base 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.medusa/server/src/admin/admin-components/features/plugin-features/components/PluginSettingsForm.js +12 -9
  2. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginFeatureEnabled.d.ts +5 -1
  3. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginFeatures.d.ts +20 -4
  4. package/.medusa/server/src/admin/admin-components/features/plugin-features/hooks/usePluginSettingsForm.d.ts +5 -1
  5. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-features-admin-client.d.ts +20 -4
  6. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.d.ts +0 -5
  7. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/plugin-view.js +0 -5
  8. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-layout.d.ts +4 -0
  9. package/.medusa/server/src/admin/admin-components/features/plugin-features/lib/settings-layout.js +4 -0
  10. package/.medusa/server/src/admin/admin-components/i18n/texts.d.ts +1 -2
  11. package/.medusa/server/src/admin/admin-components/i18n/texts.js +1 -2
  12. package/.medusa/server/src/admin/index.js +142 -87
  13. package/.medusa/server/src/admin/index.mjs +144 -89
  14. package/.medusa/server/src/api/admin/email/templates/[kind]/send-test/route.js +7 -1
  15. package/.medusa/server/src/customer-auth/plugin-features.js +10 -10
  16. package/.medusa/server/src/email/application/warn-credential-expiry.js +77 -0
  17. package/.medusa/server/src/email/config/index.js +8 -2
  18. package/.medusa/server/src/email/config/notification-module.js +28 -2
  19. package/.medusa/server/src/email/domain/business-keys.js +5 -2
  20. package/.medusa/server/src/email/domain/failure-classification.js +9 -1
  21. package/.medusa/server/src/email/plugin-features.js +123 -1
  22. package/.medusa/server/src/email/rendering/default-templates.js +16 -1
  23. package/.medusa/server/src/email/rendering/render-email.js +2 -1
  24. package/.medusa/server/src/email/types/email-kinds.js +2 -1
  25. package/.medusa/server/src/email/types/template-data.js +8 -1
  26. package/.medusa/server/src/jobs/email-credential-expiry.js +27 -0
  27. package/.medusa/server/src/modules/email/domain/message-state.js +3 -1
  28. package/.medusa/server/src/modules/email/migrations/Migration20261011120000.js +20 -0
  29. package/.medusa/server/src/modules/plugin-features/domain/plugin-settings.js +27 -6
  30. package/.medusa/server/src/modules/plugin-features/service.js +4 -3
  31. package/.medusa/server/src/providers/smtp/delivery-settings.js +75 -0
  32. package/.medusa/server/src/providers/smtp/graph-transport.js +150 -0
  33. package/.medusa/server/src/providers/smtp/service.js +105 -37
  34. package/.medusa/server/src/providers/smtp/types.js +7 -1
  35. package/.medusa/server/src/public/descriptor.js +24 -3
  36. package/.medusa/server/src/shared/lib/dates.js +8 -2
  37. package/.medusa/server/src/shop-config/build-food-shop-config.js +3 -5
  38. package/README.md +100 -65
  39. package/dist/customer-auth/plugin-features.js +9 -9
  40. package/dist/email/config/index.d.ts +6 -1
  41. package/dist/email/config/index.js +7 -1
  42. package/dist/email/config/notification-module.d.ts +25 -0
  43. package/dist/email/config/notification-module.js +27 -1
  44. package/dist/email/types/email-kinds.d.ts +2 -1
  45. package/dist/email/types/email-kinds.js +1 -0
  46. package/dist/email/types/template-data.d.ts +10 -0
  47. package/dist/email/types/template-data.js +7 -0
  48. package/dist/public/descriptor.js +23 -2
  49. package/dist/public/types.d.ts +14 -4
  50. package/dist/shared/lib/dates.d.ts +2 -0
  51. package/dist/shared/lib/dates.js +7 -1
  52. package/dist/shop-config/build-food-shop-config.d.ts +1 -1
  53. package/dist/shop-config/build-food-shop-config.js +2 -4
  54. package/package.json +2 -2
@@ -28,7 +28,8 @@ const texts = {
28
28
  },
29
29
  breadcrumbs: {
30
30
  hub: "Plugins",
31
- emailAttachments: "Anhänge"
31
+ emailAttachments: "Anhänge",
32
+ emailDelivery: "Versand"
32
33
  },
33
34
  page: {
34
35
  title: "Hartl Services Plugins",
@@ -94,8 +95,6 @@ const texts = {
94
95
  secretClearUndoLabel: (label) => `${label} nicht löschen`,
95
96
  /** Shown on a secret: the settings whose change deletes it ("A", "A oder B"). */
96
97
  invalidatedBy: (labels) => `Wird gelöscht, wenn ${labels.length > 1 ? `${labels.slice(0, -1).join(", ")} oder ${labels[labels.length - 1]}` : labels.join("")} sich ändert.`,
97
- /** Shown on the changing setting: the secrets its change deletes. */
98
- invalidates: (labels) => `Beim Ändern muss ${labels} neu eingegeben werden.`,
99
98
  invalidNumber: "Bitte eine gültige Zahl eingeben.",
100
99
  invalidColor: "Bitte eine Hex-Farbe wie #1a2b3c eingeben.",
101
100
  colorPlaceholder: "Standard",
@@ -396,9 +395,6 @@ const disabledParent = (features, feature) => {
396
395
  };
397
396
  const invalidatedByLabels = (settings, secretKey) => settings.filter((setting) => setting.invalidates.includes(secretKey)).map((setting) => setting.label);
398
397
  const isGroupDimmed = (features, requiresFeature) => requiresFeature !== null && features.find((feature) => feature.key === requiresFeature)?.effective_enabled === false;
399
- const invalidatesLabels = (settings, setting) => setting.invalidates.map(
400
- (key) => settings.find((candidate) => candidate.key === key)?.label ?? key
401
- );
402
398
  const settingInfoText = (setting) => setting.hint !== null && setting.description !== null && setting.description !== setting.hint ? setting.description : null;
403
399
  const headerInfoText = (plugin2) => plugin2.summary !== null && plugin2.description !== null && plugin2.description !== plugin2.summary ? plugin2.description : null;
404
400
  const showsSourceHint = (source) => source !== "default";
@@ -963,6 +959,35 @@ const changedSettingValues = (settings, draft, unparsable = []) => {
963
959
  }
964
960
  return values;
965
961
  };
962
+ const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
963
+ const DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
964
+ const pad = (value, length = 2) => String(value).padStart(length, "0");
965
+ const build = ([year, month, day, hour = 0, minute = 0]) => {
966
+ const date = new Date(year, month - 1, day, hour, minute);
967
+ const matches = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day && date.getHours() === hour && date.getMinutes() === minute;
968
+ return matches ? date : null;
969
+ };
970
+ const dateFromInputValue = (value) => {
971
+ const match = value ? DATE_PATTERN.exec(value) : null;
972
+ return match ? build(match.slice(1).map(Number)) : null;
973
+ };
974
+ const inputValueFromDate = (date) => {
975
+ if (!date || Number.isNaN(date.getTime())) {
976
+ return "";
977
+ }
978
+ return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
979
+ };
980
+ const dateTimeFromInputValue = (value) => {
981
+ const match = value ? DATE_TIME_PATTERN.exec(value) : null;
982
+ return match ? build(match.slice(1).map(Number)) : null;
983
+ };
984
+ const inputValueFromDateTime = (date) => {
985
+ if (!date || Number.isNaN(date.getTime())) {
986
+ return "";
987
+ }
988
+ return `${inputValueFromDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
989
+ };
990
+ const AdminDatePicker = (props) => /* @__PURE__ */ jsxRuntime.jsx(ui.I18nProvider, { locale: ADMIN_LOCALE, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { ...props }) });
966
991
  const buildSettingBlocks = (settings) => {
967
992
  const blocks = [];
968
993
  let previousSection = null;
@@ -1014,11 +1039,13 @@ const buildSettingGroups = (settings, groups, ungroupedTitle) => {
1014
1039
  description: meta?.description ?? null,
1015
1040
  note: meta?.note ?? null,
1016
1041
  requiresFeature: meta?.requires_feature ?? null,
1042
+ visibleWhen: meta?.visible_when ?? null,
1017
1043
  settings: groupSettings,
1018
1044
  blocks: buildSettingBlocks(groupSettings)
1019
1045
  };
1020
1046
  });
1021
1047
  };
1048
+ const isGroupVisible = (group, draft) => group.visibleWhen === null || draft[group.visibleWhen.setting] === group.visibleWhen.equals;
1022
1049
  function usePluginSettingsForm(plugin2) {
1023
1050
  const mutation = useUpdatePluginSettings(plugin2.plugin_key);
1024
1051
  const [draft, setDraft] = react.useState(
@@ -1312,13 +1339,13 @@ const SettingField = ({
1312
1339
  pluginKey,
1313
1340
  setting,
1314
1341
  invalidatedBy,
1315
- invalidates,
1316
1342
  value,
1317
1343
  invalid,
1318
1344
  disabled,
1319
1345
  onChange
1320
1346
  }) => {
1321
1347
  const id = fieldId(pluginKey, setting.key);
1348
+ const labelId = `${id}-label`;
1322
1349
  const hintId = `${id}-hint`;
1323
1350
  const clearPending = setting.type === "secret" && value === null;
1324
1351
  const bounds = setting.type === "number" ? boundsText(setting) : void 0;
@@ -1326,13 +1353,13 @@ const SettingField = ({
1326
1353
  const info = settingInfoText(setting);
1327
1354
  const showBounds = Boolean(bounds) && showsBoundsLine(setting);
1328
1355
  const required = setting.required;
1329
- const hasHints = Boolean(hint) || clearPending || invalidatedBy.length > 0 || invalidates.length > 0 || showBounds || invalid;
1356
+ const hasHints = Boolean(hint) || clearPending || invalidatedBy.length > 0 || showBounds || invalid;
1330
1357
  const describedBy = hasHints ? hintId : void 0;
1331
1358
  const placeholder = setting.placeholder ?? void 0;
1332
1359
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
1333
1360
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1334
1361
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-1", children: [
1335
- /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }),
1362
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { id: labelId, htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }),
1336
1363
  info && /* @__PURE__ */ jsxRuntime.jsx(InfoTooltip, { label: texts.info.label(setting.label), children: info })
1337
1364
  ] }),
1338
1365
  showsSourceHint(setting.source) && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-muted shrink-0", children: texts.settings.source[setting.source] })
@@ -1406,6 +1433,16 @@ const SettingField = ({
1406
1433
  disabled,
1407
1434
  onChange: (next) => onChange(next)
1408
1435
  }
1436
+ ) : setting.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
1437
+ AdminDatePicker,
1438
+ {
1439
+ id,
1440
+ "aria-labelledby": labelId,
1441
+ "aria-describedby": describedBy,
1442
+ isDisabled: disabled,
1443
+ value: dateFromInputValue(typeof value === "string" ? value : ""),
1444
+ onChange: (date) => onChange(inputValueFromDate(date))
1445
+ }
1409
1446
  ) : setting.type === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
1410
1447
  ui.Input,
1411
1448
  {
@@ -1440,7 +1477,6 @@ const SettingField = ({
1440
1477
  hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: hint }),
1441
1478
  clearPending && /* @__PURE__ */ jsxRuntime.jsx(ui.Hint, { variant: "info", children: texts.settings.secretClearPending }),
1442
1479
  invalidatedBy.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.invalidatedBy(invalidatedBy) }),
1443
- invalidates.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.invalidates(invalidates.join(", ")) }),
1444
1480
  showBounds && !invalid && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: bounds }),
1445
1481
  invalid && /* @__PURE__ */ jsxRuntime.jsx(ui.Hint, { variant: "error", children: setting.type === "color" ? texts.settings.invalidColor : bounds ?? texts.settings.invalidNumber })
1446
1482
  ] })
@@ -1453,9 +1489,9 @@ const PluginSettingsForm = ({ plugin: plugin2 }) => {
1453
1489
  plugin2.settings,
1454
1490
  plugin2.setting_groups,
1455
1491
  texts.sections.settings
1456
- );
1492
+ ).filter((group) => isGroupVisible(group, form.draft));
1457
1493
  const hasTextFields = plugin2.settings.some(
1458
- (setting) => setting.type === "string" || setting.type === "url" || setting.type === "number" || setting.type === "color"
1494
+ (setting) => setting.type === "string" || setting.type === "url" || setting.type === "number" || setting.type === "color" || setting.type === "date"
1459
1495
  );
1460
1496
  const renderField = (setting) => /* @__PURE__ */ jsxRuntime.jsx(
1461
1497
  SettingField,
@@ -1463,7 +1499,6 @@ const PluginSettingsForm = ({ plugin: plugin2 }) => {
1463
1499
  pluginKey: plugin2.plugin_key,
1464
1500
  setting,
1465
1501
  invalidatedBy: setting.type === "secret" ? invalidatedByLabels(plugin2.settings, setting.key) : [],
1466
- invalidates: invalidatesLabels(plugin2.settings, setting),
1467
1502
  value: form.draft[setting.key],
1468
1503
  invalid: form.invalid.includes(setting.key),
1469
1504
  disabled: pending,
@@ -1641,29 +1676,6 @@ const PluginFeatureDisabledNotice = ({
1641
1676
  ]
1642
1677
  }
1643
1678
  );
1644
- const DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
1645
- const pad = (value, length = 2) => String(value).padStart(length, "0");
1646
- const build = ([year, month, day, hour = 0, minute = 0]) => {
1647
- const date = new Date(year, month - 1, day, hour, minute);
1648
- const matches = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day && date.getHours() === hour && date.getMinutes() === minute;
1649
- return matches ? date : null;
1650
- };
1651
- const inputValueFromDate = (date) => {
1652
- if (!date || Number.isNaN(date.getTime())) {
1653
- return "";
1654
- }
1655
- return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
1656
- };
1657
- const dateTimeFromInputValue = (value) => {
1658
- const match = value ? DATE_TIME_PATTERN.exec(value) : null;
1659
- return match ? build(match.slice(1).map(Number)) : null;
1660
- };
1661
- const inputValueFromDateTime = (date) => {
1662
- if (!date || Number.isNaN(date.getTime())) {
1663
- return "";
1664
- }
1665
- return `${inputValueFromDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
1666
- };
1667
1679
  const fetchPdfObjectUrl = async (path) => {
1668
1680
  const response = await sdk.client.fetch(path, {
1669
1681
  headers: { accept: "application/pdf" }
@@ -1751,7 +1763,6 @@ const useDebouncedValue = (value, delayMs) => {
1751
1763
  }, [value, delayMs]);
1752
1764
  return debounced;
1753
1765
  };
1754
- const AdminDatePicker = (props) => /* @__PURE__ */ jsxRuntime.jsx(ui.I18nProvider, { locale: ADMIN_LOCALE, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { ...props }) });
1755
1766
  const FocusModalShell = ({
1756
1767
  open,
1757
1768
  onOpenChange,
@@ -3510,7 +3521,7 @@ const PluginFeaturesSettingsPage = () => {
3510
3521
  const config = adminSdk.defineRouteConfig({
3511
3522
  label: texts.menu.label
3512
3523
  });
3513
- const handle$a = {
3524
+ const handle$b = {
3514
3525
  breadcrumb: () => texts.breadcrumbs.hub
3515
3526
  };
3516
3527
  const KINDS = [
@@ -3526,7 +3537,8 @@ const KINDS = [
3526
3537
  "auth.login_code",
3527
3538
  "affiliate.consignment_report_due",
3528
3539
  "affiliate.application_submitted.admin",
3529
- "user.invite"
3540
+ "user.invite",
3541
+ "system.credential_expiry.admin"
3530
3542
  ];
3531
3543
  const KIND_LABELS = {
3532
3544
  "order.received": "Bestellbestätigung (Kunde)",
@@ -3541,10 +3553,12 @@ const KIND_LABELS = {
3541
3553
  "auth.login_code": "Anmeldecode",
3542
3554
  "affiliate.consignment_report_due": "Affiliate-Meldung",
3543
3555
  "affiliate.application_submitted.admin": "Neue Partner-Bewerbung (Shop)",
3544
- "user.invite": "Admin-Einladung"
3556
+ "user.invite": "Admin-Einladung",
3557
+ "system.credential_expiry.admin": "Secret läuft ab (Shop)"
3545
3558
  };
3546
3559
  const EMAIL_PAGE_PATH = `${PLUGIN_HUB_PATH}/email`;
3547
3560
  const EMAIL_ATTACHMENTS_PAGE_PATH = `${EMAIL_PAGE_PATH}/attachments`;
3561
+ const EMAIL_DELIVERY_PAGE_PATH = `${EMAIL_PAGE_PATH}/delivery`;
3548
3562
  const emailAttachmentPagePath = (attachmentId) => `${EMAIL_ATTACHMENTS_PAGE_PATH}/${encodeURIComponent(attachmentId)}`;
3549
3563
  const NO_MAIL_ASSIGNED_LABEL = "keiner Mail zugeordnet";
3550
3564
  const MAIL_KIND_OPTIONS = email$1.EMAIL_ATTACHABLE_KINDS.map((kind) => ({
@@ -3802,52 +3816,61 @@ const LinkRow = ({
3802
3816
  description,
3803
3817
  to,
3804
3818
  disabled
3805
- }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4 px-6 py-3", children: [
3806
- /* @__PURE__ */ jsxRuntime.jsx(ui.IconBadge, { children: icon }),
3807
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-y-0.5", children: [
3808
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", weight: "plus", children: title }),
3809
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: description })
3810
- ] }),
3811
- disabled ? /* @__PURE__ */ jsxRuntime.jsx(
3812
- ui.Button,
3813
- {
3814
- "aria-label": texts.plugin.openLabel(title),
3815
- disabled: true,
3816
- size: "small",
3817
- variant: "secondary",
3818
- children: texts.plugin.open
3819
- }
3820
- ) : /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, size: "small", variant: "secondary", children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { "aria-label": texts.plugin.openLabel(title), to, children: texts.plugin.open }) })
3821
- ] });
3822
- const EmailManagementSection = ({ disabled }) => /* @__PURE__ */ jsxRuntime.jsxs(
3823
- AdminSection,
3819
+ }) => /* @__PURE__ */ jsxRuntime.jsxs(
3820
+ "div",
3824
3821
  {
3825
- className: `transition-opacity ${disabled ? "opacity-50" : ""}`,
3826
- title: "Verwaltung",
3822
+ className: `flex items-center gap-4 px-6 py-3 transition-opacity ${disabled ? "opacity-50" : ""}`,
3827
3823
  children: [
3828
- /* @__PURE__ */ jsxRuntime.jsx(
3829
- LinkRow,
3830
- {
3831
- description: "PDFs an Kunden-Mails, z. B. AGB.",
3832
- disabled,
3833
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.PaperClip, {}),
3834
- title: "Anhänge",
3835
- to: EMAIL_ATTACHMENTS_PAGE_PATH
3836
- }
3837
- ),
3838
- /* @__PURE__ */ jsxRuntime.jsx(
3839
- LinkRow,
3824
+ /* @__PURE__ */ jsxRuntime.jsx(ui.IconBadge, { children: icon }),
3825
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-y-0.5", children: [
3826
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", weight: "plus", children: title }),
3827
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: description })
3828
+ ] }),
3829
+ disabled ? /* @__PURE__ */ jsxRuntime.jsx(
3830
+ ui.Button,
3840
3831
  {
3841
- description: "Status und Zustellversuche.",
3842
- disabled,
3843
- icon: /* @__PURE__ */ jsxRuntime.jsx(icons.ClockSolid, {}),
3844
- title: "E-Mail-Protokoll",
3845
- to: EMAIL_LOG_PAGE_PATH
3832
+ "aria-label": texts.plugin.openLabel(title),
3833
+ disabled: true,
3834
+ size: "small",
3835
+ variant: "secondary",
3836
+ children: texts.plugin.open
3846
3837
  }
3847
- )
3838
+ ) : /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, size: "small", variant: "secondary", children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { "aria-label": texts.plugin.openLabel(title), to, children: texts.plugin.open }) })
3848
3839
  ]
3849
3840
  }
3850
3841
  );
3842
+ const EmailManagementSection = ({ disabled }) => /* @__PURE__ */ jsxRuntime.jsxs(AdminSection, { title: "Verwaltung", children: [
3843
+ /* @__PURE__ */ jsxRuntime.jsx(
3844
+ LinkRow,
3845
+ {
3846
+ description: "SMTP oder Microsoft 365.",
3847
+ disabled: false,
3848
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.PaperPlane, {}),
3849
+ title: texts.breadcrumbs.emailDelivery,
3850
+ to: EMAIL_DELIVERY_PAGE_PATH
3851
+ }
3852
+ ),
3853
+ /* @__PURE__ */ jsxRuntime.jsx(
3854
+ LinkRow,
3855
+ {
3856
+ description: "PDFs an Kunden-Mails, z. B. AGB.",
3857
+ disabled,
3858
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.PaperClip, {}),
3859
+ title: "Anhänge",
3860
+ to: EMAIL_ATTACHMENTS_PAGE_PATH
3861
+ }
3862
+ ),
3863
+ /* @__PURE__ */ jsxRuntime.jsx(
3864
+ LinkRow,
3865
+ {
3866
+ description: "Status und Zustellversuche.",
3867
+ disabled,
3868
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.ClockSolid, {}),
3869
+ title: "E-Mail-Protokoll",
3870
+ to: EMAIL_LOG_PAGE_PATH
3871
+ }
3872
+ )
3873
+ ] });
3851
3874
  const EmailOverviewSections = () => {
3852
3875
  const plugin2 = usePluginFeature("email").data?.plugin;
3853
3876
  if (!plugin2) {
@@ -3859,7 +3882,7 @@ const EmailOverviewSections = () => {
3859
3882
  ] });
3860
3883
  };
3861
3884
  const EmailOverviewPage = () => /* @__PURE__ */ jsxRuntime.jsx(PluginPageShell, { pluginKey: "email", variant: "header", children: /* @__PURE__ */ jsxRuntime.jsx(EmailOverviewSections, {}) });
3862
- const handle$9 = {
3885
+ const handle$a = {
3863
3886
  breadcrumb: (_match) => /* @__PURE__ */ jsxRuntime.jsx(PluginLabelCrumb, { pluginKey: "email" })
3864
3887
  };
3865
3888
  const EMAIL_TEMPLATES_PATH = `${EMAIL_PAGE_PATH}/templates`;
@@ -3979,11 +4002,11 @@ const EmailTemplateGroupPage = ({
3979
4002
  ) })
3980
4003
  ] });
3981
4004
  };
3982
- const handle$8 = {
4005
+ const handle$9 = {
3983
4006
  breadcrumb: () => EMAIL_TEMPLATE_GROUPS.account.label
3984
4007
  };
3985
4008
  const EmailAccountMailsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsxRuntime.jsx(EmailTemplateGroupPage, { group: "account" }) });
3986
- const handle$7 = {
4009
+ const handle$8 = {
3987
4010
  breadcrumb: () => EMAIL_TEMPLATE_GROUPS.affiliate.label
3988
4011
  };
3989
4012
  const EmailAffiliateMailsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsxRuntime.jsx(EmailTemplateGroupPage, { group: "affiliate" }) });
@@ -4384,10 +4407,37 @@ const EmailAttachmentsPage = () => {
4384
4407
  /* @__PURE__ */ jsxRuntime.jsx(NewAttachmentModal, { onOpenChange: setNewOpen, open: newOpen })
4385
4408
  ] });
4386
4409
  };
4387
- const handle$6 = {
4410
+ const handle$7 = {
4388
4411
  breadcrumb: () => texts.breadcrumbs.emailAttachments
4389
4412
  };
4390
4413
  const EmailAttachmentsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsxRuntime.jsx(EmailAttachmentsPage, {}) });
4414
+ const EmailDeliveryPage = () => {
4415
+ const query = usePluginFeature("email");
4416
+ const plugin2 = query.data?.plugin;
4417
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
4418
+ /* @__PURE__ */ jsxRuntime.jsx(
4419
+ AdminPageHeader,
4420
+ {
4421
+ badges: plugin2 && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: plugin2.configured ? "green" : "orange", children: plugin2.configured ? texts.chips.configured : texts.chips.notConfigured }),
4422
+ description: "SMTP oder Microsoft 365.",
4423
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.PaperPlane, {}),
4424
+ title: texts.breadcrumbs.emailDelivery
4425
+ }
4426
+ ),
4427
+ plugin2 ? /* @__PURE__ */ jsxRuntime.jsx(PluginSettingsForm, { plugin: plugin2 }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "p-0", children: /* @__PURE__ */ jsxRuntime.jsx(
4428
+ QueryState,
4429
+ {
4430
+ error: query.error,
4431
+ errorText: texts.states.error,
4432
+ isLoading: query.isPending,
4433
+ loadingText: texts.states.loading
4434
+ }
4435
+ ) })
4436
+ ] });
4437
+ };
4438
+ const handle$6 = {
4439
+ breadcrumb: () => texts.breadcrumbs.emailDelivery
4440
+ };
4391
4441
  const LOG_PAGE_SIZE = 20;
4392
4442
  const LOG_MAX_OFFSET = 1e4;
4393
4443
  const LOG_MAX_RESOURCE_ID_LENGTH = 255;
@@ -6687,26 +6737,31 @@ const routeModule = {
6687
6737
  {
6688
6738
  Component: PluginFeaturesSettingsPage,
6689
6739
  path: "/settings/hartl-services",
6690
- handle: { label: config.label, translationNs: config.translationNs, ...handle$a }
6740
+ handle: { label: config.label, translationNs: config.translationNs, ...handle$b }
6691
6741
  },
6692
6742
  {
6693
6743
  Component: EmailOverviewPage,
6694
6744
  path: "/settings/hartl-services/email",
6695
- handle: handle$9
6745
+ handle: handle$a
6696
6746
  },
6697
6747
  {
6698
6748
  Component: EmailAccountMailsRoute,
6699
6749
  path: "/settings/hartl-services/email/account",
6700
- handle: handle$8
6750
+ handle: handle$9
6701
6751
  },
6702
6752
  {
6703
6753
  Component: EmailAffiliateMailsRoute,
6704
6754
  path: "/settings/hartl-services/email/affiliate",
6705
- handle: handle$7
6755
+ handle: handle$8
6706
6756
  },
6707
6757
  {
6708
6758
  Component: EmailAttachmentsRoute,
6709
6759
  path: "/settings/hartl-services/email/attachments",
6760
+ handle: handle$7
6761
+ },
6762
+ {
6763
+ Component: EmailDeliveryPage,
6764
+ path: "/settings/hartl-services/email/delivery",
6710
6765
  handle: handle$6
6711
6766
  },
6712
6767
  {