feat: enhance contact modal and guide components with translation support; update pricing display and improve accessibility

This commit is contained in:
Marco Pedone 2026-01-20 15:30:25 +01:00
parent 47be8bd54a
commit cb97919157
7 changed files with 188 additions and 69 deletions

View file

@ -11,22 +11,27 @@ import {
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { FormContattoAnnucio } from "~/forms/FormContattoAnnuncio"; import { FormContattoAnnucio } from "~/forms/FormContattoAnnuncio";
import { useAnnuncio } from "~/providers/AnnuncioProvider"; import { useAnnuncio } from "~/providers/AnnuncioProvider";
import { useTranslation } from "~/providers/I18nProvider";
export const ContattoAnnuncio = () => { export const ContattoAnnuncio = () => {
const { codice } = useAnnuncio(); const { codice } = useAnnuncio();
const { t } = useTranslation();
return ( return (
<Credenza> <Credenza>
<CredenzaTrigger asChild> <CredenzaTrigger asChild>
<Button className="w-full" variant="destructive"> <Button
<MessageCirclePlus /> Contattaci className="w-full text-lg md:text-base lg:text-lg"
size="xl"
variant="success"
>
<MessageCirclePlus /> {t.contatto.annuncio_cta.button}
</Button> </Button>
</CredenzaTrigger> </CredenzaTrigger>
<CredenzaContent className="max-h-[90vh]"> <CredenzaContent className="max-h-[90vh]">
<CredenzaHeader> <CredenzaHeader>
<CredenzaTitle>Contattaci per avere informazioni</CredenzaTitle> <CredenzaTitle>{t.contatto.annuncio_cta.title}</CredenzaTitle>
<CredenzaDescription> <CredenzaDescription>
Ti contatteremo il prima possibile per darti più informazioni {t.contatto.annuncio_cta.description}
</CredenzaDescription> </CredenzaDescription>
</CredenzaHeader> </CredenzaHeader>
<CredenzaBody className="max-h-[80vh] overflow-auto pb-5"> <CredenzaBody className="max-h-[80vh] overflow-auto pb-5">

View file

@ -1,5 +1,6 @@
import { HelpCircle } from "lucide-react"; import { HelpCircle } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useTranslation } from "~/providers/I18nProvider";
import { import {
ExpandableScreen, ExpandableScreen,
ExpandableScreenContent, ExpandableScreenContent,
@ -8,39 +9,8 @@ import {
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import { Separator } from "./ui/separator"; import { Separator } from "./ui/separator";
const texts: {
title: string;
parts: (string | { href: string; text: string })[];
}[] = [
{
title: "Costo del servizio",
parts: [
"Il costo del servizio è personalizzato in base alla tipologia e parametri della tua ricerca.",
"Per contattare i proprietari ti chiederemo un acconto di 35€ sul totale.",
"Potrai contattare tutti i proprietari degli immobili che desideri durante la durata del servizio.",
{ href: "/prezzi", text: "Scopri i prezzi" },
],
},
{
title: "Come procedere",
parts: [
"Contattaci per un immobile, prepareremo per te degli annunci compatibili alle tue esigenze e ti invieremo un invito via email per accedere alla piattaforma.",
"Una volta effettuato l'accesso, potrai acquistare il servizio e visualizzare i contatti dei proprietari degli immobili.",
{
href: "/annunci",
text: "Vai agli annunci",
},
],
},
{
title: "Nulla di fatto",
parts: [
"Se non trovi un immobile che soddisfi le tue esigenze, avrai 14 giorni di tempo per interrompere il servizio senza necessità di ulteriori pagamenti.",
],
},
];
export const ComeFunziona = () => { export const ComeFunziona = () => {
const { t } = useTranslation();
return ( return (
<ExpandableScreen <ExpandableScreen
contentRadius="14px" contentRadius="14px"
@ -48,15 +18,19 @@ export const ComeFunziona = () => {
triggerRadius="100px" triggerRadius="100px"
> >
<ExpandableScreenTrigger className="w-full"> <ExpandableScreenTrigger className="w-full">
<Button className="w-full"> <Button
className="w-full text-lg md:text-base lg:text-lg"
size="xl"
variant="info"
>
<HelpCircle /> <HelpCircle />
<span>Scopri come funziona</span> <span>{t.come_funziona_guida.title}</span>
</Button> </Button>
</ExpandableScreenTrigger> </ExpandableScreenTrigger>
<ExpandableScreenContent className="bg-primary text-primary-foreground"> <ExpandableScreenContent className="bg-info text-primary-foreground">
<div className="h-full space-y-6 p-6 py-12 sm:space-y-16 sm:px-10"> <div className="h-full space-y-6 p-6 py-12 sm:space-y-16 sm:px-10">
{texts.map((text, index) => ( {t.come_funziona_guida.features.map((text, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: <ok> // biome-ignore lint/suspicious/noArrayIndexKey: <ok>
<div className="space-y-6" key={index}> <div className="space-y-6" key={index}>
<div className="space-y-2"> <div className="space-y-2">

View file

@ -153,7 +153,7 @@ export const PricingComponent = ({
data-role="wrapper" data-role="wrapper"
> >
<div className="flex w-2/5 xs:w-1/3 flex-col items-center gap-2"> <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"> <h2 className="truncate font-semibold text-2xl" data-role="title">
{t.acconto} {t.acconto}
</h2> </h2>
<p <p

View file

@ -183,7 +183,7 @@ export const en: LangDict = {
}, },
accedi_per_procedere: "Log in to proceed", accedi_per_procedere: "Log in to proceed",
accetta: "Accept", accetta: "Accept",
acconto: "Down payment", acconto: "Dwn. Paym.",
account: { account: {
azieda_titolo: "Company Data", azieda_titolo: "Company Data",
azienda_o_pf: "Personal or Company?", azienda_o_pf: "Personal or Company?",
@ -295,6 +295,20 @@ export const en: LangDict = {
tutto: "All", tutto: "All",
}, },
titolo: "Search Properties", titolo: "Search Properties",
find_similar: "Find similar ads",
codice: "Code",
euro_mese: "€/month",
prezzo_giorno: "Price per day",
euro_giorno: "€/day",
fornitura: "Supply",
bagni: "bathrooms",
posti_auto: "parking sp.",
dati_immobile: "Property data",
p_terra: "ground floor",
p_rialzato: "raised floor",
piano: "floor",
in_aggiornamento: "Ad updating",
disponibile: "Availability",
}, },
annuncio_load_fail: { annuncio_load_fail: {
CTA: "Back", CTA: "Back",
@ -453,7 +467,44 @@ export const en: LangDict = {
titolo: "Contact Form", titolo: "Contact Form",
}, },
contatti: "Contacts", contatti: "Contacts",
come_funziona_guida: {
title: "How does Infoalloggi work?",
features: [
{
title: "Service cost",
parts: [
"The cost of the service is customized based on the type and parameters of your search.",
"To contact the owners, we will ask you for a deposit of €35 on the total.",
"You can contact all the owners of the properties you want during the duration of the service.",
{ href: "/prezzi", text: "Discover the prices" },
],
},
{
title: "How to proceed",
parts: [
"Contact us for a property, we will prepare compatible listings for you based on your needs and send you an email invitation to access the platform.",
"Once logged in, you can purchase the service and view the contacts of the property owners.",
{
href: "/annunci",
text: "Go to listings",
},
],
},
{
title: "No results",
parts: [
"If you do not find a property that meets your needs, you will have 14 days to cancel the service without any further payments.",
],
},
],
},
contatto: { contatto: {
annuncio_cta: {
button: "Are you interested? Contact us!",
title: "Contact us for more information",
description:
"We will contact you as soon as possible to provide more information about the property.",
},
apri_nav: "Open in navigator", apri_nav: "Open in navigator",
button: "Contact details", button: "Contact details",
contatta: "Contact the owners at appropriate times.", contatta: "Contact the owners at appropriate times.",

View file

@ -301,6 +301,20 @@ export const it: LangDict = {
tutto: "Tutto", tutto: "Tutto",
}, },
titolo: "Ricerca Immobili", titolo: "Ricerca Immobili",
find_similar: "Cerca annunci simili",
codice: "Codice",
euro_mese: "€/mese",
prezzo_giorno: "Prezzo al giorno",
euro_giorno: "€/gg",
fornitura: "Fornitura",
bagni: "bagni",
posti_auto: "posti auto",
dati_immobile: "Dati immobile",
p_terra: "piano terra",
p_rialzato: "piano rialz.",
piano: "piano",
in_aggiornamento: "Annuncio in aggiornamento",
disponibile: "Disponibilità",
}, },
annuncio_load_fail: { annuncio_load_fail: {
CTA: "Indietro", CTA: "Indietro",
@ -458,7 +472,44 @@ export const it: LangDict = {
titolo: "Form di contatto", titolo: "Form di contatto",
}, },
contatti: "Contatti", contatti: "Contatti",
come_funziona_guida: {
title: "Come funziona Infoalloggi?",
features: [
{
title: "Costo del servizio",
parts: [
"Il costo del servizio è personalizzato in base alla tipologia e parametri della tua ricerca.",
"Per contattare i proprietari ti chiederemo un acconto di 35€ sul totale.",
"Potrai contattare tutti i proprietari degli immobili che desideri durante la durata del servizio.",
{ href: "/prezzi", text: "Scopri i prezzi" },
],
},
{
title: "Come procedere",
parts: [
"Contattaci per un immobile, prepareremo per te degli annunci compatibili alle tue esigenze e ti invieremo un invito via email per accedere alla piattaforma.",
"Una volta effettuato l'accesso, potrai acquistare il servizio e visualizzare i contatti dei proprietari degli immobili.",
{
href: "/annunci",
text: "Vai agli annunci",
},
],
},
{
title: "Nulla di fatto",
parts: [
"Se non trovi un immobile che soddisfi le tue esigenze, avrai 14 giorni di tempo per interrompere il servizio senza necessità di ulteriori pagamenti.",
],
},
],
},
contatto: { contatto: {
annuncio_cta: {
button: "Sei Interessato? Contattaci!",
title: "Contattaci per avere informazioni",
description:
"Ti contatteremo il prima possibile per darti più informazioni sull'immobile.",
},
apri_nav: "Apri nel navigatore", apri_nav: "Apri nel navigatore",
button: "Dati di contatto", button: "Dati di contatto",
contatta: "Contattare i proprietari in orari consoni.", contatta: "Contattare i proprietari in orari consoni.",

View file

@ -252,6 +252,20 @@ export type LangDict = {
budgetMin: string; budgetMin: string;
budgetMax: string; budgetMax: string;
accordion_minifaq: Faq[]; accordion_minifaq: Faq[];
find_similar: string;
codice: string;
euro_mese: string;
prezzo_giorno: string;
euro_giorno: string;
fornitura: string;
bagni: string;
posti_auto: string;
dati_immobile: string;
p_terra: string;
p_rialzato: string;
piano: string;
in_aggiornamento: string;
disponibile: string;
}; };
faq: { faq: {
titolo: string; titolo: string;
@ -640,7 +654,20 @@ export type LangDict = {
già_verificata: string; già_verificata: string;
inviata: string; inviata: string;
}; };
come_funziona_guida: {
title: string;
features: {
title: string;
parts: (string | { href: string; text: string })[];
}[];
};
contatto: { contatto: {
annuncio_cta: {
button: string;
title: string;
description: string;
};
title: string; title: string;
button: string; button: string;
descrizione: string; descrizione: string;

View file

@ -1,9 +1,9 @@
import { import {
Armchair, Armchair,
ArrowUp01,
BadgeEuro, BadgeEuro,
Bath, Bath,
BedDouble, BedDouble,
Building2,
Car, Car,
Clock, Clock,
Copy, Copy,
@ -352,7 +352,7 @@ const Pricing = ({
</CollapsibleTrigger> </CollapsibleTrigger>
<CollapsibleContent className="mt-2"> <CollapsibleContent className="mt-2">
<PricingComponent <PricingComponent
className={cn(style)} className={cn(style, "max-w-full")}
cta={cta} cta={cta}
opzioni={opzioni} opzioni={opzioni}
pricingData={pricing} pricingData={pricing}
@ -441,7 +441,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
<Card className="relative w-full bg-secondary text-secondary-foreground outline outline-secondary-foreground"> <Card className="relative w-full bg-secondary text-secondary-foreground outline outline-secondary-foreground">
<CardHeader> <CardHeader>
<CardTitle className="text-center font-bold text-3xl md:pl-8 lg:pl-0"> <CardTitle className="text-center font-bold text-3xl md:pl-8 lg:pl-0">
Codice {data.codice} {t.annunci.codice} {data.codice}
</CardTitle> </CardTitle>
</CardHeader> </CardHeader>
<CardContent className="flex flex-col gap-4 p-4 py-0"> <CardContent className="flex flex-col gap-4 p-4 py-0">
@ -449,7 +449,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
<div className="relative"> <div className="relative">
<div className="rounded-md bg-primary-foreground p-2 text-center text-primary"> <div className="rounded-md bg-primary-foreground p-2 text-center text-primary">
{data.prezzo && (data.prezzo / 1e2).toFixed(2).replace(".", ",")}{" "} {data.prezzo && (data.prezzo / 1e2).toFixed(2).replace(".", ",")}{" "}
/mese {t.annunci.euro_mese}
</div> </div>
{data.prezzo && ( {data.prezzo && (
<InformationBubble <InformationBubble
@ -458,10 +458,11 @@ const CardInfos = ({ data }: { data: Annunci }) => {
> >
<div className="text-fxd-foreground"> <div className="text-fxd-foreground">
<span className="font-semibold text-sm"> <span className="font-semibold text-sm">
Prezzo al giorno {t.annunci.prezzo_giorno}
</span> </span>
<div className="text-sm"> <div className="text-sm">
{(data.prezzo / 1e2 / 30).toFixed(2).replace(".", ",")} /gg {(data.prezzo / 1e2 / 30).toFixed(2).replace(".", ",")}{" "}
{t.annunci.euro_giorno}
</div> </div>
</div> </div>
</InformationBubble> </InformationBubble>
@ -487,7 +488,7 @@ const CardInfos = ({ data }: { data: Annunci }) => {
</div> </div>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h3 className="px-2">Fornitura:</h3> <h3 className="px-2">{t.annunci.fornitura}:</h3>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<BedDouble /> <BedDouble />
@ -501,7 +502,9 @@ const CardInfos = ({ data }: { data: Annunci }) => {
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Bath /> <Bath />
<span>{data.numero_bagni || 0} bagni</span> <span>
{data.numero_bagni || 0} {t.annunci.bagni}
</span>
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Armchair /> <Armchair />
@ -509,12 +512,14 @@ const CardInfos = ({ data }: { data: Annunci }) => {
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Car /> <Car />
<span>{data.numero_postiauto || 0} posti auto</span> <span>
{data.numero_postiauto || 0} {t.annunci.posti_auto}
</span>
</div> </div>
</div> </div>
</div> </div>
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<h3 className="px-2">Dati immobile:</h3> <h3 className="px-2">{t.annunci.dati_immobile}:</h3>
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Ruler /> <Ruler />
@ -525,15 +530,20 @@ const CardInfos = ({ data }: { data: Annunci }) => {
</div> </div>
<div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary"> <div className="flex justify-center gap-2 rounded-md bg-primary-foreground p-2 text-primary">
<Building2 /> {data.piano !== null && data.piano !== "" && (
<span> <>
{(() => { <ArrowUp01 />
if (data.piano === "0") return "piano terra"; <span>
if (data.piano === "0.5") return "piano rialz."; {(() => {
if (data.piano) return `${data.piano}° piano`; if (data.piano === "0") return t.annunci.p_terra;
return ""; if (data.piano === "0.5") return t.annunci.p_rialzato;
})()} if (data.piano)
</span> return `${data.piano}° ${t.annunci.piano}`;
return "--";
})()}
</span>
</>
)}
</div> </div>
</div> </div>
</div> </div>
@ -542,12 +552,12 @@ const CardInfos = ({ data }: { data: Annunci }) => {
{data.stato === "Trattativa" ? ( {data.stato === "Trattativa" ? (
<div className="flex items-center justify-center gap-1 rounded-md py-1.5 text-trattativa outline-2 outline-trattativa dark:bg-secondary-foreground"> <div className="flex items-center justify-center gap-1 rounded-md py-1.5 text-trattativa outline-2 outline-trattativa dark:bg-secondary-foreground">
<TrafficCone className="inline-block size-5" /> <TrafficCone className="inline-block size-5" />
<span>Annuncio in aggiornamento</span> <span>{t.annunci.in_aggiornamento}</span>
</div> </div>
) : ( ) : (
<div className="flex items-center justify-center gap-1 rounded-md py-1.5 outline-2 outline-secondary-foreground"> <div className="flex items-center justify-center gap-1 rounded-md py-1.5 outline-2 outline-secondary-foreground">
<Clock className="inline-block size-5" /> <Clock className="inline-block size-5" />
Disponibilità:{" "} {t.annunci.disponibile}:{" "}
<span className="text-red-500"> <span className="text-red-500">
{handleConsegna({ {handleConsegna({
aggiornamento: t.card.in_aggiornamento, aggiornamento: t.card.in_aggiornamento,
@ -656,6 +666,7 @@ const AnnuncioFooter = ({
external_videos: string[]; external_videos: string[];
updated_at: Date | null; updated_at: Date | null;
}) => { }) => {
const { t } = useTranslation();
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<p className="px-2 font-semibold text-lg">Video:</p> <p className="px-2 font-semibold text-lg">Video:</p>
@ -698,15 +709,15 @@ const AnnuncioFooter = ({
href={{ href={{
pathname: "/annunci", pathname: "/annunci",
query: { query: {
comune: comune, c: comune,
consegna: consegna, f: consegna,
tipo: tipo, t: tipo,
}, },
}} }}
target="_blank" target="_blank"
> >
<Button className="my-10 w-full text-xl" variant="info"> <Button className="my-10 w-full text-xl" variant="secondary">
Cerca annunci simili <ExternalLink className="size-6" /> {t.annunci.find_similar} <ExternalLink className="size-6" />
</Button> </Button>
</Link> </Link>
</div> </div>