@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
@@ -0,0 +1,94 @@
1
+ import styled from "styled-components";
2
+
3
+ import Button from "@ax/components/Button";
4
+
5
+ const Wrapper = styled.div`
6
+ display: flex;
7
+ flex-direction: column;
8
+ align-items: center;
9
+ width: 100%;
10
+ height: 100%;
11
+ `;
12
+
13
+ const DragAndDropWrapper = styled.div`
14
+ width: 290px;
15
+ margin: ${(p) => p.theme.spacing.s} auto;
16
+ `;
17
+
18
+ const IconWrapper = styled.div`
19
+ svg path {
20
+ fill: ${(p) => p.theme.color.interactive01};
21
+ }
22
+ `;
23
+
24
+ const DragTitle = styled.p`
25
+ ${(p) => p.theme.textStyle.fieldLabel};
26
+ color: ${(p) => p.theme.color.interactive01};
27
+ `;
28
+
29
+ const DropZone = styled.div<{ inDropZone: boolean }>`
30
+ width: 276px;
31
+ height: 276px;
32
+ border-radius: 50%;
33
+ border: 2px dashed ${(p) => p.theme.color.interactive01};
34
+ background-color: ${(p) => (p.inDropZone ? p.theme.color.interactive01 : "transparent")};
35
+ display: flex;
36
+ flex-direction: column;
37
+ align-items: center;
38
+ justify-content: center;
39
+ gap: ${(p) => p.theme.spacing.xxs};
40
+ transition: background-color 0.15s;
41
+
42
+ ${DragTitle} {
43
+ color: ${(p) => (p.inDropZone ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
44
+ }
45
+
46
+ ${IconWrapper} {
47
+ svg {
48
+ path {
49
+ fill: ${(p) => (p.inDropZone ? p.theme.color.interactiveInverse : p.theme.color.interactive01)};
50
+ }
51
+ }
52
+ }
53
+ `;
54
+
55
+ const DragSubtitle = styled.p`
56
+ ${(p) => p.theme.textStyle.uiXS};
57
+ color: ${(p) => p.theme.color.textMediumEmphasis};
58
+ `;
59
+
60
+ const DragError = styled.p`
61
+ ${(p) => p.theme.textStyle.uiXS};
62
+ color: ${(p) => p.theme.color.error};
63
+ width: 100%;
64
+ text-align: center;
65
+ `;
66
+
67
+ const FilesInput = styled.input`
68
+ display: none;
69
+ `;
70
+
71
+ const _Button: any = (props: any) => <Button {...props} />;
72
+ const SelectButton = styled(_Button)`
73
+ margin-top: ${(p) => p.theme.spacing.xs};
74
+ `;
75
+
76
+ const Actions = styled.div`
77
+ display: flex;
78
+ width: 100%;
79
+ justify-content: flex-end;
80
+ margin-top: auto;
81
+ `;
82
+
83
+ export {
84
+ Wrapper,
85
+ DragAndDropWrapper,
86
+ Actions,
87
+ IconWrapper,
88
+ DragTitle,
89
+ DragSubtitle,
90
+ DropZone,
91
+ FilesInput,
92
+ SelectButton,
93
+ DragError,
94
+ };
@@ -0,0 +1,44 @@
1
+ import { useState } from "react";
2
+ import type { Area } from "react-easy-crop";
3
+
4
+ import CropView from "./CropView";
5
+ import UploadView from "./UploadView";
6
+
7
+ import * as S from "./style";
8
+
9
+ const ImageStep = (props: IProps) => {
10
+ const { userImage, onCropConfirmed, onSkip } = props;
11
+
12
+ const [imageSrc, setImageSrc] = useState<string | null>(userImage || null);
13
+
14
+ return (
15
+ <S.Wrapper>
16
+ <S.InfoWrapper>
17
+ <S.Title>Upload Your Profile Picture</S.Title>
18
+ <S.Text>
19
+ Customize how people see you. If you don't upload a picture, one will be generated based on your initials and
20
+ a random color.
21
+ </S.Text>
22
+ </S.InfoWrapper>
23
+ <S.ImageWrapper>
24
+ {!imageSrc ? (
25
+ <UploadView onImageLoaded={setImageSrc} onSkip={onSkip} />
26
+ ) : (
27
+ <CropView
28
+ imageSrc={imageSrc}
29
+ onConfirm={(croppedAreaPixels) => onCropConfirmed(imageSrc, croppedAreaPixels)}
30
+ onUploadNew={() => setImageSrc(null)}
31
+ />
32
+ )}
33
+ </S.ImageWrapper>
34
+ </S.Wrapper>
35
+ );
36
+ };
37
+
38
+ interface IProps {
39
+ userImage?: string;
40
+ onCropConfirmed: (imageSrc: string, croppedAreaPixels: Area) => void;
41
+ onSkip: () => void;
42
+ }
43
+
44
+ export default ImageStep;
@@ -0,0 +1,31 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ display: flex;
5
+ flex-direction: column;
6
+ width: 100%;
7
+ height: 100%;
8
+ padding: ${(p) => `32px ${p.theme.spacing.m} ${p.theme.spacing.m} ${p.theme.spacing.m}`};
9
+ `;
10
+
11
+ const InfoWrapper = styled.div`
12
+ width: 450px;
13
+ margin-left: 85px;
14
+ margin-bottom: 16px;
15
+ `;
16
+
17
+ const Title = styled.div`
18
+ ${(p) => p.theme.textStyle.headingM};
19
+ margin-bottom: ${(p) => p.theme.spacing.xs};
20
+ `;
21
+
22
+ const Text = styled.div`
23
+ ${(p) => p.theme.textStyle.uiS};
24
+ font-weight: 400;
25
+ `;
26
+
27
+ const ImageWrapper = styled.div`
28
+ flex-grow: 1;
29
+ `;
30
+
31
+ export { Wrapper, InfoWrapper, Title, Text, ImageWrapper };
@@ -0,0 +1,51 @@
1
+ import { Button, Select } from "@ax/components";
2
+
3
+ import WorldMap from "react-timezone-map-select/dist/WorldMap";
4
+
5
+ import { timezones } from "../../../../Settings/Globals/constants";
6
+ import type { IUserFormState } from "..";
7
+
8
+ import * as S from "./style";
9
+
10
+ const TimezoneStep = (props: IProps) => {
11
+ const { onClickNext, onClickBack, form, isSaving, setForm } = props;
12
+
13
+ const handleChange = (timeZoneName: string) => {
14
+ setForm({ ...form, timezone: timeZoneName });
15
+ };
16
+
17
+ return (
18
+ <S.Wrapper>
19
+ <S.Header>
20
+ <S.InfoWrapper>
21
+ <S.Title>Complete your job information</S.Title>
22
+ <S.Text>Select a time zone in the list or click an area on the map.</S.Text>
23
+ </S.InfoWrapper>
24
+ <S.SelectWrapper>
25
+ <Select name="timezone" value={form.timezone} options={timezones} onChange={handleChange} />
26
+ </S.SelectWrapper>
27
+ </S.Header>
28
+ <S.MapWrapper>
29
+ <WorldMap timeZoneName={form.timezone} onChange={handleChange} />
30
+ </S.MapWrapper>
31
+ <S.Actions>
32
+ <Button type="button" buttonStyle="text" onClick={onClickBack}>
33
+ Back
34
+ </Button>
35
+ <Button type="button" onClick={onClickNext} disabled={!form.timezone || isSaving}>
36
+ {isSaving ? "Saving..." : "Save profile"}
37
+ </Button>
38
+ </S.Actions>
39
+ </S.Wrapper>
40
+ );
41
+ };
42
+
43
+ interface IProps {
44
+ form: IUserFormState;
45
+ isSaving: boolean;
46
+ setForm: (data: IUserFormState) => void;
47
+ onClickNext: () => void;
48
+ onClickBack: () => void;
49
+ }
50
+
51
+ export default TimezoneStep;
@@ -0,0 +1,52 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ display: flex;
5
+ flex-direction: column;
6
+ width: 100%;
7
+ height: 100%;
8
+ padding: ${(p) => `32px ${p.theme.spacing.m} ${p.theme.spacing.m} ${p.theme.spacing.m}`};
9
+ `;
10
+
11
+ const InfoWrapper = styled.div``;
12
+
13
+ const Title = styled.div`
14
+ ${(p) => p.theme.textStyle.headingM};
15
+ margin-bottom: ${(p) => p.theme.spacing.xs};
16
+ `;
17
+
18
+ const Text = styled.div`
19
+ ${(p) => p.theme.textStyle.uiS};
20
+ font-weight: 400;
21
+ `;
22
+
23
+ const MapWrapper = styled.div`
24
+ flex: 1;
25
+ min-height: 0;
26
+
27
+ svg {
28
+ display: block;
29
+ width: 100%;
30
+ height: auto;
31
+ margin: auto;
32
+ }
33
+ `;
34
+
35
+ const Actions = styled.div`
36
+ display: flex;
37
+ gap: ${(p) => p.theme.spacing.s};
38
+ justify-content: flex-end;
39
+ margin-top: auto;
40
+ padding-top: ${(p) => p.theme.spacing.s};
41
+ `;
42
+
43
+ const Header = styled.div`
44
+ display: flex;
45
+ `;
46
+
47
+ const SelectWrapper = styled.div`
48
+ width: 252px;
49
+ margin-left: auto;
50
+ `;
51
+
52
+ export { Wrapper, InfoWrapper, Title, Text, MapWrapper, Actions, Header, SelectWrapper };
@@ -0,0 +1,40 @@
1
+ import { Button } from "@ax/components";
2
+
3
+ import * as S from "./style";
4
+
5
+ const WelcomeStep = (props: IWelcomeStepProps) => {
6
+ const { onSkip, onClickNext } = props;
7
+
8
+ return (
9
+ <S.Wrapper>
10
+ <S.WelcomeWrapper>
11
+ <S.EmojiWrapper>👋</S.EmojiWrapper>
12
+ <S.Title>Welcome to Griddo :)</S.Title>
13
+ <S.Text>
14
+ <p>We’re excited to have you onboard to help us build a better web.</p>
15
+ <p>
16
+ Let's start <strong>completing your profile</strong> in Griddo. This will help the team to recognise your
17
+ and improve collaboraation.
18
+ </p>
19
+ </S.Text>
20
+ </S.WelcomeWrapper>
21
+ <S.ImageWrapper>
22
+ <S.Actions>
23
+ <Button type="button" buttonStyle="text" onClick={() => onSkip()}>
24
+ Skip, I’ll do it later
25
+ </Button>
26
+ <Button type="button" onClick={() => onClickNext()}>
27
+ Complete profile
28
+ </Button>
29
+ </S.Actions>
30
+ </S.ImageWrapper>
31
+ </S.Wrapper>
32
+ );
33
+ };
34
+
35
+ interface IWelcomeStepProps {
36
+ onSkip: () => void;
37
+ onClickNext: () => void;
38
+ }
39
+
40
+ export default WelcomeStep;
@@ -0,0 +1,53 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div`
4
+ display: flex;
5
+ height: 100%;
6
+ `;
7
+
8
+ const WelcomeWrapper = styled.div`
9
+ display: flex;
10
+ flex-direction: column;
11
+ padding: 45px;
12
+ `;
13
+
14
+ const ImageWrapper = styled.div`
15
+ display: flex;
16
+ width: 516px;
17
+ background-image: url(/img/welcome.svg);
18
+ flex-shrink: 0;
19
+ align-items: flex-end;
20
+ justify-content: flex-end;
21
+ `;
22
+
23
+ const EmojiWrapper = styled.div`
24
+ font-size: 44px;
25
+ line-height: 100%;
26
+ margin-bottom: ${(p) => p.theme.spacing.xs};
27
+ `;
28
+
29
+ const Title = styled.div`
30
+ font-size: 28px;
31
+ line-height: 37px;
32
+ font-weight: 700;
33
+ margin-bottom: ${(p) => p.theme.spacing.s};
34
+ `;
35
+
36
+ const Text = styled.div`
37
+ ${(p) => p.theme.textStyle.uiL};
38
+ font-weight: 400;
39
+ p {
40
+ margin-bottom: ${(p) => p.theme.spacing.s};
41
+ }
42
+ strong {
43
+ font-weight: 600;
44
+ }
45
+ `;
46
+
47
+ const Actions = styled.div`
48
+ display: flex;
49
+ gap: ${(p) => p.theme.spacing.s};
50
+ padding: ${(p) => p.theme.spacing.m};
51
+ `;
52
+
53
+ export { Wrapper, WelcomeWrapper, ImageWrapper, EmojiWrapper, Title, Text, Actions };
@@ -0,0 +1,215 @@
1
+ import { useState } from "react";
2
+ import type { Area } from "react-easy-crop";
3
+ import { connect } from "react-redux";
4
+
5
+ import { Modal } from "@ax/components";
6
+ import { appActions } from "@ax/containers/App";
7
+ import { galleryActions } from "@ax/containers/Gallery";
8
+ import { usersActions } from "@ax/containers/Users";
9
+ import { getCroppedImg } from "@ax/helpers";
10
+ import type { IImage, IModal, IRole, IRootState, ISite, IUser } from "@ax/types";
11
+
12
+ import DataStep from "./DataStep";
13
+ import FinalStep from "./FinalStep";
14
+ import ImageStep from "./ImageStep";
15
+ import TimezoneStep from "./TimezoneStep";
16
+ import { getCompanyFromEmail } from "./utils";
17
+ import WelcomeStep from "./WelcomeStep";
18
+
19
+ import * as S from "./style";
20
+
21
+ type Step = "welcome" | "image" | "data" | "timezone" | "final";
22
+ const STEPS: Step[] = ["welcome", "image", "data", "timezone", "final"];
23
+
24
+ const WelcomeModal = (props: IProps) => {
25
+ const { isOpen, toggleModal, currentUser, roles, sites, uploadImage, updateUser, setHistoryPush } = props;
26
+
27
+ const [currentStep, setCurrentStep] = useState<Step>("welcome");
28
+ const [croppedImageFile, setCroppedImageFile] = useState<File | null>(null);
29
+ const [isSaving, setIsSaving] = useState(false);
30
+ const [userForm, setUserForm] = useState<IUserFormState>({
31
+ name: currentUser.name,
32
+ username: currentUser.username,
33
+ position: currentUser.position || "",
34
+ company: getCompanyFromEmail(currentUser.email),
35
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone ?? "",
36
+ });
37
+
38
+ const stepIndex = STEPS.indexOf(currentStep);
39
+ const { isSuperAdmin, roles: userRoles } = currentUser;
40
+
41
+ const handleBack = () => setCurrentStep(STEPS[stepIndex - 1]);
42
+
43
+ const handleNext = () => setCurrentStep(STEPS[stepIndex + 1]);
44
+
45
+ const handleSaveAndFinish = async () => {
46
+ if (!currentUser?.id) return;
47
+ setIsSaving(true);
48
+ try {
49
+ let image: IImage | null = null;
50
+ if (croppedImageFile) {
51
+ image = await uploadImage(croppedImageFile, "global", undefined, false);
52
+ }
53
+ await updateUser(
54
+ currentUser.id,
55
+ {
56
+ ...currentUser,
57
+ username: userForm.username,
58
+ company: userForm.company,
59
+ timezone: userForm.timezone,
60
+ position: userForm.position,
61
+ image,
62
+ profileCreated: true,
63
+ },
64
+ true,
65
+ false,
66
+ );
67
+ } catch (e) {
68
+ console.error("Profile update failed", e);
69
+ }
70
+ setIsSaving(false);
71
+ setCurrentStep(STEPS[stepIndex + 1]);
72
+ };
73
+
74
+ const handleSkipAndClose = async () => {
75
+ if (!currentUser?.id) return;
76
+ if (currentStep !== "final") {
77
+ await updateUser(currentUser.id, { ...currentUser, profileCreated: true }, true, false);
78
+ setCurrentStep("final");
79
+ } else {
80
+ toggleModal();
81
+ }
82
+ };
83
+
84
+ const handleClose = () => {
85
+ setCurrentStep("welcome");
86
+ setCroppedImageFile(null);
87
+ toggleModal();
88
+ };
89
+
90
+ const handleViewProfile = () => {
91
+ handleClose();
92
+ setHistoryPush("/profile");
93
+ };
94
+
95
+ const handleCropConfirmed = async (imageSrc: string, croppedAreaPixels: Area) => {
96
+ const file = await getCroppedImg(imageSrc, croppedAreaPixels);
97
+ setCroppedImageFile(file);
98
+ setCurrentStep(STEPS[stepIndex + 1]);
99
+ };
100
+
101
+ const getTitle = (step: string) => {
102
+ switch (step) {
103
+ case "image":
104
+ return "Step 1 of 3: Profile Picture";
105
+ case "data":
106
+ return "Step 2 of 3: Profile data";
107
+ case "timezone":
108
+ return "Step 3 of 3: Define timezone";
109
+ case "final":
110
+ return "🎉 Ready to start :)";
111
+ default:
112
+ return "Welcome";
113
+ }
114
+ };
115
+
116
+ const getBarSize = (step: string) => {
117
+ switch (step) {
118
+ case "image":
119
+ return "33%";
120
+ case "data":
121
+ return "66%";
122
+ case "timezone":
123
+ return "100%";
124
+ default:
125
+ return "0";
126
+ }
127
+ };
128
+
129
+ const globalSiteRole = userRoles.find((ur) => ur.siteId === "global");
130
+ const allSiteRole = userRoles.find((ur) => ur.siteId === "all");
131
+ const hasOnlyAllSites = !!allSiteRole && !globalSiteRole;
132
+ const size = currentStep === "final" && !isSuperAdmin && !hasOnlyAllSites ? "XL" : "L";
133
+ const height = currentStep === "final" && !isSuperAdmin && !hasOnlyAllSites ? 608 : 563;
134
+
135
+ return (
136
+ <Modal isOpen={isOpen} hide={handleSkipAndClose} title={getTitle(currentStep)} size={size} height={height}>
137
+ <>
138
+ <S.ProgressBar width={getBarSize(currentStep)} />
139
+ {currentStep === "welcome" && <WelcomeStep onSkip={handleSkipAndClose} onClickNext={handleNext} />}
140
+ {currentStep === "image" && (
141
+ <ImageStep onCropConfirmed={handleCropConfirmed} onSkip={handleNext} userImage={currentUser.image?.url} />
142
+ )}
143
+ {currentStep === "data" && (
144
+ <DataStep
145
+ onClickNext={handleNext}
146
+ onClickBack={handleBack}
147
+ form={userForm}
148
+ setForm={setUserForm}
149
+ croppedImageUrl={croppedImageFile ? URL.createObjectURL(croppedImageFile) : null}
150
+ />
151
+ )}
152
+ {currentStep === "timezone" && (
153
+ <TimezoneStep
154
+ onClickNext={handleSaveAndFinish}
155
+ onClickBack={handleBack}
156
+ form={userForm}
157
+ setForm={setUserForm}
158
+ isSaving={isSaving}
159
+ />
160
+ )}
161
+ {currentStep === "final" && (
162
+ <FinalStep
163
+ onClose={handleClose}
164
+ onViewProfile={handleViewProfile}
165
+ isSuperAdmin={isSuperAdmin}
166
+ userRoles={userRoles}
167
+ roles={roles}
168
+ sites={sites}
169
+ />
170
+ )}
171
+ </>
172
+ </Modal>
173
+ );
174
+ };
175
+
176
+ interface IStateProps {
177
+ currentUser: IUser;
178
+ roles: IRole[];
179
+ sites: ISite[];
180
+ }
181
+
182
+ export interface IUserFormState {
183
+ name: string;
184
+ username: string;
185
+ position: string;
186
+ company: string;
187
+ timezone: string;
188
+ }
189
+
190
+ interface IDispatchProps {
191
+ uploadImage: (
192
+ file: File,
193
+ site: number | string,
194
+ setProgress?: (progress: number) => void,
195
+ visible?: boolean,
196
+ ) => Promise<IImage | null>;
197
+ updateUser: (id: number, data: IUser, isProfile: boolean, isList?: boolean) => Promise<boolean>;
198
+ setHistoryPush: (path: string) => void;
199
+ }
200
+
201
+ type IProps = IModal & IStateProps & IDispatchProps;
202
+
203
+ const mapStateToProps = (state: IRootState): IStateProps => ({
204
+ currentUser: state.users.currentUser!,
205
+ roles: state.users.roles,
206
+ sites: state.sites.allSites,
207
+ });
208
+
209
+ const mapDispatchToProps = {
210
+ uploadImage: galleryActions.uploadImage,
211
+ updateUser: usersActions.updateUser,
212
+ setHistoryPush: appActions.setHistoryPush,
213
+ };
214
+
215
+ export default connect(mapStateToProps, mapDispatchToProps)(WelcomeModal);
@@ -0,0 +1,12 @@
1
+ import styled from "styled-components";
2
+
3
+ const ProgressBar = styled.div<{ width: string }>`
4
+ position: absolute;
5
+ top: 0;
6
+ left: 0;
7
+ height: 2px;
8
+ width: ${(p) => p.width};
9
+ background-color: ${(p) => p.theme.color.interactive01};
10
+ `;
11
+
12
+ export { ProgressBar };
@@ -0,0 +1,26 @@
1
+ import type { ISiteRoles } from "@ax/types";
2
+
3
+ const GENERIC_DOMAINS = new Set(["gmail", "hotmail", "outlook", "yahoo", "icloud", "live", "msn", "aol"]);
4
+
5
+ const getCompanyFromEmail = (email: string): string => {
6
+ const domain = email.split("@")[1] ?? "";
7
+ const company = domain.substring(0, domain.lastIndexOf("."));
8
+ if (GENERIC_DOMAINS.has(company)) return "";
9
+ return company.charAt(0).toUpperCase() + company.slice(1);
10
+ };
11
+
12
+ const getMostFrequentRole = (userRoles: ISiteRoles[]): number | null => {
13
+ const roleCounts: Record<number, number> = {};
14
+
15
+ userRoles.forEach(({ roles }) => {
16
+ roles.forEach((roleId) => {
17
+ roleCounts[roleId] = (roleCounts[roleId] || 0) + 1;
18
+ });
19
+ });
20
+
21
+ if (Object.keys(roleCounts).length === 0) return null;
22
+
23
+ return Number(Object.entries(roleCounts).reduce((prev, curr) => (curr[1] > prev[1] ? curr : prev))[0]);
24
+ };
25
+
26
+ export { getCompanyFromEmail, getMostFrequentRole };
@@ -1,11 +1,9 @@
1
- import React from "react";
2
-
3
1
  import { trimText } from "@ax/helpers";
4
2
  import { Tooltip, Image } from "@ax/components";
5
3
 
6
4
  import * as S from "./style";
7
5
 
8
- export const ItemName = (props: IItemNameProps): JSX.Element => {
6
+ const ItemName = (props: IItemNameProps): JSX.Element => {
9
7
  const { name, length } = props;
10
8
  return name.length > length ? (
11
9
  <Tooltip content={name} left={120}>
@@ -16,7 +14,7 @@ export const ItemName = (props: IItemNameProps): JSX.Element => {
16
14
  );
17
15
  };
18
16
 
19
- export const ItemThumbnail = (props: IItemThumbnailProps): JSX.Element => {
17
+ const ItemThumbnail = (props: IItemThumbnailProps): JSX.Element => {
20
18
  const { src, width, height, borderRadius } = props;
21
19
  const thumbnailPlaceholder = "/img/placeholder/thumbnail@1x.png";
22
20
  return src ? (
@@ -45,3 +43,5 @@ interface IItemThumbnailProps {
45
43
  height: number;
46
44
  borderRadius?: boolean;
47
45
  }
46
+
47
+ export { ItemName, ItemThumbnail };