@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.
- package/config/griddo-config/index.js +1 -0
- package/package.json +3 -4
- package/src/GlobalStore.tsx +91 -35
- package/src/__tests__/modules/ActivityLog/ActivityLog.uc.test.tsx +132 -0
- package/src/__tests__/modules/Analytics/Analytics.uc.test.tsx +168 -0
- package/src/__tests__/modules/GlobalSettings/Robots/Robots.test.tsx +110 -0
- package/src/__tests__/modules/GlobalSettings/Robots/Robots.uc.test.tsx +125 -0
- package/src/__tests__/modules/Redirects/Redirects.uc.test.tsx +164 -0
- package/src/__tests__/modules/Settings/Languages/Languages.uc.test.tsx +213 -0
- package/src/__tests__/modules/Settings/Social/Social.uc.test.tsx +171 -0
- package/src/__tests__/store/activityLog.test.ts +301 -0
- package/src/__tests__/store/analytics.test.ts +134 -0
- package/src/__tests__/store/domains.test.ts +107 -0
- package/src/__tests__/store/languages.test.ts +129 -0
- package/src/__tests__/store/persist.test.ts +47 -0
- package/src/__tests__/store/redirects.test.ts +313 -0
- package/src/__tests__/store/social.test.ts +165 -0
- package/src/components/MainWrapper/index.tsx +14 -2
- package/src/containers/ActivityLog/index.tsx +16 -3
- package/src/containers/ActivityLog/slice.ts +45 -0
- package/src/containers/ActivityLog/{actions.tsx → thunks.ts} +104 -89
- package/src/containers/Analytics/index.tsx +14 -3
- package/src/containers/Analytics/slice.ts +48 -0
- package/src/containers/Analytics/thunks.ts +103 -0
- package/src/containers/Domains/index.tsx +12 -3
- package/src/containers/Domains/slice.ts +37 -0
- package/src/containers/Domains/thunks.ts +60 -0
- package/src/containers/Redirects/constants.tsx +1 -17
- package/src/containers/Redirects/index.tsx +14 -3
- package/src/containers/Redirects/slice.ts +60 -0
- package/src/containers/Redirects/thunks.ts +196 -0
- package/src/containers/Settings/Languages/index.tsx +15 -3
- package/src/containers/Settings/Languages/slice.ts +48 -0
- package/src/containers/Settings/Languages/thunks.ts +83 -0
- package/src/containers/Settings/Social/index.tsx +11 -3
- package/src/containers/Settings/Social/slice.ts +40 -0
- package/src/containers/Settings/Social/thunks.ts +68 -0
- package/src/containers/Sites/actions.tsx +6 -2
- package/src/index.tsx +7 -6
- package/src/modules/ActivityLog/ItemLog/EventItem/index.tsx +13 -27
- package/src/modules/ActivityLog/ItemLogUser/UserItem/EventItem/index.tsx +13 -27
- package/src/modules/ActivityLog/index.tsx +22 -51
- package/src/modules/Analytics/index.tsx +33 -42
- package/src/modules/App/Routing/index.tsx +5 -4
- package/src/modules/FileDrive/index.tsx +1 -1
- package/src/modules/GlobalSettings/Robots/index.tsx +24 -47
- package/src/modules/MediaGallery/index.tsx +1 -1
- package/src/modules/Redirects/RedirectItem/index.tsx +9 -22
- package/src/modules/Redirects/RedirectPanel/index.tsx +13 -14
- package/src/modules/Redirects/index.tsx +26 -69
- package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +4 -3
- package/src/modules/Settings/Languages/LanguagePanel/Form/ConnectedField/index.tsx +11 -18
- package/src/modules/Settings/Languages/LanguagePanel/Form/index.tsx +7 -9
- package/src/modules/Settings/Languages/LanguagePanel/index.tsx +38 -44
- package/src/modules/Settings/Languages/Table/Header/index.tsx +12 -19
- package/src/modules/Settings/Languages/Table/Item/index.tsx +21 -36
- package/src/modules/Settings/Languages/Table/index.tsx +6 -14
- package/src/modules/Settings/Languages/index.tsx +18 -30
- package/src/modules/Settings/SeoAnalyticsSettings/Analytics/index.tsx +42 -52
- package/src/modules/Settings/Social/index.tsx +17 -12
- package/src/modules/StructuredData/StructuredDataList/index.tsx +3 -2
- package/src/store/hooks.ts +37 -0
- package/src/store/index.ts +2 -0
- package/src/types/index.tsx +5 -6
- package/tsconfig.paths.json +1 -0
- package/src/containers/ActivityLog/constants.tsx +0 -6
- package/src/containers/ActivityLog/interfaces.tsx +0 -12
- package/src/containers/ActivityLog/reducer.tsx +0 -24
- package/src/containers/Analytics/actions.tsx +0 -109
- package/src/containers/Analytics/constants.tsx +0 -5
- package/src/containers/Analytics/interfaces.tsx +0 -9
- package/src/containers/Analytics/reducer.tsx +0 -28
- package/src/containers/Domains/actions.tsx +0 -58
- package/src/containers/Domains/constants.tsx +0 -5
- package/src/containers/Domains/interfaces.tsx +0 -9
- package/src/containers/Domains/reducer.tsx +0 -22
- package/src/containers/Redirects/actions.tsx +0 -190
- package/src/containers/Redirects/interfaces.tsx +0 -23
- package/src/containers/Redirects/reducer.tsx +0 -35
- package/src/containers/Settings/Languages/actions.tsx +0 -88
- package/src/containers/Settings/Languages/constants.tsx +0 -6
- package/src/containers/Settings/Languages/reducer.tsx +0 -31
- package/src/containers/Settings/Social/actions.tsx +0 -63
- package/src/containers/Settings/Social/constants.tsx +0 -3
- package/src/containers/Settings/Social/interfaces.tsx +0 -9
- package/src/containers/Settings/Social/reducer.tsx +0 -18
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
3
2
|
|
|
4
3
|
import {
|
|
5
4
|
EmptyState,
|
|
@@ -14,7 +13,8 @@ import {
|
|
|
14
13
|
import { appActions } from "@ax/containers/App";
|
|
15
14
|
import { redirectsActions } from "@ax/containers/Redirects";
|
|
16
15
|
import { useBulkSelection, useModals, useToast } from "@ax/hooks";
|
|
17
|
-
import
|
|
16
|
+
import { useAppDispatch, useAppSelector } from "@ax/store";
|
|
17
|
+
import type { IEmptyStateProps, INavItem, IQueryValue, IRedirect } from "@ax/types";
|
|
18
18
|
|
|
19
19
|
import { DeleteModal, ImportCheckModal, ImportModal, OverwriteModal } from "./atoms";
|
|
20
20
|
import BulkHeader from "./BulkHeader";
|
|
@@ -26,22 +26,15 @@ import { getSortedListStatus } from "./utils";
|
|
|
26
26
|
import * as S from "./style";
|
|
27
27
|
|
|
28
28
|
const Redirects = (props: IProps): JSX.Element => {
|
|
29
|
-
const {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
currentSite,
|
|
39
|
-
importRedirects,
|
|
40
|
-
imports,
|
|
41
|
-
totalImports,
|
|
42
|
-
isLoading,
|
|
43
|
-
isSaving,
|
|
44
|
-
} = props;
|
|
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);
|
|
45
38
|
|
|
46
39
|
const itemsPerPage = 50;
|
|
47
40
|
const firstPage = 1;
|
|
@@ -88,7 +81,7 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
88
81
|
// biome-ignore lint/correctness/useExhaustiveDependencies: TODO: fix this
|
|
89
82
|
useEffect(() => {
|
|
90
83
|
const params = getParams();
|
|
91
|
-
getRedirects(params, filterQuery);
|
|
84
|
+
dispatch(redirectsActions.getRedirects({ params, filters: filterQuery }));
|
|
92
85
|
if (tableRef.current) {
|
|
93
86
|
tableRef.current.scrollTo(0, 0);
|
|
94
87
|
}
|
|
@@ -125,7 +118,7 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
125
118
|
} = useBulkSelection(redIds);
|
|
126
119
|
|
|
127
120
|
const bulkDelete = async () => {
|
|
128
|
-
const deleted = await deleteRedirect(selectedItems.all);
|
|
121
|
+
const deleted = await dispatch(redirectsActions.deleteRedirect({ redirectID: selectedItems.all })).unwrap();
|
|
129
122
|
if (deleted) {
|
|
130
123
|
toggleToast();
|
|
131
124
|
}
|
|
@@ -191,7 +184,7 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
191
184
|
};
|
|
192
185
|
|
|
193
186
|
const handleMenuClick = (path: string) => {
|
|
194
|
-
setHistoryPush(path);
|
|
187
|
+
dispatch(appActions.setHistoryPush(path));
|
|
195
188
|
};
|
|
196
189
|
|
|
197
190
|
const handleModal = () => {
|
|
@@ -202,7 +195,13 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
202
195
|
|
|
203
196
|
const handleAddRedirect = async (force?: boolean) => {
|
|
204
197
|
const toPage = formValues.to.pageId ? formValues.to.pageId : formValues.to.url;
|
|
205
|
-
|
|
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");
|
|
206
205
|
isOpen("edit") && toggleModal("edit");
|
|
207
206
|
};
|
|
208
207
|
|
|
@@ -230,14 +229,14 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
230
229
|
|
|
231
230
|
const handleCheckImportData = async (data: { from: string; to: string }[]) => {
|
|
232
231
|
setImportData(data);
|
|
233
|
-
await importRedirects(data, true);
|
|
232
|
+
await dispatch(redirectsActions.importRedirects({ rows: data, check: true }));
|
|
234
233
|
toggleModal("import");
|
|
235
234
|
setIsUploading(false);
|
|
236
235
|
toggleModal("checkImport");
|
|
237
236
|
};
|
|
238
237
|
|
|
239
238
|
const handleImportData = async () => {
|
|
240
|
-
const imported = await importRedirects(importData, false);
|
|
239
|
+
const imported = await dispatch(redirectsActions.importRedirects({ rows: importData, check: false })).unwrap();
|
|
241
240
|
if (imported) {
|
|
242
241
|
toggleImportToast();
|
|
243
242
|
}
|
|
@@ -253,6 +252,7 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
253
252
|
<MainWrapper
|
|
254
253
|
backLink={false}
|
|
255
254
|
title="SEO Settings"
|
|
255
|
+
screen="redirects"
|
|
256
256
|
rightButton={rightButtonProps}
|
|
257
257
|
rightLineButton={rightLineButtonProps}
|
|
258
258
|
searchAction={handleSearch}
|
|
@@ -360,52 +360,9 @@ const Redirects = (props: IProps): JSX.Element => {
|
|
|
360
360
|
);
|
|
361
361
|
};
|
|
362
362
|
|
|
363
|
-
|
|
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 {
|
|
363
|
+
interface IProps {
|
|
381
364
|
navItems: INavItem[];
|
|
382
365
|
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
|
-
};
|
|
394
366
|
}
|
|
395
367
|
|
|
396
|
-
|
|
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);
|
|
368
|
+
export default Redirects;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
|
+
import { useHistory } from "react-router-dom";
|
|
3
4
|
|
|
4
5
|
import { EmptyState, ErrorToast, Loading, MainWrapper, Modal, Toast } from "@ax/components";
|
|
5
6
|
import { appActions } from "@ax/containers/App";
|
|
@@ -30,9 +31,11 @@ const DataPacks = (props: IProps): JSX.Element => {
|
|
|
30
31
|
addDataPacks,
|
|
31
32
|
deleteSiteDataPack,
|
|
32
33
|
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
|
+
|
|
36
39
|
const isFromPage = selected?.templates && selected.templates.length > 0;
|
|
37
40
|
|
|
38
41
|
const { isDirty, setIsDirty } = useShouldBeSaved(selected, form);
|
|
@@ -220,7 +223,6 @@ interface IProps {
|
|
|
220
223
|
selected: any;
|
|
221
224
|
form: any;
|
|
222
225
|
activatedDataPacks: IDataPack[];
|
|
223
|
-
routerAction: string;
|
|
224
226
|
getSiteDataPacks: (queryParams?: string) => void;
|
|
225
227
|
setSelected: (dataPack?: any) => void;
|
|
226
228
|
updateDataPack: (dataPack?: any) => Promise<boolean>;
|
|
@@ -246,7 +248,6 @@ const mapStateToProps = (state: IRootState) => ({
|
|
|
246
248
|
selected: state.dataPacks.selected,
|
|
247
249
|
form: state.dataPacks.configFormData,
|
|
248
250
|
activatedDataPacks: state.dataPacks.activated,
|
|
249
|
-
routerAction: state.router.action,
|
|
250
251
|
});
|
|
251
252
|
|
|
252
253
|
export default connect(mapStateToProps, mapDispatchToProps)(DataPacks);
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
1
|
+
import React, { memo } from "react";
|
|
3
2
|
|
|
4
|
-
import type { IRootState } from "@ax/types";
|
|
5
|
-
import { languagesActions } from "@ax/containers/Settings";
|
|
6
3
|
import { FieldsBehavior } from "@ax/components";
|
|
4
|
+
import { languagesActions } from "@ax/containers/Settings";
|
|
5
|
+
import { useAppDispatch, useAppSelector } from "@ax/store";
|
|
7
6
|
|
|
8
7
|
const ConnectedField = (props: IProps) => {
|
|
9
|
-
const {
|
|
8
|
+
const { type, name } = props;
|
|
10
9
|
|
|
11
|
-
const
|
|
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]);
|
|
12
14
|
|
|
13
15
|
const handleChange = (newValue: any) => {
|
|
14
|
-
updateFormValue({ [name]: newValue });
|
|
16
|
+
dispatch(languagesActions.updateFormValue({ [name]: newValue }));
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
const fieldProps = {
|
|
@@ -24,7 +26,6 @@ const ConnectedField = (props: IProps) => {
|
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
interface IProps {
|
|
27
|
-
form: any;
|
|
28
29
|
type: string;
|
|
29
30
|
title: string;
|
|
30
31
|
name: string;
|
|
@@ -33,16 +34,8 @@ interface IProps {
|
|
|
33
34
|
helptext?: string;
|
|
34
35
|
options?: any;
|
|
35
36
|
filter?: any;
|
|
36
|
-
updateFormValue: (value: any) => void;
|
|
37
37
|
autoComplete?: string;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const mapDispatchToProps = {
|
|
45
|
-
updateFormValue: languagesActions.updateFormValue,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export default connect(mapStateToProps, mapDispatchToProps)(ConnectedField);
|
|
40
|
+
// `connect` used to memoise this component implicitly; keep that now it is gone.
|
|
41
|
+
export default memo(ConnectedField);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
3
2
|
|
|
4
|
-
import type { ILanguage, IRootState } from "@ax/types";
|
|
5
3
|
import { Flag } from "@ax/components";
|
|
4
|
+
import { useAppSelector } from "@ax/store";
|
|
5
|
+
import type { ILanguage } from "@ax/types";
|
|
6
|
+
|
|
6
7
|
import ConnectedField from "./ConnectedField";
|
|
7
8
|
|
|
8
9
|
import * as S from "./style";
|
|
9
10
|
|
|
10
11
|
const Form = (props: IProps): JSX.Element => {
|
|
11
|
-
const {
|
|
12
|
+
const { item } = props;
|
|
13
|
+
|
|
14
|
+
const currentSiteLanguages = useAppSelector((state) => state.sites.currentSiteLanguages);
|
|
12
15
|
|
|
13
16
|
const currentSiteLanguagesIds = currentSiteLanguages.map((e) => e.id);
|
|
14
17
|
|
|
@@ -52,13 +55,8 @@ const Form = (props: IProps): JSX.Element => {
|
|
|
52
55
|
);
|
|
53
56
|
};
|
|
54
57
|
|
|
55
|
-
const mapStateToProps = (state: IRootState) => ({
|
|
56
|
-
currentSiteLanguages: state.sites.currentSiteLanguages,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
58
|
interface IProps {
|
|
60
59
|
item?: ILanguage;
|
|
61
|
-
currentSiteLanguages: ILanguage[];
|
|
62
60
|
}
|
|
63
61
|
|
|
64
|
-
export default
|
|
62
|
+
export default Form;
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
3
2
|
|
|
4
|
-
import
|
|
3
|
+
import { Button, FloatingPanel } from "@ax/components";
|
|
5
4
|
import { languagesActions } from "@ax/containers/Settings";
|
|
6
|
-
import {
|
|
5
|
+
import { useAppDispatch, useAppSelector } from "@ax/store";
|
|
6
|
+
import type { ILanguage } from "@ax/types";
|
|
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,
|
|
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);
|
|
14
18
|
|
|
15
19
|
if (!currentSiteID) {
|
|
16
20
|
throw new Error(`ERROR: User reached Languages with null site info`);
|
|
17
21
|
}
|
|
18
22
|
|
|
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.
|
|
19
25
|
const addItemAction = () => {
|
|
20
|
-
createSiteLanguage(currentSiteID, languageData);
|
|
26
|
+
dispatch(languagesActions.createSiteLanguage({ siteID: currentSiteID, data: languageData }));
|
|
21
27
|
toggleModal();
|
|
22
28
|
};
|
|
23
29
|
|
|
24
30
|
const editItemAction = () => {
|
|
25
|
-
updateSiteLanguage(currentSiteID, languageData);
|
|
31
|
+
dispatch(languagesActions.updateSiteLanguage({ siteID: currentSiteID, data: languageData }));
|
|
26
32
|
toggleModal();
|
|
27
33
|
};
|
|
28
34
|
|
|
@@ -40,52 +46,40 @@ const LanguagePanel = (props: IProps): JSX.Element => {
|
|
|
40
46
|
|
|
41
47
|
const title = item ? "Update Language" : "New Language";
|
|
42
48
|
|
|
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.
|
|
43
57
|
return (
|
|
44
58
|
<FloatingPanel title={title} toggleModal={toggleModal} isOpen={isOpen}>
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{editButton.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{addButton.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
)}
|
|
59
75
|
</FloatingPanel>
|
|
60
76
|
);
|
|
61
77
|
};
|
|
62
78
|
|
|
63
|
-
|
|
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 {
|
|
79
|
+
interface IProps {
|
|
79
80
|
item?: ILanguage;
|
|
80
81
|
isOpen: boolean;
|
|
81
82
|
toggleModal(): any;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
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);
|
|
85
|
+
export default LanguagePanel;
|
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
3
2
|
|
|
4
|
-
import { useModal } from "@ax/hooks";
|
|
5
|
-
import { languagesActions } from "@ax/containers/Settings";
|
|
6
|
-
import type { ILanguage } from "@ax/types";
|
|
7
3
|
import { IconAction } from "@ax/components";
|
|
4
|
+
import { languagesActions } from "@ax/containers/Settings";
|
|
8
5
|
import { Restricted } from "@ax/guards";
|
|
6
|
+
import { useModal } from "@ax/hooks";
|
|
7
|
+
import { useAppDispatch } from "@ax/store";
|
|
8
|
+
import type { ILanguage } from "@ax/types";
|
|
9
|
+
|
|
9
10
|
import LanguagePanel from "../../LanguagePanel";
|
|
10
11
|
|
|
11
12
|
import * as S from "./style";
|
|
12
13
|
|
|
13
14
|
const Header = (props: IProps): JSX.Element => {
|
|
14
|
-
const { items
|
|
15
|
+
const { items } = props;
|
|
16
|
+
|
|
17
|
+
const dispatch = useAppDispatch();
|
|
15
18
|
|
|
16
19
|
const title = items.length > 1 ? `${items.length} items` : `${items.length} item`;
|
|
17
20
|
|
|
18
21
|
const { isOpen, toggleModal } = useModal();
|
|
19
22
|
|
|
20
|
-
const resetValues = () => resetForm();
|
|
21
|
-
|
|
22
23
|
const openModal = () => {
|
|
23
|
-
|
|
24
|
+
dispatch(languagesActions.resetForm());
|
|
24
25
|
toggleModal();
|
|
25
26
|
};
|
|
26
27
|
|
|
@@ -34,17 +35,9 @@ const Header = (props: IProps): JSX.Element => {
|
|
|
34
35
|
</S.Header>
|
|
35
36
|
);
|
|
36
37
|
};
|
|
37
|
-
|
|
38
|
+
|
|
39
|
+
interface IProps {
|
|
38
40
|
items: ILanguage[];
|
|
39
41
|
}
|
|
40
|
-
interface IDispatchProps {
|
|
41
|
-
resetForm(): void;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type IProps = ITableHeaderProps & IDispatchProps;
|
|
45
|
-
|
|
46
|
-
const mapDispatchToProps = {
|
|
47
|
-
resetForm: languagesActions.resetForm,
|
|
48
|
-
};
|
|
49
42
|
|
|
50
|
-
export default
|
|
43
|
+
export default Header;
|
|
@@ -1,26 +1,32 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
1
|
+
import React, { memo } from "react";
|
|
3
2
|
|
|
4
3
|
import { ContextMenu, Flag } from "@ax/components";
|
|
5
|
-
import { useContextMenu, useModal, usePermission } from "@ax/hooks";
|
|
6
|
-
import type { ILanguage, ISiteLanguage, IRootState, ISite } from "@ax/types";
|
|
7
4
|
import { languagesActions } from "@ax/containers/Settings";
|
|
5
|
+
import { useContextMenu, useModal, usePermission } from "@ax/hooks";
|
|
6
|
+
import { useAppDispatch, useAppSelector } from "@ax/store";
|
|
7
|
+
import type { ILanguage, ISiteLanguage } from "@ax/types";
|
|
8
|
+
|
|
8
9
|
import LanguagePanel from "../../LanguagePanel";
|
|
9
10
|
|
|
10
11
|
import * as S from "./style";
|
|
11
12
|
|
|
12
|
-
const Item = (props:
|
|
13
|
-
const { item
|
|
13
|
+
const Item = (props: IProps): JSX.Element => {
|
|
14
|
+
const { item } = props;
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
16
|
+
const dispatch = useAppDispatch();
|
|
17
|
+
const currentSiteInfo = useAppSelector((state) => state.sites.currentSiteInfo);
|
|
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
|
+
|
|
24
30
|
const siteLanguage: ISiteLanguage = {
|
|
25
31
|
language: item.id,
|
|
26
32
|
path: item.path || "",
|
|
@@ -28,7 +34,8 @@ const Item = (props: IItemProps): JSX.Element => {
|
|
|
28
34
|
isDefault: item.isDefault,
|
|
29
35
|
};
|
|
30
36
|
|
|
31
|
-
const removeItemAction = () =>
|
|
37
|
+
const removeItemAction = () =>
|
|
38
|
+
dispatch(languagesActions.deleteSiteLanguage({ siteID: currentSiteInfo.id, languageID: item.id }));
|
|
32
39
|
|
|
33
40
|
const menuOptions = isAllowedToDelete
|
|
34
41
|
? [
|
|
@@ -40,13 +47,9 @@ const Item = (props: IItemProps): JSX.Element => {
|
|
|
40
47
|
]
|
|
41
48
|
: [];
|
|
42
49
|
|
|
43
|
-
const resetValues = () => resetForm();
|
|
44
|
-
|
|
45
|
-
const setItem = (item: ISiteLanguage) => setForm(item);
|
|
46
|
-
|
|
47
50
|
const openModal = () => {
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
dispatch(languagesActions.resetForm());
|
|
52
|
+
dispatch(languagesActions.setForm(siteLanguage));
|
|
50
53
|
toggleModal();
|
|
51
54
|
};
|
|
52
55
|
|
|
@@ -68,25 +71,7 @@ const Item = (props: IItemProps): JSX.Element => {
|
|
|
68
71
|
|
|
69
72
|
interface IProps {
|
|
70
73
|
item: ILanguage;
|
|
71
|
-
currentSiteInfo: ISite | null;
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
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);
|
|
76
|
+
// One per row: `connect` memoised it implicitly, so keep that now it is gone.
|
|
77
|
+
export default memo(Item);
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
3
2
|
|
|
4
|
-
import
|
|
3
|
+
import { useAppSelector } from "@ax/store";
|
|
4
|
+
import type { ILanguage } from "@ax/types";
|
|
5
5
|
|
|
6
|
-
import Item from "./Item";
|
|
7
6
|
import Header from "./Header";
|
|
7
|
+
import Item from "./Item";
|
|
8
8
|
|
|
9
9
|
import * as S from "./style";
|
|
10
10
|
|
|
11
|
-
const Table = (
|
|
12
|
-
const
|
|
11
|
+
const Table = (): JSX.Element => {
|
|
12
|
+
const items = useAppSelector((state) => state.sites.currentSiteLanguages);
|
|
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,12 +22,4 @@ const Table = (props: IStateProps): JSX.Element => {
|
|
|
22
22
|
);
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
items: state.sites.currentSiteLanguages,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
interface IStateProps {
|
|
30
|
-
items: ILanguage[];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default connect(mapStateToProps)(Table);
|
|
25
|
+
export default Table;
|
|
@@ -1,30 +1,37 @@
|
|
|
1
1
|
import React, { useEffect } from "react";
|
|
2
|
-
import { connect } from "react-redux";
|
|
3
2
|
|
|
4
|
-
import
|
|
5
|
-
import { sitesActions } from "@ax/containers/Sites";
|
|
3
|
+
import { ErrorToast, Loading, MainWrapper, Nav } from "@ax/components";
|
|
6
4
|
import { appActions } from "@ax/containers/App";
|
|
7
|
-
import {
|
|
5
|
+
import { sitesActions } from "@ax/containers/Sites";
|
|
6
|
+
import { useAppDispatch, useAppSelector } from "@ax/store";
|
|
7
|
+
import type { INavItem } from "@ax/types";
|
|
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 {
|
|
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);
|
|
15
19
|
|
|
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.
|
|
16
23
|
useEffect(() => {
|
|
17
24
|
if (currentSiteInfo) {
|
|
18
|
-
getSiteLanguages(currentSiteInfo.id);
|
|
25
|
+
dispatch(sitesActions.getSiteLanguages(currentSiteInfo.id));
|
|
19
26
|
}
|
|
20
|
-
}, [currentSiteInfo,
|
|
27
|
+
}, [currentSiteInfo, dispatch]);
|
|
21
28
|
|
|
22
29
|
const handleMenuClick = (path: string) => {
|
|
23
|
-
setHistoryPush(path);
|
|
30
|
+
dispatch(appActions.setHistoryPush(path));
|
|
24
31
|
};
|
|
25
32
|
|
|
26
33
|
return (
|
|
27
|
-
<MainWrapper title={`Languages`}>
|
|
34
|
+
<MainWrapper title={`Languages`} screen="languages">
|
|
28
35
|
<S.Wrapper>
|
|
29
36
|
<Nav current={currentNavItem} items={navItems} onClick={handleMenuClick} />
|
|
30
37
|
<S.ContentWrapper>
|
|
@@ -42,28 +49,9 @@ const Languages = (props: IProps): JSX.Element => {
|
|
|
42
49
|
);
|
|
43
50
|
};
|
|
44
51
|
|
|
45
|
-
|
|
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;
|
|
52
|
+
interface IProps {
|
|
63
53
|
navItems: INavItem[];
|
|
64
54
|
currentNavItem: INavItem;
|
|
65
55
|
}
|
|
66
56
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
export default connect(mapStateToProps, mapDispatchToProps)(Languages);
|
|
57
|
+
export default Languages;
|