Refactor CookieBanner and Contact API for improved functionality and security
- Removed localStorage fallback from CookieBanner, simplifying consent management. - Refactored manual review email handling in the Contact API to utilize HTML templates for better structure and security. - Enhanced email content generation by escaping HTML special characters and using template files for dynamic data insertion.
This commit is contained in:
@@ -69,19 +69,6 @@ const t = getTranslation(lang);
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check localStorage as a fallback
|
|
||||||
try {
|
|
||||||
if (localStorage && localStorage.getItem('cookieConsentAccepted') === 'true') {
|
|
||||||
cookieBanner.style.display = 'none';
|
|
||||||
// Also set the cookie for future visits
|
|
||||||
setCookie('cookieConsentAccepted', 'true', 365);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error accessing localStorage:', e);
|
|
||||||
// Continue checking cookies
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the banner
|
// Show the banner
|
||||||
cookieBanner.style.display = 'block';
|
cookieBanner.style.display = 'block';
|
||||||
|
|
||||||
@@ -93,17 +80,9 @@ const t = getTranslation(lang);
|
|||||||
|
|
||||||
// Handle accept button click
|
// Handle accept button click
|
||||||
acceptButton.addEventListener('click', () => {
|
acceptButton.addEventListener('click', () => {
|
||||||
// Store consent in cookie (primary storage)
|
// Store consent in cookie
|
||||||
setCookie('cookieConsentAccepted', 'true', 365);
|
setCookie('cookieConsentAccepted', 'true', 365);
|
||||||
|
|
||||||
// Also store in localStorage as backup
|
|
||||||
try {
|
|
||||||
localStorage.setItem('cookieConsentAccepted', 'true');
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error setting localStorage:', e);
|
|
||||||
// Continue with cookie storage
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hide the banner with animation
|
// Hide the banner with animation
|
||||||
cookieBanner.classList.add('translate-y-full');
|
cookieBanner.classList.add('translate-y-full');
|
||||||
|
|
||||||
|
@@ -311,22 +311,4 @@ export const POST: APIRoute = async ({ request, clientAddress }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const manualReviewPOST: APIRoute = async ({ request }) => {
|
|
||||||
const { token, email: submittedEmail, justification } = await request.json();
|
|
||||||
try {
|
|
||||||
const payload = jwt.verify(token, MANUAL_REVIEW_SECRET) as { email: string, message: string };
|
|
||||||
if (payload.email !== submittedEmail) {
|
|
||||||
return new Response(JSON.stringify({ error: 'Email does not match original submission.' }), { status: 403 });
|
|
||||||
}
|
|
||||||
// Send to manual review mailbox
|
|
||||||
await sendEmail(
|
|
||||||
MANUAL_REVIEW_EMAIL,
|
|
||||||
'Manual Review Requested: Contact Form Submission',
|
|
||||||
`<p><strong>Email:</strong> ${submittedEmail}</p><p><strong>Message:</strong> ${payload.message}</p><p><strong>Justification:</strong> ${justification || 'None provided'}</p>`,
|
|
||||||
`Email: ${submittedEmail}\nMessage: ${payload.message}\nJustification: ${justification || 'None provided'}`
|
|
||||||
);
|
|
||||||
return new Response(JSON.stringify({ success: true }));
|
|
||||||
} catch (_err) {
|
|
||||||
return new Response(JSON.stringify({ error: 'Invalid or expired token.' }), { status: 400 });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
@@ -1,18 +1,19 @@
|
|||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from 'astro';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { sendEmail } from '../../../utils/email-handler';
|
import { sendEmail, escapeHtml } from '../../../utils/email-handler';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
const MANUAL_REVIEW_SECRET = process.env.MANUAL_REVIEW_SECRET;
|
const MANUAL_REVIEW_SECRET = process.env.MANUAL_REVIEW_SECRET;
|
||||||
const MANUAL_REVIEW_EMAIL = 'manual-review@365devnet.eu';
|
const MANUAL_REVIEW_EMAIL = 'manual-review@365devnet.eu';
|
||||||
|
|
||||||
// Utility to escape HTML special characters
|
async function getTemplate(templateName: string, data: Record<string, string>): Promise<string> {
|
||||||
function escapeHtml(str: string): string {
|
const templatePath = path.join(process.cwd(), 'src', 'templates', 'email', `${templateName}.html`);
|
||||||
return str
|
let template = await fs.readFile(templatePath, 'utf-8');
|
||||||
.replace(/&/g, '&')
|
for (const key in data) {
|
||||||
.replace(/</g, '<')
|
template = template.replace(new RegExp(`{{${key}}}`, 'g'), data[key]);
|
||||||
.replace(/>/g, '>')
|
}
|
||||||
.replace(/"/g, '"')
|
return template;
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const POST: APIRoute = async ({ request }) => {
|
export const POST: APIRoute = async ({ request }) => {
|
||||||
@@ -23,10 +24,15 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
return new Response(JSON.stringify({ error: 'Email does not match original submission.' }), { status: 403 });
|
return new Response(JSON.stringify({ error: 'Email does not match original submission.' }), { status: 403 });
|
||||||
}
|
}
|
||||||
// Send to manual review mailbox
|
// Send to manual review mailbox
|
||||||
|
const html = await getTemplate('manual-review', {
|
||||||
|
email: escapeHtml(submittedEmail),
|
||||||
|
message: escapeHtml(payload.message),
|
||||||
|
justification: escapeHtml(justification || 'None provided'),
|
||||||
|
});
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
MANUAL_REVIEW_EMAIL,
|
MANUAL_REVIEW_EMAIL,
|
||||||
'Manual Review Requested: Contact Form Submission',
|
'Manual Review Requested: Contact Form Submission',
|
||||||
`<p><strong>Email:</strong> ${escapeHtml(submittedEmail)}</p><p><strong>Message:</strong> ${escapeHtml(payload.message)}</p><p><strong>Justification:</strong> ${escapeHtml(justification || 'None provided')}</p>`,
|
html,
|
||||||
`Email: ${submittedEmail}\nMessage: ${payload.message}\nJustification: ${justification || 'None provided'}`
|
`Email: ${submittedEmail}\nMessage: ${payload.message}\nJustification: ${justification || 'None provided'}`
|
||||||
);
|
);
|
||||||
return new Response(JSON.stringify({ success: true }));
|
return new Response(JSON.stringify({ success: true }));
|
||||||
@@ -35,3 +41,5 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
95
src/templates/email/admin-notification.html
Normal file
95
src/templates/email/admin-notification.html
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>New Contact Form Submission</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
.field-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #4b5563;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.field-value {
|
||||||
|
background-color: white;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
.message-content {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
background-color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
.meta-info {
|
||||||
|
font-size: 0.85em;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>New Contact Form Submission</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="field">
|
||||||
|
<div class="field-label">Name</div>
|
||||||
|
<div class="field-value">{{name}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="field-label">Email</div>
|
||||||
|
<div class="field-value">{{email}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="field-label">Message</div>
|
||||||
|
<div class="message-content">{{message}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="meta-info">
|
||||||
|
<div><strong>IP Address:</strong> {{ipAddress}}</div>
|
||||||
|
<div><strong>User Agent:</strong> {{userAgent}}</div>
|
||||||
|
<div><strong>Time:</strong> {{time}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<p>This message was sent from the contact form on {{websiteName}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
1
src/templates/email/manual-review.html
Normal file
1
src/templates/email/manual-review.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<p><strong>Email:</strong> {{email}}</p><p><strong>Message:</strong> {{message}}</p><p><strong>Justification:</strong> {{justification}}</p>
|
81
src/templates/email/user-confirmation.html
Normal file
81
src/templates/email/user-confirmation.html
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Thank you for your message</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-top: none;
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
background-color: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
background-color: #2563eb;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.button:hover {
|
||||||
|
background-color: #1d4ed8;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>Thank you for your message</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Dear {{name}},</p>
|
||||||
|
<p>Thank you for contacting {{websiteName}}. We have received your message and will get back to you as soon as possible.</p>
|
||||||
|
|
||||||
|
<div class="message">
|
||||||
|
<h3>Your Message:</h3>
|
||||||
|
<p>{{message}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>If you have any additional information to share, please don't hesitate to reply to this email.</p>
|
||||||
|
|
||||||
|
<a href="https://www.365devnet.eu" class="button">Visit Our Website</a>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Best regards,<br>{{websiteName}} Team</p>
|
||||||
|
<p><small>This is an automated message, please do not reply directly to this email.</small></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -2,6 +2,8 @@ import nodemailer from 'nodemailer';
|
|||||||
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
import { RateLimiterMemory } from 'rate-limiter-flexible';
|
||||||
import { createHash } from 'crypto';
|
import { createHash } from 'crypto';
|
||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
// Environment variables
|
// Environment variables
|
||||||
const {
|
const {
|
||||||
@@ -14,8 +16,7 @@ const {
|
|||||||
} = process.env;
|
} = process.env;
|
||||||
|
|
||||||
// Email configuration
|
// Email configuration
|
||||||
// Force production mode for testing
|
const isProduction = process.env.NODE_ENV === 'production';
|
||||||
const isProduction = true; // NODE_ENV === 'production';
|
|
||||||
|
|
||||||
// Create a transporter for sending emails
|
// Create a transporter for sending emails
|
||||||
let transporter: nodemailer.Transporter;
|
let transporter: nodemailer.Transporter;
|
||||||
@@ -172,7 +173,7 @@ export async function sendEmail(to: string, subject: string, html: string, text:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Utility to escape HTML special characters
|
// Utility to escape HTML special characters
|
||||||
function escapeHtml(str: string): string {
|
export function escapeHtml(str: string): string {
|
||||||
return str
|
return str
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
@@ -181,6 +182,15 @@ function escapeHtml(str: string): string {
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getTemplate(templateName: string, data: Record<string, string>): Promise<string> {
|
||||||
|
const templatePath = path.join(process.cwd(), 'src', 'templates', 'email', `${templateName}.html`);
|
||||||
|
let template = await fs.readFile(templatePath, 'utf-8');
|
||||||
|
for (const key in data) {
|
||||||
|
template = template.replace(new RegExp(`{{${key}}}`, 'g'), data[key]);
|
||||||
|
}
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
// Send admin notification email
|
// Send admin notification email
|
||||||
export async function sendAdminNotification(
|
export async function sendAdminNotification(
|
||||||
name: string,
|
name: string,
|
||||||
@@ -206,103 +216,15 @@ export async function sendAdminNotification(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subject = `New Contact Form Submission from ${escapeHtml(name)}`;
|
const subject = `New Contact Form Submission from ${escapeHtml(name)}`;
|
||||||
const html = `
|
const html = await getTemplate('admin-notification', {
|
||||||
<!DOCTYPE html>
|
name: escapeHtml(name),
|
||||||
<html>
|
email: escapeHtml(email),
|
||||||
<head>
|
message: escapeHtml(message).replace(/\n/g, '<br>'),
|
||||||
<meta charset="utf-8">
|
ipAddress: escapeHtml(ipAddress || ''),
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
userAgent: escapeHtml(userAgent || ''),
|
||||||
<title>New Contact Form Submission</title>
|
time: new Date().toLocaleString(),
|
||||||
<style>
|
websiteName: WEBSITE_NAME,
|
||||||
body {
|
});
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-color: #2563eb;
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-top: none;
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
}
|
|
||||||
.field {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.field-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: #4b5563;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
.field-value {
|
|
||||||
background-color: white;
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
.message-content {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
background-color: white;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid #e2e8f0;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
.meta-info {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: #6b7280;
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 10px;
|
|
||||||
border-top: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header">
|
|
||||||
<h1>New Contact Form Submission</h1>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<div class="field">
|
|
||||||
<div class="field-label">Name</div>
|
|
||||||
<div class="field-value">${escapeHtml(name)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<div class="field-label">Email</div>
|
|
||||||
<div class="field-value">${escapeHtml(email)}</div>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<div class="field-label">Message</div>
|
|
||||||
<div class="message-content">${escapeHtml(message).replace(/\n/g, '<br>')}</div>
|
|
||||||
</div>
|
|
||||||
<div class="meta-info">
|
|
||||||
${ipAddress ? `<div><strong>IP Address:</strong> ${escapeHtml(ipAddress)}</div>` : ''}
|
|
||||||
${userAgent ? `<div><strong>User Agent:</strong> ${escapeHtml(userAgent)}</div>` : ''}
|
|
||||||
<div><strong>Time:</strong> ${new Date().toLocaleString()}</div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
<p>This message was sent from the contact form on ${WEBSITE_NAME}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
const text = `
|
const text = `
|
||||||
New Contact Form Submission
|
New Contact Form Submission
|
||||||
|
|
||||||
@@ -334,89 +256,11 @@ export async function sendUserConfirmation(name: string, email: string, message:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const subject = `Thank you for contacting ${WEBSITE_NAME}`;
|
const subject = `Thank you for contacting ${WEBSITE_NAME}`;
|
||||||
const html = `
|
const html = await getTemplate('user-confirmation', {
|
||||||
<!DOCTYPE html>
|
name: escapeHtml(name),
|
||||||
<html>
|
message: escapeHtml(message).replace(/\n/g, '<br>'),
|
||||||
<head>
|
websiteName: WEBSITE_NAME,
|
||||||
<meta charset="utf-8">
|
});
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Thank you for your message</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: #333;
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.header {
|
|
||||||
background-color: #2563eb;
|
|
||||||
color: white;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
.content {
|
|
||||||
background-color: #f8fafc;
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-top: none;
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
}
|
|
||||||
.message {
|
|
||||||
background-color: white;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
.footer {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding-top: 20px;
|
|
||||||
border-top: 1px solid #e2e8f0;
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
|
||||||
.button {
|
|
||||||
display: inline-block;
|
|
||||||
background-color: #2563eb;
|
|
||||||
color: white;
|
|
||||||
padding: 12px 24px;
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
.button:hover {
|
|
||||||
background-color: #1d4ed8;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header">
|
|
||||||
<h1>Thank you for your message</h1>
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<p>Dear ${escapeHtml(name)},</p>
|
|
||||||
<p>Thank you for contacting ${WEBSITE_NAME}. We have received your message and will get back to you as soon as possible.</p>
|
|
||||||
|
|
||||||
<div class="message">
|
|
||||||
<h3>Your Message:</h3>
|
|
||||||
<p>${escapeHtml(message).replace(/\n/g, '<br>')}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>If you have any additional information to share, please don't hesitate to reply to this email.</p>
|
|
||||||
|
|
||||||
<a href="https://www.365devnet.eu" class="button">Visit Our Website</a>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>Best regards,<br>${WEBSITE_NAME} Team</p>
|
|
||||||
<p><small>This is an automated message, please do not reply directly to this email.</small></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
const text = `
|
const text = `
|
||||||
Thank you for your message
|
Thank you for your message
|
||||||
|
|
||||||
@@ -458,3 +302,4 @@ export async function testEmailConfiguration(): Promise<boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue
Block a user