feat: implement email sending for contact details with PDF attachment
This commit is contained in:
parent
e5c0ca04ee
commit
3ea9354abb
8 changed files with 214 additions and 33 deletions
|
|
@ -41,7 +41,7 @@ const Base = ({
|
|||
|
||||
<Preview>{preview}</Preview>
|
||||
<Body className="py-2 text-center font-sans text-base text-neutral-700">
|
||||
<Container className="max-w-xl rounded-lg border border-[#eaeaea] border-solid pt-2 pb-10">
|
||||
<Container className="max-w-2xl rounded-lg border border-[#eaeaea] border-solid pt-2 pb-10">
|
||||
<Img
|
||||
alt="Infoalloggi.it"
|
||||
className="mx-auto my-2 scale-80"
|
||||
|
|
@ -51,9 +51,9 @@ const Base = ({
|
|||
width={230.5}
|
||||
/>
|
||||
|
||||
<Section className="max-w-lg px-8">
|
||||
<Section className="max-w-xl px-4">
|
||||
<Hr />
|
||||
<Section className="max-w-md">{children}</Section>
|
||||
<Section className="max-w-lg">{children}</Section>
|
||||
|
||||
<Section className="mt-4">
|
||||
{noreply && (
|
||||
|
|
|
|||
|
|
@ -1,24 +1,33 @@
|
|||
import { Button, Heading, Row, Section, Text } from "@react-email/components";
|
||||
import type { ReactNode } from "react";
|
||||
import Base from "./base";
|
||||
export type Generic_NewMail = {
|
||||
mailType: "generic";
|
||||
props: GenericProps;
|
||||
};
|
||||
|
||||
type Corpo = {
|
||||
testo?: undefined;
|
||||
corpo: ReactNode;
|
||||
};
|
||||
type Testo = {
|
||||
testo: string;
|
||||
corpo?: undefined;
|
||||
};
|
||||
type GenericProps = {
|
||||
title: string;
|
||||
noreply?: boolean;
|
||||
testo: string;
|
||||
|
||||
link?: {
|
||||
href: string;
|
||||
label?: string;
|
||||
};
|
||||
};
|
||||
} & (Corpo | Testo);
|
||||
|
||||
const Generic = ({
|
||||
title,
|
||||
noreply = false,
|
||||
testo,
|
||||
corpo,
|
||||
link = undefined,
|
||||
}: GenericProps) => {
|
||||
return (
|
||||
|
|
@ -26,9 +35,13 @@ const Generic = ({
|
|||
<>
|
||||
<Heading className="text-center text-3xl leading-8">{title}</Heading>
|
||||
<Section className="px-5 text-left text-base md:px-12">
|
||||
{corpo ? (
|
||||
corpo
|
||||
) : (
|
||||
<Row>
|
||||
<Text>{testo}</Text>
|
||||
</Row>
|
||||
)}
|
||||
{link && (
|
||||
<Section className="mb-5">
|
||||
<Button
|
||||
|
|
|
|||
79
apps/infoalloggi/emails/scheda-annuncio.tsx
Normal file
79
apps/infoalloggi/emails/scheda-annuncio.tsx
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { Heading, Row, Section, Text } from "@react-email/components";
|
||||
import Base from "./base";
|
||||
export type SchedaContatto_NewMail = {
|
||||
mailType: "emailSchedaContatto";
|
||||
props: EmailSchedaContattoProps;
|
||||
};
|
||||
type EmailSchedaContattoProps = {
|
||||
nominativo: string;
|
||||
telefono: string;
|
||||
indirizzo: string;
|
||||
};
|
||||
|
||||
const EmailSchedaContatto = ({
|
||||
nominativo,
|
||||
telefono,
|
||||
indirizzo,
|
||||
}: EmailSchedaContattoProps) => {
|
||||
return (
|
||||
<Base noreply={true} preview="Dati Contatto Annuncio - Infoalloggi.it">
|
||||
<>
|
||||
<Heading className="text-center text-3xl leading-8">
|
||||
Dati Contatto Annuncio
|
||||
</Heading>
|
||||
<Section className="px-5 text-left md:px-12">
|
||||
<Row>
|
||||
<Text className="text-base">Salve,</Text>
|
||||
|
||||
<Text className="text-base">
|
||||
come da accordi le invio i dettagli del proprietario/referente.
|
||||
</Text>
|
||||
|
||||
<Text className="text-base">
|
||||
Scriva un messaggio WhatsApp ({" "}
|
||||
<a href={`https://wa.me/${telefono}`} rel="noopener noreferrer">
|
||||
Clicca per aprire WhatsApp
|
||||
</a>{" "}
|
||||
) avvisando di aver ricevuto da Infoalloggi il suo contatto e
|
||||
chieda un appuntamento, poi ci aggiorniamo per l'esito.
|
||||
</Text>
|
||||
|
||||
<Text className="text-base">Nominativo: {nominativo}</Text>
|
||||
<Text className="text-base">Tel: {telefono}</Text>
|
||||
<Text className="text-base">Indirizzo immobile: {indirizzo}</Text>
|
||||
|
||||
<Text className="text-base">
|
||||
In allegato trova la scheda completa dell'annuncio
|
||||
</Text>
|
||||
<br />
|
||||
|
||||
<Text className="text-base">
|
||||
Per ulteriori informazioni e chiarimenti non esiti a contattarci
|
||||
all'indirizzo{" "}
|
||||
<a href="mailto:web@infoalloggi.it" rel="noopener noreferrer">
|
||||
web@infoalloggi.it
|
||||
</a>{" "}
|
||||
oppure tramite tel. 04241760915.
|
||||
</Text>
|
||||
<Text className="text-base">Cordiali saluti</Text>
|
||||
<br />
|
||||
<Text className="text-xs">
|
||||
{" "}
|
||||
Le informazioni contenute in questo messaggio di posta elettronica
|
||||
sono riservate confidenziali e ne è vietata la diffusione in
|
||||
qualunque modo eseguita. Qualora Lei non fosse la persona a cui il
|
||||
presente messaggio è destinato, La invitiamo gentilmente ad
|
||||
eliminarlo dopo averne dato tempestiva comunicazione al mittente e
|
||||
a non utilizzare in alcun caso il suo contenuto. Qualsiasi
|
||||
utilizzo non autorizzato di questo messaggio e dei suoi eventuali
|
||||
allegati espone il responsabile alle relative conseguenze civili e
|
||||
penali.
|
||||
</Text>
|
||||
</Row>
|
||||
</Section>
|
||||
</>
|
||||
</Base>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmailSchedaContatto;
|
||||
|
|
@ -3,7 +3,9 @@
|
|||
import html2canvas from "html2canvas-pro";
|
||||
import jsPDF from "jspdf";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useReactToPrint } from "react-to-print";
|
||||
import z from "zod";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Card } from "~/components/ui/card";
|
||||
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
|
||||
|
|
@ -15,28 +17,63 @@ import type { Annunci } from "~/schemas/public/Annunci";
|
|||
import { api } from "~/utils/api";
|
||||
import { IconMatrix } from "./IconComponents";
|
||||
import { GoogleMapsIcon, LogoSvg, WhatsAppIcon } from "./svgs";
|
||||
import { Input } from "./ui/input";
|
||||
import { Label } from "./ui/label";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||
|
||||
export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) {
|
||||
const [inputEmail, setInputEmail] = useState<string>("");
|
||||
const [openPopover, setOpenPopover] = useState<boolean>(false);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const reactToPrintFn = useReactToPrint({
|
||||
contentRef,
|
||||
//print: async (target) => {console.log("Printing...", target.contentDocument);},
|
||||
});
|
||||
|
||||
const { mutate } = api.comunicazioni.sendSchedaAnnuncioEmail.useMutation();
|
||||
const reactToPrintFn2 = useReactToPrint({
|
||||
const { mutate } = api.comunicazioni.sendSchedaAnnuncioEmail.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success("Email inviata con successo!", {
|
||||
id: "send-scheda-annuncio-email",
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error(`Errore durante l'invio dell'email: ${error.message}`, {
|
||||
id: "send-scheda-annuncio-email",
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const sendEmail = useReactToPrint({
|
||||
contentRef,
|
||||
print: async (target) => {
|
||||
try {
|
||||
const htmlContent = target.contentDocument?.documentElement;
|
||||
if (!htmlContent) return;
|
||||
if (!htmlContent) {
|
||||
toast.error("Impossibile generare il contenuto per la stampa.");
|
||||
return;
|
||||
}
|
||||
if (!data || !inputEmail || !data.locatore || !data.numero) {
|
||||
toast.error("Dati annuncio o indirizzo email mancanti.");
|
||||
return;
|
||||
}
|
||||
toast.loading("Invio email in corso...", {
|
||||
id: "send-scheda-annuncio-email",
|
||||
});
|
||||
setOpenPopover(false);
|
||||
htmlContent.querySelectorAll(".print\\:border-none").forEach((el) => {
|
||||
el.classList.remove("border");
|
||||
});
|
||||
htmlContent.querySelectorAll(".print\\:shadow-none").forEach((el) => {
|
||||
el.classList.remove("shadow-sm");
|
||||
});
|
||||
|
||||
htmlContent.querySelectorAll("#navigationLink").forEach((el) => {
|
||||
el.classList.remove("flex");
|
||||
el.classList.add("hidden");
|
||||
});
|
||||
htmlContent.querySelectorAll("#mainLogoSvg text").forEach((el) => {
|
||||
el.classList.remove("font-sans");
|
||||
el.classList.add("font-[system-ui]");
|
||||
});
|
||||
const canvas = await html2canvas(htmlContent, {
|
||||
//scale: 2,
|
||||
useCORS: true,
|
||||
|
|
@ -44,7 +81,7 @@ export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) {
|
|||
backgroundColor: "#ffffff",
|
||||
});
|
||||
|
||||
const imgData = canvas.toDataURL("image/png", 0.85);
|
||||
const imgData = canvas.toDataURL("image/png");
|
||||
const pdf = new jsPDF("p", "mm", "a4");
|
||||
|
||||
// A4 dimensions in mm
|
||||
|
|
@ -119,19 +156,25 @@ export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) {
|
|||
}
|
||||
}
|
||||
const pdfBase64 = pdf.output("dataurlstring");
|
||||
console.log(
|
||||
"PDF size:",
|
||||
((pdfBase64.length * 0.75) / 1024 / 1024).toFixed(2),
|
||||
"MB",
|
||||
);
|
||||
|
||||
mutate({
|
||||
pdf: pdfBase64,
|
||||
to: "m.pedone98@gmail.com",
|
||||
to: inputEmail,
|
||||
codice: data.codice,
|
||||
numero: data.numero,
|
||||
nominativo: data.locatore,
|
||||
indirizzo: `${data.indirizzo}, ${data.civico} ${data.comune} ${data.cap} (${data.provincia})`,
|
||||
});
|
||||
setInputEmail("");
|
||||
} catch (e) {
|
||||
toast.error(
|
||||
`Errore durante l'invio dell'email: ${(e as Error).message}`,
|
||||
{
|
||||
id: "send-scheda-annuncio-email",
|
||||
},
|
||||
);
|
||||
console.error("Error during print:", e);
|
||||
setInputEmail("");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -141,13 +184,42 @@ export function SchedaAnnuncioStampabile({ data }: { data: Annunci }) {
|
|||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="font-bold text-2xl">Scheda Annuncio Stampabile</h1>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button className="print:hidden" onClick={() => reactToPrintFn()}>
|
||||
Stampa
|
||||
Stampa Scheda
|
||||
</Button>
|
||||
<Button className="print:hidden" onClick={() => reactToPrintFn2()}>
|
||||
Stampa
|
||||
<Popover onOpenChange={setOpenPopover} open={openPopover}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button className="print:hidden" variant="outline">
|
||||
Invia tramite email
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="flex w-80 flex-col gap-4 print:hidden">
|
||||
<Label htmlFor="indirizzo">Indirizzo Email</Label>
|
||||
<Input
|
||||
id="indirizzo"
|
||||
onChange={(e) => setInputEmail(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
z.safeParse(z.email(), inputEmail).success
|
||||
) {
|
||||
sendEmail();
|
||||
}
|
||||
}}
|
||||
type="email"
|
||||
/>
|
||||
<Button
|
||||
disabled={
|
||||
z.safeParse(z.email(), inputEmail).success === false
|
||||
}
|
||||
onClick={sendEmail}
|
||||
>
|
||||
Invia
|
||||
</Button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<div ref={contentRef}>
|
||||
<SchedaAnnuncio data={data} />
|
||||
|
|
@ -239,6 +311,7 @@ export function SchedaAnnuncio({ data }: { data: Annunci }) {
|
|||
<a
|
||||
className="flex items-center gap-2 text-blue-600 underline underline-offset-2"
|
||||
href={navigationUrl}
|
||||
id="navigationLink"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -427,6 +427,7 @@ export const LogoSvg = (props: SVGProps<SVGSVGElement>) => (
|
|||
width={258.758}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
id="mainLogoSvg"
|
||||
>
|
||||
<title>{"Infolloggi.it "}</title>
|
||||
<defs>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export type initialValues = {
|
|||
id: FlagsId;
|
||||
value: string;
|
||||
};
|
||||
//TODO salvare flags come boolean
|
||||
type FormProps = {
|
||||
initialValues: initialValues | null;
|
||||
submitMutation: (values: initialValues) => void;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,9 @@ export const comunicazioniRouter = createTRPCRouter({
|
|||
z.object({
|
||||
pdf: z.string(),
|
||||
codice: z.string(),
|
||||
numero: z.string(),
|
||||
nominativo: z.string(),
|
||||
indirizzo: z.string(),
|
||||
to: z.email(),
|
||||
}),
|
||||
)
|
||||
|
|
@ -53,13 +56,14 @@ export const comunicazioniRouter = createTRPCRouter({
|
|||
const base64Content = input.pdf.includes("base64,")
|
||||
? input.pdf.split("base64,")[1]
|
||||
: input.pdf;
|
||||
|
||||
await NewMail({
|
||||
template: {
|
||||
mailType: "generic",
|
||||
mailType: "emailSchedaContatto",
|
||||
props: {
|
||||
noreply: true,
|
||||
testo: `In allegato la scheda annuncio stampabile per l'annuncio con codice ${input.codice}.`,
|
||||
title: "Scheda Annuncio Stampabile",
|
||||
nominativo: input.nominativo,
|
||||
telefono: input.numero,
|
||||
indirizzo: input.indirizzo,
|
||||
},
|
||||
},
|
||||
mail: {
|
||||
|
|
@ -67,7 +71,7 @@ export const comunicazioniRouter = createTRPCRouter({
|
|||
to: input.to,
|
||||
attachments: [
|
||||
{
|
||||
filename: `scheda-annuncio-${input.codice}.pdf`,
|
||||
filename: `scheda annuncio ${input.codice}.pdf`,
|
||||
content: base64Content,
|
||||
encoding: "base64",
|
||||
contentType: "application/pdf",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import type { Recesso_NewMail } from "emails/recesso";
|
|||
import Recesso from "emails/recesso";
|
||||
import type { RegistrazioneAvvenuta_NewMail } from "emails/registrazione-avvenuta";
|
||||
import RegistrazioneAvvenuta from "emails/registrazione-avvenuta";
|
||||
import EmailSchedaContatto, {
|
||||
type SchedaContatto_NewMail,
|
||||
} from "emails/scheda-annuncio";
|
||||
import VerificaEmail, {
|
||||
type VerificaEmail_NewMail,
|
||||
} from "emails/VerificaEmail";
|
||||
|
|
@ -85,7 +88,8 @@ export type MailsTemplates =
|
|||
| RegistrazioneAvvenuta_NewMail
|
||||
| VerificaEmail_NewMail
|
||||
| OnboardingServizio_NewMail
|
||||
| Generic_NewMail;
|
||||
| Generic_NewMail
|
||||
| SchedaContatto_NewMail;
|
||||
|
||||
export const genMail = async ({ ...mail }: MailsTemplates) => {
|
||||
let mailComponent: JSX.Element | null = null;
|
||||
|
|
@ -165,6 +169,12 @@ export const genMail = async ({ ...mail }: MailsTemplates) => {
|
|||
});
|
||||
title = mail.props.title || "Email Generica";
|
||||
break;
|
||||
case "emailSchedaContatto":
|
||||
mailComponent = EmailSchedaContatto({
|
||||
...mail.props,
|
||||
});
|
||||
title = "Scheda Contatto Annuncio";
|
||||
break;
|
||||
}
|
||||
if (mailComponent === null) {
|
||||
throw new TRPCError({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue