@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
@@ -2,7 +2,6 @@ import { memo, useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  import { cleanPageValues } from "@ax/forms";
4
4
  import { deepClone, isEmptyObj } from "@ax/helpers";
5
- import type { FormContent, IUser } from "@ax/types";
6
5
 
7
6
  import isEqual from "lodash.isequal";
8
7
 
@@ -197,60 +196,58 @@ const useIsDirty = (
197
196
  return { isDirty, setIsDirty, resetDirty };
198
197
  };
199
198
 
200
- const cleanModified = (updatedValues: any, originalValues: any) => {
201
- delete updatedValues.modified;
199
+ const useShouldBeSaved = (form: Record<string, any> | null | undefined, debug = DEBUG_DIRTY) => {
200
+ const [isDirty, setIsDirtyState] = useState(false);
201
+ const originalRef = useRef<Record<string, any>>();
202
202
 
203
- delete originalValues.modified;
204
-
205
- return {
206
- cleanUpdatedValues: updatedValues,
207
- cleanOriginalValues: originalValues,
208
- };
209
- };
210
-
211
- const useShouldBeSaved = (form: Record<string, unknown> | IUser | FormContent, debug = DEBUG_DIRTY) => {
212
- const [isDirty, setIsDirty] = useState(false);
213
- const formRef = useRef();
214
-
215
- const stringValue = form && JSON.stringify(form);
203
+ // Intercept setIsDirty to update baseline when saved
204
+ const setIsDirty = useCallback(
205
+ (value: boolean) => {
206
+ setIsDirtyState(value);
207
+ if (value === false && form) {
208
+ originalRef.current = deepClone(form);
209
+ }
210
+ },
211
+ [form],
212
+ );
216
213
 
217
- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO fix this
218
214
  useEffect(() => {
219
- if (!formRef.current) {
220
- formRef.current = stringValue && JSON.parse(stringValue);
215
+ if (!form) {
216
+ if (debug) console.log("[useShouldBeSaved] form=null isDirty=false");
217
+ setIsDirtyState(false);
218
+ return;
221
219
  }
222
- if (form !== null) {
223
- const { cleanUpdatedValues, cleanOriginalValues } = cleanModified(formRef.current, form);
224
- const isFormDirty =
225
- cleanOriginalValues.id && !cleanUpdatedValues.id ? false : !isEqual(cleanUpdatedValues, cleanOriginalValues);
226
220
 
227
- if (debug) {
228
- console.groupCollapsed(
229
- `[useShouldBeSaved] isDirty: %c${isFormDirty}`,
230
- isFormDirty ? "color: red; font-weight: bold" : "color: green",
231
- );
232
- console.log("Stored ref (cleaned):", cleanUpdatedValues);
233
- console.log("Current (cleaned):", cleanOriginalValues);
234
- if (cleanOriginalValues.id && !cleanUpdatedValues.id) {
235
- console.log("Suppressed: original has id, ref does not.");
236
- }
237
- const diffs = getChangedKeys(
238
- cleanUpdatedValues as Record<string, unknown>,
239
- cleanOriginalValues as Record<string, unknown>,
240
- );
241
- if (diffs.length > 0) {
242
- console.table(diffs);
243
- }
244
- console.groupEnd();
245
- }
221
+ // Initialize baseline on first render
222
+ if (!originalRef.current) {
223
+ originalRef.current = deepClone(form);
224
+ return;
225
+ }
246
226
 
247
- setIsDirty(isFormDirty);
248
- formRef.current = stringValue && JSON.parse(stringValue);
249
- } else {
250
- if (debug) console.log("[useShouldBeSaved] form=null → isDirty=false");
251
- setIsDirty(false);
227
+ // Compare against the baseline (original)
228
+ const original = { ...originalRef.current } as Record<string, unknown>;
229
+ const current = { ...form } as Record<string, unknown>;
230
+ delete original.modified;
231
+ delete current.modified;
232
+
233
+ const hasChanges = !isEqual(original, current);
234
+
235
+ if (debug) {
236
+ console.groupCollapsed(
237
+ `[useShouldBeSaved] isDirty: %c${hasChanges}`,
238
+ hasChanges ? "color: red; font-weight: bold" : "color: green",
239
+ );
240
+ console.log("Original (cleaned):", original);
241
+ console.log("Current (cleaned):", current);
242
+ const diffs = getChangedKeys(original as Record<string, unknown>, current as Record<string, unknown>);
243
+ if (diffs.length > 0) {
244
+ console.table(diffs);
245
+ }
246
+ console.groupEnd();
252
247
  }
253
- }, [form]);
248
+
249
+ setIsDirtyState(hasChanges);
250
+ }, [form, debug]);
254
251
 
255
252
  return { isDirty, setIsDirty };
256
253
  };
@@ -2,9 +2,10 @@ import { useCallback, useEffect, useRef, useState } from "react";
2
2
 
3
3
  const useModal = (initialState?: boolean, bodyBlock = true) => {
4
4
  const [isOpen, setIsOpen] = useState(initialState || false);
5
- const toggleModal = () => {
6
- setIsOpen(!isOpen);
7
- };
5
+
6
+ const toggleModal = useCallback(() => {
7
+ setIsOpen((prev) => !prev);
8
+ }, []);
8
9
 
9
10
  useEffect(() => {
10
11
  if (isOpen && bodyBlock) {
@@ -19,7 +19,7 @@ const UserItem = (props: IUserItemProps) => {
19
19
  <S.Item>
20
20
  <S.User>
21
21
  <S.AvatarWrapper>
22
- <Avatar image={image} name={name || ""} size="s" />
22
+ <Avatar image={image} name={name || ""} size={24} />
23
23
  </S.AvatarWrapper>
24
24
  <S.Name>{name}</S.Name>
25
25
  {!isOpen && <S.CounterWrapper>{logActivity.length} events created</S.CounterWrapper>}
@@ -1,17 +1,15 @@
1
- import React, { useEffect } from "react";
1
+ import { useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import { ILogoutAction } from "@ax/containers/App/interfaces";
5
4
  import { appActions } from "@ax/containers/App";
5
+ import type { ILogoutAction } from "@ax/containers/App/interfaces";
6
6
 
7
7
  const Logout = (props: IProps) => {
8
8
  const { logout, setHistoryPush } = props;
9
9
 
10
10
  useEffect(() => {
11
+ logout();
11
12
  setHistoryPush("/login");
12
- return () => {
13
- logout();
14
- };
15
13
  }, [logout, setHistoryPush]);
16
14
 
17
15
  return <></>;
@@ -1,17 +1,17 @@
1
1
  import React, { useContext } from "react";
2
2
 
3
- import { IRouter } from "@ax/routes";
3
+ import { Avatar, Icon } from "@ax/components";
4
4
  import { Restricted } from "@ax/guards";
5
- import { Icon } from "@ax/components";
5
+ import type { IRouter } from "@ax/routes";
6
6
 
7
- import NavSubItem from "./NavSubItem";
8
7
  import { NavContext } from "./../context";
8
+ import NavSubItem from "./NavSubItem";
9
9
 
10
10
  import * as S from "./style";
11
11
 
12
12
  const NavItem = (props: IProps) => {
13
13
  const {
14
- route: { component, path, name, icon, routesGroups, onClick, url, target = "_blank" },
14
+ route: { component, path, name, icon, routesGroups, onClick, url, target = "_blank", avatar },
15
15
  location,
16
16
  type,
17
17
  isOpened,
@@ -20,89 +20,110 @@ const NavItem = (props: IProps) => {
20
20
 
21
21
  const { state, toggleSubmenu } = useContext(NavContext)!;
22
22
 
23
- const isSelected = location && (location.pathname === path || location.pathname.includes(path));
23
+ const isSelected = location ? location.pathname === path || location.pathname.includes(path) : false;
24
24
  const isSelectedClass = isSelected ? "selected" : "";
25
+ const hasImage = avatar ? "withImage" : "";
25
26
 
26
27
  const hasSubmenu = routesGroups && routesGroups.length > 0;
28
+ const isSubmenuOpen = !!state[name];
27
29
 
28
30
  const handleClick = () => {
29
31
  if (onClick) {
30
32
  onClick();
31
33
  } else if (url) {
32
34
  window.open(url, target);
33
- } else if (component && location.pathname !== path) {
35
+ } else if (component && location && location.pathname !== path) {
34
36
  setHistoryPush(path);
35
37
  } else if (isOpened && hasSubmenu) {
36
38
  toggleSubmenu(name);
37
39
  }
38
40
  };
39
41
 
40
- const subMenuIcon = state[name] ? "UpArrow" : "DownArrow";
42
+ const subMenuIcon = isSubmenuOpen ? "UpArrow" : "DownArrow";
41
43
 
42
44
  const DropdownClass = isOpened ? "inline" : "floating";
43
45
 
44
46
  return (
45
- <S.Item key={name} type={type} isOpened={isOpened} isSubmenuOpen={state[name]} active={isSelected}>
46
- <S.NavLink onClick={handleClick} className={isSelectedClass}>
47
- <S.Icon>{icon && <Icon name={icon} size="24px" />}</S.Icon>
48
- <S.LinkName active={isSelected || state[name]}>{name}</S.LinkName>
47
+ <S.Item key={name} type={type} isOpened={isOpened} isSubmenuOpen={isSubmenuOpen} active={isSelected}>
48
+ <S.NavLink onClick={handleClick} className={`${isSelectedClass} ${hasImage}`}>
49
+ {avatar ? (
50
+ <S.AvatarWithBorder>
51
+ <Avatar image={avatar.image} name={avatar.name} size={32} background bold />
52
+ </S.AvatarWithBorder>
53
+ ) : (
54
+ <S.Icon>{icon && <Icon name={icon} size="24px" />}</S.Icon>
55
+ )}
56
+ <S.LinkName active={isSelected || isSubmenuOpen}>{name}</S.LinkName>
49
57
  {hasSubmenu && isOpened && (
50
58
  <S.Arrow>
51
59
  <Icon name={subMenuIcon} />
52
60
  </S.Arrow>
53
61
  )}
54
62
  </S.NavLink>
55
- <S.Dropdown isSubmenu={hasSubmenu} isSubmenuOpen={state[name]} className={DropdownClass} type={type}>
63
+ <S.Dropdown isSubmenu={hasSubmenu} isSubmenuOpen={isSubmenuOpen} className={DropdownClass} type={type}>
56
64
  <S.Heading onClick={handleClick}>
57
65
  <div>{name}</div>
58
66
  </S.Heading>
59
- {routesGroups &&
60
- routesGroups.map((subGroup: any, groupI: any) => (
61
- <React.Fragment key={groupI}>
62
- {subGroup.routes && (
63
- <>
64
- {subGroup.routes.map((subLink: any, subI: any) => {
65
- const goToSubLink = () => {
66
- if (location.pathname !== subLink.path) {
67
- const isEditor = subLink.isEditor ? true : false;
68
- setHistoryPush(subLink.path, isEditor);
69
- }
70
- if (subLink.setDataAction) {
71
- const setCurrentData = (id: string) => subLink.setDataAction(id);
72
- setCurrentData(subLink.item.id);
73
- }
74
- };
75
-
76
- const isSelectedSubItem = location.pathname === subLink.path ? true : false;
77
-
78
- const navSubItem = (
79
- <NavSubItem key={subI} active={isSelectedSubItem} type={type}>
80
- <S.NavLink onClick={goToSubLink}>
81
- <S.LinkName active={isSelected}>{subLink.name}</S.LinkName>
82
- </S.NavLink>
83
- </NavSubItem>
84
- );
85
-
86
- return subLink.permission ? (
87
- <Restricted to={subLink.permission} key={subI}>
88
- {navSubItem}
89
- </Restricted>
90
- ) : (
91
- navSubItem
92
- );
93
- })}
94
- </>
95
- )}
96
- </React.Fragment>
97
- ))}
67
+ {routesGroups?.map((subGroup: IRouteGroup, groupI: number) => (
68
+ <React.Fragment key={`${subGroup.name}-${groupI}`}>
69
+ {subGroup.routes && (
70
+ <>
71
+ {subGroup.routes.map((subLink: ISubRoute, subI: number) => {
72
+ const goToSubLink = () => {
73
+ if (location && location.pathname !== subLink.path) {
74
+ const isEditor = !!subLink.isEditor;
75
+ setHistoryPush(subLink.path, isEditor);
76
+ }
77
+ if (subLink.setDataAction && subLink.item) {
78
+ subLink.setDataAction(subLink.item.id);
79
+ }
80
+ };
81
+
82
+ const isSelectedSubItem = location ? location.pathname === subLink.path : false;
83
+
84
+ const navSubItem = (
85
+ <NavSubItem key={`${subLink.path}-${subI}`} active={isSelectedSubItem} type={type}>
86
+ <S.NavLink onClick={goToSubLink}>
87
+ <S.LinkName active={isSelected}>{subLink.name}</S.LinkName>
88
+ </S.NavLink>
89
+ </NavSubItem>
90
+ );
91
+
92
+ return subLink.permission ? (
93
+ <Restricted to={subLink.permission} key={`${subLink.path}-${subI}`}>
94
+ {navSubItem}
95
+ </Restricted>
96
+ ) : (
97
+ navSubItem
98
+ );
99
+ })}
100
+ </>
101
+ )}
102
+ </React.Fragment>
103
+ ))}
98
104
  </S.Dropdown>
99
105
  </S.Item>
100
106
  );
101
107
  };
102
108
 
109
+ interface ISubRoute {
110
+ path: string;
111
+ component?: any;
112
+ name: string;
113
+ permission?: string;
114
+ isEditor?: boolean;
115
+ setDataAction?: (id: string) => void;
116
+ item?: { id: string };
117
+ }
118
+
119
+ interface IRouteGroup {
120
+ name: string;
121
+ routes: ISubRoute[];
122
+ }
123
+
103
124
  interface IProps {
104
125
  route: IRouter;
105
- location?: any;
126
+ location?: Location;
106
127
  type: string;
107
128
  isOpened: boolean;
108
129
  setHistoryPush(path: string, isEditor?: boolean): void;
@@ -1,6 +1,6 @@
1
1
  import styled from "styled-components";
2
2
 
3
- export const Heading = styled.li`
3
+ const Heading = styled.li`
4
4
  ${(p) => p.theme.textStyle.headingXXS};
5
5
  width: 100%;
6
6
  height: 100%;
@@ -8,7 +8,7 @@ export const Heading = styled.li`
8
8
  align-items: center;
9
9
  `;
10
10
 
11
- export const Dropdown = styled.ul<{ isSubmenu?: boolean; isSubmenuOpen: boolean; type: string }>`
11
+ const Dropdown = styled.ul<{ isSubmenu?: boolean; isSubmenuOpen: boolean; type: string }>`
12
12
  background-color: ${(p) =>
13
13
  p.type === "multisite" ? p.theme.color.uiMainMenuBackgroundDark : p.theme.color.uiSiteMenuDark};
14
14
  color: ${(p) => p.theme.color.textHighEmphasisInverse};
@@ -54,7 +54,7 @@ export const Dropdown = styled.ul<{ isSubmenu?: boolean; isSubmenuOpen: boolean;
54
54
  }
55
55
  `;
56
56
 
57
- export const Arrow = styled.div`
57
+ const Arrow = styled.div`
58
58
  margin-left: auto;
59
59
  height: ${(p) => p.theme.spacing.m};
60
60
  width: ${(p) => p.theme.spacing.m};
@@ -64,13 +64,13 @@ export const Arrow = styled.div`
64
64
  }
65
65
  `;
66
66
 
67
- export const LinkName = styled.span<{ active: boolean }>`
67
+ const LinkName = styled.span<{ active: boolean }>`
68
68
  white-space: nowrap;
69
69
  overflow: hidden;
70
70
  color: ${(p) => (p.active ? p.theme.color.textHighEmphasisInverse : p.theme.color.textMediumEmphasisInverse)};
71
71
  `;
72
72
 
73
- export const Item = styled.li<{ type: string; isOpened: boolean; isSubmenuOpen: boolean; active: boolean }>`
73
+ const Item = styled.li<{ type: string; isOpened: boolean; isSubmenuOpen: boolean; active: boolean }>`
74
74
  position: relative;
75
75
  margin-left: ${(p) => (p.isSubmenuOpen ? "0" : p.theme.spacing.s)};
76
76
  margin-right: ${(p) => (p.isOpened && !p.isSubmenuOpen ? p.theme.spacing.s : "0")};
@@ -155,11 +155,16 @@ export const Item = styled.li<{ type: string; isOpened: boolean; isSubmenuOpen:
155
155
  }
156
156
  `;
157
157
 
158
- export const NavLink = styled.a`
158
+ const NavLink = styled.a`
159
159
  display: flex;
160
+
161
+ &.withImage {
162
+ padding: 2px;
163
+ margin-bottom: ${(p) => p.theme.spacing.s};
164
+ }
160
165
  `;
161
166
 
162
- export const Icon = styled.div`
167
+ const Icon = styled.div`
163
168
  display: flex;
164
169
  margin-right: ${(p) => p.theme.spacing.s};
165
170
  height: ${(p) => p.theme.spacing.m};
@@ -169,3 +174,12 @@ export const Icon = styled.div`
169
174
  fill: ${(p) => p.theme.color.textMediumEmphasisInverse};
170
175
  }
171
176
  `;
177
+
178
+ const AvatarWithBorder = styled.div`
179
+ display: flex;
180
+ border: 2px solid;
181
+ border-color: ${(p) => p.theme.color.textHighEmphasisInverse};
182
+ border-radius: 50%;
183
+ `;
184
+
185
+ export { Heading, Dropdown, Arrow, LinkName, Item, NavLink, Icon, AvatarWithBorder };
@@ -1,18 +1,17 @@
1
- import React, { useState } from "react";
1
+ import { useState } from "react";
2
2
  import { connect } from "react-redux";
3
- import { RouteComponentProps, withRouter } from "react-router-dom";
4
- import { version } from "./../../../../../package.json";
5
-
6
- import { IRouter, multisite, site } from "@ax/routes";
3
+ import { type RouteComponentProps, withRouter } from "react-router-dom";
7
4
 
8
- import { ILanguage, IRootState, ISite, IStructuredData } from "@ax/types";
9
- import { IGlobalSettings } from "@ax/containers/App/reducer";
10
- import { appActions } from "@ax/containers/App";
11
5
  import { Icon, Tag } from "@ax/components";
6
+ import { appActions } from "@ax/containers/App";
7
+ import type { IGlobalSettings } from "@ax/containers/App/reducer";
12
8
  import { Restricted } from "@ax/guards";
9
+ import { type IRouter, multisite, site } from "@ax/routes";
10
+ import type { ILanguage, IRootState, ISite, IStructuredData, IUser } from "@ax/types";
13
11
 
14
- import NavItem from "./NavItem";
12
+ import { version } from "./../../../../../package.json";
15
13
  import { NavProvider } from "./context";
14
+ import NavItem from "./NavItem";
16
15
 
17
16
  import * as S from "./style";
18
17
 
@@ -20,13 +19,14 @@ const NavMenu = (props: IProps) => {
20
19
  const {
21
20
  location,
22
21
  setHistoryPush,
23
- logout,
22
+ logoutAndNavigate,
24
23
  currentSiteInfo,
25
24
  siteLanguages,
26
25
  lang,
27
26
  categories,
28
27
  globalSettings,
29
28
  structuredData,
29
+ currentUser,
30
30
  } = props;
31
31
 
32
32
  const { useForms } = globalSettings;
@@ -48,9 +48,8 @@ const NavMenu = (props: IProps) => {
48
48
  setIsOpened(!isOpened);
49
49
  };
50
50
 
51
- const siteLogo = currentSiteInfo && currentSiteInfo.bigAvatar ? currentSiteInfo.bigAvatar : logoPlaceholder;
52
- const siteLogoMini =
53
- currentSiteInfo && currentSiteInfo.smallAvatar ? currentSiteInfo.smallAvatar : logoMiniPlaceholder;
51
+ const siteLogo = currentSiteInfo?.bigAvatar ? currentSiteInfo.bigAvatar : logoPlaceholder;
52
+ const siteLogoMini = currentSiteInfo?.smallAvatar ? currentSiteInfo.smallAvatar : logoMiniPlaceholder;
54
53
 
55
54
  const sitesPath = "/sites/";
56
55
  const isSite =
@@ -59,7 +58,7 @@ const NavMenu = (props: IProps) => {
59
58
 
60
59
  const goToPublishedSite = () => {
61
60
  const language = siteLanguages.find((l) => l.id === lang.id);
62
- if (language && language.home) {
61
+ if (language?.home) {
63
62
  const urlHome = `${language.home}${language.home.endsWith("/") ? "" : "/"}`;
64
63
  window.open(urlHome, "_blank");
65
64
  }
@@ -74,10 +73,10 @@ const NavMenu = (props: IProps) => {
74
73
  setHistoryPush(profileRoute);
75
74
  };
76
75
 
77
- const filteredSiteRoutes = site.filter((route: IRouter) => useForms || (!useForms && !route.path.includes("/forms")));
78
- const filteredMultisiteRoutes = multisite.filter(
79
- (route: IRouter) => useForms || (!useForms && !route.path.includes("/forms")),
80
- );
76
+ const filterRoutes = (routes: IRouter[]) => routes.filter((route) => useForms || !route.path.includes("/forms"));
77
+
78
+ const filteredSiteRoutes = filterRoutes(site);
79
+ const filteredMultisiteRoutes = filterRoutes(multisite);
81
80
 
82
81
  const config: IConfig = isSite
83
82
  ? {
@@ -114,7 +113,7 @@ const NavMenu = (props: IProps) => {
114
113
  name: "Logout",
115
114
  icon: "Power",
116
115
  path: "",
117
- onClick: logout,
116
+ onClick: logoutAndNavigate,
118
117
  };
119
118
 
120
119
  const profileRoute = {
@@ -123,6 +122,7 @@ const NavMenu = (props: IProps) => {
123
122
  icon: "User",
124
123
  path: isSite ? "/site/profile" : "/profile",
125
124
  onClick: goToProfile,
125
+ avatar: currentUser ? { image: currentUser.image?.url, name: currentUser.name } : undefined,
126
126
  };
127
127
 
128
128
  const toggleIcon = isOpened ? "Collapsed" : "Extend";
@@ -140,15 +140,21 @@ const NavMenu = (props: IProps) => {
140
140
 
141
141
  if (isSite) {
142
142
  const isSiteCategoriesAvailable = !!categories.site.length;
143
- const siteCategoriesRouteIdx = config.routes.findIndex((route: IRouter) => route.path === "/sites/categories");
144
- config.routes[siteCategoriesRouteIdx].showInNav = isSiteCategoriesAvailable;
143
+ config.routes = config.routes.map((route) =>
144
+ route.path === "/sites/categories" ? { ...route, showInNav: isSiteCategoriesAvailable } : route,
145
+ );
145
146
  } else {
146
147
  const isGlobalCategoriesAvailable = !!categories.global.length;
147
148
  const isGlobalDataAvailable = !!structuredData.global.length;
148
- const globalCategoriesRouteIdx = config.routes.findIndex((route: IRouter) => route.path === "/categories");
149
- const globalDataRouteIdx = config.routes.findIndex((route: IRouter) => route.path === "/data");
150
- config.routes[globalCategoriesRouteIdx].showInNav = isGlobalCategoriesAvailable;
151
- config.routes[globalDataRouteIdx].showInNav = isGlobalDataAvailable;
149
+ config.routes = config.routes.map((route) => {
150
+ if (route.path === "/categories") {
151
+ return { ...route, showInNav: isGlobalCategoriesAvailable };
152
+ }
153
+ if (route.path === "/data") {
154
+ return { ...route, showInNav: isGlobalDataAvailable };
155
+ }
156
+ return route;
157
+ });
152
158
  }
153
159
 
154
160
  return (
@@ -165,37 +171,34 @@ const NavMenu = (props: IProps) => {
165
171
  </S.Home>
166
172
  <S.Lists>
167
173
  <S.List>
168
- {config.routes &&
169
- config.routes
170
- .filter((route: IRouter) => route.showInNav)
171
- .map((route: IRouter): JSX.Element => {
172
- const navItem = (
173
- <NavItem
174
- setHistoryPush={setHistoryPush}
175
- key={route.name}
176
- route={route}
177
- location={location}
178
- type={config.type}
179
- isOpened={isOpened}
180
- />
181
- );
182
-
183
- return route.permission ? (
184
- <Restricted key={route.name} to={route.permission}>
185
- {navItem}
186
- </Restricted>
187
- ) : (
188
- navItem
189
- );
190
- })}
174
+ {config.routes
175
+ ?.filter((route: IRouter) => route.showInNav)
176
+ .map((route: IRouter): JSX.Element => {
177
+ const navItem = (
178
+ <NavItem
179
+ setHistoryPush={setHistoryPush}
180
+ key={route.name}
181
+ route={route}
182
+ location={location}
183
+ type={config.type}
184
+ isOpened={isOpened}
185
+ />
186
+ );
187
+
188
+ return route.permission ? (
189
+ <Restricted key={route.name} to={route.permission}>
190
+ {navItem}
191
+ </Restricted>
192
+ ) : (
193
+ navItem
194
+ );
195
+ })}
191
196
  </S.List>
192
197
  <S.List>
193
198
  {isSitePublished && (
194
- <>
195
- <NavItem setHistoryPush={setHistoryPush} route={siteRoute} type={config.type} isOpened={isOpened} />
196
- <S.Separator />
197
- </>
199
+ <NavItem setHistoryPush={setHistoryPush} route={siteRoute} type={config.type} isOpened={isOpened} />
198
200
  )}
201
+ <S.Separator />
199
202
  <NavItem
200
203
  setHistoryPush={setHistoryPush}
201
204
  route={profileRoute}
@@ -214,7 +217,7 @@ const NavMenu = (props: IProps) => {
214
217
  };
215
218
 
216
219
  interface IDispatchProps {
217
- logout(): void;
220
+ logoutAndNavigate(): void;
218
221
  setHistoryPush(path: string): void;
219
222
  }
220
223
 
@@ -237,6 +240,7 @@ interface INavMenuProps {
237
240
  siteLanguages: ILanguage[];
238
241
  lang: { locale: string; id: number };
239
242
  globalSettings: IGlobalSettings;
243
+ currentUser: IUser | null;
240
244
  }
241
245
 
242
246
  const mapStateToProps = (state: IRootState) => ({
@@ -246,12 +250,13 @@ const mapStateToProps = (state: IRootState) => ({
246
250
  siteLanguages: state.sites.currentSiteLanguages,
247
251
  lang: state.app.lang,
248
252
  globalSettings: state.app.globalSettings,
253
+ currentUser: state.users.currentUser,
249
254
  });
250
255
 
251
256
  type IProps = INavMenuProps & IDispatchProps & RouteComponentProps;
252
257
 
253
258
  const mapDispatchToProps = {
254
- logout: appActions.logout,
259
+ logoutAndNavigate: appActions.logoutAndNavigate,
255
260
  setHistoryPush: appActions.setHistoryPush,
256
261
  };
257
262