@griddo/ax 12.6.1 → 12.6.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.
Files changed (91) hide show
  1. package/config/griddo-config/index.js +0 -1
  2. package/package.json +4 -3
  3. package/src/GlobalStore.tsx +35 -91
  4. package/src/__tests__/hooks/heartbeat.test.tsx +88 -0
  5. package/src/components/MainWrapper/index.tsx +2 -14
  6. package/src/containers/ActivityLog/{thunks.ts → actions.tsx} +89 -104
  7. package/src/containers/ActivityLog/constants.tsx +6 -0
  8. package/src/containers/ActivityLog/index.tsx +3 -16
  9. package/src/containers/ActivityLog/interfaces.tsx +12 -0
  10. package/src/containers/ActivityLog/reducer.tsx +24 -0
  11. package/src/containers/Analytics/actions.tsx +109 -0
  12. package/src/containers/Analytics/constants.tsx +5 -0
  13. package/src/containers/Analytics/index.tsx +3 -14
  14. package/src/containers/Analytics/interfaces.tsx +9 -0
  15. package/src/containers/Analytics/reducer.tsx +28 -0
  16. package/src/containers/Domains/actions.tsx +58 -0
  17. package/src/containers/Domains/constants.tsx +5 -0
  18. package/src/containers/Domains/index.tsx +3 -12
  19. package/src/containers/Domains/interfaces.tsx +9 -0
  20. package/src/containers/Domains/reducer.tsx +22 -0
  21. package/src/containers/Redirects/actions.tsx +190 -0
  22. package/src/containers/Redirects/constants.tsx +17 -1
  23. package/src/containers/Redirects/index.tsx +3 -14
  24. package/src/containers/Redirects/interfaces.tsx +23 -0
  25. package/src/containers/Redirects/reducer.tsx +35 -0
  26. package/src/containers/Settings/Languages/actions.tsx +88 -0
  27. package/src/containers/Settings/Languages/constants.tsx +6 -0
  28. package/src/containers/Settings/Languages/index.tsx +3 -15
  29. package/src/containers/Settings/Languages/reducer.tsx +31 -0
  30. package/src/containers/Settings/Social/actions.tsx +63 -0
  31. package/src/containers/Settings/Social/constants.tsx +3 -0
  32. package/src/containers/Settings/Social/index.tsx +3 -11
  33. package/src/containers/Settings/Social/interfaces.tsx +9 -0
  34. package/src/containers/Settings/Social/reducer.tsx +18 -0
  35. package/src/containers/Sites/actions.tsx +2 -6
  36. package/src/hooks/heartbeat.ts +32 -0
  37. package/src/hooks/index.tsx +2 -0
  38. package/src/index.tsx +6 -7
  39. package/src/modules/ActivityLog/ItemLog/EventItem/index.tsx +27 -13
  40. package/src/modules/ActivityLog/ItemLogUser/UserItem/EventItem/index.tsx +27 -13
  41. package/src/modules/ActivityLog/index.tsx +51 -22
  42. package/src/modules/Analytics/index.tsx +42 -33
  43. package/src/modules/App/Routing/index.tsx +4 -5
  44. package/src/modules/FileDrive/index.tsx +1 -1
  45. package/src/modules/GlobalEditor/index.tsx +6 -8
  46. package/src/modules/GlobalSettings/Robots/index.tsx +47 -24
  47. package/src/modules/MediaGallery/index.tsx +1 -1
  48. package/src/modules/PageEditor/index.tsx +5 -9
  49. package/src/modules/Redirects/RedirectItem/index.tsx +22 -9
  50. package/src/modules/Redirects/RedirectPanel/index.tsx +14 -13
  51. package/src/modules/Redirects/index.tsx +69 -26
  52. package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +3 -4
  53. package/src/modules/Settings/Languages/LanguagePanel/Form/ConnectedField/index.tsx +18 -11
  54. package/src/modules/Settings/Languages/LanguagePanel/Form/index.tsx +9 -7
  55. package/src/modules/Settings/Languages/LanguagePanel/index.tsx +44 -38
  56. package/src/modules/Settings/Languages/Table/Header/index.tsx +19 -12
  57. package/src/modules/Settings/Languages/Table/Item/index.tsx +36 -21
  58. package/src/modules/Settings/Languages/Table/index.tsx +14 -6
  59. package/src/modules/Settings/Languages/index.tsx +30 -18
  60. package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +52 -42
  61. package/src/modules/Settings/Social/index.tsx +12 -17
  62. package/src/modules/StructuredData/StructuredDataList/index.tsx +2 -3
  63. package/src/types/index.tsx +6 -5
  64. package/tsconfig.paths.json +0 -1
  65. package/src/__tests__/modules/ActivityLog/ActivityLog.uc.test.tsx +0 -132
  66. package/src/__tests__/modules/Analytics/Analytics.uc.test.tsx +0 -168
  67. package/src/__tests__/modules/GlobalSettings/Robots/Robots.test.tsx +0 -110
  68. package/src/__tests__/modules/GlobalSettings/Robots/Robots.uc.test.tsx +0 -125
  69. package/src/__tests__/modules/Redirects/Redirects.uc.test.tsx +0 -164
  70. package/src/__tests__/modules/Settings/Languages/Languages.uc.test.tsx +0 -213
  71. package/src/__tests__/modules/Settings/Social/Social.uc.test.tsx +0 -171
  72. package/src/__tests__/store/activityLog.test.ts +0 -301
  73. package/src/__tests__/store/analytics.test.ts +0 -134
  74. package/src/__tests__/store/domains.test.ts +0 -107
  75. package/src/__tests__/store/languages.test.ts +0 -129
  76. package/src/__tests__/store/persist.test.ts +0 -47
  77. package/src/__tests__/store/redirects.test.ts +0 -313
  78. package/src/__tests__/store/social.test.ts +0 -165
  79. package/src/containers/ActivityLog/slice.ts +0 -45
  80. package/src/containers/Analytics/slice.ts +0 -48
  81. package/src/containers/Analytics/thunks.ts +0 -103
  82. package/src/containers/Domains/slice.ts +0 -37
  83. package/src/containers/Domains/thunks.ts +0 -60
  84. package/src/containers/Redirects/slice.ts +0 -60
  85. package/src/containers/Redirects/thunks.ts +0 -196
  86. package/src/containers/Settings/Languages/slice.ts +0 -48
  87. package/src/containers/Settings/Languages/thunks.ts +0 -83
  88. package/src/containers/Settings/Social/slice.ts +0 -40
  89. package/src/containers/Settings/Social/thunks.ts +0 -68
  90. package/src/store/hooks.ts +0 -37
  91. package/src/store/index.ts +0 -2
@@ -1,25 +1,22 @@
1
- import React, { memo, useEffect, useState } from "react";
1
+ import React, { useEffect, useState } from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
- import { ErrorToast, FieldsBehavior, Loading, MainWrapper } from "@ax/components";
4
- import { analyticsActions } from "@ax/containers/Analytics";
4
+ import type { IAnalytics, IDimension, IDimensionsGroup, IRootState } from "@ax/types";
5
5
  import { appActions } from "@ax/containers/App";
6
+ import { analyticsActions } from "@ax/containers/Analytics";
7
+ import { MainWrapper, ErrorToast, Loading, FieldsBehavior } from "@ax/components";
6
8
  import { RouteLeavingGuard } from "@ax/guards";
7
- import { useIsDirty, useModal } from "@ax/hooks";
8
- import { useAppDispatch, useAppSelector } from "@ax/store";
9
- import type { IDimension, IDimensionsGroup } from "@ax/types";
9
+ import { useIsDirty } from "@ax/hooks";
10
+ import { useModal } from "@ax/hooks";
10
11
 
11
12
  import DimensionItem from "./DimensionItem";
12
13
  import DimensionPanel from "./DimensionPanel";
13
14
  import GroupItem from "./GroupItem";
14
15
  import GroupPanel from "./GroupPanel";
15
-
16
16
  import * as S from "./style";
17
17
 
18
- const Analytics = (): JSX.Element => {
19
- const dispatch = useAppDispatch();
20
- const isSaving = useAppSelector((state) => state.app.isSaving);
21
- const isLoading = useAppSelector((state) => state.app.isLoading);
22
- const analytics = useAppSelector((state) => state.analytics);
18
+ const Analytics = (props: IProps): JSX.Element => {
19
+ const { isSaving, isLoading, analytics, getAnalytics, setHistoryPush, updateAnalytics } = props;
23
20
 
24
21
  const [analyticsState, setAnalyticsState] = useState(analytics);
25
22
  const { isDirty, resetDirty } = useIsDirty(analyticsState);
@@ -34,27 +31,16 @@ const Analytics = (): JSX.Element => {
34
31
 
35
32
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
36
33
  useEffect(() => {
37
- // `null` is the global scope, which the API spells "global".
38
- dispatch(analyticsActions.getAnalytics(null));
34
+ const handleGetAnalytics = async () => await getAnalytics();
35
+ handleGetAnalytics();
39
36
  }, []);
40
37
 
41
- // `resetDirty()`, not `setIsDirty(false)`: it also moves useIsDirty's baseline to the
42
- // state being hydrated here. useIsDirty snapshots its baseline during render, so on
43
- // the first visit — nothing persisted for this scope — the store is still the initial
44
- // state and the empty→loaded hop reads as an edit: Save enabled and the leave guard
45
- // armed before the user touches anything, against this screen's own AC6. Same mine as
46
- // Settings/Social (docs/RTK-MIGRATION.md §5).
47
38
  useEffect(() => {
48
39
  setAnalyticsState(analytics);
49
- resetDirty();
50
- }, [analytics, resetDirty]);
40
+ }, [analytics]);
51
41
 
52
42
  const handleSave = async () => {
53
- // `.unwrap()` is what hands over handleRequest's boolean: the dispatch itself
54
- // resolves to the fulfilled action, which would be truthy even on failure.
55
- const isSaved = await dispatch(
56
- analyticsActions.updateAnalytics({ analytics: analyticsState, siteId: null }),
57
- ).unwrap();
43
+ const isSaved = await updateAnalytics(analyticsState);
58
44
  if (isSaved) resetDirty();
59
45
  };
60
46
 
@@ -123,7 +109,7 @@ const Analytics = (): JSX.Element => {
123
109
  changeGroups(updatedGroups);
124
110
  };
125
111
 
126
- const setRoute = (path: string) => dispatch(appActions.setHistoryPush(path));
112
+ const setRoute = (path: string) => setHistoryPush(path);
127
113
  const modalText = (
128
114
  <>
129
115
  Some analytics <strong>are not saved</strong>.{" "}
@@ -137,7 +123,7 @@ const Analytics = (): JSX.Element => {
137
123
  return (
138
124
  <>
139
125
  <RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
140
- <MainWrapper backLink={false} title="Analytics Settings" rightButton={rightButtonProps} screen="analytics-global">
126
+ <MainWrapper backLink={false} title="Analytics Settings" rightButton={rightButtonProps}>
141
127
  <ErrorToast />
142
128
  <S.Wrapper>
143
129
  <S.FormWrapper>
@@ -200,7 +186,30 @@ const Analytics = (): JSX.Element => {
200
186
  );
201
187
  };
202
188
 
203
- // This screen is the global scope of the analytics settings: it reads everything
204
- // from the store and takes no props. `connect` memoised it implicitly; `memo` keeps
205
- // that now it is gone.
206
- export default memo(Analytics);
189
+ const mapStateToProps = (state: IRootState) => ({
190
+ isSaving: state.app.isSaving,
191
+ isLoading: state.app.isLoading,
192
+ analytics: state.analytics,
193
+ });
194
+
195
+ const mapDispatchToProps = {
196
+ setHistoryPush: appActions.setHistoryPush,
197
+ getAnalytics: analyticsActions.getAnalytics,
198
+ updateAnalytics: analyticsActions.updateAnalytics,
199
+ };
200
+
201
+ interface IAnalyticsProps {
202
+ isSaving: boolean;
203
+ isLoading: boolean;
204
+ analytics: IAnalytics;
205
+ }
206
+
207
+ interface IDispatchProps {
208
+ getAnalytics(siteId?: number | null): Promise<void>;
209
+ setHistoryPush(path: string, isEditor?: boolean): void;
210
+ updateAnalytics(analyticsState: IAnalytics, siteId?: number | null): Promise<boolean>;
211
+ }
212
+
213
+ type IProps = IAnalyticsProps & IDispatchProps;
214
+
215
+ export default connect(mapStateToProps, mapDispatchToProps)(Analytics);
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
- import { Switch, Route, matchPath, useLocation } from "react-router-dom";
3
+ import { Switch, Route, matchPath } from "react-router-dom";
4
4
 
5
5
  import { ErrorGuard } from "@ax/guards";
6
6
  import type { IRootState, IUser } from "@ax/types";
@@ -17,10 +17,7 @@ import Logout from "./Logout";
17
17
  import * as S from "./style";
18
18
 
19
19
  const Routing = (props: IProps) => {
20
- const { token, currentUser, globalSettings, setHistoryPush, logout } = props;
21
-
22
- // Was `state.router.location.pathname` from connected-react-router's router slice.
23
- const { pathname: path } = useLocation();
20
+ const { path, token, currentUser, globalSettings, setHistoryPush, logout } = props;
24
21
 
25
22
  const { useForms } = globalSettings;
26
23
 
@@ -115,6 +112,7 @@ const Routing = (props: IProps) => {
115
112
  };
116
113
 
117
114
  const mapStateToProps = (state: IRootState) => ({
115
+ path: state.router.location.pathname,
118
116
  token: state.app.token,
119
117
  currentUser: state.users.currentUser,
120
118
  globalSettings: state.app.globalSettings,
@@ -126,6 +124,7 @@ interface IDispatchProps {
126
124
  }
127
125
 
128
126
  interface IStateProps {
127
+ path?: string;
129
128
  token: string;
130
129
  currentUser: IUser | null;
131
130
  globalSettings: IGlobalSettings;
@@ -605,7 +605,7 @@ const FileDrive = (props: IProps) => {
605
605
  );
606
606
 
607
607
  return (
608
- <MainWrapper backLink={false} title="File Drive Manager" rightButton={rightButtonProps} screen="file-drive">
608
+ <MainWrapper backLink={false} title="File Drive Manager" rightButton={rightButtonProps}>
609
609
  <S.Wrapper ref={wrapperRef}>
610
610
  <S.FolderPanel
611
611
  isOpen={isPanelOpen}
@@ -26,7 +26,7 @@ import { structuredDataActions } from "@ax/containers/StructuredData";
26
26
  import { usersActions } from "@ax/containers/Users";
27
27
  import { RouteLeavingGuard } from "@ax/guards";
28
28
  import { dateToString, getDefaultTheme } from "@ax/helpers";
29
- import { useIsDirty, useModals, usePermissionsForPage } from "@ax/hooks";
29
+ import { useEditingHeartbeat, useIsDirty, useModals, usePermissionsForPage } from "@ax/hooks";
30
30
  import type {
31
31
  HeadingFilter,
32
32
  IErrorItem,
@@ -77,6 +77,7 @@ const GlobalEditor = (props: IProps) => {
77
77
  schemaVersion,
78
78
  updatePageAccessGrants,
79
79
  accessGrants,
80
+ sendPagePing,
80
81
  } = props;
81
82
 
82
83
  const isAllowedTo = usePermissionsForPage(
@@ -153,7 +154,7 @@ const GlobalEditor = (props: IProps) => {
153
154
 
154
155
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
155
156
  useEffect(() => {
156
- const { pageID, getPage, sendPagePing, setStructuredDataFilter } = props;
157
+ const { setStructuredDataFilter } = props;
157
158
 
158
159
  editorContent?.structuredData && setStructuredDataFilter(editorContent.structuredData);
159
160
  const handleGetPage = async () => await getPage(pageID, true);
@@ -163,13 +164,10 @@ const GlobalEditor = (props: IProps) => {
163
164
  if (!pageID) {
164
165
  setIsDirty(false);
165
166
  }
166
-
167
- const interval = setInterval(() => {
168
- pageID && sendPagePing(pageID);
169
- }, 30000);
170
- return () => clearInterval(interval);
171
167
  }, []);
172
168
 
169
+ useEditingHeartbeat(pageID, sendPagePing);
170
+
173
171
  useEffect(() => {
174
172
  setSelectedTab(defaultTab);
175
173
  }, [defaultTab]);
@@ -181,7 +179,7 @@ const GlobalEditor = (props: IProps) => {
181
179
 
182
180
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
183
181
  useEffect(() => {
184
- const { pageID, sendPagePing, currentUserID } = props;
182
+ const { currentUserID } = props;
185
183
  if (userEditing && userEditing.id !== currentUserID) {
186
184
  setIsReadOnly(true);
187
185
  !isOpen("userEditing") && toggleModal("userEditing");
@@ -1,31 +1,35 @@
1
- import { memo, useEffect, useState } from "react";
1
+ import { useEffect, useState } from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
4
  import { ErrorToast, Loading, MainWrapper, Nav } from "@ax/components";
4
5
  import { appActions } from "@ax/containers/App";
5
6
  import { domainsActions } from "@ax/containers/Domains";
6
7
  import { RouteLeavingGuard } from "@ax/guards";
7
8
  import { useIsDirty } from "@ax/hooks";
8
- import { useAppDispatch, useAppSelector } from "@ax/store";
9
- import type { IDomainRobot, INavItem } from "@ax/types";
9
+ import type { IDomainRobot, INavItem, IRootState } from "@ax/types";
10
10
 
11
11
  import Item from "./Item";
12
12
 
13
13
  import * as S from "./style";
14
14
 
15
15
  const Robots = (props: IProps): JSX.Element => {
16
- const { navItems, currentNavItem } = props;
17
-
18
- const dispatch = useAppDispatch();
19
- const isSaving = useAppSelector((state) => state.app.isSaving);
20
- const isLoading = useAppSelector((state) => state.app.isLoading);
21
- const robots = useAppSelector((state) => state.domains.robots);
16
+ const {
17
+ isSaving,
18
+ isLoading,
19
+ navItems,
20
+ currentNavItem,
21
+ robots,
22
+ getDomainsRobots,
23
+ setHistoryPush,
24
+ updateDomainRobots,
25
+ } = props;
22
26
 
23
27
  const [robotItems, setRobotItems] = useState(robots);
24
28
  const { isDirty, setIsDirty, resetDirty } = useIsDirty(robotItems);
25
29
 
26
30
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
27
31
  useEffect(() => {
28
- dispatch(domainsActions.getDomainsRobots());
32
+ getDomainsRobots();
29
33
  resetDirty();
30
34
  }, []);
31
35
 
@@ -34,7 +38,7 @@ const Robots = (props: IProps): JSX.Element => {
34
38
  }, [robots]);
35
39
 
36
40
  const handleSave = async () => {
37
- const isSaved = await dispatch(domainsActions.updateDomainsRobots(robotItems)).unwrap();
41
+ const isSaved = await updateDomainRobots(robotItems);
38
42
  if (isSaved) {
39
43
  setIsDirty(false);
40
44
  }
@@ -47,23 +51,22 @@ const Robots = (props: IProps): JSX.Element => {
47
51
  };
48
52
 
49
53
  const handleMenuClick = (path: string) => {
50
- dispatch(appActions.setHistoryPush(path));
54
+ setHistoryPush(path);
51
55
  };
52
56
 
53
57
  const mapItemList = (items: IDomainRobot[]) =>
54
58
  Array.isArray(items) &&
55
59
  items.map((item: IDomainRobot, i: number) => {
56
- // Replaces the item instead of assigning onto it: the list comes from the
57
- // store, so the previous `newItems[i].content = …` on a shallow copy was
58
- // mutating state — and now that the slice runs through Immer, the object
59
- // is frozen and that assignment would throw.
60
- const handleSetItem = (newContent: string) =>
61
- setRobotItems((current) => current.map((it, index) => (index === i ? { ...it, content: newContent } : it)));
60
+ const handleSetItem = (newContent: string) => {
61
+ const newItems = [...robotItems];
62
+ newItems[i].content = newContent;
63
+ setRobotItems(newItems);
64
+ };
62
65
 
63
66
  return <Item key={`${item.path}${i}`} item={item} setItemContent={handleSetItem} />;
64
67
  });
65
68
 
66
- const setRoute = (path: string) => dispatch(appActions.setHistoryPush(path));
69
+ const setRoute = (path: string) => setHistoryPush(path);
67
70
  const modalText = (
68
71
  <>
69
72
  Some robots <strong>are not saved</strong>.{" "}
@@ -73,7 +76,7 @@ const Robots = (props: IProps): JSX.Element => {
73
76
  return (
74
77
  <>
75
78
  <RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
76
- <MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps} screen="robots">
79
+ <MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps}>
77
80
  <S.Wrapper>
78
81
  <Nav current={currentNavItem} items={navItems} onClick={handleMenuClick} />
79
82
  <S.ContentWrapper>
@@ -90,11 +93,31 @@ const Robots = (props: IProps): JSX.Element => {
90
93
  );
91
94
  };
92
95
 
93
- interface IProps {
96
+ const mapStateToProps = (state: IRootState) => ({
97
+ isSaving: state.app.isSaving,
98
+ isLoading: state.app.isLoading,
99
+ robots: state.domains.robots,
100
+ });
101
+
102
+ const mapDispatchToProps = {
103
+ setHistoryPush: appActions.setHistoryPush,
104
+ getDomainsRobots: domainsActions.getDomainsRobots,
105
+ updateDomainRobots: domainsActions.updateDomainsRobots,
106
+ };
107
+ interface IRobotsProps {
108
+ isSaving: boolean;
109
+ isLoading: boolean;
94
110
  navItems: INavItem[];
95
111
  currentNavItem: INavItem;
112
+ robots: IDomainRobot[];
96
113
  }
97
114
 
98
- // `connect` memoised this screen implicitly; `memo` keeps that now that its props
99
- // come from the parent instead of the store.
100
- export default memo(Robots);
115
+ interface IDispatchProps {
116
+ getDomainsRobots(): void;
117
+ setHistoryPush(path: string, isEditor?: boolean): void;
118
+ updateDomainRobots(robots: IDomainRobot[]): Promise<boolean>;
119
+ }
120
+
121
+ type IProps = IRobotsProps & IDispatchProps;
122
+
123
+ export default connect(mapStateToProps, mapDispatchToProps)(Robots);
@@ -623,7 +623,7 @@ const MediaGallery = (props: IProps) => {
623
623
  );
624
624
 
625
625
  return (
626
- <MainWrapper backLink={false} title="Media Gallery" rightButton={rightButtonProps} screen="gallery">
626
+ <MainWrapper backLink={false} title="Media Gallery" rightButton={rightButtonProps}>
627
627
  <S.Wrapper ref={wrapperRef}>
628
628
  <S.FolderPanel
629
629
  isOpen={isPanelOpen}
@@ -24,7 +24,7 @@ import { pageStatus } from "@ax/containers/PageEditor/interfaces";
24
24
  import { dataPacksActions } from "@ax/containers/Settings/DataPacks";
25
25
  import { RouteLeavingGuard } from "@ax/guards";
26
26
  import { dateToString, getDeactivatedModules, isModuleDisabled } from "@ax/helpers";
27
- import { useIsDirty, useModals, usePermissionsForPage } from "@ax/hooks";
27
+ import { useEditingHeartbeat, useIsDirty, useModals, usePermissionsForPage } from "@ax/hooks";
28
28
  import type {
29
29
  HeadingFilter,
30
30
  IErrorItem,
@@ -80,6 +80,7 @@ const PageEditor = (props: IProps) => {
80
80
  currentSiteInfo,
81
81
  updatePageAccessGrants,
82
82
  accessGrants,
83
+ sendPagePing,
83
84
  } = props;
84
85
 
85
86
  const isAllowedTo = usePermissionsForPage(
@@ -158,8 +159,6 @@ const PageEditor = (props: IProps) => {
158
159
 
159
160
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
160
161
  useEffect(() => {
161
- const { pageID, getPage, sendPagePing } = props;
162
-
163
162
  const handleGetPage = async () => {
164
163
  if (isNewTranslation) {
165
164
  await getDefaults();
@@ -175,20 +174,17 @@ const PageEditor = (props: IProps) => {
175
174
  if (!pageID) {
176
175
  setIsDirty(false);
177
176
  }
178
-
179
- const interval = setInterval(() => {
180
- pageID && sendPagePing(pageID);
181
- }, 30000);
182
- return () => clearInterval(interval);
183
177
  }, []);
184
178
 
179
+ useEditingHeartbeat(pageID, sendPagePing);
180
+
185
181
  useEffect(() => {
186
182
  setSelectedTab(defaultTab);
187
183
  }, [defaultTab]);
188
184
 
189
185
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
190
186
  useEffect(() => {
191
- const { pageID, sendPagePing, currentUserID } = props;
187
+ const { currentUserID } = props;
192
188
  if (userEditing && userEditing.id !== currentUserID) {
193
189
  setIsReadOnly(true);
194
190
  !isOpen("userEditing") && toggleModal("userEditing");
@@ -1,10 +1,10 @@
1
1
  import { useState } from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
4
  import { CheckField, ContextMenu, ElementsTooltip } from "@ax/components";
4
5
  import { redirectsActions } from "@ax/containers/Redirects";
5
6
  import { useContextMenu, useModals } from "@ax/hooks";
6
- import { useAppDispatch, useAppSelector } from "@ax/store";
7
- import type { ICheck, IRedirect } from "@ax/types";
7
+ import type { ICheck, IRedirect, IRootState } from "@ax/types";
8
8
 
9
9
  import { format } from "date-fns";
10
10
 
@@ -13,11 +13,12 @@ import RedirectPanel from "../RedirectPanel";
13
13
 
14
14
  import * as S from "./style";
15
15
 
16
- const RedirectItem = (props: IProps): JSX.Element => {
16
+ const RedirectItem = (props: IRedirectItemProps): JSX.Element => {
17
17
  const {
18
18
  redirect,
19
19
  isSelected,
20
20
  onChange,
21
+ deleteRedirect,
21
22
  toggleToast,
22
23
  setFormValues,
23
24
  formValues,
@@ -25,10 +26,9 @@ const RedirectItem = (props: IProps): JSX.Element => {
25
26
  currentFilter,
26
27
  isSiteItem,
27
28
  hoverCheck,
29
+ isSaving,
28
30
  } = props;
29
31
 
30
- const dispatch = useAppDispatch();
31
- const isSaving = useAppSelector((state) => state.app.isSaving);
32
32
  const { isOpen, toggleModal } = useModals(["edit", "delete"]);
33
33
  const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
34
34
  const [isOpenedSecond, setIsOpenedSecond] = useState(false);
@@ -47,9 +47,7 @@ const RedirectItem = (props: IProps): JSX.Element => {
47
47
  const removeItem = async () => {
48
48
  try {
49
49
  if (redirect.id) {
50
- const deleted = await dispatch(
51
- redirectsActions.deleteRedirect({ redirectID: redirect.id, filter: currentFilter }),
52
- ).unwrap();
50
+ const deleted = await deleteRedirect(redirect.id, currentFilter);
53
51
  if (deleted) {
54
52
  toggleToast();
55
53
  }
@@ -150,6 +148,21 @@ interface IProps {
150
148
  currentFilter: string;
151
149
  isSiteItem: boolean;
152
150
  hoverCheck: boolean;
151
+ isSaving: boolean;
153
152
  }
154
153
 
155
- export default RedirectItem;
154
+ interface IDispatchProps {
155
+ deleteRedirect(redirectID: number, filter?: string): Promise<boolean>;
156
+ }
157
+
158
+ type IRedirectItemProps = IProps & IDispatchProps;
159
+
160
+ const mapStateToProps = (state: IRootState) => ({
161
+ isSaving: state.app.isSaving,
162
+ });
163
+
164
+ const mapDispatchToProps = {
165
+ deleteRedirect: redirectsActions.deleteRedirect,
166
+ };
167
+
168
+ export default connect(mapStateToProps, mapDispatchToProps)(RedirectItem);
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
4
  import type { IRedirect } from "@ax/types";
4
5
  import { Button, FieldsBehavior, FloatingPanel } from "@ax/components";
5
6
  import { redirectsActions } from "@ax/containers/Redirects";
6
- import { useAppDispatch } from "@ax/store";
7
7
 
8
8
  import * as S from "./style";
9
9
 
@@ -15,13 +15,12 @@ const RedirectPanel = (props: IProps): JSX.Element => {
15
15
  isOpenedSecond,
16
16
  toggleSecondaryPanel,
17
17
  addRedirect,
18
+ updateRedirect,
18
19
  formValues,
19
20
  setFormValues,
20
21
  currentFilter,
21
22
  } = props;
22
23
 
23
- const dispatch = useAppDispatch();
24
-
25
24
  const handleOldUrl = (newValue: string) => setFormValues({ ...formValues, from: newValue });
26
25
 
27
26
  const handleNewUrl = (newValue: any) => {
@@ -35,14 +34,7 @@ const RedirectPanel = (props: IProps): JSX.Element => {
35
34
 
36
35
  const editItemAction = async () => {
37
36
  const toPage = formValues.to.pageId ? formValues.to.pageId : formValues.to.url;
38
- formValues.id &&
39
- (await dispatch(
40
- redirectsActions.updateRedirect({
41
- redirect: { from: formValues.from, to: toPage },
42
- redirectID: formValues.id,
43
- filter: currentFilter,
44
- }),
45
- ));
37
+ formValues.id && (await updateRedirect({ from: formValues.from, to: toPage }, formValues.id, currentFilter));
46
38
  toggleModal();
47
39
  };
48
40
 
@@ -105,7 +97,10 @@ const RedirectPanel = (props: IProps): JSX.Element => {
105
97
  );
106
98
  };
107
99
 
108
- interface IProps {
100
+ const mapDispatchToProps = {
101
+ updateRedirect: redirectsActions.updateRedirect,
102
+ };
103
+ interface ICategoryPanelProps {
109
104
  isOpen: boolean;
110
105
  isOpenedSecond: boolean;
111
106
  toggleSecondaryPanel(): any;
@@ -114,7 +109,13 @@ interface IProps {
114
109
  formValues: IRedirect;
115
110
  setFormValues(redirect: IRedirect): void;
116
111
  currentFilter: string;
112
+ }
113
+
114
+ interface IDispatchProps {
117
115
  addRedirect(force?: boolean): void;
116
+ updateRedirect(redirect: { from: string; to: string | number }, redirectID: number, filter?: string): Promise<void>;
118
117
  }
119
118
 
120
- export default RedirectPanel;
119
+ type IProps = ICategoryPanelProps & IDispatchProps;
120
+
121
+ export default connect(null, mapDispatchToProps)(RedirectPanel);