@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,4 +1,5 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
4
  import {
4
5
  EmptyState,
@@ -13,8 +14,7 @@ import {
13
14
  import { appActions } from "@ax/containers/App";
14
15
  import { redirectsActions } from "@ax/containers/Redirects";
15
16
  import { useBulkSelection, useModals, useToast } from "@ax/hooks";
16
- import { useAppDispatch, useAppSelector } from "@ax/store";
17
- import type { IEmptyStateProps, INavItem, IQueryValue, IRedirect } from "@ax/types";
17
+ import type { IEmptyStateProps, INavItem, IQueryValue, IRedirect, IRootState, ISite } from "@ax/types";
18
18
 
19
19
  import { DeleteModal, ImportCheckModal, ImportModal, OverwriteModal } from "./atoms";
20
20
  import BulkHeader from "./BulkHeader";
@@ -26,15 +26,22 @@ import { getSortedListStatus } from "./utils";
26
26
  import * as S from "./style";
27
27
 
28
28
  const Redirects = (props: IProps): JSX.Element => {
29
- const { navItems, currentNavItem } = props;
30
- const dispatch = useAppDispatch();
31
- const redirects = useAppSelector((state) => state.redirects.redirects);
32
- const totalItems = useAppSelector((state) => state.redirects.totalItems);
33
- const totalImports = useAppSelector((state) => state.redirects.totalImports);
34
- const imports = useAppSelector((state) => state.redirects.imports);
35
- const currentSite = useAppSelector((state) => state.sites.currentSiteInfo);
36
- const isLoading = useAppSelector((state) => state.app.isLoading);
37
- const isSaving = useAppSelector((state) => state.app.isSaving);
29
+ const {
30
+ navItems,
31
+ currentNavItem,
32
+ setHistoryPush,
33
+ getRedirects,
34
+ redirects,
35
+ totalItems,
36
+ deleteRedirect,
37
+ addRedirect,
38
+ currentSite,
39
+ importRedirects,
40
+ imports,
41
+ totalImports,
42
+ isLoading,
43
+ isSaving,
44
+ } = props;
38
45
 
39
46
  const itemsPerPage = 50;
40
47
  const firstPage = 1;
@@ -81,7 +88,7 @@ const Redirects = (props: IProps): JSX.Element => {
81
88
  // biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
82
89
  useEffect(() => {
83
90
  const params = getParams();
84
- dispatch(redirectsActions.getRedirects({ params, filters: filterQuery }));
91
+ getRedirects(params, filterQuery);
85
92
  if (tableRef.current) {
86
93
  tableRef.current.scrollTo(0, 0);
87
94
  }
@@ -118,7 +125,7 @@ const Redirects = (props: IProps): JSX.Element => {
118
125
  } = useBulkSelection(redIds);
119
126
 
120
127
  const bulkDelete = async () => {
121
- const deleted = await dispatch(redirectsActions.deleteRedirect({ redirectID: selectedItems.all })).unwrap();
128
+ const deleted = await deleteRedirect(selectedItems.all);
122
129
  if (deleted) {
123
130
  toggleToast();
124
131
  }
@@ -184,7 +191,7 @@ const Redirects = (props: IProps): JSX.Element => {
184
191
  };
185
192
 
186
193
  const handleMenuClick = (path: string) => {
187
- dispatch(appActions.setHistoryPush(path));
194
+ setHistoryPush(path);
188
195
  };
189
196
 
190
197
  const handleModal = () => {
@@ -195,13 +202,7 @@ const Redirects = (props: IProps): JSX.Element => {
195
202
 
196
203
  const handleAddRedirect = async (force?: boolean) => {
197
204
  const toPage = formValues.to.pageId ? formValues.to.pageId : formValues.to.url;
198
- // A taken origin (409) comes back as data now, instead of through a callback the thunk
199
- // used to be handed: a function in `meta.arg` trips the serializableCheck, and deciding
200
- // which modal to open was never the thunk's business (docs/RTK-MIGRATION.md §5).
201
- const { isOriginTaken } = await dispatch(
202
- redirectsActions.addRedirect({ redirect: { from: formValues.from, to: toPage }, force, filter: filterQuery }),
203
- ).unwrap();
204
- if (isOriginTaken) toggleModal("overwrite");
205
+ await addRedirect({ from: formValues.from, to: toPage }, () => toggleModal("overwrite"), force, filterQuery);
205
206
  isOpen("edit") && toggleModal("edit");
206
207
  };
207
208
 
@@ -229,14 +230,14 @@ const Redirects = (props: IProps): JSX.Element => {
229
230
 
230
231
  const handleCheckImportData = async (data: { from: string; to: string }[]) => {
231
232
  setImportData(data);
232
- await dispatch(redirectsActions.importRedirects({ rows: data, check: true }));
233
+ await importRedirects(data, true);
233
234
  toggleModal("import");
234
235
  setIsUploading(false);
235
236
  toggleModal("checkImport");
236
237
  };
237
238
 
238
239
  const handleImportData = async () => {
239
- const imported = await dispatch(redirectsActions.importRedirects({ rows: importData, check: false })).unwrap();
240
+ const imported = await importRedirects(importData, false);
240
241
  if (imported) {
241
242
  toggleImportToast();
242
243
  }
@@ -252,7 +253,6 @@ const Redirects = (props: IProps): JSX.Element => {
252
253
  <MainWrapper
253
254
  backLink={false}
254
255
  title="SEO Settings"
255
- screen="redirects"
256
256
  rightButton={rightButtonProps}
257
257
  rightLineButton={rightLineButtonProps}
258
258
  searchAction={handleSearch}
@@ -360,9 +360,52 @@ const Redirects = (props: IProps): JSX.Element => {
360
360
  );
361
361
  };
362
362
 
363
- interface IProps {
363
+ const mapStateToProps = (state: IRootState) => ({
364
+ redirects: state.redirects.redirects,
365
+ totalItems: state.redirects.totalItems,
366
+ currentSite: state.sites.currentSiteInfo,
367
+ totalImports: state.redirects.totalImports,
368
+ imports: state.redirects.imports,
369
+ isLoading: state.app.isLoading,
370
+ isSaving: state.app.isSaving,
371
+ });
372
+
373
+ const mapDispatchToProps = {
374
+ setHistoryPush: appActions.setHistoryPush,
375
+ getRedirects: redirectsActions.getRedirects,
376
+ deleteRedirect: redirectsActions.deleteRedirect,
377
+ addRedirect: redirectsActions.addRedirect,
378
+ importRedirects: redirectsActions.importRedirects,
379
+ };
380
+ interface IRedirectsProps {
364
381
  navItems: INavItem[];
365
382
  currentNavItem: INavItem;
383
+ redirects: IRedirect[];
384
+ totalItems: number;
385
+ currentSite: ISite | null;
386
+ totalImports: number;
387
+ isLoading: boolean;
388
+ isSaving: boolean;
389
+ imports: null | {
390
+ error: IRedirect[];
391
+ existing: IRedirect[];
392
+ ok: IRedirect[];
393
+ };
366
394
  }
367
395
 
368
- export default Redirects;
396
+ interface IDispatchProps {
397
+ setHistoryPush(path: string, isEditor?: boolean): void;
398
+ getRedirects(params: any, filters: string): void;
399
+ deleteRedirect(redirectID: number[]): Promise<boolean>;
400
+ addRedirect(
401
+ redirect: { from: string; to: string | number },
402
+ errorAction: () => void,
403
+ force?: boolean,
404
+ filter?: string,
405
+ ): Promise<void>;
406
+ importRedirects(redirects: { from: string; to: string | number }[], check: boolean): Promise<boolean>;
407
+ }
408
+
409
+ type IProps = IRedirectsProps & IDispatchProps;
410
+
411
+ export default connect(mapStateToProps, mapDispatchToProps)(Redirects);
@@ -1,6 +1,5 @@
1
1
  import React, { useCallback, useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
- import { useHistory } from "react-router-dom";
4
3
 
5
4
  import { EmptyState, ErrorToast, Loading, MainWrapper, Modal, Toast } from "@ax/components";
6
5
  import { appActions } from "@ax/containers/App";
@@ -31,11 +30,9 @@ const DataPacks = (props: IProps): JSX.Element => {
31
30
  addDataPacks,
32
31
  deleteSiteDataPack,
33
32
  activatedDataPacks,
33
+ routerAction,
34
34
  } = props;
35
35
 
36
- // Was `state.router.action` from connected-react-router's router slice.
37
- const { action: routerAction } = useHistory();
38
-
39
36
  const isFromPage = selected?.templates && selected.templates.length > 0;
40
37
 
41
38
  const { isDirty, setIsDirty } = useShouldBeSaved(selected, form);
@@ -223,6 +220,7 @@ interface IProps {
223
220
  selected: any;
224
221
  form: any;
225
222
  activatedDataPacks: IDataPack[];
223
+ routerAction: string;
226
224
  getSiteDataPacks: (queryParams?: string) => void;
227
225
  setSelected: (dataPack?: any) => void;
228
226
  updateDataPack: (dataPack?: any) => Promise<boolean>;
@@ -248,6 +246,7 @@ const mapStateToProps = (state: IRootState) => ({
248
246
  selected: state.dataPacks.selected,
249
247
  form: state.dataPacks.configFormData,
250
248
  activatedDataPacks: state.dataPacks.activated,
249
+ routerAction: state.router.action,
251
250
  });
252
251
 
253
252
  export default connect(mapStateToProps, mapDispatchToProps)(DataPacks);
@@ -1,19 +1,17 @@
1
- import React, { memo } from "react";
1
+ import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
- import { FieldsBehavior } from "@ax/components";
4
+ import type { IRootState } from "@ax/types";
4
5
  import { languagesActions } from "@ax/containers/Settings";
5
- import { useAppDispatch, useAppSelector } from "@ax/store";
6
+ import { FieldsBehavior } from "@ax/components";
6
7
 
7
8
  const ConnectedField = (props: IProps) => {
8
- const { type, name } = props;
9
+ const { form, type, name, updateFormValue } = props;
9
10
 
10
- const dispatch = useAppDispatch();
11
- // Granular selector: this field only re-renders when its own value changes,
12
- // not on every change to the form.
13
- const value = useAppSelector((state) => state.languages.form[name as keyof typeof state.languages.form]);
11
+ const value = form[name];
14
12
 
15
13
  const handleChange = (newValue: any) => {
16
- dispatch(languagesActions.updateFormValue({ [name]: newValue }));
14
+ updateFormValue({ [name]: newValue });
17
15
  };
18
16
 
19
17
  const fieldProps = {
@@ -26,6 +24,7 @@ const ConnectedField = (props: IProps) => {
26
24
  };
27
25
 
28
26
  interface IProps {
27
+ form: any;
29
28
  type: string;
30
29
  title: string;
31
30
  name: string;
@@ -34,8 +33,16 @@ interface IProps {
34
33
  helptext?: string;
35
34
  options?: any;
36
35
  filter?: any;
36
+ updateFormValue: (value: any) => void;
37
37
  autoComplete?: string;
38
38
  }
39
39
 
40
- // `connect` used to memoise this component implicitly; keep that now it is gone.
41
- export default memo(ConnectedField);
40
+ const mapStateToProps = (state: IRootState) => ({
41
+ form: state.languages.form,
42
+ });
43
+
44
+ const mapDispatchToProps = {
45
+ updateFormValue: languagesActions.updateFormValue,
46
+ };
47
+
48
+ export default connect(mapStateToProps, mapDispatchToProps)(ConnectedField);
@@ -1,17 +1,14 @@
1
1
  import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
4
+ import type { ILanguage, IRootState } from "@ax/types";
3
5
  import { Flag } from "@ax/components";
4
- import { useAppSelector } from "@ax/store";
5
- import type { ILanguage } from "@ax/types";
6
-
7
6
  import ConnectedField from "./ConnectedField";
8
7
 
9
8
  import * as S from "./style";
10
9
 
11
10
  const Form = (props: IProps): JSX.Element => {
12
- const { item } = props;
13
-
14
- const currentSiteLanguages = useAppSelector((state) => state.sites.currentSiteLanguages);
11
+ const { currentSiteLanguages, item } = props;
15
12
 
16
13
  const currentSiteLanguagesIds = currentSiteLanguages.map((e) => e.id);
17
14
 
@@ -55,8 +52,13 @@ const Form = (props: IProps): JSX.Element => {
55
52
  );
56
53
  };
57
54
 
55
+ const mapStateToProps = (state: IRootState) => ({
56
+ currentSiteLanguages: state.sites.currentSiteLanguages,
57
+ });
58
+
58
59
  interface IProps {
59
60
  item?: ILanguage;
61
+ currentSiteLanguages: ILanguage[];
60
62
  }
61
63
 
62
- export default Form;
64
+ export default connect(mapStateToProps)(Form);
@@ -1,34 +1,28 @@
1
1
  import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
- import { Button, FloatingPanel } from "@ax/components";
4
+ import type { ILanguage, IRootState, ISiteLanguage } from "@ax/types";
4
5
  import { languagesActions } from "@ax/containers/Settings";
5
- import { useAppDispatch, useAppSelector } from "@ax/store";
6
- import type { ILanguage } from "@ax/types";
6
+ import { FloatingPanel, Button } from "@ax/components";
7
7
 
8
8
  import Form from "./Form";
9
9
 
10
10
  import * as S from "./style";
11
11
 
12
12
  const LanguagePanel = (props: IProps): JSX.Element => {
13
- const { isOpen, toggleModal, item } = props;
14
-
15
- const dispatch = useAppDispatch();
16
- const currentSiteID = useAppSelector((state) => state.sites.currentSiteInfo?.id ?? null);
17
- const languageData = useAppSelector((state) => state.languages.form);
13
+ const { isOpen, toggleModal, createSiteLanguage, updateSiteLanguage, currentSiteID, languageData, item } = props;
18
14
 
19
15
  if (!currentSiteID) {
20
16
  throw new Error(`ERROR: User reached Languages with null site info`);
21
17
  }
22
18
 
23
- // Closes without awaiting the thunk, as the connect version did: a failure
24
- // still closes the panel and only surfaces through the error toast.
25
19
  const addItemAction = () => {
26
- dispatch(languagesActions.createSiteLanguage({ siteID: currentSiteID, data: languageData }));
20
+ createSiteLanguage(currentSiteID, languageData);
27
21
  toggleModal();
28
22
  };
29
23
 
30
24
  const editItemAction = () => {
31
- dispatch(languagesActions.updateSiteLanguage({ siteID: currentSiteID, data: languageData }));
25
+ updateSiteLanguage(currentSiteID, languageData);
32
26
  toggleModal();
33
27
  };
34
28
 
@@ -46,40 +40,52 @@ const LanguagePanel = (props: IProps): JSX.Element => {
46
40
 
47
41
  const title = item ? "Update Language" : "New Language";
48
42
 
49
- // The panel itself stays mounted — FloatingPanel animates off `isOpen` and
50
- // unmounting it would drop that animation — but its body only mounts once the
51
- // panel opens. The Form holds the Language and Domain AsyncSelects, each of
52
- // which fetches its options on mount, and there is one panel per row plus one
53
- // in the header: keeping them mounted meant N+1 requests for `/select/domains`
54
- // on every visit to a screen where the user may open no panel at all. The form
55
- // state lives in the store (the row dispatches setForm before opening), so
56
- // mounting late loses nothing.
57
43
  return (
58
44
  <FloatingPanel title={title} toggleModal={toggleModal} isOpen={isOpen}>
59
- {isOpen && (
60
- <S.Wrapper>
61
- <Form item={item} />
62
- <S.Footer>
63
- {item ? (
64
- <Button className="button" type="button" onClick={editButton.action} disabled={editButton.disabled}>
65
- {editButton.label}
66
- </Button>
67
- ) : (
68
- <Button className="button" type="button" onClick={addButton.action} disabled={addButton.disabled}>
69
- {addButton.label}
70
- </Button>
71
- )}
72
- </S.Footer>
73
- </S.Wrapper>
74
- )}
45
+ <S.Wrapper>
46
+ <Form item={item} />
47
+ <S.Footer>
48
+ {item ? (
49
+ <Button className="button" type="button" onClick={editButton.action} disabled={editButton.disabled}>
50
+ {editButton.label}
51
+ </Button>
52
+ ) : (
53
+ <Button className="button" type="button" onClick={addButton.action} disabled={addButton.disabled}>
54
+ {addButton.label}
55
+ </Button>
56
+ )}
57
+ </S.Footer>
58
+ </S.Wrapper>
75
59
  </FloatingPanel>
76
60
  );
77
61
  };
78
62
 
79
- interface IProps {
63
+ const mapStateToProps = (state: IRootState) => ({
64
+ currentSiteID: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
65
+ languageData: state.languages.form,
66
+ });
67
+
68
+ const mapDispatchToProps = {
69
+ createSiteLanguage: languagesActions.createSiteLanguage,
70
+ updateSiteLanguage: languagesActions.updateSiteLanguage,
71
+ };
72
+
73
+ interface IStateProps {
74
+ currentSiteID: number | null;
75
+ languageData: ISiteLanguage;
76
+ }
77
+
78
+ interface ILanguagePanelProps {
80
79
  item?: ILanguage;
81
80
  isOpen: boolean;
82
81
  toggleModal(): any;
83
82
  }
84
83
 
85
- export default LanguagePanel;
84
+ interface IDispatchProps {
85
+ createSiteLanguage(siteID: number, data: ISiteLanguage): void;
86
+ updateSiteLanguage(siteID: number, data: ISiteLanguage): void;
87
+ }
88
+
89
+ type IProps = IDispatchProps & ILanguagePanelProps & IStateProps;
90
+
91
+ export default connect(mapStateToProps, mapDispatchToProps)(LanguagePanel);
@@ -1,27 +1,26 @@
1
1
  import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
- import { IconAction } from "@ax/components";
4
- import { languagesActions } from "@ax/containers/Settings";
5
- import { Restricted } from "@ax/guards";
6
4
  import { useModal } from "@ax/hooks";
7
- import { useAppDispatch } from "@ax/store";
5
+ import { languagesActions } from "@ax/containers/Settings";
8
6
  import type { ILanguage } from "@ax/types";
9
-
7
+ import { IconAction } from "@ax/components";
8
+ import { Restricted } from "@ax/guards";
10
9
  import LanguagePanel from "../../LanguagePanel";
11
10
 
12
11
  import * as S from "./style";
13
12
 
14
13
  const Header = (props: IProps): JSX.Element => {
15
- const { items } = props;
16
-
17
- const dispatch = useAppDispatch();
14
+ const { items, resetForm } = props;
18
15
 
19
16
  const title = items.length > 1 ? `${items.length} items` : `${items.length} item`;
20
17
 
21
18
  const { isOpen, toggleModal } = useModal();
22
19
 
20
+ const resetValues = () => resetForm();
21
+
23
22
  const openModal = () => {
24
- dispatch(languagesActions.resetForm());
23
+ resetValues();
25
24
  toggleModal();
26
25
  };
27
26
 
@@ -35,9 +34,17 @@ const Header = (props: IProps): JSX.Element => {
35
34
  </S.Header>
36
35
  );
37
36
  };
38
-
39
- interface IProps {
37
+ interface ITableHeaderProps {
40
38
  items: ILanguage[];
41
39
  }
40
+ interface IDispatchProps {
41
+ resetForm(): void;
42
+ }
43
+
44
+ type IProps = ITableHeaderProps & IDispatchProps;
45
+
46
+ const mapDispatchToProps = {
47
+ resetForm: languagesActions.resetForm,
48
+ };
42
49
 
43
- export default Header;
50
+ export default connect(null, mapDispatchToProps)(Header);
@@ -1,32 +1,26 @@
1
- import React, { memo } from "react";
1
+ import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
4
  import { ContextMenu, Flag } from "@ax/components";
4
- import { languagesActions } from "@ax/containers/Settings";
5
5
  import { useContextMenu, useModal, usePermission } from "@ax/hooks";
6
- import { useAppDispatch, useAppSelector } from "@ax/store";
7
- import type { ILanguage, ISiteLanguage } from "@ax/types";
8
-
6
+ import type { ILanguage, ISiteLanguage, IRootState, ISite } from "@ax/types";
7
+ import { languagesActions } from "@ax/containers/Settings";
9
8
  import LanguagePanel from "../../LanguagePanel";
10
9
 
11
10
  import * as S from "./style";
12
11
 
13
- const Item = (props: IProps): JSX.Element => {
14
- const { item } = props;
12
+ const Item = (props: IItemProps): JSX.Element => {
13
+ const { item, resetForm, setForm, deleteSiteLanguage, currentSiteInfo } = props;
15
14
 
16
- const dispatch = useAppDispatch();
17
- const currentSiteInfo = useAppSelector((state) => state.sites.currentSiteInfo);
15
+ if (!currentSiteInfo) {
16
+ throw new Error(`ERROR: User reached Site Languages with null site info`);
17
+ }
18
18
 
19
19
  const { isOpen, toggleModal } = useModal();
20
20
  const isAllowedToEdit = usePermission("general.editLanguage");
21
21
  const isAllowedToDelete = usePermission("general.deleteLanguages");
22
22
  const { contextMenu, handleContextMenu, closeContextMenu } = useContextMenu();
23
23
 
24
- // After the hooks, not before: bailing out earlier would make the hook order
25
- // depend on state. Same guard as the connect version, just moved down.
26
- if (!currentSiteInfo) {
27
- throw new Error(`ERROR: User reached Site Languages with null site info`);
28
- }
29
-
30
24
  const siteLanguage: ISiteLanguage = {
31
25
  language: item.id,
32
26
  path: item.path || "",
@@ -34,8 +28,7 @@ const Item = (props: IProps): JSX.Element => {
34
28
  isDefault: item.isDefault,
35
29
  };
36
30
 
37
- const removeItemAction = () =>
38
- dispatch(languagesActions.deleteSiteLanguage({ siteID: currentSiteInfo.id, languageID: item.id }));
31
+ const removeItemAction = () => deleteSiteLanguage(currentSiteInfo.id, item.id);
39
32
 
40
33
  const menuOptions = isAllowedToDelete
41
34
  ? [
@@ -47,9 +40,13 @@ const Item = (props: IProps): JSX.Element => {
47
40
  ]
48
41
  : [];
49
42
 
43
+ const resetValues = () => resetForm();
44
+
45
+ const setItem = (item: ISiteLanguage) => setForm(item);
46
+
50
47
  const openModal = () => {
51
- dispatch(languagesActions.resetForm());
52
- dispatch(languagesActions.setForm(siteLanguage));
48
+ resetValues();
49
+ setItem(siteLanguage);
53
50
  toggleModal();
54
51
  };
55
52
 
@@ -71,7 +68,25 @@ const Item = (props: IProps): JSX.Element => {
71
68
 
72
69
  interface IProps {
73
70
  item: ILanguage;
71
+ currentSiteInfo: ISite | null;
74
72
  }
75
73
 
76
- // One per row: `connect` memoised it implicitly, so keep that now it is gone.
77
- export default memo(Item);
74
+ const mapStateToProps = (state: IRootState) => ({
75
+ currentSiteInfo: state.sites.currentSiteInfo,
76
+ });
77
+
78
+ const mapDispatchToProps = {
79
+ resetForm: languagesActions.resetForm,
80
+ setForm: languagesActions.setForm,
81
+ deleteSiteLanguage: languagesActions.deleteSiteLanguage,
82
+ };
83
+
84
+ interface IDispatchProps {
85
+ setForm(item: ISiteLanguage): void;
86
+ resetForm(): void;
87
+ deleteSiteLanguage(siteId: number, languageId: number): void;
88
+ }
89
+
90
+ type IItemProps = IProps & IDispatchProps;
91
+
92
+ export default connect(mapStateToProps, mapDispatchToProps)(Item);
@@ -1,15 +1,15 @@
1
1
  import React from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
- import { useAppSelector } from "@ax/store";
4
- import type { ILanguage } from "@ax/types";
4
+ import type { ILanguage, IRootState } from "@ax/types";
5
5
 
6
- import Header from "./Header";
7
6
  import Item from "./Item";
7
+ import Header from "./Header";
8
8
 
9
9
  import * as S from "./style";
10
10
 
11
- const Table = (): JSX.Element => {
12
- const items = useAppSelector((state) => state.sites.currentSiteLanguages);
11
+ const Table = (props: IStateProps): JSX.Element => {
12
+ const { items } = props;
13
13
 
14
14
  const mapItemList = (menu: any) =>
15
15
  menu && menu.map((item: ILanguage, i: number) => <Item key={`${item.label}${i}`} item={item} />);
@@ -22,4 +22,12 @@ const Table = (): JSX.Element => {
22
22
  );
23
23
  };
24
24
 
25
- export default Table;
25
+ const mapStateToProps = (state: IRootState) => ({
26
+ items: state.sites.currentSiteLanguages,
27
+ });
28
+
29
+ interface IStateProps {
30
+ items: ILanguage[];
31
+ }
32
+
33
+ export default connect(mapStateToProps)(Table);
@@ -1,37 +1,30 @@
1
1
  import React, { useEffect } from "react";
2
+ import { connect } from "react-redux";
2
3
 
3
- import { ErrorToast, Loading, MainWrapper, Nav } from "@ax/components";
4
- import { appActions } from "@ax/containers/App";
4
+ import type { INavItem, IRootState, ISite } from "@ax/types";
5
5
  import { sitesActions } from "@ax/containers/Sites";
6
- import { useAppDispatch, useAppSelector } from "@ax/store";
7
- import type { INavItem } from "@ax/types";
6
+ import { appActions } from "@ax/containers/App";
7
+ import { MainWrapper, Loading, ErrorToast, Nav } from "@ax/components";
8
8
 
9
9
  import Table from "./Table";
10
10
 
11
11
  import * as S from "./style";
12
12
 
13
13
  const Languages = (props: IProps): JSX.Element => {
14
- const { navItems, currentNavItem } = props;
15
-
16
- const dispatch = useAppDispatch();
17
- const currentSiteInfo = useAppSelector((state) => state.sites.currentSiteInfo);
18
- const isLoading = useAppSelector((state) => state.app.isLoading);
14
+ const { isLoading, currentSiteInfo, getSiteLanguages, setHistoryPush, navItems, currentNavItem } = props;
19
15
 
20
- // getSiteLanguages and setHistoryPush are still legacy thunks from other areas
21
- // (sites, app). The RTK dispatch takes them unchanged, which is what makes the
22
- // area-by-area strangler possible.
23
16
  useEffect(() => {
24
17
  if (currentSiteInfo) {
25
- dispatch(sitesActions.getSiteLanguages(currentSiteInfo.id));
18
+ getSiteLanguages(currentSiteInfo.id);
26
19
  }
27
- }, [currentSiteInfo, dispatch]);
20
+ }, [currentSiteInfo, getSiteLanguages]);
28
21
 
29
22
  const handleMenuClick = (path: string) => {
30
- dispatch(appActions.setHistoryPush(path));
23
+ setHistoryPush(path);
31
24
  };
32
25
 
33
26
  return (
34
- <MainWrapper title={`Languages`} screen="languages">
27
+ <MainWrapper title={`Languages`}>
35
28
  <S.Wrapper>
36
29
  <Nav current={currentNavItem} items={navItems} onClick={handleMenuClick} />
37
30
  <S.ContentWrapper>
@@ -49,9 +42,28 @@ const Languages = (props: IProps): JSX.Element => {
49
42
  );
50
43
  };
51
44
 
52
- interface IProps {
45
+ const mapStateToProps = (state: IRootState) => ({
46
+ currentSiteInfo: state.sites.currentSiteInfo,
47
+ isLoading: state.app.isLoading,
48
+ });
49
+
50
+ const mapDispatchToProps = {
51
+ getSiteLanguages: sitesActions.getSiteLanguages,
52
+ setHistoryPush: appActions.setHistoryPush,
53
+ };
54
+
55
+ interface IDispatchProps {
56
+ getSiteLanguages(siteID: number): void;
57
+ setHistoryPush(path: string, isEditor?: boolean): void;
58
+ }
59
+
60
+ interface ILanguagesProps {
61
+ currentSiteInfo: ISite | null;
62
+ isLoading: boolean;
53
63
  navItems: INavItem[];
54
64
  currentNavItem: INavItem;
55
65
  }
56
66
 
57
- export default Languages;
67
+ type IProps = ILanguagesProps & IDispatchProps;
68
+
69
+ export default connect(mapStateToProps, mapDispatchToProps)(Languages);