@gorgo/medusa-integration 0.3.0-beta.1 → 0.3.0-beta.2

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.
@@ -291,113 +291,399 @@ const IntegrationsBreadcrumb = () => {
291
291
  const handle$2 = {
292
292
  breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(IntegrationsBreadcrumb, {})
293
293
  };
294
- const Dash = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "-" });
295
- const IntegrationFieldValue = ({
296
- field,
297
- value,
298
- secretConfigured = false
299
- }) => {
300
- const { t } = reactI18next.useTranslation();
301
- if (field.secret || field.control === "secret") {
302
- return secretConfigured ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: "••••••••" }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
303
- }
304
- if (field.control === "switch") {
305
- if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
306
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: value ? "green" : "grey", children: value ? t("integration.status.enabled") : t("integration.status.disabled") });
294
+ function isSafeHref(raw) {
295
+ try {
296
+ return new URL(raw).protocol === "https:";
297
+ } catch {
298
+ return false;
307
299
  }
308
- if (field.control === "json") {
309
- if (Array.isArray(value)) {
310
- return value.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.form.items", { count: value.length }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
300
+ }
301
+ const INLINE_RE = /`([^`\n]+)`|\[([^\]\n]*)\]\(([^)\s]+)\)|\*\*([^*\n]+)\*\*|\*([^*\n]+)\*|_([^_\n]+)_/;
302
+ const MAX_DEPTH = 3;
303
+ function parseInline(src, depth = 0) {
304
+ if (!src) return [];
305
+ if (depth >= MAX_DEPTH) return [{ type: "text", value: src }];
306
+ const out = [];
307
+ const re = new RegExp(INLINE_RE, "g");
308
+ let last = 0;
309
+ let m;
310
+ while ((m = re.exec(src)) !== null) {
311
+ const [full, code, linkText, linkHref, strong, star, underscore] = m;
312
+ if (m.index > last) out.push({ type: "text", value: src.slice(last, m.index) });
313
+ last = m.index + full.length;
314
+ if (code !== void 0) {
315
+ out.push({ type: "code", value: code });
316
+ } else if (linkHref !== void 0) {
317
+ const isImage = m.index > 0 && src[m.index - 1] === "!";
318
+ if (!isImage && isSafeHref(linkHref)) {
319
+ out.push({ type: "link", href: linkHref, children: parseInline(linkText, depth + 1) });
320
+ } else {
321
+ out.push({ type: "text", value: full });
322
+ }
323
+ } else if (strong !== void 0) {
324
+ out.push({ type: "strong", children: parseInline(strong, depth + 1) });
325
+ } else {
326
+ out.push({ type: "em", children: parseInline(star ?? underscore, depth + 1) });
311
327
  }
312
- if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
313
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.status.configured") });
314
- }
315
- if (value == null || value === "") return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
316
- if (field.control === "select") {
317
- const labelKey = field.optionLabels?.[String(value)];
318
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: labelKey ? t(labelKey) : String(value) });
319
- }
320
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: String(value) });
321
- };
322
- var isCheckBoxInput = (element) => element.type === "checkbox";
323
- var isFileInput = (element) => element.type === "file";
324
- var isDateObject = (value) => value instanceof Date;
325
- var isNullOrUndefined = (value) => value == null;
326
- const isObjectType = (value) => typeof value === "object";
327
- var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value);
328
- var getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : isFileInput(event.target) ? event.target.files : event.target.value : event;
329
- var isNameInFieldArray = (names, name) => name.split(".").some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join(".")));
330
- var isPlainObject = (tempObject) => {
331
- const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
332
- return isObject(prototypeCopy) && prototypeCopy.hasOwnProperty("isPrototypeOf");
333
- };
334
- var isWeb = typeof window !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof document !== "undefined";
335
- function cloneObject(data) {
336
- if (data instanceof Date) {
337
- return new Date(data);
338
- }
339
- const isFileListInstance = typeof FileList !== "undefined" && data instanceof FileList;
340
- if (isWeb && (data instanceof Blob || isFileListInstance)) {
341
- return data;
342
- }
343
- const isArray = Array.isArray(data);
344
- if (!isArray && !(isObject(data) && isPlainObject(data))) {
345
- return data;
346
328
  }
347
- const copy = isArray ? [] : Object.create(Object.getPrototypeOf(data));
348
- for (const key in data) {
349
- if (Object.prototype.hasOwnProperty.call(data, key)) {
350
- copy[key] = cloneObject(data[key]);
329
+ if (last < src.length) out.push({ type: "text", value: src.slice(last) });
330
+ return out;
331
+ }
332
+ const MD_MAX_CHARS = 8e3;
333
+ const MD_MAX_BLOCKS = 200;
334
+ const FENCE_RE = /^```(.*)$/;
335
+ const HEADING_RE = /^(#{1,3})\s+(.*)$/;
336
+ const HR_RE = /^-{3,}\s*$/;
337
+ const UL_RE = /^[-*]\s+(.*)$/;
338
+ const OL_RE = /^\d+\.\s+(.*)$/;
339
+ function fenceLabel(info) {
340
+ const first = info.trim().split(/\s+/)[0] ?? "";
341
+ return first.replace(/[^a-zA-Z0-9._/-]/g, "").slice(0, 40) || null;
342
+ }
343
+ function parseMarkdown(src) {
344
+ if (typeof src !== "string" || !src) return [];
345
+ if (src.length > MD_MAX_CHARS) return [{ type: "truncated" }];
346
+ const lines = src.replace(/\r\n?/g, "\n").split("\n");
347
+ const blocks = [];
348
+ let para = [];
349
+ const flushPara = () => {
350
+ if (!para.length) return;
351
+ blocks.push({ type: "paragraph", children: parseInline(para.join(" ")) });
352
+ para = [];
353
+ };
354
+ for (let i = 0; i < lines.length; i++) {
355
+ const line = lines[i];
356
+ const fence = FENCE_RE.exec(line);
357
+ if (fence) {
358
+ flushPara();
359
+ const body = [];
360
+ i++;
361
+ while (i < lines.length && !FENCE_RE.test(lines[i])) body.push(lines[i++]);
362
+ blocks.push({ type: "codeBlock", label: fenceLabel(fence[1]), value: body.join("\n") });
363
+ continue;
364
+ }
365
+ if (!line.trim()) {
366
+ flushPara();
367
+ continue;
368
+ }
369
+ if (HR_RE.test(line)) {
370
+ flushPara();
371
+ blocks.push({ type: "hr" });
372
+ continue;
373
+ }
374
+ const heading = HEADING_RE.exec(line);
375
+ if (heading) {
376
+ flushPara();
377
+ blocks.push({
378
+ type: "heading",
379
+ level: heading[1].length + 1,
380
+ children: parseInline(heading[2])
381
+ });
382
+ continue;
383
+ }
384
+ const ul = UL_RE.exec(line);
385
+ const ol = ul ? null : OL_RE.exec(line);
386
+ if (ul || ol) {
387
+ flushPara();
388
+ const ordered = !!ol;
389
+ const itemRe = ordered ? OL_RE : UL_RE;
390
+ const items = [];
391
+ while (i < lines.length) {
392
+ const item = itemRe.exec(lines[i]);
393
+ if (!item) break;
394
+ items.push(parseInline(item[1]));
395
+ i++;
396
+ }
397
+ i--;
398
+ blocks.push({ type: "list", ordered, items });
399
+ continue;
351
400
  }
401
+ para.push(line);
352
402
  }
353
- return copy;
403
+ flushPara();
404
+ const weight = blocks.reduce((n, b) => n + (b.type === "list" ? b.items.length : 1), 0);
405
+ return weight > MD_MAX_BLOCKS ? [{ type: "truncated" }] : blocks;
354
406
  }
355
- const EVENTS = {
356
- BLUR: "blur",
357
- FOCUS_OUT: "focusout",
358
- CHANGE: "change",
359
- SUBMIT: "submit",
360
- TRIGGER: "trigger",
361
- VALID: "valid"
362
- };
363
- const VALIDATION_MODE = {
364
- onBlur: "onBlur",
365
- onChange: "onChange",
366
- onSubmit: "onSubmit",
367
- onTouched: "onTouched",
368
- all: "all"
369
- };
370
- const INPUT_VALIDATION_RULES = {
371
- max: "max",
372
- min: "min",
373
- maxLength: "maxLength",
374
- minLength: "minLength",
375
- pattern: "pattern",
376
- required: "required",
377
- validate: "validate"
378
- };
379
- const ROOT_ERROR_TYPE = "root";
380
- const PROTOTYPE_KEYWORDS = ["__proto__", "constructor", "prototype"];
381
- const IS_KEY_RE = /^\w*$/;
382
- var isKey = (value) => IS_KEY_RE.test(value);
383
- var isUndefined = (val) => val === void 0;
384
- const FIELD_PATH_RE = /[.[\]'"]/;
385
- var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
386
- var get = (object, path, defaultValue) => {
387
- if (!path || !isObject(object)) {
388
- return defaultValue;
407
+ const Inline = ({ nodes }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node, i) => {
408
+ switch (node.type) {
409
+ case "text":
410
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.value }, i);
411
+ case "code":
412
+ return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "txt-compact-small bg-ui-bg-component rounded px-1 py-0.5", children: node.value }, i);
413
+ case "strong":
414
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-compact-small-plus", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
415
+ case "em":
416
+ return /* @__PURE__ */ jsxRuntime.jsx("em", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
417
+ case "link":
418
+ return /* @__PURE__ */ jsxRuntime.jsx(
419
+ "a",
420
+ {
421
+ href: node.href,
422
+ target: "_blank",
423
+ rel: "noopener noreferrer",
424
+ className: "text-ui-fg-interactive hover:underline",
425
+ children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children })
426
+ },
427
+ i
428
+ );
389
429
  }
390
- const paths = isKey(path) ? [path] : stringToPath(path);
391
- if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
392
- return defaultValue;
430
+ }) });
431
+ const Block = ({ block, truncatedLabel }) => {
432
+ switch (block.type) {
433
+ case "heading":
434
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: block.level === 2 ? "h2" : "h3", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
435
+ case "paragraph":
436
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
437
+ case "list": {
438
+ const List = block.ordered ? "ol" : "ul";
439
+ return /* @__PURE__ */ jsxRuntime.jsx(
440
+ List,
441
+ {
442
+ className: `text-ui-fg-subtle txt-small flex flex-col gap-y-1 pl-5 ${block.ordered ? "list-decimal" : "list-disc"}`,
443
+ children: block.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: item }) }, i))
444
+ }
445
+ );
446
+ }
447
+ case "codeBlock":
448
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-ui-bg-subtle flex flex-col gap-y-2 rounded-md px-3 pb-1 pt-2", children: [
449
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-2", children: [
450
+ block.label ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-subtle", children: block.label }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
451
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: block.value })
452
+ ] }),
453
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "txt-compact-small text-ui-fg-subtle overflow-x-auto whitespace-pre pb-2", children: block.value })
454
+ ] });
455
+ case "hr":
456
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-ui-border-base border-t" });
457
+ case "truncated":
458
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: truncatedLabel });
393
459
  }
394
- const result = paths.reduce((result2, key) => {
395
- return isNullOrUndefined(result2) ? void 0 : result2[key];
396
- }, object);
397
- return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result;
398
460
  };
399
- var isBoolean = (value) => typeof value === "boolean";
400
- var isFunction = (value) => typeof value === "function";
461
+ const Markdown = ({
462
+ source,
463
+ truncatedLabel
464
+ }) => {
465
+ const blocks = React.useMemo(() => parseMarkdown(source), [source]);
466
+ if (!blocks.length) return null;
467
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-4", children: blocks.map((block, i) => /* @__PURE__ */ jsxRuntime.jsx(Block, { block, truncatedLabel }, i)) });
468
+ };
469
+ const PUBLISH_URL = "https://docs.gorgojs.com/medusa-modules/integration";
470
+ const API_URL = "https://gorgojs.com";
471
+ const iconSrc = (icon) => icon.startsWith("data:") ? icon : API_URL + icon;
472
+ const InstallModal = ({
473
+ item,
474
+ onOpenChange
475
+ }) => {
476
+ const { t, i18n } = reactI18next.useTranslation();
477
+ const lang = i18n.language.split("-")[0] === "ru" ? "ru" : "en";
478
+ const source = item?.docsSnippet?.[lang] || item?.docsSnippet?.en || item?.docsSnippet?.ru || "";
479
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
481
+ item && /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label, size: "small" }),
482
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: item?.label })
483
+ ] }) }) }),
484
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: source.trim() ? /* @__PURE__ */ jsxRuntime.jsx(Markdown, { source, truncatedLabel: t("integration.browse.install_truncated") }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.install_empty") }) }),
485
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
486
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }),
487
+ item?.docsUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "primary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: item.docsUrl, target: "_blank", rel: "noopener noreferrer", children: [
488
+ t("integration.browse.install_docs"),
489
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
490
+ ] }) })
491
+ ] })
492
+ ] }) });
493
+ };
494
+ const BrowsePage = () => {
495
+ const { t } = reactI18next.useTranslation();
496
+ const navigate = reactRouterDom.useNavigate();
497
+ const [activeTab, setActiveTab] = React.useState("all");
498
+ const [search, setSearch] = React.useState("");
499
+ const [installItem, setInstallItem] = React.useState(null);
500
+ const { data, isLoading } = reactQuery.useQuery({
501
+ queryKey: ["integration-catalog"],
502
+ queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
503
+ });
504
+ const items = data?.integrations ?? [];
505
+ const categories = React.useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
506
+ const q = search.trim().toLowerCase();
507
+ const filtered = items.filter((i) => {
508
+ if (activeTab !== "all" && i.category !== activeTab) return false;
509
+ if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
510
+ return true;
511
+ });
512
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
513
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
514
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
515
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
516
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("integration.browse.title") }),
517
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
518
+ ] }),
519
+ false
520
+ ] }),
521
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
522
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
523
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
524
+ categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
525
+ ] }) }),
526
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
527
+ ui.Input,
528
+ {
529
+ placeholder: t("integration.browse.searchPlaceholder"),
530
+ value: search,
531
+ onChange: (e) => setSearch(e.target.value)
532
+ }
533
+ ) })
534
+ ] })
535
+ ] }),
536
+ isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.list.emptyFiltered") }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4", children: filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shadow-borders-base bg-ui-bg-base flex flex-col rounded-lg", children: [
537
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
538
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
539
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
540
+ /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label }),
541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
542
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
543
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
544
+ ] })
545
+ ] }),
546
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
547
+ ] }),
548
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
549
+ ] }),
550
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
551
+ item.installed && item.provider_id ? /* @__PURE__ */ jsxRuntime.jsx(
552
+ ui.Button,
553
+ {
554
+ size: "small",
555
+ variant: "primary",
556
+ onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
557
+ children: t("integration.browse.settings")
558
+ }
559
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
560
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
561
+ t("integration.browse.add")
562
+ ] }),
563
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
564
+ ] })
565
+ ] }, item.integrationId)) }),
566
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: PUBLISH_URL, target: "_blank", rel: "noreferrer", className: "text-ui-fg-base txt-small", children: [
567
+ t("integration.browse.learnMore"),
568
+ " →"
569
+ ] }) }),
570
+ /* @__PURE__ */ jsxRuntime.jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
571
+ ] });
572
+ };
573
+ const BrowseBreadcrumb = () => {
574
+ const { t } = reactI18next.useTranslation();
575
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("integration.browse.title") });
576
+ };
577
+ const handle$1 = {
578
+ breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(BrowseBreadcrumb, {})
579
+ };
580
+ const Dash = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "-" });
581
+ const IntegrationFieldValue = ({
582
+ field,
583
+ value,
584
+ secretConfigured = false
585
+ }) => {
586
+ const { t } = reactI18next.useTranslation();
587
+ if (field.secret || field.control === "secret") {
588
+ return secretConfigured ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: "••••••••" }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
589
+ }
590
+ if (field.control === "switch") {
591
+ if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
592
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: value ? "green" : "grey", children: value ? t("integration.status.enabled") : t("integration.status.disabled") });
593
+ }
594
+ if (field.control === "json") {
595
+ if (Array.isArray(value)) {
596
+ return value.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.form.items", { count: value.length }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
597
+ }
598
+ if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
599
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.status.configured") });
600
+ }
601
+ if (value == null || value === "") return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
602
+ if (field.control === "select") {
603
+ const labelKey = field.optionLabels?.[String(value)];
604
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: labelKey ? t(labelKey) : String(value) });
605
+ }
606
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: String(value) });
607
+ };
608
+ var isCheckBoxInput = (element) => element.type === "checkbox";
609
+ var isFileInput = (element) => element.type === "file";
610
+ var isDateObject = (value) => value instanceof Date;
611
+ var isNullOrUndefined = (value) => value == null;
612
+ const isObjectType = (value) => typeof value === "object";
613
+ var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value);
614
+ var getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : isFileInput(event.target) ? event.target.files : event.target.value : event;
615
+ var isNameInFieldArray = (names, name) => name.split(".").some((part, index, arr) => !isNaN(Number(part)) && names.has(arr.slice(0, index).join(".")));
616
+ var isPlainObject = (tempObject) => {
617
+ const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
618
+ return isObject(prototypeCopy) && prototypeCopy.hasOwnProperty("isPrototypeOf");
619
+ };
620
+ var isWeb = typeof window !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof document !== "undefined";
621
+ function cloneObject(data) {
622
+ if (data instanceof Date) {
623
+ return new Date(data);
624
+ }
625
+ const isFileListInstance = typeof FileList !== "undefined" && data instanceof FileList;
626
+ if (isWeb && (data instanceof Blob || isFileListInstance)) {
627
+ return data;
628
+ }
629
+ const isArray = Array.isArray(data);
630
+ if (!isArray && !(isObject(data) && isPlainObject(data))) {
631
+ return data;
632
+ }
633
+ const copy = isArray ? [] : Object.create(Object.getPrototypeOf(data));
634
+ for (const key in data) {
635
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
636
+ copy[key] = cloneObject(data[key]);
637
+ }
638
+ }
639
+ return copy;
640
+ }
641
+ const EVENTS = {
642
+ BLUR: "blur",
643
+ FOCUS_OUT: "focusout",
644
+ CHANGE: "change",
645
+ SUBMIT: "submit",
646
+ TRIGGER: "trigger",
647
+ VALID: "valid"
648
+ };
649
+ const VALIDATION_MODE = {
650
+ onBlur: "onBlur",
651
+ onChange: "onChange",
652
+ onSubmit: "onSubmit",
653
+ onTouched: "onTouched",
654
+ all: "all"
655
+ };
656
+ const INPUT_VALIDATION_RULES = {
657
+ max: "max",
658
+ min: "min",
659
+ maxLength: "maxLength",
660
+ minLength: "minLength",
661
+ pattern: "pattern",
662
+ required: "required",
663
+ validate: "validate"
664
+ };
665
+ const ROOT_ERROR_TYPE = "root";
666
+ const PROTOTYPE_KEYWORDS = ["__proto__", "constructor", "prototype"];
667
+ const IS_KEY_RE = /^\w*$/;
668
+ var isKey = (value) => IS_KEY_RE.test(value);
669
+ var isUndefined = (val) => val === void 0;
670
+ const FIELD_PATH_RE = /[.[\]'"]/;
671
+ var stringToPath = (input) => input.split(FIELD_PATH_RE).filter(Boolean);
672
+ var get = (object, path, defaultValue) => {
673
+ if (!path || !isObject(object)) {
674
+ return defaultValue;
675
+ }
676
+ const paths = isKey(path) ? [path] : stringToPath(path);
677
+ if (paths.some((key) => PROTOTYPE_KEYWORDS.includes(key))) {
678
+ return defaultValue;
679
+ }
680
+ const result = paths.reduce((result2, key) => {
681
+ return isNullOrUndefined(result2) ? void 0 : result2[key];
682
+ }, object);
683
+ return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result;
684
+ };
685
+ var isBoolean = (value) => typeof value === "boolean";
686
+ var isFunction = (value) => typeof value === "function";
401
687
  var set = (object, path, value) => {
402
688
  let index = -1;
403
689
  const tempPath = isKey(path) ? [path] : stringToPath(path);
@@ -2399,857 +2685,571 @@ function createFormControl(props = {}) {
2399
2685
  return _names;
2400
2686
  },
2401
2687
  set _names(value) {
2402
- _names = value;
2403
- },
2404
- get _formState() {
2405
- return _formState;
2406
- },
2407
- get _options() {
2408
- return _options;
2409
- },
2410
- set _options(value) {
2411
- _options = {
2412
- ..._options,
2413
- ...value
2414
- };
2415
- _validationModeBeforeSubmit = getValidationModes(_options.mode);
2416
- _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2417
- }
2418
- },
2419
- subscribe,
2420
- trigger,
2421
- register,
2422
- handleSubmit,
2423
- watch,
2424
- setValue,
2425
- setValues,
2426
- getValues,
2427
- reset,
2428
- resetField,
2429
- resetDefaultValues,
2430
- clearErrors,
2431
- unregister,
2432
- setError,
2433
- setFocus,
2434
- getFieldState
2435
- };
2436
- return {
2437
- ...methods,
2438
- formControl: methods
2439
- };
2440
- }
2441
- function useForm(props = {}) {
2442
- const _formControl = React__default.default.useRef(void 0);
2443
- const _values = React__default.default.useRef(void 0);
2444
- const _formControlProp = React__default.default.useRef(props.formControl);
2445
- const [formState, updateFormState] = React__default.default.useState(() => ({
2446
- ...cloneObject(DEFAULT_FORM_STATE),
2447
- isLoading: isFunction(props.defaultValues),
2448
- errors: props.errors || {},
2449
- disabled: props.disabled || false,
2450
- defaultValues: isFunction(props.defaultValues) ? void 0 : props.defaultValues
2451
- }));
2452
- if (!_formControl.current || props.formControl && _formControlProp.current !== props.formControl) {
2453
- _formControlProp.current = props.formControl;
2454
- if (props.formControl) {
2455
- _formControl.current = {
2456
- ...props.formControl,
2457
- formState
2458
- };
2459
- if (props.defaultValues && !isFunction(props.defaultValues)) {
2460
- props.formControl.reset(props.defaultValues, props.resetOptions);
2461
- }
2462
- } else {
2463
- const { formControl, ...rest } = createFormControl(props);
2464
- _formControl.current = {
2465
- ...rest,
2466
- formState
2467
- };
2468
- }
2469
- }
2470
- const control = _formControl.current.control;
2471
- control._options = props;
2472
- useIsomorphicLayoutEffect(() => {
2473
- const sub = control._subscribe({
2474
- formState: control._proxyFormState,
2475
- callback: () => updateFormState({
2476
- ...control._formState,
2477
- defaultValues: control._defaultValues
2478
- }),
2479
- reRenderRoot: true
2480
- });
2481
- updateFormState((data) => ({
2482
- ...data,
2483
- isReady: true
2484
- }));
2485
- control._formState.isReady = true;
2486
- return sub;
2487
- }, [control]);
2488
- React__default.default.useEffect(() => control._disableForm(props.disabled), [control, props.disabled]);
2489
- React__default.default.useEffect(() => {
2490
- if (props.mode) {
2491
- control._options.mode = props.mode;
2492
- }
2493
- if (props.reValidateMode) {
2494
- control._options.reValidateMode = props.reValidateMode;
2495
- }
2496
- }, [control, props.mode, props.reValidateMode]);
2497
- React__default.default.useEffect(() => {
2498
- if (props.errors) {
2499
- control._setErrors(props.errors);
2500
- control._focusError();
2501
- }
2502
- }, [control, props.errors]);
2503
- React__default.default.useEffect(() => {
2504
- props.shouldUnregister && control._subjects.state.next({
2505
- values: control._getWatch()
2506
- });
2507
- }, [control, props.shouldUnregister]);
2508
- React__default.default.useEffect(() => {
2509
- if (control._proxyFormState.isDirty) {
2510
- const isDirty = control._getDirty();
2511
- if (isDirty !== formState.isDirty) {
2512
- control._subjects.state.next({
2513
- isDirty
2514
- });
2515
- }
2516
- }
2517
- }, [control, formState.isDirty]);
2518
- React__default.default.useEffect(() => {
2519
- var _a;
2520
- if (props.values && !deepEqual(props.values, _values.current)) {
2521
- control._reset(props.values, {
2522
- keepFieldsRef: true,
2523
- ...control._options.resetOptions
2524
- });
2525
- if (!((_a = control._options.resetOptions) === null || _a === void 0 ? void 0 : _a.keepIsValid)) {
2526
- control._setValid();
2527
- }
2528
- _values.current = props.values;
2529
- updateFormState((state) => ({ ...state }));
2530
- } else {
2531
- control._resetDefaultValues();
2532
- }
2533
- }, [control, props.values]);
2534
- React__default.default.useEffect(() => {
2535
- if (!control._state.mount) {
2536
- control._setValid();
2537
- control._state.mount = true;
2538
- }
2539
- if (control._state.watch) {
2540
- control._state.watch = false;
2541
- control._subjects.state.next({ ...control._formState });
2542
- }
2543
- control._removeUnmounted();
2544
- });
2545
- _formControl.current.formState = React__default.default.useMemo(() => getProxyFormState(formState, control), [control, formState]);
2546
- return _formControl.current;
2547
- }
2548
- const jsonToText = (v) => v == null || v === "" ? "" : JSON.stringify(v, null, 2);
2549
- const JsonField = ({ field, f }) => {
2550
- const { t } = reactI18next.useTranslation();
2551
- const [raw, setRaw] = React.useState(() => jsonToText(f.value));
2552
- const [error, setError] = React.useState(null);
2553
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2554
- /* @__PURE__ */ jsxRuntime.jsx(
2555
- ui.Textarea,
2556
- {
2557
- rows: 6,
2558
- className: "font-mono",
2559
- placeholder: field.placeholder ? t(field.placeholder) : "[]",
2560
- value: raw,
2561
- disabled: field.readonly === true,
2562
- onBlur: f.onBlur,
2563
- onChange: (e) => {
2564
- const text = e.target.value;
2565
- setRaw(text);
2566
- if (text.trim() === "") {
2567
- setError(null);
2568
- f.onChange(null);
2569
- return;
2570
- }
2571
- try {
2572
- f.onChange(JSON.parse(text));
2573
- setError(null);
2574
- } catch {
2575
- setError(t("integration.form.invalidJson"));
2576
- }
2577
- }
2578
- }
2579
- ),
2580
- error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: error })
2581
- ] });
2582
- };
2583
- const IntegrationField = ({
2584
- field,
2585
- control,
2586
- secretConfigured = false
2587
- }) => {
2588
- const { t } = reactI18next.useTranslation();
2589
- const enforceRequired = field.required && field.control !== "switch" && !(field.control === "secret" && secretConfigured);
2590
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
2591
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Label, { size: "small", weight: "plus", children: [
2592
- t(field.label),
2593
- field.required ? " *" : ""
2594
- ] }),
2595
- /* @__PURE__ */ jsxRuntime.jsx(
2596
- Controller,
2597
- {
2598
- name: field.name,
2599
- control,
2600
- rules: {
2601
- required: enforceRequired ? t("integration.form.required", { field: t(field.label) }) : false
2602
- },
2603
- render: ({ field: f, fieldState }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2604
- renderControl(field, f, secretConfigured, t),
2605
- fieldState.error?.message && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: fieldState.error.message })
2606
- ] })
2607
- }
2608
- ),
2609
- field.hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: t(field.hint) })
2610
- ] });
2611
- };
2612
- function renderControl(field, f, secretConfigured, t) {
2613
- const placeholder = field.placeholder ? t(field.placeholder) : void 0;
2614
- const disabled = field.readonly === true;
2615
- switch (field.control) {
2616
- case "switch":
2617
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, { checked: !!f.value, onCheckedChange: f.onChange, disabled });
2618
- case "select": {
2619
- const declared = field.options ?? [];
2620
- return /* @__PURE__ */ jsxRuntime.jsxs(
2621
- ui.Select,
2622
- {
2623
- value: f.value == null ? "" : String(f.value),
2624
- onValueChange: (next) => f.onChange(declared.find((o) => String(o) === next) ?? next),
2625
- disabled,
2626
- children: [
2627
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder }) }),
2628
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: declared.map((o) => {
2629
- const labelKey = field.optionLabels?.[String(o)];
2630
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: String(o), children: labelKey ? t(labelKey) : String(o) }, String(o));
2631
- }) })
2632
- ]
2633
- }
2634
- );
2635
- }
2636
- case "number":
2637
- return /* @__PURE__ */ jsxRuntime.jsx(
2638
- ui.Input,
2639
- {
2640
- type: "number",
2641
- placeholder,
2642
- value: f.value ?? "",
2643
- disabled,
2644
- onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
2645
- }
2646
- );
2647
- case "url":
2648
- return /* @__PURE__ */ jsxRuntime.jsx(
2649
- ui.Input,
2650
- {
2651
- type: "url",
2652
- placeholder,
2653
- value: f.value ?? "",
2654
- disabled,
2655
- onChange: f.onChange
2656
- }
2657
- );
2658
- case "secret":
2659
- return /* @__PURE__ */ jsxRuntime.jsx(
2660
- ui.Input,
2661
- {
2662
- type: "password",
2663
- placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
2664
- value: f.value ?? "",
2665
- disabled,
2666
- onChange: f.onChange,
2667
- className: !f.value ? "[&~div]:hidden" : void 0
2668
- }
2669
- );
2670
- case "json":
2671
- return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { field, f });
2672
- default:
2673
- return /* @__PURE__ */ jsxRuntime.jsx(
2674
- ui.Input,
2675
- {
2676
- placeholder,
2677
- value: f.value ?? "",
2678
- disabled,
2679
- onChange: f.onChange
2680
- }
2681
- );
2682
- }
2683
- }
2684
- function isFieldVisible(field, values) {
2685
- const rule = field.visibleWhen;
2686
- if (!rule) return true;
2687
- return values[rule.field] === rule.equals;
2688
- }
2689
- function sectionDefaults(section, values) {
2690
- const out = {};
2691
- for (const f of section.fields) {
2692
- if (f.secret || f.control === "secret") out[f.name] = "";
2693
- else if (f.control === "switch") out[f.name] = values?.[f.name] ?? f.default ?? false;
2694
- else if (f.control === "json") out[f.name] = values?.[f.name] ?? f.default;
2695
- else out[f.name] = values?.[f.name] ?? f.default ?? "";
2696
- }
2697
- return out;
2698
- }
2699
- const EditSectionDrawer = ({
2700
- open,
2701
- onOpenChange,
2702
- providerId,
2703
- section,
2704
- values,
2705
- configuredSecrets = []
2706
- }) => {
2707
- const { t } = reactI18next.useTranslation();
2708
- const qc = reactQuery.useQueryClient();
2709
- const configured = new Set(configuredSecrets);
2710
- const [defaults] = React.useState(() => sectionDefaults(section, values));
2711
- const form = useForm({ values: defaults });
2712
- const current = form.watch();
2713
- const visibilityValues = { ...values, ...current };
2714
- const save = reactQuery.useMutation({
2715
- mutationFn: (sectionValues) => sdk.client.fetch(`/admin/integrations/${providerId}`, {
2716
- method: "POST",
2717
- // Only this section's values + its id — the server validates just this section and
2718
- // merges over the rest of the stored config (keeping blank/untouched secrets).
2719
- body: { section_id: section.id, values: sectionValues }
2720
- }),
2721
- onSuccess: () => {
2722
- ui.toast.success(t("integration.toast.saved"));
2723
- qc.invalidateQueries({ queryKey: ["integration", providerId] });
2724
- qc.invalidateQueries({ queryKey: ["integration-overview"] });
2725
- onOpenChange(false);
2726
- },
2727
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.saveFailed"))
2728
- });
2729
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
2730
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: t(section.title) }) }),
2731
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: section.fields.filter((f) => isFieldVisible(f, visibilityValues)).map((f) => /* @__PURE__ */ jsxRuntime.jsx(
2732
- IntegrationField,
2733
- {
2734
- field: f,
2735
- control: form.control,
2736
- secretConfigured: configured.has(f.name)
2737
- },
2738
- f.name
2739
- )) }),
2740
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
2741
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", disabled: save.isPending, children: t("integration.actions.cancel") }) }),
2742
- /* @__PURE__ */ jsxRuntime.jsx(
2743
- ui.Button,
2744
- {
2745
- size: "small",
2746
- isLoading: save.isPending,
2747
- onClick: form.handleSubmit((v) => save.mutate(v)),
2748
- children: t("integration.actions.save")
2749
- }
2750
- )
2751
- ] })
2752
- ] }) });
2753
- };
2754
- const Row = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4", children: [
2755
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
2756
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-2", children })
2757
- ] });
2758
- const EditPage = () => {
2759
- const { t } = reactI18next.useTranslation();
2760
- const { provider_id } = reactRouterDom.useParams();
2761
- const qc = reactQuery.useQueryClient();
2762
- const navigate = reactRouterDom.useNavigate();
2763
- const prompt = ui.usePrompt();
2764
- const [editingSection, setEditingSection] = React.useState(null);
2765
- const { data, isLoading } = reactQuery.useQuery({
2766
- queryKey: ["integration", provider_id],
2767
- queryFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`)
2768
- });
2769
- const descriptor = data?.descriptor;
2770
- const record = data?.integration;
2771
- const isConfigured = !!record;
2772
- const isComplete = data?.is_complete ?? false;
2773
- const invalidate = () => {
2774
- qc.invalidateQueries({ queryKey: ["integration", provider_id] });
2775
- qc.invalidateQueries({ queryKey: ["integration-overview"] });
2776
- };
2777
- const test = reactQuery.useMutation({
2778
- mutationFn: () => sdk.client.fetch(
2779
- `/admin/integrations/${provider_id}/test-connection`,
2780
- { method: "POST" }
2781
- ),
2782
- onSuccess: (r) => {
2783
- r.status === "passed" ? ui.toast.success(t("integration.toast.connectionOk")) : ui.toast.warning(r.message ?? r.status);
2784
- invalidate();
2785
- },
2786
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.testFailed"))
2787
- });
2788
- const toggleEnabled = reactQuery.useMutation({
2789
- mutationFn: (next) => sdk.client.fetch(`/admin/integrations/${provider_id}/enable`, {
2790
- method: "POST",
2791
- body: { is_enabled: next }
2792
- }),
2793
- onSuccess: (_d, next) => {
2794
- ui.toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
2795
- invalidate();
2796
- },
2797
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.updateFailed"))
2798
- });
2799
- const remove = reactQuery.useMutation({
2800
- mutationFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`, { method: "DELETE" }),
2801
- onSuccess: () => {
2802
- ui.toast.success(t("integration.toast.deleted"));
2803
- invalidate();
2804
- navigate("/settings/integrations");
2805
- },
2806
- onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.deleteFailed"))
2807
- });
2808
- const onDelete = async () => {
2809
- const confirmed = await prompt({
2810
- title: t("integration.delete.title"),
2811
- description: t("integration.delete.description", {
2812
- name: descriptor ? t(descriptor.displayName) : ""
2813
- }),
2814
- confirmText: t("integration.actions.delete"),
2815
- cancelText: t("integration.actions.cancel")
2816
- });
2817
- if (confirmed) remove.mutate();
2818
- };
2819
- if (isLoading) {
2820
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) });
2821
- }
2822
- if (!descriptor) {
2823
- throw new Medusa.FetchError(
2824
- `Integration with provider_id: ${provider_id} was not found`,
2825
- "There is no page at this address",
2826
- 404
2827
- );
2828
- }
2829
- const version = data?.version ?? null;
2830
- const author = data?.author ?? null;
2831
- const authorUrl = data?.author_url ?? null;
2832
- 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") };
2833
- 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;
2834
- const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
2835
- const configuredSecrets = new Set(record?.configured_secrets ?? []);
2836
- const sectionData = {
2837
- providerId: provider_id,
2838
- identifier: descriptor.identifier,
2839
- values: record?.values ?? {},
2840
- isComplete
2841
- };
2842
- const renderSection = (section) => /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: section.id, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2843
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
2844
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t(section.title) }),
2845
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2846
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
2847
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: () => setEditingSection(section.id), children: [
2848
- /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
2849
- t("integration.actions.edit")
2850
- ] }) })
2851
- ] })
2852
- ] }),
2853
- section.fields.filter((f) => isFieldVisible(f, record?.values ?? {})).map((f) => /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t(f.label), children: /* @__PURE__ */ jsxRuntime.jsx(
2854
- IntegrationFieldValue,
2855
- {
2856
- field: f,
2857
- value: record?.values?.[f.name] ?? f.default,
2858
- secretConfigured: configuredSecrets.has(f.name)
2859
- }
2860
- ) }, f.name))
2861
- ] }, section.id) });
2862
- const mainSections = descriptor.sections.filter((s) => s.column !== "side");
2863
- const sideSections = descriptor.sections.filter((s) => s.column === "side");
2864
- const main = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2865
- /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2866
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
2867
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2868
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
2869
- /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
2870
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
2871
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
2872
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: t(descriptor.displayName) }),
2873
- provider_id && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toModuleProviderId(provider_id, descriptor.category) })
2874
- ] }),
2875
- version && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
2876
- t("integration.meta.version", { version }),
2877
- author && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2878
- " • ",
2879
- t("integration.meta.authorPrefix"),
2880
- " ",
2881
- authorUrl ? /* @__PURE__ */ jsxRuntime.jsx(
2882
- "a",
2883
- {
2884
- href: authorUrl,
2885
- target: "_blank",
2886
- rel: "noreferrer",
2887
- className: "text-ui-fg-interactive",
2888
- children: author
2889
- }
2890
- ) : author
2891
- ] })
2892
- ] })
2893
- ] })
2894
- ] }),
2895
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
2896
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
2897
- connection && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: connection.color, children: connection.label }),
2898
- isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2899
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
2900
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
2901
- descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
2902
- isConfigured && /* @__PURE__ */ jsxRuntime.jsx(
2903
- ui.DropdownMenu.Item,
2904
- {
2905
- disabled: toggleEnabled.isPending,
2906
- onClick: () => toggleEnabled.mutate(!record.is_enabled),
2907
- children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
2908
- }
2909
- ),
2910
- isConfigured && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
2911
- isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
2912
- /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
2913
- t("integration.actions.delete")
2914
- ] })
2915
- ] })
2916
- ] })
2917
- ] })
2918
- ] }),
2919
- descriptor.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) })
2920
- ] }),
2921
- /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("integration.labels.category"), children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: t(`integration.categories.${descriptor.category}`) }) })
2922
- ] }) }),
2923
- mainSections.map(renderSection),
2924
- editing && /* @__PURE__ */ jsxRuntime.jsx(
2925
- EditSectionDrawer,
2926
- {
2927
- open: true,
2928
- onOpenChange: (open) => !open && setEditingSection(null),
2929
- providerId: provider_id,
2930
- section: editing,
2931
- values: record?.values ?? {},
2932
- configuredSecrets: record?.configured_secrets ?? []
2933
- },
2934
- editing.id
2935
- )
2936
- ] });
2937
- const side = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sideSections.map(renderSection) });
2938
- return /* @__PURE__ */ jsxRuntime.jsx(
2939
- components.LayoutComposer,
2940
- {
2941
- widgetsZonePrefix: `gorgo.integration.${descriptor.identifier}`,
2942
- preferredLayoutId: descriptor.preferredLayoutId,
2943
- sections: { main, side },
2944
- data: sectionData,
2945
- hasOutlet: false
2946
- }
2947
- );
2948
- };
2949
- const IntegrationBreadcrumb = ({ params }) => {
2950
- const { t } = reactI18next.useTranslation();
2951
- const providerId = params.provider_id;
2952
- const { data } = reactQuery.useQuery({
2953
- queryKey: ["integration", providerId],
2954
- queryFn: () => sdk.client.fetch(`/admin/integrations/${providerId}`),
2955
- enabled: !!providerId
2956
- });
2957
- const descriptor = data?.descriptor;
2958
- if (!descriptor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
2959
- const name = t(descriptor.displayName);
2960
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2961
- name + " ",
2962
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: providerId })
2963
- ] });
2964
- };
2965
- const handle$1 = {
2966
- breadcrumb: ({ params }) => /* @__PURE__ */ jsxRuntime.jsx(IntegrationBreadcrumb, { params })
2967
- };
2968
- function isSafeHref(raw) {
2969
- try {
2970
- return new URL(raw).protocol === "https:";
2971
- } catch {
2972
- return false;
2973
- }
2688
+ _names = value;
2689
+ },
2690
+ get _formState() {
2691
+ return _formState;
2692
+ },
2693
+ get _options() {
2694
+ return _options;
2695
+ },
2696
+ set _options(value) {
2697
+ _options = {
2698
+ ..._options,
2699
+ ...value
2700
+ };
2701
+ _validationModeBeforeSubmit = getValidationModes(_options.mode);
2702
+ _validationModeAfterSubmit = getValidationModes(_options.reValidateMode);
2703
+ }
2704
+ },
2705
+ subscribe,
2706
+ trigger,
2707
+ register,
2708
+ handleSubmit,
2709
+ watch,
2710
+ setValue,
2711
+ setValues,
2712
+ getValues,
2713
+ reset,
2714
+ resetField,
2715
+ resetDefaultValues,
2716
+ clearErrors,
2717
+ unregister,
2718
+ setError,
2719
+ setFocus,
2720
+ getFieldState
2721
+ };
2722
+ return {
2723
+ ...methods,
2724
+ formControl: methods
2725
+ };
2974
2726
  }
2975
- const INLINE_RE = /`([^`\n]+)`|\[([^\]\n]*)\]\(([^)\s]+)\)|\*\*([^*\n]+)\*\*|\*([^*\n]+)\*|_([^_\n]+)_/;
2976
- const MAX_DEPTH = 3;
2977
- function parseInline(src, depth = 0) {
2978
- if (!src) return [];
2979
- if (depth >= MAX_DEPTH) return [{ type: "text", value: src }];
2980
- const out = [];
2981
- const re = new RegExp(INLINE_RE, "g");
2982
- let last = 0;
2983
- let m;
2984
- while ((m = re.exec(src)) !== null) {
2985
- const [full, code, linkText, linkHref, strong, star, underscore] = m;
2986
- if (m.index > last) out.push({ type: "text", value: src.slice(last, m.index) });
2987
- last = m.index + full.length;
2988
- if (code !== void 0) {
2989
- out.push({ type: "code", value: code });
2990
- } else if (linkHref !== void 0) {
2991
- const isImage = m.index > 0 && src[m.index - 1] === "!";
2992
- if (!isImage && isSafeHref(linkHref)) {
2993
- out.push({ type: "link", href: linkHref, children: parseInline(linkText, depth + 1) });
2994
- } else {
2995
- out.push({ type: "text", value: full });
2727
+ function useForm(props = {}) {
2728
+ const _formControl = React__default.default.useRef(void 0);
2729
+ const _values = React__default.default.useRef(void 0);
2730
+ const _formControlProp = React__default.default.useRef(props.formControl);
2731
+ const [formState, updateFormState] = React__default.default.useState(() => ({
2732
+ ...cloneObject(DEFAULT_FORM_STATE),
2733
+ isLoading: isFunction(props.defaultValues),
2734
+ errors: props.errors || {},
2735
+ disabled: props.disabled || false,
2736
+ defaultValues: isFunction(props.defaultValues) ? void 0 : props.defaultValues
2737
+ }));
2738
+ if (!_formControl.current || props.formControl && _formControlProp.current !== props.formControl) {
2739
+ _formControlProp.current = props.formControl;
2740
+ if (props.formControl) {
2741
+ _formControl.current = {
2742
+ ...props.formControl,
2743
+ formState
2744
+ };
2745
+ if (props.defaultValues && !isFunction(props.defaultValues)) {
2746
+ props.formControl.reset(props.defaultValues, props.resetOptions);
2996
2747
  }
2997
- } else if (strong !== void 0) {
2998
- out.push({ type: "strong", children: parseInline(strong, depth + 1) });
2999
2748
  } else {
3000
- out.push({ type: "em", children: parseInline(star ?? underscore, depth + 1) });
2749
+ const { formControl, ...rest } = createFormControl(props);
2750
+ _formControl.current = {
2751
+ ...rest,
2752
+ formState
2753
+ };
3001
2754
  }
3002
2755
  }
3003
- if (last < src.length) out.push({ type: "text", value: src.slice(last) });
3004
- return out;
3005
- }
3006
- const MD_MAX_CHARS = 8e3;
3007
- const MD_MAX_BLOCKS = 200;
3008
- const FENCE_RE = /^```(.*)$/;
3009
- const HEADING_RE = /^(#{1,3})\s+(.*)$/;
3010
- const HR_RE = /^-{3,}\s*$/;
3011
- const UL_RE = /^[-*]\s+(.*)$/;
3012
- const OL_RE = /^\d+\.\s+(.*)$/;
3013
- function fenceLabel(info) {
3014
- const first = info.trim().split(/\s+/)[0] ?? "";
3015
- return first.replace(/[^a-zA-Z0-9._/-]/g, "").slice(0, 40) || null;
3016
- }
3017
- function parseMarkdown(src) {
3018
- if (typeof src !== "string" || !src) return [];
3019
- if (src.length > MD_MAX_CHARS) return [{ type: "truncated" }];
3020
- const lines = src.replace(/\r\n?/g, "\n").split("\n");
3021
- const blocks = [];
3022
- let para = [];
3023
- const flushPara = () => {
3024
- if (!para.length) return;
3025
- blocks.push({ type: "paragraph", children: parseInline(para.join(" ")) });
3026
- para = [];
3027
- };
3028
- for (let i = 0; i < lines.length; i++) {
3029
- const line = lines[i];
3030
- const fence = FENCE_RE.exec(line);
3031
- if (fence) {
3032
- flushPara();
3033
- const body = [];
3034
- i++;
3035
- while (i < lines.length && !FENCE_RE.test(lines[i])) body.push(lines[i++]);
3036
- blocks.push({ type: "codeBlock", label: fenceLabel(fence[1]), value: body.join("\n") });
3037
- continue;
2756
+ const control = _formControl.current.control;
2757
+ control._options = props;
2758
+ useIsomorphicLayoutEffect(() => {
2759
+ const sub = control._subscribe({
2760
+ formState: control._proxyFormState,
2761
+ callback: () => updateFormState({
2762
+ ...control._formState,
2763
+ defaultValues: control._defaultValues
2764
+ }),
2765
+ reRenderRoot: true
2766
+ });
2767
+ updateFormState((data) => ({
2768
+ ...data,
2769
+ isReady: true
2770
+ }));
2771
+ control._formState.isReady = true;
2772
+ return sub;
2773
+ }, [control]);
2774
+ React__default.default.useEffect(() => control._disableForm(props.disabled), [control, props.disabled]);
2775
+ React__default.default.useEffect(() => {
2776
+ if (props.mode) {
2777
+ control._options.mode = props.mode;
3038
2778
  }
3039
- if (!line.trim()) {
3040
- flushPara();
3041
- continue;
2779
+ if (props.reValidateMode) {
2780
+ control._options.reValidateMode = props.reValidateMode;
3042
2781
  }
3043
- if (HR_RE.test(line)) {
3044
- flushPara();
3045
- blocks.push({ type: "hr" });
3046
- continue;
2782
+ }, [control, props.mode, props.reValidateMode]);
2783
+ React__default.default.useEffect(() => {
2784
+ if (props.errors) {
2785
+ control._setErrors(props.errors);
2786
+ control._focusError();
3047
2787
  }
3048
- const heading = HEADING_RE.exec(line);
3049
- if (heading) {
3050
- flushPara();
3051
- blocks.push({
3052
- type: "heading",
3053
- level: heading[1].length + 1,
3054
- children: parseInline(heading[2])
2788
+ }, [control, props.errors]);
2789
+ React__default.default.useEffect(() => {
2790
+ props.shouldUnregister && control._subjects.state.next({
2791
+ values: control._getWatch()
2792
+ });
2793
+ }, [control, props.shouldUnregister]);
2794
+ React__default.default.useEffect(() => {
2795
+ if (control._proxyFormState.isDirty) {
2796
+ const isDirty = control._getDirty();
2797
+ if (isDirty !== formState.isDirty) {
2798
+ control._subjects.state.next({
2799
+ isDirty
2800
+ });
2801
+ }
2802
+ }
2803
+ }, [control, formState.isDirty]);
2804
+ React__default.default.useEffect(() => {
2805
+ var _a;
2806
+ if (props.values && !deepEqual(props.values, _values.current)) {
2807
+ control._reset(props.values, {
2808
+ keepFieldsRef: true,
2809
+ ...control._options.resetOptions
3055
2810
  });
3056
- continue;
2811
+ if (!((_a = control._options.resetOptions) === null || _a === void 0 ? void 0 : _a.keepIsValid)) {
2812
+ control._setValid();
2813
+ }
2814
+ _values.current = props.values;
2815
+ updateFormState((state) => ({ ...state }));
2816
+ } else {
2817
+ control._resetDefaultValues();
3057
2818
  }
3058
- const ul = UL_RE.exec(line);
3059
- const ol = ul ? null : OL_RE.exec(line);
3060
- if (ul || ol) {
3061
- flushPara();
3062
- const ordered = !!ol;
3063
- const itemRe = ordered ? OL_RE : UL_RE;
3064
- const items = [];
3065
- while (i < lines.length) {
3066
- const item = itemRe.exec(lines[i]);
3067
- if (!item) break;
3068
- items.push(parseInline(item[1]));
3069
- i++;
2819
+ }, [control, props.values]);
2820
+ React__default.default.useEffect(() => {
2821
+ if (!control._state.mount) {
2822
+ control._setValid();
2823
+ control._state.mount = true;
2824
+ }
2825
+ if (control._state.watch) {
2826
+ control._state.watch = false;
2827
+ control._subjects.state.next({ ...control._formState });
2828
+ }
2829
+ control._removeUnmounted();
2830
+ });
2831
+ _formControl.current.formState = React__default.default.useMemo(() => getProxyFormState(formState, control), [control, formState]);
2832
+ return _formControl.current;
2833
+ }
2834
+ const jsonToText = (v) => v == null || v === "" ? "" : JSON.stringify(v, null, 2);
2835
+ const JsonField = ({ field, f }) => {
2836
+ const { t } = reactI18next.useTranslation();
2837
+ const [raw, setRaw] = React.useState(() => jsonToText(f.value));
2838
+ const [error, setError] = React.useState(null);
2839
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2840
+ /* @__PURE__ */ jsxRuntime.jsx(
2841
+ ui.Textarea,
2842
+ {
2843
+ rows: 6,
2844
+ className: "font-mono",
2845
+ placeholder: field.placeholder ? t(field.placeholder) : "[]",
2846
+ value: raw,
2847
+ disabled: field.readonly === true,
2848
+ onBlur: f.onBlur,
2849
+ onChange: (e) => {
2850
+ const text = e.target.value;
2851
+ setRaw(text);
2852
+ if (text.trim() === "") {
2853
+ setError(null);
2854
+ f.onChange(null);
2855
+ return;
2856
+ }
2857
+ try {
2858
+ f.onChange(JSON.parse(text));
2859
+ setError(null);
2860
+ } catch {
2861
+ setError(t("integration.form.invalidJson"));
2862
+ }
2863
+ }
2864
+ }
2865
+ ),
2866
+ error && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: error })
2867
+ ] });
2868
+ };
2869
+ const IntegrationField = ({
2870
+ field,
2871
+ control,
2872
+ secretConfigured = false
2873
+ }) => {
2874
+ const { t } = reactI18next.useTranslation();
2875
+ const enforceRequired = field.required && field.control !== "switch" && !(field.control === "secret" && secretConfigured);
2876
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
2877
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Label, { size: "small", weight: "plus", children: [
2878
+ t(field.label),
2879
+ field.required ? " *" : ""
2880
+ ] }),
2881
+ /* @__PURE__ */ jsxRuntime.jsx(
2882
+ Controller,
2883
+ {
2884
+ name: field.name,
2885
+ control,
2886
+ rules: {
2887
+ required: enforceRequired ? t("integration.form.required", { field: t(field.label) }) : false
2888
+ },
2889
+ render: ({ field: f, fieldState }) => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2890
+ renderControl(field, f, secretConfigured, t),
2891
+ fieldState.error?.message && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-error", children: fieldState.error.message })
2892
+ ] })
3070
2893
  }
3071
- i--;
3072
- blocks.push({ type: "list", ordered, items });
3073
- continue;
2894
+ ),
2895
+ field.hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: t(field.hint) })
2896
+ ] });
2897
+ };
2898
+ function renderControl(field, f, secretConfigured, t) {
2899
+ const placeholder = field.placeholder ? t(field.placeholder) : void 0;
2900
+ const disabled = field.readonly === true;
2901
+ switch (field.control) {
2902
+ case "switch":
2903
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, { checked: !!f.value, onCheckedChange: f.onChange, disabled });
2904
+ case "select": {
2905
+ const declared = field.options ?? [];
2906
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2907
+ ui.Select,
2908
+ {
2909
+ value: f.value == null ? "" : String(f.value),
2910
+ onValueChange: (next) => f.onChange(declared.find((o) => String(o) === next) ?? next),
2911
+ disabled,
2912
+ children: [
2913
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder }) }),
2914
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: declared.map((o) => {
2915
+ const labelKey = field.optionLabels?.[String(o)];
2916
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: String(o), children: labelKey ? t(labelKey) : String(o) }, String(o));
2917
+ }) })
2918
+ ]
2919
+ }
2920
+ );
3074
2921
  }
3075
- para.push(line);
3076
- }
3077
- flushPara();
3078
- const weight = blocks.reduce((n, b) => n + (b.type === "list" ? b.items.length : 1), 0);
3079
- return weight > MD_MAX_BLOCKS ? [{ type: "truncated" }] : blocks;
3080
- }
3081
- const Inline = ({ nodes }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node, i) => {
3082
- switch (node.type) {
3083
- case "text":
3084
- return /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.value }, i);
3085
- case "code":
3086
- return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "txt-compact-small bg-ui-bg-component rounded px-1 py-0.5", children: node.value }, i);
3087
- case "strong":
3088
- return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-compact-small-plus", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
3089
- case "em":
3090
- return /* @__PURE__ */ jsxRuntime.jsx("em", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
3091
- case "link":
2922
+ case "number":
3092
2923
  return /* @__PURE__ */ jsxRuntime.jsx(
3093
- "a",
2924
+ ui.Input,
3094
2925
  {
3095
- href: node.href,
3096
- target: "_blank",
3097
- rel: "noopener noreferrer",
3098
- className: "text-ui-fg-interactive hover:underline",
3099
- children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children })
3100
- },
3101
- i
2926
+ type: "number",
2927
+ placeholder,
2928
+ value: f.value ?? "",
2929
+ disabled,
2930
+ onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
2931
+ }
3102
2932
  );
3103
- }
3104
- }) });
3105
- const Block = ({ block, truncatedLabel }) => {
3106
- switch (block.type) {
3107
- case "heading":
3108
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: block.level === 2 ? "h2" : "h3", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
3109
- case "paragraph":
3110
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
3111
- case "list": {
3112
- const List = block.ordered ? "ol" : "ul";
2933
+ case "url":
3113
2934
  return /* @__PURE__ */ jsxRuntime.jsx(
3114
- List,
2935
+ ui.Input,
3115
2936
  {
3116
- className: `text-ui-fg-subtle txt-small flex flex-col gap-y-1 pl-5 ${block.ordered ? "list-decimal" : "list-disc"}`,
3117
- children: block.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: item }) }, i))
2937
+ type: "url",
2938
+ placeholder,
2939
+ value: f.value ?? "",
2940
+ disabled,
2941
+ onChange: f.onChange
2942
+ }
2943
+ );
2944
+ case "secret":
2945
+ return /* @__PURE__ */ jsxRuntime.jsx(
2946
+ ui.Input,
2947
+ {
2948
+ type: "password",
2949
+ placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
2950
+ value: f.value ?? "",
2951
+ disabled,
2952
+ onChange: f.onChange,
2953
+ className: !f.value ? "[&~div]:hidden" : void 0
2954
+ }
2955
+ );
2956
+ case "json":
2957
+ return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { field, f });
2958
+ default:
2959
+ return /* @__PURE__ */ jsxRuntime.jsx(
2960
+ ui.Input,
2961
+ {
2962
+ placeholder,
2963
+ value: f.value ?? "",
2964
+ disabled,
2965
+ onChange: f.onChange
3118
2966
  }
3119
2967
  );
3120
- }
3121
- case "codeBlock":
3122
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-ui-bg-subtle flex flex-col gap-y-2 rounded-md px-3 pb-1 pt-2", children: [
3123
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-2", children: [
3124
- block.label ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-subtle", children: block.label }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
3125
- /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: block.value })
3126
- ] }),
3127
- /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "txt-compact-small text-ui-fg-subtle overflow-x-auto whitespace-pre pb-2", children: block.value })
3128
- ] });
3129
- case "hr":
3130
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-ui-border-base border-t" });
3131
- case "truncated":
3132
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: truncatedLabel });
3133
2968
  }
3134
- };
3135
- const Markdown = ({
3136
- source,
3137
- truncatedLabel
3138
- }) => {
3139
- const blocks = React.useMemo(() => parseMarkdown(source), [source]);
3140
- if (!blocks.length) return null;
3141
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-4", children: blocks.map((block, i) => /* @__PURE__ */ jsxRuntime.jsx(Block, { block, truncatedLabel }, i)) });
3142
- };
3143
- const PUBLISH_URL = "https://docs.gorgojs.com/medusa-modules/integration";
3144
- const API_URL = "https://gorgojs.com";
3145
- const iconSrc = (icon) => icon.startsWith("data:") ? icon : API_URL + icon;
3146
- const InstallModal = ({
3147
- item,
3148
- onOpenChange
2969
+ }
2970
+ function isFieldVisible(field, values) {
2971
+ const rule = field.visibleWhen;
2972
+ if (!rule) return true;
2973
+ return values[rule.field] === rule.equals;
2974
+ }
2975
+ function sectionDefaults(section, values) {
2976
+ const out = {};
2977
+ for (const f of section.fields) {
2978
+ if (f.secret || f.control === "secret") out[f.name] = "";
2979
+ else if (f.control === "switch") out[f.name] = values?.[f.name] ?? f.default ?? false;
2980
+ else if (f.control === "json") out[f.name] = values?.[f.name] ?? f.default;
2981
+ else out[f.name] = values?.[f.name] ?? f.default ?? "";
2982
+ }
2983
+ return out;
2984
+ }
2985
+ const EditSectionDrawer = ({
2986
+ open,
2987
+ onOpenChange,
2988
+ providerId,
2989
+ section,
2990
+ values,
2991
+ configuredSecrets = []
3149
2992
  }) => {
3150
- const { t, i18n } = reactI18next.useTranslation();
3151
- const lang = i18n.language.split("-")[0] === "ru" ? "ru" : "en";
3152
- const source = item?.docsSnippet?.[lang] || item?.docsSnippet?.en || item?.docsSnippet?.ru || "";
3153
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
3154
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3155
- item && /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label, size: "small" }),
3156
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: item?.label })
3157
- ] }) }) }),
3158
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: source.trim() ? /* @__PURE__ */ jsxRuntime.jsx(Markdown, { source, truncatedLabel: t("integration.browse.install_truncated") }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.install_empty") }) }),
2993
+ const { t } = reactI18next.useTranslation();
2994
+ const qc = reactQuery.useQueryClient();
2995
+ const configured = new Set(configuredSecrets);
2996
+ const [defaults] = React.useState(() => sectionDefaults(section, values));
2997
+ const form = useForm({ values: defaults });
2998
+ const current = form.watch();
2999
+ const visibilityValues = { ...values, ...current };
3000
+ const save = reactQuery.useMutation({
3001
+ mutationFn: (sectionValues) => sdk.client.fetch(`/admin/integrations/${providerId}`, {
3002
+ method: "POST",
3003
+ // Only this section's values + its id — the server validates just this section and
3004
+ // merges over the rest of the stored config (keeping blank/untouched secrets).
3005
+ body: { section_id: section.id, values: sectionValues }
3006
+ }),
3007
+ onSuccess: () => {
3008
+ ui.toast.success(t("integration.toast.saved"));
3009
+ qc.invalidateQueries({ queryKey: ["integration", providerId] });
3010
+ qc.invalidateQueries({ queryKey: ["integration-overview"] });
3011
+ onOpenChange(false);
3012
+ },
3013
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.saveFailed"))
3014
+ });
3015
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
3016
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: t(section.title) }) }),
3017
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: section.fields.filter((f) => isFieldVisible(f, visibilityValues)).map((f) => /* @__PURE__ */ jsxRuntime.jsx(
3018
+ IntegrationField,
3019
+ {
3020
+ field: f,
3021
+ control: form.control,
3022
+ secretConfigured: configured.has(f.name)
3023
+ },
3024
+ f.name
3025
+ )) }),
3159
3026
  /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
3160
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }),
3161
- item?.docsUrl && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "primary", asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: item.docsUrl, target: "_blank", rel: "noopener noreferrer", children: [
3162
- t("integration.browse.install_docs"),
3163
- /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
3164
- ] }) })
3027
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", disabled: save.isPending, children: t("integration.actions.cancel") }) }),
3028
+ /* @__PURE__ */ jsxRuntime.jsx(
3029
+ ui.Button,
3030
+ {
3031
+ size: "small",
3032
+ isLoading: save.isPending,
3033
+ onClick: form.handleSubmit((v) => save.mutate(v)),
3034
+ children: t("integration.actions.save")
3035
+ }
3036
+ )
3165
3037
  ] })
3166
3038
  ] }) });
3167
3039
  };
3168
- const BrowsePage = () => {
3040
+ const Row = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4", children: [
3041
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
3042
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-2", children })
3043
+ ] });
3044
+ const EditPage = () => {
3169
3045
  const { t } = reactI18next.useTranslation();
3046
+ const { provider_id } = reactRouterDom.useParams();
3047
+ const qc = reactQuery.useQueryClient();
3170
3048
  const navigate = reactRouterDom.useNavigate();
3171
- const [activeTab, setActiveTab] = React.useState("all");
3172
- const [search, setSearch] = React.useState("");
3173
- const [installItem, setInstallItem] = React.useState(null);
3174
- const { data, isLoading } = reactQuery.useQuery({
3175
- queryKey: ["integration-catalog"],
3176
- queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
3049
+ const prompt = ui.usePrompt();
3050
+ const [editingSection, setEditingSection] = React.useState(null);
3051
+ const { data, isLoading } = reactQuery.useQuery({
3052
+ queryKey: ["integration", provider_id],
3053
+ queryFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`)
3177
3054
  });
3178
- const items = data?.integrations ?? [];
3179
- const categories = React.useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
3180
- const q = search.trim().toLowerCase();
3181
- const filtered = items.filter((i) => {
3182
- if (activeTab !== "all" && i.category !== activeTab) return false;
3183
- if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
3184
- return true;
3055
+ const descriptor = data?.descriptor;
3056
+ const record = data?.integration;
3057
+ const isConfigured = !!record;
3058
+ const isComplete = data?.is_complete ?? false;
3059
+ const invalidate = () => {
3060
+ qc.invalidateQueries({ queryKey: ["integration", provider_id] });
3061
+ qc.invalidateQueries({ queryKey: ["integration-overview"] });
3062
+ };
3063
+ const test = reactQuery.useMutation({
3064
+ mutationFn: () => sdk.client.fetch(
3065
+ `/admin/integrations/${provider_id}/test-connection`,
3066
+ { method: "POST" }
3067
+ ),
3068
+ onSuccess: (r) => {
3069
+ r.status === "passed" ? ui.toast.success(t("integration.toast.connectionOk")) : ui.toast.warning(r.message ?? r.status);
3070
+ invalidate();
3071
+ },
3072
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.testFailed"))
3185
3073
  });
3186
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
3187
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3188
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
3189
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
3190
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("integration.browse.title") }),
3191
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
3192
- ] }),
3193
- false
3194
- ] }),
3195
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
3196
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
3197
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
3198
- categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
3199
- ] }) }),
3200
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
3201
- ui.Input,
3202
- {
3203
- placeholder: t("integration.browse.searchPlaceholder"),
3204
- value: search,
3205
- onChange: (e) => setSearch(e.target.value)
3206
- }
3207
- ) })
3074
+ const toggleEnabled = reactQuery.useMutation({
3075
+ mutationFn: (next) => sdk.client.fetch(`/admin/integrations/${provider_id}/enable`, {
3076
+ method: "POST",
3077
+ body: { is_enabled: next }
3078
+ }),
3079
+ onSuccess: (_d, next) => {
3080
+ ui.toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
3081
+ invalidate();
3082
+ },
3083
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.updateFailed"))
3084
+ });
3085
+ const remove = reactQuery.useMutation({
3086
+ mutationFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`, { method: "DELETE" }),
3087
+ onSuccess: () => {
3088
+ ui.toast.success(t("integration.toast.deleted"));
3089
+ invalidate();
3090
+ navigate("/settings/integrations");
3091
+ },
3092
+ onError: (e) => ui.toast.error(e?.message ?? t("integration.toast.deleteFailed"))
3093
+ });
3094
+ const onDelete = async () => {
3095
+ const confirmed = await prompt({
3096
+ title: t("integration.delete.title"),
3097
+ description: t("integration.delete.description", {
3098
+ name: descriptor ? t(descriptor.displayName) : ""
3099
+ }),
3100
+ confirmText: t("integration.actions.delete"),
3101
+ cancelText: t("integration.actions.cancel")
3102
+ });
3103
+ if (confirmed) remove.mutate();
3104
+ };
3105
+ if (isLoading) {
3106
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) });
3107
+ }
3108
+ if (!descriptor) {
3109
+ throw new Medusa.FetchError(
3110
+ `Integration with provider_id: ${provider_id} was not found`,
3111
+ "There is no page at this address",
3112
+ 404
3113
+ );
3114
+ }
3115
+ const version = data?.version ?? null;
3116
+ const author = data?.author ?? null;
3117
+ const authorUrl = data?.author_url ?? null;
3118
+ 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") };
3119
+ 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;
3120
+ const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
3121
+ const configuredSecrets = new Set(record?.configured_secrets ?? []);
3122
+ const sectionData = {
3123
+ providerId: provider_id,
3124
+ identifier: descriptor.identifier,
3125
+ values: record?.values ?? {},
3126
+ isComplete
3127
+ };
3128
+ const renderSection = (section) => /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: section.id, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3129
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
3130
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t(section.title) }),
3131
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
3132
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
3133
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: () => setEditingSection(section.id), children: [
3134
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
3135
+ t("integration.actions.edit")
3136
+ ] }) })
3208
3137
  ] })
3209
3138
  ] }),
3210
- isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "text-ui-fg-subtle animate-spin" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.list.emptyFiltered") }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4", children: filtered.map((item) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "shadow-borders-base bg-ui-bg-base flex flex-col rounded-lg", children: [
3211
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
3212
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
3139
+ section.fields.filter((f) => isFieldVisible(f, record?.values ?? {})).map((f) => /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t(f.label), children: /* @__PURE__ */ jsxRuntime.jsx(
3140
+ IntegrationFieldValue,
3141
+ {
3142
+ field: f,
3143
+ value: record?.values?.[f.name] ?? f.default,
3144
+ secretConfigured: configuredSecrets.has(f.name)
3145
+ }
3146
+ ) }, f.name))
3147
+ ] }, section.id) });
3148
+ const mainSections = descriptor.sections.filter((s) => s.column !== "side");
3149
+ const sideSections = descriptor.sections.filter((s) => s.column === "side");
3150
+ const main = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3151
+ /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
3152
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
3153
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3213
3154
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
3214
- /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label }),
3215
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
3216
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
3217
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
3155
+ /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
3156
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
3157
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3158
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: t(descriptor.displayName) }),
3159
+ provider_id && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toModuleProviderId(provider_id, descriptor.category) })
3160
+ ] }),
3161
+ version && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
3162
+ t("integration.meta.version", { version }),
3163
+ author && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3164
+ " • ",
3165
+ t("integration.meta.authorPrefix"),
3166
+ " ",
3167
+ authorUrl ? /* @__PURE__ */ jsxRuntime.jsx(
3168
+ "a",
3169
+ {
3170
+ href: authorUrl,
3171
+ target: "_blank",
3172
+ rel: "noreferrer",
3173
+ className: "text-ui-fg-interactive",
3174
+ children: author
3175
+ }
3176
+ ) : author
3177
+ ] })
3178
+ ] })
3218
3179
  ] })
3219
3180
  ] }),
3220
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
3181
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
3182
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
3183
+ connection && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: connection.color, children: connection.label }),
3184
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
3185
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
3186
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
3187
+ descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
3188
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsx(
3189
+ ui.DropdownMenu.Item,
3190
+ {
3191
+ disabled: toggleEnabled.isPending,
3192
+ onClick: () => toggleEnabled.mutate(!record.is_enabled),
3193
+ children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
3194
+ }
3195
+ ),
3196
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
3197
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
3198
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
3199
+ t("integration.actions.delete")
3200
+ ] })
3201
+ ] })
3202
+ ] })
3203
+ ] })
3221
3204
  ] }),
3222
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
3205
+ descriptor.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) })
3223
3206
  ] }),
3224
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
3225
- item.installed && item.provider_id ? /* @__PURE__ */ jsxRuntime.jsx(
3226
- ui.Button,
3227
- {
3228
- size: "small",
3229
- variant: "primary",
3230
- onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
3231
- children: t("integration.browse.settings")
3232
- }
3233
- ) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
3234
- /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
3235
- t("integration.browse.add")
3236
- ] }),
3237
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
3238
- ] })
3239
- ] }, item.integrationId)) }),
3240
- /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("a", { href: PUBLISH_URL, target: "_blank", rel: "noreferrer", className: "text-ui-fg-base txt-small", children: [
3241
- t("integration.browse.learnMore"),
3242
- " →"
3207
+ /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t("integration.labels.category"), children: /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: t(`integration.categories.${descriptor.category}`) }) })
3243
3208
  ] }) }),
3244
- /* @__PURE__ */ jsxRuntime.jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
3209
+ mainSections.map(renderSection),
3210
+ editing && /* @__PURE__ */ jsxRuntime.jsx(
3211
+ EditSectionDrawer,
3212
+ {
3213
+ open: true,
3214
+ onOpenChange: (open) => !open && setEditingSection(null),
3215
+ providerId: provider_id,
3216
+ section: editing,
3217
+ values: record?.values ?? {},
3218
+ configuredSecrets: record?.configured_secrets ?? []
3219
+ },
3220
+ editing.id
3221
+ )
3245
3222
  ] });
3223
+ const side = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sideSections.map(renderSection) });
3224
+ return /* @__PURE__ */ jsxRuntime.jsx(
3225
+ components.LayoutComposer,
3226
+ {
3227
+ widgetsZonePrefix: `gorgo.integration.${descriptor.identifier}`,
3228
+ preferredLayoutId: descriptor.preferredLayoutId,
3229
+ sections: { main, side },
3230
+ data: sectionData,
3231
+ hasOutlet: false
3232
+ }
3233
+ );
3246
3234
  };
3247
- const BrowseBreadcrumb = () => {
3235
+ const IntegrationBreadcrumb = ({ params }) => {
3248
3236
  const { t } = reactI18next.useTranslation();
3249
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("integration.browse.title") });
3237
+ const providerId = params.provider_id;
3238
+ const { data } = reactQuery.useQuery({
3239
+ queryKey: ["integration", providerId],
3240
+ queryFn: () => sdk.client.fetch(`/admin/integrations/${providerId}`),
3241
+ enabled: !!providerId
3242
+ });
3243
+ const descriptor = data?.descriptor;
3244
+ if (!descriptor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
3245
+ const name = t(descriptor.displayName);
3246
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3247
+ name + " ",
3248
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: providerId })
3249
+ ] });
3250
3250
  };
3251
3251
  const handle = {
3252
- breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(BrowseBreadcrumb, {})
3252
+ breadcrumb: ({ params }) => /* @__PURE__ */ jsxRuntime.jsx(IntegrationBreadcrumb, { params })
3253
3253
  };
3254
3254
  const integration$w = { "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": "العودة إلى التكاملات" };
3255
3255
  const ar = {
@@ -3427,13 +3427,13 @@ const routeModule = {
3427
3427
  handle: { label: config.label, translationNs: config.translationNs, ...handle$2 }
3428
3428
  },
3429
3429
  {
3430
- Component: EditPage,
3431
- path: "/settings/integrations/:provider_id",
3430
+ Component: BrowsePage,
3431
+ path: "/settings/integrations/browse",
3432
3432
  handle: handle$1
3433
3433
  },
3434
3434
  {
3435
- Component: BrowsePage,
3436
- path: "/settings/integrations/browse",
3435
+ Component: EditPage,
3436
+ path: "/settings/integrations/:provider_id",
3437
3437
  handle
3438
3438
  }
3439
3439
  ]