@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,5 +1,15 @@
1
- import type { Dispatch } from "redux";
2
- import { format } from "date-fns";
1
+ import { logs } from "@ax/api";
2
+ import { appActions } from "@ax/containers/App";
3
+ import { formsActions } from "@ax/containers/Forms";
4
+ import { integrationsActions } from "@ax/containers/Integrations";
5
+ import { navigationActions } from "@ax/containers/Navigation";
6
+ import { pageEditorActions } from "@ax/containers/PageEditor";
7
+ import { sitesActions } from "@ax/containers/Sites";
8
+ import { structuredDataActions } from "@ax/containers/StructuredData";
9
+ import { usersActions } from "@ax/containers/Users";
10
+ import { handleRequest } from "@ax/helpers";
11
+ import type { AppDispatch } from "@ax/store";
12
+ import type { IRootState } from "@ax/types";
3
13
 
4
14
  import type {
5
15
  LogActivityExportRequest,
@@ -10,99 +20,106 @@ import type {
10
20
  LogContentTypeDTO,
11
21
  PaginationResponse,
12
22
  } from "@griddo/api-types";
13
- import { logs } from "@ax/api";
14
- import { appActions } from "@ax/containers/App";
15
- import { usersActions } from "@ax/containers/Users";
16
- import { pageEditorActions } from "@ax/containers/PageEditor";
17
- import { sitesActions } from "@ax/containers/Sites";
18
- import { integrationsActions } from "@ax/containers/Integrations";
19
- import { formsActions } from "@ax/containers/Forms";
20
- import { navigationActions } from "@ax/containers/Navigation";
21
- import { structuredDataActions } from "@ax/containers/StructuredData";
22
- import { handleRequest } from "@ax/helpers";
23
- import type { ILogItemsByDay, ILogItemsByUser, IRootState } from "@ax/types";
24
-
25
- import { SET_ACTIVITY_LOG, SET_LIST_MODE } from "./constants";
26
- import type { ISetActivityLog, ISetListMode } from "./interfaces";
27
-
28
- function setActivityLog(activityLog: ILogItemsByDay | ILogItemsByUser): ISetActivityLog {
29
- return { type: SET_ACTIVITY_LOG, payload: { activityLog } };
30
- }
31
-
32
- function setListMode(listMode: string): ISetListMode {
33
- return { type: SET_LIST_MODE, payload: { listMode } };
34
- }
35
-
36
- function getActivityLog(params: LogActivityPaginationRequest): (dispatch: Dispatch) => Promise<void> {
37
- return async (dispatch) => {
38
- try {
39
- const callback = async () => logs.getLogActivityTimelineByDay(params);
23
+ import { createAsyncThunk } from "@reduxjs/toolkit";
24
+ import { format } from "date-fns";
25
+ import type { Dispatch } from "redux";
40
26
 
41
- const responseActions = {
42
- handleSuccess: (response: PaginationResponse<LogActivityGroupedDayDTO>) => {
43
- dispatch(setActivityLog(response));
44
- },
45
- handleError: (response: any) => appActions.handleError(response)(dispatch),
46
- };
27
+ import { setActivityLog } from "./slice";
47
28
 
48
- await handleRequest(callback, responseActions, [appActions.setIsLoading])(dispatch);
49
- } catch (e) {
50
- console.log(e);
51
- }
52
- };
53
- }
29
+ /**
30
+ * Read and export side of UC-AX-view-activity-log, plus the deep-link dispatcher, as
31
+ * createAsyncThunk.
32
+ *
33
+ * They keep calling `handleRequest` inside for the same reason the Languages pilot does —
34
+ * see docs/RTK-MIGRATION.md §5.
35
+ *
36
+ * `goToLogContent` is the last of the wave's mines and the one that does not get cleaned up
37
+ * here: it is a 160-line navigation switch that reaches into SEVEN areas that have not been
38
+ * migrated yet, invoking their thunks by hand as `thunk(args)(dispatch, getState)`. That still
39
+ * works while those areas are legacy, and it is exactly what stops working when they are not —
40
+ * a createAsyncThunk expects the `(dispatch, getState, extra)` triple. **When `sites` migrates
41
+ * in wave 3, the twelve `sitesActions.getSite(site)(dispatch, getState)` calls below break.**
42
+ * Its body is transplanted verbatim on purpose — diffed line by line against the legacy one —
43
+ * and the single-argument rule is the only edit it took.
44
+ */
45
+ const onError = (response: unknown, dispatch: Dispatch) => appActions.handleError(response)(dispatch);
54
46
 
55
- function getActivityLogByUser(params: LogActivityPaginationRequest): (dispatch: Dispatch) => Promise<void> {
56
- return async (dispatch) => {
57
- try {
58
- const callback = async () => logs.getLogActivityTimelineByUser(params);
47
+ /**
48
+ * The store's real dispatch and state, declared once.
49
+ *
50
+ * `AppDispatch` is a redux 4 `AnyAction` thunk dispatch (`src/store/hooks.ts`), so the
51
+ * nineteen legacy action creators and the seven legacy thunks that `goToLogContent` still
52
+ * reaches for keep type-checking, and `getState()` comes back as `IRootState` instead of
53
+ * `unknown`. Without this RTK types `dispatch` against redux 5's `UnknownAction`, which the
54
+ * hand-written action interfaces are not assignable to — the §8.3 friction, reaching
55
+ * production code here for the first time rather than just test files.
56
+ */
57
+ const createAppAsyncThunk = createAsyncThunk.withTypes<{ state: IRootState; dispatch: AppDispatch }>();
59
58
 
60
- const responseActions = {
61
- handleSuccess: (response: PaginationResponse<LogActivityGroupedUserDTO<LogActivityGroupedUserDTO[]>>) => {
62
- dispatch(setActivityLog(response));
63
- },
64
- handleError: (response: any) => appActions.handleError(response)(dispatch),
65
- };
59
+ export const getActivityLog = createAsyncThunk(
60
+ "activityLog/getActivityLog",
61
+ async (params: LogActivityPaginationRequest, { dispatch }) =>
62
+ handleRequest(
63
+ async () => logs.getLogActivityTimelineByDay(params),
64
+ {
65
+ handleSuccess: (response: PaginationResponse<LogActivityGroupedDayDTO>) => dispatch(setActivityLog(response)),
66
+ handleError: (response: unknown) => onError(response, dispatch),
67
+ },
68
+ [appActions.setIsLoading],
69
+ )(dispatch),
70
+ );
66
71
 
67
- await handleRequest(callback, responseActions, [appActions.setIsLoading])(dispatch);
68
- } catch (e) {
69
- console.log(e);
70
- }
71
- };
72
- }
72
+ export const getActivityLogByUser = createAsyncThunk(
73
+ "activityLog/getActivityLogByUser",
74
+ async (params: LogActivityPaginationRequest, { dispatch }) =>
75
+ handleRequest(
76
+ async () => logs.getLogActivityTimelineByUser(params),
77
+ {
78
+ handleSuccess: (response: PaginationResponse<LogActivityGroupedUserDTO<LogActivityGroupedUserDTO[]>>) =>
79
+ dispatch(setActivityLog(response)),
80
+ handleError: (response: unknown) => onError(response, dispatch),
81
+ },
82
+ [appActions.setIsLoading],
83
+ )(dispatch),
84
+ );
73
85
 
74
- function downloadActivityLog(data: LogActivityExportRequest): (dispatch: Dispatch) => Promise<void> {
75
- return async (dispatch) => {
76
- try {
77
- const responseActions = {
78
- handleSuccess: (response: any) => {
79
- const dateString = format(new Date(), "yyyy-MM-dd");
80
- const url = window.URL.createObjectURL(new Blob([response]));
81
- const a = document.createElement("a");
82
- a.href = url;
83
- a.download = `activity-log-${dateString}.${data.format[0]}`;
84
- document.body.appendChild(a);
85
- a.click();
86
- window.URL.revokeObjectURL(url);
87
- },
88
- handleError: (response: any) => appActions.handleError(response)(dispatch),
89
- };
86
+ // The export comes back as a blob and is handed to the browser through a throwaway
87
+ // `<a download>`. Kept verbatim from the legacy thunk, side effects included: it is the only
88
+ // place in the area that touches the DOM.
89
+ const saveExport = (payload: BlobPart, extension: string) => {
90
+ const dateString = format(new Date(), "yyyy-MM-dd");
91
+ const url = window.URL.createObjectURL(new Blob([payload]));
92
+ const a = document.createElement("a");
93
+ a.href = url;
94
+ a.download = `activity-log-${dateString}.${extension}`;
95
+ document.body.appendChild(a);
96
+ a.click();
97
+ window.URL.revokeObjectURL(url);
98
+ };
90
99
 
91
- const callback = async () => logs.logExport(data);
100
+ export const downloadActivityLog = createAsyncThunk(
101
+ "activityLog/downloadActivityLog",
102
+ // No loading flag, as before: the export runs without the screen's spinner.
103
+ async (data: LogActivityExportRequest, { dispatch }) =>
104
+ handleRequest(
105
+ async () => logs.logExport(data),
106
+ {
107
+ handleSuccess: (response: BlobPart) => saveExport(response, data.format[0]),
108
+ handleError: (response: unknown) => onError(response, dispatch),
109
+ },
110
+ [],
111
+ )(dispatch),
112
+ );
92
113
 
93
- await handleRequest(callback, responseActions, [])(dispatch);
94
- } catch (e) {
95
- console.log(e);
96
- }
97
- };
114
+ interface IGoToLogContentArgs {
115
+ contentType: LogContentTypeDTO | null;
116
+ content: LogContentDTO | null;
117
+ site: number | null;
98
118
  }
99
119
 
100
- function goToLogContent(
101
- contentType: LogContentTypeDTO | null,
102
- content: LogContentDTO | null,
103
- site: number | null,
104
- ): (dispatch: Dispatch, getState: () => IRootState) => void {
105
- return async (dispatch, getState) => {
120
+ export const goToLogContent = createAppAsyncThunk(
121
+ "activityLog/goToLogContent",
122
+ async ({ contentType, content, site }: IGoToLogContentArgs, { dispatch, getState }) => {
106
123
  const {
107
124
  app: { lang, globalLangs },
108
125
  } = getState();
@@ -257,7 +274,5 @@ function goToLogContent(
257
274
  break;
258
275
  }
259
276
  }
260
- };
261
- }
262
-
263
- export { getActivityLog, getActivityLogByUser, setListMode, downloadActivityLog, goToLogContent };
277
+ },
278
+ );
@@ -1,4 +1,15 @@
1
- import * as analyticsActions from "./actions";
2
- import { analyticsReducer } from "./reducer";
1
+ import { setAnalytics } from "./slice";
2
+ import { deleteScriptCode, getAnalytics, updateAnalytics, updateScriptCode } from "./thunks";
3
3
 
4
- export { analyticsActions, analyticsReducer };
4
+ const analyticsActions = {
5
+ setAnalytics,
6
+ getAnalytics,
7
+ updateAnalytics,
8
+ updateScriptCode,
9
+ deleteScriptCode,
10
+ };
11
+
12
+ export { analyticsActions };
13
+
14
+ export type { IAnalyticsState } from "./slice";
15
+ export { analyticsInitialState, analyticsReducer } from "./slice";
@@ -0,0 +1,48 @@
1
+ import type { IAnalytics, IDimension, IDimensionsGroup } from "@ax/types";
2
+
3
+ import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
4
+
5
+ /**
6
+ * The `analytics` area holds the tracking script and the Data Layer definition
7
+ * (dimensions and their groups) of whatever scope is loaded — global or one site
8
+ * (UC-AX-configure-seo-analytics). There is a single slot: loading a site
9
+ * overwrites what global left, which is why the two settings screens reload on mount.
10
+ *
11
+ * STAYS in redux-persist, against the rule of ADR 0002: `AnalyticsField` (the page
12
+ * editor's SEO/analytics field) reads `groups` from here and never fetches, so the
13
+ * persist is what feeds it after a hard refresh. It decides which of two different
14
+ * fields to render, so an empty `groups` is not a flash but the wrong editor. Leaves
15
+ * when PageEditor migrates (wave 4).
16
+ */
17
+ export interface IAnalyticsState {
18
+ scriptCode: string;
19
+ siteScriptCodeExists: boolean;
20
+ dimensions: IDimension[];
21
+ groups: IDimensionsGroup[];
22
+ }
23
+
24
+ export const initialState: IAnalyticsState = {
25
+ scriptCode: "",
26
+ siteScriptCodeExists: false,
27
+ dimensions: [],
28
+ groups: [],
29
+ };
30
+
31
+ const analyticsSlice = createSlice({
32
+ name: "analytics",
33
+ initialState,
34
+ reducers: {
35
+ // MERGES, it does not replace: the legacy reducer spread the payload onto the
36
+ // state and `Sites/actions.tsx` dispatches this with whatever the site's
37
+ // metrics endpoint returned, which need not carry every field. Typed as a
38
+ // partial to say so out loud.
39
+ setAnalytics(state, action: PayloadAction<Partial<IAnalytics>>) {
40
+ Object.assign(state, action.payload);
41
+ },
42
+ },
43
+ });
44
+
45
+ export const { setAnalytics } = analyticsSlice.actions;
46
+
47
+ export { initialState as analyticsInitialState };
48
+ export const analyticsReducer = analyticsSlice.reducer;
@@ -0,0 +1,103 @@
1
+ import { analytics } from "@ax/api";
2
+ import { appActions } from "@ax/containers/App";
3
+ import { handleRequest } from "@ax/helpers";
4
+ import type { IAnalytics } from "@ax/types";
5
+
6
+ import { createAsyncThunk } from "@reduxjs/toolkit";
7
+ import type { Dispatch } from "redux";
8
+
9
+ import { setAnalytics } from "./slice";
10
+
11
+ /**
12
+ * Read and write side of UC-AX-configure-seo-analytics, as createAsyncThunk. They
13
+ * keep calling `handleRequest` inside — see the Languages pilot and §5 of
14
+ * docs/RTK-MIGRATION.md.
15
+ *
16
+ * `siteId` is `null` for the global scope, which the API spells `"global"`. The
17
+ * legacy signatures made the argument optional; createAsyncThunk takes exactly one
18
+ * argument, so the scope is now always explicit at the call site. That is the whole
19
+ * of the churn this migration pushes onto the callers.
20
+ */
21
+ const onError = (response: unknown, dispatch: Dispatch, isMultiple = false, message?: string) =>
22
+ appActions.handleError(response, isMultiple, message)(dispatch);
23
+
24
+ const scopeOf = (siteId: number | null) => siteId ?? "global";
25
+
26
+ export const getAnalytics = createAsyncThunk("analytics/getAnalytics", async (siteId: number | null, { dispatch }) =>
27
+ handleRequest(
28
+ async () => analytics.getAnalytics(scopeOf(siteId)),
29
+ {
30
+ handleSuccess: (data: Partial<IAnalytics>) => dispatch(setAnalytics(data)),
31
+ handleError: (response: unknown) => onError(response, dispatch),
32
+ },
33
+ [],
34
+ )(dispatch),
35
+ );
36
+
37
+ interface IUpdateAnalyticsArgs {
38
+ analytics: IAnalytics;
39
+ siteId: number | null;
40
+ }
41
+
42
+ export const updateAnalytics = createAsyncThunk(
43
+ "analytics/updateAnalytics",
44
+ async ({ analytics: state, siteId }: IUpdateAnalyticsArgs, { dispatch }) => {
45
+ const { scriptCode, dimensions, groups } = state;
46
+ const scope = scopeOf(siteId);
47
+
48
+ return handleRequest(
49
+ // The three writes go out in PARALLEL and `handleRequest` treats the array
50
+ // of responses as a whole: any one of them failing fails the save.
51
+ async () =>
52
+ Promise.all([
53
+ analytics.updateScriptCode(scope, { scriptCode }),
54
+ analytics.createDimensions(scope, { dimensions }),
55
+ analytics.createDimensionsGroups(scope, { groups }),
56
+ ]),
57
+ {
58
+ // Not awaited, as the legacy version did not await it either: the button
59
+ // stops saying "Saving" before the reload lands.
60
+ handleSuccess: () => dispatch(getAnalytics(siteId)),
61
+ handleError: (response: { data: { message?: string | string[] } }) => {
62
+ const { message } = response.data;
63
+ const isMultiple = Array.isArray(message) && message.length > 1;
64
+ const msg = isMultiple ? `The action failed due to ${message.length} errors.` : undefined;
65
+ onError(response, dispatch, isMultiple, msg);
66
+ },
67
+ },
68
+ [appActions.setIsSaving],
69
+ )(dispatch);
70
+ },
71
+ );
72
+
73
+ interface IUpdateScriptCodeArgs {
74
+ scriptCode: string;
75
+ siteId: number | null;
76
+ }
77
+
78
+ export const updateScriptCode = createAsyncThunk(
79
+ "analytics/updateScriptCode",
80
+ async ({ scriptCode, siteId }: IUpdateScriptCodeArgs, { dispatch }) =>
81
+ handleRequest(
82
+ async () => analytics.updateScriptCode(scopeOf(siteId), { scriptCode }),
83
+ {
84
+ handleSuccess: () => dispatch(getAnalytics(siteId)),
85
+ handleError: (response: unknown) => onError(response, dispatch),
86
+ },
87
+ [appActions.setIsSaving],
88
+ )(dispatch),
89
+ );
90
+
91
+ export const deleteScriptCode = createAsyncThunk(
92
+ "analytics/deleteScriptCode",
93
+ // No loading flag, as before: resetting to the global defaults shows no spinner.
94
+ async (siteId: number | null, { dispatch }) =>
95
+ handleRequest(
96
+ async () => analytics.deleteScriptCode(scopeOf(siteId)),
97
+ {
98
+ handleSuccess: () => dispatch(getAnalytics(siteId)),
99
+ handleError: (response: unknown) => onError(response, dispatch),
100
+ },
101
+ [],
102
+ )(dispatch),
103
+ );
@@ -1,4 +1,13 @@
1
- import * as domainsActions from "./actions";
2
- import { domainsReducer } from "./reducer";
1
+ import { setDomainsRobots } from "./slice";
2
+ import { getDomainsRobots, updateDomainsRobots } from "./thunks";
3
3
 
4
- export { domainsActions, domainsReducer };
4
+ const domainsActions = {
5
+ setDomainsRobots,
6
+ getDomainsRobots,
7
+ updateDomainsRobots,
8
+ };
9
+
10
+ export { domainsActions };
11
+
12
+ export type { IDomainsState } from "./slice";
13
+ export { domainsInitialState, domainsReducer } from "./slice";
@@ -0,0 +1,37 @@
1
+ import type { IDomainRobot } from "@ax/types";
2
+
3
+ import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
4
+
5
+ /**
6
+ * The `domains` area holds the robots.txt of every domain (UC-AX-configure-seo-analytics,
7
+ * `/settings/robots`). Despite the name it is not a domain manager: the list of
8
+ * domains a site has is read through `sites`, and this slice only carries what the
9
+ * robots.txt editor renders.
10
+ *
11
+ * Out of redux-persist since this migration (ADR 0002): the screen calls
12
+ * `getDomainsRobots()` on mount, so the persisted copy was never read.
13
+ */
14
+ export interface IDomainsState {
15
+ robots: IDomainRobot[];
16
+ }
17
+
18
+ export const initialState: IDomainsState = {
19
+ robots: [],
20
+ };
21
+
22
+ const domainsSlice = createSlice({
23
+ name: "domains",
24
+ initialState,
25
+ reducers: {
26
+ // The legacy action wrapped the list in `{ robots }` to spread it onto the
27
+ // state; as a reducer the payload is just the list.
28
+ setDomainsRobots(state, action: PayloadAction<IDomainRobot[]>) {
29
+ state.robots = action.payload;
30
+ },
31
+ },
32
+ });
33
+
34
+ export const { setDomainsRobots } = domainsSlice.actions;
35
+
36
+ export { initialState as domainsInitialState };
37
+ export const domainsReducer = domainsSlice.reducer;
@@ -0,0 +1,60 @@
1
+ import { domains } from "@ax/api";
2
+ import { appActions } from "@ax/containers/App";
3
+ import { handleRequest } from "@ax/helpers";
4
+ import type { IDomainRobot } from "@ax/types";
5
+
6
+ import { createAsyncThunk } from "@reduxjs/toolkit";
7
+ import type { Dispatch } from "redux";
8
+
9
+ import { setDomainsRobots } from "./slice";
10
+
11
+ /**
12
+ * Read and write side of the robots.txt editor, as createAsyncThunk.
13
+ *
14
+ * They keep calling `handleRequest` inside for the same reason the Languages
15
+ * pilot does: that helper owns the loading-flag and error-channel conventions of
16
+ * the whole codebase (152 call-sites) and replacing it is a cross-cutting job,
17
+ * not this area's — see docs/RTK-MIGRATION.md §5.
18
+ *
19
+ * What does change: the legacy versions wrapped everything in a `try/catch` whose
20
+ * body was `console.log(e)`, swallowing failures. createAsyncThunk turns a throw
21
+ * into a `rejected` action instead, so the failure is observable.
22
+ */
23
+ // Called through the namespace rather than destructured at module scope: a
24
+ // destructured reference is frozen at import time and cannot be spied on, which
25
+ // would make the reload-after-save behaviour untestable.
26
+ const onError = (response: unknown, dispatch: Dispatch, isMultiple = false, message?: string) =>
27
+ appActions.handleError(response, isMultiple, message)(dispatch);
28
+
29
+ export const getDomainsRobots = createAsyncThunk<boolean, void>(
30
+ "domains/getDomainsRobots",
31
+ async (_arg, { dispatch }) =>
32
+ handleRequest(
33
+ async () => domains.getDomainsRobots(),
34
+ {
35
+ handleSuccess: (robots: IDomainRobot[]) => dispatch(setDomainsRobots(robots)),
36
+ handleError: (response: unknown) => onError(response, dispatch),
37
+ },
38
+ [appActions.setIsLoading],
39
+ )(dispatch),
40
+ );
41
+
42
+ export const updateDomainsRobots = createAsyncThunk(
43
+ "domains/updateDomainsRobots",
44
+ async (robots: IDomainRobot[], { dispatch }) =>
45
+ handleRequest(
46
+ async () => domains.updateDomainsRobots(robots),
47
+ {
48
+ // Reloading from the server is what makes `computedContent` (the
49
+ // inherited rules, read-only) reflect the content just saved.
50
+ handleSuccess: () => dispatch(getDomainsRobots()),
51
+ handleError: (response: { data: { message?: string | string[] } }) => {
52
+ const { message } = response.data;
53
+ const isMultiple = Array.isArray(message) && message.length > 1;
54
+ const msg = isMultiple ? `The action failed due to ${message.length} errors.` : undefined;
55
+ onError(response, dispatch, isMultiple, msg);
56
+ },
57
+ },
58
+ [appActions.setIsSaving],
59
+ )(dispatch),
60
+ );
@@ -1,10 +1,3 @@
1
- const NAME = "redirects";
2
-
3
- const SET_REDIRECTS = `${NAME}/SET_REDIRECTS`;
4
- const SET_TOTAL_ITEMS = `${NAME}/SET_TOTAL_ITEMS`;
5
- const SET_TOTAL_IMPORTS = `${NAME}/SET_TOTAL_IMPORTS`;
6
- const SET_IMPORTS = `${NAME}/SET_IMPORTS`;
7
-
8
1
  const ITEMS_PER_PAGE = 50;
9
2
 
10
3
  const DEFAULT_PARAMS = {
@@ -17,13 +10,4 @@ const ERROR_CODE = {
17
10
  isBeingUsed: 409,
18
11
  };
19
12
 
20
- export {
21
- NAME,
22
- SET_REDIRECTS,
23
- SET_TOTAL_ITEMS,
24
- ERROR_CODE,
25
- DEFAULT_PARAMS,
26
- ITEMS_PER_PAGE,
27
- SET_IMPORTS,
28
- SET_TOTAL_IMPORTS,
29
- };
13
+ export { ERROR_CODE, DEFAULT_PARAMS, ITEMS_PER_PAGE };
@@ -1,4 +1,15 @@
1
- import * as redirectsActions from "./actions";
2
- import { redirectsReducer } from "./reducer";
1
+ import { addRedirect, deleteRedirect, getRedirects, importRedirects, updateRedirect } from "./thunks";
3
2
 
4
- export { redirectsActions, redirectsReducer };
3
+ const redirectsActions = {
4
+ getRedirects,
5
+ addRedirect,
6
+ updateRedirect,
7
+ deleteRedirect,
8
+ importRedirects,
9
+ };
10
+
11
+ export { redirectsActions };
12
+
13
+ export type { IRedirectsState } from "./slice";
14
+ export { redirectsInitialState, redirectsReducer } from "./slice";
15
+ export type { IAddRedirectResult, IRedirectsQuery } from "./thunks";
@@ -0,0 +1,60 @@
1
+ import type { IRedirect } from "@ax/types";
2
+
3
+ import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
4
+
5
+ /**
6
+ * The `redirects` area holds ONE page of the redirect catalogue plus the result of the
7
+ * last CSV import check (UC-AX-manage-redirects). Paging, searching and filtering live in
8
+ * the view, so the list is whatever page was asked for last and `totalItems` is what the
9
+ * pager counts with.
10
+ *
11
+ * The four setters stay internal to the area: nothing outside dispatches them — the legacy
12
+ * `actions.tsx` did not export them either — so the barrel keeps them private and the
13
+ * public surface is just the five thunks.
14
+ *
15
+ * Out of redux-persist since this migration (ADR 0002).
16
+ */
17
+ export interface IRedirectsState {
18
+ redirects: IRedirect[];
19
+ totalItems: number;
20
+ totalImports: number;
21
+ imports: null | {
22
+ error: IRedirect[];
23
+ existing: IRedirect[];
24
+ ok: IRedirect[];
25
+ };
26
+ }
27
+
28
+ export const initialState: IRedirectsState = {
29
+ redirects: [],
30
+ totalItems: 0,
31
+ totalImports: 0,
32
+ imports: null,
33
+ };
34
+
35
+ const redirectsSlice = createSlice({
36
+ name: "redirects",
37
+ initialState,
38
+ // The legacy reducer had all four action types share one `return { ...state, ...payload }`
39
+ // branch, which worked because each payload carried exactly one key. One reducer per
40
+ // field says the same thing without the indirection.
41
+ reducers: {
42
+ setRedirects(state, action: PayloadAction<IRedirect[]>) {
43
+ state.redirects = action.payload;
44
+ },
45
+ setTotalItems(state, action: PayloadAction<number>) {
46
+ state.totalItems = action.payload;
47
+ },
48
+ setImports(state, action: PayloadAction<IRedirectsState["imports"]>) {
49
+ state.imports = action.payload;
50
+ },
51
+ setTotalImports(state, action: PayloadAction<number>) {
52
+ state.totalImports = action.payload;
53
+ },
54
+ },
55
+ });
56
+
57
+ export const { setRedirects, setTotalItems, setImports, setTotalImports } = redirectsSlice.actions;
58
+
59
+ export { initialState as redirectsInitialState };
60
+ export const redirectsReducer = redirectsSlice.reducer;