Email sending framework in place with nodemailer. Some rendering is happening dynamically, but i todesn't bring in most styles.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Input, Table, Button } from "antd";
|
import { Button, Input, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -123,8 +123,9 @@ export default function JobLinesComponent({
|
|||||||
{record.allocations && record.allocations.length > 0
|
{record.allocations && record.allocations.length > 0
|
||||||
? record.allocations.map(item => (
|
? record.allocations.map(item => (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={
|
||||||
>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
|
item.id
|
||||||
|
}>{`${item.employee.first_name} ${item.employee.last_name} (${item.hours})`}</div>
|
||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
<AllocationsAssignmentContainer
|
<AllocationsAssignmentContainer
|
||||||
@@ -184,8 +185,7 @@ export default function JobLinesComponent({
|
|||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
disabled={selectedLines.length > 0 ? false : true}
|
disabled={selectedLines.length > 0 ? false : true}
|
||||||
onClick={() => setPartsModalVisible(true)}
|
onClick={() => setPartsModalVisible(true)}>
|
||||||
>
|
|
||||||
{t("parts.actions.order")}
|
{t("parts.actions.order")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +193,7 @@ export default function JobLinesComponent({
|
|||||||
}}
|
}}
|
||||||
{...formItemLayout}
|
{...formItemLayout}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="small"
|
size='small'
|
||||||
pagination={{ position: "bottom", defaultPageSize: 50 }}
|
pagination={{ position: "bottom", defaultPageSize: 50 }}
|
||||||
rowSelection={{
|
rowSelection={{
|
||||||
// selectedRowKeys: selectedLines,
|
// selectedRowKeys: selectedLines,
|
||||||
@@ -201,7 +201,7 @@ export default function JobLinesComponent({
|
|||||||
setSelectedLines(selectedRows)
|
setSelectedLines(selectedRows)
|
||||||
}}
|
}}
|
||||||
columns={columns.map(item => ({ ...item }))}
|
columns={columns.map(item => ({ ...item }))}
|
||||||
rowKey="id"
|
rowKey='id'
|
||||||
dataSource={jobLines}
|
dataSource={jobLines}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -56,6 +56,10 @@ export default connect(
|
|||||||
data: linesToOrder.reduce((acc, value) => {
|
data: linesToOrder.reduce((acc, value) => {
|
||||||
acc.push({
|
acc.push({
|
||||||
line_desc: value.line_desc,
|
line_desc: value.line_desc,
|
||||||
|
oem_partno: value.oem_partno,
|
||||||
|
db_price: value.db_price,
|
||||||
|
act_price: value.act_price,
|
||||||
|
line_remarks: "Alalala",
|
||||||
joblineid: value.joblineid,
|
joblineid: value.joblineid,
|
||||||
status:
|
status:
|
||||||
bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
bodyshop.md_order_statuses.default_ordered || "Ordered*"
|
||||||
|
|||||||
@@ -9,3 +9,47 @@ export const INSERT_NEW_PARTS_ORDERS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const REPORT_QUERY_PARTS_ORDER_BY_PK = gql`
|
||||||
|
query REPORT_QUERY_PARTS_ORDER_BY_PK($id: uuid!) {
|
||||||
|
parts_orders_by_pk(id: $id) {
|
||||||
|
job {
|
||||||
|
id
|
||||||
|
vehicle {
|
||||||
|
id
|
||||||
|
v_model_desc
|
||||||
|
v_make_desc
|
||||||
|
v_model_yr
|
||||||
|
v_vin
|
||||||
|
}
|
||||||
|
ro_number
|
||||||
|
est_number
|
||||||
|
}
|
||||||
|
id
|
||||||
|
deliver_by
|
||||||
|
parts_order_lines {
|
||||||
|
id
|
||||||
|
db_price
|
||||||
|
act_price
|
||||||
|
line_desc
|
||||||
|
line_remarks
|
||||||
|
oem_partno
|
||||||
|
status
|
||||||
|
}
|
||||||
|
status
|
||||||
|
user_email
|
||||||
|
}
|
||||||
|
bodyshops(where: { associations: { active: { _eq: true } } }) {
|
||||||
|
id
|
||||||
|
address1
|
||||||
|
address2
|
||||||
|
city
|
||||||
|
email
|
||||||
|
federal_tax_id
|
||||||
|
state
|
||||||
|
shopname
|
||||||
|
zip_post
|
||||||
|
logo_img_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -1,9 +1,42 @@
|
|||||||
import React from 'react'
|
import { Button } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import React from "react";
|
||||||
|
import { useApolloClient } from "react-apollo";
|
||||||
|
import ReactDOMServer from "react-dom/server";
|
||||||
|
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../graphql/parts-orders.queries";
|
||||||
|
import ReportPartsOrder from "../../reports/pages/parts-order/parts-order.component";
|
||||||
|
|
||||||
export default function ManageRootPageComponent() {
|
export default function ManageRootPageComponent() {
|
||||||
return (
|
const client = useApolloClient();
|
||||||
<div>
|
return (
|
||||||
Temporary Home Page.
|
<div>
|
||||||
</div>
|
<Button
|
||||||
)
|
onClick={() => {
|
||||||
|
client
|
||||||
|
.query({
|
||||||
|
query: REPORT_QUERY_PARTS_ORDER_BY_PK,
|
||||||
|
variables: { id: "ebe0fb6b-6ec4-4ae0-8fdc-49bdf1e37ff3" },
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
axios.post("/sendemail", {
|
||||||
|
from: { name: "Kavia Autobody" },
|
||||||
|
to: "snaptsoft@gmail.com",
|
||||||
|
replyTo: "patrickwf@gmail.com",
|
||||||
|
subject: "Sending Email using Node.js",
|
||||||
|
text: "Plaintext version of the message",
|
||||||
|
html: ReactDOMServer.renderToStaticMarkup(
|
||||||
|
<ReportPartsOrder
|
||||||
|
order={response.data.parts_orders_by_pk}
|
||||||
|
bodyshop={response.data.bodyshops[0]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
Test Email
|
||||||
|
</Button>
|
||||||
|
<div>Testing Section-Report</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { ApolloProvider } from "react-apollo";
|
||||||
|
|
||||||
|
export default function ReportEmailWrapper({ client, children }) {
|
||||||
|
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Row, Col, Typography } from "antd";
|
||||||
|
export default function RcShopHeaderComponent({ bodyshop }) {
|
||||||
|
if (!bodyshop) return null;
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col span={8}>
|
||||||
|
{bodyshop.logo_img_path ? (
|
||||||
|
<img
|
||||||
|
alt='Shop Logo'
|
||||||
|
src={bodyshop.logo_img_path}
|
||||||
|
style={{ height: "80px" }}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</Col>
|
||||||
|
<Col span={16}>
|
||||||
|
<Row>
|
||||||
|
<Typography.Title>{`${bodyshop.shopname}`}</Typography.Title>
|
||||||
|
</Row>
|
||||||
|
<Row>{`${bodyshop.address1 || ""} ${bodyshop.address2 ||
|
||||||
|
""}, ${bodyshop.city || ""}, ${bodyshop.state ||
|
||||||
|
""} ${bodyshop.zip_post || ""}`}</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { Row, Col, Table } from "antd";
|
||||||
|
import CurrencyFormatter from "../../../utils/CurrencyFormatter";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export default function ReportPartsOrderComponent({ order, bodyshop }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.line_desc"),
|
||||||
|
dataIndex: "line_desc",
|
||||||
|
key: "line_desc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.oem_partno"),
|
||||||
|
dataIndex: "oem_partno",
|
||||||
|
key: "oem_partno",
|
||||||
|
render: (text, record) => (
|
||||||
|
<span>
|
||||||
|
{record.oem_partno ? record.oem_partno : record.op_code_desc}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.line_remarks"),
|
||||||
|
dataIndex: "line_remarks",
|
||||||
|
key: "line_remarks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.db_price"),
|
||||||
|
dataIndex: "db_price",
|
||||||
|
key: "db_price",
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.db_price}</CurrencyFormatter>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("joblines.fields.act_price"),
|
||||||
|
dataIndex: "act_price",
|
||||||
|
key: "act_price",
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Row>
|
||||||
|
<Col span={4}>
|
||||||
|
<div>{`PO Number: ${order.po_number || ""}`}</div>
|
||||||
|
<div>{`Delivery By: ${order.deliver_by || ""}`}</div>
|
||||||
|
<div>{`Ordered By: ${order.user_email || ""}`}</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<div>{`Vehicle Description: ${order.job.vehicle.v_model_yr ||
|
||||||
|
""} ${order.job.vehicle.v_make_desc || ""} ${order.job.vehicle
|
||||||
|
.v_model_desc || ""}`}</div>
|
||||||
|
<div>{`VIN: ${order.job.vehicle.v_vin || ""}`}</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Table
|
||||||
|
size='small'
|
||||||
|
columns={columns.map(item => ({ ...item }))}
|
||||||
|
pagination={false}
|
||||||
|
rowKey='id'
|
||||||
|
dataSource={order.parts_order_lines ? order.parts_order_lines : null}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useQuery } from "react-apollo";
|
||||||
|
import AlertComponent from "../../../components/alert/alert.component";
|
||||||
|
import LoadingSpinner from "../../../components/loading-spinner/loading-spinner.component";
|
||||||
|
import { REPORT_QUERY_PARTS_ORDER_BY_PK } from "../../../graphql/parts-orders.queries";
|
||||||
|
import RcShopHeaderComponent from "../../components/shop-header/shop-header.component";
|
||||||
|
import ReportPartsOrderComponent from "./parts-order.component";
|
||||||
|
|
||||||
|
export default function ReportPartsOrderContainer({ orderId }) {
|
||||||
|
const { loading, error, data } = useQuery(REPORT_QUERY_PARTS_ORDER_BY_PK, {
|
||||||
|
variables: { id: orderId },
|
||||||
|
fetchPolicy: "network-only",
|
||||||
|
});
|
||||||
|
if (loading) return <LoadingSpinner />;
|
||||||
|
if (error) return <AlertComponent message={error.message} type='error' />;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RcShopHeaderComponent bodyshop={data ? data.bodyshops[0] : null} />
|
||||||
|
<ReportPartsOrderComponent
|
||||||
|
order={data ? data.parts_orders_by_pk : null}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -11196,8 +11196,6 @@ rxjs@^6.5.3:
|
|||||||
version "6.5.4"
|
version "6.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c"
|
||||||
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
|
integrity sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==
|
||||||
dependencies:
|
|
||||||
tslib "^1.9.0"
|
|
||||||
|
|
||||||
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
"dotenv": "8.2.0",
|
"dotenv": "8.2.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-sslify": "^1.2.0",
|
"express-sslify": "^1.2.0",
|
||||||
"firebase-tools": "^7.12.1"
|
"firebase-tools": "^7.12.1",
|
||||||
|
"nodemailer": "^6.4.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^4.0.1",
|
"concurrently": "^4.0.1",
|
||||||
|
|||||||
45
sendemail.js
Normal file
45
sendemail.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
var nodemailer = require("nodemailer");
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
var transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.email_server,
|
||||||
|
port: 465,
|
||||||
|
secure: true, // upgrade later with STARTTLS
|
||||||
|
auth: {
|
||||||
|
user: process.env.email_api,
|
||||||
|
pass: process.env.email_secret
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// verify connection configuration
|
||||||
|
transporter.verify(function(error, success) {
|
||||||
|
if (error) {
|
||||||
|
console.log(error);
|
||||||
|
} else {
|
||||||
|
console.log("[EMAIL] Succesfully connected to SMTP server.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
exports.sendEmail = (req, res) => {
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
console.log("[EMAIL] Incoming Message Body", req.body);
|
||||||
|
}
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
...req.body,
|
||||||
|
from: {
|
||||||
|
name: req.body.from.name || "No Reply @ Bodyshop.app",
|
||||||
|
address: "noreply@bodyshop.app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(error, info) {
|
||||||
|
if (error) {
|
||||||
|
console.log("[EMAIL] Email send failed. ", error);
|
||||||
|
res.json({ success: false, error: error });
|
||||||
|
} else {
|
||||||
|
console.log("[EMAIL] Email sent: " + info.response);
|
||||||
|
res.json({ success: true, response: info.response });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -23,6 +23,8 @@ app.post("/sign_s3", s3upload.sign_s3);
|
|||||||
app.get("/sign_s3", s3upload.get_s3);
|
app.get("/sign_s3", s3upload.get_s3);
|
||||||
app.post("/delete_s3", s3upload.delete_s3);
|
app.post("/delete_s3", s3upload.delete_s3);
|
||||||
|
|
||||||
|
var sendEmail = require("./sendemail.js");
|
||||||
|
app.post("/sendemail", sendEmail.sendEmail);
|
||||||
// app.get("/test", function(req, res) {
|
// app.get("/test", function(req, res) {
|
||||||
// res.json({ success: true });
|
// res.json({ success: true });
|
||||||
// });
|
// });
|
||||||
|
|||||||
@@ -2872,6 +2872,11 @@ node-forge@^0.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
|
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5"
|
||||||
integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
|
integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ==
|
||||||
|
|
||||||
|
nodemailer@^6.4.2:
|
||||||
|
version "6.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.4.2.tgz#7147550e32cdc37453380ab78d2074533966090a"
|
||||||
|
integrity sha512-g0n4nH1ONGvqYo1v72uSWvF/MRNnnq1LzmSzXb/6EPF3LFb51akOhgG3K2+aETAsJx90/Q5eFNTntu4vBCwyQQ==
|
||||||
|
|
||||||
normalize-package-data@^2.3.2:
|
normalize-package-data@^2.3.2:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||||
|
|||||||
Reference in New Issue
Block a user