@griddo/ax 1.63.4 → 1.64.1

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 (132) 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 +35 -64
  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/index.tsx +3 -0
  85. package/src/modules/Analytics/GroupPanel/utils.tsx +3 -3
  86. package/src/modules/App/Routing/NavMenu/index.tsx +13 -12
  87. package/src/modules/Content/PageItem/index.tsx +31 -9
  88. package/src/modules/Content/PageItem/style.tsx +0 -7
  89. package/src/modules/Content/atoms.tsx +78 -0
  90. package/src/modules/Content/index.tsx +104 -33
  91. package/src/modules/Content/style.tsx +10 -7
  92. package/src/modules/GlobalEditor/PageBrowser/index.tsx +0 -4
  93. package/src/modules/GlobalEditor/index.tsx +3 -3
  94. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/DefaultsBrowser/index.tsx +0 -4
  95. package/src/modules/PageEditor/PageBrowser/index.tsx +0 -4
  96. package/src/modules/PageEditor/atoms.tsx +74 -0
  97. package/src/modules/PageEditor/index.tsx +30 -9
  98. package/src/modules/PageEditor/style.tsx +4 -0
  99. package/src/modules/PublicPreview/index.tsx +3 -5
  100. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/TemplateBrowser/index.tsx +0 -4
  101. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/index.tsx +2 -3
  102. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/index.tsx +1 -1
  103. package/src/modules/Settings/Globals/index.tsx +3 -3
  104. package/src/modules/StructuredData/Form/index.tsx +2 -4
  105. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/index.tsx +22 -18
  106. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/atoms.tsx +3 -24
  107. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +2 -2
  108. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/style.tsx +0 -7
  109. package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +2 -4
  110. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +46 -14
  111. package/src/modules/StructuredData/StructuredDataList/hooks.tsx +21 -9
  112. package/src/modules/StructuredData/StructuredDataList/index.tsx +48 -20
  113. package/src/modules/Users/Profile/index.tsx +12 -7
  114. package/src/modules/Users/UserCreate/SiteItem/index.tsx +44 -0
  115. package/src/modules/Users/UserCreate/SiteItem/style.tsx +30 -0
  116. package/src/modules/Users/UserCreate/index.tsx +120 -10
  117. package/src/modules/Users/UserCreate/style.tsx +54 -1
  118. package/src/modules/Users/UserEdit/index.tsx +53 -15
  119. package/src/modules/Users/UserForm/index.tsx +152 -5
  120. package/src/modules/Users/UserForm/style.tsx +40 -2
  121. package/src/modules/Users/UserList/BulkHeader/TableHeader/index.tsx +40 -2
  122. package/src/modules/Users/UserList/BulkHeader/TableHeader/style.tsx +0 -1
  123. package/src/modules/Users/UserList/BulkHeader/index.tsx +10 -1
  124. package/src/modules/Users/UserList/UserItem/index.tsx +70 -15
  125. package/src/modules/Users/UserList/hooks.tsx +58 -1
  126. package/src/modules/Users/UserList/index.tsx +80 -34
  127. package/src/modules/Users/index.tsx +18 -11
  128. package/src/routes/site.tsx +8 -0
  129. package/src/types/index.tsx +7 -0
  130. package/tsconfig.json +2 -0
  131. package/patches/connected-react-router+6.9.2.patch +0 -12
  132. package/scripts/test.js +0 -45
@@ -1,25 +1,34 @@
1
- import React, { useState } from "react";
1
+ import React, { useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { appActions } from "@ax/containers/App";
5
5
  import { usersActions } from "@ax/containers/Users";
6
6
  import { Loading, MainWrapper, Modal } from "@ax/components";
7
- import { IRootState, IUser } from "@ax/types";
7
+ import { IRootState, ISite, IUser } from "@ax/types";
8
8
  import { useModal } from "@ax/hooks";
9
9
  import UserForm from "../UserForm";
10
10
 
11
11
  import * as S from "./style";
12
12
 
13
13
  const UserEdit = (props: IProps) => {
14
- const { user, updateUser, isSaving, isLoading, deleteUser, currentUser, setHistoryPush } = props;
14
+ const { user, updateUser, isSaving, isLoading, deleteUser, currentUser, setHistoryPush, site, sites } = props;
15
15
 
16
16
  const { timezone } = user;
17
- const initForm = { ...user };
17
+ const initForm = { ...user, sites: user.sites || [] };
18
18
  if (!timezone) initForm.timezone = "Europe/Madrid";
19
-
19
+ const isSiteView = !!site;
20
+ const sitesIds = sites.map((site: ISite) => site.id);
21
+ const usersRoute = isSiteView ? "/sites/users" : "users";
20
22
  const [form, setForm] = useState<IUser>(initForm);
21
23
  const { isOpen, toggleModal } = useModal();
22
24
 
25
+ useEffect(() => {
26
+ const { timezone } = user;
27
+ const initForm = { ...user };
28
+ if (!timezone) initForm.timezone = "Europe/Madrid";
29
+ setForm(initForm);
30
+ }, [user]);
31
+
23
32
  const handleSave = () => {
24
33
  form.id && updateUser(form.id, form, false);
25
34
  };
@@ -35,7 +44,7 @@ const UserEdit = (props: IProps) => {
35
44
  deleteUser(user.id).then((deleted: boolean) => {
36
45
  if (deleted) {
37
46
  setForm({ ...form, id: null });
38
- setHistoryPush("/users");
47
+ setHistoryPush(usersRoute);
39
48
  }
40
49
  });
41
50
  };
@@ -44,12 +53,30 @@ const UserEdit = (props: IProps) => {
44
53
 
45
54
  const rightLineButtonProps = !isSameUser
46
55
  ? {
47
- label: "Delete User",
56
+ label: isSiteView ? "Remove user from this site" : "Delete User",
48
57
  action: toggleModal,
49
58
  }
50
59
  : undefined;
51
60
 
52
- const mainDeleteAction = { title: "Delete User", onClick: handleDelete };
61
+ const getUpdatedSites = (currentUserSiteIds: any) =>
62
+ currentUserSiteIds[0] === "all"
63
+ ? sitesIds.filter((id: number) => id !== site.id)
64
+ : currentUserSiteIds.filter((id: number) => id !== site.id);
65
+
66
+ const removeUserFromSite = () => {
67
+ const updatedSites = getUpdatedSites(form.sites);
68
+ const formWithUpdatedSites = { ...form, sites: updatedSites };
69
+
70
+ user.id &&
71
+ updateUser(user.id, formWithUpdatedSites, true).then(() => {
72
+ setHistoryPush(usersRoute);
73
+ });
74
+ };
75
+
76
+ const mainDeleteAction = {
77
+ title: isSiteView ? "Remove user" : "Delete User",
78
+ onClick: isSiteView ? removeUserFromSite : handleDelete,
79
+ };
53
80
  const secondaryDeleteAction = { title: "Cancel", onClick: toggleModal };
54
81
 
55
82
  if (isLoading) return <Loading />;
@@ -59,22 +86,29 @@ const UserEdit = (props: IProps) => {
59
86
  title="Edit User"
60
87
  rightButton={rightButtonProps}
61
88
  backLink={true}
62
- rightLineButton={rightLineButtonProps}
89
+ rightLineButton={isSiteView ? rightLineButtonProps : undefined}
63
90
  >
64
- <UserForm form={form} setForm={setForm} user={user} />
91
+ <UserForm form={form} setForm={setForm} user={user} isSiteView={isSiteView} />
65
92
  <Modal
66
93
  isOpen={isOpen}
67
94
  hide={toggleModal}
68
- title="Delete User?"
95
+ title={isSiteView ? "Remove user from this site?" : "Delete User?"}
69
96
  secondaryAction={secondaryDeleteAction}
70
97
  mainAction={mainDeleteAction}
71
98
  >
72
99
  {isOpen ? (
73
100
  <S.ModalContent>
74
- <p>
75
- Are you sure you want to delete <strong>{user.email}</strong>? If you delete it, this user will no longer
76
- be able to log in.
77
- </p>
101
+ {isSiteView ? (
102
+ <p>
103
+ Are you sure you want to remove <strong>{user.email}</strong> from this site? If you remove it, this
104
+ user will no longer have access to this site but it will still be able to log in.
105
+ </p>
106
+ ) : (
107
+ <p>
108
+ Are you sure you want to delete <strong>{user.email}</strong>? If you delete it, this user will no
109
+ longer be able to log in.
110
+ </p>
111
+ )}
78
112
  <p>
79
113
  This action <strong>cannot be undone</strong>.
80
114
  </p>
@@ -87,6 +121,8 @@ const UserEdit = (props: IProps) => {
87
121
 
88
122
  const mapStateToProps = (state: IRootState) => ({
89
123
  user: state.users.userForm,
124
+ site: state.sites.currentSiteInfo,
125
+ sites: state.sites.sites,
90
126
  isSaving: state.app.isSaving,
91
127
  isLoading: state.app.isLoading,
92
128
  currentUser: state.users.currentUser,
@@ -106,9 +142,11 @@ const mapDispatchToProps = {
106
142
 
107
143
  interface IProfileProps {
108
144
  user: IUser;
145
+ site: ISite;
109
146
  isSaving: boolean;
110
147
  isLoading: boolean;
111
148
  currentUser: IUser;
149
+ sites: ISite[];
112
150
  }
113
151
 
114
152
  type IProps = IProfileProps & IDispatchProps;
@@ -1,9 +1,9 @@
1
- import React from "react";
1
+ import React, { useState, useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { appActions } from "@ax/containers/App";
5
- import { ErrorToast, FieldsBehavior } from "@ax/components";
6
- import { IImage, IRootState, IUser } from "@ax/types";
5
+ import { Button, ErrorToast, FieldsBehavior, SearchField } from "@ax/components";
6
+ import { IImage, IRootState, IUser, ISite } from "@ax/types";
7
7
  import { useModal } from "@ax/hooks";
8
8
  import { RouteLeavingGuard } from "@ax/guards";
9
9
 
@@ -11,14 +11,27 @@ import { timezones } from "../../Settings/Globals/constants";
11
11
  import { PasswordModal } from "./atoms";
12
12
  import { shouldBeSaved } from "./helpers";
13
13
 
14
+ import SiteItem from "../UserCreate/SiteItem";
15
+
14
16
  import * as S from "./style";
15
17
 
16
18
  const UserForm = (props: IProps) => {
17
- const { user, form, setForm, setHistoryPush, currentUser } = props;
18
-
19
+ const { user, form, setForm, setHistoryPush, currentUser, sites, readOnlySites = false, isSiteView = false } = props;
19
20
  const { id, username, name, email, image, company, description, timezone } = form;
20
21
 
22
+ const getSortedItemsByName = (items: any) => items.sort((item1: any, item2: any) => item1.name.localeCompare(item2.name));
23
+
24
+ const sortedByNameSites = getSortedItemsByName(sites);
25
+
21
26
  const { isOpen, toggleModal } = useModal(false);
27
+ const [sitesList, setSiteList] = useState(sortedByNameSites);
28
+ const [showMore, setShowMore] = useState(false);
29
+
30
+ useEffect(() => {
31
+ if (!form.sites[0]) {
32
+ setShowMore(true);
33
+ }
34
+ }, [form.sites]);
22
35
 
23
36
  const handleImageChange = (value: IImage) => setForm({ ...form, image: value || null });
24
37
  const handleNameChange = (value: string) => setForm({ ...form, name: value });
@@ -35,16 +48,58 @@ const UserForm = (props: IProps) => {
35
48
  };
36
49
 
37
50
  const action = (path: string) => setHistoryPush(path);
51
+
52
+ const filterSitesList = (query: string) => {
53
+ const filteredSites = sites.filter((site: any) =>
54
+ site.name.toLowerCase().replace(" ", "").includes(query.toLowerCase().replace(" ", ""))
55
+ );
56
+ const sortedSites = getSortedItemsByName(filteredSites);
57
+ setSiteList(sortedSites);
58
+ };
59
+
60
+ const selectAllSites = () => {
61
+ const currentSites = form.sites;
62
+ if (currentSites.includes("all")) {
63
+ setForm({ ...form, sites: [] });
64
+ setShowMore(true);
65
+ } else {
66
+ setForm({ ...form, sites: ["all"] });
67
+ }
68
+ };
69
+
70
+ const selectSite = (id: number | string) => {
71
+ const currentSites = form.sites;
72
+ if (currentSites.includes(id)) {
73
+ const filteredSites = [...currentSites].filter((site) => site && site !== id);
74
+ setForm({ ...form, sites: filteredSites });
75
+ } else {
76
+ const filteredSites = [...currentSites].filter((site) => site && site !== "all");
77
+ setForm({ ...form, sites: [...filteredSites, id] });
78
+ }
79
+ };
80
+
38
81
  const text = (
39
82
  <>
40
83
  Some changes <strong>are not saved</strong>.
41
84
  </>
42
85
  );
86
+
43
87
  const allowedRoutes = ["/profile"];
44
88
  const { isDirty } = shouldBeSaved(user, form);
45
89
 
46
90
  const isSameUser = currentUser.id === user.id;
47
91
 
92
+ const ShowMoreButton = () => (
93
+ <Button type="button" buttonStyle="line" onClick={() => setShowMore(!showMore)}>
94
+ {showMore ? "Show less sites" : "Show more sites"}
95
+ </Button>
96
+ );
97
+
98
+ const getSiteName = (id: number | string) => {
99
+ const currentSite = sites.find((site) => site.id === id) || { name: "" };
100
+ return currentSite.name;
101
+ };
102
+
48
103
  return (
49
104
  <>
50
105
  <RouteLeavingGuard when={isDirty} action={action} text={text} allowedRoutes={allowedRoutes} />
@@ -128,6 +183,94 @@ const UserForm = (props: IProps) => {
128
183
  onChange={handleTimezoneChange}
129
184
  mandatory={true}
130
185
  />
186
+ {isSiteView ? null : (
187
+ <>
188
+ <S.SettingsWrapper>
189
+ <S.Heading>Sites assigned</S.Heading>
190
+
191
+ {form.sites.length > 0 ? (
192
+ <S.SettingContent>
193
+ <S.SettingText>
194
+ {readOnlySites
195
+ ? "You have access to this sites."
196
+ : form.sites[0] !== "all"
197
+ ? "You can give access to one or more sites to this user."
198
+ : "You can give access to all sites at the same time."}
199
+ </S.SettingText>
200
+ </S.SettingContent>
201
+ ) : null}
202
+
203
+ {form.sites[0] !== "all" &&
204
+ form.sites.map((id: number | string, index: number) => {
205
+ if (id && id !== "all") {
206
+ return (
207
+ <SiteItem
208
+ readOnly={readOnlySites}
209
+ disabled={form.sites[0] === "all"}
210
+ sites={form.sites}
211
+ key={`${index}${getSiteName(id)}`}
212
+ item={{ name: getSiteName(id), id, onChange: () => selectSite(id) }}
213
+ />
214
+ );
215
+ } else {
216
+ return null;
217
+ }
218
+ })}
219
+ {form.sites[0] === "all" && (
220
+ <S.SelectAllSitesFieldWrapper>
221
+ <SiteItem
222
+ readOnly={readOnlySites}
223
+ sites={form.sites}
224
+ item={{ name: "Access to All Sites", id: "all", onChange: selectAllSites }}
225
+ />
226
+ </S.SelectAllSitesFieldWrapper>
227
+ )}
228
+ {!showMore && !readOnlySites ? <ShowMoreButton /> : null}
229
+ </S.SettingsWrapper>
230
+ {showMore ? (
231
+ <>
232
+ {form.sites[0] !== "all" && (
233
+ <>
234
+ <S.SettingContent>
235
+ <S.SettingText>You can give access to all sites at the same time.</S.SettingText>
236
+ </S.SettingContent>
237
+ <S.SelectAllSitesFieldWrapper>
238
+ <SiteItem
239
+ readOnly={readOnlySites}
240
+ sites={form.sites}
241
+ item={{ name: "Access to All Sites", id: "all", onChange: selectAllSites }}
242
+ />
243
+ </S.SelectAllSitesFieldWrapper>
244
+ </>
245
+ )}
246
+ <S.SubTitle>Sites list</S.SubTitle>
247
+ <S.SearchFieldWrapper>
248
+ <SearchField
249
+ disabled={form.sites[0] === "all"}
250
+ placeholder="Search"
251
+ onChange={filterSitesList}
252
+ searchOnEnter={false}
253
+ />
254
+ </S.SearchFieldWrapper>
255
+ {sitesList.length > 0 &&
256
+ sitesList.map((site: ISite) => {
257
+ return (
258
+ !form.sites.includes(site.id) && (
259
+ <SiteItem
260
+ readOnly={readOnlySites}
261
+ disabled={form.sites[0] === "all"}
262
+ sites={form.sites}
263
+ key={site.id}
264
+ item={{ name: site.name, id: site.id, onChange: () => selectSite(site.id) }}
265
+ />
266
+ )
267
+ );
268
+ })}
269
+ {showMore && form.sites.length > 0 && !readOnlySites ? <ShowMoreButton /> : null}
270
+ </>
271
+ ) : null}
272
+ </>
273
+ )}
131
274
  </S.Wrapper>
132
275
  <PasswordModal {...modalProps} />
133
276
  </>
@@ -138,6 +281,7 @@ const mapStateToProps = (state: IRootState) => ({
138
281
  isSaving: state.app.isSaving,
139
282
  isLoading: state.app.isLoading,
140
283
  currentUser: state.users.currentUser,
284
+ sites: state.sites.sites,
141
285
  });
142
286
 
143
287
  interface IDispatchProps {
@@ -155,6 +299,9 @@ interface IProfileProps {
155
299
  isLoading: boolean;
156
300
  setForm: (form: any) => void;
157
301
  currentUser: IUser;
302
+ sites: ISite[];
303
+ readOnlySites?: boolean;
304
+ isSiteView?: boolean;
158
305
  }
159
306
 
160
307
  type IProps = IProfileProps & IDispatchProps;
@@ -22,9 +22,47 @@ const ModalContent = styled.div`
22
22
  padding: ${p => p.theme.spacing.m};
23
23
  `;
24
24
 
25
+ const SettingsWrapper = styled.div`
26
+ position: relative;
27
+ border-top: 1px solid ${(p) => p.theme.color.uiLine};
28
+ padding-top: ${(p) => p.theme.spacing.m};
29
+ `;
30
+
31
+ const Heading = styled.div`
32
+ ${(p) => p.theme.textStyle.headingXS};
33
+ color: ${(p) => p.theme.color.textHighEmphasis};
34
+ padding-bottom: ${(p) => p.theme.spacing.xs};
35
+ `;
36
+
37
+ const SettingContent = styled.div`
38
+ padding-bottom: ${(p) => p.theme.spacing.m};
39
+ `;
40
+
41
+ const SettingText = styled.div`
42
+ ${(p) => p.theme.textStyle.uiM};
43
+ color: ${(p) => p.theme.color.textMediumEmphasis};
44
+ width: calc(${(p) => p.theme.spacing.l} * 12);
45
+ `;
46
+
47
+ const SearchFieldWrapper = styled.div`
48
+ padding-top: ${(p) => p.theme.spacing.xs};
49
+ padding-bottom: ${(p) => p.theme.spacing.xs};
50
+ max-width: calc(${(p) => p.theme.spacing.l} * 7);
51
+ `;
52
+
53
+ const SelectAllSitesFieldWrapper = styled.div`
54
+ padding-bottom: ${(p) => p.theme.spacing.s};
55
+ `;
56
+
25
57
  export {
26
58
  Wrapper,
27
59
  NameTitle,
28
60
  SubTitle,
29
- ModalContent
30
- };
61
+ ModalContent,
62
+ SettingsWrapper,
63
+ SettingText,
64
+ SettingContent,
65
+ Heading,
66
+ SearchFieldWrapper,
67
+ SelectAllSitesFieldWrapper
68
+ };
@@ -1,11 +1,22 @@
1
1
  import React from "react";
2
2
 
3
- import { CheckField, TableCounter } from "@ax/components";
3
+ import { CheckField, TableCounter, SiteFilter } from "@ax/components";
4
+
4
5
  import Name from "../../HeaderMenus/Name";
6
+
5
7
  import * as S from "./style";
6
8
 
7
9
  const TableHeader = (props: IProps) => {
8
- const { totalItems, selectAllItems, isScrolling, sortItems, sortedListStatus } = props;
10
+ const {
11
+ totalItems,
12
+ selectAllItems,
13
+ isScrolling,
14
+ sortItems,
15
+ filterItems,
16
+ sortedListStatus,
17
+ filterValues,
18
+ showSiteFilter = true,
19
+ } = props;
9
20
 
10
21
  return (
11
22
  <S.TableHeader isScrolling={isScrolling}>
@@ -23,6 +34,30 @@ const TableHeader = (props: IProps) => {
23
34
  <S.NameWrapper>
24
35
  <Name sortItems={sortItems} sortedState={sortedListStatus} />
25
36
  </S.NameWrapper>
37
+ {showSiteFilter ? (
38
+ <S.NameWrapper>
39
+ <SiteFilter
40
+ filterItems={filterItems}
41
+ value={filterValues}
42
+ pointer="filterSites"
43
+ center={false}
44
+ label="Sites"
45
+ selectAllOption="noFilter"
46
+ filters={[
47
+ {
48
+ name: "noFilter",
49
+ value: "noFilter",
50
+ title: "All",
51
+ },
52
+ {
53
+ name: "all",
54
+ value: "all",
55
+ title: "Access to all sites",
56
+ },
57
+ ]}
58
+ />
59
+ </S.NameWrapper>
60
+ ) : null}
26
61
  <S.ActionsHeader>
27
62
  <TableCounter totalItems={totalItems} />
28
63
  </S.ActionsHeader>
@@ -33,9 +68,12 @@ const TableHeader = (props: IProps) => {
33
68
  interface IProps {
34
69
  totalItems: number;
35
70
  isScrolling: boolean;
71
+ showSiteFilter: boolean;
36
72
  selectAllItems: () => void;
73
+ filterItems: (filterPointer: string, filtersSelected: string) => void;
37
74
  sortItems: any;
38
75
  sortedListStatus: any;
76
+ filterValues: any;
39
77
  }
40
78
 
41
79
  export default TableHeader;
@@ -15,7 +15,6 @@ const CheckHeader = styled(Header as any)`
15
15
  `;
16
16
 
17
17
  const NameWrapper = styled.div`
18
- width: 40%;
19
18
  flex-grow: 1;
20
19
  position: relative;
21
20
  `;
@@ -12,13 +12,16 @@ const BulkHeader = (props: IProps): JSX.Element => {
12
12
  totalItems,
13
13
  isScrolling,
14
14
  sortItems,
15
+ filterItems,
15
16
  sortedListStatus,
17
+ filterValues,
18
+ isSiteView = false,
16
19
  } = props;
17
20
 
18
21
  const bulkActions = [
19
22
  {
20
23
  icon: "delete",
21
- text: "delete",
24
+ text: isSiteView ? "Remove from this site" : "delete",
22
25
  action: bulkDelete,
23
26
  },
24
27
  ];
@@ -32,17 +35,21 @@ const BulkHeader = (props: IProps): JSX.Element => {
32
35
  />
33
36
  ) : (
34
37
  <TableHeader
38
+ filterValues={filterValues}
35
39
  totalItems={totalItems}
36
40
  selectAllItems={selectAllItems}
41
+ filterItems={filterItems}
37
42
  isScrolling={isScrolling}
38
43
  sortItems={sortItems}
39
44
  sortedListStatus={sortedListStatus}
45
+ showSiteFilter={!isSiteView}
40
46
  />
41
47
  );
42
48
  };
43
49
 
44
50
  interface IProps {
45
51
  showBulk: boolean;
52
+ isSiteView?: boolean;
46
53
  checkState: any;
47
54
  bulkDelete: () => void;
48
55
  selectItems: () => void;
@@ -51,6 +58,8 @@ interface IProps {
51
58
  isScrolling: boolean;
52
59
  sortItems: (orderPointer: string, isAscending: boolean) => void;
53
60
  sortedListStatus: any;
61
+ filterValues: any;
62
+ filterItems: (filterPointer: string, filtersSelected: string) => void;
54
63
  }
55
64
 
56
65
  export default BulkHeader;
@@ -1,24 +1,36 @@
1
1
  import React from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { ICheck, IUser } from "@ax/types";
4
+ import { ICheck, IUser, ISite } from "@ax/types";
5
5
  import { useModal, useToast } from "@ax/hooks";
6
6
  import { getDaysAgo } from "@ax/helpers";
7
7
  import { usersActions } from "@ax/containers/Users";
8
- import { CheckField, Avatar, Modal, Tag, Tooltip, Toast } from "@ax/components";
8
+ import { CheckField, Avatar, Modal, Tag, Tooltip, Toast, ElementsTooltip } from "@ax/components";
9
9
 
10
10
  import * as S from "./style";
11
11
 
12
12
  const UserItem = (props: IProps): JSX.Element => {
13
- const { user, isSelected, onChange, handleClick, deleteUser, resendInvitation } = props;
13
+ const {
14
+ user,
15
+ sites,
16
+ siteId,
17
+ isSelected,
18
+ onChange,
19
+ handleClick,
20
+ deleteUser,
21
+ updateUser,
22
+ resendInvitation,
23
+ isSiteView = false,
24
+ } = props;
14
25
 
15
26
  const { isOpen, toggleModal } = useModal();
16
27
  const { isVisible, toggleToast, setIsVisible } = useToast();
17
28
 
18
29
  const handleOnChange = (value: ICheck) => onChange(value);
30
+ const sitesIds = sites.map((site: ISite) => site.id);
19
31
 
20
32
  const deleteOption = {
21
- label: "delete",
33
+ label: isSiteView ? "remove from site" : "delete",
22
34
  icon: "delete",
23
35
  action: () => toggleModal(),
24
36
  };
@@ -48,7 +60,20 @@ const UserItem = (props: IProps): JSX.Element => {
48
60
 
49
61
  const menuOptions = user.status === "invited" ? [resendOption, deleteOption] : [deleteOption];
50
62
 
51
- const mainDeleteAction = { title: "Delete User", onClick: handleDeleteUser };
63
+ const getUpdatedSites = (currentUserSiteIds: any) =>
64
+ currentUserSiteIds[0] === "all"
65
+ ? sitesIds.filter((id: number) => id !== siteId)
66
+ : currentUserSiteIds.filter((id: number) => id !== siteId);
67
+
68
+ const removeUserFromSite = () => {
69
+ const updatedSites = getUpdatedSites(user.sites);
70
+ const updatedUser = { ...user, sites: updatedSites };
71
+ user.id && updateUser(user.id, updatedUser, false, true).then(() => toggleModal());
72
+ };
73
+ const mainDeleteAction = {
74
+ title: isSiteView ? "Remove user" : "Delete user",
75
+ onClick: isSiteView ? removeUserFromSite : handleDeleteUser,
76
+ };
52
77
  const secondaryDeleteAction = { title: "Cancel", onClick: toggleModal };
53
78
 
54
79
  const tagColor = user.status === "invited" ? "#FFBB37" : "#AFC628";
@@ -61,6 +86,17 @@ const UserItem = (props: IProps): JSX.Element => {
61
86
  message: "Invitation sended",
62
87
  };
63
88
 
89
+ const getSiteNameById = (id: number | string) => {
90
+ if (id === "all") {
91
+ return "ALL SITES";
92
+ } else {
93
+ const currentSite = sites.find((site: ISite) => site.id === id);
94
+ return currentSite ? currentSite?.name : "";
95
+ }
96
+ };
97
+
98
+ const getElementsNames = () => user?.sites.map((id: string | number) => getSiteNameById(id));
99
+
64
100
  return (
65
101
  <>
66
102
  <S.UserRow role="rowgroup" selected={isSelected}>
@@ -77,11 +113,18 @@ const UserItem = (props: IProps): JSX.Element => {
77
113
  <S.UserUsername>@{user.username}</S.UserUsername>
78
114
  </S.UserInfo>
79
115
  </S.UserCell>
80
- <S.StatusCell>
81
- <Tooltip content={tooltipText} hideOnClick>
82
- <Tag type="status" text={user.status ?? ""} color={tagColor} />
83
- </Tooltip>
84
- </S.StatusCell>
116
+ {isSiteView ? null : (
117
+ <S.UserCell role="cell" onClick={handleClick}>
118
+ {user?.sites?.length > 0 && <ElementsTooltip elements={getElementsNames()} elementsPerRow={3} defaultElements={3} />}
119
+ </S.UserCell>
120
+ )}
121
+ {isSiteView ? null : (
122
+ <S.StatusCell>
123
+ <Tooltip content={tooltipText} hideOnClick>
124
+ <Tag type="status" text={user.status ?? ""} color={tagColor} />
125
+ </Tooltip>
126
+ </S.StatusCell>
127
+ )}
85
128
  <S.ActionsCell role="cell">
86
129
  <S.StyledActionMenu icon="more" options={menuOptions} tooltip="Actions" />
87
130
  </S.ActionsCell>
@@ -89,16 +132,23 @@ const UserItem = (props: IProps): JSX.Element => {
89
132
  <Modal
90
133
  isOpen={isOpen}
91
134
  hide={toggleModal}
92
- title="Delete User?"
135
+ title={isSiteView ? "Remove user from this site?" : "Delete User?"}
93
136
  secondaryAction={secondaryDeleteAction}
94
137
  mainAction={mainDeleteAction}
95
138
  >
96
139
  {isOpen ? (
97
140
  <S.ModalContent>
98
- <p>
99
- Are you sure you want to delete <strong>{user.email}</strong>? If you delete it, this user will no longer
100
- be able to log in.
101
- </p>
141
+ {isSiteView ? (
142
+ <p>
143
+ Are you sure you want to remove <strong>{user.email}</strong> from this site? If you remove it, this
144
+ user will no longer have access to this site but it will still be able to log in.
145
+ </p>
146
+ ) : (
147
+ <p>
148
+ Are you sure you want to delete <strong>{user.email}</strong>? If you delete it, this user will no
149
+ longer be able to log in.
150
+ </p>
151
+ )}
102
152
  <p>
103
153
  This action <strong>cannot be undone</strong>.
104
154
  </p>
@@ -112,17 +162,22 @@ const UserItem = (props: IProps): JSX.Element => {
112
162
 
113
163
  interface IUserItemProps {
114
164
  user: IUser;
165
+ siteId?: number;
166
+ sites: ISite[];
115
167
  isSelected: boolean;
168
+ isSiteView?: boolean;
116
169
  onChange: (value: ICheck) => void;
117
170
  handleClick: (id: number) => void;
118
171
  }
119
172
 
120
173
  const mapDispatchToProps = {
121
174
  deleteUser: usersActions.deleteUser,
175
+ updateUser: usersActions.updateUser,
122
176
  resendInvitation: usersActions.resendInvitation,
123
177
  };
124
178
 
125
179
  interface IDispatchProps {
180
+ updateUser(id: number, data: any, isProfile: boolean, isList?: boolean): any;
126
181
  deleteUser(id: number): Promise<boolean>;
127
182
  resendInvitation(id: number): Promise<boolean>;
128
183
  }