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

391 lines
13 KiB
TypeScript
Raw Normal View History

2025-08-04 17:45:44 +02:00
import { useTranslation } from "~/providers/I18nProvider";
import { Button } from "~/components/ui/button";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
} from "~/components/ui/card";
import { cn } from "~/lib/utils";
import { AnimatedNumber } from "~/components/custom_ui/animated-number";
import { ExternalLink } from "lucide-react";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs";
import Link from "next/link";
import { api } from "~/utils/api";
import { LoadingPage } from "~/components/loading";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { useEffect, useState } from "react";
export const PricingChoice = () => {
const { t } = useTranslation();
const { data: prezziario, isLoading } =
api.prezziario.getPrezziPerTipologiaAll.useQuery();
if (isLoading) return <LoadingPage />;
if (!prezziario) return null;
const pricingBreve = prezziario.transitorio.saldi.map((s) => ({
downPayment: prezziario.transitorio.acconto.prezzo_cent / 100,
secondPayment: s.prezzo_cent / 100,
}));
const pricingStabile = prezziario.stabile.saldi.map((s) => ({
downPayment: prezziario.stabile.acconto.prezzo_cent / 100,
secondPayment: s.prezzo_cent / 100,
}));
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">
<TabsTrigger
value="transitorio"
className="w-full cursor-pointer px-2 text-xl sm:px-3"
>
Transitorio
</TabsTrigger>
<TabsTrigger
value="stabile"
className="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-rose-400 bg-rose-400"
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-indigo-400 bg-indigo-400"
tipo={TipologiaPosizioneEnum.Stabile}
/>
</TabsContent>
</Tabs>
<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>
);
};
type PricingData = { downPayment: number; secondPayment: number }[];
export const PricingComponent = ({
pricingData,
opzioni,
cta,
statico = false,
className,
title,
tipo,
}: {
pricingData: PricingData;
opzioni: string[];
title: string;
cta: string;
statico?: boolean;
className?: string;
tipo: TipologiaPosizioneEnum;
}) => {
const { t } = useTranslation();
const [Value, setValue] = useState<number>(0);
const p = pricingData[Value];
const downPayment = p?.downPayment || 0;
const secondPayment = p?.secondPayment || 0;
const customFormat = (num: number) => `${num}`;
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
useEffect(() => {
if (statico) return;
const interval = setInterval(() => {
setValue((prevValue) => (prevValue + 1) % pricingData.length);
}, 2500);
setIntervalId(interval);
return () => clearInterval(interval);
}, []);
const ClearCarousel = () => {
if (intervalId) {
clearInterval(intervalId);
}
};
return (
<Card
className={cn(
"w-full max-w-xl gap-4 border-orange-500 bg-orange-500 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>
<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} />
</p>
</div>
<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>
{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>
);
};
export const PricingComponentConsulenza = ({
pricingData,
opzioni,
cta,
statico = false,
className,
title,
}: {
pricingData: number[];
opzioni: string[];
title: string;
cta: string;
statico?: boolean;
className?: string;
}) => {
const [Value, setValue] = useState<number>(0);
const p = pricingData[Value];
const customFormat = (num: number) => `${num}`;
const [intervalId, setIntervalId] = useState<NodeJS.Timeout | null>(null);
useEffect(() => {
if (statico) return;
const interval = setInterval(() => {
setValue((prevValue) => (prevValue + 1) % pricingData.length);
}, 2500);
setIntervalId(interval);
return () => clearInterval(interval);
}, []);
const ClearCarousel = () => {
if (intervalId) {
clearInterval(intervalId);
}
};
return (
<Card
className={cn(
"w-full max-w-xl border-orange-500 bg-orange-500",
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>
);
};
export const PrezziShow = () => {
const { t } = useTranslation();
const { data: prezziario, isLoading } =
api.prezziario.getPrezziPerTipologiaAll.useQuery();
if (isLoading) return <LoadingPage />;
if (!prezziario) return null;
const pricingBreve = prezziario.transitorio.saldi.map((s) => ({
downPayment: prezziario.transitorio.acconto.prezzo_cent / 100,
secondPayment: s.prezzo_cent / 100,
}));
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-rose-400 bg-rose-400"
/>
</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-indigo-400 bg-indigo-400"
tipo={TipologiaPosizioneEnum.Stabile}
/>
</TabsContent>
</Tabs>
</div>
);
};