infoalloggi-monorepo/apps/infoalloggi/src/components/acquisto_processing.tsx

248 lines
6.2 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
"use client";
import {
2025-08-28 18:27:07 +02:00
Elements,
PaymentElement,
useElements,
useStripe,
2025-08-04 17:45:44 +02:00
} from "@stripe/react-stripe-js";
2025-08-28 18:27:07 +02:00
import type { Stripe } from "@stripe/stripe-js";
import { ArrowRight, ExternalLink } from "lucide-react";
import Link from "next/link";
import { type FormEvent, useState } from "react";
import LoadingButton from "~/components/custom_ui/loading-button";
2025-08-04 17:45:44 +02:00
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
2025-08-28 18:27:07 +02:00
import { Button } from "~/components/ui/button";
2025-08-04 17:45:44 +02:00
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
2025-08-28 18:27:07 +02:00
import { env } from "~/env.mjs";
2025-08-04 17:45:44 +02:00
import { formatCurrency } from "~/lib/utils";
2025-08-28 18:27:07 +02:00
import { useTranslation } from "~/providers/I18nProvider";
2025-08-04 17:45:44 +02:00
import type { PaymentsId } from "~/schemas/public/Payments";
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
2025-08-28 18:27:07 +02:00
import { api } from "~/utils/api";
2025-08-04 17:45:44 +02:00
type AcquistoProcessingProps = {
2025-08-28 18:27:07 +02:00
paymentId: PaymentsId;
packId: PrezziarioIdprezziario;
stripePromise: Promise<Stripe | null>;
2025-08-04 17:45:44 +02:00
};
export const AcquistoProcessing = ({
2025-08-28 18:27:07 +02:00
paymentId,
packId,
stripePromise,
2025-08-04 17:45:44 +02:00
}: AcquistoProcessingProps) => {
2025-08-28 18:27:07 +02:00
const [stripeIntent, setStripeIntent] = useState<{
clientSecret: string | null;
titolo: string;
prezzo: number;
descrizione: string;
} | null>(null);
const { t } = useTranslation();
const [generatedIntent, setGeneratedIntent] = useState(false);
const { mutate, isPending } = api.stripe.createIntent.useMutation({
onSuccess: (data) => {
setStripeIntent(data);
},
});
if (generatedIntent && !isPending && !stripeIntent) {
return <Status500 />;
}
return (
<div className="mx-auto my-4 w-full max-w-3xl space-y-8 rounded-md">
<h3 className="mt-2 text-center text-2xl font-bold tracking-wide text-neutral-700 uppercase">
{t.acquisto.titolo}
</h3>
{(() => {
if (!generatedIntent) {
return (
<PricePreparation
onProcedi={() => {
setGeneratedIntent(true);
mutate({
paymentId,
});
2025-08-28 18:27:07 +02:00
}}
packId={packId}
/>
);
}
if (isPending) {
return <LoadingPage />;
}
if (stripeIntent?.clientSecret) {
return (
<>
<Elements
options={{
appearance: {
theme: "stripe",
variables: {
colorPrimary: "#18181b",
colorText: "#18181b",
},
},
clientSecret: stripeIntent.clientSecret,
}}
stripe={stripePromise}
>
<CheckoutForm
descrizione={stripeIntent.descrizione}
prezzo={stripeIntent.prezzo}
titolo={stripeIntent.titolo}
/>
</Elements>
</>
);
}
})()}
2025-08-28 18:27:07 +02:00
</div>
);
2025-08-04 17:45:44 +02:00
};
export const PricePreparation = ({
2025-08-28 18:27:07 +02:00
packId,
onProcedi,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
packId: PrezziarioIdprezziario;
onProcedi: () => void;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const { data: prezziario, isLoading } =
api.prezziario.getPrezzoPerServizio.useQuery({
idprezziario: packId,
});
const { t } = useTranslation();
return (
<div className="mx-auto mt-4 max-w-2xl p-2 sm:mt-10">
{isLoading || !prezziario ? (
<>
<h3 className="text-2xl font-semibold">{t.acquisto.preparazione}</h3>
<LoadingPage />
</>
) : (
<div className="space-y-8">
<h3 className="text-center text-2xl font-semibold">
{t.acquisto.servizi_acquisto}
</h3>
<div className="space-y-6">
<p className="text-3xl">{prezziario.nome_it}</p>
<p className="text-lg">
Cod: &ldquo;{prezziario.idprezziario}&rdquo;
</p>
<p className="text-4xl font-bold">
{formatCurrency(prezziario.prezzo_cent / 100)}
</p>
<p>{prezziario.desc_it}</p>
</div>
{prezziario.testo_condizioni && (
<Link
aria-label="Leggi le condizioni"
2025-08-29 16:18:32 +02:00
className="block w-fit"
2025-08-28 18:27:07 +02:00
href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
target="_blank"
>
<Button className="flex w-full items-center gap-2 text-sm">
<span>{t.acquisto.leggi_condizioni}</span>
<ExternalLink />
</Button>
</Link>
)}
<Button
className="flex w-full items-center gap-2 text-lg"
disabled={isLoading || !prezziario}
2025-08-29 16:18:32 +02:00
onClick={onProcedi}
size="xl"
variant="info"
2025-08-28 18:27:07 +02:00
>
<span>{t.acquisto.procedi_pagamento}</span> <ArrowRight />
</Button>
</div>
)}
</div>
);
2025-08-04 17:45:44 +02:00
};
const CheckoutForm = ({
2025-08-28 18:27:07 +02:00
titolo,
prezzo,
descrizione,
2025-08-04 17:45:44 +02:00
}: {
2025-08-28 18:27:07 +02:00
titolo: string;
prezzo: number;
descrizione: string;
2025-08-04 17:45:44 +02:00
}) => {
2025-08-28 18:27:07 +02:00
const stripe = useStripe();
const elements = useElements();
const [message, setMessage] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
console.log("submitting payment");
if (!stripe || !elements) {
return;
}
setIsLoading(true);
const { error } = await stripe.confirmPayment({
confirmParams: {
return_url: `${env.NEXT_PUBLIC_BASE_URL}/servizio/acquisto-processing`,
},
2025-08-29 16:18:32 +02:00
elements,
2025-08-28 18:27:07 +02:00
});
if (error.type === "card_error" || error.type === "validation_error") {
setMessage(error.message || "An unexpected error occurred.");
} else {
setMessage("An unexpected error occurred.");
}
setIsLoading(false);
};
return (
<Card className="flex w-full flex-col justify-between text-left">
<CardHeader>
<CardTitle>
<p className="text-2xl">{titolo}</p>
</CardTitle>
<p>{descrizione}</p>
<span>
<span className="text-3xl font-bold">
{formatCurrency(prezzo / 100)}
</span>{" "}
<span className="text-base font-normal sm:text-sm">
{t.pricing_cmp.risultati_iva}
</span>
</span>
</CardHeader>
<CardContent>
2025-08-29 16:18:32 +02:00
<form className="space-y-5" id="payment-form" onSubmit={handleSubmit}>
2025-08-28 18:27:07 +02:00
<PaymentElement
id="payment-element"
options={{ layout: "accordion" }}
/>
<LoadingButton
aria-label="Procedi al pagamento"
className="w-full"
disabled={isLoading || !stripe || !elements}
loading={isLoading || !stripe || !elements}
2025-08-29 16:18:32 +02:00
type="submit"
2025-08-28 18:27:07 +02:00
>
{t.procedi}
</LoadingButton>
{message && <div id="payment-message">{message}</div>}
</form>
</CardContent>
</Card>
);
2025-08-04 17:45:44 +02:00
};