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