@griddo/ax 12.6.0 → 12.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/config/griddo-config/index.js +1 -0
  2. package/package.json +3 -4
  3. package/src/GlobalStore.tsx +91 -35
  4. package/src/__tests__/modules/ActivityLog/ActivityLog.uc.test.tsx +132 -0
  5. package/src/__tests__/modules/Analytics/Analytics.uc.test.tsx +168 -0
  6. package/src/__tests__/modules/GlobalSettings/Robots/Robots.test.tsx +110 -0
  7. package/src/__tests__/modules/GlobalSettings/Robots/Robots.uc.test.tsx +125 -0
  8. package/src/__tests__/modules/Redirects/Redirects.uc.test.tsx +164 -0
  9. package/src/__tests__/modules/Settings/Languages/Languages.uc.test.tsx +213 -0
  10. package/src/__tests__/modules/Settings/Social/Social.uc.test.tsx +171 -0
  11. package/src/__tests__/store/activityLog.test.ts +301 -0
  12. package/src/__tests__/store/analytics.test.ts +134 -0
  13. package/src/__tests__/store/domains.test.ts +107 -0
  14. package/src/__tests__/store/languages.test.ts +129 -0
  15. package/src/__tests__/store/persist.test.ts +47 -0
  16. package/src/__tests__/store/redirects.test.ts +313 -0
  17. package/src/__tests__/store/social.test.ts +165 -0
  18. package/src/components/MainWrapper/index.tsx +14 -2
  19. package/src/containers/ActivityLog/index.tsx +16 -3
  20. package/src/containers/ActivityLog/slice.ts +45 -0
  21. package/src/containers/ActivityLog/{actions.tsx → thunks.ts} +104 -89
  22. package/src/containers/Analytics/index.tsx +14 -3
  23. package/src/containers/Analytics/slice.ts +48 -0
  24. package/src/containers/Analytics/thunks.ts +103 -0
  25. package/src/containers/Domains/index.tsx +12 -3
  26. package/src/containers/Domains/slice.ts +37 -0
  27. package/src/containers/Domains/thunks.ts +60 -0
  28. package/src/containers/Redirects/constants.tsx +1 -17
  29. package/src/containers/Redirects/index.tsx +14 -3
  30. package/src/containers/Redirects/slice.ts +60 -0
  31. package/src/containers/Redirects/thunks.ts +196 -0
  32. package/src/containers/Settings/Languages/index.tsx +15 -3
  33. package/src/containers/Settings/Languages/slice.ts +48 -0
  34. package/src/containers/Settings/Languages/thunks.ts +83 -0
  35. package/src/containers/Settings/Social/index.tsx +11 -3
  36. package/src/containers/Settings/Social/slice.ts +40 -0
  37. package/src/containers/Settings/Social/thunks.ts +68 -0
  38. package/src/containers/Sites/actions.tsx +6 -2
  39. package/src/index.tsx +7 -6
  40. package/src/modules/ActivityLog/ItemLog/EventItem/index.tsx +13 -27
  41. package/src/modules/ActivityLog/ItemLogUser/UserItem/EventItem/index.tsx +13 -27
  42. package/src/modules/ActivityLog/index.tsx +22 -51
  43. package/src/modules/Analytics/index.tsx +33 -42
  44. package/src/modules/App/Routing/index.tsx +5 -4
  45. package/src/modules/FileDrive/index.tsx +1 -1
  46. package/src/modules/GlobalSettings/Robots/index.tsx +24 -47
  47. package/src/modules/MediaGallery/index.tsx +1 -1
  48. package/src/modules/Redirects/RedirectItem/index.tsx +9 -22
  49. package/src/modules/Redirects/RedirectPanel/index.tsx +13 -14
  50. package/src/modules/Redirects/index.tsx +26 -69
  51. package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +4 -3
  52. package/src/modules/Settings/Languages/LanguagePanel/Form/ConnectedField/index.tsx +11 -18
  53. package/src/modules/Settings/Languages/LanguagePanel/Form/index.tsx +7 -9
  54. package/src/modules/Settings/Languages/LanguagePanel/index.tsx +38 -44
  55. package/src/modules/Settings/Languages/Table/Header/index.tsx +12 -19
  56. package/src/modules/Settings/Languages/Table/Item/index.tsx +21 -36
  57. package/src/modules/Settings/Languages/Table/index.tsx +6 -14
  58. package/src/modules/Settings/Languages/index.tsx +18 -30
  59. package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +42 -52
  60. package/src/modules/Settings/Social/index.tsx +17 -12
  61. package/src/modules/StructuredData/StructuredDataList/index.tsx +3 -2
  62. package/src/store/hooks.ts +37 -0
  63. package/src/store/index.ts +2 -0
  64. package/src/types/index.tsx +5 -6
  65. package/tsconfig.paths.json +1 -0
  66. package/src/containers/ActivityLog/constants.tsx +0 -6
  67. package/src/containers/ActivityLog/interfaces.tsx +0 -12
  68. package/src/containers/ActivityLog/reducer.tsx +0 -24
  69. package/src/containers/Analytics/actions.tsx +0 -109
  70. package/src/containers/Analytics/constants.tsx +0 -5
  71. package/src/containers/Analytics/interfaces.tsx +0 -9
  72. package/src/containers/Analytics/reducer.tsx +0 -28
  73. package/src/containers/Domains/actions.tsx +0 -58
  74. package/src/containers/Domains/constants.tsx +0 -5
  75. package/src/containers/Domains/interfaces.tsx +0 -9
  76. package/src/containers/Domains/reducer.tsx +0 -22
  77. package/src/containers/Redirects/actions.tsx +0 -190
  78. package/src/containers/Redirects/interfaces.tsx +0 -23
  79. package/src/containers/Redirects/reducer.tsx +0 -35
  80. package/src/containers/Settings/Languages/actions.tsx +0 -88
  81. package/src/containers/Settings/Languages/constants.tsx +0 -6
  82. package/src/containers/Settings/Languages/reducer.tsx +0 -31
  83. package/src/containers/Settings/Social/actions.tsx +0 -63
  84. package/src/containers/Settings/Social/constants.tsx +0 -3
  85. package/src/containers/Settings/Social/interfaces.tsx +0 -9
  86. package/src/containers/Settings/Social/reducer.tsx +0 -18
@@ -1,6 +1,5 @@
1
1
  import type React from "react";
2
2
  import { useState } from "react";
3
- import { connect } from "react-redux";
4
3
 
5
4
  import { RestoreModal } from "@ax/components";
6
5
  import { activityLogActions } from "@ax/containers/ActivityLog";
@@ -8,9 +7,9 @@ import { navigationActions } from "@ax/containers/Navigation";
8
7
  import { pageEditorActions } from "@ax/containers/PageEditor";
9
8
  import { structuredDataActions } from "@ax/containers/StructuredData";
10
9
  import { useModal } from "@ax/hooks";
11
- import type { IRootState } from "@ax/types";
10
+ import { useAppDispatch, useAppSelector } from "@ax/store";
12
11
 
13
- import type { LogActivityDTO, LogContentDTO, LogContentTypeDTO } from "@griddo/api-types";
12
+ import type { LogActivityDTO } from "@griddo/api-types";
14
13
 
15
14
  import { eventKey } from "./../../../constants";
16
15
  import DetailModal from "./../../../DetailModal";
@@ -19,7 +18,10 @@ import { getContentTypeSchemaVersion, getPageSchemaVersion } from "./../../../ut
19
18
  import * as S from "./style";
20
19
 
21
20
  const EventItem = (props: IEventItemProps) => {
22
- const { item, schemaVersion, restorePage, restoreNavigation, restoreStructuredData, goToLogContent, getLog } = props;
21
+ const { item, getLog } = props;
22
+
23
+ const dispatch = useAppDispatch();
24
+ const schemaVersion = useAppSelector((state) => state.structuredData.schemaVersion);
23
25
 
24
26
  const { created, site, contentType, content, eventType } = item;
25
27
 
@@ -33,14 +35,14 @@ const EventItem = (props: IEventItemProps) => {
33
35
  switch (type) {
34
36
  case eventKey.DELETED_PAGE:
35
37
  case eventKey.DELETED_CONTENT_TYPE_PAGE:
36
- return await restorePage(id);
38
+ return await dispatch(pageEditorActions.restorePage(id));
37
39
  case eventKey.DELETED_HEADER:
38
- return await restoreNavigation(id, "header");
40
+ return await dispatch(navigationActions.restoreNavigation(id, "header"));
39
41
  case eventKey.DELETED_FOOTER:
40
- return await restoreNavigation(id, "footer");
42
+ return await dispatch(navigationActions.restoreNavigation(id, "footer"));
41
43
  case eventKey.DELETED_CATEGORY:
42
44
  case eventKey.DELETED_CONTENT_TYPE:
43
- return await restoreStructuredData(id);
45
+ return await dispatch(structuredDataActions.restoreStructuredDataContent(id));
44
46
  default:
45
47
  return false;
46
48
  }
@@ -84,12 +86,12 @@ const EventItem = (props: IEventItemProps) => {
84
86
  const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
85
87
  e.stopPropagation();
86
88
  const siteID = site?.id || null;
87
- goToLogContent(contentType, content, siteID);
89
+ dispatch(activityLogActions.goToLogContent({ contentType, content, site: siteID }));
88
90
  };
89
91
 
90
92
  const handleKeyDown = () => {
91
93
  const siteID = site?.id || null;
92
- goToLogContent(contentType, content, siteID);
94
+ dispatch(activityLogActions.goToLogContent({ contentType, content, site: siteID }));
93
95
  };
94
96
 
95
97
  return (
@@ -151,23 +153,7 @@ const EventItem = (props: IEventItemProps) => {
151
153
 
152
154
  interface IEventItemProps {
153
155
  item: LogActivityDTO;
154
- schemaVersion: string;
155
- restorePage(id: number): Promise<boolean>;
156
- restoreNavigation(navID: number, type: string): Promise<boolean>;
157
- restoreStructuredData(dataID: number): Promise<boolean>;
158
- goToLogContent(contentType: LogContentTypeDTO | null, content: LogContentDTO | null, site: number | null): void;
159
156
  getLog(): void;
160
157
  }
161
158
 
162
- const mapStateToProps = (state: IRootState) => ({
163
- schemaVersion: state.structuredData.schemaVersion,
164
- });
165
-
166
- const mapDispatchToProps = {
167
- restorePage: pageEditorActions.restorePage,
168
- restoreNavigation: navigationActions.restoreNavigation,
169
- restoreStructuredData: structuredDataActions.restoreStructuredDataContent,
170
- goToLogContent: activityLogActions.goToLogContent,
171
- };
172
-
173
- export default connect(mapStateToProps, mapDispatchToProps)(EventItem);
159
+ export default EventItem;
@@ -1,33 +1,27 @@
1
1
  import React, { useCallback, useEffect, useRef, useState } from "react";
2
- import { connect } from "react-redux";
3
- import { format, subDays } from "date-fns";
4
2
 
5
- import type { LogActivityExportRequest, LogActivityPaginationRequest } from "@griddo/api-types";
6
- import type { ILogItemsByDay, ILogItemsByUser, IQueryValue, IRootState, IStructuredData } from "@ax/types";
7
3
  import { EmptyState, ErrorToast, FilterTagsBar, MainWrapper, SearchTagsBar, Select, TableList } from "@ax/components";
8
4
  import { activityLogActions } from "@ax/containers/ActivityLog";
9
5
  import { structuredDataActions } from "@ax/containers/StructuredData";
10
6
  import { useModal } from "@ax/hooks";
7
+ import { useAppDispatch, useAppSelector } from "@ax/store";
8
+ import type { ILogItemsByDay, ILogItemsByUser, IQueryValue } from "@ax/types";
11
9
 
10
+ import type { LogActivityExportRequest, LogActivityPaginationRequest } from "@griddo/api-types";
11
+ import { format, subDays } from "date-fns";
12
+
13
+ import DownloadModal from "./DownloadModal";
12
14
  import { useFilterQuery } from "./hooks";
13
- import TableHeader from "./TableHeader";
14
15
  import ItemLog from "./ItemLog";
15
16
  import ItemLogUser from "./ItemLogUser";
16
- import DownloadModal from "./DownloadModal";
17
+ import TableHeader from "./TableHeader";
17
18
 
18
19
  import * as S from "./style";
19
20
 
20
- const ActivityLog = (props: IActivityLogProps) => {
21
- const {
22
- activityLog,
23
- listMode,
24
- setListMode,
25
- getActivityLog,
26
- getActivityLogByUser,
27
- downloadActivityLog,
28
- getStructuredData,
29
- setCurrentData,
30
- } = props;
21
+ const ActivityLog = () => {
22
+ const dispatch = useAppDispatch();
23
+ const activityLog = useAppSelector((state) => state.activityLog.activityLog);
24
+ const listMode = useAppSelector((state) => state.activityLog.listMode);
31
25
 
32
26
  const itemsPerPage = 100;
33
27
  const firstPage = 1;
@@ -68,13 +62,14 @@ const ActivityLog = (props: IActivityLogProps) => {
68
62
  const handleGetLog = async () => {
69
63
  const params = getParams();
70
64
  if (isModeUser) {
71
- await getActivityLogByUser(params);
65
+ await dispatch(activityLogActions.getActivityLogByUser(params));
72
66
  } else {
73
- await getActivityLog(params);
67
+ await dispatch(activityLogActions.getActivityLog(params));
74
68
  }
75
69
  };
76
70
 
77
- const handleUpdateStructuredDate = async () => await getStructuredData(null, null, false);
71
+ const handleUpdateStructuredDate = async () =>
72
+ await dispatch(structuredDataActions.getStructuredData(null, null, false));
78
73
 
79
74
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
80
75
  useEffect(() => {
@@ -84,7 +79,7 @@ const ActivityLog = (props: IActivityLogProps) => {
84
79
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
85
80
  useEffect(() => {
86
81
  handleUpdateStructuredDate();
87
- setCurrentData(null);
82
+ dispatch(structuredDataActions.setCurrentData(null));
88
83
  }, []);
89
84
 
90
85
  const onScroll = (e: any) => setIsScrolling(e.target.scrollTop > 0);
@@ -92,11 +87,11 @@ const ActivityLog = (props: IActivityLogProps) => {
92
87
  const handleSelectChange = async (value: string) => {
93
88
  const params = getParams();
94
89
  if (value === "user") {
95
- await getActivityLogByUser({ ...params, page: firstPage });
90
+ await dispatch(activityLogActions.getActivityLogByUser({ ...params, page: firstPage }));
96
91
  } else {
97
- await getActivityLog({ ...params, page: firstPage });
92
+ await dispatch(activityLogActions.getActivityLog({ ...params, page: firstPage }));
98
93
  }
99
- setListMode(value);
94
+ dispatch(activityLogActions.setListMode(value));
100
95
  };
101
96
 
102
97
  const sortItems = async (isAscending: boolean) => {
@@ -139,7 +134,7 @@ const ActivityLog = (props: IActivityLogProps) => {
139
134
  format: [downloadForm.format],
140
135
  };
141
136
 
142
- await downloadActivityLog(data);
137
+ await dispatch(activityLogActions.downloadActivityLog(data));
143
138
 
144
139
  toggleDownloadModal();
145
140
  setDownloadForm(initialState);
@@ -211,6 +206,7 @@ const ActivityLog = (props: IActivityLogProps) => {
211
206
  return (
212
207
  <MainWrapper
213
208
  title="Logs"
209
+ screen="activity-log"
214
210
  rightButton={rightButtonProps}
215
211
  rightLineButton={rightLineButtonProps}
216
212
  searchAction={handleSearch}
@@ -294,29 +290,4 @@ export interface IDownloadFormState {
294
290
  endDate: string | null;
295
291
  }
296
292
 
297
- interface IActivityLogProps {
298
- activityLog: ILogItemsByDay | ILogItemsByUser;
299
- listMode: string;
300
- getActivityLog(params: LogActivityPaginationRequest): Promise<void>;
301
- getActivityLogByUser(params: LogActivityPaginationRequest): Promise<void>;
302
- setListMode(listMode: string): void;
303
- downloadActivityLog(data: LogActivityExportRequest): Promise<void>;
304
- getStructuredData(token: string | null, siteID?: number | null, hasLoading?: boolean): Promise<void>;
305
- setCurrentData(currentStructuredData: IStructuredData | null): void;
306
- }
307
-
308
- const mapStateToProps = (state: IRootState) => ({
309
- activityLog: state.activityLog.activityLog,
310
- listMode: state.activityLog.listMode,
311
- });
312
-
313
- const mapDispatchToProps = {
314
- getActivityLog: activityLogActions.getActivityLog,
315
- getActivityLogByUser: activityLogActions.getActivityLogByUser,
316
- setListMode: activityLogActions.setListMode,
317
- downloadActivityLog: activityLogActions.downloadActivityLog,
318
- getStructuredData: structuredDataActions.getStructuredData,
319
- setCurrentData: structuredDataActions.setCurrentData,
320
- };
321
-
322
- export default connect(mapStateToProps, mapDispatchToProps)(ActivityLog);
293
+ export default ActivityLog;
@@ -1,22 +1,25 @@
1
- import React, { useEffect, useState } from "react";
2
- import { connect } from "react-redux";
1
+ import React, { memo, useEffect, useState } from "react";
3
2
 
4
- import type { IAnalytics, IDimension, IDimensionsGroup, IRootState } from "@ax/types";
5
- import { appActions } from "@ax/containers/App";
3
+ import { ErrorToast, FieldsBehavior, Loading, MainWrapper } from "@ax/components";
6
4
  import { analyticsActions } from "@ax/containers/Analytics";
7
- import { MainWrapper, ErrorToast, Loading, FieldsBehavior } from "@ax/components";
5
+ import { appActions } from "@ax/containers/App";
8
6
  import { RouteLeavingGuard } from "@ax/guards";
9
- import { useIsDirty } from "@ax/hooks";
10
- import { useModal } from "@ax/hooks";
7
+ import { useIsDirty, useModal } from "@ax/hooks";
8
+ import { useAppDispatch, useAppSelector } from "@ax/store";
9
+ import type { IDimension, IDimensionsGroup } from "@ax/types";
11
10
 
12
11
  import DimensionItem from "./DimensionItem";
13
12
  import DimensionPanel from "./DimensionPanel";
14
13
  import GroupItem from "./GroupItem";
15
14
  import GroupPanel from "./GroupPanel";
15
+
16
16
  import * as S from "./style";
17
17
 
18
- const Analytics = (props: IProps): JSX.Element => {
19
- const { isSaving, isLoading, analytics, getAnalytics, setHistoryPush, updateAnalytics } = props;
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);
20
23
 
21
24
  const [analyticsState, setAnalyticsState] = useState(analytics);
22
25
  const { isDirty, resetDirty } = useIsDirty(analyticsState);
@@ -31,16 +34,27 @@ const Analytics = (props: IProps): JSX.Element => {
31
34
 
32
35
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
33
36
  useEffect(() => {
34
- const handleGetAnalytics = async () => await getAnalytics();
35
- handleGetAnalytics();
37
+ // `null` is the global scope, which the API spells "global".
38
+ dispatch(analyticsActions.getAnalytics(null));
36
39
  }, []);
37
40
 
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).
38
47
  useEffect(() => {
39
48
  setAnalyticsState(analytics);
40
- }, [analytics]);
49
+ resetDirty();
50
+ }, [analytics, resetDirty]);
41
51
 
42
52
  const handleSave = async () => {
43
- const isSaved = await updateAnalytics(analyticsState);
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();
44
58
  if (isSaved) resetDirty();
45
59
  };
46
60
 
@@ -109,7 +123,7 @@ const Analytics = (props: IProps): JSX.Element => {
109
123
  changeGroups(updatedGroups);
110
124
  };
111
125
 
112
- const setRoute = (path: string) => setHistoryPush(path);
126
+ const setRoute = (path: string) => dispatch(appActions.setHistoryPush(path));
113
127
  const modalText = (
114
128
  <>
115
129
  Some analytics <strong>are not saved</strong>.{" "}
@@ -123,7 +137,7 @@ const Analytics = (props: IProps): JSX.Element => {
123
137
  return (
124
138
  <>
125
139
  <RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
126
- <MainWrapper backLink={false} title="Analytics Settings" rightButton={rightButtonProps}>
140
+ <MainWrapper backLink={false} title="Analytics Settings" rightButton={rightButtonProps} screen="analytics-global">
127
141
  <ErrorToast />
128
142
  <S.Wrapper>
129
143
  <S.FormWrapper>
@@ -186,30 +200,7 @@ const Analytics = (props: IProps): JSX.Element => {
186
200
  );
187
201
  };
188
202
 
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);
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);
@@ -1,6 +1,6 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { connect } from "react-redux";
3
- import { Switch, Route, matchPath } from "react-router-dom";
3
+ import { Switch, Route, matchPath, useLocation } from "react-router-dom";
4
4
 
5
5
  import { ErrorGuard } from "@ax/guards";
6
6
  import type { IRootState, IUser } from "@ax/types";
@@ -17,7 +17,10 @@ import Logout from "./Logout";
17
17
  import * as S from "./style";
18
18
 
19
19
  const Routing = (props: IProps) => {
20
- const { path, token, currentUser, globalSettings, setHistoryPush, logout } = props;
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();
21
24
 
22
25
  const { useForms } = globalSettings;
23
26
 
@@ -112,7 +115,6 @@ const Routing = (props: IProps) => {
112
115
  };
113
116
 
114
117
  const mapStateToProps = (state: IRootState) => ({
115
- path: state.router.location.pathname,
116
118
  token: state.app.token,
117
119
  currentUser: state.users.currentUser,
118
120
  globalSettings: state.app.globalSettings,
@@ -124,7 +126,6 @@ interface IDispatchProps {
124
126
  }
125
127
 
126
128
  interface IStateProps {
127
- path?: string;
128
129
  token: string;
129
130
  currentUser: IUser | null;
130
131
  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}>
608
+ <MainWrapper backLink={false} title="File Drive Manager" rightButton={rightButtonProps} screen="file-drive">
609
609
  <S.Wrapper ref={wrapperRef}>
610
610
  <S.FolderPanel
611
611
  isOpen={isPanelOpen}
@@ -1,35 +1,31 @@
1
- import { useEffect, useState } from "react";
2
- import { connect } from "react-redux";
1
+ import { memo, useEffect, useState } from "react";
3
2
 
4
3
  import { ErrorToast, Loading, MainWrapper, Nav } from "@ax/components";
5
4
  import { appActions } from "@ax/containers/App";
6
5
  import { domainsActions } from "@ax/containers/Domains";
7
6
  import { RouteLeavingGuard } from "@ax/guards";
8
7
  import { useIsDirty } from "@ax/hooks";
9
- import type { IDomainRobot, INavItem, IRootState } from "@ax/types";
8
+ import { useAppDispatch, useAppSelector } from "@ax/store";
9
+ import type { IDomainRobot, INavItem } 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 {
17
- isSaving,
18
- isLoading,
19
- navItems,
20
- currentNavItem,
21
- robots,
22
- getDomainsRobots,
23
- setHistoryPush,
24
- updateDomainRobots,
25
- } = props;
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);
26
22
 
27
23
  const [robotItems, setRobotItems] = useState(robots);
28
24
  const { isDirty, setIsDirty, resetDirty } = useIsDirty(robotItems);
29
25
 
30
26
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
31
27
  useEffect(() => {
32
- getDomainsRobots();
28
+ dispatch(domainsActions.getDomainsRobots());
33
29
  resetDirty();
34
30
  }, []);
35
31
 
@@ -38,7 +34,7 @@ const Robots = (props: IProps): JSX.Element => {
38
34
  }, [robots]);
39
35
 
40
36
  const handleSave = async () => {
41
- const isSaved = await updateDomainRobots(robotItems);
37
+ const isSaved = await dispatch(domainsActions.updateDomainsRobots(robotItems)).unwrap();
42
38
  if (isSaved) {
43
39
  setIsDirty(false);
44
40
  }
@@ -51,22 +47,23 @@ const Robots = (props: IProps): JSX.Element => {
51
47
  };
52
48
 
53
49
  const handleMenuClick = (path: string) => {
54
- setHistoryPush(path);
50
+ dispatch(appActions.setHistoryPush(path));
55
51
  };
56
52
 
57
53
  const mapItemList = (items: IDomainRobot[]) =>
58
54
  Array.isArray(items) &&
59
55
  items.map((item: IDomainRobot, i: number) => {
60
- const handleSetItem = (newContent: string) => {
61
- const newItems = [...robotItems];
62
- newItems[i].content = newContent;
63
- setRobotItems(newItems);
64
- };
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)));
65
62
 
66
63
  return <Item key={`${item.path}${i}`} item={item} setItemContent={handleSetItem} />;
67
64
  });
68
65
 
69
- const setRoute = (path: string) => setHistoryPush(path);
66
+ const setRoute = (path: string) => dispatch(appActions.setHistoryPush(path));
70
67
  const modalText = (
71
68
  <>
72
69
  Some robots <strong>are not saved</strong>.{" "}
@@ -76,7 +73,7 @@ const Robots = (props: IProps): JSX.Element => {
76
73
  return (
77
74
  <>
78
75
  <RouteLeavingGuard when={isDirty} action={setRoute} text={modalText} />
79
- <MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps}>
76
+ <MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps} screen="robots">
80
77
  <S.Wrapper>
81
78
  <Nav current={currentNavItem} items={navItems} onClick={handleMenuClick} />
82
79
  <S.ContentWrapper>
@@ -93,31 +90,11 @@ const Robots = (props: IProps): JSX.Element => {
93
90
  );
94
91
  };
95
92
 
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;
93
+ interface IProps {
110
94
  navItems: INavItem[];
111
95
  currentNavItem: INavItem;
112
- robots: IDomainRobot[];
113
96
  }
114
97
 
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);
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);
@@ -623,7 +623,7 @@ const MediaGallery = (props: IProps) => {
623
623
  );
624
624
 
625
625
  return (
626
- <MainWrapper backLink={false} title="Media Gallery" rightButton={rightButtonProps}>
626
+ <MainWrapper backLink={false} title="Media Gallery" rightButton={rightButtonProps} screen="gallery">
627
627
  <S.Wrapper ref={wrapperRef}>
628
628
  <S.FolderPanel
629
629
  isOpen={isPanelOpen}
@@ -1,10 +1,10 @@
1
1
  import { useState } from "react";
2
- import { connect } from "react-redux";
3
2
 
4
3
  import { CheckField, ContextMenu, ElementsTooltip } from "@ax/components";
5
4
  import { redirectsActions } from "@ax/containers/Redirects";
6
5
  import { useContextMenu, useModals } from "@ax/hooks";
7
- import type { ICheck, IRedirect, IRootState } from "@ax/types";
6
+ import { useAppDispatch, useAppSelector } from "@ax/store";
7
+ import type { ICheck, IRedirect } from "@ax/types";
8
8
 
9
9
  import { format } from "date-fns";
10
10
 
@@ -13,12 +13,11 @@ import RedirectPanel from "../RedirectPanel";
13
13
 
14
14
  import * as S from "./style";
15
15
 
16
- const RedirectItem = (props: IRedirectItemProps): JSX.Element => {
16
+ const RedirectItem = (props: IProps): JSX.Element => {
17
17
  const {
18
18
  redirect,
19
19
  isSelected,
20
20
  onChange,
21
- deleteRedirect,
22
21
  toggleToast,
23
22
  setFormValues,
24
23
  formValues,
@@ -26,9 +25,10 @@ const RedirectItem = (props: IRedirectItemProps): JSX.Element => {
26
25
  currentFilter,
27
26
  isSiteItem,
28
27
  hoverCheck,
29
- isSaving,
30
28
  } = props;
31
29
 
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,7 +47,9 @@ const RedirectItem = (props: IRedirectItemProps): JSX.Element => {
47
47
  const removeItem = async () => {
48
48
  try {
49
49
  if (redirect.id) {
50
- const deleted = await deleteRedirect(redirect.id, currentFilter);
50
+ const deleted = await dispatch(
51
+ redirectsActions.deleteRedirect({ redirectID: redirect.id, filter: currentFilter }),
52
+ ).unwrap();
51
53
  if (deleted) {
52
54
  toggleToast();
53
55
  }
@@ -148,21 +150,6 @@ interface IProps {
148
150
  currentFilter: string;
149
151
  isSiteItem: boolean;
150
152
  hoverCheck: boolean;
151
- isSaving: boolean;
152
153
  }
153
154
 
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);
155
+ export default RedirectItem;
@@ -1,9 +1,9 @@
1
1
  import React from "react";
2
- import { connect } from "react-redux";
3
2
 
4
3
  import type { IRedirect } from "@ax/types";
5
4
  import { Button, FieldsBehavior, FloatingPanel } from "@ax/components";
6
5
  import { redirectsActions } from "@ax/containers/Redirects";
6
+ import { useAppDispatch } from "@ax/store";
7
7
 
8
8
  import * as S from "./style";
9
9
 
@@ -15,12 +15,13 @@ const RedirectPanel = (props: IProps): JSX.Element => {
15
15
  isOpenedSecond,
16
16
  toggleSecondaryPanel,
17
17
  addRedirect,
18
- updateRedirect,
19
18
  formValues,
20
19
  setFormValues,
21
20
  currentFilter,
22
21
  } = props;
23
22
 
23
+ const dispatch = useAppDispatch();
24
+
24
25
  const handleOldUrl = (newValue: string) => setFormValues({ ...formValues, from: newValue });
25
26
 
26
27
  const handleNewUrl = (newValue: any) => {
@@ -34,7 +35,14 @@ const RedirectPanel = (props: IProps): JSX.Element => {
34
35
 
35
36
  const editItemAction = async () => {
36
37
  const toPage = formValues.to.pageId ? formValues.to.pageId : formValues.to.url;
37
- formValues.id && (await updateRedirect({ from: formValues.from, to: toPage }, formValues.id, currentFilter));
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
+ ));
38
46
  toggleModal();
39
47
  };
40
48
 
@@ -97,10 +105,7 @@ const RedirectPanel = (props: IProps): JSX.Element => {
97
105
  );
98
106
  };
99
107
 
100
- const mapDispatchToProps = {
101
- updateRedirect: redirectsActions.updateRedirect,
102
- };
103
- interface ICategoryPanelProps {
108
+ interface IProps {
104
109
  isOpen: boolean;
105
110
  isOpenedSecond: boolean;
106
111
  toggleSecondaryPanel(): any;
@@ -109,13 +114,7 @@ interface ICategoryPanelProps {
109
114
  formValues: IRedirect;
110
115
  setFormValues(redirect: IRedirect): void;
111
116
  currentFilter: string;
112
- }
113
-
114
- interface IDispatchProps {
115
117
  addRedirect(force?: boolean): void;
116
- updateRedirect(redirect: { from: string; to: string | number }, redirectID: number, filter?: string): Promise<void>;
117
118
  }
118
119
 
119
- type IProps = ICategoryPanelProps & IDispatchProps;
120
-
121
- export default connect(null, mapDispatchToProps)(RedirectPanel);
120
+ export default RedirectPanel;