@griddo/ax 11.14.1 → 11.14.2-rc.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 (142) hide show
  1. package/config/jest/reactEasyCropMock.js +15 -0
  2. package/config/jest/reactTimezoneMock.js +13 -0
  3. package/package.json +221 -219
  4. package/public/img/welcome.svg +127 -0
  5. package/src/__tests__/components/Browser/Browser.test.tsx +27 -51
  6. package/src/__tests__/components/CategoryCell/CategoryCell.test.tsx +10 -5
  7. package/src/__tests__/components/ElementsTooltip/ElementsTooltip.test.tsx +27 -14
  8. package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +2 -0
  9. package/src/__tests__/components/HeadingsPreviewModal/HeadingsPreviewModal.utils.test.tsx +138 -1
  10. package/src/__tests__/components/ImageDragAndDrop/CropStep/CropStep.test.tsx +84 -0
  11. package/src/__tests__/components/ImageDragAndDrop/ImageDragAndDrop.test.tsx +169 -0
  12. package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
  13. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
  14. package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
  15. package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
  16. package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
  17. package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
  18. package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
  19. package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
  20. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
  21. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
  22. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
  23. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
  24. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
  25. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
  26. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
  27. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
  28. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
  29. package/src/api/sites.tsx +4 -4
  30. package/src/components/Avatar/index.tsx +26 -5
  31. package/src/components/Avatar/style.tsx +20 -10
  32. package/src/components/Browser/index.tsx +7 -1
  33. package/src/components/ConfigPanel/index.tsx +5 -4
  34. package/src/components/ElementsTooltip/index.tsx +96 -34
  35. package/src/components/ElementsTooltip/style.tsx +12 -1
  36. package/src/components/Fields/FileField/index.tsx +16 -17
  37. package/src/components/Fields/HeadingField/index.tsx +1 -1
  38. package/src/components/Fields/ImageField/index.tsx +9 -38
  39. package/src/components/Fields/ImageField/style.tsx +12 -1
  40. package/src/components/Fields/ToggleField/index.tsx +1 -1
  41. package/src/components/Fields/Wysiwyg/index.tsx +25 -20
  42. package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
  43. package/src/components/FileGallery/index.tsx +33 -28
  44. package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
  45. package/src/components/Gallery/index.tsx +0 -2
  46. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
  47. package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
  48. package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
  49. package/src/components/HeadingsPreviewModal/index.tsx +13 -3
  50. package/src/components/HeadingsPreviewModal/style.tsx +18 -0
  51. package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
  52. package/src/components/Image/index.tsx +2 -2
  53. package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
  54. package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
  55. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
  56. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
  57. package/src/components/ProfileImage/index.tsx +55 -0
  58. package/src/components/ProfileImage/style.tsx +58 -0
  59. package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
  60. package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
  61. package/src/components/ResizePanel/index.tsx +25 -4
  62. package/src/components/Tabs/style.tsx +1 -1
  63. package/src/components/Tag/index.tsx +0 -1
  64. package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
  65. package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
  66. package/src/components/UserRolesAndSites/index.tsx +102 -0
  67. package/src/components/UserRolesAndSites/style.tsx +67 -0
  68. package/src/components/index.tsx +6 -0
  69. package/src/constants/index.ts +13 -1
  70. package/src/containers/App/actions.tsx +8 -1
  71. package/src/containers/Sites/actions.tsx +26 -0
  72. package/src/containers/Sites/constants.tsx +1 -0
  73. package/src/containers/Sites/interfaces.tsx +6 -0
  74. package/src/containers/Sites/reducer.tsx +5 -1
  75. package/src/containers/Users/reducer.tsx +6 -5
  76. package/src/guards/routeLeaving/index.tsx +9 -11
  77. package/src/helpers/images.tsx +50 -3
  78. package/src/helpers/index.tsx +2 -1
  79. package/src/hooks/forms.tsx +45 -48
  80. package/src/hooks/modals.tsx +4 -3
  81. package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
  82. package/src/modules/App/Routing/Logout/index.tsx +3 -5
  83. package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
  84. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
  85. package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
  86. package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
  87. package/src/modules/CreatePass/index.tsx +1 -1
  88. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
  89. package/src/modules/FileDrive/FileModal/index.tsx +8 -9
  90. package/src/modules/FileDrive/index.tsx +1 -18
  91. package/src/modules/Forms/FormEditor/index.tsx +1 -1
  92. package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
  93. package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
  94. package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
  95. package/src/modules/MediaGallery/index.tsx +1 -3
  96. package/src/modules/Settings/Globals/constants.tsx +942 -106
  97. package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
  98. package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
  99. package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
  100. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
  101. package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
  102. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
  103. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
  104. package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
  105. package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
  106. package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
  107. package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
  108. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
  109. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
  110. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
  111. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
  112. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
  113. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
  114. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
  115. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
  116. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
  117. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
  118. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
  119. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
  120. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
  121. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
  122. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
  123. package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
  124. package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
  125. package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
  126. package/src/modules/Sites/SitesList/atoms.tsx +4 -4
  127. package/src/modules/Sites/SitesList/hooks.tsx +149 -16
  128. package/src/modules/Sites/SitesList/index.tsx +127 -125
  129. package/src/modules/Sites/SitesList/style.tsx +1 -117
  130. package/src/modules/Sites/SitesList/utils.tsx +9 -2
  131. package/src/modules/Sites/index.tsx +19 -8
  132. package/src/modules/Users/Profile/index.tsx +169 -31
  133. package/src/modules/Users/Profile/style.tsx +81 -1
  134. package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
  135. package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
  136. package/src/modules/Users/UserForm/atoms.tsx +3 -3
  137. package/src/modules/Users/UserForm/index.tsx +25 -29
  138. package/src/modules/Users/UserForm/style.tsx +15 -2
  139. package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
  140. package/src/routes/index.tsx +1 -0
  141. package/src/types/index.tsx +2 -0
  142. /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
@@ -1,4 +1,4 @@
1
- import styled, { css, keyframes } from "styled-components";
1
+ import styled, { keyframes } from "styled-components";
2
2
 
3
3
  const OpacityAnimation = keyframes`
4
4
  to {
@@ -12,113 +12,6 @@ const TranslateAnimation = keyframes`
12
12
  }
13
13
  }`;
14
14
 
15
- const SectionHeader = styled.div<{ isRecentSites?: boolean }>`
16
- display: flex;
17
- justify-content: space-between;
18
- margin: 0 ${(p) => (p.isRecentSites ? p.theme.spacing.s : p.theme.spacing.m)};
19
- margin-top: ${(p) => p.theme.spacing.m};
20
- `;
21
-
22
- const Label = styled.span`
23
- margin-right: 14px;
24
- `;
25
-
26
- const Title = styled.h1<{ isActive?: boolean }>`
27
- margin: 0;
28
- margin-right: ${(p) => p.theme.spacing.m};
29
- ${(p) => p.theme.textStyle.headingM};
30
- color: ${(p) => (p.isActive ? p.theme.colors.textHighEmphasis : p.theme.colors.textMediumEmphasis)};
31
- `;
32
-
33
- const HeaderIconsWrapper = styled.div`
34
- display: flex;
35
- align-items: center;
36
- `;
37
-
38
- const FilterSelectLabel = styled.span`
39
- color: ${(p) => p.theme.color.interactive01};
40
- `;
41
-
42
- const FilterSelect = styled.div`
43
- ${(p) => p.theme.textStyle.uiS};
44
- line-height: 32px;
45
- display: flex;
46
- align-items: center;
47
- position: relative;
48
- color: ${(p) => p.theme.color.textLowEmphasis};
49
- `;
50
-
51
- const CollapseButton = styled.div`
52
- display: flex;
53
- align-items: center;
54
- ${(p) => p.theme.textStyle.uiS};
55
- color: ${(p) => p.theme.colors.textMediumEmphasis};
56
- padding: 0;
57
- margin: 0;
58
- cursor: pointer;
59
- `;
60
-
61
- const RecentSites = styled.div<{ fullWidth: boolean }>`
62
- display: ${(p) => (p.fullWidth ? "block" : "inline-block")};
63
- padding-bottom: ${(p) => p.theme.spacing.s};
64
- border-radius: ${(p) => p.theme.radii.s};
65
- background: ${(p) => p.theme.color.uiBackground02};
66
- min-height: 56px;
67
- overflow: hidden;
68
- margin: ${(p) => p.theme.spacing.m};
69
- margin-bottom: 0;
70
- flex-shrink: 0;
71
-
72
- h1 {
73
- padding-left: 0;
74
- }
75
- `;
76
-
77
- const RecentSitesItemsWrapper = styled.div<{ isHidden: boolean }>`
78
- display: grid;
79
- grid-auto-flow: column;
80
- overflow: auto;
81
- grid-gap: ${(p) => p.theme.spacing.m};
82
- padding: ${(p) => p.theme.spacing.s} ${(p) => p.theme.spacing.s} 0px ${(p) => p.theme.spacing.s};
83
- overflow: hidden;
84
-
85
- div {
86
- min-width: 100%;
87
- }
88
-
89
- ${(props) =>
90
- props.isHidden &&
91
- css`
92
- height: 0;
93
- padding: 0;
94
- padding-left: ${(p) => p.theme.spacing.s};
95
- padding-top: ${(p) => p.theme.spacing.xs};
96
- `};
97
-
98
- @media (min-width: 1200px) {
99
- div:nth-child(6),
100
- div:nth-child(7) {
101
- display: none;
102
- }
103
- }
104
-
105
- @media (min-width: 1600px) {
106
- div:nth-child(6) {
107
- display: block;
108
- }
109
- div:nth-child(7) {
110
- display: none;
111
- }
112
- }
113
-
114
- @media (min-width: 1750px) {
115
- div:nth-child(6),
116
- div:nth-child(7) {
117
- display: block;
118
- }
119
- }
120
- `;
121
-
122
15
  const SitesListWrapper = styled.div`
123
16
  max-width: calc(100vw - calc(24px * 3));
124
17
  height: 100%;
@@ -205,15 +98,6 @@ const SearchTagsGrid = styled.div`
205
98
  `;
206
99
 
207
100
  export {
208
- SectionHeader,
209
- CollapseButton,
210
- Label,
211
- Title,
212
- HeaderIconsWrapper,
213
- FilterSelect,
214
- FilterSelectLabel,
215
- RecentSites,
216
- RecentSitesItemsWrapper,
217
101
  SitesListWrapper,
218
102
  GridList,
219
103
  EmptyStateWrapper,
@@ -1,6 +1,13 @@
1
- import { ISite } from "@ax/types";
1
+ import type { ISite } from "@ax/types";
2
2
 
3
- const getSortedListStatus = (orderPointer: string, isAscending: boolean): any => {
3
+ export interface ISortedListStatus {
4
+ isAscending: boolean;
5
+ sortedByTitle: boolean;
6
+ sortedByLastAccess: boolean;
7
+ sortedByDateCreated: boolean;
8
+ }
9
+
10
+ const getSortedListStatus = (orderPointer: string, isAscending: boolean): ISortedListStatus => {
4
11
  const sortedListStatus = {
5
12
  isAscending,
6
13
  sortedByTitle: orderPointer === "name",
@@ -24,9 +24,9 @@ const Sites = (props: ISitesProps): JSX.Element => {
24
24
  resetCurrentData,
25
25
  setIsLoading,
26
26
  getUserCurrentPermissions,
27
+ getAllSites,
27
28
  } = props;
28
29
 
29
- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
30
30
  useLayoutEffect(() => {
31
31
  const fetchInitialData = async () => {
32
32
  setIsLoading(true);
@@ -37,22 +37,31 @@ const Sites = (props: ISitesProps): JSX.Element => {
37
37
  await getUserCurrentPermissions();
38
38
  await getStructuredData(token, null);
39
39
  await getAllDataPacks();
40
+ getAllSites();
40
41
 
41
42
  const defaultLanguage = globalLangs.find((language) => language.isDefault);
42
43
  if (defaultLanguage) {
43
44
  const { locale, id } = defaultLanguage;
44
- const lang = {
45
- locale,
46
- id,
47
- };
48
-
49
- setLanguage(lang);
45
+ setLanguage({ locale, id });
50
46
  }
51
47
  setIsLoading(false);
52
48
  };
53
49
 
54
50
  fetchInitialData();
55
- }, [token]);
51
+ }, [
52
+ token,
53
+ globalLangs,
54
+ setIsLoading,
55
+ setCurrentSiteInfo,
56
+ updateCurrentSearch,
57
+ resetCurrentData,
58
+ getRoles,
59
+ getUserCurrentPermissions,
60
+ getStructuredData,
61
+ getAllDataPacks,
62
+ setLanguage,
63
+ getAllSites,
64
+ ]);
56
65
 
57
66
  return <SitesList />;
58
67
  };
@@ -77,6 +86,7 @@ interface IDispatchProps {
77
86
  resetCurrentData(): Promise<void>;
78
87
  setIsLoading(isLoading: boolean): void;
79
88
  getUserCurrentPermissions(): Promise<void>;
89
+ getAllSites: () => Promise<void>;
80
90
  }
81
91
 
82
92
  export type ISitesProps = IStateProps & IDispatchProps & RouteComponentProps;
@@ -91,6 +101,7 @@ const mapDispatchToProps = {
91
101
  resetCurrentData: structuredDataActions.resetCurrentData,
92
102
  setIsLoading: appActions.setIsLoading,
93
103
  getUserCurrentPermissions: usersActions.getUserCurrentPermissions,
104
+ getAllSites: sitesActions.getAllSites,
94
105
  };
95
106
 
96
107
  export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Sites));
@@ -1,43 +1,68 @@
1
- import { useEffect, useState } from "react";
1
+ import { useCallback, useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
+ import {
5
+ ErrorToast,
6
+ FieldsBehavior,
7
+ FloatingNote,
8
+ Loading,
9
+ MainWrapper,
10
+ ProfileImage,
11
+ Tabs,
12
+ UserRolesAndSites,
13
+ } from "@ax/components";
4
14
  import { appActions } from "@ax/containers/App";
5
- import { usersActions } from "@ax/containers/Users";
6
15
  import { sitesActions } from "@ax/containers/Sites";
7
- import { Loading, MainWrapper } from "@ax/components";
8
- import type { IRootState, ISite, IUser } from "@ax/types";
16
+ import { usersActions } from "@ax/containers/Users";
17
+ import { RouteLeavingGuard } from "@ax/guards";
9
18
  import { useModal, useShouldBeSaved, useURLSearchParam } from "@ax/hooks";
19
+ import type { IImage, IRole, IRootState, ISite, IUser } from "@ax/types";
10
20
 
11
- import UserForm from "../UserForm";
21
+ import { timezones } from "../../Settings/Globals/constants";
12
22
  import { LinkDeviceModal } from "./atoms";
13
23
 
24
+ import * as S from "./style";
25
+
26
+ const PROFILE_TABS = {
27
+ PROFILE: "Profile",
28
+ PERMISSIONS: "Your permissions",
29
+ } as const;
30
+
31
+ const PROFILE_TAB_LIST = [PROFILE_TABS.PROFILE, PROFILE_TABS.PERMISSIONS] as const;
32
+
14
33
  const Profile = (props: IProps) => {
15
- const { user, updateUser, isSaving, isLoading, getSites, currentSiteInfo, token } = props;
34
+ const { user, updateUser, isSaving, isLoading, getSites, token, roles, sites, setHistoryPush } = props;
16
35
 
17
36
  if (!user) {
18
37
  throw new Error(`ERROR: User reached Profile with null user`);
19
38
  }
20
39
 
21
40
  const isUserInit = useURLSearchParam("init");
41
+ const initForm = {
42
+ ...user,
43
+ timezone: user.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone ?? "",
44
+ };
22
45
 
23
- const [form, setForm] = useState<IUser>({ ...user });
46
+ const [form, setForm] = useState<IUser>(initForm);
47
+ const [activeTab, setActiveTab] = useState<(typeof PROFILE_TABS)[keyof typeof PROFILE_TABS]>(PROFILE_TABS.PROFILE);
24
48
  const { isOpen, toggleModal } = useModal();
25
49
  const { isDirty, setIsDirty } = useShouldBeSaved(form);
26
50
 
27
- const isSiteView = !!currentSiteInfo;
28
-
29
- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
30
51
  useEffect(() => {
31
- const getUserData = async () => await getSites();
32
- isUserInit && getUserData();
33
- }, []);
52
+ if (isUserInit) {
53
+ getSites();
54
+ }
55
+ }, [isUserInit, getSites]);
34
56
 
35
- useEffect(() => {
36
- const { timezone } = user;
37
- const newForm = { ...user };
38
- if (!timezone) newForm.timezone = "Europe/Madrid";
39
- setForm(newForm);
40
- }, [user]);
57
+ const { username, name, email, image, company, position, timezone, isSuperAdmin } = form;
58
+
59
+ const handleNameChange = useCallback((value: string) => setForm((prev) => ({ ...prev, name: value })), []);
60
+ const handleEmailChange = useCallback((value: string) => setForm((prev) => ({ ...prev, email: value })), []);
61
+ const handleUsernameChange = useCallback((value: string) => setForm((prev) => ({ ...prev, username: value })), []);
62
+ const handleCompanyChange = useCallback((value: string) => setForm((prev) => ({ ...prev, company: value })), []);
63
+ const handlePositionChange = useCallback((value: string) => setForm((prev) => ({ ...prev, position: value })), []);
64
+ const handleTimezoneChange = useCallback((value: string) => setForm((prev) => ({ ...prev, timezone: value })), []);
65
+ const handleImageChange = useCallback((value: IImage) => setForm((prev) => ({ ...prev, image: value })), []);
41
66
 
42
67
  const handleSave = async () => {
43
68
  const isUpdated = form.id ? await updateUser(form.id, form, true, false) : false;
@@ -58,29 +83,141 @@ const Profile = (props: IProps) => {
58
83
  }
59
84
  : undefined;
60
85
 
86
+ const guardProps = {
87
+ action: (path: string) => setHistoryPush(path),
88
+ text: (
89
+ <>
90
+ Some changes <strong>are not saved</strong>.
91
+ </>
92
+ ),
93
+ };
94
+
61
95
  if (isLoading) return <Loading />;
62
96
 
63
97
  return (
64
98
  <MainWrapper title="My Profile" rightLineButton={lineButtonProps} rightButton={rightButtonProps}>
65
- <UserForm
66
- form={form}
67
- setForm={setForm}
68
- user={user}
69
- readOnlySites={true}
70
- site={currentSiteInfo}
71
- isSiteView={isSiteView}
72
- />
73
- {isOpen && <LinkDeviceModal isOpen={isOpen} toggleModal={toggleModal} token={token} />}
99
+ <RouteLeavingGuard when={isDirty} {...guardProps} />
100
+ <ErrorToast size="l" />
101
+ <S.ProfileWrapper>
102
+ <S.Header>
103
+ <S.HeaderContent>
104
+ <S.AvatarWrapper>
105
+ <ProfileImage imageUrl={image?.url} handleImage={handleImageChange} />
106
+ </S.AvatarWrapper>
107
+ <S.UserName>
108
+ <S.SubTitle>USER DATA</S.SubTitle>
109
+ <S.NameTitle>{form.name}</S.NameTitle>
110
+ </S.UserName>
111
+ </S.HeaderContent>
112
+ </S.Header>
113
+ <S.TabsWrapper>
114
+ <Tabs tabs={Array.from(PROFILE_TAB_LIST)} active={activeTab} setSelectedTab={setActiveTab} />
115
+ </S.TabsWrapper>
116
+ <S.Content>
117
+ {activeTab === PROFILE_TABS.PROFILE && (
118
+ <S.UserForm>
119
+ <S.FormColumn>
120
+ <FieldsBehavior
121
+ title="Name"
122
+ name="name"
123
+ fieldType="TextField"
124
+ placeholder="Type a name"
125
+ mandatory
126
+ value={name}
127
+ onChange={handleNameChange}
128
+ />
129
+ <FieldsBehavior
130
+ title="Alias"
131
+ name="username"
132
+ fieldType="TextField"
133
+ placeholder="Type an alias"
134
+ mandatory
135
+ value={username}
136
+ onChange={handleUsernameChange}
137
+ prefix="@"
138
+ />
139
+ <FieldsBehavior
140
+ title="Email"
141
+ name="email"
142
+ fieldType="TextField"
143
+ placeholder="Type an email"
144
+ mandatory
145
+ value={email}
146
+ onChange={handleEmailChange}
147
+ disabled={true}
148
+ />
149
+ <FieldsBehavior
150
+ title="Password"
151
+ name="password"
152
+ fieldType="TextField"
153
+ inputType="password"
154
+ placeholder="Type a password"
155
+ mandatory
156
+ readonly
157
+ value="********"
158
+ icon="edit"
159
+ onClickIcon={toggleModal}
160
+ />
161
+ </S.FormColumn>
162
+ <S.FormColumn>
163
+ <FieldsBehavior
164
+ title="Job Title"
165
+ name="position"
166
+ fieldType="TextField"
167
+ placeholder="Type your job title"
168
+ value={position || ""}
169
+ onChange={handlePositionChange}
170
+ />
171
+ <FieldsBehavior
172
+ title="Company"
173
+ name="company"
174
+ fieldType="TextField"
175
+ placeholder="Type a company"
176
+ value={company || ""}
177
+ onChange={handleCompanyChange}
178
+ />
179
+ <FieldsBehavior
180
+ title="Time zone"
181
+ name="timezone"
182
+ fieldType="Select"
183
+ value={timezone}
184
+ options={timezones}
185
+ onChange={handleTimezoneChange}
186
+ mandatory={true}
187
+ helptext="Used to display time in emails and ensure accurate scheduling."
188
+ />
189
+ </S.FormColumn>
190
+ </S.UserForm>
191
+ )}
192
+ {activeTab === PROFILE_TABS.PERMISSIONS && (
193
+ <S.UserPermissions>
194
+ <FloatingNote
195
+ icon="info"
196
+ message="This information is not editable. Changes require appropriate permissions and must be made in the user list."
197
+ />
198
+ <UserRolesAndSites
199
+ isSuperAdmin={isSuperAdmin}
200
+ userRoles={user.roles}
201
+ sites={sites}
202
+ roles={roles}
203
+ showRows={false}
204
+ />
205
+ </S.UserPermissions>
206
+ )}
207
+ </S.Content>
208
+ </S.ProfileWrapper>
209
+ <LinkDeviceModal isOpen={isOpen} toggleModal={toggleModal} token={token} />
74
210
  </MainWrapper>
75
211
  );
76
212
  };
77
213
 
78
214
  const mapStateToProps = (state: IRootState) => ({
79
- user: state.users.currentUser,
215
+ user: state.users.currentUser!,
80
216
  isSaving: state.app.isSaving,
81
217
  isLoading: state.app.isLoading,
82
218
  token: state.app.token,
83
- currentSiteInfo: state.sites.currentSiteInfo,
219
+ roles: state.users.roles,
220
+ sites: state.sites.allSites,
84
221
  });
85
222
 
86
223
  interface IDispatchProps {
@@ -100,7 +237,8 @@ interface IProfileProps {
100
237
  isSaving: boolean;
101
238
  isLoading: boolean;
102
239
  token: string;
103
- currentSiteInfo: ISite | null;
240
+ roles: IRole[];
241
+ sites: ISite[];
104
242
  }
105
243
 
106
244
  type IProps = IProfileProps & IDispatchProps;
@@ -14,4 +14,84 @@ const QRWrapper = styled.div`
14
14
  margin: auto;
15
15
  `;
16
16
 
17
- export { ModalContent, QRWrapper };
17
+ const ProfileWrapper = styled.div`
18
+ display: flex;
19
+ flex-direction: column;
20
+ `;
21
+
22
+ const Header = styled.div`
23
+ height: 160px;
24
+ width: 100%;
25
+ `;
26
+
27
+ const HeaderContent = styled.div`
28
+ max-width: 720px;
29
+ margin: auto;
30
+ display: flex;
31
+ align-items: center;
32
+ height: 100%;
33
+ gap: ${(p) => p.theme.spacing.m};
34
+ `;
35
+
36
+ const AvatarWrapper = styled.div``;
37
+
38
+ const UserName = styled.div``;
39
+
40
+ const NameTitle = styled.div`
41
+ ${(p) => p.theme.textStyle.headingM};
42
+ color: ${(p) => p.theme.color.textHighEmphasis};
43
+ `;
44
+
45
+ const SubTitle = styled.div`
46
+ ${(p) => p.theme.textStyle.headingXS};
47
+ color: ${(p) => p.theme.color.textMediumEmphasis};
48
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
49
+ `;
50
+
51
+ const TabsWrapper = styled.div`
52
+ background-color: ${(p) => p.theme.color.uiBackground02};
53
+ border-top: ${(p) => `1px solid ${p.theme.color.uiLine}`};
54
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
55
+ div {
56
+ max-width: 720px;
57
+ margin: 0 auto;
58
+ border-bottom: none;
59
+ }
60
+ `;
61
+
62
+ const Content = styled.div`
63
+ max-width: 720px;
64
+ margin: auto;
65
+ width: 100%;
66
+ `;
67
+
68
+ const UserForm = styled.div`
69
+ display: grid;
70
+ grid-template-columns: 1fr 1fr;
71
+ gap: ${(p) => `calc(${p.theme.spacing.m} + ${p.theme.spacing.xs})`};
72
+ align-items: start;
73
+ padding-top: ${(p) => `calc(${p.theme.spacing.m} + ${p.theme.spacing.xs})`};
74
+ `;
75
+
76
+ const FormColumn = styled.div``;
77
+
78
+ const UserPermissions = styled.div`
79
+ padding-top: ${(p) => p.theme.spacing.s};
80
+ `;
81
+
82
+ export {
83
+ ModalContent,
84
+ QRWrapper,
85
+ ProfileWrapper,
86
+ Header,
87
+ HeaderContent,
88
+ TabsWrapper,
89
+ Content,
90
+ NameTitle,
91
+ SubTitle,
92
+ AvatarWrapper,
93
+ UserName,
94
+ UserForm,
95
+ FormColumn,
96
+ UserPermissions,
97
+ };
@@ -20,7 +20,7 @@ const RoleItem = (props: IRoleItemProps): JSX.Element => {
20
20
  const avatarList =
21
21
  isSiteView && roleUsers.length
22
22
  ? roleUsers.map(
23
- (user: IUser, i: number) => i < 5 && <Avatar image={user.image?.thumb} name={user.name} size="s" key={i} />,
23
+ (user: IUser, i: number) => i < 5 && <Avatar image={user.image?.thumb} name={user.name} size={24} key={i} />,
24
24
  )
25
25
  : [];
26
26
 
@@ -28,7 +28,7 @@ const RoleItem = (props: IRoleItemProps): JSX.Element => {
28
28
  isSiteView && roleUsers.length
29
29
  ? roleUsers.map((user: IUser) => (
30
30
  <S.AvatarContainer addSpacing={true} key={user.id}>
31
- <Avatar image={user.image?.thumb} name={user.name} size="s" key={user.id} />
31
+ <Avatar image={user.image?.thumb} name={user.name} size={24} key={user.id} />
32
32
  </S.AvatarContainer>
33
33
  ))
34
34
  : [];
@@ -1,9 +1,7 @@
1
- import React from "react";
2
-
3
1
  import { CheckField, Button, ElementsTooltip } from "@ax/components";
4
2
  import { useModal } from "@ax/hooks";
5
3
 
6
- import { IRole } from "@ax/types";
4
+ import type { IRole } from "@ax/types";
7
5
  import RolesModal from "./RolesModal";
8
6
 
9
7
  import * as S from "./style";
@@ -24,19 +22,18 @@ const SiteItem = (props: ISiteItemProps): JSX.Element => {
24
22
 
25
23
  const handleChange = () => onChange(id);
26
24
 
27
- const handleAddRoles = (roles: number[]) => addRoles && addRoles({ siteId: id, roles });
25
+ const handleAddRoles = (roles: number[]) => addRoles?.({ siteId: id, roles });
26
+
27
+ const rolesString = roles?.filter((role: IRole) => selected?.includes(role.id)).map((role: IRole) => role.name);
28
28
 
29
- const rolesString =
30
- roles && roles.filter((role: IRole) => selected && selected.includes(role.id)).map((role: IRole) => role.name);
31
- const colors =
32
- rolesString &&
33
- rolesString.reduce((prev, current) => {
34
- const color: IRole | undefined = roles.find((role: IRole) => role.name === current);
35
- return { ...prev, [current]: color?.hex };
36
- }, {});
29
+ const colors = rolesString?.reduce((prev: Record<string, string | undefined>, current) => {
30
+ const color: IRole | undefined = roles?.find((role: IRole) => role.name === current);
31
+ prev[current] = color?.hex;
32
+ return prev;
33
+ }, {});
37
34
 
38
- const textButton = selected && selected.length ? "Edit Roles" : "Add Roles";
39
- const iconButton = selected && selected.length ? "edit" : undefined;
35
+ const textButton = selected?.length ? "Edit Roles" : "Add Roles";
36
+ const iconButton = selected?.length ? "edit" : undefined;
40
37
 
41
38
  return (
42
39
  <>
@@ -1,9 +1,9 @@
1
- import React, { useState } from "react";
1
+ import { useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
4
  import { ErrorToast, FieldsBehavior, Modal } from "@ax/components";
5
5
  import { usersActions } from "@ax/containers/Users";
6
- import { IModal, IRootState } from "@ax/types";
6
+ import type { IModal, IRootState } from "@ax/types";
7
7
 
8
8
  import * as S from "./style";
9
9
 
@@ -63,7 +63,7 @@ const PasswordForm = (props: IPasswordFormProps) => {
63
63
  };
64
64
 
65
65
  return (
66
- <Modal isOpen={isOpen} hide={toggleModal} size="M" {...modalProps}>
66
+ <Modal isOpen={isOpen} hide={toggleModal} height={470} size="M" {...modalProps}>
67
67
  <ErrorToast size="l" />
68
68
  <S.ModalContent>
69
69
  <FieldsBehavior