@griddo/ax 11.10.18 → 11.10.20

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.10.18",
4
+ "version": "11.10.20",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -217,5 +217,5 @@
217
217
  "publishConfig": {
218
218
  "access": "public"
219
219
  },
220
- "gitHead": "c46c1b9a165b95f927130025ae823bd96da7db4f"
220
+ "gitHead": "cc4cb47324099928259991fc63e6a516679c83db"
221
221
  }
@@ -1,9 +1,10 @@
1
- import React, { useEffect, useState } from "react";
2
- import { InputActionMeta } from "react-select";
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { components } from "react-select";
3
+ import type { GroupBase, InputActionMeta, OptionsOrGroups } from "react-select";
3
4
 
4
5
  import { isReqOk } from "@ax/helpers";
5
6
  import { selects, checkgroups } from "@ax/api";
6
- import { ILanguage, IPage, ISite } from "@ax/types";
7
+ import type { ILanguage, IPage, ISite } from "@ax/types";
7
8
 
8
9
  import * as S from "./style";
9
10
 
@@ -38,9 +39,13 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
38
39
  };
39
40
 
40
41
  const [state, setState] = useState(initialState);
42
+ const menuRef = useRef<HTMLDivElement | null>(null);
43
+
41
44
 
42
45
  const isPage = entity === "pages";
43
46
  const isCategories = entity === "categories";
47
+ const className = error ? `react-select-error ${type}` : type;
48
+ const isSearchable = type !== "inline";
44
49
 
45
50
  const languagesIds = Array.isArray(contentLanguages)
46
51
  ? languages?.filter((lang) => contentLanguages.includes(lang.locale)).map((lang) => lang.id)
@@ -56,7 +61,7 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
56
61
  if (entity) {
57
62
  let result = null;
58
63
  if (site && !isCategories) {
59
- const fullOptions = languagesIds && languagesIds.length ? { ...options, languagesIds } : options;
64
+ const fullOptions = languagesIds?.length ? { ...options, languagesIds } : options;
60
65
  result =
61
66
  isPage && selectedContent
62
67
  ? await selects.getSelectSiteItems(site.id, entity, fullOptions, selectedContent.id)
@@ -80,7 +85,6 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
80
85
  }
81
86
  }
82
87
  } catch (e) {
83
- // biome-ignore lint/suspicious/noConsole: TODO: fix this
84
88
  console.log(e);
85
89
  }
86
90
  return data;
@@ -97,7 +101,6 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
97
101
  : optionValues;
98
102
  if (isMounted) setState((state) => ({ ...state, items: filteredOptions }));
99
103
  })
100
- // biome-ignore lint/suspicious/noConsole: TODO: fix this
101
104
  .catch((apiError) => console.log(apiError));
102
105
  return () => {
103
106
  isMounted = false;
@@ -121,21 +124,45 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
121
124
  }
122
125
  };
123
126
 
124
- const loadOptions = (inputValue: string, callback: any) => {
127
+ const loadOptions = (inputValue: string, callback: (options: OptionsOrGroups<unknown, GroupBase<unknown>>) => void) => {
125
128
  setTimeout(() => {
126
129
  callback(filterOptions(inputValue));
127
130
  }, 0);
128
131
  };
129
132
 
130
- const getObjectValue = (val: any, options: IOption[]) => {
133
+ const getObjectValue = (val: string | number | null | undefined, options: IOption[]) => {
131
134
  const fixedVal = Array.isArray(val) ? val[0] : val;
132
135
  const selectedValue = fixedVal && typeof fixedVal === "object" ? fixedVal.id : fixedVal;
133
136
 
134
- return options && options.find((option: IOption) => option.value === selectedValue);
137
+ return options?.find((option: IOption) => option.value === selectedValue);
135
138
  };
136
139
 
137
- const className = error ? `react-select-error ${type}` : type;
138
- const searchable = type === "inline" ? false : true;
140
+ const handleKeyDown = (e: React.KeyboardEvent) => {
141
+ if (!menuRef.current || isSearchable) return;
142
+
143
+ const char = e.key.toLowerCase();
144
+ if (!/^[a-z]$/i.test(char)) return;
145
+
146
+ const optionNodes = menuRef.current.querySelectorAll<HTMLDivElement>(
147
+ ".react-select__option"
148
+ );
149
+
150
+ const targetNode = Array.from(optionNodes).find((node) =>
151
+ node.innerText.trim().toLowerCase().startsWith(char)
152
+ );
153
+
154
+ if (targetNode) {
155
+ targetNode.scrollIntoView({
156
+ block: "nearest",
157
+ });
158
+ }
159
+ };
160
+
161
+ const Menu = (menuProps: any) => (
162
+ <div ref={menuRef}>
163
+ <components.Menu {...menuProps} />
164
+ </div>
165
+ );
139
166
 
140
167
  return (
141
168
  <div data-testid="asyncSelect">
@@ -153,9 +180,11 @@ const AsyncSelect = (props: IAsyncSelectProps): JSX.Element => {
153
180
  className={className}
154
181
  required={mandatory}
155
182
  isClearable={false}
156
- isSearchable={searchable}
183
+ isSearchable={isSearchable}
157
184
  maxWidth={maxWidth}
158
185
  inputValue={state.inputText}
186
+ components={{ Menu }}
187
+ onKeyDown={handleKeyDown}
159
188
  />
160
189
  </div>
161
190
  );
@@ -1,5 +1,6 @@
1
- import React, { useState } from "react";
2
- import { InputActionMeta } from "react-select";
1
+ import { useRef, useState } from "react";
2
+ import type { InputActionMeta } from "react-select";
3
+ import { components } from "react-select";
3
4
 
4
5
  import * as S from "./style";
5
6
 
@@ -19,11 +20,16 @@ const Select = (props: ISelectProps): JSX.Element => {
19
20
  alignRight,
20
21
  maxWidth,
21
22
  } = props;
23
+
22
24
  const className = error ? `react-select-error ${type}` : type;
23
25
  const emptyOption = { value: "", label: placeholder || "Empty" };
26
+ const isSearchable = !(type === "inline" || type === "mini" );
27
+
24
28
 
25
29
  const [hasEmptyOption, setHasEmptyOption] = useState(!mandatory);
26
30
  const [inputText, setInputText] = useState<string>("");
31
+ const menuRef = useRef<HTMLDivElement | null>(null);
32
+
27
33
 
28
34
  const optionValues: IOptionProps[] = hasEmptyOption ? [emptyOption, ...options] : options;
29
35
 
@@ -37,10 +43,35 @@ const Select = (props: ISelectProps): JSX.Element => {
37
43
  }
38
44
  };
39
45
 
46
+ const handleKeyDown = (e: React.KeyboardEvent) => {
47
+ if (!menuRef.current || isSearchable) return;
48
+
49
+ const char = e.key.toLowerCase();
50
+ if (!/^[a-z]$/i.test(char)) return;
51
+
52
+ const optionNodes = menuRef.current.querySelectorAll<HTMLDivElement>(
53
+ ".react-select__option"
54
+ );
55
+
56
+ const targetNode = Array.from(optionNodes).find((node) =>
57
+ node.innerText.trim().toLowerCase().startsWith(char)
58
+ );
59
+
60
+ if (targetNode) {
61
+ targetNode.scrollIntoView({
62
+ block: "nearest",
63
+ });
64
+ }
65
+ };
66
+
40
67
  const getObjectValue = (value: string | undefined, options: IOptionProps[]) =>
41
68
  options.find((option) => option.value === value);
42
69
 
43
- const searchable = type === "inline" || type === "mini" ? false : true;
70
+ const Menu = (menuProps: any) => (
71
+ <div ref={menuRef}>
72
+ <components.Menu {...menuProps} />
73
+ </div>
74
+ );
44
75
 
45
76
  return (
46
77
  <div data-testid="select-component">
@@ -56,13 +87,15 @@ const Select = (props: ISelectProps): JSX.Element => {
56
87
  classNamePrefix="react-select"
57
88
  error={error}
58
89
  onChange={handleChange}
59
- isSearchable={searchable}
90
+ isSearchable={isSearchable}
60
91
  isClearable={false}
61
92
  onInputChange={handleInputChange}
62
93
  alignRight={alignRight}
63
94
  aria-label={name}
64
95
  maxWidth={maxWidth}
65
96
  inputValue={inputText}
97
+ components={{ Menu }}
98
+ onKeyDown={handleKeyDown}
66
99
  />
67
100
  </div>
68
101
  );
@@ -59,7 +59,7 @@ const buttons = [
59
59
  const inlineToolbar = ["langDropdown", "undo", "redo"];
60
60
 
61
61
  const wysiwygConfig = {
62
- key: process.env.REACT_APP_FROALA_KEY,
62
+ key: process.env.GRIDDO_FROALA_KEY || process.env.REACT_APP_FROALA_KEY,
63
63
  // pastePlain: false,
64
64
  pasteDeniedAttrs: ["class", "id", "style", "dir"],
65
65
  listAdvancedTypes: false,
@@ -110,10 +110,8 @@ FroalaEditor.RegisterCommand("langDropdown", {
110
110
  undo: true,
111
111
  focus: true,
112
112
  refreshAfterCallback: true,
113
- html: function () {
114
- return getLanguageMenuHtml(richTextConfig?.editorLangs ? richTextConfig.editorLangs : languages);
115
- },
116
- callback: function (cmd: any, val: string) {
113
+ html: () => getLanguageMenuHtml(richTextConfig?.editorLangs ? richTextConfig.editorLangs : languages),
114
+ callback: function (_cmd: any, val: string) {
117
115
  const html = this.selection.text();
118
116
  if (val !== "") {
119
117
  this.html.insert(`<span lang="${val}" translate="no">${html}</span>`, true);
@@ -1,9 +1,8 @@
1
- import React from "react";
2
1
  import { connect } from "react-redux";
3
2
  import FroalaEditorComponent from "react-froala-wysiwyg";
4
3
  import FroalaEditor from "froala-editor";
5
4
  import { decodeEntities } from "@ax/helpers";
6
- import { IImage, ISite, IRootState } from "@ax/types";
5
+ import type { IImage, ISite, IRootState } from "@ax/types";
7
6
  import { galleryActions } from "@ax/containers/Gallery";
8
7
 
9
8
  import { wysiwygConfig, buttons, buttonsFull, inlineToolbar } from "./config";
@@ -40,7 +39,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
40
39
  toolbarButtons: inline ? inlineToolbar : full ? buttonsFull : buttons,
41
40
  toolbarInline: inline,
42
41
  charCounterCount: !inline,
43
- imageUpload: full && !inline ? true : false,
42
+ imageUpload: !!(full && !inline ),
44
43
  multiLine: !inline,
45
44
  enter: inline ? FroalaEditor.ENTER_BR : FroalaEditor.ENTER_P,
46
45
  requestHeaders: {
@@ -70,7 +69,7 @@ const Wysiwyg = (props: IWysiwygProps): JSX.Element => {
70
69
  const editor: any = this;
71
70
  const html = editor.html.get();
72
71
  const stripedHtml = decodeEntities(html);
73
- handleValidation && handleValidation(stripedHtml);
72
+ handleValidation?.(stripedHtml);
74
73
  },
75
74
  },
76
75
  };
@@ -1,16 +1,15 @@
1
- import React from "react";
2
1
  import { createPortal } from "react-dom";
3
2
  import { Icon } from "@ax/components";
4
3
 
5
4
  import * as S from "./style";
6
5
 
7
6
  const OcassionalToast = (props: IOcassionalToastProps): JSX.Element => {
8
- const { message } = props;
7
+ const { message, icon = "alert" } = props;
9
8
 
10
9
  return createPortal(
11
10
  <S.Wrapper data-testid="occasional-toast-wrapper">
12
11
  <S.IconWrapper>
13
- <Icon name="alert" />
12
+ <Icon name={icon} />
14
13
  </S.IconWrapper>
15
14
  <S.Text data-testid="occasional-toast-message">{message}</S.Text>
16
15
  </S.Wrapper>,
@@ -20,6 +19,7 @@ const OcassionalToast = (props: IOcassionalToastProps): JSX.Element => {
20
19
 
21
20
  export interface IOcassionalToastProps {
22
21
  message: string;
22
+ icon?: string;
23
23
  }
24
24
 
25
25
  export default OcassionalToast;
@@ -3,9 +3,10 @@ import styled from "styled-components";
3
3
  const Wrapper = styled.div`
4
4
  position: fixed;
5
5
  display: flex;
6
- background: ${(p) => p.theme.color.uiMainMenuBackground};
6
+ background: ${(p) => p.theme.color.uiBackground04};
7
7
  justify-content: space-between;
8
8
  align-items: center;
9
+ border: 1px solid ${(p) => p.theme.color.borderInverse};
9
10
  border-radius: ${(p) => p.theme.radii.s};
10
11
  z-index: 2000;
11
12
  bottom: ${(p) => p.theme.spacing.m};
@@ -1,5 +1,5 @@
1
1
  import { Icon } from "@ax/components";
2
- import React, { useEffect, useRef, useState } from "react";
2
+ import { useEffect, useRef, useState } from "react";
3
3
  import { Select } from "../Fields";
4
4
 
5
5
  import * as S from "./style";
@@ -19,7 +19,7 @@ const SearchField = (props: ISearchFieldProps): JSX.Element => {
19
19
  value,
20
20
  } = props;
21
21
 
22
- const [isOpen, setIsOpen] = useState(value && value.trim() !== "" ? true : false);
22
+ const [isOpen, setIsOpen] = useState(!!(value && value.trim() !== "" ));
23
23
  const [inputValue, setInputValue] = useState(value || "");
24
24
  const [selectValue, setSelectValue] = useState<string>("");
25
25
  const inputRef = useRef<HTMLInputElement | null>(null);
@@ -71,14 +71,14 @@ const SearchField = (props: ISearchFieldProps): JSX.Element => {
71
71
 
72
72
  const handleSelectChange = (value: string) => {
73
73
  setSelectValue(value);
74
- onFilterChange && onFilterChange(value);
74
+ onFilterChange?.(value);
75
75
  };
76
76
 
77
77
  return (
78
78
  <S.Wrapper data-testid="search-field-wrapper">
79
79
  {showField ? (
80
80
  <S.FieldWrapper closeOnInactive={closeOnInactive} disabled={disabled} data-testid="field-wrapper">
81
- {searchFilters && searchFilters.length && (
81
+ {searchFilters?.length && (
82
82
  <>
83
83
  <S.FilterWrapper data-testid="filter-wrapper">
84
84
  <Select
@@ -1,4 +1,4 @@
1
- import { Dispatch } from "redux";
1
+ import type { Dispatch } from "redux";
2
2
 
3
3
  import {
4
4
  isReqOk,
@@ -75,7 +75,7 @@ import {
75
75
  SET_SCROLL_EDITOR_ID,
76
76
  } from "./constants";
77
77
 
78
- import {
78
+ import type {
79
79
  IPage,
80
80
  ISavePageParams,
81
81
  IBreadcrumbItem,
@@ -90,7 +90,7 @@ import {
90
90
  IRootState,
91
91
  IGetSitePagesParams,
92
92
  } from "@ax/types";
93
- import {
93
+ import type {
94
94
  ISetBreadcrumb,
95
95
  ISetSchema,
96
96
  ISetTab,
@@ -110,7 +110,6 @@ import {
110
110
  ISetValidated,
111
111
  ISetSitePageID,
112
112
  ISetUserEditing,
113
- pageStatus,
114
113
  ISetLastElementAddedId,
115
114
  ISetCopyModule,
116
115
  ISetIsIATranslated,
@@ -119,6 +118,8 @@ import {
119
118
  ISetScrollEditorID,
120
119
  } from "./interfaces";
121
120
 
121
+ import { pageStatus } from "./interfaces";
122
+
122
123
  const { setIsLoading, setIsSaving, handleError } = appActions;
123
124
  const { getDefaults } = navigationActions;
124
125
 
@@ -353,12 +354,12 @@ function getPage(pageID?: number, global?: boolean): (dispatch: Dispatch, getSta
353
354
  const { liveStatus, component } = getDefaultSchema(baseSchema);
354
355
 
355
356
  if (isNewTranslation) {
356
- page["liveStatus"] = liveStatus;
357
- page["canBeTranslated"] = true;
358
- page["originalLanguage"] = page.language;
357
+ page.liveStatus = liveStatus;
358
+ page.canBeTranslated = true;
359
+ page.originalLanguage = page.language;
359
360
  }
360
361
 
361
- if (global) page["component"] = component;
362
+ if (global) page.component = component;
362
363
 
363
364
  const pageLiveStatus =
364
365
  page.draftFromPage && !page.publicationScheduled
@@ -409,14 +410,19 @@ function getTemplatePage(template: string): (dispatch: Dispatch, getState: () =>
409
410
  };
410
411
  }
411
412
 
412
- function savePage(
413
- createDraft: boolean,
414
- publishPage?: any,
413
+ function savePage(data?: {
414
+ createDraft?: boolean,
415
+ publishPage?: { status: string },
415
416
  publishDraft?: boolean,
416
- ): (dispatch: Dispatch, getState: any) => Promise<boolean> {
417
+ setToast?: (msg: string) => void
418
+ }): (dispatch: Dispatch, getState: any) => Promise<boolean> {
417
419
  return async (dispatch, getState) => {
418
420
  try {
419
421
  dispatch(setIsSaving(true));
422
+ const { createDraft = false, publishPage, publishDraft, setToast } = data || {};
423
+
424
+ console.log(setToast, "setToast");
425
+
420
426
  const {
421
427
  pageEditor: {
422
428
  selectedEditorID,
@@ -449,20 +455,20 @@ function savePage(
449
455
  }
450
456
 
451
457
  if (createDraft) {
452
- values["draftFromPage"] = values.id;
458
+ values.draftFromPage = values.id;
453
459
  delete values.id;
454
460
  }
455
461
 
456
462
  /* remove header and footer if page should get the default */
457
463
  const { defaultHeader, defaultFooter } =
458
- (templateConfig && templateConfig.templates && templateConfig.templates[template.templateType]) || {};
464
+ (templateConfig?.templates?.[template.templateType]) || {};
459
465
 
460
466
  if (header && ((header.setAsDefault && !defaultHeader) || header.id === defaultHeader)) {
461
- values["header"] = null;
467
+ values.header = null;
462
468
  }
463
469
 
464
470
  if (footer && ((footer.setAsDefault && !defaultFooter) || footer.id === defaultFooter)) {
465
- values["footer"] = null;
471
+ values.footer = null;
466
472
  }
467
473
 
468
474
  const saveResponse =
@@ -503,7 +509,11 @@ function savePage(
503
509
  dispatch(setIsSaving(false));
504
510
  return true;
505
511
  } else {
506
- appActions.handleError(saveResponse)(dispatch);
512
+ if(typeof setToast === 'function'){
513
+ setToast(saveResponse.data.message);
514
+ } else {
515
+ appActions.handleError(saveResponse)(dispatch);
516
+ }
507
517
  dispatch(setIsSaving(false));
508
518
  return false;
509
519
  }
@@ -738,7 +748,7 @@ function addComponent(
738
748
 
739
749
  generatePageContent(updatedPageContent)(dispatch, getState);
740
750
 
741
- if (typeof type === "object" && Object.prototype.hasOwnProperty.call(type, "editorID")) {
751
+ if (typeof type === "object" && Object.hasOwn(type, "editorID")) {
742
752
  setSelectedContent(type.editorID)(dispatch, getState);
743
753
  } else {
744
754
  const { sections: generatedSections } = getStateValues(getState);
@@ -804,7 +814,7 @@ function addModule(
804
814
  type,
805
815
  };
806
816
 
807
- let updatedSections,
817
+ let updatedSections: any,
808
818
  updatedSectionIndex = 0;
809
819
 
810
820
  if (isComponentModule) {
@@ -1068,7 +1078,7 @@ function pasteModule(
1068
1078
  if (isReqOk(response.status)) {
1069
1079
  const validDataIds: number[] = [];
1070
1080
  response.data.items.forEach(
1071
- (data: IStructuredDataContent) => data.relatedSite && validDataIds.push(data.id),
1081
+ (data: IStructuredDataContent) => { data.relatedSite && validDataIds.push(data.id) },
1072
1082
  );
1073
1083
  const hasInvalidData = validDataIds.length < fixed.length;
1074
1084
  validatedModuleCopy.data.fixed = validDataIds;
@@ -1111,7 +1121,11 @@ function overwriteHeaderConfig(params: IFieldProps): (dispatch: Dispatch, getSta
1111
1121
  const updatedContent = deepClone(content);
1112
1122
  const { headerConfig } = updatedContent.editorContent;
1113
1123
 
1114
- headerConfig[id] ? (headerConfig[id][key] = value) : (headerConfig[id] = { [key]: value });
1124
+ if(headerConfig[id]){
1125
+ headerConfig[id][key] = value
1126
+ } else {
1127
+ headerConfig[id] = { [key]: value }
1128
+ }
1115
1129
 
1116
1130
  dispatch(setEditorContent({ ...updatedContent }));
1117
1131
  };
@@ -1126,7 +1140,7 @@ function generatePageContent(editorContent: IPage): (dispatch: Dispatch, getStat
1126
1140
  } = getState();
1127
1141
 
1128
1142
  const { header, footer, isGlobal } = editorContent;
1129
- const { defaultHeader, defaultFooter } = (configFormData?.templates && configFormData?.templates[template]) || {};
1143
+ const { defaultHeader, defaultFooter } = (configFormData?.templates?.[template]) || {};
1130
1144
 
1131
1145
  const headerID =
1132
1146
  header === null || header === undefined ? defaultHeader : typeof header === "object" ? header.id : header;
@@ -1333,7 +1347,7 @@ function getTemplateConfig(template: string): (dispatch: Dispatch, getState: ()
1333
1347
  const {
1334
1348
  sites: { currentSiteInfo },
1335
1349
  } = getState();
1336
- const currentSiteID = currentSiteInfo && currentSiteInfo.id;
1350
+ const currentSiteID = currentSiteInfo?.id || null;
1337
1351
  const responseActions = {
1338
1352
  handleSuccess: (data: any) => dispatch(setTemplateConfig(data)),
1339
1353
  handleError: () => console.log("Error en getTemplateConfig"),
@@ -1381,7 +1395,7 @@ function validatePage(publish?: boolean): (dispatch: Dispatch, getState: () => I
1381
1395
  const fieldErrors = findFieldsErrors(content);
1382
1396
 
1383
1397
  errors = [...errors, ...fieldErrors];
1384
- let packagesActivationErrors;
1398
+ let packagesActivationErrors: any;
1385
1399
  if (!isGlobalPage) {
1386
1400
  packagesActivationErrors = findPackagesActivationErrors(pageEditor, modules, templates);
1387
1401
  }
@@ -1541,8 +1555,8 @@ function getPageSummary(): (dispatch: Dispatch, getState: () => IRootState) => P
1541
1555
  const responseActions = {
1542
1556
  handleSuccess: (data: { summary: string; keywords: string[] }) => {
1543
1557
  const content = deepClone(editorContent);
1544
- content["metaDescription"] = data.summary;
1545
- content["metaKeywords"] = data.keywords;
1558
+ content.metaDescription = data.summary;
1559
+ content.metaKeywords = data.keywords;
1546
1560
  generatePageContent(content)(dispatch, getState);
1547
1561
  },
1548
1562
  handleError: () => console.log("Error en GetPageSummary"),
@@ -1567,7 +1581,7 @@ function getPageTranslation(langID: number): (dispatch: Dispatch, getState: () =
1567
1581
 
1568
1582
  const responseActions = {
1569
1583
  handleSuccess: (data: IPage) => {
1570
- data["canBeTranslated"] = false;
1584
+ data.canBeTranslated = false;
1571
1585
  generatePageContent(data)(dispatch, getState);
1572
1586
  dispatch(setCurrentPageName(data.title));
1573
1587
  dispatch(setIsIATranslated(true));
@@ -1609,7 +1623,7 @@ function schedulePublication(
1609
1623
  const updatedEditorContent = { ...editorContent, publicationScheduled: date };
1610
1624
 
1611
1625
  dispatch(setEditorContent(updatedEditorContent));
1612
- return await savePage(false, { status })(dispatch, getState);
1626
+ return await savePage({createDraft: false, publishPage: { status }})(dispatch, getState);
1613
1627
  } catch (e) {
1614
1628
  console.log("Error", e);
1615
1629
  return false;
@@ -1,4 +1,4 @@
1
- import { IPage, IBreadcrumbItem, ISchema, IErrorItem, IUserEditing, IModule, IPageLanguage } from "@ax/types";
1
+ import type { IPage, IBreadcrumbItem, ISchema, IErrorItem, IUserEditing, IModule, IPageLanguage } from "@ax/types";
2
2
 
3
3
  import {
4
4
  SET_EDITOR_CONTENT,
@@ -1,5 +1,5 @@
1
- import React, { memo } from "react";
2
- import { ISchemaField } from "@ax/types";
1
+ import { memo } from "react";
2
+ import type { ISchemaField } from "@ax/types";
3
3
 
4
4
  import * as S from "./style";
5
5
 
@@ -8,8 +8,14 @@ const SectionOption = (props: IProps) => {
8
8
 
9
9
  const setOption = () => handleClick(option);
10
10
 
11
+ const handleKeyDown = (e: React.KeyboardEvent) => {
12
+ if (e.key === "Enter") {
13
+ setOption();
14
+ }
15
+ };
16
+
11
17
  return (
12
- <S.Item onClick={setOption} data-testid="section-option">
18
+ <S.Item onClick={setOption} onKeyDown={handleKeyDown} tabIndex={0} data-testid="section-option">
13
19
  Fields for {option.title}
14
20
  </S.Item>
15
21
  );
@@ -1,4 +1,4 @@
1
- import React, { memo } from "react";
1
+ import { memo } from "react";
2
2
 
3
3
  import { getDisplayName, getThumbnailProps, filterImageText } from "@ax/helpers";
4
4
 
@@ -16,8 +16,14 @@ const SideModalOption = (props: IProps) => {
16
16
 
17
17
  const setOption = () => handleClick(option);
18
18
 
19
+ const handleKeyDown = (e: React.KeyboardEvent) => {
20
+ if (e.key === "Enter") {
21
+ setOption();
22
+ }
23
+ };
24
+
19
25
  return (
20
- <S.Item onClick={setOption} data-testid="side-modal-option">
26
+ <S.Item onClick={setOption} onKeyDown={handleKeyDown} tabIndex={0} data-testid="side-modal-option">
21
27
  {thumbnailProps && <S.Thumbnail data-testid="side-modal-option-img" {...thumbnailProps} />}
22
28
  {label}
23
29
  </S.Item>
@@ -1,8 +1,7 @@
1
- import React, { useState } from "react";
2
-
1
+ import { useState } from "react";
3
2
  import { getDisplayName } from "@ax/helpers";
4
3
  import { SearchField, FloatingButton, NoteField } from "@ax/components";
5
- import { ISchemaField } from "@ax/types";
4
+ import type { ISchemaField } from "@ax/types";
6
5
 
7
6
  import SideModalOption from "./SideModalOption";
8
7
  import SectionOption from "./SectionOption";
@@ -27,19 +26,19 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
27
26
  setIsOpen((state) => !state);
28
27
  };
29
28
 
30
- const filteredOptions = selectedField?.whiteList?.map((option: string, i: number) => {
29
+ const filteredOptions = selectedField?.whiteList?.map((option: string) => {
31
30
  const displayName = getDisplayName(option);
32
31
  if (searchQuery.length > 0) {
33
32
  const name = displayName.toLowerCase();
34
33
  const search = searchQuery.toLocaleLowerCase();
35
- if (!name.includes(search)) return <></>;
34
+ if (!name.includes(search)) return null;
36
35
  }
37
36
  return (
38
- <SideModalOption option={option} handleClick={handleClick} key={`${option}${i}`} theme={theme}>
37
+ <SideModalOption option={option} handleClick={handleClick} key={option} theme={theme}>
39
38
  {displayName}
40
39
  </SideModalOption>
41
40
  );
42
- }) || <></>;
41
+ }) || null;
43
42
 
44
43
  const sectionOptions = fieldArrays.map((option) => (
45
44
  <SectionOption option={option} handleClick={handleSectionClick} key={option.key} />
@@ -69,7 +68,7 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
69
68
  </S.ColumnsWrapper>
70
69
  </>
71
70
  ) : (
72
- <>
71
+ <S.ModalContent>
73
72
  <S.Header>
74
73
  {isMultiArray && selectedField && (
75
74
  <S.BreadCrumb>
@@ -86,6 +85,7 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
86
85
  closeOnInactive={false}
87
86
  small={true}
88
87
  placeholder="Search field"
88
+ focus={false}
89
89
  />
90
90
  </S.SearchWrapper>
91
91
  </S.Header>
@@ -95,7 +95,7 @@ const SideModal = (props: ISideModalProps): JSX.Element | null => {
95
95
  </S.FloatingButtonWrapper>
96
96
  <S.Content>{filteredOptions}</S.Content>
97
97
  </S.ColumnsWrapper>
98
- </>
98
+ </S.ModalContent>
99
99
  )}
100
100
  </S.Wrapper>
101
101
  <S.ClosedWrapper isOpen={!isOpen}>
@@ -7,17 +7,19 @@ const Header = styled.div`
7
7
  padding: ${(p) => `${p.theme.spacing.s} ${p.theme.spacing.s} 0`};
8
8
  `;
9
9
 
10
- const Content = styled.div`
10
+ const Content = styled.ul`
11
11
  list-style: none;
12
12
  padding: ${(p) => p.theme.spacing.s};
13
- height: ${(p) => `calc(100vh - ${p.theme.spacing.xl})`};
13
+ height: 100%;
14
14
  width: ${(p) => `calc(${p.theme.spacing.xl} * 3)`};
15
- overflow: auto;
16
15
  border-right: 1px solid ${(p) => p.theme.colors.uiLine};
17
16
  &:last-child {
18
17
  border-right: 0;
19
18
  width: ${(p) => `calc(${p.theme.spacing.xl} * 4)`};
20
19
  }
20
+ & > li:last-child {
21
+ margin-bottom: 0;
22
+ }
21
23
  `;
22
24
 
23
25
  const Wrapper = styled.div<{ isOpen: boolean }>`
@@ -102,6 +104,23 @@ const BreadCrumbItem = styled.button<{ isLastItem: boolean }>`
102
104
  }
103
105
  `;
104
106
 
107
+ const ModalContent = styled.div`
108
+ width: 100%;
109
+ height: 100%;
110
+ overflow: auto;
111
+
112
+ ::-webkit-scrollbar {
113
+ -webkit-appearance: none;
114
+ width: 4px;
115
+ height: 100%;
116
+ }
117
+
118
+ ::-webkit-scrollbar-thumb {
119
+ border-radius: 4px;
120
+ background-color: ${(p) => p.theme.color.iconNonActive};
121
+ }
122
+ `;
123
+
105
124
  export {
106
125
  Wrapper,
107
126
  Content,
@@ -113,4 +132,5 @@ export {
113
132
  ClosedFloatingButtonWrapper,
114
133
  BreadCrumb,
115
134
  BreadCrumbItem,
135
+ ModalContent
116
136
  };
@@ -1,9 +1,9 @@
1
- import React, { useEffect, useState, useRef } from "react";
1
+ import { useEffect, useState, useRef } from "react";
2
2
  import { connect } from "react-redux";
3
- import { RouteComponentProps } from "react-router-dom";
3
+ import type { RouteComponentProps } from "react-router-dom";
4
4
  import { withErrorBoundary } from "react-error-boundary";
5
5
 
6
- import {
6
+ import type {
7
7
  IErrorItem,
8
8
  ILanguage,
9
9
  INotification,
@@ -23,6 +23,7 @@ import {
23
23
  CancelScheduleModal,
24
24
  ScheduleModal,
25
25
  RestoreModal,
26
+ OcassionalToast,
26
27
  } from "@ax/components";
27
28
  import { pageEditorActions } from "@ax/containers/PageEditor";
28
29
  import { structuredDataActions } from "@ax/containers/StructuredData";
@@ -81,6 +82,7 @@ const GlobalEditor = (props: IProps) => {
81
82
  const [isReadOnly, setIsReadOnly] = useState(false);
82
83
  const [selectedTab, setSelectedTab] = useState(defaultTab);
83
84
  const [notification, setNotification] = useState<INotification | null>(null);
85
+ const [toastMsg, setToastMsg] = useState<string | null>(null);
84
86
  const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent, isNewTranslation);
85
87
  const [errorPagesChecked, setErrorPagesChecked] = useState(false);
86
88
  const [scheduleDate, setScheduleDate] = useState({ date: dateToString(new Date(), "yyy/MM/dd"), time: "12:00 am" });
@@ -92,7 +94,7 @@ const GlobalEditor = (props: IProps) => {
92
94
  const isPublished = props.pageStatus === pageStatus.PUBLISHED || props.pageStatus === pageStatus.UPLOAD_PENDING;
93
95
  const isDraft = props.pageStatus === pageStatus.MODIFIED || !!editorContent.draftFromPage;
94
96
  const hasDraft: boolean = editorContent && !!editorContent.haveDraftPage;
95
- const isLivePageChanged = editorContent && editorContent.liveChanged;
97
+ const isLivePageChanged = editorContent?.liveChanged;
96
98
  const structuredData = editorContent ? editorContent.structuredData : "";
97
99
  const isEditLive = isPublished && hasDraft;
98
100
  const isScheduled = props.pageStatus === pageStatus.SCHEDULED;
@@ -117,10 +119,11 @@ const GlobalEditor = (props: IProps) => {
117
119
  useEffect(() => {
118
120
  const { pageID, getPage, setTab, sendPagePing, setStructuredDataFilter } = props;
119
121
 
120
- editorContent && editorContent.structuredData && setStructuredDataFilter(editorContent.structuredData);
122
+ editorContent?.structuredData && setStructuredDataFilter(editorContent.structuredData);
121
123
  const defaultTab = "content";
122
124
  const handleGetPage = async () => await getPage(pageID, true);
123
125
  setTab(defaultTab);
126
+ setToastMsg(null);
124
127
  handleGetPage();
125
128
  if (!pageID) {
126
129
  setIsDirty(false);
@@ -186,7 +189,7 @@ const GlobalEditor = (props: IProps) => {
186
189
  const isSaved =
187
190
  pageID && !isNewTranslation
188
191
  ? await updatePageStatus([pageID], pageStatus.UPLOAD_PENDING)
189
- : await savePage(false, publishPage);
192
+ : await savePage({ publishPage});
190
193
 
191
194
  if (isSaved) {
192
195
  resetDirty();
@@ -206,7 +209,7 @@ const GlobalEditor = (props: IProps) => {
206
209
  status: pageStatus.UPLOAD_PENDING,
207
210
  };
208
211
 
209
- const isSaved = await savePage(false, publishPage);
212
+ const isSaved = await savePage({publishPage});
210
213
  if (isSaved) {
211
214
  resetDirty();
212
215
  }
@@ -240,7 +243,7 @@ const GlobalEditor = (props: IProps) => {
240
243
  const validated = skipReviewOnPublish ? true : await validatePage(true);
241
244
 
242
245
  if (validated) {
243
- const isSaved = await savePage(false, null, true);
246
+ const isSaved = await savePage({ publishDraft: true });
244
247
  if (isSaved) resetDirty();
245
248
  } else {
246
249
  setNotification({ text: errorNotificationText, type: "error" });
@@ -262,7 +265,7 @@ const GlobalEditor = (props: IProps) => {
262
265
  const handleCreateDraft = async () => {
263
266
  const { savePage } = props;
264
267
 
265
- const isSaved = await savePage(true);
268
+ const isSaved = await savePage({ createDraft: true});
266
269
  if (isSaved) resetDirty();
267
270
  };
268
271
 
@@ -378,13 +381,16 @@ const GlobalEditor = (props: IProps) => {
378
381
  options: menuOptions,
379
382
  };
380
383
 
381
- const isEditable = editorContent && editorContent.editable;
384
+ const isEditable = editorContent?.editable;
382
385
 
383
- const handleSavePage = async () => {
386
+ const handleSavePage = async (setToast?: (msg: string) => void) => {
384
387
  const { savePage } = props;
385
388
 
386
- const isSaved = await savePage(false);
387
- if (isSaved) resetDirty();
389
+ const isSaved = await savePage({setToast});
390
+ if (isSaved){
391
+ resetDirty();
392
+ setToastMsg(null);
393
+ }
388
394
  };
389
395
 
390
396
  const goToPages = (path: string) => setRoute(path);
@@ -504,10 +510,17 @@ const GlobalEditor = (props: IProps) => {
504
510
  ]
505
511
  : [{ name: "view", text: "Preview mode" }];
506
512
 
513
+ const handleSelectedTab = async (tab: string) => {
514
+ if(tab === "view" && (!isPublished || isDraft)){
515
+ handleSavePage(setToastMsg);
516
+ }
517
+ setSelectedTab(tab);
518
+ }
519
+
507
520
  const tabsPreview = {
508
521
  icons: tabIcons,
509
522
  selectedTab,
510
- action: (tab: string) => setSelectedTab(tab),
523
+ action: (tab: string) => handleSelectedTab(tab),
511
524
  };
512
525
 
513
526
  const filteredLanguages = globalLangs.filter((lang) =>
@@ -627,7 +640,10 @@ const GlobalEditor = (props: IProps) => {
627
640
  </S.Content>
628
641
  </>
629
642
  ) : (
630
- <Preview theme={theme} />
643
+ <>
644
+ <Preview theme={theme} />
645
+ {toastMsg?.length && <OcassionalToast icon="warning" message={toastMsg} />}
646
+ </>
631
647
  )}
632
648
  <Modal
633
649
  isOpen={isOpen}
@@ -640,8 +656,8 @@ const GlobalEditor = (props: IProps) => {
640
656
  {isOpen && (
641
657
  <S.ModalContent>
642
658
  <p>
643
- <strong>{userEditing && userEditing.name}</strong> is currently working on this page. You can preview
644
- the page but <strong>you cannot make changes to it</strong> until {userEditing && userEditing.name}{" "}
659
+ <strong>{userEditing?.name}</strong> is currently working on this page. You can preview
660
+ the page but <strong>you cannot make changes to it</strong> until {userEditing?.name}{" "}
645
661
  leaves the page.
646
662
  </p>
647
663
  </S.ModalContent>
@@ -705,7 +721,7 @@ const mapStateToProps = (state: IRootState): IPageEditorStateProps => ({
705
721
  sitePageID: state.pageEditor.sitePageID,
706
722
  savedSiteInfo: state.sites.savedSiteInfo,
707
723
  userEditing: state.pageEditor.userEditing,
708
- currentUserID: state.users.currentUser && state.users.currentUser.id,
724
+ currentUserID: state.users.currentUser?.id || null,
709
725
  isNewTranslation: state.pageEditor.isNewTranslation,
710
726
  currentSiteErrorPages: state.sites.currentSiteErrorPages,
711
727
  skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
@@ -756,7 +772,7 @@ const mapDispatchToProps = {
756
772
 
757
773
  interface IPageEditorDispatchProps {
758
774
  getPage(pageID?: number, global?: boolean): Promise<void>;
759
- savePage(createDraft: boolean, publishPage?: any, publishDraft?: boolean): Promise<boolean>;
775
+ savePage(data?: {createDraft?: boolean; publishPage?: { status: string }; publishDraft?: boolean; setToast?: (msg: string) => void}): Promise<boolean>;
760
776
  deletePage(params?: ISavePageParams): Promise<boolean>;
761
777
  validatePage(publish?: boolean): Promise<boolean>;
762
778
  updatePageStatus(id: number[], status: string): Promise<boolean>;
@@ -1,11 +1,11 @@
1
- import React, { useEffect, useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
  import { connect } from "react-redux";
3
3
  import { useParams } from "react-router-dom";
4
4
 
5
- import { IRootState } from "@ax/types";
5
+ import type { IRootState } from "@ax/types";
6
6
  import { appActions } from "@ax/containers/App";
7
7
  import { Login } from "@ax/components";
8
- import { IGlobalSettings } from "@ax/containers/App/reducer";
8
+ import type { IGlobalSettings } from "@ax/containers/App/reducer";
9
9
  import { errorText } from "./constants";
10
10
 
11
11
  const LoginModule = (props: IProps) => {
@@ -49,7 +49,7 @@ const LoginModule = (props: IProps) => {
49
49
  }, [petitionId, errorSSO]);
50
50
 
51
51
  useEffect(() => {
52
- const prefix = process.env.REACT_APP_SITE_TITLE ? process.env.REACT_APP_SITE_TITLE : "";
52
+ const prefix = process.env.GRIDDO_SITE_TITLE || process.env.REACT_APP_SITE_TITLE || "";
53
53
  document.title = `${prefix} ${globalSettings.welcomeText2}`;
54
54
  }, [globalSettings]);
55
55
 
@@ -110,7 +110,6 @@ const LoginModule = (props: IProps) => {
110
110
 
111
111
  interface IProps {
112
112
  isLoggingIn: boolean;
113
- children: any;
114
113
  globalSettings: IGlobalSettings;
115
114
  login(email?: string, password?: string, rememberMe?: boolean, petitionId?: string): Promise<boolean>;
116
115
  loginSSO(): Promise<string | null>;
@@ -1,9 +1,9 @@
1
- import React, { useEffect, useState, useRef } from "react";
1
+ import { useEffect, useState, useRef } from "react";
2
2
  import { connect } from "react-redux";
3
- import { RouteComponentProps } from "react-router-dom";
3
+ import type { RouteComponentProps } from "react-router-dom";
4
4
  import { withErrorBoundary } from "react-error-boundary";
5
5
 
6
- import {
6
+ import type {
7
7
  IErrorItem,
8
8
  INotification,
9
9
  IRootState,
@@ -22,6 +22,7 @@ import {
22
22
  CancelScheduleModal,
23
23
  ScheduleModal,
24
24
  RestoreModal,
25
+ OcassionalToast,
25
26
  } from "@ax/components";
26
27
  import { pageEditorActions } from "@ax/containers/PageEditor";
27
28
  import { appActions } from "@ax/containers/App";
@@ -82,6 +83,7 @@ const PageEditor = (props: IProps) => {
82
83
  const [isReadOnly, setIsReadOnly] = useState(false);
83
84
  const [selectedTab, setSelectedTab] = useState(defaultTab);
84
85
  const [notification, setNotification] = useState<INotification | null>(null);
86
+ const [toastMsg, setToastMsg] = useState<string | null>(null);
85
87
  const [scheduleDate, setScheduleDate] = useState({ date: dateToString(new Date(), "yyy/MM/dd"), time: "12:00 am" });
86
88
  const { isDirty, setIsDirty, resetDirty } = useIsDirty(editorContent, isNewTranslation);
87
89
  const { isOpen, toggleModal } = useModal();
@@ -94,16 +96,16 @@ const PageEditor = (props: IProps) => {
94
96
  const browserRef = useRef<HTMLDivElement>(null);
95
97
 
96
98
  const isGlobal = editorContent && editorContent.origin === "GLOBAL";
97
- const isEditable = editorContent && editorContent.editable;
99
+ const isEditable = editorContent?.editable;
98
100
  const isPublished = props.pageStatus === pageStatus.PUBLISHED || props.pageStatus === pageStatus.UPLOAD_PENDING;
99
101
  const isDraft = props.pageStatus === pageStatus.MODIFIED || !!editorContent.draftFromPage;
100
- const hasDraft = editorContent && editorContent.haveDraftPage;
101
- const isLivePageChanged = editorContent && editorContent.liveChanged;
102
+ const hasDraft = editorContent?.haveDraftPage;
103
+ const isLivePageChanged = editorContent?.liveChanged;
102
104
  const isTranslated = pageLanguages.length > 1;
103
105
  const structuredData = editorContent ? editorContent.structuredData : "";
104
106
  const isEditLive = isPublished && hasDraft;
105
107
  const isAllowedToDelete = (isPublished && isAllowedToDeletePublishedPage) || (!isPublished && isAllowedToDeletePage);
106
- const canBeUnpublished = editorContent && editorContent.canBeUnpublished;
108
+ const canBeUnpublished = editorContent?.canBeUnpublished;
107
109
  const deleteHelpText = !canBeUnpublished ? "This is the canonical site of the page. You cannot unpublish it." : null;
108
110
  const isScheduled = !!editorContent && !!editorContent.publicationScheduled;
109
111
  const isDeleted = editorContent.deleted;
@@ -127,6 +129,7 @@ const PageEditor = (props: IProps) => {
127
129
 
128
130
  setTab(defaultTab);
129
131
  resetDirty();
132
+ setToastMsg(null);
130
133
  handleGetPage();
131
134
 
132
135
  if (!pageID) {
@@ -190,7 +193,7 @@ const PageEditor = (props: IProps) => {
190
193
  const isSaved =
191
194
  pageID && !isNewTranslation
192
195
  ? await updatePageStatus([pageID], pageStatus.UPLOAD_PENDING)
193
- : await savePage(false, publishPage);
196
+ : await savePage({publishPage});
194
197
 
195
198
  if (isSaved) {
196
199
  resetDirty();
@@ -210,7 +213,7 @@ const PageEditor = (props: IProps) => {
210
213
  status: pageStatus.UPLOAD_PENDING,
211
214
  };
212
215
 
213
- const isSaved = await savePage(false, publishPage);
216
+ const isSaved = await savePage({publishPage});
214
217
  if (isSaved) {
215
218
  resetDirty();
216
219
  }
@@ -244,7 +247,7 @@ const PageEditor = (props: IProps) => {
244
247
  const validated = skipReviewOnPublish ? true : await validatePage(true);
245
248
 
246
249
  if (validated) {
247
- const isSaved = await savePage(false, null, true);
250
+ const isSaved = await savePage({publishDraft: true});
248
251
  if (isSaved) resetDirty();
249
252
  } else {
250
253
  setNotification({ text: errorNotificationText, type: "error" });
@@ -267,7 +270,7 @@ const PageEditor = (props: IProps) => {
267
270
  const handleCreateDraft = async () => {
268
271
  const { savePage } = props;
269
272
 
270
- const isSaved = await savePage(true);
273
+ const isSaved = await savePage({createDraft: true});
271
274
  if (isSaved) resetDirty();
272
275
  };
273
276
 
@@ -386,11 +389,14 @@ const PageEditor = (props: IProps) => {
386
389
  options: !isGlobal ? menuOptions : [],
387
390
  };
388
391
 
389
- const handleSavePage = async () => {
392
+ const handleSavePage = async (setToast?: (msg: string) => void) => {
390
393
  const { savePage } = props;
391
394
 
392
- const isSaved = await savePage(false);
393
- if (isSaved) resetDirty();
395
+ const isSaved = await savePage({ setToast });
396
+ if (isSaved) {
397
+ resetDirty();
398
+ setToastMsg(null);
399
+ }
394
400
  };
395
401
 
396
402
  const goToPages = (path: string) => setRoute(path);
@@ -412,7 +418,7 @@ const PageEditor = (props: IProps) => {
412
418
  let isTemplateActivated = true;
413
419
  let hasDeactivatedModules = false;
414
420
  let deactivatedModules: string[] = [];
415
- if (editorContent && editorContent.template) {
421
+ if (editorContent?.template) {
416
422
  const editorTemplate = editorContent.template;
417
423
  const mainContentModules = editorTemplate?.mainContent?.modules;
418
424
 
@@ -423,7 +429,7 @@ const PageEditor = (props: IProps) => {
423
429
  hasDeactivatedModules = isModuleDisabled(selectedComponent, schema.schemaType, activatedModules);
424
430
  }
425
431
 
426
- isTemplateActivated = activatedTemplates.find((temp) => temp.id === editorTemplate.templateType) ? true : false;
432
+ isTemplateActivated = !!activatedTemplates.find((temp) => temp.id === editorTemplate.templateType);
427
433
  }
428
434
 
429
435
  let availableLanguages = siteLanguages;
@@ -505,7 +511,7 @@ const PageEditor = (props: IProps) => {
505
511
  "This content is part of disabled content type package. To edit it, you must first activate it.";
506
512
 
507
513
  const handleClickNotification = () => {
508
- if (editorContent && editorContent.template) {
514
+ if (editorContent?.template) {
509
515
  const editorTemplate = editorContent.template;
510
516
  if (!isTemplateActivated) {
511
517
  getSiteDataPackbyTemplate(editorTemplate.templateType);
@@ -554,10 +560,17 @@ const PageEditor = (props: IProps) => {
554
560
  ]
555
561
  : [{ name: "view", text: "Preview mode" }];
556
562
 
563
+ const handleSelectedTab = async (tab: string) => {
564
+ if(tab === "view" && (!isPublished || isDraft)){
565
+ handleSavePage(setToastMsg);
566
+ }
567
+ setSelectedTab(tab);
568
+ }
569
+
557
570
  const tabsPreview = {
558
571
  icons: tabIcons,
559
572
  selectedTab,
560
- action: (tab: string) => setSelectedTab(tab),
573
+ action: (tab: string) => handleSelectedTab(tab),
561
574
  };
562
575
 
563
576
  const contentLanguages: ILanguage[] = [];
@@ -680,8 +693,12 @@ const PageEditor = (props: IProps) => {
680
693
  </S.Content>
681
694
  </>
682
695
  ) : (
683
- <Preview />
696
+ <>
697
+ <Preview />
698
+ {toastMsg?.length && <OcassionalToast icon="warning" message={toastMsg} />}
699
+ </>
684
700
  )}
701
+
685
702
  <Modal
686
703
  isOpen={isOpen}
687
704
  hide={toggleModal}
@@ -693,8 +710,8 @@ const PageEditor = (props: IProps) => {
693
710
  {isOpen && (
694
711
  <S.ModalContent>
695
712
  <p>
696
- <strong>{userEditing && userEditing.name}</strong> is currently working on this page. You can preview
697
- the page but <strong>you cannot make changes to it</strong> until {userEditing && userEditing.name}{" "}
713
+ <strong>{userEditing?.name}</strong> is currently working on this page. You can preview
714
+ the page but <strong>you cannot make changes to it</strong> until {userEditing?.name}{" "}
698
715
  leaves the page.
699
716
  </p>
700
717
  </S.ModalContent>
@@ -767,7 +784,7 @@ const mapStateToProps = (state: IRootState): IPageEditorStateProps => ({
767
784
  activatedModules: state.dataPacks.modules,
768
785
  errors: state.pageEditor.errors,
769
786
  userEditing: state.pageEditor.userEditing,
770
- currentUserID: state.users.currentUser && state.users.currentUser.id,
787
+ currentUserID: state.users.currentUser?.id || null,
771
788
  isNewTranslation: state.pageEditor.isNewTranslation,
772
789
  currentSiteErrorPages: state.sites.currentSiteErrorPages,
773
790
  skipReviewOnPublish: state.app.globalSettings.skipReviewOnPublish,
@@ -821,7 +838,7 @@ const mapDispatchToProps = {
821
838
 
822
839
  interface IPageEditorDispatchProps {
823
840
  getPage(pageID?: number): Promise<void>;
824
- savePage(createDraft: boolean, publishPage?: any, publishDraft?: boolean): Promise<boolean>;
841
+ savePage(data?: { createDraft?: boolean; publishPage?: { status: string }; publishDraft?: boolean; setToast?: (msg: string) => void }): Promise<boolean>;
825
842
  deletePage(params?: ISavePageParams): Promise<boolean>;
826
843
  validatePage(publish?: boolean): Promise<boolean>;
827
844
  updatePageStatus(id: number[], status: string): Promise<boolean>;
@@ -226,6 +226,12 @@
226
226
  "warning": {
227
227
  "description": "warning modified",
228
228
  "value": "#ffbb37"
229
+ },
230
+ "borderInverse": {
231
+ "description": "Border for ocassional toasts",
232
+ "type": "RGB",
233
+ "value": ["219", "221", "233"],
234
+ "opacity": "0.3"
229
235
  }
230
236
  }
231
237
  },