@griddo/ax 1.64.8 → 1.65.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 (35) hide show
  1. package/package.json +2 -2
  2. package/src/components/CategoryCell/style.tsx +3 -2
  3. package/src/components/Fields/FieldGroup/index.test.tsx +61 -0
  4. package/src/components/Fields/FieldGroup/index.tsx +7 -5
  5. package/src/components/Fields/ImageField/index.tsx +7 -2
  6. package/src/components/FloatingPanel/index.tsx +12 -2
  7. package/src/components/Modal/style.tsx +3 -3
  8. package/src/components/SideModal/index.tsx +6 -1
  9. package/src/components/SideModal/style.tsx +7 -2
  10. package/src/components/TableFilters/CategoryFilter/index.tsx +1 -1
  11. package/src/components/TableFilters/DateFilter/index.tsx +7 -4
  12. package/src/components/TableFilters/LiveFilter/index.tsx +1 -1
  13. package/src/components/TableFilters/NameFilter/index.tsx +7 -4
  14. package/src/components/TableFilters/SiteFilter/index.tsx +1 -1
  15. package/src/components/TableFilters/StatusFilter/index.tsx +7 -4
  16. package/src/components/TableFilters/TranslationsFilter/index.tsx +1 -1
  17. package/src/components/TableFilters/TypeFilter/index.tsx +2 -2
  18. package/src/containers/Navigation/Menu/reducer.tsx +2 -1
  19. package/src/containers/PageEditor/actions.tsx +9 -8
  20. package/src/containers/StructuredData/actions.tsx +4 -3
  21. package/src/forms/errors.tsx +1 -0
  22. package/src/forms/index.tsx +2 -2
  23. package/src/forms/validators.tsx +55 -41
  24. package/src/modules/Content/PageItem/index.tsx +2 -1
  25. package/src/modules/CreatePass/index.tsx +4 -2
  26. package/src/modules/GlobalEditor/index.tsx +48 -35
  27. package/src/modules/Navigation/Menus/List/Table/Item/index.tsx +1 -0
  28. package/src/modules/Navigation/Menus/List/Table/SidePanel/Form/index.tsx +16 -5
  29. package/src/modules/Navigation/Menus/List/Table/SidePanel/index.tsx +31 -22
  30. package/src/modules/Navigation/Menus/List/Table/SidePanel/style.tsx +18 -2
  31. package/src/modules/PageEditor/index.tsx +47 -34
  32. package/src/modules/Sites/SitesList/index.tsx +15 -6
  33. package/src/modules/StructuredData/Form/index.tsx +29 -21
  34. package/src/modules/StructuredData/StructuredDataList/utils.tsx +1 -1
  35. package/src/types/index.tsx +3 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "1.64.8",
4
+ "version": "1.65.0",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Carlos Torres <carlos.torres@secuoyas.com>",
@@ -220,5 +220,5 @@
220
220
  "publishConfig": {
221
221
  "access": "public"
222
222
  },
223
- "gitHead": "4793066098fcdaf760cba760acb8c1fd0e78fa23"
223
+ "gitHead": "4550a3457d35104d6200d1008d961dece1f308a5"
224
224
  }
@@ -1,11 +1,12 @@
1
+ import React from "react";
1
2
  import styled from "styled-components";
2
3
 
3
4
  import { Cell } from "@ax/components/TableList/TableItem/style";
4
5
 
5
- const CategoryCell = styled(Cell)`
6
+ const CategoryCell = styled((props) => <Cell {...props} />)`
6
7
  flex: 0 0 150px;
7
8
  align-items: center;
8
9
  position: relative;
9
10
  `;
10
11
 
11
- export { CategoryCell }
12
+ export { CategoryCell };
@@ -0,0 +1,61 @@
1
+ import React from "react";
2
+ import FieldGroup from "./index";
3
+ import { ThemeProvider } from "styled-components";
4
+ import "@testing-library/jest-dom";
5
+ import { parseTheme } from "@griddo/core";
6
+ import globalTheme from "../../../themes/theme.json";
7
+ import { render, screen, cleanup, fireEvent } from "@testing-library/react";
8
+
9
+ afterEach(cleanup);
10
+
11
+ const defaultProps = {
12
+ title: "title",
13
+ children: "",
14
+ collapsed: true,
15
+ };
16
+
17
+ describe("FieldGroup component rendering", () => {
18
+ it("should render the component", () => {
19
+ render(
20
+ <ThemeProvider theme={parseTheme(globalTheme)}>
21
+ <FieldGroup {...defaultProps} />
22
+ </ThemeProvider>
23
+ );
24
+
25
+ const fieldGroupWrapper = screen.getByTestId("fieldGroupWrapper");
26
+ const fieldGroupLabel = screen.getByTestId("fieldGroupLabel");
27
+ const fieldGroupContent = screen.getByTestId("fieldGroupContent");
28
+
29
+ expect(fieldGroupWrapper).toBeTruthy();
30
+ expect(fieldGroupLabel).toBeTruthy();
31
+ expect(fieldGroupLabel).toHaveTextContent("title");
32
+ expect(fieldGroupContent).toBeTruthy();
33
+ expect(fieldGroupContent).toHaveTextContent("");
34
+ });
35
+
36
+ it("should trigger the onClick", () => {
37
+ const props = {
38
+ title: "title",
39
+ children: "blablabla",
40
+ collapsed: false,
41
+ };
42
+
43
+ const setStateMock = jest.fn();
44
+ const useStateMock: any = (useState: string) => [useState, setStateMock];
45
+ jest.spyOn(React, "useState").mockImplementation(useStateMock);
46
+
47
+ render(
48
+ <ThemeProvider theme={parseTheme(globalTheme)}>
49
+ <FieldGroup {...props} />
50
+ </ThemeProvider>
51
+ );
52
+
53
+ const fieldGroupLabel = screen.getByTestId("fieldGroupLabel");
54
+ const fieldGroupContent = screen.getByTestId("fieldGroupContent");
55
+ expect(fieldGroupLabel).toBeTruthy();
56
+ expect(fieldGroupContent).toBeTruthy();
57
+ expect(fieldGroupContent).toHaveTextContent("blablabla");
58
+ fireEvent.click(fieldGroupLabel);
59
+ expect(setStateMock).toHaveBeenCalled();
60
+ });
61
+ });
@@ -1,21 +1,23 @@
1
- import React, { useState } from "react";
1
+ import React from "react";
2
2
 
3
3
  import * as S from "./style";
4
4
 
5
5
  const FieldGroup = (props: IFieldsGroupProps): React.ReactElement => {
6
6
  const { title, children, collapsed } = props;
7
- const [isOpen, setIsOpen] = useState(!collapsed);
7
+ const [isOpen, setIsOpen] = React.useState(!collapsed);
8
8
 
9
9
  const handleClick = () => {
10
10
  setIsOpen(!isOpen);
11
11
  };
12
12
 
13
13
  return (
14
- <S.Wrapper>
15
- <S.Label onClick={handleClick} isOpen={isOpen}>
14
+ <S.Wrapper data-testid="fieldGroupWrapper">
15
+ <S.Label data-testid="fieldGroupLabel" onClick={handleClick} isOpen={isOpen}>
16
16
  {title}
17
17
  </S.Label>
18
- <S.Content isOpen={isOpen}>{children}</S.Content>
18
+ <S.Content data-testid="fieldGroupContent" isOpen={isOpen}>
19
+ {children}
20
+ </S.Content>
19
21
  </S.Wrapper>
20
22
  );
21
23
  };
@@ -8,7 +8,8 @@ import { useModal } from "@ax/hooks";
8
8
  import * as S from "./style";
9
9
 
10
10
  const ImageField = (props: IImageFieldProps) => {
11
- const { value, error, onChange, selectedContent, disabled, handleValidation, validators, site } = props;
11
+ const { value, error, onChange, selectedContent, disabled, handleValidation, validators, site, setIsGalleryOpened } =
12
+ props;
12
13
 
13
14
  const isLinkableImage = selectedContent && selectedContent.component === "LinkableImage";
14
15
  const hasImage = value && Object.prototype.hasOwnProperty.call(value, "url");
@@ -34,18 +35,21 @@ const ImageField = (props: IImageFieldProps) => {
34
35
  const url = typeof img === "string" ? img : img.url;
35
36
  setPreviewSrc(url);
36
37
  onChange(img);
38
+ setIsGalleryOpened && setIsGalleryOpened();
37
39
  error && handleValidation && handleValidation(url, validators);
38
40
  }
39
41
  };
40
42
 
41
43
  const handleClick = () => {
42
44
  if (!disabled) {
45
+ setIsGalleryOpened && setIsGalleryOpened();
43
46
  toggleModal();
44
47
  }
45
48
  };
46
49
 
47
50
  const handleChange = () => {
48
51
  if (!disabled) {
52
+ setIsGalleryOpened && setIsGalleryOpened();
49
53
  toggleModal();
50
54
  }
51
55
  };
@@ -88,9 +92,10 @@ interface IImageFieldProps {
88
92
  onChange: (value: any) => void;
89
93
  selectedContent: any;
90
94
  disabled?: boolean;
95
+ setIsGalleryOpened?: () => void;
91
96
  handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
92
97
  validators?: Record<string, unknown>;
93
98
  site: ISite;
94
99
  }
95
100
 
96
- export default memo(ImageField);
101
+ export default memo(ImageField);
@@ -7,12 +7,21 @@ import { IconAction } from "@ax/components";
7
7
  import * as S from "./style";
8
8
 
9
9
  const FloatingPanel = (props: IProps): JSX.Element | null => {
10
- const { children, title, isOpen, toggleModal, isOpenedSecond, handlePanel, secondary } = props;
10
+ const {
11
+ children,
12
+ title,
13
+ isOpen,
14
+ toggleModal,
15
+ isOpenedSecond,
16
+ handlePanel,
17
+ secondary,
18
+ closeOnOutsideClick = true,
19
+ } = props;
11
20
 
12
21
  const node = useRef<HTMLElement>(null);
13
22
 
14
23
  const handleClickOutside = (e: any) => {
15
- if ((node.current && node.current.contains(e.target)) || isOpenedSecond) {
24
+ if ((node.current && node.current.contains(e.target)) || isOpenedSecond || !closeOnOutsideClick) {
16
25
  return;
17
26
  }
18
27
  toggleModal();
@@ -54,6 +63,7 @@ interface IProps {
54
63
  toggleModal: () => void;
55
64
  handlePanel?: (value: boolean) => void;
56
65
  secondary?: boolean;
66
+ closeOnOutsideClick?: boolean;
57
67
  }
58
68
 
59
69
  export default memo(FloatingPanel);
@@ -30,7 +30,7 @@ export const ModalOverlay = styled.div`
30
30
  position: fixed;
31
31
  top: 0;
32
32
  left: 0;
33
- z-index: 1040;
33
+ z-index: 1240;
34
34
  width: 100vw;
35
35
  height: 100vh;
36
36
  background: ${(p) => p.theme.color.overlay};
@@ -43,7 +43,7 @@ export const ModalWrapper = styled.div`
43
43
  position: fixed;
44
44
  top: 0;
45
45
  left: 0;
46
- z-index: 1050;
46
+ z-index: 1250;
47
47
  width: 100%;
48
48
  height: 100%;
49
49
  overflow-x: hidden;
@@ -98,4 +98,4 @@ export const ModalFooter = styled.div`
98
98
  margin-left: ${(p) => p.theme.spacing.s};
99
99
  }
100
100
  border-radius: 0 0 4px 4px;
101
- `;
101
+ `;
@@ -4,7 +4,7 @@ import { createPortal } from "react-dom";
4
4
  import { useHandleClickOutside } from "@ax/hooks";
5
5
  import { getDisplayName, filterByCategory } from "@ax/helpers";
6
6
  import SideModalOption from "@ax/components/SideModal/SideModalOption";
7
- import { CheckField, MenuItem, SearchField } from "@ax/components";
7
+ import { CheckField, MenuItem, SearchField, IconAction } from "@ax/components";
8
8
  import { ModuleCategoryInfo } from "@ax/types";
9
9
 
10
10
  import * as S from "./style";
@@ -145,6 +145,11 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
145
145
  <SearchField onChange={setSearchQuery} closeOnInactive />
146
146
  </S.SearchWrapper>
147
147
  )}
148
+ {!showSearch && (
149
+ <S.ButtonWrapper>
150
+ <IconAction icon="close" onClick={toggleModal} />
151
+ </S.ButtonWrapper>
152
+ )}
148
153
  </S.Header>
149
154
  {setDefault && !setDefault.checked && (
150
155
  <S.CheckFieldWrapper>
@@ -6,8 +6,8 @@ const Wrapper = styled.div<{ optionsType?: string }>`
6
6
  position: fixed;
7
7
  left: ${(p) => (p.optionsType && placeRight.includes(p.optionsType) ? "unset" : 0)};
8
8
  right: ${(p) => (p.optionsType && placeRight.includes(p.optionsType) ? 0 : "unset")};
9
- top: ${(p) => p.theme.spacing.xl};
10
- z-index: 10;
9
+ top: 0;
10
+ z-index: 1000;
11
11
 
12
12
  height: 100vh;
13
13
  background: ${(p) => p.theme.colors.uiBackground01};
@@ -71,6 +71,10 @@ const Link = styled.div<{ active: boolean }>`
71
71
  color: ${p => p.active ? p.theme.color.textHighEmphasis : p.theme.color.textMediumEmphasis};
72
72
  `;
73
73
 
74
+ const ButtonWrapper = styled.div`
75
+ margin: 0 0 0 auto;
76
+ `;
77
+
74
78
  export {
75
79
  Wrapper,
76
80
  Content,
@@ -82,4 +86,5 @@ export {
82
86
  FeaturedWrapper,
83
87
  SearchWrapper,
84
88
  Link,
89
+ ButtonWrapper,
85
90
  };
@@ -68,7 +68,7 @@ const CategoryFilter = (props: ICategoryFilterProps): JSX.Element => {
68
68
  <S.HeaderWrapper isActive={isActive}>
69
69
  {structuredData.title}
70
70
  <S.IconsWrapper>
71
- <Icon name="Filter" size="16" />
71
+ {isActive ? <Icon name="Filter" size="16" /> : <Icon name="DownArrow" size="16" />}
72
72
  </S.IconsWrapper>
73
73
  </S.HeaderWrapper>
74
74
  );
@@ -19,10 +19,13 @@ const DateFilter = (props: IStatusFilterProps): JSX.Element => {
19
19
  <S.Date isActive={sortedByDate}>
20
20
  Date
21
21
  <S.IconsWrapper>
22
- {sortedByDate && <SortedStateArrow />}
23
- <S.InteractiveArrow>
24
- <Icon name="DownArrow" size="16" />
25
- </S.InteractiveArrow>
22
+ {sortedByDate ? (
23
+ <SortedStateArrow />
24
+ ) : (
25
+ <S.InteractiveArrow>
26
+ <Icon name="DownArrow" size="16" />
27
+ </S.InteractiveArrow>
28
+ )}
26
29
  </S.IconsWrapper>
27
30
  </S.Date>
28
31
  );
@@ -80,7 +80,7 @@ const LiveFilter = (props: ILiveFilterProps): JSX.Element => {
80
80
  <S.LiveOptions isActive={isActive}>
81
81
  Live
82
82
  <S.IconsWrapper>
83
- <Icon name="Filter" size="16" />
83
+ {isActive ? <Icon name="Filter" size="16" /> : <Icon name="DownArrow" size="16" />}
84
84
  </S.IconsWrapper>
85
85
  </S.LiveOptions>
86
86
  </S.MenuWrapper>
@@ -23,10 +23,13 @@ const NameFilter = (props: INameFilterProps): JSX.Element => {
23
23
  <S.NameHeader isActive={isActive}>
24
24
  {title}
25
25
  <S.IconsWrapper>
26
- {(sortedByTitle || sortedByURL) && <SortedStateArrow />}
27
- <S.InteractiveArrow>
28
- <Icon name="DownArrow" size="16" />
29
- </S.InteractiveArrow>
26
+ {isActive ? (
27
+ <SortedStateArrow />
28
+ ) : (
29
+ <S.InteractiveArrow>
30
+ <Icon name="DownArrow" size="16" />
31
+ </S.InteractiveArrow>
32
+ )}
30
33
  </S.IconsWrapper>
31
34
  </S.NameHeader>
32
35
  );
@@ -78,7 +78,7 @@ const SiteFilter = (props: ISiteFilterProps): JSX.Element => {
78
78
  <S.Site isActive={isActive} center={center}>
79
79
  {label}
80
80
  <S.IconsWrapper>
81
- <Icon name="Filter" size="16" />
81
+ {isActive ? <Icon name="Filter" size="16" /> : <Icon name="DownArrow" size="16" />}
82
82
  </S.IconsWrapper>
83
83
  </S.Site>
84
84
  );
@@ -20,10 +20,13 @@ const StatusFilter = (props: IStatusFilterProps) => {
20
20
  <S.Status isActive={sortedByDate}>
21
21
  Status
22
22
  <S.IconsWrapper>
23
- {sortedByDate && <SortedStateArrow />}
24
- <S.InteractiveArrow>
25
- <Icon name="DownArrow" size="16" />
26
- </S.InteractiveArrow>
23
+ {sortedByDate ? (
24
+ <SortedStateArrow />
25
+ ) : (
26
+ <S.InteractiveArrow>
27
+ <Icon name="DownArrow" size="16" />
28
+ </S.InteractiveArrow>
29
+ )}
27
30
  </S.IconsWrapper>
28
31
  </S.Status>
29
32
  );
@@ -43,7 +43,7 @@ const TranslationsFilter = (props: ITranslationsFilterProps): JSX.Element => {
43
43
  <S.Translations isActive={isActive}>
44
44
  Trans.
45
45
  <S.IconsWrapper>
46
- <Icon name="Filter" size="16" />
46
+ {isActive ? <Icon name="Filter" size="16" /> : <Icon name="DownArrow" size="16" />}
47
47
  </S.IconsWrapper>
48
48
  </S.Translations>
49
49
  );
@@ -12,7 +12,7 @@ const TypeFilter = (props: ITypeFilterProps): JSX.Element => {
12
12
  const [selectedValue, setSelectedValue] = useState(initialState);
13
13
 
14
14
  useEffect(() => {
15
- setSelectedValue([value]);
15
+ value && setSelectedValue([value]);
16
16
  }, [value]);
17
17
 
18
18
  const setFilterQuery = (selection: any) => {
@@ -30,7 +30,7 @@ const TypeFilter = (props: ITypeFilterProps): JSX.Element => {
30
30
  <S.Types isActive={isActive}>
31
31
  Types
32
32
  <S.IconsWrapper>
33
- <Icon name="Filter" size="16" />
33
+ {isActive ? <Icon name="Filter" size="16" /> : <Icon name="DownArrow" size="16" />}
34
34
  </S.IconsWrapper>
35
35
  </S.Types>
36
36
  );
@@ -34,6 +34,7 @@ const initialState = {
34
34
  item: null,
35
35
  form: {
36
36
  itemLink: null,
37
+ itemImage: null,
37
38
  itemLabel: "",
38
39
  itemAuxText: "",
39
40
  type: "link",
@@ -59,4 +60,4 @@ function reducer(state = initialState, action: MenuActionsCreators): IMenuState
59
60
  }
60
61
  }
61
62
 
62
- export { initialState as menuInitialState, reducer as menuReducer };
63
+ export { initialState as menuInitialState, reducer as menuReducer };
@@ -24,7 +24,7 @@ import {
24
24
  getLastModuleEditorID,
25
25
  getLastComponentEditorID,
26
26
  replaceElements,
27
- findMandatoryFieldsErrors,
27
+ findFieldsErrors,
28
28
  getParentKey,
29
29
  } from "@ax/forms";
30
30
  import { appActions } from "@ax/containers/App";
@@ -292,8 +292,8 @@ function getPage(pageID?: number, global?: boolean): (dispatch: Dispatch, getSta
292
292
  const pageLiveStatus = page.draftFromPage
293
293
  ? pageStatus.MODIFIED
294
294
  : isNewTranslation
295
- ? pageStatus.OFFLINE
296
- : page.liveStatus.status;
295
+ ? pageStatus.OFFLINE
296
+ : page.liveStatus.status;
297
297
 
298
298
  if (isReqOk(response.status)) {
299
299
  addTemplate(page.templateId)(dispatch);
@@ -741,7 +741,7 @@ function generatePageContent(editorContent: IPage, dispatch: Dispatch, getState:
741
741
  dataPacks: { configFormData },
742
742
  } = getState();
743
743
  const { header, footer, isGlobal } = editorContent;
744
- const { defaultHeader, defaultFooter } = configFormData.templates && configFormData.templates[template] || {};
744
+ const { defaultHeader, defaultFooter } = (configFormData.templates && configFormData.templates[template]) || {};
745
745
  const { header: pageHeader, footer: pageFooter } = getPageNavigation(
746
746
  header || defaultHeader,
747
747
  footer || defaultFooter,
@@ -928,19 +928,20 @@ function getTemplateConfig(template: string): (dispatch: Dispatch, getState: any
928
928
  };
929
929
  }
930
930
 
931
- function validatePage(): (dispatch: Dispatch, getState: any) => Promise<boolean> {
931
+ function validatePage(publish: boolean): (dispatch: Dispatch, getState: any) => Promise<boolean> {
932
932
  return async (dispatch, getState) => {
933
933
  try {
934
934
  const { editorContent } = getStateValues(getState);
935
935
 
936
- const errors = findMandatoryFieldsErrors(editorContent);
936
+ const errors = findFieldsErrors(editorContent);
937
937
  dispatch(setErrors(errors));
938
938
  if (errors.length === 0) {
939
- dispatch(setValidated(true));
939
+ !publish && dispatch(setValidated(true));
940
+ return true;
940
941
  } else {
941
942
  dispatch(setValidated(false));
943
+ return false;
942
944
  }
943
- return true;
944
945
  } catch (e) {
945
946
  console.log(e);
946
947
  return false;
@@ -483,7 +483,7 @@ function setStatusStructuredDataContent(
483
483
  };
484
484
  }
485
485
 
486
- function validateForm(): (dispatch: Dispatch, getState: any) => Promise<boolean> {
486
+ function validateForm(publish?: boolean): (dispatch: Dispatch, getState: any) => Promise<boolean> {
487
487
  return async (dispatch, getState) => {
488
488
  try {
489
489
  const {
@@ -496,11 +496,12 @@ function validateForm(): (dispatch: Dispatch, getState: any) => Promise<boolean>
496
496
  const errors = findMandatoryStructuredDataErrors(content, schema);
497
497
  dispatch(setErrors(errors));
498
498
  if (errors.length === 0) {
499
- dispatch(setValidated(true));
499
+ !publish && dispatch(setValidated(true));
500
+ return true;
500
501
  } else {
501
502
  dispatch(setValidated(false));
503
+ return false;
502
504
  }
503
- return true;
504
505
  } catch (e) {
505
506
  console.log(e);
506
507
  return false;
@@ -39,6 +39,7 @@ const ERRORS: Record<string, string> = {
39
39
  ERR038: "Sorry, the content you are trying to link is broken or unpublished.",
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
+ ERR041: "Sorry, the password doesn’t match.",
42
43
  };
43
44
 
44
45
  export { ERRORS };
@@ -23,7 +23,7 @@ import {
23
23
  replaceElements,
24
24
  } from "./elements";
25
25
  import { getInnerFields, getStructuredDataInnerFields } from "./fields";
26
- import { getValidity, findMandatoryFieldsErrors, findMandatoryStructuredDataErrors } from "./validators";
26
+ import { getValidity, findFieldsErrors, findMandatoryStructuredDataErrors } from "./validators";
27
27
 
28
28
  export {
29
29
  parseData,
@@ -49,6 +49,6 @@ export {
49
49
  getLastComponentEditorID,
50
50
  getParentKey,
51
51
  getValidity,
52
- findMandatoryFieldsErrors,
52
+ findFieldsErrors,
53
53
  findMandatoryStructuredDataErrors,
54
54
  };
@@ -7,12 +7,14 @@ const VALIDATORS = {
7
7
  const isValid = flag && val !== undefined && val !== null && val.length > 0;
8
8
  return { isValid, errorCode: "ERR001" };
9
9
  },
10
- maxChar: (val: string, len: number): IError => {
11
- const isValid = val.length <= len;
10
+ maxChar: (val: string | null, max: number): IError => {
11
+ const length = val ? val.length : 0;
12
+ const isValid = length <= max;
12
13
  return { isValid, errorCode: "ERR003" };
13
14
  },
14
- minChar: (val: string, len: number): IError => {
15
- const isValid = val.length >= len;
15
+ minChar: (val: string | null, min: number): IError => {
16
+ const length = val ? val.length : 0;
17
+ const isValid = length >= min;
16
18
  return { isValid, errorCode: "ERR002" };
17
19
  },
18
20
  maxValue: (val: string, len: number): IError => {
@@ -94,6 +96,10 @@ const VALIDATORS = {
94
96
  return { isValid: true, errorCode: "" };
95
97
  }
96
98
  },
99
+ isSamePass: (pass1: string, pass2: string): IError => {
100
+ const isValid = pass1 === pass2;
101
+ return { isValid, errorCode: "ERR041" };
102
+ },
97
103
  };
98
104
 
99
105
  const getErrorMessage = (key: string, val: number | string | null): string => {
@@ -148,38 +154,17 @@ const isEmptyField = (value: any, fieldType: string) => {
148
154
  }
149
155
  };
150
156
 
151
- const getMandatoryFields = (fields: any[]): any[] => {
152
- let mandatory: any[] = [];
157
+ const getValidationErrors = (
158
+ fields: Record<string, unknown>[],
159
+ current: any,
160
+ name: string | null,
161
+ tab: string,
162
+ template: boolean
163
+ ) => {
164
+ let errors: IErrorItem[] = [];
165
+
153
166
  fields.forEach((field: any) => {
154
167
  if (field.mandatory) {
155
- mandatory.push(field);
156
- }
157
-
158
- if (field.fields) {
159
- const innerMandatories = getMandatoryFields(field.fields);
160
- if (innerMandatories) {
161
- mandatory = [...mandatory, ...innerMandatories];
162
- }
163
- }
164
- });
165
- return mandatory;
166
- };
167
-
168
- const getMandatoryFieldsErrors = (content: any, current: any, name: string | null, tab: string, template: boolean) => {
169
- let mandatoryFields = [];
170
- const errors: IErrorItem[] = [];
171
-
172
- const conditionalKey = content.find((field: { type: string }) => field.type === "ConditionalField")?.key;
173
- if (conditionalKey) {
174
- mandatoryFields = getMandatoryFields(content).filter((field) => {
175
- return field.condition === undefined || field.condition === current[conditionalKey];
176
- });
177
- } else {
178
- mandatoryFields = getMandatoryFields(content);
179
- }
180
-
181
- mandatoryFields &&
182
- mandatoryFields.forEach((field: any) => {
183
168
  const isEmpty = isEmptyField(current[field.key], field.type);
184
169
  if (isEmpty) {
185
170
  errors.push({
@@ -194,12 +179,41 @@ const getMandatoryFieldsErrors = (content: any, current: any, name: string | nul
194
179
  template,
195
180
  });
196
181
  }
197
- });
182
+ }
183
+
184
+ if (Object.prototype.hasOwnProperty.call(field, "validators")) {
185
+ const { isValid, errorText } = getValidity(field.validators, current[field.key]);
186
+
187
+ if (!isValid) {
188
+ errors.push({
189
+ type: "Error",
190
+ message: errorText,
191
+ validator: field.validators,
192
+ editorID: current.editorID ? current.editorID : null,
193
+ component: current.component ? current.component : null,
194
+ name: name ? name : field.title,
195
+ key: field.key,
196
+ tab,
197
+ template,
198
+ });
199
+ }
200
+ }
201
+
202
+ if (Object.prototype.hasOwnProperty.call(field, "fields") && field.fields.length) {
203
+ const innerFields =
204
+ field.type === "ConditionalField"
205
+ ? field.fields.filter((f: any) => f.condition === undefined || f.condition === current[field.key])
206
+ : field.fields;
207
+
208
+ const innerErrors = getValidationErrors(innerFields, current, name, tab, template);
209
+ errors = [...errors, ...innerErrors];
210
+ }
211
+ });
198
212
 
199
213
  return errors;
200
214
  };
201
215
 
202
- const findMandatoryFieldsErrors = (content: any): IErrorItem[] => {
216
+ const findFieldsErrors = (content: any): IErrorItem[] => {
203
217
  const queue: any[] = [content];
204
218
  let errors: IErrorItem[] = [];
205
219
  while (queue.length > 0) {
@@ -209,14 +223,14 @@ const findMandatoryFieldsErrors = (content: any): IErrorItem[] => {
209
223
  let schemaErrors: any[] = [];
210
224
  const schema: any = getSchema(currentObj.component);
211
225
  schema.configTabs.forEach((tab: any) => {
212
- const tabErrors: IErrorItem[] = getMandatoryFieldsErrors(
226
+ const valErrors: IErrorItem[] = getValidationErrors(
213
227
  tab.fields,
214
228
  currentObj,
215
229
  schema.displayName,
216
230
  tab.title,
217
231
  false
218
232
  );
219
- schemaErrors = [...schemaErrors, ...tabErrors];
233
+ schemaErrors = [...schemaErrors, ...valErrors];
220
234
  });
221
235
  errors = [...errors, ...schemaErrors];
222
236
  }
@@ -224,7 +238,7 @@ const findMandatoryFieldsErrors = (content: any): IErrorItem[] => {
224
238
  if (currentObj.type === "template") {
225
239
  const templateSchema: any = getTemplate(currentObj.templateType);
226
240
  const { content } = templateSchema;
227
- const templateErrors: IErrorItem[] = getMandatoryFieldsErrors(
241
+ const templateErrors: IErrorItem[] = getValidationErrors(
228
242
  content,
229
243
  currentObj,
230
244
  templateSchema.displayName,
@@ -247,7 +261,7 @@ const findMandatoryFieldsErrors = (content: any): IErrorItem[] => {
247
261
  };
248
262
 
249
263
  const findMandatoryStructuredDataErrors = (content: any, schema: any): IErrorItem[] => {
250
- const errors: IErrorItem[] = getMandatoryFieldsErrors(schema.fields, content, null, "", false);
264
+ const errors: IErrorItem[] = getValidationErrors(schema.fields, content, null, "", false);
251
265
  return errors;
252
266
  };
253
267
 
@@ -256,4 +270,4 @@ interface IError {
256
270
  errorCode: string;
257
271
  }
258
272
 
259
- export { getValidity, findMandatoryFieldsErrors, findMandatoryStructuredDataErrors };
273
+ export { getValidity, findFieldsErrors, findMandatoryStructuredDataErrors };