@griddo/ax 1.66.1 → 1.66.4

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 (73) hide show
  1. package/package.json +2 -2
  2. package/src/api/pages.tsx +15 -3
  3. package/src/api/redirects.tsx +4 -2
  4. package/src/api/sites.tsx +4 -2
  5. package/src/components/Browser/index.tsx +3 -1
  6. package/src/components/Browser/style.tsx +2 -2
  7. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +0 -1
  8. package/src/components/ErrorCenter/index.tsx +8 -5
  9. package/src/components/ErrorCenter/style.tsx +21 -8
  10. package/src/components/Fields/ColorPicker/index.tsx +1 -0
  11. package/src/components/Fields/LinkField/index.tsx +85 -0
  12. package/src/components/Fields/ReferenceField/ItemList/index.tsx +5 -1
  13. package/src/components/Fields/ReferenceField/index.tsx +18 -14
  14. package/src/components/Fields/UrlField/index.tsx +13 -1
  15. package/src/components/Fields/index.tsx +2 -0
  16. package/src/components/FieldsBehavior/index.tsx +14 -1
  17. package/src/components/Icon/components/Copy.js +14 -0
  18. package/src/components/Icon/svgs/Copy2.svg +3 -0
  19. package/src/components/Image/index.tsx +17 -9
  20. package/src/components/Image/utils.ts +55 -0
  21. package/src/components/MainWrapper/AppBar/index.tsx +21 -10
  22. package/src/components/MainWrapper/AppBar/style.tsx +11 -3
  23. package/src/components/MainWrapper/index.tsx +2 -0
  24. package/src/components/Modal/style.tsx +0 -1
  25. package/src/components/SearchField/index.tsx +36 -4
  26. package/src/components/SearchField/style.tsx +23 -10
  27. package/src/components/SideModal/style.tsx +6 -6
  28. package/src/components/TableFilters/StatusFilter/index.tsx +2 -2
  29. package/src/components/index.tsx +2 -0
  30. package/src/containers/App/actions.tsx +3 -7
  31. package/src/containers/PageEditor/actions.tsx +91 -22
  32. package/src/containers/PageEditor/constants.tsx +1 -1
  33. package/src/containers/PageEditor/interfaces.tsx +6 -6
  34. package/src/containers/PageEditor/reducer.tsx +4 -4
  35. package/src/containers/PageEditor/utils.tsx +2 -1
  36. package/src/containers/Sites/actions.tsx +35 -23
  37. package/src/containers/Sites/constants.tsx +1 -0
  38. package/src/containers/Sites/interfaces.tsx +6 -0
  39. package/src/containers/Sites/reducer.tsx +4 -0
  40. package/src/forms/editor.tsx +34 -1
  41. package/src/forms/errors.tsx +1 -0
  42. package/src/forms/index.tsx +15 -1
  43. package/src/forms/validators.tsx +168 -9
  44. package/src/guards/error/index.tsx +1 -1
  45. package/src/helpers/dataPacks.tsx +8 -1
  46. package/src/helpers/index.tsx +2 -1
  47. package/src/modules/Content/PageItem/index.tsx +54 -4
  48. package/src/modules/Content/atoms.tsx +41 -3
  49. package/src/modules/Content/index.tsx +111 -64
  50. package/src/modules/Content/style.tsx +8 -1
  51. package/src/modules/GlobalEditor/Editor/index.tsx +3 -1
  52. package/src/modules/GlobalEditor/PageBrowser/index.tsx +3 -0
  53. package/src/modules/GlobalEditor/index.tsx +8 -6
  54. package/src/modules/Navigation/Menus/List/Table/SidePanel/Form/index.tsx +8 -0
  55. package/src/modules/PageEditor/Editor/index.tsx +6 -2
  56. package/src/modules/PageEditor/PageBrowser/index.tsx +3 -0
  57. package/src/modules/PageEditor/index.tsx +29 -15
  58. package/src/modules/Redirects/index.tsx +40 -10
  59. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/index.tsx +1 -1
  60. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/index.tsx +1 -1
  61. package/src/modules/Settings/ContentTypes/DataPacks/Config/index.tsx +1 -1
  62. package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +1 -1
  63. package/src/modules/Sites/index.tsx +3 -3
  64. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +1 -1
  65. package/src/modules/StructuredData/StructuredDataList/atoms.tsx +1 -1
  66. package/src/modules/Users/Profile/index.tsx +3 -4
  67. package/src/modules/Users/UserCreate/SiteItem/index.tsx +1 -1
  68. package/src/modules/Users/UserCreate/SiteItem/style.tsx +1 -1
  69. package/src/modules/Users/UserForm/style.tsx +3 -3
  70. package/src/modules/Users/UserList/UserItem/index.tsx +3 -1
  71. package/src/modules/Users/UserList/hooks.tsx +1 -1
  72. package/src/modules/Users/UserList/index.tsx +2 -2
  73. package/src/types/index.tsx +16 -3
@@ -1,6 +1,7 @@
1
1
  import { Dispatch } from "redux";
2
2
  import {
3
3
  SET_SITES,
4
+ SET_SITES_BY_LANG,
4
5
  SET_CURRENT_SITE_INFO,
5
6
  SET_CURRENT_SITE_PAGES,
6
7
  SET_FILTER,
@@ -11,6 +12,7 @@ import {
11
12
  } from "./constants";
12
13
  import {
13
14
  ISetSitesAction,
15
+ ISetSitesByLangAction,
14
16
  ISetCurrentSiteInfoAction,
15
17
  ISetCurrentSitePagesAction,
16
18
  ISetFilter,
@@ -42,6 +44,10 @@ function setSites(sitesList: ISite[]): ISetSitesAction {
42
44
  return { type: SET_SITES, payload: { sites: sitesList } };
43
45
  }
44
46
 
47
+ function setSitesByLang(sitesList: ISite[]): ISetSitesByLangAction {
48
+ return { type: SET_SITES_BY_LANG, payload: { sitesByLang: sitesList } };
49
+ }
50
+
45
51
  function setCurrentSiteInfo(currentSiteInfo: ISite): ISetCurrentSiteInfoAction {
46
52
  return { type: SET_CURRENT_SITE_INFO, payload: { currentSiteInfo } };
47
53
  }
@@ -67,20 +73,34 @@ function setSavedSiteInfo(savedSiteInfo: ISite): ISetSavedSiteInfoAction {
67
73
  }
68
74
 
69
75
  // TODO: hay que controlar que cuando da error la API borrar los sites ya guardados y sacar el error (ver los siguientes FIXME)
70
- function getSites(token: string): (dispatch: Dispatch) => Promise<void> {
76
+ function getSites(): (dispatch: Dispatch) => Promise<void> {
71
77
  return async (dispatch) => {
72
78
  try {
73
- dispatch(setIsLoading(true));
74
- const sitesResponse: { status: number; data: ISite[] } = await sites.getAllSites(token); // FIXME: establecer type
75
- if (isReqOk(sitesResponse.status)) {
76
- dispatch(setSites(sitesResponse.data));
77
- } else {
78
- console.log("Error en getSites"); // TODO: capturar errores mejor
79
- }
80
- dispatch(setIsLoading(false));
79
+ const responseActions = {
80
+ handleSuccess: (data: any) => dispatch(setSites(data)),
81
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
82
+ };
83
+ const callback = async () => sites.getAllSites();
84
+
85
+ await handleRequest(callback, responseActions, [])(dispatch);
81
86
  } catch (e) {
82
- dispatch(setIsLoading(false));
83
- console.log(e); // TODO: capturar errores mejor
87
+ console.log(e);
88
+ }
89
+ };
90
+ }
91
+
92
+ function getSitesByLang(language: number): (dispatch: Dispatch) => Promise<void> {
93
+ return async (dispatch) => {
94
+ try {
95
+ const responseActions = {
96
+ handleSuccess: (data: any) => dispatch(setSitesByLang(data)),
97
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
98
+ };
99
+ const callback = async () => sites.getAllSites(language);
100
+
101
+ await handleRequest(callback, responseActions, [])(dispatch);
102
+ } catch (e) {
103
+ console.log(e);
84
104
  }
85
105
  };
86
106
  }
@@ -306,10 +326,7 @@ function deleteSite(siteID: number): (dispatch: Dispatch, getState: any) => Prom
306
326
  try {
307
327
  const response = await sites.deleteSite(siteID);
308
328
  if (isReqOk(response.status)) {
309
- const {
310
- app: { token },
311
- } = getState();
312
- getSites(token)(dispatch);
329
+ getSites()(dispatch);
313
330
  }
314
331
  } catch (e) {
315
332
  console.log(e); // TODO: capturar error bien
@@ -322,10 +339,7 @@ function publishSite(siteID: number): (dispatch: Dispatch, getState: any) => Pro
322
339
  try {
323
340
  const response = await sites.publishSite(siteID);
324
341
  if (isReqOk(response.status)) {
325
- const {
326
- app: { token },
327
- } = getState();
328
- getSites(token)(dispatch);
342
+ getSites()(dispatch);
329
343
  }
330
344
  } catch (e) {
331
345
  console.log(e); // TODO: capturar error bien
@@ -338,10 +352,7 @@ function unpublishSite(siteID: number): (dispatch: Dispatch, getState: any) => P
338
352
  try {
339
353
  const response = await sites.unpublishSite(siteID);
340
354
  if (isReqOk(response.status)) {
341
- const {
342
- app: { token },
343
- } = getState();
344
- getSites(token)(dispatch);
355
+ getSites()(dispatch);
345
356
  }
346
357
  } catch (e) {
347
358
  console.log(e); // TODO: capturar error bien
@@ -464,6 +475,7 @@ export {
464
475
  setTotalItems,
465
476
  setCurrentSiteLanguages,
466
477
  getSites,
478
+ getSitesByLang,
467
479
  setSiteInfo,
468
480
  getFilteredContent,
469
481
  getSitePages,
@@ -1,6 +1,7 @@
1
1
  export const NAME = "sites";
2
2
 
3
3
  export const SET_SITES = `${NAME}/SET_SITES`;
4
+ export const SET_SITES_BY_LANG = `${NAME}/SET_SITES_BY_LANG`;
4
5
  export const SET_CURRENT_SITE_INFO = `${NAME}/SET_CURRENT_SITE_INFO`;
5
6
  export const SET_CURRENT_SITE_PAGES = `${NAME}/SET_CURRENT_SITE_PAGES`;
6
7
  export const SET_FILTER: string | null = `${NAME}/SET_FILTER`;
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  SET_SITES,
3
+ SET_SITES_BY_LANG,
3
4
  SET_CURRENT_SITE_INFO,
4
5
  SET_CURRENT_SITE_PAGES,
5
6
  SET_FILTER,
@@ -20,6 +21,11 @@ export interface ISetSitesAction {
20
21
  payload: { sites: ISite[] };
21
22
  }
22
23
 
24
+ export interface ISetSitesByLangAction {
25
+ type: typeof SET_SITES_BY_LANG;
26
+ payload: { sitesByLang: ISite[] };
27
+ }
28
+
23
29
  export interface ISetCurrentSiteInfoAction {
24
30
  type: typeof SET_CURRENT_SITE_INFO;
25
31
  payload: { currentSiteInfo: ISite };
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  SET_SITES,
3
+ SET_SITES_BY_LANG,
3
4
  SET_CURRENT_SITE_INFO,
4
5
  SET_CURRENT_SITE_PAGES,
5
6
  SET_FILTER,
@@ -17,6 +18,7 @@ export interface ISitesState {
17
18
  currentSiteName: string | null;
18
19
  currentSitePages: IPage[];
19
20
  sites: ISite[];
21
+ sitesByLang: ISite[];
20
22
  currentSiteInfo: any;
21
23
  currentFilter: string | null;
22
24
  totalItems: number;
@@ -28,6 +30,7 @@ export const initialState = {
28
30
  currentSiteName: null,
29
31
  currentSitePages: [],
30
32
  sites: [],
33
+ sitesByLang: [],
31
34
  currentSiteInfo: null,
32
35
  currentFilter: "unique-pages",
33
36
  totalItems: 0,
@@ -39,6 +42,7 @@ export function reducer(state = initialState, action: SitesActionsCreators): ISi
39
42
  switch (action.type) {
40
43
  case SET_FILTER:
41
44
  case SET_SITES:
45
+ case SET_SITES_BY_LANG:
42
46
  case SET_CURRENT_SITE_INFO:
43
47
  case SET_CURRENT_SITE_PAGES:
44
48
  case SET_TOTAL_ITEMS:
@@ -1,4 +1,4 @@
1
- import { deepClone } from "@ax/helpers";
1
+ import { deepClone, getSchema } from "@ax/helpers";
2
2
  import { IPage, IBreadcrumbItem } from "@ax/types";
3
3
 
4
4
  const configKeys = ["headerConfig", "footerConfig"];
@@ -164,6 +164,38 @@ const getParentKey = (parentModule: any, editorID: number) => {
164
164
  return keyFound;
165
165
  };
166
166
 
167
+ const checkMaxModules = (content: any, type: string): { isMaxModules: boolean; errorMessage?: string } => {
168
+ const { maxModulesPerPage } = getSchema(type);
169
+ const queue: any[] = [content];
170
+ let counter = 0;
171
+
172
+ while (queue.length > 0 && counter < maxModulesPerPage) {
173
+ const obj = queue.shift();
174
+ const currentObj = obj;
175
+
176
+ if (currentObj.component === type) {
177
+ counter++;
178
+ }
179
+
180
+ const keys = currentObj instanceof Object ? Object.keys(currentObj) : [];
181
+
182
+ for (const key of keys) {
183
+ const objVal = currentObj[key];
184
+ if (objVal instanceof Object) {
185
+ queue.push(objVal);
186
+ }
187
+ }
188
+ }
189
+
190
+ const isMaxModules = counter >= maxModulesPerPage;
191
+ const errorMessage = `There can be only ${maxModulesPerPage} ${type} on page. You already have it.`;
192
+
193
+ return {
194
+ isMaxModules,
195
+ ...(isMaxModules && { errorMessage }),
196
+ };
197
+ };
198
+
167
199
  export {
168
200
  parseData,
169
201
  cleanContent,
@@ -177,4 +209,5 @@ export {
177
209
  getLastModuleEditorID,
178
210
  getLastComponentEditorID,
179
211
  getParentKey,
212
+ checkMaxModules,
180
213
  };
@@ -40,6 +40,7 @@ const ERRORS: Record<string, string> = {
40
40
  ERR039: "Sorry, this color doesn't exist. Please add new one.",
41
41
  ERR040: "Sorry, the file is not in a valid format.",
42
42
  ERR041: "Sorry, the password doesn’t match.",
43
+ ERR042: "This content is part of disabled content type package. To publish it, you must first activate it."
43
44
  };
44
45
 
45
46
  export { ERRORS };
@@ -10,6 +10,7 @@ import {
10
10
  getLastModuleEditorID,
11
11
  getLastComponentEditorID,
12
12
  getParentKey,
13
+ checkMaxModules,
13
14
  } from "./editor";
14
15
  import {
15
16
  getUpdatedComponents,
@@ -23,7 +24,15 @@ import {
23
24
  replaceElements,
24
25
  } from "./elements";
25
26
  import { getInnerFields, getStructuredDataInnerFields } from "./fields";
26
- import { getValidity, findFieldsErrors, findMandatoryStructuredDataErrors } from "./validators";
27
+ import {
28
+ getValidity,
29
+ findPackagesActivationErrors,
30
+ findFieldsErrors,
31
+ isTemplateActivated,
32
+ findMandatoryStructuredDataErrors,
33
+ checkH1content,
34
+ parseValidationErrors,
35
+ } from "./validators";
27
36
 
28
37
  export {
29
38
  parseData,
@@ -49,6 +58,11 @@ export {
49
58
  getLastComponentEditorID,
50
59
  getParentKey,
51
60
  getValidity,
61
+ findPackagesActivationErrors,
52
62
  findFieldsErrors,
63
+ isTemplateActivated,
53
64
  findMandatoryStructuredDataErrors,
65
+ checkMaxModules,
66
+ checkH1content,
67
+ parseValidationErrors,
54
68
  };
@@ -1,5 +1,15 @@
1
- import { dateToString, getSchema, getTemplate, isComponentEmpty, isEmptyContainer } from "@ax/helpers";
2
- import { IErrorItem } from "@ax/types";
1
+ import {
2
+ dateToString,
3
+ getDeactivatedModules,
4
+ getDefaultSchema,
5
+ getSchema,
6
+ getTemplate,
7
+ isComponentEmpty,
8
+ isEmptyContainer,
9
+ isModuleDisabled,
10
+ } from "@ax/helpers";
11
+ import { findByEditorID } from "@ax/forms";
12
+ import { IErrorItem, ITemplate } from "@ax/types";
3
13
  import { ERRORS } from "./errors";
4
14
 
5
15
  const VALIDATORS = {
@@ -96,6 +106,13 @@ const VALIDATORS = {
96
106
  return { isValid: true, errorCode: "" };
97
107
  }
98
108
  },
109
+ isMockup: (val: any, field: { type: string; defaultValue: any }): IError => {
110
+ const isValid = !checkMockupByType(field.type, val, field.defaultValue);
111
+ return { isValid, errorCode: "ERR016" };
112
+ },
113
+ apiValidator: (val: any, code: string): IError => {
114
+ return { isValid: false, errorCode: code };
115
+ },
99
116
  isSamePass: (pass1: string, pass2: string): IError => {
100
117
  const isValid = pass1 === pass2;
101
118
  return { isValid, errorCode: "ERR041" };
@@ -161,6 +178,23 @@ const isEmptyField = (value: any, fieldType: string, multiple: boolean) => {
161
178
  }
162
179
  };
163
180
 
181
+ const checkMockupByType = (type: string, value: any, defaultValue: any) => {
182
+ if (!value || !defaultValue) return false;
183
+ switch (type) {
184
+ case "HeadingField":
185
+ return value.content && defaultValue.content && value.content.trim() === defaultValue.content.trim();
186
+ case "ImageField":
187
+ return value.publicId === defaultValue.publicId;
188
+ default:
189
+ return value.trim() === defaultValue.trim();
190
+ }
191
+ };
192
+
193
+ const checkMockupContent = (component: string, key: string, type: string, value: any) => {
194
+ const moduleDefault = getDefaultSchema(component);
195
+ return { isMockup: checkMockupByType(type, value, moduleDefault[key]), defaultValue: moduleDefault[key] };
196
+ };
197
+
164
198
  const getValidationErrors = (
165
199
  fields: Record<string, unknown>[],
166
200
  current: any,
@@ -176,8 +210,8 @@ const getValidationErrors = (
176
210
  const isEmpty = isEmptyField(current[field.key], field.type, hasMultipleOptions);
177
211
  if (isEmpty) {
178
212
  errors.push({
179
- type: "Error",
180
- message: "Empty Field",
213
+ type: "error",
214
+ message: getErrorMessage("ERR015", null),
181
215
  validator: { mandatory: true },
182
216
  editorID: current.editorID ? current.editorID : null,
183
217
  component: current.component ? current.component : null,
@@ -189,14 +223,42 @@ const getValidationErrors = (
189
223
  }
190
224
  }
191
225
 
192
- if (Object.prototype.hasOwnProperty.call(field, "validators")) {
193
- const { isValid, errorText } = getValidity(field.validators, current[field.key]);
226
+ if (current.component && field.isMockup) {
227
+ const { isMockup, defaultValue } = checkMockupContent(
228
+ current.component,
229
+ field.key,
230
+ field.type,
231
+ current[field.key]
232
+ );
233
+
234
+ if (isMockup) {
235
+ errors.push({
236
+ type: "error",
237
+ message: getErrorMessage("ERR016", null),
238
+ validator: { isMockup: { type: field.type, defaultValue } },
239
+ editorID: current.editorID ? current.editorID : null,
240
+ component: current.component ? current.component : null,
241
+ name: name ? name : field.title,
242
+ key: field.key,
243
+ tab,
244
+ template,
245
+ });
246
+ }
247
+ }
248
+
249
+ let fieldValidators: Record<string, unknown> = field.maxValue ? { maxValue: field.maxValue } : {};
250
+ fieldValidators = field.minValue ? { ...fieldValidators, minValue: field.minValue } : fieldValidators;
251
+
252
+ if (Object.prototype.hasOwnProperty.call(field, "validators") || Object.keys(fieldValidators).length) {
253
+ const allValidators = { ...field.validators, ...fieldValidators };
254
+
255
+ const { isValid, errorText } = getValidity(allValidators, current[field.key]);
194
256
 
195
257
  if (!isValid) {
196
258
  errors.push({
197
- type: "Error",
259
+ type: "error",
198
260
  message: errorText,
199
- validator: field.validators,
261
+ validator: allValidators,
200
262
  editorID: current.editorID ? current.editorID : null,
201
263
  component: current.component ? current.component : null,
202
264
  name: name ? name : field.title,
@@ -221,6 +283,57 @@ const getValidationErrors = (
221
283
  return errors;
222
284
  };
223
285
 
286
+ const isTemplateActivated = (templates: ITemplate[], currentTemplateType: string): boolean =>
287
+ templates.find((temp: ITemplate) => temp.id === currentTemplateType) ? true : false;
288
+
289
+ const findPackagesActivationErrors = (
290
+ pageEditor: any,
291
+ modules: string[],
292
+ templates: ITemplate[]
293
+ ): IErrorItem | null => {
294
+ const {
295
+ schema,
296
+ selectedContent: { component },
297
+ } = pageEditor;
298
+
299
+ let deactivatedModules: string[] = [];
300
+ let isCurrentTemplateActivated = true;
301
+ let hasDeactivatedModules = false;
302
+
303
+ const {
304
+ editorContent: { template },
305
+ } = pageEditor?.editorContent;
306
+
307
+ if (template) {
308
+ const mainContentModules = template?.mainContent?.modules;
309
+
310
+ if (mainContentModules) {
311
+ deactivatedModules = getDeactivatedModules(modules, mainContentModules);
312
+ hasDeactivatedModules = deactivatedModules.length > 0;
313
+ } else {
314
+ hasDeactivatedModules = isModuleDisabled(component, schema.schemaType, modules);
315
+ }
316
+
317
+ isCurrentTemplateActivated = isTemplateActivated(templates, template.templateType);
318
+ }
319
+
320
+ if (!isCurrentTemplateActivated || hasDeactivatedModules) {
321
+ return {
322
+ type: "error",
323
+ message: getErrorMessage("ERR042", null),
324
+ validator: {},
325
+ editorID: null,
326
+ component: "",
327
+ name: "",
328
+ key: "",
329
+ tab: "",
330
+ template: false,
331
+ };
332
+ }
333
+
334
+ return null;
335
+ };
336
+
224
337
  const findFieldsErrors = (content: any): IErrorItem[] => {
225
338
  const queue: any[] = [content];
226
339
  let errors: IErrorItem[] = [];
@@ -274,9 +387,55 @@ const findMandatoryStructuredDataErrors = (content: any, schema: any): IErrorIte
274
387
  return errors;
275
388
  };
276
389
 
390
+ const checkH1content = (content: any): IErrorItem | null => {
391
+ const h1s = content.getElementsByTagName("h1");
392
+
393
+ if (!h1s.length) {
394
+ return {
395
+ type: "warning",
396
+ message: getErrorMessage("ERR018", null),
397
+ validator: {},
398
+ editorID: null,
399
+ component: null,
400
+ name: "",
401
+ key: "",
402
+ tab: "",
403
+ template: false,
404
+ };
405
+ }
406
+
407
+ return null;
408
+ };
409
+
410
+ const parseValidationErrors = (errors: any[], content: any) => {
411
+ return errors.map((err: any) => {
412
+ const { element: module } = findByEditorID(content, err.editorID);
413
+ const schema = getSchema(module.component);
414
+ return {
415
+ type: "error",
416
+ message: getErrorMessage(err.error, null),
417
+ validator: { apiValidator: err.error },
418
+ editorID: err.editorID,
419
+ component: module.component,
420
+ name: schema.displayName,
421
+ key: err.key,
422
+ tab: "content",
423
+ template: false,
424
+ };
425
+ });
426
+ };
427
+
277
428
  interface IError {
278
429
  isValid: boolean;
279
430
  errorCode: string;
280
431
  }
281
432
 
282
- export { getValidity, findFieldsErrors, findMandatoryStructuredDataErrors };
433
+ export {
434
+ getValidity,
435
+ isTemplateActivated,
436
+ findPackagesActivationErrors,
437
+ findFieldsErrors,
438
+ findMandatoryStructuredDataErrors,
439
+ checkH1content,
440
+ parseValidationErrors,
441
+ };
@@ -50,7 +50,7 @@ const ErrorGuard = (props: IProps) => {
50
50
  return domNode && createPortal(Notifications, domNode);
51
51
  };
52
52
 
53
- return code ? isBlocking ? <ErrorView code={code} text={text} /> : createErrorNotification() : null;
53
+ return code || text ? isBlocking ? <ErrorView code={code} text={text} /> : createErrorNotification() : null;
54
54
  };
55
55
 
56
56
  const mapStateToProps = (state: IRootState) => {
@@ -8,4 +8,11 @@ const getActivatedDataPacksIds = (activatedDataPacks: any) => {
8
8
  const isModuleDisabled = (selectedComponent: string, type: string, activatedModules: string[]): boolean =>
9
9
  type === "module" && !activatedModules.includes(selectedComponent);
10
10
 
11
- export { getActivatedDataPacksIds, isModuleDisabled };
11
+ const getDeactivatedModules = (modules: any, currentModules: any) => {
12
+ const deactivatedModules = currentModules
13
+ .map((module: any) => (isModuleDisabled(module.component, "module", modules) ? module.component : null))
14
+ .filter((module: string | null) => !!module);
15
+ return deactivatedModules;
16
+ };
17
+
18
+ export { getActivatedDataPacksIds, isModuleDisabled, getDeactivatedModules };
@@ -91,7 +91,7 @@ import { imageResizeCropAndCompress, compressImage } from "./imageResize";
91
91
 
92
92
  import { isEmptyArray, moveArrayElement } from "./arrays";
93
93
 
94
- import { getActivatedDataPacksIds, isModuleDisabled } from "./dataPacks";
94
+ import { getActivatedDataPacksIds, isModuleDisabled, getDeactivatedModules } from "./dataPacks";
95
95
 
96
96
  import { isDevelopment } from "./environment";
97
97
 
@@ -153,6 +153,7 @@ export {
153
153
  handleRequest,
154
154
  getActivatedDataPacksIds,
155
155
  isModuleDisabled,
156
+ getDeactivatedModules,
156
157
  getInitials,
157
158
  getSchemaType,
158
159
  getModuleCategories,
@@ -1,8 +1,9 @@
1
1
  import React, { memo, useState } from "react";
2
2
 
3
+ import { schemas } from "components";
3
4
  import { useModal } from "@ax/hooks";
4
5
  import { getHumanLastModifiedDate, getTemplateDisplayName, slugify } from "@ax/helpers";
5
- import { IPage, ISite, ISavePageParams, ICheck, IColumn, IPageLanguage, IDataPack } from "@ax/types";
6
+ import { IPage, ISite, ISavePageParams, ICheck, IColumn, IDataPack, IPageLanguage } from "@ax/types";
6
7
  import { pageStatus, ISetCurrentPageIDAction } from "@ax/containers/PageEditor/interfaces";
7
8
 
8
9
  import {
@@ -18,7 +19,7 @@ import {
18
19
  CategoryCell,
19
20
  } from "@ax/components";
20
21
 
21
- import { DeleteModal } from "../atoms";
22
+ import { DeleteModal, CopyModal } from "../atoms";
22
23
 
23
24
  import * as S from "./style";
24
25
 
@@ -34,6 +35,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
34
35
  categoryColors,
35
36
  addCategoryColors,
36
37
  dataPacks,
38
+ sites,
37
39
  } = props;
38
40
  const { isSelected, siteLanguages, page, lang, isDuplicable } = item;
39
41
  const {
@@ -47,9 +49,10 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
47
49
  languageActions,
48
50
  duplicatePage,
49
51
  removePageFromSite,
50
- deleteBulk,
51
52
  getDataPack,
53
+ deleteBulk,
52
54
  setTemplateInstanceError,
55
+ toggleCopiedToast,
53
56
  } = functions;
54
57
  const { locale } = lang;
55
58
  const {
@@ -62,16 +65,22 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
62
65
  templateId,
63
66
  structuredDataContent,
64
67
  } = page;
68
+
65
69
  const displayName = getTemplateDisplayName(templateId);
66
70
 
67
71
  const initValue = { title: "", slug: "" };
72
+ const [site, setSite] = useState(null);
68
73
  const [modalState, setModalState] = useState(initValue);
69
74
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
70
75
  const { isOpen, toggleModal } = useModal();
71
76
  const { isOpen: isRemoveOpen, toggleModal: toggleRemoveModal } = useModal();
72
77
  const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
73
78
  const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
79
+ const { isOpen: isCopyOpen, toggleModal: toggleCopyModal } = useModal();
80
+
81
+ const currentTemplateDataPacks = schemas.templates[templateId].dataPacks;
74
82
 
83
+ const isCopyable = !currentTemplateDataPacks;
75
84
  const isGlobal = origin === "GLOBAL";
76
85
  const isTranslated = pageLanguages.length > 1;
77
86
  const activeColumns = Object.keys(columns).filter((col: string) => columns[col].show);
@@ -311,6 +320,13 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
311
320
  action: toggleModal,
312
321
  };
313
322
 
323
+ const copyOption = {
324
+ label: "Copy page in another site",
325
+ icon: "copy",
326
+ action: toggleCopyModal,
327
+ };
328
+
329
+ if (isCopyable) menuOptions.unshift(copyOption);
314
330
  if (!isGlobal) menuOptions.unshift(duplicateOption);
315
331
 
316
332
  const getPublishItem = (status: string, canBeUnpublished: boolean) => {
@@ -403,6 +419,25 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
403
419
  };
404
420
 
405
421
  const secondaryDeleteModalAction = { title: "Cancel", onClick: toggleDeleteModal };
422
+ const copyToOtherSite = () => {
423
+ if (site) {
424
+ const siteID = parseInt(site);
425
+
426
+ duplicatePage(page.id, null, siteID).then((successEvent: boolean) => {
427
+ if (successEvent) {
428
+ toggleCopiedToast();
429
+ }
430
+ toggleCopyModal();
431
+ });
432
+ }
433
+ };
434
+
435
+ const secondaryCopyModalAction = {
436
+ title: "Cancel",
437
+ onClick: toggleCopyModal,
438
+ };
439
+
440
+ const mainCopyModalAction = { title: "Copy page", onClick: copyToOtherSite, disabled: !site };
406
441
 
407
442
  const CategoryColumns =
408
443
  isGlobal &&
@@ -471,6 +506,18 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
471
506
  <S.StyledActionMenu icon="more" options={menuOptions} tooltip="Page actions" />
472
507
  </S.ActionsCell>
473
508
  </S.PageRow>
509
+ <CopyModal
510
+ isOpen={isCopyOpen}
511
+ toggleModal={() => {
512
+ setSite(null);
513
+ toggleCopyModal();
514
+ }}
515
+ mainModalAction={mainCopyModalAction}
516
+ secondaryModalAction={secondaryCopyModalAction}
517
+ sites={sites}
518
+ site={site}
519
+ setSite={setSite}
520
+ />
474
521
  <Modal
475
522
  isOpen={isOpen}
476
523
  hide={toggleModal}
@@ -534,6 +581,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
534
581
  </S.ModalContent>
535
582
  )}
536
583
  </Modal>
584
+
537
585
  <DeleteModal
538
586
  isOpen={isDeleteOpen}
539
587
  toggleModal={toggleDeleteModal}
@@ -576,11 +624,12 @@ interface IPageItemProps {
576
624
  setHistoryPush(path: string, isEditor: boolean): void;
577
625
  updatePageStatus(ids: number[], status: string, updatedFromList: boolean): Promise<boolean>;
578
626
  setCurrentPageID(currentPageID: number | null): ISetCurrentPageIDAction;
579
- duplicatePage(pageID: number, data: any): Promise<void>;
627
+ duplicatePage(pageID: number, data: any, siteID?: number): Promise<boolean>;
580
628
  removePageFromSite(pageID: number): Promise<boolean>;
581
629
  deleteBulk(ids: number[]): void;
582
630
  setTemplateInstanceError(error: any): void;
583
631
  getDataPack: (id: string) => Promise<void>;
632
+ toggleCopiedToast(): void;
584
633
  };
585
634
  activatedTemplates: any[];
586
635
  toggleToast(): void;
@@ -590,6 +639,7 @@ interface IPageItemProps {
590
639
  categoryColors: any;
591
640
  addCategoryColors(cats: string[]): void;
592
641
  dataPacks: IDataPack[];
642
+ sites: ISite[];
593
643
  }
594
644
 
595
645
  export default memo(PageItem);