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

385 lines
11 KiB
TypeScript
Raw Normal View History

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 (
<div className="text-primary flex flex-col items-center justify-center gap-4">
<h2 className="text-3xl font-semibold">Scegli il tuo servizio</h2>
<Tabs defaultValue="transitorio" className="mb-2 w-full max-w-xl">
<TabsList className="flex h-auto w-full items-center justify-between gap-1 p-1">
<TabsTrigger
value="transitorio"
className="bg-transitorio [&[data-state=active]]:bg-transitorio w-full cursor-pointer px-2 text-xl sm:px-3"
>
Transitorio
</TabsTrigger>
<TabsTrigger
value="stabile"
className="bg-stabile [&[data-state=active]]:bg-stabile w-full cursor-pointer px-2 text-xl sm:px-3"
>
Stabile
</TabsTrigger>
</TabsList>
<TabsContent value="transitorio" className="w-full">
<PricingComponent
title={t.prezzi.titolo_transitori}
cta={t.pricing_cmp.cta_breve}
pricingData={pricingBreve}
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,
]}
className="border-transitorio bg-transitorio"
tipo={TipologiaPosizioneEnum.Transitorio}
/>
</TabsContent>
<TabsContent value="stabile">
<PricingComponent
title={t.prezzi.titolo_stabile}
cta={t.pricing_cmp.cta_stabile}
pricingData={pricingStabile}
opzioni={[
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
]}
className="border-stabile bg-stabile"
tipo={TipologiaPosizioneEnum.Stabile}
/>
</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>
<ExternalLink className="size-5" />
</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 (
<Card className={cn("w-full max-w-xl gap-4 py-4", className)}>
<CardHeader className="py-2">
<CardTitle className="pt-1 text-center text-3xl font-semibold">
{title}
</CardTitle>
<CardDescription className="sr-only">
Prezzo del servizio
</CardDescription>
</CardHeader>
<CardContent className="xs:px-6 flex w-full flex-col items-center justify-center gap-4 p-0 px-2 py-0 sm:gap-6 sm:py-4">
<div
className="xs:gap-x-4 xs:gap-y-1 flex w-full items-center justify-center gap-0"
data-role="wrapper"
>
<div className="xs:w-1/3 flex w-2/5 flex-col items-center gap-2">
<h2 className="text-2xl font-semibold" data-role="title">
{t.acconto}
</h2>
<p
className="xs:text-3xl text-2xl font-semibold sm:text-6xl"
data-role="amount"
>
{downPayment}
</p>
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="xs:w-1/3 flex w-2/5 flex-col items-center gap-2 text-center">
<h2 className="text-2xl font-semibold" data-role="title">
{t.saldo}
</h2>
<p
className="xs:text-3xl text-2xl font-semibold sm:text-6xl"
data-role="amount"
>
<AnimatedNumber
value={secondPayment}
format={customFormat}
precision={secondPayment % 1 === 0 ? 0 : 2}
/>
</p>
</div>
2025-08-04 17:45:44 +02:00
2025-08-28 18:27:07 +02:00
<div className="xs:w-1/3 flex w-2/5 flex-col items-center gap-2 text-center">
<h2 className="text-2xl font-semibold" data-role="title">
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
className="xs:text-3xl text-2xl font-semibold sm:text-6xl"
data-role="amount"
>
0
</span>
</div>
) : (
<Link
href={"/prezzi#contratti"}
className="flex flex-col justify-center leading-none sm:h-16"
>
<Button className="flex items-center gap-2">
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
key={idx}
size="default"
className={cn(
"xs:h-10 xs:px-4 xs:py-2 h-9 rounded-md border-none px-3",
idx === Value ? "animate-in fade-in-0" : "",
)}
variant={idx === Value ? "default" : "outline"}
onClick={() => {
ClearCarousel();
setValue(idx);
}}
>
{opzione}
</Button>
))}
</div>
<h2 className="text-center text-xl font-semibold text-white">{cta}</h2>
</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">
<CardTitle className="pt-1 text-center text-3xl font-semibold">
{title}
</CardTitle>
<CardDescription className="sr-only">
Prezzo del servizio
</CardDescription>
</CardHeader>
<CardContent className="xs:px-6 flex flex-col items-center justify-center gap-8 p-0 px-2 pt-4 pb-4">
<div className="xs:gap-4 flex w-full items-center justify-center gap-0">
<div className="flex flex-col items-center gap-2">
<p className="xs:text-6xl text-3xl font-semibold">
<AnimatedNumber value={p || 0} format={customFormat} />
</p>
</div>
</div>
<div className="flex w-full flex-wrap items-center justify-center gap-2">
{opzioni.map((opzione, idx) => (
<Button
key={idx}
size="default"
className={cn(
"xs:h-10 xs:px-4 xs:py-2 h-9 rounded-md border-none px-3",
idx === Value ? "animate-in fade-in-0" : "",
)}
variant={idx === Value ? "default" : "outline"}
onClick={() => {
ClearCarousel();
setValue(idx);
}}
>
{opzione}
</Button>
))}
</div>
<h2 className="text-center text-xl font-semibold text-white">{cta}</h2>
</CardContent>
</Card>
);
2025-08-04 17:45:44 +02:00
};
export const PrezziShow = () => {
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,
}));
return (
<div className="text-primary mx-auto flex w-full flex-col items-center justify-center gap-8 py-6 sm:max-w-5xl sm:flex-row">
<div className="flex w-full flex-col gap-2 text-start sm:w-2/5 sm:text-left">
<h1 className="text-primary text-3xl font-bold">
Quanto costa il servizio?
</h1>
<p className="text-muted-foreground text-lg">
Il nostro servizio è pensato per essere accessibile e conveniente, con
tariffe chiare e senza sorprese. Basate sul tipo di affitto che
desideri, le nostre tariffe sono suddivise in due categorie
principali: Affitto Transitorio e Affitto Stabile.
</p>
</div>
<Tabs defaultValue="transitorio" className="w-full sm:w-3/5">
<TabsList className="flex h-auto w-full items-center justify-between">
<TabsTrigger value="transitorio" className="w-full text-xl">
Affitto Transitorio
</TabsTrigger>
<TabsTrigger value="stabile" className="w-full text-xl">
Affitto Stabile
</TabsTrigger>
</TabsList>
<TabsContent value="transitorio">
<PricingComponent
title={t.prezzi.titolo_transitori}
cta={t.pricing_cmp.cta_breve}
pricingData={pricingBreve}
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,
]}
tipo={TipologiaPosizioneEnum.Transitorio}
className="border-transitorio bg-transitorio"
/>
</TabsContent>
<TabsContent value="stabile">
<PricingComponent
title={t.prezzi.titolo_stabile}
cta={t.pricing_cmp.cta_stabile}
pricingData={pricingStabile}
opzioni={[
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
]}
className="border-stabile bg-stabile"
tipo={TipologiaPosizioneEnum.Stabile}
/>
</TabsContent>
</Tabs>
</div>
);
2025-08-04 17:45:44 +02:00
};