@griddo/ax 11.9.3 → 11.9.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.9.3",
4
+ "version": "11.9.4",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -119,6 +119,7 @@
119
119
  "react-draft-wysiwyg": "1.15.0",
120
120
  "react-error-boundary": "4.0.13",
121
121
  "react-froala-wysiwyg": "4.0.4",
122
+ "react-qr-code": "2.0.18",
122
123
  "react-redux": "7.2.9",
123
124
  "react-refresh": "0.14.2",
124
125
  "react-router-dom": "5.1.2",
@@ -224,5 +225,5 @@
224
225
  "publishConfig": {
225
226
  "access": "public"
226
227
  },
227
- "gitHead": "f31de0826e406056c03e32ee1752196c6115aa19"
228
+ "gitHead": "1f7eb12dd7b7ac163c4e259f9b180446eb529620"
228
229
  }
@@ -15,7 +15,12 @@ import { parseTheme } from "@ax/helpers";
15
15
  import globalTheme from "@ax/themes/theme.json";
16
16
  import UserItem, { IUserItemProps } from "./../../../../../modules/Users/UserList/UserItem";
17
17
 
18
- import { appStoreMock, sitesStoreGlobalMock, usersSiteStoreMock, usersStoreMock } from "../../../../../__mocks__/store/UserList";
18
+ import {
19
+ appStoreMock,
20
+ sitesStoreGlobalMock,
21
+ usersSiteStoreMock,
22
+ usersStoreMock,
23
+ } from "../../../../../__mocks__/store/UserList";
19
24
  import { defaultStore } from "./../../../../../__mocks__/store/GenericStore";
20
25
 
21
26
  const middlewares: Middleware[] = [thunk];
@@ -29,9 +34,10 @@ let mockedAxios: MockAdapter;
29
34
 
30
35
  const onChangeMock = jest.fn();
31
36
  const onClickMock = jest.fn();
32
- const updateUserMock = jest.fn();
33
37
  const deleteUserMock = jest.fn();
34
38
  const resendInvitationMock = jest.fn();
39
+ const getUsersMock = jest.fn();
40
+ const removeUserFromSiteMock = jest.fn();
35
41
  const toggleDeleteToastMock = jest.fn();
36
42
  const defaultProps: IUserItemProps = {
37
43
  user: {
@@ -55,7 +61,8 @@ const defaultProps: IUserItemProps = {
55
61
  width: 1536,
56
62
  height: 961,
57
63
  orientation: "L",
58
- site: "global",
64
+ site: null,
65
+ folderId: null,
59
66
  },
60
67
  failed: 0,
61
68
  timezone: "Europe/Madrid",
@@ -162,12 +169,14 @@ const defaultProps: IUserItemProps = {
162
169
  },
163
170
  ],
164
171
  isSelected: false,
172
+ hoverCheck: false,
165
173
  onChange: onChangeMock,
166
174
  onClick: onClickMock,
167
175
  deleteUser: deleteUserMock,
168
- updateUser: updateUserMock,
169
176
  resendInvitation: resendInvitationMock,
170
177
  toggleDeleteToast: toggleDeleteToastMock,
178
+ getUsers: getUsersMock,
179
+ removeUserFromSite: removeUserFromSiteMock,
171
180
  };
172
181
 
173
182
  beforeAll(() => {
@@ -348,8 +357,8 @@ describe("UserItem events", () => {
348
357
  };
349
358
 
350
359
  const store = mockStore({ ...defaultStore, ...initialStore });
351
- mockedAxios.onPut("undefined/user/171").reply(200, {});
352
- mockedAxios.onGet(`undefined/users?order=dateCreated`).reply(200, []);
360
+ mockedAxios.onDelete("undefined/user/171/site/2").reply(200, {});
361
+ mockedAxios.onGet(`undefined/users`).reply(200, []);
353
362
 
354
363
  const Component = (
355
364
  <Router history={history}>
@@ -375,10 +384,10 @@ describe("UserItem events", () => {
375
384
  await user.click(removeButton);
376
385
  await waitFor(() => expect(screen.queryByTestId("modal-delete-content")).toBeFalsy());
377
386
 
378
- expect(store.getActions()).toContainEqual({
387
+ /*expect(store.getActions()).toContainEqual({
379
388
  payload: { users: [] },
380
389
  type: "users/SET_USERS",
381
- });
390
+ });*/
382
391
  });
383
392
 
384
393
  it("should resend invitation", async () => {
@@ -4,8 +4,8 @@ import { connect } from "react-redux";
4
4
  import { FieldContainer } from "@ax/components";
5
5
  import { navigationActions } from "@ax/containers/Navigation";
6
6
  import { getInnerFields } from "@ax/forms";
7
- import { IRootState } from "@ax/types";
8
- import { areEqual, filterThemeModules } from "@ax/helpers";
7
+ import { IRootState, ISchemaField } from "@ax/types";
8
+ import { areEqual, filterThemeModules, getFormTemplate } from "@ax/helpers";
9
9
 
10
10
  const NavConnectedField = (props: any): JSX.Element => {
11
11
  const {
@@ -28,6 +28,7 @@ const NavConnectedField = (props: any): JSX.Element => {
28
28
  moduleCopy,
29
29
  themeElements,
30
30
  isReadOnly,
31
+ tab,
31
32
  } = props;
32
33
 
33
34
  const updateValue = (key: string, value: any) => {
@@ -47,6 +48,8 @@ const NavConnectedField = (props: any): JSX.Element => {
47
48
  const isDefaultNav = defaultNavId === selectedContentId;
48
49
  const isDisabled = (isSetAsDefaultField && isDefaultNav) || isReadOnly;
49
50
  const isConditional = field.type === "ConditionalField";
51
+ const isFormPage = selectedContent.component === "FormPage";
52
+ const isFormTemplate = field.type === "formTemplate";
50
53
 
51
54
  let isTemplateActivated = true;
52
55
  if (selectedContent.template) {
@@ -73,6 +76,47 @@ const NavConnectedField = (props: any): JSX.Element => {
73
76
 
74
77
  const filteredWhiteList = whiteList ? filterThemeModules(themeElements, whiteList) : whiteList;
75
78
 
79
+ if (isFormTemplate) {
80
+ const template = getFormTemplate(selectedContent[field.key].templateType);
81
+ if (!template) return <></>;
82
+
83
+ const templateFields: ISchemaField[] = template[tab].filter((templateField: ISchemaField) => {
84
+ const isHidden = templateField.hidden || !templateField.overwrite;
85
+ return !isHidden;
86
+ });
87
+
88
+ return (
89
+ <>
90
+ {templateFields.map((formField) => {
91
+ const { key, whiteList = [], disabled } = formField;
92
+ return (
93
+ <FieldContainer
94
+ whiteList={whiteList}
95
+ key={key}
96
+ objKey={key}
97
+ field={formField}
98
+ selectedContent={selectedContent}
99
+ goTo={goTo}
100
+ updateValue={updateValue}
101
+ pages={pages}
102
+ actions={actions}
103
+ site={site}
104
+ lang={lang}
105
+ disabled={disabled}
106
+ innerFields={innerFields}
107
+ theme={theme}
108
+ moduleCopy={moduleCopy}
109
+ />
110
+ );
111
+ })}
112
+ </>
113
+ );
114
+ }
115
+
116
+ if (isFormPage) {
117
+ return <></>;
118
+ }
119
+
76
120
  return (
77
121
  <FieldContainer
78
122
  whiteList={filteredWhiteList}
@@ -105,6 +149,7 @@ const mapStateToProps = (state: IRootState) => ({
105
149
  menus: state.menu.savedMenus,
106
150
  moduleCopy: state.navigation.moduleCopy,
107
151
  themeElements: state.sites.themeElements,
152
+ tab: state.navigation.tab,
108
153
  });
109
154
 
110
155
  const mapDispatchToProps = {
@@ -7,9 +7,17 @@ import { getNullValue } from "@ax/helpers";
7
7
  const LinkField = (props: ILinkFieldProps): JSX.Element => {
8
8
  const { value, onChange, disabled, whiteList, goTo, theme, actions, selectedContent, objKey } = props;
9
9
 
10
- const handleTextChange = (newValue: string) => onChange({ ...value, text: newValue });
10
+ const defaultValues = {
11
+ text: "",
12
+ linkType: "url",
13
+ url: null,
14
+ modal: {},
15
+ };
16
+
17
+ const safeValue = value ? value : defaultValues;
18
+ const handleTextChange = (newValue: string) => onChange({ ...safeValue, text: newValue });
11
19
  const handleLinkTypeChange = (newValue: string) => {
12
- let newObjValue: ILinkField = { ...value, linkType: newValue };
20
+ let newObjValue: ILinkField = { ...safeValue, linkType: newValue };
13
21
 
14
22
  if (newValue === "url") {
15
23
  newObjValue = { ...newObjValue, modal: getNullValue(newObjValue.modal, true) };
@@ -21,8 +29,8 @@ const LinkField = (props: ILinkFieldProps): JSX.Element => {
21
29
 
22
30
  onChange(newObjValue);
23
31
  };
24
- const handleUrlChange = (newValue: IUrlField) => onChange({ ...value, url: newValue });
25
- const handleModalChange = (newValue: any) => onChange({ ...value, modal: newValue });
32
+ const handleUrlChange = (newValue: IUrlField) => onChange({ ...safeValue, url: newValue });
33
+ const handleModalChange = (newValue: any) => onChange({ ...safeValue, modal: newValue });
26
34
 
27
35
  const modalFieldSchema = {
28
36
  title: "Modal",
@@ -48,7 +56,7 @@ const LinkField = (props: ILinkFieldProps): JSX.Element => {
48
56
  title="Text"
49
57
  name="text"
50
58
  fieldType="TextField"
51
- value={value.text}
59
+ value={safeValue.text}
52
60
  onChange={handleTextChange}
53
61
  disabled={disabled}
54
62
  />
@@ -56,7 +64,7 @@ const LinkField = (props: ILinkFieldProps): JSX.Element => {
56
64
  title="Type of link"
57
65
  name="linkType"
58
66
  fieldType="ConditionalField"
59
- value={value.linkType}
67
+ value={safeValue.linkType}
60
68
  onChange={handleLinkTypeChange}
61
69
  disabled={disabled}
62
70
  options={[
@@ -77,7 +85,7 @@ const LinkField = (props: ILinkFieldProps): JSX.Element => {
77
85
  title="Modal"
78
86
  name="modal"
79
87
  fieldType="ComponentContainer"
80
- value={value.modal}
88
+ value={safeValue.modal}
81
89
  onChange={handleModalChange}
82
90
  disabled={disabled}
83
91
  whiteList={whiteList}
@@ -96,7 +104,7 @@ const LinkField = (props: ILinkFieldProps): JSX.Element => {
96
104
  title="URL"
97
105
  name="url"
98
106
  fieldType="UrlField"
99
- value={value.url}
107
+ value={safeValue.url}
100
108
  onChange={handleUrlChange}
101
109
  disabled={disabled}
102
110
  mandatory
@@ -111,7 +119,7 @@ const LinkField = (props: ILinkFieldProps): JSX.Element => {
111
119
  };
112
120
 
113
121
  interface ILinkFieldProps {
114
- value: ILinkField;
122
+ value: ILinkField | null;
115
123
  onChange: (value: ILinkField | null) => void;
116
124
  disabled?: boolean;
117
125
  whiteList: string[];
@@ -0,0 +1,10 @@
1
+ import * as React from "react";
2
+ const SvgQrCode = (props) => (
3
+ <svg xmlns="http://www.w3.org/2000/svg" width={24} height={24} fill="none" {...props}>
4
+ <path
5
+ fill="#5057FF"
6
+ d="M3 10V4c0-.283.096-.52.288-.712A.968.968 0 0 1 4 3h6c.283 0 .52.096.713.288.191.191.287.429.287.712v6c0 .283-.096.52-.287.713A.968.968 0 0 1 10 11H4a.967.967 0 0 1-.712-.287A.968.968 0 0 1 3 10Zm2-1h4V5H5v4ZM3 20v-6c0-.283.096-.52.288-.713A.967.967 0 0 1 4 13h6c.283 0 .52.096.713.287.191.192.287.43.287.713v6c0 .283-.096.52-.287.712A.968.968 0 0 1 10 21H4a.967.967 0 0 1-.712-.288A.968.968 0 0 1 3 20Zm2-1h4v-4H5v4Zm8-9V4c0-.283.096-.52.287-.712A.968.968 0 0 1 14 3h6c.283 0 .52.096.712.288.192.191.288.429.288.712v6c0 .283-.096.52-.288.713A.968.968 0 0 1 20 11h-6a.968.968 0 0 1-.713-.287A.968.968 0 0 1 13 10Zm2-1h4V5h-4v4Zm4 12v-2h2v2h-2Zm-6-6v-2h2v2h-2Zm2 2v-2h2v2h-2Zm-2 2v-2h2v2h-2Zm2 2v-2h2v2h-2Zm2-2v-2h2v2h-2Zm0-4v-2h2v2h-2Zm2 2v-2h2v2h-2Z"
7
+ />
8
+ </svg>
9
+ );
10
+ export default SvgQrCode;
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M3 10V4C3 3.71667 3.09583 3.47917 3.2875 3.2875C3.47917 3.09583 3.71667 3 4 3H10C10.2833 3 10.5208 3.09583 10.7125 3.2875C10.9042 3.47917 11 3.71667 11 4V10C11 10.2833 10.9042 10.5208 10.7125 10.7125C10.5208 10.9042 10.2833 11 10 11H4C3.71667 11 3.47917 10.9042 3.2875 10.7125C3.09583 10.5208 3 10.2833 3 10ZM5 9H9V5H5V9ZM3 20V14C3 13.7167 3.09583 13.4792 3.2875 13.2875C3.47917 13.0958 3.71667 13 4 13H10C10.2833 13 10.5208 13.0958 10.7125 13.2875C10.9042 13.4792 11 13.7167 11 14V20C11 20.2833 10.9042 20.5208 10.7125 20.7125C10.5208 20.9042 10.2833 21 10 21H4C3.71667 21 3.47917 20.9042 3.2875 20.7125C3.09583 20.5208 3 20.2833 3 20ZM5 19H9V15H5V19ZM13 10V4C13 3.71667 13.0958 3.47917 13.2875 3.2875C13.4792 3.09583 13.7167 3 14 3H20C20.2833 3 20.5208 3.09583 20.7125 3.2875C20.9042 3.47917 21 3.71667 21 4V10C21 10.2833 20.9042 10.5208 20.7125 10.7125C20.5208 10.9042 20.2833 11 20 11H14C13.7167 11 13.4792 10.9042 13.2875 10.7125C13.0958 10.5208 13 10.2833 13 10ZM15 9H19V5H15V9ZM19 21V19H21V21H19ZM13 15V13H15V15H13ZM15 17V15H17V17H15ZM13 19V17H15V19H13ZM15 21V19H17V21H15ZM17 19V17H19V19H17ZM17 15V13H19V15H17ZM19 17V15H21V17H19Z" fill="#5057FF"/>
3
+ </svg>
@@ -63,7 +63,7 @@ const LanguageMenu = (props: ILanguageMenuProps): JSX.Element => {
63
63
 
64
64
  return (
65
65
  <S.ActionMenu data-testid="language-menu">
66
- {!!availableLanguages.length && availableLanguages.map((item) => languageMenuItem(item))}
66
+ {!!availableLanguages?.length && availableLanguages.map((item) => languageMenuItem(item))}
67
67
  </S.ActionMenu>
68
68
  );
69
69
  };
@@ -800,10 +800,15 @@ function updateEditorContent(
800
800
  navigation: { selectedContent, editorContent },
801
801
  } = getState();
802
802
 
803
- const updatedSelectedContent = updateByEditorID(selectedContent, selectedEditorID, key, value);
804
- const updatedEditorContent = updateByEditorID(editorContent, selectedEditorID, key, value);
805
- setSelectedContent(updatedSelectedContent);
806
- dispatch(setEditorContent({ ...updatedEditorContent }));
803
+ const clonedSelected = deepClone(selectedContent);
804
+ const clonedEditor = deepClone(editorContent);
805
+
806
+ const updatedSelectedContent = updateByEditorID(clonedSelected, selectedEditorID, key, value);
807
+ const updatedEditorContent = updateByEditorID(clonedEditor, selectedEditorID, key, value);
808
+ dispatch(setSelectedDefaultContent(updatedSelectedContent));
809
+ dispatch(setEditorContent(updatedEditorContent));
810
+
811
+ generateContent(updatedEditorContent)(dispatch, getState);
807
812
  };
808
813
  }
809
814
 
@@ -1093,15 +1093,16 @@ function updateEditorContent(
1093
1093
  } = getState();
1094
1094
 
1095
1095
  const clonedContent = deepClone(editorContent);
1096
+ const clonedSelected = deepClone(selectedContent);
1096
1097
 
1097
- if (selectedContent.component === "FormPage") {
1098
+ if (clonedSelected.component === "FormPage") {
1098
1099
  protectFormKeys(clonedContent, key);
1099
1100
  }
1100
1101
 
1101
- const updatedSelectedContent = updateByEditorID(selectedContent, selectedEditorID, key, value);
1102
+ const updatedSelectedContent = updateByEditorID(clonedSelected, selectedEditorID, key, value);
1102
1103
  let updatedEditorContent = updateByEditorID(clonedContent, selectedEditorID, key, value);
1103
1104
 
1104
- setSelectedContent(updatedSelectedContent);
1105
+ dispatch(setSelectedPageContent(updatedSelectedContent));
1105
1106
  dispatch(setEditorContent(updatedEditorContent));
1106
1107
 
1107
1108
  if (lastTimeout) {
@@ -321,7 +321,7 @@ const GlobalPageItem = (props: IGlobalPageItemProps): JSX.Element => {
321
321
  const getSelectedPageLanguage = (language: ILanguage) =>
322
322
  pageLanguages.find((lang: IPageLanguage) => lang.languageId === language.id);
323
323
 
324
- const handleLanguage = (language: ILanguage) => () => {
324
+ const handleLanguage = (language: ILanguage) => {
325
325
  if (!languageActions || !languageActions.setLanguage) return;
326
326
 
327
327
  const { locale, id } = language;
@@ -0,0 +1,47 @@
1
+ import React from "react";
2
+ import QRCode from "react-qr-code";
3
+
4
+ import { IModal } from "@ax/types";
5
+ import { Modal } from "@ax/components";
6
+
7
+ import * as S from "./style";
8
+
9
+ const LinkDeviceModal = (props: ILinkDeviceModalProps): JSX.Element => {
10
+ const { isOpen, toggleModal, token } = props;
11
+
12
+ const showQRCode = () => {
13
+ const apiUrl = process.env.GRIDDO_API_URL || process.env.REACT_APP_API_ENDPOINT;
14
+ if (!apiUrl || !token) return <></>;
15
+
16
+ const content = {
17
+ apiUrl: apiUrl.endsWith("/") ? apiUrl.slice(0, -1) : apiUrl,
18
+ token,
19
+ };
20
+
21
+ return (
22
+ <S.QRWrapper>
23
+ <QRCode value={JSON.stringify(content)} size={200} />
24
+ </S.QRWrapper>
25
+ );
26
+ };
27
+
28
+ return (
29
+ <Modal isOpen={isOpen} hide={toggleModal} title="Link Device" size="S" height={406}>
30
+ <S.ModalContent>
31
+ <div>
32
+ <p>
33
+ Download the <strong>Griddo app</strong>, open it and <strong>scan this QR code</strong> to link your device
34
+ to your user account.
35
+ </p>
36
+ </div>
37
+ {showQRCode()}
38
+ </S.ModalContent>
39
+ </Modal>
40
+ );
41
+ };
42
+
43
+ interface ILinkDeviceModalProps extends IModal {
44
+ token: string;
45
+ }
46
+
47
+ export { LinkDeviceModal };
@@ -6,12 +6,13 @@ import { usersActions } from "@ax/containers/Users";
6
6
  import { sitesActions } from "@ax/containers/Sites";
7
7
  import { Loading, MainWrapper } from "@ax/components";
8
8
  import { IGetRoles, IRootState, ISite, IUser } from "@ax/types";
9
- import { useShouldBeSaved, useURLSearchParam } from "@ax/hooks";
9
+ import { useModal, useShouldBeSaved, useURLSearchParam } from "@ax/hooks";
10
10
 
11
11
  import UserForm from "../UserForm";
12
+ import { LinkDeviceModal } from "./atoms";
12
13
 
13
14
  const Profile = (props: IProps) => {
14
- const { user, getUser, updateUser, isSaving, isLoading, getSites, getRoles, currentSiteInfo } = props;
15
+ const { user, getUser, updateUser, isSaving, isLoading, getSites, getRoles, currentSiteInfo, token } = props;
15
16
 
16
17
  if (!user) {
17
18
  throw new Error(`ERROR: User reached Profile with null user`);
@@ -20,7 +21,7 @@ const Profile = (props: IProps) => {
20
21
  const isUserInit = useURLSearchParam("init");
21
22
 
22
23
  const [form, setForm] = useState<IUser>({ ...user });
23
-
24
+ const { isOpen, toggleModal } = useModal();
24
25
  const { isDirty, setIsDirty } = useShouldBeSaved(form);
25
26
 
26
27
  const isSiteView = !!currentSiteInfo;
@@ -50,10 +51,18 @@ const Profile = (props: IProps) => {
50
51
  action: handleSave,
51
52
  };
52
53
 
54
+ const lineButtonProps = user.isSuperAdmin
55
+ ? {
56
+ label: "Link Device",
57
+ action: toggleModal,
58
+ icon: "QrCode",
59
+ }
60
+ : undefined;
61
+
53
62
  if (isLoading) return <Loading />;
54
63
 
55
64
  return (
56
- <MainWrapper title="My Profile" rightButton={rightButtonProps}>
65
+ <MainWrapper title="My Profile" rightLineButton={lineButtonProps} rightButton={rightButtonProps}>
57
66
  <UserForm
58
67
  form={form}
59
68
  setForm={setForm}
@@ -62,6 +71,7 @@ const Profile = (props: IProps) => {
62
71
  site={currentSiteInfo}
63
72
  isSiteView={isSiteView}
64
73
  />
74
+ {isOpen && <LinkDeviceModal isOpen={isOpen} toggleModal={toggleModal} token={token} />}
65
75
  </MainWrapper>
66
76
  );
67
77
  };
@@ -0,0 +1,17 @@
1
+ import styled from "styled-components";
2
+
3
+ const ModalContent = styled.div`
4
+ display: flex;
5
+ flex-direction: column;
6
+ padding: ${(p) => p.theme.spacing.m};
7
+ p {
8
+ margin-bottom: ${(p) => p.theme.spacing.m};
9
+ }
10
+ `;
11
+
12
+ const QRWrapper = styled.div`
13
+ padding-top: ${(p) => p.theme.spacing.xs};
14
+ margin: auto;
15
+ `;
16
+
17
+ export { ModalContent, QRWrapper };