saifisvibin's picture
Add customizable sender name for emails - shows in recipient inbox instead of default
493ea13
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { motion } from 'framer-motion';
import { FaPaperPlane, FaDownload, FaChevronLeft, FaEnvelope, FaKey } from 'react-icons/fa';
const ReviewSendStep = ({ fileData, designParams, mappings, onBack }) => {
const [eventName, setEventName] = useState("");
const [eventDate, setEventDate] = useState("");
const [clientCompany, setClientCompany] = useState("");
const [subject, setSubject] = useState("Your Attendance Certificate - {event_name}_{event_date}");
const [body, setBody] = useState("Dear Dr {first_name},\n\nThank you for attending {event_name}.\n\nYour certificate is attached.\n\nBest Regards,\n\nVolaris Team on behalf of {client_company}");
const [fromEmail, setFromEmail] = useState("");
const [senderName, setSenderName] = useState("Volaris Team");
const [sending, setSending] = useState(false);
const [status, setStatus] = useState(null);
const [progress, setProgress] = useState(null);
const [jobId, setJobId] = useState(null);
const [failedEmails, setFailedEmails] = useState([]); // Store failed email details
// Fetch email configuration on mount
useEffect(() => {
const fetchEmailConfig = async () => {
try {
const response = await axios.get("/api/email/config");
setFromEmail(response.data.from_email);
} catch (error) {
console.error("Failed to fetch email config:", error);
}
};
fetchEmailConfig();
}, []);
const handleSend = async () => {
if (!eventName || !eventDate || !clientCompany) {
alert("Please fill in all required fields: Event Name, Event Date, and Client Company");
return;
}
setSending(true);
const formData = new FormData();
formData.append("subject", subject);
formData.append("body", body);
formData.append("name_column", mappings.name_column);
formData.append("email_column", mappings.email_column);
formData.append("event_name", eventName);
formData.append("event_date", eventDate);
formData.append("client_company", clientCompany);
formData.append("sender_name", senderName);
formData.append("name_color", designParams.name_color);
formData.append("font_size", designParams.font_size);
formData.append("fontname", designParams.fontname);
if (designParams.x !== null) formData.append("x", designParams.x);
if (designParams.y !== null) formData.append("y", designParams.y);
try {
const response = await axios.post("/api/email/send", formData);
const newJobId = response.data.job_id;
setJobId(newJobId);
setProgress({ sent: 0, total: fileData.total_rows, failed: 0 });
// Start polling for progress
const pollInterval = setInterval(async () => {
try {
const progressResponse = await axios.get(`/api/email/progress/${newJobId}`);
const progressData = progressResponse.data;
setProgress(progressData);
// Stop polling when all emails are sent
if (progressData.sent >= progressData.total) {
clearInterval(pollInterval);
setSending(false);
// Store failed emails for display
if (progressData.failed_emails && progressData.failed_emails.length > 0) {
setFailedEmails(progressData.failed_emails);
}
const successCount = progressData.sent - progressData.failed;
setStatus({
type: progressData.failed > 0 ? 'warning' : 'success',
msg: `Successfully sent ${successCount} emails!${progressData.failed > 0 ? ` (${progressData.failed} failed)` : ''}`
});
setProgress(null);
}
} catch (pollError) {
console.error("Progress polling error:", pollError);
}
}, 500); // Poll every 500ms
} catch (error) {
setStatus({ type: 'error', msg: "Failed to start sending process." });
console.error(error);
setSending(false);
}
};
const handleDownload = async () => {
const formData = new FormData();
formData.append("name_column", mappings.name_column);
formData.append("email_column", mappings.email_column);
formData.append("name_color", designParams.name_color);
formData.append("font_size", designParams.font_size);
formData.append("fontname", designParams.fontname);
if (designParams.x !== null) formData.append("x", designParams.x);
if (designParams.y !== null) formData.append("y", designParams.y);
try {
const response = await axios.post("/api/certificates/generate", formData, {
responseType: 'blob'
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'certificates.zip');
document.body.appendChild(link);
link.click();
} catch (error) {
console.error("Download failed", error);
}
};
return (
<div className="max-w-5xl mx-auto">
<div className="text-center mb-8">
<h2 className="text-2xl font-bold text-gray-900 mb-2">Review & Send</h2>
<p className="text-gray-600">Configure your email settings and launch the campaign.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Email Configuration */}
<div className="space-y-6">
<div className="bg-white border border-gray-200 p-6 rounded-lg shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
Event Details
</h3>
<div className="space-y-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Event Name *</label>
<input
type="text"
className="w-full border border-gray-300 rounded-lg p-3 text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
placeholder="e.g., Annual Medical Conference"
value={eventName}
onChange={(e) => setEventName(e.target.value)}
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Event Date *</label>
<input
type="text"
className="w-full border border-gray-300 rounded-lg p-3 text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
placeholder="e.g., November 2024"
value={eventDate}
onChange={(e) => setEventDate(e.target.value)}
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Client Company *</label>
<input
type="text"
className="w-full border border-gray-300 rounded-lg p-3 text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
placeholder="e.g., Pharmaceutical Corp"
value={clientCompany}
onChange={(e) => setClientCompany(e.target.value)}
/>
</div>
</div>
</div>
<div className="bg-white border border-gray-200 p-6 rounded-lg shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<FaEnvelope className="mr-2 text-blue-600" /> Email Configuration
</h3>
<div className="space-y-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Sender Name</label>
<input
type="text"
className="w-full border border-gray-300 rounded-lg p-3 text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
placeholder="e.g., Volaris Team"
value={senderName}
onChange={(e) => setSenderName(e.target.value)}
/>
<p className="text-xs text-gray-500 mt-1">This name appears in the recipient's inbox</p>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Sender Email</label>
<div className="w-full border border-gray-200 rounded-lg p-3 bg-gray-50 text-gray-700 font-medium">
{fromEmail || 'Loading...'}
</div>
<p className="text-xs text-gray-500 mt-1">Configured in environment settings</p>
</div>
</div>
</div>
<div className="bg-white border border-gray-200 p-6 rounded-lg shadow-sm">
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<FaEnvelope className="mr-2 text-blue-600" /> Email Content
</h3>
<div className="space-y-4">
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Subject Line</label>
<input
type="text"
className="w-full border border-gray-300 rounded-lg p-3 text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none"
value={subject}
onChange={(e) => setSubject(e.target.value)}
/>
</div>
<div>
<label className="block text-xs font-medium text-gray-700 mb-1">Email Body</label>
<textarea
rows={6}
className="w-full border border-gray-300 rounded-lg p-3 text-gray-900 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none font-mono text-sm"
value={body}
onChange={(e) => setBody(e.target.value)}
/>
<p className="text-xs text-gray-500 mt-2">
Variables: <span className="text-blue-600 font-mono">{'{first_name}'}</span>, <span className="text-blue-600 font-mono">{'{name}'}</span>, <span className="text-blue-600 font-mono">{'{event_name}'}</span>, <span className="text-blue-600 font-mono">{'{event_date}'}</span>, <span className="text-blue-600 font-mono">{'{client_company}'}</span>
</p>
</div>
</div>
</div>
</div>
{/* Summary & Actions */}
<div className="space-y-6">
<div className="bg-gradient-to-br from-blue-50 to-blue-100 border border-blue-200 p-6 rounded-lg">
<h3 className="text-lg font-semibold text-gray-900 mb-4">Campaign Summary</h3>
<ul className="space-y-3 text-sm text-gray-700">
<li className="flex justify-between border-b border-blue-200 pb-2">
<span>Total Recipients</span>
<span className="font-bold text-gray-900">{fileData.total_rows}</span>
</li>
<li className="flex justify-between border-b border-blue-200 pb-2">
<span>Name Column</span>
<span className="font-mono text-blue-700">{mappings.name_column}</span>
</li>
<li className="flex justify-between border-b border-blue-200 pb-2">
<span>Email Column</span>
<span className="font-mono text-blue-700">{mappings.email_column}</span>
</li>
</ul>
</div>
<div className="space-y-4">
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={handleDownload}
className="w-full py-4 px-6 border border-blue-600 text-blue-600 rounded-lg hover:bg-blue-50 font-medium flex items-center justify-center space-x-2 transition-colors"
>
<FaDownload />
<span>Download All Certificates (ZIP)</span>
</motion.button>
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={handleSend}
disabled={sending}
className={`w-full py-4 px-6 rounded-lg text-white font-bold shadow-md flex items-center justify-center space-x-2 transition-all ${sending
? 'bg-gray-400 cursor-not-allowed'
: 'bg-green-600 hover:bg-green-700 hover:shadow-lg'
}`}
>
{sending ? (
<span>Sending Campaign...</span>
) : (
<>
<FaPaperPlane />
<span>Send Certificates Now</span>
</>
)}
</motion.button>
</div>
{progress && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="bg-white border border-blue-200 p-6 rounded-lg shadow-sm"
>
<h4 className="text-sm font-semibold text-gray-900 mb-3">Sending Progress</h4>
<div className="space-y-3">
<div className="flex justify-between text-sm text-gray-700 mb-2">
<span className="font-medium">{progress.sent} / {progress.total} emails sent</span>
<span className="text-blue-600 font-bold">{Math.round((progress.sent / progress.total) * 100)}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-3 overflow-hidden">
<motion.div
className="bg-gradient-to-r from-blue-500 to-green-500 h-full rounded-full"
initial={{ width: 0 }}
animate={{ width: `${(progress.sent / progress.total) * 100}%` }}
transition={{ duration: 0.3 }}
/>
</div>
{progress.failed > 0 && (
<p className="text-xs text-red-600">⚠️ {progress.failed} failed</p>
)}
</div>
</motion.div>
)}
{status && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className={`p-4 rounded-lg border text-center ${status.type === 'success'
? 'bg-green-50 border-green-200 text-green-700'
: status.type === 'warning'
? 'bg-yellow-50 border-yellow-200 text-yellow-700'
: 'bg-red-50 border-red-200 text-red-700'
}`}
>
{status.msg}
</motion.div>
)}
{/* Failed Emails Details */}
{failedEmails.length > 0 && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="bg-red-50 border border-red-200 p-6 rounded-lg"
>
<h4 className="text-sm font-semibold text-red-800 mb-3 flex items-center">
⚠️ Failed Emails ({failedEmails.length})
</h4>
<div className="max-h-48 overflow-y-auto space-y-2">
{failedEmails.map((failed, index) => (
<div key={index} className="bg-white rounded p-3 border border-red-100">
<div className="flex justify-between items-start">
<div>
<p className="text-sm font-medium text-gray-900">{failed.name}</p>
<p className="text-xs text-gray-600">{failed.email}</p>
</div>
</div>
<p className="text-xs text-red-600 mt-1 font-mono">{failed.error}</p>
</div>
))}
</div>
</motion.div>
)}
</div>
</div>
<div className="mt-8">
<button
onClick={onBack}
className="text-gray-600 hover:text-gray-900 flex items-center space-x-2 transition-colors font-medium"
>
<FaChevronLeft />
<span>Back to Design</span>
</button>
</div>
</div>
);
};
export default ReviewSendStep;