@griddo/ax 1.60.7 → 1.61.0

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 (148) hide show
  1. package/package.json +4 -2
  2. package/public/fonts/fonts.css +48 -660
  3. package/public/index.html +19 -16
  4. package/public/templates/template-redirects.csv +2 -0
  5. package/src/GlobalStore.tsx +3 -0
  6. package/src/Style/fonts.tsx +98 -72
  7. package/src/api/analytics.tsx +78 -0
  8. package/src/api/index.tsx +2 -0
  9. package/src/api/redirects.tsx +30 -2
  10. package/src/components/Button/style.tsx +1 -0
  11. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/Field/index.tsx +1 -1
  12. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/TemplateManager/index.tsx +4 -3
  13. package/src/components/ConfigPanel/Form/ConnectedField/PageConnectedField/index.tsx +6 -1
  14. package/src/components/FieldContainer/index.tsx +2 -2
  15. package/src/components/Fields/AnalyticsField/PageAnalytics/atoms.tsx +75 -0
  16. package/src/components/Fields/AnalyticsField/PageAnalytics/index.tsx +139 -0
  17. package/src/components/Fields/AnalyticsField/StructuredDataAnalytics/atoms.tsx +77 -0
  18. package/src/components/Fields/AnalyticsField/StructuredDataAnalytics/index.tsx +89 -0
  19. package/src/components/Fields/AnalyticsField/index.tsx +38 -0
  20. package/src/components/Fields/AnalyticsField/style.tsx +13 -0
  21. package/src/components/Fields/AnalyticsField/utils.tsx +13 -0
  22. package/src/components/Fields/FieldGroup/style.tsx +1 -1
  23. package/src/components/Fields/ImageField/index.tsx +1 -1
  24. package/src/components/Fields/MultiCheckSelect/index.tsx +23 -6
  25. package/src/components/Fields/MultiCheckSelect/style.tsx +1 -0
  26. package/src/components/Fields/NoteField/index.tsx +1 -1
  27. package/src/components/Fields/RadioGroup/index.tsx +4 -3
  28. package/src/components/Fields/RadioGroup/style.tsx +8 -3
  29. package/src/components/Fields/RichText/index.tsx +13 -8
  30. package/src/components/Fields/TextArea/index.tsx +1 -3
  31. package/src/components/Fields/TextArea/style.tsx +3 -3
  32. package/src/components/Fields/UrlField/style.tsx +1 -0
  33. package/src/components/Fields/index.tsx +2 -0
  34. package/src/components/Modal/index.tsx +1 -1
  35. package/src/components/Modal/style.tsx +5 -1
  36. package/src/components/Notification/style.tsx +2 -5
  37. package/src/components/TableFilters/LiveFilter/index.tsx +4 -0
  38. package/src/components/TableFilters/NameFilter/style.tsx +1 -0
  39. package/src/components/TableFilters/TypeFilter/index.tsx +7 -2
  40. package/src/containers/Analytics/actions.tsx +58 -0
  41. package/src/containers/Analytics/constants.tsx +5 -0
  42. package/src/containers/Analytics/index.tsx +4 -0
  43. package/src/containers/Analytics/interfaces.tsx +9 -0
  44. package/src/containers/Analytics/reducer.tsx +26 -0
  45. package/src/containers/PageEditor/actions.tsx +1 -0
  46. package/src/containers/Redirects/actions.tsx +49 -3
  47. package/src/containers/Redirects/constants.tsx +12 -1
  48. package/src/containers/Redirects/interfaces.tsx +11 -1
  49. package/src/containers/Redirects/reducer.tsx +12 -1
  50. package/src/containers/Sites/actions.tsx +3 -0
  51. package/src/global.d.ts +2 -0
  52. package/src/helpers/index.tsx +4 -0
  53. package/src/helpers/requests.tsx +12 -7
  54. package/src/helpers/strings.tsx +13 -0
  55. package/src/hooks/forms.tsx +1 -1
  56. package/src/index.tsx +1 -0
  57. package/src/modules/Analytics/DimensionItem/index.tsx +71 -0
  58. package/src/modules/Analytics/DimensionItem/style.tsx +59 -0
  59. package/src/modules/Analytics/DimensionPanel/index.tsx +110 -0
  60. package/src/modules/Analytics/DimensionPanel/style.tsx +14 -0
  61. package/src/modules/Analytics/GroupItem/index.tsx +75 -0
  62. package/src/modules/Analytics/GroupItem/style.tsx +80 -0
  63. package/src/modules/Analytics/GroupPanel/index.tsx +178 -0
  64. package/src/modules/Analytics/GroupPanel/style.tsx +67 -0
  65. package/src/modules/Analytics/GroupPanel/utils.tsx +29 -0
  66. package/src/modules/Analytics/index.tsx +207 -0
  67. package/src/modules/Analytics/style.tsx +68 -0
  68. package/src/modules/Content/BulkHeader/TableHeader/index.tsx +1 -1
  69. package/src/modules/Content/PageItem/index.tsx +3 -3
  70. package/src/modules/Content/PageItem/style.tsx +1 -1
  71. package/src/modules/Content/hooks.tsx +2 -2
  72. package/src/modules/Content/index.tsx +22 -20
  73. package/src/modules/GlobalEditor/index.tsx +1 -1
  74. package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +2 -2
  75. package/src/modules/PageEditor/index.tsx +1 -1
  76. package/src/modules/Redirects/RedirectItem/index.tsx +6 -2
  77. package/src/modules/Redirects/RedirectPanel/index.tsx +2 -0
  78. package/src/modules/Redirects/atoms.tsx +212 -0
  79. package/src/modules/Redirects/index.tsx +85 -27
  80. package/src/modules/Redirects/style.tsx +124 -3
  81. package/src/modules/Settings/ContentTypes/DataPacks/index.tsx +1 -1
  82. package/src/modules/StructuredData/StructuredDataList/BulkHeader/TableHeader/index.tsx +1 -1
  83. package/src/modules/StructuredData/StructuredDataList/GlobalPageItem/index.tsx +12 -3
  84. package/src/modules/StructuredData/StructuredDataList/index.tsx +17 -2
  85. package/src/routes/multisite.tsx +20 -2
  86. package/src/schemas/pages/GlobalPage.tsx +75 -64
  87. package/src/schemas/pages/Page.tsx +79 -67
  88. package/src/types/index.tsx +25 -3
  89. package/public/fonts/Source_Sans_Pro-200-cyrillic-ext107.woff2 +0 -0
  90. package/public/fonts/Source_Sans_Pro-200-cyrillic-ext149.woff2 +0 -0
  91. package/public/fonts/Source_Sans_Pro-200-cyrillic108.woff2 +0 -0
  92. package/public/fonts/Source_Sans_Pro-200-cyrillic150.woff2 +0 -0
  93. package/public/fonts/Source_Sans_Pro-200-greek-ext109.woff2 +0 -0
  94. package/public/fonts/Source_Sans_Pro-200-greek-ext151.woff2 +0 -0
  95. package/public/fonts/Source_Sans_Pro-200-greek110.woff2 +0 -0
  96. package/public/fonts/Source_Sans_Pro-200-greek152.woff2 +0 -0
  97. package/public/fonts/Source_Sans_Pro-200-vietnamese111.woff2 +0 -0
  98. package/public/fonts/Source_Sans_Pro-200-vietnamese153.woff2 +0 -0
  99. package/public/fonts/Source_Sans_Pro-300-cyrillic-ext114.woff2 +0 -0
  100. package/public/fonts/Source_Sans_Pro-300-cyrillic-ext156.woff2 +0 -0
  101. package/public/fonts/Source_Sans_Pro-300-cyrillic115.woff2 +0 -0
  102. package/public/fonts/Source_Sans_Pro-300-cyrillic157.woff2 +0 -0
  103. package/public/fonts/Source_Sans_Pro-300-greek-ext116.woff2 +0 -0
  104. package/public/fonts/Source_Sans_Pro-300-greek-ext158.woff2 +0 -0
  105. package/public/fonts/Source_Sans_Pro-300-greek117.woff2 +0 -0
  106. package/public/fonts/Source_Sans_Pro-300-greek159.woff2 +0 -0
  107. package/public/fonts/Source_Sans_Pro-300-vietnamese118.woff2 +0 -0
  108. package/public/fonts/Source_Sans_Pro-300-vietnamese160.woff2 +0 -0
  109. package/public/fonts/Source_Sans_Pro-400-cyrillic-ext121.woff2 +0 -0
  110. package/public/fonts/Source_Sans_Pro-400-cyrillic-ext163.woff2 +0 -0
  111. package/public/fonts/Source_Sans_Pro-400-cyrillic122.woff2 +0 -0
  112. package/public/fonts/Source_Sans_Pro-400-cyrillic164.woff2 +0 -0
  113. package/public/fonts/Source_Sans_Pro-400-greek-ext123.woff2 +0 -0
  114. package/public/fonts/Source_Sans_Pro-400-greek-ext165.woff2 +0 -0
  115. package/public/fonts/Source_Sans_Pro-400-greek124.woff2 +0 -0
  116. package/public/fonts/Source_Sans_Pro-400-greek166.woff2 +0 -0
  117. package/public/fonts/Source_Sans_Pro-400-vietnamese125.woff2 +0 -0
  118. package/public/fonts/Source_Sans_Pro-400-vietnamese167.woff2 +0 -0
  119. package/public/fonts/Source_Sans_Pro-600-cyrillic-ext128.woff2 +0 -0
  120. package/public/fonts/Source_Sans_Pro-600-cyrillic-ext170.woff2 +0 -0
  121. package/public/fonts/Source_Sans_Pro-600-cyrillic129.woff2 +0 -0
  122. package/public/fonts/Source_Sans_Pro-600-cyrillic171.woff2 +0 -0
  123. package/public/fonts/Source_Sans_Pro-600-greek-ext130.woff2 +0 -0
  124. package/public/fonts/Source_Sans_Pro-600-greek-ext172.woff2 +0 -0
  125. package/public/fonts/Source_Sans_Pro-600-greek131.woff2 +0 -0
  126. package/public/fonts/Source_Sans_Pro-600-greek173.woff2 +0 -0
  127. package/public/fonts/Source_Sans_Pro-600-vietnamese132.woff2 +0 -0
  128. package/public/fonts/Source_Sans_Pro-600-vietnamese174.woff2 +0 -0
  129. package/public/fonts/Source_Sans_Pro-700-cyrillic-ext135.woff2 +0 -0
  130. package/public/fonts/Source_Sans_Pro-700-cyrillic-ext177.woff2 +0 -0
  131. package/public/fonts/Source_Sans_Pro-700-cyrillic136.woff2 +0 -0
  132. package/public/fonts/Source_Sans_Pro-700-cyrillic178.woff2 +0 -0
  133. package/public/fonts/Source_Sans_Pro-700-greek-ext137.woff2 +0 -0
  134. package/public/fonts/Source_Sans_Pro-700-greek-ext179.woff2 +0 -0
  135. package/public/fonts/Source_Sans_Pro-700-greek138.woff2 +0 -0
  136. package/public/fonts/Source_Sans_Pro-700-greek180.woff2 +0 -0
  137. package/public/fonts/Source_Sans_Pro-700-vietnamese139.woff2 +0 -0
  138. package/public/fonts/Source_Sans_Pro-700-vietnamese181.woff2 +0 -0
  139. package/public/fonts/Source_Sans_Pro-900-cyrillic-ext142.woff2 +0 -0
  140. package/public/fonts/Source_Sans_Pro-900-cyrillic-ext184.woff2 +0 -0
  141. package/public/fonts/Source_Sans_Pro-900-cyrillic143.woff2 +0 -0
  142. package/public/fonts/Source_Sans_Pro-900-cyrillic185.woff2 +0 -0
  143. package/public/fonts/Source_Sans_Pro-900-greek-ext144.woff2 +0 -0
  144. package/public/fonts/Source_Sans_Pro-900-greek-ext186.woff2 +0 -0
  145. package/public/fonts/Source_Sans_Pro-900-greek145.woff2 +0 -0
  146. package/public/fonts/Source_Sans_Pro-900-greek187.woff2 +0 -0
  147. package/public/fonts/Source_Sans_Pro-900-vietnamese146.woff2 +0 -0
  148. package/public/fonts/Source_Sans_Pro-900-vietnamese188.woff2 +0 -0
@@ -64,7 +64,7 @@ const PageEditor = (props: IProps) => {
64
64
  handleGetPage();
65
65
 
66
66
  if (!pageID) {
67
- setIsDirty(true);
67
+ setIsDirty(false);
68
68
  }
69
69
 
70
70
  const interval = setInterval(() => {
@@ -59,8 +59,12 @@ const RedirectItem = (props: IRedirectItemProps): JSX.Element => {
59
59
  title: "Delete redirect",
60
60
  onClick: removeItem,
61
61
  };
62
+
62
63
  const secondaryDeleteModalAction = { title: "Cancel", onClick: toggleModalDelete };
63
64
 
65
+ const target = `(${redirect.domain || ""}|${redirect.site?.siteUrl || ""})`;
66
+ const regex = new RegExp(target, "g");
67
+
64
68
  return (
65
69
  <>
66
70
  <S.ItemRow role="rowgroup" selected={isSelected}>
@@ -75,10 +79,10 @@ const RedirectItem = (props: IRedirectItemProps): JSX.Element => {
75
79
  </S.SiteCell>
76
80
  )}
77
81
  <S.UrlCell role="cell" onClick={handleClick}>
78
- {redirect.from}
82
+ {redirect.from.replace(regex, "")}
79
83
  </S.UrlCell>
80
84
  <S.UrlCell role="cell" onClick={handleClick}>
81
- {redirect.to?.url}
85
+ {redirect.to?.url.replace(regex, "")}
82
86
  </S.UrlCell>
83
87
  <S.ActionsCell role="cell">
84
88
  <S.StyledActionMenu icon="more" options={menuOptions} tooltip="Actions" />
@@ -67,6 +67,7 @@ const RedirectPanel = (props: IProps): JSX.Element => {
67
67
  value={formValues.from}
68
68
  onChange={handleOldUrl}
69
69
  autoComplete="redirect-old"
70
+ helptext="Type the complete old page url you want to redirect."
70
71
  />
71
72
  <FieldsBehavior
72
73
  title="New URL"
@@ -77,6 +78,7 @@ const RedirectPanel = (props: IProps): JSX.Element => {
77
78
  handlePanel={toggleSecondaryPanel}
78
79
  inFloatingPanel={true}
79
80
  autoComplete="redirect-new"
81
+ helptext="Select an internal page or paste an external url."
80
82
  />
81
83
  <S.Footer>
82
84
  {redirect ? (
@@ -0,0 +1,212 @@
1
+ import React, { useRef } from "react";
2
+
3
+ import { IModal, IRedirect } from "@ax/types";
4
+ import { Button, Icon, Modal } from "@ax/components";
5
+ import { trimText } from "@ax/helpers";
6
+
7
+ import * as S from "./style";
8
+
9
+ const DeleteModal = (props: IModal): JSX.Element => {
10
+ const { isOpen, toggleModal, mainModalAction, secondaryModalAction } = props;
11
+
12
+ return (
13
+ <Modal
14
+ isOpen={isOpen}
15
+ hide={toggleModal}
16
+ title="Delete Redirects"
17
+ secondaryAction={secondaryModalAction}
18
+ mainAction={mainModalAction}
19
+ size="S"
20
+ >
21
+ <S.ModalContent>
22
+ Are you sure you want to delete the selected <strong>redirects</strong>? This action{" "}
23
+ <strong>cannot be undone</strong>.
24
+ </S.ModalContent>
25
+ </Modal>
26
+ );
27
+ };
28
+
29
+ const OverwriteModal = (props: IModal): JSX.Element => {
30
+ const { isOpen, toggleModal, mainModalAction, secondaryModalAction } = props;
31
+
32
+ return (
33
+ <Modal
34
+ isOpen={isOpen}
35
+ hide={toggleModal}
36
+ title="Overwrite Redirect"
37
+ secondaryAction={secondaryModalAction}
38
+ mainAction={mainModalAction}
39
+ size="S"
40
+ >
41
+ <S.ModalContent>
42
+ This Redirect is already created. It will overwrite.
43
+ <br />
44
+ This action <strong>cannot be undone</strong>.
45
+ </S.ModalContent>
46
+ </Modal>
47
+ );
48
+ };
49
+
50
+ const ImportModal = (props: IImportModal): JSX.Element => {
51
+ const { isOpen, toggleModal, checkImportData, isUploading, setIsUploading } = props;
52
+
53
+ const fileInputRef = useRef<HTMLInputElement>(null);
54
+
55
+ const handleUploadClick = () => fileInputRef.current && fileInputRef.current.click();
56
+
57
+ const csvToArray = (str: string, delimiter = ";") => {
58
+ const fixedStr = str.replace(/,/g, ";").replace(/\r/g, "");
59
+ const rows = fixedStr.slice(fixedStr.indexOf("\n") + 1).split("\n");
60
+
61
+ const arr = rows.map((row: string) => {
62
+ const values = row.split(delimiter);
63
+ return {
64
+ from: values[0],
65
+ to: values[1],
66
+ };
67
+ });
68
+
69
+ return arr;
70
+ };
71
+
72
+ const handleFileChange = (e: any) => {
73
+ setIsUploading(true);
74
+ const file = e.currentTarget.files[0];
75
+ const reader = new FileReader();
76
+
77
+ reader.onload = (e: any) => {
78
+ const text = e.target.result;
79
+ const data = csvToArray(text);
80
+ checkImportData(data);
81
+ };
82
+
83
+ reader.readAsText(file);
84
+ };
85
+
86
+ return (
87
+ <Modal isOpen={isOpen} hide={toggleModal} title="Import Redirects" size="M">
88
+ <S.ModalCenterContent>
89
+ {isUploading ? (
90
+ <>
91
+ <S.ModalIconWrapper>
92
+ <Icon name="uploadFile" size="48" />
93
+ </S.ModalIconWrapper>
94
+ <S.ModalUploadingText>Uploading...</S.ModalUploadingText>
95
+ </>
96
+ ) : (
97
+ <S.ModalContentWrapper>
98
+ <S.ModalIcon>
99
+ <Icon name="uploadFile" size="32" />
100
+ </S.ModalIcon>
101
+ <S.ModalTitle>Import 301 Redirects</S.ModalTitle>
102
+ <S.ModalText>
103
+ Import your URL redirects by uploading a CSV file.
104
+ <br />
105
+ To use the right format, please download the template available below.
106
+ </S.ModalText>
107
+ <S.ModalButtons>
108
+ <input
109
+ onChange={handleFileChange}
110
+ multiple={false}
111
+ ref={fileInputRef}
112
+ type="file"
113
+ accept=".csv,.xlsx,.xls"
114
+ hidden
115
+ />
116
+ <Button className="button" type="button" buttonStyle="line">
117
+ <a
118
+ href={process.env.PUBLIC_URL + "/templates/template-redirects.csv"}
119
+ download={"template-redirects.csv"}
120
+ >
121
+ Download Template
122
+ </a>
123
+ </Button>
124
+ <Button className="button" type="button" onClick={handleUploadClick}>
125
+ Upload CSV file
126
+ </Button>
127
+ </S.ModalButtons>
128
+ </S.ModalContentWrapper>
129
+ )}
130
+ </S.ModalCenterContent>
131
+ </Modal>
132
+ );
133
+ };
134
+
135
+ const ImportCheckModal = (props: IImportCheckModal): JSX.Element => {
136
+ const { isOpen, toggleModal, mainModalAction, secondaryModalAction, imports, totalImports } = props;
137
+
138
+ const mapModalItems = (items: IRedirect[], icon: string, type: "online" | "error" | "info") => {
139
+ return items.map((item: IRedirect) => (
140
+ <S.ModalImportItem key={item.from}>
141
+ <S.ModalItemIcon type={type}>
142
+ <Icon name={icon} size="16" />
143
+ </S.ModalItemIcon>
144
+ <S.ModalUrl>{item.from}</S.ModalUrl>
145
+ <S.ModalItemArrow>
146
+ <Icon name="fullArrowRight" size="16" />
147
+ </S.ModalItemArrow>
148
+ <S.ModalUrl>{typeof item.to === "string" && item.to}</S.ModalUrl>
149
+ </S.ModalImportItem>
150
+ ));
151
+ };
152
+
153
+ return (
154
+ <Modal
155
+ isOpen={isOpen}
156
+ hide={toggleModal}
157
+ mainAction={mainModalAction}
158
+ secondaryAction={secondaryModalAction}
159
+ title="Import Redirects"
160
+ size="L"
161
+ >
162
+ {imports && (
163
+ <S.ModalContent>
164
+ <S.ModalImportResult>
165
+ We found <strong>{totalImports} redirects</strong>. Continue to finish the uploading.
166
+ </S.ModalImportResult>
167
+ {imports.error.length > 0 && (
168
+ <S.ModalImportWrapper>
169
+ <S.ModalImportSection>
170
+ <strong>{imports.error.length}</strong> redirects that do not correspond to any site.
171
+ </S.ModalImportSection>
172
+ {mapModalItems(imports.error, "close", "error")}
173
+ </S.ModalImportWrapper>
174
+ )}
175
+ {imports.existing.length > 0 && (
176
+ <S.ModalImportWrapper>
177
+ <S.ModalImportSection>
178
+ <strong>{imports.existing.length}</strong> redirects already created. It will overwrite.
179
+ </S.ModalImportSection>
180
+ {mapModalItems(imports.existing, "info", "info")}
181
+ </S.ModalImportWrapper>
182
+ )}
183
+ {imports.ok.length > 0 && (
184
+ <S.ModalImportWrapper>
185
+ <S.ModalImportSection>
186
+ <strong>{imports.ok.length}</strong> redirects imported correctly.
187
+ </S.ModalImportSection>
188
+ {mapModalItems(imports.ok, "successSolid", "online")}
189
+ </S.ModalImportWrapper>
190
+ )}
191
+ </S.ModalContent>
192
+ )}
193
+ </Modal>
194
+ );
195
+ };
196
+
197
+ interface IImportModal extends IModal {
198
+ isUploading: boolean;
199
+ setIsUploading: React.Dispatch<React.SetStateAction<boolean>>;
200
+ checkImportData: (data: { from: string; to: string }[]) => void;
201
+ }
202
+
203
+ interface IImportCheckModal extends IModal {
204
+ totalImports: number;
205
+ imports: null | {
206
+ error: IRedirect[];
207
+ existing: IRedirect[];
208
+ ok: IRedirect[];
209
+ };
210
+ }
211
+
212
+ export { DeleteModal, ImportModal, OverwriteModal, ImportCheckModal };
@@ -4,11 +4,12 @@ import { connect } from "react-redux";
4
4
  import { INavItem, IRedirect, IRootState } from "@ax/types";
5
5
  import { appActions } from "@ax/containers/App";
6
6
  import { redirectsActions } from "@ax/containers/Redirects";
7
- import { MainWrapper, ErrorToast, Nav, TableList, EmptyState, Modal, Toast } from "@ax/components";
7
+ import { MainWrapper, ErrorToast, Nav, TableList, EmptyState, Toast } from "@ax/components";
8
8
  import { useBulkSelection, useModal, useToast } from "@ax/hooks";
9
9
  import BulkHeader from "./BulkHeader";
10
10
  import RedirectItem from "./RedirectItem";
11
11
  import RedirectPanel from "./RedirectPanel";
12
+ import { DeleteModal, ImportCheckModal, ImportModal, OverwriteModal } from "./atoms";
12
13
 
13
14
  import * as S from "./style";
14
15
 
@@ -23,6 +24,9 @@ const Redirects = (props: IProps): JSX.Element => {
23
24
  deleteRedirect,
24
25
  addRedirect,
25
26
  currentSiteID,
27
+ importRedirects,
28
+ imports,
29
+ totalImports,
26
30
  } = props;
27
31
 
28
32
  const itemsPerPage = 50;
@@ -35,7 +39,12 @@ const Redirects = (props: IProps): JSX.Element => {
35
39
  const [currentFilterQuery, setCurrentFilterQuery] = useState("");
36
40
  const [filterValues, setFilterValues] = useState({ sites: "all" });
37
41
  const { isVisible, toggleToast, setIsVisible } = useToast();
42
+ const { isVisible: isImportVisible, toggleToast: toggleImportToast, setIsVisible: setIsImportVisible } = useToast();
38
43
  const { isOpen: isOpenOverwrite, toggleModal: toggleOverwriteModal } = useModal();
44
+ const { isOpen: isOpenImport, toggleModal: toggleImportModal } = useModal();
45
+ const { isOpen: isOpenCheckImport, toggleModal: toggleCheckImportModal } = useModal();
46
+ const [importData, setImportData] = useState<{ from: string; to: string }[]>([]);
47
+ const [isUploading, setIsUploading] = useState(false);
39
48
 
40
49
  const initState = {
41
50
  from: "",
@@ -127,6 +136,11 @@ const Redirects = (props: IProps): JSX.Element => {
127
136
  action: () => handleModal(),
128
137
  };
129
138
 
139
+ const rightLineButtonProps = {
140
+ label: "Import Redirects",
141
+ action: () => toggleImportModal(),
142
+ };
143
+
130
144
  const handleMenuClick = (path: string) => {
131
145
  setHistoryPush(path);
132
146
  };
@@ -166,6 +180,11 @@ const Redirects = (props: IProps): JSX.Element => {
166
180
  message: "Redirect deleted",
167
181
  };
168
182
 
183
+ const toastImportProps = {
184
+ setIsVisible: setIsImportVisible,
185
+ message: `${(imports?.ok.length || 0) + (imports?.existing.length || 0)} Redirects imported`,
186
+ };
187
+
169
188
  const mainDeleteModalAction = {
170
189
  title: "Delete redirects",
171
190
  onClick: bulkDelete,
@@ -180,9 +199,37 @@ const Redirects = (props: IProps): JSX.Element => {
180
199
 
181
200
  const secondaryOverwriteModalAction = { title: "Cancel", onClick: toggleOverwriteModal };
182
201
 
202
+ const handleCheckImportData = async (data: { from: string; to: string }[]) => {
203
+ setImportData(data);
204
+ await importRedirects(data, true);
205
+ toggleImportModal();
206
+ setIsUploading(false);
207
+ toggleCheckImportModal();
208
+ };
209
+
210
+ const handleImportData = async () => {
211
+ const imported = await importRedirects(importData, false);
212
+ if (imported) {
213
+ toggleImportToast();
214
+ }
215
+ toggleCheckImportModal();
216
+ };
217
+
218
+ const mainImportModalAction = {
219
+ title: "Import correct directs",
220
+ onClick: handleImportData,
221
+ };
222
+
223
+ const secondaryImportModalAction = { title: "Cancel", onClick: toggleCheckImportModal };
224
+
183
225
  return (
184
226
  <>
185
- <MainWrapper backLink={false} title="SEO Settings" rightButton={rightButtonProps}>
227
+ <MainWrapper
228
+ backLink={false}
229
+ title="SEO Settings"
230
+ rightButton={rightButtonProps}
231
+ rightLineButton={rightLineButtonProps}
232
+ >
186
233
  <S.Wrapper>
187
234
  <Nav current={currentNavItem} items={navItems} onClick={handleMenuClick} />
188
235
  <S.ContentWrapper>
@@ -227,6 +274,7 @@ const Redirects = (props: IProps): JSX.Element => {
227
274
  </TableList>
228
275
  </S.TableWrapper>
229
276
  {isVisible && <Toast {...toastProps} />}
277
+ {isImportVisible && <Toast {...toastImportProps} />}
230
278
  </S.ContentWrapper>
231
279
  </S.Wrapper>
232
280
  {isOpen && (
@@ -241,33 +289,33 @@ const Redirects = (props: IProps): JSX.Element => {
241
289
  currentFilter={currentFilterQuery}
242
290
  />
243
291
  )}
244
- <Modal
292
+ <DeleteModal
245
293
  isOpen={isOpenDelete}
246
- hide={toggleModalDelete}
247
- title="Delete Redirects"
248
- secondaryAction={secondaryDeleteModalAction}
249
- mainAction={mainDeleteModalAction}
250
- size="S"
251
- >
252
- <S.ModalContent>
253
- Are you sure you want to delete the selected <strong>redirects</strong>? This action{" "}
254
- <strong>cannot be undone</strong>.
255
- </S.ModalContent>
256
- </Modal>
257
- <Modal
294
+ toggleModal={toggleModalDelete}
295
+ secondaryModalAction={secondaryDeleteModalAction}
296
+ mainModalAction={mainDeleteModalAction}
297
+ />
298
+ <OverwriteModal
258
299
  isOpen={isOpenOverwrite}
259
- hide={toggleOverwriteModal}
260
- title="Overwrite Redirect"
261
- secondaryAction={secondaryOverwriteModalAction}
262
- mainAction={mainOverwriteModalAction}
263
- size="S"
264
- >
265
- <S.ModalContent>
266
- This Redirect is already created. It will overwrite.
267
- <br />
268
- This action <strong>cannot be undone</strong>.
269
- </S.ModalContent>
270
- </Modal>
300
+ toggleModal={toggleOverwriteModal}
301
+ secondaryModalAction={secondaryOverwriteModalAction}
302
+ mainModalAction={mainOverwriteModalAction}
303
+ />
304
+ <ImportModal
305
+ isOpen={isOpenImport}
306
+ toggleModal={toggleImportModal}
307
+ checkImportData={handleCheckImportData}
308
+ isUploading={isUploading}
309
+ setIsUploading={setIsUploading}
310
+ />
311
+ <ImportCheckModal
312
+ isOpen={isOpenCheckImport}
313
+ toggleModal={toggleCheckImportModal}
314
+ mainModalAction={mainImportModalAction}
315
+ secondaryModalAction={secondaryImportModalAction}
316
+ imports={imports}
317
+ totalImports={totalImports}
318
+ />
271
319
  </MainWrapper>
272
320
  </>
273
321
  );
@@ -277,6 +325,8 @@ const mapStateToProps = (state: IRootState) => ({
277
325
  redirects: state.redirects.redirects,
278
326
  totalItems: state.redirects.totalItems,
279
327
  currentSiteID: state.sites.currentSiteInfo && state.sites.currentSiteInfo.id,
328
+ totalImports: state.redirects.totalImports,
329
+ imports: state.redirects.imports,
280
330
  });
281
331
 
282
332
  const mapDispatchToProps = {
@@ -284,6 +334,7 @@ const mapDispatchToProps = {
284
334
  getRedirects: redirectsActions.getRedirects,
285
335
  deleteRedirect: redirectsActions.deleteRedirect,
286
336
  addRedirect: redirectsActions.addRedirect,
337
+ importRedirects: redirectsActions.importRedirects,
287
338
  };
288
339
  interface IRedirectsProps {
289
340
  navItems: INavItem[];
@@ -291,6 +342,12 @@ interface IRedirectsProps {
291
342
  redirects: IRedirect[];
292
343
  totalItems: number;
293
344
  currentSiteID: number | null;
345
+ totalImports: number;
346
+ imports: null | {
347
+ error: IRedirect[];
348
+ existing: IRedirect[];
349
+ ok: IRedirect[];
350
+ };
294
351
  }
295
352
 
296
353
  interface IDispatchProps {
@@ -303,6 +360,7 @@ interface IDispatchProps {
303
360
  force?: boolean,
304
361
  filter?: string
305
362
  ): Promise<void>;
363
+ importRedirects(redirects: { from: string; to: string | number }[], check: boolean): Promise<boolean>;
306
364
  }
307
365
 
308
366
  type IProps = IRedirectsProps & IDispatchProps;
@@ -43,10 +43,131 @@ const EmptyWrapper = styled.div`
43
43
 
44
44
  const ModalContent = styled.div`
45
45
  padding: ${(p) => p.theme.spacing.m};
46
+ `;
47
+
48
+ const ModalCenterContent = styled.div`
49
+ display: flex;
50
+ flex-direction: column;
51
+ justify-content: center;
52
+ align-items: center;
53
+ padding: ${(p) => p.theme.spacing.m};
54
+ `;
55
+
56
+ const ModalContentWrapper = styled.div`
57
+ width: 350px;
58
+ `;
59
+
60
+ const ModalIcon = styled.div`
61
+ display: flex;
62
+ background-color: ${(p) => p.theme.color.uiBackground03};
63
+ width: ${(p) => p.theme.spacing.xl};
64
+ height: ${(p) => p.theme.spacing.xl};
65
+ justify-content: center;
66
+ align-items: center;
67
+ border-radius: 50%;
68
+ margin: ${(p) => `0 auto ${p.theme.spacing.m} auto`};
69
+ `;
70
+
71
+ const ModalIconWrapper = styled.div`
72
+ width: ${(p) => p.theme.spacing.l};
73
+ height: ${(p) => p.theme.spacing.l};
74
+ margin-top: ${(p) => p.theme.spacing.xl};
75
+ margin-bottom: ${(p) => p.theme.spacing.s};
76
+ `;
77
+
78
+ const ModalUploadingText = styled.div`
79
+ ${(p) => p.theme.textStyle.fieldLabel};
80
+ color: ${(p) => p.theme.color.interactive01};
81
+ text-align: center;
82
+ `;
83
+
84
+ const ModalTitle = styled.div`
85
+ ${(p) => p.theme.textStyle.headingS};
86
+ color: ${(p) => p.theme.color.textHighEmphasis};
87
+ text-align: center;
88
+ margin-bottom: ${(p) => p.theme.spacing.s};
89
+ `;
90
+
91
+ const ModalText = styled.div`
92
+ ${(p) => p.theme.textStyle.uiM};
93
+ color: ${(p) => p.theme.color.textMediumEmphasis};
94
+ text-align: center;
95
+ margin-bottom: ${(p) => p.theme.spacing.m};
96
+ `;
46
97
 
47
- p {
48
- margin-bottom: ${(p) => p.theme.spacing.m};
98
+ const ModalButtons = styled.div`
99
+ display: flex;
100
+ justify-content: space-evenly;
101
+ `;
102
+
103
+ const ModalImportResult = styled.div`
104
+ ${(p) => p.theme.textStyle.uiM};
105
+ color: ${(p) => p.theme.color.textHighEmphasis};
106
+ margin-bottom: ${(p) => p.theme.spacing.s};
107
+ `;
108
+
109
+ const ModalImportWrapper = styled.div`
110
+ ${(p) => p.theme.textStyle.uiXS};
111
+ border-bottom: ${(p) => `1px solid ${p.theme.color.uiLine}`};
112
+ padding-bottom: ${(p) => p.theme.spacing.xs};
113
+ margin-bottom: ${(p) => p.theme.spacing.xs};
114
+ `;
115
+
116
+ const ModalImportSection = styled.div`
117
+ color: ${(p) => p.theme.color.textMediumEmphasis};
118
+ margin-bottom: ${(p) => p.theme.spacing.xs};
119
+ `;
120
+
121
+ const ModalImportItem = styled.div`
122
+ display: flex;
123
+ color: ${(p) => p.theme.color.textHighEmphasis};
124
+ margin-bottom: ${(p) => p.theme.spacing.xs};
125
+ `;
126
+
127
+ const ModalItemIcon = styled.div<{ type: "error" | "info" | "online" }>`
128
+ width: ${(p) => p.theme.spacing.s};
129
+ height: ${(p) => p.theme.spacing.s};
130
+ margin-right: ${(p) => p.theme.spacing.xs};
131
+ svg {
132
+ path {
133
+ fill: ${(p) => p.theme.color[p.type]};
134
+ }
49
135
  }
50
136
  `;
51
137
 
52
- export { Wrapper, ContentWrapper, TitleWrapper, Title, Description, TableWrapper, EmptyWrapper, ModalContent };
138
+ const ModalItemArrow = styled.div`
139
+ width: ${(p) => p.theme.spacing.s};
140
+ height: ${(p) => p.theme.spacing.s};
141
+ margin-right: ${(p) => p.theme.spacing.xs};
142
+ margin-left: ${(p) => p.theme.spacing.xs};
143
+ `;
144
+
145
+ const ModalUrl = styled.div`
146
+ width: 50%;
147
+ `;
148
+
149
+ export {
150
+ Wrapper,
151
+ ContentWrapper,
152
+ TitleWrapper,
153
+ Title,
154
+ Description,
155
+ TableWrapper,
156
+ EmptyWrapper,
157
+ ModalContent,
158
+ ModalCenterContent,
159
+ ModalIcon,
160
+ ModalTitle,
161
+ ModalText,
162
+ ModalButtons,
163
+ ModalContentWrapper,
164
+ ModalImportResult,
165
+ ModalImportSection,
166
+ ModalImportWrapper,
167
+ ModalImportItem,
168
+ ModalItemIcon,
169
+ ModalItemArrow,
170
+ ModalUrl,
171
+ ModalIconWrapper,
172
+ ModalUploadingText,
173
+ };
@@ -199,7 +199,7 @@ const DataPacks = (props: IProps): JSX.Element => {
199
199
  )}
200
200
  </S.ContentWrapper>
201
201
  </S.ListWrapper>
202
- <Modal isOpen={isOpen} hide={toggleModal} size="L" title="Available packages">
202
+ <Modal isOpen={isOpen} hide={toggleModal} size="XL" title="Available packages">
203
203
  {isOpen ? <AddModal toggleModal={toggleModal} /> : null}
204
204
  </Modal>
205
205
  </MainWrapper>
@@ -72,7 +72,7 @@ const TableHeader = (props: IProps): JSX.Element => {
72
72
  </S.NameWrapper>
73
73
  {isAllPages && activeColumns.includes("type") && (
74
74
  <S.HeaderWrapper>
75
- <TypeFilter filterItems={filterItems} filters={typeFilters} pointer="types" />
75
+ <TypeFilter filterItems={filterItems} filters={typeFilters} value={filterValues.types} pointer="types" />
76
76
  </S.HeaderWrapper>
77
77
  )}
78
78
  {!isAllPages && activeColumns.includes("site") && (
@@ -51,7 +51,16 @@ const GlobalPageItem = (props: IGlobalPageItemProps): JSX.Element => {
51
51
  const [deleteAllVersions, setDeleteAllVersions] = useState(false);
52
52
 
53
53
  const { locale } = lang;
54
- const { title, pageLanguages, metaDescription, metaTitle, isIndexed, availableSites, structuredData, structuredDataContent } = globalPage;
54
+ const {
55
+ title,
56
+ pageLanguages,
57
+ metaDescription,
58
+ metaTitle,
59
+ isIndexed,
60
+ availableSites,
61
+ structuredData,
62
+ structuredDataContent,
63
+ } = globalPage;
55
64
 
56
65
  const publishedTooltip: Record<string, string> = {
57
66
  active: "Live",
@@ -305,8 +314,8 @@ const GlobalPageItem = (props: IGlobalPageItemProps): JSX.Element => {
305
314
  const mainUnpublishAction = { title: "Ok", onClick: toggleUnpublishModal };
306
315
 
307
316
  const CategoryColumns = categoryColumns.map((col: any) => {
308
- const type = structuredDataContent[col.from];
309
- const categories = type && type.map((cat: any) => cat.label || cat.title);
317
+ const type = structuredDataContent && structuredDataContent[col.from];
318
+ const categories = Array.isArray(type) && type.map((cat: any) => cat.label || cat.title);
310
319
  return (
311
320
  activeColumns.includes(col.key) && (
312
321
  <CategoryCell