Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 125 additions & 18 deletions src/sections/Profile/Admin/Form/CertificatesForm/SendCertificate.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { useParams } from "react-router-dom";
import { Button, Input } from "../../../../../components";
import { api } from "../../../../../services";
import * as XLSX from "xlsx";
import { sendBatchMail } from "./tools/certificateTools";
import { Alert, MicroLoading } from "../../../../../microInteraction";
import {
getCertificatePreview,
generatedAndSendCertificate,
accessOrCreateEventByFormId,
} from "../CertificatesForm/tools/certificateTools";
testCertificateSending,
} from "./tools/certificateTools";
import { Alert, MicroLoading } from "../../../../../microInteraction";
import AuthContext from "../../../../../context/AuthContext.jsx";

const Checkbox = ({ id, checked, onCheckedChange }) => {
Expand Down Expand Up @@ -41,6 +41,7 @@ const SendCertificate = () => {
const [fileUploading, setFileUploading] = useState(false);
const [certificatePreview, setCertificatePreview] = useState("Loading...");
const [alert, setAlert] = useState(null);
const [failedEmails, setFailedEmails] = useState([]);

useEffect(() => {
const fetchCertificatePreview = async () => {
Expand Down Expand Up @@ -211,6 +212,7 @@ const SendCertificate = () => {
}

setSendingMail(true);
setFailedEmails([]);

try {
const eventData = await accessOrCreateEventByFormId(
Expand Down Expand Up @@ -248,13 +250,22 @@ const SendCertificate = () => {
token: authCtx.token,
});

if (response?.status === 200 || response?.status === 207) {
if (response?.status === 200) {
setAlert({
type: response.status === 200 ? "success" : "warning",
message: response.data?.message || "Certificates processed.",
type: "success",
message: response.data?.message || "All certificates sent successfully!",
position: "top-right",
duration: 4000,
});
} else if (response?.status === 207) {
const failed = response.data?.failed || [];
setFailedEmails(failed);
setAlert({
type: "warning",
message: response.data?.message || "Some certificates failed to send.",
position: "top-right",
duration: 5000,
});
} else {
throw new Error(response?.data?.error || "Failed to send certificates");
}
Expand Down Expand Up @@ -284,28 +295,31 @@ const SendCertificate = () => {

setSendingMail(true);
try {
await sendBatchMail({
batchSize: 1,
formId: eventId,
const eventData = await accessOrCreateEventByFormId(
eventId,
authCtx.token
);
if (!eventData || !eventData.id) {
throw new Error("Event data retrieval failed");
}

const response = await testCertificateSending({
eventId: eventData.id,
email: checkedAttendees[0].email,
name: checkedAttendees[0].name || "",
subject: `[TEST] ${subject}`,
htmlContent: description,
recipients: [
{
email: checkedAttendees[0].email,
name: checkedAttendees[0].name || "",
},
],
token: authCtx.token,
});

if (response.status === 200) {
if (response && response.status === 200) {
setAlert({
type: "success",
message: "Test mail sent successfully!",
position: "top-right",
duration: 3000,
});
} else {
throw new Error(response.data?.error || "Failed to send test mail");
throw new Error(response?.data?.error || "Failed to send test mail");
}
} catch (error) {
setAlert({
Expand Down Expand Up @@ -580,6 +594,99 @@ const SendCertificate = () => {
</Button>
</div>
</div>

{failedEmails.length > 0 && (
<div
style={{
padding: 20,
border: "1px solid #e74c3c",
borderRadius: 10,
backgroundColor: "rgba(231, 76, 60, 0.08)",
marginTop: 20,
}}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: 10,
}}
>
<h3 style={{ color: "#e74c3c", margin: 0 }}>
⚠ Failed Emails ({failedEmails.length})
</h3>
<div style={{ display: "flex", gap: 10 }}>
<Button
onClick={() => {
const failedAttendeeEmails = failedEmails.map((f) => f.email);
const failedAttendees = failedAttendeeEmails
.map((email) => {
const existing = attendees.find((a) => a.email === email);
return existing || { email, name: "" };
})
.filter(
(fa) =>
!checkedAttendees.some((ca) => ca.email === fa.email)
);
setCheckedAttendees((prev) => [...prev, ...failedAttendees]);
setAlert({
type: "info",
message: `${failedAttendees.length} failed recipient(s) added back for retry`,
position: "top-right",
duration: 3000,
});
}}
>
Retry Failed
</Button>
<Button
onClick={() => setFailedEmails([])}
style={{ backgroundColor: "transparent", color: "#e74c3c" }}
>
Dismiss
</Button>
</div>
</div>
<div
style={{
maxHeight: 200,
overflowY: "auto",
border: "1px solid rgba(231, 76, 60, 0.3)",
borderRadius: 5,
padding: 10,
}}
>
{failedEmails.map((item, idx) => (
<div
key={idx}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "6px 0",
borderBottom:
idx < failedEmails.length - 1
? "1px solid rgba(231, 76, 60, 0.15)"
: "none",
}}
>
<span style={{ fontWeight: 500 }}>{item.email}</span>
<span
style={{
color: "#e74c3c",
fontSize: "0.85em",
maxWidth: "50%",
textAlign: "right",
}}
>
{item.error || "Unknown error"}
</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,31 @@ const generatedAndSendCertificate = async ({
}
};

const testCertificateSending = async ({ eventId, email, name, subject, token }) => {
try {
const response = await api.post(
"/api/certificate/testCertificateSending",
{
eventId,
email,
name,
subject,
},
{
headers: { Authorization: `Bearer ${token}` },
}
);
return response;
} catch (error) {
console.error("Error sending test certificate:", error);
return error.response;
}
};

export {
accessOrCreateEventByFormId,
getCertificatePreview,
sendBatchMail,
generatedAndSendCertificate,
testCertificateSending,
};
Loading