@griddo/ax 1.65.26 → 1.66.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/package.json +2 -2
- package/src/GlobalStore.tsx +4 -3
- package/src/api/structuredData.tsx +15 -1
- package/src/components/Browser/index.tsx +13 -16
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +12 -0
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +11 -0
- package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +13 -0
- package/src/components/ConfigPanel/Form/index.tsx +3 -1
- package/src/components/ConfigPanel/index.tsx +19 -5
- package/src/components/ConfigPanel/style.tsx +5 -0
- package/src/components/FieldContainer/index.tsx +4 -0
- package/src/components/Fields/ComponentArray/MixableComponentArray/PasteModuleButton/index.tsx +49 -0
- package/src/components/Fields/ComponentArray/MixableComponentArray/index.tsx +51 -11
- package/src/components/Fields/ComponentArray/MixableComponentArray/style.tsx +14 -1
- package/src/components/Fields/ComponentContainer/index.tsx +24 -5
- package/src/components/Fields/ImageField/index.tsx +18 -5
- package/src/components/Image/index.tsx +25 -0
- package/src/components/Notification/index.tsx +3 -1
- package/src/components/index.tsx +2 -0
- package/src/containers/Navigation/Defaults/actions.tsx +27 -9
- package/src/containers/PageEditor/actions.tsx +104 -5
- package/src/containers/PageEditor/constants.tsx +2 -0
- package/src/containers/PageEditor/interfaces.tsx +12 -0
- package/src/containers/PageEditor/reducer.tsx +8 -0
- package/src/containers/PageEditor/utils.tsx +2 -2
- package/src/helpers/index.tsx +6 -0
- package/src/helpers/schemas.tsx +36 -7
- package/src/modules/App/Routing/index.tsx +1 -1
- package/src/modules/Content/OptionTable/index.tsx +44 -43
- package/src/modules/Content/OptionTable/store.tsx +1 -1
- package/src/modules/Content/OptionTable/style.tsx +27 -12
- package/src/modules/Content/PageItem/index.tsx +14 -4
- package/src/modules/Content/atoms.tsx +19 -2
- package/src/modules/Content/index.tsx +37 -14
- package/src/modules/Content/utils.tsx +27 -12
- package/src/modules/GlobalEditor/Editor/index.tsx +12 -1
- package/src/modules/GlobalEditor/index.tsx +20 -2
- package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +13 -0
- package/src/modules/Navigation/Defaults/atoms.tsx +28 -0
- package/src/modules/Navigation/Defaults/index.tsx +30 -4
- package/src/modules/Navigation/Defaults/style.tsx +32 -1
- package/src/modules/PageEditor/Editor/index.tsx +16 -1
- package/src/modules/PageEditor/index.tsx +14 -1
- package/src/modules/PublicPreview/index.tsx +15 -18
- package/src/modules/Settings/Globals/NavigationModules/SideModal/SideModalOption/index.tsx +35 -0
- package/src/modules/Settings/Globals/NavigationModules/SideModal/SideModalOption/style.tsx +22 -0
- package/src/modules/Settings/Globals/NavigationModules/SideModal/index.tsx +111 -0
- package/src/modules/Settings/Globals/NavigationModules/SideModal/style.tsx +64 -0
- package/src/modules/Settings/Globals/NavigationModules/index.tsx +89 -0
- package/src/modules/Settings/Globals/NavigationModules/style.tsx +36 -0
- package/src/modules/Settings/Globals/index.tsx +38 -1
- package/src/modules/Sites/SitesList/SiteItem/index.tsx +7 -5
- package/src/modules/StructuredData/StructuredDataList/OptionTable/index.tsx +14 -3
- package/src/modules/StructuredData/StructuredDataList/OptionTable/style.tsx +11 -2
- package/src/modules/StructuredData/StructuredDataList/atoms.tsx +19 -2
- package/src/modules/StructuredData/StructuredDataList/index.tsx +4 -13
- package/src/types/index.tsx +11 -0
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
3
|
import { RouteComponentProps } from "react-router-dom";
|
|
4
4
|
|
|
5
|
-
import { IErrorItem, IRootState, ISavePageParams, IUserEditing } from "@ax/types";
|
|
5
|
+
import { IErrorItem, INotification, IRootState, ISavePageParams, IUserEditing } from "@ax/types";
|
|
6
6
|
import { MainWrapper, Loading, ErrorToast, Notification, Modal } from "@ax/components";
|
|
7
7
|
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
8
8
|
import { structuredDataActions } from "@ax/containers/StructuredData";
|
|
@@ -48,6 +48,7 @@ const GlobalEditor = (props: IProps) => {
|
|
|
48
48
|
const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
|
|
49
49
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
|
50
50
|
const [selectedTab, setSelectedTab] = useState("edit");
|
|
51
|
+
const [notification, setNotification] = useState<INotification | null>(null);
|
|
51
52
|
const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent.editorContent, isNewTranslation);
|
|
52
53
|
|
|
53
54
|
const isPublished = props.pageStatus === pageStatus.PUBLISHED || props.pageStatus === pageStatus.UPLOAD_PENDING;
|
|
@@ -442,10 +443,27 @@ const GlobalEditor = (props: IProps) => {
|
|
|
442
443
|
/>
|
|
443
444
|
</S.NotificationWrapper>
|
|
444
445
|
)}
|
|
446
|
+
{notification && (
|
|
447
|
+
<S.NotificationWrapper>
|
|
448
|
+
<Notification
|
|
449
|
+
type={notification.type}
|
|
450
|
+
text={notification.text}
|
|
451
|
+
btnText={notification.btnText}
|
|
452
|
+
onClick={notification.onClick}
|
|
453
|
+
resetError={() => setNotification(null)}
|
|
454
|
+
/>
|
|
455
|
+
</S.NotificationWrapper>
|
|
456
|
+
)}
|
|
445
457
|
<ErrorToast size="l" />
|
|
446
458
|
{selectedTab === "edit" ? (
|
|
447
459
|
<S.Content>
|
|
448
|
-
<Editor
|
|
460
|
+
<Editor
|
|
461
|
+
isGlobal={true}
|
|
462
|
+
isEditable={isEditable}
|
|
463
|
+
isReadOnly={isReadOnly}
|
|
464
|
+
theme={theme}
|
|
465
|
+
setNotification={setNotification}
|
|
466
|
+
/>
|
|
449
467
|
</S.Content>
|
|
450
468
|
) : (
|
|
451
469
|
<Preview theme={theme} />
|
|
@@ -31,6 +31,7 @@ const DefaultsEditor = (props: IProps) => {
|
|
|
31
31
|
setHeader,
|
|
32
32
|
setFooter,
|
|
33
33
|
setHistoryPush,
|
|
34
|
+
currentSiteInfo,
|
|
34
35
|
} = props;
|
|
35
36
|
|
|
36
37
|
const { isOpen, toggleModal } = useModal();
|
|
@@ -52,6 +53,16 @@ const DefaultsEditor = (props: IProps) => {
|
|
|
52
53
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
53
54
|
}, [lang]);
|
|
54
55
|
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const navigationModuleComponent = currentSiteInfo.navigationModules?.[editorContent?.type];
|
|
58
|
+
const currentNavigation = currentDefaultsContent?.find((item: any) => item.id === editorContent?.id);
|
|
59
|
+
if (navigationModuleComponent && editorContent && currentNavigation) {
|
|
60
|
+
const isNavigationModuleChanged = navigationModuleComponent !== currentNavigation.component;
|
|
61
|
+
isNavigationModuleChanged && setIsDirty(true);
|
|
62
|
+
}
|
|
63
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
64
|
+
}, [currentSiteInfo, editorContent?.id]);
|
|
65
|
+
|
|
55
66
|
const save = () => {
|
|
56
67
|
isNew || isNewTranslation
|
|
57
68
|
? createNavigation().then((isSaved: boolean) => {
|
|
@@ -160,6 +171,7 @@ const mapStateToProps = (state: IRootState) => ({
|
|
|
160
171
|
navLanguages: state.navigation.currentNavigationLanguages,
|
|
161
172
|
currentDefaultsContent: state.navigation.currentDefaultsContent,
|
|
162
173
|
isNewTranslation: state.navigation.isNewTranslation,
|
|
174
|
+
currentSiteInfo: state.sites.currentSiteInfo,
|
|
163
175
|
});
|
|
164
176
|
|
|
165
177
|
interface IStateProps {
|
|
@@ -174,6 +186,7 @@ interface IStateProps {
|
|
|
174
186
|
footer: number | null;
|
|
175
187
|
navLanguages: any[];
|
|
176
188
|
isNewTranslation: boolean;
|
|
189
|
+
currentSiteInfo: any;
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
const mapDispatchToProps = {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import * as S from "./style";
|
|
3
|
+
|
|
4
|
+
const NavigationModulesWarning = (props: INavigationModulesWarning): JSX.Element => {
|
|
5
|
+
const { goTo } = props;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<S.NavigationModulesWarning>
|
|
9
|
+
<S.NavigationModulesTitle>Navigation Modules</S.NavigationModulesTitle>
|
|
10
|
+
<S.NavigationModulesDescription>
|
|
11
|
+
Create as many headers as you need for your site. You can decide on which page to use each one. To change the
|
|
12
|
+
header design, you have to go to{" "}
|
|
13
|
+
<S.SiteSettingsLink>
|
|
14
|
+
<span onClick={goTo} onKeyPress={goTo} role="checkbox" aria-checked="false" tabIndex={0}>
|
|
15
|
+
Site Settings
|
|
16
|
+
</span>
|
|
17
|
+
</S.SiteSettingsLink>
|
|
18
|
+
.
|
|
19
|
+
</S.NavigationModulesDescription>
|
|
20
|
+
</S.NavigationModulesWarning>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
interface INavigationModulesWarning {
|
|
25
|
+
goTo(): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { NavigationModulesWarning };
|
|
@@ -5,14 +5,15 @@ import { appActions } from "@ax/containers/App";
|
|
|
5
5
|
import { menuActions } from "@ax/containers/Navigation";
|
|
6
6
|
import { IRootState, IHeader, IFooter } from "@ax/types";
|
|
7
7
|
import { useBulkSelection, useToast } from "@ax/hooks";
|
|
8
|
-
import { capitalize } from "@ax/helpers";
|
|
8
|
+
import { capitalize, isMultipleNavigationModules } from "@ax/helpers";
|
|
9
9
|
import { navigationActions } from "@ax/containers/Navigation";
|
|
10
|
-
import { MainWrapper, TableList, ErrorToast, Toast } from "@ax/components";
|
|
10
|
+
import { MainWrapper, TableList, ErrorToast, Toast, Notification } from "@ax/components";
|
|
11
11
|
|
|
12
12
|
import DefaultItem from "./Item";
|
|
13
13
|
import DefaultNav from "./Nav";
|
|
14
14
|
import BulkHeader from "./BulkHeader";
|
|
15
15
|
|
|
16
|
+
import { NavigationModulesWarning } from "./atoms";
|
|
16
17
|
import * as S from "./style";
|
|
17
18
|
|
|
18
19
|
const DefaultsList = (props: IProps): JSX.Element => {
|
|
@@ -33,6 +34,7 @@ const DefaultsList = (props: IProps): JSX.Element => {
|
|
|
33
34
|
deleteNavigation,
|
|
34
35
|
getMenus,
|
|
35
36
|
resetDefaultsValues,
|
|
37
|
+
currentSiteInfo,
|
|
36
38
|
} = props;
|
|
37
39
|
|
|
38
40
|
const [page, setPage] = useState(1);
|
|
@@ -40,6 +42,7 @@ const DefaultsList = (props: IProps): JSX.Element => {
|
|
|
40
42
|
const { isVisible, toggleToast, setIsVisible } = useToast();
|
|
41
43
|
const [isScrolling, setIsScrolling] = useState(false);
|
|
42
44
|
const tableRef = useRef<HTMLDivElement>(null);
|
|
45
|
+
const [isNavigationNotificationOpen, setIsNavigationNotificationOpen] = useState(false);
|
|
43
46
|
|
|
44
47
|
const navIds = currentDefaultsContent && currentDefaultsContent.map((nav: any) => nav.id);
|
|
45
48
|
|
|
@@ -58,6 +61,8 @@ const DefaultsList = (props: IProps): JSX.Element => {
|
|
|
58
61
|
|
|
59
62
|
const currentType = selectedDefault === "Headers" ? "header" : "footer";
|
|
60
63
|
|
|
64
|
+
const showNavigationModulesWarning = isMultipleNavigationModules();
|
|
65
|
+
|
|
61
66
|
const getParams = useCallback(() => {
|
|
62
67
|
return {
|
|
63
68
|
page,
|
|
@@ -92,13 +97,23 @@ const DefaultsList = (props: IProps): JSX.Element => {
|
|
|
92
97
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
93
98
|
}, []);
|
|
94
99
|
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const isNavigationModulesChanged =
|
|
102
|
+
currentSiteInfo.navigationModules?.[currentType] &&
|
|
103
|
+
currentDefaultsContent.some(
|
|
104
|
+
(navigation) => navigation.component !== currentSiteInfo.navigationModules[currentType]
|
|
105
|
+
);
|
|
106
|
+
setIsNavigationNotificationOpen(isNavigationModulesChanged);
|
|
107
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
108
|
+
}, [currentDefaultsContent]);
|
|
109
|
+
|
|
95
110
|
const handleClick = (selectedDefault: string) => {
|
|
96
111
|
getContents(selectedDefault);
|
|
97
112
|
};
|
|
98
113
|
|
|
99
114
|
const setContent = (item: any) => {
|
|
100
|
-
const {
|
|
101
|
-
const isHeader =
|
|
115
|
+
const { type, id } = item;
|
|
116
|
+
const isHeader = type === "header";
|
|
102
117
|
isHeader ? setHeader(id) : setFooter(id);
|
|
103
118
|
};
|
|
104
119
|
|
|
@@ -166,6 +181,8 @@ const DefaultsList = (props: IProps): JSX.Element => {
|
|
|
166
181
|
/>
|
|
167
182
|
);
|
|
168
183
|
|
|
184
|
+
const goToSiteSettings = () => setHistoryPush("/sites/settings/globals");
|
|
185
|
+
|
|
169
186
|
return (
|
|
170
187
|
<MainWrapper
|
|
171
188
|
title="Navigation modules"
|
|
@@ -178,6 +195,13 @@ const DefaultsList = (props: IProps): JSX.Element => {
|
|
|
178
195
|
<DefaultNav current={selectedDefault} defaultTypes={defaultTypes} onClick={handleClick} />
|
|
179
196
|
<S.TableWrapper>
|
|
180
197
|
<ErrorToast />
|
|
198
|
+
{isNavigationNotificationOpen && (
|
|
199
|
+
<Notification
|
|
200
|
+
type="warning"
|
|
201
|
+
text="The design of the navigation modules has changed. Please, check the previously created and save them."
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
{showNavigationModulesWarning && <NavigationModulesWarning goTo={goToSiteSettings} />}
|
|
181
205
|
<TableList
|
|
182
206
|
tableHeader={TableHeader}
|
|
183
207
|
pagination={pagination}
|
|
@@ -217,6 +241,7 @@ const mapStateToProps = (state: IRootState) => ({
|
|
|
217
241
|
selectedDefault: state.navigation.selectedDefault,
|
|
218
242
|
currentDefaultsContent: state.navigation.currentDefaultsContent,
|
|
219
243
|
totalItems: state.navigation.totalItems,
|
|
244
|
+
currentSiteInfo: state.sites.currentSiteInfo,
|
|
220
245
|
});
|
|
221
246
|
|
|
222
247
|
interface IDispatchProps {
|
|
@@ -239,6 +264,7 @@ interface IDefaultsProps {
|
|
|
239
264
|
selectedDefault: string;
|
|
240
265
|
currentDefaultsContent: (IHeader | IFooter)[];
|
|
241
266
|
totalItems: number;
|
|
267
|
+
currentSiteInfo: any;
|
|
242
268
|
}
|
|
243
269
|
|
|
244
270
|
type IProps = IDefaultsProps & IDispatchProps;
|
|
@@ -20,4 +20,35 @@ const PaginationWrapper = styled.span`
|
|
|
20
20
|
margin-top: ${(p) => p.theme.spacing.m};
|
|
21
21
|
`;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
const NavigationModulesWarning = styled.div`
|
|
24
|
+
padding: ${(p) => p.theme.spacing.m};
|
|
25
|
+
border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const NavigationModulesTitle = styled.div`
|
|
29
|
+
${(p) => p.theme.textStyle.headingXS};
|
|
30
|
+
margin-bottom: ${(p) => p.theme.spacing.xs};
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const NavigationModulesDescription = styled.div`
|
|
34
|
+
${(p) => p.theme.textStyle.uiM};
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const SiteSettingsLink = styled.div`
|
|
38
|
+
display: inline;
|
|
39
|
+
${(p) => p.theme.textStyle.uiS};
|
|
40
|
+
color: ${(p) => p.theme.color.interactive01};
|
|
41
|
+
span {
|
|
42
|
+
cursor: pointer;
|
|
43
|
+
}
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
DefaultListWrapper,
|
|
48
|
+
PaginationWrapper,
|
|
49
|
+
TableWrapper,
|
|
50
|
+
NavigationModulesWarning,
|
|
51
|
+
NavigationModulesTitle,
|
|
52
|
+
NavigationModulesDescription,
|
|
53
|
+
SiteSettingsLink,
|
|
54
|
+
};
|
|
@@ -5,7 +5,7 @@ import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
|
5
5
|
import { sitesActions } from "@ax/containers/Sites";
|
|
6
6
|
import { appActions } from "@ax/containers/App";
|
|
7
7
|
import { ConfigPanel, ResizePanel } from "@ax/components";
|
|
8
|
-
import { IBreadcrumbItem, IRootState, ISchema, ISite, IUserEditing } from "@ax/types";
|
|
8
|
+
import { IBreadcrumbItem, INotification, IRootState, ISchema, ISite, IUserEditing } from "@ax/types";
|
|
9
9
|
import PageBrowser from "../PageBrowser";
|
|
10
10
|
|
|
11
11
|
const Editor = (props: IProps) => {
|
|
@@ -35,6 +35,10 @@ const Editor = (props: IProps) => {
|
|
|
35
35
|
isReadOnly,
|
|
36
36
|
userEditing,
|
|
37
37
|
site,
|
|
38
|
+
lastElementAddedId,
|
|
39
|
+
copyModule,
|
|
40
|
+
pasteModule,
|
|
41
|
+
setNotification,
|
|
38
42
|
} = props;
|
|
39
43
|
|
|
40
44
|
const actions = {
|
|
@@ -47,6 +51,9 @@ const Editor = (props: IProps) => {
|
|
|
47
51
|
replaceElementsInCollectionAction: replaceElementsInCollection,
|
|
48
52
|
getGlobalFromLocalPageAction: getGlobalFromLocalPage,
|
|
49
53
|
saveCurrentSiteInfoAction: saveCurrentSiteInfo,
|
|
54
|
+
copyModuleAction: copyModule,
|
|
55
|
+
pasteModuleAction: pasteModule,
|
|
56
|
+
setNotificationAction: setNotification,
|
|
50
57
|
};
|
|
51
58
|
|
|
52
59
|
return (
|
|
@@ -71,6 +78,7 @@ const Editor = (props: IProps) => {
|
|
|
71
78
|
isReadOnly={isReadOnly}
|
|
72
79
|
userEditing={userEditing}
|
|
73
80
|
theme={site.theme}
|
|
81
|
+
lastElementAddedId={lastElementAddedId}
|
|
74
82
|
/>
|
|
75
83
|
}
|
|
76
84
|
/>
|
|
@@ -87,6 +95,7 @@ interface IEditorStateProps {
|
|
|
87
95
|
isLoading: boolean;
|
|
88
96
|
userEditing: IUserEditing | null;
|
|
89
97
|
site: ISite;
|
|
98
|
+
lastElementAddedId: null | number;
|
|
90
99
|
}
|
|
91
100
|
|
|
92
101
|
interface IPageBrowserDispatchProps {
|
|
@@ -102,6 +111,9 @@ interface IPageBrowserDispatchProps {
|
|
|
102
111
|
setHistoryPush(path: string, isEditor: boolean): void;
|
|
103
112
|
getGlobalFromLocalPage(): void;
|
|
104
113
|
saveCurrentSiteInfo(): void;
|
|
114
|
+
copyModule(editorID: number): boolean;
|
|
115
|
+
pasteModule(editorID: number): Promise<{ error?: INotification }>;
|
|
116
|
+
setNotification: (notification: INotification) => void;
|
|
105
117
|
isTemplateActivated: boolean;
|
|
106
118
|
isGlobal: boolean;
|
|
107
119
|
isEditable: boolean;
|
|
@@ -120,6 +132,7 @@ const mapStateToProps = (state: IRootState): IEditorStateProps => ({
|
|
|
120
132
|
isLoading: state.app.isLoading,
|
|
121
133
|
userEditing: state.pageEditor.userEditing,
|
|
122
134
|
site: state.sites.currentSiteInfo,
|
|
135
|
+
lastElementAddedId: state.pageEditor.lastElementAddedId,
|
|
123
136
|
});
|
|
124
137
|
|
|
125
138
|
const mapDispatchToProps = {
|
|
@@ -135,6 +148,8 @@ const mapDispatchToProps = {
|
|
|
135
148
|
setHistoryPush: appActions.setHistoryPush,
|
|
136
149
|
getGlobalFromLocalPage: pageEditorActions.getGlobalFromLocalPage,
|
|
137
150
|
saveCurrentSiteInfo: sitesActions.saveCurrentSiteInfo,
|
|
151
|
+
copyModule: pageEditorActions.copyModule,
|
|
152
|
+
pasteModule: pageEditorActions.pasteModule,
|
|
138
153
|
};
|
|
139
154
|
|
|
140
155
|
export default connect(mapStateToProps, mapDispatchToProps)(Editor);
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|
|
2
2
|
import { connect } from "react-redux";
|
|
3
3
|
import { RouteComponentProps } from "react-router-dom";
|
|
4
4
|
|
|
5
|
-
import { IErrorItem,
|
|
5
|
+
import { IErrorItem, INotification, IRootState, ISavePageParams, IUserEditing, IPageLanguage } from "@ax/types";
|
|
6
6
|
import { MainWrapper, Loading, ErrorToast, Notification, Modal } from "@ax/components";
|
|
7
7
|
import { pageEditorActions } from "@ax/containers/PageEditor";
|
|
8
8
|
import { appActions } from "@ax/containers/App";
|
|
@@ -49,6 +49,7 @@ const PageEditor = (props: IProps) => {
|
|
|
49
49
|
const [deleteAllVersions, setDeleteAllVersions] = useState(false);
|
|
50
50
|
const [isReadOnly, setIsReadOnly] = useState(false);
|
|
51
51
|
const [selectedTab, setSelectedTab] = useState("edit");
|
|
52
|
+
const [notification, setNotification] = useState<INotification | null>(null);
|
|
52
53
|
const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent.editorContent, isNewTranslation);
|
|
53
54
|
const { isOpen, toggleModal } = useModal();
|
|
54
55
|
const { isOpen: isUnpublishOpen, toggleModal: toggleUnpublishModal } = useModal();
|
|
@@ -485,6 +486,17 @@ const PageEditor = (props: IProps) => {
|
|
|
485
486
|
<Notification type="warning" text={modifiedNotificationText} />
|
|
486
487
|
</S.NotificationWrapper>
|
|
487
488
|
)}
|
|
489
|
+
{notification && (
|
|
490
|
+
<S.NotificationWrapper>
|
|
491
|
+
<Notification
|
|
492
|
+
type={notification.type}
|
|
493
|
+
text={notification.text}
|
|
494
|
+
btnText={notification.btnText}
|
|
495
|
+
onClick={notification.onClick}
|
|
496
|
+
resetError={() => setNotification(null)}
|
|
497
|
+
/>
|
|
498
|
+
</S.NotificationWrapper>
|
|
499
|
+
)}
|
|
488
500
|
<ErrorToast size="l" />
|
|
489
501
|
{selectedTab === "edit" ? (
|
|
490
502
|
<S.Content>
|
|
@@ -494,6 +506,7 @@ const PageEditor = (props: IProps) => {
|
|
|
494
506
|
isEditable={isEditable}
|
|
495
507
|
pageTitle={pageName}
|
|
496
508
|
isReadOnly={isReadOnly}
|
|
509
|
+
setNotification={setNotification}
|
|
497
510
|
/>
|
|
498
511
|
</S.Content>
|
|
499
512
|
) : (
|
|
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
|
|
|
2
2
|
import { useParams } from "react-router-dom";
|
|
3
3
|
|
|
4
4
|
import * as components from "components";
|
|
5
|
-
import {
|
|
5
|
+
import { SiteProvider } from "components";
|
|
6
6
|
import { Preview } from "@griddo/core";
|
|
7
7
|
import { pages } from "@ax/api";
|
|
8
8
|
import { getDefaultTheme, isReqOk } from "@ax/helpers";
|
|
@@ -43,7 +43,6 @@ const PublicPreview = () => {
|
|
|
43
43
|
|
|
44
44
|
const API_URL = process.env.REACT_APP_API_ENDPOINT;
|
|
45
45
|
const PUBLIC_API_URL = process.env.REACT_APP_PUBLIC_API_ENDPOINT;
|
|
46
|
-
const { SiteProvider, AnimationProvider } = providers;
|
|
47
46
|
|
|
48
47
|
const globalTheme = getDefaultTheme();
|
|
49
48
|
const theme = state && state.site ? state.siteInfo.theme : globalTheme;
|
|
@@ -64,22 +63,20 @@ const PublicPreview = () => {
|
|
|
64
63
|
publicApiUrl={PUBLIC_API_URL}
|
|
65
64
|
siteId={state && state.site}
|
|
66
65
|
>
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
</S.Wrapper>
|
|
82
|
-
</AnimationProvider>
|
|
66
|
+
<S.Wrapper ref={(ref: any) => ((window as any).browserRef = ref)}>
|
|
67
|
+
{state && (
|
|
68
|
+
<Preview
|
|
69
|
+
isPage={true}
|
|
70
|
+
apiUrl={API_URL}
|
|
71
|
+
library={components}
|
|
72
|
+
content={state}
|
|
73
|
+
header={state && state.headerContent}
|
|
74
|
+
footer={state && state.footerContent}
|
|
75
|
+
languageId={state && state.language}
|
|
76
|
+
pageLanguages={state && state.pageLanguages}
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
</S.Wrapper>
|
|
83
80
|
</SiteProvider>
|
|
84
81
|
);
|
|
85
82
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React, { memo } from "react";
|
|
2
|
+
|
|
3
|
+
import { getThumbnailProps, filterImageText } from "@ax/helpers";
|
|
4
|
+
|
|
5
|
+
import * as S from "./style";
|
|
6
|
+
|
|
7
|
+
const getThumbnailData = (option: any, theme: string) => {
|
|
8
|
+
option = filterImageText(option.component);
|
|
9
|
+
return getThumbnailProps(option, false, theme);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const SideModalOption = (props: IProps) => {
|
|
13
|
+
const { option, handleClick, theme, selected } = props;
|
|
14
|
+
|
|
15
|
+
const thumbnailProps = getThumbnailData(option, theme);
|
|
16
|
+
|
|
17
|
+
const setOption = () => {
|
|
18
|
+
handleClick(option);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<S.Item onClick={setOption} selected={selected}>
|
|
23
|
+
<S.Thumbnail {...thumbnailProps} />
|
|
24
|
+
</S.Item>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface IProps {
|
|
29
|
+
option: any;
|
|
30
|
+
handleClick: any;
|
|
31
|
+
theme: string;
|
|
32
|
+
selected: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default memo(SideModalOption);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
|
|
3
|
+
export const Item = styled.li<{ selected: boolean }>`
|
|
4
|
+
cursor: pointer;
|
|
5
|
+
padding: ${(p) => p.theme.spacing.xs};
|
|
6
|
+
margin-bottom: ${(p) => p.theme.spacing.s};
|
|
7
|
+
box-shadow: ${(p) => p.theme.shadow.shadowS};
|
|
8
|
+
border-radius: ${(p) => p.theme.radii.s};
|
|
9
|
+
${(p) => p.theme.textStyle.uiS};
|
|
10
|
+
background-color: ${(p) => p.theme.color.interactiveBackground};
|
|
11
|
+
border: 2px solid ${(p) => (p.selected ? p.theme.color.interactive01 : "transparent")};
|
|
12
|
+
&:hover {
|
|
13
|
+
background: ${(p) => p.theme.color.overlayHoverPrimary};
|
|
14
|
+
}
|
|
15
|
+
&:focus {
|
|
16
|
+
background-color: ${(p) => p.theme.color.overlayFocusPrimary};
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
export const Thumbnail = styled.img`
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
`;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import React, { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { createPortal } from "react-dom";
|
|
3
|
+
|
|
4
|
+
import { useHandleClickOutside } from "@ax/hooks";
|
|
5
|
+
import { Button, IconAction, NoteField } from "@ax/components";
|
|
6
|
+
|
|
7
|
+
import SideModalOption from "./SideModalOption";
|
|
8
|
+
import * as S from "./style";
|
|
9
|
+
|
|
10
|
+
const SideModal = (props: ISideModalProps): JSX.Element | null => {
|
|
11
|
+
const {
|
|
12
|
+
navigationModules,
|
|
13
|
+
form,
|
|
14
|
+
isOpen,
|
|
15
|
+
toggleModal,
|
|
16
|
+
theme,
|
|
17
|
+
setNavigationModulesValue,
|
|
18
|
+
isNavigationModulesChanged,
|
|
19
|
+
setIsNavigationModulesChanged,
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
const [previousNavigationModules, setPreviousNavigationModules] = useState(form.navigationModules);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
isOpen && setPreviousNavigationModules(form.navigationModules);
|
|
26
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
27
|
+
}, [isOpen]);
|
|
28
|
+
|
|
29
|
+
const closeDiscardingChanges = () => {
|
|
30
|
+
setNavigationModulesValue(previousNavigationModules);
|
|
31
|
+
toggleModal();
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const save = () => {
|
|
35
|
+
setIsNavigationModulesChanged(true);
|
|
36
|
+
toggleModal();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
40
|
+
const handleClickOutside = (e: any) => {
|
|
41
|
+
if (wrapperRef.current?.contains(e.target)) return;
|
|
42
|
+
closeDiscardingChanges();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
useHandleClickOutside(isOpen, handleClickOutside);
|
|
46
|
+
|
|
47
|
+
const getNavigationOptions = (type: string): JSX.Element => {
|
|
48
|
+
return (
|
|
49
|
+
<S.NavigationOptionsWrapper>
|
|
50
|
+
<S.NavigationOptionsTitle>{type}</S.NavigationOptionsTitle>
|
|
51
|
+
{navigationModules[type].map((option: any, index: number) => {
|
|
52
|
+
const isSelected = form?.navigationModules && form?.navigationModules[type] === option.component;
|
|
53
|
+
const isDefault = !(form?.navigationModules && form?.navigationModules[type]) && option.defaultNavigation;
|
|
54
|
+
const handleClick = (option: any) =>
|
|
55
|
+
setNavigationModulesValue({ ...form.navigationModules, [type]: option.component });
|
|
56
|
+
return (
|
|
57
|
+
<SideModalOption
|
|
58
|
+
option={option}
|
|
59
|
+
selected={isSelected || isDefault}
|
|
60
|
+
handleClick={handleClick}
|
|
61
|
+
theme={theme}
|
|
62
|
+
key={`${option}${index}`}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</S.NavigationOptionsWrapper>
|
|
67
|
+
);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const noteFieldText = isNavigationModulesChanged
|
|
71
|
+
? "If you change the design, this will be changed in all headers and footers of all pages of the site."
|
|
72
|
+
: "Select the design you want for this site and create as many headers and footers as you need.";
|
|
73
|
+
|
|
74
|
+
const buttonText = isNavigationModulesChanged ? "Change design" : "Select design";
|
|
75
|
+
|
|
76
|
+
return isOpen
|
|
77
|
+
? createPortal(
|
|
78
|
+
<S.Wrapper ref={wrapperRef}>
|
|
79
|
+
<S.Header>
|
|
80
|
+
<S.Title>Navigation modules design</S.Title>
|
|
81
|
+
<S.ButtonWrapper>
|
|
82
|
+
<IconAction icon="close" onClick={closeDiscardingChanges} />
|
|
83
|
+
</S.ButtonWrapper>
|
|
84
|
+
</S.Header>
|
|
85
|
+
<S.Content>
|
|
86
|
+
<NoteField value={{ text: noteFieldText }} />
|
|
87
|
+
{["header", "footer"].map((type: string) => getNavigationOptions(type))}
|
|
88
|
+
</S.Content>
|
|
89
|
+
<S.Footer>
|
|
90
|
+
<Button type="button" onClick={save} buttonStyle="solid">
|
|
91
|
+
{buttonText}
|
|
92
|
+
</Button>
|
|
93
|
+
</S.Footer>
|
|
94
|
+
</S.Wrapper>,
|
|
95
|
+
document.body
|
|
96
|
+
)
|
|
97
|
+
: null;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
interface ISideModalProps {
|
|
101
|
+
isOpen: boolean;
|
|
102
|
+
navigationModules: Record<string, any[]>;
|
|
103
|
+
toggleModal: () => void;
|
|
104
|
+
theme: string;
|
|
105
|
+
form: any;
|
|
106
|
+
setNavigationModulesValue: (value: any) => void;
|
|
107
|
+
isNavigationModulesChanged: boolean;
|
|
108
|
+
setIsNavigationModulesChanged: Dispatch<SetStateAction<boolean>>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export default SideModal;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import styled from "styled-components";
|
|
2
|
+
|
|
3
|
+
const Wrapper = styled.div`
|
|
4
|
+
position: fixed;
|
|
5
|
+
width: calc(6 * ${(p) => p.theme.spacing.xl});
|
|
6
|
+
right: 0;
|
|
7
|
+
top: 0;
|
|
8
|
+
z-index: 1000;
|
|
9
|
+
height: 100vh;
|
|
10
|
+
background: ${(p) => p.theme.colors.uiBackground01};
|
|
11
|
+
box-shadow: ${(p) => p.theme.shadow.rightPanel};
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const Header = styled.div`
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: space-between;
|
|
18
|
+
height: ${(p) => p.theme.spacing.xl};
|
|
19
|
+
width: 100%;
|
|
20
|
+
padding: 0 ${(p) => p.theme.spacing.m};
|
|
21
|
+
background-color: ${(p) => p.theme.colors.uiBackground02};
|
|
22
|
+
border-bottom: 1px solid ${(p) => p.theme.colors.uiLine};
|
|
23
|
+
h6 {
|
|
24
|
+
${(p) => p.theme.textStyle.headingM}
|
|
25
|
+
color: ${(p) => p.theme.colors.textHighEmphasis};
|
|
26
|
+
text-transform: capitalize;
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
|
|
30
|
+
const Title = styled.h6``;
|
|
31
|
+
|
|
32
|
+
const Content = styled.div`
|
|
33
|
+
list-style: none;
|
|
34
|
+
padding: ${(p) => p.theme.spacing.m};
|
|
35
|
+
height: ${(p) => `calc(100vh - (${p.theme.spacing.xl} * 2))`};
|
|
36
|
+
width: ${(p) => `calc(${p.theme.spacing.xl} * 6)`};
|
|
37
|
+
overflow: auto;
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
const ButtonWrapper = styled.div`
|
|
41
|
+
margin: 0 0 0 auto;
|
|
42
|
+
`;
|
|
43
|
+
|
|
44
|
+
const NavigationOptionsWrapper = styled.div`
|
|
45
|
+
margin: ${(p) => p.theme.spacing.m} 0 ${(p) => p.theme.spacing.l};
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const NavigationOptionsTitle = styled.div`
|
|
49
|
+
${(p) => p.theme.textStyle.headingXSS}
|
|
50
|
+
color: ${(p) => p.theme.colors.textMediumEmphasis};
|
|
51
|
+
text-transform: uppercase;
|
|
52
|
+
padding-bottom: ${(p) => p.theme.spacing.xs};
|
|
53
|
+
margin-bottom: ${(p) => p.theme.spacing.s};
|
|
54
|
+
border-bottom: 1px solid ${(p) => p.theme.colors.uiLine};
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const Footer = styled.div`
|
|
58
|
+
padding: ${(p) => p.theme.spacing.s} ${(p) => p.theme.spacing.m} 0;
|
|
59
|
+
display: flex;
|
|
60
|
+
justify-content: flex-end;
|
|
61
|
+
border-top: 1px solid ${(p) => p.theme.color.uiLine};
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
export { Wrapper, Content, Header, Title, ButtonWrapper, NavigationOptionsWrapper, NavigationOptionsTitle, Footer };
|