@hartl-services/medusa-base 2.7.0 → 2.8.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.
@@ -6,7 +6,9 @@ import { InfoTooltip } from "../../../shared/ui/InfoTooltip";
6
6
  import { SwitchRow } from "../../../shared/ui/SwitchRow";
7
7
  import { invalidatedByLabels, invalidatesLabels, isGroupDimmed, settingInfoText, showsBoundsLine, showsSourceHint, } from "../lib/plugin-view";
8
8
  import { isHexColor, normalizeColorInput } from "../lib/settings-draft";
9
- import { buildSettingGroups } from "../lib/settings-layout";
9
+ import { dateFromInputValue, inputValueFromDate } from "../../../shared/lib/date-input";
10
+ import { AdminDatePicker } from "../../../shared/ui/AdminDatePicker";
11
+ import { buildSettingGroups, isGroupVisible, } from "../lib/settings-layout";
10
12
  import { usePluginSettingsForm } from "../hooks/usePluginSettingsForm";
11
13
  import { ImageSettingInput } from "./ImageSettingInput";
12
14
  const fieldId = (pluginKey, key) => `plugin-setting-${pluginKey}-${key}`;
@@ -36,6 +38,7 @@ const ColorSettingInput = ({ id, hintId, label, value, invalid, required, disabl
36
38
  /** A non-boolean setting: label (with source hint), control and hints. */
37
39
  const SettingField = ({ pluginKey, setting, invalidatedBy, invalidates, value, invalid, disabled, onChange, }) => {
38
40
  const id = fieldId(pluginKey, setting.key);
41
+ const labelId = `${id}-label`;
39
42
  const hintId = `${id}-hint`;
40
43
  const clearPending = setting.type === "secret" && value === null;
41
44
  const bounds = setting.type === "number" ? boundsText(setting) : undefined;
@@ -51,22 +54,23 @@ const SettingField = ({ pluginKey, setting, invalidatedBy, invalidates, value, i
51
54
  invalid;
52
55
  const describedBy = hasHints ? hintId : undefined;
53
56
  const placeholder = setting.placeholder ?? undefined;
54
- return (_jsxs("div", { className: "flex flex-col gap-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-x-1", children: [_jsx(Label, { htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }), info && (_jsx(InfoTooltip, { label: texts.info.label(setting.label), children: info }))] }), showsSourceHint(setting.source) && (_jsx(Text, { size: "xsmall", className: "text-ui-fg-muted shrink-0", children: texts.settings.source[setting.source] }))] }), setting.type === "secret" ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { id: id, "aria-describedby": describedBy, "aria-required": required || undefined, className: "flex-1", type: "password", autoComplete: "new-password", placeholder: setting.is_set ? texts.settings.secretPlaceholder : placeholder, value: typeof value === "string" ? value : "", disabled: disabled || clearPending, onChange: (event) => onChange(event.target.value) }), setting.source === "stored" && (_jsx(Button, { type: "button", size: "small", variant: "secondary", disabled: disabled, "aria-label": clearPending
57
+ return (_jsxs("div", { className: "flex flex-col gap-y-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "flex items-center gap-x-1", children: [_jsx(Label, { id: labelId, htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }), info && (_jsx(InfoTooltip, { label: texts.info.label(setting.label), children: info }))] }), showsSourceHint(setting.source) && (_jsx(Text, { size: "xsmall", className: "text-ui-fg-muted shrink-0", children: texts.settings.source[setting.source] }))] }), setting.type === "secret" ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Input, { id: id, "aria-describedby": describedBy, "aria-required": required || undefined, className: "flex-1", type: "password", autoComplete: "new-password", placeholder: setting.is_set ? texts.settings.secretPlaceholder : placeholder, value: typeof value === "string" ? value : "", disabled: disabled || clearPending, onChange: (event) => onChange(event.target.value) }), setting.source === "stored" && (_jsx(Button, { type: "button", size: "small", variant: "secondary", disabled: disabled, "aria-label": clearPending
55
58
  ? texts.settings.secretClearUndoLabel(setting.label)
56
59
  : texts.settings.secretClearLabel(setting.label), onClick: () => onChange(clearPending ? "" : null), children: clearPending
57
60
  ? texts.settings.secretClearUndo
58
- : texts.settings.secretClear }))] })) : setting.type === "select" ? (_jsxs(Select, { value: typeof value === "string" ? value : "", disabled: disabled, onValueChange: (next) => onChange(next), children: [_jsx(Select.Trigger, { id: id, "aria-describedby": describedBy, "aria-required": required || undefined, children: _jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }), _jsx(Select.Content, { children: setting.options.map((option) => (_jsx(Select.Item, { value: option.value, children: option.label }, option.value))) })] })) : setting.type === "image" ? (_jsx(ImageSettingInput, { id: id, describedBy: describedBy, label: setting.label, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (url) => onChange(url) })) : setting.type === "color" ? (_jsx(ColorSettingInput, { id: id, hintId: describedBy, label: setting.label, value: typeof value === "string" ? value : "", invalid: invalid, required: required, disabled: disabled, onChange: (next) => onChange(next) })) : setting.type === "number" ? (_jsx(Input, { id: id, "aria-describedby": describedBy, "aria-invalid": invalid, "aria-required": required || undefined, type: "number", step: setting.integer ? 1 : "any", min: setting.min ?? undefined, max: setting.max ?? undefined, placeholder: placeholder, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value, event.target.validity.badInput) })) : (_jsx(Input, { id: id, "aria-describedby": describedBy, "aria-invalid": invalid, "aria-required": required || undefined, type: setting.type === "url" ? "url" : "text", placeholder: placeholder, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value) })), hasHints && (_jsxs("div", { id: hintId, className: "flex flex-col gap-y-1", children: [hint && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: hint })), clearPending && (_jsx(Hint, { variant: "info", children: texts.settings.secretClearPending })), invalidatedBy.length > 0 && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.invalidatedBy(invalidatedBy) })), invalidates.length > 0 && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.invalidates(invalidates.join(", ")) })), showBounds && !invalid && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds })), invalid && (_jsx(Hint, { variant: "error", children: setting.type === "color"
61
+ : texts.settings.secretClear }))] })) : setting.type === "select" ? (_jsxs(Select, { value: typeof value === "string" ? value : "", disabled: disabled, onValueChange: (next) => onChange(next), children: [_jsx(Select.Trigger, { id: id, "aria-describedby": describedBy, "aria-required": required || undefined, children: _jsx(Select.Value, { placeholder: texts.settings.selectPlaceholder }) }), _jsx(Select.Content, { children: setting.options.map((option) => (_jsx(Select.Item, { value: option.value, children: option.label }, option.value))) })] })) : setting.type === "image" ? (_jsx(ImageSettingInput, { id: id, describedBy: describedBy, label: setting.label, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (url) => onChange(url) })) : setting.type === "color" ? (_jsx(ColorSettingInput, { id: id, hintId: describedBy, label: setting.label, value: typeof value === "string" ? value : "", invalid: invalid, required: required, disabled: disabled, onChange: (next) => onChange(next) })) : setting.type === "date" ? (_jsx(AdminDatePicker, { id: id, "aria-labelledby": labelId, "aria-describedby": describedBy, isDisabled: disabled, value: dateFromInputValue(typeof value === "string" ? value : ""), onChange: (date) => onChange(inputValueFromDate(date)) })) : setting.type === "number" ? (_jsx(Input, { id: id, "aria-describedby": describedBy, "aria-invalid": invalid, "aria-required": required || undefined, type: "number", step: setting.integer ? 1 : "any", min: setting.min ?? undefined, max: setting.max ?? undefined, placeholder: placeholder, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value, event.target.validity.badInput) })) : (_jsx(Input, { id: id, "aria-describedby": describedBy, "aria-invalid": invalid, "aria-required": required || undefined, type: setting.type === "url" ? "url" : "text", placeholder: placeholder, value: typeof value === "string" ? value : "", disabled: disabled, onChange: (event) => onChange(event.target.value) })), hasHints && (_jsxs("div", { id: hintId, className: "flex flex-col gap-y-1", children: [hint && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: hint })), clearPending && (_jsx(Hint, { variant: "info", children: texts.settings.secretClearPending })), invalidatedBy.length > 0 && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.invalidatedBy(invalidatedBy) })), invalidates.length > 0 && (_jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: texts.settings.invalidates(invalidates.join(", ")) })), showBounds && !invalid && (_jsx(Text, { size: "small", className: "text-ui-fg-muted", children: bounds })), invalid && (_jsx(Hint, { variant: "error", children: setting.type === "color"
59
62
  ? texts.settings.invalidColor
60
63
  : (bounds ?? texts.settings.invalidNumber) }))] }))] }));
61
64
  };
62
65
  export const PluginSettingsForm = ({ plugin }) => {
63
66
  const form = usePluginSettingsForm(plugin);
64
67
  const pending = form.mutation.isPending;
65
- const groups = buildSettingGroups(plugin.settings, plugin.setting_groups, texts.sections.settings);
68
+ const groups = buildSettingGroups(plugin.settings, plugin.setting_groups, texts.sections.settings).filter((group) => isGroupVisible(group, form.draft));
66
69
  const hasTextFields = plugin.settings.some((setting) => setting.type === "string" ||
67
70
  setting.type === "url" ||
68
71
  setting.type === "number" ||
69
- setting.type === "color");
72
+ setting.type === "color" ||
73
+ setting.type === "date");
70
74
  const renderField = (setting) => (_jsx(SettingField, { pluginKey: plugin.plugin_key, setting: setting, invalidatedBy: setting.type === "secret"
71
75
  ? invalidatedByLabels(plugin.settings, setting.key)
72
76
  : [], invalidates: invalidatesLabels(plugin.settings, setting), value: form.draft[setting.key], invalid: form.invalid.includes(setting.key), disabled: pending, onChange: (value, badInput) => form.setValue(setting.key, value, badInput) }, setting.key));
@@ -90,7 +90,7 @@ export declare const pluginFeatureEnabledQueryOptions: (pluginKey: string, featu
90
90
  width: "full" | "half";
91
91
  section: string | null;
92
92
  } | {
93
- type: "string" | "boolean" | "color" | "image" | "url";
93
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
94
94
  default: string | number | boolean | null;
95
95
  value: string | number | boolean | null;
96
96
  key: string;
@@ -130,6 +130,10 @@ export declare const pluginFeatureEnabledQueryOptions: (pluginKey: string, featu
130
130
  description: string | null;
131
131
  note: string | null;
132
132
  requires_feature: string | null;
133
+ visible_when: {
134
+ setting: string;
135
+ equals: string | boolean;
136
+ } | null;
133
137
  }[];
134
138
  }[];
135
139
  }>;
@@ -70,7 +70,7 @@ export declare function usePluginFeatures(): import("@tanstack/react-query").Use
70
70
  width: "full" | "half";
71
71
  section: string | null;
72
72
  } | {
73
- type: "string" | "boolean" | "color" | "image" | "url";
73
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
74
74
  default: string | number | boolean | null;
75
75
  value: string | number | boolean | null;
76
76
  key: string;
@@ -110,6 +110,10 @@ export declare function usePluginFeatures(): import("@tanstack/react-query").Use
110
110
  description: string | null;
111
111
  note: string | null;
112
112
  requires_feature: string | null;
113
+ visible_when: {
114
+ setting: string;
115
+ equals: string | boolean;
116
+ } | null;
113
117
  }[];
114
118
  }[];
115
119
  }, Error>;
@@ -192,7 +196,7 @@ export declare function usePluginFeature(pluginKey: string | undefined): import(
192
196
  width: "full" | "half";
193
197
  section: string | null;
194
198
  } | {
195
- type: "string" | "boolean" | "color" | "image" | "url";
199
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
196
200
  default: string | number | boolean | null;
197
201
  value: string | number | boolean | null;
198
202
  key: string;
@@ -232,6 +236,10 @@ export declare function usePluginFeature(pluginKey: string | undefined): import(
232
236
  description: string | null;
233
237
  note: string | null;
234
238
  requires_feature: string | null;
239
+ visible_when: {
240
+ setting: string;
241
+ equals: string | boolean;
242
+ } | null;
235
243
  }[];
236
244
  };
237
245
  }, Error>;
@@ -308,7 +316,7 @@ export declare function useUpdatePluginFeatures(): import("@tanstack/react-query
308
316
  width: "full" | "half";
309
317
  section: string | null;
310
318
  } | {
311
- type: "string" | "boolean" | "color" | "image" | "url";
319
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
312
320
  default: string | number | boolean | null;
313
321
  value: string | number | boolean | null;
314
322
  key: string;
@@ -348,6 +356,10 @@ export declare function useUpdatePluginFeatures(): import("@tanstack/react-query
348
356
  description: string | null;
349
357
  note: string | null;
350
358
  requires_feature: string | null;
359
+ visible_when: {
360
+ setting: string;
361
+ equals: string | boolean;
362
+ } | null;
351
363
  }[];
352
364
  };
353
365
  }, Error, {
@@ -428,7 +440,7 @@ export declare function useUpdatePluginSettings(pluginKey: string): import("@tan
428
440
  width: "full" | "half";
429
441
  section: string | null;
430
442
  } | {
431
- type: "string" | "boolean" | "color" | "image" | "url";
443
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
432
444
  default: string | number | boolean | null;
433
445
  value: string | number | boolean | null;
434
446
  key: string;
@@ -468,6 +480,10 @@ export declare function useUpdatePluginSettings(pluginKey: string): import("@tan
468
480
  description: string | null;
469
481
  note: string | null;
470
482
  requires_feature: string | null;
483
+ visible_when: {
484
+ setting: string;
485
+ equals: string | boolean;
486
+ } | null;
471
487
  }[];
472
488
  };
473
489
  }, Error, {
@@ -79,7 +79,7 @@ export declare function usePluginSettingsForm(plugin: AdminPluginFeatures): {
79
79
  width: "full" | "half";
80
80
  section: string | null;
81
81
  } | {
82
- type: "string" | "boolean" | "color" | "image" | "url";
82
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
83
83
  default: string | number | boolean | null;
84
84
  value: string | number | boolean | null;
85
85
  key: string;
@@ -119,6 +119,10 @@ export declare function usePluginSettingsForm(plugin: AdminPluginFeatures): {
119
119
  description: string | null;
120
120
  note: string | null;
121
121
  requires_feature: string | null;
122
+ visible_when: {
123
+ setting: string;
124
+ equals: string | boolean;
125
+ } | null;
122
126
  }[];
123
127
  };
124
128
  }, Error, {
@@ -71,7 +71,7 @@ export declare const listPluginFeatures: () => Promise<{
71
71
  width: "full" | "half";
72
72
  section: string | null;
73
73
  } | {
74
- type: "string" | "boolean" | "color" | "image" | "url";
74
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
75
75
  default: string | number | boolean | null;
76
76
  value: string | number | boolean | null;
77
77
  key: string;
@@ -111,6 +111,10 @@ export declare const listPluginFeatures: () => Promise<{
111
111
  description: string | null;
112
112
  note: string | null;
113
113
  requires_feature: string | null;
114
+ visible_when: {
115
+ setting: string;
116
+ equals: string | boolean;
117
+ } | null;
114
118
  }[];
115
119
  }[];
116
120
  }>;
@@ -186,7 +190,7 @@ export declare const retrievePluginFeatures: (pluginKey: string) => Promise<{
186
190
  width: "full" | "half";
187
191
  section: string | null;
188
192
  } | {
189
- type: "string" | "boolean" | "color" | "image" | "url";
193
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
190
194
  default: string | number | boolean | null;
191
195
  value: string | number | boolean | null;
192
196
  key: string;
@@ -226,6 +230,10 @@ export declare const retrievePluginFeatures: (pluginKey: string) => Promise<{
226
230
  description: string | null;
227
231
  note: string | null;
228
232
  requires_feature: string | null;
233
+ visible_when: {
234
+ setting: string;
235
+ equals: string | boolean;
236
+ } | null;
229
237
  }[];
230
238
  };
231
239
  }>;
@@ -301,7 +309,7 @@ export declare const updatePluginFeatures: (payload: UpdatePluginFeaturesPayload
301
309
  width: "full" | "half";
302
310
  section: string | null;
303
311
  } | {
304
- type: "string" | "boolean" | "color" | "image" | "url";
312
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
305
313
  default: string | number | boolean | null;
306
314
  value: string | number | boolean | null;
307
315
  key: string;
@@ -341,6 +349,10 @@ export declare const updatePluginFeatures: (payload: UpdatePluginFeaturesPayload
341
349
  description: string | null;
342
350
  note: string | null;
343
351
  requires_feature: string | null;
352
+ visible_when: {
353
+ setting: string;
354
+ equals: string | boolean;
355
+ } | null;
344
356
  }[];
345
357
  };
346
358
  }>;
@@ -416,7 +428,7 @@ export declare const updatePluginSettings: (pluginKey: string, payload: UpdatePl
416
428
  width: "full" | "half";
417
429
  section: string | null;
418
430
  } | {
419
- type: "string" | "boolean" | "color" | "image" | "url";
431
+ type: "string" | "boolean" | "color" | "image" | "url" | "date";
420
432
  default: string | number | boolean | null;
421
433
  value: string | number | boolean | null;
422
434
  key: string;
@@ -456,6 +468,10 @@ export declare const updatePluginSettings: (pluginKey: string, payload: UpdatePl
456
468
  description: string | null;
457
469
  note: string | null;
458
470
  requires_feature: string | null;
471
+ visible_when: {
472
+ setting: string;
473
+ equals: string | boolean;
474
+ } | null;
459
475
  }[];
460
476
  };
461
477
  }>;
@@ -30,6 +30,8 @@ export type SettingGroupView = {
30
30
  description: string | null;
31
31
  note: string | null;
32
32
  requiresFeature: string | null;
33
+ /** The group applies only while this setting's draft value equals `equals`. */
34
+ visibleWhen: AdminPluginSettingGroup["visible_when"];
33
35
  settings: AdminPluginSetting[];
34
36
  blocks: SettingBlock[];
35
37
  };
@@ -41,3 +43,5 @@ export declare const buildSettingBlocks: (settings: readonly AdminPluginSetting[
41
43
  * `groups` supplies the description, note and required feature per group.
42
44
  */
43
45
  export declare const buildSettingGroups: (settings: readonly AdminPluginSetting[], groups: readonly AdminPluginSettingGroup[], ungroupedTitle: string) => SettingGroupView[];
46
+ /** Whether a group's `visible_when` holds for the current form values. */
47
+ export declare const isGroupVisible: (group: Pick<SettingGroupView, "visibleWhen">, draft: Readonly<Record<string, string | boolean | null>>) => boolean;
@@ -59,8 +59,12 @@ export const buildSettingGroups = (settings, groups, ungroupedTitle) => {
59
59
  description: meta?.description ?? null,
60
60
  note: meta?.note ?? null,
61
61
  requiresFeature: meta?.requires_feature ?? null,
62
+ visibleWhen: meta?.visible_when ?? null,
62
63
  settings: groupSettings,
63
64
  blocks: buildSettingBlocks(groupSettings),
64
65
  };
65
66
  });
66
67
  };
68
+ /** Whether a group's `visible_when` holds for the current form values. */
69
+ export const isGroupVisible = (group, draft) => group.visibleWhen === null ||
70
+ draft[group.visibleWhen.setting] === group.visibleWhen.equals;
@@ -963,6 +963,35 @@ const changedSettingValues = (settings, draft, unparsable = []) => {
963
963
  }
964
964
  return values;
965
965
  };
966
+ const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
967
+ const DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
968
+ const pad = (value, length = 2) => String(value).padStart(length, "0");
969
+ const build = ([year, month, day, hour = 0, minute = 0]) => {
970
+ const date = new Date(year, month - 1, day, hour, minute);
971
+ const matches = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day && date.getHours() === hour && date.getMinutes() === minute;
972
+ return matches ? date : null;
973
+ };
974
+ const dateFromInputValue = (value) => {
975
+ const match = value ? DATE_PATTERN.exec(value) : null;
976
+ return match ? build(match.slice(1).map(Number)) : null;
977
+ };
978
+ const inputValueFromDate = (date) => {
979
+ if (!date || Number.isNaN(date.getTime())) {
980
+ return "";
981
+ }
982
+ return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
983
+ };
984
+ const dateTimeFromInputValue = (value) => {
985
+ const match = value ? DATE_TIME_PATTERN.exec(value) : null;
986
+ return match ? build(match.slice(1).map(Number)) : null;
987
+ };
988
+ const inputValueFromDateTime = (date) => {
989
+ if (!date || Number.isNaN(date.getTime())) {
990
+ return "";
991
+ }
992
+ return `${inputValueFromDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
993
+ };
994
+ const AdminDatePicker = (props) => /* @__PURE__ */ jsxRuntime.jsx(ui.I18nProvider, { locale: ADMIN_LOCALE, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { ...props }) });
966
995
  const buildSettingBlocks = (settings) => {
967
996
  const blocks = [];
968
997
  let previousSection = null;
@@ -1014,11 +1043,13 @@ const buildSettingGroups = (settings, groups, ungroupedTitle) => {
1014
1043
  description: meta?.description ?? null,
1015
1044
  note: meta?.note ?? null,
1016
1045
  requiresFeature: meta?.requires_feature ?? null,
1046
+ visibleWhen: meta?.visible_when ?? null,
1017
1047
  settings: groupSettings,
1018
1048
  blocks: buildSettingBlocks(groupSettings)
1019
1049
  };
1020
1050
  });
1021
1051
  };
1052
+ const isGroupVisible = (group, draft) => group.visibleWhen === null || draft[group.visibleWhen.setting] === group.visibleWhen.equals;
1022
1053
  function usePluginSettingsForm(plugin2) {
1023
1054
  const mutation = useUpdatePluginSettings(plugin2.plugin_key);
1024
1055
  const [draft, setDraft] = react.useState(
@@ -1319,6 +1350,7 @@ const SettingField = ({
1319
1350
  onChange
1320
1351
  }) => {
1321
1352
  const id = fieldId(pluginKey, setting.key);
1353
+ const labelId = `${id}-label`;
1322
1354
  const hintId = `${id}-hint`;
1323
1355
  const clearPending = setting.type === "secret" && value === null;
1324
1356
  const bounds = setting.type === "number" ? boundsText(setting) : void 0;
@@ -1332,7 +1364,7 @@ const SettingField = ({
1332
1364
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
1333
1365
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1334
1366
  /* @__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) }),
1367
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { id: labelId, htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }),
1336
1368
  info && /* @__PURE__ */ jsxRuntime.jsx(InfoTooltip, { label: texts.info.label(setting.label), children: info })
1337
1369
  ] }),
1338
1370
  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 +1438,16 @@ const SettingField = ({
1406
1438
  disabled,
1407
1439
  onChange: (next) => onChange(next)
1408
1440
  }
1441
+ ) : setting.type === "date" ? /* @__PURE__ */ jsxRuntime.jsx(
1442
+ AdminDatePicker,
1443
+ {
1444
+ id,
1445
+ "aria-labelledby": labelId,
1446
+ "aria-describedby": describedBy,
1447
+ isDisabled: disabled,
1448
+ value: dateFromInputValue(typeof value === "string" ? value : ""),
1449
+ onChange: (date) => onChange(inputValueFromDate(date))
1450
+ }
1409
1451
  ) : setting.type === "number" ? /* @__PURE__ */ jsxRuntime.jsx(
1410
1452
  ui.Input,
1411
1453
  {
@@ -1453,9 +1495,9 @@ const PluginSettingsForm = ({ plugin: plugin2 }) => {
1453
1495
  plugin2.settings,
1454
1496
  plugin2.setting_groups,
1455
1497
  texts.sections.settings
1456
- );
1498
+ ).filter((group) => isGroupVisible(group, form.draft));
1457
1499
  const hasTextFields = plugin2.settings.some(
1458
- (setting) => setting.type === "string" || setting.type === "url" || setting.type === "number" || setting.type === "color"
1500
+ (setting) => setting.type === "string" || setting.type === "url" || setting.type === "number" || setting.type === "color" || setting.type === "date"
1459
1501
  );
1460
1502
  const renderField = (setting) => /* @__PURE__ */ jsxRuntime.jsx(
1461
1503
  SettingField,
@@ -1641,29 +1683,6 @@ const PluginFeatureDisabledNotice = ({
1641
1683
  ]
1642
1684
  }
1643
1685
  );
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
1686
  const fetchPdfObjectUrl = async (path) => {
1668
1687
  const response = await sdk.client.fetch(path, {
1669
1688
  headers: { accept: "application/pdf" }
@@ -1751,7 +1770,6 @@ const useDebouncedValue = (value, delayMs) => {
1751
1770
  }, [value, delayMs]);
1752
1771
  return debounced;
1753
1772
  };
1754
- const AdminDatePicker = (props) => /* @__PURE__ */ jsxRuntime.jsx(ui.I18nProvider, { locale: ADMIN_LOCALE, children: /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { ...props }) });
1755
1773
  const FocusModalShell = ({
1756
1774
  open,
1757
1775
  onOpenChange,
@@ -4619,17 +4637,17 @@ const handle$5 = {
4619
4637
  breadcrumb: () => "Protokoll"
4620
4638
  };
4621
4639
  const EmailLogRoute = () => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsxRuntime.jsx(EmailLogPage, {}) });
4622
- const handle$4 = {
4623
- breadcrumb: () => EMAIL_TEMPLATE_GROUPS.order.label
4624
- };
4625
- const EmailOrderMailsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsxRuntime.jsx(EmailTemplateGroupPage, { group: "order" }) });
4626
4640
  const PluginDetailPage = () => {
4627
4641
  const { plugin_key: pluginKey = "" } = reactRouterDom.useParams();
4628
4642
  return /* @__PURE__ */ jsxRuntime.jsx(PluginPageShell, { pluginKey, variant: "header", children: /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureSections, { pluginKey }) });
4629
4643
  };
4630
- const handle$3 = {
4644
+ const handle$4 = {
4631
4645
  breadcrumb: (match) => /* @__PURE__ */ jsxRuntime.jsx(PluginLabelCrumb, { pluginKey: match.params.plugin_key ?? "" })
4632
4646
  };
4647
+ const handle$3 = {
4648
+ breadcrumb: () => EMAIL_TEMPLATE_GROUPS.order.label
4649
+ };
4650
+ const EmailOrderMailsRoute = () => /* @__PURE__ */ jsxRuntime.jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsxRuntime.jsx(EmailTemplateGroupPage, { group: "order" }) });
4633
4651
  const STATUS_BADGES = {
4634
4652
  current: { label: "aktuell", color: "green" },
4635
4653
  planned: { label: "geplant", color: "blue" },
@@ -6715,13 +6733,13 @@ const routeModule = {
6715
6733
  handle: handle$5
6716
6734
  },
6717
6735
  {
6718
- Component: EmailOrderMailsRoute,
6719
- path: "/settings/hartl-services/email/order",
6736
+ Component: PluginDetailPage,
6737
+ path: "/settings/hartl-services/plugin/:plugin_key",
6720
6738
  handle: handle$4
6721
6739
  },
6722
6740
  {
6723
- Component: PluginDetailPage,
6724
- path: "/settings/hartl-services/plugin/:plugin_key",
6741
+ Component: EmailOrderMailsRoute,
6742
+ path: "/settings/hartl-services/email/order",
6725
6743
  handle: handle$3
6726
6744
  },
6727
6745
  {
@@ -1,6 +1,6 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { Container, IconBadge, Heading, Text, Tooltip, IconButton, toast, StatusBadge, Button, clx, usePrompt, Label, Switch, Hint, Input, Select, Alert, I18nProvider, DatePicker, FocusModal, DropdownMenu, Badge, Table, Prompt } from "@medusajs/ui";
3
+ import { Container, IconBadge, Heading, Text, Tooltip, IconButton, toast, StatusBadge, Button, clx, usePrompt, Label, Switch, I18nProvider, DatePicker, Hint, Input, Select, Alert, FocusModal, DropdownMenu, Badge, Table, Prompt } from "@medusajs/ui";
4
4
  import { useQuery, useQueryClient, useMutation, keepPreviousData, skipToken } from "@tanstack/react-query";
5
5
  import { emailAdminPaths } from "@hartl-services/medusa-food-supplements-contracts/paths/email";
6
6
  import Medusa from "@medusajs/js-sdk";
@@ -957,6 +957,35 @@ const changedSettingValues = (settings, draft, unparsable = []) => {
957
957
  }
958
958
  return values;
959
959
  };
960
+ const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
961
+ const DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
962
+ const pad = (value, length = 2) => String(value).padStart(length, "0");
963
+ const build = ([year, month, day, hour = 0, minute = 0]) => {
964
+ const date = new Date(year, month - 1, day, hour, minute);
965
+ const matches = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day && date.getHours() === hour && date.getMinutes() === minute;
966
+ return matches ? date : null;
967
+ };
968
+ const dateFromInputValue = (value) => {
969
+ const match = value ? DATE_PATTERN.exec(value) : null;
970
+ return match ? build(match.slice(1).map(Number)) : null;
971
+ };
972
+ const inputValueFromDate = (date) => {
973
+ if (!date || Number.isNaN(date.getTime())) {
974
+ return "";
975
+ }
976
+ return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
977
+ };
978
+ const dateTimeFromInputValue = (value) => {
979
+ const match = value ? DATE_TIME_PATTERN.exec(value) : null;
980
+ return match ? build(match.slice(1).map(Number)) : null;
981
+ };
982
+ const inputValueFromDateTime = (date) => {
983
+ if (!date || Number.isNaN(date.getTime())) {
984
+ return "";
985
+ }
986
+ return `${inputValueFromDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
987
+ };
988
+ const AdminDatePicker = (props) => /* @__PURE__ */ jsx(I18nProvider, { locale: ADMIN_LOCALE, children: /* @__PURE__ */ jsx(DatePicker, { ...props }) });
960
989
  const buildSettingBlocks = (settings) => {
961
990
  const blocks = [];
962
991
  let previousSection = null;
@@ -1008,11 +1037,13 @@ const buildSettingGroups = (settings, groups, ungroupedTitle) => {
1008
1037
  description: meta?.description ?? null,
1009
1038
  note: meta?.note ?? null,
1010
1039
  requiresFeature: meta?.requires_feature ?? null,
1040
+ visibleWhen: meta?.visible_when ?? null,
1011
1041
  settings: groupSettings,
1012
1042
  blocks: buildSettingBlocks(groupSettings)
1013
1043
  };
1014
1044
  });
1015
1045
  };
1046
+ const isGroupVisible = (group, draft) => group.visibleWhen === null || draft[group.visibleWhen.setting] === group.visibleWhen.equals;
1016
1047
  function usePluginSettingsForm(plugin2) {
1017
1048
  const mutation = useUpdatePluginSettings(plugin2.plugin_key);
1018
1049
  const [draft, setDraft] = useState(
@@ -1313,6 +1344,7 @@ const SettingField = ({
1313
1344
  onChange
1314
1345
  }) => {
1315
1346
  const id = fieldId(pluginKey, setting.key);
1347
+ const labelId = `${id}-label`;
1316
1348
  const hintId = `${id}-hint`;
1317
1349
  const clearPending = setting.type === "secret" && value === null;
1318
1350
  const bounds = setting.type === "number" ? boundsText(setting) : void 0;
@@ -1326,7 +1358,7 @@ const SettingField = ({
1326
1358
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-2", children: [
1327
1359
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1328
1360
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-1", children: [
1329
- /* @__PURE__ */ jsx(Label, { htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }),
1361
+ /* @__PURE__ */ jsx(Label, { id: labelId, htmlFor: id, size: "small", weight: "plus", children: labelText(setting) }),
1330
1362
  info && /* @__PURE__ */ jsx(InfoTooltip, { label: texts.info.label(setting.label), children: info })
1331
1363
  ] }),
1332
1364
  showsSourceHint(setting.source) && /* @__PURE__ */ jsx(Text, { size: "xsmall", className: "text-ui-fg-muted shrink-0", children: texts.settings.source[setting.source] })
@@ -1400,6 +1432,16 @@ const SettingField = ({
1400
1432
  disabled,
1401
1433
  onChange: (next) => onChange(next)
1402
1434
  }
1435
+ ) : setting.type === "date" ? /* @__PURE__ */ jsx(
1436
+ AdminDatePicker,
1437
+ {
1438
+ id,
1439
+ "aria-labelledby": labelId,
1440
+ "aria-describedby": describedBy,
1441
+ isDisabled: disabled,
1442
+ value: dateFromInputValue(typeof value === "string" ? value : ""),
1443
+ onChange: (date) => onChange(inputValueFromDate(date))
1444
+ }
1403
1445
  ) : setting.type === "number" ? /* @__PURE__ */ jsx(
1404
1446
  Input,
1405
1447
  {
@@ -1447,9 +1489,9 @@ const PluginSettingsForm = ({ plugin: plugin2 }) => {
1447
1489
  plugin2.settings,
1448
1490
  plugin2.setting_groups,
1449
1491
  texts.sections.settings
1450
- );
1492
+ ).filter((group) => isGroupVisible(group, form.draft));
1451
1493
  const hasTextFields = plugin2.settings.some(
1452
- (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"
1453
1495
  );
1454
1496
  const renderField = (setting) => /* @__PURE__ */ jsx(
1455
1497
  SettingField,
@@ -1635,29 +1677,6 @@ const PluginFeatureDisabledNotice = ({
1635
1677
  ]
1636
1678
  }
1637
1679
  );
1638
- const DATE_TIME_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})$/;
1639
- const pad = (value, length = 2) => String(value).padStart(length, "0");
1640
- const build = ([year, month, day, hour = 0, minute = 0]) => {
1641
- const date = new Date(year, month - 1, day, hour, minute);
1642
- const matches = date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day && date.getHours() === hour && date.getMinutes() === minute;
1643
- return matches ? date : null;
1644
- };
1645
- const inputValueFromDate = (date) => {
1646
- if (!date || Number.isNaN(date.getTime())) {
1647
- return "";
1648
- }
1649
- return `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
1650
- };
1651
- const dateTimeFromInputValue = (value) => {
1652
- const match = value ? DATE_TIME_PATTERN.exec(value) : null;
1653
- return match ? build(match.slice(1).map(Number)) : null;
1654
- };
1655
- const inputValueFromDateTime = (date) => {
1656
- if (!date || Number.isNaN(date.getTime())) {
1657
- return "";
1658
- }
1659
- return `${inputValueFromDate(date)}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
1660
- };
1661
1680
  const fetchPdfObjectUrl = async (path) => {
1662
1681
  const response = await sdk.client.fetch(path, {
1663
1682
  headers: { accept: "application/pdf" }
@@ -1745,7 +1764,6 @@ const useDebouncedValue = (value, delayMs) => {
1745
1764
  }, [value, delayMs]);
1746
1765
  return debounced;
1747
1766
  };
1748
- const AdminDatePicker = (props) => /* @__PURE__ */ jsx(I18nProvider, { locale: ADMIN_LOCALE, children: /* @__PURE__ */ jsx(DatePicker, { ...props }) });
1749
1767
  const FocusModalShell = ({
1750
1768
  open,
1751
1769
  onOpenChange,
@@ -4613,17 +4631,17 @@ const handle$5 = {
4613
4631
  breadcrumb: () => "Protokoll"
4614
4632
  };
4615
4633
  const EmailLogRoute = () => /* @__PURE__ */ jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsx(EmailLogPage, {}) });
4616
- const handle$4 = {
4617
- breadcrumb: () => EMAIL_TEMPLATE_GROUPS.order.label
4618
- };
4619
- const EmailOrderMailsRoute = () => /* @__PURE__ */ jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsx(EmailTemplateGroupPage, { group: "order" }) });
4620
4634
  const PluginDetailPage = () => {
4621
4635
  const { plugin_key: pluginKey = "" } = useParams();
4622
4636
  return /* @__PURE__ */ jsx(PluginPageShell, { pluginKey, variant: "header", children: /* @__PURE__ */ jsx(PluginFeatureSections, { pluginKey }) });
4623
4637
  };
4624
- const handle$3 = {
4638
+ const handle$4 = {
4625
4639
  breadcrumb: (match) => /* @__PURE__ */ jsx(PluginLabelCrumb, { pluginKey: match.params.plugin_key ?? "" })
4626
4640
  };
4641
+ const handle$3 = {
4642
+ breadcrumb: () => EMAIL_TEMPLATE_GROUPS.order.label
4643
+ };
4644
+ const EmailOrderMailsRoute = () => /* @__PURE__ */ jsx(PluginFeatureRoute, { pluginKey: "email", children: /* @__PURE__ */ jsx(EmailTemplateGroupPage, { group: "order" }) });
4627
4645
  const STATUS_BADGES = {
4628
4646
  current: { label: "aktuell", color: "green" },
4629
4647
  planned: { label: "geplant", color: "blue" },
@@ -6709,13 +6727,13 @@ const routeModule = {
6709
6727
  handle: handle$5
6710
6728
  },
6711
6729
  {
6712
- Component: EmailOrderMailsRoute,
6713
- path: "/settings/hartl-services/email/order",
6730
+ Component: PluginDetailPage,
6731
+ path: "/settings/hartl-services/plugin/:plugin_key",
6714
6732
  handle: handle$4
6715
6733
  },
6716
6734
  {
6717
- Component: PluginDetailPage,
6718
- path: "/settings/hartl-services/plugin/:plugin_key",
6735
+ Component: EmailOrderMailsRoute,
6736
+ path: "/settings/hartl-services/email/order",
6719
6737
  handle: handle$3
6720
6738
  },
6721
6739
  {