Refactor Annuncio Card Component and Remove Legacy Code
- Updated `CardAnnuncio` component to enhance functionality and styling. - Removed `CardAnnuncio2` component as it was redundant. - Added new icons and badges for better visual representation of availability and status. - Improved layout and responsiveness of the card. - Updated translations in English and Italian for consistency. - Enhanced pagination controls in the `Annunci` page with new button styles. - Adjusted footer components to accept additional class names for customization. - Cleaned up unused CSS animations and styles in `globals.css`. - Modified server-side controller to include homepage field in the Annuncio data structure.
This commit is contained in:
parent
4ca5c59ffe
commit
1fc855393f
17 changed files with 316 additions and 604 deletions
|
|
@ -27,6 +27,7 @@ import { WhatsAppIcon2 } from "~/components/svgs";
|
|||
import { Button } from "~/components/ui/button";
|
||||
import { Separator } from "~/components/ui/separator";
|
||||
import { UserViewContext, UserViewProvider } from "~/lib/userViewContext";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import {
|
||||
EnforcedSessionContext,
|
||||
|
|
@ -37,41 +38,55 @@ import type { ValidSession } from "~/server/api/trpc";
|
|||
import { api } from "~/utils/api";
|
||||
|
||||
interface Props {
|
||||
bodyClassName?: string;
|
||||
containerClassName?: string;
|
||||
children: ReactNode;
|
||||
noFooter?: boolean;
|
||||
footerClassName?: string;
|
||||
}
|
||||
export const Layout = ({ children, noFooter }: Props) => {
|
||||
export const Layout = ({
|
||||
children,
|
||||
noFooter,
|
||||
bodyClassName,
|
||||
containerClassName,
|
||||
footerClassName,
|
||||
}: Props) => {
|
||||
const { data: bannerData } = api.settings.getBannerData.useQuery({
|
||||
area_riservata: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen w-full flex-col">
|
||||
<div className={cn("flex min-h-screen w-full flex-col", bodyClassName)}>
|
||||
<SiteHeader />
|
||||
{bannerData?.map((banner) => (
|
||||
<div key={`banner-elem-${banner.idbanner}`}>
|
||||
{BannerFactory(banner)}
|
||||
</div>
|
||||
))}
|
||||
<main className="h-full flex-1 grow">{children}</main>
|
||||
<main className={cn("h-full flex-1 grow", containerClassName)}>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
{!noFooter && <Footer />}
|
||||
{!noFooter && <Footer className={footerClassName} />}
|
||||
{/* <CookieConsent /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface AreaRiservataLayoutProps extends Props {
|
||||
ignoreSessionCheck?: boolean;
|
||||
noSidebar?: boolean;
|
||||
}
|
||||
|
||||
export const AreaRiservataLayout = ({
|
||||
children,
|
||||
noSidebar,
|
||||
noFooter,
|
||||
ignoreSessionCheck,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
noSidebar?: boolean;
|
||||
noFooter?: boolean;
|
||||
ignoreSessionCheck?: boolean;
|
||||
}) => {
|
||||
bodyClassName,
|
||||
containerClassName,
|
||||
footerClassName,
|
||||
}: AreaRiservataLayoutProps) => {
|
||||
const { data: bannerData } = api.settings.getBannerData.useQuery({
|
||||
area_riservata: true,
|
||||
});
|
||||
|
|
@ -79,43 +94,70 @@ export const AreaRiservataLayout = ({
|
|||
if (!ignoreSessionCheck) {
|
||||
if (session.status === "pending")
|
||||
return (
|
||||
<div className="flex h-full min-h-screen flex-col text-clip">
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full min-h-screen flex-col text-clip",
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
<SiteHeader />
|
||||
|
||||
<main className="flex h-full flex-1 overflow-auto">
|
||||
<main
|
||||
className={cn(
|
||||
"flex h-full flex-1 overflow-auto",
|
||||
containerClassName,
|
||||
)}
|
||||
>
|
||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||
<LoadingPage />
|
||||
</div>
|
||||
</main>
|
||||
{!noFooter && <MiniFooter />}
|
||||
{!noFooter && <MiniFooter className={footerClassName} />}
|
||||
</div>
|
||||
);
|
||||
if (session.status !== "success" || !session.user) {
|
||||
window.location.reload();
|
||||
return (
|
||||
<div className="flex h-full min-h-screen flex-col text-clip">
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full min-h-screen flex-col text-clip",
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
<SiteHeader />
|
||||
|
||||
<main className="flex h-full flex-1 overflow-auto">
|
||||
<main
|
||||
className={cn(
|
||||
"flex h-full flex-1 overflow-auto",
|
||||
containerClassName,
|
||||
)}
|
||||
>
|
||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||
<LoadingPage />
|
||||
</div>
|
||||
</main>
|
||||
{!noFooter && <MiniFooter />}
|
||||
{!noFooter && <MiniFooter className={footerClassName} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full min-h-screen flex-col text-clip">
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full min-h-screen flex-col text-clip",
|
||||
bodyClassName,
|
||||
)}
|
||||
>
|
||||
<SiteHeader />
|
||||
{bannerData?.map((banner) => (
|
||||
<div key={`banner-elem-${banner.idbanner}`}>
|
||||
{BannerFactory(banner)}
|
||||
</div>
|
||||
))}
|
||||
<main className="flex h-full flex-1 overflow-auto">
|
||||
<main
|
||||
className={cn("flex h-full flex-1 overflow-auto", containerClassName)}
|
||||
>
|
||||
<div className="flex h-auto w-full flex-col md:flex-row">
|
||||
{noSidebar ? null : (
|
||||
<Sidebar
|
||||
|
|
@ -128,7 +170,7 @@ export const AreaRiservataLayout = ({
|
|||
</EnforcedSessionContext.Provider>
|
||||
</div>
|
||||
</main>
|
||||
{!noFooter && <MiniFooter />}
|
||||
{!noFooter && <MiniFooter className={footerClassName} />}
|
||||
|
||||
{/* <CookieConsent /> */}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { cn } from "~/lib/utils";
|
|||
|
||||
interface AccordionCompProps extends InputHTMLAttributes<HTMLDivElement> {
|
||||
texts: Faq[];
|
||||
defaultOpen?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -29,7 +30,7 @@ interface AccordionCompProps extends InputHTMLAttributes<HTMLDivElement> {
|
|||
* ```
|
||||
*/
|
||||
export const AccordionComp = forwardRef<HTMLDivElement, AccordionCompProps>(
|
||||
({ texts, className }, ref) => {
|
||||
({ texts, defaultOpen, className }, ref) => {
|
||||
if (!texts || texts.length === 0) {
|
||||
return null; // Return null if no texts are provided
|
||||
}
|
||||
|
|
@ -38,7 +39,14 @@ export const AccordionComp = forwardRef<HTMLDivElement, AccordionCompProps>(
|
|||
className={cn("mx-auto flex max-w-xl justify-center", className)}
|
||||
ref={ref}
|
||||
>
|
||||
<Accordion className="w-full" collapsible type="single">
|
||||
<Accordion
|
||||
className="w-full"
|
||||
collapsible
|
||||
defaultValue={
|
||||
defaultOpen !== undefined ? `item-${defaultOpen}` : undefined
|
||||
}
|
||||
type="single"
|
||||
>
|
||||
{texts.map((text, index) => (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: <index is safe here>
|
||||
<AccordionItem key={`ac-item-${index}`} value={`item-${index}`}>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { CardAnnuncio } from "~/components/annuncio_card2";
|
||||
import { getTitoloTranslation } from "~/lib/annuncio_details";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
import { CardAnnuncio } from "~/components/annuncio_card";
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
|
||||
export const AnnunciGrid = ({ pagedata }: { pagedata: AnnuncioRicerca[] }) => {
|
||||
return (
|
||||
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<div className="relative z-0 mx-auto grid max-w-7xl grid-flow-row grid-cols-1 gap-4 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
|
||||
{pagedata.map((annuncio) => (
|
||||
<CardAnnuncio key={annuncio.codice} {...annuncio} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
import { BedDouble, CalendarClock, Maximize2, Ruler } from "lucide-react";
|
||||
import {
|
||||
BedDouble,
|
||||
CalendarClock,
|
||||
MapPin,
|
||||
Maximize2,
|
||||
Ruler,
|
||||
Siren,
|
||||
Star,
|
||||
TrafficCone,
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
|
|
@ -20,69 +29,95 @@ import {
|
|||
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
|
||||
import { cn, formatCurrency } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
|
||||
import type { AnnuncioRicerca } from "~/server/controllers/annunci.controller";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { VideoPlayer } from "./videoPlayer";
|
||||
|
||||
type CardAnnuncioProps = {
|
||||
id: number;
|
||||
codice: string;
|
||||
prezzo: number;
|
||||
titolo: string | null;
|
||||
mq: string | null;
|
||||
comune: string | null;
|
||||
provincia: string | null;
|
||||
consegna: number | null;
|
||||
camere: number | null;
|
||||
//immagini: string[] | undefined;
|
||||
tipo: string | null;
|
||||
stato: string | null;
|
||||
type CardAnnuncioProps = AnnuncioRicerca & {
|
||||
className?: string;
|
||||
videos?: string[] | undefined;
|
||||
updated_at: Date | null;
|
||||
images: { img: string; thumb: string }[];
|
||||
};
|
||||
|
||||
export const CardAnnuncio = ({
|
||||
id,
|
||||
codice,
|
||||
prezzo,
|
||||
titolo,
|
||||
titolo_it,
|
||||
titolo_en,
|
||||
mq,
|
||||
comune,
|
||||
provincia,
|
||||
consegna,
|
||||
camere,
|
||||
//immagini,
|
||||
numero_camere: camere,
|
||||
tipo,
|
||||
stato,
|
||||
className,
|
||||
videos,
|
||||
url_video: videos,
|
||||
updated_at,
|
||||
images,
|
||||
homepage,
|
||||
}: CardAnnuncioProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { t, locale } = useTranslation();
|
||||
const currentMonth = new Date().getMonth() + 1;
|
||||
const isAvailableNow = consegna === 0 || consegna === currentMonth;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-[464px] rounded-md bg-white shadow-neutral-100 shadow-sm outline outline-neutral-400 hover:shadow-md hover:outline-neutral-600",
|
||||
"h-[31.1rem] rounded-md bg-white shadow-neutral-100 shadow-sm hover:shadow-md hover:outline-neutral-600",
|
||||
className,
|
||||
)}
|
||||
id={`card-annuncio-${id}`}
|
||||
>
|
||||
<Link
|
||||
className="block p-2"
|
||||
//target="_blank"
|
||||
className="flex h-full flex-col gap-2 p-2"
|
||||
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
||||
>
|
||||
<div className="group relative overflow-clip text-clip rounded-md outline outline-neutral-300">
|
||||
<div
|
||||
className={cn("group relative overflow-clip text-clip rounded-md")}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"absolute inset-0 z-20 size-full rounded-md",
|
||||
homepage &&
|
||||
(isAvailableNow
|
||||
? "border-[.35rem] border-green-500"
|
||||
: "border-[.35rem] border-blue-500"),
|
||||
|
||||
stato === "Trattativa" && "border-[.35rem] border-violet-500",
|
||||
)}
|
||||
></div>
|
||||
{stato !== "Trattativa" && homepage && (
|
||||
<div className="absolute top-1 z-20 flex w-full justify-center px-2">
|
||||
<Badge
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-t-none px-1 py-0.5 font-semibold text-base text-white shadow-lg [&>svg]:size-4.5",
|
||||
isAvailableNow ? "bg-green-500" : "bg-blue-500",
|
||||
)}
|
||||
>
|
||||
{isAvailableNow ? (
|
||||
<>
|
||||
<span>Disponibile Subito</span>
|
||||
<Siren />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span>In Evidenza</span>
|
||||
<Star />
|
||||
</>
|
||||
)}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
{stato === "Trattativa" && (
|
||||
<div>
|
||||
<div className="absolute z-20 h-56 w-full">
|
||||
<div className="absolute bottom-0 w-full touch-none select-none rounded-b-md bg-violet-500 text-center text-lg text-white">
|
||||
Annuncio in Trattativa
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute top-1 z-20 flex w-full justify-center px-2">
|
||||
<Badge
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-t-none bg-violet-500 px-1 py-0.5 font-semibold text-base text-white shadow-lg [&>svg]:size-4.5",
|
||||
)}
|
||||
>
|
||||
<span>In Trattativa</span>
|
||||
<TrafficCone />
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -93,7 +128,7 @@ export const CardAnnuncio = ({
|
|||
<ImageFlbk
|
||||
alt={t.card.alt_immagine}
|
||||
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
||||
className={"h-56 w-full rounded-md object-cover"}
|
||||
className={"h-64 w-full object-cover"}
|
||||
height={1080}
|
||||
priority={idx === 0}
|
||||
src={`/storage-api/get/${img.img}?image=true`}
|
||||
|
|
@ -149,27 +184,27 @@ export const CardAnnuncio = ({
|
|||
</div>
|
||||
</Carousel>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"mt-2 flex w-full items-center justify-center rounded-md",
|
||||
tipo === "Transitorio" && "bg-transitorio",
|
||||
tipo === "Stabile" && "bg-stabile",
|
||||
tipo === "Cessione" && "bg-blue-400",
|
||||
tipo === "Vendita" && "bg-green-400",
|
||||
)}
|
||||
>
|
||||
<div className="font-semibold text-white">{tipo}</div>
|
||||
</div>
|
||||
<div className="mt-2 space-y-2">
|
||||
<div className="flex flex-row justify-around">
|
||||
<div className="text-center">
|
||||
<div className="flex h-full grow flex-col gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex w-full items-center justify-center rounded-md",
|
||||
tipo === "Transitorio" && "bg-transitorio",
|
||||
tipo === "Stabile" && "bg-stabile",
|
||||
tipo === "Cessione" && "bg-blue-400",
|
||||
tipo === "Vendita" && "bg-green-400",
|
||||
)}
|
||||
>
|
||||
<div className="font-semibold text-white">Affitto {tipo}</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-between gap-3 px-4">
|
||||
<div className="w-full text-center">
|
||||
<span className="sr-only">{t.card.codice}</span>
|
||||
|
||||
<span className="font-semibold text-red-600 text-xl">
|
||||
<span className="font-semibold text-red-500 text-xl">
|
||||
Cod: {codice}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-full text-center">
|
||||
<span className="sr-only">{t.card.prezzo}</span>
|
||||
|
||||
<span className="font-semibold text-xl">
|
||||
|
|
@ -177,58 +212,61 @@ export const CardAnnuncio = ({
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full grow flex-col justify-between">
|
||||
<div className="px-4 text-center text-muted-foreground">
|
||||
<span className="sr-only">{t.card.titolo}</span>
|
||||
|
||||
<div className="mb-3 text-center text-muted-foreground">
|
||||
<span className="sr-only">{t.card.titolo}</span>
|
||||
|
||||
<span className="line-clamp-2 min-h-[3.5rem] font-medium text-base md:text-lg">
|
||||
{titolo}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-center text-muted-foreground">
|
||||
<span className="sr-only">{t.card.indirizzo}</span>
|
||||
|
||||
<span className="font-medium text-sm">
|
||||
{comune && provincia && `${comune} (${provincia})`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center justify-around gap-2 text-muted-foreground text-xs">
|
||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
||||
<Ruler className="size-4 text-indigo-700" />
|
||||
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{mq ? Number.parseFloat(mq) : 0}{" "}
|
||||
<span>
|
||||
m<sup>2</sup>
|
||||
<span className="line-clamp-3 overflow-ellipsis font-medium text-base">
|
||||
{locale === "it" ? titolo_it : titolo_en}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="text-center text-muted-foreground">
|
||||
<span className="sr-only">{t.card.indirizzo}</span>
|
||||
<MapPin className="mr-1 mb-1 inline-block size-4" />
|
||||
<span className="font-medium text-sm">
|
||||
{comune && provincia && `${comune} (${provincia})`}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
||||
<BedDouble className="size-4 text-indigo-700" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{camereTesti({ camere: camere, testi: t.card })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
||||
<CalendarClock className="size-4 text-indigo-700" />
|
||||
<div className="flex h-9 items-center justify-around gap-1.5 text-neutral-700 text-xs">
|
||||
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-neutral-100">
|
||||
<Ruler className="size-4 text-neutral-900" />
|
||||
|
||||
<div>
|
||||
<p className="truncate font-medium">
|
||||
{handleConsegna({
|
||||
aggiornamento: t.card.in_aggiornamento,
|
||||
consegna: consegna,
|
||||
consegna_da: t.card.consegna_da,
|
||||
mesi: t.preferenze.mesi,
|
||||
subito: t.card.consegna_subito,
|
||||
})}
|
||||
</p>
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{mq ? Number.parseFloat(mq) : 0}{" "}
|
||||
<span>
|
||||
m<sup>2</sup>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-neutral-100">
|
||||
<BedDouble className="size-4 text-neutral-900" />
|
||||
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{camereTesti({ camere: camere, testi: t.card })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full items-center justify-center gap-1 rounded-md bg-neutral-100">
|
||||
<CalendarClock className="size-4 text-neutral-900" />
|
||||
|
||||
<div>
|
||||
<p className="truncate font-medium">
|
||||
{handleConsegna({
|
||||
aggiornamento: t.card.in_aggiornamento,
|
||||
consegna: consegna,
|
||||
consegna_da: t.card.consegna_da,
|
||||
mesi: t.preferenze.mesi,
|
||||
subito: t.card.consegna_subito,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -273,7 +311,7 @@ export const CarouselAnnuncio = ({
|
|||
<ImageFlbk
|
||||
alt={img}
|
||||
className={cn(
|
||||
"aspect-square max-h-92 w-full rounded-md object-cover sm:max-h-80",
|
||||
"aspect-square max-h-92 w-full rounded-md object-cover sm:max-h-80 xl:max-h-[25rem]",
|
||||
immagini.length === 1 && "object-contain",
|
||||
)}
|
||||
height={400}
|
||||
|
|
|
|||
|
|
@ -1,404 +0,0 @@
|
|||
import { BedDouble, CalendarClock, Maximize2, Ruler } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselNext,
|
||||
CarouselPrevious,
|
||||
} from "~/components/ui/carousel";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "~/components/ui/dialog";
|
||||
import { camereTesti, handleConsegna } from "~/lib/annuncio_details";
|
||||
import { cn, formatCurrency } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
|
||||
import { VideoPlayer } from "./videoPlayer";
|
||||
|
||||
type CardAnnuncioProps = {
|
||||
id: number;
|
||||
codice: string;
|
||||
prezzo: number;
|
||||
titolo: string | null;
|
||||
mq: string | null;
|
||||
comune: string | null;
|
||||
provincia: string | null;
|
||||
consegna: number | null;
|
||||
camere: number | null;
|
||||
//immagini: string[] | undefined;
|
||||
tipo: string | null;
|
||||
stato: string | null;
|
||||
className?: string;
|
||||
videos?: string[] | undefined;
|
||||
updated_at: Date | null;
|
||||
images: { img: string; thumb: string }[];
|
||||
};
|
||||
|
||||
export const CardAnnuncio = ({
|
||||
id,
|
||||
codice,
|
||||
prezzo,
|
||||
titolo,
|
||||
mq,
|
||||
comune,
|
||||
provincia,
|
||||
consegna,
|
||||
camere,
|
||||
//immagini,
|
||||
tipo,
|
||||
stato,
|
||||
className,
|
||||
videos,
|
||||
updated_at,
|
||||
images,
|
||||
}: CardAnnuncioProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"h-[464px] rounded-md bg-white shadow-neutral-100 shadow-sm hover:shadow-md hover:outline-neutral-600",
|
||||
className,
|
||||
)}
|
||||
id={`card-annuncio-${id}`}
|
||||
>
|
||||
<Link
|
||||
className="block p-2"
|
||||
//target="_blank"
|
||||
href={`/annuncio/${codice}`} //duration-700 ease-in-out animate-in fade-in
|
||||
>
|
||||
<div className="group relative overflow-clip text-clip rounded-md">
|
||||
<div className="absolute top-2 left-2 z-20 rounded-md bg-white px-2 py-1 font-semibold text-neutral-800 text-sm shadow-md shadow-neutral-300 backdrop-blur-sm">
|
||||
Cod: {codice}
|
||||
</div>
|
||||
{stato === "Trattativa" && (
|
||||
<div>
|
||||
<div className="absolute z-20 h-56 w-full">
|
||||
<div className="absolute bottom-0 w-full touch-none select-none rounded-b-md bg-violet-500 text-center text-lg text-white">
|
||||
Annuncio in Trattativa
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Carousel opts={{ loop: true }}>
|
||||
<CarouselContent>
|
||||
{images?.map((img, idx) => (
|
||||
<CarouselItem key={`${img.img}key`}>
|
||||
<ImageFlbk
|
||||
alt={t.card.alt_immagine}
|
||||
blurDataURL={`/storage-api/get/${img.thumb}?image=true`}
|
||||
className={"h-64 w-full object-cover"}
|
||||
height={1080}
|
||||
priority={idx === 0}
|
||||
src={`/storage-api/get/${img.img}?image=true`}
|
||||
width={1920}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
{videos?.map((video) => {
|
||||
if (!video) return null;
|
||||
if (video.includes("youtu")) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CarouselItem
|
||||
className={cn("group relative")}
|
||||
key={`videoplayer-${video}`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<VideoPlayer
|
||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
||||
className="h-56"
|
||||
coverImage={
|
||||
images?.[0]
|
||||
? `/storage-api/get/${images[0].img}?image=true`
|
||||
: ""
|
||||
}
|
||||
key={`videoplayer-${video}`}
|
||||
videoSrc={video}
|
||||
/>
|
||||
</CarouselItem>
|
||||
);
|
||||
})}
|
||||
</CarouselContent>
|
||||
|
||||
<div
|
||||
className="block opacity-0 transition-all duration-200 group-hover:opacity-60 disabled:group-hover:opacity-0"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
>
|
||||
<CarouselPrevious
|
||||
className="left-2 cursor-pointer"
|
||||
disabled={!images || images.length === 1}
|
||||
/>
|
||||
<CarouselNext
|
||||
className="right-2 cursor-pointer"
|
||||
disabled={!images || images.length === 1}
|
||||
/>
|
||||
</div>
|
||||
</Carousel>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
"mt-2 flex w-full items-center justify-center rounded-md",
|
||||
tipo === "Transitorio" && "bg-transitorio",
|
||||
tipo === "Stabile" && "bg-stabile",
|
||||
tipo === "Cessione" && "bg-blue-400",
|
||||
tipo === "Vendita" && "bg-green-400",
|
||||
)}
|
||||
>
|
||||
<div className="font-semibold text-white">{tipo}</div>
|
||||
</div>
|
||||
<div className="mt-2 space-y-2">
|
||||
<div className="flex flex-row justify-around">
|
||||
<div className="text-center">
|
||||
<span className="sr-only">{t.card.codice}</span>
|
||||
|
||||
<span className="font-semibold text-red-600 text-xl">
|
||||
Cod: {codice}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<span className="sr-only">{t.card.prezzo}</span>
|
||||
|
||||
<span className="font-semibold text-xl">
|
||||
{formatCurrency(prezzo / 1e2)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-3 text-center text-muted-foreground">
|
||||
<span className="sr-only">{t.card.titolo}</span>
|
||||
|
||||
<span className="line-clamp-2 min-h-[3.5rem] font-medium text-base md:text-lg">
|
||||
{titolo}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-center text-muted-foreground">
|
||||
<span className="sr-only">{t.card.indirizzo}</span>
|
||||
|
||||
<span className="font-medium text-sm">
|
||||
{comune && provincia && `${comune} (${provincia})`}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center justify-around gap-2 text-muted-foreground text-xs">
|
||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
||||
<Ruler className="size-4 text-indigo-700" />
|
||||
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{mq ? Number.parseFloat(mq) : 0}{" "}
|
||||
<span>
|
||||
m<sup>2</sup>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
||||
<BedDouble className="size-4 text-indigo-700" />
|
||||
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{camereTesti({ camere: camere, testi: t.card })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-center gap-1 rounded-md bg-neutral-100 py-3">
|
||||
<CalendarClock className="size-4 text-indigo-700" />
|
||||
|
||||
<div>
|
||||
<p className="truncate font-medium">
|
||||
{handleConsegna({
|
||||
aggiornamento: t.card.in_aggiornamento,
|
||||
consegna: consegna,
|
||||
consegna_da: t.card.consegna_da,
|
||||
mesi: t.preferenze.mesi,
|
||||
subito: t.card.consegna_subito,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CarouselAnnuncio = ({
|
||||
single,
|
||||
immagini,
|
||||
videos,
|
||||
updated_at,
|
||||
}: {
|
||||
single?: boolean;
|
||||
immagini: string[] | null;
|
||||
videos: string[] | null;
|
||||
updated_at: Date | null;
|
||||
}) => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const [idxModal, setIdxModal] = useState(0);
|
||||
function handleOpenModal(position: number) {
|
||||
setIdxModal(position);
|
||||
setOpenModal(true);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto my-4 w-full text-clip rounded-md">
|
||||
<div className="relative">
|
||||
<Carousel opts={{ align: "start", loop: true }}>
|
||||
<CarouselContent>
|
||||
{immagini?.map((img, idx) => (
|
||||
<CarouselItem
|
||||
className={cn(
|
||||
"group relative flex items-center justify-center",
|
||||
immagini && immagini.length === 1
|
||||
? ""
|
||||
: !single && "md:basis-1/2 lg:basis-1/3",
|
||||
)}
|
||||
key={`${img}-cF`}
|
||||
>
|
||||
<ImageFlbk
|
||||
alt={img}
|
||||
className={cn(
|
||||
"aspect-square max-h-92 w-full rounded-md object-cover sm:max-h-80",
|
||||
immagini.length === 1 && "object-contain",
|
||||
)}
|
||||
height={400}
|
||||
onClick={() => handleOpenModal(idx)}
|
||||
priority={idx === 0}
|
||||
src={`/storage-api/get/${img}?image=true&${updated_at?.toString() || new Date().toString()}`}
|
||||
width={800}
|
||||
/>
|
||||
<Maximize2
|
||||
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-white/70 stroke-2 p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
||||
onClick={() => handleOpenModal(idx)}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
{videos?.map((video, idx) => {
|
||||
if (!video) return null;
|
||||
if (video.includes("youtu")) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CarouselItem
|
||||
className={cn(
|
||||
"group relative",
|
||||
immagini && immagini.length === 1
|
||||
? ""
|
||||
: !single && "md:basis-1/2 lg:basis-1/3",
|
||||
)}
|
||||
key={`videoplayer-${video}`}
|
||||
>
|
||||
<VideoPlayer
|
||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
||||
className="h-72"
|
||||
coverImage={
|
||||
immagini?.[0]
|
||||
? `/storage-api/get/${immagini[0]}?image=true`
|
||||
: ""
|
||||
}
|
||||
key={`videoplayer-${idx}-${openModal}`}
|
||||
videoSrc={video}
|
||||
/>
|
||||
|
||||
<Maximize2
|
||||
className="absolute right-2 bottom-2 size-7 origin-bottom-right cursor-pointer rounded-md bg-white/70 stroke-2 p-1 transition-all duration-300 ease-in-out hover:scale-150 group-hover:opacity-100 sm:opacity-0"
|
||||
onClick={() =>
|
||||
handleOpenModal(immagini?.length || 0 + idx)
|
||||
}
|
||||
/>
|
||||
</CarouselItem>
|
||||
);
|
||||
})}
|
||||
</CarouselContent>
|
||||
|
||||
<CarouselPrevious
|
||||
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
|
||||
disabled={!immagini || immagini.length === 1}
|
||||
type="button"
|
||||
/>
|
||||
<CarouselNext
|
||||
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
|
||||
disabled={!immagini || immagini.length === 1}
|
||||
type="button"
|
||||
/>
|
||||
</Carousel>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog onOpenChange={setOpenModal} open={openModal}>
|
||||
<DialogContent className="flex h-full w-full max-w-full items-center justify-center border-transparent border-none bg-white p-0 md:max-w-[80vw] md:p-6 [&_#dialog-close>svg]:size-8">
|
||||
<DialogHeader className="absolute top-0 right-0 left-0 z-10">
|
||||
<DialogTitle className="sr-only">Image Dialog</DialogTitle>
|
||||
<DialogDescription className="sr-only">Immagine</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Carousel opts={{ loop: true, startIndex: idxModal }}>
|
||||
<CarouselContent>
|
||||
{immagini?.map((img, idx) => (
|
||||
<CarouselItem key={`${img}-cD`}>
|
||||
<Image
|
||||
alt={`carousel-img-${idx}`}
|
||||
className="mx-auto h-[90vh] w-full rounded-md object-contain sm:w-[80vw]"
|
||||
height={1080}
|
||||
src={`/storage-api/get/${img}?image=true&${updated_at?.toString() || new Date().toString()}`}
|
||||
width={1920}
|
||||
/>
|
||||
</CarouselItem>
|
||||
))}
|
||||
{videos?.map((video) => {
|
||||
if (!video) return null;
|
||||
if (video.includes("youtu")) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<CarouselItem
|
||||
className="relative flex items-center justify-center"
|
||||
key={`carousel-videoplayer-${video}`}
|
||||
>
|
||||
<VideoPlayer
|
||||
cacheKey={updated_at?.toString() || new Date().toString()}
|
||||
className="absolute mx-auto h-[90vh] w-full max-w-fit rounded-md object-contain"
|
||||
coverImage={
|
||||
immagini?.[0]
|
||||
? `/storage-api/get/${immagini[0]}?image=true`
|
||||
: ""
|
||||
}
|
||||
videoSrc={video}
|
||||
/>
|
||||
</CarouselItem>
|
||||
);
|
||||
})}
|
||||
</CarouselContent>
|
||||
|
||||
<CarouselPrevious
|
||||
className="left-2 cursor-pointer opacity-60 disabled:opacity-0"
|
||||
disabled={!immagini || immagini.length === 1}
|
||||
type="button"
|
||||
/>
|
||||
<CarouselNext
|
||||
className="right-2 cursor-pointer opacity-60 disabled:opacity-0"
|
||||
disabled={!immagini || immagini.length === 1}
|
||||
type="button"
|
||||
/>
|
||||
</Carousel>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,12 +1,22 @@
|
|||
import Link from "next/link";
|
||||
import BlurryDivider from "~/components/blurry_divider";
|
||||
import { LogoSvg } from "~/components/svgs";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { useTranslation } from "~/providers/I18nProvider";
|
||||
export const Footer = () => {
|
||||
|
||||
type FooterProps = {
|
||||
className?: string;
|
||||
};
|
||||
export const Footer = ({ className }: FooterProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="mt-auto">
|
||||
<footer className="bottom-0 z-40 w-full bg-muted text-muted-foreground">
|
||||
<footer
|
||||
className={cn(
|
||||
"bottom-0 z-40 w-full bg-muted text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto max-w-6xl px-4 py-6 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<Link
|
||||
|
|
@ -73,11 +83,16 @@ export const Footer = () => {
|
|||
</div>
|
||||
);
|
||||
};
|
||||
export const MiniFooter = () => {
|
||||
export const MiniFooter = ({ className }: FooterProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="mt-auto flex w-full">
|
||||
<footer className="z-40 w-full bg-neutral-100 text-neutral-700 dark:bg-primary dark:text-white">
|
||||
<footer
|
||||
className={cn(
|
||||
"z-40 w-full bg-neutral-100 text-neutral-700 dark:bg-primary dark:text-white",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="mx-auto max-w-5xl px-2 py-2 sm:px-6 lg:px-8">
|
||||
<div className="text-center">
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -278,9 +278,9 @@ export const en: LangDict = {
|
|||
annulla: "Cancel",
|
||||
annunci: {
|
||||
accordion_minifaq: [
|
||||
Faqs.cosa_significa_transitorio,
|
||||
Faqs.posso_visitarli,
|
||||
Faqs.posso_contattarvi,
|
||||
Faqs.cosa_significa_transitorio,
|
||||
Faqs.come_confermare,
|
||||
],
|
||||
comune: "City",
|
||||
|
|
|
|||
|
|
@ -283,9 +283,9 @@ export const it: LangDict = {
|
|||
annulla: "Annulla",
|
||||
annunci: {
|
||||
accordion_minifaq: [
|
||||
Faqs.cosa_significa_transitorio,
|
||||
Faqs.posso_visitarli,
|
||||
Faqs.posso_contattarvi,
|
||||
Faqs.cosa_significa_transitorio,
|
||||
Faqs.come_confermare,
|
||||
],
|
||||
comune: "Comune",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { keepPreviousData } from "@tanstack/react-query";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
ChevronsRight,
|
||||
ChevronsLeft,
|
||||
ChevronUp,
|
||||
Filter,
|
||||
FilterX,
|
||||
|
|
@ -18,6 +19,7 @@ import { AnnunciGrid } from "~/components/annunci_grid";
|
|||
import { MapSection } from "~/components/annunci_map";
|
||||
import { CTA_TipologiaModal } from "~/components/annunci_tutorial";
|
||||
import { CodiceBox } from "~/components/codiceRicerca";
|
||||
import { Layout } from "~/components/Layout";
|
||||
import { LoadingPage } from "~/components/loading";
|
||||
import { Status500 } from "~/components/status-page";
|
||||
import { Badge } from "~/components/ui/badge";
|
||||
|
|
@ -60,23 +62,52 @@ const Annunci = ({ options }: AnnunciPageProps) => {
|
|||
<meta content={t.heads.annunci_description} name="description" />
|
||||
</Head>
|
||||
|
||||
<main className="mx-auto w-full max-w-7xl space-y-5 bg-background2 px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
||||
<main className="relative mx-auto w-full max-w-7xl space-y-5 bg-background2 px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||
{t.annunci.titolo}
|
||||
</div>
|
||||
|
||||
<GoBackButton />
|
||||
|
||||
<Ricerca />
|
||||
<AccordionComp
|
||||
className="max-w-6xl px-4 py-4 md:py-8"
|
||||
defaultOpen={0}
|
||||
texts={t.annunci.accordion_minifaq}
|
||||
/>
|
||||
</main>
|
||||
</RicercaProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const GoBackButton = () => {
|
||||
const { page, setPage } = useRicerca();
|
||||
if (page >= 2) {
|
||||
return (
|
||||
<Button
|
||||
className="absolute right-0 h-auto py-0 text-base text-neutral-600 underline underline-offset-1 sm:hidden"
|
||||
onClick={async () => {
|
||||
await setPage(0);
|
||||
}}
|
||||
variant="link"
|
||||
>
|
||||
Torna all'inizio
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
export default Annunci;
|
||||
|
||||
Annunci.getLayout = (page: React.ReactNode) => {
|
||||
return (
|
||||
<Layout
|
||||
containerClassName="bg-background2"
|
||||
footerClassName="bg-background2"
|
||||
>
|
||||
{page}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSideProps = (async () => {
|
||||
const helper = generateSSGHelper();
|
||||
const options = await helper.annunci.getAnnunciOptions.fetch();
|
||||
|
|
@ -94,11 +125,11 @@ const Ricerca = () => {
|
|||
}, [page]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-5">
|
||||
<RicercaFilters />
|
||||
<br />
|
||||
|
||||
{map ? <MapSection /> : <AnnunciList />}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -338,63 +369,53 @@ const AnnunciList = () => {
|
|||
) : (
|
||||
<AnnunciGrid pagedata={data.annunci} />
|
||||
)}
|
||||
<br />
|
||||
|
||||
<div className="mx-auto grid max-w-sm grid-cols-5 items-center justify-between gap-2">
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Reset Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
page < 2 ? "invisible" : "visible",
|
||||
)}
|
||||
disabled={page < 2}
|
||||
onClick={async () => {
|
||||
await setPage(0);
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<ChevronsRight className="rotate-180" height={30} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Previous Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
page < 1 ? "invisible" : "visible",
|
||||
)}
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="flex w-24 items-center justify-end gap-2">
|
||||
{page >= 2 && (
|
||||
<Button
|
||||
aria-label="Go to first page"
|
||||
onClick={async () => {
|
||||
await setPage(0);
|
||||
}}
|
||||
title="Go to first page"
|
||||
variant="outline"
|
||||
>
|
||||
<ChevronsLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
aria-label="Previous page"
|
||||
disabled={page === 0}
|
||||
onClick={async () => {
|
||||
await setPage((old) => (old ? Math.max(old - 1, 0) : 0));
|
||||
}}
|
||||
type="button"
|
||||
title="Previous page"
|
||||
variant="outline"
|
||||
>
|
||||
<ChevronRight className="rotate-180" height={30} />
|
||||
</button>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<span className="font-semibold text-lg text-neutral-500">
|
||||
{page + 1}
|
||||
</span>
|
||||
|
||||
<div className="mx-4 w-12 rounded-md bg-white py-2 text-center font-medium text-lg">
|
||||
{page + 1}
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<button
|
||||
aria-label="Next Page"
|
||||
className={cn(
|
||||
"rounded-lg bg-red-500/80 px-4 py-2 text-white hover:bg-red-500 active:shadow-lg",
|
||||
data?.hasMore ? "visible" : "invisible",
|
||||
)}
|
||||
|
||||
<div className="flex w-24 items-center justify-start gap-2">
|
||||
<Button
|
||||
aria-label="Next page"
|
||||
disabled={isPlaceholderData || !data?.hasMore}
|
||||
onClick={async () => {
|
||||
await setPage((old) =>
|
||||
old ? (data?.hasMore ? old + 1 : old) : 1,
|
||||
);
|
||||
}}
|
||||
type="button"
|
||||
title="Next page"
|
||||
variant="outline"
|
||||
>
|
||||
<ChevronRight height={30} />
|
||||
</button>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
|||
return (
|
||||
<AnnuncioContext.Provider value={data}>
|
||||
<TouchProvider>
|
||||
<div className="mx-auto w-full px-2 sm:px-8">
|
||||
<div className="mx-auto w-full max-w-[100rem] px-2 sm:px-8">
|
||||
<CarouselAnnuncio
|
||||
immagini={data.images.map((img) => img.img)}
|
||||
updated_at={data.updated_at}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const ChiSiamo: NextPage = () => {
|
|||
<meta content={t.heads.main_description} name="description" />
|
||||
</Head>
|
||||
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
||||
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||
{t.chi_siamo.title}
|
||||
</div>
|
||||
<div className="mx-auto w-full max-w-6xl space-y-5 sm:space-y-14">
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ const Contatto: NextPage = () => {
|
|||
<meta content={t.heads.main_description} name="description" />
|
||||
</Head>
|
||||
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
||||
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||
{t.contatti}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const Guida: NextPage = () => {
|
|||
<meta content={t.heads.main_description} name="description" />
|
||||
</Head>
|
||||
<main className="mx-auto w-full max-w-7xl px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
||||
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||
{t.guida}
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const Prezzi: NextPage = () => {
|
|||
<meta content={t.heads.prezzi_description} name="description" />
|
||||
</Head>
|
||||
<section className="mx-auto max-w-7xl px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
||||
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||
{t.prezzi_titolo}
|
||||
</div>
|
||||
<div className="mt-5 space-y-14">
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const Proprietari: NextPage = () => {
|
|||
<meta content={t.proprietari.description} name="description" />
|
||||
</Head>
|
||||
<main className="mx-auto w-full max-w-7xl space-y-5 px-2 py-5 md:px-8">
|
||||
<div className="inline-block rounded-md bg-primary/10 px-3 py-1 text-primary text-sm">
|
||||
<div className="inline-block rounded-md bg-accent px-3 py-1 text-primary text-sm outline outline-neutral-500">
|
||||
{t.proprietari.titolo}
|
||||
</div>
|
||||
<div className="mx-auto w-full space-y-5 sm:space-y-14">
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ export const get_AnnunciPositionsHandler = async ({
|
|||
"lon_secondario",
|
||||
"lat_secondario",
|
||||
"updated_at",
|
||||
"homepage",
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("images_refs")
|
||||
|
|
@ -268,6 +269,7 @@ export type AnnuncioRicerca = Pick<
|
|||
| "stato"
|
||||
| "url_video"
|
||||
| "updated_at"
|
||||
| "homepage"
|
||||
> & {
|
||||
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||
};
|
||||
|
|
@ -286,7 +288,10 @@ export const getCursor_AnnunciHandler = async ({
|
|||
comune?: string;
|
||||
consegna?: number;
|
||||
sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
|
||||
}) => {
|
||||
}): Promise<{
|
||||
annunci: AnnuncioRicerca[];
|
||||
hasMore: boolean;
|
||||
}> => {
|
||||
try {
|
||||
let query = db
|
||||
.selectFrom("annunci")
|
||||
|
|
@ -306,6 +311,7 @@ export const getCursor_AnnunciHandler = async ({
|
|||
"stato",
|
||||
"url_video",
|
||||
"updated_at",
|
||||
"homepage",
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom("images_refs")
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
--accordion-down: accordion-down 0.2s ease-out;
|
||||
--accordion-up: accordion-up 0.2s ease-out;
|
||||
--caret-blink: caret-blink 1.25s ease-out infinite;
|
||||
--border-pulse: border-pulse 2s infinite;
|
||||
|
||||
|
||||
@keyframes accordion-down {
|
||||
0% {
|
||||
|
|
@ -141,19 +141,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes border-pulse {
|
||||
0%: {
|
||||
border-color: hsl(var(--border)) / 10;
|
||||
}
|
||||
|
||||
50%: {
|
||||
border-color: hsl(var(--border));
|
||||
}
|
||||
|
||||
100%: {
|
||||
border-color: hsl(var(--border)) / 10;
|
||||
}
|
||||
}
|
||||
|
||||
--animate-expand: expand 0.3s ease-out forwards;
|
||||
@keyframes expand {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue