@griddo/ax 1.63.5 → 1.64.2

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 (130) hide show
  1. package/config/griddo-config/cx-polyfills/builder.ssr.js +6 -0
  2. package/config/griddo-config/cx-polyfills/componentsBundle.js +4 -0
  3. package/config/{griddo-config.js → griddo-config/index.js} +36 -15
  4. package/config/griddo-config/ssrHelpers.js +47 -0
  5. package/config/jest/componentsMock.js +29 -0
  6. package/config/jest/fileMock.js +1 -0
  7. package/config/jest/setup.js +5 -0
  8. package/config/jest/styleMock.js +1 -0
  9. package/config/jest/test-utils.js +17 -0
  10. package/config/paths.js +36 -5
  11. package/config/webpack.config.js +1 -1
  12. package/config/webpackDevServer.config.js +4 -1
  13. package/config/webpackSchemas.config.js +4 -1
  14. package/package.json +33 -59
  15. package/scripts/build.js +9 -2
  16. package/src/__mocks__/reducers/analyticsState.tsx +14 -0
  17. package/src/__mocks__/reducers/pageEditor.tsx +30 -0
  18. package/src/api/sites.tsx +28 -6
  19. package/src/api/structuredData.tsx +1 -1
  20. package/src/api/users.tsx +5 -4
  21. package/src/components/ActionMenu/style.tsx +2 -0
  22. package/src/components/Browser/index.tsx +9 -5
  23. package/src/{modules/Content/PageItem/atoms.tsx → components/CategoryCell/index.tsx} +4 -6
  24. package/src/components/CategoryCell/style.tsx +11 -0
  25. package/src/components/Fields/AnalyticsField/PageAnalytics/index.tsx +19 -19
  26. package/src/components/Fields/AnalyticsField/StructuredDataAnalytics/atoms.tsx +26 -16
  27. package/src/components/Fields/AnalyticsField/StructuredDataAnalytics/index.tsx +8 -13
  28. package/src/components/Fields/AnalyticsField/index.test.tsx +100 -0
  29. package/src/components/Fields/AnalyticsField/index.tsx +9 -2
  30. package/src/components/Fields/AnalyticsField/utils.tsx +2 -2
  31. package/src/components/Fields/ArrayFieldGroup/ArrayFieldItem/style.tsx +2 -1
  32. package/src/components/Fields/CheckField/index.test.tsx +95 -0
  33. package/src/components/Fields/CheckField/index.tsx +9 -3
  34. package/src/components/Fields/CheckField/style.tsx +32 -24
  35. package/src/components/Fields/CheckGroup/index.test.tsx +274 -0
  36. package/src/components/Fields/CheckGroup/index.tsx +2 -1
  37. package/src/components/Fields/FileField/FileDragAndDrop/style.tsx +3 -2
  38. package/src/components/Fields/FileField/style.tsx +2 -1
  39. package/src/components/Fields/MultiCheckSelect/style.tsx +18 -18
  40. package/src/components/Fields/NoteField/style.tsx +9 -9
  41. package/src/components/Fields/ReferenceField/AutoPanel/AutoItem/index.tsx +1 -1
  42. package/src/components/Fields/Select/style.tsx +41 -37
  43. package/src/components/Fields/TagField/index.test.tsx +136 -0
  44. package/src/components/Fields/TagField/index.tsx +8 -12
  45. package/src/components/Fields/TextArea/index.test.tsx +69 -0
  46. package/src/components/Fields/TextArea/index.tsx +4 -13
  47. package/src/components/Fields/TextArea/style.tsx +2 -2
  48. package/src/components/Fields/TextField/index.test.tsx +144 -0
  49. package/src/components/Fields/TextField/index.tsx +23 -19
  50. package/src/components/Fields/TextField/style.tsx +16 -7
  51. package/src/components/Fields/UniqueCheck/index.test.tsx +43 -0
  52. package/src/components/Fields/UrlField/utils.tsx +8 -6
  53. package/src/components/FieldsBehavior/index.tsx +0 -2
  54. package/src/components/FieldsBehavior/style.tsx +21 -21
  55. package/src/components/Gallery/GalleryFilters/Orientation/style.tsx +2 -1
  56. package/src/components/Gallery/GalleryFilters/SortBy/style.tsx +2 -1
  57. package/src/components/Icon/index.tsx +12 -10
  58. package/src/components/IconAction/index.tsx +7 -1
  59. package/src/components/IconAction/style.tsx +10 -10
  60. package/src/components/SearchField/index.tsx +11 -8
  61. package/src/components/SearchField/style.tsx +21 -12
  62. package/src/components/TableFilters/CategoryFilter/index.tsx +1 -1
  63. package/src/components/TableFilters/CategoryFilter/style.tsx +2 -1
  64. package/src/components/TableFilters/DateFilter/style.tsx +2 -1
  65. package/src/components/TableFilters/LiveFilter/index.tsx +2 -2
  66. package/src/components/TableFilters/LiveFilter/style.tsx +2 -1
  67. package/src/components/TableFilters/NameFilter/style.tsx +2 -1
  68. package/src/components/TableFilters/SiteFilter/index.tsx +38 -24
  69. package/src/components/TableFilters/SiteFilter/style.tsx +2 -1
  70. package/src/components/TableFilters/StatusFilter/style.tsx +2 -1
  71. package/src/components/TableFilters/TranslationsFilter/style.tsx +2 -1
  72. package/src/components/TableFilters/TypeFilter/style.tsx +2 -1
  73. package/src/components/Tag/index.tsx +9 -7
  74. package/src/components/Tag/style.tsx +20 -8
  75. package/src/components/index.tsx +4 -2
  76. package/src/containers/App/reducer.tsx +0 -2
  77. package/src/containers/PageEditor/actions.tsx +2 -2
  78. package/src/containers/Sites/actions.tsx +30 -19
  79. package/src/containers/Users/actions.tsx +10 -2
  80. package/src/containers/Users/reducer.tsx +3 -1
  81. package/src/helpers/fields.tsx +2 -4
  82. package/src/helpers/index.tsx +3 -0
  83. package/src/helpers/themes.tsx +9 -0
  84. package/src/modules/Analytics/GroupPanel/utils.tsx +3 -3
  85. package/src/modules/App/Routing/NavMenu/index.tsx +13 -12
  86. package/src/modules/Content/PageItem/index.tsx +31 -9
  87. package/src/modules/Content/PageItem/style.tsx +0 -7
  88. package/src/modules/Content/atoms.tsx +78 -0
  89. package/src/modules/Content/index.tsx +104 -33
  90. package/src/modules/Content/style.tsx +10 -7
  91. package/src/modules/GlobalEditor/PageBrowser/index.tsx +0 -4
  92. package/src/modules/GlobalEditor/index.tsx +3 -3
  93. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/DefaultsBrowser/index.tsx +0 -4
  94. package/src/modules/PageEditor/PageBrowser/index.tsx +0 -4
  95. package/src/modules/PageEditor/atoms.tsx +74 -0
  96. package/src/modules/PageEditor/index.tsx +30 -9
  97. package/src/modules/PageEditor/style.tsx +4 -0
  98. package/src/modules/PublicPreview/index.tsx +3 -5
  99. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/TemplateBrowser/index.tsx +0 -4
  100. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/index.tsx +2 -3
  101. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/index.tsx +1 -1
  102. package/src/modules/Settings/Globals/index.tsx +3 -3
  103. package/src/modules/StructuredData/Form/index.tsx +2 -4
  104. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/index.tsx +22 -18
  105. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/atoms.tsx +3 -24
  106. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +2 -2
  107. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/style.tsx +0 -7
  108. package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +2 -4
  109. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +46 -14
  110. package/src/modules/StructuredData/StructuredDataList/hooks.tsx +21 -9
  111. package/src/modules/StructuredData/StructuredDataList/index.tsx +48 -20
  112. package/src/modules/Users/Profile/index.tsx +12 -7
  113. package/src/modules/Users/UserCreate/SiteItem/index.tsx +44 -0
  114. package/src/modules/Users/UserCreate/SiteItem/style.tsx +30 -0
  115. package/src/modules/Users/UserCreate/index.tsx +120 -10
  116. package/src/modules/Users/UserCreate/style.tsx +54 -1
  117. package/src/modules/Users/UserEdit/index.tsx +53 -15
  118. package/src/modules/Users/UserForm/index.tsx +152 -5
  119. package/src/modules/Users/UserForm/style.tsx +40 -2
  120. package/src/modules/Users/UserList/BulkHeader/TableHeader/index.tsx +40 -2
  121. package/src/modules/Users/UserList/BulkHeader/TableHeader/style.tsx +0 -1
  122. package/src/modules/Users/UserList/BulkHeader/index.tsx +10 -1
  123. package/src/modules/Users/UserList/UserItem/index.tsx +70 -15
  124. package/src/modules/Users/UserList/hooks.tsx +58 -1
  125. package/src/modules/Users/UserList/index.tsx +80 -34
  126. package/src/modules/Users/index.tsx +18 -11
  127. package/src/routes/site.tsx +8 -0
  128. package/src/types/index.tsx +7 -0
  129. package/tsconfig.json +2 -0
  130. package/scripts/test.js +0 -45
@@ -90,6 +90,8 @@ const StructuredDataList = (props: IProps): JSX.Element => {
90
90
  const lastPage = Math.ceil(totalItems / itemsPerPage);
91
91
  const isLastItem = page === lastPage && currentSitePages.length === 1;
92
92
 
93
+ const [structuredDataType, setStructuredDataType] = useState("all");
94
+
93
95
  const [isScrolling, setIsScrolling] = useState(false);
94
96
  const [deletedItem, setDeletedItem] = useState<number | number[] | null>(null);
95
97
  const { isVisible: isDataToast, toggleToast: toggleDataToast, setIsVisible: setIsDataToast } = useToast();
@@ -102,7 +104,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
102
104
  const { isOpen: isNewOpen, toggleModal: toggleNewModal } = useModal();
103
105
  const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
104
106
  const { sortedListStatus, setSortedListStatus } = useSortedListStatus();
105
- const { setFiltersSelection, setFilterQuery, filterValues, resetFilterQuery } = useFilterQuery();
107
+ const { setFiltersSelection, setFilterQuery, filterValues, resetFilterQuery } = useFilterQuery(currentStructuredData);
106
108
  const [currentFilterQuery, setCurrentFilterQuery] = useState("");
107
109
  const history = useHistory();
108
110
  const [isFirstRender, setIsFirstRender] = useState(true);
@@ -130,20 +132,24 @@ const StructuredDataList = (props: IProps): JSX.Element => {
130
132
  live: { title: "Live", show: true },
131
133
  status: { title: "Status", show: true },
132
134
  translation: { title: "Trans.", show: true },
133
- seo: { title: "SEO", show: true },
135
+ ...(isStructuredDataFromPage && { seo: { title: "SEO", show: true } }),
134
136
  };
135
137
 
136
138
  const extraColumns = categoryColumns.reduce((acc: Record<string, IColumn>, cur: any) => {
137
- acc[cur.key] = { title: cur.title, show: false };
139
+ acc[cur.key] = { title: cur.title, show: !isStructuredDataFromPage };
138
140
  return acc;
139
141
  }, {});
140
142
 
141
143
  const allColumns = { type: { title: "Types", show: true }, ...extraColumns, ...defaultColumns };
142
- const filterColumns = { site: { title: "Site", show: true }, ...extraColumns, ...defaultColumns };
144
+ const filterColumns = {
145
+ ...(isStructuredDataFromPage && { site: { title: "Site", show: true } }),
146
+ ...extraColumns,
147
+ ...defaultColumns,
148
+ };
143
149
 
144
150
  const initialColumns = isAllPages ? allColumns : filterColumns;
145
151
 
146
- const [columnsState, setColumnsState] = useState(initialColumns);
152
+ const [columnsState, setColumnsState] = useState<Record<string, any>>({ all: allColumns });
147
153
 
148
154
  const {
149
155
  resetBulkSelection,
@@ -186,8 +192,10 @@ const StructuredDataList = (props: IProps): JSX.Element => {
186
192
  getGlobalPages(params, currentFilterQuery);
187
193
  };
188
194
 
195
+ const handleGetStructuredData = () => getStructuredData(filter, currentFilterQuery);
196
+
189
197
  const handleGetData = () => {
190
- isStructuredDataFromPage ? handleGetGlobalPages() : getStructuredData(filter, currentFilterQuery);
198
+ isStructuredDataFromPage ? handleGetGlobalPages() : handleGetStructuredData();
191
199
  };
192
200
 
193
201
  const resetFilter = () => {
@@ -196,37 +204,52 @@ const StructuredDataList = (props: IProps): JSX.Element => {
196
204
  }
197
205
  };
198
206
 
207
+ const changeColumnsState = (updatedColumns: any) => {
208
+ setColumnsState((state) => ({ ...state, [structuredDataType]: updatedColumns }));
209
+ };
210
+
199
211
  useLayoutEffect(() => {
200
212
  resetFilter();
201
- handleGetData();
202
213
  setIsFirstRender(false);
203
214
  resetPageEditor();
204
215
  // eslint-disable-next-line react-hooks/exhaustive-deps
205
216
  }, []);
206
217
 
218
+ useEffect(() => {
219
+ const isGlobalData = structuredData.global.some((data) => data.id === currentStructuredData?.id);
220
+ const type = currentStructuredData && isGlobalData ? currentStructuredData.id : "all";
221
+ setStructuredDataType(type);
222
+ // eslint-disable-next-line react-hooks/exhaustive-deps
223
+ }, [currentStructuredData]);
224
+
207
225
  useEffect(() => {
208
226
  filterItems("types", "all");
209
227
  if (filter === "all-pages" || !isStructuredDataFromPage) {
210
228
  filterItems("filterSites", "all");
211
229
  }
212
230
  unselectAllItems();
213
- if (filter === "all-pages") {
214
- setColumnsState(filter === "all-pages" ? allColumns : filterColumns);
215
- } else {
216
- setColumnsState({ site: { title: "Site", show: true }, ...extraColumns, ...initialColumns });
231
+ if (filter !== "all-pages" && currentStructuredData) {
232
+ setColumnsState((state) => ({
233
+ [currentStructuredData.id]: {
234
+ site: { title: "Site", show: true },
235
+ ...extraColumns,
236
+ ...initialColumns,
237
+ },
238
+ ...state,
239
+ }));
217
240
  }
218
241
  // eslint-disable-next-line react-hooks/exhaustive-deps
219
242
  }, [filter]);
220
243
 
221
244
  useEffect(() => {
222
- if (!isFirstRender || filter === "all-pages") {
245
+ if (!isFirstRender) {
223
246
  handleGetData();
224
247
  }
225
248
  if (tableRef.current) {
226
249
  tableRef.current.scrollTo(0, 0);
227
250
  }
228
251
  // eslint-disable-next-line react-hooks/exhaustive-deps
229
- }, [filter, lang.locale, page, searchQuery, currentFilterQuery]);
252
+ }, [lang.locale, page, searchQuery, filterValues]);
230
253
 
231
254
  useEffect(() => {
232
255
  if (wrapperRef.current) {
@@ -241,7 +264,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
241
264
  };
242
265
 
243
266
  useEffect(() => {
244
- if (!isLoading) {
267
+ if (!isFirstRender && !isLoading) {
245
268
  const emptyState: IEmptyStateProps = {};
246
269
  const isSearching = searchQuery.length > 0;
247
270
  if (isSearching) {
@@ -277,8 +300,6 @@ const StructuredDataList = (props: IProps): JSX.Element => {
277
300
  };
278
301
 
279
302
  const handleMenuClick = (dataID: string) => {
280
- resetFilterQuery();
281
- setCurrentFilterQuery("");
282
303
  setPage(firstPage);
283
304
  setSelectedStructuredData(dataID, scope);
284
305
  };
@@ -383,11 +404,14 @@ const StructuredDataList = (props: IProps): JSX.Element => {
383
404
 
384
405
  const filterItems = async (filterPointer: string, filtersSelected: string) => {
385
406
  setPage(firstPage);
407
+ if (!isStructuredDataFromPage && filterPointer === "categories") filterPointer = "related";
386
408
  const filtersSelection = setFiltersSelection(filterPointer, filtersSelected);
387
409
  const filterQuery = setFilterQuery(filtersSelection);
388
410
  setCurrentFilterQuery(filterQuery);
389
411
  };
390
412
 
413
+ const currentDataColumnsState = currentStructuredData ? columnsState[structuredDataType] || [] : columnsState["all"];
414
+
391
415
  const TableHeader = (
392
416
  <BulkHeader
393
417
  showBulk={areItemsSelected(dataIds)}
@@ -404,11 +428,11 @@ const StructuredDataList = (props: IProps): JSX.Element => {
404
428
  sortItems={sortItems}
405
429
  sortedListStatus={sortedListStatus}
406
430
  filterItems={filterItems}
407
- filterValues={filterValues}
431
+ filterValues={filterValues[structuredDataType]}
408
432
  isAllPages={isAllPages}
409
433
  categoryColumns={categoryColumns}
410
- columns={columnsState}
411
- setColumns={setColumnsState}
434
+ columns={currentDataColumnsState}
435
+ setColumns={changeColumnsState}
412
436
  />
413
437
  );
414
438
 
@@ -470,6 +494,10 @@ const StructuredDataList = (props: IProps): JSX.Element => {
470
494
  setDeletedItem={setDeletedItem}
471
495
  isEditable={isDataEditable}
472
496
  activatedDataPacks={activatedDataPacks}
497
+ categoryColumns={categoryColumns}
498
+ columns={columnsState}
499
+ categoryColors={categoryColors}
500
+ addCategoryColors={addCategoryColors}
473
501
  />
474
502
  );
475
503
  });
@@ -498,7 +526,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
498
526
  toggleToast={togglePageToast}
499
527
  setDeletedItem={setDeletedItem}
500
528
  categoryColumns={categoryColumns}
501
- columns={columnsState}
529
+ columns={currentDataColumnsState}
502
530
  categoryColors={categoryColors}
503
531
  addCategoryColors={addCategoryColors}
504
532
  />
@@ -3,21 +3,22 @@ import { connect } from "react-redux";
3
3
 
4
4
  import { appActions } from "@ax/containers/App";
5
5
  import { usersActions } from "@ax/containers/Users";
6
+ import { sitesActions } from "@ax/containers/Sites";
6
7
  import { Loading, MainWrapper } from "@ax/components";
7
8
  import { IRootState, IUser } from "@ax/types";
8
9
  import { useURLSearchParam } from "@ax/hooks";
10
+
9
11
  import UserForm from "../UserForm";
10
12
 
11
13
  const Profile = (props: IProps) => {
12
- const { user, getUser, updateUser, isSaving, isLoading } = props;
14
+ const { user, getUser, updateUser, isSaving, isLoading, getSites, token } = props;
13
15
 
14
16
  const isUserInit = useURLSearchParam("init");
15
17
 
16
- const [form, setForm] = useState<any>({});
17
- const { id } = form;
18
+ const [form, setForm] = useState<IUser>({ ...user });
18
19
 
19
20
  useEffect(() => {
20
- if (!isUserInit) getUser("me");
21
+ isUserInit ? getSites(token) : getUser("me");
21
22
  // eslint-disable-next-line react-hooks/exhaustive-deps
22
23
  }, []);
23
24
 
@@ -29,7 +30,7 @@ const Profile = (props: IProps) => {
29
30
  }, [user]);
30
31
 
31
32
  const handleSave = () => {
32
- updateUser(id, form, true);
33
+ form.id && updateUser(form.id, form, true, false);
33
34
  };
34
35
 
35
36
  const rightButtonProps = {
@@ -42,7 +43,7 @@ const Profile = (props: IProps) => {
42
43
 
43
44
  return (
44
45
  <MainWrapper title="My Profile" rightButton={rightButtonProps}>
45
- <UserForm form={form} setForm={setForm} user={user} />
46
+ <UserForm form={form} setForm={setForm} user={user} readOnlySites={true} />
46
47
  </MainWrapper>
47
48
  );
48
49
  };
@@ -51,24 +52,28 @@ const mapStateToProps = (state: IRootState) => ({
51
52
  user: state.users.currentUser,
52
53
  isSaving: state.app.isSaving,
53
54
  isLoading: state.app.isLoading,
55
+ token: state.app.token,
54
56
  });
55
57
 
56
58
  interface IDispatchProps {
57
59
  setHistoryPush(path: string): any;
58
60
  getUser(id: string): any;
59
- updateUser(id: number, data: any, isProfile: boolean): any;
61
+ getSites(token: string): Promise<void>;
62
+ updateUser(id: number, data: any, isProfile: boolean, isList: boolean): any;
60
63
  }
61
64
 
62
65
  const mapDispatchToProps = {
63
66
  setHistoryPush: appActions.setHistoryPush,
64
67
  getUser: usersActions.getUser,
65
68
  updateUser: usersActions.updateUser,
69
+ getSites: sitesActions.getSites,
66
70
  };
67
71
 
68
72
  interface IProfileProps {
69
73
  user: IUser;
70
74
  isSaving: boolean;
71
75
  isLoading: boolean;
76
+ token: string;
72
77
  }
73
78
 
74
79
  type IProps = IProfileProps & IDispatchProps;
@@ -0,0 +1,44 @@
1
+ import React from "react";
2
+
3
+ import { CheckField } from "@ax/components";
4
+
5
+ import * as S from "./style";
6
+
7
+ const SiteItem = (props: IProps) => {
8
+ const { item, sites, disabled = false, readOnly = false } = props;
9
+ const { name, id, onChange } = item;
10
+ const isSelected = sites.includes(id);
11
+
12
+ const onClick = (e: React.MouseEvent) => {
13
+ if (readOnly) return;
14
+ e.preventDefault();
15
+ e.stopPropagation();
16
+ onChange();
17
+ };
18
+
19
+ return (
20
+ <S.SiteItem
21
+ disabled={disabled}
22
+ role="rowgroup"
23
+ readOnly={readOnly}
24
+ selected={!readOnly && isSelected}
25
+ onClick={onClick}
26
+ >
27
+ {!readOnly ? (
28
+ <S.CheckCell role="cell">
29
+ <CheckField disabled={disabled} name="check" value={id} checked={isSelected} onChange={onChange} />
30
+ </S.CheckCell>
31
+ ) : null}
32
+ <S.NameCell disabled={disabled}>{name}</S.NameCell>
33
+ </S.SiteItem>
34
+ );
35
+ };
36
+
37
+ interface IProps {
38
+ item: { name: string; id: number | string; onChange: () => void };
39
+ sites: (string | number)[];
40
+ disabled?: boolean;
41
+ readOnly?: boolean;
42
+ }
43
+
44
+ export default SiteItem;
@@ -0,0 +1,30 @@
1
+ import styled from "styled-components";
2
+ import { Cell, Row } from "@ax/components/TableList/TableItem/style";
3
+
4
+ const CheckCell = styled(Cell)`
5
+ padding-left: ${(p) => p.theme.spacing.m};
6
+ width: 32px;
7
+ label {
8
+ margin-bottom: ${(p) => p.theme.spacing.s};
9
+ }
10
+ `;
11
+
12
+ const NameCell = styled(Cell)<{ disabled?: boolean }>`
13
+ width: 40%;
14
+ ${(p) => p.theme.textStyle.uiL};
15
+ color: ${(p) => (p.disabled ? p.theme.color.interactiveDisabled : p.theme.color.textHighEmphasis)};
16
+ `;
17
+
18
+ const SiteItem = styled(Row)<{ disabled?: boolean; readOnly?: boolean }>`
19
+ pointer-events: ${(p) => (p.disabled || p.readOnly ? "none" : "auto")};
20
+ &:hover {
21
+ background-color: ${(p) =>
22
+ p.readOnly
23
+ ? p.theme.color.uiBackground02
24
+ : p.selected
25
+ ? p.theme.color.overlayPressedPrimary
26
+ : p.theme.color.overlayHoverPrimary};
27
+ }
28
+ `;
29
+
30
+ export { CheckCell, NameCell, SiteItem };
@@ -1,16 +1,25 @@
1
1
  import React, { useState } from "react";
2
2
  import { connect } from "react-redux";
3
+ import { ISite, IRootState, IUser } from "@ax/types";
3
4
  import { appActions } from "@ax/containers/App";
4
5
  import { usersActions } from "@ax/containers/Users";
5
- import { ErrorToast, FieldsBehavior, MainWrapper } from "@ax/components";
6
+ import { ErrorToast, FieldsBehavior, MainWrapper, SearchField, Notification } from "@ax/components";
7
+ import SiteItem from "./SiteItem";
6
8
 
7
9
  import * as S from "./style";
8
10
 
9
11
  const UserCreate = (props: IProps) => {
10
- const { setHistoryPush, createUser } = props;
12
+ const { setHistoryPush, createUser, sites, site, users, updateUser } = props;
13
+ const isSiteView = !!site;
14
+ const initState: any = { name: "", email: "", sites: isSiteView ? [site.id] : ["all"] };
15
+ const getSortedSitesByName = (sites: any) =>
16
+ sites.sort((site1: any, site2: any) => site1.name.localeCompare(site2.name));
17
+ const sortedByNameSites = getSortedSitesByName(sites);
11
18
 
12
- const initState = { name: "", email: "", sites: ["all"] };
19
+ const [sitesList, setSiteList] = useState(sortedByNameSites);
13
20
  const [state, setState] = useState(initState);
21
+ const [userAlreadyExists, setUserAlreadyExists] = useState(false);
22
+ const BASE_URL = isSiteView ? "/sites/users" : "/users";
14
23
 
15
24
  const handleNameChange = (value: string) => {
16
25
  setState({ ...state, name: value });
@@ -21,11 +30,28 @@ const UserCreate = (props: IProps) => {
21
30
  };
22
31
 
23
32
  const handleCreate = () => {
24
- createUser(state).then((created: boolean) => {
25
- if (created) {
26
- setHistoryPush("/users");
33
+ const { email } = state;
34
+ const globalUser = users.find((user) => user.email === email);
35
+ setUserAlreadyExists(false);
36
+
37
+ if (isSiteView && globalUser?.id) {
38
+ const { id, sites } = globalUser;
39
+ const userAlreadyExists = sites[0] === "all" || sites.includes(site.id);
40
+ if (userAlreadyExists) {
41
+ setUserAlreadyExists(true);
42
+ } else {
43
+ const form = { ...globalUser, sites: [...sites, site.id] };
44
+ updateUser(id, form, false).then(() => {
45
+ setHistoryPush(BASE_URL);
46
+ });
27
47
  }
28
- });
48
+ } else {
49
+ createUser(state).then((created: boolean) => {
50
+ if (created) {
51
+ setHistoryPush(BASE_URL);
52
+ }
53
+ });
54
+ }
29
55
  };
30
56
 
31
57
  const rightButtonProps = {
@@ -35,12 +61,47 @@ const UserCreate = (props: IProps) => {
35
61
 
36
62
  const name = state.name.trim() === "" ? "Name" : state.name;
37
63
 
64
+ const selectAllSites = () => {
65
+ const { sites } = state;
66
+ if (sites.includes("all")) {
67
+ const filteredSites = sites.filter((site: number | string) => site !== "all");
68
+ const sortedSites = getSortedSitesByName(filteredSites);
69
+ setState({ ...state, sites: sortedSites });
70
+ } else {
71
+ setState({ ...state, sites: ["all"] });
72
+ }
73
+ };
74
+
75
+ const selectSite = (id: number) => {
76
+ const { sites } = state;
77
+ if (sites.includes(id)) {
78
+ const filteredSites = sites.filter((site: number | string) => site !== id && site !== "all");
79
+ setState({ ...state, sites: filteredSites });
80
+ } else {
81
+ const filteredSites: string[] | number[] = [...sites].filter((site) => site !== "all");
82
+ setState({ ...state, sites: [...filteredSites, id] });
83
+ }
84
+ };
85
+
86
+ const filterSitesList = (query: string) => {
87
+ const filteredSites = sites.filter((site) =>
88
+ site.name.toLowerCase().replace(" ", "").includes(query.toLowerCase().replace(" ", ""))
89
+ );
90
+ const sortedSites = getSortedSitesByName(filteredSites);
91
+ setSiteList(sortedSites);
92
+ };
93
+
38
94
  return (
39
95
  <>
40
96
  <MainWrapper backLink={true} title="New User" rightButton={rightButtonProps}>
41
97
  <ErrorToast size="l" />
98
+ {userAlreadyExists && (
99
+ <S.NotificationWrapper>
100
+ <Notification type="error" text="User already has access to this site." />
101
+ </S.NotificationWrapper>
102
+ )}
42
103
  <S.Wrapper>
43
- <S.SubTitle>USER DATA</S.SubTitle>
104
+ <S.SubTitle>User data</S.SubTitle>
44
105
  <S.NameTitle>{name}</S.NameTitle>
45
106
  <FieldsBehavior
46
107
  title="Name"
@@ -60,20 +121,69 @@ const UserCreate = (props: IProps) => {
60
121
  value={state.email}
61
122
  onChange={handleEmailChange}
62
123
  />
124
+ {isSiteView ? null : (
125
+ <>
126
+ <S.SettingsWrapper>
127
+ <S.Heading>Sites assigned</S.Heading>
128
+ <S.SettingContent>
129
+ <S.SettingText>You can give access to one or more sites to this user.</S.SettingText>
130
+ </S.SettingContent>
131
+ </S.SettingsWrapper>
132
+ <S.SelectAllSitesFieldWrapper>
133
+ <SiteItem
134
+ sites={state.sites}
135
+ item={{ name: "Access to All Sites", id: "all", onChange: selectAllSites }}
136
+ />
137
+ </S.SelectAllSitesFieldWrapper>
138
+ <S.SubTitle>Sites list</S.SubTitle>
139
+ <S.SearchFieldWrapper>
140
+ <SearchField
141
+ disabled={state.sites[0] === "all"}
142
+ placeholder="Search"
143
+ onChange={filterSitesList}
144
+ searchOnEnter={false}
145
+ />
146
+ </S.SearchFieldWrapper>
147
+ {sitesList.length > 0 &&
148
+ sitesList.map((site: any) => (
149
+ <SiteItem
150
+ disabled={state.sites[0] === "all"}
151
+ sites={state.sites}
152
+ key={site.id}
153
+ item={{ name: site.name, id: site.id, onChange: () => selectSite(site.id) }}
154
+ />
155
+ ))}
156
+ </>
157
+ )}
63
158
  </S.Wrapper>
64
159
  </MainWrapper>
65
160
  </>
66
161
  );
67
162
  };
68
163
 
69
- interface IProps {
164
+ interface IDispatchProps {
70
165
  setHistoryPush(path: string): any;
71
166
  createUser(data: { name: string; email: string; sites: any[] }): Promise<boolean>;
167
+ updateUser(id: number, data: any, isProfile: boolean): any;
168
+ }
169
+ interface IProfileProps {
170
+ users: IUser[];
171
+ sites: ISite[];
172
+ site: ISite;
72
173
  }
73
174
 
175
+ const mapStateToProps = (state: IRootState) => ({
176
+ users: state.users.users,
177
+ sites: state.sites.sites,
178
+ site: state.sites.currentSiteInfo,
179
+ });
180
+
74
181
  const mapDispatchToProps = {
75
182
  setHistoryPush: appActions.setHistoryPush,
76
183
  createUser: usersActions.createUser,
184
+ updateUser: usersActions.updateUser,
77
185
  };
78
186
 
79
- export default connect(null, mapDispatchToProps)(UserCreate);
187
+ type IProps = IProfileProps & IDispatchProps;
188
+
189
+ export default connect(mapStateToProps, mapDispatchToProps)(UserCreate);
@@ -17,9 +17,62 @@ const SubTitle = styled.div`
17
17
  margin-bottom: ${(p) => p.theme.spacing.xs};
18
18
  `;
19
19
 
20
+ const Heading = styled.div`
21
+ ${(p) => p.theme.textStyle.headingXS};
22
+ color: ${(p) => p.theme.color.textHighEmphasis};
23
+ padding-bottom: ${(p) => p.theme.spacing.xs};
24
+ `;
25
+
26
+ const SettingsWrapper = styled.div`
27
+ position: relative;
28
+ border-top: 1px solid ${(p) => p.theme.color.uiLine};
29
+ padding-top: ${(p) => p.theme.spacing.m};
30
+ `;
31
+
32
+ const SettingContent = styled.div`
33
+ padding-bottom: ${(p) => p.theme.spacing.m};
34
+ `;
35
+
36
+ const SearchFieldWrapper = styled.div`
37
+ padding-top: ${(p) => p.theme.spacing.xs};
38
+ padding-bottom: ${(p) => p.theme.spacing.xs};
39
+ max-width: calc(${(p) => p.theme.spacing.l} * 7);
40
+ `;
41
+
42
+ const SelectAllSitesFieldWrapper = styled.div`
43
+ padding-bottom: ${(p) => p.theme.spacing.s};
44
+ `;
45
+
46
+ const SettingText = styled.div`
47
+ ${(p) => p.theme.textStyle.uiM};
48
+ color: ${(p) => p.theme.color.textMediumEmphasis};
49
+ width: calc(${(p) => p.theme.spacing.l} * 12);
50
+ `;
51
+
52
+ const FormWrapper = styled.div`
53
+ width: 720px;
54
+ margin: ${(p) => `${p.theme.spacing.m} 0 0 ${p.theme.spacing.m}`};
55
+ `;
56
+
57
+ const NotificationWrapper = styled.div`
58
+ position: fixed;
59
+ width: 100%;
60
+ top: ${(p) => `calc(${p.theme.spacing.s} * 4)`};
61
+ left: 0;
62
+ right: 0;
63
+ z-index: 2;
64
+ `;
20
65
 
21
66
  export {
22
67
  Wrapper,
23
68
  NameTitle,
24
- SubTitle
69
+ SubTitle,
70
+ Heading,
71
+ SettingsWrapper,
72
+ SettingText,
73
+ SettingContent,
74
+ SearchFieldWrapper,
75
+ SelectAllSitesFieldWrapper,
76
+ FormWrapper,
77
+ NotificationWrapper,
25
78
  };