feat: implement email sharing functionality for selected listings
This commit is contained in:
parent
07132e8cd6
commit
95315e6605
6 changed files with 177 additions and 20 deletions
|
|
@ -9,13 +9,12 @@ import {
|
||||||
} from "@react-email/components";
|
} from "@react-email/components";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import Base from "./base";
|
import Base from "./base";
|
||||||
export type OnboardingServizio_NewMail = {
|
export type ShareAnnunci_NewMail = {
|
||||||
mailType: "onboardingServizio";
|
mailType: "shareAnnunci";
|
||||||
props: OnboardingServizioProps;
|
props: ShareAnnunciProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
type OnboardingServizioProps = {
|
type ShareAnnunciProps = {
|
||||||
token: string;
|
|
||||||
nome: string;
|
nome: string;
|
||||||
annunci?: {
|
annunci?: {
|
||||||
titolo: string | null;
|
titolo: string | null;
|
||||||
|
|
@ -25,11 +24,7 @@ type OnboardingServizioProps = {
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const OnboardingServizio = ({
|
const ShareAnnunci = ({ nome, annunci }: ShareAnnunciProps) => {
|
||||||
token,
|
|
||||||
nome,
|
|
||||||
annunci,
|
|
||||||
}: OnboardingServizioProps) => {
|
|
||||||
return (
|
return (
|
||||||
<Base preview="Procedi ora su Infoalloggi.it">
|
<Base preview="Procedi ora su Infoalloggi.it">
|
||||||
<>
|
<>
|
||||||
|
|
@ -48,9 +43,9 @@ const OnboardingServizio = ({
|
||||||
<Section className="mb-5">
|
<Section className="mb-5">
|
||||||
<Button
|
<Button
|
||||||
className="mx-auto box-border inline-flex h-10 items-center justify-center whitespace-nowrap rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold text-neutral-900"
|
className="mx-auto box-border inline-flex h-10 items-center justify-center whitespace-nowrap rounded-md bg-neutral-100 px-4 py-2 text-center align-middle font-semibold text-neutral-900"
|
||||||
href={`${env.BASE_URL}/servizio/pre-onboard/${token}`}
|
href={`${env.BASE_URL}`}
|
||||||
>
|
>
|
||||||
Vai al servizio
|
Vai al Sito
|
||||||
</Button>
|
</Button>
|
||||||
</Section>
|
</Section>
|
||||||
<Section>
|
<Section>
|
||||||
|
|
@ -100,4 +95,4 @@ const OnboardingServizio = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default OnboardingServizio;
|
export default ShareAnnunci;
|
||||||
|
|
@ -1,6 +1,14 @@
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
|
import { useState } from "react";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
import Input from "~/components/custom_ui/input";
|
||||||
|
import { MultiSelect } from "~/components/custom_ui/multiselect";
|
||||||
import { AreaRiservataLayout } from "~/components/Layout";
|
import { AreaRiservataLayout } from "~/components/Layout";
|
||||||
|
import { LoadingPage } from "~/components/loading";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Label } from "~/components/ui/label";
|
||||||
import type { NextPageWithLayout } from "~/pages/_app";
|
import type { NextPageWithLayout } from "~/pages/_app";
|
||||||
|
import type { AnnunciId } from "~/schemas/public/Annunci";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
const Impostazioni: NextPageWithLayout = () => {
|
const Impostazioni: NextPageWithLayout = () => {
|
||||||
|
|
@ -17,6 +25,7 @@ const Impostazioni: NextPageWithLayout = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StripeSection />
|
<StripeSection />
|
||||||
|
<SendEmailSection />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
@ -59,3 +68,91 @@ const StripeSection = () => {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SendEmailSection = () => {
|
||||||
|
const { data: codici, isLoading: loading } =
|
||||||
|
api.annunci.getOnline_AnnuncioIdCodice.useQuery();
|
||||||
|
const [selectedAnnunci, setSelectedAnnunci] = useState<
|
||||||
|
{ value: AnnunciId; label: string }[]
|
||||||
|
>([]);
|
||||||
|
const [email, setEmail] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const { mutate } = api.comunicazioni.sendAnnunciEmail.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
setSelectedAnnunci([]);
|
||||||
|
setEmail("");
|
||||||
|
toast.success("Email inviata con successo");
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
toast.error(`Errore durante l'invio dell'email: ${error.message}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 rounded-lg border p-4">
|
||||||
|
<h4 className="font-semibold text-lg">
|
||||||
|
Invia email con annunci selezionati
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="flex max-w-xl flex-col gap-2">
|
||||||
|
<Label className="font-medium" htmlFor="email-to">
|
||||||
|
Indirizzo email destinatario
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="email-to"
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="Inserisci l'indirizzo email"
|
||||||
|
type="email"
|
||||||
|
value={email || ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex max-w-xl flex-col gap-2">
|
||||||
|
{loading ? (
|
||||||
|
<LoadingPage />
|
||||||
|
) : (
|
||||||
|
<div className="flex max-w-xl flex-col">
|
||||||
|
<Label className="font-medium" htmlFor="select-annuncio">
|
||||||
|
Seleziona gli annunci da inviare
|
||||||
|
</Label>
|
||||||
|
<MultiSelect
|
||||||
|
defaultValue={undefined}
|
||||||
|
elementId="select-annuncio"
|
||||||
|
elementName="annuncio"
|
||||||
|
isMulti={true}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSelectedAnnunci(
|
||||||
|
value.map((v) => ({
|
||||||
|
label: v.label,
|
||||||
|
value: parseInt(v.value) as AnnunciId,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
options={
|
||||||
|
codici?.map((cod) => ({
|
||||||
|
label: cod.codice,
|
||||||
|
value: cod.id.toString(),
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
placeholder=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
aria-label="Cancel Add Annuncio"
|
||||||
|
disabled={!selectedAnnunci.length || !email}
|
||||||
|
onClick={() => {
|
||||||
|
if (!selectedAnnunci.length || !email) return;
|
||||||
|
mutate({
|
||||||
|
annunci: selectedAnnunci.map((annuncio) => annuncio.value),
|
||||||
|
to: email,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Invia
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
getAnnunciMetaByCod,
|
getAnnunciMetaByCod,
|
||||||
getAnnunciRicerca,
|
getAnnunciRicerca,
|
||||||
getCodici_AnnunciHandler,
|
getCodici_AnnunciHandler,
|
||||||
|
getOnline_AnnuncioIdCodice_AnnunciHandler,
|
||||||
getOptions_AnnunciHandler,
|
getOptions_AnnunciHandler,
|
||||||
getProprietarioDataHandler,
|
getProprietarioDataHandler,
|
||||||
} from "~/server/controllers/annunci.controller";
|
} from "~/server/controllers/annunci.controller";
|
||||||
|
|
@ -39,6 +40,9 @@ export const annunciRouter = createTRPCRouter({
|
||||||
getAnnunciOptions: publicProcedure.query(async () => {
|
getAnnunciOptions: publicProcedure.query(async () => {
|
||||||
return await getOptions_AnnunciHandler();
|
return await getOptions_AnnunciHandler();
|
||||||
}),
|
}),
|
||||||
|
getOnline_AnnuncioIdCodice: publicProcedure.query(async () => {
|
||||||
|
return await getOnline_AnnuncioIdCodice_AnnunciHandler();
|
||||||
|
}),
|
||||||
getAnnuncio: publicProcedure
|
getAnnuncio: publicProcedure
|
||||||
.input(
|
.input(
|
||||||
z.object({
|
z.object({
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,14 @@ import {
|
||||||
createTRPCRouter,
|
createTRPCRouter,
|
||||||
protectedProcedure,
|
protectedProcedure,
|
||||||
} from "~/server/api/trpc";
|
} from "~/server/api/trpc";
|
||||||
|
import { db } from "~/server/db";
|
||||||
import {
|
import {
|
||||||
deleteAllUserEmails,
|
deleteAllUserEmails,
|
||||||
deleteEmail,
|
deleteEmail,
|
||||||
getEmails,
|
getEmails,
|
||||||
} from "~/server/services/email.service";
|
} from "~/server/services/email.service";
|
||||||
import { NewMail } from "~/server/services/mailer";
|
import { NewMail } from "~/server/services/mailer";
|
||||||
import { zUserId } from "~/server/utils/zod_types";
|
import { zAnnuncioId, zUserId } from "~/server/utils/zod_types";
|
||||||
|
|
||||||
export const comunicazioniRouter = createTRPCRouter({
|
export const comunicazioniRouter = createTRPCRouter({
|
||||||
// genEmail: publicProcedure
|
// genEmail: publicProcedure
|
||||||
|
|
@ -95,4 +96,45 @@ export const comunicazioniRouter = createTRPCRouter({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
sendAnnunciEmail: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
annunci: zAnnuncioId.array(),
|
||||||
|
to: z.email(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
try {
|
||||||
|
const annunci = await db
|
||||||
|
.selectFrom("annunci")
|
||||||
|
.select([
|
||||||
|
"annunci.titolo_it as titolo",
|
||||||
|
"annunci.codice",
|
||||||
|
"annunci.prezzo",
|
||||||
|
])
|
||||||
|
.innerJoin("images_refs", "images_refs.codice", "annunci.codice")
|
||||||
|
.select("images_refs.img as immagine")
|
||||||
|
.where("images_refs.ordine", "=", 0)
|
||||||
|
.where("annunci.id", "in", input.annunci)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
await NewMail({
|
||||||
|
template: {
|
||||||
|
mailType: "shareAnnunci",
|
||||||
|
props: {
|
||||||
|
nome: "Admin",
|
||||||
|
annunci,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mail: {
|
||||||
|
subject: `Scheda Annuncio - Infoalloggi.it`,
|
||||||
|
to: input.to,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(
|
||||||
|
`Errore nell'invio dell'email: ${(e as Error).message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,27 @@ export const getAnnunciListHandler = async (): Promise<
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getOnline_AnnuncioIdCodice_AnnunciHandler = async () => {
|
||||||
|
try {
|
||||||
|
const data = await db
|
||||||
|
.selectFrom("annunci")
|
||||||
|
.select(["codice", "id"])
|
||||||
|
.where("annunci.web", "=", true)
|
||||||
|
.where("stato", "!=", "Sospeso")
|
||||||
|
.execute();
|
||||||
|
if (!data) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.filter((c) => c.id && c.codice);
|
||||||
|
} catch (e) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: `Errore query getOnline_AnnuncioIdCodice_AnnunciHandler: ${(e as Error).message}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getCodici_AnnunciHandler = async (): Promise<string[]> => {
|
export const getCodici_AnnunciHandler = async (): Promise<string[]> => {
|
||||||
try {
|
try {
|
||||||
const codici = await db
|
const codici = await db
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,6 @@ import type { Generic_NewMail } from "emails/gereric-email";
|
||||||
import Generic from "emails/gereric-email";
|
import Generic from "emails/gereric-email";
|
||||||
import type { Invito_NewMail } from "emails/invito";
|
import type { Invito_NewMail } from "emails/invito";
|
||||||
import Invito from "emails/invito";
|
import Invito from "emails/invito";
|
||||||
import OnboardingServizio, {
|
|
||||||
type OnboardingServizio_NewMail,
|
|
||||||
} from "emails/onboarding-servizio";
|
|
||||||
import type { PagamentoConferma_NewMail } from "emails/pagamento-conferma";
|
import type { PagamentoConferma_NewMail } from "emails/pagamento-conferma";
|
||||||
import PagamentoConferma from "emails/pagamento-conferma";
|
import PagamentoConferma from "emails/pagamento-conferma";
|
||||||
import type { PagamentoErrore_NewMail } from "emails/pagamento-errore";
|
import type { PagamentoErrore_NewMail } from "emails/pagamento-errore";
|
||||||
|
|
@ -30,6 +27,7 @@ import RegistrazioneAvvenuta from "emails/registrazione-avvenuta";
|
||||||
import EmailSchedaContatto, {
|
import EmailSchedaContatto, {
|
||||||
type SchedaContatto_NewMail,
|
type SchedaContatto_NewMail,
|
||||||
} from "emails/scheda-annuncio";
|
} from "emails/scheda-annuncio";
|
||||||
|
import ShareAnnunci, { type ShareAnnunci_NewMail } from "emails/share-annunci";
|
||||||
import VerificaEmail, {
|
import VerificaEmail, {
|
||||||
type VerificaEmail_NewMail,
|
type VerificaEmail_NewMail,
|
||||||
} from "emails/VerificaEmail";
|
} from "emails/VerificaEmail";
|
||||||
|
|
@ -89,7 +87,7 @@ export type MailsTemplates =
|
||||||
| Recesso_NewMail
|
| Recesso_NewMail
|
||||||
| RegistrazioneAvvenuta_NewMail
|
| RegistrazioneAvvenuta_NewMail
|
||||||
| VerificaEmail_NewMail
|
| VerificaEmail_NewMail
|
||||||
| OnboardingServizio_NewMail
|
| ShareAnnunci_NewMail
|
||||||
| Generic_NewMail
|
| Generic_NewMail
|
||||||
| SchedaContatto_NewMail
|
| SchedaContatto_NewMail
|
||||||
| Invito_NewMail;
|
| Invito_NewMail;
|
||||||
|
|
@ -160,8 +158,8 @@ export const genMail = async ({ ...mail }: MailsTemplates) => {
|
||||||
title = "Verifica email";
|
title = "Verifica email";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "onboardingServizio":
|
case "shareAnnunci":
|
||||||
mailComponent = OnboardingServizio({
|
mailComponent = ShareAnnunci({
|
||||||
...mail.props,
|
...mail.props,
|
||||||
});
|
});
|
||||||
title = "Onboarding Servizio";
|
title = "Onboarding Servizio";
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue