@deenruv/merchant-plugin 1.0.17-dev.18 → 1.0.17-dev.20

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 (28) hide show
  1. package/README.md +14 -0
  2. package/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +20 -1
  3. package/dist/plugin-server/api/platform-integration-admin-resolver.js +44 -19
  4. package/dist/plugin-server/entities/merchant-sync-item.entity.d.ts +14 -0
  5. package/dist/plugin-server/entities/merchant-sync-item.entity.js +56 -0
  6. package/dist/plugin-server/entities/merchant-sync-run.entity.d.ts +19 -0
  7. package/dist/plugin-server/entities/merchant-sync-run.entity.js +72 -0
  8. package/dist/plugin-server/extensions/api-extensions.js +53 -2
  9. package/dist/plugin-server/index.js +10 -1
  10. package/dist/plugin-server/services/google-merchant-api.d.ts +48 -6
  11. package/dist/plugin-server/services/google-merchant-api.js +247 -32
  12. package/dist/plugin-server/services/google-platform-integration.service.d.ts +8 -7
  13. package/dist/plugin-server/services/google-platform-integration.service.js +79 -6
  14. package/dist/plugin-server/services/merchant-sync-history.service.d.ts +25 -0
  15. package/dist/plugin-server/services/merchant-sync-history.service.js +97 -0
  16. package/dist/plugin-server/services/platform-integration.service.d.ts +6 -2
  17. package/dist/plugin-server/services/platform-integration.service.js +53 -29
  18. package/dist/plugin-server/services/subscriber.service.d.ts +32 -4
  19. package/dist/plugin-server/services/subscriber.service.js +141 -23
  20. package/dist/plugin-server/ui/graphql/mutations.ts +7 -0
  21. package/dist/plugin-server/ui/graphql/queries.ts +99 -0
  22. package/dist/plugin-server/ui/pages/GooglePage.tsx +158 -16
  23. package/dist/plugin-ui/graphql/mutations.d.ts +5 -0
  24. package/dist/plugin-ui/graphql/mutations.js +6 -0
  25. package/dist/plugin-ui/graphql/queries.d.ts +53 -2
  26. package/dist/plugin-ui/graphql/queries.js +49 -0
  27. package/dist/plugin-ui/pages/GooglePage.js +51 -13
  28. package/package.json +8 -7
@@ -2,6 +2,8 @@ import { typedGql } from "../zeus/typedDocumentNode";
2
2
  import { scalars } from "./scalars";
3
3
  import { merchantPlatformSettingsSelector } from "./mutations";
4
4
  import { $ } from "../zeus/index";
5
+ import { gql } from "graphql-tag";
6
+ import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
5
7
 
6
8
  const query = typedGql("query", { scalars });
7
9
  export const getMerchantPlatformSettings = query({
@@ -16,3 +18,100 @@ export const getMerchantPlatformInfo = query({
16
18
  { isValidConnection: true, productsCount: true },
17
19
  ],
18
20
  });
21
+
22
+ export type GoogleMerchantDiagnostic = {
23
+ checkedAt?: string;
24
+ connectionStatus?: string;
25
+ dataSourceVerified?: boolean;
26
+ disapprovedProductsCount?: number;
27
+ isValidConnection: boolean;
28
+ issues?: Array<{
29
+ code: string;
30
+ description: string;
31
+ offerId: string;
32
+ severity: string;
33
+ }>;
34
+ issuesCount?: number;
35
+ lastError?: {
36
+ code: string;
37
+ message: string;
38
+ retryable: boolean;
39
+ };
40
+ latencyMs?: number;
41
+ productsCount: number;
42
+ };
43
+
44
+ export type MerchantSyncRunSummary = {
45
+ createdAt: string;
46
+ errorSummary?: string;
47
+ failed: number;
48
+ finishedAt?: string;
49
+ id: string;
50
+ items: Array<{
51
+ attempts: number;
52
+ errorCode?: string;
53
+ errorMessage?: string;
54
+ offerId: string;
55
+ operation: string;
56
+ status: string;
57
+ }>;
58
+ status: string;
59
+ succeeded: number;
60
+ total: number;
61
+ trigger: string;
62
+ };
63
+
64
+ export const getGoogleMerchantDiagnostic = gql`
65
+ query GetGoogleMerchantDiagnostic {
66
+ getMerchantPlatformInfo(platform: "google") {
67
+ isValidConnection
68
+ productsCount
69
+ connectionStatus
70
+ dataSourceVerified
71
+ checkedAt
72
+ latencyMs
73
+ disapprovedProductsCount
74
+ issuesCount
75
+ lastError {
76
+ code
77
+ message
78
+ retryable
79
+ }
80
+ issues {
81
+ offerId
82
+ code
83
+ description
84
+ severity
85
+ }
86
+ }
87
+ }
88
+ ` as TypedDocumentNode<{
89
+ getMerchantPlatformInfo?: GoogleMerchantDiagnostic[];
90
+ }>;
91
+
92
+ export const getGoogleMerchantSyncHistory = gql`
93
+ query GetGoogleMerchantSyncHistory($take: Int) {
94
+ getMerchantSyncHistory(platform: "google", take: $take) {
95
+ id
96
+ createdAt
97
+ trigger
98
+ status
99
+ total
100
+ succeeded
101
+ failed
102
+ errorSummary
103
+ finishedAt
104
+ items {
105
+ offerId
106
+ operation
107
+ status
108
+ errorCode
109
+ errorMessage
110
+ attempts
111
+ }
112
+ }
113
+ }
114
+ ` as TypedDocumentNode<
115
+ { getMerchantSyncHistory: MerchantSyncRunSummary[] },
116
+ { take?: number }
117
+ >;
@@ -9,10 +9,14 @@ import { NotificationService } from "@deenruv/admin-ui/core";
9
9
  import {
10
10
  removeOrphanItems,
11
11
  saveMerchantPlatformSettings,
12
+ sendAllProductsToMerchantPlatform,
12
13
  } from "../graphql/mutations";
13
14
  import {
14
- getMerchantPlatformInfo,
15
+ getGoogleMerchantDiagnostic,
16
+ getGoogleMerchantSyncHistory,
15
17
  getMerchantPlatformSettings,
18
+ GoogleMerchantDiagnostic,
19
+ MerchantSyncRunSummary,
16
20
  } from "../graphql/queries";
17
21
 
18
22
  export const GooglePage = () => {
@@ -20,9 +24,16 @@ export const GooglePage = () => {
20
24
  const [fetchMerchantPlatformSettings] = useLazyQuery(
21
25
  getMerchantPlatformSettings,
22
26
  );
23
- const [fetchMerchantPlatformInfo] = useLazyQuery(getMerchantPlatformInfo);
27
+ const [fetchMerchantPlatformInfo] = useLazyQuery(
28
+ getGoogleMerchantDiagnostic,
29
+ );
30
+ const [fetchSyncHistory] = useLazyQuery(getGoogleMerchantSyncHistory);
24
31
  const [mutate] = useMutation(saveMerchantPlatformSettings);
25
32
  const [removeOldItems] = useMutation(removeOrphanItems);
33
+ const [sendAllProducts] = useMutation(sendAllProductsToMerchantPlatform);
34
+ const [diagnostic, setDiagnostic] =
35
+ useState<GoogleMerchantDiagnostic | null>(null);
36
+ const [syncHistory, setSyncHistory] = useState<MerchantSyncRunSummary[]>([]);
26
37
  const [serviceInfo, setServiceInfo] = useState({
27
38
  productsCount: 0,
28
39
  connectionStatus: false,
@@ -55,6 +66,8 @@ export const GooglePage = () => {
55
66
  brand: "",
56
67
  merchantId: "",
57
68
  dataSource: "",
69
+ contentLanguage: "pl",
70
+ feedLabel: "PL",
58
71
  credentials: "",
59
72
  autoUpdate: true,
60
73
  firstSync: true,
@@ -63,9 +76,10 @@ export const GooglePage = () => {
63
76
  const refetch = async () => {
64
77
  try {
65
78
  setIsLoading(true);
66
- const [settingsData, infoData] = await Promise.all([
79
+ const [settingsData, infoData, historyData] = await Promise.all([
67
80
  fetchMerchantPlatformSettings({ platform: "google" }),
68
- fetchMerchantPlatformInfo({ platform: "google" }),
81
+ fetchMerchantPlatformInfo(),
82
+ fetchSyncHistory({ take: 10 }),
69
83
  ]);
70
84
  const settings =
71
85
  settingsData?.getMerchantPlatformSettings?.entries?.reduce(
@@ -79,6 +93,12 @@ export const GooglePage = () => {
79
93
  if (key === "dataSource") {
80
94
  acc.dataSource = value;
81
95
  }
96
+ if (key === "contentLanguage") {
97
+ acc.contentLanguage = value;
98
+ }
99
+ if (key === "feedLabel") {
100
+ acc.feedLabel = value;
101
+ }
82
102
  if (key === "credentials") {
83
103
  acc.credentials = value;
84
104
  }
@@ -94,17 +114,20 @@ export const GooglePage = () => {
94
114
  brand: string;
95
115
  merchantId: string;
96
116
  dataSource: string;
117
+ contentLanguage: string;
118
+ feedLabel: string;
97
119
  credentials: string;
98
120
  autoUpdate: boolean;
99
121
  firstSync: boolean;
100
122
  },
101
123
  );
102
124
  setSettingsForm((prev) => ({ ...prev, ...settings }));
125
+ const currentDiagnostic = infoData?.getMerchantPlatformInfo?.[0];
126
+ setDiagnostic(currentDiagnostic ?? null);
127
+ setSyncHistory(historyData?.getMerchantSyncHistory ?? []);
103
128
  setServiceInfo({
104
- productsCount:
105
- infoData?.getMerchantPlatformInfo?.[0]?.productsCount || 0,
106
- connectionStatus:
107
- infoData?.getMerchantPlatformInfo?.[0]?.isValidConnection || false,
129
+ productsCount: currentDiagnostic?.productsCount || 0,
130
+ connectionStatus: currentDiagnostic?.isValidConnection || false,
108
131
  });
109
132
  setIsLoading(false);
110
133
  } catch (error) {
@@ -137,7 +160,11 @@ export const GooglePage = () => {
137
160
  }
138
161
  } catch (error) {
139
162
  console.error(error);
140
- toast.error("Failed to save settings");
163
+ toast.error(
164
+ `Failed to save settings: ${
165
+ error instanceof Error ? error.message : "Unknown error"
166
+ }`,
167
+ );
141
168
  }
142
169
  };
143
170
 
@@ -214,6 +241,34 @@ export const GooglePage = () => {
214
241
  }
215
242
  />
216
243
  </div>
244
+ <div className="flex justify-between gap-4">
245
+ <div className="w-full flex flex-col gap-2">
246
+ <label>Content language</label>
247
+ <input
248
+ required
249
+ value={settingsForm.contentLanguage}
250
+ onChange={(e) =>
251
+ setSettingsForm({
252
+ ...settingsForm,
253
+ contentLanguage: e.target.value,
254
+ })
255
+ }
256
+ />
257
+ </div>
258
+ <div className="w-full flex flex-col gap-2">
259
+ <label>Feed label</label>
260
+ <input
261
+ required
262
+ value={settingsForm.feedLabel}
263
+ onChange={(e) =>
264
+ setSettingsForm({
265
+ ...settingsForm,
266
+ feedLabel: e.target.value,
267
+ })
268
+ }
269
+ />
270
+ </div>
271
+ </div>
217
272
  <div className="flex flex-col gap-4">
218
273
  <div className="flex flex-col">
219
274
  <label>Google Account Credentials</label>
@@ -271,7 +326,67 @@ export const GooglePage = () => {
271
326
  </form>
272
327
  <div className="flex gap-2">
273
328
  <span>Connection status</span>
274
- {serviceInfo.connectionStatus ? <div>💚</div> : <div>💔</div>}
329
+ <strong>
330
+ {serviceInfo.connectionStatus ? "Connected" : "Disconnected"}
331
+ </strong>
332
+ </div>
333
+ <div className="flex gap-2">
334
+ <span>Products count</span>
335
+ <span>{serviceInfo.productsCount}</span>
336
+ </div>
337
+ <div className="flex flex-col gap-1">
338
+ <span>Diagnostic: {diagnostic?.connectionStatus ?? "UNKNOWN"}</span>
339
+ <span>
340
+ Data source verified:{" "}
341
+ {diagnostic?.dataSourceVerified ? "yes" : "no"}
342
+ </span>
343
+ <span>Latency: {diagnostic?.latencyMs ?? 0} ms</span>
344
+ <span>
345
+ Disapproved products: {diagnostic?.disapprovedProductsCount ?? 0}
346
+ </span>
347
+ <span>Product issues: {diagnostic?.issuesCount ?? 0}</span>
348
+ {diagnostic?.lastError ? (
349
+ <div className="alert alert-danger">
350
+ {diagnostic.lastError.code}: {diagnostic.lastError.message}
351
+ </div>
352
+ ) : null}
353
+ {(diagnostic?.issues ?? []).slice(0, 20).map((issue) => (
354
+ <div
355
+ className="alert alert-warning"
356
+ key={`${issue.offerId}-${issue.code}`}
357
+ >
358
+ {issue.offerId} · {issue.severity} · {issue.code}:{" "}
359
+ {issue.description}
360
+ </div>
361
+ ))}
362
+ </div>
363
+ <div className="mt-4 flex gap-2">
364
+ <button
365
+ type="button"
366
+ className="btn btn-secondary"
367
+ onClick={async () => {
368
+ await refetch();
369
+ toast.success("Connection diagnostic completed");
370
+ }}
371
+ >
372
+ Test connection
373
+ </button>
374
+ <button
375
+ type="button"
376
+ className="btn btn-primary"
377
+ onClick={async () => {
378
+ try {
379
+ await sendAllProducts({ platform: "google" });
380
+ toast.success("Full synchronization queued");
381
+ await refetch();
382
+ } catch (error) {
383
+ console.error(error);
384
+ toast.error("Failed to queue full synchronization");
385
+ }
386
+ }}
387
+ >
388
+ Synchronize all
389
+ </button>
275
390
  </div>
276
391
  {serviceInfo.connectionStatus ? (
277
392
  <div style={{ marginTop: "12px" }}>
@@ -280,11 +395,11 @@ export const GooglePage = () => {
280
395
  onClick={async () => {
281
396
  try {
282
397
  await removeOldItems({ platform: "google" });
283
- toast.success("Old items removed successfully");
398
+ toast.success("Orphan cleanup queued");
284
399
  refetch();
285
400
  } catch (error) {
286
401
  console.error(error);
287
- toast.error("Failed to remove old items");
402
+ toast.error("Failed to queue orphan cleanup");
288
403
  }
289
404
  }}
290
405
  >
@@ -292,10 +407,37 @@ export const GooglePage = () => {
292
407
  </button>
293
408
  </div>
294
409
  ) : null}
295
- {/* <div className="flex gap-2">
296
- <span>Products count</span>
297
- <span>{serviceInfo.productsCount}</span>
298
- </div> */}
410
+ <div className="mt-8 flex flex-col gap-2">
411
+ <h3>Recent synchronizations</h3>
412
+ {syncHistory.length === 0 ? (
413
+ <span>No synchronization history</span>
414
+ ) : (
415
+ syncHistory.map((run) => (
416
+ <div className="border rounded p-2" key={run.id}>
417
+ <div>
418
+ {run.status} · {run.succeeded}/{run.total} successful ·{" "}
419
+ {run.failed} failed
420
+ </div>
421
+ <div>{new Date(run.createdAt).toLocaleString()}</div>
422
+ {run.errorSummary ? (
423
+ <div className="text-danger">{run.errorSummary}</div>
424
+ ) : null}
425
+ {run.items
426
+ .filter((item) => item.status === "FAILED")
427
+ .slice(0, 20)
428
+ .map((item) => (
429
+ <div
430
+ className="text-danger"
431
+ key={`${run.id}-${item.offerId}-${item.operation}`}
432
+ >
433
+ {item.offerId} · {item.errorCode ?? "ERROR"} · attempts:{" "}
434
+ {item.attempts} · {item.errorMessage}
435
+ </div>
436
+ ))}
437
+ </div>
438
+ ))
439
+ )}
440
+ </div>
299
441
  </div>
300
442
  </PageDetailLayout>
301
443
  );
@@ -27,3 +27,8 @@ export declare const removeOrphanItems: import("@graphql-typed-document-node/cor
27
27
  }, {} & {
28
28
  platform: string;
29
29
  }>;
30
+ export declare const sendAllProductsToMerchantPlatform: import("@graphql-typed-document-node/core").TypedDocumentNode<{
31
+ sendAllProductsToMerchantPlatform?: boolean | undefined;
32
+ }, {} & {
33
+ platform: string;
34
+ }>;
@@ -15,3 +15,9 @@ export const saveMerchantPlatformSettings = mutation({
15
15
  export const removeOrphanItems = mutation({
16
16
  removeOrphanItems: [{ platform: $("platform", "String!") }, true],
17
17
  });
18
+ export const sendAllProductsToMerchantPlatform = mutation({
19
+ sendAllProductsToMerchantPlatform: [
20
+ { platform: $("platform", "String!") },
21
+ true,
22
+ ],
23
+ });
@@ -1,4 +1,5 @@
1
- export declare const getMerchantPlatformSettings: import("@graphql-typed-document-node/core").TypedDocumentNode<{
1
+ import type { TypedDocumentNode } from "@graphql-typed-document-node/core";
2
+ export declare const getMerchantPlatformSettings: TypedDocumentNode<{
2
3
  getMerchantPlatformSettings?: {
3
4
  entries?: {
4
5
  value: string;
@@ -9,7 +10,7 @@ export declare const getMerchantPlatformSettings: import("@graphql-typed-documen
9
10
  }, {} & {
10
11
  platform: string;
11
12
  }>;
12
- export declare const getMerchantPlatformInfo: import("@graphql-typed-document-node/core").TypedDocumentNode<{
13
+ export declare const getMerchantPlatformInfo: TypedDocumentNode<{
13
14
  getMerchantPlatformInfo?: {
14
15
  isValidConnection: boolean;
15
16
  productsCount: number;
@@ -17,3 +18,53 @@ export declare const getMerchantPlatformInfo: import("@graphql-typed-document-no
17
18
  }, {} & {
18
19
  platform: string;
19
20
  }>;
21
+ export type GoogleMerchantDiagnostic = {
22
+ checkedAt?: string;
23
+ connectionStatus?: string;
24
+ dataSourceVerified?: boolean;
25
+ disapprovedProductsCount?: number;
26
+ isValidConnection: boolean;
27
+ issues?: Array<{
28
+ code: string;
29
+ description: string;
30
+ offerId: string;
31
+ severity: string;
32
+ }>;
33
+ issuesCount?: number;
34
+ lastError?: {
35
+ code: string;
36
+ message: string;
37
+ retryable: boolean;
38
+ };
39
+ latencyMs?: number;
40
+ productsCount: number;
41
+ };
42
+ export type MerchantSyncRunSummary = {
43
+ createdAt: string;
44
+ errorSummary?: string;
45
+ failed: number;
46
+ finishedAt?: string;
47
+ id: string;
48
+ items: Array<{
49
+ attempts: number;
50
+ errorCode?: string;
51
+ errorMessage?: string;
52
+ offerId: string;
53
+ operation: string;
54
+ status: string;
55
+ }>;
56
+ status: string;
57
+ succeeded: number;
58
+ total: number;
59
+ trigger: string;
60
+ };
61
+ export declare const getGoogleMerchantDiagnostic: TypedDocumentNode<{
62
+ getMerchantPlatformInfo?: GoogleMerchantDiagnostic[] | undefined;
63
+ }, {
64
+ [key: string]: any;
65
+ }>;
66
+ export declare const getGoogleMerchantSyncHistory: TypedDocumentNode<{
67
+ getMerchantSyncHistory: MerchantSyncRunSummary[];
68
+ }, {
69
+ take?: number | undefined;
70
+ }>;
@@ -2,6 +2,7 @@ import { typedGql } from "../zeus/typedDocumentNode";
2
2
  import { scalars } from "./scalars";
3
3
  import { merchantPlatformSettingsSelector } from "./mutations";
4
4
  import { $ } from "../zeus/index";
5
+ import { gql } from "graphql-tag";
5
6
  const query = typedGql("query", { scalars });
6
7
  export const getMerchantPlatformSettings = query({
7
8
  getMerchantPlatformSettings: [
@@ -15,3 +16,51 @@ export const getMerchantPlatformInfo = query({
15
16
  { isValidConnection: true, productsCount: true },
16
17
  ],
17
18
  });
19
+ export const getGoogleMerchantDiagnostic = gql `
20
+ query GetGoogleMerchantDiagnostic {
21
+ getMerchantPlatformInfo(platform: "google") {
22
+ isValidConnection
23
+ productsCount
24
+ connectionStatus
25
+ dataSourceVerified
26
+ checkedAt
27
+ latencyMs
28
+ disapprovedProductsCount
29
+ issuesCount
30
+ lastError {
31
+ code
32
+ message
33
+ retryable
34
+ }
35
+ issues {
36
+ offerId
37
+ code
38
+ description
39
+ severity
40
+ }
41
+ }
42
+ }
43
+ `;
44
+ export const getGoogleMerchantSyncHistory = gql `
45
+ query GetGoogleMerchantSyncHistory($take: Int) {
46
+ getMerchantSyncHistory(platform: "google", take: $take) {
47
+ id
48
+ createdAt
49
+ trigger
50
+ status
51
+ total
52
+ succeeded
53
+ failed
54
+ errorSummary
55
+ finishedAt
56
+ items {
57
+ offerId
58
+ operation
59
+ status
60
+ errorCode
61
+ errorMessage
62
+ attempts
63
+ }
64
+ }
65
+ }
66
+ `;
@@ -1,14 +1,18 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { Label, Input, PageBlock, useLazyQuery, useMutation, Checkbox, Button, } from "@deenruv/react-ui-devkit";
4
- import { getMerchantPlatformInfo, getMerchantPlatformSettings, } from "../graphql/queries";
5
- import { removeOrphanItems, saveMerchantPlatformSettings, } from "../graphql/mutations";
4
+ import { getGoogleMerchantDiagnostic, getGoogleMerchantSyncHistory, getMerchantPlatformSettings, } from "../graphql/queries";
5
+ import { removeOrphanItems, saveMerchantPlatformSettings, sendAllProductsToMerchantPlatform, } from "../graphql/mutations";
6
6
  import { toast } from "sonner";
7
7
  export const GooglePage = () => {
8
8
  const [fetchMerchantPlatformSettings] = useLazyQuery(getMerchantPlatformSettings);
9
- const [fetchMerchantPlatformInfo] = useLazyQuery(getMerchantPlatformInfo);
9
+ const [fetchMerchantPlatformInfo] = useLazyQuery(getGoogleMerchantDiagnostic);
10
+ const [fetchSyncHistory] = useLazyQuery(getGoogleMerchantSyncHistory);
10
11
  const [mutate] = useMutation(saveMerchantPlatformSettings);
11
12
  const [removeOldItems] = useMutation(removeOrphanItems);
13
+ const [sendAllProducts] = useMutation(sendAllProductsToMerchantPlatform);
14
+ const [diagnostic, setDiagnostic] = useState(null);
15
+ const [syncHistory, setSyncHistory] = useState([]);
12
16
  const [serviceInfo, setServiceInfo] = useState({
13
17
  productsCount: 0,
14
18
  connectionStatus: false,
@@ -42,6 +46,8 @@ export const GooglePage = () => {
42
46
  brand: "",
43
47
  merchantId: "",
44
48
  dataSource: "",
49
+ contentLanguage: "pl",
50
+ feedLabel: "PL",
45
51
  credentials: "",
46
52
  autoUpdate: true,
47
53
  firstSync: true,
@@ -49,9 +55,10 @@ export const GooglePage = () => {
49
55
  const refetch = async () => {
50
56
  try {
51
57
  setIsLoading(true);
52
- const [settingsData, infoData] = await Promise.all([
58
+ const [settingsData, infoData, historyData] = await Promise.all([
53
59
  fetchMerchantPlatformSettings({ platform: "google" }),
54
- fetchMerchantPlatformInfo({ platform: "google" }),
60
+ fetchMerchantPlatformInfo(),
61
+ fetchSyncHistory({ take: 10 }),
55
62
  ]);
56
63
  const settings = settingsData?.getMerchantPlatformSettings?.entries?.reduce((acc, { key, value }) => {
57
64
  if (key === "brand") {
@@ -63,6 +70,12 @@ export const GooglePage = () => {
63
70
  if (key === "dataSource") {
64
71
  acc.dataSource = value;
65
72
  }
73
+ if (key === "contentLanguage") {
74
+ acc.contentLanguage = value;
75
+ }
76
+ if (key === "feedLabel") {
77
+ acc.feedLabel = value;
78
+ }
66
79
  if (key === "credentials") {
67
80
  acc.credentials = value;
68
81
  }
@@ -75,9 +88,12 @@ export const GooglePage = () => {
75
88
  return acc;
76
89
  }, {});
77
90
  setSettingsForm((prev) => ({ ...prev, ...settings }));
91
+ const currentDiagnostic = infoData?.getMerchantPlatformInfo?.[0];
92
+ setDiagnostic(currentDiagnostic ?? null);
93
+ setSyncHistory(historyData?.getMerchantSyncHistory ?? []);
78
94
  setServiceInfo({
79
- productsCount: infoData?.getMerchantPlatformInfo?.[0]?.productsCount || 0,
80
- connectionStatus: infoData?.getMerchantPlatformInfo?.[0]?.isValidConnection || false,
95
+ productsCount: currentDiagnostic?.productsCount || 0,
96
+ connectionStatus: currentDiagnostic?.isValidConnection || false,
81
97
  });
82
98
  setIsLoading(false);
83
99
  }
@@ -111,7 +127,7 @@ export const GooglePage = () => {
111
127
  }
112
128
  catch (error) {
113
129
  console.error(error);
114
- toast.error("Failed to save settings");
130
+ toast.error(`Failed to save settings: ${error instanceof Error ? error.message : "Unknown error"}`);
115
131
  }
116
132
  };
117
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: {
@@ -138,7 +154,13 @@ export const GooglePage = () => {
138
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({
139
155
  ...settingsForm,
140
156
  dataSource: e.target.value,
141
- }) })] }), _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: {
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({
158
+ ...settingsForm,
159
+ 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({
161
+ ...settingsForm,
162
+ 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: {
142
164
  border: "none",
143
165
  backgroundColor: "transparent",
144
166
  background: "none",
@@ -148,15 +170,31 @@ export const GooglePage = () => {
148
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({
149
171
  ...settingsForm,
150
172
  firstSync: typeof checked === "boolean" ? checked : false,
151
- }) })] }), _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 () => {
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 () => {
174
+ await refetch();
175
+ toast.success("Connection diagnostic completed");
176
+ }, children: "Test connection" }), _jsx(Button, { type: "button", onClick: async () => {
177
+ try {
178
+ await sendAllProducts({ platform: "google" });
179
+ toast.success("Full synchronization queued");
180
+ await refetch();
181
+ }
182
+ catch (error) {
183
+ console.error(error);
184
+ toast.error("Failed to queue full synchronization");
185
+ }
186
+ }, children: "Synchronize all" })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
152
187
  try {
153
188
  await removeOldItems({ platform: "google" });
154
- toast.success("Old items removed successfully");
189
+ toast.success("Orphan cleanup queued");
155
190
  refetch();
156
191
  }
157
192
  catch (error) {
158
193
  console.error(error);
159
- toast.error("Failed to remove old items");
194
+ toast.error("Failed to queue orphan cleanup");
160
195
  }
161
- }, children: "Remove old items" }) })) : null] }) }));
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
197
+ .filter((item) => item.status === "FAILED")
198
+ .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))))] })] }) }));
162
200
  };