@deenruv/merchant-plugin 1.0.17-dev.31 → 1.0.17-dev.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { createDeenruvUIPlugin, DEENRUV_UI_VERSION } from "@deenruv/react-ui-devkit";
2
+ import { createDeenruvUIPlugin, DEENRUV_UI_VERSION, } from "@deenruv/react-ui-devkit";
3
3
  import { GooglePage } from "./pages/GooglePage.js";
4
4
  import { FacebookPage } from "./pages/FacebookPage.js";
5
5
  import { GlobeIcon } from "lucide-react";
@@ -4,5 +4,64 @@ declare const _default: {
4
4
  facebookCommerce: string;
5
5
  merchantPlatforms: string;
6
6
  };
7
+ common: {
8
+ brand: string;
9
+ save: string;
10
+ loading: string;
11
+ autoUpdate: string;
12
+ updateAllOnSave: string;
13
+ connectionStatus: string;
14
+ connected: string;
15
+ disconnected: string;
16
+ productsCount: string;
17
+ removeOldItems: string;
18
+ settingsSaved: string;
19
+ settingsSaveFailed: string;
20
+ settingsSaveFailedWithError: string;
21
+ settingsLoadFailed: string;
22
+ unknownError: string;
23
+ oldItemsRemoved: string;
24
+ oldItemsRemoveFailed: string;
25
+ };
26
+ google: {
27
+ title: string;
28
+ merchantId: string;
29
+ dataSource: string;
30
+ contentLanguage: string;
31
+ feedLabel: string;
32
+ credentials: string;
33
+ invalidCredentialsFile: string;
34
+ credentialsReadFailed: string;
35
+ diagnostics: {
36
+ title: string;
37
+ status: string;
38
+ unknown: string;
39
+ dataSourceVerified: string;
40
+ latency: string;
41
+ disapprovedProducts: string;
42
+ productIssues: string;
43
+ yes: string;
44
+ no: string;
45
+ test: string;
46
+ completed: string;
47
+ };
48
+ sync: {
49
+ all: string;
50
+ queued: string;
51
+ queueFailed: string;
52
+ cleanupQueued: string;
53
+ cleanupQueueFailed: string;
54
+ recent: string;
55
+ empty: string;
56
+ successful: string;
57
+ failed: string;
58
+ attempts: string;
59
+ };
60
+ };
61
+ facebook: {
62
+ title: string;
63
+ catalogId: string;
64
+ accessToken: string;
65
+ };
7
66
  }[];
8
67
  export default _default;
@@ -3,5 +3,64 @@
3
3
  "googleMerchant": "Google Merchant",
4
4
  "facebookCommerce": "Facebook Commerce",
5
5
  "merchantPlatforms": "Merchant Platforms"
6
+ },
7
+ "common": {
8
+ "brand": "Brand",
9
+ "save": "Save",
10
+ "loading": "Loading platform settings",
11
+ "autoUpdate": "Automatically update when a product changes",
12
+ "updateAllOnSave": "Update all products when saving",
13
+ "connectionStatus": "Connection status",
14
+ "connected": "Connected",
15
+ "disconnected": "Disconnected",
16
+ "productsCount": "Product count",
17
+ "removeOldItems": "Remove old items",
18
+ "settingsSaved": "Settings saved successfully",
19
+ "settingsSaveFailed": "Failed to save settings",
20
+ "settingsSaveFailedWithError": "Failed to save settings: {{message}}",
21
+ "settingsLoadFailed": "Failed to load platform settings",
22
+ "unknownError": "Unknown error",
23
+ "oldItemsRemoved": "Old items removed successfully",
24
+ "oldItemsRemoveFailed": "Failed to remove old items"
25
+ },
26
+ "google": {
27
+ "title": "Google Merchant",
28
+ "merchantId": "Merchant ID",
29
+ "dataSource": "Data source",
30
+ "contentLanguage": "Content language",
31
+ "feedLabel": "Feed label",
32
+ "credentials": "Google account credentials",
33
+ "invalidCredentialsFile": "The credentials file is not valid JSON",
34
+ "credentialsReadFailed": "Failed to read the credentials file",
35
+ "diagnostics": {
36
+ "title": "Connection diagnostics",
37
+ "status": "Diagnostic status",
38
+ "unknown": "UNKNOWN",
39
+ "dataSourceVerified": "Data source verified",
40
+ "latency": "Latency",
41
+ "disapprovedProducts": "Disapproved products",
42
+ "productIssues": "Product issues",
43
+ "yes": "yes",
44
+ "no": "no",
45
+ "test": "Test connection",
46
+ "completed": "Connection diagnostic completed"
47
+ },
48
+ "sync": {
49
+ "all": "Synchronize all",
50
+ "queued": "Full synchronization queued",
51
+ "queueFailed": "Failed to queue full synchronization",
52
+ "cleanupQueued": "Orphan cleanup queued",
53
+ "cleanupQueueFailed": "Failed to queue orphan cleanup",
54
+ "recent": "Recent synchronizations",
55
+ "empty": "No synchronization history",
56
+ "successful": "successful",
57
+ "failed": "failed",
58
+ "attempts": "attempts"
59
+ }
60
+ },
61
+ "facebook": {
62
+ "title": "Facebook Commerce",
63
+ "catalogId": "Catalog ID",
64
+ "accessToken": "Facebook access token"
6
65
  }
7
66
  }
@@ -4,5 +4,64 @@ declare const _default: {
4
4
  facebookCommerce: string;
5
5
  merchantPlatforms: string;
6
6
  };
7
+ common: {
8
+ brand: string;
9
+ save: string;
10
+ loading: string;
11
+ autoUpdate: string;
12
+ updateAllOnSave: string;
13
+ connectionStatus: string;
14
+ connected: string;
15
+ disconnected: string;
16
+ productsCount: string;
17
+ removeOldItems: string;
18
+ settingsSaved: string;
19
+ settingsSaveFailed: string;
20
+ settingsSaveFailedWithError: string;
21
+ settingsLoadFailed: string;
22
+ unknownError: string;
23
+ oldItemsRemoved: string;
24
+ oldItemsRemoveFailed: string;
25
+ };
26
+ google: {
27
+ title: string;
28
+ merchantId: string;
29
+ dataSource: string;
30
+ contentLanguage: string;
31
+ feedLabel: string;
32
+ credentials: string;
33
+ invalidCredentialsFile: string;
34
+ credentialsReadFailed: string;
35
+ diagnostics: {
36
+ title: string;
37
+ status: string;
38
+ unknown: string;
39
+ dataSourceVerified: string;
40
+ latency: string;
41
+ disapprovedProducts: string;
42
+ productIssues: string;
43
+ yes: string;
44
+ no: string;
45
+ test: string;
46
+ completed: string;
47
+ };
48
+ sync: {
49
+ all: string;
50
+ queued: string;
51
+ queueFailed: string;
52
+ cleanupQueued: string;
53
+ cleanupQueueFailed: string;
54
+ recent: string;
55
+ empty: string;
56
+ successful: string;
57
+ failed: string;
58
+ attempts: string;
59
+ };
60
+ };
61
+ facebook: {
62
+ title: string;
63
+ catalogId: string;
64
+ accessToken: string;
65
+ };
7
66
  }[];
8
67
  export default _default;
@@ -2,6 +2,65 @@
2
2
  "nav": {
3
3
  "googleMerchant": "Google Merchant",
4
4
  "facebookCommerce": "Facebook Commerce",
5
- "merchantPlatforms": "Merchant Platforms"
5
+ "merchantPlatforms": "Platformy sprzedażowe"
6
+ },
7
+ "common": {
8
+ "brand": "Marka",
9
+ "save": "Zapisz",
10
+ "loading": "Ładowanie ustawień platformy",
11
+ "autoUpdate": "Aktualizuj automatycznie po zmianie produktu",
12
+ "updateAllOnSave": "Zaktualizuj wszystkie produkty podczas zapisywania",
13
+ "connectionStatus": "Stan połączenia",
14
+ "connected": "Połączono",
15
+ "disconnected": "Rozłączono",
16
+ "productsCount": "Liczba produktów",
17
+ "removeOldItems": "Usuń stare pozycje",
18
+ "settingsSaved": "Ustawienia zostały zapisane",
19
+ "settingsSaveFailed": "Nie udało się zapisać ustawień",
20
+ "settingsSaveFailedWithError": "Nie udało się zapisać ustawień: {{message}}",
21
+ "settingsLoadFailed": "Nie udało się wczytać ustawień platformy",
22
+ "unknownError": "Nieznany błąd",
23
+ "oldItemsRemoved": "Stare pozycje zostały usunięte",
24
+ "oldItemsRemoveFailed": "Nie udało się usunąć starych pozycji"
25
+ },
26
+ "google": {
27
+ "title": "Google Merchant",
28
+ "merchantId": "Identyfikator sprzedawcy",
29
+ "dataSource": "Źródło danych",
30
+ "contentLanguage": "Język treści",
31
+ "feedLabel": "Etykieta pliku danych",
32
+ "credentials": "Dane logowania konta Google",
33
+ "invalidCredentialsFile": "Plik danych logowania nie zawiera poprawnego JSON-a",
34
+ "credentialsReadFailed": "Nie udało się odczytać pliku danych logowania",
35
+ "diagnostics": {
36
+ "title": "Diagnostyka połączenia",
37
+ "status": "Stan diagnostyki",
38
+ "unknown": "NIEZNANY",
39
+ "dataSourceVerified": "Źródło danych zweryfikowane",
40
+ "latency": "Opóźnienie",
41
+ "disapprovedProducts": "Odrzucone produkty",
42
+ "productIssues": "Problemy z produktami",
43
+ "yes": "tak",
44
+ "no": "nie",
45
+ "test": "Sprawdź połączenie",
46
+ "completed": "Diagnostyka połączenia zakończona"
47
+ },
48
+ "sync": {
49
+ "all": "Synchronizuj wszystko",
50
+ "queued": "Pełna synchronizacja została dodana do kolejki",
51
+ "queueFailed": "Nie udało się dodać pełnej synchronizacji do kolejki",
52
+ "cleanupQueued": "Usuwanie osieroconych pozycji zostało dodane do kolejki",
53
+ "cleanupQueueFailed": "Nie udało się dodać usuwania osieroconych pozycji do kolejki",
54
+ "recent": "Ostatnie synchronizacje",
55
+ "empty": "Brak historii synchronizacji",
56
+ "successful": "zakończonych powodzeniem",
57
+ "failed": "nieudanych",
58
+ "attempts": "próby"
59
+ }
60
+ },
61
+ "facebook": {
62
+ "title": "Facebook Commerce",
63
+ "catalogId": "Identyfikator katalogu",
64
+ "accessToken": "Token dostępu Facebooka"
6
65
  }
7
66
  }
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, } from "@deenruv/react-ui-devkit";
3
+ import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, useTranslation, } from "@deenruv/react-ui-devkit";
4
4
  import { getMerchantPlatformInfo, getMerchantPlatformSettings, } from "../graphql/queries";
5
5
  import { saveMerchantPlatformSettings, removeOrphanItems, } from "../graphql/mutations";
6
6
  import { toast } from "sonner";
7
+ import { translationNS } from "../translation-ns.js";
7
8
  export const FacebookPage = () => {
9
+ const { t } = useTranslation(translationNS);
8
10
  const [fetchMerchantPlatformSettings] = useLazyQuery(getMerchantPlatformSettings);
9
11
  const [fetchMerchantPlatformInfo] = useLazyQuery(getMerchantPlatformInfo);
10
12
  const [mutate] = useMutation(saveMerchantPlatformSettings);
@@ -14,6 +16,7 @@ export const FacebookPage = () => {
14
16
  connectionStatus: false,
15
17
  });
16
18
  const [isLoading, setIsLoading] = useState(true);
19
+ const [loadError, setLoadError] = useState(false);
17
20
  const [settingsForm, setSettingsForm] = useState({
18
21
  brand: "",
19
22
  merchantId: "",
@@ -24,6 +27,7 @@ export const FacebookPage = () => {
24
27
  const refetch = async () => {
25
28
  try {
26
29
  setIsLoading(true);
30
+ setLoadError(false);
27
31
  const [settingsData, infoData] = await Promise.all([
28
32
  fetchMerchantPlatformSettings({ platform: "facebook" }),
29
33
  fetchMerchantPlatformInfo({ platform: "facebook" }),
@@ -55,6 +59,7 @@ export const FacebookPage = () => {
55
59
  }
56
60
  catch (error) {
57
61
  console.error(error);
62
+ setLoadError(true);
58
63
  setIsLoading(false);
59
64
  }
60
65
  };
@@ -74,19 +79,21 @@ export const FacebookPage = () => {
74
79
  },
75
80
  });
76
81
  if (saveMerchantPlatformSettings) {
77
- toast.success("Settings saved successfully");
82
+ toast.success(t("common.settingsSaved"));
78
83
  refetch();
79
84
  }
80
85
  else {
81
- toast.error("Failed to save settings");
86
+ toast.error(t("common.settingsSaveFailed"));
82
87
  }
83
88
  }
84
89
  catch (error) {
85
90
  console.error(error);
86
- toast.error("Failed to save settings");
91
+ toast.error(t("common.settingsSaveFailedWithError", {
92
+ message: error instanceof Error ? error.message : t("common.unknownError"),
93
+ }));
87
94
  }
88
95
  };
89
- return (_jsx(PageBlock, { children: _jsxs("div", { style: { position: "relative" }, className: "flex flex-col p-4 relative max-w-[800px] mx-auto", children: [isLoading && (_jsx("div", { style: {
96
+ return (_jsx(PageBlock, { children: _jsxs("div", { style: { position: "relative" }, className: "flex flex-col p-4 relative max-w-[800px] mx-auto", children: [isLoading && (_jsx("div", { role: "status", "aria-label": t("common.loading"), style: {
90
97
  position: "absolute",
91
98
  top: 0,
92
99
  left: 0,
@@ -104,27 +111,29 @@ export const FacebookPage = () => {
104
111
  alignItems: "center",
105
112
  width: "42px",
106
113
  height: "42px",
107
- }, className: "spinner" }) })), " ", _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Brand" }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Catalog ID" }), _jsx(Input, { className: "w-full", value: settingsForm.merchantId, onChange: (e) => setSettingsForm({
114
+ }, className: "spinner" }) })), _jsx("h2", { className: "mb-4 text-xl font-semibold", children: t("facebook.title") }), loadError ? (_jsx("div", { className: "mb-4 rounded border border-red-400 p-3 text-red-700", role: "alert", children: t("common.settingsLoadFailed") })) : null, _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("common.brand") }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("facebook.catalogId") }), _jsx(Input, { className: "w-full", value: settingsForm.merchantId, onChange: (e) => setSettingsForm({
108
115
  ...settingsForm,
109
116
  merchantId: e.target.value,
110
- }) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: "Facebook Access Token" }), _jsx(Input, { className: "w-full", value: settingsForm.credentials, onChange: (e) => setSettingsForm({
117
+ }) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: t("facebook.accessToken") }), _jsx(Input, { className: "w-full", value: settingsForm.credentials, onChange: (e) => setSettingsForm({
111
118
  ...settingsForm,
112
119
  credentials: e.target.value,
113
120
  }) })] }), _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(Checkbox, { id: "facebook-account-credentials", checked: settingsForm.autoUpdate, onCheckedChange: (checked) => setSettingsForm({
114
121
  ...settingsForm,
115
122
  autoUpdate: typeof checked === "boolean" ? checked : false,
116
- }) }), _jsx(Label, { htmlFor: "facebook-account-credentials", children: "Auto update on Product's change" })] })] }), _jsx("div", { className: "flex justify-end", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Label, { htmlFor: "auto-update-on-products-change", children: "Update ALL products with saving" }), _jsx(Checkbox, { id: "auto-update-on-products-change", checked: settingsForm.firstSync, onCheckedChange: (checked) => setSettingsForm({
123
+ }) }), _jsx(Label, { htmlFor: "facebook-account-credentials", children: t("common.autoUpdate") })] })] }), _jsx("div", { className: "flex justify-end", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Label, { htmlFor: "auto-update-on-products-change", children: t("common.updateAllOnSave") }), _jsx(Checkbox, { id: "auto-update-on-products-change", checked: settingsForm.firstSync, onCheckedChange: (checked) => setSettingsForm({
117
124
  ...settingsForm,
118
125
  firstSync: typeof checked === "boolean" ? checked : false,
119
- }) })] }), _jsx(Button, { children: "Save" })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Connection status" }), serviceInfo.connectionStatus ? _jsx("div", { children: "\uD83D\uDC9A" }) : _jsx("div", { children: "\uD83D\uDC94" })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
126
+ }) })] }), _jsx(Button, { children: t("common.save") })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: t("common.connectionStatus") }), _jsx("strong", { children: serviceInfo.connectionStatus
127
+ ? t("common.connected")
128
+ : t("common.disconnected") })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
120
129
  try {
121
130
  await removeOldItems({ platform: "facebook" });
122
- toast.success("Old items removed successfully");
131
+ toast.success(t("common.oldItemsRemoved"));
123
132
  refetch();
124
133
  }
125
134
  catch (error) {
126
135
  console.error(error);
127
- toast.error("Failed to remove old items");
136
+ toast.error(t("common.oldItemsRemoveFailed"));
128
137
  }
129
- }, children: "Remove old items" }) })) : null] }) }));
138
+ }, children: t("common.removeOldItems") }) })) : null] }) }));
130
139
  };
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, } from "@deenruv/react-ui-devkit";
3
+ import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, useTranslation, } from "@deenruv/react-ui-devkit";
4
4
  import { getGoogleMerchantDiagnostic, getGoogleMerchantSyncHistory, getMerchantPlatformSettings, } from "../graphql/queries";
5
5
  import { removeOrphanItems, saveMerchantPlatformSettings, sendAllProductsToMerchantPlatform, } from "../graphql/mutations";
6
6
  import { toast } from "sonner";
7
+ import { translationNS } from "../translation-ns.js";
7
8
  export const GooglePage = () => {
9
+ const { t, i18n } = useTranslation(translationNS);
8
10
  const [fetchMerchantPlatformSettings] = useLazyQuery(getMerchantPlatformSettings);
9
11
  const [fetchMerchantPlatformInfo] = useLazyQuery(getGoogleMerchantDiagnostic);
10
12
  const [fetchSyncHistory] = useLazyQuery(getGoogleMerchantSyncHistory);
@@ -32,16 +34,19 @@ export const GooglePage = () => {
32
34
  }
33
35
  catch (error) {
34
36
  console.error("Invalid JSON file:", error);
37
+ toast.error(t("google.invalidCredentialsFile"));
35
38
  }
36
39
  };
37
40
  reader.onerror = (e) => {
38
41
  console.error("Error reading file", e);
42
+ toast.error(t("google.credentialsReadFailed"));
39
43
  };
40
44
  if (file)
41
45
  reader.readAsText(file);
42
46
  }
43
47
  };
44
48
  const [isLoading, setIsLoading] = useState(true);
49
+ const [loadError, setLoadError] = useState(false);
45
50
  const [settingsForm, setSettingsForm] = useState({
46
51
  brand: "",
47
52
  merchantId: "",
@@ -55,6 +60,7 @@ export const GooglePage = () => {
55
60
  const refetch = async () => {
56
61
  try {
57
62
  setIsLoading(true);
63
+ setLoadError(false);
58
64
  const [settingsData, infoData, historyData] = await Promise.all([
59
65
  fetchMerchantPlatformSettings({ platform: "google" }),
60
66
  fetchMerchantPlatformInfo(),
@@ -99,6 +105,7 @@ export const GooglePage = () => {
99
105
  }
100
106
  catch (error) {
101
107
  console.error(error);
108
+ setLoadError(true);
102
109
  setIsLoading(false);
103
110
  }
104
111
  };
@@ -118,19 +125,21 @@ export const GooglePage = () => {
118
125
  },
119
126
  });
120
127
  if (saveMerchantPlatformSettings) {
121
- toast.success("Settings saved successfully");
128
+ toast.success(t("common.settingsSaved"));
122
129
  refetch();
123
130
  }
124
131
  else {
125
- toast.error("Failed to save settings");
132
+ toast.error(t("common.settingsSaveFailed"));
126
133
  }
127
134
  }
128
135
  catch (error) {
129
136
  console.error(error);
130
- toast.error(`Failed to save settings: ${error instanceof Error ? error.message : "Unknown error"}`);
137
+ toast.error(t("common.settingsSaveFailedWithError", {
138
+ message: error instanceof Error ? error.message : t("common.unknownError"),
139
+ }));
131
140
  }
132
141
  };
133
- return (_jsx(PageBlock, { children: _jsxs("div", { style: { position: "relative" }, className: "flex flex-col p-4 relative max-w-[800px] mx-auto", children: [isLoading && (_jsx("div", { style: {
142
+ return (_jsx(PageBlock, { children: _jsxs("div", { style: { position: "relative" }, className: "flex flex-col p-4 relative max-w-[800px] mx-auto", children: [isLoading && (_jsx("div", { role: "status", "aria-label": t("common.loading"), style: {
134
143
  position: "absolute",
135
144
  top: 0,
136
145
  left: 0,
@@ -148,53 +157,57 @@ export const GooglePage = () => {
148
157
  alignItems: "center",
149
158
  width: "42px",
150
159
  height: "42px",
151
- }, className: "spinner" }) })), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Brand" }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Merchant ID" }), _jsx(Input, { className: "w-full", required: true, value: settingsForm.merchantId, onChange: (e) => setSettingsForm({
160
+ }, className: "spinner" }) })), _jsx("h2", { className: "mb-4 text-xl font-semibold", children: t("google.title") }), loadError ? (_jsx("div", { className: "mb-4 rounded border border-red-400 p-3 text-red-700", role: "alert", children: t("common.settingsLoadFailed") })) : null, _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("common.brand") }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.merchantId") }), _jsx(Input, { className: "w-full", required: true, value: settingsForm.merchantId, onChange: (e) => setSettingsForm({
152
161
  ...settingsForm,
153
162
  merchantId: e.target.value,
154
- }) })] })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Data source" }), _jsx(Input, { className: "w-full", placeholder: "accounts/{merchantId}/dataSources/{id}", required: true, value: settingsForm.dataSource, onChange: (e) => setSettingsForm({
163
+ }) })] })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.dataSource") }), _jsx(Input, { className: "w-full", placeholder: "accounts/{merchantId}/dataSources/{id}", required: true, value: settingsForm.dataSource, onChange: (e) => setSettingsForm({
155
164
  ...settingsForm,
156
165
  dataSource: e.target.value,
157
- }) })] }), _jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Content language" }), _jsx(Input, { required: true, value: settingsForm.contentLanguage, onChange: (e) => setSettingsForm({
166
+ }) })] }), _jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.contentLanguage") }), _jsx(Input, { required: true, value: settingsForm.contentLanguage, onChange: (e) => setSettingsForm({
158
167
  ...settingsForm,
159
168
  contentLanguage: e.target.value,
160
- }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Feed label" }), _jsx(Input, { required: true, value: settingsForm.feedLabel, onChange: (e) => setSettingsForm({
169
+ }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: t("google.feedLabel") }), _jsx(Input, { required: true, value: settingsForm.feedLabel, onChange: (e) => setSettingsForm({
161
170
  ...settingsForm,
162
171
  feedLabel: e.target.value,
163
- }) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: "Google Account Credentials" }), _jsx(Input, { style: {
172
+ }) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: t("google.credentials") }), _jsx(Input, { style: {
164
173
  border: "none",
165
174
  backgroundColor: "transparent",
166
175
  background: "none",
167
176
  }, type: "file", accept: ".json", onChange: handleSelectedFile })] }), _jsxs("div", { className: "flex gap-2 items-center", children: [_jsx(Checkbox, { id: "google-account-credentials", checked: settingsForm.autoUpdate, onCheckedChange: (checked) => setSettingsForm({
168
177
  ...settingsForm,
169
178
  autoUpdate: typeof checked === "boolean" ? checked : false,
170
- }) }), _jsx(Label, { htmlFor: "google-account-credentials", children: "Auto update on Product's change" })] })] }), _jsx("div", { className: "flex justify-end", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Label, { htmlFor: "auto-update-on-products-change", children: "Update ALL products with saving" }), _jsx(Checkbox, { id: "auto-update-on-products-change", checked: settingsForm.firstSync, onCheckedChange: (checked) => setSettingsForm({
179
+ }) }), _jsx(Label, { htmlFor: "google-account-credentials", children: t("common.autoUpdate") })] })] }), _jsx("div", { className: "flex justify-end", children: _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "flex gap-2", children: [_jsx(Label, { htmlFor: "auto-update-on-products-change", children: t("common.updateAllOnSave") }), _jsx(Checkbox, { id: "auto-update-on-products-change", checked: settingsForm.firstSync, onCheckedChange: (checked) => setSettingsForm({
171
180
  ...settingsForm,
172
181
  firstSync: typeof checked === "boolean" ? checked : false,
173
- }) })] }), _jsx(Button, { children: "Save" })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Connection status" }), _jsx("strong", { children: serviceInfo.connectionStatus ? "Connected" : "Disconnected" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Products count" }), _jsx("span", { children: serviceInfo.productsCount })] }), _jsxs("div", { className: "flex flex-col gap-1 text-sm", children: [_jsxs("span", { children: ["Diagnostic: ", diagnostic?.connectionStatus ?? "UNKNOWN"] }), _jsxs("span", { children: ["Data source verified:", " ", diagnostic?.dataSourceVerified ? "yes" : "no"] }), _jsxs("span", { children: ["Latency: ", diagnostic?.latencyMs ?? 0, " ms"] }), _jsxs("span", { children: ["Disapproved products: ", diagnostic?.disapprovedProductsCount ?? 0] }), _jsxs("span", { children: ["Product issues: ", diagnostic?.issuesCount ?? 0] }), diagnostic?.lastError ? (_jsxs("div", { className: "rounded border border-red-400 p-2 text-red-700", children: [diagnostic.lastError.code, ": ", diagnostic.lastError.message] })) : null, (diagnostic?.issues ?? []).slice(0, 20).map((issue) => (_jsxs("div", { className: "rounded border border-amber-400 p-2", children: [issue.offerId, " \u00B7 ", issue.severity, " \u00B7 ", issue.code, ":", " ", issue.description] }, `${issue.offerId}-${issue.code}`)))] }), _jsxs("div", { className: "mt-4 flex gap-2", children: [_jsx(Button, { type: "button", onClick: async () => {
182
+ }) })] }), _jsx(Button, { children: t("common.save") })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: t("common.connectionStatus") }), _jsx("strong", { children: serviceInfo.connectionStatus
183
+ ? t("common.connected")
184
+ : t("common.disconnected") })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: t("common.productsCount") }), _jsx("span", { children: serviceInfo.productsCount })] }), _jsxs("div", { className: "flex flex-col gap-1 text-sm", children: [_jsx("h3", { className: "font-semibold", children: t("google.diagnostics.title") }), _jsxs("span", { children: [t("google.diagnostics.status"), ":", " ", diagnostic?.connectionStatus ?? t("google.diagnostics.unknown")] }), _jsxs("span", { children: [t("google.diagnostics.dataSourceVerified"), ":", " ", diagnostic?.dataSourceVerified
185
+ ? t("google.diagnostics.yes")
186
+ : t("google.diagnostics.no")] }), _jsxs("span", { children: [t("google.diagnostics.latency"), ": ", diagnostic?.latencyMs ?? 0, " ms"] }), _jsxs("span", { children: [t("google.diagnostics.disapprovedProducts"), ":", " ", diagnostic?.disapprovedProductsCount ?? 0] }), _jsxs("span", { children: [t("google.diagnostics.productIssues"), ":", " ", diagnostic?.issuesCount ?? 0] }), diagnostic?.lastError ? (_jsxs("div", { className: "rounded border border-red-400 p-2 text-red-700", children: [diagnostic.lastError.code, ": ", diagnostic.lastError.message] })) : null, (diagnostic?.issues ?? []).slice(0, 20).map((issue) => (_jsxs("div", { className: "rounded border border-amber-400 p-2", children: [issue.offerId, " \u00B7 ", issue.severity, " \u00B7 ", issue.code, ":", " ", issue.description] }, `${issue.offerId}-${issue.code}`)))] }), _jsxs("div", { className: "mt-4 flex gap-2", children: [_jsx(Button, { type: "button", onClick: async () => {
174
187
  await refetch();
175
- toast.success("Connection diagnostic completed");
176
- }, children: "Test connection" }), _jsx(Button, { type: "button", onClick: async () => {
188
+ toast.success(t("google.diagnostics.completed"));
189
+ }, children: t("google.diagnostics.test") }), _jsx(Button, { type: "button", onClick: async () => {
177
190
  try {
178
191
  await sendAllProducts({ platform: "google" });
179
- toast.success("Full synchronization queued");
192
+ toast.success(t("google.sync.queued"));
180
193
  await refetch();
181
194
  }
182
195
  catch (error) {
183
196
  console.error(error);
184
- toast.error("Failed to queue full synchronization");
197
+ toast.error(t("google.sync.queueFailed"));
185
198
  }
186
- }, children: "Synchronize all" })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
199
+ }, children: t("google.sync.all") })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
187
200
  try {
188
201
  await removeOldItems({ platform: "google" });
189
- toast.success("Orphan cleanup queued");
202
+ toast.success(t("google.sync.cleanupQueued"));
190
203
  refetch();
191
204
  }
192
205
  catch (error) {
193
206
  console.error(error);
194
- toast.error("Failed to queue orphan cleanup");
207
+ toast.error(t("google.sync.cleanupQueueFailed"));
195
208
  }
196
- }, children: "Remove old items" }) })) : null, _jsxs("div", { className: "mt-8 flex flex-col gap-2", children: [_jsx("h3", { className: "font-semibold", children: "Recent synchronizations" }), syncHistory.length === 0 ? (_jsx("span", { children: "No synchronization history" })) : (syncHistory.map((run) => (_jsxs("div", { className: "rounded border p-2 text-sm", children: [_jsxs("div", { children: [run.status, " \u00B7 ", run.succeeded, "/", run.total, " successful \u00B7", " ", run.failed, " failed"] }), _jsx("div", { children: new Date(run.createdAt).toLocaleString() }), run.errorSummary ? (_jsx("div", { className: "text-red-700", children: run.errorSummary })) : null, run.items
209
+ }, children: t("common.removeOldItems") }) })) : null, _jsxs("div", { className: "mt-8 flex flex-col gap-2", children: [_jsx("h3", { className: "font-semibold", children: t("google.sync.recent") }), syncHistory.length === 0 ? (_jsx("span", { children: t("google.sync.empty") })) : (syncHistory.map((run) => (_jsxs("div", { className: "rounded border p-2 text-sm", children: [_jsxs("div", { children: [run.status, " \u00B7 ", run.succeeded, "/", run.total, " ", t("google.sync.successful"), " \u00B7 ", run.failed, " ", t("google.sync.failed")] }), _jsx("div", { children: new Date(run.createdAt).toLocaleString(i18n.language) }), run.errorSummary ? (_jsx("div", { className: "text-red-700", children: run.errorSummary })) : null, run.items
197
210
  .filter((item) => item.status === "FAILED")
198
211
  .slice(0, 20)
199
- .map((item) => (_jsxs("div", { className: "mt-1 text-red-700", children: [item.offerId, " \u00B7 ", item.errorCode ?? "ERROR", " \u00B7 attempts:", " ", item.attempts, " \u00B7 ", item.errorMessage] }, `${run.id}-${item.offerId}-${item.operation}`)))] }, run.id))))] })] }) }));
212
+ .map((item) => (_jsxs("div", { className: "mt-1 text-red-700", children: [item.offerId, " \u00B7 ", item.errorCode ?? "ERROR", " \u00B7", " ", t("google.sync.attempts"), ": ", item.attempts, " \u00B7", " ", item.errorMessage] }, `${run.id}-${item.offerId}-${item.operation}`)))] }, run.id))))] })] }) }));
200
213
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { fileURLToPath, URL } from "node:url";
3
+ import { describe, expect, it } from "vitest";
4
+ const readJson = (relativePath) => JSON.parse(readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), "utf8"));
5
+ const flattenKeys = (tree, prefix = "") => Object.entries(tree).flatMap(([key, value]) => {
6
+ const path = prefix ? `${prefix}.${key}` : key;
7
+ return typeof value === "string" ? [path] : flattenKeys(value, path);
8
+ });
9
+ const source = ["./pages/GooglePage.tsx", "./pages/FacebookPage.tsx"]
10
+ .map((relativePath) => readFileSync(fileURLToPath(new URL(relativePath, import.meta.url)), "utf8"))
11
+ .join("\n");
12
+ describe("merchant plugin translations", () => {
13
+ const en = readJson("./locales/en/merchant.json");
14
+ const pl = readJson("./locales/pl/merchant.json");
15
+ it("keeps complete recursive EN/PL key parity", () => {
16
+ expect(flattenKeys(pl).sort()).toEqual(flattenKeys(en).sort());
17
+ });
18
+ it("defines every statically referenced translation key", () => {
19
+ const referencedKeys = Array.from(source.matchAll(/\bt\("([^"]+)"/g), (match) => match[1]);
20
+ const availableKeys = new Set(flattenKeys(en));
21
+ expect(referencedKeys.length).toBeGreaterThan(0);
22
+ expect(referencedKeys.filter((key) => !availableKeys.has(key))).toEqual([]);
23
+ });
24
+ it("preserves platform brands and translates the Polish navigation group", () => {
25
+ expect(en.nav).toMatchObject({
26
+ googleMerchant: "Google Merchant",
27
+ facebookCommerce: "Facebook Commerce",
28
+ });
29
+ expect(pl.nav).toMatchObject({
30
+ googleMerchant: "Google Merchant",
31
+ facebookCommerce: "Facebook Commerce",
32
+ merchantPlatforms: "Platformy sprzedażowe",
33
+ });
34
+ });
35
+ it("formats Google synchronization timestamps in the active UI language", () => {
36
+ expect(source).toContain("toLocaleString(i18n.language)");
37
+ });
38
+ it("does not reintroduce audited hard-coded shell copy", () => {
39
+ for (const literal of [
40
+ ">Brand<",
41
+ ">Save<",
42
+ ">Connection status<",
43
+ ">Remove old items<",
44
+ 'toast.success("Settings saved successfully")',
45
+ 'toast.error("Failed to save settings")',
46
+ ">Recent synchronizations<",
47
+ ">No synchronization history<",
48
+ ]) {
49
+ expect(source).not.toContain(literal);
50
+ }
51
+ });
52
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deenruv/merchant-plugin",
3
- "version": "1.0.17-dev.31",
3
+ "version": "1.0.17-dev.33",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -32,11 +32,11 @@
32
32
  "react-i18next": "^14.0.5",
33
33
  "recharts": "^2.12.7",
34
34
  "sonner": "^1.4.41",
35
- "@deenruv/admin-types": "^1.0.17-dev.31",
36
- "@deenruv/common": "^1.0.17-dev.31",
37
- "@deenruv/ui-devkit": "^1.0.17-dev.31",
38
- "@deenruv/admin-ui": "^1.0.17-dev.31",
39
- "@deenruv/react-ui-devkit": "^1.0.17-dev.31"
35
+ "@deenruv/admin-types": "^1.0.17-dev.33",
36
+ "@deenruv/admin-ui": "^1.0.17-dev.33",
37
+ "@deenruv/react-ui-devkit": "^1.0.17-dev.33",
38
+ "@deenruv/common": "^1.0.17-dev.33",
39
+ "@deenruv/ui-devkit": "^1.0.17-dev.33"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@graphql-typed-document-node/core": "3.2.0",
@@ -48,7 +48,7 @@
48
48
  "rimraf": "^5.0.10",
49
49
  "tslib": "^2.6.2",
50
50
  "typescript": "5.3.3",
51
- "@deenruv/core": "^1.0.17-dev.31"
51
+ "@deenruv/core": "^1.0.17-dev.33"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@deenruv/core": "^1.0.0 || ^1.0.17-dev.0"