infoalloggi-monorepo/apps/infoalloggi/src/components/servizio/rinnovo.tsx
Marco Pedone 1307f352a4 feat: enhance payment preparation logic and add rinnovo handling
- Refactor `preparePayment` function to handle multiple order types including Rinnovo.
- Introduce new `RinnovoSolver` function to calculate renewal packs based on duration.
- Add `createRinnovo` service to manage the creation of renewal orders.
- Update `whIntentSucceededHandler` to handle payment confirmations for Rinnovo and other order types.
- Introduce default conditions for packs in `prezziario.service.ts`.
- Add new utility functions for managing renewal durations and permanenza.
- Update database interactions to ensure proper handling of orders and renewals.
- Enhance error handling and logging throughout the payment and renewal processes.
2026-03-17 18:46:43 +01:00

179 lines
5.1 KiB
TypeScript

import { add, formatDuration, intervalToDuration } from "date-fns";
import { it } from "date-fns/locale";
import { ArrowRight, CalendarClock, Clock, PackageCheck } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import toast from "react-hot-toast";
import { Button } from "~/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog";
import { FormRinnovo } from "~/forms/FormRinnovo";
import { useRinnovo } from "~/providers/RinnovoProvider";
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
import type { UsersId } from "~/schemas/public/Users";
import { api } from "~/utils/api";
export const Rinnovo = () => {
const { rinnovo, isAdmin } = useRinnovo();
const hasPaid = rinnovo.ordini
.filter((o) => o.type === OrderTypeEnum.Rinnovo)
.find((o) => o.isActive);
return (
<div className="w-full space-y-8">
<div className="flex flex-col flex-wrap items-start justify-between gap-4 sm:flex-row">
<div className="flex flex-col gap-1">
<h3 className="font-semibold text-2xl">
Rinnovo permanenza {rinnovo.codice}
</h3>
<div className="flex items-center gap-2">
<Clock className="size-5" />
<span>
Decorrenza: {rinnovo.decorrenza.toLocaleDateString("it-IT")}
</span>
</div>
<div className="flex flex-wrap items-center gap-2">
<div className="flex items-center gap-2">
<CalendarClock className="size-5" />
<span>
Scadenza: {rinnovo.scadenza.toLocaleDateString("it-IT")}
</span>
</div>
</div>
<p>
Permanenza:{" "}
{formatDuration(
intervalToDuration({
start: new Date(rinnovo.decorrenza),
end: add(new Date(rinnovo.scadenza), { days: 1 }),
}),
{ locale: it, format: ["years", "months", "days"] },
)}
</p>
</div>
<div className="flex items-center gap-2">
{isAdmin && <EditRinnovoModal />}
</div>
</div>
<div className="flex w-full justify-center">
{hasPaid ? (
<div className="flex w-full max-w-md flex-col items-center justify-center gap-4 text-center">
<PackageCheck className="size-46 stroke-1" />
<p className="font-medium text-green-600 text-lg">
Pagamento ricevuto!
</p>
</div>
) : (
<div className="flex w-full max-w-md flex-col items-center justify-center gap-4 text-center">
<PackageCheck className="size-46 stroke-1" />
<Link
aria-label="Attiva Servizio"
className="w-full"
href={`/servizio/pagamento?rinnovoId=${rinnovo.id}&type=${OrderTypeEnum.Rinnovo}`}
>
<Button className="w-full text-lg" variant="info">
<span>Procedi al pagamento</span>
<ArrowRight />
</Button>
</Link>
</div>
)}
</div>
</div>
);
};
const EditRinnovoModal = () => {
const { rinnovo, userId } = useRinnovo();
const utils = api.useUtils();
const router = useRouter();
const { mutate: edit } = api.servizio.updateRinnovo.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Rinnovo modificato con successo");
await utils.servizio.invalidate();
},
});
const { mutate: remove } = api.servizio.removeRinnovo.useMutation({
onError: (error) => {
toast.error(error.message);
},
onMutate: async () => {
await router.push(`/area-riservata/admin/user-view/ricerca/${userId}`);
},
onSuccess: async () => {
toast.success("Rinnovo modificato con successo");
await utils.servizio.invalidate();
},
});
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Modifica Rinnovo</Button>
</DialogTrigger>
<DialogContent className="w-full px-4 py-10 sm:max-w-5xl">
<DialogHeader>
<DialogTitle>Modifica Rinnovo</DialogTitle>
<DialogDescription className="sr-only">desc</DialogDescription>
</DialogHeader>
<FormRinnovo
initialData={rinnovo}
onDelete={() => remove({ rinnovoId: rinnovo.id })}
onSubmit={(v) => edit({ rinnovoId: rinnovo.id, data: v })}
/>
</DialogContent>
</Dialog>
);
};
export const NewRinnovoModal = ({ userId }: { userId: UsersId }) => {
const [open, setOpen] = useState(false);
const utils = api.useUtils();
const { mutate: add } = api.servizio.createRinnovo.useMutation({
onError: (error) => {
toast.error(error.message);
},
onSuccess: async () => {
toast.success("Rinnovo creato con successo");
await utils.servizio.invalidate();
setOpen(false);
},
});
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger asChild>
<Button
onClick={() => {
setOpen(true);
}}
variant="outline"
>
Nuovo Rinnovo
</Button>
</DialogTrigger>
<DialogContent className="w-full px-4 py-10 sm:max-w-5xl">
<DialogHeader>
<DialogTitle>Creazione Rinnovo</DialogTitle>
<DialogDescription className="sr-only">desc</DialogDescription>
</DialogHeader>
<FormRinnovo onSubmit={(v) => add({ data: { userId, ...v } })} />
</DialogContent>
</Dialog>
);
};