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