@gorgo/medusa-integration 0.3.0 → 0.3.1

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.
@@ -498,113 +498,399 @@ const IntegrationsBreadcrumb = () => {
498
498
  const handle$2 = {
499
499
  breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(IntegrationsBreadcrumb, {})
500
500
  };
501
- const Dash = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "-" });
502
- const IntegrationFieldValue = ({
503
- field,
504
- value,
505
- secretConfigured = false
506
- }) => {
507
- const { t } = reactI18next.useTranslation();
508
- if (field.secret || field.control === "secret") {
509
- return secretConfigured ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: "••••••••" }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
510
- }
511
- if (field.control === "switch") {
512
- if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
513
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: value ? "green" : "grey", children: value ? t("integration.status.enabled") : t("integration.status.disabled") });
501
+ function isSafeHref(raw) {
502
+ try {
503
+ return new URL(raw).protocol === "https:";
504
+ } catch {
505
+ return false;
514
506
  }
515
- if (field.control === "json") {
516
- if (Array.isArray(value)) {
517
- return value.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.form.items", { count: value.length }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
507
+ }
508
+ const INLINE_RE = /`([^`\n]+)`|\[([^\]\n]*)\]\(([^)\s]+)\)|\*\*([^*\n]+)\*\*|\*([^*\n]+)\*|_([^_\n]+)_/;
509
+ const MAX_DEPTH = 3;
510
+ function parseInline(src, depth = 0) {
511
+ if (!src) return [];
512
+ if (depth >= MAX_DEPTH) return [{ type: "text", value: src }];
513
+ const out = [];
514
+ const re = new RegExp(INLINE_RE, "g");
515
+ let last = 0;
516
+ let m;
517
+ while ((m = re.exec(src)) !== null) {
518
+ const [full, code, linkText, linkHref, strong, star, underscore] = m;
519
+ if (m.index > last) out.push({ type: "text", value: src.slice(last, m.index) });
520
+ last = m.index + full.length;
521
+ if (code !== void 0) {
522
+ out.push({ type: "code", value: code });
523
+ } else if (linkHref !== void 0) {
524
+ const isImage = m.index > 0 && src[m.index - 1] === "!";
525
+ if (!isImage && isSafeHref(linkHref)) {
526
+ out.push({ type: "link", href: linkHref, children: parseInline(linkText, depth + 1) });
527
+ } else {
528
+ out.push({ type: "text", value: full });
529
+ }
530
+ } else if (strong !== void 0) {
531
+ out.push({ type: "strong", children: parseInline(strong, depth + 1) });
532
+ } else {
533
+ out.push({ type: "em", children: parseInline(star ?? underscore, depth + 1) });
518
534
  }
519
- if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
520
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.status.configured") });
521
- }
522
- if (value == null || value === "") return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
523
- if (field.control === "select") {
524
- const labelKey = field.optionLabels?.[String(value)];
525
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: labelKey ? t(labelKey) : String(value) });
526
- }
527
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: String(value) });
528
- };
529
- var isCheckBoxInput = (element) => element.type === "checkbox";
530
- var isFileInput = (element) => element.type === "file";
531
- var isDateObject = (value) => value instanceof Date;
532
- var isNullOrUndefined = (value) => value == null;
533
- const isObjectType = (value) => typeof value === "object";
534
- var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value);
535
- var getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : isFileInput(event.target) ? event.target.files : event.target.value : event;
536
- var isNameInFieldArray = (names, name) => name.split(".").some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join(".")));
537
- var isPlainObject = (tempObject) => {
538
- const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
539
- return isObject(prototypeCopy) && prototypeCopy.hasOwnProperty("isPrototypeOf");
540
- };
541
- var isWeb = typeof window !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof document !== "undefined";
542
- function cloneObject(data) {
543
- if (data instanceof Date) {
544
- return new Date(data);
545
- }
546
- const isFileListInstance = typeof FileList !== "undefined" && data instanceof FileList;
547
- if (isWeb && (data instanceof Blob || isFileListInstance)) {
548
- return data;
549
- }
550
- const isArray = Array.isArray(data);
551
- if (!isArray && !(isObject(data) && isPlainObject(data))) {
552
- return data;
553
535
  }
554
- const copy = isArray ? [] : Object.create(Object.getPrototypeOf(data));
555
- for (const key in data) {
556
- if (Object.prototype.hasOwnProperty.call(data, key)) {
557
- copy[key] = cloneObject(data[key]);
536
+ if (last < src.length) out.push({ type: "text", value: src.slice(last) });
537
+ return out;
538
+ }
539
+ const MD_MAX_CHARS = 8e3;
540
+ const MD_MAX_BLOCKS = 200;
541
+ const FENCE_RE = /^```(.*)$/;
542
+ const HEADING_RE = /^(#{1,3})\s+(.*)$/;
543
+ const HR_RE = /^-{3,}\s*$/;
544
+ const UL_RE = /^[-*]\s+(.*)$/;
545
+ const OL_RE = /^\d+\.\s+(.*)$/;
546
+ function fenceLabel(info) {
547
+ const first = info.trim().split(/\s+/)[0] ?? "";
548
+ return first.replace(/[^a-zA-Z0-9._/-]/g, "").slice(0, 40) || null;
549
+ }
550
+ function parseMarkdown(src) {
551
+ if (typeof src !== "string" || !src) return [];
552
+ if (src.length > MD_MAX_CHARS) return [{ type: "truncated" }];
553
+ const lines = src.replace(/\r\n?/g, "\n").split("\n");
554
+ const blocks = [];
555
+ let para = [];
556
+ const flushPara = () => {
557
+ if (!para.length) return;
558
+ blocks.push({ type: "paragraph", children: parseInline(para.join(" ")) });
559
+ para = [];
560
+ };
561
+ for (let i = 0; i < lines.length; i++) {
562
+ const line = lines[i];
563
+ const fence = FENCE_RE.exec(line);
564
+ if (fence) {
565
+ flushPara();
566
+ const body = [];
567
+ i++;
568
+ while (i < lines.length && !FENCE_RE.test(lines[i])) body.push(lines[i++]);
569
+ blocks.push({ type: "codeBlock", label: fenceLabel(fence[1]), value: body.join("\n") });
570
+ continue;
571
+ }
572
+ if (!line.trim()) {
573
+ flushPara();
574
+ continue;
575
+ }
576
+ if (HR_RE.test(line)) {
577
+ flushPara();
578
+ blocks.push({ type: "hr" });
579
+ continue;
580
+ }
581
+ const heading = HEADING_RE.exec(line);
582
+ if (heading) {
583
+ flushPara();
584
+ blocks.push({
585
+ type: "heading",
586
+ level: heading[1].length + 1,
587
+ children: parseInline(heading[2])
588
+ });
589
+ continue;
590
+ }
591
+ const ul = UL_RE.exec(line);
592
+ const ol = ul ? null : OL_RE.exec(line);
593
+ if (ul || ol) {
594
+ flushPara();
595
+ const ordered = !!ol;
596
+ const itemRe = ordered ? OL_RE : UL_RE;
597
+ const items = [];
598
+ while (i < lines.length) {
599
+ const item = itemRe.exec(lines[i]);
600
+ if (!item) break;
601
+ items.push(parseInline(item[1]));
602
+ i++;
603
+ }
604
+ i--;
605
+ blocks.push({ type: "list", ordered, items });
606
+ continue;
558
607
  }
608
+ para.push(line);
559
609
  }
560
- return copy;
610
+ flushPara();
611
+ const weight = blocks.reduce((n, b) => n + (b.type === "list" ? b.items.length : 1), 0);
612
+ return weight > MD_MAX_BLOCKS ? [{ type: "truncated" }] : blocks;
561
613
  }
562
- const EVENTS = {
563
- BLUR: "blur",
564
- FOCUS_OUT: "focusout",
565
- CHANGE: "change",
566
- SUBMIT: "submit",
567
- TRIGGER: "trigger",
568
- VALID: "valid"
569
- };
570
- const VALIDATION_MODE = {
571
- onBlur: "onBlur",
572
- onChange: "onChange",
573
- onSubmit: "onSubmit",
574
- onTouched: "onTouched",
575
- all: "all"
576
- };
577
- const INPUT_VALIDATION_RULES = {
578
- max: "max",
579
- min: "min",
580
- maxLength: "maxLength",
581
- minLength: "minLength",
582
- pattern: "pattern",
583
- required: "required",
584
- validate: "validate"
585
- };
586
- const ROOT_ERROR_TYPE = "root";
587
- const PROTOTYPE_KEYWORDS = ["__proto__", "constructor", "prototype"];
588
- const IS_KEY_RE = /^\w*$/;
589
- var isKey = (value) => IS_KEY_RE.test(value);
590
- var isUndefined = (val) => val === void 0;
591
- const FIELD_PATH_RE = /[.[\]'"]/;
592
- var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
593
- var get = (object, path, defaultValue) => {
594
- if (!path || !isObject(object)) {
595
- return defaultValue;
614
+ const Inline = ({ nodes }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node, i) => {
615
+ switch (node.type) {
616
+ case "text":
617
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.value }, i);
618
+ case "code":
619
+ return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "txt-compact-small bg-ui-bg-component rounded px-1 py-0.5", children: node.value }, i);
620
+ case "strong":
621
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-compact-small-plus", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
622
+ case "em":
623
+ return /* @__PURE__ */ jsxRuntime.jsx("em", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
624
+ case "link":
625
+ return /* @__PURE__ */ jsxRuntime.jsx(
626
+ "a",
627
+ {
628
+ href: node.href,
629
+ target: "_blank",
630
+ rel: "noopener noreferrer",
631
+ className: "text-ui-fg-interactive hover:underline",
632
+ children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children })
633
+ },
634
+ i
635
+ );
596
636
  }
597
- const paths = isKey(path) ? [path] : stringToPath(path);
598
- if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
599
- return defaultValue;
637
+ }) });
638
+ const Block = ({ block, truncatedLabel }) => {
639
+ switch (block.type) {
640
+ case "heading":
641
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: block.level === 2 ? "h2" : "h3", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
642
+ case "paragraph":
643
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
644
+ case "list": {
645
+ const List = block.ordered ? "ol" : "ul";
646
+ return /* @__PURE__ */ jsxRuntime.jsx(
647
+ List,
648
+ {
649
+ className: `text-ui-fg-subtle txt-small flex flex-col gap-y-1 pl-5 ${block.ordered ? "list-decimal" : "list-disc"}`,
650
+ children: block.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: item }) }, i))
651
+ }
652
+ );
653
+ }
654
+ case "codeBlock":
655
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-ui-bg-subtle flex flex-col gap-y-2 rounded-md px-3 pb-1 pt-2", children: [
656
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-2", children: [
657
+ block.label ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-subtle", children: block.label }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
658
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: block.value })
659
+ ] }),
660
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "txt-compact-small text-ui-fg-subtle overflow-x-auto whitespace-pre pb-2", children: block.value })
661
+ ] });
662
+ case "hr":
663
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-ui-border-base border-t" });
664
+ case "truncated":
665
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: truncatedLabel });
600
666
  }
601
- const result = paths.reduce((result2, key) => {
602
- return isNullOrUndefined(result2) ? void 0 : result2[key];
603
- }, object);
604
- return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result;
605
667
  };
606
- var isBoolean = (value) => typeof value === "boolean";
607
- var isFunction = (value) => typeof value === "function";
668
+ const Markdown = ({
669
+ source,
670
+ truncatedLabel
671
+ }) => {
672
+ const blocks = React.useMemo(() => parseMarkdown(source), [source]);
673
+ if (!blocks.length) return null;
674
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-4", children: blocks.map((block, i) => /* @__PURE__ */ jsxRuntime.jsx(Block, { block, truncatedLabel }, i)) });
675
+ };
676
+ const PUBLISH_URL = "https://docs.gorgojs.com/medusa-modules/integration";
677
+ const API_URL = "https://gorgojs.com";
678
+ const iconSrc = (icon) => icon.startsWith("data:") ? icon : API_URL + icon;
679
+ const InstallModal = ({
680
+ item,
681
+ onOpenChange
682
+ }) => {
683
+ const { t, i18n } = reactI18next.useTranslation();
684
+ const lang = i18n.language.split("-")[0] === "ru" ? "ru" : "en";
685
+ const source = item?.docsSnippet?.[lang] || item?.docsSnippet?.en || item?.docsSnippet?.ru || "";
686
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
687
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
688
+ item && /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label, size: "small" }),
689
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: item?.label })
690
+ ] }) }) }),
691
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: source.trim() ? /* @__PURE__ */ jsxRuntime.jsx(Markdown, { source, truncatedLabel: t("integration.browse.install_truncated") }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.install_empty") }) }),
692
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
693
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }),
694
+ item?.docsUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "primary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: item.docsUrl, target: "_blank", rel: "noopener noreferrer", children: [
695
+ t("integration.browse.install_docs"),
696
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
697
+ ] }) })
698
+ ] })
699
+ ] }) });
700
+ };
701
+ const BrowsePage = () => {
702
+ const { t } = reactI18next.useTranslation();
703
+ const navigate = reactRouterDom.useNavigate();
704
+ const [activeTab, setActiveTab] = React.useState("all");
705
+ const [search, setSearch] = React.useState("");
706
+ const [installItem, setInstallItem] = React.useState(null);
707
+ const { data, isLoading } = reactQuery.useQuery({
708
+ queryKey: ["integration-catalog"],
709
+ queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
710
+ });
711
+ const items = data?.integrations ?? [];
712
+ const categories = React.useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
713
+ const q = search.trim().toLowerCase();
714
+ const filtered = items.filter((i) => {
715
+ if (activeTab !== "all" && i.category !== activeTab) return false;
716
+ if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
717
+ return true;
718
+ });
719
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
720
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
721
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
722
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
723
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("integration.browse.title") }),
724
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
725
+ ] }),
726
+ false
727
+ ] }),
728
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
729
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
730
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
731
+ categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
732
+ ] }) }),
733
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
734
+ ui.Input,
735
+ {
736
+ placeholder: t("integration.browse.searchPlaceholder"),
737
+ value: search,
738
+ onChange: (e) => setSearch(e.target.value)
739
+ }
740
+ ) })
741
+ ] })
742
+ ] }),
743
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.list.emptyFiltered") }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4", children: filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shadow-borders-base bg-ui-bg-base flex flex-col rounded-lg", children: [
744
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
745
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
746
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
747
+ /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label }),
748
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
749
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
750
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
751
+ ] })
752
+ ] }),
753
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
754
+ ] }),
755
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
756
+ ] }),
757
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
758
+ item.installed && item.provider_id ? /* @__PURE__ */ jsxRuntime.jsx(
759
+ ui.Button,
760
+ {
761
+ size: "small",
762
+ variant: "primary",
763
+ onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
764
+ children: t("integration.browse.settings")
765
+ }
766
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
767
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
768
+ t("integration.browse.add")
769
+ ] }),
770
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
771
+ ] })
772
+ ] }, item.integrationId)) }),
773
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: PUBLISH_URL, target: "_blank", rel: "noreferrer", className: "text-ui-fg-base txt-small", children: [
774
+ t("integration.browse.learnMore"),
775
+ " →"
776
+ ] }) }),
777
+ /* @__PURE__ */ jsxRuntime.jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
778
+ ] });
779
+ };
780
+ const BrowseBreadcrumb = () => {
781
+ const { t } = reactI18next.useTranslation();
782
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("integration.browse.title") });
783
+ };
784
+ const handle$1 = {
785
+ breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(BrowseBreadcrumb, {})
786
+ };
787
+ const Dash = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "-" });
788
+ const IntegrationFieldValue = ({
789
+ field,
790
+ value,
791
+ secretConfigured = false
792
+ }) => {
793
+ const { t } = reactI18next.useTranslation();
794
+ if (field.secret || field.control === "secret") {
795
+ return secretConfigured ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: "••••••••" }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
796
+ }
797
+ if (field.control === "switch") {
798
+ if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
799
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: value ? "green" : "grey", children: value ? t("integration.status.enabled") : t("integration.status.disabled") });
800
+ }
801
+ if (field.control === "json") {
802
+ if (Array.isArray(value)) {
803
+ return value.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.form.items", { count: value.length }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
804
+ }
805
+ if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
806
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.status.configured") });
807
+ }
808
+ if (value == null || value === "") return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
809
+ if (field.control === "select") {
810
+ const labelKey = field.optionLabels?.[String(value)];
811
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: labelKey ? t(labelKey) : String(value) });
812
+ }
813
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: String(value) });
814
+ };
815
+ var isCheckBoxInput = (element) => element.type === "checkbox";
816
+ var isFileInput = (element) => element.type === "file";
817
+ var isDateObject = (value) => value instanceof Date;
818
+ var isNullOrUndefined = (value) => value == null;
819
+ const isObjectType = (value) => typeof value === "object";
820
+ var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value);
821
+ var getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : isFileInput(event.target) ? event.target.files : event.target.value : event;
822
+ var isNameInFieldArray = (names, name) => name.split(".").some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join(".")));
823
+ var isPlainObject = (tempObject) => {
824
+ const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
825
+ return isObject(prototypeCopy) && prototypeCopy.hasOwnProperty("isPrototypeOf");
826
+ };
827
+ var isWeb = typeof window !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof document !== "undefined";
828
+ function cloneObject(data) {
829
+ if (data instanceof Date) {
830
+ return new Date(data);
831
+ }
832
+ const isFileListInstance = typeof FileList !== "undefined" && data instanceof FileList;
833
+ if (isWeb && (data instanceof Blob || isFileListInstance)) {
834
+ return data;
835
+ }
836
+ const isArray = Array.isArray(data);
837
+ if (!isArray && !(isObject(data) && isPlainObject(data))) {
838
+ return data;
839
+ }
840
+ const copy = isArray ? [] : Object.create(Object.getPrototypeOf(data));
841
+ for (const key in data) {
842
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
843
+ copy[key] = cloneObject(data[key]);
844
+ }
845
+ }
846
+ return copy;
847
+ }
848
+ const EVENTS = {
849
+ BLUR: "blur",
850
+ FOCUS_OUT: "focusout",
851
+ CHANGE: "change",
852
+ SUBMIT: "submit",
853
+ TRIGGER: "trigger",
854
+ VALID: "valid"
855
+ };
856
+ const VALIDATION_MODE = {
857
+ onBlur: "onBlur",
858
+ onChange: "onChange",
859
+ onSubmit: "onSubmit",
860
+ onTouched: "onTouched",
861
+ all: "all"
862
+ };
863
+ const INPUT_VALIDATION_RULES = {
864
+ max: "max",
865
+ min: "min",
866
+ maxLength: "maxLength",
867
+ minLength: "minLength",
868
+ pattern: "pattern",
869
+ required: "required",
870
+ validate: "validate"
871
+ };
872
+ const ROOT_ERROR_TYPE = "root";
873
+ const PROTOTYPE_KEYWORDS = ["__proto__", "constructor", "prototype"];
874
+ const IS_KEY_RE = /^\w*$/;
875
+ var isKey = (value) => IS_KEY_RE.test(value);
876
+ var isUndefined = (val) => val === void 0;
877
+ const FIELD_PATH_RE = /[.[\]'"]/;
878
+ var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
879
+ var get = (object, path, defaultValue) => {
880
+ if (!path || !isObject(object)) {
881
+ return defaultValue;
882
+ }
883
+ const paths = isKey(path) ? [path] : stringToPath(path);
884
+ if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
885
+ return defaultValue;
886
+ }
887
+ const result = paths.reduce((result2, key) => {
888
+ return isNullOrUndefined(result2) ? void 0 : result2[key];
889
+ }, object);
890
+ return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result;
891
+ };
892
+ var isBoolean = (value) => typeof value === "boolean";
893
+ var isFunction = (value) => typeof value === "function";
608
894
  var set = (object, path, value) => {
609
895
  let index = -1;
610
896
  const tempPath = isKey(path) ? [path] : stringToPath(path);
@@ -2630,881 +2916,595 @@ function createFormControl(props = {}) {
2630
2916
  watch,
2631
2917
  setValue,
2632
2918
  setValues,
2633
- getValues,
2634
- reset,
2635
- resetField,
2636
- resetDefaultValues,
2637
- clearErrors,
2638
- unregister,
2639
- setError,
2640
- setFocus,
2641
- getFieldState
2642
- };
2643
- return {
2644
- ...methods,
2645
- formControl: methods
2646
- };
2647
- }
2648
- function useForm(props = {}) {
2649
- const _formControl = React__default.default.useRef(void 0);
2650
- const _values = React__default.default.useRef(void 0);
2651
- const _formControlProp = React__default.default.useRef(props.formControl);
2652
- const [formState, updateFormState] = React__default.default.useState(() => ({
2653
- ...cloneObject(DEFAULT_FORM_STATE),
2654
- isLoading: isFunction(props.defaultValues),
2655
- errors: props.errors || {},
2656
- disabled: props.disabled || false,
2657
- defaultValues: isFunction(props.defaultValues) ? void 0 : props.defaultValues
2658
- }));
2659
- if (!_formControl.current || props.formControl && _formControlProp.current !== props.formControl) {
2660
- _formControlProp.current = props.formControl;
2661
- if (props.formControl) {
2662
- _formControl.current = {
2663
- ...props.formControl,
2664
- formState
2665
- };
2666
- if (props.defaultValues && !isFunction(props.defaultValues)) {
2667
- props.formControl.reset(props.defaultValues, props.resetOptions);
2668
- }
2669
- } else {
2670
- const { formControl, ...rest } = createFormControl(props);
2671
- _formControl.current = {
2672
- ...rest,
2673
- formState
2674
- };
2675
- }
2676
- }
2677
- const control = _formControl.current.control;
2678
- control._options = props;
2679
- useIsomorphicLayoutEffect(() => {
2680
- const sub = control._subscribe({
2681
- formState: control._proxyFormState,
2682
- callback: () => updateFormState({
2683
- ...control._formState,
2684
- defaultValues: control._defaultValues
2685
- }),
2686
- reRenderRoot: true
2687
- });
2688
- updateFormState((data) => ({
2689
- ...data,
2690
- isReady: true
2691
- }));
2692
- control._formState.isReady = true;
2693
- return sub;
2694
- }, [control]);
2695
- React__default.default.useEffect(() => control._disableForm(props.disabled), [control, props.disabled]);
2696
- React__default.default.useEffect(() => {
2697
- if (props.mode) {
2698
- control._options.mode = props.mode;
2699
- }
2700
- if (props.reValidateMode) {
2701
- control._options.reValidateMode = props.reValidateMode;
2702
- }
2703
- }, [control, props.mode, props.reValidateMode]);
2704
- React__default.default.useEffect(() => {
2705
- if (props.errors) {
2706
- control._setErrors(props.errors);
2707
- control._focusError();
2708
- }
2709
- }, [control, props.errors]);
2710
- React__default.default.useEffect(() => {
2711
- props.shouldUnregister && control._subjects.state.next({
2712
- values: control._getWatch()
2713
- });
2714
- }, [control, props.shouldUnregister]);
2715
- React__default.default.useEffect(() => {
2716
- if (control._proxyFormState.isDirty) {
2717
- const isDirty = control._getDirty();
2718
- if (isDirty !== formState.isDirty) {
2719
- control._subjects.state.next({
2720
- isDirty
2721
- });
2722
- }
2723
- }
2724
- }, [control, formState.isDirty]);
2725
- React__default.default.useEffect(() => {
2726
- var _a;
2727
- if (props.values && !deepEqual(props.values, _values.current)) {
2728
- control._reset(props.values, {
2729
- keepFieldsRef: true,
2730
- ...control._options.resetOptions
2731
- });
2732
- if (!((_a = control._options.resetOptions) === null || _a === void 0 ? void 0 : _a.keepIsValid)) {
2733
- control._setValid();
2734
- }
2735
- _values.current = props.values;
2736
- updateFormState((state) => ({ ...state }));
2737
- } else {
2738
- control._resetDefaultValues();
2739
- }
2740
- }, [control, props.values]);
2741
- React__default.default.useEffect(() => {
2742
- if (!control._state.mount) {
2743
- control._setValid();
2744
- control._state.mount = true;
2745
- }
2746
- if (control._state.watch) {
2747
- control._state.watch = false;
2748
- control._subjects.state.next({ ...control._formState });
2749
- }
2750
- control._removeUnmounted();
2751
- });
2752
- _formControl.current.formState = React__default.default.useMemo(() => getProxyFormState(formState, control), [control, formState]);
2753
- return _formControl.current;
2754
- }
2755
- const jsonToText = (v) => v == null || v === "" ? "" : JSON.stringify(v, null, 2);
2756
- const JsonField = ({ field, f }) => {
2757
- const { t } = reactI18next.useTranslation();
2758
- const [raw, setRaw] = React.useState(() => jsonToText(f.value));
2759
- const [error, setError] = React.useState(null);
2760
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2761
- /* @__PURE__ */ jsxRuntime.jsx(
2762
- ui.Textarea,
2763
- {
2764
- rows: 6,
2765
- className: "font-mono",
2766
- placeholder: field.placeholder ? t(field.placeholder) : "[]",
2767
- value: raw,
2768
- disabled: field.readonly === true,
2769
- onBlur: f.onBlur,
2770
- onChange: (e) => {
2771
- const text2 = e.target.value;
2772
- setRaw(text2);
2773
- if (text2.trim() === "") {
2774
- setError(null);
2775
- f.onChange(null);
2776
- return;
2777
- }
2778
- try {
2779
- f.onChange(JSON.parse(text2));
2780
- setError(null);
2781
- } catch {
2782
- setError(t("integration.form.invalidJson"));
2783
- }
2784
- }
2785
- }
2786
- ),
2787
- error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: error })
2788
- ] });
2789
- };
2790
- const IntegrationField = ({
2791
- field,
2792
- control,
2793
- secretConfigured = false
2794
- }) => {
2795
- const { t } = reactI18next.useTranslation();
2796
- const enforceRequired = field.required && field.control !== "switch" && !(field.control === "secret" && secretConfigured);
2797
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
2798
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Label, { size: "small", weight: "plus", children: [
2799
- t(field.label),
2800
- field.required ? " *" : ""
2801
- ] }),
2802
- /* @__PURE__ */ jsxRuntime.jsx(
2803
- Controller,
2804
- {
2805
- name: field.name,
2806
- control,
2807
- rules: {
2808
- required: enforceRequired ? t("integration.form.required", { field: t(field.label) }) : false
2809
- },
2810
- render: ({ field: f, fieldState }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2811
- renderControl(field, f, secretConfigured, t),
2812
- fieldState.error?.message && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: fieldState.error.message })
2813
- ] })
2814
- }
2815
- ),
2816
- field.hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: t(field.hint) })
2817
- ] });
2818
- };
2819
- function renderControl(field, f, secretConfigured, t) {
2820
- const placeholder = field.placeholder ? t(field.placeholder) : void 0;
2821
- const disabled = field.readonly === true;
2822
- switch (field.control) {
2823
- case "switch":
2824
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, { checked: !!f.value, onCheckedChange: f.onChange, disabled });
2825
- case "select": {
2826
- const declared = field.options ?? [];
2827
- return /* @__PURE__ */ jsxRuntime.jsxs(
2828
- ui.Select,
2829
- {
2830
- value: f.value == null ? "" : String(f.value),
2831
- onValueChange: (next) => f.onChange(declared.find((o) => String(o) === next) ?? next),
2832
- disabled,
2833
- children: [
2834
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder }) }),
2835
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: declared.map((o) => {
2836
- const labelKey = field.optionLabels?.[String(o)];
2837
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: String(o), children: labelKey ? t(labelKey) : String(o) }, String(o));
2838
- }) })
2839
- ]
2840
- }
2841
- );
2842
- }
2843
- case "number":
2844
- return /* @__PURE__ */ jsxRuntime.jsx(
2845
- ui.Input,
2846
- {
2847
- type: "number",
2848
- placeholder,
2849
- value: f.value ?? "",
2850
- disabled,
2851
- onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
2852
- }
2853
- );
2854
- case "url":
2855
- return /* @__PURE__ */ jsxRuntime.jsx(
2856
- ui.Input,
2857
- {
2858
- type: "url",
2859
- placeholder,
2860
- value: f.value ?? "",
2861
- disabled,
2862
- onChange: f.onChange
2863
- }
2864
- );
2865
- case "secret":
2866
- return /* @__PURE__ */ jsxRuntime.jsx(
2867
- ui.Input,
2868
- {
2869
- type: "password",
2870
- placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
2871
- value: f.value ?? "",
2872
- disabled,
2873
- onChange: f.onChange,
2874
- className: !f.value ? "[&~div]:hidden" : void 0
2875
- }
2876
- );
2877
- case "json":
2878
- return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { field, f });
2879
- default:
2880
- return /* @__PURE__ */ jsxRuntime.jsx(
2881
- ui.Input,
2882
- {
2883
- placeholder,
2884
- value: f.value ?? "",
2885
- disabled,
2886
- onChange: f.onChange
2887
- }
2888
- );
2889
- }
2890
- }
2891
- function isFieldVisible(field, values) {
2892
- const rule = field.visibleWhen;
2893
- if (!rule) return true;
2894
- return values[rule.field] === rule.equals;
2895
- }
2896
- function sectionDefaults(section, values) {
2897
- const out = {};
2898
- for (const f of section.fields) {
2899
- if (f.secret || f.control === "secret") out[f.name] = "";
2900
- else if (f.control === "switch") out[f.name] = values?.[f.name] ?? f.default ?? false;
2901
- else if (f.control === "json") out[f.name] = values?.[f.name] ?? f.default;
2902
- else out[f.name] = values?.[f.name] ?? f.default ?? "";
2903
- }
2904
- return out;
2905
- }
2906
- const EditSectionDrawer = ({
2907
- open,
2908
- onOpenChange,
2909
- providerId,
2910
- section,
2911
- values,
2912
- configuredSecrets = []
2913
- }) => {
2914
- const { t } = reactI18next.useTranslation();
2915
- const qc = reactQuery.useQueryClient();
2916
- const configured = new Set(configuredSecrets);
2917
- const [defaults] = React.useState(() => sectionDefaults(section, values));
2918
- const form = useForm({ values: defaults });
2919
- const current = form.watch();
2920
- const visibilityValues = { ...values, ...current };
2921
- const save = reactQuery.useMutation({
2922
- mutationFn: (sectionValues) => sdk.client.fetch(`/admin/integrations/${providerId}`, {
2923
- method: "POST",
2924
- // Only this section's values + its id — the server validates just this section and
2925
- // merges over the rest of the stored config (keeping blank/untouched secrets).
2926
- body: { section_id: section.id, values: sectionValues }
2927
- }),
2928
- onSuccess: () => {
2929
- ui.toast.success(t("integration.toast.saved"));
2930
- qc.invalidateQueries({ queryKey: ["integration", providerId] });
2931
- qc.invalidateQueries({ queryKey: ["integration-overview"] });
2932
- onOpenChange(false);
2933
- },
2934
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.saveFailed"))
2935
- });
2936
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
2937
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: t(section.title) }) }),
2938
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: section.fields.filter((f) => isFieldVisible(f, visibilityValues)).map((f) => /* @__PURE__ */ jsxRuntime.jsx(
2939
- IntegrationField,
2940
- {
2941
- field: f,
2942
- control: form.control,
2943
- secretConfigured: configured.has(f.name)
2944
- },
2945
- f.name
2946
- )) }),
2947
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
2948
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", disabled: save.isPending, children: t("integration.actions.cancel") }) }),
2949
- /* @__PURE__ */ jsxRuntime.jsx(
2950
- ui.Button,
2951
- {
2952
- size: "small",
2953
- isLoading: save.isPending,
2954
- onClick: form.handleSubmit((v) => save.mutate(v)),
2955
- children: t("integration.actions.save")
2956
- }
2957
- )
2958
- ] })
2959
- ] }) });
2960
- };
2961
- const SURFACE = {
2962
- green: "bg-ui-bg-subtle border-ui-border-base",
2963
- grey: "bg-ui-bg-subtle border-ui-border-base",
2964
- orange: "bg-ui-tag-orange-bg border-ui-tag-orange-border",
2965
- red: "bg-ui-tag-red-bg border-ui-tag-red-border"
2966
- };
2967
- const RECHECK = {
2968
- green: void 0,
2969
- grey: void 0,
2970
- orange: "hover:bg-ui-tag-orange-bg-hover active:bg-ui-tag-orange-bg-hover",
2971
- red: "hover:bg-ui-tag-red-bg-hover active:bg-ui-tag-red-bg-hover"
2972
- };
2973
- const LicenseSummary = ({ entry }) => {
2974
- const { t } = reactI18next.useTranslation();
2975
- const view = useDescribeLicense()(entry);
2976
- const details = useLicenseDetails()(view);
2977
- const recheck = useRecheckLicense();
2978
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.clx("flex items-center gap-x-4 rounded-lg border px-4 py-3", SURFACE[view.tone]), children: [
2979
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-y-0.5", children: [
2980
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-1", children: [
2981
- /* @__PURE__ */ jsxRuntime.jsx(LicenseSquare, { tone: view.tone }),
2982
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", weight: "plus", className: "text-ui-fg-base", children: t(view.title.key, view.title.values) })
2983
- ] }),
2984
- details && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: details })
2985
- ] }),
2986
- entry.state !== "pending" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-x-2", children: [
2987
- /* @__PURE__ */ jsxRuntime.jsx(
2988
- ui.Button,
2989
- {
2990
- size: "small",
2991
- variant: "transparent",
2992
- className: RECHECK[view.tone],
2993
- isLoading: recheck.isPending,
2994
- disabled: recheck.isPending,
2995
- onClick: () => recheck.mutate(),
2996
- children: t("integration.license.recheck")
2997
- }
2998
- ),
2999
- view.action && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: view.action.href, target: "_blank", rel: "noopener noreferrer", children: [
3000
- t(view.action.key),
3001
- /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
3002
- ] }) })
3003
- ] })
3004
- ] });
3005
- };
3006
- const Row = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4 [overflow-wrap:anywhere]", children: [
3007
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
3008
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-2", children })
3009
- ] });
3010
- const EditPage = () => {
3011
- const { t } = reactI18next.useTranslation();
3012
- const { provider_id } = reactRouterDom.useParams();
3013
- const qc = reactQuery.useQueryClient();
3014
- const navigate = reactRouterDom.useNavigate();
3015
- const prompt = ui.usePrompt();
3016
- const [editingSection, setEditingSection] = React.useState(null);
3017
- const { data, isLoading } = reactQuery.useQuery({
3018
- queryKey: ["integration", provider_id],
3019
- queryFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`)
3020
- });
3021
- const { data: licenseData } = useLicenseStatus();
3022
- const descriptor = data?.descriptor;
3023
- const record = data?.integration;
3024
- const isConfigured = !!record;
3025
- const isComplete = data?.is_complete ?? false;
3026
- const invalidate = () => {
3027
- qc.invalidateQueries({ queryKey: ["integration", provider_id] });
3028
- qc.invalidateQueries({ queryKey: ["integration-overview"] });
3029
- };
3030
- const test = reactQuery.useMutation({
3031
- mutationFn: () => sdk.client.fetch(
3032
- `/admin/integrations/${provider_id}/test-connection`,
3033
- { method: "POST" }
3034
- ),
3035
- onSuccess: (r) => {
3036
- r.status === "passed" ? ui.toast.success(t("integration.toast.connectionOk")) : ui.toast.warning(r.message ?? r.status);
3037
- invalidate();
3038
- },
3039
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.testFailed"))
3040
- });
3041
- const toggleEnabled = reactQuery.useMutation({
3042
- mutationFn: (next) => sdk.client.fetch(`/admin/integrations/${provider_id}/enable`, {
3043
- method: "POST",
3044
- body: { is_enabled: next }
3045
- }),
3046
- onSuccess: (_d, next) => {
3047
- ui.toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
3048
- invalidate();
3049
- },
3050
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.updateFailed"))
3051
- });
3052
- const remove = reactQuery.useMutation({
3053
- mutationFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`, { method: "DELETE" }),
3054
- onSuccess: () => {
3055
- ui.toast.success(t("integration.toast.deleted"));
3056
- invalidate();
3057
- navigate("/settings/integrations");
3058
- },
3059
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.deleteFailed"))
3060
- });
3061
- const onDelete = async () => {
3062
- const confirmed = await prompt({
3063
- title: t("integration.delete.title"),
3064
- description: t("integration.delete.description", {
3065
- name: descriptor ? t(descriptor.displayName) : ""
3066
- }),
3067
- confirmText: t("integration.actions.delete"),
3068
- cancelText: t("integration.actions.cancel")
3069
- });
3070
- if (confirmed) remove.mutate();
3071
- };
3072
- if (isLoading) {
3073
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) });
3074
- }
3075
- if (!descriptor) {
3076
- throw new Medusa.FetchError(
3077
- `Integration with provider_id: ${provider_id} was not found`,
3078
- "There is no page at this address",
3079
- 404
3080
- );
3081
- }
3082
- const version = data?.version ?? null;
3083
- const author = data?.author ?? null;
3084
- const authorUrl = data?.author_url ?? null;
3085
- const lifecycle = !isConfigured ? { color: "grey", label: t("integration.status.notConfigured") } : !isComplete ? { color: "orange", label: t("integration.status.incomplete") } : record.is_enabled ? { color: "green", label: t("integration.status.enabled") } : { color: "grey", label: t("integration.status.disabled") };
3086
- const connection = descriptor.hasTestConnection ? record?.last_test_status === "passed" ? { color: "green", label: t("integration.status.connected") } : record?.last_test_status === "failed" ? { color: "red", label: t("integration.status.connectionFailed") } : null : null;
3087
- const license = licenseData?.packages.find((entry) => entry.identifiers.includes(descriptor.identifier));
3088
- const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
3089
- const configuredSecrets = new Set(record?.configured_secrets ?? []);
3090
- const sectionData = {
3091
- providerId: provider_id,
3092
- identifier: descriptor.identifier,
3093
- values: record?.values ?? {},
3094
- isComplete
3095
- };
3096
- const renderSection = (section) => /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: section.id, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3097
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
3098
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t(section.title) }),
3099
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
3100
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
3101
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: () => setEditingSection(section.id), children: [
3102
- /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
3103
- t("integration.actions.edit")
3104
- ] }) })
3105
- ] })
3106
- ] }),
3107
- section.fields.filter((f) => isFieldVisible(f, record?.values ?? {})).map((f) => /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t(f.label), children: /* @__PURE__ */ jsxRuntime.jsx(
3108
- IntegrationFieldValue,
3109
- {
3110
- field: f,
3111
- value: record?.values?.[f.name] ?? f.default,
3112
- secretConfigured: configuredSecrets.has(f.name)
3113
- }
3114
- ) }, f.name))
3115
- ] }, section.id) });
3116
- const mainSections = descriptor.sections.filter((s) => s.column !== "side");
3117
- const sideSections = descriptor.sections.filter((s) => s.column === "side");
3118
- const main = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3119
- /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3120
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
3121
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3122
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
3123
- /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
3124
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
3125
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3126
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: t(descriptor.displayName) }),
3127
- provider_id && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toModuleProviderId(provider_id, descriptor.category) })
3128
- ] }),
3129
- version && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
3130
- t("integration.meta.version", { version }),
3131
- author && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3132
- " • ",
3133
- t("integration.meta.authorPrefix"),
3134
- " ",
3135
- authorUrl ? /* @__PURE__ */ jsxRuntime.jsx(
3136
- "a",
3137
- {
3138
- href: authorUrl,
3139
- target: "_blank",
3140
- rel: "noreferrer",
3141
- className: "text-ui-fg-interactive",
3142
- children: author
3143
- }
3144
- ) : author
3145
- ] })
3146
- ] })
3147
- ] })
3148
- ] }),
3149
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3150
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
3151
- connection && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: connection.color, children: connection.label }),
3152
- isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
3153
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
3154
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
3155
- descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
3156
- isConfigured && /* @__PURE__ */ jsxRuntime.jsx(
3157
- ui.DropdownMenu.Item,
3158
- {
3159
- disabled: toggleEnabled.isPending,
3160
- onClick: () => toggleEnabled.mutate(!record.is_enabled),
3161
- children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
3162
- }
3163
- ),
3164
- isConfigured && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
3165
- isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
3166
- /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
3167
- t("integration.actions.delete")
3168
- ] })
3169
- ] })
3170
- ] })
3171
- ] })
3172
- ] }),
3173
- descriptor.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) }),
3174
- license && /* @__PURE__ */ jsxRuntime.jsx(LicenseSummary, { entry: license })
3175
- ] }),
3176
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("integration.labels.category"), children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: t(`integration.categories.${descriptor.category}`) }) })
3177
- ] }) }),
3178
- mainSections.map(renderSection),
3179
- editing && /* @__PURE__ */ jsxRuntime.jsx(
3180
- EditSectionDrawer,
3181
- {
3182
- open: true,
3183
- onOpenChange: (open) => !open && setEditingSection(null),
3184
- providerId: provider_id,
3185
- section: editing,
3186
- values: record?.values ?? {},
3187
- configuredSecrets: record?.configured_secrets ?? []
3188
- },
3189
- editing.id
3190
- )
3191
- ] });
3192
- const side = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sideSections.map(renderSection) });
3193
- return /* @__PURE__ */ jsxRuntime.jsx(
3194
- components.LayoutComposer,
3195
- {
3196
- widgetsZonePrefix: `gorgo.integration.${descriptor.identifier}`,
3197
- preferredLayoutId: descriptor.preferredLayoutId,
3198
- sections: { main, side },
3199
- data: sectionData,
3200
- hasOutlet: false
3201
- }
3202
- );
3203
- };
3204
- const IntegrationBreadcrumb = ({ params }) => {
3205
- const { t } = reactI18next.useTranslation();
3206
- const providerId = params.provider_id;
3207
- const { data } = reactQuery.useQuery({
3208
- queryKey: ["integration", providerId],
3209
- queryFn: () => sdk.client.fetch(`/admin/integrations/${providerId}`),
3210
- enabled: !!providerId
3211
- });
3212
- const descriptor = data?.descriptor;
3213
- if (!descriptor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
3214
- const name = t(descriptor.displayName);
3215
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3216
- name + " ",
3217
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: providerId })
3218
- ] });
3219
- };
3220
- const handle$1 = {
3221
- breadcrumb: ({ params }) => /* @__PURE__ */ jsxRuntime.jsx(IntegrationBreadcrumb, { params })
3222
- };
3223
- function isSafeHref(raw) {
3224
- try {
3225
- return new URL(raw).protocol === "https:";
3226
- } catch {
3227
- return false;
3228
- }
3229
- }
3230
- const INLINE_RE = /`([^`\n]+)`|\[([^\]\n]*)\]\(([^)\s]+)\)|\*\*([^*\n]+)\*\*|\*([^*\n]+)\*|_([^_\n]+)_/;
3231
- const MAX_DEPTH = 3;
3232
- function parseInline(src, depth = 0) {
3233
- if (!src) return [];
3234
- if (depth >= MAX_DEPTH) return [{ type: "text", value: src }];
3235
- const out = [];
3236
- const re = new RegExp(INLINE_RE, "g");
3237
- let last = 0;
3238
- let m;
3239
- while ((m = re.exec(src)) !== null) {
3240
- const [full, code, linkText, linkHref, strong, star, underscore] = m;
3241
- if (m.index > last) out.push({ type: "text", value: src.slice(last, m.index) });
3242
- last = m.index + full.length;
3243
- if (code !== void 0) {
3244
- out.push({ type: "code", value: code });
3245
- } else if (linkHref !== void 0) {
3246
- const isImage = m.index > 0 && src[m.index - 1] === "!";
3247
- if (!isImage && isSafeHref(linkHref)) {
3248
- out.push({ type: "link", href: linkHref, children: parseInline(linkText, depth + 1) });
3249
- } else {
3250
- out.push({ type: "text", value: full });
2919
+ getValues,
2920
+ reset,
2921
+ resetField,
2922
+ resetDefaultValues,
2923
+ clearErrors,
2924
+ unregister,
2925
+ setError,
2926
+ setFocus,
2927
+ getFieldState
2928
+ };
2929
+ return {
2930
+ ...methods,
2931
+ formControl: methods
2932
+ };
2933
+ }
2934
+ function useForm(props = {}) {
2935
+ const _formControl = React__default.default.useRef(void 0);
2936
+ const _values = React__default.default.useRef(void 0);
2937
+ const _formControlProp = React__default.default.useRef(props.formControl);
2938
+ const [formState, updateFormState] = React__default.default.useState(() => ({
2939
+ ...cloneObject(DEFAULT_FORM_STATE),
2940
+ isLoading: isFunction(props.defaultValues),
2941
+ errors: props.errors || {},
2942
+ disabled: props.disabled || false,
2943
+ defaultValues: isFunction(props.defaultValues) ? void 0 : props.defaultValues
2944
+ }));
2945
+ if (!_formControl.current || props.formControl && _formControlProp.current !== props.formControl) {
2946
+ _formControlProp.current = props.formControl;
2947
+ if (props.formControl) {
2948
+ _formControl.current = {
2949
+ ...props.formControl,
2950
+ formState
2951
+ };
2952
+ if (props.defaultValues && !isFunction(props.defaultValues)) {
2953
+ props.formControl.reset(props.defaultValues, props.resetOptions);
3251
2954
  }
3252
- } else if (strong !== void 0) {
3253
- out.push({ type: "strong", children: parseInline(strong, depth + 1) });
3254
2955
  } else {
3255
- out.push({ type: "em", children: parseInline(star ?? underscore, depth + 1) });
2956
+ const { formControl, ...rest } = createFormControl(props);
2957
+ _formControl.current = {
2958
+ ...rest,
2959
+ formState
2960
+ };
3256
2961
  }
3257
2962
  }
3258
- if (last < src.length) out.push({ type: "text", value: src.slice(last) });
3259
- return out;
3260
- }
3261
- const MD_MAX_CHARS = 8e3;
3262
- const MD_MAX_BLOCKS = 200;
3263
- const FENCE_RE = /^```(.*)$/;
3264
- const HEADING_RE = /^(#{1,3})\s+(.*)$/;
3265
- const HR_RE = /^-{3,}\s*$/;
3266
- const UL_RE = /^[-*]\s+(.*)$/;
3267
- const OL_RE = /^\d+\.\s+(.*)$/;
3268
- function fenceLabel(info) {
3269
- const first = info.trim().split(/\s+/)[0] ?? "";
3270
- return first.replace(/[^a-zA-Z0-9._/-]/g, "").slice(0, 40) || null;
3271
- }
3272
- function parseMarkdown(src) {
3273
- if (typeof src !== "string" || !src) return [];
3274
- if (src.length > MD_MAX_CHARS) return [{ type: "truncated" }];
3275
- const lines = src.replace(/\r\n?/g, "\n").split("\n");
3276
- const blocks = [];
3277
- let para = [];
3278
- const flushPara = () => {
3279
- if (!para.length) return;
3280
- blocks.push({ type: "paragraph", children: parseInline(para.join(" ")) });
3281
- para = [];
3282
- };
3283
- for (let i = 0; i < lines.length; i++) {
3284
- const line = lines[i];
3285
- const fence = FENCE_RE.exec(line);
3286
- if (fence) {
3287
- flushPara();
3288
- const body = [];
3289
- i++;
3290
- while (i < lines.length && !FENCE_RE.test(lines[i])) body.push(lines[i++]);
3291
- blocks.push({ type: "codeBlock", label: fenceLabel(fence[1]), value: body.join("\n") });
3292
- continue;
2963
+ const control = _formControl.current.control;
2964
+ control._options = props;
2965
+ useIsomorphicLayoutEffect(() => {
2966
+ const sub = control._subscribe({
2967
+ formState: control._proxyFormState,
2968
+ callback: () => updateFormState({
2969
+ ...control._formState,
2970
+ defaultValues: control._defaultValues
2971
+ }),
2972
+ reRenderRoot: true
2973
+ });
2974
+ updateFormState((data) => ({
2975
+ ...data,
2976
+ isReady: true
2977
+ }));
2978
+ control._formState.isReady = true;
2979
+ return sub;
2980
+ }, [control]);
2981
+ React__default.default.useEffect(() => control._disableForm(props.disabled), [control, props.disabled]);
2982
+ React__default.default.useEffect(() => {
2983
+ if (props.mode) {
2984
+ control._options.mode = props.mode;
3293
2985
  }
3294
- if (!line.trim()) {
3295
- flushPara();
3296
- continue;
2986
+ if (props.reValidateMode) {
2987
+ control._options.reValidateMode = props.reValidateMode;
3297
2988
  }
3298
- if (HR_RE.test(line)) {
3299
- flushPara();
3300
- blocks.push({ type: "hr" });
3301
- continue;
2989
+ }, [control, props.mode, props.reValidateMode]);
2990
+ React__default.default.useEffect(() => {
2991
+ if (props.errors) {
2992
+ control._setErrors(props.errors);
2993
+ control._focusError();
3302
2994
  }
3303
- const heading = HEADING_RE.exec(line);
3304
- if (heading) {
3305
- flushPara();
3306
- blocks.push({
3307
- type: "heading",
3308
- level: heading[1].length + 1,
3309
- children: parseInline(heading[2])
2995
+ }, [control, props.errors]);
2996
+ React__default.default.useEffect(() => {
2997
+ props.shouldUnregister && control._subjects.state.next({
2998
+ values: control._getWatch()
2999
+ });
3000
+ }, [control, props.shouldUnregister]);
3001
+ React__default.default.useEffect(() => {
3002
+ if (control._proxyFormState.isDirty) {
3003
+ const isDirty = control._getDirty();
3004
+ if (isDirty !== formState.isDirty) {
3005
+ control._subjects.state.next({
3006
+ isDirty
3007
+ });
3008
+ }
3009
+ }
3010
+ }, [control, formState.isDirty]);
3011
+ React__default.default.useEffect(() => {
3012
+ var _a;
3013
+ if (props.values && !deepEqual(props.values, _values.current)) {
3014
+ control._reset(props.values, {
3015
+ keepFieldsRef: true,
3016
+ ...control._options.resetOptions
3310
3017
  });
3311
- continue;
3018
+ if (!((_a = control._options.resetOptions) === null || _a === void 0 ? void 0 : _a.keepIsValid)) {
3019
+ control._setValid();
3020
+ }
3021
+ _values.current = props.values;
3022
+ updateFormState((state) => ({ ...state }));
3023
+ } else {
3024
+ control._resetDefaultValues();
3312
3025
  }
3313
- const ul = UL_RE.exec(line);
3314
- const ol = ul ? null : OL_RE.exec(line);
3315
- if (ul || ol) {
3316
- flushPara();
3317
- const ordered = !!ol;
3318
- const itemRe = ordered ? OL_RE : UL_RE;
3319
- const items = [];
3320
- while (i < lines.length) {
3321
- const item = itemRe.exec(lines[i]);
3322
- if (!item) break;
3323
- items.push(parseInline(item[1]));
3324
- i++;
3026
+ }, [control, props.values]);
3027
+ React__default.default.useEffect(() => {
3028
+ if (!control._state.mount) {
3029
+ control._setValid();
3030
+ control._state.mount = true;
3031
+ }
3032
+ if (control._state.watch) {
3033
+ control._state.watch = false;
3034
+ control._subjects.state.next({ ...control._formState });
3035
+ }
3036
+ control._removeUnmounted();
3037
+ });
3038
+ _formControl.current.formState = React__default.default.useMemo(() => getProxyFormState(formState, control), [control, formState]);
3039
+ return _formControl.current;
3040
+ }
3041
+ const jsonToText = (v) => v == null || v === "" ? "" : JSON.stringify(v, null, 2);
3042
+ const JsonField = ({ field, f }) => {
3043
+ const { t } = reactI18next.useTranslation();
3044
+ const [raw, setRaw] = React.useState(() => jsonToText(f.value));
3045
+ const [error, setError] = React.useState(null);
3046
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3047
+ /* @__PURE__ */ jsxRuntime.jsx(
3048
+ ui.Textarea,
3049
+ {
3050
+ rows: 6,
3051
+ className: "font-mono",
3052
+ placeholder: field.placeholder ? t(field.placeholder) : "[]",
3053
+ value: raw,
3054
+ disabled: field.readonly === true,
3055
+ onBlur: f.onBlur,
3056
+ onChange: (e) => {
3057
+ const text2 = e.target.value;
3058
+ setRaw(text2);
3059
+ if (text2.trim() === "") {
3060
+ setError(null);
3061
+ f.onChange(null);
3062
+ return;
3063
+ }
3064
+ try {
3065
+ f.onChange(JSON.parse(text2));
3066
+ setError(null);
3067
+ } catch {
3068
+ setError(t("integration.form.invalidJson"));
3069
+ }
3070
+ }
3325
3071
  }
3326
- i--;
3327
- blocks.push({ type: "list", ordered, items });
3328
- continue;
3072
+ ),
3073
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: error })
3074
+ ] });
3075
+ };
3076
+ const IntegrationField = ({
3077
+ field,
3078
+ control,
3079
+ secretConfigured = false
3080
+ }) => {
3081
+ const { t } = reactI18next.useTranslation();
3082
+ const enforceRequired = field.required && field.control !== "switch" && !(field.control === "secret" && secretConfigured);
3083
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
3084
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Label, { size: "small", weight: "plus", children: [
3085
+ t(field.label),
3086
+ field.required ? " *" : ""
3087
+ ] }),
3088
+ /* @__PURE__ */ jsxRuntime.jsx(
3089
+ Controller,
3090
+ {
3091
+ name: field.name,
3092
+ control,
3093
+ rules: {
3094
+ required: enforceRequired ? t("integration.form.required", { field: t(field.label) }) : false
3095
+ },
3096
+ render: ({ field: f, fieldState }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3097
+ renderControl(field, f, secretConfigured, t),
3098
+ fieldState.error?.message && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: fieldState.error.message })
3099
+ ] })
3100
+ }
3101
+ ),
3102
+ field.hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: t(field.hint) })
3103
+ ] });
3104
+ };
3105
+ function renderControl(field, f, secretConfigured, t) {
3106
+ const placeholder = field.placeholder ? t(field.placeholder) : void 0;
3107
+ const disabled = field.readonly === true;
3108
+ switch (field.control) {
3109
+ case "switch":
3110
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, { checked: !!f.value, onCheckedChange: f.onChange, disabled });
3111
+ case "select": {
3112
+ const declared = field.options ?? [];
3113
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3114
+ ui.Select,
3115
+ {
3116
+ value: f.value == null ? "" : String(f.value),
3117
+ onValueChange: (next) => f.onChange(declared.find((o) => String(o) === next) ?? next),
3118
+ disabled,
3119
+ children: [
3120
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder }) }),
3121
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: declared.map((o) => {
3122
+ const labelKey = field.optionLabels?.[String(o)];
3123
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: String(o), children: labelKey ? t(labelKey) : String(o) }, String(o));
3124
+ }) })
3125
+ ]
3126
+ }
3127
+ );
3329
3128
  }
3330
- para.push(line);
3331
- }
3332
- flushPara();
3333
- const weight = blocks.reduce((n, b) => n + (b.type === "list" ? b.items.length : 1), 0);
3334
- return weight > MD_MAX_BLOCKS ? [{ type: "truncated" }] : blocks;
3335
- }
3336
- const Inline = ({ nodes }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node, i) => {
3337
- switch (node.type) {
3338
- case "text":
3339
- return /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.value }, i);
3340
- case "code":
3341
- return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "txt-compact-small bg-ui-bg-component rounded px-1 py-0.5", children: node.value }, i);
3342
- case "strong":
3343
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-compact-small-plus", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
3344
- case "em":
3345
- return /* @__PURE__ */ jsxRuntime.jsx("em", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
3346
- case "link":
3129
+ case "number":
3347
3130
  return /* @__PURE__ */ jsxRuntime.jsx(
3348
- "a",
3131
+ ui.Input,
3349
3132
  {
3350
- href: node.href,
3351
- target: "_blank",
3352
- rel: "noopener noreferrer",
3353
- className: "text-ui-fg-interactive hover:underline",
3354
- children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children })
3355
- },
3356
- i
3133
+ type: "number",
3134
+ placeholder,
3135
+ value: f.value ?? "",
3136
+ disabled,
3137
+ onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
3138
+ }
3357
3139
  );
3358
- }
3359
- }) });
3360
- const Block = ({ block, truncatedLabel }) => {
3361
- switch (block.type) {
3362
- case "heading":
3363
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: block.level === 2 ? "h2" : "h3", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
3364
- case "paragraph":
3365
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
3366
- case "list": {
3367
- const List = block.ordered ? "ol" : "ul";
3140
+ case "url":
3368
3141
  return /* @__PURE__ */ jsxRuntime.jsx(
3369
- List,
3142
+ ui.Input,
3370
3143
  {
3371
- className: `text-ui-fg-subtle txt-small flex flex-col gap-y-1 pl-5 ${block.ordered ? "list-decimal" : "list-disc"}`,
3372
- children: block.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: item }) }, i))
3144
+ type: "url",
3145
+ placeholder,
3146
+ value: f.value ?? "",
3147
+ disabled,
3148
+ onChange: f.onChange
3149
+ }
3150
+ );
3151
+ case "secret":
3152
+ return /* @__PURE__ */ jsxRuntime.jsx(
3153
+ ui.Input,
3154
+ {
3155
+ type: "password",
3156
+ placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
3157
+ value: f.value ?? "",
3158
+ disabled,
3159
+ onChange: f.onChange,
3160
+ className: !f.value ? "[&~div]:hidden" : void 0
3161
+ }
3162
+ );
3163
+ case "json":
3164
+ return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { field, f });
3165
+ default:
3166
+ return /* @__PURE__ */ jsxRuntime.jsx(
3167
+ ui.Input,
3168
+ {
3169
+ placeholder,
3170
+ value: f.value ?? "",
3171
+ disabled,
3172
+ onChange: f.onChange
3373
3173
  }
3374
3174
  );
3375
- }
3376
- case "codeBlock":
3377
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-ui-bg-subtle flex flex-col gap-y-2 rounded-md px-3 pb-1 pt-2", children: [
3378
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-2", children: [
3379
- block.label ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-subtle", children: block.label }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
3380
- /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: block.value })
3381
- ] }),
3382
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "txt-compact-small text-ui-fg-subtle overflow-x-auto whitespace-pre pb-2", children: block.value })
3383
- ] });
3384
- case "hr":
3385
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-ui-border-base border-t" });
3386
- case "truncated":
3387
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: truncatedLabel });
3388
3175
  }
3389
- };
3390
- const Markdown = ({
3391
- source,
3392
- truncatedLabel
3393
- }) => {
3394
- const blocks = React.useMemo(() => parseMarkdown(source), [source]);
3395
- if (!blocks.length) return null;
3396
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-4", children: blocks.map((block, i) => /* @__PURE__ */ jsxRuntime.jsx(Block, { block, truncatedLabel }, i)) });
3397
- };
3398
- const PUBLISH_URL = "https://docs.gorgojs.com/medusa-modules/integration";
3399
- const API_URL = "https://gorgojs.com";
3400
- const iconSrc = (icon) => icon.startsWith("data:") ? icon : API_URL + icon;
3401
- const InstallModal = ({
3402
- item,
3403
- onOpenChange
3176
+ }
3177
+ function isFieldVisible(field, values) {
3178
+ const rule = field.visibleWhen;
3179
+ if (!rule) return true;
3180
+ return values[rule.field] === rule.equals;
3181
+ }
3182
+ function sectionDefaults(section, values) {
3183
+ const out = {};
3184
+ for (const f of section.fields) {
3185
+ if (f.secret || f.control === "secret") out[f.name] = "";
3186
+ else if (f.control === "switch") out[f.name] = values?.[f.name] ?? f.default ?? false;
3187
+ else if (f.control === "json") out[f.name] = values?.[f.name] ?? f.default;
3188
+ else out[f.name] = values?.[f.name] ?? f.default ?? "";
3189
+ }
3190
+ return out;
3191
+ }
3192
+ const EditSectionDrawer = ({
3193
+ open,
3194
+ onOpenChange,
3195
+ providerId,
3196
+ section,
3197
+ values,
3198
+ configuredSecrets = []
3404
3199
  }) => {
3405
- const { t, i18n } = reactI18next.useTranslation();
3406
- const lang = i18n.language.split("-")[0] === "ru" ? "ru" : "en";
3407
- const source = item?.docsSnippet?.[lang] || item?.docsSnippet?.en || item?.docsSnippet?.ru || "";
3408
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
3409
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3410
- item && /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label, size: "small" }),
3411
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: item?.label })
3412
- ] }) }) }),
3413
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: source.trim() ? /* @__PURE__ */ jsxRuntime.jsx(Markdown, { source, truncatedLabel: t("integration.browse.install_truncated") }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.install_empty") }) }),
3200
+ const { t } = reactI18next.useTranslation();
3201
+ const qc = reactQuery.useQueryClient();
3202
+ const configured = new Set(configuredSecrets);
3203
+ const [defaults] = React.useState(() => sectionDefaults(section, values));
3204
+ const form = useForm({ values: defaults });
3205
+ const current = form.watch();
3206
+ const visibilityValues = { ...values, ...current };
3207
+ const save = reactQuery.useMutation({
3208
+ mutationFn: (sectionValues) => sdk.client.fetch(`/admin/integrations/${providerId}`, {
3209
+ method: "POST",
3210
+ // Only this section's values + its id — the server validates just this section and
3211
+ // merges over the rest of the stored config (keeping blank/untouched secrets).
3212
+ body: { section_id: section.id, values: sectionValues }
3213
+ }),
3214
+ onSuccess: () => {
3215
+ ui.toast.success(t("integration.toast.saved"));
3216
+ qc.invalidateQueries({ queryKey: ["integration", providerId] });
3217
+ qc.invalidateQueries({ queryKey: ["integration-overview"] });
3218
+ onOpenChange(false);
3219
+ },
3220
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.saveFailed"))
3221
+ });
3222
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
3223
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: t(section.title) }) }),
3224
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: section.fields.filter((f) => isFieldVisible(f, visibilityValues)).map((f) => /* @__PURE__ */ jsxRuntime.jsx(
3225
+ IntegrationField,
3226
+ {
3227
+ field: f,
3228
+ control: form.control,
3229
+ secretConfigured: configured.has(f.name)
3230
+ },
3231
+ f.name
3232
+ )) }),
3414
3233
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
3415
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }),
3416
- item?.docsUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "primary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: item.docsUrl, target: "_blank", rel: "noopener noreferrer", children: [
3417
- t("integration.browse.install_docs"),
3234
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", disabled: save.isPending, children: t("integration.actions.cancel") }) }),
3235
+ /* @__PURE__ */ jsxRuntime.jsx(
3236
+ ui.Button,
3237
+ {
3238
+ size: "small",
3239
+ isLoading: save.isPending,
3240
+ onClick: form.handleSubmit((v) => save.mutate(v)),
3241
+ children: t("integration.actions.save")
3242
+ }
3243
+ )
3244
+ ] })
3245
+ ] }) });
3246
+ };
3247
+ const SURFACE = {
3248
+ green: "bg-ui-bg-subtle border-ui-border-base",
3249
+ grey: "bg-ui-bg-subtle border-ui-border-base",
3250
+ orange: "bg-ui-tag-orange-bg border-ui-tag-orange-border",
3251
+ red: "bg-ui-tag-red-bg border-ui-tag-red-border"
3252
+ };
3253
+ const RECHECK = {
3254
+ green: void 0,
3255
+ grey: void 0,
3256
+ orange: "hover:bg-ui-tag-orange-bg-hover active:bg-ui-tag-orange-bg-hover",
3257
+ red: "hover:bg-ui-tag-red-bg-hover active:bg-ui-tag-red-bg-hover"
3258
+ };
3259
+ const LicenseSummary = ({ entry }) => {
3260
+ const { t } = reactI18next.useTranslation();
3261
+ const view = useDescribeLicense()(entry);
3262
+ const details = useLicenseDetails()(view);
3263
+ const recheck = useRecheckLicense();
3264
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.clx("flex items-center gap-x-4 rounded-lg border px-4 py-3", SURFACE[view.tone]), children: [
3265
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-y-0.5", children: [
3266
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-1", children: [
3267
+ /* @__PURE__ */ jsxRuntime.jsx(LicenseSquare, { tone: view.tone }),
3268
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", weight: "plus", className: "text-ui-fg-base", children: t(view.title.key, view.title.values) })
3269
+ ] }),
3270
+ details && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: details })
3271
+ ] }),
3272
+ entry.state !== "pending" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-x-2", children: [
3273
+ /* @__PURE__ */ jsxRuntime.jsx(
3274
+ ui.Button,
3275
+ {
3276
+ size: "small",
3277
+ variant: "transparent",
3278
+ className: RECHECK[view.tone],
3279
+ isLoading: recheck.isPending,
3280
+ disabled: recheck.isPending,
3281
+ onClick: () => recheck.mutate(),
3282
+ children: t("integration.license.recheck")
3283
+ }
3284
+ ),
3285
+ view.action && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: view.action.href, target: "_blank", rel: "noopener noreferrer", children: [
3286
+ t(view.action.key),
3418
3287
  /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
3419
3288
  ] }) })
3420
3289
  ] })
3421
- ] }) });
3290
+ ] });
3422
3291
  };
3423
- const BrowsePage = () => {
3292
+ const Row = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4 [overflow-wrap:anywhere]", children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
3294
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-2", children })
3295
+ ] });
3296
+ const EditPage = () => {
3424
3297
  const { t } = reactI18next.useTranslation();
3298
+ const { provider_id } = reactRouterDom.useParams();
3299
+ const qc = reactQuery.useQueryClient();
3425
3300
  const navigate = reactRouterDom.useNavigate();
3426
- const [activeTab, setActiveTab] = React.useState("all");
3427
- const [search, setSearch] = React.useState("");
3428
- const [installItem, setInstallItem] = React.useState(null);
3301
+ const prompt = ui.usePrompt();
3302
+ const [editingSection, setEditingSection] = React.useState(null);
3429
3303
  const { data, isLoading } = reactQuery.useQuery({
3430
- queryKey: ["integration-catalog"],
3431
- queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
3304
+ queryKey: ["integration", provider_id],
3305
+ queryFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`)
3432
3306
  });
3433
- const items = data?.integrations ?? [];
3434
- const categories = React.useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
3435
- const q = search.trim().toLowerCase();
3436
- const filtered = items.filter((i) => {
3437
- if (activeTab !== "all" && i.category !== activeTab) return false;
3438
- if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
3439
- return true;
3307
+ const { data: licenseData } = useLicenseStatus();
3308
+ const descriptor = data?.descriptor;
3309
+ const record = data?.integration;
3310
+ const isConfigured = !!record;
3311
+ const isComplete = data?.is_complete ?? false;
3312
+ const invalidate = () => {
3313
+ qc.invalidateQueries({ queryKey: ["integration", provider_id] });
3314
+ qc.invalidateQueries({ queryKey: ["integration-overview"] });
3315
+ };
3316
+ const test = reactQuery.useMutation({
3317
+ mutationFn: () => sdk.client.fetch(
3318
+ `/admin/integrations/${provider_id}/test-connection`,
3319
+ { method: "POST" }
3320
+ ),
3321
+ onSuccess: (r) => {
3322
+ r.status === "passed" ? ui.toast.success(t("integration.toast.connectionOk")) : ui.toast.warning(r.message ?? r.status);
3323
+ invalidate();
3324
+ },
3325
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.testFailed"))
3440
3326
  });
3441
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
3442
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3443
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
3444
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
3445
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("integration.browse.title") }),
3446
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
3447
- ] }),
3448
- false
3449
- ] }),
3450
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
3451
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
3452
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
3453
- categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
3454
- ] }) }),
3455
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
3456
- ui.Input,
3457
- {
3458
- placeholder: t("integration.browse.searchPlaceholder"),
3459
- value: search,
3460
- onChange: (e) => setSearch(e.target.value)
3461
- }
3462
- ) })
3327
+ const toggleEnabled = reactQuery.useMutation({
3328
+ mutationFn: (next) => sdk.client.fetch(`/admin/integrations/${provider_id}/enable`, {
3329
+ method: "POST",
3330
+ body: { is_enabled: next }
3331
+ }),
3332
+ onSuccess: (_d, next) => {
3333
+ ui.toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
3334
+ invalidate();
3335
+ },
3336
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.updateFailed"))
3337
+ });
3338
+ const remove = reactQuery.useMutation({
3339
+ mutationFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`, { method: "DELETE" }),
3340
+ onSuccess: () => {
3341
+ ui.toast.success(t("integration.toast.deleted"));
3342
+ invalidate();
3343
+ navigate("/settings/integrations");
3344
+ },
3345
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.deleteFailed"))
3346
+ });
3347
+ const onDelete = async () => {
3348
+ const confirmed = await prompt({
3349
+ title: t("integration.delete.title"),
3350
+ description: t("integration.delete.description", {
3351
+ name: descriptor ? t(descriptor.displayName) : ""
3352
+ }),
3353
+ confirmText: t("integration.actions.delete"),
3354
+ cancelText: t("integration.actions.cancel")
3355
+ });
3356
+ if (confirmed) remove.mutate();
3357
+ };
3358
+ if (isLoading) {
3359
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) });
3360
+ }
3361
+ if (!descriptor) {
3362
+ throw new Medusa.FetchError(
3363
+ `Integration with provider_id: ${provider_id} was not found`,
3364
+ "There is no page at this address",
3365
+ 404
3366
+ );
3367
+ }
3368
+ const version = data?.version ?? null;
3369
+ const author = data?.author ?? null;
3370
+ const authorUrl = data?.author_url ?? null;
3371
+ const lifecycle = !isConfigured ? { color: "grey", label: t("integration.status.notConfigured") } : !isComplete ? { color: "orange", label: t("integration.status.incomplete") } : record.is_enabled ? { color: "green", label: t("integration.status.enabled") } : { color: "grey", label: t("integration.status.disabled") };
3372
+ const connection = descriptor.hasTestConnection ? record?.last_test_status === "passed" ? { color: "green", label: t("integration.status.connected") } : record?.last_test_status === "failed" ? { color: "red", label: t("integration.status.connectionFailed") } : null : null;
3373
+ const license = licenseData?.packages.find((entry) => entry.identifiers.includes(descriptor.identifier));
3374
+ const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
3375
+ const configuredSecrets = new Set(record?.configured_secrets ?? []);
3376
+ const sectionData = {
3377
+ providerId: provider_id,
3378
+ identifier: descriptor.identifier,
3379
+ values: record?.values ?? {},
3380
+ isComplete
3381
+ };
3382
+ const renderSection = (section) => /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: section.id, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3383
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
3384
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t(section.title) }),
3385
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
3386
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
3387
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: () => setEditingSection(section.id), children: [
3388
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
3389
+ t("integration.actions.edit")
3390
+ ] }) })
3463
3391
  ] })
3464
3392
  ] }),
3465
- isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.list.emptyFiltered") }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4", children: filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shadow-borders-base bg-ui-bg-base flex flex-col rounded-lg", children: [
3466
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
3467
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
3393
+ section.fields.filter((f) => isFieldVisible(f, record?.values ?? {})).map((f) => /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t(f.label), children: /* @__PURE__ */ jsxRuntime.jsx(
3394
+ IntegrationFieldValue,
3395
+ {
3396
+ field: f,
3397
+ value: record?.values?.[f.name] ?? f.default,
3398
+ secretConfigured: configuredSecrets.has(f.name)
3399
+ }
3400
+ ) }, f.name))
3401
+ ] }, section.id) });
3402
+ const mainSections = descriptor.sections.filter((s) => s.column !== "side");
3403
+ const sideSections = descriptor.sections.filter((s) => s.column === "side");
3404
+ const main = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3405
+ /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
3407
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3468
3408
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
3469
- /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label }),
3470
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
3471
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
3472
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
3409
+ /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
3410
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
3411
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3412
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: t(descriptor.displayName) }),
3413
+ provider_id && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toModuleProviderId(provider_id, descriptor.category) })
3414
+ ] }),
3415
+ version && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
3416
+ t("integration.meta.version", { version }),
3417
+ author && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3418
+ " • ",
3419
+ t("integration.meta.authorPrefix"),
3420
+ " ",
3421
+ authorUrl ? /* @__PURE__ */ jsxRuntime.jsx(
3422
+ "a",
3423
+ {
3424
+ href: authorUrl,
3425
+ target: "_blank",
3426
+ rel: "noreferrer",
3427
+ className: "text-ui-fg-interactive",
3428
+ children: author
3429
+ }
3430
+ ) : author
3431
+ ] })
3432
+ ] })
3473
3433
  ] })
3474
3434
  ] }),
3475
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
3435
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3436
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
3437
+ connection && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: connection.color, children: connection.label }),
3438
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
3439
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
3440
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
3441
+ descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
3442
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsx(
3443
+ ui.DropdownMenu.Item,
3444
+ {
3445
+ disabled: toggleEnabled.isPending,
3446
+ onClick: () => toggleEnabled.mutate(!record.is_enabled),
3447
+ children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
3448
+ }
3449
+ ),
3450
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
3451
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
3452
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
3453
+ t("integration.actions.delete")
3454
+ ] })
3455
+ ] })
3456
+ ] })
3457
+ ] })
3476
3458
  ] }),
3477
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
3459
+ descriptor.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) }),
3460
+ license && /* @__PURE__ */ jsxRuntime.jsx(LicenseSummary, { entry: license })
3478
3461
  ] }),
3479
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
3480
- item.installed && item.provider_id ? /* @__PURE__ */ jsxRuntime.jsx(
3481
- ui.Button,
3482
- {
3483
- size: "small",
3484
- variant: "primary",
3485
- onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
3486
- children: t("integration.browse.settings")
3487
- }
3488
- ) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
3489
- /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
3490
- t("integration.browse.add")
3491
- ] }),
3492
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
3493
- ] })
3494
- ] }, item.integrationId)) }),
3495
- /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: PUBLISH_URL, target: "_blank", rel: "noreferrer", className: "text-ui-fg-base txt-small", children: [
3496
- t("integration.browse.learnMore"),
3497
- " →"
3462
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("integration.labels.category"), children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: t(`integration.categories.${descriptor.category}`) }) })
3498
3463
  ] }) }),
3499
- /* @__PURE__ */ jsxRuntime.jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
3464
+ mainSections.map(renderSection),
3465
+ editing && /* @__PURE__ */ jsxRuntime.jsx(
3466
+ EditSectionDrawer,
3467
+ {
3468
+ open: true,
3469
+ onOpenChange: (open) => !open && setEditingSection(null),
3470
+ providerId: provider_id,
3471
+ section: editing,
3472
+ values: record?.values ?? {},
3473
+ configuredSecrets: record?.configured_secrets ?? []
3474
+ },
3475
+ editing.id
3476
+ )
3500
3477
  ] });
3478
+ const side = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sideSections.map(renderSection) });
3479
+ return /* @__PURE__ */ jsxRuntime.jsx(
3480
+ components.LayoutComposer,
3481
+ {
3482
+ widgetsZonePrefix: `gorgo.integration.${descriptor.identifier}`,
3483
+ preferredLayoutId: descriptor.preferredLayoutId,
3484
+ sections: { main, side },
3485
+ data: sectionData,
3486
+ hasOutlet: false
3487
+ }
3488
+ );
3501
3489
  };
3502
- const BrowseBreadcrumb = () => {
3490
+ const IntegrationBreadcrumb = ({ params }) => {
3503
3491
  const { t } = reactI18next.useTranslation();
3504
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("integration.browse.title") });
3492
+ const providerId = params.provider_id;
3493
+ const { data } = reactQuery.useQuery({
3494
+ queryKey: ["integration", providerId],
3495
+ queryFn: () => sdk.client.fetch(`/admin/integrations/${providerId}`),
3496
+ enabled: !!providerId
3497
+ });
3498
+ const descriptor = data?.descriptor;
3499
+ if (!descriptor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
3500
+ const name = t(descriptor.displayName);
3501
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3502
+ name + " ",
3503
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: providerId })
3504
+ ] });
3505
3505
  };
3506
3506
  const handle = {
3507
- breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(BrowseBreadcrumb, {})
3507
+ breadcrumb: ({ params }) => /* @__PURE__ */ jsxRuntime.jsx(IntegrationBreadcrumb, { params })
3508
3508
  };
3509
3509
  const integration$x = { "list": { "title": "التكاملات", "subtitle": "إدارة إعدادات التكاملات", "browseIntegrations": "تصفح التكاملات", "learnMore": "تعرّف على كيفية إنشاء التكاملات", "searchPlaceholder": "البحث في التكاملات", "empty": "لا توجد تكاملات مسجلة. عرّف المزودين ضمن options.providers الخاصة بإضافة التكاملات في medusa-config.", "emptyFiltered": "لا توجد تكاملات مطابقة لعوامل التصفية.", "tested": "تم الاختبار", "testFailed": "فشل الاختبار", "tabs": { "all": "الكل" } }, "browse": { "title": "التصفح", "subtitle": "ابحث عن التكاملات المتاحة", "publish": "إضافة تكامل", "learnMore": "تعرّف على كيفية إنشاء ونشر تكاملك الخاص", "searchPlaceholder": "البحث في التكاملات", "by": "بواسطة {{author}}", "installed": "مثبَّت", "notInstalled": "غير مثبَّت", "add": "إضافة", "settings": "الإعدادات", "install_docs": "قراءة التوثيق", "install_empty": "تعليمات الإعداد لهذا التكامل غير متوفرة بعد.", "install_truncated": "هذه التعليمات أطول من أن تُعرض هنا — افتح التوثيق." }, "columns": { "integration": "التكامل", "description": "الوصف", "category": "الفئة", "status": "الحالة" }, "status": { "notConfigured": "غير مُهيّأ", "incomplete": "غير مكتمل", "enabled": "ممكّن", "disabled": "معطل", "configured": "مُهيّأ", "connected": "متصل", "connectionFailed": "فشل الاتصال" }, "meta": { "version": "v{{version}}", "authorPrefix": "بواسطة" }, "labels": { "category": "الفئة" }, "categories": { "payment": "الدفع", "notification": "الإشعارات", "fulfillment": "التنفيذ", "search": "البحث", "content": "المحتوى", "analytics": "التحليلات", "authentication": "المصادقة", "erp": "ERP", "crm": "CRM", "pim": "PIM", "marketplace": "الأسواق", "feed": "الخلاصات", "file_providers": "مزودو الملفات", "other": "أخرى" }, "actions": { "test": "اختبار الاتصال", "enable": "تمكين", "disable": "تعطيل", "delete": "حذف", "edit": "تحرير", "save": "حفظ", "cancel": "إلغاء" }, "delete": { "title": "حذف التهيئة", "description": "هل تريد إزالة التهيئة المحفوظة لـ {{name}}؟ لا يمكن التراجع عن هذا الإجراء." }, "form": { "required": "{{field}} مطلوب", "invalidJson": "JSON غير صالح", "keepSecret": "•••••• – اتركه فارغًا للإبقاء عليه", "items": "{{count}} عناصر" }, "toast": { "saved": "تم الحفظ", "saveFailed": "فشل الحفظ", "connectionOk": "الاتصال ناجح", "testFailed": "فشل الاختبار", "enabled": "تم التمكين", "disabled": "تم التعطيل", "deleted": "تم حذف التهيئة", "deleteFailed": "فشل الحذف", "updateFailed": "فشل التحديث" }, "unknown": 'تكامل غير معروف "{{id}}".', "back": "العودة إلى التكاملات", "license": { "title": { "pending": "Checking license", "active": "License active", "renews": "License active, renews {{date}}", "until": "License until {{date}}", "ending": "License ends {{when}}", "ended": "Subscription ended {{date}}", "missing": "License key not set", "malformed": "License key has the wrong format", "invalid_token": "License key not accepted", "no_subscription": "No subscription for this package", "domain_not_allowed": "Domain not in the project", "version_after_cutoff": "Version released after the subscription ended", "version_unverifiable": "Version could not be verified", "unknown": "License refused", "unreachable": "License not checked" }, "hint": { "pending": "The check runs at startup and takes a few seconds", "ending": "The installed version keeps working. Renew to get new versions", "ended": "The license covers versions up to {{version}}", "missing": "Add GORGO_LICENSE to the environment of the Medusa server and worker, then restart them", "malformed": "GORGO_LICENSE does not look like a project key", "invalid_token": "The key is unknown or revoked", "no_subscription": "Project “{{project}}” has no subscription for this package", "domain_not_allowed": "{{host}} is not among the domains of project “{{project}}”", "version_after_cutoff": "Install {{version}} or renew the subscription", "version_unverifiable": "The check will run again automatically", "unknown": "The license service refused the key", "unreachable": "The license service is unreachable. The check will run again in 15 minutes" }, "action": { "account": "Open account", "renew": "Renew", "getKey": "Get a key", "reissueKey": "Reissue key", "subscribe": "Subscribe", "addDomain": "Add domain" }, "checked": "checked {{when}}", "local": "local", "recheck": "Check now", "recheckFailed": "Could not check the license" } };
3510
3510
  const ar = {
@@ -3687,13 +3687,13 @@ const routeModule = {
3687
3687
  handle: { label: config.label, translationNs: config.translationNs, ...handle$2 }
3688
3688
  },
3689
3689
  {
3690
- Component: EditPage,
3691
- path: "/settings/integrations/:provider_id",
3690
+ Component: BrowsePage,
3691
+ path: "/settings/integrations/browse",
3692
3692
  handle: handle$1
3693
3693
  },
3694
3694
  {
3695
- Component: BrowsePage,
3696
- path: "/settings/integrations/browse",
3695
+ Component: EditPage,
3696
+ path: "/settings/integrations/:provider_id",
3697
3697
  handle
3698
3698
  }
3699
3699
  ]