2025-08-28 18:27:07 +02:00
|
|
|
import { ExternalLink } from "lucide-react";
|
|
|
|
|
import Link from "next/link";
|
|
|
|
|
import { useEffect, useState } from "react";
|
|
|
|
|
import { AnimatedNumber } from "~/components/custom_ui/animated-number";
|
|
|
|
|
import { LoadingPage } from "~/components/loading";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { Button } from "~/components/ui/button";
|
|
|
|
|
import {
|
2025-08-28 18:27:07 +02:00
|
|
|
Card,
|
|
|
|
|
CardContent,
|
|
|
|
|
CardDescription,
|
|
|
|
|
CardHeader,
|
|
|
|
|
CardTitle,
|
2025-08-04 17:45:44 +02:00
|
|
|
} from "~/components/ui/card";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
|
2025-08-04 17:45:44 +02:00
|
|
|
import { cn } 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 TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
2025-08-28 18:27:07 +02:00
|
|
|
import { api } from "~/utils/api";
|
2025-08-04 17:45:44 +02:00
|
|
|
|
|
|
|
|
export const PricingChoice = () => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const { data: prezziario, isLoading } =
|
|
|
|
|
api.prezziario.getPrezziPerTipologiaAll.useQuery();
|
|
|
|
|
if (isLoading) return <LoadingPage />;
|
|
|
|
|
if (!prezziario) return null;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const pricingBreve = prezziario.transitorio.saldi.map((s) => ({
|
|
|
|
|
downPayment: prezziario.transitorio.acconto.prezzo_cent / 100,
|
|
|
|
|
secondPayment: s.prezzo_cent / 100,
|
|
|
|
|
}));
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const pricingStabile = prezziario.stabile.saldi.map((s) => ({
|
|
|
|
|
downPayment: prezziario.stabile.acconto.prezzo_cent / 100,
|
|
|
|
|
secondPayment: s.prezzo_cent / 100,
|
|
|
|
|
}));
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
2025-11-14 17:21:21 +01:00
|
|
|
<div className="flex flex-col items-center justify-center gap-4">
|
2025-10-10 16:18:43 +02:00
|
|
|
<h2 className="font-semibold text-3xl">Scegli il tuo servizio</h2>
|
2025-08-29 16:18:32 +02:00
|
|
|
<Tabs className="mb-2 w-full max-w-xl" defaultValue="transitorio">
|
2026-01-12 17:16:19 +01:00
|
|
|
<TabsList className="flex h-auto w-full items-center justify-between gap-1 p-1 dark:bg-primary">
|
2025-08-28 18:27:07 +02:00
|
|
|
<TabsTrigger
|
2026-01-16 15:47:38 +01:00
|
|
|
className="w-full cursor-pointer border-0 bg-transitorio px-2 text-xl data-[state=active]:border-0 data-[state=active]:bg-transitorio sm:px-3 dark:border-0 dark:text-fxd-foreground dark:data-[state=active]:border-0 dark:data-[state=active]:bg-transitorio dark:data-[state=active]:text-fxd-foreground"
|
2025-08-29 16:18:32 +02:00
|
|
|
value="transitorio"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
Transitorio
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
<TabsTrigger
|
2026-01-16 15:47:38 +01:00
|
|
|
className="w-full cursor-pointer border-0 bg-stabile px-2 text-xl data-[state=active]:border-0 data-[state=active]:bg-stabile sm:px-3 dark:border-0 dark:text-fxd-foreground dark:data-[state=active]:border-0 dark:data-[state=active]:bg-stabile dark:data-[state=active]:text-fxd-foreground"
|
2025-08-29 16:18:32 +02:00
|
|
|
value="stabile"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
Stabile
|
|
|
|
|
</TabsTrigger>
|
|
|
|
|
</TabsList>
|
2025-08-29 16:18:32 +02:00
|
|
|
<TabsContent className="w-full" value="transitorio">
|
2025-08-28 18:27:07 +02:00
|
|
|
<PricingComponent
|
2025-11-14 17:21:21 +01:00
|
|
|
className="border-transitorio bg-transitorio text-fxd-foreground"
|
2025-08-28 18:27:07 +02:00
|
|
|
cta={t.pricing_cmp.cta_breve}
|
|
|
|
|
opzioni={[
|
|
|
|
|
t.pricing_cmp.breve_option1,
|
|
|
|
|
t.pricing_cmp.breve_option2,
|
|
|
|
|
t.pricing_cmp.breve_option3,
|
|
|
|
|
t.pricing_cmp.breve_option4,
|
|
|
|
|
t.pricing_cmp.breve_option5,
|
|
|
|
|
]}
|
2025-08-29 16:18:32 +02:00
|
|
|
pricingData={pricingBreve}
|
2025-08-28 18:27:07 +02:00
|
|
|
tipo={TipologiaPosizioneEnum.Transitorio}
|
2025-08-29 16:18:32 +02:00
|
|
|
title={t.prezzi.titolo_transitori}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
<TabsContent value="stabile">
|
|
|
|
|
<PricingComponent
|
2025-11-14 17:21:21 +01:00
|
|
|
className="border-stabile bg-stabile text-fxd-foreground"
|
2025-08-28 18:27:07 +02:00
|
|
|
cta={t.pricing_cmp.cta_stabile}
|
|
|
|
|
opzioni={[
|
|
|
|
|
t.pricing_cmp.stabile_option1,
|
|
|
|
|
t.pricing_cmp.stabile_option2,
|
|
|
|
|
]}
|
2025-08-29 16:18:32 +02:00
|
|
|
pricingData={pricingStabile}
|
2025-08-28 18:27:07 +02:00
|
|
|
tipo={TipologiaPosizioneEnum.Stabile}
|
2025-08-29 16:18:32 +02:00
|
|
|
title={t.prezzi.titolo_stabile}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</TabsContent>
|
|
|
|
|
</Tabs>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
<Link aria-label="Link to pricing page" href="/prezzi">
|
|
|
|
|
<Button className="flex h-12 flex-row gap-2 px-6 py-4 text-2xl hover:underline">
|
|
|
|
|
<span>Scopri di più sui prezzi</span>
|
2026-01-16 15:47:38 +01:00
|
|
|
<ExternalLink />
|
2025-08-28 18:27:07 +02:00
|
|
|
</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type PricingData = { downPayment: number; secondPayment: number }[];
|
|
|
|
|
|
|
|
|
|
export const PricingComponent = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
pricingData,
|
|
|
|
|
opzioni,
|
|
|
|
|
cta,
|
|
|
|
|
statico = false,
|
|
|
|
|
className,
|
|
|
|
|
title,
|
|
|
|
|
tipo,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
pricingData: PricingData;
|
|
|
|
|
opzioni: string[];
|
|
|
|
|
title: string;
|
|
|
|
|
cta: string;
|
|
|
|
|
statico?: boolean;
|
|
|
|
|
className?: string;
|
|
|
|
|
tipo: TipologiaPosizioneEnum;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const { t } = useTranslation();
|
|
|
|
|
const [Value, setValue] = useState<number>(0);
|
|
|
|
|
const p = pricingData[Value];
|
|
|
|
|
const downPayment = p?.downPayment || 0;
|
|
|
|
|
const secondPayment = p?.secondPayment || 0;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const customFormat = (num: number) => `${num}€`;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (statico) return;
|
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
|
setValue((prevValue) => (prevValue + 1) % pricingData.length);
|
|
|
|
|
}, 2500);
|
|
|
|
|
setIntervalId(interval);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
}, []);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const ClearCarousel = () => {
|
|
|
|
|
if (intervalId) {
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
2026-01-12 17:08:27 +01:00
|
|
|
<Card className={cn("w-full max-w-xl gap-4 rounded-md py-4", className)}>
|
2025-08-28 18:27:07 +02:00
|
|
|
<CardHeader className="py-2">
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardTitle className="pt-1 text-center font-semibold text-3xl">
|
2025-08-28 18:27:07 +02:00
|
|
|
{title}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription className="sr-only">
|
|
|
|
|
Prezzo del servizio
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
2025-11-14 17:21:21 +01:00
|
|
|
<CardContent className="flex w-full flex-col items-center justify-center gap-4 p-0 px-2 xs:px-6 py-0 text-fxd-foreground sm:gap-6 sm:py-4">
|
2025-08-28 18:27:07 +02:00
|
|
|
<div
|
2025-10-10 16:18:43 +02:00
|
|
|
className="flex w-full items-center justify-center gap-0 xs:gap-x-4 xs:gap-y-1"
|
2025-08-28 18:27:07 +02:00
|
|
|
data-role="wrapper"
|
|
|
|
|
>
|
2025-10-10 16:18:43 +02:00
|
|
|
<div className="flex w-2/5 xs:w-1/3 flex-col items-center gap-2">
|
2026-01-20 15:30:25 +01:00
|
|
|
<h2 className="truncate font-semibold text-2xl" data-role="title">
|
2025-08-28 18:27:07 +02:00
|
|
|
{t.acconto}
|
|
|
|
|
</h2>
|
|
|
|
|
<p
|
2025-10-10 16:18:43 +02:00
|
|
|
className="font-semibold text-2xl xs:text-3xl sm:text-6xl"
|
2025-08-28 18:27:07 +02:00
|
|
|
data-role="amount"
|
|
|
|
|
>
|
|
|
|
|
{downPayment}€
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<div className="flex w-2/5 xs:w-1/3 flex-col items-center gap-2 text-center">
|
|
|
|
|
<h2 className="font-semibold text-2xl" data-role="title">
|
2025-08-28 18:27:07 +02:00
|
|
|
{t.saldo}
|
|
|
|
|
</h2>
|
|
|
|
|
<p
|
2025-10-10 16:18:43 +02:00
|
|
|
className="font-semibold text-2xl xs:text-3xl sm:text-6xl"
|
2025-08-28 18:27:07 +02:00
|
|
|
data-role="amount"
|
|
|
|
|
>
|
|
|
|
|
<AnimatedNumber
|
|
|
|
|
format={customFormat}
|
|
|
|
|
precision={secondPayment % 1 === 0 ? 0 : 2}
|
2025-08-29 16:18:32 +02:00
|
|
|
value={secondPayment}
|
2025-08-28 18:27:07 +02:00
|
|
|
/>
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-10-10 16:18:43 +02:00
|
|
|
<div className="flex w-2/5 xs:w-1/3 flex-col items-center gap-2 text-center">
|
|
|
|
|
<h2 className="font-semibold text-2xl" data-role="title">
|
2025-08-28 18:27:07 +02:00
|
|
|
Contratto
|
|
|
|
|
</h2>
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
{tipo === TipologiaPosizioneEnum.Transitorio ? (
|
|
|
|
|
<div className="flex flex-col justify-center leading-none">
|
|
|
|
|
<span
|
2025-10-10 16:18:43 +02:00
|
|
|
className="font-semibold text-2xl xs:text-3xl sm:text-6xl"
|
2025-08-28 18:27:07 +02:00
|
|
|
data-role="amount"
|
|
|
|
|
>
|
|
|
|
|
0 €
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<Link
|
2025-10-13 10:07:09 +02:00
|
|
|
className="flex h-8 xs:h-10 flex-col justify-center leading-none sm:h-15"
|
2025-08-29 16:18:32 +02:00
|
|
|
href={"/prezzi#contratti"}
|
2025-10-10 16:36:49 +02:00
|
|
|
id="contratto-link"
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
2025-11-03 10:59:45 +01:00
|
|
|
<Button>
|
2025-08-28 18:27:07 +02:00
|
|
|
Più info <ExternalLink className="size-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</Link>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex w-full flex-wrap items-center justify-center gap-2">
|
|
|
|
|
{opzioni.map((opzione, idx) => (
|
|
|
|
|
<Button
|
|
|
|
|
className={cn(
|
2025-10-10 16:18:43 +02:00
|
|
|
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
|
|
|
|
|
idx === Value ? "fade-in-0 animate-in" : "",
|
2025-08-28 18:27:07 +02:00
|
|
|
)}
|
2025-10-28 11:55:01 +01:00
|
|
|
key={opzione}
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
ClearCarousel();
|
|
|
|
|
setValue(idx);
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
size="default"
|
|
|
|
|
variant={idx === Value ? "default" : "outline"}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
{opzione}
|
|
|
|
|
</Button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2025-10-10 16:18:43 +02:00
|
|
|
<h2 className="text-center font-semibold text-white text-xl">{cta}</h2>
|
2025-08-28 18:27:07 +02:00
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const PricingComponentConsulenza = ({
|
2025-08-28 18:27:07 +02:00
|
|
|
pricingData,
|
|
|
|
|
opzioni,
|
|
|
|
|
cta,
|
|
|
|
|
statico = false,
|
|
|
|
|
className,
|
|
|
|
|
title,
|
2025-08-04 17:45:44 +02:00
|
|
|
}: {
|
2025-08-28 18:27:07 +02:00
|
|
|
pricingData: number[];
|
|
|
|
|
opzioni: string[];
|
|
|
|
|
title: string;
|
|
|
|
|
cta: string;
|
|
|
|
|
statico?: boolean;
|
|
|
|
|
className?: string;
|
2025-08-04 17:45:44 +02:00
|
|
|
}) => {
|
2025-08-28 18:27:07 +02:00
|
|
|
const [Value, setValue] = useState<number>(0);
|
|
|
|
|
const p = pricingData[Value];
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const customFormat = (num: number) => `${num}€`;
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (statico) return;
|
|
|
|
|
const interval = setInterval(() => {
|
|
|
|
|
setValue((prevValue) => (prevValue + 1) % pricingData.length);
|
|
|
|
|
}, 2500);
|
|
|
|
|
setIntervalId(interval);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return () => clearInterval(interval);
|
|
|
|
|
}, []);
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
const ClearCarousel = () => {
|
|
|
|
|
if (intervalId) {
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-04 17:45:44 +02:00
|
|
|
|
2025-08-28 18:27:07 +02:00
|
|
|
return (
|
|
|
|
|
<Card className={cn("w-full max-w-xl", className)}>
|
|
|
|
|
<CardHeader className="p-3 px-5">
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardTitle className="pt-1 text-center font-semibold text-3xl">
|
2025-08-28 18:27:07 +02:00
|
|
|
{title}
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<CardDescription className="sr-only">
|
|
|
|
|
Prezzo del servizio
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
2025-10-10 16:18:43 +02:00
|
|
|
<CardContent className="flex flex-col items-center justify-center gap-8 p-0 px-2 xs:px-6 pt-4 pb-4">
|
|
|
|
|
<div className="flex w-full items-center justify-center gap-0 xs:gap-4">
|
2025-08-28 18:27:07 +02:00
|
|
|
<div className="flex flex-col items-center gap-2">
|
2025-10-10 16:18:43 +02:00
|
|
|
<p className="font-semibold text-3xl xs:text-6xl">
|
2025-08-29 16:18:32 +02:00
|
|
|
<AnimatedNumber format={customFormat} value={p || 0} />
|
2025-08-28 18:27:07 +02:00
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex w-full flex-wrap items-center justify-center gap-2">
|
|
|
|
|
{opzioni.map((opzione, idx) => (
|
|
|
|
|
<Button
|
|
|
|
|
className={cn(
|
2025-10-10 16:18:43 +02:00
|
|
|
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
|
|
|
|
|
idx === Value ? "fade-in-0 animate-in" : "",
|
2025-08-28 18:27:07 +02:00
|
|
|
)}
|
2025-10-28 11:55:01 +01:00
|
|
|
key={opzione}
|
2025-08-28 18:27:07 +02:00
|
|
|
onClick={() => {
|
|
|
|
|
ClearCarousel();
|
|
|
|
|
setValue(idx);
|
|
|
|
|
}}
|
2025-08-29 16:18:32 +02:00
|
|
|
size="default"
|
|
|
|
|
variant={idx === Value ? "default" : "outline"}
|
2025-08-28 18:27:07 +02:00
|
|
|
>
|
|
|
|
|
{opzione}
|
|
|
|
|
</Button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
2025-10-10 16:18:43 +02:00
|
|
|
<h2 className="text-center font-semibold text-white text-xl">{cta}</h2>
|
2025-08-28 18:27:07 +02:00
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
);
|
2025-08-04 17:45:44 +02:00
|
|
|
};
|