@griddo/ax 11.14.2-rc.0 → 11.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) 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 +173 -0
  12. package/src/__tests__/components/KeywordsPreviewModal/KeywordsPreviewModal.test.tsx +3 -4
  13. package/src/__tests__/components/ProfileImage/ProfileImage.test.tsx +120 -0
  14. package/src/__tests__/components/ResizePanel/ResizePanel.test.tsx +8 -0
  15. package/src/__tests__/components/UserRolesAndSites/RoleItem/RoleItem.test.tsx +190 -0
  16. package/src/__tests__/components/UserRolesAndSites/UserRolesAndSites.test.tsx +471 -0
  17. package/src/__tests__/modules/FramePreview/HeadingsOverlay/HeadingsOverlay.test.tsx +15 -2
  18. package/src/__tests__/modules/Sites/Sites.test.tsx +68 -224
  19. package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +21 -17
  20. package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +65 -565
  21. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/DataStep/DataStep.test.tsx +109 -0
  22. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/FinalStep/FinalStep.test.tsx +157 -0
  23. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/CropView.test.tsx +51 -0
  24. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/ImageStep.test.tsx +70 -0
  25. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/UploadView.test.tsx +92 -0
  26. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/TimezoneStep/TimezoneStep.test.tsx +94 -0
  27. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeModal.test.tsx +78 -0
  28. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/WelcomeStep/WelcomeStep.test.tsx +39 -0
  29. package/src/__tests__/modules/Sites/SitesList/WelcomeModal/utils.test.ts +55 -0
  30. package/src/api/sites.tsx +4 -4
  31. package/src/components/Avatar/index.tsx +26 -5
  32. package/src/components/Avatar/style.tsx +20 -10
  33. package/src/components/Browser/index.tsx +7 -1
  34. package/src/components/ConfigPanel/index.tsx +11 -7
  35. package/src/components/ElementsTooltip/index.tsx +96 -34
  36. package/src/components/ElementsTooltip/style.tsx +12 -1
  37. package/src/components/Fields/FileField/index.tsx +16 -18
  38. package/src/components/Fields/HeadingField/index.tsx +1 -1
  39. package/src/components/Fields/ImageField/index.tsx +9 -38
  40. package/src/components/Fields/ImageField/style.tsx +12 -1
  41. package/src/components/Fields/ToggleField/index.tsx +1 -1
  42. package/src/components/Fields/Wysiwyg/index.tsx +25 -20
  43. package/src/components/FileGallery/GalleryPanel/index.tsx +15 -7
  44. package/src/components/FileGallery/index.tsx +33 -28
  45. package/src/components/Gallery/GalleryPanel/index.tsx +5 -16
  46. package/src/components/Gallery/index.tsx +0 -2
  47. package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +11 -2
  48. package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +21 -3
  49. package/src/components/HeadingsPreviewModal/ErrorsBanner/style.tsx +2 -2
  50. package/src/components/HeadingsPreviewModal/index.tsx +13 -3
  51. package/src/components/HeadingsPreviewModal/style.tsx +18 -0
  52. package/src/components/HeadingsPreviewModal/utils.tsx +31 -3
  53. package/src/components/Image/index.tsx +2 -2
  54. package/src/components/ImageDragAndDrop/CropStep/index.tsx +95 -0
  55. package/src/components/ImageDragAndDrop/CropStep/style.tsx +101 -0
  56. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/index.tsx +103 -40
  57. package/src/{modules/MediaGallery → components}/ImageDragAndDrop/style.tsx +14 -2
  58. package/src/components/KeywordsPreviewModal/atoms.tsx +2 -2
  59. package/src/components/KeywordsPreviewModal/index.tsx +6 -6
  60. package/src/components/KeywordsPreviewModal/utils.tsx +2 -2
  61. package/src/components/ProfileImage/index.tsx +55 -0
  62. package/src/components/ProfileImage/style.tsx +58 -0
  63. package/src/components/ResizePanel/ResizeHandle/index.tsx +44 -6
  64. package/src/components/ResizePanel/ResizeHandle/style.tsx +7 -0
  65. package/src/components/ResizePanel/index.tsx +25 -4
  66. package/src/components/Tabs/style.tsx +1 -1
  67. package/src/components/Tag/index.tsx +0 -1
  68. package/src/components/UserRolesAndSites/RoleItem/index.tsx +42 -0
  69. package/src/components/UserRolesAndSites/RoleItem/style.tsx +29 -0
  70. package/src/components/UserRolesAndSites/index.tsx +102 -0
  71. package/src/components/UserRolesAndSites/style.tsx +67 -0
  72. package/src/components/index.tsx +6 -0
  73. package/src/constants/index.ts +13 -1
  74. package/src/containers/App/actions.tsx +8 -1
  75. package/src/containers/Sites/actions.tsx +26 -0
  76. package/src/containers/Sites/constants.tsx +1 -0
  77. package/src/containers/Sites/interfaces.tsx +6 -0
  78. package/src/containers/Sites/reducer.tsx +5 -1
  79. package/src/containers/Users/reducer.tsx +6 -5
  80. package/src/guards/routeLeaving/index.tsx +9 -11
  81. package/src/helpers/images.tsx +50 -3
  82. package/src/helpers/index.tsx +2 -1
  83. package/src/hooks/forms.tsx +45 -48
  84. package/src/hooks/index.tsx +2 -1
  85. package/src/hooks/modals.tsx +4 -3
  86. package/src/hooks/window.ts +50 -2
  87. package/src/modules/ActivityLog/ItemLogUser/UserItem/index.tsx +1 -1
  88. package/src/modules/App/Routing/Logout/index.tsx +3 -5
  89. package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +73 -52
  90. package/src/modules/App/Routing/NavMenu/NavItem/style.tsx +21 -7
  91. package/src/modules/App/Routing/NavMenu/index.tsx +59 -54
  92. package/src/modules/App/Routing/NavMenu/style.tsx +13 -11
  93. package/src/modules/CreatePass/index.tsx +1 -1
  94. package/src/modules/FileDrive/FileDragAndDrop/index.tsx +11 -8
  95. package/src/modules/FileDrive/FileModal/index.tsx +8 -9
  96. package/src/modules/FileDrive/index.tsx +1 -18
  97. package/src/modules/Forms/FormEditor/index.tsx +1 -1
  98. package/src/modules/FramePreview/HeadingsOverlay/index.tsx +22 -11
  99. package/src/modules/FramePreview/HeadingsOverlay/style.tsx +1 -1
  100. package/src/modules/MediaGallery/ImageModal/index.tsx +1 -5
  101. package/src/modules/MediaGallery/index.tsx +1 -3
  102. package/src/modules/Settings/Globals/constants.tsx +942 -106
  103. package/src/modules/Sites/SitesList/AllSitesHeader/index.tsx +33 -0
  104. package/src/modules/Sites/SitesList/AllSitesHeader/style.tsx +35 -0
  105. package/src/modules/Sites/SitesList/GridView/GridHeaderFilter/index.tsx +5 -5
  106. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +23 -119
  107. package/src/modules/Sites/SitesList/ListView/BulkHeader/TableHeader/index.tsx +4 -4
  108. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +4 -3
  109. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +23 -120
  110. package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/index.tsx +4 -5
  111. package/src/modules/Sites/SitesList/RecentSites/index.tsx +49 -0
  112. package/src/modules/Sites/SitesList/RecentSites/style.tsx +92 -0
  113. package/src/modules/Sites/SitesList/SiteModal/index.tsx +8 -7
  114. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/index.tsx +72 -0
  115. package/src/modules/Sites/SitesList/WelcomeModal/DataStep/style.tsx +59 -0
  116. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/constants.tsx +78 -0
  117. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +78 -0
  118. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/style.tsx +141 -0
  119. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/index.tsx +93 -0
  120. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/CropView/style.tsx +77 -0
  121. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/index.tsx +100 -0
  122. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/UploadView/style.tsx +94 -0
  123. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/index.tsx +44 -0
  124. package/src/modules/Sites/SitesList/WelcomeModal/ImageStep/style.tsx +31 -0
  125. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/index.tsx +51 -0
  126. package/src/modules/Sites/SitesList/WelcomeModal/TimezoneStep/style.tsx +52 -0
  127. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/index.tsx +40 -0
  128. package/src/modules/Sites/SitesList/WelcomeModal/WelcomeStep/style.tsx +53 -0
  129. package/src/modules/Sites/SitesList/WelcomeModal/index.tsx +215 -0
  130. package/src/modules/Sites/SitesList/WelcomeModal/style.tsx +12 -0
  131. package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +26 -0
  132. package/src/modules/Sites/SitesList/atoms.tsx +4 -4
  133. package/src/modules/Sites/SitesList/hooks.tsx +149 -16
  134. package/src/modules/Sites/SitesList/index.tsx +127 -125
  135. package/src/modules/Sites/SitesList/style.tsx +1 -117
  136. package/src/modules/Sites/SitesList/utils.tsx +9 -2
  137. package/src/modules/Sites/index.tsx +19 -8
  138. package/src/modules/Users/Profile/index.tsx +169 -31
  139. package/src/modules/Users/Profile/style.tsx +81 -1
  140. package/src/modules/Users/Roles/RoleItem/index.tsx +2 -2
  141. package/src/modules/Users/UserCreate/SiteItem/index.tsx +11 -14
  142. package/src/modules/Users/UserForm/atoms.tsx +3 -3
  143. package/src/modules/Users/UserForm/index.tsx +25 -29
  144. package/src/modules/Users/UserForm/style.tsx +15 -2
  145. package/src/modules/Users/UserList/UserItem/index.tsx +4 -4
  146. package/src/routes/index.tsx +1 -0
  147. package/src/types/index.tsx +2 -0
  148. /package/src/modules/Sites/SitesList/{RecentSiteItem → RecentSites/RecentSiteItem}/style.tsx +0 -0
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState } from "react";
1
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
2
2
 
3
3
  import ResizeHandle from "./ResizeHandle";
4
4
 
@@ -10,12 +10,19 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
10
10
  const { leftPanel, rightPanel, fixed = true, full = false, disabled } = props;
11
11
 
12
12
  const [rwidth, setRwidth] = useState(MIN_WIDTH);
13
+ const [containerWidth, setContainerWidth] = useState(0);
13
14
  const rightPanelRef = useRef<HTMLDivElement>(null);
14
15
  const containerRef = useRef<HTMLDivElement>(null);
15
16
 
17
+ useLayoutEffect(() => {
18
+ if (containerRef.current) {
19
+ setContainerWidth(containerRef.current.offsetWidth);
20
+ }
21
+ }, []);
22
+
16
23
  const calculateFixedPanelMinWidth = (currentWidth: number) => {
17
- const containerWidth = containerRef.current?.offsetWidth ?? 1280;
18
- const minWidth = Math.max(500, containerWidth - currentWidth - 32);
24
+ const width = containerWidth || window.innerWidth;
25
+ const minWidth = Math.max(500, width - currentWidth - 32);
19
26
  return `${minWidth}px`;
20
27
  };
21
28
 
@@ -34,6 +41,20 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
34
41
  return () => window.removeEventListener("resize", updateWidth);
35
42
  }, []);
36
43
 
44
+ useEffect(() => {
45
+ const resizeObserver = new ResizeObserver(() => {
46
+ if (containerRef.current) {
47
+ setContainerWidth(containerRef.current.offsetWidth);
48
+ }
49
+ });
50
+
51
+ if (containerRef.current) {
52
+ resizeObserver.observe(containerRef.current);
53
+ }
54
+
55
+ return () => resizeObserver.disconnect();
56
+ }, []);
57
+
37
58
  const resize = (value: number) => {
38
59
  if (value > MIN_WIDTH) setRwidth(value);
39
60
  };
@@ -53,7 +74,7 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
53
74
  leftPanel
54
75
  )}
55
76
  </S.LeftPanel>
56
- {!disabled && <ResizeHandle onMouseMove={resize} />}
77
+ {!disabled && <ResizeHandle onMouseMove={resize} currentWidth={rwidth} />}
57
78
  <S.RightPanel ref={rightPanelRef} data-testid="right-panel" style={{ width: rwidth ? `${rwidth}px` : "auto" }}>
58
79
  {rightPanel}
59
80
  </S.RightPanel>
@@ -9,7 +9,7 @@ const TabsRow = styled.div<{ icons?: boolean; isInAppBar?: boolean; noMargins?:
9
9
  `;
10
10
 
11
11
  const TabItem = styled.button<{ active: boolean; isInAppBar?: boolean; inversed?: boolean; isDisabled?: boolean }>`
12
- flex-grow: 1;
12
+ flex: 1;
13
13
  ${(p) => p.theme.textStyle.headingXS};
14
14
  border: none;
15
15
  border-bottom: ${(p) => (p.active ? "4px solid" : "none")};
@@ -1,4 +1,3 @@
1
- import React from "react";
2
1
  import { Icon } from "@ax/components";
3
2
 
4
3
  import * as S from "./style";
@@ -0,0 +1,42 @@
1
+ import { ElementsTooltip } from "@ax/components";
2
+ import type { IRole } from "@ax/types";
3
+
4
+ import * as S from "./style";
5
+
6
+ const RoleItem = ({ title, description, roles, isSuperAdmin = false }: IProps) => {
7
+ const rolesString = isSuperAdmin ? ["Super-admin"] : roles.map((role: IRole) => role.name);
8
+
9
+ const colors = isSuperAdmin
10
+ ? { "Super-admin": "#E7CBFE" }
11
+ : rolesString?.reduce((prev: Record<string, string | undefined>, current) => {
12
+ const color: IRole | undefined = roles.find((role: IRole) => role.name === current);
13
+ prev[current] = color?.hex;
14
+ return prev;
15
+ }, {});
16
+
17
+ return (
18
+ <S.Wrapper>
19
+ <S.Title>{title}</S.Title>
20
+ <S.Description>{description}</S.Description>
21
+ <S.TagWrapper>
22
+ <ElementsTooltip
23
+ elements={rolesString}
24
+ maxChar={30}
25
+ rounded={true}
26
+ defaultElements={1}
27
+ elementsPerRow={2}
28
+ colors={colors}
29
+ />
30
+ </S.TagWrapper>
31
+ </S.Wrapper>
32
+ );
33
+ };
34
+
35
+ interface IProps {
36
+ title: string;
37
+ description: string;
38
+ roles: IRole[];
39
+ isSuperAdmin?: boolean;
40
+ }
41
+
42
+ export default RoleItem;
@@ -0,0 +1,29 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ display: flex;
5
+ position: relative;
6
+ flex-direction: column;
7
+ background-color: ${(p) => p.theme.color.uiBackground02};
8
+ border-radius: ${(p) => p.theme.radii.s};
9
+ border: 1px solid ${(p) => p.theme.color.uiLine};
10
+ padding: ${(p) => p.theme.spacing.s};
11
+ `;
12
+
13
+ const Title = styled.div`
14
+ ${(p) => p.theme.textStyle.uiL};
15
+ margin-bottom: ${(p) => p.theme.spacing.xs};
16
+ `;
17
+
18
+ const Description = styled.div`
19
+ ${(p) => p.theme.textStyle.uiXS};
20
+ color: ${(p) => p.theme.color.textMediumEmphasis};
21
+ `;
22
+
23
+ const TagWrapper = styled.div`
24
+ position: absolute;
25
+ right: ${(p) => p.theme.spacing.s};
26
+ top: ${(p) => p.theme.spacing.s};
27
+ `;
28
+
29
+ export { Wrapper, Title, Description, TagWrapper };
@@ -0,0 +1,102 @@
1
+ import { ElementsTooltip } from "@ax/components";
2
+ import type { IRole, ISite, ISiteRoles } from "@ax/types";
3
+
4
+ import RoleItem from "./RoleItem";
5
+
6
+ import * as S from "./style";
7
+
8
+ const UserRolesAndSites = (props: IUserRolesAndSitesProps): JSX.Element => {
9
+ const { isSuperAdmin, userRoles, sites, roles, showRows: showRowsProp } = props;
10
+
11
+ const globalSiteRole = userRoles.find((ur) => ur.siteId === "global");
12
+ const globalRoles = globalSiteRole ? roles.filter((r) => globalSiteRole.roles.includes(r.id)) : [];
13
+
14
+ const allSiteRole = userRoles.find((ur) => ur.siteId === "all");
15
+ const allRoles = allSiteRole ? roles.filter((r) => allSiteRole.roles.includes(r.id)) : [];
16
+
17
+ const siteRoles = userRoles.filter((ur) => ur.siteId !== "global" && ur.siteId !== "all");
18
+
19
+ const showRows = showRowsProp !== undefined ? showRowsProp : isSuperAdmin || (!!allSiteRole && !globalSiteRole);
20
+
21
+ return (
22
+ <>
23
+ <S.RolesContent>
24
+ {isSuperAdmin && (
25
+ <div data-testid="final-step-super-admin">
26
+ <RoleItem
27
+ title="You are a Super admin"
28
+ description="You have full access to all features and their configuration. You have access to all sites."
29
+ roles={[]}
30
+ isSuperAdmin={isSuperAdmin}
31
+ />
32
+ </div>
33
+ )}
34
+ {globalRoles.length > 0 && (
35
+ <div data-testid="final-step-global-role">
36
+ <RoleItem
37
+ title="Global data permissions"
38
+ description="You can access to Global data based on the permissions assigned to the role."
39
+ roles={globalRoles}
40
+ />
41
+ </div>
42
+ )}
43
+ </S.RolesContent>
44
+ {(siteRoles.length > 0 || allRoles.length > 0) && !isSuperAdmin && (
45
+ <S.SitesContent data-testid="final-step-sites-content">
46
+ {!showRows && <S.SitesHeader>Access to Sites</S.SitesHeader>}
47
+ {allRoles.length > 0 && (
48
+ <RoleItem
49
+ title="Access to All Sites"
50
+ description="You can access to all existing and future sites depending on the permissions assigned to the role."
51
+ roles={allRoles}
52
+ />
53
+ )}
54
+ <S.SitesGrid>
55
+ {siteRoles.map((ur) => {
56
+ const site = sites.find((s) => s.id === ur.siteId);
57
+ if (!site) return null;
58
+
59
+ const rolesString = roles
60
+ ?.filter((role: IRole) => ur.roles?.includes(role.id))
61
+ .map((role: IRole) => role.name);
62
+
63
+ const colors = rolesString?.reduce((prev: Record<string, string | undefined>, current) => {
64
+ const color: IRole | undefined = roles.find((role: IRole) => role.name === current);
65
+ prev[current] = color?.hex;
66
+ return prev;
67
+ }, {});
68
+
69
+ return (
70
+ <S.SiteCard key={site.id}>
71
+ <S.SiteCardHeader>
72
+ <S.SiteName>{site.name}</S.SiteName>
73
+ </S.SiteCardHeader>
74
+ <S.TagsRow>
75
+ <ElementsTooltip
76
+ elements={rolesString}
77
+ maxChar={30}
78
+ rounded={true}
79
+ defaultElements={1}
80
+ elementsPerRow={2}
81
+ colors={colors}
82
+ />
83
+ </S.TagsRow>
84
+ </S.SiteCard>
85
+ );
86
+ })}
87
+ </S.SitesGrid>
88
+ </S.SitesContent>
89
+ )}
90
+ </>
91
+ );
92
+ };
93
+
94
+ interface IUserRolesAndSitesProps {
95
+ isSuperAdmin: boolean;
96
+ userRoles: ISiteRoles[];
97
+ sites: ISite[];
98
+ roles: IRole[];
99
+ showRows?: boolean;
100
+ }
101
+
102
+ export default UserRolesAndSites;
@@ -0,0 +1,67 @@
1
+ import styled from "styled-components";
2
+
3
+ const SitesContent = styled.div`
4
+ padding-top: ${(p) => p.theme.spacing.s};
5
+ `;
6
+
7
+ const RolesContent = styled.div`
8
+ padding-top: ${(p) => p.theme.spacing.s};
9
+ `;
10
+
11
+ const SitesHeader = styled.div`
12
+ ${(p) => p.theme.textStyle.headingXS};
13
+ margin-bottom: ${(p) => p.theme.spacing.s};
14
+ `;
15
+
16
+ const SitesGrid = styled.div`
17
+ display: grid;
18
+ grid-template-columns: repeat(3, 1fr);
19
+ gap: ${(p) => p.theme.spacing.xs};
20
+ `;
21
+
22
+ const SiteCard = styled.div`
23
+ display: flex;
24
+ flex-direction: column;
25
+ gap: ${(p) => p.theme.spacing.xxs};
26
+ background-color: ${(p) => p.theme.color.uiBackground02};
27
+ border: 1px solid ${(p) => p.theme.color.uiLine};
28
+ border-radius: ${(p) => p.theme.radii.s};
29
+ padding: ${(p) => p.theme.spacing.s};
30
+ height: 96px;
31
+ `;
32
+
33
+ const SiteCardHeader = styled.div`
34
+ display: flex;
35
+ align-items: center;
36
+ gap: ${(p) => p.theme.spacing.xxs};
37
+ `;
38
+
39
+ const SiteAvatar = styled.img`
40
+ width: 32px;
41
+ height: 32px;
42
+ border-radius: ${(p) => p.theme.radii.xs};
43
+ object-fit: cover;
44
+ flex-shrink: 0;
45
+ margin-right: ${(p) => p.theme.spacing.xxs};
46
+ `;
47
+
48
+ const SiteName = styled.div`
49
+ ${(p) => p.theme.textStyle.uiL};
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+ white-space: nowrap;
53
+ `;
54
+
55
+ const TagsRow = styled.div`
56
+ display: flex;
57
+ align-items: center;
58
+ gap: ${(p) => p.theme.spacing.xxs};
59
+ flex-wrap: wrap;
60
+ margin-top: auto;
61
+
62
+ & > div {
63
+ margin-bottom: 0;
64
+ }
65
+ `;
66
+
67
+ export { RolesContent, SitesContent, SitesHeader, SitesGrid, SiteCard, SiteCardHeader, SiteAvatar, SiteName, TagsRow };
@@ -73,6 +73,7 @@ import HeadingsPreviewModal from "./HeadingsPreviewModal";
73
73
  import Icon from "./Icon";
74
74
  import IconAction from "./IconAction";
75
75
  import Image from "./Image";
76
+ import ImageDragAndDrop from "./ImageDragAndDrop";
76
77
  import InformativeMenu from "./InformativeMenu";
77
78
  import KeywordsPreviewModal from "./KeywordsPreviewModal";
78
79
  import LanguageMenu from "./LanguageMenu";
@@ -89,6 +90,7 @@ import Notification from "./Notification";
89
90
  import OcassionalToast from "./OcassionalToast";
90
91
  import PageFinder from "./PageFinder";
91
92
  import Pagination from "./Pagination";
93
+ import ProfileImage from "./ProfileImage";
92
94
  import ProgressBar from "./ProgressBar";
93
95
  import ReorderArrows from "./ReorderArrows";
94
96
  import ResizePanel from "./ResizePanel";
@@ -123,6 +125,7 @@ import Tabs from "./Tabs";
123
125
  import Tag from "./Tag";
124
126
  import Toast from "./Toast";
125
127
  import Tooltip from "./Tooltip";
128
+ import UserRolesAndSites from "./UserRolesAndSites";
126
129
 
127
130
  export {
128
131
  ActionMenu,
@@ -181,6 +184,7 @@ export {
181
184
  Icon,
182
185
  IconAction,
183
186
  Image,
187
+ ImageDragAndDrop,
184
188
  ImageField,
185
189
  InformativeMenu,
186
190
  KeywordsPreviewModal,
@@ -206,6 +210,7 @@ export {
206
210
  OcassionalToast,
207
211
  PageFinder,
208
212
  Pagination,
213
+ ProfileImage,
209
214
  PermissionsFilter,
210
215
  ProgressBar,
211
216
  RadioField,
@@ -245,6 +250,7 @@ export {
245
250
  UniqueCheck,
246
251
  UrlField,
247
252
  UsersFilter,
253
+ UserRolesAndSites,
248
254
  VisualOption,
249
255
  VisualUniqueSelection,
250
256
  Wysiwyg,
@@ -24,4 +24,16 @@ const itemLabel = {
24
24
 
25
25
  type ItemLabel = ValueOf<typeof itemLabel>;
26
26
 
27
- export { itemLabel, type ItemLabel };
27
+ const VALID_IMAGE_FORMATS = ["jpeg", "jpg", "png", "svg", "gif", "webp"];
28
+ const VALID_DOCUMENT_FORMATS = ["pdf", "doc", "docx", "xls", "xlsx", "zip", "csv", "txt"];
29
+ const VALID_VIDEO_FORMATS = ["mov", "mp4", "wmv", "avi", "webm", "mkv"];
30
+ const VALID_FILE_FORMATS = [...VALID_DOCUMENT_FORMATS, ...VALID_VIDEO_FORMATS];
31
+
32
+ export {
33
+ itemLabel,
34
+ type ItemLabel,
35
+ VALID_IMAGE_FORMATS,
36
+ VALID_DOCUMENT_FORMATS,
37
+ VALID_VIDEO_FORMATS,
38
+ VALID_FILE_FORMATS,
39
+ };
@@ -1,7 +1,7 @@
1
1
  import { global, languages } from "@ax/api";
2
+ import { usersActions } from "@ax/containers/Users";
2
3
  import { isReqOk } from "@ax/helpers";
3
4
  import type { IRootState } from "@ax/types";
4
- import { usersActions } from "@ax/containers/Users";
5
5
 
6
6
  import type { AxiosResponse } from "axios";
7
7
  import differenceInSeconds from "date-fns/differenceInSeconds";
@@ -89,6 +89,12 @@ function logout(): ILogoutAction {
89
89
  return { type: LOGOUT, payload: { token: "" } };
90
90
  }
91
91
 
92
+ function logoutAndNavigate(): (dispatch: Dispatch) => Promise<void> {
93
+ return async (dispatch) => {
94
+ await setHistoryPush("/logout")(dispatch);
95
+ };
96
+ }
97
+
92
98
  function setLanguage(lang: { locale: string; id: number }): ISetLanguageAction {
93
99
  localStorage.setItem("langID", JSON.stringify(lang.id));
94
100
  return { type: SET_LANGUAGE, payload: { lang } };
@@ -267,6 +273,7 @@ export {
267
273
  login,
268
274
  loginSSO,
269
275
  logout,
276
+ logoutAndNavigate,
270
277
  resetError,
271
278
  setError,
272
279
  setGlobalLanguages,
@@ -27,6 +27,7 @@ import type { Dispatch } from "redux";
27
27
  import {
28
28
  DEFAULT_PARAMS,
29
29
  SET_ALL_SITE_PAGES,
30
+ SET_ALL_SITES,
30
31
  SET_CONFIG,
31
32
  SET_CONTENT_FILTERS,
32
33
  SET_CURRENT_SEARCH,
@@ -46,6 +47,7 @@ import {
46
47
  } from "./constants";
47
48
  import type {
48
49
  ISetAllSitePagesAction,
50
+ ISetAllSitesAction,
49
51
  ISetConfig,
50
52
  ISetContentFilters,
51
53
  ISetCurrentSearch,
@@ -87,6 +89,10 @@ function setSitesByLang(sitesList: ISite[]): ISetSitesByLangAction {
87
89
  return { type: SET_SITES_BY_LANG, payload: { sitesByLang: sitesList } };
88
90
  }
89
91
 
92
+ function setAllSites(allSites: ISite[]): ISetAllSitesAction {
93
+ return { type: SET_ALL_SITES, payload: { allSites } };
94
+ }
95
+
90
96
  function setCurrentSiteInfo(currentSiteInfo: ISite | null): ISetCurrentSiteInfoAction {
91
97
  localStorage.setItem("siteID", JSON.stringify(currentSiteInfo?.id || "global"));
92
98
  return { type: SET_CURRENT_SITE_INFO, payload: { currentSiteInfo } };
@@ -203,6 +209,25 @@ function getSitesByLang(params: IGetSitesParams): (dispatch: Dispatch) => Promis
203
209
  };
204
210
  }
205
211
 
212
+ function getAllSites(): (dispatch: Dispatch) => Promise<void> {
213
+ return async (dispatch) => {
214
+ try {
215
+ const responseActions = {
216
+ handleSuccess: (data: any) => {
217
+ dispatch(setAllSites(data.allSites));
218
+ },
219
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
220
+ };
221
+
222
+ const callback = async () => sites.getAllSites({ pagination: false });
223
+
224
+ await handleRequest(callback, responseActions, [])(dispatch);
225
+ } catch (e) {
226
+ console.log(e);
227
+ }
228
+ };
229
+ }
230
+
206
231
  function saveSettings(form: ISettingsForm): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
207
232
  return async (dispatch, getState) => {
208
233
  try {
@@ -817,4 +842,5 @@ export {
817
842
  getSite,
818
843
  updateCurrentSearch,
819
844
  setIsSitesLoading,
845
+ getAllSites,
820
846
  };
@@ -18,6 +18,7 @@ export const SET_CONFIG = `${NAME}/SET_CONFIG`;
18
18
  export const SET_CURRENT_SEARCH = `${NAME}/SET_CURRENT_SEARCH`;
19
19
  export const SET_THEME_ELEMENTS = `${NAME}/SET_THEME_ELEMENTS`;
20
20
  export const SET_IS_SITES_LOADING = `${NAME}/SET_IS_SITES_LOADING`;
21
+ export const SET_ALL_SITES = `${NAME}/SET_ALL_SITES`;
21
22
 
22
23
  export const ITEMS_PER_PAGE = 50;
23
24
 
@@ -17,6 +17,7 @@ import {
17
17
  SET_CURRENT_SEARCH,
18
18
  SET_THEME_ELEMENTS,
19
19
  SET_IS_SITES_LOADING,
20
+ SET_ALL_SITES,
20
21
  } from "./constants";
21
22
  import { IQueryValue, ISite, ISiteListConfig, IThemeElements } from "@ax/types";
22
23
 
@@ -110,4 +111,9 @@ export interface ISetIsSitesLoading {
110
111
  payload: { isSitesLoading: boolean };
111
112
  }
112
113
 
114
+ export interface ISetAllSitesAction {
115
+ type: typeof SET_ALL_SITES;
116
+ payload: { allSites: ISite[] };
117
+ }
118
+
113
119
  export type SitesActionsCreators = ISetSitesAction & ISetCurrentSiteInfoAction;
@@ -17,9 +17,10 @@ import {
17
17
  SET_CURRENT_SEARCH,
18
18
  SET_THEME_ELEMENTS,
19
19
  SET_IS_SITES_LOADING,
20
+ SET_ALL_SITES,
20
21
  } from "./constants";
21
22
 
22
- import { ISite, IPage, ILanguage, ISiteListConfig, IQueryValue, IThemeElements } from "@ax/types";
23
+ import type { ISite, IPage, ILanguage, ISiteListConfig, IQueryValue, IThemeElements } from "@ax/types";
23
24
 
24
25
  import { SitesActionsCreators } from "./interfaces";
25
26
 
@@ -30,6 +31,7 @@ export interface ISitesState {
30
31
  sites: ISite[];
31
32
  recentSites: ISite[];
32
33
  sitesTotalItems: number;
34
+ allSites: ISite[];
33
35
  sitesByLang: ISite[];
34
36
  currentSiteInfo: ISite | null;
35
37
  currentFilter: string | null;
@@ -65,6 +67,7 @@ export const initialState = {
65
67
  currentSitePages: [],
66
68
  allSitePages: [],
67
69
  sites: [],
70
+ allSites: [],
68
71
  recentSites: [],
69
72
  sitesByLang: [],
70
73
  currentSiteInfo: null,
@@ -101,6 +104,7 @@ export function reducer(state = initialState, action: any): ISitesState {
101
104
  case SET_CURRENT_SEARCH:
102
105
  case SET_THEME_ELEMENTS:
103
106
  case SET_IS_SITES_LOADING:
107
+ case SET_ALL_SITES:
104
108
  return { ...state, ...action.payload };
105
109
  default:
106
110
  return state;
@@ -1,11 +1,12 @@
1
- import { IRole, IUser } from "@ax/types";
1
+ import type { IRole, IUser } from "@ax/types";
2
+
2
3
  import {
3
- SET_USERS,
4
- SET_USER_FORM,
5
- SET_CURRENT_USER,
6
- SET_ROLES,
7
4
  SET_CURRENT_PERMISSIONS,
5
+ SET_CURRENT_USER,
8
6
  SET_GLOBAL_PERMISSIONS,
7
+ SET_ROLES,
8
+ SET_USER_FORM,
9
+ SET_USERS,
9
10
  } from "./constants";
10
11
 
11
12
  export interface IUsersState {
@@ -1,8 +1,8 @@
1
- import React, { useEffect, useState } from "react";
1
+ import { type ReactNode, useEffect, useState } from "react";
2
2
  import { Prompt } from "react-router-dom";
3
3
 
4
- import { useModal } from "@ax/hooks";
5
4
  import { Modal } from "@ax/components";
5
+ import { useModal } from "@ax/hooks";
6
6
 
7
7
  import * as S from "./style";
8
8
 
@@ -13,8 +13,8 @@ const RouteLeavingGuard = (props: IProps) => {
13
13
  const [lastLocation, setLastLocation] = useState<Location | null>(null);
14
14
  const [confirmedNavigation, setConfirmedNavigation] = useState(false);
15
15
 
16
- const handleBlockedNavigation = (nextLocation: any): boolean => {
17
- const isAllowedNavigation = allowedRoutes && allowedRoutes.includes(nextLocation.pathname);
16
+ const handleBlockedNavigation = (nextLocation: Location): boolean => {
17
+ const isAllowedNavigation = allowedRoutes?.includes(nextLocation.pathname);
18
18
  if (!confirmedNavigation && !isAllowedNavigation) {
19
19
  toggleModal();
20
20
  setLastLocation(nextLocation);
@@ -55,11 +55,9 @@ const RouteLeavingGuard = (props: IProps) => {
55
55
  mainAction={mainAction}
56
56
  secondaryAction={secondaryAction}
57
57
  >
58
- {
59
- <S.ModalContent>
60
- {text} If you exit without saving it, it will be lost. Do you want to discard your changes?
61
- </S.ModalContent>
62
- }
58
+ <S.ModalContent>
59
+ {text} If you exit without saving it, it will be lost. Do you want to discard your changes?
60
+ </S.ModalContent>
63
61
  </Modal>
64
62
  </>
65
63
  );
@@ -68,8 +66,8 @@ const RouteLeavingGuard = (props: IProps) => {
68
66
  interface IRouterLeavingProps {
69
67
  when: boolean;
70
68
  action(path: string): void;
71
- text: any;
72
- allowedRoutes?: any;
69
+ text: ReactNode;
70
+ allowedRoutes?: string[];
73
71
  }
74
72
 
75
73
  type IProps = IRouterLeavingProps;
@@ -1,3 +1,5 @@
1
+ import type { Area } from "react-easy-crop";
2
+
1
3
  import { toBlob } from "html-to-image";
2
4
 
3
5
  const formatBytes = (bytes: number | null, decimals = 2) => {
@@ -9,7 +11,7 @@ const formatBytes = (bytes: number | null, decimals = 2) => {
9
11
 
10
12
  const i = Math.floor(Math.log(bytes) / Math.log(k));
11
13
 
12
- return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
14
+ return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
13
15
  };
14
16
 
15
17
  const imageToBase64 = (file: any): Promise<string> => {
@@ -35,7 +37,7 @@ const getImageFromHtml = async (html: HTMLDivElement, fileName: string): Promise
35
37
 
36
38
  const elChildren = browserContent.querySelectorAll("*");
37
39
  let maxAbsoluteElementHeight = 0;
38
- [].forEach.call(elChildren, function (element: HTMLElement) {
40
+ [].forEach.call(elChildren, (element: HTMLElement) => {
39
41
  const isAbsolutePosition = getComputedStyle(element).position === "absolute";
40
42
  const isMaxAbsolutePositionHeight = element.clientHeight > maxAbsoluteElementHeight;
41
43
  if (isAbsolutePosition && isMaxAbsolutePositionHeight) {
@@ -90,4 +92,49 @@ const getImageFromIFrame = async (html: HTMLDivElement, fileName: string): Promi
90
92
  });
91
93
  };
92
94
 
93
- export { formatBytes, imageToBase64, getImageFromHtml, getImageFromIFrame };
95
+ const createImage = (url: string): Promise<HTMLImageElement> =>
96
+ new Promise((resolve, reject) => {
97
+ const image = new Image();
98
+ image.addEventListener("load", () => resolve(image));
99
+ image.addEventListener("error", (error) => reject(error));
100
+ image.setAttribute("crossOrigin", "anonymous");
101
+ image.src = url;
102
+ });
103
+
104
+ const getCroppedImg = async (imageSrc: string, pixelCrop: Area): Promise<File> => {
105
+ const image = await createImage(imageSrc);
106
+ const canvas = document.createElement("canvas");
107
+ canvas.width = pixelCrop.width;
108
+ canvas.height = pixelCrop.height;
109
+ const ctx = canvas.getContext("2d");
110
+
111
+ if (!ctx) throw new Error("Could not get canvas context");
112
+
113
+ ctx.drawImage(
114
+ image,
115
+ pixelCrop.x,
116
+ pixelCrop.y,
117
+ pixelCrop.width,
118
+ pixelCrop.height,
119
+ 0,
120
+ 0,
121
+ pixelCrop.width,
122
+ pixelCrop.height,
123
+ );
124
+
125
+ return new Promise((resolve, reject) => {
126
+ canvas.toBlob(
127
+ (blob) => {
128
+ if (!blob) {
129
+ reject(new Error("Canvas is empty"));
130
+ return;
131
+ }
132
+ resolve(new File([blob], "avatar.jpg", { type: "image/jpeg" }));
133
+ },
134
+ "image/jpeg",
135
+ 0.9,
136
+ );
137
+ });
138
+ };
139
+
140
+ export { formatBytes, imageToBase64, getImageFromHtml, getImageFromIFrame, createImage, getCroppedImg };