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

243 lines
6.8 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
"use client";
import { api } from "~/utils/api";
import { useState, type FormEvent } from "react";
import {
Elements,
PaymentElement,
useElements,
useStripe,
} from "@stripe/react-stripe-js";
import { LoadingPage } from "~/components/loading";
import { Status500 } from "~/components/status-page";
import LoadingButton from "~/components/custom_ui/loading-button";
import { useTranslation } from "~/providers/I18nProvider";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import type { Stripe } from "@stripe/stripe-js";
import { formatCurrency } from "~/lib/utils";
import { Button } from "~/components/ui/button";
import type { PaymentsId } from "~/schemas/public/Payments";
import type { PrezziarioIdprezziario } from "~/schemas/public/Prezziario";
import { ArrowRight, ExternalLink } from "lucide-react";
import { env } from "~/env.mjs";
import Link from "next/link";
type AcquistoProcessingProps = {
paymentId: PaymentsId;
packId: PrezziarioIdprezziario;
stripePromise: Promise<Stripe | null>;
};
export const AcquistoProcessing = ({
paymentId,
packId,
stripePromise,
}: AcquistoProcessingProps) => {
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>
{!generatedIntent ? (
<PricePreparation
packId={packId}
onProcedi={() => {
setGeneratedIntent(true);
mutate({
paymentId,
});
}}
/>
) : isPending ? (
<LoadingPage />
) : (
stripeIntent &&
stripeIntent.clientSecret && (
<>
<Elements
options={{
appearance: {
theme: "stripe",
variables: {
colorPrimary: "#18181b",
colorText: "#18181b",
},
},
clientSecret: stripeIntent.clientSecret,
}}
stripe={stripePromise}
>
<CheckoutForm
titolo={stripeIntent.titolo}
prezzo={stripeIntent.prezzo}
descrizione={stripeIntent.descrizione}
/>
</Elements>
</>
)
)}
</div>
);
};
export const PricePreparation = ({
packId,
onProcedi,
}: {
packId: PrezziarioIdprezziario;
onProcedi: () => void;
}) => {
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"
href={`/servizio/condizioni/${prezziario.testo_condizioni}`}
target="_blank"
className="block w-fit"
>
<Button className="flex w-full items-center gap-2 text-sm">
<span>{t.acquisto.leggi_condizioni}</span>
<ExternalLink />
</Button>
</Link>
)}
<Button
onClick={onProcedi}
className="flex w-full items-center gap-2 text-lg"
variant="info"
size="xl"
disabled={isLoading || !prezziario}
>
<span>{t.acquisto.procedi_pagamento}</span> <ArrowRight />
</Button>
</div>
)}
</div>
);
};
const CheckoutForm = ({
titolo,
prezzo,
descrizione,
}: {
titolo: string;
prezzo: number;
descrizione: string;
}) => {
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({
elements,
confirmParams: {
return_url: `${env.NEXT_PUBLIC_BASE_URL}/servizio/acquisto-processing`,
},
});
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>
<form id="payment-form" onSubmit={handleSubmit} className="space-y-5">
<PaymentElement
id="payment-element"
options={{ layout: "accordion" }}
/>
<LoadingButton
aria-label="Procedi al pagamento"
type="submit"
className="w-full"
disabled={isLoading || !stripe || !elements}
loading={isLoading || !stripe || !elements}
>
{t.procedi}
</LoadingButton>
{message && <div id="payment-message">{message}</div>}
</form>
</CardContent>
</Card>
);
};