diff --git a/apps/infoalloggi/emails/onboarding-servizio.tsx b/apps/infoalloggi/emails/onboarding-servizio.tsx index 25a5b18..2a4d51f 100644 --- a/apps/infoalloggi/emails/onboarding-servizio.tsx +++ b/apps/infoalloggi/emails/onboarding-servizio.tsx @@ -59,27 +59,27 @@ const OnboardingServizio = ({ Annunci selezionati per te: -
- +
+ {`Annuncio -
+
Codice: {annuncio.codice}
{annuncio.titolo || "N/A"} - + Prezzo: € {(annuncio.prezzo / 1e2).toFixed(2)} diff --git a/apps/infoalloggi/public/bkp.sitemap.xml b/apps/infoalloggi/public/bkp.sitemap.xml deleted file mode 100644 index 499646b..0000000 --- a/apps/infoalloggi/public/bkp.sitemap.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - https://www.infoalloggi.it - 2025-08-23T15:09:55+00:00 - 1.00 - - - https://www.infoalloggi.it/annunci - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/prezzi - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/guida - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/contatti - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/proprietari - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/annunci?comune=Bassano+del+Grappa - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/annunci?tipo=Transitorio - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/annunci?comune=Bassano+del+Grappa&tipo=Transitorio - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/annunci?tipo=Stabile - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/annunci?comune=Marostica - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/chi-siamo - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/termini-condizioni - 2025-08-23T15:09:55+00:00 - 0.80 - - - https://www.infoalloggi.it/privacy-policy - 2025-08-23T15:09:55+00:00 - 0.80 - - - - \ No newline at end of file diff --git a/apps/infoalloggi/src/_app/layout.tsx b/apps/infoalloggi/src/_app/layout.tsx deleted file mode 100644 index cfb33e4..0000000 --- a/apps/infoalloggi/src/_app/layout.tsx +++ /dev/null @@ -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 ( - - {children} - - ); -} diff --git a/apps/infoalloggi/src/_app/lib/annunci.ts b/apps/infoalloggi/src/_app/lib/annunci.ts deleted file mode 100644 index 398c1df..0000000 --- a/apps/infoalloggi/src/_app/lib/annunci.ts +++ /dev/null @@ -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 []; - } -} diff --git a/apps/infoalloggi/src/_app/testapp/page.tsx b/apps/infoalloggi/src/_app/testapp/page.tsx deleted file mode 100644 index 1241c85..0000000 --- a/apps/infoalloggi/src/_app/testapp/page.tsx +++ /dev/null @@ -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 ( -
-

Test App Page

- -
-

Data from Database:

- - }> - - -
-
- ); -} diff --git a/apps/infoalloggi/src/_app/ui/annunci.tsx b/apps/infoalloggi/src/_app/ui/annunci.tsx deleted file mode 100644 index a359d22..0000000 --- a/apps/infoalloggi/src/_app/ui/annunci.tsx +++ /dev/null @@ -1,19 +0,0 @@ -"use client"; -import { use } from "react"; -import type { Annunci } from "~/schemas/public/Annunci"; - -type AnnunciProps = { - annunci: Promise[]>; -}; - -export default function AnnunciList({ annunci }: AnnunciProps) { - const allAnnunci = use(annunci); - - return ( -
    - {allAnnunci.map((ann) => ( -
  • {ann.codice}
  • - ))} -
- ); -} diff --git a/apps/infoalloggi/src/components/annunci_map.tsx b/apps/infoalloggi/src/components/annunci_map.tsx index ea0e4fc..0484340 100644 --- a/apps/infoalloggi/src/components/annunci_map.tsx +++ b/apps/infoalloggi/src/components/annunci_map.tsx @@ -53,14 +53,14 @@ const MapDisplay = memo( selectMaxBudget, }: { selectTipo: string; - selectComune: string; + selectComune: string | null; selectConsegna: number[] | null; setSelected: Dispatch>; selectMinBudget: number | null; selectMaxBudget: number | null; }) => { const { data, isLoading } = api.annunci.getMapping.useQuery({ - comune: selectComune, + comune: selectComune || undefined, consegna: selectConsegna || undefined, tipologia: selectTipo, minBudget: selectMinBudget || undefined, diff --git a/apps/infoalloggi/src/components/annunci_tutorial.tsx b/apps/infoalloggi/src/components/annunci_tutorial.tsx index d797199..a714837 100644 --- a/apps/infoalloggi/src/components/annunci_tutorial.tsx +++ b/apps/infoalloggi/src/components/annunci_tutorial.tsx @@ -14,6 +14,7 @@ import { DialogTitle, } from "~/components/ui/dialog"; import { useTranslation } from "~/providers/I18nProvider"; +import { buildRicercaUrl } from "~/providers/RicercaProvider"; 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 }); if (v !== null) { - window.location.assign(`/annunci?tipo=${v}`); + window.location.assign(buildRicercaUrl({ tipo: v })); } setOpen(false); }; diff --git a/apps/infoalloggi/src/components/annuncio_card.tsx b/apps/infoalloggi/src/components/annuncio_card.tsx index 3122e69..79084e1 100644 --- a/apps/infoalloggi/src/components/annuncio_card.tsx +++ b/apps/infoalloggi/src/components/annuncio_card.tsx @@ -99,7 +99,7 @@ const SkeletonCardAnnuncio = () => {
-
+
@@ -451,7 +451,7 @@ export const CarouselAnnuncio = ({ {img.img} +
-
+

diff --git a/apps/infoalloggi/src/components/frequent_searches.tsx b/apps/infoalloggi/src/components/frequent_searches.tsx new file mode 100644 index 0000000..9700aec --- /dev/null +++ b/apps/infoalloggi/src/components/frequent_searches.tsx @@ -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 ( + + + + {t.title} + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/apps/infoalloggi/src/components/servizio/compatibili_dialog.tsx b/apps/infoalloggi/src/components/servizio/compatibili_dialog.tsx index 24d06bc..c9676cb 100644 --- a/apps/infoalloggi/src/components/servizio/compatibili_dialog.tsx +++ b/apps/infoalloggi/src/components/servizio/compatibili_dialog.tsx @@ -35,7 +35,7 @@ export const AnnunciCompatibili = () => { Esplora Annunci Compatibili - + Annunci compatibili con i parametri del servizio diff --git a/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx b/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx index 926d805..acf4adb 100644 --- a/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx +++ b/apps/infoalloggi/src/forms/FormEditAnnuncio.tsx @@ -104,7 +104,7 @@ export const AnnuncioEditForm = ({ data }: { data: AnnunciWithMedia }) => { stato: z.string().nullable(), // DATI CONTRATTO - tipo: z.string().nullable(), + tipo: z.string(), titolo_en: z.string().nullable(), titolo_it: z.string().nullable(), unita_condominio: z.number().nullable(), diff --git a/apps/infoalloggi/src/i18n/locales.ts b/apps/infoalloggi/src/i18n/locales.ts index 2c14366..c25b794 100644 --- a/apps/infoalloggi/src/i18n/locales.ts +++ b/apps/infoalloggi/src/i18n/locales.ts @@ -235,6 +235,7 @@ export type LangDict = { endtxt: string; endtxt2: string; }; + annunci: { titolo: string; fs_tipologia: { diff --git a/apps/infoalloggi/src/pages/500Auth.tsx b/apps/infoalloggi/src/pages/500Auth.tsx index 6e79be0..24c10ab 100644 --- a/apps/infoalloggi/src/pages/500Auth.tsx +++ b/apps/infoalloggi/src/pages/500Auth.tsx @@ -5,7 +5,7 @@ export default function Custom500Auth() { const { t } = useTranslation(); return (
-
+

diff --git a/apps/infoalloggi/src/pages/annunci.tsx b/apps/infoalloggi/src/pages/annunci.tsx index cd6a1e4..31aa5e9 100644 --- a/apps/infoalloggi/src/pages/annunci.tsx +++ b/apps/infoalloggi/src/pages/annunci.tsx @@ -105,14 +105,17 @@ export const getServerSideProps = (async () => { }) satisfies GetServerSideProps; const Ricerca = () => { - const { map } = useRicerca(); + const { map, isLoading } = useRicerca(); return (
-
{map ? : }
+ +
+ {isLoading ? : map ? : } +
); @@ -122,14 +125,12 @@ const Filters = () => { const { t } = useTranslation(); const { tipo, - setTipo, comune, - setComune, sort, - setSort, comuniOptions, tipologieOptions, - ResetParams, + setAllParams, + reset, nFiltri, } = useRicerca(); @@ -138,39 +139,55 @@ const Filters = () => { { - await setTipo(""); + await setAllParams({ + tipo: "", + }); }} + id="tipologia-filter" label={t.annunci.fs_tipologia.titolo} onValueChange={async (value: string) => { - await setTipo(value); + await setAllParams({ + tipo: value, + }); }} options={tipologieOptions} placeholder={t.seleziona_placeholder} value={tipo} /> { - await setComune(null); + await setAllParams({ + comune: null, + }); }} + id="comune-filter" + key={comune} label={t.annunci.comune} onValueChange={async (value: string) => { - await setComune(value); + await setAllParams({ + comune: value, + }); }} options={comuniOptions} placeholder={t.seleziona_placeholder} - value={comune} + value={comune || undefined} /> { - await setSort("Recenti"); + await setAllParams({ + sort: "Recenti", + }); }} + id="sort-filter" label={t.ordina_per} onValueChange={async (value: SortTypes) => { - await setSort(value); + await setAllParams({ + sort: value, + }); }} options={["Recenti", "Prezzo", "Consegna", "Mq"]} placeholder={t.seleziona_placeholder} @@ -179,7 +196,7 @@ const Filters = () => {

- +
@@ -106,68 +105,3 @@ const TrovaCasaCTA = (props: { testi: LangDict }) => {
); }; - -const FrequentSearches = () => { - const { locale } = useTranslation(); - return ( - - - - {locale === "en" ? "Frequent Searches" : "Ricerche frequenti"} - - - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/apps/infoalloggi/src/pages/sitemap.xml.ts b/apps/infoalloggi/src/pages/sitemap.xml.ts index 2c9173a..f3c294a 100644 --- a/apps/infoalloggi/src/pages/sitemap.xml.ts +++ b/apps/infoalloggi/src/pages/sitemap.xml.ts @@ -1,5 +1,6 @@ import type { GetServerSideProps } from "next"; import { env } from "~/env"; +import { buildRicercaUrl } from "~/providers/RicercaProvider"; import { generateSSGHelper } from "~/server/utils/ssgHelper"; async function generateSiteMap() { @@ -13,10 +14,11 @@ async function generateSiteMap() { `${env.NEXT_PUBLIC_BASE_URL}/proprietari`, `${env.NEXT_PUBLIC_BASE_URL}/guida`, `${env.NEXT_PUBLIC_BASE_URL}/annunci`, - `${env.NEXT_PUBLIC_BASE_URL}/annunci?t=Transitorio`, - `${env.NEXT_PUBLIC_BASE_URL}/annunci?c=Bassano+del+Grappa`, - `${env.NEXT_PUBLIC_BASE_URL}/annunci?t=Stabile`, - `${env.NEXT_PUBLIC_BASE_URL}/annunci?c=Marostica`, + `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ tipo: "Transitorio" })}`, + `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ comune: "Bassano del Grappa" })}`, + `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ comune: "Bassano del Grappa", tipo: "Transitorio" })}`, + `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ tipo: "Stabile" })}`, + `${env.NEXT_PUBLIC_BASE_URL}${buildRicercaUrl({ comune: "Marostica" })}`, ]; const allUrls = [...staticUrls, ...dynamicUrls]; diff --git a/apps/infoalloggi/src/providers/RicercaProvider.tsx b/apps/infoalloggi/src/providers/RicercaProvider.tsx index 952c3b2..83c9206 100644 --- a/apps/infoalloggi/src/providers/RicercaProvider.tsx +++ b/apps/infoalloggi/src/providers/RicercaProvider.tsx @@ -5,64 +5,85 @@ import { parseAsInteger, parseAsString, parseAsStringLiteral, - useQueryState, + useQueryStates, } from "nuqs"; import { createContext, type FC, type ReactNode, + useCallback, useContext, + useEffect, useMemo, + useState, useTransition, } 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): 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 = { map: boolean; - setMap: ( - value: boolean | ((old: boolean) => boolean | null) | null, - ) => Promise; - tipo: string; - setTipo: ( - value: string | ((old: string) => string | null) | null, - ) => Promise; - comune: string; - setComune: ( - value: string | ((old: string) => string | null) | null, - ) => Promise; - consegna: number[] | null; - setConsegna: ( - value: number[] | ((old: number[] | null) => number[] | null) | null, - ) => Promise; - - minBudget: number | null; - setMinBudget: ( - value: number | ((old: number | null) => number | null) | null, - ) => Promise; - maxBudget: number | null; - setMaxBudget: ( - value: number | ((old: number | null) => number | null) | null, - ) => Promise; - + tipo: RicercaUrlParams["tipo"]; + comune: RicercaUrlParams["comune"] | null; + consegna: RicercaUrlParams["consegna"] | null; + minBudget: RicercaUrlParams["minBudget"] | null; + maxBudget: RicercaUrlParams["maxBudget"] | null; 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; + reset: () => void; comuniOptions: string[]; tipologieOptions: string[]; consegnaOptions: number[]; sortOptions: typeof sortOptions; - ResetParams: () => Promise; - nFiltri: number; isLoading: boolean; }; -const sortOptions = ["Recenti", "Prezzo", "Consegna", "Mq"] as const; - -//sort as string union -export type SortTypes = (typeof sortOptions)[number]; - export type AnnunciOptions = { comune: string; tipo: string; @@ -84,39 +105,36 @@ export const RicercaProvider: FC<{ children: ReactNode; options: AnnunciOptions; }> = ({ children, options }) => { + const [nFiltri, setNFiltri] = useState(0); const [isLoading, startTransition] = useTransition(); - const [map, setMap] = useQueryState( - "m", - parseAsBoolean.withDefault(false).withOptions({ startTransition }), - ); - const [tipo, setTipo] = useQueryState( - "t", - parseAsString.withDefault("").withOptions({ startTransition }), - ); - const [comune, setComune] = useQueryState( - "c", - parseAsString.withDefault("").withOptions({ startTransition }), - ); - const [consegna, setConsegna] = useQueryState( - "f", - 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 [ + { map, tipo, comune, consegna, minBudget, maxBudget, sort }, + setAllParams, + ] = useQueryStates( + { + map: parseAsBoolean.withDefault(false), + tipo: parseAsString.withDefault(""), + comune: parseAsString.withDefault(""), + consegna: parseAsArrayOf(parseAsInteger), + minBudget: parseAsInteger, + maxBudget: parseAsInteger, + sort: parseAsStringLiteral(sortOptions).withDefault("Recenti"), + }, + { startTransition, shallow: true, urlKeys: paramsMapping }, ); + const reset = useCallback(() => { + setAllParams({ + tipo: "", + comune: "", + consegna: null, + minBudget: null, + maxBudget: null, + sort: "Recenti", + }); + }, []); + const tipologieOptions = useMemo(() => { const set = new Set(); options.forEach((item) => { @@ -172,56 +190,43 @@ export const RicercaProvider: FC<{ }); }, [options, tipo, comune, minBudget, maxBudget]); - const ResetParams = async () => { - await setTipo(""); - await setComune(""); - await setConsegna(null); - await setMinBudget(null); - await setMaxBudget(null); - await setSort("Recenti"); + const updateNFiltri = () => { + setNFiltri( + [tipo, comune].reduce( + (count, item) => (item !== "" ? count + 1 : count), + 0, + ) + + (consegna !== null ? 1 : 0) + + (sort !== "Recenti" ? 1 : 0) + + (minBudget !== null ? 1 : 0) + + (maxBudget !== null ? 1 : 0), + ); }; - const nFiltri = - [tipo, comune].reduce( - (count, item) => (item !== "" ? count + 1 : count), - 0, - ) + - (consegna !== null ? 1 : 0) + - (sort !== "Recenti" ? 1 : 0) + - (minBudget !== null ? 1 : 0) + - (maxBudget !== null ? 1 : 0); + useEffect(() => { + updateNFiltri(); + }, [tipo, comune, consegna, minBudget, maxBudget, sort]); return ( {children} diff --git a/apps/infoalloggi/src/server/controllers/annunci.controller.ts b/apps/infoalloggi/src/server/controllers/annunci.controller.ts index 3b0031b..2f94630 100644 --- a/apps/infoalloggi/src/server/controllers/annunci.controller.ts +++ b/apps/infoalloggi/src/server/controllers/annunci.controller.ts @@ -19,6 +19,7 @@ import { revalidate } from "../utils/revalidationHelper"; // analytics: true, // }); export type AnnunciWithMedia = Annunci & { + tipo: string; images: Pick[]; videos: Pick[]; }; @@ -82,14 +83,15 @@ export const getAnnunciByCod = async ({ .selectAll() .select((_eb) => [withImages(), withVideos()]) .where("annunci.web", "=", true) + .where("tipo", "is not", null) .where("codice", "=", cod) .executeTakeFirst(); - if (!annuncio) { + if (!annuncio || annuncio.tipo === null) { return null; } - return annuncio; + return annuncio as AnnunciWithMedia; } catch (e) { throw new TRPCError({ cause: (e as Error).cause, @@ -151,15 +153,16 @@ export const getAnnunciById_rawImgUrls = async ({ .selectAll() .select((_eb) => [withImages(), withVideos()]) .where("id", "=", id) + .where("tipo", "is not", null) .executeTakeFirst(); - if (!annuncio) { + if (!annuncio || annuncio.tipo === null) { throw new TRPCError({ code: "NOT_FOUND", message: "Annuncio non trovato", }); } - return annuncio; + return annuncio as AnnunciWithMedia; } catch (e) { throw new TRPCError({ code: "INTERNAL_SERVER_ERROR",