@gorgo/medusa-integration 0.0.3 → 0.0.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.
Files changed (75) hide show
  1. package/.medusa/server/src/admin/index.js +546 -119
  2. package/.medusa/server/src/admin/index.mjs +549 -122
  3. package/.medusa/server/src/api/admin/integrations/[provider_id]/route.js +7 -2
  4. package/.medusa/server/src/api/admin/integrations/__tests__/helpers.unit.spec.d.ts +1 -0
  5. package/.medusa/server/src/api/admin/integrations/__tests__/helpers.unit.spec.js +30 -0
  6. package/.medusa/server/src/api/admin/integrations/_helpers.d.ts +3 -3
  7. package/.medusa/server/src/api/admin/integrations/_helpers.js +8 -8
  8. package/.medusa/server/src/api/admin/integrations/catalog/route.d.ts +3 -0
  9. package/.medusa/server/src/api/admin/integrations/catalog/route.js +10 -0
  10. package/.medusa/server/src/api/admin/integrations/middlewares.js +16 -1
  11. package/.medusa/server/src/api/admin/integrations/route.js +17 -3
  12. package/.medusa/server/src/api/admin/integrations/validators.d.ts +7 -0
  13. package/.medusa/server/src/api/admin/integrations/validators.js +8 -2
  14. package/.medusa/server/src/index.d.ts +1 -1
  15. package/.medusa/server/src/jobs/__tests__/test-integration-connections.unit.spec.d.ts +1 -0
  16. package/.medusa/server/src/jobs/__tests__/test-integration-connections.unit.spec.js +27 -0
  17. package/.medusa/server/src/jobs/test-integration-connections.d.ts +7 -0
  18. package/.medusa/server/src/jobs/test-integration-connections.js +21 -8
  19. package/.medusa/server/src/lib/__tests__/catalog-fallback.unit.spec.d.ts +1 -0
  20. package/.medusa/server/src/lib/__tests__/catalog-fallback.unit.spec.js +37 -0
  21. package/.medusa/server/src/lib/__tests__/catalog.unit.spec.d.ts +1 -0
  22. package/.medusa/server/src/lib/__tests__/catalog.unit.spec.js +71 -0
  23. package/.medusa/server/src/lib/catalog.d.ts +9 -0
  24. package/.medusa/server/src/lib/catalog.generated.d.ts +2 -0
  25. package/.medusa/server/src/lib/catalog.generated.js +88 -0
  26. package/.medusa/server/src/lib/catalog.js +55 -0
  27. package/.medusa/server/src/modules/integration/descriptor/__tests__/build-zod.unit.spec.js +43 -1
  28. package/.medusa/server/src/modules/integration/descriptor/__tests__/define.unit.spec.js +3 -3
  29. package/.medusa/server/src/modules/integration/descriptor/__tests__/introspect.unit.spec.js +3 -3
  30. package/.medusa/server/src/modules/integration/descriptor/__tests__/section-write.unit.spec.d.ts +1 -0
  31. package/.medusa/server/src/modules/integration/descriptor/__tests__/section-write.unit.spec.js +64 -0
  32. package/.medusa/server/src/modules/integration/descriptor/__tests__/validate.unit.spec.js +28 -2
  33. package/.medusa/server/src/modules/integration/descriptor/build-zod.js +7 -1
  34. package/.medusa/server/src/modules/integration/descriptor/define.d.ts +5 -5
  35. package/.medusa/server/src/modules/integration/descriptor/define.js +1 -1
  36. package/.medusa/server/src/modules/integration/descriptor/introspect.js +2 -2
  37. package/.medusa/server/src/modules/integration/descriptor/meta.d.ts +1 -1
  38. package/.medusa/server/src/modules/integration/descriptor/section-write.d.ts +10 -0
  39. package/.medusa/server/src/modules/integration/descriptor/section-write.js +24 -0
  40. package/.medusa/server/src/modules/integration/loaders/__tests__/package-meta.unit.spec.d.ts +1 -0
  41. package/.medusa/server/src/modules/integration/loaders/__tests__/package-meta.unit.spec.js +63 -0
  42. package/.medusa/server/src/modules/integration/loaders/load-providers.js +10 -38
  43. package/.medusa/server/src/modules/integration/loaders/package-meta.d.ts +22 -0
  44. package/.medusa/server/src/modules/integration/loaders/package-meta.js +45 -0
  45. package/.medusa/server/src/modules/integration/loaders/resolve-package-meta.d.ts +8 -0
  46. package/.medusa/server/src/modules/integration/loaders/resolve-package-meta.js +59 -0
  47. package/.medusa/server/src/modules/integration/migrations/Migration20260720130920.d.ts +5 -0
  48. package/.medusa/server/src/modules/integration/migrations/Migration20260720130920.js +18 -0
  49. package/.medusa/server/src/modules/integration/migrations/Migration20260724101525.d.ts +5 -0
  50. package/.medusa/server/src/modules/integration/migrations/Migration20260724101525.js +16 -0
  51. package/.medusa/server/src/modules/integration/models/integration.d.ts +9 -5
  52. package/.medusa/server/src/modules/integration/models/integration.js +3 -3
  53. package/.medusa/server/src/modules/integration/services/__tests__/integration-module.unit.spec.d.ts +1 -0
  54. package/.medusa/server/src/modules/integration/services/__tests__/integration-module.unit.spec.js +344 -0
  55. package/.medusa/server/src/modules/integration/services/__tests__/integration-provider.unit.spec.js +2 -2
  56. package/.medusa/server/src/modules/integration/services/integration-module.d.ts +48 -11
  57. package/.medusa/server/src/modules/integration/services/integration-module.js +56 -6
  58. package/.medusa/server/src/modules/integration/types/index.d.ts +11 -0
  59. package/.medusa/server/src/modules/integration/types/index.js +4 -2
  60. package/.medusa/server/src/modules/integration/utils/integration.d.ts +11 -7
  61. package/.medusa/server/src/modules/integration/utils/integration.js +12 -8
  62. package/.medusa/server/src/subscribers/__tests__/invalidate-options-cache.unit.spec.d.ts +1 -0
  63. package/.medusa/server/src/subscribers/__tests__/invalidate-options-cache.unit.spec.js +53 -0
  64. package/.medusa/server/src/types/http/integration/admin/entities.d.ts +39 -4
  65. package/.medusa/server/src/types/http/integration/admin/responses.d.ts +17 -2
  66. package/.medusa/server/src/types/integration/common.d.ts +3 -3
  67. package/.medusa/server/src/workflows/integration/steps/apply-integration-section.d.ts +1 -1
  68. package/.medusa/server/src/workflows/integration/steps/apply-integration-section.js +13 -20
  69. package/.medusa/server/src/workflows/integration/steps/set-integration-enabled.d.ts +1 -1
  70. package/.medusa/server/src/workflows/integration/steps/upsert-integration-record.d.ts +2 -2
  71. package/.medusa/server/src/workflows/integration/workflows/set-integration-enabled.d.ts +1 -1
  72. package/.medusa/server/src/workflows/integration/workflows/upsert-integration.js +2 -2
  73. package/README.md +41 -2
  74. package/README.ru.md +85 -0
  75. package/package.json +7 -3
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { PuzzleSolid, CogSixTooth, Spinner, EllipsisHorizontal, Trash, PencilSquare } from "@medusajs/icons";
4
- import { clx, Container, Heading, Text, Badge, StatusBadge, Label, Input, Select, Switch, Textarea, toast, Drawer, Button, usePrompt, DropdownMenu, IconButton } from "@medusajs/ui";
5
- import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query";
3
+ import { PuzzleSolid, CogSixTooth, Trash, Spinner, EllipsisHorizontal, PencilSquare, Plus } 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
+ import { useQueryClient, useQuery, keepPreviousData, useMutation } from "@tanstack/react-query";
6
+ import React, { useState, useMemo } from "react";
6
7
  import { useTranslation } from "react-i18next";
7
- import { Link, useParams, useNavigate } from "react-router-dom";
8
+ import { useNavigate, useParams, Link } from "react-router-dom";
8
9
  import Medusa from "@medusajs/js-sdk";
9
- import React, { useState } from "react";
10
10
  import { LayoutComposer } from "@medusajs/dashboard/components";
11
11
  const sdk = new Medusa({
12
12
  baseUrl: "/",
@@ -18,9 +18,23 @@ const sdk = new Medusa({
18
18
  const IntegrationIcon = ({
19
19
  src,
20
20
  alt,
21
- size = "base"
21
+ size = "base",
22
+ framed = true
22
23
  }) => {
23
- const dim = size === "small" ? "h-6 w-6" : "h-8 w-8";
24
+ const dim = size === "small" ? "h-6 w-6" : "h-10 w-10";
25
+ const [errored, setErrored] = useState(false);
26
+ const showImage = !!src && !errored;
27
+ if (showImage && !framed) {
28
+ return /* @__PURE__ */ jsx(
29
+ "img",
30
+ {
31
+ src,
32
+ alt,
33
+ className: clx("shrink-0 object-contain", dim),
34
+ onError: () => setErrored(true)
35
+ }
36
+ );
37
+ }
24
38
  return /* @__PURE__ */ jsx(
25
39
  "div",
26
40
  {
@@ -28,50 +42,237 @@ const IntegrationIcon = ({
28
42
  "bg-ui-bg-component shadow-borders-base flex shrink-0 items-center justify-center overflow-hidden rounded-md",
29
43
  dim
30
44
  ),
31
- children: src ? /* @__PURE__ */ jsx("img", { src, alt, className: "h-full w-full object-cover" }) : /* @__PURE__ */ jsx(PuzzleSolid, { className: "text-ui-fg-subtle" })
45
+ children: showImage ? /* @__PURE__ */ jsx(
46
+ "img",
47
+ {
48
+ src,
49
+ alt,
50
+ className: "h-full w-full object-cover",
51
+ onError: () => setErrored(true)
52
+ }
53
+ ) : /* @__PURE__ */ jsx(PuzzleSolid, { className: "text-ui-fg-subtle" })
32
54
  }
33
55
  );
34
56
  };
57
+ const PAGE_SIZE = 20;
58
+ const DOCS_URL = "https://docs.gorgojs.com/ru/medusa-plugins/integration-module";
59
+ const IntegrationCell = ({ item }) => {
60
+ const { t } = useTranslation();
61
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
62
+ /* @__PURE__ */ jsx(IntegrationIcon, { src: item.icon, alt: t(item.display_name), size: "base" }),
63
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
64
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
65
+ /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", weight: "plus", children: t(item.display_name) }),
66
+ item.provider_id && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: item.provider_id })
67
+ ] }),
68
+ item.version && /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
69
+ t("integration.meta.version", { version: item.version }),
70
+ item.author && /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ " • ",
72
+ t("integration.meta.authorPrefix"),
73
+ " ",
74
+ item.author_url ? /* @__PURE__ */ jsx(
75
+ "a",
76
+ {
77
+ href: item.author_url,
78
+ target: "_blank",
79
+ rel: "noreferrer",
80
+ className: "text-ui-fg-interactive",
81
+ onClick: (e) => e.stopPropagation(),
82
+ children: item.author
83
+ }
84
+ ) : item.author
85
+ ] })
86
+ ] })
87
+ ] })
88
+ ] });
89
+ };
90
+ const StatusCell = ({ item }) => {
91
+ const { t } = useTranslation();
92
+ const lifecycle = !item.is_configured ? { color: "grey", label: t("integration.status.notConfigured") } : !item.is_complete ? { color: "orange", label: t("integration.status.incomplete") } : item.is_enabled ? { color: "green", label: t("integration.status.enabled") } : { color: "grey", label: t("integration.status.disabled") };
93
+ const connection = item.has_test_connection ? item.last_test_status === "passed" ? { color: "green", label: t("integration.status.connected") } : item.last_test_status === "failed" ? { color: "red", label: t("integration.status.connectionFailed") } : null : null;
94
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-1", children: [
95
+ /* @__PURE__ */ jsx(StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
96
+ connection && /* @__PURE__ */ jsx(StatusBadge, { color: connection.color, children: connection.label })
97
+ ] });
98
+ };
99
+ const columnHelper = createDataTableColumnHelper();
35
100
  const IntegrationsPage = () => {
36
101
  const { t } = useTranslation();
102
+ const navigate = useNavigate();
103
+ const qc = useQueryClient();
104
+ const prompt = usePrompt();
105
+ const [activeTab, setActiveTab] = useState("all");
106
+ const [search, setSearch] = useState("");
107
+ const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: PAGE_SIZE });
108
+ const category = activeTab === "all" ? void 0 : activeTab;
109
+ const queryParams = {
110
+ q: search || void 0,
111
+ category,
112
+ limit: pagination.pageSize,
113
+ offset: pagination.pageIndex * pagination.pageSize
114
+ };
37
115
  const { data, isLoading } = useQuery({
38
- queryKey: ["integration-overview"],
39
- queryFn: () => sdk.client.fetch("/admin/integrations")
116
+ queryKey: ["integration-overview", queryParams],
117
+ queryFn: () => sdk.client.fetch("/admin/integrations", { query: queryParams }),
118
+ placeholderData: keepPreviousData
40
119
  });
41
- const items = (data == null ? void 0 : data.integrations) ?? [];
42
- const byModule = items.reduce((acc, i) => {
43
- var _a;
44
- (acc[_a = i.module] ?? (acc[_a] = [])).push(i);
45
- return acc;
46
- }, {});
120
+ const integrations = (data == null ? void 0 : data.integrations) ?? [];
121
+ const count = (data == null ? void 0 : data.count) ?? 0;
122
+ const categories = (data == null ? void 0 : data.categories) ?? [];
123
+ const invalidate = () => qc.invalidateQueries({ queryKey: ["integration-overview"] });
124
+ const toggleEnabled = useMutation({
125
+ mutationFn: ({ providerId, next }) => sdk.client.fetch(`/admin/integrations/${providerId}/enable`, {
126
+ method: "POST",
127
+ body: { is_enabled: next }
128
+ }),
129
+ onSuccess: (_d, { next }) => {
130
+ toast.success(next ? t("integration.toast.enabled") : t("integration.toast.disabled"));
131
+ invalidate();
132
+ },
133
+ onError: (e) => toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.updateFailed"))
134
+ });
135
+ const test = useMutation({
136
+ mutationFn: (providerId) => sdk.client.fetch(
137
+ `/admin/integrations/${providerId}/test-connection`,
138
+ { method: "POST" }
139
+ ),
140
+ onSuccess: (r) => {
141
+ r.status === "passed" ? toast.success(t("integration.toast.connectionOk")) : toast.warning(r.message ?? r.status);
142
+ invalidate();
143
+ },
144
+ onError: (e) => toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.testFailed"))
145
+ });
146
+ const remove = useMutation({
147
+ mutationFn: (providerId) => sdk.client.fetch(`/admin/integrations/${providerId}`, { method: "DELETE" }),
148
+ onSuccess: () => {
149
+ toast.success(t("integration.toast.deleted"));
150
+ invalidate();
151
+ },
152
+ onError: (e) => toast.error((e == null ? void 0 : e.message) ?? t("integration.toast.deleteFailed"))
153
+ });
154
+ const onDelete = async (item) => {
155
+ const confirmed = await prompt({
156
+ title: t("integration.delete.title"),
157
+ description: t("integration.delete.description", { name: t(item.display_name) }),
158
+ confirmText: t("integration.actions.delete"),
159
+ cancelText: t("integration.actions.cancel")
160
+ });
161
+ if (confirmed) remove.mutate(item.provider_id);
162
+ };
163
+ const buildActions = (item) => {
164
+ const groups = [];
165
+ const primary = [];
166
+ if (item.is_configured) {
167
+ primary.push({
168
+ label: item.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable"),
169
+ onClick: () => toggleEnabled.mutate({ providerId: item.provider_id, next: !item.is_enabled })
170
+ });
171
+ }
172
+ if (item.is_configured && item.has_test_connection && item.is_complete) {
173
+ primary.push({
174
+ label: t("integration.actions.test"),
175
+ onClick: () => test.mutate(item.provider_id)
176
+ });
177
+ }
178
+ if (primary.length) groups.push(primary);
179
+ if (item.is_configured) {
180
+ groups.push([
181
+ {
182
+ label: t("integration.actions.delete"),
183
+ icon: /* @__PURE__ */ jsx(Trash, {}),
184
+ onClick: () => onDelete(item)
185
+ }
186
+ ]);
187
+ }
188
+ return groups.length > 0 ? groups : null;
189
+ };
190
+ const columns = useMemo(
191
+ () => [
192
+ columnHelper.display({
193
+ id: "integration",
194
+ header: t("integration.columns.integration"),
195
+ cell: ({ row }) => /* @__PURE__ */ jsx(IntegrationCell, { item: row.original })
196
+ }),
197
+ columnHelper.display({
198
+ id: "description",
199
+ header: t("integration.columns.description"),
200
+ cell: ({ row }) => row.original.description ? /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle line-clamp-2", children: t(row.original.description) }) : null
201
+ }),
202
+ columnHelper.display({
203
+ id: "category",
204
+ header: t("integration.columns.category"),
205
+ cell: ({ row }) => /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: t(`integration.categories.${row.original.category}`) })
206
+ }),
207
+ columnHelper.display({
208
+ id: "status",
209
+ header: t("integration.columns.status"),
210
+ cell: ({ row }) => /* @__PURE__ */ jsx(StatusCell, { item: row.original })
211
+ }),
212
+ columnHelper.action({
213
+ actions: (ctx) => buildActions(ctx.row.original)
214
+ })
215
+ ],
216
+ // Safe to memo on [t] only: react-query keeps each mutation's `mutate` ref stable across
217
+ // renders (options updated internally), so the closed-over buildActions/mutations stay current.
218
+ // eslint-disable-next-line react-hooks/exhaustive-deps
219
+ [t]
220
+ );
221
+ const table = useDataTable({
222
+ data: integrations,
223
+ columns,
224
+ rowCount: count,
225
+ isLoading,
226
+ getRowId: (row) => row.provider_id,
227
+ onRowClick: (_e, row) => {
228
+ const item = row.original;
229
+ navigate(`/settings/integrations/${item.provider_id}`);
230
+ },
231
+ search: { state: search, onSearchChange: setSearch },
232
+ pagination: { state: pagination, onPaginationChange: setPagination }
233
+ });
234
+ const isFiltered = activeTab !== "all" || search.trim().length > 0;
47
235
  return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
48
- /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Heading, { level: "h2", children: t("integration.list.title") }) }),
49
- isLoading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center px-6 py-8", children: /* @__PURE__ */ jsx(Spinner, { className: "text-ui-fg-subtle animate-spin" }) }) : items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.list.empty") }) }) : Object.entries(byModule).map(([module, group]) => /* @__PURE__ */ jsxs("div", { className: "px-6 py-4", children: [
50
- /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", children: t(`integration.modules.${module}`) }),
51
- /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-col gap-2", children: group.map((i) => {
52
- const configured = i.is_configured && i.is_enabled;
53
- return /* @__PURE__ */ jsxs(
54
- Link,
236
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
237
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
238
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: t("integration.list.title") }),
239
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.list.subtitle") })
240
+ ] }),
241
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "primary", onClick: () => navigate("/settings/integrations/browse"), children: t("integration.list.browseIntegrations") })
242
+ ] }),
243
+ /* @__PURE__ */ jsxs(DataTable, { instance: table, className: "[&_td]:py-3", children: [
244
+ /* @__PURE__ */ jsx(DataTable.Toolbar, { children: /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between gap-3", children: [
245
+ /* @__PURE__ */ jsx(
246
+ Tabs,
55
247
  {
56
- to: `/settings/integrations/${i.provider_id}`,
57
- className: "flex items-center justify-between rounded-md border px-3 py-2 hover:bg-ui-bg-base-hover",
58
- children: [
59
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
60
- /* @__PURE__ */ jsx(IntegrationIcon, { src: i.icon, alt: t(i.display_name), size: "small" }),
61
- /* @__PURE__ */ jsx(Text, { size: "small", children: t(i.display_name) }),
62
- i.instance_id && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: i.instance_id })
63
- ] }),
64
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
65
- i.last_test_status === "passed" && /* @__PURE__ */ jsx(Badge, { color: "green", size: "2xsmall", children: t("integration.list.tested") }),
66
- i.last_test_status === "failed" && /* @__PURE__ */ jsx(Badge, { color: "red", size: "2xsmall", children: t("integration.list.testFailed") }),
67
- i.is_configured && !i.is_complete ? /* @__PURE__ */ jsx(StatusBadge, { color: "orange", children: t("integration.status.incomplete") }) : /* @__PURE__ */ jsx(StatusBadge, { color: configured ? "green" : "grey", children: !i.is_configured ? t("integration.status.notConfigured") : i.is_enabled ? t("integration.status.enabled") : t("integration.status.disabled") })
68
- ] })
69
- ]
70
- },
71
- i.provider_id
72
- );
73
- }) })
74
- ] }, module))
248
+ value: activeTab,
249
+ onValueChange: (v) => {
250
+ setActiveTab(v);
251
+ setPagination((p) => ({ ...p, pageIndex: 0 }));
252
+ },
253
+ children: /* @__PURE__ */ jsxs(Tabs.List, { children: [
254
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
255
+ categories.map((c) => /* @__PURE__ */ jsx(Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
256
+ ] })
257
+ }
258
+ ),
259
+ /* @__PURE__ */ jsx(DataTable.Search, { placeholder: t("integration.list.searchPlaceholder") })
260
+ ] }) }),
261
+ /* @__PURE__ */ jsx(
262
+ DataTable.Table,
263
+ {
264
+ emptyState: {
265
+ empty: { heading: isFiltered ? t("integration.list.emptyFiltered") : t("integration.list.empty") },
266
+ filtered: { heading: t("integration.list.emptyFiltered") }
267
+ }
268
+ }
269
+ ),
270
+ /* @__PURE__ */ jsx(DataTable.Pagination, {})
271
+ ] }),
272
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center px-6 py-4", children: /* @__PURE__ */ jsxs("a", { href: DOCS_URL, target: "_blank", rel: "noreferrer", className: "text-ui-fg-base txt-small", children: [
273
+ t("integration.list.learnMore"),
274
+ " →"
275
+ ] }) })
75
276
  ] });
76
277
  };
77
278
  const config = defineRouteConfig({
@@ -83,7 +284,7 @@ const IntegrationsBreadcrumb = () => {
83
284
  const { t } = useTranslation();
84
285
  return /* @__PURE__ */ jsx(Fragment, { children: t("integration.list.title") });
85
286
  };
86
- const handle$1 = {
287
+ const handle$2 = {
87
288
  breadcrumb: () => /* @__PURE__ */ jsx(IntegrationsBreadcrumb, {})
88
289
  };
89
290
  const Dash = () => /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: "-" });
@@ -1679,7 +1880,7 @@ function useForm(props = {}) {
1679
1880
  _formControl.current.formState = getProxyFormState(formState, control);
1680
1881
  return _formControl.current;
1681
1882
  }
1682
- const jsonToText = (v) => v === void 0 || v === "" ? "" : JSON.stringify(v, null, 2);
1883
+ const jsonToText = (v) => v == null || v === "" ? "" : JSON.stringify(v, null, 2);
1683
1884
  const JsonField = ({ field, f }) => {
1684
1885
  const { t } = useTranslation();
1685
1886
  const [raw, setRaw] = useState(() => jsonToText(f.value));
@@ -1699,7 +1900,7 @@ const JsonField = ({ field, f }) => {
1699
1900
  setRaw(text);
1700
1901
  if (text.trim() === "") {
1701
1902
  setError(null);
1702
- f.onChange(void 0);
1903
+ f.onChange(null);
1703
1904
  return;
1704
1905
  }
1705
1906
  try {
@@ -1769,7 +1970,18 @@ function renderControl(field, f, secretConfigured, t) {
1769
1970
  placeholder,
1770
1971
  value: f.value ?? "",
1771
1972
  disabled,
1772
- onChange: (e) => f.onChange(e.target.value === "" ? void 0 : e.target.valueAsNumber)
1973
+ onChange: (e) => f.onChange(e.target.value === "" ? null : e.target.valueAsNumber)
1974
+ }
1975
+ );
1976
+ case "url":
1977
+ return /* @__PURE__ */ jsx(
1978
+ Input,
1979
+ {
1980
+ type: "url",
1981
+ placeholder,
1982
+ value: f.value ?? "",
1983
+ disabled,
1984
+ onChange: f.onChange
1773
1985
  }
1774
1986
  );
1775
1987
  case "secret":
@@ -1780,7 +1992,8 @@ function renderControl(field, f, secretConfigured, t) {
1780
1992
  placeholder: secretConfigured ? t("integration.form.keepSecret") : placeholder ?? "",
1781
1993
  value: f.value ?? "",
1782
1994
  disabled,
1783
- onChange: f.onChange
1995
+ onChange: f.onChange,
1996
+ className: !f.value ? "[&~div]:hidden" : void 0
1784
1997
  }
1785
1998
  );
1786
1999
  case "json":
@@ -1818,10 +2031,11 @@ const EditSectionDrawer = ({
1818
2031
  providerId,
1819
2032
  section,
1820
2033
  values,
1821
- hasSecrets = false
2034
+ configuredSecrets = []
1822
2035
  }) => {
1823
2036
  const { t } = useTranslation();
1824
2037
  const qc = useQueryClient();
2038
+ const configured = new Set(configuredSecrets);
1825
2039
  const [defaults] = useState(() => sectionDefaults(section, values));
1826
2040
  const form = useForm({ values: defaults });
1827
2041
  const current = form.watch();
@@ -1848,7 +2062,7 @@ const EditSectionDrawer = ({
1848
2062
  {
1849
2063
  field: f,
1850
2064
  control: form.control,
1851
- secretConfigured: hasSecrets
2065
+ secretConfigured: configured.has(f.name)
1852
2066
  },
1853
2067
  f.name
1854
2068
  )) }),
@@ -1866,7 +2080,6 @@ const EditSectionDrawer = ({
1866
2080
  ] })
1867
2081
  ] }) });
1868
2082
  };
1869
- const testColor = (s) => s === "passed" ? "green" : s === "failed" ? "red" : "grey";
1870
2083
  const Row = ({ label, children }) => /* @__PURE__ */ jsxs("div", { className: "text-ui-fg-subtle grid grid-cols-2 px-6 py-4", children: [
1871
2084
  /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", leading: "compact", children: label }),
1872
2085
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-x-2", children })
@@ -1938,10 +2151,16 @@ const EditPage = () => {
1938
2151
  if (!descriptor) {
1939
2152
  return /* @__PURE__ */ jsxs(Container, { className: "p-6", children: [
1940
2153
  /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.unknown", { id: provider_id }) }),
1941
- /* @__PURE__ */ jsx(Link, { to: "/settings/integrations", className: "text-ui-fg-interactive", children: t("integration.back") })
2154
+ /* @__PURE__ */ jsx(Link, { to: "/settings/integrations", className: "text-ui-fg-base", children: t("integration.back") })
1942
2155
  ] });
1943
2156
  }
2157
+ const version = (data == null ? void 0 : data.version) ?? null;
2158
+ const author = (data == null ? void 0 : data.author) ?? null;
2159
+ const authorUrl = (data == null ? void 0 : data.author_url) ?? null;
2160
+ 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") };
2161
+ 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;
1944
2162
  const editing = descriptor.sections.find((s) => s.id === editingSection) ?? null;
2163
+ const configuredSecrets = new Set((record == null ? void 0 : record.configured_secrets) ?? []);
1945
2164
  const sectionData = {
1946
2165
  providerId: provider_id,
1947
2166
  identifier: descriptor.identifier,
@@ -1966,7 +2185,7 @@ const EditPage = () => {
1966
2185
  {
1967
2186
  field: f,
1968
2187
  value: (_a = record == null ? void 0 : record.values) == null ? void 0 : _a[f.name],
1969
- secretConfigured: !!(record == null ? void 0 : record.has_secrets)
2188
+ secretConfigured: configuredSecrets.has(f.name)
1970
2189
  }
1971
2190
  ) }, f.name);
1972
2191
  })
@@ -1975,40 +2194,62 @@ const EditPage = () => {
1975
2194
  const sideSections = descriptor.sections.filter((s) => s.column === "side");
1976
2195
  const main = /* @__PURE__ */ jsxs(Fragment, { children: [
1977
2196
  /* @__PURE__ */ jsx(LayoutComposer.Entry, { id: "main", children: /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
1978
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
1979
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-3", children: [
1980
- /* @__PURE__ */ jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
1981
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
1982
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
1983
- /* @__PURE__ */ jsx(Heading, { children: t(descriptor.displayName) }),
1984
- descriptor.instanceId && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: descriptor.instanceId })
1985
- ] }),
1986
- descriptor.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) })
1987
- ] })
1988
- ] }),
1989
- isConfigured && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
1990
- /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsx(EllipsisHorizontal, {}) }) }),
1991
- /* @__PURE__ */ jsxs(DropdownMenu.Content, { children: [
1992
- descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsx(DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
1993
- isConfigured && /* @__PURE__ */ jsx(
1994
- DropdownMenu.Item,
1995
- {
1996
- disabled: toggleEnabled.isPending,
1997
- onClick: () => toggleEnabled.mutate(!record.is_enabled),
1998
- children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
1999
- }
2000
- ),
2001
- isConfigured && /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
2002
- isConfigured && /* @__PURE__ */ jsxs(DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
2003
- /* @__PURE__ */ jsx(Trash, { className: "text-ui-fg-subtle" }),
2004
- t("integration.actions.delete")
2197
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3 px-6 py-4", children: [
2198
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2199
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-3", children: [
2200
+ /* @__PURE__ */ jsx(IntegrationIcon, { src: descriptor.icon, alt: t(descriptor.displayName) }),
2201
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-1", children: [
2202
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
2203
+ /* @__PURE__ */ jsx(Heading, { children: t(descriptor.displayName) }),
2204
+ provider_id && /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: provider_id })
2205
+ ] }),
2206
+ version && /* @__PURE__ */ jsxs(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: [
2207
+ t("integration.meta.version", { version }),
2208
+ author && /* @__PURE__ */ jsxs(Fragment, { children: [
2209
+ " • ",
2210
+ t("integration.meta.authorPrefix"),
2211
+ " ",
2212
+ authorUrl ? /* @__PURE__ */ jsx(
2213
+ "a",
2214
+ {
2215
+ href: authorUrl,
2216
+ target: "_blank",
2217
+ rel: "noreferrer",
2218
+ className: "text-ui-fg-interactive",
2219
+ children: author
2220
+ }
2221
+ ) : author
2222
+ ] })
2223
+ ] })
2224
+ ] })
2225
+ ] }),
2226
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
2227
+ /* @__PURE__ */ jsx(StatusBadge, { color: lifecycle.color, children: lifecycle.label }),
2228
+ connection && /* @__PURE__ */ jsx(StatusBadge, { color: connection.color, children: connection.label }),
2229
+ isConfigured && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2230
+ /* @__PURE__ */ jsx(DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsx(IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsx(EllipsisHorizontal, {}) }) }),
2231
+ /* @__PURE__ */ jsxs(DropdownMenu.Content, { children: [
2232
+ descriptor.hasTestConnection && record.is_enabled && isComplete && /* @__PURE__ */ jsx(DropdownMenu.Item, { disabled: test.isPending, onClick: () => test.mutate(), children: t("integration.actions.test") }),
2233
+ isConfigured && /* @__PURE__ */ jsx(
2234
+ DropdownMenu.Item,
2235
+ {
2236
+ disabled: toggleEnabled.isPending,
2237
+ onClick: () => toggleEnabled.mutate(!record.is_enabled),
2238
+ children: record.is_enabled ? t("integration.actions.disable") : t("integration.actions.enable")
2239
+ }
2240
+ ),
2241
+ isConfigured && /* @__PURE__ */ jsx(DropdownMenu.Separator, {}),
2242
+ isConfigured && /* @__PURE__ */ jsxs(DropdownMenu.Item, { className: "gap-x-2", onClick: onDelete, children: [
2243
+ /* @__PURE__ */ jsx(Trash, { className: "text-ui-fg-subtle" }),
2244
+ t("integration.actions.delete")
2245
+ ] })
2246
+ ] })
2005
2247
  ] })
2006
2248
  ] })
2007
- ] })
2249
+ ] }),
2250
+ descriptor.description && /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t(descriptor.description) })
2008
2251
  ] }),
2009
- /* @__PURE__ */ jsx(Row, { label: t("integration.labels.status"), children: !isConfigured ? /* @__PURE__ */ jsx(StatusBadge, { color: "grey", children: t("integration.status.notConfigured") }) : !isComplete ? /* @__PURE__ */ jsx(StatusBadge, { color: "orange", children: t("integration.status.incomplete") }) : /* @__PURE__ */ jsx(StatusBadge, { color: record.is_enabled ? "green" : "grey", children: record.is_enabled ? t("integration.status.enabled") : t("integration.status.disabled") }) }),
2010
- /* @__PURE__ */ jsx(Row, { label: t("integration.labels.module"), children: /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: t(`integration.modules.${descriptor.module}`) }) }),
2011
- /* @__PURE__ */ jsx(Row, { label: t("integration.labels.lastTest"), children: (record == null ? void 0 : record.last_test_status) ? /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(StatusBadge, { color: testColor(record.last_test_status), children: t(`integration.testStatus.${record.last_test_status}`) }) }) : /* @__PURE__ */ jsx(Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.labels.neverTested") }) })
2252
+ /* @__PURE__ */ jsx(Row, { label: t("integration.labels.category"), children: /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: t(`integration.categories.${descriptor.category}`) }) })
2012
2253
  ] }) }),
2013
2254
  mainSections.map(renderSection),
2014
2255
  editing && /* @__PURE__ */ jsx(
@@ -2019,7 +2260,7 @@ const EditPage = () => {
2019
2260
  providerId: provider_id,
2020
2261
  section: editing,
2021
2262
  values: (record == null ? void 0 : record.values) ?? {},
2022
- hasSecrets: !!(record == null ? void 0 : record.has_secrets)
2263
+ configuredSecrets: (record == null ? void 0 : record.configured_secrets) ?? []
2023
2264
  },
2024
2265
  editing.id
2025
2266
  )
@@ -2045,50 +2286,199 @@ const IntegrationBreadcrumb = ({ params }) => {
2045
2286
  enabled: !!providerId
2046
2287
  });
2047
2288
  const descriptor = data == null ? void 0 : data.descriptor;
2048
- if (!descriptor) return /* @__PURE__ */ jsx(Fragment, { children: providerId });
2289
+ if (!descriptor) return /* @__PURE__ */ jsx(Fragment, {});
2049
2290
  const name = t(descriptor.displayName);
2050
- return /* @__PURE__ */ jsx(Fragment, { children: descriptor.instanceId ? `${name} · ${descriptor.instanceId}` : name });
2291
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2292
+ name + " ",
2293
+ /* @__PURE__ */ jsx(Badge, { size: "2xsmall", color: "grey", children: providerId })
2294
+ ] });
2051
2295
  };
2052
- const handle = {
2296
+ const handle$1 = {
2053
2297
  breadcrumb: ({ params }) => /* @__PURE__ */ jsx(IntegrationBreadcrumb, { params })
2054
2298
  };
2299
+ const PUBLISH_URL = "https://docs.gorgojs.com/integrations";
2300
+ const API_URL = "https://gorgojs.com";
2301
+ const InstallModal = ({ item, onOpenChange }) => {
2302
+ const { t } = useTranslation();
2303
+ return /* @__PURE__ */ jsx(Drawer, { open: !!item, onOpenChange, children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
2304
+ /* @__PURE__ */ jsx(Drawer.Header, { children: /* @__PURE__ */ jsx(Drawer.Title, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-2", children: [
2305
+ item && /* @__PURE__ */ jsx(IntegrationIcon, { src: API_URL + item.icon, alt: item.label, size: "small" }),
2306
+ /* @__PURE__ */ jsx("span", { children: item == null ? void 0 : item.label })
2307
+ ] }) }) }),
2308
+ /* @__PURE__ */ jsx(Drawer.Body, { className: "flex flex-1 flex-col gap-y-4 overflow-auto", children: item && /* @__PURE__ */ jsxs(Fragment, { children: [
2309
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.install_addToConfig") }),
2310
+ /* @__PURE__ */ jsxs("div", { className: "bg-ui-bg-subtle flex items-center justify-between gap-x-2 rounded-md px-3 py-2", children: [
2311
+ /* @__PURE__ */ jsx("code", { className: "txt-compact-small text-ui-fg-subtle", children: `npm i ${item.npm}` }),
2312
+ /* @__PURE__ */ jsx(Copy, { content: `npm i ${item.npm}` })
2313
+ ] }),
2314
+ item.configSnippet && /* @__PURE__ */ jsxs("div", { className: "bg-ui-bg-subtle flex flex-col gap-y-2 rounded-md px-3 py-2", children: [
2315
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-x-2", children: [
2316
+ /* @__PURE__ */ jsx(Text, { size: "xsmall", weight: "plus", className: "text-ui-fg-subtle", children: "medusa-config.ts" }),
2317
+ /* @__PURE__ */ jsx(Copy, { content: item.configSnippet })
2318
+ ] }),
2319
+ /* @__PURE__ */ jsx("pre", { className: "txt-compact-small text-ui-fg-subtle overflow-x-auto whitespace-pre", children: item.configSnippet })
2320
+ ] }),
2321
+ /* @__PURE__ */ jsxs("a", { href: item.docsUrl, target: "_blank", rel: "noreferrer", className: "text-ui-fg-base txt-small", children: [
2322
+ t("integration.browse.install_docs"),
2323
+ " →"
2324
+ ] })
2325
+ ] }) }),
2326
+ /* @__PURE__ */ jsx(Drawer.Footer, { children: /* @__PURE__ */ jsx(Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", children: t("integration.actions.cancel") }) }) })
2327
+ ] }) });
2328
+ };
2329
+ const BrowsePage = () => {
2330
+ const { t } = useTranslation();
2331
+ const navigate = useNavigate();
2332
+ const [activeTab, setActiveTab] = useState("all");
2333
+ const [search, setSearch] = useState("");
2334
+ const [installItem, setInstallItem] = useState(null);
2335
+ const { data, isLoading } = useQuery({
2336
+ queryKey: ["integration-catalog"],
2337
+ queryFn: () => sdk.client.fetch("/admin/integrations/catalog")
2338
+ });
2339
+ const items = (data == null ? void 0 : data.integrations) ?? [];
2340
+ const categories = useMemo(() => [...new Set(items.map((i) => i.category))], [items]);
2341
+ const q = search.trim().toLowerCase();
2342
+ const filtered = items.filter((i) => {
2343
+ if (activeTab !== "all" && i.category !== activeTab) return false;
2344
+ if (q && ![i.label, i.shortDescription, i.npm].some((s) => s.toLowerCase().includes(q))) return false;
2345
+ return true;
2346
+ });
2347
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-y-3", children: [
2348
+ /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
2349
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between px-6 py-4", children: [
2350
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
2351
+ /* @__PURE__ */ jsx(Heading, { level: "h2", children: t("integration.browse.title") }),
2352
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t("integration.browse.subtitle") })
2353
+ ] }),
2354
+ false
2355
+ ] }),
2356
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 px-6 py-4", children: [
2357
+ /* @__PURE__ */ jsx(Tabs, { value: activeTab, onValueChange: setActiveTab, children: /* @__PURE__ */ jsxs(Tabs.List, { children: [
2358
+ /* @__PURE__ */ jsx(Tabs.Trigger, { value: "all", children: t("integration.list.tabs.all") }),
2359
+ categories.map((c) => /* @__PURE__ */ jsx(Tabs.Trigger, { value: c, children: t(`integration.categories.${c}`) }, c))
2360
+ ] }) }),
2361
+ /* @__PURE__ */ jsx("div", { className: "w-64", children: /* @__PURE__ */ jsx(
2362
+ Input,
2363
+ {
2364
+ placeholder: t("integration.browse.searchPlaceholder"),
2365
+ value: search,
2366
+ onChange: (e) => setSearch(e.target.value)
2367
+ }
2368
+ ) })
2369
+ ] })
2370
+ ] }),
2371
+ 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: [
2372
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-y-3 p-4", children: [
2373
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-x-2", children: [
2374
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-x-3", children: [
2375
+ /* @__PURE__ */ jsx(IntegrationIcon, { src: API_URL + item.icon, alt: item.label }),
2376
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
2377
+ /* @__PURE__ */ jsx(Text, { size: "small", weight: "plus", leading: "compact", children: item.label }),
2378
+ /* @__PURE__ */ jsx(Text, { size: "xsmall", leading: "compact", className: "text-ui-fg-subtle", children: t("integration.browse.by", { author: item.authorLocalized }) })
2379
+ ] })
2380
+ ] }),
2381
+ /* @__PURE__ */ jsx(StatusBadge, { color: item.installed ? "green" : "grey", children: item.installed ? t("integration.browse.installed") : t("integration.browse.notInstalled") })
2382
+ ] }),
2383
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle line-clamp-2", children: item.shortDescription })
2384
+ ] }),
2385
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t px-4 py-3", children: [
2386
+ item.installed && item.provider_id ? /* @__PURE__ */ jsx(
2387
+ Button,
2388
+ {
2389
+ size: "small",
2390
+ variant: "primary",
2391
+ onClick: () => navigate(`/settings/integrations/${item.provider_id}`),
2392
+ children: t("integration.browse.settings")
2393
+ }
2394
+ ) : /* @__PURE__ */ jsxs(Button, { size: "small", variant: "secondary", onClick: () => setInstallItem(item), children: [
2395
+ /* @__PURE__ */ jsx(Plus, {}),
2396
+ t("integration.browse.add")
2397
+ ] }),
2398
+ /* @__PURE__ */ jsx(Text, { size: "small", className: "text-ui-fg-subtle", children: t(`integration.categories.${item.category}`) })
2399
+ ] })
2400
+ ] }, item.integrationId)) }),
2401
+ /* @__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: [
2402
+ t("integration.browse.learnMore"),
2403
+ " →"
2404
+ ] }) }),
2405
+ /* @__PURE__ */ jsx(InstallModal, { item: installItem, onOpenChange: (open) => !open && setInstallItem(null) })
2406
+ ] });
2407
+ };
2408
+ const BrowseBreadcrumb = () => {
2409
+ const { t } = useTranslation();
2410
+ return /* @__PURE__ */ jsx(Fragment, { children: t("integration.browse.title") });
2411
+ };
2412
+ const handle = {
2413
+ breadcrumb: () => /* @__PURE__ */ jsx(BrowseBreadcrumb, {})
2414
+ };
2055
2415
  const integration$1 = {
2056
2416
  list: {
2057
2417
  title: "Integrations",
2418
+ subtitle: "Manage integrations settings",
2419
+ browseIntegrations: "Browse Integrations",
2420
+ learnMore: "Learn how to build integrations",
2421
+ searchPlaceholder: "Search integrations",
2058
2422
  empty: "No integrations registered. Declare providers under the integration plugin's options.providers in medusa-config.",
2423
+ emptyFiltered: "No integrations match your filters.",
2059
2424
  tested: "tested",
2060
- testFailed: "test failed"
2425
+ testFailed: "test failed",
2426
+ tabs: {
2427
+ all: "All"
2428
+ }
2429
+ },
2430
+ browse: {
2431
+ title: "Browse",
2432
+ subtitle: "Find available integrations",
2433
+ publish: "Add Integration",
2434
+ learnMore: "Learn how to build and publish your own integration",
2435
+ searchPlaceholder: "Search integrations",
2436
+ by: "by {{author}}",
2437
+ installed: "Installed",
2438
+ notInstalled: "Not installed",
2439
+ add: "Add",
2440
+ settings: "Settings",
2441
+ install_addToConfig: "Add this to your medusa-config.ts and restart the server.",
2442
+ install_docs: "Documentation"
2443
+ },
2444
+ columns: {
2445
+ integration: "Integration",
2446
+ description: "Description",
2447
+ category: "Category",
2448
+ status: "Status"
2061
2449
  },
2062
2450
  status: {
2063
2451
  notConfigured: "Not configured",
2064
2452
  incomplete: "Incomplete",
2065
2453
  enabled: "Enabled",
2066
2454
  disabled: "Disabled",
2067
- configured: "Configured"
2455
+ configured: "Configured",
2456
+ connected: "Connected",
2457
+ connectionFailed: "Connection failed"
2458
+ },
2459
+ meta: {
2460
+ version: "v{{version}}",
2461
+ authorPrefix: "by"
2068
2462
  },
2069
2463
  labels: {
2070
- status: "Status",
2071
- module: "Module",
2072
- lastTest: "Last test",
2073
- neverTested: "Never tested"
2464
+ category: "Category"
2074
2465
  },
2075
- modules: {
2466
+ categories: {
2076
2467
  payment: "Payment",
2468
+ notification: "Notification",
2077
2469
  fulfillment: "Fulfillment",
2078
- marketplace: "Marketplace",
2079
- crm: "CRM",
2470
+ search: "Search",
2471
+ content: "Content",
2472
+ analytics: "Analytics",
2473
+ authentication: "Authentication",
2080
2474
  erp: "ERP",
2475
+ crm: "CRM",
2081
2476
  pim: "PIM",
2082
- notification: "Notification",
2477
+ marketplace: "Marketplace",
2083
2478
  feed: "Feed",
2084
- tax: "Tax",
2479
+ file_providers: "File providers",
2085
2480
  other: "Other"
2086
2481
  },
2087
- testStatus: {
2088
- passed: "Passed",
2089
- failed: "Failed",
2090
- skipped: "Skipped"
2091
- },
2092
2482
  actions: {
2093
2483
  test: "Test connection",
2094
2484
  enable: "Enable",
@@ -2105,7 +2495,7 @@ const integration$1 = {
2105
2495
  form: {
2106
2496
  required: "{{field}} is required",
2107
2497
  invalidJson: "Invalid JSON",
2108
- keepSecret: "•••••• — leave blank to keep",
2498
+ keepSecret: "•••••• – leave blank to keep",
2109
2499
  items: "{{count}} items"
2110
2500
  },
2111
2501
  toast: {
@@ -2128,40 +2518,70 @@ const en = {
2128
2518
  const integration = {
2129
2519
  list: {
2130
2520
  title: "Интеграции",
2521
+ subtitle: "Управление настройками интеграций",
2522
+ browseIntegrations: "Найти интеграции",
2523
+ learnMore: "Как создавать интеграции",
2524
+ searchPlaceholder: "Поиск интеграций",
2131
2525
  empty: "Нет зарегистрированных интеграций. Объявите провайдеров в options.providers плагина интеграций в medusa-config.",
2526
+ emptyFiltered: "Нет интеграций по заданным фильтрам",
2132
2527
  tested: "проверено",
2133
- testFailed: "ошибка проверки"
2528
+ testFailed: "ошибка проверки",
2529
+ tabs: {
2530
+ all: "Все"
2531
+ }
2532
+ },
2533
+ browse: {
2534
+ title: "Каталог",
2535
+ subtitle: "Поиск доступных интеграций",
2536
+ publish: "Добавить интеграцию",
2537
+ learnMore: "Как создать и опубликовать свою интеграцию",
2538
+ searchPlaceholder: "Поиск интеграций",
2539
+ by: "от {{author}}",
2540
+ installed: "Установлено",
2541
+ notInstalled: "Не установлено",
2542
+ add: "Добавить",
2543
+ settings: "Настройки",
2544
+ install_addToConfig: "Добавьте это в medusa-config.ts и перезапустите сервер.",
2545
+ install_docs: "Документация"
2546
+ },
2547
+ columns: {
2548
+ integration: "Интеграция",
2549
+ description: "Описание",
2550
+ category: "Категория",
2551
+ status: "Статус"
2134
2552
  },
2135
2553
  status: {
2136
2554
  notConfigured: "Не настроено",
2137
2555
  incomplete: "Не завершено",
2138
2556
  enabled: "Включено",
2139
2557
  disabled: "Выключено",
2140
- configured: "Настроено"
2558
+ configured: "Настроено",
2559
+ connected: "Подключено",
2560
+ connectionFailed: "Ошибка подключения"
2561
+ },
2562
+ meta: {
2563
+ version: "v{{version}}",
2564
+ authorPrefix: "от"
2141
2565
  },
2142
2566
  labels: {
2143
- status: "Статус",
2144
- module: "Модуль",
2145
- lastTest: "Последняя проверка",
2146
- neverTested: "Не проверялось"
2567
+ category: "Категория"
2147
2568
  },
2148
- modules: {
2569
+ categories: {
2149
2570
  payment: "Платежи",
2571
+ notification: "Уведомления",
2150
2572
  fulfillment: "Доставка",
2151
- marketplace: "Маркетплейс",
2152
- crm: "CRM",
2573
+ search: "Поиск",
2574
+ content: "Контент",
2575
+ analytics: "Аналитика",
2576
+ authentication: "Аутентификация",
2153
2577
  erp: "ERP",
2578
+ crm: "CRM",
2154
2579
  pim: "PIM",
2155
- notification: "Уведомления",
2580
+ marketplace: "Маркетплейс",
2156
2581
  feed: "Фиды",
2157
- tax: "Налоги",
2582
+ file_providers: "Файловые провайдеры",
2158
2583
  other: "Прочее"
2159
2584
  },
2160
- testStatus: {
2161
- passed: "Пройдено",
2162
- failed: "Ошибка",
2163
- skipped: "Пропущено"
2164
- },
2165
2585
  actions: {
2166
2586
  test: "Проверить соединение",
2167
2587
  enable: "Включить",
@@ -2178,7 +2598,7 @@ const integration = {
2178
2598
  form: {
2179
2599
  required: "Поле «{{field}}» обязательно",
2180
2600
  invalidJson: "Некорректный JSON",
2181
- keepSecret: "•••••• — оставьте пустым, чтобы не менять",
2601
+ keepSecret: "•••••• – оставьте пустым, чтобы не менять",
2182
2602
  items: "{{count}} шт."
2183
2603
  },
2184
2604
  toast: {
@@ -2208,11 +2628,16 @@ const routeModule = {
2208
2628
  {
2209
2629
  Component: IntegrationsPage,
2210
2630
  path: "/settings/integrations",
2211
- handle: handle$1
2631
+ handle: { label: config.label, translationNs: config.translationNs, ...handle$2 }
2212
2632
  },
2213
2633
  {
2214
2634
  Component: EditPage,
2215
2635
  path: "/settings/integrations/:provider_id",
2636
+ handle: handle$1
2637
+ },
2638
+ {
2639
+ Component: BrowsePage,
2640
+ path: "/settings/integrations/browse",
2216
2641
  handle
2217
2642
  }
2218
2643
  ]
@@ -2234,6 +2659,7 @@ const displayModule = {
2234
2659
  displays: {}
2235
2660
  };
2236
2661
  const i18nModule = { resources: i18nTranslations0 };
2662
+ const cellRendererModule = {};
2237
2663
  const layoutModule = { layouts: [] };
2238
2664
  const plugin = {
2239
2665
  widgetModule,
@@ -2242,6 +2668,7 @@ const plugin = {
2242
2668
  formModule,
2243
2669
  displayModule,
2244
2670
  i18nModule,
2671
+ cellRendererModule,
2245
2672
  layoutModule
2246
2673
  };
2247
2674
  export {