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