@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,181 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { useSelector } from "react-redux";
3
+
4
+ import { Loading, Modal } from "@ax/components";
5
+ import type { IModal, IRootState, IUser, PageAccessGrantsSummary } from "@ax/types";
6
+
7
+ import CurrentAccessList from "./CurrentAccessList";
8
+ import OptionsStep from "./OptionsStep";
9
+ import RolesList from "./RolesList";
10
+ import UsersList from "./UsersList";
11
+ import { fetchUsers } from "./utils";
12
+
13
+ import * as S from "./style";
14
+
15
+ type Step = "current" | "options" | "list";
16
+ const STEPS: Step[] = ["current", "options", "list"];
17
+
18
+ const RestrictionsModal = (props: IRestrictionsModalProps): JSX.Element => {
19
+ const { isOpen, toggleModal, currentAccessGrants, onUpdateAccessGrants, isSaving } = props;
20
+ const currentSiteInfo = useSelector((state: IRootState) => state.sites.currentSiteInfo);
21
+ const [currentStep, setCurrentStep] = useState<Step>("options");
22
+ const [restrictionType, setRestrictionType] = useState<"users" | "roles">("users");
23
+ const [selectedUsers, setSelectedUsers] = useState<IUser[]>([]);
24
+ const [isLoadingUsers, setIsLoadingUsers] = useState(false);
25
+ const hasInitializedRef = useRef(false);
26
+
27
+ useEffect(() => {
28
+ if (currentAccessGrants?.users && currentAccessGrants?.users.length > 0 && isOpen) {
29
+ const initializeSelected = async () => {
30
+ setIsLoadingUsers(true);
31
+ const data = await fetchUsers("", currentSiteInfo);
32
+ if (data) {
33
+ const selected = data.filter((user: IUser) => currentAccessGrants.users.includes(user.id!));
34
+ setSelectedUsers(selected);
35
+ }
36
+ setIsLoadingUsers(false);
37
+ };
38
+ initializeSelected();
39
+ }
40
+ }, [currentAccessGrants?.users, currentSiteInfo, isOpen]);
41
+
42
+ useEffect(() => {
43
+ if (isOpen && !hasInitializedRef.current) {
44
+ hasInitializedRef.current = true;
45
+
46
+ // Determine initial step and restriction type
47
+ if (currentAccessGrants?.users?.length) {
48
+ setRestrictionType("users");
49
+ setCurrentStep("current");
50
+ } else if (currentAccessGrants?.roles?.length) {
51
+ setRestrictionType("roles");
52
+ setCurrentStep("current");
53
+ } else {
54
+ setRestrictionType("users");
55
+ setCurrentStep("options");
56
+ }
57
+ } else if (!isOpen) {
58
+ hasInitializedRef.current = false;
59
+ }
60
+ }, [isOpen, currentAccessGrants]);
61
+
62
+ const stepIndex = STEPS.indexOf(currentStep);
63
+
64
+ const handleNext = () => {
65
+ setCurrentStep(STEPS[stepIndex + 1]);
66
+ };
67
+
68
+ const handleEdit = () => {
69
+ setCurrentStep("list");
70
+ };
71
+
72
+ const handleResetAccess = async () => {
73
+ setCurrentStep("options");
74
+ const newAccessGrants = {
75
+ users: [],
76
+ roles: [],
77
+ };
78
+ setSelectedUsers([]);
79
+ await onUpdateAccessGrants(newAccessGrants);
80
+ toggleModal();
81
+ };
82
+
83
+ const handleClose = () => {
84
+ setCurrentStep("options");
85
+ setRestrictionType("users");
86
+ setSelectedUsers([]);
87
+ toggleModal();
88
+ };
89
+
90
+ const handleApply = async (ids: number[]) => {
91
+ const newAccessGrants = {
92
+ users: restrictionType === "users" ? ids : [],
93
+ roles: restrictionType === "roles" ? ids : [],
94
+ };
95
+ setSelectedUsers([]);
96
+ await onUpdateAccessGrants(newAccessGrants);
97
+ };
98
+
99
+ const handleRestrictionTypeChange = (value: string) => {
100
+ if (value === "users" || value === "roles") {
101
+ setRestrictionType(value);
102
+ }
103
+ };
104
+
105
+ const getTitle = (): string => {
106
+ switch (currentStep) {
107
+ case "current":
108
+ case "list":
109
+ return "Page restrictions";
110
+ default:
111
+ return "Page restrictions";
112
+ }
113
+ };
114
+
115
+ return (
116
+ <Modal
117
+ isOpen={isOpen}
118
+ hide={handleClose}
119
+ title={getTitle()}
120
+ size="S"
121
+ height={currentStep === "options" || currentStep === "current" ? "auto" : 640}
122
+ >
123
+ <S.ModalContent>
124
+ {currentStep === "current" &&
125
+ (isLoadingUsers ? (
126
+ <S.LoadingContainer>
127
+ <Loading />
128
+ </S.LoadingContainer>
129
+ ) : (
130
+ <CurrentAccessList
131
+ restrictionType={restrictionType}
132
+ currentAccessGrants={currentAccessGrants}
133
+ onEdit={handleEdit}
134
+ users={selectedUsers}
135
+ />
136
+ ))}
137
+
138
+ {currentStep === "options" && (
139
+ <OptionsStep
140
+ restrictionType={restrictionType}
141
+ onRestrictionTypeChange={handleRestrictionTypeChange}
142
+ onCancel={handleClose}
143
+ onNext={handleNext}
144
+ />
145
+ )}
146
+
147
+ {currentStep === "list" && (
148
+ <>
149
+ {restrictionType === "users" && (
150
+ <UsersList
151
+ onCancel={handleClose}
152
+ onApply={handleApply}
153
+ selectedUserIds={currentAccessGrants?.users || []}
154
+ isSaving={isSaving}
155
+ onResetAccess={handleResetAccess}
156
+ users={selectedUsers}
157
+ />
158
+ )}
159
+ {restrictionType === "roles" && (
160
+ <RolesList
161
+ onCancel={handleClose}
162
+ onApply={handleApply}
163
+ selectedRoleIds={currentAccessGrants?.roles || []}
164
+ isSaving={isSaving}
165
+ onResetAccess={handleResetAccess}
166
+ />
167
+ )}
168
+ </>
169
+ )}
170
+ </S.ModalContent>
171
+ </Modal>
172
+ );
173
+ };
174
+
175
+ interface IRestrictionsModalProps extends IModal {
176
+ currentAccessGrants?: PageAccessGrantsSummary;
177
+ onUpdateAccessGrants: (accessGrants: PageAccessGrantsSummary) => Promise<void>;
178
+ isSaving: boolean;
179
+ }
180
+
181
+ export default RestrictionsModal;
@@ -0,0 +1,31 @@
1
+ import styled from "styled-components";
2
+
3
+ const ModalContent = styled.div`
4
+ display: flex;
5
+ flex-direction: column;
6
+ height: 100%;
7
+ `;
8
+
9
+ const Text = styled.div`
10
+ ${(p) => p.theme.textStyle.uiM};
11
+ margin-bottom: ${(p) => p.theme.spacing.m};
12
+ `;
13
+
14
+ const OptionsGroup = styled.div`
15
+ display: flex;
16
+ flex-direction: column;
17
+ gap: ${(p) => p.theme.spacing.xs};
18
+ margin: ${(p) => `${p.theme.spacing.m} 0`};
19
+ `;
20
+
21
+ const ListContainer = styled.div`
22
+ margin-top: ${(p) => p.theme.spacing.m};
23
+ `;
24
+
25
+ const OptionsList = styled.ul``;
26
+
27
+ const LoadingContainer = styled.div`
28
+ padding: ${(p) => p.theme.spacing.m};
29
+ `;
30
+
31
+ export { ListContainer, LoadingContainer, ModalContent, OptionsList, OptionsGroup, Text };
@@ -0,0 +1,24 @@
1
+ import { users } from "@ax/api";
2
+ import { isReqOk } from "@ax/helpers";
3
+ import type { IUser } from "@ax/types";
4
+
5
+ const fetchUsers = async (query = "", currentSiteInfo?: { id: number } | null) => {
6
+ try {
7
+ const searchQuery = query.length ? `&query=${query}` : "";
8
+ const params = { filterQuery: "?order=name-asc", query: searchQuery };
9
+ const items = currentSiteInfo ? await users.getSiteUsers(params, currentSiteInfo.id) : await users.getUsers(params);
10
+ if (items && isReqOk(items.status)) {
11
+ let data = items.data;
12
+ // Filter to global users only when no specific site is selected
13
+ if (!currentSiteInfo) {
14
+ data = data.filter((user: IUser) => user.roles.some((role) => role.siteId === "global"));
15
+ }
16
+ return data;
17
+ }
18
+ } catch (e) {
19
+ console.log(e);
20
+ }
21
+ return null;
22
+ };
23
+
24
+ export { fetchUsers };
@@ -7,7 +7,7 @@ import type { IModal } from "@ax/types";
7
7
  import * as S from "./style";
8
8
 
9
9
  const ScheduleModal = (props: IScheduleModal): JSX.Element => {
10
- const { isOpen, toggleModal, mainModalAction, secondaryModalAction, scheduleDate, setScheduleDate } = props;
10
+ const { isOpen, toggleModal, scheduleDate, setScheduleDate, onSchedule } = props;
11
11
 
12
12
  const [error, setError] = useState(false);
13
13
 
@@ -33,15 +33,24 @@ const ScheduleModal = (props: IScheduleModal): JSX.Element => {
33
33
  }
34
34
  };
35
35
 
36
- const modalAction: any = { ...mainModalAction, disabled: error };
36
+ const mainAction = {
37
+ title: "Schedule",
38
+ onClick: onSchedule,
39
+ disabled: error,
40
+ };
41
+
42
+ const secondaryAction = {
43
+ title: "Cancel",
44
+ onClick: () => toggleModal(),
45
+ };
37
46
 
38
47
  return (
39
48
  <Modal
40
49
  isOpen={isOpen}
41
50
  hide={toggleModal}
42
51
  title="Schedule publication"
43
- secondaryAction={secondaryModalAction}
44
- mainAction={modalAction}
52
+ secondaryAction={secondaryAction}
53
+ mainAction={mainAction}
45
54
  size="M"
46
55
  height={330}
47
56
  overflow="visible"
@@ -77,16 +86,26 @@ const ScheduleModal = (props: IScheduleModal): JSX.Element => {
77
86
  );
78
87
  };
79
88
 
80
- const CancelScheduleModal = (props: IModal): JSX.Element => {
81
- const { isOpen, toggleModal, mainModalAction, secondaryModalAction } = props;
89
+ const CancelScheduleModal = (props: ICancelScheduleModal): JSX.Element => {
90
+ const { isOpen, toggleModal, onCancelSchedule } = props;
91
+
92
+ const mainAction = {
93
+ title: "Cancel Schedule",
94
+ onClick: onCancelSchedule,
95
+ };
96
+
97
+ const secondaryAction = {
98
+ title: "Back",
99
+ onClick: () => toggleModal(),
100
+ };
82
101
 
83
102
  return (
84
103
  <Modal
85
104
  isOpen={isOpen}
86
105
  hide={toggleModal}
87
106
  title="Cancel schedule publication"
88
- secondaryAction={secondaryModalAction}
89
- mainAction={mainModalAction}
107
+ secondaryAction={secondaryAction}
108
+ mainAction={mainAction}
90
109
  size="S"
91
110
  height={240}
92
111
  >
@@ -103,6 +122,11 @@ const CancelScheduleModal = (props: IModal): JSX.Element => {
103
122
  interface IScheduleModal extends IModal {
104
123
  scheduleDate: { date: string; time: string };
105
124
  setScheduleDate: (scheduleDate: { date: string; time: string }) => void;
125
+ onSchedule: () => void;
126
+ }
127
+
128
+ interface ICancelScheduleModal extends IModal {
129
+ onCancelSchedule: () => void;
106
130
  }
107
131
 
108
132
  export { ScheduleModal, CancelScheduleModal };
@@ -0,0 +1,65 @@
1
+ import { Button, CheckField, RadioField, TruncatedTooltip } from "@ax/components";
2
+
3
+ import * as S from "./style";
4
+
5
+ const SelectionComponent = (props: IProps): JSX.Element => {
6
+ const { name, value, label, description, isSelected, multi, btnText, btnAction, onToggle } = props;
7
+
8
+ const handleToggle = () => {
9
+ onToggle(value);
10
+ };
11
+
12
+ const handleFieldClick = (e: React.MouseEvent) => {
13
+ e.stopPropagation();
14
+ };
15
+
16
+ const handleKeyDown = (e: React.KeyboardEvent) => {
17
+ if (e.key === "Enter") {
18
+ e.preventDefault();
19
+ handleToggle();
20
+ }
21
+ };
22
+
23
+ return (
24
+ <S.Wrapper isSelected={isSelected} onClick={handleToggle} onKeyDown={handleKeyDown} role="button" tabIndex={0}>
25
+ {multi ? (
26
+ <button type="button" onClick={handleFieldClick} onKeyDown={handleKeyDown} tabIndex={-1}>
27
+ <CheckField name={name} value={value} checked={isSelected} onChange={() => onToggle(value)} tabIndex={-1} />
28
+ </button>
29
+ ) : (
30
+ <button type="button" onClick={handleFieldClick} onKeyDown={handleKeyDown} tabIndex={-1}>
31
+ <RadioField name={name} value={value} checked={isSelected} onChange={() => onToggle(value)} tabIndex={-1} />
32
+ </button>
33
+ )}
34
+ <S.Info>
35
+ <S.LabelWrapper>
36
+ <TruncatedTooltip content={label} expanded top={-5}>
37
+ <S.Label>{label}</S.Label>
38
+ </TruncatedTooltip>
39
+ </S.LabelWrapper>
40
+ {description && <S.Description>{description}</S.Description>}
41
+ {btnText && (
42
+ <S.ButtonWrapper>
43
+ <Button type="button" buttonStyle="text" onClick={btnAction}>
44
+ {btnText}
45
+ </Button>
46
+ </S.ButtonWrapper>
47
+ )}
48
+ </S.Info>
49
+ </S.Wrapper>
50
+ );
51
+ };
52
+
53
+ interface IProps {
54
+ name: string;
55
+ value: string;
56
+ label: string;
57
+ description?: string;
58
+ isSelected: boolean;
59
+ multi: boolean;
60
+ onToggle: (value: string) => void;
61
+ btnText?: string;
62
+ btnAction?: () => void;
63
+ }
64
+
65
+ export default SelectionComponent;
@@ -0,0 +1,69 @@
1
+ import styled from "styled-components";
2
+
3
+ const Wrapper = styled.div<{ isSelected: boolean }>`
4
+ position: relative;
5
+ display: flex;
6
+ border: ${(p) => (p.isSelected ? `1px solid ${p.theme.color.interactive01}` : `1px solid ${p.theme.color.uiLine}`)};
7
+ border-radius: ${(p) => p.theme.radii.s};
8
+ padding: ${(p) => p.theme.spacing.s};
9
+ background-color: ${(p) => p.theme.color.uiBackground02};
10
+ cursor: pointer;
11
+ :before {
12
+ content: "";
13
+ border-radius: ${(p) => p.theme.radii.s};
14
+ position: absolute;
15
+ top: 0;
16
+ left: 0;
17
+ width: 100%;
18
+ height: 100%;
19
+ opacity: 0;
20
+ transition: opacity 0.1s;
21
+ }
22
+ :hover:before {
23
+ background-color: ${(p) => p.theme.color.overlayHoverPrimary};
24
+ opacity: 1;
25
+ }
26
+ `;
27
+
28
+ const Info = styled.div`
29
+ display: flex;
30
+ flex-flow: column nowrap;
31
+ flex-grow: 1;
32
+ min-width: 0;
33
+ padding-left: ${(p) => p.theme.spacing.xs};
34
+ `;
35
+
36
+ const LabelWrapper = styled.div`
37
+ width: 100%;
38
+ min-width: 0;
39
+ > * {
40
+ min-width: 0;
41
+ max-width: 100%;
42
+ }
43
+ `;
44
+
45
+ const Label = styled.div`
46
+ width: 100%;
47
+ ${(p) => p.theme.textStyle.uiL};
48
+ font-weight: 600;
49
+ color: ${(p) => p.theme.colors.textHighEmphasis};
50
+ overflow: hidden;
51
+ text-overflow: ellipsis;
52
+ white-space: nowrap;
53
+ display: block;
54
+ min-width: 0;
55
+ `;
56
+
57
+ const Description = styled.div`
58
+ ${(p) => p.theme.textStyle.uiXS};
59
+ color: ${(p) => p.theme.colors.textMediumEmphasis};
60
+ margin-top: ${(p) => p.theme.spacing.xxs};
61
+ width: 100%;
62
+ min-width: 0;
63
+ `;
64
+
65
+ const ButtonWrapper = styled.div`
66
+ margin-top: ${(p) => p.theme.spacing.xs};
67
+ `;
68
+
69
+ export { Wrapper, Info, LabelWrapper, Description, Label, ButtonWrapper };
@@ -1,8 +1,8 @@
1
- import React, { useLayoutEffect, useState } from "react";
1
+ import { useLayoutEffect, useState } from "react";
2
2
 
3
3
  import { CheckGroup, FloatingMenu, Icon, ListTitle } from "@ax/components";
4
4
  import { areEquals } from "@ax/helpers";
5
- import { IFilterValue, IQueryValue } from "@ax/types";
5
+ import type { IFilterValue, IQueryValue } from "@ax/types";
6
6
 
7
7
  import * as S from "./style";
8
8
 
@@ -31,7 +31,7 @@ const RoleFilter = (props: IRoleFilterProps): JSX.Element => {
31
31
  }
32
32
  setSelectedValue(selection);
33
33
  const queryFilters: IQueryValue[] = selection.map((value: string | number) => {
34
- const label = filters && filters.find((opt: IFilterValue) => opt.value === value);
34
+ const label = filters?.find((opt: IFilterValue) => opt.value === value);
35
35
  return { value, label: label?.title || "" };
36
36
  });
37
37
  filterItems(pointer, queryFilters);
@@ -23,17 +23,13 @@ export const Row = styled.div<{ selected: boolean; global?: boolean }>`
23
23
 
24
24
  display: flex;
25
25
  flex-flow: row;
26
- border: 1px solid ${(p) => p.theme.color.uiLine};
26
+ border: 1px solid ${(p) => (p.selected ? `${p.theme.color.interactive01}` : p.theme.color.uiLine)};
27
27
  border-radius: ${(p) => p.theme.radii.s};
28
28
  margin-bottom: ${(p) => p.theme.spacing.xs};
29
29
  cursor: pointer;
30
- :before {
30
+ &:before {
31
31
  content: "";
32
- background-color: ${(p) => (p.selected ? p.theme.color.overlayPressedPrimary : `transparent`)};
33
- /* This border-radius does not comply with the design system, but it prevents
34
- * a "gap" from appearing between the row and the selected row due to the
35
- * border-radius not matching (because of the border-width) */
36
- border-radius: 3px;
32
+ border-radius: 4px;
37
33
  position: absolute;
38
34
  top: 0;
39
35
  left: 0;
@@ -43,10 +39,8 @@ export const Row = styled.div<{ selected: boolean; global?: boolean }>`
43
39
  transition: opacity 0.1s;
44
40
  pointer-events: none;
45
41
  }
46
- :hover {
47
- &:before {
48
- background-color: ${(p) =>
49
- p.selected ? p.theme.color.overlayPressedPrimary : p.theme.color.overlayHoverPrimary};
50
- opacity: 1;
51
- }
42
+ &:hover:before {
43
+ background-color: ${(p) => p.theme.color.overlayHoverPrimary};
44
+ opacity: 1;
45
+ }
52
46
  `;
@@ -6,9 +6,10 @@ import * as S from "./style";
6
6
  const Tabs = (props: ITabsProps): JSX.Element => {
7
7
  const { tabs, icons, active, setSelectedTab, isInAppBar, noMargins, inversed, disabled } = props;
8
8
 
9
+ // biome-ignore lint/correctness/useExhaustiveDependencies: setSelectedTab is stable and should not trigger re-renders
9
10
  useEffect(() => {
10
11
  tabs && tabs.length === 1 && setSelectedTab(tabs[0]);
11
- }, [tabs, setSelectedTab]);
12
+ }, [tabs]);
12
13
 
13
14
  if (tabs) {
14
15
  return (
@@ -1,5 +1,5 @@
1
1
  import { ElementsTooltip } from "@ax/components";
2
- import type { IRole, ISite, ISiteRoles } from "@ax/types";
2
+ import type { IRole, ISite, UserRoleAssignment } from "@ax/types";
3
3
 
4
4
  import RoleItem from "./RoleItem";
5
5
 
@@ -93,7 +93,7 @@ const UserRolesAndSites = (props: IUserRolesAndSitesProps): JSX.Element => {
93
93
 
94
94
  interface IUserRolesAndSitesProps {
95
95
  isSuperAdmin: boolean;
96
- userRoles: ISiteRoles[];
96
+ userRoles: UserRoleAssignment[];
97
97
  sites: ISite[];
98
98
  roles: IRole[];
99
99
  showRows?: boolean;
@@ -8,6 +8,7 @@ import BulkSelectionOptions from "./BulkSelectionOptions";
8
8
  import Button from "./Button";
9
9
  import CategoryCell from "./CategoryCell";
10
10
  import Circle from "./Circle";
11
+ import ContentItem from "./ContentItem";
11
12
  import ConfigPanel from "./ConfigPanel";
12
13
  import DragAndDrop from "./DragAndDrop";
13
14
  import ElementsTooltip from "./ElementsTooltip";
@@ -95,9 +96,11 @@ import ProgressBar from "./ProgressBar";
95
96
  import ReorderArrows from "./ReorderArrows";
96
97
  import ResizePanel from "./ResizePanel";
97
98
  import RestoreModal from "./RestoreModal";
99
+ import RestrictionsModal from "./RestrictionsModal";
98
100
  import { CancelScheduleModal, ScheduleModal } from "./ScheduleModal";
99
101
  import SearchField from "./SearchField";
100
102
  import SearchTagsBar from "./SearchTagsBar";
103
+ import SelectionComponent from "./SelectionComponent";
101
104
  import SharePageModal from "./SharePageModal";
102
105
  import SideModal from "./SideModal";
103
106
  import StatusTile from "./StatusTile";
@@ -150,6 +153,7 @@ export {
150
153
  Circle,
151
154
  ComponentArray,
152
155
  ComponentContainer,
156
+ ContentItem,
153
157
  ConditionalField,
154
158
  ConfigPanel,
155
159
  CustomizeFilters,
@@ -220,12 +224,14 @@ export {
220
224
  ReorderArrows,
221
225
  ResizePanel,
222
226
  RestoreModal,
227
+ RestrictionsModal,
223
228
  RichText,
224
229
  RoleFilter,
225
230
  ScheduleModal,
226
231
  SearchField,
227
232
  SearchTagsBar,
228
233
  Select,
234
+ SelectionComponent,
229
235
  SEOPreview,
230
236
  SharePageModal,
231
237
  SideModal,
@@ -51,6 +51,7 @@ import type {
51
51
  ISchema,
52
52
  IStructuredDataContent,
53
53
  IUserEditing,
54
+ PageAccessGrantsSummary,
54
55
  } from "@ax/types";
55
56
 
56
57
  import type { Dispatch } from "redux";
@@ -69,6 +70,7 @@ import {
69
70
  SET_IS_NEW_TRANSLATION,
70
71
  SET_LAST_ELEMENT_ADDED_ID,
71
72
  SET_LAST_TIMEOUT,
73
+ SET_PAGE_ACCESS_GRANTS,
72
74
  SET_SCHEMA,
73
75
  SET_SCROLL_EDITOR_ID,
74
76
  SET_SELECTED_CONTENT,
@@ -96,6 +98,7 @@ import type {
96
98
  ISetIsNewTranslation,
97
99
  ISetLastElementAddedId,
98
100
  ISetLastTimeout,
101
+ ISetPageAccessGrants,
99
102
  ISetSchema,
100
103
  ISetScrollEditorID,
101
104
  ISetSelectedContent,
@@ -230,6 +233,10 @@ function setScrollEditorID(scrollEditorID: number | null): ISetScrollEditorID {
230
233
  return { type: SET_SCROLL_EDITOR_ID, payload: { scrollEditorID } };
231
234
  }
232
235
 
236
+ function setPageAccessGrants(accessGrants: PageAccessGrantsSummary): ISetPageAccessGrants {
237
+ return { type: SET_PAGE_ACCESS_GRANTS, payload: { accessGrants } };
238
+ }
239
+
233
240
  function setTranslatedParent(): (dispatch: Dispatch, getState: any) => void {
234
241
  return async (dispatch, getState) => {
235
242
  try {
@@ -384,6 +391,7 @@ function getPage(
384
391
  dispatch(setTemplateConfig(page.templateConfig));
385
392
  dispatch(setUserEditing(page.editing));
386
393
  dispatch(setCurrentPageStatus(pageLiveStatus));
394
+ dispatch(setPageAccessGrants(page.accessGrants));
387
395
  }
388
396
  } else {
389
397
  if (!isNewTranslation) {
@@ -1632,6 +1640,40 @@ function schedulePublication(
1632
1640
  };
1633
1641
  }
1634
1642
 
1643
+ function updatePageAccessGrants(data: {
1644
+ siteID: number | "global";
1645
+ pageID: number | null;
1646
+ accessGrants: PageAccessGrantsSummary;
1647
+ }): (dispatch: Dispatch, getState: () => IRootState) => Promise<boolean> {
1648
+ return async (dispatch, getState) => {
1649
+ try {
1650
+ if (!data.pageID) {
1651
+ dispatch(setPageAccessGrants(data.accessGrants));
1652
+ updateEditorContent(0, "accessGrants", data.accessGrants)(dispatch, getState);
1653
+ return true;
1654
+ }
1655
+
1656
+ const responseActions = {
1657
+ handleSuccess: () => {
1658
+ dispatch(setPageAccessGrants(data.accessGrants));
1659
+ },
1660
+ handleError: (response: any) => appActions.handleError(response)(dispatch),
1661
+ };
1662
+
1663
+ const callback = async () => {
1664
+ if (data.pageID) {
1665
+ return await pages.setPageAccessGrants({ ...data, pageID: data.pageID });
1666
+ }
1667
+ };
1668
+
1669
+ return await handleRequest(callback, responseActions, [appActions.setIsSaving])(dispatch);
1670
+ } catch (e) {
1671
+ console.log(e);
1672
+ return false;
1673
+ }
1674
+ };
1675
+ }
1676
+
1635
1677
  export {
1636
1678
  setEditorContent,
1637
1679
  setTemplate,
@@ -1687,4 +1729,5 @@ export {
1687
1729
  setScrollEditorID,
1688
1730
  addComponentInArray,
1689
1731
  replaceModuleInArray,
1732
+ updatePageAccessGrants,
1690
1733
  };
@@ -24,6 +24,7 @@ export const SET_IS_IA_TRANSLATED = `${NAME}/SET_IS_IA_TRANSLATED`;
24
24
  export const SET_LAST_TIMEOUT = `${NAME}/SET_LAST_TIMEOUT`;
25
25
  export const RESET_PAGE_EDITOR_STORE = `${NAME}/RESET_PAGE_EDITOR_STORE`;
26
26
  export const SET_SCROLL_EDITOR_ID = `${NAME}/SET_SCROLL_EDITOR_ID`;
27
+ export const SET_PAGE_ACCESS_GRANTS = `${NAME}/SET_PAGE_ACCESS_GRANTS`;
27
28
 
28
29
  export const INITIAL_TEMPLATE = "BasicTemplate";
29
30
  export const MULTIMEDIA_COMPONENTS = ["LinkableImage", "Video"];