feat: add custom message functionality for diploma emails and WhatsApp notifications
This commit is contained in:
parent
cddb2291c3
commit
c747b4615c
|
@ -8,11 +8,16 @@ import {
|
||||||
import Diploma from "@/components/Diploma";
|
import Diploma from "@/components/Diploma";
|
||||||
import { PDFDownloadLink, PDFViewer, pdf } from "@react-pdf/renderer";
|
import { PDFDownloadLink, PDFViewer, pdf } from "@react-pdf/renderer";
|
||||||
import { supabaseClient } from "@/utils/supabase";
|
import { supabaseClient } from "@/utils/supabase";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { mensajesSchema } from "@/schemas/mensajesSchema";
|
||||||
|
import { Textarea } from "../ui/textarea";
|
||||||
|
|
||||||
function VistaPreviaDiplomaDialog({
|
function VistaPreviaDiplomaDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
alumno,
|
alumno,
|
||||||
|
curso,
|
||||||
competencias: competenciasProp,
|
competencias: competenciasProp,
|
||||||
fecha,
|
fecha,
|
||||||
competenciasAcreditadas,
|
competenciasAcreditadas,
|
||||||
|
@ -20,8 +25,77 @@ function VistaPreviaDiplomaDialog({
|
||||||
const [mostrarVistaPrevia, setMostrarVistaPrevia] = useState(false);
|
const [mostrarVistaPrevia, setMostrarVistaPrevia] = useState(false);
|
||||||
const [enviando, setEnviando] = useState(false);
|
const [enviando, setEnviando] = useState(false);
|
||||||
const [mensaje, setMensaje] = useState("");
|
const [mensaje, setMensaje] = useState("");
|
||||||
|
const [loadingMensajes, setLoadingMensajes] = useState(false);
|
||||||
const [competencias, setCompetencias] = useState([]);
|
const [competencias, setCompetencias] = useState([]);
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
resolver: zodResolver(mensajesSchema),
|
||||||
|
defaultValues: {
|
||||||
|
correo: "",
|
||||||
|
whatsapp: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
formState: { errors },
|
||||||
|
} = form;
|
||||||
|
|
||||||
|
// 🔄 Cargar mensajes al abrir el modal
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
setLoadingMensajes(true);
|
||||||
|
Promise.all([
|
||||||
|
supabaseClient.from("mensaje_correo").select("id, mensaje").single(),
|
||||||
|
supabaseClient.from("mensaje_whatsapp").select("id, mensaje").single(),
|
||||||
|
])
|
||||||
|
.then(([correoRes, whatsappRes]) => {
|
||||||
|
if (correoRes.data) setValue("correo", correoRes.data.mensaje);
|
||||||
|
if (whatsappRes.data) setValue("whatsapp", whatsappRes.data.mensaje);
|
||||||
|
})
|
||||||
|
.finally(() => setLoadingMensajes(false));
|
||||||
|
}
|
||||||
|
}, [open, setValue]);
|
||||||
|
|
||||||
|
// 📝 Guardar mensajes personalizados
|
||||||
|
const handleGuardarMensajes = async () => {
|
||||||
|
const { correo, whatsapp } = getValues();
|
||||||
|
|
||||||
|
const [{ data: correoExistente }] = await Promise.all([
|
||||||
|
supabaseClient.from("mensaje_correo").select("id").maybeSingle(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (correoExistente) {
|
||||||
|
await supabaseClient
|
||||||
|
.from("mensaje_correo")
|
||||||
|
.update({ mensaje: correo })
|
||||||
|
.eq("id", correoExistente.id);
|
||||||
|
} else {
|
||||||
|
await supabaseClient.from("mensaje_correo").insert({ mensaje: correo });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: whatsappExistente } = await supabaseClient
|
||||||
|
.from("mensaje_whatsapp")
|
||||||
|
.select("id")
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (whatsappExistente) {
|
||||||
|
await supabaseClient
|
||||||
|
.from("mensaje_whatsapp")
|
||||||
|
.update({ mensaje: whatsapp })
|
||||||
|
.eq("id", whatsappExistente.id);
|
||||||
|
} else {
|
||||||
|
await supabaseClient
|
||||||
|
.from("mensaje_whatsapp")
|
||||||
|
.insert({ mensaje: whatsapp });
|
||||||
|
}
|
||||||
|
|
||||||
|
setMensaje("Mensajes guardados correctamente.");
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (alumno && alumno.curso?.id) {
|
if (alumno && alumno.curso?.id) {
|
||||||
supabaseClient
|
supabaseClient
|
||||||
|
@ -41,28 +115,25 @@ function VistaPreviaDiplomaDialog({
|
||||||
? competencias.filter((comp) => competenciasAcreditadas.includes(comp.id))
|
? competencias.filter((comp) => competenciasAcreditadas.includes(comp.id))
|
||||||
: competencias;
|
: competencias;
|
||||||
|
|
||||||
// Simulación de envío de PDF por correo y WhatsApp
|
|
||||||
const handleEnviar = async () => {
|
const handleEnviar = async () => {
|
||||||
setEnviando(true);
|
setEnviando(true);
|
||||||
setMensaje("");
|
setMensaje("");
|
||||||
// Genera el PDF como blob
|
|
||||||
const blob = await pdf(
|
const blob = await pdf(
|
||||||
<Diploma
|
<Diploma
|
||||||
alumno={alumno}
|
alumno={alumno}
|
||||||
curso={alumno.curso}
|
curso={curso}
|
||||||
competencias={competenciasMostradas}
|
competencias={competenciasMostradas}
|
||||||
fecha={fecha || new Date().toLocaleDateString()}
|
fecha={fecha || new Date().toLocaleDateString()}
|
||||||
/>
|
/>
|
||||||
).toBlob();
|
).toBlob();
|
||||||
|
|
||||||
// Convierte el blob a base64
|
|
||||||
const pdfBase64 = await new Promise((resolve) => {
|
const pdfBase64 = await new Promise((resolve) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onloadend = () => resolve(reader.result.split(",")[1]);
|
reader.onloadend = () => resolve(reader.result.split(",")[1]);
|
||||||
reader.readAsDataURL(blob);
|
reader.readAsDataURL(blob);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Llama a tu API de Next.js
|
|
||||||
const resp = await fetch("/api/send-diploma", {
|
const resp = await fetch("/api/send-diploma", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
@ -71,32 +142,30 @@ function VistaPreviaDiplomaDialog({
|
||||||
nombre: alumno.nombre,
|
nombre: alumno.nombre,
|
||||||
curso: alumno.curso?.nombre || "Sin curso",
|
curso: alumno.curso?.nombre || "Sin curso",
|
||||||
pdfBase64,
|
pdfBase64,
|
||||||
|
mensajeCorreo: getValues("correo"),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
// WhatsApp real (abre ventana)
|
|
||||||
const telefono = alumno.telefono.replace(/\D/g, "");
|
const telefono = alumno.telefono.replace(/\D/g, "");
|
||||||
const mensajeWhatsapp = encodeURIComponent(
|
const mensajeWhatsapp = encodeURIComponent(getValues("whatsapp"));
|
||||||
`Hola ${alumno.nombre}, tu diploma ha sido generado y enviado a tu correo (${alumno.correo}). ¡Felicidades!`
|
|
||||||
);
|
|
||||||
window.open(
|
window.open(
|
||||||
`https://wa.me/${telefono}?text=${mensajeWhatsapp}`,
|
`https://wa.me/${telefono}?text=${mensajeWhatsapp}`,
|
||||||
"_blank"
|
"_blank"
|
||||||
);
|
);
|
||||||
|
setMensaje(`Diploma enviado por correo a ${alumno.correo}.`);
|
||||||
setMensaje(
|
|
||||||
`Diploma enviado por correo a ${alumno.correo} y mensaje enviado por WhatsApp al ${alumno.telefono}.`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setMensaje("Error enviando el diploma por correo.");
|
setMensaje("Error enviando el diploma.");
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnviando(false);
|
setEnviando(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!alumno) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-lg text-black">
|
<DialogContent className="max-w-lg h-screen text-black overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Diploma</DialogTitle>
|
<DialogTitle>Diploma</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
@ -117,15 +186,35 @@ function VistaPreviaDiplomaDialog({
|
||||||
<div className="text-lg mb-2">
|
<div className="text-lg mb-2">
|
||||||
<b>Fecha:</b> {fecha || new Date().toLocaleDateString()}
|
<b>Fecha:</b> {fecha || new Date().toLocaleDateString()}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-auto text-gray-400 text-xs text-right">
|
{/* Campos de mensaje */}
|
||||||
Vista previa
|
<form>
|
||||||
</div>
|
<div className="mb-4">
|
||||||
<div className="mt-4 flex gap-2 justify-center flex-wrap">
|
<Textarea
|
||||||
|
label="Mensaje para correo"
|
||||||
|
placeholder="Escribe un mensaje personalizado para el correo"
|
||||||
|
{...register("correo")}
|
||||||
|
error={errors.correo?.message}
|
||||||
|
disabled={loadingMensajes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<Textarea
|
||||||
|
label="Mensaje para WhatsApp"
|
||||||
|
placeholder="Escribe un mensaje personalizado para WhatsApp"
|
||||||
|
{...register("whatsapp")}
|
||||||
|
error={errors.whatsapp?.message}
|
||||||
|
disabled={loadingMensajes}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* Acciones */}
|
||||||
|
<div className="mt-4 flex flex-wrap gap-2 justify-center">
|
||||||
<PDFDownloadLink
|
<PDFDownloadLink
|
||||||
document={
|
document={
|
||||||
<Diploma
|
<Diploma
|
||||||
alumno={alumno}
|
alumno={alumno}
|
||||||
curso={alumno.curso}
|
curso={curso}
|
||||||
competencias={competenciasMostradas}
|
competencias={competenciasMostradas}
|
||||||
fecha={fecha || new Date().toLocaleDateString()}
|
fecha={fecha || new Date().toLocaleDateString()}
|
||||||
/>
|
/>
|
||||||
|
@ -144,12 +233,14 @@ function VistaPreviaDiplomaDialog({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</PDFDownloadLink>
|
</PDFDownloadLink>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded"
|
className="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded"
|
||||||
onClick={() => setMostrarVistaPrevia(true)}
|
onClick={() => setMostrarVistaPrevia(true)}
|
||||||
>
|
>
|
||||||
Ver vista previa PDF
|
Ver vista previa PDF
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
className="bg-purple-600 hover:bg-purple-800 text-white px-4 py-2 rounded"
|
className="bg-purple-600 hover:bg-purple-800 text-white px-4 py-2 rounded"
|
||||||
onClick={handleEnviar}
|
onClick={handleEnviar}
|
||||||
|
@ -157,20 +248,31 @@ function VistaPreviaDiplomaDialog({
|
||||||
>
|
>
|
||||||
{enviando ? "Enviando..." : "Enviar por correo y WhatsApp"}
|
{enviando ? "Enviando..." : "Enviar por correo y WhatsApp"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
className="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded"
|
||||||
|
onClick={handleGuardarMensajes}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Guardar mensajes
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{mensaje && (
|
{mensaje && (
|
||||||
<div className="mt-4 text-green-700 font-semibold text-center">
|
<div className="mt-4 text-green-700 font-semibold text-center">
|
||||||
{mensaje}
|
{mensaje}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Vista previa PDF */}
|
||||||
{mostrarVistaPrevia && (
|
{mostrarVistaPrevia && (
|
||||||
<div className="fixed inset-0 bg-black bg-opacity-60 flex flex-col items-center justify-center z-50">
|
<div className="fixed inset-0 bg-black bg-opacity-60 flex items-center justify-center z-50">
|
||||||
<div className="bg-white rounded shadow-lg p-4 flex flex-col items-center">
|
<div className="bg-white rounded shadow-lg p-4">
|
||||||
<div className="w-[80vw] h-[80vh] lg:h-[90vh] mb-4 border">
|
<div className="w-[80vw] h-[90vh] mb-4 border">
|
||||||
<PDFViewer width="100%" height="100%">
|
<PDFViewer width="100%" height="100%">
|
||||||
<Diploma
|
<Diploma
|
||||||
alumno={alumno}
|
alumno={alumno}
|
||||||
curso={alumno.curso}
|
curso={curso}
|
||||||
competencias={competenciasMostradas}
|
competencias={competenciasMostradas}
|
||||||
fecha={fecha || new Date().toLocaleDateString()}
|
fecha={fecha || new Date().toLocaleDateString()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,14 +5,18 @@ sgMail.setApiKey(process.env.SENDGRID_API_KEY);
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
if (req.method !== "POST") return res.status(405).end();
|
if (req.method !== "POST") return res.status(405).end();
|
||||||
|
|
||||||
const { email, nombre, curso, pdfBase64 } = req.body;
|
// Recibe mensajeCorreo desde el body
|
||||||
|
const { email, nombre, curso, pdfBase64, mensajeCorreo } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sgMail.send({
|
await sgMail.send({
|
||||||
to: email,
|
to: email,
|
||||||
from: "rviverosgonzalez@outlook.com", // Cambia esto por tu correo verificado en SendGrid
|
from: "rviverosgonzalez@outlook.com", // Cambia esto por tu correo verificado en SendGrid
|
||||||
subject: "Tu diploma",
|
subject: "Tu diploma",
|
||||||
text: `Hola ${nombre}, has concluido tu curso ${curso} por lo que adjuntamos tu diploma.`,
|
// Usa el mensajeCorreo personalizado, si no viene usa el texto por defecto
|
||||||
|
text:
|
||||||
|
mensajeCorreo ||
|
||||||
|
`Hola ${nombre}, has concluido tu curso ${curso} por lo que adjuntamos tu diploma.`,
|
||||||
attachments: [
|
attachments: [
|
||||||
{
|
{
|
||||||
content: pdfBase64,
|
content: pdfBase64,
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const mensajesSchema = z.object({
|
||||||
|
correo: z.string().min(1, "El mensaje de correo es obligatorio"),
|
||||||
|
whatsapp: z.string().min(1, "El mensaje de WhatsApp es obligatorio"),
|
||||||
|
});
|
Loading…
Reference in New Issue