@open-mercato/core 0.4.6-develop-c2b70de148 → 0.4.6-develop-03a1e82e85
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.
- package/dist/modules/auth/frontend/login.js +13 -2
- package/dist/modules/auth/frontend/login.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductCategorizeSection.js +1 -0
- package/dist/modules/catalog/components/products/ProductCategorizeSection.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +8 -2
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +1 -1
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +1 -1
- package/dist/modules/feature_toggles/components/formConfig.js +1 -0
- package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
- package/dist/modules/feature_toggles/components/overrideFormConfig.js +1 -0
- package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/filters.js +40 -0
- package/dist/modules/integrations/backend/integrations/filters.js.map +7 -0
- package/dist/modules/integrations/backend/integrations/page.js +34 -22
- package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/auth/frontend/login-injection.ts +2 -0
- package/src/modules/auth/frontend/login.tsx +15 -2
- package/src/modules/catalog/components/products/ProductCategorizeSection.tsx +1 -0
- package/src/modules/data_sync/lib/sync-engine.ts +8 -2
- package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +1 -1
- package/src/modules/feature_toggles/components/formConfig.tsx +1 -0
- package/src/modules/feature_toggles/components/overrideFormConfig.tsx +1 -0
- package/src/modules/integrations/backend/integrations/filters.ts +39 -0
- package/src/modules/integrations/backend/integrations/page.tsx +51 -37
- package/src/modules/integrations/i18n/de.json +88 -11
- package/src/modules/integrations/i18n/en.json +79 -2
- package/src/modules/integrations/i18n/es.json +96 -19
- package/src/modules/integrations/i18n/pl.json +105 -28
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/feature_toggles/components/overrideFormConfig.tsx"],
|
|
4
|
-
"sourcesContent": ["
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport { CrudFormGroup, CrudCustomFieldRenderProps, CrudField } from \"@open-mercato/ui/backend/CrudForm\";\nimport { JsonBuilder } from \"@open-mercato/ui/backend/JsonBuilder\";\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\nexport function renderOverrideValueComponent(props: CrudCustomFieldRenderProps) {\n const t = useT()\n const toggleType = props.values?.toggleType as string;\n const isOverride = props.values?.isOverride as boolean;\n\n if (!isOverride) {\n return (\n <div className=\"text-sm text-muted-foreground p-4 text-center bg-muted/20 rounded border border-dashed\">\n {t('feature_toggles.override.disabled', 'Override is disabled. Values will be inherited from the default configuration.')}\n </div>\n );\n }\n\n switch (toggleType) {\n case 'boolean':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.override.fields.value.boolean.label', 'Override Value (Boolean)')}</label>\n <select\n value={props.value as string || 'false'}\n onChange={(e) => props.setValue(e.target.value === 'true')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n >\n <option value=\"true\">{t('feature_toggles.values.true', 'True')}</option>\n <option value=\"false\">{t('feature_toggles.values.false', 'False')}</option>\n </select>\n </div>\n );\n\n case 'string':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.override.fields.value.string.label', 'Override Value (String)')}</label>\n <input\n type=\"text\"\n value={props.value as string || ''}\n onChange={(e) => props.setValue(e.target.value)}\n placeholder={t('feature_toggles.override.fields.value.string.placeholder', 'Enter override string value')}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n />\n </div>\n );\n\n case 'number':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.override.fields.value.number.label', 'Override Value (Number)')}</label>\n <input\n type=\"number\"\n value={props.value as number || 0}\n onChange={(e) => props.setValue(Number(e.target.value) || 0)}\n className=\"w-full h-9 rounded border px-2 text-sm\"\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n />\n </div>\n );\n\n case 'json':\n return (\n <div>\n <label className=\"block text-sm font-medium mb-2\">{t('feature_toggles.override.fields.value.json.label', 'Override Value (JSON)')}</label>\n <JsonBuilder\n value={props.value}\n onChange={props.setValue}\n disabled={props.disabled}\n />\n </div>\n );\n\n default:\n return (\n <div className=\"text-sm text-muted-foreground p-4 text-center bg-muted/20 rounded border border-dashed\">\n {t('feature_toggles.override.unknownType', 'Unknown toggle type. Cannot configure override value.')}\n </div>\n );\n }\n}\n\nexport function createOverrideFieldDefinitions(\n t: (key: string) => string,\n): CrudField[] {\n return [\n {\n id: 'isOverride',\n label: t('feature_toggles.override.fields.isOverride.label'),\n type: 'checkbox',\n required: false,\n description: t('feature_toggles.override.fields.isOverride.description'),\n },\n {\n id: 'overrideValue',\n label: '',\n type: 'custom',\n component: renderOverrideValueComponent,\n description: t('feature_toggles.override.fields.overrideValue.description'),\n },\n ]\n}\n\nexport function createOverrideFormGroups(\n t: (key: string) => string,\n): CrudFormGroup[] {\n return [\n {\n id: 'overrideMode',\n title: t('feature_toggles.override.groups.mode'),\n column: 1,\n fields: ['isOverride'],\n },\n {\n id: 'overrideValue',\n title: t('feature_toggles.override.groups.value'),\n column: 1,\n fields: ['overrideValue'],\n },\n ]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const INTEGRATION_MARKETPLACE_CATEGORIES = [
|
|
2
|
+
"all",
|
|
3
|
+
"payment",
|
|
4
|
+
"shipping",
|
|
5
|
+
"data_sync",
|
|
6
|
+
"communication",
|
|
7
|
+
"notification",
|
|
8
|
+
"storage",
|
|
9
|
+
"webhook"
|
|
10
|
+
];
|
|
11
|
+
function buildIntegrationMarketplaceFilterDefs(t) {
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
id: "category",
|
|
15
|
+
label: t("integrations.marketplace.filters.category", "Category"),
|
|
16
|
+
type: "select",
|
|
17
|
+
options: INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => ({
|
|
18
|
+
value: category,
|
|
19
|
+
label: t(`integrations.marketplace.categories.${category}`)
|
|
20
|
+
})),
|
|
21
|
+
formatValue: (value) => t(`integrations.marketplace.categories.${value}`, value)
|
|
22
|
+
}
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
function normalizeIntegrationMarketplaceFilterValues(values) {
|
|
26
|
+
const category = typeof values.category === "string" ? values.category : "";
|
|
27
|
+
if (!category || category === "all") return {};
|
|
28
|
+
return { category };
|
|
29
|
+
}
|
|
30
|
+
function getIntegrationMarketplaceCategory(values) {
|
|
31
|
+
const category = typeof values.category === "string" ? values.category : "";
|
|
32
|
+
return category || "all";
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
INTEGRATION_MARKETPLACE_CATEGORIES,
|
|
36
|
+
buildIntegrationMarketplaceFilterDefs,
|
|
37
|
+
getIntegrationMarketplaceCategory,
|
|
38
|
+
normalizeIntegrationMarketplaceFilterValues
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=filters.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/integrations/backend/integrations/filters.ts"],
|
|
4
|
+
"sourcesContent": ["import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\n\nexport const INTEGRATION_MARKETPLACE_CATEGORIES = [\n 'all',\n 'payment',\n 'shipping',\n 'data_sync',\n 'communication',\n 'notification',\n 'storage',\n 'webhook',\n] as const\n\nexport function buildIntegrationMarketplaceFilterDefs(t: TranslateFn): FilterDef[] {\n return [\n {\n id: 'category',\n label: t('integrations.marketplace.filters.category', 'Category'),\n type: 'select',\n options: INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => ({\n value: category,\n label: t(`integrations.marketplace.categories.${category}`),\n })),\n formatValue: (value) => t(`integrations.marketplace.categories.${value}`, value),\n },\n ]\n}\n\nexport function normalizeIntegrationMarketplaceFilterValues(values: FilterValues): FilterValues {\n const category = typeof values.category === 'string' ? values.category : ''\n if (!category || category === 'all') return {}\n return { category }\n}\n\nexport function getIntegrationMarketplaceCategory(values: FilterValues): string {\n const category = typeof values.category === 'string' ? values.category : ''\n return category || 'all'\n}\n"],
|
|
5
|
+
"mappings": "AAGO,MAAM,qCAAqC;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,sCAAsC,GAA6B;AACjF,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,EAAE,6CAA6C,UAAU;AAAA,MAChE,MAAM;AAAA,MACN,SAAS,mCAAmC,IAAI,CAAC,cAAc;AAAA,QAC7D,OAAO;AAAA,QACP,OAAO,EAAE,uCAAuC,QAAQ,EAAE;AAAA,MAC5D,EAAE;AAAA,MACF,aAAa,CAAC,UAAU,EAAE,uCAAuC,KAAK,IAAI,KAAK;AAAA,IACjF;AAAA,EACF;AACF;AAEO,SAAS,4CAA4C,QAAoC;AAC9F,QAAM,WAAW,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;AACzE,MAAI,CAAC,YAAY,aAAa,MAAO,QAAO,CAAC;AAC7C,SAAO,EAAE,SAAS;AACpB;AAEO,SAAS,kCAAkC,QAA8B;AAC9E,QAAM,WAAW,OAAO,OAAO,aAAa,WAAW,OAAO,WAAW;AACzE,SAAO,YAAY;AACrB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -13,16 +13,14 @@ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
|
13
13
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
14
14
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
15
15
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
16
|
+
import { FilterBar } from "@open-mercato/ui/backend/FilterBar";
|
|
17
|
+
import { Bell, CreditCard, HardDrive, LayoutGrid, MessageSquare, RefreshCw, Truck, Webhook } from "lucide-react";
|
|
16
18
|
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Bell,
|
|
23
|
-
HardDrive,
|
|
24
|
-
Webhook
|
|
25
|
-
} from "lucide-react";
|
|
19
|
+
buildIntegrationMarketplaceFilterDefs,
|
|
20
|
+
getIntegrationMarketplaceCategory,
|
|
21
|
+
INTEGRATION_MARKETPLACE_CATEGORIES,
|
|
22
|
+
normalizeIntegrationMarketplaceFilterValues
|
|
23
|
+
} from "./filters.js";
|
|
26
24
|
const CATEGORY_ICONS = {
|
|
27
25
|
all: LayoutGrid,
|
|
28
26
|
payment: CreditCard,
|
|
@@ -33,7 +31,6 @@ const CATEGORY_ICONS = {
|
|
|
33
31
|
storage: HardDrive,
|
|
34
32
|
webhook: Webhook
|
|
35
33
|
};
|
|
36
|
-
const CATEGORIES = ["all", "payment", "shipping", "data_sync", "communication", "notification", "storage", "webhook"];
|
|
37
34
|
function categoryBadgeVariant(category) {
|
|
38
35
|
if (!category) return "outline";
|
|
39
36
|
return "secondary";
|
|
@@ -42,10 +39,12 @@ function IntegrationsMarketplacePage() {
|
|
|
42
39
|
const [data, setData] = React.useState(null);
|
|
43
40
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
44
41
|
const [search, setSearch] = React.useState("");
|
|
45
|
-
const [
|
|
42
|
+
const [filterValues, setFilterValues] = React.useState({});
|
|
46
43
|
const [togglingIds, setTogglingIds] = React.useState(/* @__PURE__ */ new Set());
|
|
47
44
|
const scopeVersion = useOrganizationScopeVersion();
|
|
48
45
|
const t = useT();
|
|
46
|
+
const categoryFilters = React.useMemo(() => buildIntegrationMarketplaceFilterDefs(t), [t]);
|
|
47
|
+
const selectedCategory = React.useMemo(() => getIntegrationMarketplaceCategory(filterValues), [filterValues]);
|
|
49
48
|
const load = React.useCallback(async () => {
|
|
50
49
|
setIsLoading(true);
|
|
51
50
|
const fallback = { items: [], bundles: [] };
|
|
@@ -94,8 +93,8 @@ function IntegrationsMarketplacePage() {
|
|
|
94
93
|
const q = search.toLowerCase();
|
|
95
94
|
items = items.filter((item) => item.title.toLowerCase().includes(q) || item.description?.toLowerCase().includes(q));
|
|
96
95
|
}
|
|
97
|
-
if (
|
|
98
|
-
items = items.filter((item) => item.category ===
|
|
96
|
+
if (selectedCategory !== "all") {
|
|
97
|
+
items = items.filter((item) => item.category === selectedCategory);
|
|
99
98
|
}
|
|
100
99
|
const bundled = /* @__PURE__ */ new Map();
|
|
101
100
|
const standalone = [];
|
|
@@ -110,7 +109,7 @@ function IntegrationsMarketplacePage() {
|
|
|
110
109
|
}
|
|
111
110
|
const bundles = (data.bundles ?? []).filter((b) => bundled.has(b.id)).map((b) => ({ ...b, integrations: bundled.get(b.id) ?? [] }));
|
|
112
111
|
return { bundles, standalone };
|
|
113
|
-
}, [data, search,
|
|
112
|
+
}, [data, search, selectedCategory]);
|
|
114
113
|
if (isLoading) {
|
|
115
114
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx(Spinner, {}) }) }) });
|
|
116
115
|
}
|
|
@@ -119,7 +118,20 @@ function IntegrationsMarketplacePage() {
|
|
|
119
118
|
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: t("integrations.marketplace.title") }),
|
|
120
119
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("integrations.marketplace.description") })
|
|
121
120
|
] }) }),
|
|
122
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ jsx("div", { className: "lg:hidden", children: /* @__PURE__ */ jsx(
|
|
122
|
+
FilterBar,
|
|
123
|
+
{
|
|
124
|
+
searchValue: search,
|
|
125
|
+
onSearchChange: setSearch,
|
|
126
|
+
searchPlaceholder: t("integrations.marketplace.search"),
|
|
127
|
+
searchAlign: "left",
|
|
128
|
+
filters: categoryFilters,
|
|
129
|
+
values: filterValues,
|
|
130
|
+
onApply: (values) => setFilterValues(normalizeIntegrationMarketplaceFilterValues(values)),
|
|
131
|
+
onClear: () => setFilterValues({})
|
|
132
|
+
}
|
|
133
|
+
) }),
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "hidden lg:flex lg:flex-col gap-4", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center", children: [
|
|
123
135
|
/* @__PURE__ */ jsx(
|
|
124
136
|
Input,
|
|
125
137
|
{
|
|
@@ -129,24 +141,24 @@ function IntegrationsMarketplacePage() {
|
|
|
129
141
|
className: "max-w-sm"
|
|
130
142
|
}
|
|
131
143
|
),
|
|
132
|
-
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children:
|
|
133
|
-
const Icon = CATEGORY_ICONS[
|
|
144
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => {
|
|
145
|
+
const Icon = CATEGORY_ICONS[category];
|
|
134
146
|
return /* @__PURE__ */ jsxs(
|
|
135
147
|
Button,
|
|
136
148
|
{
|
|
137
149
|
type: "button",
|
|
138
|
-
variant:
|
|
150
|
+
variant: selectedCategory === category ? "default" : "outline",
|
|
139
151
|
size: "sm",
|
|
140
|
-
onClick: () =>
|
|
152
|
+
onClick: () => setFilterValues(normalizeIntegrationMarketplaceFilterValues({ category })),
|
|
141
153
|
children: [
|
|
142
154
|
Icon ? /* @__PURE__ */ jsx(Icon, { className: "mr-1.5 h-3.5 w-3.5" }) : null,
|
|
143
|
-
t(`integrations.marketplace.categories.${
|
|
155
|
+
t(`integrations.marketplace.categories.${category}`)
|
|
144
156
|
]
|
|
145
157
|
},
|
|
146
|
-
|
|
158
|
+
category
|
|
147
159
|
);
|
|
148
160
|
}) })
|
|
149
|
-
] }),
|
|
161
|
+
] }) }),
|
|
150
162
|
filteredItems.bundles.map((bundle) => /* @__PURE__ */ jsxs(Card, { children: [
|
|
151
163
|
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
152
164
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/integrations/backend/integrations/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n LayoutGrid,\n CreditCard,\n Truck,\n RefreshCw,\n MessageSquare,\n Bell,\n HardDrive,\n Webhook,\n} from 'lucide-react'\n\ntype IntegrationItem = {\n id: string\n title: string\n description?: string\n category?: string\n icon?: string\n bundleId?: string\n isEnabled: boolean\n hasCredentials: boolean\n}\n\ntype BundleItem = {\n id: string\n title: string\n description?: string\n icon?: string\n integrationCount: number\n enabledCount: number\n}\n\ntype ListResponse = {\n items: IntegrationItem[]\n bundles: BundleItem[]\n}\n\nconst CATEGORY_ICONS: Record<string, React.ElementType> = {\n all: LayoutGrid,\n payment: CreditCard,\n shipping: Truck,\n data_sync: RefreshCw,\n communication: MessageSquare,\n notification: Bell,\n storage: HardDrive,\n webhook: Webhook,\n}\n\nconst CATEGORIES = ['all', 'payment', 'shipping', 'data_sync', 'communication', 'notification', 'storage', 'webhook'] as const\n\nfunction categoryBadgeVariant(category: string | undefined): 'default' | 'secondary' | 'outline' {\n if (!category) return 'outline'\n return 'secondary'\n}\n\nexport default function IntegrationsMarketplacePage() {\n const [data, setData] = React.useState<ListResponse | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [search, setSearch] = React.useState('')\n const [category, setCategory] = React.useState<string>('all')\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n const fallback: ListResponse = { items: [], bundles: [] }\n const call = await apiCall<ListResponse>('/api/integrations', undefined, { fallback })\n if (!call.ok) {\n flash(t('integrations.marketplace.loadError'), 'error')\n setIsLoading(false)\n return\n }\n setData(call.result ?? fallback)\n setIsLoading(false)\n }, [t])\n\n React.useEffect(() => { void load() }, [load, scopeVersion])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n\n if (!call.ok) {\n flash(t('integrations.detail.stateError'), 'error')\n } else {\n setData((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n items: prev.items.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const filteredItems = React.useMemo(() => {\n if (!data) return { bundles: [], standalone: [] }\n\n let items = data.items\n if (search) {\n const q = search.toLowerCase()\n items = items.filter((item) => item.title.toLowerCase().includes(q) || item.description?.toLowerCase().includes(q))\n }\n if (category !== 'all') {\n items = items.filter((item) => item.category === category)\n }\n\n const bundled = new Map<string, IntegrationItem[]>()\n const standalone: IntegrationItem[] = []\n for (const item of items) {\n if (item.bundleId) {\n const list = bundled.get(item.bundleId) ?? []\n list.push(item)\n bundled.set(item.bundleId, list)\n } else {\n standalone.push(item)\n }\n }\n\n const bundles = (data.bundles ?? [])\n .filter((b) => bundled.has(b.id))\n .map((b) => ({ ...b, integrations: bundled.get(b.id) ?? [] }))\n\n return { bundles, standalone }\n }, [data, search, category])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center py-16\">\n <Spinner />\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <section className=\"space-y-6 rounded-lg border bg-background p-6\">\n <header className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">{t('integrations.marketplace.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('integrations.marketplace.description')}\n </p>\n </div>\n </header>\n\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <Input\n placeholder={t('integrations.marketplace.search')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"max-w-sm\"\n />\n <div className=\"flex flex-wrap gap-1.5\">\n {CATEGORIES.map((cat) => {\n const Icon = CATEGORY_ICONS[cat]\n return (\n <Button\n key={cat}\n type=\"button\"\n variant={category === cat ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setCategory(cat)}\n >\n {Icon ? <Icon className=\"mr-1.5 h-3.5 w-3.5\" /> : null}\n {t(`integrations.marketplace.categories.${cat}`)}\n </Button>\n )\n })}\n </div>\n </div>\n\n {filteredItems.bundles.map((bundle) => (\n <Card key={bundle.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div>\n <CardTitle>{bundle.title}</CardTitle>\n {bundle.description && (\n <p className=\"text-muted-foreground text-sm mt-1\">{bundle.description}</p>\n )}\n <p className=\"text-muted-foreground text-xs mt-1\">\n {t('integrations.marketplace.integrations', { count: bundle.integrations.length })}\n </p>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/integrations/bundle/${encodeURIComponent(bundle.id)}`}>\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {bundle.integrations.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between rounded-lg border p-3\"\n >\n <div className=\"min-w-0\">\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant={categoryBadgeVariant(item.category)} className=\"ml-2 text-xs\">\n {item.category}\n </Badge>\n )}\n </div>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n ))}\n\n {filteredItems.standalone.length > 0 && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {filteredItems.standalone.map((item) => (\n <Card key={item.id} className=\"flex flex-col\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{item.title}</CardTitle>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n {item.category && (\n <Badge variant={categoryBadgeVariant(item.category)} className=\"w-fit text-xs\">\n {item.category}\n </Badge>\n )}\n </CardHeader>\n <CardContent className=\"flex-1\">\n {item.description && (\n <p className=\"text-muted-foreground text-sm\">{item.description}</p>\n )}\n </CardContent>\n <div className=\"px-6 pb-4\">\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </Card>\n ))}\n </div>\n )}\n\n {filteredItems.bundles.length === 0 && filteredItems.standalone.length === 0 && (\n <div className=\"text-center py-12 text-muted-foreground\">\n {t('integrations.marketplace.noResults')}\n </div>\n )}\n </section>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Card, CardHeader, CardTitle, CardContent } from '@open-mercato/ui/primitives/card'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { FilterBar, type FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Bell, CreditCard, HardDrive, LayoutGrid, MessageSquare, RefreshCw, Truck, Webhook } from 'lucide-react'\nimport {\n buildIntegrationMarketplaceFilterDefs,\n getIntegrationMarketplaceCategory,\n INTEGRATION_MARKETPLACE_CATEGORIES,\n normalizeIntegrationMarketplaceFilterValues,\n} from './filters'\n\ntype IntegrationItem = {\n id: string\n title: string\n description?: string\n category?: string\n icon?: string\n bundleId?: string\n isEnabled: boolean\n hasCredentials: boolean\n}\n\ntype BundleItem = {\n id: string\n title: string\n description?: string\n icon?: string\n integrationCount: number\n enabledCount: number\n}\n\ntype ListResponse = {\n items: IntegrationItem[]\n bundles: BundleItem[]\n}\n\nconst CATEGORY_ICONS: Record<string, React.ElementType> = {\n all: LayoutGrid,\n payment: CreditCard,\n shipping: Truck,\n data_sync: RefreshCw,\n communication: MessageSquare,\n notification: Bell,\n storage: HardDrive,\n webhook: Webhook,\n}\n\nfunction categoryBadgeVariant(category: string | undefined): 'default' | 'secondary' | 'outline' {\n if (!category) return 'outline'\n return 'secondary'\n}\n\nexport default function IntegrationsMarketplacePage() {\n const [data, setData] = React.useState<ListResponse | null>(null)\n const [isLoading, setIsLoading] = React.useState(true)\n const [search, setSearch] = React.useState('')\n const [filterValues, setFilterValues] = React.useState<FilterValues>({})\n const [togglingIds, setTogglingIds] = React.useState<Set<string>>(new Set())\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n\n const categoryFilters = React.useMemo(() => buildIntegrationMarketplaceFilterDefs(t), [t])\n const selectedCategory = React.useMemo(() => getIntegrationMarketplaceCategory(filterValues), [filterValues])\n\n const load = React.useCallback(async () => {\n setIsLoading(true)\n const fallback: ListResponse = { items: [], bundles: [] }\n const call = await apiCall<ListResponse>('/api/integrations', undefined, { fallback })\n if (!call.ok) {\n flash(t('integrations.marketplace.loadError'), 'error')\n setIsLoading(false)\n return\n }\n setData(call.result ?? fallback)\n setIsLoading(false)\n }, [t])\n\n React.useEffect(() => { void load() }, [load, scopeVersion])\n\n const handleToggle = React.useCallback(async (integrationId: string, enabled: boolean) => {\n setTogglingIds((prev) => new Set(prev).add(integrationId))\n const call = await apiCall(`/api/integrations/${encodeURIComponent(integrationId)}/state`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ isEnabled: enabled }),\n }, { fallback: null })\n\n if (!call.ok) {\n flash(t('integrations.detail.stateError'), 'error')\n } else {\n setData((prev) => {\n if (!prev) return prev\n return {\n ...prev,\n items: prev.items.map((item) =>\n item.id === integrationId ? { ...item, isEnabled: enabled } : item,\n ),\n }\n })\n }\n setTogglingIds((prev) => { const next = new Set(prev); next.delete(integrationId); return next })\n }, [t])\n\n const filteredItems = React.useMemo(() => {\n if (!data) return { bundles: [], standalone: [] }\n\n let items = data.items\n if (search) {\n const q = search.toLowerCase()\n items = items.filter((item) => item.title.toLowerCase().includes(q) || item.description?.toLowerCase().includes(q))\n }\n if (selectedCategory !== 'all') {\n items = items.filter((item) => item.category === selectedCategory)\n }\n\n const bundled = new Map<string, IntegrationItem[]>()\n const standalone: IntegrationItem[] = []\n for (const item of items) {\n if (item.bundleId) {\n const list = bundled.get(item.bundleId) ?? []\n list.push(item)\n bundled.set(item.bundleId, list)\n } else {\n standalone.push(item)\n }\n }\n\n const bundles = (data.bundles ?? [])\n .filter((b) => bundled.has(b.id))\n .map((b) => ({ ...b, integrations: bundled.get(b.id) ?? [] }))\n\n return { bundles, standalone }\n }, [data, search, selectedCategory])\n\n if (isLoading) {\n return (\n <Page>\n <PageBody>\n <div className=\"flex items-center justify-center py-16\">\n <Spinner />\n </div>\n </PageBody>\n </Page>\n )\n }\n\n return (\n <Page>\n <PageBody className=\"space-y-6\">\n <section className=\"space-y-6 rounded-lg border bg-background p-6\">\n <header className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">{t('integrations.marketplace.title')}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('integrations.marketplace.description')}\n </p>\n </div>\n </header>\n\n <div className=\"lg:hidden\">\n <FilterBar\n searchValue={search}\n onSearchChange={setSearch}\n searchPlaceholder={t('integrations.marketplace.search')}\n searchAlign=\"left\"\n filters={categoryFilters}\n values={filterValues}\n onApply={(values) => setFilterValues(normalizeIntegrationMarketplaceFilterValues(values))}\n onClear={() => setFilterValues({})}\n />\n </div>\n\n <div className=\"hidden lg:flex lg:flex-col gap-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center\">\n <Input\n placeholder={t('integrations.marketplace.search')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"max-w-sm\"\n />\n <div className=\"flex flex-wrap gap-1.5\">\n {INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => {\n const Icon = CATEGORY_ICONS[category]\n return (\n <Button\n key={category}\n type=\"button\"\n variant={selectedCategory === category ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => setFilterValues(normalizeIntegrationMarketplaceFilterValues({ category }))}\n >\n {Icon ? <Icon className=\"mr-1.5 h-3.5 w-3.5\" /> : null}\n {t(`integrations.marketplace.categories.${category}`)}\n </Button>\n )\n })}\n </div>\n </div>\n </div>\n\n {filteredItems.bundles.map((bundle) => (\n <Card key={bundle.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <div>\n <CardTitle>{bundle.title}</CardTitle>\n {bundle.description && (\n <p className=\"text-muted-foreground text-sm mt-1\">{bundle.description}</p>\n )}\n <p className=\"text-muted-foreground text-xs mt-1\">\n {t('integrations.marketplace.integrations', { count: bundle.integrations.length })}\n </p>\n </div>\n <Button asChild variant=\"outline\" size=\"sm\">\n <Link href={`/backend/integrations/bundle/${encodeURIComponent(bundle.id)}`}>\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {bundle.integrations.map((item) => (\n <div\n key={item.id}\n className=\"flex items-center justify-between rounded-lg border p-3\"\n >\n <div className=\"min-w-0\">\n <Link\n href={`/backend/integrations/${encodeURIComponent(item.id)}`}\n className=\"text-sm font-medium hover:underline\"\n >\n {item.title}\n </Link>\n {item.category && (\n <Badge variant={categoryBadgeVariant(item.category)} className=\"ml-2 text-xs\">\n {item.category}\n </Badge>\n )}\n </div>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n ))}\n\n {filteredItems.standalone.length > 0 && (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n {filteredItems.standalone.map((item) => (\n <Card key={item.id} className=\"flex flex-col\">\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{item.title}</CardTitle>\n <Switch\n checked={item.isEnabled}\n disabled={togglingIds.has(item.id)}\n onCheckedChange={(checked) => void handleToggle(item.id, checked)}\n />\n </div>\n {item.category && (\n <Badge variant={categoryBadgeVariant(item.category)} className=\"w-fit text-xs\">\n {item.category}\n </Badge>\n )}\n </CardHeader>\n <CardContent className=\"flex-1\">\n {item.description && (\n <p className=\"text-muted-foreground text-sm\">{item.description}</p>\n )}\n </CardContent>\n <div className=\"px-6 pb-4\">\n <Button asChild variant=\"outline\" size=\"sm\" className=\"w-full\">\n <Link href={`/backend/integrations/${encodeURIComponent(item.id)}`}>\n {t('integrations.marketplace.configure')}\n </Link>\n </Button>\n </div>\n </Card>\n ))}\n </div>\n )}\n\n {filteredItems.bundles.length === 0 && filteredItems.standalone.length === 0 && (\n <div className=\"text-center py-12 text-muted-foreground\">\n {t('integrations.marketplace.noResults')}\n </div>\n )}\n </section>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.6-develop-
|
|
3
|
+
"version": "0.4.6-develop-03a1e82e85",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.6-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.6-develop-03a1e82e85",
|
|
211
211
|
"@types/html-to-text": "^9.0.4",
|
|
212
212
|
"@types/semver": "^7.5.8",
|
|
213
213
|
"@xyflow/react": "^12.6.0",
|
|
@@ -40,6 +40,8 @@ export type LoginFormWidgetContext = {
|
|
|
40
40
|
searchParams: URLSearchParams
|
|
41
41
|
/** Set or clear an alternative auth provider for the current email */
|
|
42
42
|
setAuthOverride: (override: AuthOverride | null) => void
|
|
43
|
+
/** Mark whether auth override detection is still resolving */
|
|
44
|
+
setAuthOverridePending?: (pending: boolean) => void
|
|
43
45
|
/** Display an error message in the login form's error area */
|
|
44
46
|
setError: (error: string | null) => void
|
|
45
47
|
}
|
|
@@ -89,6 +89,8 @@ export default function LoginPage() {
|
|
|
89
89
|
const [error, setError] = useState<string | null>(null)
|
|
90
90
|
const [submitting, setSubmitting] = useState(false)
|
|
91
91
|
const [authOverride, setAuthOverride] = useState<AuthOverride | null>(null)
|
|
92
|
+
const [authOverridePending, setAuthOverridePending] = useState(false)
|
|
93
|
+
const [clientReady, setClientReady] = useState(false)
|
|
92
94
|
const [email, setEmail] = useState('')
|
|
93
95
|
const [tenantId, setTenantId] = useState<string | null>(null)
|
|
94
96
|
const [tenantName, setTenantName] = useState<string | null>(null)
|
|
@@ -96,6 +98,10 @@ export default function LoginPage() {
|
|
|
96
98
|
const [tenantInvalid, setTenantInvalid] = useState<string | null>(null)
|
|
97
99
|
const showTenantInvalid = tenantId != null && tenantInvalid === tenantId
|
|
98
100
|
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
setClientReady(true)
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
99
105
|
useEffect(() => {
|
|
100
106
|
const tenantParam = (searchParams.get('tenant') || '').trim()
|
|
101
107
|
if (tenantParam) {
|
|
@@ -167,6 +173,9 @@ export default function LoginPage() {
|
|
|
167
173
|
|
|
168
174
|
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
169
175
|
e.preventDefault()
|
|
176
|
+
if (!clientReady || authOverridePending) {
|
|
177
|
+
return
|
|
178
|
+
}
|
|
170
179
|
setError(null)
|
|
171
180
|
if (authOverride) {
|
|
172
181
|
authOverride.onSubmit()
|
|
@@ -248,9 +257,12 @@ export default function LoginPage() {
|
|
|
248
257
|
tenantId,
|
|
249
258
|
searchParams,
|
|
250
259
|
setAuthOverride,
|
|
260
|
+
setAuthOverridePending,
|
|
251
261
|
setError,
|
|
252
262
|
}), [email, tenantId, searchParams])
|
|
253
263
|
|
|
264
|
+
const formReady = clientReady && !authOverridePending
|
|
265
|
+
|
|
254
266
|
return (
|
|
255
267
|
<div className="min-h-svh flex items-center justify-center p-4">
|
|
256
268
|
<Card className="w-full max-w-sm">
|
|
@@ -260,7 +272,7 @@ export default function LoginPage() {
|
|
|
260
272
|
<CardDescription>{translate('auth.login.subtitle', 'Access your workspace')}</CardDescription>
|
|
261
273
|
</CardHeader>
|
|
262
274
|
<CardContent>
|
|
263
|
-
<form className="grid gap-3" onSubmit={onSubmit} noValidate>
|
|
275
|
+
<form className="grid gap-3" onSubmit={onSubmit} noValidate data-auth-ready={formReady ? '1' : '0'}>
|
|
264
276
|
{tenantId ? (
|
|
265
277
|
<input type="hidden" name="tenantId" value={tenantId} />
|
|
266
278
|
) : null}
|
|
@@ -318,6 +330,7 @@ export default function LoginPage() {
|
|
|
318
330
|
type="email"
|
|
319
331
|
required
|
|
320
332
|
aria-invalid={!!error}
|
|
333
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
321
334
|
onBlur={(e) => setEmail(e.target.value)}
|
|
322
335
|
/>
|
|
323
336
|
</div>
|
|
@@ -337,7 +350,7 @@ export default function LoginPage() {
|
|
|
337
350
|
<span>{translate('auth.login.rememberMe', 'Remember me')}</span>
|
|
338
351
|
</label>
|
|
339
352
|
)}
|
|
340
|
-
<Button type="submit" disabled={submitting} className="h-10 mt-2">
|
|
353
|
+
<Button type="submit" disabled={submitting || !formReady} className="h-10 mt-2">
|
|
341
354
|
{submitting
|
|
342
355
|
? translate('auth.login.loading', 'Loading...')
|
|
343
356
|
: authOverride
|
|
@@ -171,7 +171,10 @@ export function createSyncEngine(deps: EngineDeps) {
|
|
|
171
171
|
return {
|
|
172
172
|
async runImport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {
|
|
173
173
|
const run = await syncRunService.getRun(runId, scope)
|
|
174
|
-
if (!run)
|
|
174
|
+
if (!run) {
|
|
175
|
+
console.warn(`[data-sync] Skipping stale import job for missing run ${runId}`)
|
|
176
|
+
return
|
|
177
|
+
}
|
|
175
178
|
|
|
176
179
|
const providerKey = resolveProviderKey(run.integrationId)
|
|
177
180
|
const adapter = getDataSyncAdapter(providerKey)
|
|
@@ -271,7 +274,10 @@ export function createSyncEngine(deps: EngineDeps) {
|
|
|
271
274
|
|
|
272
275
|
async runExport(runId: string, batchSize: number, scope: SyncScope): Promise<void> {
|
|
273
276
|
const run = await syncRunService.getRun(runId, scope)
|
|
274
|
-
if (!run)
|
|
277
|
+
if (!run) {
|
|
278
|
+
console.warn(`[data-sync] Skipping stale export job for missing run ${runId}`)
|
|
279
|
+
return
|
|
280
|
+
}
|
|
275
281
|
|
|
276
282
|
const providerKey = resolveProviderKey(run.integrationId)
|
|
277
283
|
const adapter = getDataSyncAdapter(providerKey)
|
|
@@ -222,7 +222,7 @@ export function DictionaryEntrySelect({
|
|
|
222
222
|
<div className="flex items-center gap-2">
|
|
223
223
|
<select
|
|
224
224
|
className={[
|
|
225
|
-
'h-9 w-full rounded border
|
|
225
|
+
'h-9 w-full rounded border pl-3 pr-8 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-70',
|
|
226
226
|
selectClassName,
|
|
227
227
|
]
|
|
228
228
|
.filter(Boolean)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
|
|
2
|
+
import type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
3
|
+
|
|
4
|
+
export const INTEGRATION_MARKETPLACE_CATEGORIES = [
|
|
5
|
+
'all',
|
|
6
|
+
'payment',
|
|
7
|
+
'shipping',
|
|
8
|
+
'data_sync',
|
|
9
|
+
'communication',
|
|
10
|
+
'notification',
|
|
11
|
+
'storage',
|
|
12
|
+
'webhook',
|
|
13
|
+
] as const
|
|
14
|
+
|
|
15
|
+
export function buildIntegrationMarketplaceFilterDefs(t: TranslateFn): FilterDef[] {
|
|
16
|
+
return [
|
|
17
|
+
{
|
|
18
|
+
id: 'category',
|
|
19
|
+
label: t('integrations.marketplace.filters.category', 'Category'),
|
|
20
|
+
type: 'select',
|
|
21
|
+
options: INTEGRATION_MARKETPLACE_CATEGORIES.map((category) => ({
|
|
22
|
+
value: category,
|
|
23
|
+
label: t(`integrations.marketplace.categories.${category}`),
|
|
24
|
+
})),
|
|
25
|
+
formatValue: (value) => t(`integrations.marketplace.categories.${value}`, value),
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function normalizeIntegrationMarketplaceFilterValues(values: FilterValues): FilterValues {
|
|
31
|
+
const category = typeof values.category === 'string' ? values.category : ''
|
|
32
|
+
if (!category || category === 'all') return {}
|
|
33
|
+
return { category }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getIntegrationMarketplaceCategory(values: FilterValues): string {
|
|
37
|
+
const category = typeof values.category === 'string' ? values.category : ''
|
|
38
|
+
return category || 'all'
|
|
39
|
+
}
|