rental request email to owner

This commit is contained in:
jackiettran
2025-10-15 15:19:23 -04:00
parent b9e6cfc54d
commit 407c69aa22
9 changed files with 658 additions and 56 deletions

View File

@@ -41,6 +41,7 @@ class EmailService {
"lateReturnCS.html",
"damageReportCS.html",
"lostItemCS.html",
"rentalRequest.html",
];
for (const templateFile of templateFiles) {
@@ -65,38 +66,40 @@ class EmailService {
* Strips HTML tags and formats content for plain text email clients
*/
htmlToPlainText(html) {
return html
// Remove style and script tags and their content
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
// Convert common HTML elements to text equivalents
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<\/p>/gi, '\n\n')
.replace(/<\/div>/gi, '\n')
.replace(/<\/li>/gi, '\n')
.replace(/<\/h[1-6]>/gi, '\n\n')
.replace(/<li>/gi, '• ')
// Remove remaining HTML tags
.replace(/<[^>]+>/g, '')
// Decode HTML entities
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
// Remove emojis and special characters that don't render well in plain text
.replace(/[\u{1F600}-\u{1F64F}]/gu, '') // Emoticons
.replace(/[\u{1F300}-\u{1F5FF}]/gu, '') // Misc Symbols and Pictographs
.replace(/[\u{1F680}-\u{1F6FF}]/gu, '') // Transport and Map
.replace(/[\u{2600}-\u{26FF}]/gu, '') // Misc symbols
.replace(/[\u{2700}-\u{27BF}]/gu, '') // Dingbats
.replace(/[\u{FE00}-\u{FE0F}]/gu, '') // Variation Selectors
.replace(/[\u{1F900}-\u{1F9FF}]/gu, '') // Supplemental Symbols and Pictographs
.replace(/[\u{1FA70}-\u{1FAFF}]/gu, '') // Symbols and Pictographs Extended-A
// Clean up excessive whitespace
.replace(/\n\s*\n\s*\n/g, '\n\n')
.trim();
return (
html
// Remove style and script tags and their content
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
// Convert common HTML elements to text equivalents
.replace(/<br\s*\/?>/gi, "\n")
.replace(/<\/p>/gi, "\n\n")
.replace(/<\/div>/gi, "\n")
.replace(/<\/li>/gi, "\n")
.replace(/<\/h[1-6]>/gi, "\n\n")
.replace(/<li>/gi, "• ")
// Remove remaining HTML tags
.replace(/<[^>]+>/g, "")
// Decode HTML entities
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
// Remove emojis and special characters that don't render well in plain text
.replace(/[\u{1F600}-\u{1F64F}]/gu, "") // Emoticons
.replace(/[\u{1F300}-\u{1F5FF}]/gu, "") // Misc Symbols and Pictographs
.replace(/[\u{1F680}-\u{1F6FF}]/gu, "") // Transport and Map
.replace(/[\u{2600}-\u{26FF}]/gu, "") // Misc symbols
.replace(/[\u{2700}-\u{27BF}]/gu, "") // Dingbats
.replace(/[\u{FE00}-\u{FE0F}]/gu, "") // Variation Selectors
.replace(/[\u{1F900}-\u{1F9FF}]/gu, "") // Supplemental Symbols and Pictographs
.replace(/[\u{1FA70}-\u{1FAFF}]/gu, "") // Symbols and Pictographs Extended-A
// Clean up excessive whitespace
.replace(/\n\s*\n\s*\n/g, "\n\n")
.trim()
);
}
async sendEmail(to, subject, htmlContent, textContent = null) {
@@ -271,6 +274,22 @@ class EmailService {
<p><strong>Didn't change your password?</strong> If you did not make this change, please contact our support team immediately.</p>
`
),
rentalRequest: baseTemplate.replace(
"{{content}}",
`
<p>Hi {{ownerName}},</p>
<h2>New Rental Request for {{itemName}}</h2>
<p>{{renterName}} would like to rent your item.</p>
<p><strong>Rental Period:</strong> {{startDate}} to {{endDate}}</p>
<p><strong>Total Amount:</strong> \${{totalAmount}}</p>
<p><strong>Your Earnings:</strong> \${{payoutAmount}}</p>
<p><strong>Delivery Method:</strong> {{deliveryMethod}}</p>
<p><strong>Renter Notes:</strong> {{rentalNotes}}</p>
<p><a href="{{approveUrl}}" class="button">Review & Respond</a></p>
<p>Please respond to this request within 24 hours.</p>
`
),
};
return (
@@ -307,7 +326,12 @@ class EmailService {
);
}
async sendRentalConfirmation(userEmail, notification, rental, recipientName = null) {
async sendRentalConfirmation(
userEmail,
notification,
rental,
recipientName = null
) {
const itemName = rental?.item?.name || "Unknown Item";
const variables = {
@@ -328,11 +352,7 @@ class EmailService {
// Use clear, transactional subject line with item name
const subject = `Rental Confirmation - ${itemName}`;
return await this.sendEmail(
userEmail,
subject,
htmlContent
);
return await this.sendEmail(userEmail, subject, htmlContent);
}
async sendVerificationEmail(user, verificationToken) {
@@ -392,6 +412,62 @@ class EmailService {
);
}
async sendRentalRequestEmail(rental) {
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
const approveUrl = `${frontendUrl}/my-listings?rentalId=${rental.id}`;
// Fetch owner details
const owner = await User.findByPk(rental.ownerId, {
attributes: ["email", "firstName", "lastName"],
});
// Fetch renter details
const renter = await User.findByPk(rental.renterId, {
attributes: ["firstName", "lastName"],
});
if (!owner || !renter) {
console.error(
"Owner or renter not found for rental request notification"
);
return { success: false, error: "User not found" };
}
const variables = {
ownerName: owner.firstName,
renterName: `${renter.firstName} ${renter.lastName}`.trim() || "A renter",
itemName: rental.item?.name || "your item",
startDate: rental.startDateTime
? new Date(rental.startDateTime).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
})
: "Not specified",
endDate: rental.endDateTime
? new Date(rental.endDateTime).toLocaleString("en-US", {
dateStyle: "medium",
timeStyle: "short",
})
: "Not specified",
totalAmount: rental.totalAmount
? parseFloat(rental.totalAmount).toFixed(2)
: "0.00",
payoutAmount: rental.payoutAmount
? parseFloat(rental.payoutAmount).toFixed(2)
: "0.00",
deliveryMethod: rental.deliveryMethod || "Not specified",
approveUrl: approveUrl,
};
const htmlContent = this.renderTemplate("rentalRequest", variables);
return await this.sendEmail(
owner.email,
`Rental Request for ${rental.item?.name || "Your Item"}`,
htmlContent
);
}
async sendTemplateEmail(toEmail, subject, templateName, variables = {}) {
const htmlContent = this.renderTemplate(templateName, variables);
return await this.sendEmail(toEmail, subject, htmlContent);
@@ -461,9 +537,9 @@ class EmailService {
return;
}
// Calculate total fees
const damageFee = damageAssessment.feeCalculation.amount;
const lateFee = lateCalculation?.lateFee || 0;
// Calculate total fees (ensure numeric values)
const damageFee = parseFloat(damageAssessment.feeCalculation.amount) || 0;
const lateFee = parseFloat(lateCalculation?.lateFee || 0);
const totalFees = damageFee + lateFee;
// Determine fee type description