mail format updated

This commit is contained in:
Alaguraj0361 2025-10-11 21:37:39 +05:30
parent 2e3e78b632
commit 5eea071398
3 changed files with 84 additions and 70 deletions

View File

@ -1,39 +1,44 @@
import { CakeOrder } from "../../models/maisondetreats/cakeOrder.model.js"; import { CakeOrder } from "../../models/maisondetreats/cakeOrder.model.js";
import { sendCakeOrderMail } from "../../utils/mailer.js"; import { sendCakeOrderMail } from "../../utils/mailer.js";
// POST → Create new cake order
export const createCakeOrder = async (req, res) => { export const createCakeOrder = async (req, res) => {
try { try {
const { order, email } = req.body; const { order, email, totalPieces, totalPrice } = req.body;
if (!order || typeof order !== "object") { if (!order || typeof order !== "object") {
return res.status(400).json({ message: "Order data is required" }); return res.status(400).json({ message: "Order data is required" });
}
const newOrder = await CakeOrder.create({ order, email });
// ✅ send confirmation email (non-blocking)
if (email) {
sendCakeOrderMail(email, order)
.then(() => console.log("Cake order email sent to", email))
.catch((err) => console.error("Email send failed:", err));
}
res.status(201).json({
message: "Cake order created successfully",
data: newOrder,
});
} catch (err) {
console.error("Error creating cake order:", err);
res.status(500).json({ message: "Server error", error: err.message });
} }
if (!totalPieces || !totalPrice) {
return res.status(400).json({ message: "Total pieces and price are required" });
}
const newOrder = await CakeOrder.create({ order, email, totalPieces, totalPrice });
if (email) {
sendCakeOrderMail(email, order, totalPieces, totalPrice)
.then(() => console.log("Cake order email sent to", email))
.catch((err) => console.error("Email send failed:", err));
}
res.status(201).json({
message: "Cake order created successfully",
data: newOrder,
});
} catch (err) {
console.error("Error creating cake order:", err);
res.status(500).json({ message: "Server error", error: err.message });
}
}; };
// GET /api/cake-orders → List all orders
// GET → Fetch all cake orders
export const getAllCakeOrders = async (_req, res) => { export const getAllCakeOrders = async (_req, res) => {
try { try {
const orders = await CakeOrder.find().sort({ createdAt: -1 }); const orders = await CakeOrder.find().sort({ createdAt: -1 });
res.json({ data: orders }); res.json({ data: orders });
} catch (err) { } catch (err) {
console.error("Error fetching cake orders:", err); console.error("Error fetching cake orders:", err);
res.status(500).json({ message: "Server error", error: err.message }); res.status(500).json({ message: "Server error", error: err.message });
} }
}; };

View File

@ -1,16 +1,24 @@
import mongoose from "mongoose"; import mongoose from "mongoose";
// Each flavour item schema
const FlavourSchema = new mongoose.Schema({
flavour: { type: String, required: true },
pieces: { type: Number, required: true },
unitPrice: { type: Number, required: true },
totalPrice: { type: Number, required: true },
});
// Main Cake Order schema
const CakeOrderSchema = new mongoose.Schema( const CakeOrderSchema = new mongoose.Schema(
{ {
email: { type: String, required: true },
order: { order: {
type: Object, type: Map,
of: [FlavourSchema], // Each category has an array of flavour objects
required: true, required: true,
// Example format:
// {
// "Mini Cakes": { "Thandai Cake": 1, "Mango Cardamom": 1 },
// "Mithai-Inspired Macarons": { "Mango macarons (pack of 6)": 1, "Pista (pack of 6)": 10 }
// }
}, },
totalPieces: { type: Number, required: true },
totalPrice: { type: Number, required: true },
}, },
{ timestamps: true } { timestamps: true }
); );

View File

@ -62,8 +62,9 @@ export async function sendResetPasswordMail(email, token) {
} }
} }
// Send cake order email
export const sendCakeOrderMail = async (toEmail, orderData) => {
export const sendCakeOrderMail = async (email, order, totalPieces, totalPrice) => {
try { try {
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
host: "mail.metatron-admin-backend.metatronhost.com", host: "mail.metatron-admin-backend.metatronhost.com",
@ -78,35 +79,37 @@ export const sendCakeOrderMail = async (toEmail, orderData) => {
// Build table rows // Build table rows
let orderRows = ""; let orderRows = "";
Object.entries(orderData).forEach(([category, flavours]) => { Object.entries(order).forEach(([category, items]) => {
Object.entries(flavours).forEach(([flavour, qty]) => { items.forEach(({ flavour, pieces, unitPrice, totalPrice: lineTotal }) => {
orderRows += ` orderRows += `
<tr> <tr>
<td style="padding:10px 12px;">${category}</td> <td style="padding:10px 12px;">${category}</td>
<td style="padding:10px 12px;">${flavour}</td> <td style="padding:10px 12px;">${flavour}</td>
<td style="padding:10px 12px;">${qty}</td> <td style="padding:10px 12px;">${pieces}</td>
<td style="padding:10px 12px;">$${unitPrice.toFixed(2)}</td>
<td style="padding:10px 12px;">$${lineTotal.toFixed(2)}</td>
</tr> </tr>
`; `;
}); });
}); });
// HTML content with local images via cid
const htmlContent = ` const htmlContent = `
<div style="max-width:750px;margin:30px auto;background-color:#fff;border-radius:20px;overflow:hidden;font-family:'Segoe UI',Tahoma,sans-serif;color:#333;"> <div style="max-width:750px;margin:30px auto;background-color:#fff;border-radius:20px;overflow:hidden;font-family:'Segoe UI',Tahoma,sans-serif;color:#333;">
<table width="100%" cellpadding="0" cellspacing="0" style="background-color:#faf4f0;padding:15px 25px;"> <table width="100%" cellpadding="0" cellspacing="0" style="background-color:#faf4f0;padding:15px 25px;">
<tr> <tr>
<td align="left"> <td align="left">
<img src="cid:logo" width="150" style="display:block;" /> <img src="cid:logo" width="150" style="display:block;" />
</td> </td>
<td align="right" style="font-size:14px;color:#d72631;font-weight:600;"> <td align="right" style="font-size:14px;color:#d72631;font-weight:600;">
Order Date: ${new Date().toLocaleString()} Order Date: ${new Date().toLocaleString()}
</td> </td>
</tr> </tr>
</table> </table>
<div style="padding:15px 25px;text-align:center;">
<div style="text-align:center;padding:30px 0;"> <div style="text-align:center;padding:30px 0;">
<img src="cid:banner" style="width:100%;max-height:300px;object-fit:cover;border-radius:0 0 20px 20px;"/> <img src="cid:banner" style="width:100%;max-height:300px;object-fit:cover;border-radius:0 0 20px 20px;"/>
</div> </div>
<div style="padding:15px 25px;text-align:center;">
<h2 style="color:rgb(255 135 174);font-family:'Brush Script MT',cursive;font-size:22px;margin-bottom:12px;font-weight:700;">Order Details</h2> <h2 style="color:rgb(255 135 174);font-family:'Brush Script MT',cursive;font-size:22px;margin-bottom:12px;font-weight:700;">Order Details</h2>
<table style="width:100%;border-collapse:collapse;font-size:14px;margin:0 auto 15px;"> <table style="width:100%;border-collapse:collapse;font-size:14px;margin:0 auto 15px;">
<thead> <thead>
@ -114,14 +117,24 @@ export const sendCakeOrderMail = async (toEmail, orderData) => {
<th style="padding:10px 12px;text-align:left;">Treats</th> <th style="padding:10px 12px;text-align:left;">Treats</th>
<th style="padding:10px 12px;text-align:left;">Flavour</th> <th style="padding:10px 12px;text-align:left;">Flavour</th>
<th style="padding:10px 12px;text-align:left;">Quantity</th> <th style="padding:10px 12px;text-align:left;">Quantity</th>
<th style="padding:10px 12px;">Unit Price</th>
<th style="padding:10px 12px;">Total Price</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${orderRows} ${orderRows}
<tr style="font-weight:bold;background-color:#f8f8f8;">
<td colspan="2" style="text-align:right;padding:10px 12px;">Total Pieces</td>
<td style="padding:10px 12px;">${totalPieces}</td>
<td style="text-align:right;padding:10px 12px;">Total Price ($)</td>
<td style="padding:10px 12px;">$${totalPrice.toFixed(2)}</td>
</tr>
</tbody> </tbody>
</table> </table>
<a href="https://maisondetreats.com/" style="display:inline-block;background-color:#d72631;color:#fff;padding:10px 25px;border-radius:30px;text-decoration:none;font-weight:bold;margin-top:15px;">Visit Our Website</a> <a href="https://maisondetreats.com/" style="display:inline-block;background-color:#d72631;color:#fff;padding:10px 25px;border-radius:30px;text-decoration:none;font-weight:bold;margin-top:15px;">Visit Our Website</a>
</div> </div>
<div style="text-align:center;padding:30px 0;background-color:#d72631;color:#fff;"> <div style="text-align:center;padding:30px 0;background-color:#d72631;color:#fff;">
<img src="cid:footer" style="width:100%;max-height:100px;object-fit:cover;"/> <img src="cid:footer" style="width:100%;max-height:100px;object-fit:cover;"/>
</div> </div>
@ -130,30 +143,18 @@ export const sendCakeOrderMail = async (toEmail, orderData) => {
const mailOptions = { const mailOptions = {
from: '"Maison de Treats" <info@metatron-admin-backend.metatronhost.com>', from: '"Maison de Treats" <info@metatron-admin-backend.metatronhost.com>',
to: toEmail, to: email,
subject: "🎉 New Cake Order from Website Form", subject: "🎉 New Cake Order from Website Form",
html: htmlContent, html: htmlContent,
attachments: [ attachments: [
{ { filename: "logo-2.webp", path: "./public/maisondetreats/img/logo-2.webp", cid: "logo" },
filename: "logo-2.webp", { filename: "thank-you.png", path: "./public/maisondetreats/img/thank-you.png", cid: "banner" },
path: "./public/maisondetreats/img/logo-2.webp", { filename: "bottom.png", path: "./public/maisondetreats/img/bottom.png", cid: "footer" },
cid: "logo",
},
{
filename: "thank-you.png",
path: "./public/maisondetreats/img/thank-you.png",
cid: "banner",
},
{
filename: "bottom.png",
path: "./public/maisondetreats/img/bottom.png",
cid: "footer",
},
], ],
}; };
await transporter.sendMail(mailOptions); await transporter.sendMail(mailOptions);
console.log("✅ Cake order email sent to", toEmail); console.log("✅ Cake order email sent to", email);
} catch (err) { } catch (err) {
console.error("❌ Failed to send cake order email:", err); console.error("❌ Failed to send cake order email:", err);
} }