@griddo/ax 11.10.41 → 11.10.42

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 (79) hide show
  1. package/config/griddo-config/index.js +1 -0
  2. package/package.json +2 -2
  3. package/src/components/BulkSelectionOptions/index.tsx +78 -28
  4. package/src/components/BulkSelectionOptions/style.tsx +7 -1
  5. package/src/components/BulkSelectionOptions/utils.tsx +25 -0
  6. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +2 -2
  7. package/src/components/ConfigPanel/GlobalPageForm/index.tsx +14 -4
  8. package/src/components/ConfigPanel/index.tsx +6 -0
  9. package/src/components/Fields/ComponentArray/SameComponentArray/index.tsx +9 -9
  10. package/src/components/Fields/ReferenceField/ItemList/Item/index.tsx +5 -6
  11. package/src/components/Fields/ReferenceField/ItemList/Item/style.tsx +16 -10
  12. package/src/components/Fields/ReferenceField/ManualPanel/Item/index.tsx +3 -4
  13. package/src/components/Fields/ReferenceField/ManualPanel/Item/style.tsx +7 -1
  14. package/src/components/Fields/VisualUniqueSelection/ScrollableSelection/index.tsx +3 -3
  15. package/src/components/Fields/VisualUniqueSelection/index.tsx +2 -4
  16. package/src/components/FloatingMenu/index.tsx +5 -5
  17. package/src/components/Loader/components/SmallCircle.js +29 -0
  18. package/src/components/Loader/index.tsx +3 -2
  19. package/src/components/MainWrapper/AppBar/atoms.tsx +4 -6
  20. package/src/components/MainWrapper/AppBar/index.tsx +14 -12
  21. package/src/components/MainWrapper/AppBar/style.tsx +23 -18
  22. package/src/components/MainWrapper/index.tsx +3 -3
  23. package/src/components/OcassionalToast/index.tsx +1 -1
  24. package/src/components/OcassionalToast/style.tsx +6 -0
  25. package/src/components/PageFinder/SelectionListItem/index.tsx +5 -4
  26. package/src/components/PageFinder/SelectionListItem/style.tsx +7 -1
  27. package/src/components/PageFinder/index.tsx +1 -0
  28. package/src/constants/index.ts +27 -0
  29. package/src/forms/elements.tsx +8 -24
  30. package/src/forms/index.tsx +0 -2
  31. package/src/helpers/containerEvaluations.tsx +5 -0
  32. package/src/helpers/content.ts +35 -0
  33. package/src/helpers/index.tsx +91 -89
  34. package/src/helpers/strings.tsx +19 -0
  35. package/src/helpers/types.ts +1 -0
  36. package/src/hooks/forms.tsx +2 -1
  37. package/src/locales/en-US.ts +29 -0
  38. package/src/locales/es-ES.ts +29 -0
  39. package/src/locales/index.ts +11 -0
  40. package/src/modules/Categories/CategoriesList/BulkHeader/index.tsx +5 -0
  41. package/src/modules/Categories/CategoriesList/index.tsx +10 -4
  42. package/src/modules/Content/BulkHeader/index.tsx +11 -0
  43. package/src/modules/Content/PageItem/index.tsx +1 -0
  44. package/src/modules/Content/atoms.tsx +1 -1
  45. package/src/modules/Content/index.tsx +27 -2
  46. package/src/modules/FileDrive/BulkGridHeader/index.tsx +6 -1
  47. package/src/modules/FileDrive/BulkListHeader/index.tsx +5 -0
  48. package/src/modules/FileDrive/index.tsx +10 -0
  49. package/src/modules/Forms/FormCategoriesList/BulkHeader/index.tsx +15 -2
  50. package/src/modules/Forms/FormCategoriesList/index.tsx +8 -0
  51. package/src/modules/Forms/FormEditor/index.tsx +10 -1
  52. package/src/modules/Forms/FormList/BulkHeader/index.tsx +5 -0
  53. package/src/modules/Forms/FormList/index.tsx +12 -3
  54. package/src/modules/Forms/FormUseModal/FormUseItem/index.tsx +1 -2
  55. package/src/modules/Forms/FormUseModal/index.tsx +3 -3
  56. package/src/modules/GlobalEditor/index.tsx +1 -0
  57. package/src/modules/MediaGallery/BulkGridHeader/index.tsx +6 -1
  58. package/src/modules/MediaGallery/BulkListHeader/index.tsx +15 -2
  59. package/src/modules/MediaGallery/index.tsx +47 -36
  60. package/src/modules/Navigation/Defaults/BulkHeader/index.tsx +5 -0
  61. package/src/modules/Navigation/Defaults/index.tsx +26 -23
  62. package/src/modules/PageEditor/Editor/index.tsx +6 -0
  63. package/src/modules/PageEditor/index.tsx +52 -0
  64. package/src/modules/Redirects/BulkHeader/index.tsx +6 -1
  65. package/src/modules/Redirects/index.tsx +7 -2
  66. package/src/modules/Settings/Integrations/BulkHeader/index.tsx +5 -0
  67. package/src/modules/Settings/Integrations/index.tsx +7 -1
  68. package/src/modules/Sites/SitesList/ListView/BulkHeader/index.tsx +5 -0
  69. package/src/modules/Sites/SitesList/index.tsx +7 -2
  70. package/src/modules/StructuredData/Form/index.tsx +1 -0
  71. package/src/modules/StructuredData/StructuredDataList/BulkHeader/index.tsx +5 -0
  72. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/atoms.tsx +3 -2
  73. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +17 -11
  74. package/src/modules/StructuredData/StructuredDataList/index.tsx +6 -0
  75. package/src/modules/Users/Roles/BulkHeader/index.tsx +5 -0
  76. package/src/modules/Users/Roles/index.tsx +6 -1
  77. package/src/modules/Users/UserList/BulkHeader/index.tsx +5 -0
  78. package/src/modules/Users/UserList/index.tsx +12 -7
  79. package/tsconfig.paths.json +4 -1
@@ -1,3 +1,5 @@
1
+ import type { ItemLabel } from "@ax/constants";
2
+
1
3
  import { enUS } from "./en-US";
2
4
  import { esES } from "./es-ES";
3
5
 
@@ -8,4 +10,13 @@ const allLocales = {
8
10
  "es-ES": esES,
9
11
  } as const;
10
12
 
13
+ /**
14
+ * Obtiene la traducción correcta basada en la cantidad.
15
+ * @param entry La entrada del objeto de locales
16
+ * @param count El número de elementos (por defecto 1)
17
+ */
18
+ export const pluralize = (entry: ItemLabel, count: number = 1): string => {
19
+ return count === 1 ? entry.one : entry.other;
20
+ };
21
+
11
22
  export const LOCALE = allLocales[currentLocale as keyof typeof allLocales];
@@ -1,4 +1,5 @@
1
1
  import { BulkSelectionOptions } from "@ax/components";
2
+ import { itemLabel } from "@ax/constants";
2
3
  import type { IBulkSelectedItems } from "@ax/hooks";
3
4
  import type { IQueryValue } from "@ax/types";
4
5
 
@@ -17,6 +18,7 @@ const BulkHeader = (props: IProps): JSX.Element => {
17
18
  filterValues,
18
19
  setHoverCheck,
19
20
  selectedItems,
21
+ isLoading,
20
22
  } = props;
21
23
 
22
24
  return showBulk ? (
@@ -26,6 +28,8 @@ const BulkHeader = (props: IProps): JSX.Element => {
26
28
  selectItems={selectItems}
27
29
  totalItems={totalItems}
28
30
  selectedItems={selectedItems}
31
+ isLoading={isLoading}
32
+ itemLabel={itemLabel.CATEGORY}
29
33
  />
30
34
  ) : (
31
35
  <TableHeader
@@ -52,6 +56,7 @@ interface IProps {
52
56
  filterItems: (filterPointer: string, filtersSelected: IQueryValue[]) => void;
53
57
  setHoverCheck: (state: boolean) => void;
54
58
  selectedItems: IBulkSelectedItems;
59
+ isLoading: boolean;
55
60
  }
56
61
 
57
62
  export default BulkHeader;
@@ -46,9 +46,10 @@ import CategoryNav from "./CategoryNav";
46
46
  import CategoryPanel from "./CategoryPanel";
47
47
  import { buildTree, type FlattenedItem, flattenTree, getProjection, removeChildrenOf, setProperty } from "./helpers";
48
48
  import { useFilterQuery } from "./hooks";
49
- import * as S from "./style";
50
49
  import { filterCategoriesAndGroups, getAllLangCategoriesIds } from "./utils";
51
50
 
51
+ import * as S from "./style";
52
+
52
53
  const CategoriesList = (props: IProps): JSX.Element => {
53
54
  const {
54
55
  currentSiteID,
@@ -71,6 +72,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
71
72
  } = props;
72
73
 
73
74
  const [isScrolling, setIsScrolling] = useState(false);
75
+ const [isBulkLoading, setIsBulkLoading] = useState(false);
74
76
  const [deleteGroupCategories, setDeleteGroupCategories] = useState(false);
75
77
  const [searchQuery, setSearchQuery] = useState<string>("");
76
78
  const [items, setItems] = useState<TreeItem[]>(currentDataContent);
@@ -94,7 +96,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
94
96
  const currentCategories = categories[scope].sort(sortBy("title", false));
95
97
  const isEmpty = currentDataContent && currentDataContent.length === 0;
96
98
  const hasCategories = currentCategories.length > 0;
97
- const categoryName = (currentStructuredData && currentStructuredData.title) || "";
99
+ const categoryName = currentStructuredData?.title || "";
98
100
  const availableLanguages = currentSiteID ? siteLanguages : globalLangs;
99
101
  const isTranslatable = currentStructuredData ? currentStructuredData.translate : true;
100
102
  const indentationWidth = 35;
@@ -102,6 +104,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
102
104
  const flattenedItems = useMemo(() => {
103
105
  const flattenedTree = flattenTree(items);
104
106
  const collapsedItems = flattenedTree.reduce<number[]>(
107
+ // biome-ignore lint/performance/noAccumulatingSpread: TODO: fix this
105
108
  (acc, { children, collapsed, id }) => (collapsed && children.length ? [...acc, id] : acc),
106
109
  [],
107
110
  );
@@ -197,6 +200,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
197
200
  : undefined;
198
201
 
199
202
  const bulkDelete = async () => {
203
+ setIsBulkLoading(true);
200
204
  const { groups, categories } = filterCategoriesAndGroups(currentDataContent);
201
205
  const idsCatsToBeDeleted = getAllLangCategoriesIds(categories, selectedItems);
202
206
  const idsGroupsToBeDeleted = getAllLangCategoriesIds(groups, selectedItems);
@@ -210,6 +214,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
210
214
  isDeleteOpen && toggleDeleteModal();
211
215
  isGroupOpen && toggleGroupModal();
212
216
  }
217
+ setIsBulkLoading(false);
213
218
  };
214
219
 
215
220
  const handleToggleDeleteModal = () => {
@@ -257,6 +262,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
257
262
  filterItems={filterItems}
258
263
  setHoverCheck={setHoverCheck}
259
264
  selectedItems={selectedItems}
265
+ isLoading={isBulkLoading}
260
266
  />
261
267
  );
262
268
 
@@ -424,7 +430,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
424
430
  depth={id === activeId && projected ? projected.depth : depth}
425
431
  indentationWidth={indentationWidth}
426
432
  collapsed={Boolean(collapsed && children.length)}
427
- onCollapse={children && children.length ? () => handleCollapse(id) : undefined}
433
+ onCollapse={children?.length ? () => handleCollapse(id) : undefined}
428
434
  />
429
435
  );
430
436
  })}
@@ -461,7 +467,7 @@ const CategoriesList = (props: IProps): JSX.Element => {
461
467
 
462
468
  const mapStateToProps = (state: IRootState) => ({
463
469
  categories: state.structuredData.categories,
464
- currentSiteID: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
470
+ currentSiteID: state.sites.currentSiteInfo?.id ?? null,
465
471
  siteLanguages: state.sites.currentSiteLanguages,
466
472
  globalLangs: state.app.globalLangs,
467
473
  currentDataContent: state.structuredData.currentDataCategory,
@@ -1,4 +1,5 @@
1
1
  import { BulkSelectionOptions } from "@ax/components";
2
+ import { getContentItemLabel } from "@ax/helpers/content";
2
3
  import type { IBulkSelectedItems } from "@ax/hooks";
3
4
  import { usePermission } from "@ax/hooks";
4
5
  import type { IColumn, IQueryValue } from "@ax/types";
@@ -31,6 +32,8 @@ const BulkHeader = (props: IProps): JSX.Element => {
31
32
  exportAction,
32
33
  setHoverCheck,
33
34
  selectedItems,
35
+ isLoading,
36
+ checkFromPage,
34
37
  } = props;
35
38
 
36
39
  const isAllowedToDeletePage = usePermission("content.deletePages");
@@ -68,6 +71,12 @@ const BulkHeader = (props: IProps): JSX.Element => {
68
71
  totalItems={totalItems}
69
72
  exportAction={exportAction}
70
73
  selectedItems={selectedItems}
74
+ isLoading={isLoading}
75
+ itemLabel={getContentItemLabel({
76
+ isPageContentType: checkFromPage,
77
+ isSimpleContentType: isStructuredData,
78
+ isStandardPage: !checkFromPage && !isStructuredData,
79
+ })}
71
80
  />
72
81
  ) : (
73
82
  <TableHeader
@@ -116,6 +125,8 @@ interface IProps {
116
125
  exportAction?(formats: (number | string)[]): void;
117
126
  setHoverCheck: (state: boolean) => void;
118
127
  selectedItems: IBulkSelectedItems;
128
+ isLoading: boolean;
129
+ checkFromPage: boolean;
119
130
  }
120
131
 
121
132
  export default BulkHeader;
@@ -300,6 +300,7 @@ const PageItem = (props: IPageItemProps): JSX.Element => {
300
300
  onClick: handleDuplicatePage,
301
301
  disabled: !modalState.title.trim() || !modalState.slug.trim() || isSaving,
302
302
  };
303
+
303
304
  const secondaryModalAction = { title: "Cancel", onClick: handleCloseDuplicate };
304
305
 
305
306
  const currentLanguages = getCurrentPageLanguages();
@@ -1,6 +1,6 @@
1
1
  import React from "react";
2
2
 
3
- import { IModal, ISite } from "@ax/types";
3
+ import type { IModal, ISite } from "@ax/types";
4
4
  import { Modal, FieldsBehavior, Select, Button } from "@ax/components";
5
5
 
6
6
  import * as S from "./style";
@@ -210,6 +210,7 @@ const Content = (props: IProps): JSX.Element => {
210
210
  const [columnsState, setColumnsState] = useState(initialColumns);
211
211
 
212
212
  const [isDeleting, setIsDeleting] = useState<boolean>(false);
213
+ const [isBulkLoading, setIsBulkLoading] = useState<boolean>(false);
213
214
 
214
215
  const createPermission = isDataPrivate ? "content.createPrivateContentTypes" : "content.createPages";
215
216
  const exportPermission = isDataPrivate ? "content.exportPrivateContentTypes" : "content.exportContentTypes";
@@ -491,6 +492,7 @@ const Content = (props: IProps): JSX.Element => {
491
492
 
492
493
  const bulkDelete = async () => {
493
494
  let allPageVersions: number[] = [];
495
+ setIsBulkLoading(true); // se podría usar setIsDeleting pero por seguridad prefiero usar uno propio
494
496
  setIsDeleting(true);
495
497
  if (deleteAllVersions) {
496
498
  const selectedPages = currentSitePages.filter((page) => selectedItems.all.includes(page.id));
@@ -516,6 +518,7 @@ const Content = (props: IProps): JSX.Element => {
516
518
  const previousPage = page - 1;
517
519
  page > 1 && (isLastItem || allPageItemsSelected) ? setPage(previousPage) : getSiteContent();
518
520
  unselectAllItems();
521
+ setIsBulkLoading(false);
519
522
  setIsDeleting(false);
520
523
  };
521
524
 
@@ -527,6 +530,7 @@ const Content = (props: IProps): JSX.Element => {
527
530
  };
528
531
 
529
532
  const bulkPublishAction = async (isPublish: boolean) => {
533
+ setIsBulkLoading(true);
530
534
  const { notPublished, published, drafts } = selectedItems;
531
535
 
532
536
  if (drafts && drafts.length > 0) {
@@ -555,11 +559,30 @@ const Content = (props: IProps): JSX.Element => {
555
559
 
556
560
  getSiteContent();
557
561
  unselectAllItems();
562
+ setIsBulkLoading(false);
558
563
  };
559
564
 
560
- const bulkPublish = () => (isStructuredData ? setDataStatus(selectedItems.all, "undraft") : bulkPublishAction(true));
565
+ const bulkPublish = () => {
566
+ if (isStructuredData) {
567
+ setIsBulkLoading(true);
568
+ setDataStatus(selectedItems.all, "undraft");
569
+ unselectAllItems();
570
+ setIsBulkLoading(false);
571
+ } else {
572
+ bulkPublishAction(true);
573
+ }
574
+ };
561
575
 
562
- const bulkUnpublish = () => (isStructuredData ? setDataStatus(selectedItems.all, "draft") : bulkPublishAction(false));
576
+ const bulkUnpublish = () => {
577
+ if (isStructuredData) {
578
+ setIsBulkLoading(true);
579
+ setDataStatus(selectedItems.all, "draft");
580
+ unselectAllItems();
581
+ setIsBulkLoading(false);
582
+ } else {
583
+ bulkPublishAction(false);
584
+ }
585
+ };
563
586
 
564
587
  const sortItems = (orderPointer: IQueryValue[], isAscending: boolean) => {
565
588
  setPage(firstPage);
@@ -600,6 +623,7 @@ const Content = (props: IProps): JSX.Element => {
600
623
  sortedListStatus={sortedListStatus}
601
624
  filterItems={filterItems}
602
625
  isEditable={isDataEditable}
626
+ isLoading={isBulkLoading}
603
627
  filterValues={filterValues}
604
628
  categoryColumns={categoryColumns}
605
629
  columns={columnsState}
@@ -610,6 +634,7 @@ const Content = (props: IProps): JSX.Element => {
610
634
  exportAction={exportContent}
611
635
  setHoverCheck={setHoverCheck}
612
636
  selectedItems={selectedItems}
637
+ checkFromPage={checkFromPage}
613
638
  />
614
639
  );
615
640
 
@@ -1,3 +1,4 @@
1
+ import { itemLabel } from "@ax/constants";
1
2
  import type { IBulkSelectedItems } from "@ax/hooks";
2
3
  import type { IBulkAction } from "@ax/types";
3
4
 
@@ -6,7 +7,8 @@ import GridHeader from "./GridHeader";
6
7
  import * as S from "./style";
7
8
 
8
9
  const BulkGridHeader = (props: IBulkHeaderProps): JSX.Element => {
9
- const { showBulk, checkState, selectItems, selectAllItems, totalItems, bulkActions, selectedItems } = props;
10
+ const { showBulk, checkState, selectItems, selectAllItems, totalItems, bulkActions, selectedItems, isLoading } =
11
+ props;
10
12
 
11
13
  return showBulk ? (
12
14
  <S.BulkWrapper>
@@ -17,6 +19,8 @@ const BulkGridHeader = (props: IBulkHeaderProps): JSX.Element => {
17
19
  selectItems={selectItems}
18
20
  totalItems={totalItems}
19
21
  selectedItems={selectedItems}
22
+ isLoading={isLoading}
23
+ itemLabel={itemLabel.FILE}
20
24
  />
21
25
  </S.BulkWrapper>
22
26
  ) : (
@@ -32,6 +36,7 @@ export interface IBulkHeaderProps {
32
36
  totalItems: number;
33
37
  bulkActions: IBulkAction[];
34
38
  selectedItems: IBulkSelectedItems;
39
+ isLoading: boolean;
35
40
  }
36
41
 
37
42
  export default BulkGridHeader;
@@ -1,3 +1,4 @@
1
+ import { itemLabel } from "@ax/constants";
1
2
  import type { IBulkSelectedItems } from "@ax/hooks";
2
3
  import type { IBulkAction } from "@ax/types";
3
4
 
@@ -16,6 +17,7 @@ const BulkListHeader = (props: IBulkHeaderProps): JSX.Element => {
16
17
  isSearching,
17
18
  setHoverCheck,
18
19
  selectedItems,
20
+ isLoading,
19
21
  } = props;
20
22
 
21
23
  return showBulk ? (
@@ -27,6 +29,8 @@ const BulkListHeader = (props: IBulkHeaderProps): JSX.Element => {
27
29
  selectItems={selectItems}
28
30
  totalItems={totalItems}
29
31
  selectedItems={selectedItems}
32
+ isLoading={isLoading}
33
+ itemLabel={itemLabel.FILE}
30
34
  />
31
35
  </S.BulkWrapper>
32
36
  ) : (
@@ -51,6 +55,7 @@ export interface IBulkHeaderProps {
51
55
  isSearching: boolean;
52
56
  setHoverCheck: (state: boolean) => void;
53
57
  selectedItems: IBulkSelectedItems;
58
+ isLoading: boolean;
54
59
  }
55
60
 
56
61
  export default BulkListHeader;
@@ -21,6 +21,7 @@ import {
21
21
  } from "@ax/components";
22
22
  import { fileDriveActions } from "@ax/containers/FileDrive";
23
23
  import { useBulkSelection, useIsDirty, useModal, usePermission, useResizable, useToast } from "@ax/hooks";
24
+ import { LOCALE, pluralize } from "@ax/locales";
24
25
  import type {
25
26
  IBulkAction,
26
27
  IFile,
@@ -78,6 +79,7 @@ const FileDrive = (props: IProps) => {
78
79
  folders,
79
80
  } = currentFolderContent || { files: { totalItems: 0, items: [] }, folders: [] };
80
81
 
82
+ const [isBulkLoading, setIsBulkLoading] = useState(false);
81
83
  const [isPanelOpen, setPanelOpen] = useState(false);
82
84
  const [fileSelected, setFileSelected] = useState<IFile | null>(null);
83
85
  const [selectedFolder, setSelectedFolder] = useState<number>(currentFolderID || 0);
@@ -341,6 +343,7 @@ const FileDrive = (props: IProps) => {
341
343
  };
342
344
 
343
345
  const handleBulkDeleteFile = async () => {
346
+ setIsBulkLoading(true);
344
347
  const isDeleted = await deleteFile(selectedItems.all, siteID);
345
348
  if (isDeleted) {
346
349
  setNumDocs(selectedItems.all.length);
@@ -348,6 +351,7 @@ const FileDrive = (props: IProps) => {
348
351
  toggleDeleteToast();
349
352
  resetBulkSelection();
350
353
  }
354
+ setIsBulkLoading(false);
351
355
  };
352
356
 
353
357
  const handleMoveFile = async (fileID: number, folderID: number) => {
@@ -359,6 +363,7 @@ const FileDrive = (props: IProps) => {
359
363
  };
360
364
 
361
365
  const handleBulkMoveFile = async () => {
366
+ setIsBulkLoading(true);
362
367
  const isMoved = await moveFile(selectedItems.all, selectedFolder, siteID);
363
368
  if (isMoved) {
364
369
  setNumDocs(selectedItems.all.length);
@@ -367,6 +372,7 @@ const FileDrive = (props: IProps) => {
367
372
  setSelectedFolder(0);
368
373
  resetBulkSelection();
369
374
  }
375
+ setIsBulkLoading(false);
370
376
  };
371
377
 
372
378
  const handleBulkEditFile = () => {
@@ -392,8 +398,10 @@ const FileDrive = (props: IProps) => {
392
398
  };
393
399
 
394
400
  const handleDownloadBulk = async () => {
401
+ setIsBulkLoading(true);
395
402
  await downloadFiles(selectedItems.all, true);
396
403
  resetBulkSelection();
404
+ setIsBulkLoading(false);
397
405
  };
398
406
 
399
407
  const handleUpdateCurrentFolder = (folderID: number | null) => {
@@ -460,6 +468,7 @@ const FileDrive = (props: IProps) => {
460
468
  isSearching={isSearching}
461
469
  setHoverCheck={setHoverCheck}
462
470
  selectedItems={selectedItems}
471
+ isLoading={isBulkLoading}
463
472
  />
464
473
  );
465
474
 
@@ -713,6 +722,7 @@ const FileDrive = (props: IProps) => {
713
722
  selectItems={selectItems}
714
723
  bulkActions={bulkActions}
715
724
  selectedItems={selectedItems}
725
+ isLoading={isBulkLoading}
716
726
  />
717
727
  )}
718
728
  {!hasFolders && isRoot && !isSearching && <NewFolderButton />}
@@ -1,3 +1,4 @@
1
+ import { itemLabel } from "@ax/constants";
1
2
  import type { IBulkSelectedItems } from "@ax/hooks";
2
3
 
3
4
  import TableHeader from "./TableHeader";
@@ -5,8 +6,17 @@ import TableHeader from "./TableHeader";
5
6
  import * as S from "./style";
6
7
 
7
8
  const BulkHeader = (props: IProps): JSX.Element => {
8
- const { showBulk, checkState, selectItems, totalItems, isScrolling, bulkActions, setHoverCheck, selectedItems } =
9
- props;
9
+ const {
10
+ showBulk,
11
+ checkState,
12
+ selectItems,
13
+ totalItems,
14
+ isScrolling,
15
+ bulkActions,
16
+ setHoverCheck,
17
+ selectedItems,
18
+ isLoading,
19
+ } = props;
10
20
 
11
21
  return showBulk ? (
12
22
  <S.StyledBulkSelectionOptions
@@ -15,6 +25,8 @@ const BulkHeader = (props: IProps): JSX.Element => {
15
25
  selectItems={selectItems}
16
26
  totalItems={totalItems}
17
27
  selectedItems={selectedItems}
28
+ isLoading={isLoading}
29
+ itemLabel={itemLabel.CATEGORY}
18
30
  />
19
31
  ) : (
20
32
  <TableHeader
@@ -36,6 +48,7 @@ interface IProps {
36
48
  bulkActions: { icon: string; text: string; action: () => void }[];
37
49
  setHoverCheck: (state: boolean) => void;
38
50
  selectedItems: IBulkSelectedItems;
51
+ isLoading: boolean;
39
52
  }
40
53
 
41
54
  export default BulkHeader;
@@ -25,6 +25,7 @@ import { DeleteModal } from "./atoms";
25
25
  import BulkHeader from "./BulkHeader";
26
26
  import CategoryItem from "./CategoryItem";
27
27
  import CategoryPanel from "./CategoryPanel";
28
+
28
29
  import * as S from "./style";
29
30
 
30
31
  const FormCategoriesList = (props: IProps): JSX.Element => {
@@ -41,6 +42,7 @@ const FormCategoriesList = (props: IProps): JSX.Element => {
41
42
  const isSiteView = !!currentSiteID;
42
43
 
43
44
  const [isScrolling, setIsScrolling] = useState(false);
45
+ const [isBulkLoading, setIsBulkLoading] = useState(false);
44
46
  const [draggingId, setDraggingId] = useState<number | null>(null);
45
47
  const [searchQuery, setSearchQuery] = useState<string>("");
46
48
  const { isVisible, toggleToast, setIsVisible, state: toastState } = useToast();
@@ -100,10 +102,14 @@ const FormCategoriesList = (props: IProps): JSX.Element => {
100
102
  : undefined;
101
103
 
102
104
  const bulkDelete = async () => {
105
+ setIsBulkLoading(true);
103
106
  const deleted = await deleteFormCategory(selectedItems.all, category);
104
107
  if (deleted) {
108
+ resetBulkSelection();
109
+ toggleToast(`${selectedItems.all.length} categor${selectedItems.all.length > 1 ? "ies" : "y"} deleted`);
105
110
  toggleDeleteModal();
106
111
  }
112
+ setIsBulkLoading(false);
107
113
  };
108
114
 
109
115
  const selectItems = () => (checkState.isAllSelected ? resetBulkSelection() : selectAllItems());
@@ -135,6 +141,7 @@ const FormCategoriesList = (props: IProps): JSX.Element => {
135
141
  isScrolling={isScrolling}
136
142
  bulkActions={bulkActions}
137
143
  setHoverCheck={setHoverCheck}
144
+ isLoading={isBulkLoading}
138
145
  />
139
146
  );
140
147
 
@@ -260,6 +267,7 @@ const FormCategoriesList = (props: IProps): JSX.Element => {
260
267
 
261
268
  const mapStateToProps = (state: IRootState) => ({
262
269
  categories: state.forms.currentFormCategories,
270
+ // biome-ignore lint/complexity/useOptionalChain: TODO: fix this
263
271
  currentSiteID: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
264
272
  });
265
273
 
@@ -90,7 +90,15 @@ const FormEditor = (props: IProps) => {
90
90
  const isAllowedToDeleteForm = usePermission(deleteFormPermission);
91
91
  const isAllowedToPublishForm = usePermission(publishFormPermission);
92
92
 
93
- const toastMsg = "This form is used in some pages. Changes will apply to all pages where it’s in use.";
93
+ const toastMsg = (
94
+ <>
95
+ This form is used in some pages. Changes will apply to all pages where it’s{" "}
96
+ <button type="button" onClick={toggleUseModal}>
97
+ in use.
98
+ </button>
99
+ </>
100
+ );
101
+
94
102
  const backLinkRoute = isSiteView ? "/sites/forms" : "/forms";
95
103
  const { defaultTheme: theme } = useDefaultTheme();
96
104
 
@@ -293,6 +301,7 @@ const FormEditor = (props: IProps) => {
293
301
  isDirty={isDirty}
294
302
  errors={errors}
295
303
  errorActions={{ goToError }}
304
+ isSaving={isSaving}
296
305
  >
297
306
  {notification && (
298
307
  <S.NotificationWrapper>
@@ -1,4 +1,5 @@
1
1
  import { BulkSelectionOptions } from "@ax/components";
2
+ import { itemLabel } from "@ax/constants";
2
3
  import type { IBulkSelectedItems } from "@ax/hooks";
3
4
  import type { IQueryValue } from "@ax/types";
4
5
 
@@ -21,6 +22,7 @@ const BulkHeader = (props: IProps): JSX.Element => {
21
22
  featuredCategory,
22
23
  bulkActions,
23
24
  siteID,
25
+ isLoading,
24
26
  } = props;
25
27
 
26
28
  return showBulk ? (
@@ -30,6 +32,8 @@ const BulkHeader = (props: IProps): JSX.Element => {
30
32
  selectItems={selectItems}
31
33
  totalItems={totalItems}
32
34
  selectedItems={selectedItems}
35
+ isLoading={isLoading}
36
+ itemLabel={itemLabel.FORM}
33
37
  />
34
38
  ) : (
35
39
  <TableHeader
@@ -64,6 +68,7 @@ interface IProps {
64
68
  filterItems: (filterPointer: string, filtersSelected: IQueryValue[]) => void;
65
69
  setHoverCheck: (state: boolean) => void;
66
70
  selectedItems: IBulkSelectedItems;
71
+ isLoading: boolean;
67
72
  }
68
73
 
69
74
  export default BulkHeader;
@@ -16,6 +16,7 @@ import { formsActions } from "@ax/containers/Forms";
16
16
  import { ITEMS_PER_PAGE } from "@ax/containers/Forms/constants";
17
17
  import { getFormTemplates, getSchemaFormCategories } from "@ax/helpers";
18
18
  import { useBulkSelection, useCategoryColors, useModal, usePermission, useToast } from "@ax/hooks";
19
+ import { LOCALE, pluralize } from "@ax/locales";
19
20
  import type {
20
21
  FormContent,
21
22
  FormState,
@@ -35,9 +36,10 @@ import BulkHeader from "./BulkHeader";
35
36
  import FormItem from "./FormItem";
36
37
  import { useFilterQuery, useSortedListStatus } from "./hooks";
37
38
  import Summary from "./Summary";
38
- import * as S from "./style";
39
39
  import { TemplateModal } from "./TemplateModal";
40
40
 
41
+ import * as S from "./style";
42
+
41
43
  const FormList = (props: IUserListProps): JSX.Element => {
42
44
  const {
43
45
  currentSiteInfo,
@@ -70,6 +72,7 @@ const FormList = (props: IUserListProps): JSX.Element => {
70
72
 
71
73
  const [page, setPage] = useState(firstPage);
72
74
  const [isScrolling, setIsScrolling] = useState(false);
75
+ const [isBulkLoading, setIsBulkLoading] = useState(false);
73
76
  const [selectedTemplate, setSelectedTemplate] = useState<ISchema | null>(null);
74
77
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
75
78
  const [arePagesTranslated, setArePagesTranslated] = useState(false);
@@ -175,14 +178,17 @@ const FormList = (props: IUserListProps): JSX.Element => {
175
178
  };
176
179
 
177
180
  const publishForm = async () => {
181
+ setIsBulkLoading(true);
178
182
  const isUpdated = await updateFormState(selectedItems.all, "active");
179
183
  if (isUpdated) {
180
184
  resetBulkSelection();
181
185
  toggleToast(`${selectedItems.all.length} form${selectedItems.all.length > 1 ? "s" : ""} published`);
182
186
  }
187
+ setIsBulkLoading(false);
183
188
  };
184
189
 
185
190
  const unpublishForm = async () => {
191
+ setIsBulkLoading(true);
186
192
  const isUsed = forms.some((form) => selectedItems.all.includes(form.id) && !!form.formInUse);
187
193
  if (isUsed) {
188
194
  setNotification({
@@ -197,9 +203,11 @@ const FormList = (props: IUserListProps): JSX.Element => {
197
203
  }
198
204
  }
199
205
  isUnpublishOpen && toggleUnpublishModal();
206
+ setIsBulkLoading(false);
200
207
  };
201
208
 
202
209
  const handleDeleteForm = async () => {
210
+ setIsBulkLoading(true);
203
211
  const idsToBeDeleted = getAllLangFormsIds(forms, selectedItems.all, deleteAllVersions);
204
212
  const isUsed = forms.some((form) => idsToBeDeleted.includes(form.id) && !!form.formInUse);
205
213
  if (isUsed) {
@@ -214,6 +222,7 @@ const FormList = (props: IUserListProps): JSX.Element => {
214
222
  }
215
223
  }
216
224
  isDeleteOpen && toggleDeleteModal();
225
+ setIsBulkLoading(false);
217
226
  };
218
227
 
219
228
  const handleToggleBulkDelete = () => {
@@ -269,6 +278,7 @@ const FormList = (props: IUserListProps): JSX.Element => {
269
278
  featuredCategory={featuredCategory}
270
279
  bulkActions={bulkActions}
271
280
  siteID={currentSiteInfo?.id || null}
281
+ isLoading={isBulkLoading}
272
282
  />
273
283
  );
274
284
 
@@ -378,8 +388,7 @@ const FormList = (props: IUserListProps): JSX.Element => {
378
388
  )}
379
389
  </S.EmptyWrapper>
380
390
  ) : (
381
- forms &&
382
- forms.map((form) => {
391
+ forms?.map((form) => {
383
392
  const isItemSelected = isSelected(form.id);
384
393
  return (
385
394
  <FormItem
@@ -1,4 +1,3 @@
1
- import React from "react";
2
1
  import { ElementsTooltip, IconAction } from "@ax/components";
3
2
 
4
3
  import * as S from "./style";
@@ -7,7 +6,7 @@ const FormUseItem = (props: IFormUseItem): JSX.Element => {
7
6
  const { isSiteView, title, subtitle, structuredDataTitle, categoryColors, availableSiteNames, goToItem } = props;
8
7
 
9
8
  return (
10
- <S.Item>
9
+ <S.Item data-testid="form-use-item">
11
10
  <S.PageInfo>
12
11
  <S.Title>{title}</S.Title>
13
12
  <S.SubTitle>{subtitle}</S.SubTitle>
@@ -1,7 +1,7 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
 
4
- import {
4
+ import type {
5
5
  IAvailableSites,
6
6
  IFilterValue,
7
7
  IGetPagesParams,
@@ -80,7 +80,7 @@ const FormUseModal = (props: IFormUseModalModal): JSX.Element => {
80
80
  let pagesData: IPage[] = [];
81
81
  let navsData: INavigation[] = [];
82
82
 
83
- if (page && page.length) {
83
+ if (page?.length) {
84
84
  const params = getParams();
85
85
  const data = await getPages(params);
86
86
 
@@ -153,7 +153,7 @@ const FormUseModal = (props: IFormUseModalModal): JSX.Element => {
153
153
  setHistoryPush("/sites/pages/editor", true);
154
154
  } else {
155
155
  setCurrentPageID(pageID);
156
- setHistoryPush(isSiteView ? "pages/editor" : "/data/pages/editor", true);
156
+ setHistoryPush(isSiteView ? "/sites/pages/editor" : "/data/pages/editor", true);
157
157
  }
158
158
  };
159
159
 
@@ -600,6 +600,7 @@ const GlobalEditor = (props: IProps) => {
600
600
  tabs={tabsPreview}
601
601
  isDirty={isDirty}
602
602
  scheduledPublication={editorContent?.publicationScheduled}
603
+ isSaving={isSaving}
603
604
  >
604
605
  {selectedTab === "edit" ? (
605
606
  <>