Added required authorization for all API calls. Added template stylesheet.
This commit is contained in:
@@ -1,112 +1,95 @@
|
||||
unlayer.registerPropertyEditor({
|
||||
name: "my_color_picker",
|
||||
layout: "bottom",
|
||||
Widget: unlayer.createWidget({
|
||||
render(value) {
|
||||
return `
|
||||
<input class="color-value" value=${value} />
|
||||
<button class="red">Red</button>
|
||||
<button class="green">Green</button>
|
||||
<button class="blue">Blue</button>
|
||||
`;
|
||||
},
|
||||
mount(node, value, updateValue) {
|
||||
var input = node.getElementsByClassName("color-value")[0];
|
||||
input.onchange = function (event) {
|
||||
updateValue(event.target.value);
|
||||
};
|
||||
// unlayer.registerPropertyEditor({
|
||||
// name: "field_name",
|
||||
// layout: "bottom",
|
||||
// Widget: unlayer.createWidget({
|
||||
// render(value) {
|
||||
// return `
|
||||
// <input class="field" value=${value} />
|
||||
// `;
|
||||
// },
|
||||
// mount(node, value, updateValue) {
|
||||
// var input = node.getElementsByClassName("field")[0];
|
||||
// input.onchange = function (event) {
|
||||
// updateValue(event.target.value);
|
||||
// };
|
||||
// },
|
||||
// }),
|
||||
// });
|
||||
|
||||
var redButton = node.getElementsByClassName("red")[0];
|
||||
redButton.onclick = function () {
|
||||
updateValue("#f00");
|
||||
};
|
||||
// unlayer.registerTool({
|
||||
// type: "whatever",
|
||||
// category: "contents",
|
||||
// label: "Begin Repeat",
|
||||
// icon: "fa-smile",
|
||||
// values: {},
|
||||
// options: {
|
||||
// default: {
|
||||
// title: null,
|
||||
// },
|
||||
// text: {
|
||||
// title: "Field",
|
||||
// position: 1,
|
||||
// options: {
|
||||
// field: {
|
||||
// label: "Field",
|
||||
// defaultValue: "",
|
||||
// widget: "field_name",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// renderer: {
|
||||
// Viewer: unlayer.createViewer({
|
||||
// render(values) {
|
||||
// console.log(values);
|
||||
// return `
|
||||
// <div style="display: none;">{{#each ${values.field}}}</div>
|
||||
// `;
|
||||
// },
|
||||
// }),
|
||||
// exporters: {
|
||||
// web: function () {},
|
||||
// email: function () {},
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
var greenButton = node.getElementsByClassName("green")[0];
|
||||
greenButton.onclick = function () {
|
||||
updateValue("#0f0");
|
||||
};
|
||||
|
||||
var blueButton = node.getElementsByClassName("blue")[0];
|
||||
blueButton.onclick = function () {
|
||||
updateValue("#00f");
|
||||
};
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
unlayer.registerTool({
|
||||
type: "whatever",
|
||||
category: "contents",
|
||||
label: "My Tool",
|
||||
icon: "fa-smile",
|
||||
values: {},
|
||||
options: {
|
||||
default: {
|
||||
title: null,
|
||||
},
|
||||
text: {
|
||||
title: "Text",
|
||||
position: 1,
|
||||
options: {
|
||||
color: {
|
||||
label: "Color",
|
||||
defaultValue: "#000",
|
||||
widget: "my_color_picker",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
Viewer: unlayer.createViewer({
|
||||
render(values) {
|
||||
return `
|
||||
<div style="color: ${values.color};">I am a custom tool.</div>
|
||||
`;
|
||||
},
|
||||
}),
|
||||
exporters: {
|
||||
web: function () {},
|
||||
email: function () {},
|
||||
},
|
||||
},
|
||||
});
|
||||
// unlayer.registerTool({
|
||||
// type: "whatever",
|
||||
// category: "contents",
|
||||
// label: "End Repeat",
|
||||
// icon: "fa-smile",
|
||||
// values: {},
|
||||
// options: {
|
||||
// default: {
|
||||
// title: null,
|
||||
// },
|
||||
// text: {
|
||||
// title: "Field",
|
||||
// position: 1,
|
||||
// options: {
|
||||
// field: {
|
||||
// label: "Field",
|
||||
// defaultValue: "",
|
||||
// widget: "field_name",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// renderer: {
|
||||
// Viewer: unlayer.createViewer({
|
||||
// render(values) {
|
||||
// return `
|
||||
// <div style="display: none;">{{ /each }}</div>
|
||||
// `;
|
||||
// },
|
||||
// }),
|
||||
// exporters: {
|
||||
// web: function () {},
|
||||
// email: function () {},
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
unlayer.registerColumns([2, 2, 2, 2, 2, 2]);
|
||||
unlayer.registerColumns([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
|
||||
unlayer.registerTool({
|
||||
type: "whatever",
|
||||
category: "contents",
|
||||
label: "Begin Repeater",
|
||||
icon: "fa-smile",
|
||||
values: {},
|
||||
options: {
|
||||
default: {
|
||||
title: "BeginRepeater",
|
||||
},
|
||||
text: {
|
||||
title: "Text",
|
||||
position: 1,
|
||||
options: {
|
||||
field: {
|
||||
label: "Field",
|
||||
defaultValue: "Field Name...",
|
||||
widget: "my_color_picker",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
Viewer: unlayer.createViewer({
|
||||
render(values) {
|
||||
return `
|
||||
<div style="color: ${values.color};">{{${values.field}}}</div>
|
||||
`;
|
||||
},
|
||||
}),
|
||||
exporters: {
|
||||
web: function () {},
|
||||
email: function () {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
85
client/public/render-styles.css
Normal file
85
client/public/render-styles.css
Normal file
@@ -0,0 +1,85 @@
|
||||
/* body {
|
||||
font-family: "Open Sans", sans-serif;
|
||||
line-height: 1.25;
|
||||
} */
|
||||
|
||||
table {
|
||||
border: 1px solid #ccc;
|
||||
border-collapse: collapse;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
table caption {
|
||||
font-size: 1.5em;
|
||||
margin: 0.5em 0 0.75em;
|
||||
}
|
||||
|
||||
table tr {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ddd;
|
||||
padding: 0.35em;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
padding: 0.625em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table th {
|
||||
font-size: 0.85em;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
table {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
table caption {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
table thead {
|
||||
border: none;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
table tr {
|
||||
border-bottom: 3px solid #ddd;
|
||||
display: block;
|
||||
margin-bottom: 0.625em;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
table td::before {
|
||||
/*
|
||||
* aria-label has no advantage, it won't be read inside a table
|
||||
content: attr(aria-label);
|
||||
*/
|
||||
content: attr(data-label);
|
||||
float: left;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
table td:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,25 @@ import App from "./App";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import moment from "moment";
|
||||
import axios from "axios";
|
||||
|
||||
moment.locale("en-US");
|
||||
|
||||
axios.interceptors.request.use(
|
||||
async (config) => {
|
||||
if (!config.headers.Authorization) {
|
||||
const token =
|
||||
auth.currentUser && (await auth.currentUser.getIdToken(true));
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => Promise.reject(error)
|
||||
);
|
||||
|
||||
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
|
||||
const httpLink = new HttpLink({
|
||||
|
||||
@@ -18,9 +18,11 @@ export default function ShopTemplateSaveButton({
|
||||
logImEXEvent("shop_template_update");
|
||||
|
||||
emailEditorRef.current.exportHtml(async (data) => {
|
||||
console.log("RAW", data.html);
|
||||
inlineCss(data.html, {
|
||||
url: `${window.location.protocol}://${window.location.host}`,
|
||||
url: `${window.location.protocol}://${window.location.host}/`,
|
||||
}).then(async function (inlineHtml) {
|
||||
console.log("Inline :>> ", inlineHtml);
|
||||
const result = await updateTemplate({
|
||||
variables: {
|
||||
templateId: templateId,
|
||||
|
||||
@@ -14,8 +14,9 @@ export default function ShopTemplateEditorComponent({
|
||||
const emailEditorRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (json && Object.keys(json).length > 0)
|
||||
if (json && Object.keys(json).length > 0 && emailEditorRef.current) {
|
||||
emailEditorRef.current.loadDesign(json);
|
||||
}
|
||||
}, [json, emailEditorRef]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -46,6 +47,12 @@ export default function ShopTemplateEditorComponent({
|
||||
<EmailEditor
|
||||
ref={emailEditorRef}
|
||||
options={{
|
||||
// customCSS: [
|
||||
// window.location.protocol +
|
||||
// "//" +
|
||||
// window.location.host +
|
||||
// "/render-styles.css",
|
||||
// ],
|
||||
customJS: [
|
||||
window.location.protocol +
|
||||
"//" +
|
||||
|
||||
@@ -5,6 +5,8 @@ const path = require("path");
|
||||
const compression = require("compression");
|
||||
const twilio = require("twilio");
|
||||
global.fetch = require("node-fetch");
|
||||
var fb = require("./server/firebase/firebase-handler");
|
||||
|
||||
//var enforce = require("express-sslify");
|
||||
|
||||
require("dotenv").config({
|
||||
@@ -21,6 +23,7 @@ const app = express();
|
||||
const port = process.env.PORT || 5000;
|
||||
//const port = 5000;
|
||||
|
||||
app.use(fb.validateFirebaseIdToken);
|
||||
app.use(compression());
|
||||
app.use(bodyParser.json({ limit: "50mb" }));
|
||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
||||
@@ -77,7 +80,6 @@ app.post("/scheduling/job", scheduling.job);
|
||||
var renderHandlebars = require("./server/render/renderHandlebars");
|
||||
app.post("/render", renderHandlebars.render);
|
||||
|
||||
var fb = require("./server/firebase/firebase-handler");
|
||||
app.post("/notifications/send", fb.sendNotification);
|
||||
|
||||
//Stripe Processing
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var admin = require("firebase-admin");
|
||||
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
@@ -42,3 +43,49 @@ exports.sendNotification = (req, res) => {
|
||||
|
||||
res.sendStatus(200);
|
||||
};
|
||||
|
||||
exports.validateFirebaseIdToken = async (req, res, next) => {
|
||||
console.log("Check if request is authorized with Firebase ID token");
|
||||
|
||||
if (
|
||||
(!req.headers.authorization ||
|
||||
!req.headers.authorization.startsWith("Bearer ")) &&
|
||||
!(req.cookies && req.cookies.__session)
|
||||
) {
|
||||
console.error("Unauthorized attempt. No authorization provided.");
|
||||
res.status(403).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
let idToken;
|
||||
if (
|
||||
req.headers.authorization &&
|
||||
req.headers.authorization.startsWith("Bearer ")
|
||||
) {
|
||||
// console.log('Found "Authorization" header');
|
||||
// Read the ID Token from the Authorization header.
|
||||
idToken = req.headers.authorization.split("Bearer ")[1];
|
||||
} else if (req.cookies) {
|
||||
//console.log('Found "__session" cookie');
|
||||
// Read the ID Token from cookie.
|
||||
idToken = req.cookies.__session;
|
||||
} else {
|
||||
// No cookie
|
||||
console.error("Unauthorized attempt. No cookie provided.");
|
||||
|
||||
res.status(403).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
|
||||
//console.log("ID Token correctly decoded", decodedIdToken);
|
||||
req.user = decodedIdToken;
|
||||
next();
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("Error while verifying Firebase ID token:", error);
|
||||
res.status(403).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user