@griddo/ax 1.72.9 → 1.73.0

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 (76) hide show
  1. package/package.json +2 -2
  2. package/scripts/griddo-sync-schemas.js +1 -1
  3. package/src/__tests__/components/ErrorCenter/ErrorCenter.test.tsx +186 -0
  4. package/src/__tests__/components/Flag/Flag.test.tsx +60 -0
  5. package/src/__tests__/components/FloatingMenu/FloatingMenu.test.tsx +712 -0
  6. package/src/__tests__/components/FloatingPanel/FloatingPanel.test.tsx +149 -0
  7. package/src/__tests__/components/GuardModal/GuardModal.test.tsx +31 -0
  8. package/src/__tests__/components/Icon/Icon.test.tsx +76 -0
  9. package/src/__tests__/components/IconAction/IconAction.test.tsx +91 -0
  10. package/src/__tests__/components/Notification/Notification.test.tsx +206 -0
  11. package/src/__tests__/components/Notification/SubNotification/Subnotification.test.tsx +46 -0
  12. package/src/__tests__/components/ReorderArrows/ReorderArrows.test.tsx +96 -0
  13. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +200 -0
  14. package/src/__tests__/components/SearchField/SearchField.test.tsx +375 -0
  15. package/src/api/analytics.tsx +4 -4
  16. package/src/components/ActionMenu/style.tsx +1 -0
  17. package/src/components/ConfigPanel/Form/ConnectedField/NavConnectedField/index.tsx +1 -1
  18. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +2 -1
  19. package/src/components/ConfigPanel/Form/index.tsx +22 -1
  20. package/src/components/ConfigPanel/Form/style.tsx +19 -0
  21. package/src/components/ConfigPanel/GlobalPageForm/index.tsx +22 -3
  22. package/src/components/ConfigPanel/GlobalPageForm/style.tsx +18 -2
  23. package/src/components/ConfigPanel/NavigationForm/Field/index.tsx +25 -13
  24. package/src/components/ConfigPanel/index.tsx +8 -0
  25. package/src/components/ErrorCenter/index.tsx +8 -4
  26. package/src/components/Fields/DateField/DatePickerInput/index.tsx +30 -8
  27. package/src/components/Fields/DateField/index.tsx +8 -2
  28. package/src/components/Fields/Select/index.tsx +1 -0
  29. package/src/components/Flag/index.tsx +13 -11
  30. package/src/components/FloatingMenu/index.tsx +23 -7
  31. package/src/components/FloatingMenu/style.tsx +1 -0
  32. package/src/components/FloatingPanel/index.tsx +9 -3
  33. package/src/components/GuardModal/index.tsx +3 -3
  34. package/src/components/Icon/index.tsx +2 -1
  35. package/src/components/IconAction/index.tsx +3 -3
  36. package/src/components/MainWrapper/AppBar/index.tsx +3 -1
  37. package/src/components/MainWrapper/AppBar/style.tsx +3 -0
  38. package/src/components/MenuItem/index.tsx +1 -1
  39. package/src/components/Modal/index.tsx +1 -1
  40. package/src/components/Notification/SubNotification/index.tsx +33 -0
  41. package/src/components/Notification/SubNotification/style.tsx +49 -0
  42. package/src/components/Notification/index.tsx +31 -17
  43. package/src/components/Notification/style.tsx +33 -8
  44. package/src/components/ReorderArrows/index.tsx +3 -3
  45. package/src/components/ResizePanel/ResizeHandle/index.tsx +29 -38
  46. package/src/components/ResizePanel/index.tsx +13 -15
  47. package/src/components/SearchField/index.tsx +9 -8
  48. package/src/containers/Analytics/actions.tsx +14 -4
  49. package/src/containers/App/actions.tsx +18 -6
  50. package/src/containers/App/reducer.tsx +1 -0
  51. package/src/containers/Domains/actions.tsx +8 -1
  52. package/src/containers/Navigation/Defaults/actions.tsx +16 -2
  53. package/src/containers/PageEditor/actions.tsx +83 -7
  54. package/src/containers/PageEditor/utils.tsx +28 -10
  55. package/src/containers/Redirects/actions.tsx +16 -2
  56. package/src/containers/Sites/actions.tsx +80 -3
  57. package/src/containers/StructuredData/actions.tsx +24 -3
  58. package/src/containers/Users/actions.tsx +8 -1
  59. package/src/forms/errors.tsx +1 -0
  60. package/src/forms/fields.tsx +6 -3
  61. package/src/forms/validators.tsx +14 -4
  62. package/src/guards/error/index.tsx +17 -21
  63. package/src/helpers/dates.tsx +2 -0
  64. package/src/helpers/index.tsx +2 -0
  65. package/src/hooks/modals.tsx +4 -4
  66. package/src/modules/Content/OptionTable/index.tsx +20 -7
  67. package/src/modules/Content/index.tsx +4 -7
  68. package/src/modules/Content/utils.tsx +18 -13
  69. package/src/modules/FramePreview/index.tsx +39 -12
  70. package/src/modules/GlobalEditor/index.tsx +17 -20
  71. package/src/modules/Navigation/Menus/List/Table/index.tsx +2 -2
  72. package/src/modules/PageEditor/Editor/index.tsx +13 -0
  73. package/src/modules/PageEditor/index.tsx +17 -20
  74. package/src/modules/Redirects/RedirectItem/index.tsx +17 -3
  75. package/src/modules/Settings/ContentTypes/DataPacks/Item/index.tsx +1 -1
  76. package/src/modules/StructuredData/Form/index.tsx +10 -13
@@ -0,0 +1,49 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div<{ isVisible: boolean }>`
4
+ display: flex;
5
+ flex-direction: column;
6
+ justify-content: center;
7
+ align-items: center;
8
+ padding: ${(p) => `0 ${p.theme.spacing.m}`};
9
+ color: ${(p) => p.theme.color.textHighEmphasisInverse};
10
+ ${(p) => p.theme.textStyle.uiM};
11
+ display: ${(p) => (p.isVisible ? "block" : "none")};
12
+ transition: all 0.5s;
13
+ background-color: ${(p) => p.theme.colors.overlayHoverDark};
14
+ border-bottom: 1px solid ${(p) => p.theme.colors.iconLowEmphasisInverse};
15
+ `;
16
+
17
+ const Row = styled.div`
18
+ display: flex;
19
+ align-items: center;
20
+ width: 100%;
21
+ min-height: ${(p) => p.theme.spacing.l};
22
+ z-index: 2;
23
+ padding-left: ${(p) => `calc( ${p.theme.spacing.m} + ${p.theme.spacing.s})`};
24
+ padding-right: ${(p) => p.theme.spacing.xxs};
25
+
26
+ svg {
27
+ width: ${(p) => p.theme.spacing.s};
28
+ height: ${(p) => p.theme.spacing.s};
29
+ path {
30
+ fill: ${(p) => p.theme.color.textHighEmphasisInverse};
31
+ }
32
+ }
33
+ `;
34
+
35
+ const Text = styled.span``;
36
+
37
+ const ActionsWrapper = styled.div`
38
+ display: flex;
39
+ margin-left: auto;
40
+ align-items: center;
41
+ `;
42
+
43
+ const CloseWrapper = styled.span`
44
+ cursor: pointer;
45
+ width: 16px;
46
+ height: 16px;
47
+ `;
48
+
49
+ export { Wrapper, Text, Row, ActionsWrapper, CloseWrapper };
@@ -1,36 +1,36 @@
1
1
  import React, { useState } from "react";
2
2
  import { Icon } from "@ax/components";
3
+ import SubNotification from "./SubNotification";
3
4
 
4
5
  import * as S from "./style";
5
6
 
6
- const Notification = (props: IProps) => {
7
- const { text, type, btnText, resetError, onClick, closeButton = true, actionsBelow } = props;
7
+ const Notification = (props: INotificationProps): JSX.Element => {
8
+ const { text, type, btnText, resetError, onClick, closeButton = true, actionsBelow, subErrors } = props;
8
9
 
9
- const [isOpen, setIsOpen] = useState(true);
10
+ const [isVisible, setIsVisible] = useState(true);
11
+ const [isOpen, setIsOpen] = useState(false);
10
12
 
11
13
  const getNotificationIcon = (type: string) => {
12
14
  switch (type) {
13
15
  case "error":
14
16
  case "info":
15
17
  return (
16
- <S.IconWrapper>
18
+ <S.IconWrapper data-testid="alert-icon">
17
19
  <Icon name="alert" />
18
20
  </S.IconWrapper>
19
21
  );
20
22
  case "success":
21
23
  return (
22
- <S.IconWrapper>
24
+ <S.IconWrapper data-testid="success-icon">
23
25
  <Icon name="done" />
24
26
  </S.IconWrapper>
25
27
  );
26
28
  case "warning":
27
29
  return (
28
- <S.IconWrapper>
30
+ <S.IconWrapper data-testid="warning-icon">
29
31
  <Icon name="warning" />
30
32
  </S.IconWrapper>
31
33
  );
32
- default:
33
- return null;
34
34
  }
35
35
  };
36
36
 
@@ -40,37 +40,50 @@ const Notification = (props: IProps) => {
40
40
 
41
41
  const handleClose = () => {
42
42
  resetError && resetError();
43
- setIsOpen(false);
43
+ setIsVisible(false);
44
44
  };
45
45
 
46
+ const toggleShow = () => setIsOpen(!isOpen);
47
+
46
48
  const ActionBtn = () => (
47
- <S.ActionBtn onClick={handleClick} actionsBelow={actionsBelow}>
49
+ <S.ActionBtn onClick={handleClick} actionsBelow={actionsBelow} data-testid="action-button">
48
50
  {btnText}
49
51
  </S.ActionBtn>
50
52
  );
51
53
 
54
+ const showText = isOpen ? "Hide errors" : "Show errors";
55
+
52
56
  return (
53
- <>
54
- <S.Wrapper isOpen={isOpen} className={type}>
57
+ <S.Wrapper isVisible={isVisible} className={type} data-testid="notification-wrapper">
58
+ <S.NotificationWrapper>
55
59
  <S.Row>
56
60
  {getNotificationIcon(type)}
57
61
  <S.Text>{text}</S.Text>
58
62
  <S.ActionsWrapper>
63
+ {subErrors && subErrors.length > 0 && (
64
+ <S.Button onClick={toggleShow} isOpen={isOpen} data-testid="suberrors-button">
65
+ {showText}
66
+ </S.Button>
67
+ )}
59
68
  {btnText && !actionsBelow && <ActionBtn />}
60
69
  {closeButton && (
61
- <S.CloseWrapper onClick={handleClose}>
62
- <Icon name="close" />
70
+ <S.CloseWrapper onClick={handleClose} data-testid="close-button">
71
+ <Icon name="close" size="24" />
63
72
  </S.CloseWrapper>
64
73
  )}
65
74
  </S.ActionsWrapper>
66
75
  </S.Row>
67
76
  {btnText && actionsBelow && <ActionBtn />}
68
- </S.Wrapper>
69
- </>
77
+ </S.NotificationWrapper>
78
+ {isOpen &&
79
+ subErrors &&
80
+ subErrors.length > 0 &&
81
+ subErrors.map((not: { id: number; error: string }) => <SubNotification key={not.id} text={not.error} />)}
82
+ </S.Wrapper>
70
83
  );
71
84
  };
72
85
 
73
- interface IProps {
86
+ export interface INotificationProps {
74
87
  text: string;
75
88
  type: "error" | "info" | "success" | "warning";
76
89
  resetError?: () => void;
@@ -78,6 +91,7 @@ interface IProps {
78
91
  onClick?: () => void;
79
92
  closeButton?: boolean;
80
93
  actionsBelow?: boolean;
94
+ subErrors?: { id: number; error: string }[];
81
95
  }
82
96
 
83
97
  export default Notification;
@@ -1,14 +1,9 @@
1
1
  import styled from "styled-components";
2
2
 
3
- const Wrapper = styled.div<{ isOpen: boolean }>`
3
+ const Wrapper = styled.div<{ isVisible: boolean }>`
4
4
  display: flex;
5
5
  flex-direction: column;
6
- justify-content: center;
7
- align-items: center;
8
- display: ${(p) => (p.isOpen ? "block" : "none")};
9
- padding: ${(p) => `0 ${p.theme.spacing.m}`};
10
- color: ${(p) => p.theme.color.textHighEmphasisInverse};
11
- ${(p) => p.theme.textStyle.uiL};
6
+ display: ${(p) => (p.isVisible ? "block" : "none")};
12
7
  transition: all 0.5s;
13
8
  &.error {
14
9
  background-color: ${(p) => p.theme.colors.error};
@@ -24,6 +19,16 @@ const Wrapper = styled.div<{ isOpen: boolean }>`
24
19
  }
25
20
  `;
26
21
 
22
+ const NotificationWrapper = styled.div`
23
+ display: flex;
24
+ flex-direction: column;
25
+ justify-content: center;
26
+ align-items: center;
27
+ padding: ${(p) => `0 ${p.theme.spacing.m}`};
28
+ color: ${(p) => p.theme.color.textHighEmphasisInverse};
29
+ ${(p) => p.theme.textStyle.uiL};
30
+ `;
31
+
27
32
  const Row = styled.div`
28
33
  display: flex;
29
34
  align-items: center;
@@ -74,4 +79,24 @@ const ActionBtn = styled.button<{ actionsBelow?: boolean }>`
74
79
  margin-bottom: ${(p) => (p.actionsBelow ? p.theme.spacing.s : 0)};
75
80
  `;
76
81
 
77
- export { Wrapper, Text, IconWrapper, Row, CloseWrapper, ActionBtn, ActionsWrapper };
82
+ const Button = styled.div<{ isOpen: boolean }>`
83
+ position: relative;
84
+ ${(p) => p.theme.textStyle.uiXS};
85
+ color: ${(p) => p.theme.colors.textHighEmphasisInverse};
86
+ padding-right: ${(p) => p.theme.spacing.s};
87
+ margin-right: ${(p) => p.theme.spacing.m};
88
+ cursor: pointer;
89
+ :after {
90
+ position: absolute;
91
+ right: 0;
92
+ top: ${(p) => (p.isOpen ? `6px` : `2px`)};
93
+ content: "";
94
+ border: solid ${(p) => p.theme.color.interactiveInverse};
95
+ border-width: 0 2px 2px 0;
96
+ display: inline-block;
97
+ padding: 3px;
98
+ transform: ${(p) => (p.isOpen ? `rotate(-135deg)` : `rotate(45deg)`)};
99
+ }
100
+ `;
101
+
102
+ export { Wrapper, NotificationWrapper, Text, IconWrapper, Row, CloseWrapper, ActionBtn, ActionsWrapper, Button };
@@ -13,13 +13,13 @@ const ReorderArrows = (props: IReorderArrows): JSX.Element => {
13
13
  const Arrows = () => (
14
14
  <>
15
15
  {!isFirst && (
16
- <S.ReorderArrow setMargin={isLast}>
16
+ <S.ReorderArrow data-testid="reorder-last" setMargin={isLast}>
17
17
  {" "}
18
18
  <IconAction icon="FullArrowUp" size="s" onClick={handleUpClick} />{" "}
19
19
  </S.ReorderArrow>
20
20
  )}
21
21
  {!isLast && (
22
- <S.ReorderArrow>
22
+ <S.ReorderArrow data-testid="reorder-first">
23
23
  {" "}
24
24
  <IconAction icon="FullArrowDown" size="s" onClick={handleDownClick} />{" "}
25
25
  </S.ReorderArrow>
@@ -30,7 +30,7 @@ const ReorderArrows = (props: IReorderArrows): JSX.Element => {
30
30
  return <>{showReorderArrows && <Arrows />}</>;
31
31
  };
32
32
 
33
- interface IReorderArrows {
33
+ export interface IReorderArrows {
34
34
  index: number;
35
35
  listLength: number;
36
36
  handleUpClick: (e: any) => void;
@@ -2,46 +2,37 @@ import React from "react";
2
2
 
3
3
  import * as S from "./style";
4
4
 
5
- class ResizeHandle extends React.Component<IResizeHandleProps> {
6
- private _handleResize: any;
7
- private _handleMouseUp: any;
8
-
9
- public initListeners = () => {
10
- window.addEventListener("mousemove", this._handleResize, false);
11
- window.addEventListener("mouseup", this._handleMouseUp, false);
12
- }
13
-
14
- public removeListeners = () => {
15
- window.removeEventListener("mousemove", this._handleResize, false);
16
- window.removeEventListener("mouseup", this._handleMouseUp, false);
17
- }
18
-
19
- public handleMouseDown = () => {
20
- this._handleResize = this.handleResize.bind(this);
21
- this._handleMouseUp = this.handleMouseUp.bind(this);
22
- this.initListeners();
23
- }
24
-
25
- public handleMouseUp = () => {
26
- this.removeListeners();
27
- }
28
-
29
- public handleResize = (e: MouseEvent) => {
5
+ const ResizeHandle = (props: IResizeHandleProps): JSX.Element => {
6
+ const { left } = props;
7
+
8
+ const initListeners = () => {
9
+ window.addEventListener("mousemove", handleResize, false);
10
+ window.addEventListener("mouseup", handleMouseUp, false);
11
+ };
12
+
13
+ const removeListeners = () => {
14
+ window.removeEventListener("mousemove", handleResize, false);
15
+ window.removeEventListener("mouseup", handleMouseUp, false);
16
+ };
17
+
18
+ const handleMouseDown = () => initListeners();
19
+
20
+ const handleMouseUp = () => removeListeners();
21
+
22
+ const handleResize = (e: MouseEvent) => {
30
23
  e.preventDefault();
31
- const { onMouseMove } = this.props;
32
- const newWidth = window.document.body.offsetWidth - e.pageX;
24
+ const { onMouseMove } = props;
25
+ const newWidth = window.document.body.offsetWidth - e.clientX;
33
26
  onMouseMove(newWidth);
34
- }
35
-
36
- public render() {
37
- const { left } = this.props;
38
- return (
39
- <S.HandlerWrapper>
40
- <S.Handler onMouseDown={this.handleMouseDown} left={left ? left : 0} />
41
- </S.HandlerWrapper>
42
- );
43
- }
44
- }
27
+ };
28
+
29
+ return (
30
+ <S.HandlerWrapper>
31
+ <S.Handler onMouseDown={handleMouseDown} left={left ? left : 0} data-testid="handler" />
32
+ </S.HandlerWrapper>
33
+ );
34
+ };
35
+
45
36
  export interface IResizeHandleProps {
46
37
  onMouseMove: (value: number) => void;
47
38
  left?: number;
@@ -3,7 +3,7 @@ import ResizeHandle from "./ResizeHandle";
3
3
 
4
4
  import * as S from "./style";
5
5
 
6
- const ResizePanel = (props: IProps): JSX.Element => {
6
+ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
7
7
  const { leftPanel, rightPanel, fixed = true } = props;
8
8
 
9
9
  const [rwidth, setRwidth] = useState(0);
@@ -12,7 +12,6 @@ const ResizePanel = (props: IProps): JSX.Element => {
12
12
  const rightPanelRef = useRef<HTMLDivElement>(null);
13
13
 
14
14
  useEffect(() => {
15
-
16
15
  if (rightPanelRef.current) {
17
16
  setRwidth(rightPanelRef.current.offsetWidth);
18
17
  }
@@ -22,8 +21,7 @@ const ResizePanel = (props: IProps): JSX.Element => {
22
21
  };
23
22
 
24
23
  window.addEventListener("scroll", handleScroll);
25
-
26
- },[rightPanelRef]);
24
+ }, [rightPanelRef]);
27
25
 
28
26
  const resize = (value: number) => {
29
27
  if (value > 344) {
@@ -33,24 +31,24 @@ const ResizePanel = (props: IProps): JSX.Element => {
33
31
 
34
32
  return (
35
33
  <>
36
- { fixed ?
37
- <S.LeftPanel>
38
- <S.FixedPanel width={rwidth} left={scroll}>{leftPanel}</S.FixedPanel>
39
- </S.LeftPanel>
40
- :
41
- <S.LeftPanel>
42
- {leftPanel}
34
+ {fixed ? (
35
+ <S.LeftPanel data-testid="left-panel-fixed">
36
+ <S.FixedPanel width={rwidth} left={scroll} data-testid="fixed-panel">
37
+ {leftPanel}
38
+ </S.FixedPanel>
43
39
  </S.LeftPanel>
44
- }
45
- <S.RightPanel width={rwidth} ref={rightPanelRef}>
40
+ ) : (
41
+ <S.LeftPanel data-testid="left-panel">{leftPanel}</S.LeftPanel>
42
+ )}
43
+ <S.RightPanel width={rwidth} ref={rightPanelRef} data-testid="right-panel">
46
44
  <ResizeHandle left={scroll} onMouseMove={resize} />
47
- {rightPanel}
45
+ {rightPanel}
48
46
  </S.RightPanel>
49
47
  </>
50
48
  );
51
49
  };
52
50
 
53
- interface IProps {
51
+ export interface IResizePanelProps {
54
52
  leftPanel: JSX.Element | JSX.Element[];
55
53
  rightPanel: JSX.Element | JSX.Element[];
56
54
  fixed?: boolean;
@@ -4,7 +4,7 @@ import { Select } from "../Fields";
4
4
 
5
5
  import * as S from "./style";
6
6
 
7
- const SearchField = (props: IProps): JSX.Element => {
7
+ const SearchField = (props: ISearchFieldProps): JSX.Element => {
8
8
  const {
9
9
  onChange,
10
10
  placeholder,
@@ -56,12 +56,12 @@ const SearchField = (props: IProps): JSX.Element => {
56
56
  const showField = isOpen || !closeOnInactive;
57
57
 
58
58
  return (
59
- <S.Wrapper>
59
+ <S.Wrapper data-testid="search-field-wrapper">
60
60
  {showField ? (
61
- <S.FieldWrapper closeOnInactive={closeOnInactive} disabled={disabled}>
61
+ <S.FieldWrapper closeOnInactive={closeOnInactive} disabled={disabled} data-testid="field-wrapper">
62
62
  {searchFilters && searchFilters.length && (
63
63
  <>
64
- <S.FilterWrapper>
64
+ <S.FilterWrapper data-testid="filter-wrapper">
65
65
  <Select
66
66
  name="filterSelect"
67
67
  value={selectValue}
@@ -82,20 +82,21 @@ const SearchField = (props: IProps): JSX.Element => {
82
82
  placeholder={placeholder}
83
83
  closeOnInactive={closeOnInactive}
84
84
  disabled={disabled}
85
+ data-testid="search-input"
85
86
  />
86
87
  {inputValue.trim() !== "" && searchOnEnter && <S.HelpText>Press ENTER</S.HelpText>}
87
88
  {closeOnInactive || inputValue.length > 0 ? (
88
- <S.IconCloseWrapper onClick={handleClick}>
89
+ <S.IconCloseWrapper onClick={handleClick} data-testid="icon-close-wrapper">
89
90
  <Icon name="close" size="24" />
90
91
  </S.IconCloseWrapper>
91
92
  ) : (
92
- <S.IconSearchWrapper disabled={disabled}>
93
+ <S.IconSearchWrapper disabled={disabled} data-testid="icon-search-wrapper-2">
93
94
  <Icon name="search" size="24" />
94
95
  </S.IconSearchWrapper>
95
96
  )}
96
97
  </S.FieldWrapper>
97
98
  ) : (
98
- <S.IconSearchWrapper disabled={disabled} onClick={toggleField}>
99
+ <S.IconSearchWrapper disabled={disabled} onClick={toggleField} data-testid="icon-search-wrapper">
99
100
  <Icon name="search" size="24" />
100
101
  </S.IconSearchWrapper>
101
102
  )}
@@ -103,7 +104,7 @@ const SearchField = (props: IProps): JSX.Element => {
103
104
  );
104
105
  };
105
106
 
106
- interface IProps {
107
+ export interface ISearchFieldProps {
107
108
  onChange(query: string): void;
108
109
  placeholder?: string;
109
110
  closeOnInactive?: boolean;
@@ -28,7 +28,10 @@ function getAnalytics(siteId: number | string = "global"): (dispatch: any) => Pr
28
28
  };
29
29
  }
30
30
 
31
- function updateAnalytics(analyticsState: IAnalytics, siteId: number | string = "global"): (dispatch: any) => Promise<boolean> {
31
+ function updateAnalytics(
32
+ analyticsState: IAnalytics,
33
+ siteId: number | string = "global"
34
+ ): (dispatch: any) => Promise<boolean> {
32
35
  return async (dispatch) => {
33
36
  try {
34
37
  const { scriptCode, dimensions, groups } = analyticsState;
@@ -38,13 +41,20 @@ function updateAnalytics(analyticsState: IAnalytics, siteId: number | string = "
38
41
  promises.push(analytics.createDimensions(siteId, { dimensions }));
39
42
  promises.push(analytics.createDimensionsGroups(siteId, { groups }));
40
43
  return Promise.all(promises);
41
- }
44
+ };
42
45
 
43
46
  const responseActions = {
44
47
  handleSuccess: () => {
45
48
  dispatch(getAnalytics());
46
49
  },
47
- handleError: (response: any) => appActions.handleError(response)(dispatch),
50
+ handleError: (response: any) => {
51
+ const {
52
+ data: { message },
53
+ } = response;
54
+ const isMultiple = Array.isArray(message) && message.length > 1;
55
+ const msg = isMultiple ? `The action failed due to ${message.length} errors.` : undefined;
56
+ appActions.handleError(response, isMultiple, msg)(dispatch);
57
+ },
48
58
  };
49
59
 
50
60
  return await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
@@ -60,7 +70,7 @@ function updateScriptCode(scriptCode: string, siteId: number): (dispatch: any) =
60
70
  try {
61
71
  const callback = async () => {
62
72
  return analytics.updateScriptCode(siteId, { scriptCode });
63
- }
73
+ };
64
74
 
65
75
  const responseActions = {
66
76
  handleSuccess: () => {
@@ -97,15 +97,25 @@ function resetError(): ISetErrorAction {
97
97
  return { type: RESET_ERROR, payload: { code: undefined, text: "" } };
98
98
  }
99
99
 
100
- function handleError(response: any): (dispatch: Dispatch) => Promise<void> {
100
+ function handleError(response: any, isMultiple = false, msg?: string): (dispatch: Dispatch) => Promise<void> {
101
101
  return async (dispatch) => {
102
- const { data, btnText, actionsBelow, text } = response;
102
+ const {
103
+ data: { code, message },
104
+ btnText,
105
+ actionsBelow,
106
+ text,
107
+ } = response;
108
+
109
+ const firstMsg = Array.isArray(message) ? message[0].error : message || text;
110
+
103
111
  const error = {
104
- code: data?.code,
105
- text: data?.message || text,
112
+ code,
113
+ text: msg ? msg : firstMsg,
106
114
  btnText,
107
115
  actionsBelow,
116
+ subErrors: isMultiple ? message : [],
108
117
  };
118
+
109
119
  dispatch(setError(error));
110
120
  };
111
121
  }
@@ -117,7 +127,7 @@ function login(email: string, password: string, rememberMe: boolean): (dispatch:
117
127
  const loginResponse: any = await global.login(email, password);
118
128
  dispatch(setIsLogging(false));
119
129
  switch (loginResponse.status) {
120
- case 200:
130
+ case 200: {
121
131
  const {
122
132
  data: { token },
123
133
  } = loginResponse;
@@ -130,7 +140,8 @@ function login(email: string, password: string, rememberMe: boolean): (dispatch:
130
140
  localStorage.setItem("user", JSON.stringify(user));
131
141
  }
132
142
  break;
133
- case 403:
143
+ }
144
+ case 403: {
134
145
  const error = {
135
146
  ...loginResponse,
136
147
  btnText: "Lost your password?",
@@ -138,6 +149,7 @@ function login(email: string, password: string, rememberMe: boolean): (dispatch:
138
149
  };
139
150
  handleError(error)(dispatch);
140
151
  break;
152
+ }
141
153
  default:
142
154
  handleError(loginResponse)(dispatch);
143
155
  break;
@@ -30,6 +30,7 @@ export interface IError {
30
30
  text: any;
31
31
  btnText?: string | undefined;
32
32
  actionsBelow?: boolean | undefined;
33
+ subErrors?: any[];
33
34
  }
34
35
 
35
36
  export interface IUser {
@@ -37,7 +37,14 @@ function updateDomainsRobots(robots: IDomainRobot[]): (dispatch: any) => Promise
37
37
  handleSuccess: () => {
38
38
  dispatch(getDomainsRobots());
39
39
  },
40
- handleError: (response: any) => appActions.handleError(response)(dispatch),
40
+ handleError: (response: any) => {
41
+ const {
42
+ data: { message },
43
+ } = response;
44
+ const isMultiple = Array.isArray(message) && message.length > 1;
45
+ const msg = isMultiple ? `The action failed due to ${message.length} errors.` : undefined;
46
+ appActions.handleError(response, isMultiple, msg)(dispatch);
47
+ },
41
48
  };
42
49
 
43
50
  return await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
@@ -309,10 +309,17 @@ function deleteNavigation(
309
309
  const responseActions = {
310
310
  handleSuccess: () => getNavigationByType(type)(dispatch, getState),
311
311
  handleError: (response: any) => {
312
+ const {
313
+ data: { message },
314
+ } = response;
315
+
312
316
  if (isBulk) {
313
317
  getNavigationByType(type)(dispatch, getState);
314
318
  }
315
- appActions.handleError(response)(dispatch);
319
+
320
+ const isMultiple = Array.isArray(message) && message.length > 1;
321
+ const msg = isMultiple ? `The delete action failed due to ${message.length} errors.` : undefined;
322
+ appActions.handleError(response, isMultiple, msg)(dispatch);
316
323
  },
317
324
  };
318
325
 
@@ -335,7 +342,14 @@ function restoreNavigation(
335
342
  try {
336
343
  const responseActions = {
337
344
  handleSuccess: () => getNavigationByType(type)(dispatch, getState),
338
- handleError: (response: any) => appActions.handleError(response)(dispatch),
345
+ handleError: (response: any) => {
346
+ const {
347
+ data: { message },
348
+ } = response;
349
+ const isMultiple = Array.isArray(message) && message.length > 1;
350
+ const msg = isMultiple ? `The restore action failed due to ${message.length} errors.` : undefined;
351
+ appActions.handleError(response, isMultiple, msg)(dispatch);
352
+ },
339
353
  };
340
354
 
341
355
  const callback = async () =>