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

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