@griddo/ax 10.1.25 → 10.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "10.1.25",
4
+ "version": "10.1.27",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -230,5 +230,5 @@
230
230
  "publishConfig": {
231
231
  "access": "public"
232
232
  },
233
- "gitHead": "ea0dde6462cff4213cdf70336e2b103018ff5b4f"
233
+ "gitHead": "e536109348c8ffe2b88f14305986fcfe137ddaf0"
234
234
  }
@@ -57,6 +57,21 @@ const SERVICES: { [key: string]: IServiceConfig } = {
57
57
  endpoint: ["/navigations/", "/languages"],
58
58
  method: "GET",
59
59
  },
60
+ GET_PAGES_BY_HEADER: {
61
+ ...template,
62
+ endpoint: ["/navigations/", "/header/site/", "/pages"],
63
+ method: "GET",
64
+ },
65
+ GET_PAGES_BY_FOOTER: {
66
+ ...template,
67
+ endpoint: ["/navigations/", "/footer/site/", "/pages"],
68
+ method: "GET",
69
+ },
70
+ UPDATE_PAGE_NAVIGATION: {
71
+ ...template,
72
+ endpoint: "/navigations/page/bulk",
73
+ method: "PUT",
74
+ },
60
75
  };
61
76
 
62
77
  const getNavigation = (id: number) => {
@@ -157,6 +172,34 @@ const getNavigationsLanguages = async (navID: number) => {
157
172
  return sendRequest(SERVICES.GET_NAVIGATIONS_LANGUAGES);
158
173
  };
159
174
 
175
+ const getPagesByHeader = async (headerID: number | number[], siteID: number) => {
176
+ const {
177
+ host,
178
+ endpoint: [prefix, infix, suffix],
179
+ } = SERVICES.GET_PAGES_BY_HEADER;
180
+
181
+ const ids = Array.isArray(headerID) ? headerID.join(",") : headerID;
182
+
183
+ SERVICES.GET_PAGES_BY_HEADER.dynamicUrl = `${host}${prefix}${ids}${infix}${siteID}${suffix}`;
184
+
185
+ return sendRequest(SERVICES.GET_PAGES_BY_HEADER);
186
+ };
187
+
188
+ const getPagesByFooter = async (footerID: number | number[], siteID: number) => {
189
+ const {
190
+ host,
191
+ endpoint: [prefix, infix, suffix],
192
+ } = SERVICES.GET_PAGES_BY_FOOTER;
193
+
194
+ const ids = Array.isArray(footerID) ? footerID.join(",") : footerID;
195
+
196
+ SERVICES.GET_PAGES_BY_FOOTER.dynamicUrl = `${host}${prefix}${ids}${infix}${siteID}${suffix}`;
197
+
198
+ return sendRequest(SERVICES.GET_PAGES_BY_FOOTER);
199
+ };
200
+
201
+ const updatePageNavigation = (data: any) => sendRequest(SERVICES.UPDATE_PAGE_NAVIGATION, data);
202
+
160
203
  export default {
161
204
  getNavigation,
162
205
  getHeaders,
@@ -169,4 +212,7 @@ export default {
169
212
  getNavigationsLanguages,
170
213
  deleteNavigationBulk,
171
214
  restoreNavigationBulk,
215
+ getPagesByHeader,
216
+ getPagesByFooter,
217
+ updatePageNavigation,
172
218
  };
@@ -2,10 +2,10 @@ import React from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { IDataPack, IErrorItem, IRootState, ISite } from "@ax/types";
5
+ import { getModuleCategories } from "@ax/helpers";
6
+ import Field from "../Field";
5
7
 
6
8
  import * as S from "./style";
7
- import Field from "../Field";
8
- import { getModuleCategories } from "@ax/helpers";
9
9
 
10
10
  export const TemplateManager = (props: IProps): JSX.Element => {
11
11
  const {
@@ -37,7 +37,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
37
37
  const modulesDataPacks = activatedPacks.map((pack: IDataPack) => pack.modules).flat();
38
38
 
39
39
  const getFieldProps = (field: any) => {
40
- const { key, type, whiteList = [], slugTo } = field;
40
+ const { key, type, whiteList = [], slugTo, readonly } = field;
41
41
  const isArr = type === "ComponentArray";
42
42
  const currentContent = isArr ? templateContent[key] : templateContent;
43
43
  const fieldObjKey = !isArr ? `${key}` : `modules`;
@@ -70,6 +70,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
70
70
  currentContent,
71
71
  handleUpdate,
72
72
  error,
73
+ readonly
73
74
  };
74
75
  };
75
76
 
@@ -78,7 +79,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
78
79
  {isConfig && templateFields && <S.Title>Template Options</S.Title>}
79
80
  {templateFields &&
80
81
  templateFields.map((templateField: any, index: number) => {
81
- const { whiteList, categories, key, fieldObjKey, mappedField, currentContent, handleUpdate, error } =
82
+ const { whiteList, categories, key, fieldObjKey, mappedField, currentContent, handleUpdate, error, readonly } =
82
83
  getFieldProps(templateField);
83
84
 
84
85
  return (
@@ -106,6 +107,7 @@ export const TemplateManager = (props: IProps): JSX.Element => {
106
107
  template={template}
107
108
  setHistoryPush={setHistoryPush}
108
109
  lang={lang}
110
+ readonly={readonly}
109
111
  />
110
112
  );
111
113
  })}
@@ -3,9 +3,9 @@ import { connect } from "react-redux";
3
3
 
4
4
  import { getTemplate, isModuleDisabled, slugify, areEqual } from "@ax/helpers";
5
5
  import { IRootState } from "@ax/types";
6
+ import { pageEditorActions } from "@ax/containers/PageEditor";
6
7
 
7
8
  import TemplateManager from "./TemplateManager";
8
- import { pageEditorActions } from "@ax/containers/PageEditor";
9
9
  import Field from "./Field";
10
10
 
11
11
  const PageConnectedField = (props: any) => {
@@ -61,7 +61,8 @@ const PageConnectedField = (props: any) => {
61
61
  objKey === "parent" &&
62
62
  isTemplateActivated &&
63
63
  templateConfig &&
64
- templateConfig.modifiableOnPage === false) ||
64
+ templateConfig.modifiableOnPage === false &&
65
+ templateConfig.defaultParent) ||
65
66
  isReadOnly;
66
67
 
67
68
  const defaultParent =
@@ -70,7 +71,7 @@ const PageConnectedField = (props: any) => {
70
71
  const hasDefaultIndex = isTemplateActivated && templateConfig && templateConfig.indexDefault !== undefined;
71
72
 
72
73
  const isFieldReadOnly =
73
- (["parent", "slug"].includes(objKey) && isPageHome) || parentIsReadOnly;
74
+ (["parent", "slug"].includes(objKey) && isPageHome) || parentIsReadOnly || field.readonly;
74
75
 
75
76
  const isDisabled =
76
77
  (!isGlobal &&
@@ -36,7 +36,7 @@ const Select = (props: ISelectProps): JSX.Element => {
36
36
  };
37
37
 
38
38
  // tslint:disable-next-line: no-shadowed-variable
39
- const getObjectValue = (value: string, options: IOptionProps[]) => {
39
+ const getObjectValue = (value: string | number | undefined, options: IOptionProps[]) => {
40
40
  if (!value) {
41
41
  return null;
42
42
  } else {
@@ -44,7 +44,7 @@ const Select = (props: ISelectProps): JSX.Element => {
44
44
  }
45
45
  };
46
46
 
47
- const searchable = type === "inline" ? false : true;
47
+ const searchable = type === "inline" || type === "mini" ? false : true;
48
48
 
49
49
  return (
50
50
  <div data-testid="select-component">
@@ -72,7 +72,7 @@ const Select = (props: ISelectProps): JSX.Element => {
72
72
 
73
73
  export interface ISelectProps {
74
74
  name: string;
75
- value: string;
75
+ value: string | undefined;
76
76
  options: IOptionProps[];
77
77
  error?: boolean;
78
78
  disabled?: boolean;
@@ -139,4 +139,59 @@ export const StyledSelect = styled(Select)<{
139
139
  ${(p) => p.theme.textStyle?.fieldContent};
140
140
  }
141
141
  }
142
+
143
+ &.mini {
144
+ ${(p) => p.theme.textStyle.uiS};
145
+ text-transform: capitalize;
146
+
147
+ .react-select__control {
148
+ height: 24px;
149
+ min-height: 24px;
150
+ justify-content: ${(p) => (p.alignRight ? "flex-end" : "flex-start")};
151
+ padding: 0 5px;
152
+
153
+ .react-select__value-container {
154
+ flex: 0 1 auto;
155
+ padding: 0;
156
+ .react-select__single-value {
157
+ position: relative;
158
+ transform: none;
159
+ overflow: visible;
160
+ }
161
+ }
162
+ .react-select__indicator {
163
+ padding: 0;
164
+ svg {
165
+ width: ${(p) => p.theme.spacing.s};
166
+ height: ${(p) => p.theme.spacing.s};
167
+ }
168
+ }
169
+ .react-select__input {
170
+ input {
171
+ cursor: default;
172
+ }
173
+ }
174
+ .react-select__placeholder {
175
+ position: relative;
176
+ transform: none;
177
+ }
178
+ }
179
+
180
+ .react-select__control--is-disabled {
181
+ .react-select__value-container {
182
+ .react-select__single-value {
183
+ color: ${(p) => p.theme.color.interactiveDisabled};
184
+ }
185
+ }
186
+ }
187
+
188
+ .react-select__menu {
189
+ ${(p) => p.theme.textStyle.uiXS};
190
+
191
+ .react-select__option {
192
+ padding: ${(p) => p.theme.spacing.xs};
193
+ min-height: auto;
194
+ }
195
+ }
196
+ }
142
197
  `;
@@ -120,6 +120,7 @@ function handleError(response: any, isMultiple = false, msg?: string): (dispatch
120
120
  btnText,
121
121
  actionsBelow,
122
122
  text,
123
+ btnAction,
123
124
  } = response;
124
125
 
125
126
  const firstMsg = Array.isArray(message) && message[0] ? message[0].error : message || text;
@@ -129,6 +130,7 @@ function handleError(response: any, isMultiple = false, msg?: string): (dispatch
129
130
  text: msg ? msg : firstMsg,
130
131
  btnText,
131
132
  actionsBelow,
133
+ btnAction,
132
134
  subErrors: isMultiple ? message : [],
133
135
  };
134
136
 
@@ -33,6 +33,7 @@ export interface IError {
33
33
  btnText?: string | undefined;
34
34
  actionsBelow?: boolean | undefined;
35
35
  subErrors?: any[];
36
+ btnAction?: () => void;
36
37
  }
37
38
 
38
39
  export interface IUser {
@@ -1,7 +1,7 @@
1
1
  import { Dispatch } from "redux";
2
2
 
3
3
  import { navigation } from "@ax/api";
4
- import { IBreadcrumbItem, INotification, ISchema } from "@ax/types";
4
+ import { IBreadcrumbItem, INavPages, INotification, ISchema, IUpdateNavigationParam } from "@ax/types";
5
5
  import {
6
6
  getDefaultSchema,
7
7
  getSchema,
@@ -12,7 +12,6 @@ import {
12
12
  getDefaultNavigationModules,
13
13
  } from "@ax/helpers";
14
14
  import {
15
- checkMaxModules,
16
15
  findByEditorID,
17
16
  generateEditorIDs,
18
17
  getLastComponentEditorID,
@@ -44,6 +43,7 @@ import {
44
43
  SET_IS_NEW_TRANSLATION,
45
44
  SET_SELECTED_PARENT,
46
45
  SET_COPY_MODULE,
46
+ SET_NAV_PAGES,
47
47
  } from "./constants";
48
48
 
49
49
  import {
@@ -58,6 +58,7 @@ import {
58
58
  ISetIsNewTranslation,
59
59
  ISetSelectedParent,
60
60
  ISetCopyModule,
61
+ ISetNavPages,
61
62
  } from "./interfaces";
62
63
 
63
64
  const { setIsLoading } = appActions;
@@ -122,6 +123,10 @@ function setCopyModule(moduleCopy: Record<string, unknown> | null): ISetCopyModu
122
123
  return { type: SET_COPY_MODULE, payload: { moduleCopy } };
123
124
  }
124
125
 
126
+ function setNavPages(navigationPages: INavPages): ISetNavPages {
127
+ return { type: SET_NAV_PAGES, payload: { navigationPages } };
128
+ }
129
+
125
130
  // API RELATED FUNCTIONS
126
131
 
127
132
  function getHeaders(params: any): (dispatch: Dispatch, getState: any) => Promise<void> {
@@ -330,7 +335,8 @@ function updateNavigation(
330
335
 
331
336
  function deleteNavigation(
332
337
  navID: number | number[],
333
- type: string
338
+ type: string,
339
+ errorAction: () => void
334
340
  ): (dispatch: Dispatch, getState: any) => Promise<boolean> {
335
341
  return async (dispatch, getState) => {
336
342
  try {
@@ -339,13 +345,18 @@ function deleteNavigation(
339
345
  handleSuccess: () => getNavigationByType(type)(dispatch, getState),
340
346
  handleError: (response: any) => {
341
347
  const {
342
- data: { message },
348
+ data: { message, code },
343
349
  } = response;
344
350
 
345
351
  if (isBulk) {
346
352
  getNavigationByType(type)(dispatch, getState);
347
353
  }
348
354
 
355
+ if (code === 403) {
356
+ response.btnText = "Show Pages";
357
+ response.btnAction = errorAction;
358
+ }
359
+
349
360
  const isMultiple = Array.isArray(message) && message.length > 1;
350
361
  const msg = isMultiple ? `The delete action failed due to ${message.length} errors.` : undefined;
351
362
  appActions.handleError(response, isMultiple, msg)(dispatch);
@@ -817,6 +828,46 @@ function resetDefaultsValues(): (dispatch: Dispatch) => Promise<void> {
817
828
  };
818
829
  }
819
830
 
831
+ function getPagesByNavigation(
832
+ type: string,
833
+ navID: number | number[],
834
+ siteID: number
835
+ ): (dispatch: Dispatch) => Promise<void> {
836
+ return async (dispatch) => {
837
+ try {
838
+ const responseActions = {
839
+ handleSuccess: async (response: any) => dispatch(setNavPages(response)),
840
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
841
+ };
842
+
843
+ const callback = async () =>
844
+ type === "header" ? navigation.getPagesByHeader(navID, siteID) : navigation.getPagesByFooter(navID, siteID);
845
+
846
+ await handleRequest(callback, responseActions, [appActions.setIsLoading])(dispatch);
847
+ } catch (e) {
848
+ console.log(e);
849
+ }
850
+ };
851
+ }
852
+
853
+ function updatePageNavigation(data: IUpdateNavigationParam): (dispatch: Dispatch) => Promise<boolean> {
854
+ return async (dispatch) => {
855
+ try {
856
+ const responseActions = {
857
+ handleSuccess: async () => dispatch(appActions.resetError()),
858
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
859
+ };
860
+
861
+ const callback = async () => navigation.updatePageNavigation(data);
862
+
863
+ return await handleRequest(callback, responseActions, [])(dispatch);
864
+ } catch (e) {
865
+ console.log(e);
866
+ return false;
867
+ }
868
+ };
869
+ }
870
+
820
871
  export {
821
872
  setEditorContent,
822
873
  setBreadcrumb,
@@ -854,4 +905,6 @@ export {
854
905
  moveModule,
855
906
  copyModule,
856
907
  pasteModule,
908
+ getPagesByNavigation,
909
+ updatePageNavigation,
857
910
  };
@@ -15,5 +15,6 @@ export const SET_TOTAL_ITEMS = `${NAME}/SET_TOTAL_ITEMS`;
15
15
  export const SET_CURRENT_NAVIGATION_LANGUAGES = `${NAME}/SET_CURRENT_NAVIGATION_LANGUAGES`;
16
16
  export const SET_IS_NEW_TRANSLATION = `${NAME}/SET_IS_NEW_TRANSLATION`;
17
17
  export const SET_COPY_MODULE = `${NAME}/SET_COPY_MODULE`;
18
+ export const SET_NAV_PAGES = `${NAME}/SET_NAV_PAGES`;
18
19
 
19
20
  export const ITEMS_PER_PAGE = 50;
@@ -1,4 +1,4 @@
1
- import { IBreadcrumbItem, ISchema } from "@ax/types";
1
+ import { IBreadcrumbItem, INavPages, ISchema } from "@ax/types";
2
2
  import {
3
3
  SET_EDITOR_CONTENT,
4
4
  SET_BREADCRUMB,
@@ -14,6 +14,7 @@ import {
14
14
  SET_IS_NEW_TRANSLATION,
15
15
  SET_SELECTED_PARENT,
16
16
  SET_COPY_MODULE,
17
+ SET_NAV_PAGES,
17
18
  } from "./constants";
18
19
 
19
20
  export interface ISetEditorContent {
@@ -86,6 +87,11 @@ export interface ISetCopyModule {
86
87
  payload: { moduleCopy: Record<string, unknown> | null };
87
88
  }
88
89
 
90
+ export interface ISetNavPages {
91
+ type: typeof SET_NAV_PAGES;
92
+ payload: { navigationPages: INavPages };
93
+ }
94
+
89
95
  export type NavigationActionsCreators = ISetEditorContent &
90
96
  ISetBreadcrumb &
91
97
  ISetSchema &
@@ -1,4 +1,4 @@
1
- import { IBreadcrumbItem, ISchema } from "@ax/types";
1
+ import { IBreadcrumbItem, INavPages, ISchema } from "@ax/types";
2
2
 
3
3
  import {
4
4
  SET_EDITOR_CONTENT,
@@ -16,6 +16,7 @@ import {
16
16
  SET_IS_NEW_TRANSLATION,
17
17
  SET_SELECTED_PARENT,
18
18
  SET_COPY_MODULE,
19
+ SET_NAV_PAGES,
19
20
  } from "./constants";
20
21
  import { NavigationActionsCreators } from "./interfaces";
21
22
 
@@ -36,6 +37,7 @@ export interface INavigationState {
36
37
  isNewTranslation: boolean;
37
38
  form: any;
38
39
  moduleCopy: { date: string; element: Record<string, unknown> } | null;
40
+ navigationPages: INavPages | null;
39
41
  }
40
42
 
41
43
  export const initialState = {
@@ -55,6 +57,7 @@ export const initialState = {
55
57
  isNewTranslation: false,
56
58
  form: {},
57
59
  moduleCopy: null,
60
+ navigationPages: null,
58
61
  };
59
62
 
60
63
  export function reducer(state = initialState, action: NavigationActionsCreators): INavigationState {
@@ -74,6 +77,7 @@ export function reducer(state = initialState, action: NavigationActionsCreators)
74
77
  case SET_CURRENT_NAVIGATION_LANGUAGES:
75
78
  case SET_IS_NEW_TRANSLATION:
76
79
  case SET_COPY_MODULE:
80
+ case SET_NAV_PAGES:
77
81
  return { ...state, ...action.payload };
78
82
  default:
79
83
  return state;
@@ -397,13 +397,11 @@ function publishSite(siteID: number, params?: IGetSitesParams): (dispatch: Dispa
397
397
  };
398
398
  }
399
399
 
400
- function publishSitesBulk(ids: number[]): (dispatch: Dispatch) => Promise<void> {
400
+ function publishSitesBulk(ids: number[], params?: IGetSitesParams): (dispatch: Dispatch) => Promise<void> {
401
401
  return async (dispatch) => {
402
402
  try {
403
403
  const responseActions = {
404
- handleSuccess: () => {
405
- getSites()(dispatch);
406
- },
404
+ handleSuccess: () => getSites(params)(dispatch),
407
405
  handleError: (response: any) => appActions.handleError(response)(dispatch),
408
406
  };
409
407
  const callback = async () => sites.publishSiteBulk(ids);
@@ -428,13 +426,11 @@ function unpublishSite(siteID: number, params?: IGetSitesParams): (dispatch: Dis
428
426
  };
429
427
  }
430
428
 
431
- function unpublishSitesBulk(ids: number[]): (dispatch: Dispatch) => Promise<void> {
429
+ function unpublishSitesBulk(ids: number[], params?: IGetSitesParams): (dispatch: Dispatch) => Promise<void> {
432
430
  return async (dispatch) => {
433
431
  try {
434
432
  const responseActions = {
435
- handleSuccess: () => {
436
- getSites()(dispatch);
437
- },
433
+ handleSuccess: () => getSites(params)(dispatch),
438
434
  handleError: (response: any) => appActions.handleError(response)(dispatch),
439
435
  };
440
436
  const callback = async () => sites.unpublishSiteBulk(ids);
@@ -19,7 +19,7 @@ const ErrorView = (props: any) => {
19
19
  };
20
20
 
21
21
  const ErrorGuard = (props: IProps) => {
22
- const { text, code, resetError, btnText, actionsBelow, subErrors } = props;
22
+ const { text, code, resetError, btnText, actionsBelow, subErrors, btnAction } = props;
23
23
 
24
24
  const isBlocking = blockingErrors.includes(code);
25
25
 
@@ -38,6 +38,7 @@ const ErrorGuard = (props: IProps) => {
38
38
  btnText={btnText}
39
39
  actionsBelow={actionsBelow}
40
40
  subErrors={subErrors}
41
+ onClick={btnAction}
41
42
  />,
42
43
  domNode
43
44
  )
@@ -50,7 +51,7 @@ const ErrorGuard = (props: IProps) => {
50
51
  const mapStateToProps = (state: IRootState) => {
51
52
  const {
52
53
  app: {
53
- error: { code, text, btnText, actionsBelow, subErrors },
54
+ error: { code, text, btnText, actionsBelow, subErrors, btnAction },
54
55
  },
55
56
  } = state;
56
57
  return {
@@ -59,6 +60,7 @@ const mapStateToProps = (state: IRootState) => {
59
60
  btnText,
60
61
  actionsBelow,
61
62
  subErrors,
63
+ btnAction,
62
64
  };
63
65
  };
64
66
 
@@ -73,6 +75,7 @@ interface IProps {
73
75
  btnText?: string;
74
76
  actionsBelow?: boolean | undefined;
75
77
  subErrors?: any[];
78
+ btnAction?: () => void;
76
79
  }
77
80
 
78
81
  export default connect(mapStateToProps, mapDispatchToProps)(ErrorGuard);
@@ -29,6 +29,7 @@ const DefaultItem = (props: IProps): JSX.Element => {
29
29
  setDeletedNav,
30
30
  onChange,
31
31
  isSelected,
32
+ toggleReplaceModal
32
33
  } = props;
33
34
 
34
35
  const [inputValue, setInputValue] = useState("");
@@ -55,13 +56,15 @@ const DefaultItem = (props: IProps): JSX.Element => {
55
56
 
56
57
  const handleSetDefault = () => setDefaultNavigation(defaultContent.id, defaultContent.type);
57
58
 
58
- const removeItem = () =>
59
- deleteNavigation(defaultContent.id, defaultContent.type).then((deleted: boolean) => {
60
- if (deleted) {
61
- setDeletedNav(defaultContent.id);
62
- toggleToast();
63
- }
64
- });
59
+ const handleErrorAction = () => toggleReplaceModal();
60
+
61
+ const removeItem = async () => {
62
+ const deleted = await deleteNavigation(defaultContent.id, defaultContent.type, handleErrorAction);
63
+ setDeletedNav(defaultContent.id);
64
+ if (deleted) {
65
+ toggleToast();
66
+ }
67
+ };
65
68
 
66
69
  let menuOptions = [
67
70
  {
@@ -214,12 +217,13 @@ interface IProps {
214
217
  toggleToast(): void;
215
218
  onChange: (e: any) => void;
216
219
  setDeletedNav(item: number): void;
217
- deleteNavigation(navID: number, type: string): Promise<boolean>;
220
+ deleteNavigation(navID: number, type: string, errorAction: () => void): Promise<boolean>;
218
221
  setDefaultNavigation(navID: number, type: string): void;
219
222
  setLanguage(lang: { locale: string; id: number | null }): void;
220
223
  createNewTranslation(isNewTranslation: boolean): void;
221
224
  setHeader(id: number | null): void;
222
225
  setFooter(id: number | null): void;
226
+ toggleReplaceModal: () => void;
223
227
  }
224
228
 
225
229
  const mapDispatchToProps = {
@@ -0,0 +1,205 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import { connect } from "react-redux";
3
+
4
+ import { navigationActions } from "@ax/containers/Navigation";
5
+ import { CheckField, Loader, Modal, Select } from "@ax/components";
6
+ import { ICheck, IModal, INavPages, IRootState, IUpdateNavigationParam } from "@ax/types";
7
+ import { useBulkSelection } from "@ax/hooks";
8
+
9
+ import * as S from "./style";
10
+
11
+ const ReplaceNavModal = (props: IProps) => {
12
+ const {
13
+ isOpen,
14
+ type,
15
+ navID,
16
+ currentSiteID,
17
+ navigationPages,
18
+ isLoading,
19
+ currentDefaultsContent,
20
+ toggleModal,
21
+ toggleToast,
22
+ getPagesByNavigation,
23
+ updatePageNavigation,
24
+ } = props;
25
+
26
+ const initialItems = navigationPages ? navigationPages.items.pagesInUse : [];
27
+ const totalItems = navigationPages ? navigationPages.items.totalItems : 0;
28
+ const pagesIds = initialItems.map((item) => item.pageId);
29
+
30
+ const [navSelected, setNavSelected] = useState<any>();
31
+ const [items, setItems] = useState(initialItems);
32
+
33
+ useEffect(() => {
34
+ const getPages = async () => navID && currentSiteID && (await getPagesByNavigation(type, navID, currentSiteID));
35
+ getPages();
36
+ }, []);
37
+
38
+ useEffect(() => {
39
+ setItems(initialItems);
40
+ }, [initialItems]);
41
+
42
+ const { resetBulkSelection, selectedItems, isSelected, checkState, addToBulkSelection, selectAllItems } =
43
+ useBulkSelection(pagesIds);
44
+
45
+ const handleAllChange = () => (checkState.isAllSelected ? resetBulkSelection() : selectAllItems());
46
+
47
+ const options = currentDefaultsContent
48
+ .filter(
49
+ (nav) =>
50
+ navID && ((typeof navID === "number" && nav.id !== navID) || (Array.isArray(navID) && !navID.includes(nav.id)))
51
+ )
52
+ .map((nav) => {
53
+ return { value: nav.id, label: nav.title };
54
+ });
55
+
56
+ const fullOptions = [{ value: 0, label: `${type} by default` }, ...options];
57
+
58
+ const handleSelectChange = (value: number | string) => {
59
+ if (typeof value !== "number") return;
60
+ setNavSelected(value);
61
+ const newItems = items.map((item) => {
62
+ if (isSelected(item.pageId)) {
63
+ return { ...item, navigationId: value };
64
+ } else {
65
+ return item;
66
+ }
67
+ });
68
+ setItems(newItems);
69
+ resetBulkSelection();
70
+ };
71
+
72
+ const handleChangeNav = async () => {
73
+ const newNavs = items.reduce(
74
+ (acc: { navigationId: number | null; type: "header" | "footer"; pageId: number }[], current) => {
75
+ const original = initialItems.find((initItem) => initItem.pageId === current.pageId);
76
+ if (original && original.navigationId !== current.navigationId) {
77
+ const nav = {
78
+ navigationId: current.navigationId === 0 ? null : current.navigationId,
79
+ type,
80
+ pageId: current.pageId,
81
+ };
82
+ return [...acc, nav];
83
+ }
84
+ return acc;
85
+ },
86
+ []
87
+ );
88
+
89
+ const updated = await updatePageNavigation({ navigations: newNavs });
90
+
91
+ if (updated) {
92
+ toggleModal();
93
+ toggleToast(newNavs.length);
94
+ }
95
+ };
96
+
97
+ const secondaryAction = navSelected !== undefined ? { title: "Cancel", onClick: toggleModal } : undefined;
98
+ const mainAction = navSelected !== undefined ? { title: "Change Navigation", onClick: handleChangeNav } : undefined;
99
+
100
+ return (
101
+ <Modal
102
+ isOpen={isOpen}
103
+ hide={toggleModal}
104
+ title="Pages in use"
105
+ size="L"
106
+ secondaryAction={secondaryAction}
107
+ mainAction={mainAction}
108
+ >
109
+ {isLoading ? (
110
+ <S.LoadingWrapper>
111
+ <Loader name="circle" />
112
+ </S.LoadingWrapper>
113
+ ) : (
114
+ <S.ModalContent>
115
+ <S.TotalWrapper>
116
+ This {type} appears on <strong>{totalItems} pages</strong>:
117
+ </S.TotalWrapper>
118
+ <S.Header>
119
+ <S.CheckWrapper>
120
+ <CheckField
121
+ name="checkAll"
122
+ value="checkAll"
123
+ checked={checkState.isAllSelected}
124
+ indeterminate={checkState.indeterminate}
125
+ onChange={handleAllChange}
126
+ />
127
+ </S.CheckWrapper>
128
+ <S.PageInfo>
129
+ <S.Text>Select All</S.Text>
130
+ </S.PageInfo>
131
+ <S.HeaderInfo>
132
+ {selectedItems.all.length > 0 && (
133
+ <Select
134
+ options={fullOptions}
135
+ name="selectNav"
136
+ type="mini"
137
+ value={navSelected}
138
+ onChange={handleSelectChange}
139
+ placeholder={`Change ${type}`}
140
+ />
141
+ )}
142
+ </S.HeaderInfo>
143
+ </S.Header>
144
+ <S.ItemList>
145
+ {items.map((item) => {
146
+ const handleChange = (value: ICheck) => addToBulkSelection(value);
147
+ const navigation =
148
+ item.navigationId === 0
149
+ ? { title: `${type} by default` }
150
+ : currentDefaultsContent.find((nav: any) => nav.id === item.navigationId);
151
+ const original = initialItems.find((initItem) => initItem.pageId === item.pageId);
152
+ return (
153
+ <S.Item key={item.pageId}>
154
+ <S.CheckWrapper>
155
+ <CheckField
156
+ name={`check-${item.pageId}`}
157
+ value={item.pageId}
158
+ checked={isSelected(item.pageId)}
159
+ onChange={handleChange}
160
+ />
161
+ </S.CheckWrapper>
162
+ <S.PageInfo>
163
+ <S.Title>{item.pageTitle}</S.Title>
164
+ <S.PagePath>{item.pathString}</S.PagePath>
165
+ </S.PageInfo>
166
+ <S.HeaderInfo>
167
+ <S.Tag active={original?.navigationId !== item.navigationId}>{navigation?.title}</S.Tag>
168
+ </S.HeaderInfo>
169
+ </S.Item>
170
+ );
171
+ })}
172
+ </S.ItemList>
173
+ </S.ModalContent>
174
+ )}
175
+ </Modal>
176
+ );
177
+ };
178
+
179
+ interface IReplaceModalProps {
180
+ type: "header" | "footer";
181
+ navID: number | number[] | null;
182
+ currentSiteID: number | null;
183
+ navigationPages: INavPages | null;
184
+ isLoading: boolean;
185
+ currentDefaultsContent: any[];
186
+ toggleToast: (pages: number) => void;
187
+ getPagesByNavigation: (type: string, navID: number | number[], siteID: number) => Promise<void>;
188
+ updatePageNavigation: (data: IUpdateNavigationParam) => Promise<boolean>;
189
+ }
190
+
191
+ type IProps = IModal & IReplaceModalProps;
192
+
193
+ const mapStateToProps = (state: IRootState) => ({
194
+ currentSiteID: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
195
+ navigationPages: state.navigation.navigationPages,
196
+ isLoading: state.app.isLoading,
197
+ currentDefaultsContent: state.navigation.currentDefaultsContent,
198
+ });
199
+
200
+ const mapDispatchToProps = {
201
+ getPagesByNavigation: navigationActions.getPagesByNavigation,
202
+ updatePageNavigation: navigationActions.updatePageNavigation,
203
+ };
204
+
205
+ export default connect(mapStateToProps, mapDispatchToProps)(ReplaceNavModal);
@@ -0,0 +1,99 @@
1
+ import styled from "styled-components";
2
+
3
+ const ModalContent = styled.div`
4
+ padding: ${(p) => p.theme.spacing.m};
5
+ `;
6
+
7
+ const LoadingWrapper = styled.div`
8
+ position: relative;
9
+ width: 100%;
10
+ height: 100%;
11
+ svg {
12
+ position: absolute;
13
+ top: 50%;
14
+ left: 50%;
15
+ transform: translate(-50%, -50%);
16
+ }
17
+ `;
18
+
19
+ const TotalWrapper = styled.div`
20
+ ${(p) => p.theme.textStyle.uiM};
21
+ color: ${(p) => p.theme.color.textHighEmphasis};
22
+ margin-bottom: ${(p) => p.theme.spacing.s};
23
+ `;
24
+
25
+ const ItemList = styled.div`
26
+ overflow: auto;
27
+ `;
28
+
29
+ const Header = styled.div`
30
+ display: flex;
31
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
32
+ padding: ${(p) => `${p.theme.spacing.xs} ${p.theme.spacing.xs} 0 ${p.theme.spacing.xs}`};
33
+ margin-bottom: ${(p) => p.theme.spacing.xs};
34
+ min-height: 45px;
35
+ `;
36
+
37
+ const Item = styled.div`
38
+ display: flex;
39
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
40
+ padding: ${(p) => p.theme.spacing.xs};
41
+ `;
42
+
43
+ const CheckWrapper = styled.div`
44
+ display: flex;
45
+ align-items: flex-start;
46
+ `;
47
+
48
+ const PageInfo = styled.div`
49
+ flex-grow: 2;
50
+ `;
51
+
52
+ const Title = styled.div`
53
+ ${(p) => p.theme.textStyle.uiL};
54
+ color: ${(p) => p.theme.color.textHighEmphasis};
55
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
56
+ `;
57
+
58
+ const PagePath = styled.div`
59
+ ${(p) => p.theme.textStyle.uiXS};
60
+ color: ${(p) => p.theme.color.textMediumEmphasis};
61
+ `;
62
+
63
+ const Text = styled.div`
64
+ ${(p) => p.theme.textStyle.uiS};
65
+ color: ${(p) => p.theme.color.textMediumEmphasis};
66
+ `;
67
+
68
+ const HeaderInfo = styled.div`
69
+ display: flex;
70
+ align-items: center;
71
+ `;
72
+
73
+ const Tag = styled.div<{ active: boolean}>`
74
+ ${(p) => p.theme.textStyle.uiXS};
75
+ color: ${(p) => p.theme.color.textMediumEmphasis};
76
+ background-color: ${(p) => p.active ? p.theme.color?.interactive02 : p.theme.colors.uiBackground03};
77
+ box-sizing: border-box;
78
+ border-radius: ${(p) => p.theme.radii.xs};
79
+ white-space: nowrap;
80
+ display: inline-block;
81
+ padding: 3px 8px;
82
+ text-transform: capitalize;
83
+ `;
84
+
85
+ export {
86
+ ModalContent,
87
+ LoadingWrapper,
88
+ TotalWrapper,
89
+ ItemList,
90
+ Header,
91
+ Item,
92
+ PageInfo,
93
+ PagePath,
94
+ Title,
95
+ HeaderInfo,
96
+ Tag,
97
+ CheckWrapper,
98
+ Text,
99
+ };
@@ -4,7 +4,7 @@ import { connect } from "react-redux";
4
4
  import { appActions } from "@ax/containers/App";
5
5
  import { menuActions } from "@ax/containers/Navigation";
6
6
  import { IRootState, IHeader, IFooter } from "@ax/types";
7
- import { useBulkSelection, useToast } from "@ax/hooks";
7
+ import { useBulkSelection, useModal, useToast } from "@ax/hooks";
8
8
  import { capitalize, isMultipleNavigationModules } from "@ax/helpers";
9
9
  import { navigationActions } from "@ax/containers/Navigation";
10
10
  import { MainWrapper, TableList, ErrorToast, Toast, Notification } from "@ax/components";
@@ -14,6 +14,8 @@ import DefaultNav from "./Nav";
14
14
  import BulkHeader from "./BulkHeader";
15
15
 
16
16
  import { NavigationModulesWarning } from "./atoms";
17
+ import ReplaceNavModal from "./ReplaceNavModal";
18
+
17
19
  import * as S from "./style";
18
20
 
19
21
  const DefaultsList = (props: IProps): JSX.Element => {
@@ -39,10 +41,13 @@ const DefaultsList = (props: IProps): JSX.Element => {
39
41
 
40
42
  const [page, setPage] = useState(1);
41
43
  const [deletedNav, setDeletedNav] = useState<number | number[] | null>(null);
44
+ const [updatedPages, setUpdatedPages] = useState<number | null>(null);
42
45
  const { isVisible, toggleToast, setIsVisible } = useToast();
46
+ const { isVisible: isPagesVisible, toggleToast: togglePagesToast, setIsVisible: setIsPagesVisible } = useToast();
43
47
  const [isScrolling, setIsScrolling] = useState(false);
44
48
  const tableRef = useRef<HTMLDivElement>(null);
45
49
  const [isNavigationNotificationOpen, setIsNavigationNotificationOpen] = useState(false);
50
+ const { isOpen: isReplaceOpened, toggleModal: toggleReplaceModal } = useModal();
46
51
 
47
52
  const navIds = currentDefaultsContent && currentDefaultsContent.map((nav: any) => nav.id);
48
53
 
@@ -153,13 +158,25 @@ const DefaultsList = (props: IProps): JSX.Element => {
153
158
  message: deletedNav ? `${capitalize(currentType)} deleted` : "",
154
159
  };
155
160
 
156
- const bulkDelete = () =>
157
- deleteNavigation(selectedItems.all, currentType).then((deleted: boolean) => {
158
- if (deleted) {
159
- setDeletedNav(selectedItems.all);
160
- toggleToast();
161
- }
162
- });
161
+ const toastPagesProps = {
162
+ setIsVisible: setIsPagesVisible,
163
+ message: `${updatedPages} pages updated with new ${currentType}s.`,
164
+ };
165
+
166
+ const handleToast = (pages: number) => {
167
+ setUpdatedPages(pages);
168
+ togglePagesToast();
169
+ };
170
+
171
+ const handleErrorAction = () => toggleReplaceModal();
172
+
173
+ const bulkDelete = async () => {
174
+ const deleted = await deleteNavigation(selectedItems.all, currentType, handleErrorAction);
175
+ setDeletedNav(selectedItems.all);
176
+ if (deleted) {
177
+ toggleToast();
178
+ }
179
+ };
163
180
 
164
181
  const unselectAllItems = () => resetBulkSelection();
165
182
 
@@ -224,13 +241,24 @@ const DefaultsList = (props: IProps): JSX.Element => {
224
241
  onChange={addToBulkSelection}
225
242
  handleClick={setContent}
226
243
  setHistoryPush={setHistoryPush}
244
+ toggleReplaceModal={toggleReplaceModal}
227
245
  />
228
246
  );
229
247
  })}
230
248
  </TableList>
231
249
  </S.TableWrapper>
232
250
  {isVisible && <Toast {...toastProps} />}
251
+ {isPagesVisible && <Toast {...toastPagesProps} />}
233
252
  </S.DefaultListWrapper>
253
+ {isReplaceOpened && (
254
+ <ReplaceNavModal
255
+ isOpen={isReplaceOpened}
256
+ toggleModal={toggleReplaceModal}
257
+ type={currentType}
258
+ navID={deletedNav}
259
+ toggleToast={handleToast}
260
+ />
261
+ )}
234
262
  </MainWrapper>
235
263
  );
236
264
  };
@@ -253,7 +281,7 @@ interface IDispatchProps {
253
281
  setHeader(id: number | null): void;
254
282
  setFooter(id: number | null): void;
255
283
  restoreNavigation(navID: number | number[], type: string): void;
256
- deleteNavigation(navID: number[], type: string): Promise<boolean>;
284
+ deleteNavigation(navID: number[], type: string, errorAction: () => void): Promise<boolean>;
257
285
  getMenus(): void;
258
286
  resetDefaultsValues(): void;
259
287
  }
@@ -255,15 +255,14 @@ const SitesList = (props: ISitesListProps): JSX.Element => {
255
255
 
256
256
  const bulkPublishAction = async (isPublish: boolean) => {
257
257
  const { notPublished, published } = selectedItems;
258
+ const params = getParams();
259
+
258
260
  if (notPublished.length > 0 && isPublish) {
259
- publishSitesBulk(notPublished);
261
+ await publishSitesBulk(notPublished, params);
260
262
  }
261
263
  if (published.length > 0 && !isPublish) {
262
- unpublishSitesBulk(published);
264
+ await unpublishSitesBulk(published, params);
263
265
  }
264
-
265
- const params = getParams();
266
- getSites(params);
267
266
  unselectAllItems();
268
267
  };
269
268
 
@@ -383,9 +382,9 @@ const mapStateToProps = (state: IRootState) => ({
383
382
  interface IDispatchProps {
384
383
  setHistoryPush(path: string, isEditor?: boolean): void;
385
384
  saveSettings(form: ISettingsForm): Promise<boolean>;
386
- getSites(params: IGetSitesParams): void;
387
- publishSitesBulk(ids: number[]): void;
388
- unpublishSitesBulk(ids: number[]): void;
385
+ getSites(params: IGetSitesParams): Promise<void>;
386
+ publishSitesBulk(ids: number[], params?: IGetSitesParams): Promise<void>;
387
+ unpublishSitesBulk(ids: number[], params?: IGetSitesParams): Promise<void>;
389
388
  setListConfig(config: ISiteListConfig): void;
390
389
  }
391
390
 
@@ -833,6 +833,18 @@ export interface ICreatePasswordParams {
833
833
  retypedPassword: string;
834
834
  }
835
835
 
836
+ export interface INavPages {
837
+ availableNavigationsInSite: { id: number; isDefault: boolean };
838
+ items: {
839
+ pagesInUse: { pageId: number; pageTitle: string; pathString: string; navigationId: number }[];
840
+ totalItems: number;
841
+ };
842
+ }
843
+
844
+ export interface IUpdateNavigationParam {
845
+ navigations: { navigationId: number | null; type: "header" | "footer"; pageId: number }[];
846
+ }
847
+
836
848
  export type Field =
837
849
  | "AsyncCheckGroup"
838
850
  | "AsyncSelect"