@griddo/ax 11.15.9 → 11.16.0-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 (95) hide show
  1. package/package.json +2 -2
  2. package/src/__tests__/components/RestrictionsModal/CurrentAccessList/CurrentAccessList.test.tsx +173 -0
  3. package/src/__tests__/components/RestrictionsModal/OptionsStep/OptionsStep.test.tsx +160 -0
  4. package/src/__tests__/components/RestrictionsModal/RestrictionsModal.test.tsx +134 -0
  5. package/src/__tests__/components/RestrictionsModal/RolesList/RolesList.test.tsx +234 -0
  6. package/src/__tests__/components/RestrictionsModal/UsersList/UsersList.test.tsx +288 -0
  7. package/src/__tests__/hooks/users.test.tsx +382 -0
  8. package/src/api/pages.tsx +24 -1
  9. package/src/api/sites.tsx +14 -14
  10. package/src/api/structuredData.tsx +17 -7
  11. package/src/components/Browser/index.tsx +12 -0
  12. package/src/components/Browser/style.tsx +5 -1
  13. package/src/components/ConfigPanel/Form/index.tsx +11 -10
  14. package/src/components/ContentItem/index.tsx +31 -0
  15. package/src/components/ContentItem/style.tsx +43 -0
  16. package/src/components/Fields/ImageField/index.tsx +2 -1
  17. package/src/components/Fields/RadioField/index.tsx +3 -3
  18. package/src/components/Fields/RadioField/style.tsx +1 -1
  19. package/src/components/FloatingNote/index.tsx +12 -1
  20. package/src/components/FloatingNote/style.tsx +2 -1
  21. package/src/components/Modal/style.tsx +11 -0
  22. package/src/components/PageFinder/SelectionListItem/index.tsx +27 -17
  23. package/src/components/PageFinder/index.tsx +65 -28
  24. package/src/components/PageFinder/style.tsx +0 -1
  25. package/src/components/RestrictionsModal/CurrentAccessList/index.tsx +74 -0
  26. package/src/components/RestrictionsModal/CurrentAccessList/style.tsx +155 -0
  27. package/src/components/RestrictionsModal/OptionsStep/index.tsx +55 -0
  28. package/src/components/RestrictionsModal/OptionsStep/style.tsx +29 -0
  29. package/src/components/RestrictionsModal/RolesList/RoleItem/index.tsx +54 -0
  30. package/src/components/RestrictionsModal/RolesList/RoleItem/style.tsx +34 -0
  31. package/src/components/RestrictionsModal/RolesList/index.tsx +107 -0
  32. package/src/components/RestrictionsModal/RolesList/style.tsx +76 -0
  33. package/src/components/RestrictionsModal/UsersList/UserItem/index.tsx +78 -0
  34. package/src/components/RestrictionsModal/UsersList/UserItem/style.tsx +73 -0
  35. package/src/components/RestrictionsModal/UsersList/index.tsx +151 -0
  36. package/src/components/RestrictionsModal/UsersList/style.tsx +55 -0
  37. package/src/components/RestrictionsModal/index.tsx +181 -0
  38. package/src/components/RestrictionsModal/style.tsx +31 -0
  39. package/src/components/RestrictionsModal/utils.tsx +24 -0
  40. package/src/components/ScheduleModal/index.tsx +32 -8
  41. package/src/components/SelectionComponent/index.tsx +65 -0
  42. package/src/components/SelectionComponent/style.tsx +69 -0
  43. package/src/components/TableFilters/RoleFilter/index.tsx +3 -3
  44. package/src/components/TableList/TableItem/style.tsx +7 -13
  45. package/src/components/Tabs/index.tsx +2 -1
  46. package/src/components/UserRolesAndSites/index.tsx +2 -2
  47. package/src/components/index.tsx +6 -0
  48. package/src/containers/PageEditor/actions.tsx +43 -0
  49. package/src/containers/PageEditor/constants.tsx +1 -0
  50. package/src/containers/PageEditor/interfaces.tsx +26 -19
  51. package/src/containers/PageEditor/reducer.tsx +32 -19
  52. package/src/containers/Users/reducer.tsx +1 -0
  53. package/src/helpers/forms.tsx +3 -1
  54. package/src/hooks/index.tsx +2 -1
  55. package/src/hooks/users.tsx +154 -14
  56. package/src/modules/ActivityLog/LogFilters/UserFilter/index.tsx +11 -12
  57. package/src/modules/Content/PageItem/index.tsx +17 -10
  58. package/src/modules/Content/index.tsx +1 -3
  59. package/src/modules/GlobalEditor/Editor/index.tsx +6 -0
  60. package/src/modules/GlobalEditor/PageBrowser/index.tsx +6 -0
  61. package/src/modules/GlobalEditor/Preview/index.tsx +4 -2
  62. package/src/modules/GlobalEditor/index.tsx +72 -45
  63. package/src/modules/Navigation/Menus/List/Table/ConfigPanel/Form/index.tsx +3 -5
  64. package/src/modules/Navigation/Menus/List/Table/ConfigPanel/index.tsx +4 -4
  65. package/src/modules/Navigation/Menus/List/Table/SidePanel/Form/ConnectedField/index.tsx +2 -3
  66. package/src/modules/Navigation/Menus/List/Table/SidePanel/Form/index.tsx +3 -4
  67. package/src/modules/Navigation/Menus/List/Table/SidePanel/index.tsx +5 -6
  68. package/src/modules/PageEditor/Editor/index.tsx +6 -0
  69. package/src/modules/PageEditor/PageBrowser/index.tsx +6 -0
  70. package/src/modules/PageEditor/Preview/index.tsx +9 -2
  71. package/src/modules/PageEditor/atoms.tsx +110 -3
  72. package/src/modules/PageEditor/index.tsx +76 -95
  73. package/src/modules/Settings/Integrations/IntegrationForm/index.tsx +1 -3
  74. package/src/modules/Sites/SitesList/GridView/GridSiteItem/style.tsx +19 -2
  75. package/src/modules/Sites/SitesList/WelcomeModal/FinalStep/index.tsx +2 -2
  76. package/src/modules/Sites/SitesList/WelcomeModal/utils.ts +2 -2
  77. package/src/modules/StructuredData/Form/ConnectedField/index.tsx +2 -2
  78. package/src/modules/StructuredData/Form/index.tsx +120 -75
  79. package/src/modules/StructuredData/Form/style.tsx +7 -1
  80. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +15 -8
  81. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +15 -9
  82. package/src/modules/StructuredData/{StructuredDataList/StructuredDataItem/utils.tsx → utils.tsx} +16 -2
  83. package/src/modules/Users/Profile/atoms.tsx +62 -4
  84. package/src/modules/Users/Profile/index.tsx +83 -4
  85. package/src/modules/Users/Profile/style.tsx +60 -0
  86. package/src/modules/Users/UserCreate/SiteItem/RolesModal/RoleItem/index.tsx +3 -5
  87. package/src/modules/Users/UserCreate/SiteItem/index.tsx +2 -2
  88. package/src/modules/Users/UserCreate/atoms.tsx +69 -0
  89. package/src/modules/Users/UserCreate/index.tsx +149 -70
  90. package/src/modules/Users/UserCreate/style.tsx +24 -1
  91. package/src/modules/Users/UserForm/index.tsx +260 -146
  92. package/src/modules/Users/UserForm/style.tsx +13 -1
  93. package/src/modules/Users/UserList/UserItem/index.tsx +3 -3
  94. package/src/types/index.tsx +19 -6
  95. package/src/types/logs.tsx +1 -1
@@ -0,0 +1,54 @@
1
+ import { CheckField, Tag } from "@ax/components";
2
+ import type { ICheck, IRole } from "@ax/types";
3
+
4
+ import * as S from "./style";
5
+
6
+ const RoleItem = (props: IProps): JSX.Element => {
7
+ const { role, isSelected = false, onToggle, selection = true } = props;
8
+
9
+ const handleToggle = () => {
10
+ onToggle?.(role.id, { value: role.id, isChecked: !isSelected });
11
+ };
12
+
13
+ const handleCheckboxClick = (e: React.MouseEvent) => {
14
+ e.stopPropagation();
15
+ };
16
+
17
+ const handleKeyDown = (e: React.KeyboardEvent) => {
18
+ if (e.key === "Enter") {
19
+ e.preventDefault();
20
+ handleToggle();
21
+ }
22
+ };
23
+
24
+ return (
25
+ <S.RoleItem selected={isSelected} onClick={handleToggle} onKeyDown={handleKeyDown} role="button" tabIndex={0}>
26
+ {selection && (
27
+ <S.CheckCell role="cell" onClick={handleCheckboxClick}>
28
+ <CheckField
29
+ name={`role-${role.id}`}
30
+ value={role.id}
31
+ checked={isSelected}
32
+ onChange={(check) => onToggle?.(role.id, check)}
33
+ tabIndex={-1}
34
+ />
35
+ </S.CheckCell>
36
+ )}
37
+ <S.TitleCell role="cell">
38
+ <S.Name>
39
+ <Tag text={role.name} color={role.hex} />
40
+ </S.Name>
41
+ <S.Description>{role.description}</S.Description>
42
+ </S.TitleCell>
43
+ </S.RoleItem>
44
+ );
45
+ };
46
+
47
+ interface IProps {
48
+ role: IRole;
49
+ isSelected?: boolean;
50
+ onToggle?: (roleId: number, check: ICheck) => void;
51
+ selection?: boolean;
52
+ }
53
+
54
+ export default RoleItem;
@@ -0,0 +1,34 @@
1
+ import { Row } from "@ax/components/TableList/TableItem/style";
2
+
3
+ import styled from "styled-components";
4
+
5
+ const CheckCell = styled.div`
6
+ width: 32px;
7
+ justify-content: flex-start;
8
+ label {
9
+ margin-bottom: ${(p) => p.theme.spacing.s};
10
+ }
11
+ `;
12
+
13
+ const TitleCell = styled.div`
14
+ color: ${(p) => p.theme.color.textHighEmphasis};
15
+ width: 100%;
16
+ `;
17
+
18
+ const Name = styled.div`
19
+ ${(p) => p.theme.textStyle.uiS};
20
+ color: ${(p) => p.theme.color.textMediumEmphasis};
21
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
22
+ `;
23
+
24
+ const Description = styled.div`
25
+ ${(p) => p.theme.textStyle.fieldContent};
26
+ color: ${(p) => p.theme.color.textHighEmphasis};
27
+ `;
28
+
29
+ const RoleItem = styled(Row)`
30
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
31
+ padding: ${(p) => p.theme.spacing.s};
32
+ `;
33
+
34
+ export { RoleItem, CheckCell, TitleCell, Name, Description };
@@ -0,0 +1,107 @@
1
+ import { useState } from "react";
2
+ import { connect } from "react-redux";
3
+
4
+ import { Button, SearchField } from "@ax/components";
5
+ import type { ICheck, IRole, IRootState } from "@ax/types";
6
+
7
+ import RoleItem from "./RoleItem";
8
+
9
+ import * as S from "./style";
10
+
11
+ const RolesList = (props: IProps): JSX.Element => {
12
+ const { roles, onCancel, onApply, selectedRoleIds = [], isSaving = false, onResetAccess } = props;
13
+ const [search, setSearch] = useState("");
14
+ const [selectedRoles, setSelectedRoles] = useState<number[]>(selectedRoleIds);
15
+
16
+ const filteredRoles = roles.filter((role) => role.name.toLowerCase().includes(search.toLowerCase()));
17
+
18
+ const handleToggleRole = (roleId: number, check: ICheck) => {
19
+ if (check.isChecked) {
20
+ setSelectedRoles((prev) => [...prev, roleId]);
21
+ } else {
22
+ setSelectedRoles((prev) => prev.filter((id) => id !== roleId));
23
+ }
24
+ };
25
+
26
+ const handleSelectRoles = () => {
27
+ setSearch("");
28
+ };
29
+
30
+ const handleApply = () => {
31
+ onApply(selectedRoles);
32
+ };
33
+
34
+ const handleResetAccess = () => {
35
+ if (onResetAccess) {
36
+ onResetAccess();
37
+ }
38
+ };
39
+
40
+ return (
41
+ <S.Wrapper>
42
+ <S.Content>
43
+ <S.Text>
44
+ Select the roles you want <strong>to be able to edit this page.</strong>
45
+ </S.Text>
46
+ <S.SearchWrapper>
47
+ <SearchField
48
+ placeholder="Search"
49
+ value={search}
50
+ onChange={(value) => setSearch(value)}
51
+ searchOnEnter={false}
52
+ />
53
+ </S.SearchWrapper>
54
+ <div>
55
+ {filteredRoles.map((role) => (
56
+ <RoleItem
57
+ key={role.id}
58
+ role={role}
59
+ isSelected={selectedRoles.includes(role.id)}
60
+ onToggle={handleToggleRole}
61
+ />
62
+ ))}
63
+ </div>
64
+ </S.Content>
65
+ <S.Actions>
66
+ {!search ? (
67
+ <>
68
+ {selectedRoleIds.length > 0 ? (
69
+ <Button type="button" buttonStyle="line" onClick={handleResetAccess} disabled={isSaving}>
70
+ Reset Page Access
71
+ </Button>
72
+ ) : (
73
+ <Button type="button" buttonStyle="text" onClick={onCancel} disabled={isSaving}>
74
+ Cancel
75
+ </Button>
76
+ )}
77
+ <Button type="button" onClick={handleApply} disabled={isSaving}>
78
+ {isSaving ? "Saving" : "Apply"}
79
+ </Button>
80
+ </>
81
+ ) : (
82
+ <Button type="button" onClick={handleSelectRoles}>
83
+ Select Roles
84
+ </Button>
85
+ )}
86
+ </S.Actions>
87
+ </S.Wrapper>
88
+ );
89
+ };
90
+
91
+ interface IReduxProps {
92
+ roles: IRole[];
93
+ }
94
+
95
+ interface IProps extends IReduxProps {
96
+ onCancel: () => void;
97
+ onApply: (rolesIDs: number[]) => void;
98
+ selectedRoleIds?: number[];
99
+ isSaving?: boolean;
100
+ onResetAccess?: () => void;
101
+ }
102
+
103
+ const mapStateToProps = (state: IRootState): IReduxProps => ({
104
+ roles: state.users.roles,
105
+ });
106
+
107
+ export default connect(mapStateToProps)(RolesList);
@@ -0,0 +1,76 @@
1
+ import { Row } from "@ax/components/TableList/TableItem/style";
2
+
3
+ import styled from "styled-components";
4
+
5
+ const Wrapper = styled.div`
6
+ display: flex;
7
+ flex-direction: column;
8
+ width: 100%;
9
+ height: 100%;
10
+ `;
11
+
12
+ const CheckCell = styled.div`
13
+ width: 32px;
14
+ justify-content: flex-start;
15
+ label {
16
+ margin-bottom: ${(p) => p.theme.spacing.s};
17
+ }
18
+ `;
19
+
20
+ const TitleCell = styled.div`
21
+ color: ${(p) => p.theme.color.textHighEmphasis};
22
+ width: 100%;
23
+ `;
24
+
25
+ const Name = styled.div`
26
+ ${(p) => p.theme.textStyle.uiS};
27
+ color: ${(p) => p.theme.color.textMediumEmphasis};
28
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
29
+ `;
30
+
31
+ const Description = styled.div`
32
+ ${(p) => p.theme.textStyle.fieldContent};
33
+ color: ${(p) => p.theme.color.textHighEmphasis};
34
+ `;
35
+
36
+ const RoleItem = styled(Row)`
37
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
38
+ padding: ${(p) => p.theme.spacing.s};
39
+ `;
40
+
41
+ const Text = styled.div`
42
+ ${(p) => p.theme.textStyle.uiM};
43
+ margin-bottom: ${(p) => p.theme.spacing.s};
44
+ `;
45
+
46
+ const SearchWrapper = styled.div`
47
+ margin-bottom: ${(p) => p.theme.spacing.s};
48
+ `;
49
+
50
+ const Content = styled.div`
51
+ flex: 1;
52
+ overflow-y: auto;
53
+ min-height: 0;
54
+ padding: ${(p) => `${p.theme.spacing.m} ${p.theme.spacing.m} 0 ${p.theme.spacing.m}`};
55
+
56
+ ::-webkit-scrollbar {
57
+ -webkit-appearance: none;
58
+ width: 4px;
59
+ }
60
+
61
+ ::-webkit-scrollbar-thumb {
62
+ border-radius: 4px;
63
+ background-color: ${(p) => p.theme.colors.iconNonActive};
64
+ }
65
+ `;
66
+
67
+ const Actions = styled.div`
68
+ display: flex;
69
+ gap: ${(p) => p.theme.spacing.s};
70
+ justify-content: flex-end;
71
+ flex-shrink: 0;
72
+ padding: ${(p) => p.theme.spacing.m};
73
+ border-top: 1px solid ${(p) => p.theme.colors.uiLine};
74
+ `;
75
+
76
+ export { Wrapper, Content, CheckCell, Description, Name, RoleItem, Text, TitleCell, SearchWrapper, Actions };
@@ -0,0 +1,78 @@
1
+ import { Avatar, CheckField, TruncatedTooltip } from "@ax/components";
2
+ import type { ICheck, IUser } from "@ax/types";
3
+
4
+ import * as S from "./style";
5
+
6
+ const UserItem = (props: IProps): JSX.Element => {
7
+ const { user, isSelected = false, onToggle, allUsers, selection = true } = props;
8
+ const typedUser = user as IUser;
9
+
10
+ const checkFieldName = allUsers ? "all-users" : `user-${typedUser.id}`;
11
+ const checkFieldValue = allUsers ? "all-users" : typedUser.id!.toString();
12
+
13
+ const handleToggle = () => {
14
+ onToggle?.(user, { value: checkFieldValue, isChecked: !isSelected });
15
+ };
16
+
17
+ const handleCheckboxClick = (e: React.MouseEvent) => {
18
+ e.stopPropagation();
19
+ };
20
+
21
+ const handleKeyDown = (e: React.KeyboardEvent) => {
22
+ if (e.key === "Enter") {
23
+ e.preventDefault();
24
+ handleToggle();
25
+ }
26
+ };
27
+
28
+ return (
29
+ <S.UserItem selected={isSelected} onClick={handleToggle} onKeyDown={handleKeyDown} role="button" tabIndex={0}>
30
+ {selection && (
31
+ <button type="button" onClick={handleCheckboxClick} onKeyDown={handleKeyDown} tabIndex={-1}>
32
+ <CheckField
33
+ name={checkFieldName}
34
+ value={checkFieldValue}
35
+ checked={isSelected}
36
+ onChange={(check) => onToggle?.(user, check)}
37
+ tabIndex={-1}
38
+ />
39
+ </button>
40
+ )}
41
+ {!allUsers && (
42
+ <S.AvatarWrapper>
43
+ <Avatar image={typedUser.image?.thumb} name={typedUser.name} />
44
+ </S.AvatarWrapper>
45
+ )}
46
+ <S.UserInfo>
47
+ <S.UserName>
48
+ {allUsers ? (
49
+ <S.Name>All Users</S.Name>
50
+ ) : (
51
+ <TruncatedTooltip content={typedUser.name} expanded top={-5}>
52
+ <S.Name>{typedUser.name}</S.Name>
53
+ </TruncatedTooltip>
54
+ )}
55
+ </S.UserName>
56
+ {!allUsers && (
57
+ <S.UserEmail>
58
+ <TruncatedTooltip content={typedUser.email} expanded top={-5}>
59
+ <S.Email>{typedUser.email}</S.Email>
60
+ </TruncatedTooltip>
61
+ </S.UserEmail>
62
+ )}
63
+ </S.UserInfo>
64
+ </S.UserItem>
65
+ );
66
+ };
67
+
68
+ export type UserItemType = IUser | { id: "all-users" };
69
+
70
+ interface IProps {
71
+ user: UserItemType;
72
+ isSelected?: boolean;
73
+ onToggle?: (user: UserItemType, check: ICheck) => void;
74
+ allUsers?: boolean;
75
+ selection?: boolean;
76
+ }
77
+
78
+ export default UserItem;
@@ -0,0 +1,73 @@
1
+ import { Row } from "@ax/components/TableList/TableItem/style";
2
+
3
+ import styled from "styled-components";
4
+
5
+ const UserItem = styled(Row)`
6
+ display: flex;
7
+ align-items: center;
8
+ padding: ${(p) => `0 ${p.theme.spacing.s}`};
9
+ cursor: pointer;
10
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
11
+ height: 61px;
12
+ `;
13
+
14
+ const UserInfo = styled.div`
15
+ display: flex;
16
+ flex-flow: column nowrap;
17
+ flex-grow: 1;
18
+ min-width: 0;
19
+ padding-left: ${(p) => p.theme.spacing.xs};
20
+ `;
21
+
22
+ const AvatarWrapper = styled.div`
23
+ padding-left: ${(p) => p.theme.spacing.xs};
24
+ padding-right: ${(p) => p.theme.spacing.xs};
25
+ `;
26
+
27
+ const UserName = styled.div`
28
+ ${(p) => p.theme.textStyle.uiM};
29
+ font-weight: 600;
30
+ color: ${(p) => p.theme.colors.textHighEmphasis};
31
+ width: 100%;
32
+ min-width: 0;
33
+ > * {
34
+ min-width: 0;
35
+ max-width: 100%;
36
+ }
37
+ `;
38
+
39
+ const Name = styled.div`
40
+ width: 100%;
41
+ ${(p) => p.theme.textStyle.uiM};
42
+ font-weight: 600;
43
+ color: ${(p) => p.theme.colors.textHighEmphasis};
44
+ overflow: hidden;
45
+ text-overflow: ellipsis;
46
+ white-space: nowrap;
47
+ display: block;
48
+ min-width: 0;
49
+ `;
50
+
51
+ const UserEmail = styled.div`
52
+ ${(p) => p.theme.textStyle.uiXS};
53
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
54
+ width: 100%;
55
+ min-width: 0;
56
+ > * {
57
+ min-width: 0;
58
+ max-width: 100%;
59
+ }
60
+ `;
61
+
62
+ const Email = styled.div`
63
+ width: 100%;
64
+ ${(p) => p.theme.textStyle.uiXS};
65
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
66
+ overflow: hidden;
67
+ text-overflow: ellipsis;
68
+ white-space: nowrap;
69
+ display: block;
70
+ min-width: 0;
71
+ `;
72
+
73
+ export { UserInfo, UserItem, AvatarWrapper, UserName, UserEmail, Name, Email };
@@ -0,0 +1,151 @@
1
+ import { useEffect, useState } from "react";
2
+ import { useSelector } from "react-redux";
3
+
4
+ import { Button, Loading, SearchField } from "@ax/components";
5
+ import type { ICheck, IRootState, IUser } from "@ax/types";
6
+
7
+ import { fetchUsers } from "../utils";
8
+ import UserItem, { type UserItemType } from "./UserItem";
9
+
10
+ import * as S from "./style";
11
+
12
+ const ALL_USERS_ITEM: UserItemType = { id: "all-users" };
13
+
14
+ const UsersList = (props: IUsersListProps): JSX.Element => {
15
+ const { onCancel, onApply, selectedUserIds = [], isSaving = false, onResetAccess, users } = props;
16
+ const currentUser = useSelector((state: IRootState) => state.users.currentUser);
17
+ const currentSiteInfo = useSelector((state: IRootState) => state.sites.currentSiteInfo);
18
+ const [search, setSearch] = useState("");
19
+ const [selectedUsers, setSelectedUsers] = useState<IUser[]>(users);
20
+ const [options, setOptions] = useState<IUser[]>([]);
21
+ const [loading, setLoading] = useState(false);
22
+
23
+ const handleApply = () => {
24
+ let userIDs = selectedUsers.map((user) => user.id!);
25
+ // Ensure current user is always included unless "all users" is selected (when selectedUsers is empty)
26
+ const isAllUsersSelected = selectedUsers.length === 0;
27
+ if (currentUser && !isAllUsersSelected && !userIDs.includes(currentUser.id!)) {
28
+ userIDs = [...userIDs, currentUser.id!];
29
+ }
30
+ onApply(userIDs);
31
+ };
32
+
33
+ const handleResetAccess = () => onResetAccess();
34
+
35
+ useEffect(() => {
36
+ const loadUsers = async () => {
37
+ setLoading(true);
38
+ const data = await fetchUsers(search, currentSiteInfo);
39
+ if (data) {
40
+ setOptions(data);
41
+ }
42
+ setLoading(false);
43
+ };
44
+
45
+ loadUsers();
46
+ }, [search, currentSiteInfo]);
47
+
48
+ const handleToggleUser = (user: UserItemType, check: ICheck) => {
49
+ if (user.id === "all-users") {
50
+ if (check.isChecked) {
51
+ setSelectedUsers([]);
52
+ }
53
+ } else if (check.isChecked) {
54
+ setSelectedUsers((prev) => [...prev, user as IUser]);
55
+ } else {
56
+ setSelectedUsers((prev) => prev.filter((u) => u.id !== user.id));
57
+ }
58
+ };
59
+
60
+ const handleSelectUsers = () => {
61
+ setSearch("");
62
+ };
63
+
64
+ return (
65
+ <S.Wrapper>
66
+ <S.Content>
67
+ <S.Text>
68
+ Select the users you want <strong>to be able to edit this page.</strong>
69
+ </S.Text>
70
+ <S.SearchWrapper>
71
+ <SearchField
72
+ placeholder="Search"
73
+ value={search}
74
+ onChange={(value) => setSearch(value)}
75
+ searchOnEnter={false}
76
+ />
77
+ </S.SearchWrapper>
78
+ {loading ? (
79
+ <Loading />
80
+ ) : (
81
+ <>
82
+ {!search && (
83
+ <>
84
+ <S.Title>ACCESS TO</S.Title>
85
+ <S.UsersList>
86
+ {selectedUsers.length === 0 ? (
87
+ <UserItem user={ALL_USERS_ITEM} isSelected={true} onToggle={handleToggleUser} allUsers />
88
+ ) : (
89
+ selectedUsers.map((user) => (
90
+ <UserItem key={user.id} user={user} isSelected={true} onToggle={handleToggleUser} />
91
+ ))
92
+ )}
93
+ </S.UsersList>
94
+ </>
95
+ )}
96
+ </>
97
+ )}
98
+ <S.Title>{!search ? "AVAILABLE USERS" : "USERS"}</S.Title>
99
+ <S.UsersList>
100
+ {selectedUsers.length > 0 && !search && (
101
+ <UserItem user={ALL_USERS_ITEM} isSelected={false} onToggle={handleToggleUser} allUsers />
102
+ )}
103
+ {(search ? options : options.filter((user) => !selectedUsers.some((u) => u.id === user.id))).map((user) => {
104
+ const isSelected = selectedUsers.some((u) => u.id === user.id);
105
+ return (
106
+ <UserItem
107
+ key={user.id}
108
+ user={user}
109
+ isSelected={search ? isSelected : false}
110
+ onToggle={handleToggleUser}
111
+ />
112
+ );
113
+ })}
114
+ </S.UsersList>
115
+ </S.Content>
116
+ <S.Actions>
117
+ {!search ? (
118
+ <>
119
+ {selectedUserIds.length > 0 ? (
120
+ <Button type="button" buttonStyle="line" onClick={handleResetAccess} disabled={isSaving}>
121
+ Reset Page Access
122
+ </Button>
123
+ ) : (
124
+ <Button type="button" buttonStyle="text" onClick={onCancel} disabled={isSaving}>
125
+ Cancel
126
+ </Button>
127
+ )}
128
+ <Button type="button" onClick={handleApply} disabled={isSaving}>
129
+ {isSaving ? "Saving" : "Apply"}
130
+ </Button>
131
+ </>
132
+ ) : (
133
+ <Button type="button" onClick={handleSelectUsers}>
134
+ Select Users
135
+ </Button>
136
+ )}
137
+ </S.Actions>
138
+ </S.Wrapper>
139
+ );
140
+ };
141
+
142
+ interface IUsersListProps {
143
+ onCancel: () => void;
144
+ onApply: (userIDs: number[]) => void;
145
+ selectedUserIds?: number[];
146
+ isSaving?: boolean;
147
+ onResetAccess: () => void;
148
+ users: IUser[];
149
+ }
150
+
151
+ export default UsersList;
@@ -0,0 +1,55 @@
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
+ `;
9
+
10
+ const Text = styled.div`
11
+ ${(p) => p.theme.textStyle.uiM};
12
+ margin-bottom: ${(p) => p.theme.spacing.s};
13
+ `;
14
+
15
+ const Title = styled.div`
16
+ ${(p) => p.theme.textStyle.headingXS};
17
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
18
+ margin-bottom: ${(p) => p.theme.spacing.xxs};
19
+ `;
20
+
21
+ const UsersList = styled.div`
22
+ padding-bottom: ${(p) => p.theme.spacing.s};
23
+ `;
24
+
25
+ const SearchWrapper = styled.div`
26
+ margin-bottom: ${(p) => p.theme.spacing.s};
27
+ `;
28
+
29
+ const Content = styled.div`
30
+ flex: 1;
31
+ overflow-y: auto;
32
+ min-height: 0;
33
+ padding: ${(p) => `${p.theme.spacing.m} ${p.theme.spacing.m} 0 ${p.theme.spacing.m}`};
34
+
35
+ ::-webkit-scrollbar {
36
+ -webkit-appearance: none;
37
+ width: 4px;
38
+ }
39
+
40
+ ::-webkit-scrollbar-thumb {
41
+ border-radius: 4px;
42
+ background-color: ${(p) => p.theme.colors.iconNonActive};
43
+ }
44
+ `;
45
+
46
+ const Actions = styled.div`
47
+ display: flex;
48
+ gap: ${(p) => p.theme.spacing.s};
49
+ justify-content: flex-end;
50
+ flex-shrink: 0;
51
+ padding: ${(p) => p.theme.spacing.m};
52
+ border-top: 1px solid ${(p) => p.theme.colors.uiLine};
53
+ `;
54
+
55
+ export { Wrapper, Content, Text, Title, UsersList, SearchWrapper, Actions };