@gorgo/medusa-integration 0.1.4 → 0.1.5

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,372 +291,85 @@ const IntegrationsBreadcrumb = () => {
291
291
  const handle$2 = {
292
292
  breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(IntegrationsBreadcrumb, {})
293
293
  };
294
- function isSafeHref(raw) {
295
- try {
296
- return new URL(raw).protocol === "https:";
297
- } catch {
298
- return false;
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
+ var _a;
301
+ const { t } = reactI18next.useTranslation();
302
+ if (field.secret || field.control === "secret") {
303
+ return secretConfigured ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: "••••••••" }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
299
304
  }
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) });
327
- }
305
+ if (field.control === "switch") {
306
+ if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
307
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: value ? "green" : "grey", children: value ? t("integration.status.enabled") : t("integration.status.disabled") });
328
308
  }
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;
309
+ if (field.control === "json") {
310
+ if (Array.isArray(value)) {
311
+ return value.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.form.items", { count: value.length }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
400
312
  }
401
- para.push(line);
313
+ if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
314
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.status.configured") });
402
315
  }
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;
406
- }
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
- );
316
+ if (value == null || value === "") return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
317
+ if (field.control === "select") {
318
+ const labelKey = (_a = field.optionLabels) == null ? void 0 : _a[String(value)];
319
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: labelKey ? t(labelKey) : String(value) });
429
320
  }
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))
321
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: String(value) });
322
+ };
323
+ var isCheckBoxInput = (element) => element.type === "checkbox";
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 : event.target.value : event;
329
+ var getNodeParentName = (name) => name.substring(0, name.search(/\.\d+(\.|$)/)) || name;
330
+ var isNameInFieldArray = (names, name) => names.has(getNodeParentName(name));
331
+ var isPlainObject = (tempObject) => {
332
+ const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
333
+ return isObject(prototypeCopy) && prototypeCopy.hasOwnProperty("isPrototypeOf");
334
+ };
335
+ var isWeb = typeof window !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof document !== "undefined";
336
+ function cloneObject(data) {
337
+ let copy;
338
+ const isArray = Array.isArray(data);
339
+ if (data instanceof Date) {
340
+ copy = new Date(data);
341
+ } else if (data instanceof Set) {
342
+ copy = new Set(data);
343
+ } else if (!(isWeb && (data instanceof Blob || data instanceof FileList)) && (isArray || isObject(data))) {
344
+ copy = isArray ? [] : {};
345
+ if (!isArray && !isPlainObject(data)) {
346
+ copy = data;
347
+ } else {
348
+ for (const key in data) {
349
+ if (data.hasOwnProperty(key)) {
350
+ copy[key] = cloneObject(data[key]);
444
351
  }
445
- );
352
+ }
446
353
  }
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 });
354
+ } else {
355
+ return data;
356
+ }
357
+ return copy;
358
+ }
359
+ var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
360
+ var isUndefined = (val) => val === void 0;
361
+ var get = (object, path, defaultValue) => {
362
+ if (!path || !isObject(object)) {
363
+ return defaultValue;
459
364
  }
365
+ const result = compact(path.split(/[,[\].]+?/)).reduce((result2, key) => isNullOrUndefined(result2) ? result2 : result2[key], object);
366
+ return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result;
460
367
  };
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
- var _a, _b, _c;
477
- const { t, i18n } = reactI18next.useTranslation();
478
- const lang = i18n.language.split("-")[0] === "ru" ? "ru" : "en";
479
- const source = ((_a = item == null ? void 0 : item.docsSnippet) == null ? void 0 : _a[lang]) || ((_b = item == null ? void 0 : item.docsSnippet) == null ? void 0 : _b.en) || ((_c = item == null ? void 0 : item.docsSnippet) == null ? void 0 : _c.ru) || "";
480
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
481
- /* @__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: [
482
- item && /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label, size: "small" }),
483
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: item == null ? void 0 : item.label })
484
- ] }) }) }),
485
- /* @__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") }) }),
486
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
487
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }),
488
- (item == null ? void 0 : 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: [
489
- t("integration.browse.install_docs"),
490
- /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
491
- ] }) })
492
- ] })
493
- ] }) });
494
- };
495
- const BrowsePage = () => {
496
- const { t } = reactI18next.useTranslation();
497
- const navigate = reactRouterDom.useNavigate();
498
- const [activeTab, setActiveTab] = React.useState("all");
499
- const [search, setSearch] = React.useState("");
500
- const [installItem, setInstallItem] = React.useState(null);
501
- const { data, isLoading } = reactQuery.useQuery({
502
- queryKey: ["integration-catalog"],
503
- queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
504
- });
505
- const items = (data == null ? void 0 : data.integrations) ?? [];
506
- const categories = React.useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
507
- const q = search.trim().toLowerCase();
508
- const filtered = items.filter((i) => {
509
- if (activeTab !== "all" && i.category !== activeTab) return false;
510
- if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
511
- return true;
512
- });
513
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
514
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
515
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
516
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
517
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("integration.browse.title") }),
518
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
519
- ] }),
520
- false
521
- ] }),
522
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
523
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
524
- /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
525
- categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
526
- ] }) }),
527
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
528
- ui.Input,
529
- {
530
- placeholder: t("integration.browse.searchPlaceholder"),
531
- value: search,
532
- onChange: (e) => setSearch(e.target.value)
533
- }
534
- ) })
535
- ] })
536
- ] }),
537
- 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: [
538
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
539
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
540
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
541
- /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label }),
542
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
543
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
544
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
545
- ] })
546
- ] }),
547
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
548
- ] }),
549
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
550
- ] }),
551
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
552
- item.installed && item.provider_id ? /* @__PURE__ */ jsxRuntime.jsx(
553
- ui.Button,
554
- {
555
- size: "small",
556
- variant: "primary",
557
- onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
558
- children: t("integration.browse.settings")
559
- }
560
- ) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
561
- /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
562
- t("integration.browse.add")
563
- ] }),
564
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
565
- ] })
566
- ] }, item.integrationId)) }),
567
- /* @__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: [
568
- t("integration.browse.learnMore"),
569
- " →"
570
- ] }) }),
571
- /* @__PURE__ */ jsxRuntime.jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
572
- ] });
573
- };
574
- const BrowseBreadcrumb = () => {
575
- const { t } = reactI18next.useTranslation();
576
- return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("integration.browse.title") });
577
- };
578
- const handle$1 = {
579
- breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(BrowseBreadcrumb, {})
580
- };
581
- const Dash = () => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "-" });
582
- const IntegrationFieldValue = ({
583
- field,
584
- value,
585
- secretConfigured = false
586
- }) => {
587
- var _a;
588
- const { t } = reactI18next.useTranslation();
589
- if (field.secret || field.control === "secret") {
590
- return secretConfigured ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: "••••••••" }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
591
- }
592
- if (field.control === "switch") {
593
- if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
594
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: value ? "green" : "grey", children: value ? t("integration.status.enabled") : t("integration.status.disabled") });
595
- }
596
- if (field.control === "json") {
597
- if (Array.isArray(value)) {
598
- return value.length ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.form.items", { count: value.length }) }) : /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
599
- }
600
- if (value == null) return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
601
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: t("integration.status.configured") });
602
- }
603
- if (value == null || value === "") return /* @__PURE__ */ jsxRuntime.jsx(Dash, {});
604
- if (field.control === "select") {
605
- const labelKey = (_a = field.optionLabels) == null ? void 0 : _a[String(value)];
606
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: labelKey ? t(labelKey) : String(value) });
607
- }
608
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", leading: "compact", children: String(value) });
609
- };
610
- var isCheckBoxInput = (element) => element.type === "checkbox";
611
- var isDateObject = (value) => value instanceof Date;
612
- var isNullOrUndefined = (value) => value == null;
613
- const isObjectType = (value) => typeof value === "object";
614
- var isObject = (value) => !isNullOrUndefined(value) && !Array.isArray(value) && isObjectType(value) && !isDateObject(value);
615
- var getEventValue = (event) => isObject(event) && event.target ? isCheckBoxInput(event.target) ? event.target.checked : event.target.value : event;
616
- var getNodeParentName = (name) => name.substring(0, name.search(/\.\d+(\.|$)/)) || name;
617
- var isNameInFieldArray = (names, name) => names.has(getNodeParentName(name));
618
- var isPlainObject = (tempObject) => {
619
- const prototypeCopy = tempObject.constructor && tempObject.constructor.prototype;
620
- return isObject(prototypeCopy) && prototypeCopy.hasOwnProperty("isPrototypeOf");
621
- };
622
- var isWeb = typeof window !== "undefined" && typeof window.HTMLElement !== "undefined" && typeof document !== "undefined";
623
- function cloneObject(data) {
624
- let copy;
625
- const isArray = Array.isArray(data);
626
- if (data instanceof Date) {
627
- copy = new Date(data);
628
- } else if (data instanceof Set) {
629
- copy = new Set(data);
630
- } else if (!(isWeb && (data instanceof Blob || data instanceof FileList)) && (isArray || isObject(data))) {
631
- copy = isArray ? [] : {};
632
- if (!isArray && !isPlainObject(data)) {
633
- copy = data;
634
- } else {
635
- for (const key in data) {
636
- if (data.hasOwnProperty(key)) {
637
- copy[key] = cloneObject(data[key]);
638
- }
639
- }
640
- }
641
- } else {
642
- return data;
643
- }
644
- return copy;
645
- }
646
- var compact = (value) => Array.isArray(value) ? value.filter(Boolean) : [];
647
- var isUndefined = (val) => val === void 0;
648
- var get = (object, path, defaultValue) => {
649
- if (!path || !isObject(object)) {
650
- return defaultValue;
651
- }
652
- const result = compact(path.split(/[,[\].]+?/)).reduce((result2, key) => isNullOrUndefined(result2) ? result2 : result2[key], object);
653
- return isUndefined(result) || result === object ? isUndefined(object[path]) ? defaultValue : object[path] : result;
654
- };
655
- var isBoolean = (value) => typeof value === "boolean";
656
- const EVENTS = {
657
- BLUR: "blur",
658
- FOCUS_OUT: "focusout",
659
- CHANGE: "change"
368
+ var isBoolean = (value) => typeof value === "boolean";
369
+ const EVENTS = {
370
+ BLUR: "blur",
371
+ FOCUS_OUT: "focusout",
372
+ CHANGE: "change"
660
373
  };
661
374
  const VALIDATION_MODE = {
662
375
  onBlur: "onBlur",
@@ -2234,358 +1947,655 @@ const IntegrationField = ({
2234
1947
  ] });
2235
1948
  }
2236
1949
  }
2237
- ),
2238
- field.hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: t(field.hint) })
2239
- ] });
2240
- };
2241
- function renderControl(field, f, secretConfigured, t) {
2242
- const placeholder = field.placeholder ? t(field.placeholder) : void 0;
2243
- const disabled = field.readonly === true;
2244
- switch (field.control) {
2245
- case "switch":
2246
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, { checked: !!f.value, onCheckedChange: f.onChange, disabled });
2247
- case "select":
2248
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: f.value ?? "", onValueChange: f.onChange, disabled, children: [
2249
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder }) }),
2250
- /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: (field.options ?? []).map((o) => {
2251
- var _a;
2252
- const labelKey = (_a = field.optionLabels) == null ? void 0 : _a[o];
2253
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o, children: labelKey ? t(labelKey) : o }, o);
2254
- }) })
2255
- ] });
2256
- case "number":
2257
- return /* @__PURE__ */ jsxRuntime.jsx(
2258
- ui.Input,
2259
- {
2260
- type: "number",
2261
- placeholder,
2262
- value: f.value ?? "",
2263
- disabled,
2264
- onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
2265
- }
2266
- );
2267
- case "url":
2268
- return /* @__PURE__ */ jsxRuntime.jsx(
2269
- ui.Input,
2270
- {
2271
- type: "url",
2272
- placeholder,
2273
- value: f.value ?? "",
2274
- disabled,
2275
- onChange: f.onChange
2276
- }
2277
- );
2278
- case "secret":
1950
+ ),
1951
+ field.hint && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: t(field.hint) })
1952
+ ] });
1953
+ };
1954
+ function renderControl(field, f, secretConfigured, t) {
1955
+ const placeholder = field.placeholder ? t(field.placeholder) : void 0;
1956
+ const disabled = field.readonly === true;
1957
+ switch (field.control) {
1958
+ case "switch":
1959
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Switch, { checked: !!f.value, onCheckedChange: f.onChange, disabled });
1960
+ case "select": {
1961
+ const declared = field.options ?? [];
1962
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1963
+ ui.Select,
1964
+ {
1965
+ value: f.value == null ? "" : String(f.value),
1966
+ onValueChange: (next) => f.onChange(declared.find((o) => String(o) === next) ?? next),
1967
+ disabled,
1968
+ children: [
1969
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder }) }),
1970
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: declared.map((o) => {
1971
+ var _a;
1972
+ const labelKey = (_a = field.optionLabels) == null ? void 0 : _a[String(o)];
1973
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: String(o), children: labelKey ? t(labelKey) : String(o) }, String(o));
1974
+ }) })
1975
+ ]
1976
+ }
1977
+ );
1978
+ }
1979
+ case "number":
1980
+ return /* @__PURE__ */ jsxRuntime.jsx(
1981
+ ui.Input,
1982
+ {
1983
+ type: "number",
1984
+ placeholder,
1985
+ value: f.value ?? "",
1986
+ disabled,
1987
+ onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
1988
+ }
1989
+ );
1990
+ case "url":
1991
+ return /* @__PURE__ */ jsxRuntime.jsx(
1992
+ ui.Input,
1993
+ {
1994
+ type: "url",
1995
+ placeholder,
1996
+ value: f.value ?? "",
1997
+ disabled,
1998
+ onChange: f.onChange
1999
+ }
2000
+ );
2001
+ case "secret":
2002
+ return /* @__PURE__ */ jsxRuntime.jsx(
2003
+ ui.Input,
2004
+ {
2005
+ type: "password",
2006
+ placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
2007
+ value: f.value ?? "",
2008
+ disabled,
2009
+ onChange: f.onChange,
2010
+ className: !f.value ? "[&~div]:hidden" : void 0
2011
+ }
2012
+ );
2013
+ case "json":
2014
+ return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { field, f });
2015
+ default:
2016
+ return /* @__PURE__ */ jsxRuntime.jsx(
2017
+ ui.Input,
2018
+ {
2019
+ placeholder,
2020
+ value: f.value ?? "",
2021
+ disabled,
2022
+ onChange: f.onChange
2023
+ }
2024
+ );
2025
+ }
2026
+ }
2027
+ function isFieldVisible(field, values) {
2028
+ const rule = field.visibleWhen;
2029
+ if (!rule) return true;
2030
+ return values[rule.field] === rule.equals;
2031
+ }
2032
+ function sectionDefaults(section, values) {
2033
+ const out = {};
2034
+ for (const f of section.fields) {
2035
+ if (f.secret || f.control === "secret") out[f.name] = "";
2036
+ else if (f.control === "switch") out[f.name] = (values == null ? void 0 : values[f.name]) ?? f.default ?? false;
2037
+ else if (f.control === "json") out[f.name] = (values == null ? void 0 : values[f.name]) ?? f.default;
2038
+ else out[f.name] = (values == null ? void 0 : values[f.name]) ?? f.default ?? "";
2039
+ }
2040
+ return out;
2041
+ }
2042
+ const EditSectionDrawer = ({
2043
+ open,
2044
+ onOpenChange,
2045
+ providerId,
2046
+ section,
2047
+ values,
2048
+ configuredSecrets = []
2049
+ }) => {
2050
+ const { t } = reactI18next.useTranslation();
2051
+ const qc = reactQuery.useQueryClient();
2052
+ const configured = new Set(configuredSecrets);
2053
+ const [defaults] = React.useState(() => sectionDefaults(section, values));
2054
+ const form = useForm({ values: defaults });
2055
+ const current = form.watch();
2056
+ const visibilityValues = { ...values, ...current };
2057
+ const save = reactQuery.useMutation({
2058
+ mutationFn: (sectionValues) => sdk.client.fetch(`/admin/integrations/${providerId}`, {
2059
+ method: "POST",
2060
+ // Only this section's values + its id — the server validates just this section and
2061
+ // merges over the rest of the stored config (keeping blank/untouched secrets).
2062
+ body: { section_id: section.id, values: sectionValues }
2063
+ }),
2064
+ onSuccess: () => {
2065
+ ui.toast.success(t("integration.toast.saved"));
2066
+ qc.invalidateQueries({ queryKey: ["integration", providerId] });
2067
+ qc.invalidateQueries({ queryKey: ["integration-overview"] });
2068
+ onOpenChange(false);
2069
+ },
2070
+ onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.saveFailed"))
2071
+ });
2072
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
2073
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: t(section.title) }) }),
2074
+ /* @__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(
2075
+ IntegrationField,
2076
+ {
2077
+ field: f,
2078
+ control: form.control,
2079
+ secretConfigured: configured.has(f.name)
2080
+ },
2081
+ f.name
2082
+ )) }),
2083
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
2084
+ /* @__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") }) }),
2085
+ /* @__PURE__ */ jsxRuntime.jsx(
2086
+ ui.Button,
2087
+ {
2088
+ size: "small",
2089
+ isLoading: save.isPending,
2090
+ onClick: form.handleSubmit((v) => save.mutate(v)),
2091
+ children: t("integration.actions.save")
2092
+ }
2093
+ )
2094
+ ] })
2095
+ ] }) });
2096
+ };
2097
+ const Row = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4", children: [
2098
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
2099
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-2", children })
2100
+ ] });
2101
+ const EditPage = () => {
2102
+ const { t } = reactI18next.useTranslation();
2103
+ const { provider_id } = reactRouterDom.useParams();
2104
+ const qc = reactQuery.useQueryClient();
2105
+ const navigate = reactRouterDom.useNavigate();
2106
+ const prompt = ui.usePrompt();
2107
+ const [editingSection, setEditingSection] = React.useState(null);
2108
+ const { data, isLoading } = reactQuery.useQuery({
2109
+ queryKey: ["integration", provider_id],
2110
+ queryFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`)
2111
+ });
2112
+ const descriptor = data == null ? void 0 : data.descriptor;
2113
+ const record = data == null ? void 0 : data.integration;
2114
+ const isConfigured = !!record;
2115
+ const isComplete = (data == null ? void 0 : data.is_complete) ?? false;
2116
+ const invalidate = () => {
2117
+ qc.invalidateQueries({ queryKey: ["integration", provider_id] });
2118
+ qc.invalidateQueries({ queryKey: ["integration-overview"] });
2119
+ };
2120
+ const test = reactQuery.useMutation({
2121
+ mutationFn: () => sdk.client.fetch(
2122
+ `/admin/integrations/${provider_id}/test-connection`,
2123
+ { method: "POST" }
2124
+ ),
2125
+ onSuccess: (r) => {
2126
+ r.status === "passed" ? ui.toast.success(t("integration.toast.connectionOk")) : ui.toast.warning(r.message ?? r.status);
2127
+ invalidate();
2128
+ },
2129
+ onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.testFailed"))
2130
+ });
2131
+ const toggleEnabled = reactQuery.useMutation({
2132
+ mutationFn: (next) => sdk.client.fetch(`/admin/integrations/${provider_id}/enable`, {
2133
+ method: "POST",
2134
+ body: { is_enabled: next }
2135
+ }),
2136
+ onSuccess: (_d, next) => {
2137
+ ui.toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
2138
+ invalidate();
2139
+ },
2140
+ onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.updateFailed"))
2141
+ });
2142
+ const remove = reactQuery.useMutation({
2143
+ mutationFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`, { method: "DELETE" }),
2144
+ onSuccess: () => {
2145
+ ui.toast.success(t("integration.toast.deleted"));
2146
+ invalidate();
2147
+ navigate("/settings/integrations");
2148
+ },
2149
+ onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.deleteFailed"))
2150
+ });
2151
+ const onDelete = async () => {
2152
+ const confirmed = await prompt({
2153
+ title: t("integration.delete.title"),
2154
+ description: t("integration.delete.description", {
2155
+ name: descriptor ? t(descriptor.displayName) : ""
2156
+ }),
2157
+ confirmText: t("integration.actions.delete"),
2158
+ cancelText: t("integration.actions.cancel")
2159
+ });
2160
+ if (confirmed) remove.mutate();
2161
+ };
2162
+ if (isLoading) {
2163
+ 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" }) });
2164
+ }
2165
+ if (!descriptor) {
2166
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
2167
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.unknown", { id: provider_id }) }),
2168
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/settings/integrations", className: "text-ui-fg-base", children: t("integration.back") })
2169
+ ] });
2170
+ }
2171
+ const version = (data == null ? void 0 : data.version) ?? null;
2172
+ const author = (data == null ? void 0 : data.author) ?? null;
2173
+ const authorUrl = (data == null ? void 0 : data.author_url) ?? null;
2174
+ 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") };
2175
+ const connection = descriptor.hasTestConnection ? (record == null ? void 0 : record.last_test_status) === "passed" ? { color: "green", label: t("integration.status.connected") } : (record == null ? void 0 : record.last_test_status) === "failed" ? { color: "red", label: t("integration.status.connectionFailed") } : null : null;
2176
+ const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
2177
+ const configuredSecrets = new Set((record == null ? void 0 : record.configured_secrets) ?? []);
2178
+ const sectionData = {
2179
+ providerId: provider_id,
2180
+ identifier: descriptor.identifier,
2181
+ values: (record == null ? void 0 : record.values) ?? {},
2182
+ isComplete
2183
+ };
2184
+ const renderSection = (section) => /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: section.id, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2185
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
2186
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t(section.title) }),
2187
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2188
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
2189
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: () => setEditingSection(section.id), children: [
2190
+ /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
2191
+ t("integration.actions.edit")
2192
+ ] }) })
2193
+ ] })
2194
+ ] }),
2195
+ section.fields.filter((f) => isFieldVisible(f, (record == null ? void 0 : record.values) ?? {})).map((f) => {
2196
+ var _a;
2197
+ return /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t(f.label), children: /* @__PURE__ */ jsxRuntime.jsx(
2198
+ IntegrationFieldValue,
2199
+ {
2200
+ field: f,
2201
+ value: ((_a = record == null ? void 0 : record.values) == null ? void 0 : _a[f.name]) ?? f.default,
2202
+ secretConfigured: configuredSecrets.has(f.name)
2203
+ }
2204
+ ) }, f.name);
2205
+ })
2206
+ ] }, section.id) });
2207
+ const mainSections = descriptor.sections.filter((s) => s.column !== "side");
2208
+ const sideSections = descriptor.sections.filter((s) => s.column === "side");
2209
+ const main = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2210
+ /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2211
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
2212
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2213
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
2214
+ /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
2215
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
2216
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
2217
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: t(descriptor.displayName) }),
2218
+ provider_id && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toModuleProviderId(provider_id, descriptor.category) })
2219
+ ] }),
2220
+ version && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
2221
+ t("integration.meta.version", { version }),
2222
+ author && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2223
+ " • ",
2224
+ t("integration.meta.authorPrefix"),
2225
+ " ",
2226
+ authorUrl ? /* @__PURE__ */ jsxRuntime.jsx(
2227
+ "a",
2228
+ {
2229
+ href: authorUrl,
2230
+ target: "_blank",
2231
+ rel: "noreferrer",
2232
+ className: "text-ui-fg-interactive",
2233
+ children: author
2234
+ }
2235
+ ) : author
2236
+ ] })
2237
+ ] })
2238
+ ] })
2239
+ ] }),
2240
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
2241
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
2242
+ connection && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: connection.color, children: connection.label }),
2243
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2244
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
2245
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
2246
+ descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
2247
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsx(
2248
+ ui.DropdownMenu.Item,
2249
+ {
2250
+ disabled: toggleEnabled.isPending,
2251
+ onClick: () => toggleEnabled.mutate(!record.is_enabled),
2252
+ children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
2253
+ }
2254
+ ),
2255
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
2256
+ isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
2257
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
2258
+ t("integration.actions.delete")
2259
+ ] })
2260
+ ] })
2261
+ ] })
2262
+ ] })
2263
+ ] }),
2264
+ descriptor.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) })
2265
+ ] }),
2266
+ /* @__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}`) }) })
2267
+ ] }) }),
2268
+ mainSections.map(renderSection),
2269
+ editing && /* @__PURE__ */ jsxRuntime.jsx(
2270
+ EditSectionDrawer,
2271
+ {
2272
+ open: true,
2273
+ onOpenChange: (open) => !open && setEditingSection(null),
2274
+ providerId: provider_id,
2275
+ section: editing,
2276
+ values: (record == null ? void 0 : record.values) ?? {},
2277
+ configuredSecrets: (record == null ? void 0 : record.configured_secrets) ?? []
2278
+ },
2279
+ editing.id
2280
+ )
2281
+ ] });
2282
+ const side = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sideSections.map(renderSection) });
2283
+ return /* @__PURE__ */ jsxRuntime.jsx(
2284
+ components.LayoutComposer,
2285
+ {
2286
+ widgetsZonePrefix: `gorgo.integration.${descriptor.identifier}`,
2287
+ preferredLayoutId: descriptor.preferredLayoutId,
2288
+ sections: { main, side },
2289
+ data: sectionData,
2290
+ hasOutlet: false
2291
+ }
2292
+ );
2293
+ };
2294
+ const IntegrationBreadcrumb = ({ params }) => {
2295
+ const { t } = reactI18next.useTranslation();
2296
+ const providerId = params.provider_id;
2297
+ const { data } = reactQuery.useQuery({
2298
+ queryKey: ["integration", providerId],
2299
+ queryFn: () => sdk.client.fetch(`/admin/integrations/${providerId}`),
2300
+ enabled: !!providerId
2301
+ });
2302
+ const descriptor = data == null ? void 0 : data.descriptor;
2303
+ if (!descriptor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
2304
+ const name = t(descriptor.displayName);
2305
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2306
+ name + " ",
2307
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: providerId })
2308
+ ] });
2309
+ };
2310
+ const handle$1 = {
2311
+ breadcrumb: ({ params }) => /* @__PURE__ */ jsxRuntime.jsx(IntegrationBreadcrumb, { params })
2312
+ };
2313
+ function isSafeHref(raw) {
2314
+ try {
2315
+ return new URL(raw).protocol === "https:";
2316
+ } catch {
2317
+ return false;
2318
+ }
2319
+ }
2320
+ const INLINE_RE = /`([^`\n]+)`|\[([^\]\n]*)\]\(([^)\s]+)\)|\*\*([^*\n]+)\*\*|\*([^*\n]+)\*|_([^_\n]+)_/;
2321
+ const MAX_DEPTH = 3;
2322
+ function parseInline(src, depth = 0) {
2323
+ if (!src) return [];
2324
+ if (depth >= MAX_DEPTH) return [{ type: "text", value: src }];
2325
+ const out = [];
2326
+ const re = new RegExp(INLINE_RE, "g");
2327
+ let last = 0;
2328
+ let m;
2329
+ while ((m = re.exec(src)) !== null) {
2330
+ const [full, code, linkText, linkHref, strong, star, underscore] = m;
2331
+ if (m.index > last) out.push({ type: "text", value: src.slice(last, m.index) });
2332
+ last = m.index + full.length;
2333
+ if (code !== void 0) {
2334
+ out.push({ type: "code", value: code });
2335
+ } else if (linkHref !== void 0) {
2336
+ const isImage = m.index > 0 && src[m.index - 1] === "!";
2337
+ if (!isImage && isSafeHref(linkHref)) {
2338
+ out.push({ type: "link", href: linkHref, children: parseInline(linkText, depth + 1) });
2339
+ } else {
2340
+ out.push({ type: "text", value: full });
2341
+ }
2342
+ } else if (strong !== void 0) {
2343
+ out.push({ type: "strong", children: parseInline(strong, depth + 1) });
2344
+ } else {
2345
+ out.push({ type: "em", children: parseInline(star ?? underscore, depth + 1) });
2346
+ }
2347
+ }
2348
+ if (last < src.length) out.push({ type: "text", value: src.slice(last) });
2349
+ return out;
2350
+ }
2351
+ const MD_MAX_CHARS = 8e3;
2352
+ const MD_MAX_BLOCKS = 200;
2353
+ const FENCE_RE = /^```(.*)$/;
2354
+ const HEADING_RE = /^(#{1,3})\s+(.*)$/;
2355
+ const HR_RE = /^-{3,}\s*$/;
2356
+ const UL_RE = /^[-*]\s+(.*)$/;
2357
+ const OL_RE = /^\d+\.\s+(.*)$/;
2358
+ function fenceLabel(info) {
2359
+ const first = info.trim().split(/\s+/)[0] ?? "";
2360
+ return first.replace(/[^a-zA-Z0-9._/-]/g, "").slice(0, 40) || null;
2361
+ }
2362
+ function parseMarkdown(src) {
2363
+ if (typeof src !== "string" || !src) return [];
2364
+ if (src.length > MD_MAX_CHARS) return [{ type: "truncated" }];
2365
+ const lines = src.replace(/\r\n?/g, "\n").split("\n");
2366
+ const blocks = [];
2367
+ let para = [];
2368
+ const flushPara = () => {
2369
+ if (!para.length) return;
2370
+ blocks.push({ type: "paragraph", children: parseInline(para.join(" ")) });
2371
+ para = [];
2372
+ };
2373
+ for (let i = 0; i < lines.length; i++) {
2374
+ const line = lines[i];
2375
+ const fence = FENCE_RE.exec(line);
2376
+ if (fence) {
2377
+ flushPara();
2378
+ const body = [];
2379
+ i++;
2380
+ while (i < lines.length && !FENCE_RE.test(lines[i])) body.push(lines[i++]);
2381
+ blocks.push({ type: "codeBlock", label: fenceLabel(fence[1]), value: body.join("\n") });
2382
+ continue;
2383
+ }
2384
+ if (!line.trim()) {
2385
+ flushPara();
2386
+ continue;
2387
+ }
2388
+ if (HR_RE.test(line)) {
2389
+ flushPara();
2390
+ blocks.push({ type: "hr" });
2391
+ continue;
2392
+ }
2393
+ const heading = HEADING_RE.exec(line);
2394
+ if (heading) {
2395
+ flushPara();
2396
+ blocks.push({
2397
+ type: "heading",
2398
+ level: heading[1].length + 1,
2399
+ children: parseInline(heading[2])
2400
+ });
2401
+ continue;
2402
+ }
2403
+ const ul = UL_RE.exec(line);
2404
+ const ol = ul ? null : OL_RE.exec(line);
2405
+ if (ul || ol) {
2406
+ flushPara();
2407
+ const ordered = !!ol;
2408
+ const itemRe = ordered ? OL_RE : UL_RE;
2409
+ const items = [];
2410
+ while (i < lines.length) {
2411
+ const item = itemRe.exec(lines[i]);
2412
+ if (!item) break;
2413
+ items.push(parseInline(item[1]));
2414
+ i++;
2415
+ }
2416
+ i--;
2417
+ blocks.push({ type: "list", ordered, items });
2418
+ continue;
2419
+ }
2420
+ para.push(line);
2421
+ }
2422
+ flushPara();
2423
+ const weight = blocks.reduce((n, b) => n + (b.type === "list" ? b.items.length : 1), 0);
2424
+ return weight > MD_MAX_BLOCKS ? [{ type: "truncated" }] : blocks;
2425
+ }
2426
+ const Inline = ({ nodes }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: nodes.map((node, i) => {
2427
+ switch (node.type) {
2428
+ case "text":
2429
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { children: node.value }, i);
2430
+ case "code":
2431
+ return /* @__PURE__ */ jsxRuntime.jsx("code", { className: "txt-compact-small bg-ui-bg-component rounded px-1 py-0.5", children: node.value }, i);
2432
+ case "strong":
2433
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "txt-compact-small-plus", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
2434
+ case "em":
2435
+ return /* @__PURE__ */ jsxRuntime.jsx("em", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children }) }, i);
2436
+ case "link":
2279
2437
  return /* @__PURE__ */ jsxRuntime.jsx(
2280
- ui.Input,
2438
+ "a",
2281
2439
  {
2282
- type: "password",
2283
- placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
2284
- value: f.value ?? "",
2285
- disabled,
2286
- onChange: f.onChange,
2287
- className: !f.value ? "[&~div]:hidden" : void 0
2288
- }
2440
+ href: node.href,
2441
+ target: "_blank",
2442
+ rel: "noopener noreferrer",
2443
+ className: "text-ui-fg-interactive hover:underline",
2444
+ children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: node.children })
2445
+ },
2446
+ i
2289
2447
  );
2290
- case "json":
2291
- return /* @__PURE__ */ jsxRuntime.jsx(JsonField, { field, f });
2292
- default:
2448
+ }
2449
+ }) });
2450
+ const Block = ({ block, truncatedLabel }) => {
2451
+ switch (block.type) {
2452
+ case "heading":
2453
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: block.level === 2 ? "h2" : "h3", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
2454
+ case "paragraph":
2455
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: block.children }) });
2456
+ case "list": {
2457
+ const List = block.ordered ? "ol" : "ul";
2293
2458
  return /* @__PURE__ */ jsxRuntime.jsx(
2294
- ui.Input,
2459
+ List,
2295
2460
  {
2296
- placeholder,
2297
- value: f.value ?? "",
2298
- disabled,
2299
- onChange: f.onChange
2461
+ className: `text-ui-fg-subtle txt-small flex flex-col gap-y-1 pl-5 ${block.ordered ? "list-decimal" : "list-disc"}`,
2462
+ children: block.items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(Inline, { nodes: item }) }, i))
2300
2463
  }
2301
2464
  );
2465
+ }
2466
+ case "codeBlock":
2467
+ 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: [
2468
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-x-2", children: [
2469
+ block.label ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-subtle", children: block.label }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
2470
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Copy, { content: block.value })
2471
+ ] }),
2472
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "txt-compact-small text-ui-fg-subtle overflow-x-auto whitespace-pre pb-2", children: block.value })
2473
+ ] });
2474
+ case "hr":
2475
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-ui-border-base border-t" });
2476
+ case "truncated":
2477
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: truncatedLabel });
2302
2478
  }
2303
- }
2304
- function isFieldVisible(field, values) {
2305
- const rule = field.visibleWhen;
2306
- if (!rule) return true;
2307
- return values[rule.field] === rule.equals;
2308
- }
2309
- function sectionDefaults(section, values) {
2310
- const out = {};
2311
- for (const f of section.fields) {
2312
- if (f.secret || f.control === "secret") out[f.name] = "";
2313
- else if (f.control === "switch") out[f.name] = (values == null ? void 0 : values[f.name]) ?? f.default ?? false;
2314
- else if (f.control === "json") out[f.name] = (values == null ? void 0 : values[f.name]) ?? f.default;
2315
- else out[f.name] = (values == null ? void 0 : values[f.name]) ?? f.default ?? "";
2316
- }
2317
- return out;
2318
- }
2319
- const EditSectionDrawer = ({
2320
- open,
2321
- onOpenChange,
2322
- providerId,
2323
- section,
2324
- values,
2325
- configuredSecrets = []
2479
+ };
2480
+ const Markdown = ({
2481
+ source,
2482
+ truncatedLabel
2326
2483
  }) => {
2327
- const { t } = reactI18next.useTranslation();
2328
- const qc = reactQuery.useQueryClient();
2329
- const configured = new Set(configuredSecrets);
2330
- const [defaults] = React.useState(() => sectionDefaults(section, values));
2331
- const form = useForm({ values: defaults });
2332
- const current = form.watch();
2333
- const visibilityValues = { ...values, ...current };
2334
- const save = reactQuery.useMutation({
2335
- mutationFn: (sectionValues) => sdk.client.fetch(`/admin/integrations/${providerId}`, {
2336
- method: "POST",
2337
- // Only this section's values + its id — the server validates just this section and
2338
- // merges over the rest of the stored config (keeping blank/untouched secrets).
2339
- body: { section_id: section.id, values: sectionValues }
2340
- }),
2341
- onSuccess: () => {
2342
- ui.toast.success(t("integration.toast.saved"));
2343
- qc.invalidateQueries({ queryKey: ["integration", providerId] });
2344
- qc.invalidateQueries({ queryKey: ["integration-overview"] });
2345
- onOpenChange(false);
2346
- },
2347
- onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.saveFailed"))
2348
- });
2349
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
2350
- /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: t(section.title) }) }),
2351
- /* @__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(
2352
- IntegrationField,
2353
- {
2354
- field: f,
2355
- control: form.control,
2356
- secretConfigured: configured.has(f.name)
2357
- },
2358
- f.name
2359
- )) }),
2360
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
2361
- /* @__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") }) }),
2362
- /* @__PURE__ */ jsxRuntime.jsx(
2363
- ui.Button,
2364
- {
2365
- size: "small",
2366
- isLoading: save.isPending,
2367
- onClick: form.handleSubmit((v) => save.mutate(v)),
2368
- children: t("integration.actions.save")
2369
- }
2370
- )
2371
- ] })
2372
- ] }) });
2484
+ const blocks = React.useMemo(() => parseMarkdown(source), [source]);
2485
+ if (!blocks.length) return null;
2486
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-4", children: blocks.map((block, i) => /* @__PURE__ */ jsxRuntime.jsx(Block, { block, truncatedLabel }, i)) });
2373
2487
  };
2374
- const Row = ({ label, children }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4", children: [
2375
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: label }),
2376
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-2", children })
2377
- ] });
2378
- const EditPage = () => {
2379
- const { t } = reactI18next.useTranslation();
2380
- const { provider_id } = reactRouterDom.useParams();
2381
- const qc = reactQuery.useQueryClient();
2382
- const navigate = reactRouterDom.useNavigate();
2383
- const prompt = ui.usePrompt();
2384
- const [editingSection, setEditingSection] = React.useState(null);
2385
- const { data, isLoading } = reactQuery.useQuery({
2386
- queryKey: ["integration", provider_id],
2387
- queryFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`)
2388
- });
2389
- const descriptor = data == null ? void 0 : data.descriptor;
2390
- const record = data == null ? void 0 : data.integration;
2391
- const isConfigured = !!record;
2392
- const isComplete = (data == null ? void 0 : data.is_complete) ?? false;
2393
- const invalidate = () => {
2394
- qc.invalidateQueries({ queryKey: ["integration", provider_id] });
2395
- qc.invalidateQueries({ queryKey: ["integration-overview"] });
2396
- };
2397
- const test = reactQuery.useMutation({
2398
- mutationFn: () => sdk.client.fetch(
2399
- `/admin/integrations/${provider_id}/test-connection`,
2400
- { method: "POST" }
2401
- ),
2402
- onSuccess: (r) => {
2403
- r.status === "passed" ? ui.toast.success(t("integration.toast.connectionOk")) : ui.toast.warning(r.message ?? r.status);
2404
- invalidate();
2405
- },
2406
- onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.testFailed"))
2407
- });
2408
- const toggleEnabled = reactQuery.useMutation({
2409
- mutationFn: (next) => sdk.client.fetch(`/admin/integrations/${provider_id}/enable`, {
2410
- method: "POST",
2411
- body: { is_enabled: next }
2412
- }),
2413
- onSuccess: (_d, next) => {
2414
- ui.toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
2415
- invalidate();
2416
- },
2417
- onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.updateFailed"))
2488
+ const PUBLISH_URL = "https://docs.gorgojs.com/medusa-modules/integration";
2489
+ const API_URL = "https://gorgojs.com";
2490
+ const iconSrc = (icon) => icon.startsWith("data:") ? icon : API_URL + icon;
2491
+ const InstallModal = ({
2492
+ item,
2493
+ onOpenChange
2494
+ }) => {
2495
+ var _a, _b, _c;
2496
+ const { t, i18n } = reactI18next.useTranslation();
2497
+ const lang = i18n.language.split("-")[0] === "ru" ? "ru" : "en";
2498
+ const source = ((_a = item == null ? void 0 : item.docsSnippet) == null ? void 0 : _a[lang]) || ((_b = item == null ? void 0 : item.docsSnippet) == null ? void 0 : _b.en) || ((_c = item == null ? void 0 : item.docsSnippet) == null ? void 0 : _c.ru) || "";
2499
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
2500
+ /* @__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: [
2501
+ item && /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label, size: "small" }),
2502
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: item == null ? void 0 : item.label })
2503
+ ] }) }) }),
2504
+ /* @__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") }) }),
2505
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Footer, { children: [
2506
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }),
2507
+ (item == null ? void 0 : 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: [
2508
+ t("integration.browse.install_docs"),
2509
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUpRightOnBox, {})
2510
+ ] }) })
2511
+ ] })
2512
+ ] }) });
2513
+ };
2514
+ const BrowsePage = () => {
2515
+ const { t } = reactI18next.useTranslation();
2516
+ const navigate = reactRouterDom.useNavigate();
2517
+ const [activeTab, setActiveTab] = React.useState("all");
2518
+ const [search, setSearch] = React.useState("");
2519
+ const [installItem, setInstallItem] = React.useState(null);
2520
+ const { data, isLoading } = reactQuery.useQuery({
2521
+ queryKey: ["integration-catalog"],
2522
+ queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
2418
2523
  });
2419
- const remove = reactQuery.useMutation({
2420
- mutationFn: () => sdk.client.fetch(`/admin/integrations/${provider_id}`, { method: "DELETE" }),
2421
- onSuccess: () => {
2422
- ui.toast.success(t("integration.toast.deleted"));
2423
- invalidate();
2424
- navigate("/settings/integrations");
2425
- },
2426
- onError: (e) => ui.toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.deleteFailed"))
2524
+ const items = (data == null ? void 0 : data.integrations) ?? [];
2525
+ const categories = React.useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
2526
+ const q = search.trim().toLowerCase();
2527
+ const filtered = items.filter((i) => {
2528
+ if (activeTab !== "all" && i.category !== activeTab) return false;
2529
+ if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
2530
+ return true;
2427
2531
  });
2428
- const onDelete = async () => {
2429
- const confirmed = await prompt({
2430
- title: t("integration.delete.title"),
2431
- description: t("integration.delete.description", {
2432
- name: descriptor ? t(descriptor.displayName) : ""
2433
- }),
2434
- confirmText: t("integration.actions.delete"),
2435
- cancelText: t("integration.actions.cancel")
2436
- });
2437
- if (confirmed) remove.mutate();
2438
- };
2439
- if (isLoading) {
2440
- 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" }) });
2441
- }
2442
- if (!descriptor) {
2443
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-6", children: [
2444
- /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.unknown", { id: provider_id }) }),
2445
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/settings/integrations", className: "text-ui-fg-base", children: t("integration.back") })
2446
- ] });
2447
- }
2448
- const version = (data == null ? void 0 : data.version) ?? null;
2449
- const author = (data == null ? void 0 : data.author) ?? null;
2450
- const authorUrl = (data == null ? void 0 : data.author_url) ?? null;
2451
- 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") };
2452
- const connection = descriptor.hasTestConnection ? (record == null ? void 0 : record.last_test_status) === "passed" ? { color: "green", label: t("integration.status.connected") } : (record == null ? void 0 : record.last_test_status) === "failed" ? { color: "red", label: t("integration.status.connectionFailed") } : null : null;
2453
- const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
2454
- const configuredSecrets = new Set((record == null ? void 0 : record.configured_secrets) ?? []);
2455
- const sectionData = {
2456
- providerId: provider_id,
2457
- identifier: descriptor.identifier,
2458
- values: (record == null ? void 0 : record.values) ?? {},
2459
- isComplete
2460
- };
2461
- const renderSection = (section) => /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: section.id, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2462
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
2463
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t(section.title) }),
2464
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2465
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
2466
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Content, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: () => setEditingSection(section.id), children: [
2467
- /* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
2468
- t("integration.actions.edit")
2469
- ] }) })
2532
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
2533
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2534
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
2535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
2536
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: t("integration.browse.title") }),
2537
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
2538
+ ] }),
2539
+ false
2540
+ ] }),
2541
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
2542
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs.List, { children: [
2543
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
2544
+ categories.map((c) => /* @__PURE__ */ jsxRuntime.jsx(ui.Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
2545
+ ] }) }),
2546
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-64", children: /* @__PURE__ */ jsxRuntime.jsx(
2547
+ ui.Input,
2548
+ {
2549
+ placeholder: t("integration.browse.searchPlaceholder"),
2550
+ value: search,
2551
+ onChange: (e) => setSearch(e.target.value)
2552
+ }
2553
+ ) })
2470
2554
  ] })
2471
2555
  ] }),
2472
- section.fields.filter((f) => isFieldVisible(f, (record == null ? void 0 : record.values) ?? {})).map((f) => {
2473
- var _a;
2474
- return /* @__PURE__ */ jsxRuntime.jsx(Row, { label: t(f.label), children: /* @__PURE__ */ jsxRuntime.jsx(
2475
- IntegrationFieldValue,
2476
- {
2477
- field: f,
2478
- value: ((_a = record == null ? void 0 : record.values) == null ? void 0 : _a[f.name]) ?? f.default,
2479
- secretConfigured: configuredSecrets.has(f.name)
2480
- }
2481
- ) }, f.name);
2482
- })
2483
- ] }, section.id) });
2484
- const mainSections = descriptor.sections.filter((s) => s.column !== "side");
2485
- const sideSections = descriptor.sections.filter((s) => s.column === "side");
2486
- const main = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2487
- /* @__PURE__ */ jsxRuntime.jsx(components.LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
2488
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
2489
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2556
+ 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: [
2557
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
2558
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
2490
2559
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
2491
- /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
2492
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
2493
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
2494
- /* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { children: t(descriptor.displayName) }),
2495
- provider_id && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: toModuleProviderId(provider_id, descriptor.category) })
2496
- ] }),
2497
- version && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
2498
- t("integration.meta.version", { version }),
2499
- author && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2500
- " • ",
2501
- t("integration.meta.authorPrefix"),
2502
- " ",
2503
- authorUrl ? /* @__PURE__ */ jsxRuntime.jsx(
2504
- "a",
2505
- {
2506
- href: authorUrl,
2507
- target: "_blank",
2508
- rel: "noreferrer",
2509
- className: "text-ui-fg-interactive",
2510
- children: author
2511
- }
2512
- ) : author
2513
- ] })
2514
- ] })
2560
+ /* @__PURE__ */ jsxRuntime.jsx(IntegrationIcon, { src: iconSrc(item.icon), alt: item.label }),
2561
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
2562
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
2563
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
2515
2564
  ] })
2516
2565
  ] }),
2517
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
2518
- /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
2519
- connection && /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: connection.color, children: connection.label }),
2520
- isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2521
- /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
2522
- /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
2523
- descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
2524
- isConfigured && /* @__PURE__ */ jsxRuntime.jsx(
2525
- ui.DropdownMenu.Item,
2526
- {
2527
- disabled: toggleEnabled.isPending,
2528
- onClick: () => toggleEnabled.mutate(!record.is_enabled),
2529
- children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
2530
- }
2531
- ),
2532
- isConfigured && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
2533
- isConfigured && /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
2534
- /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
2535
- t("integration.actions.delete")
2536
- ] })
2537
- ] })
2538
- ] })
2539
- ] })
2566
+ /* @__PURE__ */ jsxRuntime.jsx(ui.StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
2540
2567
  ] }),
2541
- descriptor.description && /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) })
2568
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
2542
2569
  ] }),
2543
- /* @__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}`) }) })
2570
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
2571
+ item.installed && item.provider_id ? /* @__PURE__ */ jsxRuntime.jsx(
2572
+ ui.Button,
2573
+ {
2574
+ size: "small",
2575
+ variant: "primary",
2576
+ onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
2577
+ children: t("integration.browse.settings")
2578
+ }
2579
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
2580
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
2581
+ t("integration.browse.add")
2582
+ ] }),
2583
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
2584
+ ] })
2585
+ ] }, item.integrationId)) }),
2586
+ /* @__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: [
2587
+ t("integration.browse.learnMore"),
2588
+ " →"
2544
2589
  ] }) }),
2545
- mainSections.map(renderSection),
2546
- editing && /* @__PURE__ */ jsxRuntime.jsx(
2547
- EditSectionDrawer,
2548
- {
2549
- open: true,
2550
- onOpenChange: (open) => !open && setEditingSection(null),
2551
- providerId: provider_id,
2552
- section: editing,
2553
- values: (record == null ? void 0 : record.values) ?? {},
2554
- configuredSecrets: (record == null ? void 0 : record.configured_secrets) ?? []
2555
- },
2556
- editing.id
2557
- )
2590
+ /* @__PURE__ */ jsxRuntime.jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
2558
2591
  ] });
2559
- const side = /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: sideSections.map(renderSection) });
2560
- return /* @__PURE__ */ jsxRuntime.jsx(
2561
- components.LayoutComposer,
2562
- {
2563
- widgetsZonePrefix: `gorgo.integration.${descriptor.identifier}`,
2564
- preferredLayoutId: descriptor.preferredLayoutId,
2565
- sections: { main, side },
2566
- data: sectionData,
2567
- hasOutlet: false
2568
- }
2569
- );
2570
2592
  };
2571
- const IntegrationBreadcrumb = ({ params }) => {
2593
+ const BrowseBreadcrumb = () => {
2572
2594
  const { t } = reactI18next.useTranslation();
2573
- const providerId = params.provider_id;
2574
- const { data } = reactQuery.useQuery({
2575
- queryKey: ["integration", providerId],
2576
- queryFn: () => sdk.client.fetch(`/admin/integrations/${providerId}`),
2577
- enabled: !!providerId
2578
- });
2579
- const descriptor = data == null ? void 0 : data.descriptor;
2580
- if (!descriptor) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, {});
2581
- const name = t(descriptor.displayName);
2582
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2583
- name + " ",
2584
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "grey", children: providerId })
2585
- ] });
2595
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: t("integration.browse.title") });
2586
2596
  };
2587
2597
  const handle = {
2588
- breadcrumb: ({ params }) => /* @__PURE__ */ jsxRuntime.jsx(IntegrationBreadcrumb, { params })
2598
+ breadcrumb: () => /* @__PURE__ */ jsxRuntime.jsx(BrowseBreadcrumb, {})
2589
2599
  };
2590
2600
  const integration$1 = {
2591
2601
  list: {
@@ -2808,13 +2818,13 @@ const routeModule = {
2808
2818
  handle: { label: config.label, translationNs: config.translationNs, ...handle$2 }
2809
2819
  },
2810
2820
  {
2811
- Component: BrowsePage,
2812
- path: "/settings/integrations/browse",
2821
+ Component: EditPage,
2822
+ path: "/settings/integrations/:provider_id",
2813
2823
  handle: handle$1
2814
2824
  },
2815
2825
  {
2816
- Component: EditPage,
2817
- path: "/settings/integrations/:provider_id",
2826
+ Component: BrowsePage,
2827
+ path: "/settings/integrations/browse",
2818
2828
  handle
2819
2829
  }
2820
2830
  ]