From 51ebe0be1fe292eacb77adfa576a81ea4c59131a Mon Sep 17 00:00:00 2001 From: Marco Pedone Date: Mon, 13 Apr 2026 18:47:38 +0200 Subject: [PATCH] feat: add new invite request functionality and update invite page with new dialog --- .../src/pages/auth/accetta-invito/[token].tsx | 92 ++++++++++++++++++- .../src/server/api/routers/invite.ts | 38 ++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) diff --git a/apps/infoalloggi/src/pages/auth/accetta-invito/[token].tsx b/apps/infoalloggi/src/pages/auth/accetta-invito/[token].tsx index 302aa3d..9c49bdb 100644 --- a/apps/infoalloggi/src/pages/auth/accetta-invito/[token].tsx +++ b/apps/infoalloggi/src/pages/auth/accetta-invito/[token].tsx @@ -1,8 +1,10 @@ "use client"; -import { Eye, EyeOff, Frown } from "lucide-react"; +import { ExternalLink, Eye, EyeOff, Frown, MailPlus } from "lucide-react"; import type { GetServerSideProps } from "next"; +import Link from "next/link"; import { useRouter } from "next/router"; +import { useState } from "react"; import toast from "react-hot-toast"; import { z } from "zod"; import { @@ -13,7 +15,19 @@ import { FormLabel, FormMessage, } from "~/components/custom_ui/form"; +import LoadingButton from "~/components/custom_ui/loading-button"; import { PasswordSVG } from "~/components/svgs"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "~/components/ui/alert-dialog"; import { Button } from "~/components/ui/button"; import Input from "~/components/ui/input"; import { useStrongPassword } from "~/hooks/useStrongPassword"; @@ -103,9 +117,17 @@ const AcceptInvitePage: NextPageWithLayout = ({
-

Questo link non valido

-

Il link è scaduto o è già stato utilizzato.

-

Contattaci per ricevere una nuova email.

+

Invito non valido

+

L'invito è scaduto o è già stato utilizzato.

+
+ + + + +
); } @@ -197,6 +219,68 @@ const AcceptInvitePage: NextPageWithLayout = ({ ); }; +const NewInviteDialog = () => { + const { t } = useTranslation(); + const [email, setEmail] = useState(""); + const [open, setOpen] = useState(false); + const { mutate, isPending } = api.invite.requestNewInvite.useMutation({ + onSuccess: () => { + toast.success("Email inviata! Controlla la tua casella di posta.", { + duration: 5000, + }); + setOpen(false); + setEmail(""); + }, + onError: (error) => { + toast.error(error.message); + setOpen(false); + setEmail(""); + }, + }); + + return ( + + + + + + + Inserisci il tuo indirizzo email + + Inserisci l'indirizzo email associato al tuo account per ricevere un + nuovo invito. + + + + setEmail(e.target.value)} + placeholder="esempio@email.com" + type="email" + value={email} + /> + + + {t.cancella} + + mutate({ email })} + variant="default" + > + {t.procedi} + + + + + + ); +}; export default AcceptInvitePage; export const getServerSideProps = (async (context) => { diff --git a/apps/infoalloggi/src/server/api/routers/invite.ts b/apps/infoalloggi/src/server/api/routers/invite.ts index 01e7143..240a780 100644 --- a/apps/infoalloggi/src/server/api/routers/invite.ts +++ b/apps/infoalloggi/src/server/api/routers/invite.ts @@ -55,9 +55,47 @@ export const inviteRouter = createTRPCRouter({ } }), + requestNewInvite: publicProcedure + .input(z.object({ email: z.email() })) + .mutation(async ({ input }) => { + try { + const user = await findUser_byEmail({ email: input.email }); + if (!user) { + // For security, we don't reveal whether the email exists or not + return { success: true }; + } + + const token = await createInviteToken(user.email); + const inviteUrl = `${env.BASE_URL}/auth/accetta-invito/${token}`; + + await sendInvitoEmail({ + userId: user.id, + email: user.email, + nome: user.nome, + //password: user.password, + inviteUrl, + }); + return { success: true }; + } catch (e) { + console.error("Error in requestNewInvite:", e); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: + "Errore durante la richiesta di un nuovo invito: " + + (e instanceof Error ? e.message : "Unknown error"), + }); + } + }), + // Verify invite token (public - user clicking the link) verifyInvite: publicProcedure .input(z.object({ token: z.string() })) + .output( + z.discriminatedUnion("valid", [ + z.object({ valid: z.literal(true), email: z.email() }), + z.object({ valid: z.literal(false), email: z.null() }), + ]), + ) .query(async ({ input }) => { const invite = await verifyInviteToken(input.token);