infoalloggi-monorepo/apps/infoalloggi/src/components/servizio/interruzione.tsx
Marco Pedone 5bff7219e8 refactor: remove ServiziHeader component and integrate its functionality directly into RicercaUser page
feat: update Servizio component to handle interruzioneDays and acceptedInterr logic

refactor: simplify servizio_actions by removing unused InterruzioneServizio and EditParametri components

feat: enhance FormEditServizioAdmin to include interruzioneDays and acceptedInterr fields

fix: adjust email service to delete obsolete emails and handle invalid email types gracefully

chore: update stripe controller to utilize interruzioneDays for lock expiration logic

fix: ensure correct handling of servizio expiration and interruzione logic in service controller
2026-03-24 17:01:40 +01:00

249 lines
7.8 KiB
TypeScript

import { add, differenceInDays, isBefore } from "date-fns";
import { ArrowRight, Clock, ClockAlert, XCircle } from "lucide-react";
import toast from "react-hot-toast";
import { Confirm } from "~/components/confirm";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "~/components/ui/alert-dialog";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "~/components/ui/tooltip";
import { useTranslation } from "~/providers/I18nProvider";
import { useServizio } from "~/providers/ServizioProvider";
import { api } from "~/utils/api";
export const DisclaimerInterruzioneServizio = () => {
const { servizio, servizioId } = useServizio();
const { t } = useTranslation();
const utils = api.useUtils();
const { mutate: interrompiServizio } =
api.servizio.interruzioneServizio.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore durante la richiesta di interruzione");
},
onSuccess: async () => {
await utils.servizio.invalidate();
toast.success("Interruzione richiesta");
},
});
const { mutate: updateServizio } = api.servizio.updateServizio.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore");
},
onSuccess: async () => {
await utils.servizio.invalidate();
},
});
if (!servizio.decorrenza) return null;
return (
<main className="mx-auto max-w-4xl px-4 py-6 md:py-8">
<div className="mb-10 text-center">
<div className="mx-auto mb-6 flex size-16 items-center justify-center rounded-full bg-secondary">
<Clock className="size-8 text-primary" />
</div>
<h1 className="mb-4 text-balance font-bold text-2xl text-foreground tracking-tight md:text-4xl">
Sono passati {differenceInDays(new Date(), servizio.decorrenza)}{" "}
giorni dall'inizio della tua ricerca
</h1>
<p className="mx-auto max-w-xl text-pretty text-lg text-muted-foreground leading-relaxed">
Hai trovato qualche immobile interessante? <br /> Ricordati che{" "}
<b>se non intendi confermare</b> nessun immobile, puoi interrompere la
ricerca <b>entro 14 giorni dalla decorrenza</b>, evitando così di
dover pagare il saldo del servizio acquistato.
</p>
</div>
<div className="grid gap-6 md:grid-cols-2">
<Card className="border-2 border-primary shadow-lg">
<CardHeader className="pb-2 text-center">
<div className="mx-auto mb-3 flex size-14 items-center justify-center rounded-full bg-primary/10">
<ArrowRight className="size-7 text-primary" />
</div>
<CardTitle className="text-foreground text-xl">Continua</CardTitle>
<CardDescription className="text-base">
Continua a cercare casa
</CardDescription>
</CardHeader>
<div className="flex h-full w-full flex-col justify-between">
<CardContent className="">
<p className="mb-4 text-muted-foreground text-sm">
Avrai sempre modo di interrompere il servizio usando il pulsante
relativo.
</p>
</CardContent>
<CardFooter>
<Button
className="h-12 w-full bg-primary font-medium text-base text-primary-foreground hover:bg-primary/90"
onClick={() =>
updateServizio({ servizioId, data: { acceptedInterr: true } })
}
>
Continua con la ricerca
</Button>
</CardFooter>
</div>
</Card>
<Card className="border-border">
<CardHeader className="pb-2 text-center">
<div className="mx-auto mb-3 flex size-14 items-center justify-center rounded-full bg-muted">
<XCircle className="size-7 text-muted-foreground" />
</div>
<CardTitle className="text-foreground text-xl">
Interrompi
</CardTitle>
<CardDescription className="text-base">
Termina la tua ricerca ora
</CardDescription>
</CardHeader>
<div className="flex h-full w-full flex-col justify-between">
<CardContent className="">
<ul className="mb-6 space-y-2 text-muted-foreground text-sm">
<li className="flex items-center gap-2">
<span className="size-1.5 rounded-full bg-muted-foreground" />
Terminazione immediata del servizio
</li>
<li className="flex items-center gap-2">
<span className="size-1.5 rounded-full bg-muted-foreground" />
Nessun addebito aggiuntivo applicato
</li>
</ul>
</CardContent>
<CardFooter>
<Confirm
description={t.servizio.interruzione.description}
onConfirm={() => {
interrompiServizio({ servizioId });
}}
title={t.servizio.interruzione.title}
>
<Button
className="h-12 w-full border-border font-medium text-base text-muted-foreground hover:bg-muted hover:text-foreground"
variant="outline"
>
Interrompi il mio servizio ora
</Button>
</Confirm>
</CardFooter>
</div>
</Card>
</div>
<p className="mt-8 text-center text-muted-foreground text-sm">
Hai fino al{" "}
{add(servizio.decorrenza, {
days: servizio.interruzioneDays,
}).toLocaleDateString("it-IT")}{" "}
per interrompere il servizio senza costi aggiuntivi.
<br />
Se hai bisogno di assistenza, contattaci.
</p>
</main>
);
};
export const InterruzioneServizio = () => {
const { servizio, isAdmin } = useServizio();
const { t } = useTranslation();
const utils = api.useUtils();
const { mutate } = api.servizio.interruzioneServizio.useMutation({
onError: (error) => {
console.error(error);
toast.error("Errore durante la richiesta di interruzione");
},
onSuccess: async () => {
await utils.servizio.invalidate();
toast.success("Interruzione richiesta");
},
});
if (!servizio.decorrenza) return null;
const fineInterruzione = add(servizio.decorrenza, {
days: servizio.interruzioneDays,
});
const canInterrompere =
!servizio.isInterrotto &&
(isAdmin ||
(isBefore(new Date(), fineInterruzione) &&
servizio.annunci.every((a) => a.accettato_conferma_at === null)));
return (
<AlertDialog>
<AlertDialogTrigger asChild>
{canInterrompere ? (
<Button
aria-label="Request Interruzione"
className="w-full"
disabled={!canInterrompere}
>
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
</Button>
) : (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<span className="inline-block w-full">
<Button
aria-label="Request Interruzione"
className="w-full"
disabled={!canInterrompere}
>
<ClockAlert /> <span>{t.servizio.interruzione.btn}</span>
</Button>
</span>
</TooltipTrigger>
<TooltipContent>
<p>Periodo di interruzione scaduto, contattaci.</p>
</TooltipContent>
</Tooltip>
)}
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t.servizio.interruzione.title}</AlertDialogTitle>
<AlertDialogDescription className="space-y-2">
<p>
<b>
Fine periodo di interruzione:{" "}
{fineInterruzione.toLocaleDateString("it-IT")}
</b>
</p>
<p className="whitespace-pre-line">
{t.servizio.interruzione.description}
</p>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t.annulla}</AlertDialogCancel>
<AlertDialogAction
aria-label="Confirm Interruzione"
onClick={() => mutate({ servizioId: servizio.servizio_id })}
>
{t.conferma}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
};