From 4e44d9e48fed0d1c917aec83ebb9cf9ebb9e7b8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Castillo?= Date: Mon, 31 Aug 2026 10:55:20 -0300 Subject: [PATCH 1/8] fix: migrate edit email template into mui popup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Castillo --- src/actions/__tests__/email-actions.test.js | 2 - src/actions/email-actions.js | 4 +- .../__tests__/email-template-form.test.js | 63 +- src/components/forms/email-template-form.js | 1181 +++++++++-------- .../__tests__/email-template-input.test.js | 139 ++ src/components/inputs/email-template-input.js | 192 ++- src/i18n/en.json | 1 + src/layouts/email-layout.js | 69 +- src/pages/emails/edit-email-template-page.js | 222 ---- src/pages/emails/edit-email-template-popup.js | 206 +++ src/pages/emails/email-template-list-page.js | 87 +- 11 files changed, 1231 insertions(+), 935 deletions(-) create mode 100644 src/components/inputs/__tests__/email-template-input.test.js delete mode 100644 src/pages/emails/edit-email-template-page.js create mode 100644 src/pages/emails/edit-email-template-popup.js diff --git a/src/actions/__tests__/email-actions.test.js b/src/actions/__tests__/email-actions.test.js index 8431c6a0f..8ad8a1d2d 100644 --- a/src/actions/__tests__/email-actions.test.js +++ b/src/actions/__tests__/email-actions.test.js @@ -19,8 +19,6 @@ import { } from "../email-actions"; import * as methods from "../../utils/methods"; -jest.mock("../../history", () => ({ push: jest.fn() })); - jest.mock("openstack-uicore-foundation/lib/utils/actions", () => ({ __esModule: true, ...jest.requireActual("openstack-uicore-foundation/lib/utils/actions"), diff --git a/src/actions/email-actions.js b/src/actions/email-actions.js index 2e0799df0..a5a3c6300 100644 --- a/src/actions/email-actions.js +++ b/src/actions/email-actions.js @@ -29,7 +29,6 @@ import { } from "openstack-uicore-foundation/lib/utils/actions"; import URI from "urijs"; import debounce from "lodash/debounce"; -import history from "../history"; import { checkOrFilter, getAccessTokenSafely } from "../utils/methods"; import { saveMarketingSetting } from "./marketing-actions"; import { @@ -160,9 +159,8 @@ export const saveEmailTemplate = customErrorHandler, entity )(params)(dispatch) - .then((payload) => { + .then(() => { dispatch(showSuccessMessage(T.translate("emails.template_created"))); - history.push(`/app/emails/templates/${payload.response.id}`); }) .finally(() => { dispatch(stopLoading()); diff --git a/src/components/forms/__tests__/email-template-form.test.js b/src/components/forms/__tests__/email-template-form.test.js index a90b57e15..13fad0ec0 100644 --- a/src/components/forms/__tests__/email-template-form.test.js +++ b/src/components/forms/__tests__/email-template-form.test.js @@ -8,6 +8,7 @@ import { afterEach } from "@jest/globals"; import { render, act, fireEvent } from "@testing-library/react"; +import showConfirmDialog from "openstack-uicore-foundation/lib/components/mui/show-confirm-dialog"; import EmailTemplateForm from "../email-template-form"; @@ -16,10 +17,13 @@ jest.mock("@uiw/react-codemirror", () => ({ __esModule: true, default: () => null })); -jest.mock("sweetalert2", () => ({ - __esModule: true, - default: { fire: jest.fn(() => Promise.resolve({})) } -})); +jest.mock( + "openstack-uicore-foundation/lib/components/mui/show-confirm-dialog", + () => ({ + __esModule: true, + default: jest.fn(() => Promise.resolve(true)) + }) +); jest.mock("mjml-browser", () => ({ __esModule: true, default: () => ({ html: "" }) @@ -31,7 +35,6 @@ jest.mock("../../inputs/email-template-input", () => ({ const baseProps = (entity) => ({ entity, - match: { params: { template_id: `${entity.id}` } }, errors: {}, clients: [], preview: null, @@ -62,7 +65,10 @@ const htmlEntity = { }; describe("EmailTemplateForm preview dispatch", () => { - beforeEach(() => jest.useFakeTimers()); + beforeEach(() => { + jest.useFakeTimers(); + showConfirmDialog.mockResolvedValue(true); + }); afterEach(() => { jest.runOnlyPendingTimers(); jest.useRealTimers(); @@ -135,7 +141,7 @@ describe("EmailTemplateForm preview dispatch", () => { it("re-fires the HTML-mode preview when toggled from MJML to HTML", async () => { const props = baseProps(mjmlEntity); - const { getByDisplayValue } = render(); + const { getByText } = render(); // initial mount → one MJML-mode request await act(async () => { @@ -152,7 +158,7 @@ describe("EmailTemplateForm preview dispatch", () => { // mutates neither content field directly // T.translate returns the key string when no i18n config is loaded await act(async () => { - fireEvent.click(getByDisplayValue("emails.display_html")); + fireEvent.click(getByText("emails.display_html")); }); await act(async () => { jest.advanceTimersByTime(600); @@ -166,4 +172,45 @@ describe("EmailTemplateForm preview dispatch", () => { false ); }); + + it("warns before switching to MJML on an HTML-only template and keeps the switch on confirm", async () => { + showConfirmDialog.mockResolvedValue(true); + const props = baseProps(htmlEntity); + const { getByText } = render(); + + await act(async () => { + jest.advanceTimersByTime(600); + }); + + await act(async () => { + fireEvent.click(getByText("emails.display_mjml")); + }); + + expect(showConfirmDialog).toHaveBeenCalledWith( + expect.objectContaining({ + text: "emails.mjml_warning", + iconType: "warning" + }) + ); + + // switch is kept — the button now offers to go back to HTML + expect(getByText("emails.display_html")).toBeTruthy(); + }); + + it("reverts to HTML mode when the MJML switch warning is cancelled", async () => { + showConfirmDialog.mockResolvedValue(false); + const props = baseProps(htmlEntity); + const { getByText } = render(); + + await act(async () => { + jest.advanceTimersByTime(600); + }); + + await act(async () => { + fireEvent.click(getByText("emails.display_mjml")); + }); + + // reverted back — the button offers to switch to MJML again + expect(getByText("emails.display_mjml")).toBeTruthy(); + }); }); diff --git a/src/components/forms/email-template-form.js b/src/components/forms/email-template-form.js index 7acba8e57..1ce573389 100644 --- a/src/components/forms/email-template-form.js +++ b/src/components/forms/email-template-form.js @@ -11,19 +11,27 @@ * limitations under the License. * */ -import React, { useState, useEffect, useRef } from "react"; +import React, { + useState, + useEffect, + useRef, + forwardRef, + useImperativeHandle +} from "react"; import T from "i18n-react/dist/i18n-react"; -import "awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css"; import debounce from "lodash/debounce"; -import AjaxLoader from "openstack-uicore-foundation/lib/components/ajaxloader"; -import Dropdown from "openstack-uicore-foundation/lib/components/inputs/dropdown"; -import Input from "openstack-uicore-foundation/lib/components/inputs/text-input"; +import Box from "@mui/material/Box"; +import Button from "@mui/material/Button"; +import Grid2 from "@mui/material/Grid2"; +import TextField from "@mui/material/TextField"; +import CircularProgress from "@mui/material/CircularProgress"; +import MuiDropdown from "openstack-uicore-foundation/lib/components/mui/dropdown"; import { epochToMomentTimeZone } from "openstack-uicore-foundation/lib/utils/methods"; import CodeMirror from "@uiw/react-codemirror"; import { sublimeInit } from "@uiw/codemirror-theme-sublime"; import { html } from "@codemirror/lang-html"; import mjml2html from "mjml-browser"; -import Swal from "sweetalert2"; +import showConfirmDialog from "openstack-uicore-foundation/lib/components/mui/show-confirm-dialog"; import EmailTemplateInput from "../inputs/email-template-input"; import { scrollToError, shallowEqual, hasErrors } from "../../utils/methods"; import "./email-template.less"; @@ -47,612 +55,631 @@ const default_mjml_content = ` `; -const EmailTemplateForm = ({ - entity, - match, - errors, - clients, - preview, - templateLoading, - renderErrors, - onSubmit, - onRender, - templateJsonData, - renderEmailTemplate -}) => { - const [stateEntity, setStateEntity] = useState({ ...entity }); - const [stateErrors, setStateErrors] = useState(errors); - const [historyVersion, setHistoryVersion] = useState(null); - const [currentVersionExternalLink, setCurrentVersionExternalLink] = - useState(null); - const [mjmlEditor, setMjmlEditor] = useState(null); - const [codeOnly, setCodeOnly] = useState(false); - const [previewOnly, setPreviewOnly] = useState(false); - const [mobileView, setMobileView] = useState(false); - const [scale, setScale] = useState(1); - const [singleTab, setSingleTab] = useState(false); - const [templateLoaded, setTemplateLoaded] = useState(false); - const [previewLoaded, setPreviewLoaded] = useState(false); - const [mjmlWarning, setMjmlWarning] = useState(false); - const [mjmlRenderError, setMjmlRenderError] = useState(null); - - const previewRef = useRef(null); - - const style = mobileView - ? { width: "320px", height: "960px", transform: `scale(${scale})` } - : { width: "800px", height: "960px", transform: `scale(${scale})` }; - - useEffect(() => { - scrollToError(errors); - - // check if the current entity is sync with template_id param - const templateId = match.params.template_id; - if ( - templateId === `${entity.id}` || - templateId === entity.identifier || - (entity.id === 0 && !templateId) - ) { +const EmailTemplateForm = forwardRef( + ( + { + entity, + errors, + clients, + preview, + templateLoading, + renderErrors, + onSubmit, + onRender, + templateJsonData, + renderEmailTemplate, + onValidityChange + }, + ref + ) => { + const [stateEntity, setStateEntity] = useState({ ...entity }); + const [stateErrors, setStateErrors] = useState(errors); + const [historyVersion, setHistoryVersion] = useState(""); + const [currentVersionExternalLink, setCurrentVersionExternalLink] = + useState(null); + const [mjmlEditor, setMjmlEditor] = useState(null); + const [codeOnly, setCodeOnly] = useState(false); + const [previewOnly, setPreviewOnly] = useState(false); + const [mobileView, setMobileView] = useState(false); + const [scale, setScale] = useState(1); + const [singleTab, setSingleTab] = useState(false); + const [templateLoaded, setTemplateLoaded] = useState(false); + const [previewLoaded, setPreviewLoaded] = useState(false); + const [mjmlWarning, setMjmlWarning] = useState(false); + const [mjmlRenderError, setMjmlRenderError] = useState(null); + + const previewRef = useRef(null); + + const style = mobileView + ? { width: "320px", height: "960px", transform: `scale(${scale})` } + : { width: "800px", height: "960px", transform: `scale(${scale})` }; + + useEffect(() => { + scrollToError(errors); + setTemplateLoaded(true); - } - - if (!shallowEqual(stateErrors, errors)) { - setStateErrors({ ...errors }); - } - - if (!shallowEqual(stateEntity, entity)) { - setStateEntity({ ...entity }); - } - }, [errors, entity]); - - useEffect(() => { - // if entity is correctly loaded, set state for entity use - if (templateLoaded) { - if (entity.id === 0) { - setStateEntity({ ...entity, mjml_content: default_mjml_content }); - } else { + + if (!shallowEqual(stateErrors, errors)) { + setStateErrors({ ...errors }); + } + + if (!shallowEqual(stateEntity, entity)) { setStateEntity({ ...entity }); } - setStateErrors({}); - setMjmlEditor( - entity.mjml_content.length > 0 ? true : !entity.html_content - ); - } - }, [templateLoaded, entity.id]); - - useEffect(() => { - if (singleTab) { - setCodeOnly(true); - } else { - setCodeOnly(false); - setPreviewOnly(false); - } - }, [singleTab]); - - const DEBOUNCE_MS = 500; - const debouncedRenderTemplate = useRef( - debounce(async (content, json_data, isMjml) => { - renderEmailTemplate(json_data, content, isMjml).then(() => { - // wait until first API email preview to display template on screen - if (!previewLoaded) setPreviewLoaded(true); - }); - }, DEBOUNCE_MS) - ).current; - - // MJML mode: send raw mjml_content so the API runs Jinja -> official MJML CLI - // (same pipeline as production). mjmlEditor is in the deps so a button-only - // mode switch re-fires this; the debounce coalesces with the HTML effect so - // only one preview request goes out per mode. - useEffect(() => { - if (templateLoaded && mjmlEditor) - debouncedRenderTemplate(stateEntity.mjml_content, templateJsonData, true); - }, [stateEntity.mjml_content, mjmlEditor, entity, templateJsonData]); - - // HTML mode: unchanged Jinja-on-HTML preview. Guarded on !mjmlEditor so it - // does not fire for MJML templates. - useEffect(() => { - if (templateLoaded && !mjmlEditor) - debouncedRenderTemplate( - stateEntity.html_content, - templateJsonData, - false - ); - }, [stateEntity.html_content, mjmlEditor, entity, templateJsonData]); - - useEffect(() => { - if (mjmlEditor) { - try { - const htmlContent = mjml2html(stateEntity.mjml_content, { - validationLevel: "strict", - keepComments: false, - collapseWhitespace: true, - minifyOptions: { collapseWhitespace: false } - }).html; - setStateEntity({ ...stateEntity, html_content: htmlContent }); - setMjmlRenderError(null); - } catch (err) { - setMjmlRenderError(err); + }, [errors, entity]); + + useEffect(() => { + // if entity is correctly loaded, set state for entity use + if (templateLoaded) { + if (entity.id === 0) { + setStateEntity({ ...entity, mjml_content: default_mjml_content }); + } else { + setStateEntity({ ...entity }); + } + setStateErrors({}); + setMjmlEditor( + entity.mjml_content.length > 0 ? true : !entity.html_content + ); + } + }, [templateLoaded, entity.id]); + + useEffect(() => { + if (singleTab) { + setCodeOnly(true); + } else { + setCodeOnly(false); + setPreviewOnly(false); + } + }, [singleTab]); + + const DEBOUNCE_MS = 500; + const debouncedRenderTemplate = useRef( + debounce(async (content, json_data, isMjml) => { + renderEmailTemplate(json_data, content, isMjml).then(() => { + // wait until first API email preview to display template on screen + if (!previewLoaded) setPreviewLoaded(true); + }); + }, DEBOUNCE_MS) + ).current; + + // MJML mode: send raw mjml_content so the API runs Jinja -> official MJML CLI + // (same pipeline as production). mjmlEditor is in the deps so a button-only + // mode switch re-fires this; the debounce coalesces with the HTML effect so + // only one preview request goes out per mode. + useEffect(() => { + if (templateLoaded && mjmlEditor) + debouncedRenderTemplate( + stateEntity.mjml_content, + templateJsonData, + true + ); + }, [stateEntity.mjml_content, mjmlEditor, entity, templateJsonData]); + + // HTML mode: unchanged Jinja-on-HTML preview. Guarded on !mjmlEditor so it + // does not fire for MJML templates. + useEffect(() => { + if (templateLoaded && !mjmlEditor) + debouncedRenderTemplate( + stateEntity.html_content, + templateJsonData, + false + ); + }, [stateEntity.html_content, mjmlEditor, entity, templateJsonData]); + + useEffect(() => { + if (mjmlEditor) { + try { + const htmlContent = mjml2html(stateEntity.mjml_content, { + validationLevel: "strict", + keepComments: false, + collapseWhitespace: true, + minifyOptions: { collapseWhitespace: false } + }).html; + setStateEntity({ ...stateEntity, html_content: htmlContent }); + setMjmlRenderError(null); + } catch (err) { + setMjmlRenderError(err); + } + } + }, [stateEntity.mjml_content, historyVersion]); + + useEffect(() => { + if ( + entity.mjml_content.length === 0 && + entity.html_content.length > 0 && + mjmlEditor && + !mjmlWarning + ) { + showConfirmDialog({ + title: T.translate("general.are_you_sure"), + text: T.translate("emails.mjml_warning"), + iconType: "warning", + confirmButtonColor: "error", + confirmButtonText: T.translate("emails.understand") + }).then((confirmed) => { + if (confirmed) { + setMjmlWarning(true); + } else { + setMjmlEditor(false); + } + }); + } + }, [mjmlEditor]); + + const handleCodeMirrorHTMLChange = (value) => { + setStateErrors({ ...stateErrors, html_content: "" }); + setStateEntity({ ...stateEntity, html_content: value }); + }; + + const handleCodeMirrorMJMLChange = (value) => { + setStateErrors({ ...stateErrors, mjml_content: "" }); + setStateEntity({ ...stateEntity, mjml_content: value }); + }; + + const handleChange = (ev) => { + let { value, id } = ev.target; + + if (ev.target.type === "checkbox") { + value = ev.target.checked; + } + + if (ev.target.type === "number") { + value = parseInt(ev.target.value); + } + + setStateEntity({ ...stateEntity, [id]: value }); + setStateErrors({ ...stateErrors, [id]: "" }); + }; + + const handleClientsChange = (ev) => { + setStateEntity({ ...stateEntity, allowed_clients: ev.target.value }); + setStateErrors({ ...stateErrors, allowed_clients: "" }); + }; + + const handleJsonDataEdit = (ev) => { + ev.preventDefault(); + onRender(); + }; + + const SINGLE_TAB_BREAKPOINT = 992; + const MOBILE_PREVIEW_WIDTH = 320; + const DESKTOP_PREVIEW_WIDTH = 800; + + const handleResizeWindow = () => { + if (window.innerWidth < SINGLE_TAB_BREAKPOINT) { + setSingleTab(true); + } else { + setSingleTab(false); } - } - }, [stateEntity.mjml_content, historyVersion]); - - useEffect(() => { - if ( - entity.mjml_content.length === 0 && - entity.html_content.length > 0 && - mjmlEditor && - !mjmlWarning - ) { - console.log("warning mjml"); - Swal.fire({ - title: T.translate("general.are_you_sure"), - text: T.translate("emails.mjml_warning"), - type: "warning", - confirmButtonColor: "#DD6B55", - confirmButtonText: T.translate("emails.understand") - }).then(() => { - setMjmlWarning(true); - }); - } - }, [mjmlEditor]); - - const handleCodeMirrorHTMLChange = (value) => { - setStateErrors({ ...stateErrors, html_content: "" }); - setStateEntity({ ...stateEntity, html_content: value }); - }; - - const handleCodeMirrorMJMLChange = (value) => { - setStateErrors({ ...stateErrors, mjml_content: "" }); - setStateEntity({ ...stateEntity, mjml_content: value }); - }; - - const handleChange = (ev) => { - let { value, id } = ev.target; - - if (ev.target.type === "checkbox") { - value = ev.target.checked; - } - - if (ev.target.type === "number") { - value = parseInt(ev.target.value); - } - - setStateEntity({ ...stateEntity, [id]: value }); - setStateErrors({ ...stateErrors, [id]: "" }); - }; - - const handleSubmit = (ev) => { - ev.preventDefault(); - onSubmit(stateEntity); - }; - - const handleJsonDataEdit = (ev) => { - ev.preventDefault(); - onRender(); - }; - - const SINGLE_TAB_BREAKPOINT = 992; - const MOBILE_PREVIEW_WIDTH = 320; - const DESKTOP_PREVIEW_WIDTH = 800; - - const handleResizeWindow = () => { - if (window.innerWidth < SINGLE_TAB_BREAKPOINT) { - setSingleTab(true); - } else { - setSingleTab(false); - } - const currentPreviewWidth = previewRef?.current?.offsetWidth; - if (mobileView) { - if (currentPreviewWidth < MOBILE_PREVIEW_WIDTH) { - const newScale = currentPreviewWidth / MOBILE_PREVIEW_WIDTH; + const currentPreviewWidth = previewRef?.current?.offsetWidth; + if (mobileView) { + if (currentPreviewWidth < MOBILE_PREVIEW_WIDTH) { + const newScale = currentPreviewWidth / MOBILE_PREVIEW_WIDTH; + setScale(newScale); + } + } else if (currentPreviewWidth < DESKTOP_PREVIEW_WIDTH) { + const newScale = currentPreviewWidth / DESKTOP_PREVIEW_WIDTH; setScale(newScale); } - } else if (currentPreviewWidth < DESKTOP_PREVIEW_WIDTH) { - const newScale = currentPreviewWidth / DESKTOP_PREVIEW_WIDTH; - setScale(newScale); - } - }; - - const handleTabChange = (ev) => { - const { id } = ev.target; - if (singleTab) { - if (id === "preview") { - setCodeOnly(false); - setPreviewOnly(true); + }; + + const handleTabChange = (ev) => { + const { id } = ev.target; + if (singleTab) { + if (id === "preview") { + setCodeOnly(false); + setPreviewOnly(true); + } else { + setCodeOnly(true); + setPreviewOnly(false); + } + } else if (id === "preview") { + if (codeOnly) { + setCodeOnly(false); + } else { + setPreviewOnly(true); + } + } else if (previewOnly) { + setPreviewOnly(false); } else { setCodeOnly(true); - setPreviewOnly(false); } - } else if (id === "preview") { - if (codeOnly) { - setCodeOnly(false); - } else { - setPreviewOnly(true); + }; + + const handleVersionChange = (ev) => { + const { value } = ev.target; + if (!value) { + // restore original version + setStateEntity({ + ...stateEntity, + html_content: stateEntity.original_html_content, + mjml_content: stateEntity.original_mjml_content + }); + setHistoryVersion(""); + setCurrentVersionExternalLink(null); + return; + } + + const selectedHistory = stateEntity.versions.find((h) => h.sha === value); + setHistoryVersion(selectedHistory.sha); + setCurrentVersionExternalLink(selectedHistory.html_url); + if (selectedHistory.type === EMAIL_TEMPLATE_TYPE_HTML) { + setMjmlEditor(false); + setStateEntity({ + ...stateEntity, + html_content: selectedHistory.content + }); + } + if (selectedHistory.type === EMAIL_TEMPLATE_TYPE_MJML) { + setMjmlEditor(true); + setStateEntity({ + ...stateEntity, + mjml_content: selectedHistory.content + }); } - } else if (previewOnly) { - setPreviewOnly(false); - } else { - setCodeOnly(true); - } - }; - - const handleVersionChange = (ev) => { - const { value } = ev.target; - if (value === null) { - // restore original version - setStateEntity({ - ...stateEntity, - html_content: stateEntity.original_html_content, - mjml_content: stateEntity.original_mjml_content - }); - setHistoryVersion(null); - setCurrentVersionExternalLink(null); - return; - } - - const selectedHistory = stateEntity.versions.find((h) => h.sha === value); - setHistoryVersion(selectedHistory.sha); - setCurrentVersionExternalLink(selectedHistory.html_url); - if (selectedHistory.type === EMAIL_TEMPLATE_TYPE_HTML) { - setMjmlEditor(false); - setStateEntity({ ...stateEntity, html_content: selectedHistory.content }); - } - if (selectedHistory.type === EMAIL_TEMPLATE_TYPE_MJML) { - setMjmlEditor(true); - setStateEntity({ ...stateEntity, mjml_content: selectedHistory.content }); - } - }; - - const isTemplateInvalid = () => mjmlEditor && mjmlRenderError !== null; - - useEffect(() => { - handleResizeWindow(); - window.addEventListener("resize", handleResizeWindow); - return () => { - window.removeEventListener("resize", handleResizeWindow); }; - }); - - const email_clients_ddl = clients - ? clients.map((cli) => ({ label: cli.name, value: cli.id })) - : []; - const versions_ddl = stateEntity.versions - ? stateEntity.versions.map((v) => ({ - label: `${epochToMomentTimeZone(v.commit_date, "UTC").format( - "YYYY-MM-DD HH:mm z" - )} - ${v.sha} - ${v.commit_message}`, - value: v.sha - })) - : []; - - return ( -
- -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
- - -
-
-
-
- -
-
-
-
- {templateLoaded ? ( -
-
- {!previewOnly && ( -
+ + const isTemplateInvalid = () => mjmlEditor && mjmlRenderError !== null; + + useEffect(() => { + onValidityChange?.(isTemplateInvalid()); + }, [mjmlEditor, mjmlRenderError]); + + useImperativeHandle( + ref, + () => ({ + submit: () => onSubmit(stateEntity) + }), + [stateEntity, onSubmit] + ); + + useEffect(() => { + handleResizeWindow(); + window.addEventListener("resize", handleResizeWindow); + return () => { + window.removeEventListener("resize", handleResizeWindow); + }; + }); + + const email_clients_ddl = clients + ? clients.map((cli) => ({ label: cli.name, value: cli.id })) + : []; + const versions_ddl = stateEntity.versions + ? [ + { value: "", label: T.translate("emails.current_version") }, + ...stateEntity.versions.map((v) => ({ + label: `${epochToMomentTimeZone(v.commit_date, "UTC").format( + "YYYY-MM-DD HH:mm z" + )} - ${v.sha} - ${v.commit_message}`, + value: v.sha + })) + ] + : []; + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {templateLoaded ? ( +
+
+ {!previewOnly && (
- {mjmlEditor ? ( - <> - +
+
-
-
- {entity.id > 0 && stateEntity.versions.length > 0 && ( -
+ {T.translate("emails.display_mjml")} + + + )} +
+ {entity.id > 0 && stateEntity.versions.length > 0 && ( + +
- ({ - ...baseStyles, - color: state.isSelected ? "white" : "inherit" - }) - }} - className="email-history-ddl" onChange={handleVersionChange} /> -
- )} - {currentVersionExternalLink && ( -
- - - -
- )} -
+ + {currentVersionExternalLink && ( + + + + + + )} + + )}
-
- )} - {!codeOnly && ( -
- -
- setMobileView(!mobileView)} - className="btn btn-primary" - value={ - mobileView + )} + {!codeOnly && ( +
+ +
+
- )} -
-
-
- {!previewOnly && ( -
- {mjmlEditor ? ( - - handleCodeMirrorMJMLChange(value, viewUpdate) - } - height="960px" - theme={sublimeInit({ - settings: { - caret: "#c6c6c6", - fontFamily: "monospace" + : T.translate("emails.display_mobile")} + +
+ )} +
+
+
+ {!previewOnly && ( +
+ {mjmlEditor ? ( + + handleCodeMirrorMJMLChange(value, viewUpdate) } - })} - extensions={[ - html({ - autoCloseTags: true, - matchClosingTags: true, - selfClosingTags: true - }) - ]} - /> - ) : ( - - handleCodeMirrorHTMLChange(value, viewUpdate) - } - height="960px" - theme={sublimeInit({ - settings: { - caret: "#c6c6c6", - fontFamily: "monospace" + height="960px" + theme={sublimeInit({ + settings: { + caret: "#c6c6c6", + fontFamily: "monospace" + } + })} + extensions={[ + html({ + autoCloseTags: true, + matchClosingTags: true, + selfClosingTags: true + }) + ]} + /> + ) : ( + + handleCodeMirrorHTMLChange(value, viewUpdate) } - })} - extensions={[ - html({ - autoCloseTags: true, - matchClosingTags: true, - selfClosingTags: true - }) - ]} - /> + height="960px" + theme={sublimeInit({ + settings: { + caret: "#c6c6c6", + fontFamily: "monospace" + } + })} + extensions={[ + html({ + autoCloseTags: true, + matchClosingTags: true, + selfClosingTags: true + }) + ]} + /> + )} +
+ )} +
+ {!codeOnly && ( + + )} + {!previewOnly && ( + )}
- )} -
{!codeOnly && ( - - )} - {!previewOnly && ( - +
+ {templateLoading && ( + + + + )} + {renderErrors.length > 0 ? ( + + There is an error trying to render the email template: +
    + {renderErrors.map((err) => ( +
  • {err}
  • + ))} +
+
+ ) : mjmlRenderError?.message ? ( + + There is an error trying to render the email template: +
    {mjmlRenderError.message}
+
+ ) : ( + previewLoaded && ( +