BOD-35 Refactored email screen to use TinyMCE + added templates schema + base level email generation

This commit is contained in:
Patrick Fic
2020-04-16 15:50:07 -07:00
parent 248665aa65
commit c9cafa7ab7
27 changed files with 650 additions and 1639 deletions

View File

@@ -4,10 +4,9 @@
"private": true,
"proxy": "https://localhost:5000",
"dependencies": {
"@ckeditor/ckeditor5-build-classic": "^18.0.0",
"@ckeditor/ckeditor5-react": "^2.1.0",
"@nivo/pie": "^0.61.1",
"@tanem/react-nprogress": "^3.0.20",
"@tinymce/tinymce-react": "^3.5.0",
"aamva": "^1.2.0",
"antd": "^4.1.0",
"apollo-boost": "^0.4.4",
@@ -19,7 +18,6 @@
"axios": "^0.19.2",
"dotenv": "^8.2.0",
"firebase": "^7.13.1",
"grapesjs-react": "^3.0.4",
"graphql": "^14.6.0",
"i18next": "^19.3.4",
"node-sass": "^4.13.1",

View File

@@ -1,23 +1,19 @@
import React, { useRef, useState } from "react";
import { Editor } from "@tinymce/tinymce-react";
import { Col, Row } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { EmailSettings } from "../../emails/constants";
import {
startLoading,
endLoading,
startLoading,
} from "../../redux/application/application.actions";
import { setEmailOptions } from "../../redux/email/email.actions";
import T, {
Subject,
} from "../../emails/templates/appointment-confirmation/appointment-confirmation.template";
import { EMAIL_APPOINTMENT_CONFIRMATION } from "../../emails/templates/appointment-confirmation/appointment-confirmation.query";
import axios from "axios";
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import { Editor } from "grapesjs-react";
import "grapesjs/dist/css/grapes.min.css";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
@@ -27,551 +23,133 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(
mapStateToProps,
mapDispatchToProps
)(function Test({ setEmailOptions, load, endload }) {
const editorRef = useRef(null);
const [state, setState] = useState("");
)(function Test({ setEmailOptions, load, endload, bodyshop }) {
const [state, setState] = useState(temp);
const handleEditorChange = (content, editor) => {
setState(content);
};
return (
<div>
<Row>
<Col span={12}>
<button
onClick={() => {
axios
.post("/render", {
view: state,
context: {
people: ["Yehuda Katz", "Alan Johnson", "Charles Jolley"],
},
})
.then((r) => {
var newWin = window.open(
"url",
"windowName",
"height=300,width=300"
);
newWin.document.write(r.data);
});
}}
>
TinyMCE
</button>
<Editor
value={state}
apiKey="f3s2mjsd77ya5qvqkee9vgh612cm6h41e85efqakn2d0kknk"
init={{
height: 500,
//menubar: false,
encoding: "raw",
extended_valid_elements: "span",
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table paste code help wordcount",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
}}
onEditorChange={handleEditorChange}
/>
</Col>
</Row>
<button
onClick={() => {
axios
.post("/render", {
view: testView2,
context: {
people: ["Yehuda Katz", "Alan Johnson", "Charles Jolley"],
onClick={() =>
setEmailOptions({
messageOptions: {
from: {
name: bodyshop.shopname || EmailSettings.fromNameDefault,
address: EmailSettings.fromAddress,
},
})
.then((r) => {
var newWin = window.open(
"url",
"windowName",
"height=300,width=300"
);
newWin.document.write(r.data);
});
}}>
Test render for mustache
</button>
<button
onClick={() => {
axios
.post("/render", {
view: state,
context: {
people: ["Yehuda Katz", "Alan Johnson", "Charles Jolley"],
},
})
.then((r) => {
var newWin = window.open(
"url",
"windowName",
"height=300,width=300"
);
newWin.document.write(r.data);
});
}}>
Test render for mustache using state
to: "patrickwf@gmail.com",
replyTo: bodyshop.email,
Subject: "TODO FIX ME",
},
template: {
name: "appointment_reminder",
variables: { id: "2b42336f-b8de-4f04-a053-d6bff034d384" },
},
})
}
>
Set email config.
</button>
<button
onClick={() =>
setEmailOptions({
messageOptions: {
from: { name: "Kavia Autobody", address: "noreply@bodyshop.app" },
from: {
name: bodyshop.shopname || EmailSettings.fromNameDefault,
address: EmailSettings.fromAddress,
},
to: "patrickwf@gmail.com",
replyTo: "snaptsoft@gmail.com",
subject: Subject,
replyTo: bodyshop.email,
Subject: "TODO FIX ME",
},
template: {
name: "appointment_reminder2222222",
variables: { id: "2b42336f-b8de-4f04-a053-d6bff034d384" },
},
template: T,
queryConfig: [
EMAIL_APPOINTMENT_CONFIRMATION,
{ variables: { id: "91bb31dd-ea87-4cfc-bbe2-2ec754dcb861" } },
],
})
}>
Set email config.
}
>
Set email config. 222222
</button>
<CKEditor
editor={ClassicEditor}
data={view}
onChange={(event, editor) => {
// handleHtmlChange(editor.getData());
//TODO Ensure that removing onchange never introduces a race condition
}}
onBlur={(event, editor) => {
setState(editor.getData());
}}
/>
<Editor
blockManager={[
{
id: "section", // id is mandatory
label: "<b>Section</b>", // You can use HTML/SVG inside labels
attributes: { class: "gjs-block-section" },
content: `<section>
<h1>This is a simple title</h1>
<div>This is just a Lorem text: Lorem ipsum dolor sit amet</div>
</section>`,
},
{
id: "text2",
label: "Text2",
content:
'<div data-gjs-type="text">Insert your text here222222222</div>',
},
{
id: "image",
label: "Image",
// Select the component once it's dropped
select: true,
// You can pass components as a JSON instead of a simple HTML string,
// in this case we also use a defined component type `image`
content: { type: "image" },
// This triggers `active` event on dropped components and the `image`
// reacts by opening the AssetManager
activate: true,
},
]}
onInit={(editor) => {
console.log("editor", editor);
editor.setComponents(view);
}}
storageManager={false}
ref={editorRef}
/>
</div>
);
});
const testView2 = `<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Landing Page - Start Bootstrap Theme</title>
<!-- Bootstrap core CSS -->
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Custom fonts for this template -->
<link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet">
<link href="vendor/simple-line-icons/css/simple-line-icons.css" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Lato:300,400,700,300italic,400italic,700italic" rel="stylesheet" type="text/css">
<!-- Custom styles for this template -->
<link href="css/landing-page.min.css" rel="stylesheet">
</head>
<body>
<!-- Navigation -->
<nav class="navbar navbar-light bg-light static-top">
<div class="container">
<a class="navbar-brand" href="#">Start Bootstrap</a>
<a class="btn btn-primary" href="#">Sign In</a>
</div>
</nav>
<!-- Masthead -->
<header class="masthead text-white text-center">
<div class="overlay"></div>
<div class="container">
<div class="row">
<div class="col-xl-9 mx-auto">
<h1 class="mb-5">Build a landing page for your business or project and generate more leads!</h1>
</div>
<div class="col-md-10 col-lg-8 col-xl-7 mx-auto">
<form>
<div class="form-row">
<div class="col-12 col-md-9 mb-2 mb-md-0">
<input type="email" class="form-control form-control-lg" placeholder="Enter your email...">
</div>
<div class="col-12 col-md-3">
<button type="submit" class="btn btn-block btn-lg btn-primary">Sign up!</button>
</div>
</div>
</form>
</div>
</div>
</div>
</header>
<!-- Icons Grid -->
<section class="features-icons bg-light text-center">
<div class="container">
<div class="row">
<div class="col-lg-4">
<div class="features-icons-item mx-auto mb-5 mb-lg-0 mb-lg-3">
<div class="features-icons-icon d-flex">
<i class="icon-screen-desktop m-auto text-primary"></i>
</div>
<h3>Fully Responsive</h3>
<p class="lead mb-0">This theme will look great on any device, no matter the size!</p>
</div>
</div>
<div class="col-lg-4">
<div class="features-icons-item mx-auto mb-5 mb-lg-0 mb-lg-3">
<div class="features-icons-icon d-flex">
<i class="icon-layers m-auto text-primary"></i>
</div>
<h3>Bootstrap 4 Ready</h3>
<p class="lead mb-0">Featuring the latest build of the new Bootstrap 4 framework!</p>
</div>
</div>
<div class="col-lg-4" data-vvveb-disabled="">
<div class="features-icons-item mx-auto mb-0 mb-lg-3">
<div class="features-icons-icon d-flex">
<i class="icon-check m-auto text-primary"></i>
</div>
<h3>Easy to Use</h3>
<p class="lead mb-0">Ready to use with your own content, or customize the source files!</p>
</div>
</div>
</div>
</div>
</section>
<!-- Image Showcases -->
<section class="showcase">
<div class="container-fluid p-0">
<div class="row no-gutters">
<div class="col-lg-6 order-lg-2 text-white showcase-img" style="background-image: url('img/bg-showcase-1.jpg');"></div>
<div class="col-lg-6 order-lg-1 my-auto showcase-text">
<h2>Fully Responsive Design</h2>
<p class="lead mb-0">When you use a theme created by Start Bootstrap, you know that the theme will look great on any device, whether it's a phone, tablet, or desktop the page will behave responsively!</p>
</div>
</div>
<div class="row no-gutters">
<div class="col-lg-6 text-white showcase-img" style="background-image: url('img/bg-showcase-2.jpg');"></div>
<div class="col-lg-6 my-auto showcase-text">
<h2>Updated For Bootstrap 4</h2>
<p class="lead mb-0">Newly improved, and full of great utility classes, Bootstrap 4 is leading the way in mobile responsive web development! All of the themes on Start Bootstrap are now using Bootstrap 4!</p>
</div>
</div>
<div class="row no-gutters">
<div class="col-lg-6 order-lg-2 text-white showcase-img" style="background-image: url('img/bg-showcase-3.jpg');"></div>
<div class="col-lg-6 order-lg-1 my-auto showcase-text">
<h2>Easy to Use &amp; Customize</h2>
<p class="lead mb-0">Landing Page is just HTML and CSS with a splash of SCSS for users who demand some deeper customization options. Out of the box, just add your content and images, and your new landing page will be ready to go!</p>
</div>
</div>
</div>
</section>
<!-- Testimonials -->
<section class="testimonials text-center bg-light">
<div class="container">
<h2 class="mb-5">What people are saying...</h2>
<div class="row">
<div class="col-lg-4">
<div class="testimonial-item mx-auto mb-5 mb-lg-0">
<img class="img-fluid rounded-circle mb-3" src="img/testimonials-1.jpg" alt="">
<h5>Margaret E.</h5>
<p class="font-weight-light mb-0">"This is fantastic! Thanks so much guys!"</p>
</div>
</div>
<div class="col-lg-4">
<div class="testimonial-item mx-auto mb-5 mb-lg-0">
<img class="img-fluid rounded-circle mb-3" src="img/testimonials-2.jpg" alt="">
<h5>Fred S.</h5>
<p class="font-weight-light mb-0">"Bootstrap is amazing. I've been using it to create lots of super nice landing pages."</p>
</div>
</div>
<div class="col-lg-4" data-vvveb-disabled="">
<div class="testimonial-item mx-auto mb-5 mb-lg-0">
<img class="img-fluid rounded-circle mb-3" src="img/testimonials-3.jpg" alt="">
<h5>Sarah W.</h5>
<p class="font-weight-light mb-0">"Thanks so much for making these free resources available to us!"</p>
</div>
</div>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="call-to-action text-white text-center">
<div class="overlay"></div>
<div class="container">
<div class="row">
<div class="col-xl-9 mx-auto">
<h2 class="mb-4">Ready to get started? Sign up now!</h2>
</div>
<div class="col-md-10 col-lg-8 col-xl-7 mx-auto">
<form>
<div class="form-row">
<div class="col-12 col-md-9 mb-2 mb-md-0">
<input type="email" class="form-control form-control-lg" placeholder="Enter your email...">
</div>
<div class="col-12 col-md-3">
<button type="submit" class="btn btn-block btn-lg btn-primary">Sign up!</button>
</div>
</div>
</form>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer bg-light">
<div class="container">
<div class="row">
<div class="col-lg-6 h-100 text-center text-lg-left my-auto">
<ul class="list-inline mb-2">
<li class="list-inline-item">
<a href="#">About</a>
</li>
<li class="list-inline-item">⋅</li>
<li class="list-inline-item">
<a href="#">Contact</a>
</li>
<li class="list-inline-item">⋅</li>
<li class="list-inline-item">
<a href="#">Terms of Use</a>
</li>
<li class="list-inline-item">⋅</li>
<li class="list-inline-item">
<a href="#">Privacy Policy</a>
</li>
</ul>
<p class="text-muted small mb-4 mb-lg-0">© Your Website 2018. All Rights Reserved.</p>
</div>
<div class="col-lg-6 h-100 text-center text-lg-right my-auto">
<ul class="list-inline mb-0">
<li class="list-inline-item mr-3">
<a href="#">
<i class="fab fa-facebook fa-2x fa-fw"></i>
</a>
</li>
<li class="list-inline-item mr-3">
<a href="#">
<i class="fab fa-twitter-square fa-2x fa-fw"></i>
</a>
</li>
<li class="list-inline-item">
<a href="#">
<i class="fab fa-instagram fa-2x fa-fw"></i>
</a>
</li>
</ul>
</div>
</div>
</div>
</footer>
<!-- Bootstrap core JavaScript -->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
</body>
</html>`;
const view = `<table class="main-body" style="box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; height: 100%; background-color: rgb(234, 236, 237);" width="100%" height="100%" bgcolor="rgb(234, 236, 237)">
<tbody style="box-sizing: border-box;">
<tr class="row" style="box-sizing: border-box; vertical-align: top;" valign="top">
<td class="main-body-cell" style="box-sizing: border-box;">
<table class="container" style="box-sizing: border-box; font-family: Helvetica, serif; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-top: auto; margin-right: auto; margin-bottom: auto; margin-left: auto; height: 0px; width: 90%; max-width: 550px;" width="90%" height="0">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="container-cell" style="box-sizing: border-box; vertical-align: top; font-size: medium; padding-bottom: 50px;" valign="top">
<table class="table100 c1790" style="box-sizing: border-box; width: 100%; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; height: 0px; min-height: 30px; border-collapse: separate; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 0px;" width="100%" height="0">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td id="c1793" class="top-cell" style="box-sizing: border-box; text-align: right; color: rgb(152, 156, 165);" align="right">
<u id="c307" class="browser-link" style="box-sizing: border-box; font-size: 12px;">View in browser
</u>
</td>
</tr>
</tbody>
</table>
<table class="c1766" style="box-sizing: border-box; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: 0px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%; min-height: 30px;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="cell c1769" style="box-sizing: border-box; width: 11%;" width="11%">
<img src="//artf.github.io/grapesjs/img/grapesjs-logo.png" alt="GrapesJS." class="c926" style="box-sizing: border-box; color: rgb(158, 83, 129); width: 100%; font-size: 50px;">
</td>
<td class="cell c1776" style="box-sizing: border-box; width: 70%; vertical-align: middle;" width="70%" valign="middle">
<div class="c1144" style="box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px; font-size: 17px; font-weight: 300;">GrapesJS Newsletter Builder
<br style="box-sizing: border-box;">
</div>
</td>
</tr>
</tbody>
</table>
<table class="card" style="box-sizing: border-box; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; margin-bottom: 20px; height: 0px;" height="0">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="card-cell" style="box-sizing: border-box; background-color: rgb(255, 255, 255); overflow-x: hidden; overflow-y: hidden; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px; text-align: center;" bgcolor="rgb(255, 255, 255)" align="center">
<img src="//artf.github.io/grapesjs/img/tmp-header-txt.jpg" alt="Big image here" class="c1271" style="box-sizing: border-box; width: 100%; margin-top: 0px; margin-right: 0px; margin-bottom: 15px; margin-left: 0px; font-size: 50px; color: rgb(120, 197, 214); line-height: 250px; text-align: center;">
<table class="table100 c1357" style="box-sizing: border-box; width: 100%; min-height: 150px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; height: 0px; margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; border-collapse: collapse;" width="100%" height="0">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="card-content" style="box-sizing: border-box; font-size: 13px; line-height: 20px; color: rgb(111, 119, 125); padding-top: 10px; padding-right: 20px; padding-bottom: 0px; padding-left: 20px; vertical-align: top;" valign="top">
<h1 class="card-title" style="box-sizing: border-box; font-size: 25px; font-weight: 300; color: rgb(68, 68, 68);">Build your newsletters faster than ever
<br style="box-sizing: border-box;">
</h1>
<p class="card-text" style="box-sizing: border-box;">Import, build, test and export responsive newsletter templates faster than ever using the GrapesJS Newsletter Builder.
</p>
<div id="i3mqog" style="box-sizing: border-box; padding: 10px;">&nbsp; {{#each people}}
<br style="box-sizing: border-box;">
</div>
<div id="izgz11" style="box-sizing: border-box; padding: 10px;">&nbsp; &nbsp; {{this}}
<br style="box-sizing: border-box;">
</div>
<a class="button" style="box-sizing: border-box; font-size: 12px; padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; background-color: rgb(217, 131, 166); color: rgb(255, 255, 255); text-align: center; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; font-weight: 300;">Button</a>
<div id="ic5llo" style="box-sizing: border-box; padding: 10px;">&nbsp; {{/each}}
</div>
<table class="c1542" style="box-sizing: border-box; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; width: 100%;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td id="c1545" class="card-footer" style="box-sizing: border-box; padding-top: 20px; padding-right: 0px; padding-bottom: 20px; padding-left: 0px; text-align: center;" align="center">
<a href="https://github.com/artf/grapesjs" class="button" style="box-sizing: border-box; font-size: 12px; padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; background-color: rgb(217, 131, 166); color: rgb(255, 255, 255); text-align: center; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; font-weight: 300;">Free and Open Source
</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="list-item" style="box-sizing: border-box; height: auto; width: 100%; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="list-item-cell" style="box-sizing: border-box; background-color: rgb(255, 255, 255); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; overflow-x: hidden; overflow-y: hidden; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" bgcolor="rgb(255, 255, 255)">
<table class="list-item-content" style="box-sizing: border-box; border-collapse: collapse; margin-top: 0px; margin-right: auto; margin-bottom: 0px; margin-left: auto; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; height: 150px; width: 100%;" width="100%" height="150">
<tbody style="box-sizing: border-box;">
<tr class="list-item-row" style="box-sizing: border-box;">
<td class="list-cell-left" style="box-sizing: border-box; width: 30%; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" width="30%">
<img src="//artf.github.io/grapesjs/img/tmp-blocks.jpg" alt="Image1" class="list-item-image" style="box-sizing: border-box; color: rgb(217, 131, 166); font-size: 45px; width: 100%;">
</td>
<td class="list-cell-right" style="box-sizing: border-box; width: 70%; color: rgb(111, 119, 125); font-size: 13px; line-height: 20px; padding-top: 10px; padding-right: 20px; padding-bottom: 0px; padding-left: 20px;" width="70%">
<h1 class="card-title" style="box-sizing: border-box; font-size: 25px; font-weight: 300; color: rgb(68, 68, 68);">Built-in Blocks
</h1>
<p class="card-text" style="box-sizing: border-box;">Drag and drop built-in blocks from the right panel and style them in a matter of seconds
</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="list-item" style="box-sizing: border-box; height: auto; width: 100%; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="list-item-cell" style="box-sizing: border-box; background-color: rgb(255, 255, 255); border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; overflow-x: hidden; overflow-y: hidden; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" bgcolor="rgb(255, 255, 255)">
<table class="list-item-content" style="box-sizing: border-box; border-collapse: collapse; margin-top: 0px; margin-right: auto; margin-bottom: 0px; margin-left: auto; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px; height: 150px; width: 100%;" width="100%" height="150">
<tbody style="box-sizing: border-box;">
<tr class="list-item-row" style="box-sizing: border-box;">
<td class="list-cell-left" style="box-sizing: border-box; width: 30%; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" width="30%">
<img src="//artf.github.io/grapesjs/img/tmp-tgl-images.jpg" alt="Image2" class="list-item-image" style="box-sizing: border-box; color: rgb(217, 131, 166); font-size: 45px; width: 100%;">
</td>
<td class="list-cell-right" style="box-sizing: border-box; width: 70%; color: rgb(111, 119, 125); font-size: 13px; line-height: 20px; padding-top: 10px; padding-right: 20px; padding-bottom: 0px; padding-left: 20px;" width="70%">
<h1 class="card-title" style="box-sizing: border-box; font-size: 25px; font-weight: 300; color: rgb(68, 68, 68);">Toggle images
</h1>
<p class="card-text" style="box-sizing: border-box;">Build a good looking newsletter even without images enabled by the email clients
</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="grid-item-row" style="box-sizing: border-box; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; padding-top: 5px; padding-right: 0px; padding-bottom: 5px; padding-left: 0px; width: 100%;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="grid-item-cell2-l" style="box-sizing: border-box; vertical-align: top; padding-right: 10px; width: 50%;" width="50%" valign="top">
<table class="grid-item-card" style="box-sizing: border-box; width: 100%; padding-top: 5px; padding-right: 0px; padding-bottom: 5px; padding-left: 0px; margin-bottom: 10px;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="grid-item-card-cell" style="box-sizing: border-box; background-color: rgb(255, 255, 255); overflow-x: hidden; overflow-y: hidden; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; text-align: center; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" bgcolor="rgb(255, 255, 255)" align="center">
<img src="//artf.github.io/grapesjs/img/tmp-send-test.jpg" alt="Image1" class="grid-item-image" style="box-sizing: border-box; line-height: 150px; font-size: 50px; color: rgb(120, 197, 214); margin-bottom: 15px; width: 100%;">
<table class="grid-item-card-body" style="box-sizing: border-box;">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="grid-item-card-content" style="box-sizing: border-box; font-size: 13px; color: rgb(111, 119, 125); padding-top: 0px; padding-right: 10px; padding-bottom: 20px; padding-left: 10px; width: 100%; line-height: 20px;" width="100%">
<h1 class="card-title" style="box-sizing: border-box; font-size: 25px; font-weight: 300; color: rgb(68, 68, 68);">Test it
</h1>
<p class="card-text" style="box-sizing: border-box;">You can send email tests directly from the editor and check how are looking on your email clients
</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
<td class="grid-item-cell2-r" style="box-sizing: border-box; vertical-align: top; padding-left: 10px; width: 50%;" width="50%" valign="top">
<table class="grid-item-card" style="box-sizing: border-box; width: 100%; padding-top: 5px; padding-right: 0px; padding-bottom: 5px; padding-left: 0px; margin-bottom: 10px;" width="100%">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="grid-item-card-cell" style="box-sizing: border-box; background-color: rgb(255, 255, 255); overflow-x: hidden; overflow-y: hidden; border-top-left-radius: 3px; border-top-right-radius: 3px; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; text-align: center; padding-top: 0px; padding-right: 0px; padding-bottom: 0px; padding-left: 0px;" bgcolor="rgb(255, 255, 255)" align="center">
<img src="//artf.github.io/grapesjs/img/tmp-devices.jpg" alt="Image2" class="grid-item-image" style="box-sizing: border-box; line-height: 150px; font-size: 50px; color: rgb(120, 197, 214); margin-bottom: 15px; width: 100%;">
<table class="grid-item-card-body" style="box-sizing: border-box;">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="grid-item-card-content" style="box-sizing: border-box; font-size: 13px; color: rgb(111, 119, 125); padding-top: 0px; padding-right: 10px; padding-bottom: 20px; padding-left: 10px; width: 100%; line-height: 20px;" width="100%">
<h1 class="card-title" style="box-sizing: border-box; font-size: 25px; font-weight: 300; color: rgb(68, 68, 68);">Responsive
</h1>
<p class="card-text" style="box-sizing: border-box;">Using the device manager you'll always send a fully responsive contents
</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="footer" style="box-sizing: border-box; margin-top: 50px; color: rgb(152, 156, 165); text-align: center; font-size: 11px; padding-top: 5px; padding-right: 5px; padding-bottom: 5px; padding-left: 5px;" align="center">
<tbody style="box-sizing: border-box;">
<tr style="box-sizing: border-box;">
<td class="footer-cell" style="box-sizing: border-box;">
<div class="c2577" style="box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;">
<p class="footer-info" style="box-sizing: border-box;">GrapesJS Newsletter Builder is a free and open source preset (plugin) used on top of the GrapesJS core library.
For more information about and how to integrate it inside your applications check
</p>
<p style="box-sizing: border-box;">
<a href="https://github.com/artf/grapesjs-preset-newsletter" class="link" style="box-sizing: border-box; color: rgb(217, 131, 166);">GrapesJS Newsletter Preset</a>
<br style="box-sizing: border-box;">
</p>
</div>
<div class="c2421" style="box-sizing: border-box; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;">
MADE BY
<a href="https://github.com/artf" class="link" style="box-sizing: border-box; color: rgb(217, 131, 166);">ARTUR ARSENIEV</a>
<p style="box-sizing: border-box;">
</p>
</div>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
const temp = `<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;"><span>&rarr;<span> This is a full-featured editor demo. </span>Please explore! &larr;</span></p>
<p style="text-align: center;">&nbsp;</p>
<h2 style="text-align: center;"><span>TinyMCE is the world's most customizable, and flexible, rich text editor.</span></h2>
<p style="text-align: center;"><span><strong> A featherweight download, TinyMCE can handle any challenge you throw at it. </strong></span></p>
<p style="text-align: center;">&nbsp;</p>
<p>&nbsp;</p>
<table style="border-collapse: collapse; width: 85%; height: 86px; border-color: initial; border-style: solid; margin-left: auto; margin-right: auto;">
<tbody>
<tr style="height: 22px;">
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>🛠 50+ Plugins</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>💡 Premium Support</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>🖍 Custom Skins</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>⚙ Full API Access</span></td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px; display: none;"><span>{{#each people}}</span></td>
</tr>
<tr style="height: 22px;">
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px;"><span>{{/each}}</span></td>
</tr>
</tbody>
</table>`;
</table>
</div>`;

View File

@@ -1,12 +1,10 @@
import React from "react";
import { Editor } from "@tinymce/tinymce-react";
import { Input } from "antd";
import CKEditor from "@ckeditor/ckeditor5-react";
import ClassicEditor from "@ckeditor/ckeditor5-build-classic";
import React from "react";
export default function EmailOverlayComponent({
messageOptions,
handleConfigChange,
handleHtmlChange
handleHtmlChange,
}) {
return (
<div>
@@ -27,17 +25,24 @@ export default function EmailOverlayComponent({
onChange={handleConfigChange}
name="subject"
/>
<CKEditor
editor={ClassicEditor}
data={messageOptions.html}
onChange={(event, editor) => {
// handleHtmlChange(editor.getData());
//TODO Ensure that removing onchange never introduces a race condition
}}
onBlur={(event, editor) => {
console.log("Blur.");
handleHtmlChange(editor.getData());
<Editor
value={messageOptions.html}
apiKey="f3s2mjsd77ya5qvqkee9vgh612cm6h41e85efqakn2d0kknk"
init={{
height: 500,
//menubar: false,
encoding: "raw",
extended_valid_elements: "span",
//entity_encoding: "raw",
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table paste code help wordcount",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
}}
onEditorChange={handleHtmlChange}
/>
</div>
);

View File

@@ -1,104 +1,127 @@
import { useApolloClient } from "@apollo/react-hooks";
import { Modal, notification } from "antd";
import { gql } from "apollo-boost";
import axios from "axios";
import React, { useEffect, useState } from "react";
import { useLazyQuery } from "@apollo/react-hooks";
import ReactDOMServer from "react-dom/server";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_TEMPLATES_BY_NAME } from "../../graphql/templates.queries";
import { toggleEmailOverlayVisible } from "../../redux/email/email.actions";
import {
selectEmailConfig,
selectEmailVisible
selectEmailVisible,
} from "../../redux/email/email.selectors.js";
import { selectBodyshop } from "../../redux/user/user.selectors";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import EmailOverlayComponent from "./email-overlay.component";
const mapStateToProps = createStructuredSelector({
modalVisible: selectEmailVisible,
emailConfig: selectEmailConfig
emailConfig: selectEmailConfig,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = dispatch => ({
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible())
const mapDispatchToProps = (dispatch) => ({
toggleEmailOverlayVisible: () => dispatch(toggleEmailOverlayVisible()),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function EmailOverlayContainer({
export function EmailOverlayContainer({
emailConfig,
modalVisible,
toggleEmailOverlayVisible
toggleEmailOverlayVisible,
bodyshop,
}) {
const { t } = useTranslation();
const [messageOptions, setMessageOptions] = useState(
emailConfig.messageOptions
);
const client = useApolloClient();
useEffect(() => {
setMessageOptions(emailConfig.messageOptions);
}, [setMessageOptions, emailConfig.messageOptions]);
const renderEmail = () => {
client
.query({
query: QUERY_TEMPLATES_BY_NAME,
variables: { name: emailConfig.template.name },
fetchPolicy: "network-only",
})
.then(({ data: templateRecords }) => {
let templateToUse;
if (templateRecords.templates.length === 1) {
console.log("Only 1 Template found.");
templateToUse = templateRecords.templates[0];
} else if (templateRecords.templates.length === 2) {
console.log("2 Templates found..");
templateToUse = templateRecords.templates.filter(
(t) => !!t.bodyshopid
);
} else {
//No template found.Uh oh.
alert("Templating Error!");
}
const [executeQuery, { called, loading, data }] = useLazyQuery(
emailConfig.queryConfig[0],
{
...emailConfig.queryConfig[1],
fetchPolicy: "network-only"
}
);
client
.query({
query: gql(templateToUse.query),
variables: { ...emailConfig.template.variables },
fetchPolicy: "network-only",
})
.then(({ data: contextData }) => {
handleRender(contextData, templateToUse.html);
});
});
};
if (
emailConfig.queryConfig[0] &&
emailConfig.queryConfig[1] &&
modalVisible &&
!called
) {
executeQuery();
}
if (data && !messageOptions.html && emailConfig.template) {
setMessageOptions({
...messageOptions,
html: ReactDOMServer.renderToStaticMarkup(
<emailConfig.template data={data} />
)
});
}
const handleRender = (contextData, html) => {
axios
.post("/render", {
view: html,
context: { ...contextData, bodyshop: bodyshop },
})
.then((r) => {
setMessageOptions({ ...messageOptions, html: r.data });
});
};
const handleOk = () => {
//sendEmail(messageOptions);
axios
.post("/sendemail", messageOptions)
.then(response => {
.then((response) => {
console.log(JSON.stringify(response));
notification["success"]({ message: t("emails.successes.sent") });
toggleEmailOverlayVisible();
})
.catch(error => {
.catch((error) => {
console.log(JSON.stringify(error));
notification["error"]({
message: t("emails.errors.notsent", { message: error.message })
message: t("emails.errors.notsent", { message: error.message }),
});
});
};
const handleConfigChange = event => {
const handleConfigChange = (event) => {
const { name, value } = event.target;
setMessageOptions({ ...messageOptions, [name]: value });
};
const handleHtmlChange = text => {
const handleHtmlChange = (text) => {
setMessageOptions({ ...messageOptions, html: text });
};
useEffect(() => {
if (modalVisible) renderEmail();
}, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Modal
destroyOnClose={true}
visible={modalVisible}
width={"80%"}
onOk={handleOk}
onCancel={() => toggleEmailOverlayVisible()}
onCancel={() => {
toggleEmailOverlayVisible();
}}
>
<LoadingSpinner loading={loading}>
<LoadingSpinner loading={false}>
<EmailOverlayComponent
handleConfigChange={handleConfigChange}
messageOptions={messageOptions}
@@ -115,4 +138,8 @@ export default connect(
</LoadingSpinner>
</Modal>
);
});
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(EmailOverlayContainer);

View File

@@ -14,7 +14,7 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectPartsOrder } from "../../redux/modals/modals.selectors";
import {
selectBodyshop,
selectCurrentUser
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
@@ -24,12 +24,12 @@ import { EmailSettings } from "../../emails/constants";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
partsOrderModal: selectPartsOrder
partsOrderModal: selectPartsOrder,
});
const mapDispatchToProps = dispatch => ({
setEmailOptions: e => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder"))
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
});
export function PartsOrderModalContainer({
@@ -37,7 +37,7 @@ export function PartsOrderModalContainer({
toggleModalVisible,
currentUser,
bodyshop,
setEmailOptions
setEmailOptions,
}) {
const { t } = useTranslation();
@@ -50,12 +50,12 @@ export function PartsOrderModalContainer({
const sendType = sendTypeState[0];
const { loading, error, data } = useQuery(QUERY_ALL_VENDORS_FOR_ORDER, {
skip: !visible
skip: !visible,
});
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const handleFinish = values => {
const handleFinish = (values) => {
insertPartOrder({
variables: {
po: [
@@ -63,21 +63,21 @@ export function PartsOrderModalContainer({
...values,
jobid: jobId,
user_email: currentUser.email,
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
}
]
}
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
],
},
})
.then(r => {
.then((r) => {
updateJobLines({
variables: {
ids: values.parts_order_lines.data.map(item => item.job_line_id),
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
}
ids: values.parts_order_lines.data.map((item) => item.job_line_id),
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
})
.then(response => {
.then((response) => {
notification["success"]({
message: t("parts_orders.successes.created")
message: t("parts_orders.successes.created"),
});
if (refetch) refetch();
toggleModalVisible();
@@ -88,38 +88,38 @@ export function PartsOrderModalContainer({
messageOptions: {
from: {
name: bodyshop.shopname || EmailSettings.fromNameDefault,
address: EmailSettings.fromAddress
address: EmailSettings.fromAddress,
},
to:
data.vendors.filter(item => item.id === values.id)[0] ||
data.vendors.filter((item) => item.id === values.id)[0] ||
null,
replyTo: bodyshop.shopname || null
replyTo: bodyshop.email,
},
template: PartsOrderEmailTemplate,
queryConfig: [
REPORT_QUERY_PARTS_ORDER_BY_PK,
{
variables: {
id: r.data.insert_parts_orders.returning[0].id
}
}
]
id: r.data.insert_parts_orders.returning[0].id,
},
},
],
});
}
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: error.message
description: error.message,
});
});
//end no good
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: error.message
description: error.message,
});
});
};
@@ -133,7 +133,7 @@ export function PartsOrderModalContainer({
db_price: value.db_price,
act_price: value.act_price,
job_line_id: value.id,
status: bodyshop.md_order_statuses.default_ordered || "Ordered*"
status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
});
return acc;
}, []);

View File

@@ -3,7 +3,7 @@ import ScheduleJobModalComponent from "./schedule-job-modal.component";
import { useMutation, useQuery } from "@apollo/react-hooks";
import {
INSERT_APPOINTMENT,
QUERY_APPOINTMENTS_BY_JOBID
QUERY_APPOINTMENTS_BY_JOBID,
} from "../../graphql/appointments.queries";
import moment from "moment";
import { notification, Modal } from "antd";
@@ -18,31 +18,29 @@ import { toggleModalVisible } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
scheduleModal: selectSchedule
scheduleModal: selectSchedule,
});
const mapDispatchToProps = dispatch => ({
toggleModalVisible: () => dispatch(toggleModalVisible("schedule"))
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
});
export function ScheduleJobModalContainer({
scheduleModal,
bodyshop,
toggleModalVisible
toggleModalVisible,
}) {
const { visible, context, actions } = scheduleModal;
const { jobId } = context;
const { refetch } = actions;
const [appData, setAppData] = useState({
jobid: jobId,
start: null,
bodyshopid: bodyshop.id
});
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS, {
variables: {
jobId: jobId,
status: bodyshop.md_ro_statuses.default_scheduled
}
status: bodyshop.md_ro_statuses.default_scheduled,
},
});
const [formData, setFormData] = useState({ notifyCustomer: false });
const { t } = useTranslation();
@@ -50,20 +48,25 @@ export function ScheduleJobModalContainer({
const existingAppointments = useQuery(QUERY_APPOINTMENTS_BY_JOBID, {
variables: { jobid: jobId },
fetchPolicy: "network-only",
skip: !visible
skip: !visible,
});
//TODO Customize the amount of minutes it will add.
const handleOk = () => {
insertAppointment({
variables: {
app: { ...appData, end: moment(appData.start).add(60, "minutes") }
}
app: {
...appData,
jobid: jobId,
bodyshopid: bodyshop.id,
end: moment(appData.start).add(60, "minutes"),
},
},
})
.then(r => {
updateJobStatus().then(r => {
.then((r) => {
updateJobStatus().then((r) => {
notification["success"]({
message: t("appointments.successes.created")
message: t("appointments.successes.created"),
});
if (formData.notifyCustomer) {
@@ -74,11 +77,11 @@ export function ScheduleJobModalContainer({
if (refetch) refetch();
});
})
.catch(error => {
.catch((error) => {
notification["error"]({
message: t("appointments.errors.saving", {
message: error.message
})
message: error.message,
}),
});
});
};

View File

@@ -1,46 +0,0 @@
import React from "react";
function Column({ children }) {
return <td>{children}</td>;
}
function Row({ children }) {
return (
<tr>
{React.Children.map(children, el => {
if (el.type === Column) return el;
return <td>{el}</td>;
})}
</tr>
);
}
function Grid({ children }) {
return (
<table>
<tbody>
{React.Children.map(children, el => {
if (!el) return;
if (el.type === Row) return el;
if (el.type === Column) {
return <tr>{el}</tr>;
}
return (
<tr>
<td>{el}</td>
</tr>
);
})}
</tbody>
</table>
);
}
Grid.Row = Row;
Grid.Column = Column;
export default Grid;

View File

@@ -24,6 +24,7 @@ export const QUERY_BODYSHOP = gql`
region_config
md_responsibility_centers
messagingservicesid
template_header
employees {
id
first_name

View File

@@ -0,0 +1,13 @@
import { gql } from "apollo-boost";
export const QUERY_TEMPLATES_BY_NAME = gql`
query QUERY_TEMPLATES_BY_NAME($name: String!) {
templates(where: { name: { _eq: $name } }) {
id
html
name
query
bodyshopid
}
}
`;

View File

@@ -5,14 +5,13 @@ const INITIAL_STATE = {
messageOptions: {
from: { name: "ShopName", address: "noreply@bodyshop.app" },
to: null,
replyTo: null
replyTo: null,
},
template: null,
queryConfig: [null, { variables: null }]
template: { name: null, variables: {} },
},
visible: false,
error: null
error: null,
};
const emailReducer = (state = INITIAL_STATE, action) => {
@@ -20,13 +19,13 @@ const emailReducer = (state = INITIAL_STATE, action) => {
case EmailActionTypes.TOGGLE_EMAIL_OVERLAY_VISIBLE:
return {
...state,
visible: !state.visible
visible: !state.visible,
};
case EmailActionTypes.SET_EMAIL_OPTIONS:
return {
...state,
emailConfig: { ...action.payload },
visible: true
visible: true,
};
default:
return state;

View File

@@ -1,25 +1,19 @@
import { createSelector } from "reselect";
const selectEmail = state => state.email;
const selectEmailConfigMessageOptions = state =>
const selectEmail = (state) => state.email;
const selectEmailConfigMessageOptions = (state) =>
state.email.emailConfig.messageOptions;
const selectEmailConfigTemplate = state => state.email.emailConfig.template;
const selectEmailConfigQuery = state => state.email.emailConfig.queryConfig;
const selectEmailConfigTemplate = (state) => state.email.emailConfig.template;
export const selectEmailVisible = createSelector(
[selectEmail],
email => email.visible
(email) => email.visible
);
export const selectEmailConfig = createSelector(
[
selectEmailConfigMessageOptions,
selectEmailConfigTemplate,
selectEmailConfigQuery
],
(messageOptions, template, queryConfig) => ({
[selectEmailConfigMessageOptions, selectEmailConfigTemplate],
(messageOptions, template) => ({
messageOptions,
template,
queryConfig
})
);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "template_header";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "template_header" text NULL;
type: run_sql

View File

@@ -0,0 +1,44 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- address1
- address2
- city
- country
- created_at
- email
- federal_tax_id
- id
- insurance_vendor_id
- logo_img_path
- md_order_statuses
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- region_config
- shopname
- shoprates
- state
- state_tax_id
- updated_at
- zip_post
computed_fields: []
filter:
associations:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- address1
- address2
- city
- country
- created_at
- email
- federal_tax_id
- id
- insurance_vendor_id
- logo_img_path
- md_order_statuses
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- region_config
- shopname
- shoprates
- state
- state_tax_id
- template_header
- updated_at
- zip_post
computed_fields: []
filter:
associations:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,45 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- address1
- address2
- city
- country
- created_at
- email
- federal_tax_id
- id
- insurance_vendor_id
- logo_img_path
- md_order_statuses
- md_responsibility_centers
- md_ro_statuses
- region_config
- shopname
- shoprates
- state
- state_tax_id
- updated_at
- zip_post
filter:
associations:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -0,0 +1,46 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- address1
- address2
- city
- country
- created_at
- email
- federal_tax_id
- id
- insurance_vendor_id
- logo_img_path
- md_order_statuses
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- region_config
- shopname
- shoprates
- state
- state_tax_id
- updated_at
- zip_post
filter:
associations:
bodyshop:
associations:
user:
authid:
_eq: X-Hasura-User-Id
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: DROP TABLE "public"."templates";
type: run_sql

View File

@@ -0,0 +1,24 @@
- args:
cascade: false
read_only: false
sql: CREATE EXTENSION IF NOT EXISTS pgcrypto;
type: run_sql
- args:
cascade: false
read_only: false
sql: "CREATE TABLE \"public\".\"templates\"(\"id\" uuid NOT NULL DEFAULT gen_random_uuid(),
\"created_at\" timestamptz NOT NULL DEFAULT now(), \"updated_at\" timestamptz
NOT NULL DEFAULT now(), \"bodyshopid\" uuid, \"name\" text NOT NULL, \"html\"
text NOT NULL, \"query\" text NOT NULL, PRIMARY KEY (\"id\") , FOREIGN KEY (\"bodyshopid\")
REFERENCES \"public\".\"bodyshops\"(\"id\") ON UPDATE restrict ON DELETE restrict);\nCREATE
OR REPLACE FUNCTION \"public\".\"set_current_timestamp_updated_at\"()\nRETURNS
TRIGGER AS $$\nDECLARE\n _new record;\nBEGIN\n _new := NEW;\n _new.\"updated_at\"
= NOW();\n RETURN _new;\nEND;\n$$ LANGUAGE plpgsql;\nCREATE TRIGGER \"set_public_templates_updated_at\"\nBEFORE
UPDATE ON \"public\".\"templates\"\nFOR EACH ROW\nEXECUTE PROCEDURE \"public\".\"set_current_timestamp_updated_at\"();\nCOMMENT
ON TRIGGER \"set_public_templates_updated_at\" ON \"public\".\"templates\" \nIS
'trigger to set value of column \"updated_at\" to current timestamp on row update';"
type: run_sql
- args:
name: templates
schema: public
type: add_existing_table_or_view

View File

@@ -0,0 +1,12 @@
- args:
relationship: templates
table:
name: bodyshops
schema: public
type: drop_relationship
- args:
relationship: bodyshop
table:
name: templates
schema: public
type: drop_relationship

View File

@@ -0,0 +1,20 @@
- args:
name: templates
table:
name: bodyshops
schema: public
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: templates
schema: public
type: create_array_relationship
- args:
name: bodyshop
table:
name: templates
schema: public
using:
foreign_key_constraint_on: bodyshopid
type: create_object_relationship

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_select_permission

View File

@@ -0,0 +1,30 @@
- args:
permission:
allow_aggregations: false
columns:
- html
- name
- query
- created_at
- updated_at
- bodyshopid
- id
computed_fields: []
filter:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
limit: null
role: user
table:
name: templates
schema: public
type: create_select_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: templates
schema: public
type: drop_update_permission

View File

@@ -0,0 +1,31 @@
- args:
permission:
columns:
- html
- name
- query
- created_at
- updated_at
- bodyshopid
- id
filter:
_or:
- bodyshopid:
_is_null: true
- bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
localPresets:
- key: ""
value: ""
set: {}
role: user
table:
name: templates
schema: public
type: create_update_permission

View File

@@ -3,7 +3,14 @@ const Handlebars = require("handlebars");
exports.render = (req, res) => {
//Perform request validation
console.log("Got a render request.");
var template = Handlebars.compile(req.body.view);
let view;
if (req.body.context.bodyshop.template_header) {
console.log("[HJS Render] Including Header");
view = `${req.body.context.bodyshop.template_header}${req.body.view}`;
} else {
console.log("[HJS Render] No header to include.");
view = req.body.view;
}
var template = Handlebars.compile(view);
res.send(template(req.body.context));
};