infoalloggi-monorepo/apps/infoalloggi/src/components/prezzi.tsx
Marco Pedone d7bfd8fdf1 feat: Update UI components and styles across multiple pages
- Enhanced the CardInfos component in [cod].tsx with improved styling and added TrafficCone icon for status indication.
- Increased dialog content width in prezziario.tsx and testi-stringhe.tsx for better layout.
- Replaced static badge elements with the Badge component in chi-siamo.tsx, contatti.tsx, guida.tsx, prezzi.tsx, and proprietari.tsx for consistent styling.
- Updated button components to use the Button component in contatti.tsx and proprietari.tsx for improved accessibility and styling.
- Modified the TrovaCasaCTA and FrequentSearches components in index.tsx for better visual consistency and updated color usage.
- Refactored global CSS variables for improved color management and added new color variables for better theme support.
- Implemented dynamic loading for the KabanExample component in test.tsx to enhance performance.
2025-11-14 17:21:21 +01:00

385 lines
12 KiB
TypeScript

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";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import { cn } from "~/lib/utils";
import { useTranslation } from "~/providers/I18nProvider";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { api } from "~/utils/api";
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="flex flex-col items-center justify-center gap-4">
<h2 className="font-semibold text-3xl">Scegli il tuo servizio</h2>
<Tabs className="mb-2 w-full max-w-xl" defaultValue="transitorio">
<TabsList className="flex h-auto w-full items-center justify-between gap-1 p-1">
<TabsTrigger
className="w-full cursor-pointer border-0 bg-transitorio px-2 text-xl sm:px-3 dark:border-0 dark:text-fxd-foreground [&[data-state=active]]:border-0 [&[data-state=active]]:bg-transitorio dark:[&[data-state=active]]:border-0 dark:[&[data-state=active]]:bg-transitorio dark:[&[data-state=active]]:text-fxd-foreground"
value="transitorio"
>
Transitorio
</TabsTrigger>
<TabsTrigger
className="w-full cursor-pointer border-0 bg-stabile px-2 text-xl sm:px-3 dark:border-0 dark:text-fxd-foreground [&[data-state=active]]:border-0 [&[data-state=active]]:bg-stabile dark:[&[data-state=active]]:border-0 dark:[&[data-state=active]]:bg-stabile dark:[&[data-state=active]]:text-fxd-foreground"
value="stabile"
>
Stabile
</TabsTrigger>
</TabsList>
<TabsContent className="w-full" value="transitorio">
<PricingComponent
className="border-transitorio bg-transitorio text-fxd-foreground"
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,
]}
pricingData={pricingBreve}
tipo={TipologiaPosizioneEnum.Transitorio}
title={t.prezzi.titolo_transitori}
/>
</TabsContent>
<TabsContent value="stabile">
<PricingComponent
className="border-stabile bg-stabile text-fxd-foreground"
cta={t.pricing_cmp.cta_stabile}
opzioni={[
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
]}
pricingData={pricingStabile}
tipo={TipologiaPosizioneEnum.Stabile}
title={t.prezzi.titolo_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 py-4", className)}>
<CardHeader className="py-2">
<CardTitle className="pt-1 text-center font-semibold text-3xl">
{title}
</CardTitle>
<CardDescription className="sr-only">
Prezzo del servizio
</CardDescription>
</CardHeader>
<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">
<div
className="flex w-full items-center justify-center gap-0 xs:gap-x-4 xs:gap-y-1"
data-role="wrapper"
>
<div className="flex w-2/5 xs:w-1/3 flex-col items-center gap-2">
<h2 className="font-semibold text-2xl" data-role="title">
{t.acconto}
</h2>
<p
className="font-semibold text-2xl xs:text-3xl sm:text-6xl"
data-role="amount"
>
{downPayment}
</p>
</div>
<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">
{t.saldo}
</h2>
<p
className="font-semibold text-2xl xs:text-3xl sm:text-6xl"
data-role="amount"
>
<AnimatedNumber
format={customFormat}
precision={secondPayment % 1 === 0 ? 0 : 2}
value={secondPayment}
/>
</p>
</div>
<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">
Contratto
</h2>
{tipo === TipologiaPosizioneEnum.Transitorio ? (
<div className="flex flex-col justify-center leading-none">
<span
className="font-semibold text-2xl xs:text-3xl sm:text-6xl"
data-role="amount"
>
0
</span>
</div>
) : (
<Link
className="flex h-8 xs:h-10 flex-col justify-center leading-none sm:h-15"
href={"/prezzi#contratti"}
id="contratto-link"
>
<Button>
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(
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
idx === Value ? "fade-in-0 animate-in" : "",
)}
key={opzione}
onClick={() => {
ClearCarousel();
setValue(idx);
}}
size="default"
variant={idx === Value ? "default" : "outline"}
>
{opzione}
</Button>
))}
</div>
<h2 className="text-center font-semibold text-white text-xl">{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", className)}>
<CardHeader className="p-3 px-5">
<CardTitle className="pt-1 text-center font-semibold text-3xl">
{title}
</CardTitle>
<CardDescription className="sr-only">
Prezzo del servizio
</CardDescription>
</CardHeader>
<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">
<div className="flex flex-col items-center gap-2">
<p className="font-semibold text-3xl xs:text-6xl">
<AnimatedNumber format={customFormat} value={p || 0} />
</p>
</div>
</div>
<div className="flex w-full flex-wrap items-center justify-center gap-2">
{opzioni.map((opzione, idx) => (
<Button
className={cn(
"h-9 xs:h-10 rounded-md border-none px-3 xs:px-4 xs:py-2",
idx === Value ? "fade-in-0 animate-in" : "",
)}
key={opzione}
onClick={() => {
ClearCarousel();
setValue(idx);
}}
size="default"
variant={idx === Value ? "default" : "outline"}
>
{opzione}
</Button>
))}
</div>
<h2 className="text-center font-semibold text-white text-xl">{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="mx-auto flex w-full flex-col items-center justify-center gap-8 py-6 text-primary 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="font-bold text-3xl text-primary">
Quanto costa il servizio?
</h1>
<p className="text-lg text-muted-foreground">
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 className="w-full sm:w-3/5" defaultValue="transitorio">
<TabsList className="flex h-auto w-full items-center justify-between">
<TabsTrigger className="w-full text-xl" value="transitorio">
Affitto Transitorio
</TabsTrigger>
<TabsTrigger className="w-full text-xl" value="stabile">
Affitto Stabile
</TabsTrigger>
</TabsList>
<TabsContent value="transitorio">
<PricingComponent
className="border-transitorio bg-transitorio"
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,
]}
pricingData={pricingBreve}
tipo={TipologiaPosizioneEnum.Transitorio}
title={t.prezzi.titolo_transitori}
/>
</TabsContent>
<TabsContent value="stabile">
<PricingComponent
className="border-stabile bg-stabile"
cta={t.pricing_cmp.cta_stabile}
opzioni={[
t.pricing_cmp.stabile_option1,
t.pricing_cmp.stabile_option2,
]}
pricingData={pricingStabile}
tipo={TipologiaPosizioneEnum.Stabile}
title={t.prezzi.titolo_stabile}
/>
</TabsContent>
</Tabs>
</div>
);
};