@griddo/ax 10.1.96 → 10.2.0

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 (201) hide show
  1. package/package.json +3 -2
  2. package/src/__mocks__/axios/Roles.ts +10 -0
  3. package/src/__mocks__/axios/UserList.ts +545 -0
  4. package/src/__mocks__/store/GenericStore.ts +25 -0
  5. package/src/__mocks__/store/Roles.ts +1050 -0
  6. package/src/__mocks__/store/SitesList.ts +7 -1
  7. package/src/__mocks__/store/UserList.ts +482 -0
  8. package/src/__mocks__/store/UsersCreate.ts +298 -0
  9. package/src/__tests__/components/Avatar/Avatar.test.tsx +49 -48
  10. package/src/__tests__/components/ConfigPanel/ConfigPanel.test.tsx +2 -0
  11. package/src/__tests__/components/ConfigPanel/Form/Form.test.tsx +2 -0
  12. package/src/__tests__/components/ConfigPanel/GlobalPageForm/GlobalPageForm.test.tsx +25 -0
  13. package/src/__tests__/components/Fields/Button/Button.test.tsx +2 -2
  14. package/src/__tests__/components/Fields/ImageField/ImageField.test.tsx +2 -0
  15. package/src/__tests__/components/Fields/IntegrationsField/IntegrationsField.test.tsx +44 -2
  16. package/src/__tests__/components/Fields/Tooltip/Tooltip.test.tsx +0 -1
  17. package/src/__tests__/components/Gallery/Gallery.test.tsx +4 -0
  18. package/src/__tests__/components/Gallery/GalleryPanel/DetailPanel/DetailPanel.test.tsx +14 -0
  19. package/src/__tests__/components/Gallery/GalleryPanel/GalleryPanel.test.tsx +2 -1
  20. package/src/__tests__/components/Lists/Lists.test.tsx +3 -3
  21. package/src/__tests__/components/Login/Login.test.tsx +1 -1
  22. package/src/__tests__/components/TableFilters/DateFilter/DateFilter.test.tsx +1 -1
  23. package/src/__tests__/components/TableFilters/NameFilter/NameFilter.test.tsx +1 -1
  24. package/src/__tests__/components/TableFilters/RoleFilter/RoleFilter.test.tsx +165 -0
  25. package/src/__tests__/components/TableFilters/StatusFilter/StatusFilter.test.tsx +2 -2
  26. package/src/__tests__/components/TableFilters/UsersFilter/UsersFilter.test.tsx +153 -0
  27. package/src/__tests__/components/Tabs/Tabs.test.tsx +1 -1
  28. package/src/__tests__/modules/Settings/Integrations/Integrations.test.tsx +6 -0
  29. package/src/__tests__/modules/Sites/Sites.test.tsx +2 -1
  30. package/src/__tests__/modules/Sites/SitesList/ListView/BulkHeader/BulkHeader.test.tsx +14 -5
  31. package/src/__tests__/modules/Sites/SitesList/SitesList.test.tsx +6 -4
  32. package/src/__tests__/modules/Users/Roles/BulkHeader/BulkHeader.test.tsx +158 -0
  33. package/src/__tests__/modules/Users/Roles/Roles.test.tsx +619 -0
  34. package/src/__tests__/modules/Users/UserCreate/SiteItem/RolesModal/RoleItem/RoleItem.test.tsx +107 -0
  35. package/src/__tests__/modules/Users/UserCreate/SiteItem/RolesModal/RolesModal.test.tsx +159 -0
  36. package/src/__tests__/modules/Users/UserCreate/SiteItem/SiteItem.test.tsx +175 -0
  37. package/src/__tests__/modules/Users/UserCreate/UserCreate.test.tsx +320 -0
  38. package/src/__tests__/modules/Users/UserList/UserItem/UserItem.test.tsx +417 -0
  39. package/src/__tests__/modules/Users/UserList/UserList.test.tsx +310 -0
  40. package/src/api/index.tsx +2 -0
  41. package/src/api/roles.tsx +77 -0
  42. package/src/api/users.tsx +22 -2
  43. package/src/components/ActionMenu/index.tsx +12 -6
  44. package/src/components/Avatar/index.tsx +5 -3
  45. package/src/components/Avatar/style.tsx +8 -9
  46. package/src/components/BulkSelectionOptions/index.tsx +19 -12
  47. package/src/components/BulkSelectionOptions/style.tsx +6 -11
  48. package/src/components/ConfigPanel/Form/index.tsx +24 -1
  49. package/src/components/ConfigPanel/GlobalPageForm/index.tsx +17 -4
  50. package/src/components/ElementsTooltip/index.tsx +1 -1
  51. package/src/components/Fields/IntegrationsField/index.tsx +5 -6
  52. package/src/components/Fields/RadioField/index.tsx +1 -1
  53. package/src/components/Fields/ReferenceField/AutoPanel/index.tsx +3 -3
  54. package/src/components/Fields/ReferenceField/ItemList/index.tsx +1 -1
  55. package/src/components/Fields/ReferenceField/ManualPanel/index.tsx +3 -3
  56. package/src/components/Fields/TagsField/index.tsx +4 -2
  57. package/src/components/Fields/TextField/index.tsx +3 -0
  58. package/src/components/Fields/UrlField/index.tsx +5 -5
  59. package/src/components/Gallery/GalleryPanel/DetailPanel/index.tsx +42 -15
  60. package/src/components/Gallery/GalleryPanel/GalleryDragAndDrop/index.tsx +1 -1
  61. package/src/components/Gallery/GalleryPanel/index.tsx +20 -5
  62. package/src/components/Gallery/index.tsx +12 -6
  63. package/src/components/Icon/index.tsx +9 -1
  64. package/src/components/MainWrapper/AppBar/atoms.tsx +2 -2
  65. package/src/components/MainWrapper/AppBar/index.tsx +12 -10
  66. package/src/components/MainWrapper/AppBar/style.tsx +2 -1
  67. package/src/components/Modal/style.tsx +2 -2
  68. package/src/components/Nav/index.tsx +12 -2
  69. package/src/components/PageFinder/index.tsx +2 -2
  70. package/src/components/SearchField/index.tsx +3 -8
  71. package/src/components/TableFilters/PermissionsFilter/index.tsx +50 -0
  72. package/src/{modules/Users/UserList/HeaderMenus/Name → components/TableFilters/PermissionsFilter}/style.tsx +6 -2
  73. package/src/components/TableFilters/RoleFilter/index.tsx +61 -0
  74. package/src/components/TableFilters/RoleFilter/style.tsx +28 -0
  75. package/src/components/TableFilters/UsersFilter/index.tsx +55 -0
  76. package/src/components/TableFilters/UsersFilter/style.tsx +31 -0
  77. package/src/components/TableFilters/index.tsx +6 -0
  78. package/src/components/TableList/TableItem/style.tsx +2 -2
  79. package/src/components/TableList/style.tsx +1 -1
  80. package/src/components/index.tsx +7 -1
  81. package/src/containers/App/actions.tsx +3 -3
  82. package/src/containers/PageEditor/actions.tsx +14 -20
  83. package/src/containers/Redirects/actions.tsx +1 -0
  84. package/src/containers/Sites/actions.tsx +22 -14
  85. package/src/containers/Sites/interfaces.tsx +3 -3
  86. package/src/containers/Sites/reducer.tsx +1 -1
  87. package/src/containers/StructuredData/actions.tsx +15 -3
  88. package/src/containers/Users/actions.tsx +160 -26
  89. package/src/containers/Users/constants.tsx +8 -10
  90. package/src/containers/Users/interfaces.tsx +25 -3
  91. package/src/containers/Users/reducer.tsx +24 -15
  92. package/src/guards/index.tsx +2 -1
  93. package/src/guards/restricted/index.tsx +21 -0
  94. package/src/hooks/index.tsx +3 -0
  95. package/src/hooks/users.tsx +38 -0
  96. package/src/modules/App/Routing/NavMenu/NavItem/index.tsx +10 -5
  97. package/src/modules/App/Routing/NavMenu/index.tsx +16 -7
  98. package/src/modules/App/Routing/PrivateRoute/index.tsx +13 -5
  99. package/src/modules/App/Routing/index.tsx +17 -6
  100. package/src/modules/Categories/CategoriesList/BulkHeader/TableHeader/style.tsx +1 -0
  101. package/src/modules/Categories/CategoriesList/BulkHeader/index.tsx +2 -10
  102. package/src/modules/Categories/CategoriesList/CategoryItem/index.tsx +27 -12
  103. package/src/modules/Categories/CategoriesList/CategoryItem/style.tsx +4 -2
  104. package/src/modules/Categories/CategoriesList/index.tsx +27 -8
  105. package/src/modules/Content/BulkHeader/index.tsx +7 -3
  106. package/src/modules/Content/PageImporter/index.tsx +1 -1
  107. package/src/modules/Content/PageItem/index.tsx +45 -31
  108. package/src/modules/Content/PageItem/style.tsx +2 -1
  109. package/src/modules/Content/index.tsx +22 -13
  110. package/src/modules/FramePreview/index.tsx +2 -2
  111. package/src/modules/GlobalEditor/index.tsx +68 -53
  112. package/src/modules/GlobalSettings/index.tsx +2 -0
  113. package/src/modules/Navigation/Defaults/BulkHeader/index.tsx +2 -10
  114. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/DefaultsBrowser/index.tsx +9 -11
  115. package/src/modules/Navigation/Defaults/DefaultsEditor/Editor/index.tsx +5 -1
  116. package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +10 -5
  117. package/src/modules/Navigation/Defaults/Item/index.tsx +41 -27
  118. package/src/modules/Navigation/Defaults/Item/style.tsx +2 -1
  119. package/src/modules/Navigation/Defaults/index.tsx +29 -10
  120. package/src/modules/Navigation/Menus/List/Table/Header/index.tsx +7 -5
  121. package/src/modules/Navigation/Menus/List/Table/Item/index.tsx +10 -10
  122. package/src/modules/Navigation/Menus/List/Table/Item/style.tsx +2 -1
  123. package/src/modules/Navigation/Menus/List/index.tsx +6 -2
  124. package/src/modules/Navigation/Menus/index.tsx +12 -7
  125. package/src/modules/PageEditor/Editor/index.tsx +5 -1
  126. package/src/modules/PageEditor/PageBrowser/index.tsx +9 -3
  127. package/src/modules/PageEditor/index.tsx +67 -57
  128. package/src/modules/Redirects/index.tsx +97 -98
  129. package/src/modules/Settings/ContentTypes/DataPacks/AddModal/index.tsx +5 -1
  130. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/TemplateConfig/TemplateEditor/Editor/TemplateBrowser/index.tsx +8 -9
  131. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/index.tsx +7 -3
  132. package/src/modules/Settings/ContentTypes/DataPacks/Config/index.tsx +5 -1
  133. package/src/modules/Settings/ContentTypes/DataPacks/Item/index.tsx +5 -1
  134. package/src/modules/Settings/Integrations/BulkHeader/index.tsx +2 -17
  135. package/src/modules/Settings/Integrations/IntegrationForm/index.tsx +6 -2
  136. package/src/modules/Settings/Integrations/IntegrationItem/index.tsx +18 -8
  137. package/src/modules/Settings/Integrations/IntegrationItem/style.tsx +6 -3
  138. package/src/modules/Settings/Integrations/index.tsx +32 -7
  139. package/src/modules/Settings/Languages/LanguagePanel/index.tsx +6 -2
  140. package/src/modules/Settings/Languages/Table/Header/index.tsx +4 -2
  141. package/src/modules/Settings/Languages/Table/Item/index.tsx +19 -43
  142. package/src/modules/Settings/Languages/Table/Item/style.tsx +11 -54
  143. package/src/modules/Settings/Languages/index.tsx +2 -2
  144. package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +2 -4
  145. package/src/modules/Settings/SeoAnalyticsSettings/index.tsx +4 -2
  146. package/src/modules/Settings/Social/index.tsx +1 -1
  147. package/src/modules/Settings/index.tsx +17 -11
  148. package/src/modules/Sites/SitesList/GridView/GridSiteItem/index.tsx +31 -18
  149. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +21 -12
  150. package/src/modules/Sites/SitesList/ListView/ListSiteItem/index.tsx +31 -18
  151. package/src/modules/Sites/SitesList/RecentSiteItem/index.tsx +1 -1
  152. package/src/modules/Sites/SitesList/index.tsx +16 -24
  153. package/src/modules/Sites/index.tsx +7 -3
  154. package/src/modules/StructuredData/Form/index.tsx +1 -1
  155. package/src/modules/StructuredData/StructuredDataList/BulkHeader/index.tsx +8 -5
  156. package/src/modules/StructuredData/StructuredDataList/ContentFilters/index.tsx +8 -5
  157. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +29 -15
  158. package/src/modules/StructuredData/StructuredDataList/StructuredDataItem/index.tsx +50 -19
  159. package/src/modules/StructuredData/StructuredDataList/index.tsx +9 -6
  160. package/src/modules/Users/Profile/index.tsx +16 -4
  161. package/src/modules/Users/Roles/BulkHeader/TableHeader/index.tsx +52 -0
  162. package/src/modules/Users/Roles/BulkHeader/TableHeader/style.tsx +43 -0
  163. package/src/modules/Users/Roles/BulkHeader/index.tsx +74 -0
  164. package/src/modules/Users/Roles/RoleItem/index.tsx +104 -0
  165. package/src/modules/Users/Roles/RoleItem/style.tsx +127 -0
  166. package/src/modules/Users/Roles/SideModal/index.tsx +81 -0
  167. package/src/modules/Users/Roles/SideModal/style.tsx +132 -0
  168. package/src/modules/Users/Roles/hooks.tsx +78 -0
  169. package/src/modules/Users/Roles/index.tsx +256 -0
  170. package/src/modules/Users/Roles/style.tsx +23 -0
  171. package/src/modules/Users/Roles/utils.tsx +12 -0
  172. package/src/modules/Users/UserCreate/OptionItem/index.tsx +45 -0
  173. package/src/modules/Users/UserCreate/OptionItem/style.tsx +48 -0
  174. package/src/modules/Users/UserCreate/SiteItem/RolesModal/RoleItem/index.tsx +48 -0
  175. package/src/modules/Users/UserCreate/SiteItem/RolesModal/RoleItem/style.tsx +42 -0
  176. package/src/modules/Users/UserCreate/SiteItem/RolesModal/index.tsx +140 -0
  177. package/src/modules/Users/UserCreate/SiteItem/RolesModal/style.tsx +94 -0
  178. package/src/modules/Users/UserCreate/SiteItem/index.tsx +103 -22
  179. package/src/modules/Users/UserCreate/SiteItem/style.tsx +49 -6
  180. package/src/modules/Users/UserCreate/index.tsx +278 -121
  181. package/src/modules/Users/UserCreate/style.tsx +71 -4
  182. package/src/modules/Users/UserEdit/index.tsx +71 -24
  183. package/src/modules/Users/UserForm/atoms.tsx +40 -8
  184. package/src/modules/Users/UserForm/index.tsx +335 -116
  185. package/src/modules/Users/UserForm/style.tsx +70 -6
  186. package/src/modules/Users/UserList/BulkHeader/TableHeader/index.tsx +61 -31
  187. package/src/modules/Users/UserList/BulkHeader/TableHeader/style.tsx +18 -4
  188. package/src/modules/Users/UserList/BulkHeader/index.tsx +10 -3
  189. package/src/modules/Users/UserList/UserItem/index.tsx +121 -38
  190. package/src/modules/Users/UserList/UserItem/style.tsx +32 -14
  191. package/src/modules/Users/UserList/hooks.tsx +13 -8
  192. package/src/modules/Users/UserList/index.tsx +67 -29
  193. package/src/modules/Users/UserList/utils.tsx +1 -1
  194. package/src/modules/Users/index.tsx +20 -3
  195. package/src/routes/index.tsx +9 -17
  196. package/src/routes/multisite.tsx +73 -8
  197. package/src/routes/site.tsx +96 -10
  198. package/src/types/index.tsx +42 -1
  199. package/tsconfig.paths.json +1 -0
  200. package/src/__tests__/components/Avatar/__snapshots__/Avatar.test.tsx.snap +0 -61
  201. package/src/modules/Users/UserList/HeaderMenus/Name/index.tsx +0 -55
@@ -2,6 +2,7 @@ import React from "react";
2
2
 
3
3
  import { ISchema, ISchemaTab, ISchemaField } from "@ax/types";
4
4
  import { Tabs } from "@ax/components";
5
+ import { usePermission } from "@ax/hooks";
5
6
 
6
7
  import ConnectedField from "./ConnectedField";
7
8
 
@@ -22,6 +23,16 @@ export const Form = (props: IFormProps): JSX.Element => {
22
23
  isEditLive,
23
24
  } = props;
24
25
 
26
+ const isAllowedToEditPageContent =
27
+ (!isGlobal && usePermission("content.editContentPages")) ||
28
+ (isGlobal && usePermission("global.globalData.editAllGlobalData"));
29
+ const isAllowedToEditPageConfig =
30
+ (!isGlobal && usePermission("content.editConfigPages")) ||
31
+ (isGlobal && usePermission("global.globalData.editAllGlobalData"));
32
+ const isAllowedToEditPageSEO =
33
+ (!isGlobal && usePermission("seoAnalytics.editSeoAnalyticsPages")) ||
34
+ (isGlobal && usePermission("global.seoAnalytics.editSeoAnalyticsInGlobalPages"));
35
+
25
36
  const tabContent = schema.configTabs.find((tab: ISchemaTab) => tab.title === selectedTab);
26
37
  const setTab = (tab: string) => setSelectedTab(tab);
27
38
 
@@ -53,6 +64,7 @@ export const Form = (props: IFormProps): JSX.Element => {
53
64
  const getTabs = () => {
54
65
  let mappedTabs;
55
66
  const isHeader = schema.type === "header";
67
+ const isPageSchema = schema.schemaType === "page";
56
68
  if (isHeader && isPage) {
57
69
  mappedTabs = schema.configTabs
58
70
  .map((tab: ISchemaTab) => {
@@ -61,7 +73,18 @@ export const Form = (props: IFormProps): JSX.Element => {
61
73
  })
62
74
  .filter((value: string | boolean) => !!value);
63
75
  } else {
64
- mappedTabs = schema.configTabs.map((tab: ISchemaTab) => tab.title);
76
+ mappedTabs = schema.configTabs.reduce((acc: string[], curr: ISchemaTab) => {
77
+ if (
78
+ !isPageSchema ||
79
+ (isPageSchema &&
80
+ ((curr.title === "content" && isAllowedToEditPageContent) ||
81
+ (curr.title === "config" && isAllowedToEditPageConfig) ||
82
+ (curr.title === "SEO & Analytics" && isAllowedToEditPageSEO)))
83
+ ) {
84
+ return [...acc, curr.title];
85
+ }
86
+ return acc;
87
+ }, []);
65
88
  }
66
89
 
67
90
  return mappedTabs;
@@ -2,27 +2,39 @@ import React from "react";
2
2
  import { themes } from "components";
3
3
 
4
4
  import { Icon, NoteField, Tabs } from "@ax/components";
5
- import { ISchema } from "@ax/types";
5
+ import { INotification, ISchema } from "@ax/types";
6
+ import { useGlobalPermission } from "@ax/hooks";
7
+
6
8
  import ConnectedField from "../Form/ConnectedField";
7
9
 
8
10
  import * as S from "./style";
9
11
 
10
12
  const noteText = "This is Global content and you cannot edit it here. To do so, you must go to the Global page";
11
13
  const noteTitle = "Global content";
14
+ const errorText = "You don't have the permissions to edit the original content."
12
15
 
13
16
  const GlobalPageForm = (props: IGlobalPageFormProps): JSX.Element => {
14
17
  const { selectedTab, setSelectedTab, schema, pageTitle, setHistoryPush, actions, header, footer } = props;
15
18
  const tabs = ["content", "config"];
16
19
 
20
+ const isAllowedToEditGlobalData = useGlobalPermission("global.globalData.editAllGlobalData");
21
+
17
22
  const handleGetGlobalPage = async () => {
18
23
  actions.saveCurrentSiteInfoAction();
19
24
  await actions.getGlobalFromLocalPageAction();
20
25
  };
21
26
 
22
27
  const handleClick = async () => {
23
- await handleGetGlobalPage();
24
- const path = "/data/pages/editor";
25
- setHistoryPush && setHistoryPush(path, true);
28
+ if (isAllowedToEditGlobalData) {
29
+ await handleGetGlobalPage();
30
+ const path = "/data/pages/editor";
31
+ setHistoryPush && setHistoryPush(path, true);
32
+ } else {
33
+ actions.setNotificationAction({
34
+ type: "error",
35
+ text: errorText,
36
+ });
37
+ }
26
38
  };
27
39
 
28
40
  const parentField = {
@@ -172,6 +184,7 @@ export interface IGlobalPageFormProps {
172
184
  getGlobalFromLocalPageAction(): Promise<void>;
173
185
  saveCurrentSiteInfoAction(): Promise<void>;
174
186
  restorePageNavigationAction(type: string): Promise<void>;
187
+ setNotificationAction(notification: INotification): void;
175
188
  };
176
189
  }
177
190
 
@@ -21,7 +21,7 @@ const ElementsTooltip = (props: IElementsTooltipProps): JSX.Element => {
21
21
  return (
22
22
  <S.Wrapper data-testid="elements-wrapper">
23
23
  {visibleElements.map((fullElement, idx) => {
24
- const element = defaultElements === 1 && maxChar ? trimText(fullElement, maxChar) : fullElement;
24
+ const element = maxChar ? trimText(fullElement, maxChar) : fullElement;
25
25
  const color = colors && colors[element] ? colors[element] : undefined;
26
26
 
27
27
  return (
@@ -33,8 +33,8 @@ const IntegrationsField = (props: IIntegrationsFieldProps): JSX.Element => {
33
33
  pagination: false,
34
34
  filterState: "enable",
35
35
  };
36
- getIntegrations(site.id, params, true);
37
- }, [getIntegrations, site.id]);
36
+ site && getIntegrations(site.id, params, true);
37
+ }, [getIntegrations, site]);
38
38
 
39
39
  useEffect(() => {
40
40
  onChange && state && onChange(state);
@@ -63,10 +63,9 @@ const IntegrationsField = (props: IIntegrationsFieldProps): JSX.Element => {
63
63
  !state?.some((_integration: Partial<IIntegration>) => Number(_integration.id) === Number(integration.id))
64
64
  );
65
65
 
66
- const timeSinceIntegrationCopy = !!integrationCopy && differenceInSeconds(new Date(), new Date(integrationCopy.date));
66
+ const timeSinceIntegrationCopy = !!integrationCopy ? differenceInSeconds(new Date(), new Date(integrationCopy.date)) : null;
67
67
  const eightHoursInSeconds = 8 * 60 * 60;
68
- const showPasteIntegrationButton =
69
- !disabled && !!integrationCopy && timeSinceIntegrationCopy < eightHoursInSeconds;
68
+ const showPasteIntegrationButton = !disabled && !!integrationCopy && timeSinceIntegrationCopy !== null && timeSinceIntegrationCopy < eightHoursInSeconds;
70
69
 
71
70
  const pasteIntegration = () => {
72
71
  integrationCopy && handleAdd(integrationCopy.integration);
@@ -127,7 +126,7 @@ interface IStateProps {
127
126
  value: Partial<IIntegration>[];
128
127
  onChange: (value: Partial<IIntegration>[]) => void;
129
128
  disabled: boolean;
130
- site: ISite;
129
+ site: ISite | null;
131
130
  languages: ILanguage[];
132
131
  integrationCopy: { date: Date; integration: IIntegration } | null;
133
132
  }
@@ -32,7 +32,7 @@ const RadioField = (props: IRadioFieldProps): JSX.Element => {
32
32
 
33
33
  export interface IRadioFieldProps {
34
34
  name: string;
35
- title: string;
35
+ title?: string;
36
36
  value: string;
37
37
  checked?: boolean;
38
38
  error?: boolean;
@@ -186,11 +186,11 @@ const AutoPanel = (props: IProps): JSX.Element => {
186
186
  <S.SourceActions>
187
187
  {state.source && `${state.source.length} items`} {addSource}
188
188
  </S.SourceActions>
189
- {state.source &&
189
+ {state.source && state.source.length > 0 &&
190
190
  state.source.map((singleSource: string) => {
191
191
  const sourceFilters = state.filter.filter((f: any) => f.source === singleSource);
192
192
  const source = state.sourceTitles.find((el: IDataSource) => el.id === singleSource);
193
- return (
193
+ return source ? (
194
194
  <AutoItem
195
195
  key={singleSource}
196
196
  source={source}
@@ -201,7 +201,7 @@ const AutoPanel = (props: IProps): JSX.Element => {
201
201
  site={site}
202
202
  structuredDataSite={[...structuredData.site, ...categories.site]}
203
203
  />
204
- );
204
+ ) : <></>;
205
205
  })}
206
206
  </S.SourcesWrapper>
207
207
  <S.AllLanguagesWrapper>
@@ -112,7 +112,7 @@ const ItemList = (props: IProps) => {
112
112
 
113
113
  interface IProps {
114
114
  items: number[];
115
- currentSite: number;
115
+ currentSite: number | null;
116
116
  handleListDelete(value: string[]): void;
117
117
  handleChange(newValue: any): void;
118
118
  }
@@ -24,7 +24,7 @@ const ManualPanel = (props: IProps) => {
24
24
  const { state, setState } = useReference();
25
25
 
26
26
  const [isLoading, setIsLoading] = useState(false);
27
- const [selectedSource, setSelectedSource] = useState(state.sourceTitles[0].id);
27
+ const [selectedSource, setSelectedSource] = useState(state.sourceTitles[0]?.id);
28
28
  const [selectedLang, setSelectedLang] = useState(lang.id);
29
29
  const debouncedSearch = useDebounce(state.search);
30
30
 
@@ -58,7 +58,7 @@ const ManualPanel = (props: IProps) => {
58
58
  }
59
59
  setIsLoading(false);
60
60
  };
61
- getAndSetItems();
61
+ selectedSource && getAndSetItems();
62
62
 
63
63
  return function cleanup() {
64
64
  isMounted = false;
@@ -193,7 +193,7 @@ const ManualPanel = (props: IProps) => {
193
193
  };
194
194
 
195
195
  interface IProps {
196
- currentSite: number;
196
+ currentSite: number | null;
197
197
  onChange: (value: any) => void;
198
198
  hasMaxItems: boolean;
199
199
  handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
@@ -4,7 +4,7 @@ import { Tag } from "@ax/components";
4
4
  import * as S from "./style";
5
5
 
6
6
  const TagsField = (props: IProps): JSX.Element => {
7
- const { value, onChange } = props;
7
+ const { value, onChange, disabled } = props;
8
8
  const valueArray = value && Array.isArray(value) ? value : [];
9
9
  const [inputValue, setInputValue] = useState("");
10
10
  const inputRef = useRef<HTMLInputElement>(null);
@@ -40,7 +40,7 @@ const TagsField = (props: IProps): JSX.Element => {
40
40
  {valueArray &&
41
41
  valueArray.map((tag: string, index: number) => {
42
42
  const handleDelete = () => deleteTag(index);
43
- return <Tag key={tag} text={tag} onDeleteAction={handleDelete} />;
43
+ return <Tag key={tag} text={tag} onDeleteAction={disabled ? undefined : handleDelete} />;
44
44
  })}
45
45
  <S.Input
46
46
  data-testid="tag-field-input"
@@ -50,6 +50,7 @@ const TagsField = (props: IProps): JSX.Element => {
50
50
  onChange={_handleChange}
51
51
  placeholder={placeholder}
52
52
  onKeyDown={_handleKeyDown}
53
+ disabled={disabled}
53
54
  />
54
55
  </S.Wrapper>
55
56
  );
@@ -59,6 +60,7 @@ interface IProps {
59
60
  value: string[];
60
61
  placeholder?: string;
61
62
  onChange: (value: string[]) => void;
63
+ disabled?: boolean;
62
64
  }
63
65
 
64
66
  export default TagsField;
@@ -23,6 +23,7 @@ const TextField = (props: ITextFieldProps): JSX.Element => {
23
23
  handleValidation,
24
24
  validators,
25
25
  editorID,
26
+ autoFocus,
26
27
  inversed = false,
27
28
  } = props;
28
29
 
@@ -98,6 +99,7 @@ const TextField = (props: ITextFieldProps): JSX.Element => {
98
99
  hasPrefix={!!prefix}
99
100
  prefixWidth={width}
100
101
  aria-label={name}
102
+ autoFocus={autoFocus}
101
103
  />
102
104
  {hasBackgroundIcon && (
103
105
  <S.BackgroundIcon data-testid="background-icon-component" onClick={onClickIcon} inversed={inversed}>
@@ -132,6 +134,7 @@ interface ITextFieldProps {
132
134
  handleValidation?: (value: string, validators?: Record<string, unknown>) => void;
133
135
  validators?: Record<string, unknown>;
134
136
  editorID?: number;
137
+ autoFocus?: boolean;
135
138
  inversed?: boolean;
136
139
  }
137
140
 
@@ -113,12 +113,12 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
113
113
  };
114
114
 
115
115
  const validator = { format: "fullURL" };
116
- const defensiveHref = value ? value.href : "";
116
+ const defensiveHref = value && value.href ? value.href : "";
117
117
 
118
118
  let field = (
119
119
  <TextField
120
120
  {...props}
121
- value={defensiveHref || ""}
121
+ value={defensiveHref}
122
122
  onChange={handleChange}
123
123
  placeholder="http://"
124
124
  icon="urlLink"
@@ -145,14 +145,14 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
145
145
  type: "TextField" as Field,
146
146
  name: "title",
147
147
  title: "Title",
148
- value: value ? value.title || internalPageName : "",
148
+ value: value && value.title ? value.title : internalPageName ? internalPageName : "",
149
149
  onChange: handleTitleChange,
150
150
  },
151
151
  {
152
152
  type: "UniqueCheck" as Field,
153
153
  name: "newTab",
154
154
  options: [{ title: "Open in new tab" }],
155
- value: value ? value.newTab : null,
155
+ value: value && value.newTab ? value.newTab : null,
156
156
  onChange: handleNewTabChange,
157
157
  },
158
158
  {
@@ -225,7 +225,7 @@ const UrlField = (props: IUrlFieldProps): JSX.Element => {
225
225
  };
226
226
 
227
227
  export interface IUrlFieldProps {
228
- value: IUrlField | null;
228
+ value?: IUrlField | null;
229
229
  title: string;
230
230
  onChange: (value: IUrlField | null) => void;
231
231
  showAdvanced: boolean;
@@ -3,6 +3,7 @@ import { connect } from "react-redux";
3
3
  import { IImage, IRootState, IImageForm } from "@ax/types";
4
4
  import { Button, CheckField, FieldsBehavior, Toast, IconAction } from "@ax/components";
5
5
  import { formatBytes, getFileExtension, getFormattedDateWithTimezone } from "@ax/helpers";
6
+ import { usePermission } from "@ax/hooks";
6
7
 
7
8
  import { galleryActions } from "@ax/containers/Gallery";
8
9
  import { IIsSaving } from "@ax/containers/Gallery/reducer";
@@ -34,6 +35,14 @@ const GalleryDetailPanel = (props: IProps) => {
34
35
  const [addToGlobal, setAddToGlobal] = useState({ value: "addToGlobal", isChecked: false });
35
36
  const [deletedToast, setDeletedToast] = useState(false);
36
37
 
38
+ const isAllowedToDelete =
39
+ (isGlobalTab && usePermission("mediaGallery.deleteGlobalImagesInSite")) ||
40
+ (!isGlobalTab && usePermission("mediaGallery.deleteImages"));
41
+ const isAllowedToEdit =
42
+ (isGlobalTab && usePermission("mediaGallery.editGlobalImagesInSite")) ||
43
+ (!isGlobalTab && usePermission("mediaGallery.editImages"));
44
+ const isAllowedToUploadGlobal = usePermission("mediaGallery.addGlobalImagesFromSite");
45
+
37
46
  const setInitForm = (imageSelected: IImage) => {
38
47
  const form = {
39
48
  id: imageSelected.id,
@@ -89,7 +98,13 @@ const GalleryDetailPanel = (props: IProps) => {
89
98
  const handleSaveAndAdd = async () => {
90
99
  if (imageForm.id) {
91
100
  await updateImage(imageForm.id, imageForm, true);
92
- handleSetAsGlobal();
101
+ await handleSetAsGlobal();
102
+ setImage(imageForm);
103
+ }
104
+ };
105
+
106
+ const handleAdd = async () => {
107
+ if (imageForm.id) {
93
108
  setImage(imageForm);
94
109
  }
95
110
  };
@@ -134,7 +149,7 @@ const GalleryDetailPanel = (props: IProps) => {
134
149
  </S.AddToGlobal>
135
150
  );
136
151
 
137
- const canSetAsGlobal = !isGlobalTab && imageSelected?.file;
152
+ const canSetAsGlobal = !isGlobalTab && imageSelected?.file && isAllowedToUploadGlobal;
138
153
 
139
154
  const renderImageForm = !imageSelected ? null : (
140
155
  <S.PanelForm>
@@ -170,6 +185,7 @@ const GalleryDetailPanel = (props: IProps) => {
170
185
  value={imageForm.title}
171
186
  fieldType="TextField"
172
187
  onChange={handleTitle}
188
+ disabled={!isAllowedToEdit}
173
189
  />
174
190
  <FieldsBehavior
175
191
  title="Alternative text"
@@ -177,6 +193,7 @@ const GalleryDetailPanel = (props: IProps) => {
177
193
  value={imageForm.alt}
178
194
  fieldType="TextField"
179
195
  onChange={handleAlt}
196
+ disabled={!isAllowedToEdit}
180
197
  />
181
198
  <FieldsBehavior
182
199
  title="Description"
@@ -184,8 +201,9 @@ const GalleryDetailPanel = (props: IProps) => {
184
201
  value={imageForm.description}
185
202
  fieldType="TextArea"
186
203
  onChange={handleDescription}
204
+ disabled={!isAllowedToEdit}
187
205
  />
188
- <FieldsBehavior title="Tags" value={imageForm.tags} fieldType="TagsField" onChange={handleTags} />
206
+ <FieldsBehavior title="Tags" value={imageForm.tags} fieldType="TagsField" onChange={handleTags} disabled={!isAllowedToEdit} />
189
207
  </S.FormWrapper>
190
208
  </S.PanelForm>
191
209
  );
@@ -194,29 +212,38 @@ const GalleryDetailPanel = (props: IProps) => {
194
212
  <S.DetailPanelWrapper data-testid="detail-panel-wrapper" hidden={!isImageSelected}>
195
213
  {renderImageForm}
196
214
  <S.PanelActions>
197
- <Button
198
- type="button"
199
- buttonStyle="text"
200
- onClick={handleDelete}
201
- disabled={isSaving.save || isSaving.saveAdd || isSaving.delete}
202
- >
203
- {isSaving.delete ? `Deleting` : `Delete`}
204
- </Button>
205
- <Button
215
+ {isAllowedToDelete && (
216
+ <Button
217
+ type="button"
218
+ buttonStyle="text"
219
+ onClick={handleDelete}
220
+ disabled={isSaving.save || isSaving.saveAdd || isSaving.delete}
221
+ >
222
+ {isSaving.delete ? `Deleting` : `Delete`}
223
+ </Button>
224
+ )}
225
+ {isAllowedToEdit && <Button
206
226
  type="button"
207
227
  buttonStyle="line"
208
228
  onClick={handleSave}
209
229
  disabled={isSaving.save || isSaving.saveAdd || isSaving.delete}
210
230
  >
211
231
  {isSaving.save ? `Saving` : `Save`}
212
- </Button>
213
- <Button
232
+ </Button>}
233
+ {isAllowedToEdit && <Button
214
234
  type="button"
215
235
  onClick={handleSaveAndAdd}
216
236
  disabled={isSaving.save || isSaving.saveAdd || isSaving.delete}
217
237
  >
218
238
  {isSaving.saveAdd ? `Saving` : `Save & Add`}
219
- </Button>
239
+ </Button>}
240
+ {!isAllowedToEdit && <Button
241
+ type="button"
242
+ onClick={handleAdd}
243
+ disabled={isSaving.delete}
244
+ >
245
+ Add
246
+ </Button>}
220
247
  </S.PanelActions>
221
248
  {deletedToast && <Toast message="1 image deleted" setIsVisible={setDeletedToast} />}
222
249
  </S.DetailPanelWrapper>
@@ -188,7 +188,7 @@ const GalleryDragAndDrop = (props: IProps) => {
188
188
  export interface IGalleryDragAndDropProps {
189
189
  isImageSelected: boolean;
190
190
  validFormats: string[];
191
- site: number | string;
191
+ site: number | "global";
192
192
  allowUpload: boolean;
193
193
  refreshImages: () => Promise<void>;
194
194
  isUploading: boolean;
@@ -1,21 +1,35 @@
1
1
  import React, { memo } from "react";
2
2
 
3
- import { IImage } from "@ax/types";
3
+ import { IImage, ISite } from "@ax/types";
4
+ import { usePermission } from "@ax/hooks";
4
5
 
5
6
  import GalleryDragAndDrop from "./GalleryDragAndDrop";
6
7
  import DetailPanel from "./DetailPanel";
7
8
  import * as S from "./style";
8
9
 
9
10
  const GalleryPanel = (props: IGalleryPanelProps) => {
10
- const { imageSelected, validFormats, setImage, isGlobalTab, site, selectedTab, refreshImages, selectImage } = props;
11
- const allowUpload = !isGlobalTab || !selectedTab;
11
+ const {
12
+ imageSelected,
13
+ validFormats,
14
+ setImage,
15
+ isGlobalTab,
16
+ scope,
17
+ selectedTab,
18
+ site,
19
+ refreshImages,
20
+ selectImage
21
+ } = props;
22
+
23
+ const isAllowedToUpload = usePermission("mediaGallery.addImages");
24
+
25
+ const allowUpload = (site && (!isGlobalTab || !selectedTab) && isAllowedToUpload) || (!site && isAllowedToUpload);
12
26
 
13
27
  return (
14
28
  <S.GalleryPanel>
15
29
  <GalleryDragAndDrop
16
30
  isImageSelected={!!imageSelected}
17
31
  validFormats={validFormats}
18
- site={site}
32
+ site={scope}
19
33
  allowUpload={allowUpload}
20
34
  refreshImages={refreshImages}
21
35
  selectImage={selectImage}
@@ -37,9 +51,10 @@ export interface IGalleryPanelProps {
37
51
  validFormats: string[];
38
52
  setImage: (imageData: any) => void;
39
53
  isGlobalTab: boolean;
40
- site: number | string;
54
+ scope: number | "global";
41
55
  selectedTab: string;
42
56
  refreshImages: () => Promise<void>;
57
+ site: ISite | null;
43
58
  selectImage(item: IImage | null): void;
44
59
  }
45
60
 
@@ -5,6 +5,7 @@ import { galleryActions } from "@ax/containers/Gallery";
5
5
  import { IData, IIsLoading } from "@ax/containers/Gallery/reducer";
6
6
  import { IGetSiteImages, IImage, IRootState, ISite } from "@ax/types";
7
7
  import { Icon, Loader, Tabs, SearchField, EmptyState, ErrorToast, Notification } from "@ax/components";
8
+ import { usePermission } from "@ax/hooks";
8
9
 
9
10
  import Orientation from "./GalleryFilters/Orientation";
10
11
  import SortBy from "./GalleryFilters/SortBy";
@@ -21,13 +22,17 @@ const firstPage = 1;
21
22
  const Gallery = (props: IProps): JSX.Element => {
22
23
  const { data, isLoading, getSiteImages, getImageSelected, toggleModal, site, uploadError } = props;
23
24
 
24
- const tabs = [];
25
- if (site) tabs.unshift(...["Local", "Global"]);
26
- const [selectedTab, setSelectedTab] = useState(tabs[0]);
25
+ const isAllowedToAccessGlobalImages = usePermission("mediaGallery.accessToGlobalGalleryFromSite");
26
+
27
+ const tabs: string[] = [];
28
+ if (site && isAllowedToAccessGlobalImages) {
29
+ tabs.unshift(...["Local", "Global"]);
30
+ }
31
+ const [selectedTab, setSelectedTab] = useState(site ? "Local" : "Global");
27
32
  const [selectedImage, setSelectedImage] = useState<IImage | null>(null);
28
33
  const isLocalTab = selectedTab === "Local";
29
34
  const isGlobalTab = selectedTab === "Global";
30
- const galleryScope = isLocalTab ? site.id : "global";
35
+ const galleryScope = isLocalTab && site ? site.id : "global";
31
36
 
32
37
  const validFormats = ["jpeg", "jpg", "png", "svg", "gif"];
33
38
 
@@ -213,9 +218,10 @@ const Gallery = (props: IProps): JSX.Element => {
213
218
  validFormats={validFormats}
214
219
  setImage={setImage}
215
220
  isGlobalTab={!isLocalTab}
216
- site={galleryScope}
221
+ scope={galleryScope}
217
222
  selectedTab={selectedTab}
218
223
  refreshImages={refreshImages}
224
+ site={site}
219
225
  selectImage={setSelectedImage}
220
226
  />
221
227
  </S.Wrapper>
@@ -225,7 +231,7 @@ const Gallery = (props: IProps): JSX.Element => {
225
231
  export interface IGalleryProps {
226
232
  getImageSelected: (img: IImage | null) => void;
227
233
  toggleModal: () => void;
228
- site: ISite;
234
+ site: ISite | null;
229
235
  data: IData;
230
236
  isLoading: IIsLoading;
231
237
  }
@@ -23,7 +23,15 @@ const Icon = (props: IProps) => {
23
23
  const Svg = getImage(name);
24
24
 
25
25
  if (Svg) {
26
- return <Svg data-testid="icon-component" height={size} width={size} viewBox="0 0 24 24" fill={fill} />;
26
+ return (
27
+ <Svg
28
+ data-testid={`icon-component-${name.toLowerCase()}`}
29
+ height={size}
30
+ width={size}
31
+ viewBox="0 0 24 24"
32
+ fill={fill}
33
+ />
34
+ );
27
35
  }
28
36
 
29
37
  return null;
@@ -32,7 +32,7 @@ const ActionMenu = (props: any) => {
32
32
  const { menu } = props;
33
33
  return (
34
34
  menu &&
35
- menu.options && (
35
+ menu.options.length > 0 && (
36
36
  <S.ActionMenu>
37
37
  <S.ActionMenuTitle> More actions </S.ActionMenuTitle>
38
38
  <ActionMenuBtn menu={menu} />
@@ -52,7 +52,7 @@ const ActionSimpleMenu = (props: any) => {
52
52
  const { menu } = props;
53
53
  return (
54
54
  menu &&
55
- menu.options && (
55
+ menu.options.length > 0 && (
56
56
  <S.ActionMenu data-testid="action-simple-menu">
57
57
  {menu.options.map((item: any, i: number) => ActionMenuItem(item, true))}
58
58
  </S.ActionMenu>
@@ -142,14 +142,16 @@ const AppBar = (props: IProps): JSX.Element => {
142
142
  options: pageStatusActions,
143
143
  };
144
144
 
145
- const PageStatus = () =>
146
- statusMenu.options && statusMenu.options.length > 0 ? (
147
- <FloatingMenu Button={StatusBtn} isInAppBar={true} position="left" offset={-20}>
145
+ const PageStatus = () => {
146
+ const offset = rightButton || rightLineButton ? -20 : -135;
147
+ return statusMenu.options && statusMenu.options.length > 0 ? (
148
+ <FloatingMenu Button={StatusBtn} isInAppBar={true} position="left" offset={offset}>
148
149
  <ActionSimpleMenu menu={statusMenu} />
149
150
  </FloatingMenu>
150
151
  ) : (
151
152
  <Icon name={pageStatus ?? "offline"} size="24" />
152
153
  );
154
+ }
153
155
 
154
156
  const languageTooltip = typeof currentPageID === "number" && "Add language";
155
157
 
@@ -195,34 +197,34 @@ const AppBar = (props: IProps): JSX.Element => {
195
197
  closeOnInactive
196
198
  />
197
199
  </S.SearchWrapper>
198
- <S.Separator />
200
+ {(language || pageStatus || rightButton || rightLineButton) && <S.Separator />}
199
201
  </>
200
202
  )}
201
203
  {language && (
202
204
  <>
203
205
  <S.LanguageWrapper data-testid="language-wrapper">
204
206
  <Tooltip content={languageTooltip} hideOnClick bottom>
205
- <FloatingMenu Button={LanguageBtn} isInAppBar={true} position="left">
207
+ <FloatingMenu Button={LanguageBtn} isInAppBar={true} position="left" offset={rightButton || rightLineButton ? 0 : -85}>
206
208
  {languageMenu}
207
209
  </FloatingMenu>
208
210
  </Tooltip>
209
211
  </S.LanguageWrapper>
210
- <S.Separator />
212
+ {(pageStatus || rightButton || rightLineButton ) && <S.Separator />}
211
213
  </>
212
214
  )}
213
215
  {pageStatus && (
214
216
  <>
215
- <S.IconStatusWrapper data-testid="page-status-wrapper">
217
+ <S.IconStatusWrapper data-testid="page-status-wrapper" last={!rightButton && !rightLineButton}>
216
218
  <Tooltip content={publishedTooltip[pageStatus]} bottom>
217
219
  <PageStatus />
218
220
  </Tooltip>
219
221
  </S.IconStatusWrapper>
220
- <S.Separator />
222
+ {(rightButton || rightLineButton ) && <S.Separator />}
221
223
  </>
222
224
  )}
223
225
  {isFromEditor && errors && errors.length > 0 && (
224
226
  <>
225
- <S.IconStatusWrapper data-testid="error-center-wrapper">
227
+ <S.IconStatusWrapper last={false} data-testid="error-center-wrapper">
226
228
  <FloatingMenu Button={ErrorCenterBtn} isInAppBar={true} position="left" offset={-20}>
227
229
  <ErrorCenter errors={errors} actions={errorActions} />
228
230
  </FloatingMenu>
@@ -246,7 +248,7 @@ const AppBar = (props: IProps): JSX.Element => {
246
248
  {rightButton.label}
247
249
  </Button>
248
250
  )}
249
- {downArrowMenu && downArrowMenu.displayed && (
251
+ {downArrowMenu && downArrowMenu.displayed && (downArrowMenu.button || downArrowMenu.options.length > 0) && (
250
252
  <Tooltip content="Actions" hideOnClick bottom>
251
253
  <FloatingMenu Button={DownArrowButton} isInAppBar={true}>
252
254
  <ActionMenu menu={downArrowMenu} />