@griddo/ax 11.14.5-rc.0 → 11.15.1-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.14.5-rc.0",
4
+ "version": "11.15.1-rc.2",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -219,5 +219,5 @@
219
219
  "publishConfig": {
220
220
  "access": "public"
221
221
  },
222
- "gitHead": "35106a4e094a9b210ac358d51ad5a2d80dbc6a2d"
222
+ "gitHead": "10113091118b2f939c4861e5a39c3f8b38a839ac"
223
223
  }
@@ -67,11 +67,10 @@ const PageConnectedField = (props: any): JSX.Element => {
67
67
 
68
68
  let isTemplateActivated = true;
69
69
  if (selectedContent.template && !isGlobal && !isFormTemplate) {
70
- isTemplateActivated =
70
+ isTemplateActivated = !!(
71
71
  activatedTemplates.find((temp: any) => temp.id === selectedContent.template.templateType) &&
72
72
  !isTemplateExcludedFromTheme(themeElements, selectedContent.template.templateType)
73
- ? true
74
- : false;
73
+ );
75
74
  }
76
75
 
77
76
  const parentIsReadOnly =
@@ -1,23 +1,24 @@
1
- import React from "react";
2
-
3
1
  import { RadioGroup } from "@ax/components";
2
+
4
3
  import * as S from "./style";
5
4
 
6
5
  const ConditionalField = (props: IConditionalFieldProps) => {
7
- const { value, options, innerFields, onChange, defaultValue, disabled } = props;
6
+ const { value, options, innerFields, onChange, defaultValue, disabled, fields } = props;
8
7
 
9
8
  const safeValue = value === undefined ? defaultValue : value;
10
9
 
11
10
  const handleChange = (newValue: any) => onChange(newValue);
12
11
 
12
+ const visibleFields = innerFields?.filter((item: any, index: number) => {
13
+ const condition = item?.props?.field?.condition || fields?.[index]?.condition;
14
+ return condition === undefined || condition === value;
15
+ });
16
+
13
17
  return (
14
18
  <S.Wrapper>
15
19
  <RadioGroup name="radio" value={safeValue} options={options} onChange={handleChange} disabled={disabled} />
16
20
  <S.Content data-testid="conditionalFieldContent">
17
- {innerFields &&
18
- innerFields.map((item: any) => {
19
- return value === item.props.field.condition && item;
20
- })}
21
+ {visibleFields}
21
22
  </S.Content>
22
23
  </S.Wrapper>
23
24
  );
@@ -29,6 +30,7 @@ interface IConditionalFieldProps {
29
30
  value?: string | boolean;
30
31
  options: any[];
31
32
  innerFields: any;
33
+ fields?: any[];
32
34
  onChange: (value: any) => void;
33
35
  defaultValue?: string | boolean;
34
36
  disabled?: boolean;
@@ -20,7 +20,7 @@ const getUpdatedComponents = (editorContent: any, element: any, key: string) =>
20
20
  const mapValues = (item: any) => {
21
21
  if (item.editorID !== undefined && item.editorID === editorID) {
22
22
  if (isCollectionItem) {
23
- item[key] = updateCollection(type, item[key]);
23
+ item[key] = updateCollection(type, item[key] || []);
24
24
  } else {
25
25
  addElement(type);
26
26
  }
@@ -629,18 +629,13 @@ function setIsTranslated(isTranslated: boolean): (dispatch: Dispatch) => Promise
629
629
  };
630
630
  }
631
631
 
632
- function resetCurrentData(): (dispatch: Dispatch) => Promise<void> {
633
- return async (dispatch) => {
634
- try {
635
- dispatch(setCurrentData(null));
636
- dispatch(setCurrentDataID(null));
637
- dispatch(setCurrentDataContent([]));
638
- dispatch(setSchema({}));
639
- dispatch(setFilter("all-pages"));
640
- //dispatch(setCurrentSearch(""));
641
- } catch (e) {
642
- console.log("Error", e);
643
- }
632
+ function resetCurrentData(): (dispatch: Dispatch) => void {
633
+ return (dispatch) => {
634
+ dispatch(setCurrentData(null));
635
+ dispatch(setCurrentDataID(null));
636
+ dispatch(setCurrentDataContent([]));
637
+ dispatch(setSchema({}));
638
+ dispatch(setFilter("all-pages"));
644
639
  };
645
640
  }
646
641
 
@@ -13,7 +13,7 @@ import { useURLSearchParam } from "./location";
13
13
  import { useHandleClickOutside, useModal, useModals, useToast } from "./modals";
14
14
  import { useNetworkStatus } from "./network";
15
15
  import { useResizable } from "./resize";
16
- import { useGlobalPermission, usePermission, usePermissions } from "./users";
16
+ import { useGlobalPermission, usePermission, usePermissions, usePermissionsForSite } from "./users";
17
17
  import { useFirefoxScrollLock, useWindowSize } from "./window";
18
18
 
19
19
  export {
@@ -35,6 +35,7 @@ export {
35
35
  useOnMessageReceivedFromOutside,
36
36
  usePermission,
37
37
  usePermissions,
38
+ usePermissionsForSite,
38
39
  usePrevious,
39
40
  useResizable,
40
41
  useShouldBeSaved,
@@ -4,13 +4,22 @@ import { useSelector } from "react-redux";
4
4
  import type { IRootState } from "@ax/types";
5
5
 
6
6
  const usePermission = (permission: string | string[] | undefined): boolean => {
7
+ const currentUser = useSelector((state: IRootState) => state.users.currentUser);
7
8
  const userPermissions = useSelector((state: IRootState) => state.users.currentPermissions);
8
9
  const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
9
10
 
10
11
  const isAllowedTo = (permissions: string[]) =>
11
12
  userPermissions && permissions.some((permission: string) => userPermissions.includes(permission));
12
13
 
13
- if (!permission || isSuperAdmin) {
14
+ if (!permission) {
15
+ return true;
16
+ }
17
+
18
+ if (!currentUser) {
19
+ return false;
20
+ }
21
+
22
+ if (isSuperAdmin) {
14
23
  return true;
15
24
  }
16
25
 
@@ -20,10 +29,21 @@ const usePermission = (permission: string | string[] | undefined): boolean => {
20
29
  };
21
30
 
22
31
  const usePermissions = <T extends Record<string, string | string[]>>(permissions: T): Record<keyof T, boolean> => {
32
+ const currentUser = useSelector((state: IRootState) => state.users.currentUser);
23
33
  const userPermissions = useSelector((state: IRootState) => state.users.currentPermissions);
24
34
  const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
25
35
 
26
36
  return useMemo(() => {
37
+ if (!currentUser) {
38
+ return Object.keys(permissions).reduce(
39
+ (acc, key) => {
40
+ acc[key as keyof T] = false;
41
+ return acc;
42
+ },
43
+ {} as Record<keyof T, boolean>,
44
+ );
45
+ }
46
+
27
47
  const isAllowedTo = (permission: string | string[]) => {
28
48
  if (isSuperAdmin) return true;
29
49
  const arrayPermission = Array.isArray(permission) ? permission : [permission];
@@ -35,17 +55,26 @@ const usePermissions = <T extends Record<string, string | string[]>>(permissions
35
55
  result[key] = isAllowedTo(permissions[key]);
36
56
  }
37
57
  return result as Record<keyof T, boolean>;
38
- }, [permissions, userPermissions, isSuperAdmin]);
58
+ }, [permissions, userPermissions, isSuperAdmin, currentUser]);
39
59
  };
40
60
 
41
61
  const useGlobalPermission = (permission: string | string[] | undefined): boolean => {
62
+ const currentUser = useSelector((state: IRootState) => state.users.currentUser);
42
63
  const userPermissions = useSelector((state: IRootState) => state.users.globalPermissions);
43
64
  const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
44
65
 
45
66
  const isAllowedTo = (permissions: string[]) =>
46
67
  userPermissions && permissions.some((permission: string) => userPermissions.includes(permission));
47
68
 
48
- if (!permission || isSuperAdmin) {
69
+ if (!permission) {
70
+ return true;
71
+ }
72
+
73
+ if (!currentUser) {
74
+ return false;
75
+ }
76
+
77
+ if (isSuperAdmin) {
49
78
  return true;
50
79
  }
51
80
 
@@ -54,4 +83,66 @@ const useGlobalPermission = (permission: string | string[] | undefined): boolean
54
83
  return isAllowedTo(arrayPermission);
55
84
  };
56
85
 
57
- export { usePermission, usePermissions, useGlobalPermission };
86
+ const usePermissionsForSite = <T extends Record<string, string | string[]>>(
87
+ siteId: number | string,
88
+ permissions: T,
89
+ ): Record<keyof T, boolean> => {
90
+ const currentUser = useSelector((state: IRootState) => state.users.currentUser);
91
+ const roles = useSelector((state: IRootState) => state.users.roles);
92
+ const isSuperAdmin = useSelector((state: IRootState) => state.users.currentUser?.isSuperAdmin);
93
+
94
+ return useMemo(() => {
95
+ if (!currentUser) {
96
+ return Object.keys(permissions).reduce(
97
+ (acc, key) => {
98
+ acc[key as keyof T] = false;
99
+ return acc;
100
+ },
101
+ {} as Record<keyof T, boolean>,
102
+ );
103
+ }
104
+
105
+ if (isSuperAdmin) {
106
+ return Object.keys(permissions).reduce(
107
+ (acc, key) => {
108
+ acc[key as keyof T] = true;
109
+ return acc;
110
+ },
111
+ {} as Record<keyof T, boolean>,
112
+ );
113
+ }
114
+
115
+ const userRoles = currentUser.roles.find(
116
+ (roleSite) => roleSite.siteId === siteId || roleSite.siteId === "all",
117
+ )?.roles;
118
+
119
+ if (!userRoles?.length) {
120
+ return Object.keys(permissions).reduce(
121
+ (acc, key) => {
122
+ acc[key as keyof T] = false;
123
+ return acc;
124
+ },
125
+ {} as Record<keyof T, boolean>,
126
+ );
127
+ }
128
+
129
+ const sitePermissions: string[] = [];
130
+ userRoles.forEach((roleID: number) => {
131
+ const role = roles?.find((r) => r.id === roleID);
132
+ if (role) {
133
+ const rolePerms = role.permissions.sitePermissions.map((perm) => perm.key);
134
+ sitePermissions.push(...rolePerms);
135
+ }
136
+ });
137
+
138
+ const result: Record<string, boolean> = {};
139
+ for (const key of Object.keys(permissions)) {
140
+ const permission = permissions[key as keyof T];
141
+ const arrayPermission = Array.isArray(permission) ? permission : [permission];
142
+ result[key] = arrayPermission.some((perm: string) => sitePermissions.includes(perm));
143
+ }
144
+ return result as Record<keyof T, boolean>;
145
+ }, [siteId, permissions, currentUser, roles, isSuperAdmin]);
146
+ };
147
+
148
+ export { usePermission, usePermissions, useGlobalPermission, usePermissionsForSite };
@@ -5,7 +5,7 @@ import { EmptyState, ErrorToast, FilterTagsBar, MainWrapper, SearchTagsBar, Tabl
5
5
  import { appActions } from "@ax/containers/App";
6
6
  import { structuredDataActions } from "@ax/containers/StructuredData";
7
7
  import { sortBy } from "@ax/helpers";
8
- import { useBulkSelection, useModal, usePermission, useToast } from "@ax/hooks";
8
+ import { useBulkSelection, useModals, usePermissions, useToast } from "@ax/hooks";
9
9
  import { LOCALE, toTitleCase } from "@ax/locales";
10
10
  import type {
11
11
  ICategoryGroup,
@@ -67,7 +67,6 @@ const CategoriesList = (props: IProps): JSX.Element => {
67
67
  activatedDataPacks,
68
68
  deleteDataContent,
69
69
  setHistoryPush,
70
- resetCurrentData,
71
70
  deleteCategoryGroup,
72
71
  orderCategory,
73
72
  } = props;
@@ -81,17 +80,19 @@ const CategoriesList = (props: IProps): JSX.Element => {
81
80
  const [overId, setOverId] = useState<UniqueIdentifier | null>(null);
82
81
  const [offsetLeft, setOffsetLeft] = useState(0);
83
82
  const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();
84
- const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
85
- const { isOpen: isGroupOpen, toggleModal: toggleGroupModal } = useModal();
86
83
  const tableRef = useRef<HTMLDivElement>(null);
87
84
  const { setFiltersSelection, resetFilterQuery, filterValues, filterQuery } = useFilterQuery();
85
+ const { isOpen, toggleModal } = useModals(["panel", "delete", "group"]);
88
86
 
89
- const allowedToCreateSiteCategory = usePermission("categories.createSiteTaxonomies");
90
- const allowedToCreateGlobalCategory = usePermission("global.globalData.createTaxonomies");
91
- const allowedToDeleteSiteCategory = usePermission("categories.deleteSiteTaxonomies");
92
- const allowedToDeleteGlobalCategory = usePermission("global.globalData.deleteTaxonomies");
93
- const allowedToDeleteTaxonomy = currentSiteID ? allowedToDeleteSiteCategory : allowedToDeleteGlobalCategory;
94
- const allowedToCreateTaxonomy = currentSiteID ? allowedToCreateSiteCategory : allowedToCreateGlobalCategory;
87
+ const isAllowedTo = usePermissions({
88
+ createSiteCategory: "categories.createSiteTaxonomies",
89
+ createGlobalCategory: "global.globalData.createTaxonomies",
90
+ deleteSiteCategory: "categories.deleteSiteTaxonomies",
91
+ deleteGlobalCategory: "global.globalData.deleteTaxonomies",
92
+ });
93
+
94
+ const allowedToDeleteTaxonomy = currentSiteID ? isAllowedTo.deleteSiteCategory : isAllowedTo.deleteGlobalCategory;
95
+ const allowedToCreateTaxonomy = currentSiteID ? isAllowedTo.createSiteCategory : isAllowedTo.createGlobalCategory;
95
96
 
96
97
  const scope = currentSiteID ? "site" : "global";
97
98
  const currentCategories = categories[scope].sort(sortBy("title", false));
@@ -177,9 +178,6 @@ const CategoriesList = (props: IProps): JSX.Element => {
177
178
  if (currentCategories.length) {
178
179
  setSelectedCategory(currentCategories[0].id, scope);
179
180
  }
180
- return () => {
181
- resetCurrentData();
182
- };
183
181
  }, []);
184
182
 
185
183
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
@@ -191,12 +189,10 @@ const CategoriesList = (props: IProps): JSX.Element => {
191
189
  setSelectedCategory(dataID, scope);
192
190
  };
193
191
 
194
- const { isOpen, toggleModal } = useModal();
195
-
196
192
  const rightButtonProps = allowedToCreateTaxonomy
197
193
  ? {
198
194
  label: "New",
199
- action: toggleModal,
195
+ action: () => toggleModal("panel"),
200
196
  }
201
197
  : undefined;
202
198
 
@@ -212,8 +208,8 @@ const CategoriesList = (props: IProps): JSX.Element => {
212
208
  currentStructuredData && getContents(currentStructuredData.id);
213
209
  toggleToast(`${selectedItems.all.length} categories and/or groups deleted`);
214
210
  unselectAllItems();
215
- isDeleteOpen && toggleDeleteModal();
216
- isGroupOpen && toggleGroupModal();
211
+ isOpen("delete") && toggleModal("delete");
212
+ isOpen("group") && toggleModal("group");
217
213
  }
218
214
  setIsBulkLoading(false);
219
215
  };
@@ -221,7 +217,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
221
217
  const handleToggleDeleteModal = () => {
222
218
  const selectedCategories = currentDataContent.filter((category) => selectedItems.all.includes(category.id));
223
219
  const hasGroups = selectedCategories.some((category) => category.type === "group");
224
- hasGroups ? toggleGroupModal() : toggleDeleteModal();
220
+ hasGroups ? toggleModal("group") : toggleModal("delete");
225
221
  };
226
222
 
227
223
  const unselectAllItems = () => resetBulkSelection();
@@ -342,7 +338,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
342
338
  ? `To start using ${categoryName} categories, create as many of them as yo need.`
343
339
  : "To start using categories in your site, you must active all Content type packages as you need.",
344
340
  button: hasCategories ? `Create ${categoryName} category` : "Activate Content type packages",
345
- action: hasCategories ? toggleModal : () => setHistoryPush("/sites/settings/content-types"),
341
+ action: hasCategories ? () => toggleModal("panel") : () => setHistoryPush("/sites/settings/content-types"),
346
342
  };
347
343
 
348
344
  const mainDeleteModalAction = {
@@ -353,7 +349,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
353
349
 
354
350
  const secondaryDeleteModalAction = {
355
351
  title: "Cancel",
356
- onClick: toggleDeleteModal,
352
+ onClick: () => toggleModal("delete"),
357
353
  };
358
354
 
359
355
  const mainDeleteGroupModalAction = {
@@ -364,7 +360,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
364
360
 
365
361
  const secondaryDeleteGroupModalAction = {
366
362
  title: "cancel",
367
- onClick: toggleGroupModal,
363
+ onClick: () => toggleModal("group"),
368
364
  };
369
365
 
370
366
  return (
@@ -450,19 +446,24 @@ const CategoriesList = (props: IProps): JSX.Element => {
450
446
  </TableList>
451
447
  </S.TableWrapper>
452
448
  </S.CategoryListWrapper>
453
- <CategoryPanel isOpen={isOpen} toggleModal={toggleModal} getContents={getContents} toggleToast={toggleToast} />
454
- {isDeleteOpen && (
449
+ <CategoryPanel
450
+ isOpen={isOpen("panel")}
451
+ toggleModal={() => toggleModal("panel")}
452
+ getContents={getContents}
453
+ toggleToast={toggleToast}
454
+ />
455
+ {isOpen("delete") && (
455
456
  <DeleteModal
456
- isOpen={isDeleteOpen}
457
- toggleModal={toggleDeleteModal}
457
+ isOpen={isOpen("delete")}
458
+ toggleModal={() => toggleModal("delete")}
458
459
  mainModalAction={mainDeleteModalAction}
459
460
  secondaryModalAction={secondaryDeleteModalAction}
460
461
  />
461
462
  )}
462
- {isGroupOpen && (
463
+ {isOpen("group") && (
463
464
  <DeleteGroupModal
464
- isOpen={isGroupOpen}
465
- toggleModal={toggleGroupModal}
465
+ isOpen={isOpen("group")}
466
+ toggleModal={() => toggleModal("group")}
466
467
  mainModalAction={mainDeleteGroupModalAction}
467
468
  secondaryModalAction={secondaryDeleteGroupModalAction}
468
469
  deleteGroupCategories={deleteGroupCategories}
@@ -492,7 +493,6 @@ interface IDispatchProps {
492
493
  setSelectedCategory(id: string, scope: string): void;
493
494
  deleteDataContent(catID: number[], refresh?: boolean): Promise<boolean>;
494
495
  setHistoryPush(path: string): void;
495
- resetCurrentData(): Promise<void>;
496
496
  deleteCategoryGroup(id: number | number[], deleteChildren: boolean, refresh?: boolean): Promise<boolean>;
497
497
  orderCategory(params: IOrderCategoryParams): Promise<boolean>;
498
498
  }
@@ -519,7 +519,6 @@ const mapDispatchToProps = {
519
519
  deleteDataContent: structuredDataActions.deleteStructuredDataContent,
520
520
  deleteCategoryGroup: structuredDataActions.deleteCategoryGroup,
521
521
  setHistoryPush: appActions.setHistoryPush,
522
- resetCurrentData: structuredDataActions.resetCurrentData,
523
522
  orderCategory: structuredDataActions.orderCategory,
524
523
  };
525
524
 
@@ -1,12 +1,11 @@
1
- import React from "react";
2
1
  import { connect } from "react-redux";
3
2
 
4
- import { getFormTemplate } from "@ax/helpers";
5
- import { IErrorItem, IRootState } from "@ax/types";
6
3
  import { formsActions } from "@ax/containers/Forms";
4
+ import { getFormTemplate } from "@ax/helpers";
5
+ import type { IErrorItem, IRootState } from "@ax/types";
7
6
 
8
- import TemplateManager from "./TemplateManager";
9
7
  import Field from "./Field";
8
+ import TemplateManager from "./TemplateManager";
10
9
 
11
10
  const ConnectedField = (props: any) => {
12
11
  const {
@@ -54,8 +53,7 @@ const ConnectedField = (props: any) => {
54
53
  const template = templateType ? getFormTemplate(templateType) : null;
55
54
 
56
55
  if (!template) {
57
- actions.setNotificationAction &&
58
- actions.setNotificationAction({ text: "Form template schema not found.", type: "error" });
56
+ actions.setNotificationAction?.({ text: "Form template schema not found.", type: "error" });
59
57
  return <></>;
60
58
  }
61
59
 
@@ -1,7 +1,5 @@
1
- import React from "react";
2
-
3
- import { ISchema, ISchemaTab, ISchemaField, IErrorItem } from "@ax/types";
4
1
  import { Tabs } from "@ax/components";
2
+ import type { IErrorItem, ISchema, ISchemaField, ISchemaTab } from "@ax/types";
5
3
 
6
4
  import ConnectedField from "./ConnectedField";
7
5
 
@@ -55,8 +53,8 @@ export const Form = (props: IFormProps): JSX.Element => {
55
53
  const isPageSchema = schema.schemaType === "page";
56
54
  const mappedTabs = schema.configTabs.reduce((acc: string[], curr: ISchemaTab) => {
57
55
  const currTitle = curr.title.toLowerCase();
58
- if (!isPageSchema || (isPageSchema && (currTitle === "content" || currTitle === "config"))) {
59
- return [...acc, currTitle];
56
+ if (!isPageSchema || currTitle === "content" || currTitle === "config") {
57
+ return acc.concat(currTitle);
60
58
  }
61
59
  return acc;
62
60
  }, []);
@@ -71,7 +69,7 @@ export const Form = (props: IFormProps): JSX.Element => {
71
69
  <S.TabsWrapper headerHeight={headerHeight}>
72
70
  <Tabs tabs={tabs} active={selectedTab} setSelectedTab={setTab} />
73
71
  </S.TabsWrapper>
74
- {tabContent && tabContent.fields.map((field: ISchemaField) => generateFields(field))}
72
+ {tabContent?.fields.map((field: ISchemaField) => generateFields(field))}
75
73
  </S.Wrapper>
76
74
  );
77
75
  };
@@ -52,7 +52,7 @@ const Editor = (props: IProps) => {
52
52
  }
53
53
 
54
54
  content.forEach((field) => {
55
- if (field.type === "FieldGroup" && field.fields) {
55
+ if ((field.type === "FieldGroup" || field.type === "ConditionalField") && field.fields) {
56
56
  content = [...content, ...field.fields];
57
57
  }
58
58
  });
@@ -75,7 +75,6 @@ const StyledActionMenu = styled(ActionMenu)`
75
75
 
76
76
  const FormRow = styled(Row)<{ clickable: boolean }>`
77
77
  cursor: ${(p) => (p.clickable ? "pointer" : "default")};
78
- overflow: hidden;
79
78
  &:hover {
80
79
  ${StyledActionMenu} {
81
80
  opacity: 1;
@@ -526,7 +526,7 @@ const GlobalEditor = (props: IProps) => {
526
526
  };
527
527
 
528
528
  const handleGoBack = () => {
529
- props.setHistoryPush(backLinkRoute, false);
529
+ props.setHistoryPush(backLinkRoute, true);
530
530
  };
531
531
 
532
532
  const mainAction = { title: "Preview Page", onClick: toggleModal };
@@ -66,6 +66,7 @@ const GridSiteItem = (props: IGridSiteItemProps): JSX.Element => {
66
66
  title="Delete Site?"
67
67
  secondaryAction={secondaryDeleteAction}
68
68
  mainAction={mainDeleteAction}
69
+ height="auto"
69
70
  >
70
71
  {isOpenDelete ? (
71
72
  <S.ModalContent data-testid="delete-modal">
@@ -87,6 +88,7 @@ const GridSiteItem = (props: IGridSiteItemProps): JSX.Element => {
87
88
  title={title}
88
89
  secondaryAction={secondaryAction}
89
90
  mainAction={mainAction}
91
+ height="auto"
90
92
  >
91
93
  {isOpenPublish ? <S.ModalContent data-testid="publish-modal">{content}</S.ModalContent> : null}
92
94
  </Modal>
@@ -87,6 +87,7 @@ const ListSiteItem = (props: IListSiteItemProps): JSX.Element => {
87
87
  title="Delete Site?"
88
88
  secondaryAction={secondaryDeleteAction}
89
89
  mainAction={mainDeleteAction}
90
+ height="auto"
90
91
  >
91
92
  {isOpenDelete ? (
92
93
  <S.ModalContent data-testid="delete-modal">
@@ -108,6 +109,7 @@ const ListSiteItem = (props: IListSiteItemProps): JSX.Element => {
108
109
  title={title}
109
110
  secondaryAction={secondaryAction}
110
111
  mainAction={mainAction}
112
+ height="auto"
111
113
  >
112
114
  {isOpenPublish ? <S.ModalContent data-testid="publish-modal">{content}</S.ModalContent> : null}
113
115
  </Modal>
@@ -85,7 +85,7 @@ const DateCell = styled(Cell)`
85
85
  `;
86
86
 
87
87
  const ActionsCell = styled(Cell)`
88
- flex: 0;
88
+ flex: 0 0 64px;
89
89
  text-align: center;
90
90
  `;
91
91
 
@@ -79,6 +79,7 @@ const SiteModal = (props: ISiteModalProps): JSX.Element => {
79
79
  title="New Site"
80
80
  mainAction={mainAction}
81
81
  secondaryAction={secondaryAction}
82
+ height="auto"
82
83
  >
83
84
  <ErrorToast size="l" />
84
85
  <S.Form data-testid="site-modal-form">
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useRef, useState } from "react";
2
2
 
3
- import { useModal, usePermission } from "@ax/hooks";
3
+ import { useModals, usePermissionsForSite } from "@ax/hooks";
4
4
  import type { IGetSitesParams, IQueryValue, ISite } from "@ax/types";
5
5
 
6
6
  import type { ISortedListStatus } from "./utils";
@@ -145,13 +145,14 @@ const useSiteActions = ({
145
145
  unpublishSite,
146
146
  getParams,
147
147
  }: IUseSiteActionsParams) => {
148
- const { isOpen: isOpenDelete, toggleModal: toggleDeleteModal } = useModal();
149
- const { isOpen: isOpenPublish, toggleModal: togglePublishModal } = useModal();
148
+ const { isOpen, toggleModal } = useModals(["delete", "publish"]);
150
149
  const [inputValue, setInputValue] = useState("");
151
150
 
152
- const allowedToDeleteSite = usePermission("general.deleteSite");
153
- const allowedToPublishSite = usePermission("general.publishSite");
154
- const allowedToUnpublishSite = usePermission("general.unpublishSite");
151
+ const { allowedToDeleteSite, allowedToPublishSite, allowedToUnpublishSite } = usePermissionsForSite(site.id, {
152
+ allowedToDeleteSite: "general.deleteSite",
153
+ allowedToPublishSite: "general.publishSite",
154
+ allowedToUnpublishSite: "general.unpublishSite",
155
+ });
155
156
 
156
157
  const { updated, isPublished } = site;
157
158
  const publishedState = getPublishedState(isPublished, updated);
@@ -161,7 +162,9 @@ const useSiteActions = ({
161
162
  setHistoryPush("/sites/pages", false);
162
163
  };
163
164
 
164
- const deleteOption = allowedToDeleteSite ? { label: "delete", icon: "delete", action: toggleDeleteModal } : undefined;
165
+ const deleteOption = allowedToDeleteSite
166
+ ? { label: "delete", icon: "delete", action: () => toggleModal("delete") }
167
+ : undefined;
165
168
 
166
169
  const publishOptionProps =
167
170
  isPublished && allowedToUnpublishSite
@@ -171,7 +174,7 @@ const useSiteActions = ({
171
174
  : null;
172
175
 
173
176
  const publishOption = publishOptionProps
174
- ? { label: publishOptionProps.label, icon: publishOptionProps.icon, action: togglePublishModal }
177
+ ? { label: publishOptionProps.label, icon: publishOptionProps.icon, action: () => toggleModal("publish") }
175
178
  : undefined;
176
179
 
177
180
  const menuOptions = [deleteOption, publishOption];
@@ -179,7 +182,7 @@ const useSiteActions = ({
179
182
  const handleDeleteSite = async () => {
180
183
  const params = getParams();
181
184
  await deleteSite(site.id, params);
182
- toggleDeleteModal();
185
+ toggleModal("delete");
183
186
  };
184
187
 
185
188
  const mainDeleteAction = {
@@ -187,24 +190,24 @@ const useSiteActions = ({
187
190
  onClick: handleDeleteSite,
188
191
  disabled: inputValue !== site.name.toUpperCase(),
189
192
  };
190
- const secondaryDeleteAction = { title: "Cancel", onClick: toggleDeleteModal };
193
+ const secondaryDeleteAction = { title: "Cancel", onClick: () => toggleModal("delete") };
191
194
 
192
195
  const handlePublishSite = async () => {
193
196
  const params = getParams();
194
197
  await publishSite(site.id, params);
195
- togglePublishModal();
198
+ toggleModal("publish");
196
199
  };
197
200
 
198
201
  const handleUnpublishSite = async () => {
199
202
  const params = getParams();
200
203
  await unpublishSite(site.id, params);
201
- togglePublishModal();
204
+ toggleModal("publish");
202
205
  };
203
206
 
204
207
  const publishModal = isPublished
205
208
  ? {
206
209
  mainAction: { title: "Unpublish Site", onClick: handleUnpublishSite },
207
- secondaryAction: { title: "Cancel", onClick: togglePublishModal },
210
+ secondaryAction: { title: "Cancel", onClick: () => toggleModal("publish") },
208
211
  title: "Unpublish Site",
209
212
  content: (
210
213
  <p>
@@ -216,7 +219,7 @@ const useSiteActions = ({
216
219
  }
217
220
  : {
218
221
  mainAction: { title: "Publish Site", onClick: handlePublishSite },
219
- secondaryAction: { title: "Cancel", onClick: togglePublishModal },
222
+ secondaryAction: { title: "Cancel", onClick: () => toggleModal("publish") },
220
223
  title: "Publish Site",
221
224
  content: (
222
225
  <p>
@@ -234,12 +237,12 @@ const useSiteActions = ({
234
237
  menuOptions,
235
238
  inputValue,
236
239
  setInputValue,
237
- isOpenDelete,
238
- toggleDeleteModal,
240
+ isOpenDelete: isOpen("delete"),
241
+ toggleDeleteModal: () => toggleModal("delete"),
239
242
  mainDeleteAction,
240
243
  secondaryDeleteAction,
241
- isOpenPublish,
242
- togglePublishModal,
244
+ isOpenPublish: isOpen("publish"),
245
+ togglePublishModal: () => toggleModal("publish"),
243
246
  publishModal,
244
247
  };
245
248
  };
@@ -83,7 +83,7 @@ interface IDispatchProps {
83
83
  getAllDataPacks: () => Promise<void>;
84
84
  getRoles: (params: IGetRoles, token?: string) => Promise<void>;
85
85
  updateCurrentSearch(query: string): Promise<void>;
86
- resetCurrentData(): Promise<void>;
86
+ resetCurrentData(): void;
87
87
  setIsLoading(isLoading: boolean): void;
88
88
  getUserCurrentPermissions(): Promise<void>;
89
89
  getAllSites: () => Promise<void>;
@@ -210,7 +210,7 @@ const Form = (props: IProps) => {
210
210
  const removeItem = () => {
211
211
  deleteStructuredDataContent(form.id).then((deleted: boolean) => {
212
212
  if (deleted) {
213
- setHistoryPush(path, false);
213
+ setHistoryPush(path, true);
214
214
  }
215
215
  });
216
216
  };
@@ -37,7 +37,7 @@ const TableHeader = (props: IProps): JSX.Element => {
37
37
  } = props;
38
38
 
39
39
  const activeColumns = columns.filter((col) => col.show).map((col) => col.id);
40
- const filterCategories = isFromPage ? filterValues.categories : filterValues.related;
40
+ const filterCategories = filterValues ? (isFromPage ? filterValues.categories : filterValues.related) : [];
41
41
 
42
42
  const checkGroups = ["MultiCheckSelect", "AsyncCheckGroup", "CheckGroup"];
43
43
 
@@ -1,5 +1,5 @@
1
1
  import type React from "react";
2
- import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { connect } from "react-redux";
4
4
  import { useHistory, useLocation } from "react-router-dom";
5
5
 
@@ -25,7 +25,7 @@ import {
25
25
  import { sitesActions } from "@ax/containers/Sites";
26
26
  import { structuredDataActions } from "@ax/containers/StructuredData";
27
27
  import { getMaxColumns, updateColumns } from "@ax/helpers";
28
- import { useBulkSelection, useCategoryColors, useModal, usePermission, useToast, useWindowSize } from "@ax/hooks";
28
+ import { useBulkSelection, useCategoryColors, useModals, usePermissions, useToast, useWindowSize } from "@ax/hooks";
29
29
  import type {
30
30
  ICheck,
31
31
  IColumn,
@@ -116,8 +116,6 @@ const StructuredDataList = (props: IProps): JSX.Element => {
116
116
  const lastPage = Math.ceil(totalItems / itemsPerPage);
117
117
  const isLastItem = page === lastPage && currentSitePages.length === 1;
118
118
 
119
- const [structuredDataType, setStructuredDataType] = useState("all");
120
-
121
119
  const [isScrolling, setIsScrolling] = useState(false);
122
120
  const [isBulkLoading, setIsBulkLoading] = useState(false);
123
121
  const [deletedItem, setDeletedItem] = useState<number | number[] | null>(null);
@@ -127,8 +125,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
127
125
  const wrapperRef = useRef<HTMLDivElement>(null);
128
126
  const [isEmpty, setIsEmpty] = useState(false);
129
127
  const [emptyStateProps, setEmptyStateProps] = useState<IEmptyStateProps>({});
130
- const { isOpen: isNewOpen, toggleModal: toggleNewModal } = useModal();
131
- const { isOpen: isDeleteOpen, toggleModal: toggleDeleteModal } = useModal();
128
+ const { isOpen, toggleModal } = useModals(["new", "delete"]);
132
129
  const { sortedListStatus, setSortedListStatus } = useSortedListStatus();
133
130
  const {
134
131
  setFiltersSelection,
@@ -137,10 +134,10 @@ const StructuredDataList = (props: IProps): JSX.Element => {
137
134
  query: currentFilterQuery,
138
135
  } = useFilterQuery(currentStructuredData, contentFilters);
139
136
  const history = useHistory();
137
+ const { state: locationState } = useLocation<{ isFromEditor: boolean }>();
140
138
  const [isFirstRender, setIsFirstRender] = useState(true);
141
139
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
142
140
  const [arePagesTranslated, setArePagesTranslated] = useState(false);
143
- const { state: locationState } = useLocation<{ isFromEditor: boolean }>();
144
141
  const { categoryColors, addCategoryColors } = useCategoryColors();
145
142
  const [notification, setNotification] = useState<{
146
143
  text: string;
@@ -148,15 +145,11 @@ const StructuredDataList = (props: IProps): JSX.Element => {
148
145
  } | null>(null);
149
146
 
150
147
  const isPrivateData = currentStructuredData?.private || false;
151
- const createPermission = isPrivateData
152
- ? "global.content.createPrivateContentTypes"
153
- : "global.globalData.createAllGlobalData";
154
- const exportPermission = isPrivateData
155
- ? "global.content.exportPrivateContentTypes"
156
- : "global.content.exportContentTypes";
157
148
 
158
- const allowedToCreatePages = usePermission(createPermission);
159
- const allowedToExport = usePermission(exportPermission);
149
+ const isAllowedTo = usePermissions({
150
+ createPages: isPrivateData ? "global.content.createPrivateContentTypes" : "global.globalData.createAllGlobalData",
151
+ export: isPrivateData ? "global.content.exportPrivateContentTypes" : "global.content.exportContentTypes",
152
+ });
160
153
 
161
154
  const scope = currentSiteID ? "site" : "global";
162
155
 
@@ -177,8 +170,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
177
170
  : [];
178
171
 
179
172
  const type = currentStructuredData ? currentStructuredData.id : "all";
180
- const columns = getColumns(categoryColumns, isStructuredDataFromPage, isAllPages, maxColumns.value);
181
- const [columnsState, setColumnsState] = useState<Record<string, IColumn[]>>({ all: [], [type]: columns });
173
+ const [columnsState, setColumnsState] = useState<Record<string, IColumn[]>>({});
182
174
 
183
175
  const {
184
176
  resetBulkSelection,
@@ -229,14 +221,20 @@ const StructuredDataList = (props: IProps): JSX.Element => {
229
221
  };
230
222
 
231
223
  const resetFilter = () => {
232
- if (history.action !== "POP" && (!locationState || locationState.isFromEditor !== true)) {
224
+ const isFromEditor = locationState?.isFromEditor === true;
225
+ // Resetea si:
226
+ // (1) Navegación hacia adelante (PUSH) sin venir del editor
227
+ // (2) Back button (POP) y NO vienes del editor (para sincronizar Redux con URL)
228
+ const shouldReset = (history.action === "PUSH" && !isFromEditor) || (history.action === "POP" && !isFromEditor);
229
+
230
+ if (shouldReset) {
233
231
  setFilter("all-pages");
234
232
  resetCurrentData();
235
233
  }
236
234
  };
237
235
 
238
236
  const changeColumnsState = (updatedColumns: any) => {
239
- setColumnsState((state) => ({ ...state, [structuredDataType]: updatedColumns }));
237
+ setColumnsState((state) => ({ ...state, [type]: updatedColumns }));
240
238
  };
241
239
 
242
240
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
@@ -248,19 +246,16 @@ const StructuredDataList = (props: IProps): JSX.Element => {
248
246
  resetForm();
249
247
  }, []);
250
248
 
251
- // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
249
+ // Guardar columnas calculadas en estado cuando cambia el tipo
252
250
  useEffect(() => {
253
- const type = currentStructuredData ? currentStructuredData.id : "all";
254
- setStructuredDataType(type);
255
-
256
- if (!columnsState[type] || type === "all") {
257
- const columns = getColumns(categoryColumns, isStructuredDataFromPage, isAllPages, maxColumns.value);
258
- setColumnsState((state) => ({
259
- ...state,
260
- [type]: columns,
261
- }));
251
+ const cols = getColumns(categoryColumns, isStructuredDataFromPage, isAllPages, maxColumns.value);
252
+ if (cols.length > 0) {
253
+ setColumnsState((state) => {
254
+ if (state[type]?.length > 0) return state; // Mantener si ya existen
255
+ return { ...state, [type]: cols };
256
+ });
262
257
  }
263
- }, [filter]);
258
+ }, [type, categoryColumns, isStructuredDataFromPage, isAllPages, maxColumns]);
264
259
 
265
260
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
266
261
  useEffect(() => {
@@ -301,12 +296,12 @@ const StructuredDataList = (props: IProps): JSX.Element => {
301
296
  } else {
302
297
  emptyState.message = isAllPages
303
298
  ? "You don’t have pages with this content type yet."
304
- : allowedToCreatePages && isDataEditable
299
+ : isAllowedTo.createPages && isDataEditable
305
300
  ? "To start using pages in your site, create as many pages as you need."
306
301
  : undefined;
307
302
  emptyState.button = isAllPages
308
303
  ? "View all content"
309
- : allowedToCreatePages && isDataEditable
304
+ : isAllowedTo.createPages && isDataEditable
310
305
  ? "Create the first page"
311
306
  : undefined;
312
307
  emptyState.action = isAllPages ? resetFilterValues : isDataEditable ? addNewAction : undefined;
@@ -371,7 +366,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
371
366
  ? setPage(previousPage)
372
367
  : isStructuredDataFromPage && handleGetGlobalPages();
373
368
  unselectAllItems();
374
- toggleDeleteModal();
369
+ toggleModal("delete");
375
370
  setIsBulkLoading(false);
376
371
  };
377
372
 
@@ -379,7 +374,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
379
374
  const selectedPages = currentSitePages.filter((page) => selectedItems.all.includes(page.id));
380
375
  const hasTranslations = selectedPages.some((page) => page.pageLanguages.length > 1);
381
376
  setArePagesTranslated(hasTranslations);
382
- toggleDeleteModal();
377
+ toggleModal("delete");
383
378
  };
384
379
 
385
380
  const bulkPublishPage = async (isPublish: boolean) => {
@@ -471,7 +466,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
471
466
  };
472
467
 
473
468
  const exportContent =
474
- isDataExportable && allowedToExport
469
+ isDataExportable && isAllowedTo.export
475
470
  ? async (formats: (number | string)[]) => {
476
471
  if (currentStructuredData && formats.length) {
477
472
  const ids = selectedItems.all.length ? selectedItems.all : undefined;
@@ -480,7 +475,11 @@ const StructuredDataList = (props: IProps): JSX.Element => {
480
475
  }
481
476
  : undefined;
482
477
 
483
- const currentDataColumnsState = currentStructuredData ? columnsState[structuredDataType] || [] : columnsState.all;
478
+ const currentDataColumnsState = useMemo(() => {
479
+ const cols =
480
+ columnsState[type] ?? getColumns(categoryColumns, isStructuredDataFromPage, isAllPages, maxColumns.value);
481
+ return cols;
482
+ }, [type, columnsState, categoryColumns, isStructuredDataFromPage, isAllPages, maxColumns]);
484
483
 
485
484
  const TableHeader = (
486
485
  <BulkHeader
@@ -499,7 +498,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
499
498
  sortItems={sortItems}
500
499
  sortedListStatus={sortedListStatus}
501
500
  filterItems={filterItems}
502
- filterValues={filterValues[structuredDataType]}
501
+ filterValues={filterValues[type] ?? { categories: [], related: [], liveStatus: [], translated: [] }}
503
502
  isAllPages={isAllPages}
504
503
  categoryColumns={categoryColumns}
505
504
  columns={currentDataColumnsState}
@@ -535,9 +534,9 @@ const StructuredDataList = (props: IProps): JSX.Element => {
535
534
  }
536
535
  };
537
536
 
538
- const addNewAction = isAllPages || isStructuredDataFromPage ? toggleNewModal : createNewData;
537
+ const addNewAction = isAllPages || isStructuredDataFromPage ? () => toggleModal("new") : createNewData;
539
538
 
540
- const rightButtonProps = allowedToCreatePages
539
+ const rightButtonProps = isAllowedTo.createPages
541
540
  ? {
542
541
  label: "New",
543
542
  action: addNewAction,
@@ -665,7 +664,7 @@ const StructuredDataList = (props: IProps): JSX.Element => {
665
664
  dynamicValues={structuredData.global}
666
665
  onClick={handleMenuClick}
667
666
  addNew={addNewAction}
668
- isAllowedToCreate={allowedToCreatePages}
667
+ isAllowedToCreate={isAllowedTo.createPages}
669
668
  />
670
669
  <S.TableListWrapper>
671
670
  {!isDataEditable && (
@@ -716,27 +715,27 @@ const StructuredDataList = (props: IProps): JSX.Element => {
716
715
  </TableList>
717
716
  </S.TableListWrapper>
718
717
  </S.StructuredDataWrapper>
719
- {isNewOpen && (
718
+ {isOpen("new") && (
720
719
  <NewContentModal
721
720
  selectPage={addTemplate}
722
721
  filters={options.filters}
723
722
  values={options.values}
724
723
  selectedValue={selectedValue}
725
- isOpen={isNewOpen}
726
- toggleModal={toggleNewModal}
724
+ isOpen={isOpen("new")}
725
+ toggleModal={() => toggleModal("new")}
727
726
  addNewPage={addNewPage}
728
727
  structuredData={structuredData.global}
729
728
  />
730
729
  )}
731
730
  <DeleteModal
732
- isOpen={isDeleteOpen}
733
- toggleModal={toggleDeleteModal}
731
+ isOpen={isOpen("delete")}
732
+ toggleModal={() => toggleModal("delete")}
734
733
  mainModalAction={{
735
734
  title: isBulkLoading ? "Deleting" : "Delete",
736
735
  onClick: bulkDelete,
737
736
  disabled: isBulkLoading,
738
737
  }}
739
- secondaryModalAction={{ title: "Cancel", onClick: toggleDeleteModal }}
738
+ secondaryModalAction={{ title: "Cancel", onClick: () => toggleModal("delete") }}
740
739
  {...{ arePagesTranslated, deleteAllVersions, setDeleteAllVersions }}
741
740
  />
742
741
  {isDataToast && <Toast action={undoDeleteData} setIsVisible={setIsDataToast} message={"Data deleted"} />}
@@ -824,7 +823,7 @@ interface IDispatchProps {
824
823
  setContentFilters(contentFilters: Record<string, Record<string, IQueryValue[]>> | null): void;
825
824
  checkUserSession(): Promise<void>;
826
825
  updateCurrentSearch(query: string): Promise<void>;
827
- resetCurrentData(): Promise<void>;
826
+ resetCurrentData(): void;
828
827
  exportDataContent(structuredDataID: string, data: IExportDataParams): Promise<void>;
829
828
  }
830
829