refactor: remove unused annunci data fetching and components; update ricerca logic

- Deleted the `annunci.ts` file which contained the database fetching logic.
- Removed the `TestAppPage` component that relied on the deleted data fetching.
- Eliminated the `AnnunciList` component that was dependent on the removed data.
- Updated `annunci_map.tsx` to handle optional `selectComune` parameter.
- Refactored `annunci_tutorial.tsx` to use `buildRicercaUrl` for navigation.
- Adjusted `annuncio_card.tsx` for consistent styling and layout changes.
- Modified `codiceRicerca.tsx` to fix z-index issue.
- Enhanced `failed-loading.tsx` and `500Auth.tsx` for responsive design.
- Updated `annunci.tsx` to include loading state for map and annunci list.
- Refined filter handling in `Filters` component to use a single `setAllParams` method.
- Introduced `FrequentSearches` component for better user navigation.
- Updated `RicercaProvider` to streamline state management and URL building.
- Adjusted server-side logic in `annunci.controller.ts` to ensure valid data.
This commit is contained in:
Marco Pedone 2026-01-20 18:38:17 +01:00
parent cb97919157
commit edaee780d6
22 changed files with 335 additions and 439 deletions

View file

@ -59,27 +59,27 @@ const OnboardingServizio = ({
<Text className="font-semibold"> <Text className="font-semibold">
Annunci selezionati per te: Annunci selezionati per te:
</Text> </Text>
<Section className="mb-[30px]" key={annuncio.codice}> <Section className="mb-7.5" key={annuncio.codice}>
<Row className="mb-[12px]"> <Row className="mb-3">
<Img <Img
alt={`Annuncio image - ${annuncio.codice}`} alt={`Annuncio image - ${annuncio.codice}`}
className="block w-[240px] rounded-[4px] object-cover object-center" className="block w-60 rounded-lg object-cover object-center"
height="140px" height="140px"
src={`${env.BASE_URL}/storage-api/get/${annuncio.immagine}?media=image`} src={`${env.BASE_URL}/storage-api/get/${annuncio.immagine}?media=image`}
width="100%" width="100%"
/> />
</Row> </Row>
<Row className="block w-full"> <Row className="block w-full">
<div className="mb-[5px] flex h-[24px] w-fit items-center justify-center gap-1 rounded-full bg-indigo-600 px-2 font-semibold text-[12px] text-white leading-0"> <div className="mb-1.25 flex h-6 w-fit items-center justify-center gap-1 rounded-full bg-indigo-600 px-2 font-semibold text-[12px] text-white leading-0">
<strong>Codice: </strong> {annuncio.codice} <strong>Codice: </strong> {annuncio.codice}
</div> </div>
<Heading <Heading
as="h2" as="h2"
className="mt-0 mb-[6px] font-bold text-[16px] leading-none" className="mt-0 mb-1.5 font-bold text-[16px] leading-none"
> >
{annuncio.titolo || "N/A"} {annuncio.titolo || "N/A"}
</Heading> </Heading>
<Text className="m-0 text-[14px] text-gray-500 leading-[24px]"> <Text className="m-0 text-[14px] text-gray-500 leading-6">
<strong>Prezzo:</strong> <strong>Prezzo:</strong>
{(annuncio.prezzo / 1e2).toFixed(2)} {(annuncio.prezzo / 1e2).toFixed(2)}
</Text> </Text>

View file

@ -1,81 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<url>
<loc>https://www.infoalloggi.it</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/annunci</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/prezzi</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/guida</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/contatti</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/proprietari</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/annunci?comune=Bassano+del+Grappa</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/annunci?tipo=Transitorio</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/annunci?comune=Bassano+del+Grappa&amp;tipo=Transitorio</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/annunci?tipo=Stabile</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/annunci?comune=Marostica</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/chi-siamo</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/termini-condizioni</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://www.infoalloggi.it/privacy-policy</loc>
<lastmod>2025-08-23T15:09:55+00:00</lastmod>
<priority>0.80</priority>
</url>
</urlset>

View file

@ -1,44 +0,0 @@
import type { Metadata } from "next";
import "~/styles/globals.css";
import "react-day-picker/dist/style.css";
import { inter } from "~/utils/fonts";
export const metadata: Metadata = {
description: "Trova casa ora con Infoalloggi",
icons: {
apple: {
sizes: "180x180",
type: "image/png",
url: "/favicon/apple-touch-icon.png",
},
icon: [
{ sizes: "32x32", type: "image/png", url: "/favicon/favicon-32x32.png" },
{ sizes: "16x16", type: "image/png", url: "/favicon/favicon-16x16.png" },
],
other: [
{
color: "#5bbad5",
rel: "mask-icon",
url: "/favicon/safari-pinned-tab.svg",
},
],
},
manifest: "/site.webmanifest",
title: "Infoalloggi.it",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
className={`${inter.variable} scrollbar-default font-sans`}
lang="en"
suppressHydrationWarning
>
<body>{children}</body>
</html>
);
}

View file

@ -1,20 +0,0 @@
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { db } from "~/server/db";
export async function getDataFromDB() {
try {
// Example query - adjust to your database schema
const data = await db
.selectFrom("annunci")
.select("codice")
.where("web", "=", true)
.where("tipo", "=", TipologiaPosizioneEnum.Transitorio)
.where("stato", "=", "Attivo")
.execute();
return data;
} catch (error) {
console.error("Database error:", error);
return [];
}
}

View file

@ -1,22 +0,0 @@
import { Suspense } from "react";
import { getDataFromDB } from "~/_app/lib/annunci";
import AnnunciList from "~/_app/ui/annunci";
import { LoadingPage } from "~/components/loading";
export default function TestAppPage() {
const annunci = getDataFromDB();
return (
<div className="p-8">
<h1 className="mb-4 font-bold text-2xl">Test App Page</h1>
<div className="rounded-lg border bg-gray-50 p-4">
<h2 className="mb-2 font-semibold text-lg">Data from Database:</h2>
<Suspense fallback={<LoadingPage />}>
<AnnunciList annunci={annunci} />
</Suspense>
</div>
</div>
);
}

View file

@ -1,19 +0,0 @@
"use client";
import { use } from "react";
import type { Annunci } from "~/schemas/public/Annunci";
type AnnunciProps = {
annunci: Promise<Pick<Annunci, "codice">[]>;
};
export default function AnnunciList({ annunci }: AnnunciProps) {
const allAnnunci = use(annunci);
return (
<ul>
{allAnnunci.map((ann) => (
<li key={ann.codice}>{ann.codice}</li>
))}
</ul>
);
}

View file

@ -53,14 +53,14 @@ const MapDisplay = memo(
selectMaxBudget, selectMaxBudget,
}: { }: {
selectTipo: string; selectTipo: string;
selectComune: string; selectComune: string | null;
selectConsegna: number[] | null; selectConsegna: number[] | null;
setSelected: Dispatch<SetStateAction<AnnuncioRicercaWPosition | null>>; setSelected: Dispatch<SetStateAction<AnnuncioRicercaWPosition | null>>;
selectMinBudget: number | null; selectMinBudget: number | null;
selectMaxBudget: number | null; selectMaxBudget: number | null;
}) => { }) => {
const { data, isLoading } = api.annunci.getMapping.useQuery({ const { data, isLoading } = api.annunci.getMapping.useQuery({
comune: selectComune, comune: selectComune || undefined,
consegna: selectConsegna || undefined, consegna: selectConsegna || undefined,
tipologia: selectTipo, tipologia: selectTipo,
minBudget: selectMinBudget || undefined, minBudget: selectMinBudget || undefined,

View file

@ -14,6 +14,7 @@ import {
DialogTitle, DialogTitle,
} from "~/components/ui/dialog"; } from "~/components/ui/dialog";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { buildRicercaUrl } from "~/providers/RicercaProvider";
const COOKIE_KEY = "tipologia_tutorial_shown"; const COOKIE_KEY = "tipologia_tutorial_shown";
@ -27,7 +28,7 @@ export const CTA_TipologiaModal = () => {
expires: add(new Date(), { days: 30 }), // Set cookie to expire in 30 days expires: add(new Date(), { days: 30 }), // Set cookie to expire in 30 days
}); });
if (v !== null) { if (v !== null) {
window.location.assign(`/annunci?tipo=${v}`); window.location.assign(buildRicercaUrl({ tipo: v }));
} }
setOpen(false); setOpen(false);
}; };

View file

@ -99,7 +99,7 @@ const SkeletonCardAnnuncio = () => {
<Skeleton className="h-10 w-full rounded-md" /> <Skeleton className="h-10 w-full rounded-md" />
<Skeleton className="h-10 w-full rounded-md" /> <Skeleton className="h-10 w-full rounded-md" />
</div> </div>
<div className="flex h-[8.5rem] flex-col justify-between"> <div className="flex h-34 flex-col justify-between">
<Skeleton className="h-20 w-full rounded-md" /> <Skeleton className="h-20 w-full rounded-md" />
<div className="flex h-9 items-center justify-around gap-1.5"> <div className="flex h-9 items-center justify-around gap-1.5">
@ -451,7 +451,7 @@ export const CarouselAnnuncio = ({
<Image <Image
alt={img.img} alt={img.img}
className={cn( className={cn(
"aspect-square max-h-92 w-full rounded-md bg-[#e6e9ec] object-cover sm:max-h-80 xl:max-h-[25rem]", "aspect-square max-h-92 w-full rounded-md bg-[#e6e9ec] object-cover sm:max-h-80 xl:max-h-100",
immagini.length === 1 && "object-contain", immagini.length === 1 && "object-contain",
)} )}
height={400} height={400}
@ -484,7 +484,7 @@ export const CarouselAnnuncio = ({
key={`videoplayer-${video.video}`} key={`videoplayer-${video.video}`}
> >
<VideoPlayer <VideoPlayer
className="aspect-square max-h-92 object-cover sm:max-h-80 xl:max-h-[25rem]" className="aspect-square max-h-92 object-cover sm:max-h-80 xl:max-h-100"
coverImage={getStorageUrl({ coverImage={getStorageUrl({
storageId: video.thumb, storageId: video.thumb,
params: { params: {

View file

@ -82,7 +82,7 @@ const AutocompleteSearchBox = ({
}, [searchValue, options]); }, [searchValue, options]);
return ( return (
<div className="z-[5000] w-full" ref={wrapperRef}> <div className="z-5000 w-full" ref={wrapperRef}>
<div <div
className={cn( className={cn(
"relative flex w-full items-center justify-center rounded-md bg-secondary dark:bg-primary", "relative flex w-full items-center justify-center rounded-md bg-secondary dark:bg-primary",

View file

@ -9,7 +9,7 @@ export default function FailedAnnuncioLoading() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<main> <main>
<div className="mx-auto flex h-screen max-w-screen-xl items-center justify-start px-4 md:px-8"> <div className="mx-auto flex h-screen max-w-7xl items-center justify-start px-4 md:px-8">
<div className="mx-auto max-w-lg space-y-3 text-center"> <div className="mx-auto max-w-lg space-y-3 text-center">
<HomeIcon className="mx-auto size-20 text-destructive" /> <HomeIcon className="mx-auto size-20 text-destructive" />
<h3 className="font-semibold text-4xl text-accent-foreground sm:text-5xl"> <h3 className="font-semibold text-4xl text-accent-foreground sm:text-5xl">

View file

@ -0,0 +1,97 @@
import { ArrowRight } from "lucide-react";
import Link from "next/link";
import { useTranslation } from "~/providers/I18nProvider";
import { buildRicercaUrl } from "~/providers/RicercaProvider";
import { Button } from "./ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
const it = {
title: "Ricerche frequenti",
transitorio: "Transitori",
stabili: "Stabili",
};
const en = {
title: "Frequent Searches",
transitorio: "Short-term",
stabili: "Stable",
};
export const FrequentSearches = () => {
const { locale } = useTranslation();
const t = locale === "en" ? en : it;
return (
<Card className="mx-auto mb-4 max-w-4xl gap-4 border-0 bg-secondary shadow-none">
<CardHeader>
<CardTitle className="text-center font-bold text-3xl sm:text-2xl">
{t.title}
</CardTitle>
</CardHeader>
<CardContent className="mx-auto flex max-w-fit flex-wrap items-center justify-center gap-4 px-2 sm:px-6">
<Link href={buildRicercaUrl({ tipo: "Transitorio" })}>
<Button
className="flex items-center gap-1 border-2 border-transitorio bg-transitorio text-lg text-white"
size="lg"
variant="flat"
>
{t.transitorio}
<ArrowRight />
</Button>
</Link>
<Link href={buildRicercaUrl({ tipo: "Stabile" })}>
<Button
className="flex items-center gap-1 border-2 border-stabile bg-stabile text-lg text-white"
size="lg"
variant="flat"
>
{t.stabili}
<ArrowRight />
</Button>
</Link>
<Link
href={buildRicercaUrl({
comune: "Bassano del Grappa",
})}
>
<Button
className="flex items-center gap-1 border-2 border-primary text-lg"
size="lg"
>
Bassano del Grappa
<ArrowRight />
</Button>
</Link>
<Link
href={buildRicercaUrl({
comune: "Bassano del Grappa",
tipo: "Transitorio",
})}
>
<Button
className="flex items-center gap-1 border-2 border-transitorio bg-transitorio text-lg text-white"
size="lg"
variant="flat"
>
{t.transitorio} Bassano
<ArrowRight />
</Button>
</Link>
<Link
href={buildRicercaUrl({
comune: "Marostica",
})}
>
<Button
className="flex items-center gap-1 border-2 border-primary text-lg"
size="lg"
>
Marostica
<ArrowRight />
</Button>
</Link>
</CardContent>
</Card>
);
};

View file

@ -35,7 +35,7 @@ export const AnnunciCompatibili = () => {
Esplora Annunci Compatibili Esplora Annunci Compatibili
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent className="max-w-xl p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl 2xl:max-w-[100rem]"> <DialogContent className="max-w-xl p-2 sm:max-w-5xl sm:p-6 md:max-w-7xl 2xl:max-w-400">
<DialogHeader className="flex-row flex-wrap items-center gap-5 pt-6"> <DialogHeader className="flex-row flex-wrap items-center gap-5 pt-6">
<DialogTitle> <DialogTitle>
Annunci compatibili con i parametri del servizio Annunci compatibili con i parametri del servizio

View file

@ -104,7 +104,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => {
stato: z.string().nullable(), stato: z.string().nullable(),
// DATI CONTRATTO // DATI CONTRATTO
tipo: z.string().nullable(), tipo: z.string(),
titolo_en: z.string().nullable(), titolo_en: z.string().nullable(),
titolo_it: z.string().nullable(), titolo_it: z.string().nullable(),
unita_condominio: z.number().nullable(), unita_condominio: z.number().nullable(),

View file

@ -235,6 +235,7 @@ export type LangDict = {
endtxt: string; endtxt: string;
endtxt2: string; endtxt2: string;
}; };
annunci: { annunci: {
titolo: string; titolo: string;
fs_tipologia: { fs_tipologia: {

View file

@ -5,7 +5,7 @@ export default function Custom500Auth() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<main> <main>
<div className="mx-auto flex h-screen max-w-screen-xl items-center justify-start px-4 md:px-8"> <div className="mx-auto flex h-screen max-w-7xl items-center justify-start px-4 md:px-8">
<div className="mx-auto max-w-lg space-y-3 text-center"> <div className="mx-auto max-w-lg space-y-3 text-center">
<ShieldExclamationIcon className="mx-auto size-20 text-red-600" /> <ShieldExclamationIcon className="mx-auto size-20 text-red-600" />
<h3 className="font-semibold text-4xl text-accent-foreground sm:text-5xl"> <h3 className="font-semibold text-4xl text-accent-foreground sm:text-5xl">

View file

@ -105,14 +105,17 @@ export const getServerSideProps = (async () => {
}) satisfies GetServerSideProps<AnnunciPageProps>; }) satisfies GetServerSideProps<AnnunciPageProps>;
const Ricerca = () => { const Ricerca = () => {
const { map } = useRicerca(); const { map, isLoading } = useRicerca();
return ( return (
<div className="mx-auto w-full space-y-5"> <div className="mx-auto w-full space-y-5">
<RicercaHeader /> <RicercaHeader />
<div className="relative flex w-full gap-4"> <div className="relative flex w-full gap-4">
<RicercaSideFilters /> <RicercaSideFilters />
<div className="flex-1">{map ? <MapSection /> : <AnnunciList />}</div>
<div className="flex-1">
{isLoading ? <LoadingPage /> : map ? <MapSection /> : <AnnunciList />}
</div>
</div> </div>
</div> </div>
); );
@ -122,14 +125,12 @@ const Filters = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
tipo, tipo,
setTipo,
comune, comune,
setComune,
sort, sort,
setSort,
comuniOptions, comuniOptions,
tipologieOptions, tipologieOptions,
ResetParams, setAllParams,
reset,
nFiltri, nFiltri,
} = useRicerca(); } = useRicerca();
@ -138,39 +139,55 @@ const Filters = () => {
<SelectFilter <SelectFilter
defaultValue={""} defaultValue={""}
handleReset={async () => { handleReset={async () => {
await setTipo(""); await setAllParams({
tipo: "",
});
}} }}
id="tipologia-filter"
label={t.annunci.fs_tipologia.titolo} label={t.annunci.fs_tipologia.titolo}
onValueChange={async (value: string) => { onValueChange={async (value: string) => {
await setTipo(value); await setAllParams({
tipo: value,
});
}} }}
options={tipologieOptions} options={tipologieOptions}
placeholder={t.seleziona_placeholder} placeholder={t.seleziona_placeholder}
value={tipo} value={tipo}
/> />
<SelectFilter <SelectFilter
defaultValue={""} defaultValue={undefined}
handleReset={async () => { handleReset={async () => {
await setComune(null); await setAllParams({
comune: null,
});
}} }}
id="comune-filter"
key={comune}
label={t.annunci.comune} label={t.annunci.comune}
onValueChange={async (value: string) => { onValueChange={async (value: string) => {
await setComune(value); await setAllParams({
comune: value,
});
}} }}
options={comuniOptions} options={comuniOptions}
placeholder={t.seleziona_placeholder} placeholder={t.seleziona_placeholder}
value={comune} value={comune || undefined}
/> />
<ConsegnaFilter /> <ConsegnaFilter />
<BudgetFilters /> <BudgetFilters />
<SelectFilter <SelectFilter
defaultValue={sort} defaultValue={sort}
handleReset={async () => { handleReset={async () => {
await setSort("Recenti"); await setAllParams({
sort: "Recenti",
});
}} }}
id="sort-filter"
label={t.ordina_per} label={t.ordina_per}
onValueChange={async (value: SortTypes) => { onValueChange={async (value: SortTypes) => {
await setSort(value); await setAllParams({
sort: value,
});
}} }}
options={["Recenti", "Prezzo", "Consegna", "Mq"]} options={["Recenti", "Prezzo", "Consegna", "Mq"]}
placeholder={t.seleziona_placeholder} placeholder={t.seleziona_placeholder}
@ -179,7 +196,7 @@ const Filters = () => {
<Button <Button
className="flex flex-row gap-2" className="flex flex-row gap-2"
disabled={nFiltri === 0} disabled={nFiltri === 0}
onClick={ResetParams} onClick={reset}
variant="destructive" variant="destructive"
> >
<FilterX /> {t.annunci.filtri_reset} <FilterX /> {t.annunci.filtri_reset}
@ -189,8 +206,11 @@ const Filters = () => {
}; };
const RicercaSideFilters = () => { const RicercaSideFilters = () => {
const isBigScreen = useMediaQuery("(min-width: 96rem)");
if (!isBigScreen) return null;
return ( return (
<div className="hidden h-fit w-46 max-w-46 shrink-0 grow-0 flex-col gap-3 overflow-clip rounded-md bg-secondary p-2 outline-neutral-300 2xl:flex"> <div className="flex h-fit w-46 max-w-46 shrink-0 grow-0 flex-col gap-3 overflow-clip rounded-md bg-secondary p-2 outline-neutral-300">
<Filters /> <Filters />
</div> </div>
); );
@ -230,7 +250,7 @@ const DrowdownFilters = () => {
}; };
const RicercaHeader = () => { const RicercaHeader = () => {
const { map, setMap } = useRicerca(); const { map, setAllParams } = useRicerca();
return ( return (
<div className="mx-auto w-full max-w-10xl space-y-5"> <div className="mx-auto w-full max-w-10xl space-y-5">
@ -240,7 +260,9 @@ const RicercaHeader = () => {
<Button <Button
className="flex w-fit flex-row items-center gap-2 md:w-24" className="flex w-fit flex-row items-center gap-2 md:w-24"
onClick={async () => { onClick={async () => {
await setMap((v) => !v); await setAllParams({
map: !map,
});
}} }}
variant="default" variant="default"
> >
@ -281,10 +303,10 @@ const AnnunciList = () => {
map, map,
minBudget, minBudget,
maxBudget, maxBudget,
isLoading: ricercaLoading, isLoading: isUpdatingRicerca,
} = useRicerca(); } = useRicerca();
const { data, status } = api.annunci.annunciRicerca.useQuery( const { data, status, isLoading } = api.annunci.annunciRicerca.useQuery(
{ {
comune: comune || undefined, comune: comune || undefined,
consegna: consegna || undefined, consegna: consegna || undefined,
@ -294,13 +316,13 @@ const AnnunciList = () => {
maxBudget: maxBudget || undefined, maxBudget: maxBudget || undefined,
}, },
{ {
enabled: !map, enabled: !map && !isUpdatingRicerca,
placeholderData: keepPreviousData, placeholderData: keepPreviousData,
staleTime: 5000, staleTime: 5000,
}, },
); );
if (ricercaLoading) return <LoadingPage />; if (isLoading) return <LoadingPage />;
if (status === "error") return <Status500 />; if (status === "error") return <Status500 />;
return ( return (
@ -325,6 +347,7 @@ const AnnunciList = () => {
interface SelectFilterProps<T> { interface SelectFilterProps<T> {
label: string; label: string;
id: string;
options: string[]; options: string[];
defaultValue?: T; defaultValue?: T;
value: T | undefined; value: T | undefined;
@ -335,6 +358,7 @@ interface SelectFilterProps<T> {
export const SelectFilter = <T extends string>({ export const SelectFilter = <T extends string>({
label, label,
id,
options, options,
defaultValue, defaultValue,
value, value,
@ -350,6 +374,7 @@ export const SelectFilter = <T extends string>({
"font-semibold text-muted-foreground", "font-semibold text-muted-foreground",
value !== defaultValue && "text-primary", value !== defaultValue && "text-primary",
)} )}
htmlFor={id}
> >
{label} {label}
</Label> </Label>
@ -369,7 +394,10 @@ export const SelectFilter = <T extends string>({
onValueChange={onValueChange} onValueChange={onValueChange}
value={value} value={value}
> >
<SelectTrigger className="w-full bg-primary-foreground text-primary"> <SelectTrigger
className="w-full bg-primary-foreground text-primary"
id={id}
>
<SelectValue placeholder={placeholder} /> <SelectValue placeholder={placeholder} />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -386,12 +414,7 @@ export const SelectFilter = <T extends string>({
const ConsegnaFilter = () => { const ConsegnaFilter = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { const { consegna: consegna2, setAllParams, consegnaOptions } = useRicerca();
consegna: consegna2,
setConsegna: setConsegna2,
consegnaOptions,
} = useRicerca();
const mappedConsegnaOptions = useMemo(() => { const mappedConsegnaOptions = useMemo(() => {
if (consegnaOptions.length === 0) return []; if (consegnaOptions.length === 0) return [];
@ -404,14 +427,17 @@ const ConsegnaFilter = () => {
const onValueChange = async (value: number) => { const onValueChange = async (value: number) => {
if (consegna2?.includes(value)) { if (consegna2?.includes(value)) {
// Remove value
const newConsegna2 = consegna2.filter((c) => c !== value); const newConsegna2 = consegna2.filter((c) => c !== value);
await setConsegna2(newConsegna2.length > 0 ? newConsegna2 : null); await setAllParams({
consegna: newConsegna2.length > 0 ? newConsegna2 : null,
});
return; return;
} }
// Add value
const newConsegna2 = consegna2 ? [...consegna2, value] : [value]; const newConsegna2 = consegna2 ? [...consegna2, value] : [value];
await setConsegna2(newConsegna2); await setAllParams({
consegna: newConsegna2,
});
return; return;
}; };
return ( return (
@ -429,7 +455,9 @@ const ConsegnaFilter = () => {
<button <button
className="cursor-pointer" className="cursor-pointer"
onClick={async () => { onClick={async () => {
await setConsegna2(null); await setAllParams({
consegna: null,
});
}} }}
type="button" type="button"
> >
@ -461,20 +489,24 @@ const ConsegnaFilter = () => {
const BudgetFilters = () => { const BudgetFilters = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { minBudget, setMinBudget, maxBudget, setMaxBudget } = useRicerca(); const { minBudget, maxBudget, setAllParams } = useRicerca();
const minDebounced = useCallback( const minDebounced = useCallback(
debounce(async (value: number | null) => { debounce(async (value: number | null) => {
await setMinBudget(value); await setAllParams({
minBudget: value,
});
}, 500), }, 500),
[setMinBudget], [setAllParams],
); );
const maxDebounced = useCallback( const maxDebounced = useCallback(
debounce(async (value: number | null) => { debounce(async (value: number | null) => {
await setMaxBudget(value); await setAllParams({
maxBudget: value,
});
}, 500), }, 500),
[setMaxBudget], [setAllParams],
); );
return ( return (
@ -486,6 +518,7 @@ const BudgetFilters = () => {
"font-semibold text-muted-foreground", "font-semibold text-muted-foreground",
minBudget !== null && "text-primary", minBudget !== null && "text-primary",
)} )}
htmlFor="prezzoMIN"
> >
{t.annunci.budgetMin} {t.annunci.budgetMin}
</Label> </Label>
@ -493,7 +526,9 @@ const BudgetFilters = () => {
<button <button
className="cursor-pointer" className="cursor-pointer"
onClick={async () => { onClick={async () => {
await setMinBudget(null); await setAllParams({
minBudget: null,
});
}} }}
type="button" type="button"
> >
@ -507,7 +542,7 @@ const BudgetFilters = () => {
decimalScale={2} decimalScale={2}
decimalSeparator="," decimalSeparator=","
fixedDecimalScale fixedDecimalScale
id="prezzo" id="prezzoMIN"
min={0} min={0}
onValueChange={(v) => { onValueChange={(v) => {
if (v && v > 0) { if (v && v > 0) {
@ -529,6 +564,7 @@ const BudgetFilters = () => {
"font-semibold text-muted-foreground", "font-semibold text-muted-foreground",
maxBudget !== null && "text-primary", maxBudget !== null && "text-primary",
)} )}
htmlFor="prezzoMAX"
> >
{t.annunci.budgetMax} {t.annunci.budgetMax}
</Label> </Label>
@ -536,7 +572,9 @@ const BudgetFilters = () => {
<button <button
className="cursor-pointer" className="cursor-pointer"
onClick={async () => { onClick={async () => {
await setMaxBudget(null); await setAllParams({
maxBudget: null,
});
}} }}
type="button" type="button"
> >
@ -549,7 +587,7 @@ const BudgetFilters = () => {
decimalScale={2} decimalScale={2}
decimalSeparator="," decimalSeparator=","
fixedDecimalScale fixedDecimalScale
id="prezzo" id="prezzoMAX"
min={0} min={0}
onValueChange={(v) => { onValueChange={(v) => {
if (v && v > 0) { if (v && v > 0) {

View file

@ -66,6 +66,7 @@ import { getStorageUrl } from "~/lib/storage_utils";
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils";
import { AnnuncioContext } from "~/providers/AnnuncioProvider"; import { AnnuncioContext } from "~/providers/AnnuncioProvider";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
import { buildRicercaUrl } from "~/providers/RicercaProvider";
import { useSession } from "~/providers/SessionProvider"; import { useSession } from "~/providers/SessionProvider";
import type { Annunci } from "~/schemas/public/Annunci"; import type { Annunci } from "~/schemas/public/Annunci";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum"; import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
@ -659,7 +660,7 @@ const AnnuncioFooter = ({
external_videos, external_videos,
updated_at, updated_at,
}: { }: {
tipo: string | null; tipo: string;
comune: string | null; comune: string | null;
consegna: number | null; consegna: number | null;
videos: Pick<VideosRefs, "video" | "thumb">[]; videos: Pick<VideosRefs, "video" | "thumb">[];
@ -706,14 +707,14 @@ const AnnuncioFooter = ({
</div> </div>
<Link <Link
href={{ href={buildRicercaUrl({
pathname: "/annunci", comune: comune || undefined,
query: { consegna: consegna ? [consegna] : undefined,
c: comune, tipo:
f: consegna, tipo in ["Transitorio", "Stabile"]
t: tipo, ? (tipo as "Transitorio" | "Stabile")
}, : undefined,
}} })}
target="_blank" target="_blank"
> >
<Button className="my-10 w-full text-xl" variant="secondary"> <Button className="my-10 w-full text-xl" variant="secondary">

View file

@ -1,14 +1,13 @@
import { ArrowRight, Telescope } from "lucide-react"; import { Telescope } from "lucide-react";
import type { NextPage } from "next"; import type { NextPage } from "next";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import { AccordionComp } from "~/components/accordionComp"; import { AccordionComp } from "~/components/accordionComp";
import { CodiceBox } from "~/components/codiceRicerca"; import { CodiceBox } from "~/components/codiceRicerca";
import { ComeFunziona } from "~/components/expand_guida"; import { ComeFunziona } from "~/components/expand_guida";
import { FrequentSearches } from "~/components/frequent_searches";
import { PricingChoice } from "~/components/prezzi"; import { PricingChoice } from "~/components/prezzi";
import { HeroSvg, LogoSvg } from "~/components/svgs"; import { HeroSvg, LogoSvg } from "~/components/svgs";
import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
import { env } from "~/env"; import { env } from "~/env";
import type { LangDict } from "~/i18n/locales"; import type { LangDict } from "~/i18n/locales";
import { useTranslation } from "~/providers/I18nProvider"; import { useTranslation } from "~/providers/I18nProvider";
@ -37,11 +36,11 @@ const Home: NextPage = () => {
/> />
<meta content="Infoalloggi.it" property="og:site_name" /> <meta content="Infoalloggi.it" property="og:site_name" />
</Head> </Head>
<div className="mx-5 mt-0 mb-12 sm:mt-[2rem]"> <div className="mx-5 mt-0 mb-12 sm:mt-8">
<main className="mx-auto mb-4 max-w-8xl space-y-5 lg:mb-20"> <main className="mx-auto mb-4 max-w-8xl space-y-5 lg:mb-20">
<div className="flex flex-col items-center justify-center lg:flex-row lg:justify-around"> <div className="flex flex-col items-center justify-center lg:flex-row lg:justify-around">
<div className="-mt-2 xs:mt-5 flex h-[19rem] max-w-full items-center justify-center min-[22rem]:h-[23rem] min-[25rem]:h-[26rem]"> <div className="-mt-2 xs:mt-5 flex h-76 max-w-full items-center justify-center min-[22rem]:h-92 min-[25rem]:h-104">
<div className="flex scale-50 items-center justify-center drop-shadow-xl sm:scale-100 md:max-h-96 min-[20rem]:scale-[0.6] min-[22rem]:scale-[0.7] min-[25rem]:scale-[0.8]"> <div className="flex scale-50 xxs:scale-[0.6] items-center justify-center drop-shadow-xl sm:scale-100 md:max-h-96 min-[22rem]:scale-[0.7] min-[25rem]:scale-[0.8]">
<HeroSvg /> <HeroSvg />
</div> </div>
</div> </div>
@ -77,7 +76,7 @@ const TrovaCasaCTA = (props: { testi: LangDict }) => {
<span className="text-destructive"> {testi.index.titolo_2}</span>{" "} <span className="text-destructive"> {testi.index.titolo_2}</span>{" "}
{testi.index.titolo_3} {testi.index.titolo_3}
</h1> </h1>
<LogoSvg className="h-[3rem] w-auto sm:h-[4rem] md:h-[5rem]" /> <LogoSvg className="h-12 w-auto sm:h-16 md:h-20" />
<br /> <br />
<div className="flex flex-col space-y-4 lg:justify-start"> <div className="flex flex-col space-y-4 lg:justify-start">
@ -106,68 +105,3 @@ const TrovaCasaCTA = (props: { testi: LangDict }) => {
</div> </div>
); );
}; };
const FrequentSearches = () => {
const { locale } = useTranslation();
return (
<Card className="mx-auto mb-4 max-w-4xl gap-4 border-0 bg-secondary shadow-none">
<CardHeader>
<CardTitle className="text-center font-bold text-3xl sm:text-2xl">
{locale === "en" ? "Frequent Searches" : "Ricerche frequenti"}
</CardTitle>
</CardHeader>
<CardContent className="mx-auto flex max-w-fit flex-wrap items-center justify-center gap-4 px-2 sm:px-6">
<Link href="/annunci?tipo=Transitorio">
<Button
className="flex items-center gap-1 border-2 border-transitorio bg-transitorio text-black text-lg"
size="lg"
variant="flat"
>
Transitori
<ArrowRight />
</Button>
</Link>
<Link href="/annunci?tipo=Stabile">
<Button
className="flex items-center gap-1 border-2 border-stabile bg-stabile text-black text-lg"
size="lg"
variant="flat"
>
Stabili
<ArrowRight />
</Button>
</Link>
<Link href="/annunci?comune=Bassano+del+Grappa">
<Button
className="flex items-center gap-1 border-2 border-primary text-lg"
size="lg"
>
Bassano del Grappa
<ArrowRight />
</Button>
</Link>
<Link href="/annunci?comune=Bassano+del+Grappa&tipo=Transitorio">
<Button
className="flex items-center gap-1 border-2 border-transitorio bg-transitorio text-black text-lg"
size="lg"
variant="flat"
>
Transitori Bassano
<ArrowRight />
</Button>
</Link>
<Link href="/annunci?comune=Marostica">
<Button
className="flex items-center gap-1 border-2 border-primary text-lg"
size="lg"
>
Marostica
<ArrowRight />
</Button>
</Link>
</CardContent>
</Card>
);
};

View file

@ -1,5 +1,6 @@
import type { GetServerSideProps } from "next"; import type { GetServerSideProps } from "next";
import { env } from "~/env"; import { env } from "~/env";
import { buildRicercaUrl } from "~/providers/RicercaProvider";
import { generateSSGHelper } from "~/server/utils/ssgHelper"; import { generateSSGHelper } from "~/server/utils/ssgHelper";
async function generateSiteMap() { async function generateSiteMap() {
@ -13,10 +14,11 @@ async function generateSiteMap() {
`${env.NEXT_PUBLIC_BASE_URL}/proprietari`, `${env.NEXT_PUBLIC_BASE_URL}/proprietari`,
`${env.NEXT_PUBLIC_BASE_URL}/guida`, `${env.NEXT_PUBLIC_BASE_URL}/guida`,
`${env.NEXT_PUBLIC_BASE_URL}/annunci`, `${env.NEXT_PUBLIC_BASE_URL}/annunci`,
`${env.NEXT_PUBLIC_BASE_URL}/annunci?t=Transitorio`, `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ tipo: "Transitorio" })}`,
`${env.NEXT_PUBLIC_BASE_URL}/annunci?c=Bassano+del+Grappa`, `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ comune: "Bassano del Grappa" })}`,
`${env.NEXT_PUBLIC_BASE_URL}/annunci?t=Stabile`, `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ comune: "Bassano del Grappa", tipo: "Transitorio" })}`,
`${env.NEXT_PUBLIC_BASE_URL}/annunci?c=Marostica`, `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ tipo: "Stabile" })}`,
`${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ comune: "Marostica" })}`,
]; ];
const allUrls = [...staticUrls, ...dynamicUrls]; const allUrls = [...staticUrls, ...dynamicUrls];

View file

@ -5,64 +5,85 @@ import {
parseAsInteger, parseAsInteger,
parseAsString, parseAsString,
parseAsStringLiteral, parseAsStringLiteral,
useQueryState, useQueryStates,
} from "nuqs"; } from "nuqs";
import { import {
createContext, createContext,
type FC, type FC,
type ReactNode, type ReactNode,
useCallback,
useContext, useContext,
useEffect,
useMemo, useMemo,
useState,
useTransition, useTransition,
} from "react"; } from "react";
const sortOptions = ["Recenti", "Prezzo", "Consegna", "Mq"] as const;
export type SortTypes = (typeof sortOptions)[number];
type RicercaUrlParams = {
map: boolean;
tipo: string;
comune: string;
consegna: number[];
minBudget: number;
maxBudget: number;
sort: SortTypes;
};
export const paramsMapping = {
map: "m",
tipo: "t",
comune: "c",
consegna: "f",
minBudget: "bmin",
maxBudget: "bmax",
sort: "s",
} as const;
export const buildRicercaUrl = (params: Partial<RicercaUrlParams>): string => {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
const mappedKey = paramsMapping[key as keyof typeof paramsMapping];
if (value !== undefined && value !== null) {
searchParams.set(mappedKey, value.toString());
}
});
const queryString = searchParams.toString();
return queryString ? `/annunci?${queryString}` : "/annunci";
};
type RicercaContextType = { type RicercaContextType = {
map: boolean; map: boolean;
setMap: ( tipo: RicercaUrlParams["tipo"];
value: boolean | ((old: boolean) => boolean | null) | null, comune: RicercaUrlParams["comune"] | null;
) => Promise<URLSearchParams>; consegna: RicercaUrlParams["consegna"] | null;
tipo: string; minBudget: RicercaUrlParams["minBudget"] | null;
setTipo: ( maxBudget: RicercaUrlParams["maxBudget"] | null;
value: string | ((old: string) => string | null) | null,
) => Promise<URLSearchParams>;
comune: string;
setComune: (
value: string | ((old: string) => string | null) | null,
) => Promise<URLSearchParams>;
consegna: number[] | null;
setConsegna: (
value: number[] | ((old: number[] | null) => number[] | null) | null,
) => Promise<URLSearchParams>;
minBudget: number | null;
setMinBudget: (
value: number | ((old: number | null) => number | null) | null,
) => Promise<URLSearchParams>;
maxBudget: number | null;
setMaxBudget: (
value: number | ((old: number | null) => number | null) | null,
) => Promise<URLSearchParams>;
sort: SortTypes; sort: SortTypes;
setSort: (
value: SortTypes | ((old: SortTypes) => SortTypes | null) | null, setAllParams: (
params: Partial<{
tipo: RicercaUrlParams["tipo"];
comune: RicercaUrlParams["comune"] | null;
consegna: RicercaUrlParams["consegna"] | null;
minBudget: RicercaUrlParams["minBudget"] | null;
maxBudget: RicercaUrlParams["maxBudget"] | null;
sort: RicercaUrlParams["sort"];
map: RicercaUrlParams["map"];
}>,
) => Promise<URLSearchParams>; ) => Promise<URLSearchParams>;
reset: () => void;
comuniOptions: string[]; comuniOptions: string[];
tipologieOptions: string[]; tipologieOptions: string[];
consegnaOptions: number[]; consegnaOptions: number[];
sortOptions: typeof sortOptions; sortOptions: typeof sortOptions;
ResetParams: () => Promise<void>;
nFiltri: number; nFiltri: number;
isLoading: boolean; isLoading: boolean;
}; };
const sortOptions = ["Recenti", "Prezzo", "Consegna", "Mq"] as const;
//sort as string union
export type SortTypes = (typeof sortOptions)[number];
export type AnnunciOptions = { export type AnnunciOptions = {
comune: string; comune: string;
tipo: string; tipo: string;
@ -84,39 +105,36 @@ export const RicercaProvider: FC<{
children: ReactNode; children: ReactNode;
options: AnnunciOptions; options: AnnunciOptions;
}> = ({ children, options }) => { }> = ({ children, options }) => {
const [nFiltri, setNFiltri] = useState(0);
const [isLoading, startTransition] = useTransition(); const [isLoading, startTransition] = useTransition();
const [map, setMap] = useQueryState( const [
"m", { map, tipo, comune, consegna, minBudget, maxBudget, sort },
parseAsBoolean.withDefault(false).withOptions({ startTransition }), setAllParams,
); ] = useQueryStates(
const [tipo, setTipo] = useQueryState( {
"t", map: parseAsBoolean.withDefault(false),
parseAsString.withDefault("").withOptions({ startTransition }), tipo: parseAsString.withDefault(""),
); comune: parseAsString.withDefault(""),
const [comune, setComune] = useQueryState( consegna: parseAsArrayOf(parseAsInteger),
"c", minBudget: parseAsInteger,
parseAsString.withDefault("").withOptions({ startTransition }), maxBudget: parseAsInteger,
); sort: parseAsStringLiteral(sortOptions).withDefault("Recenti"),
const [consegna, setConsegna] = useQueryState( },
"f", { startTransition, shallow: true, urlKeys: paramsMapping },
parseAsArrayOf(parseAsInteger).withOptions({ startTransition }),
);
const [minBudget, setMinBudget] = useQueryState(
"bmin",
parseAsInteger.withOptions({ startTransition }),
);
const [maxBudget, setMaxBudget] = useQueryState(
"bmax",
parseAsInteger.withOptions({ startTransition }),
);
const [sort, setSort] = useQueryState(
"s",
parseAsStringLiteral(sortOptions)
.withDefault("Recenti")
.withOptions({ startTransition }),
); );
const reset = useCallback(() => {
setAllParams({
tipo: "",
comune: "",
consegna: null,
minBudget: null,
maxBudget: null,
sort: "Recenti",
});
}, []);
const tipologieOptions = useMemo(() => { const tipologieOptions = useMemo(() => {
const set = new Set<string>(); const set = new Set<string>();
options.forEach((item) => { options.forEach((item) => {
@ -172,56 +190,43 @@ export const RicercaProvider: FC<{
}); });
}, [options, tipo, comune, minBudget, maxBudget]); }, [options, tipo, comune, minBudget, maxBudget]);
const ResetParams = async () => { const updateNFiltri = () => {
await setTipo(""); setNFiltri(
await setComune(""); [tipo, comune].reduce(
await setConsegna(null); (count, item) => (item !== "" ? count + 1 : count),
await setMinBudget(null); 0,
await setMaxBudget(null); ) +
await setSort("Recenti"); (consegna !== null ? 1 : 0) +
(sort !== "Recenti" ? 1 : 0) +
(minBudget !== null ? 1 : 0) +
(maxBudget !== null ? 1 : 0),
);
}; };
const nFiltri = useEffect(() => {
[tipo, comune].reduce( updateNFiltri();
(count, item) => (item !== "" ? count + 1 : count), }, [tipo, comune, consegna, minBudget, maxBudget, sort]);
0,
) +
(consegna !== null ? 1 : 0) +
(sort !== "Recenti" ? 1 : 0) +
(minBudget !== null ? 1 : 0) +
(maxBudget !== null ? 1 : 0);
return ( return (
<RicercaContext.Provider <RicercaContext.Provider
value={{ value={{
comune,
setComune,
comuniOptions,
consegna,
setConsegna,
consegnaOptions,
tipo,
setTipo,
tipologieOptions,
minBudget,
setMinBudget,
maxBudget,
setMaxBudget,
sort,
setSort,
sortOptions: sortOptions,
map, map,
setMap, tipo,
comune,
consegna,
minBudget,
maxBudget,
sort,
setAllParams,
reset,
comuniOptions,
consegnaOptions,
tipologieOptions,
sortOptions,
nFiltri, nFiltri,
isLoading, isLoading,
ResetParams,
}} }}
> >
{children} {children}

View file

@ -19,6 +19,7 @@ import { revalidate } from "../utils/revalidationHelper";
// analytics: true, // analytics: true,
// }); // });
export type AnnunciWithMedia = Annunci & { export type AnnunciWithMedia = Annunci & {
tipo: string;
images: Pick<ImagesRefs, "img" | "thumb">[]; images: Pick<ImagesRefs, "img" | "thumb">[];
videos: Pick<VideosRefs, "video" | "thumb">[]; videos: Pick<VideosRefs, "video" | "thumb">[];
}; };
@ -82,14 +83,15 @@ export const getAnnunciByCod = async ({
.selectAll() .selectAll()
.select((_eb) => [withImages(), withVideos()]) .select((_eb) => [withImages(), withVideos()])
.where("annunci.web", "=", true) .where("annunci.web", "=", true)
.where("tipo", "is not", null)
.where("codice", "=", cod) .where("codice", "=", cod)
.executeTakeFirst(); .executeTakeFirst();
if (!annuncio) { if (!annuncio || annuncio.tipo === null) {
return null; return null;
} }
return annuncio; return annuncio as AnnunciWithMedia;
} catch (e) { } catch (e) {
throw new TRPCError({ throw new TRPCError({
cause: (e as Error).cause, cause: (e as Error).cause,
@ -151,15 +153,16 @@ export const getAnnunciById_rawImgUrls = async ({
.selectAll() .selectAll()
.select((_eb) => [withImages(), withVideos()]) .select((_eb) => [withImages(), withVideos()])
.where("id", "=", id) .where("id", "=", id)
.where("tipo", "is not", null)
.executeTakeFirst(); .executeTakeFirst();
if (!annuncio) { if (!annuncio || annuncio.tipo === null) {
throw new TRPCError({ throw new TRPCError({
code: "NOT_FOUND", code: "NOT_FOUND",
message: "Annuncio non trovato", message: "Annuncio non trovato",
}); });
} }
return annuncio; return annuncio as AnnunciWithMedia;
} catch (e) { } catch (e) {
throw new TRPCError({ throw new TRPCError({
code: "INTERNAL_SERVER_ERROR", code: "INTERNAL_SERVER_ERROR",