feat: implement Result type for handling success and error states in API responses
feat: create AnnuncioNotFound page to handle missing announcements gracefully feat: refactor Annuncio component to use structured AnnuncioData and improve SEO metadata handling refactor: remove unused getAnnuncioMetaByCod function and streamline getAnnuncioData logic
This commit is contained in:
parent
fd60f33bd5
commit
a87b9f57a7
5 changed files with 102 additions and 89 deletions
3
apps/infoalloggi/src/lib/result.ts
Normal file
3
apps/infoalloggi/src/lib/result.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export type Result<T> =
|
||||||
|
| { success: true; data: T }
|
||||||
|
| { success: false; message: string };
|
||||||
4
apps/infoalloggi/src/pages/annuncio-non-trovato.tsx
Normal file
4
apps/infoalloggi/src/pages/annuncio-non-trovato.tsx
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
import FailedAnnuncioLoading from "~/components/failed-loading";
|
||||||
|
export default function AnnuncioNotFound() {
|
||||||
|
return <FailedAnnuncioLoading />;
|
||||||
|
}
|
||||||
|
|
@ -66,18 +66,13 @@ import { useTranslation } from "~/providers/I18nProvider";
|
||||||
import { buildRicercaUrl } from "~/providers/RicercaProvider";
|
import { buildRicercaUrl } from "~/providers/RicercaProvider";
|
||||||
import { useSession } from "~/providers/SessionProvider";
|
import { useSession } from "~/providers/SessionProvider";
|
||||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||||
|
import type { AnnuncioData } from "~/server/controllers/annunci.controller";
|
||||||
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
|
|
||||||
type AnnuncioProps = {
|
type AnnuncioProps = {
|
||||||
cod: string;
|
data: AnnuncioData;
|
||||||
flag: string;
|
flag: string;
|
||||||
meta: {
|
|
||||||
ogImage: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
ogUrl: string;
|
|
||||||
} | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -85,25 +80,22 @@ type AnnuncioProps = {
|
||||||
* Questa pagina mostra i dettagli di un annuncio specifico, identificato dal codice (cod) passato come parametro. Utilizza getStaticProps per prefetchare i dati dell'annuncio e le relative informazioni meta per SEO e condivisione sui social media. Se l'annuncio è in stato "Sospeso" o se si verifica un errore durante il caricamento, viene mostrata una pagina di errore.
|
* Questa pagina mostra i dettagli di un annuncio specifico, identificato dal codice (cod) passato come parametro. Utilizza getStaticProps per prefetchare i dati dell'annuncio e le relative informazioni meta per SEO e condivisione sui social media. Se l'annuncio è in stato "Sospeso" o se si verifica un errore durante il caricamento, viene mostrata una pagina di errore.
|
||||||
*/
|
*/
|
||||||
const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
||||||
cod,
|
data,
|
||||||
flag,
|
flag,
|
||||||
meta,
|
|
||||||
}: AnnuncioProps) => {
|
}: AnnuncioProps) => {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>{`${data.codice} | ${data.titolo_it}`}</title>
|
||||||
{meta ? `${cod} | ${meta.title}` : "Annuncio non trovato"}
|
<meta content={data.desc_it || ""} property="description" />
|
||||||
</title>
|
<meta content={data.ogUrl} property="og:url" />
|
||||||
<meta content={meta?.description} property="description" />
|
|
||||||
<meta content={meta?.ogUrl} property="og:url" />
|
|
||||||
<meta content="website" property="og:type" />
|
<meta content="website" property="og:type" />
|
||||||
<meta
|
<meta
|
||||||
content={meta ? `${cod} | ${meta.title}` : "Annuncio non trovato"}
|
content={`${data.codice} | ${data.titolo_it}`}
|
||||||
property="og:title"
|
property="og:title"
|
||||||
/>
|
/>
|
||||||
<meta content={meta?.description} property="og:description" />
|
<meta content={data.desc_it || ""} property="og:description" />
|
||||||
<meta content={meta?.ogImage} property="og:image" />
|
<meta content={data.ogImage} property="og:image" />
|
||||||
<meta content="1200" property="og:image:width" />
|
<meta content="1200" property="og:image:width" />
|
||||||
<meta content="630" property="og:image:height" />
|
<meta content="630" property="og:image:height" />
|
||||||
<meta
|
<meta
|
||||||
|
|
@ -111,37 +103,29 @@ const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
||||||
property="og:logo"
|
property="og:logo"
|
||||||
/>
|
/>
|
||||||
<meta content="Infoalloggi.it" property="og:site_name" />
|
<meta content="Infoalloggi.it" property="og:site_name" />
|
||||||
<link href={meta?.ogUrl || ""} rel="canonical" />
|
<link href={data.ogUrl || ""} rel="canonical" />
|
||||||
<link
|
<link
|
||||||
href={`${env.NEXT_PUBLIC_BASE_URL}/annuncio/${cod}`}
|
href={`${env.NEXT_PUBLIC_BASE_URL}/annuncio/${data.codice}`}
|
||||||
hrefLang="it"
|
hrefLang="it"
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
/>
|
/>
|
||||||
<link
|
<link
|
||||||
href={`${env.NEXT_PUBLIC_BASE_URL}/en/annuncio/${cod}`}
|
href={`${env.NEXT_PUBLIC_BASE_URL}/en/annuncio/${data.codice}`}
|
||||||
hrefLang="en"
|
hrefLang="en"
|
||||||
rel="alternate"
|
rel="alternate"
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<AnnuncioView cod={cod} flag={flag} />
|
<AnnuncioView data={data} flag={flag} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
const AnnuncioView = ({ data, flag }: AnnuncioProps) => {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
useStaleImageReload();
|
useStaleImageReload();
|
||||||
|
|
||||||
const { data, isLoading } = api.annunci.getAnnuncioData.useQuery(
|
|
||||||
{
|
|
||||||
cod: cod,
|
|
||||||
},
|
|
||||||
{ refetchOnMount: false },
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||||
|
|
||||||
if (isLoading || session.status === "pending") return <LoadingPage />;
|
|
||||||
if (
|
if (
|
||||||
!data ||
|
!data ||
|
||||||
session.status === "error" ||
|
session.status === "error" ||
|
||||||
|
|
@ -156,7 +140,9 @@ const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||||
<div className="mx-auto w-full max-w-400 px-2 sm:px-8">
|
<div className="mx-auto w-full max-w-400 px-2 sm:px-8">
|
||||||
<CarouselAnnuncio
|
<CarouselAnnuncio
|
||||||
immagini={data.images}
|
immagini={data.images}
|
||||||
updated_at={data.media_updated_at}
|
updated_at={
|
||||||
|
data.media_updated_at ? new Date(data.media_updated_at) : null
|
||||||
|
}
|
||||||
videos={data.videos}
|
videos={data.videos}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
@ -221,21 +207,31 @@ export async function getStaticProps(
|
||||||
const cod = context.params?.cod;
|
const cod = context.params?.cod;
|
||||||
if (typeof cod !== "string") {
|
if (typeof cod !== "string") {
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
redirect: {
|
||||||
|
destination: "/annuncio-non-trovato",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const annuncio = await ssg.annunci.getAnnuncioData.fetch({ cod: cod });
|
||||||
|
if (!annuncio.success || annuncio.data.stato === "Sospeso") {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
destination: "/annuncio-non-trovato",
|
||||||
|
permanent: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await ssg.annunci.getAnnuncioData.prefetch({ cod: cod });
|
|
||||||
|
|
||||||
const meta = await ssg.annunci.getAnnuncioMeta.fetch({ cod: cod });
|
|
||||||
|
|
||||||
const flag = await ssg.flags.GetFlagValue.fetch({
|
const flag = await ssg.flags.GetFlagValue.fetch({
|
||||||
id: "ANNUNCIO_INTERACTIONS_DISABLED",
|
id: "ANNUNCIO_INTERACTIONS_DISABLED",
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
cod,
|
data: annuncio.data,
|
||||||
|
//cod,
|
||||||
flag,
|
flag,
|
||||||
meta,
|
//meta,
|
||||||
trpcState: ssg.dehydrate(),
|
trpcState: ssg.dehydrate(),
|
||||||
},
|
},
|
||||||
revalidate: 21600, // 6 hours - ensures fresh data 4x per day
|
revalidate: 21600, // 6 hours - ensures fresh data 4x per day
|
||||||
|
|
@ -674,7 +670,7 @@ const AnnuncioFooter = () => {
|
||||||
coverImage={getStorageUrl({
|
coverImage={getStorageUrl({
|
||||||
storageId: video.thumb,
|
storageId: video.thumb,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at || undefined,
|
||||||
media: "image",
|
media: "image",
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|
@ -682,7 +678,7 @@ const AnnuncioFooter = () => {
|
||||||
videoSrc={getStorageUrl({
|
videoSrc={getStorageUrl({
|
||||||
storageId: video.video,
|
storageId: video.video,
|
||||||
params: {
|
params: {
|
||||||
cacheKey: media_updated_at?.toISOString(),
|
cacheKey: media_updated_at || undefined,
|
||||||
media: "video",
|
media: "video",
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import {
|
||||||
get_AnnunciPositionsHandler,
|
get_AnnunciPositionsHandler,
|
||||||
getAnnunciById_rawImgUrls,
|
getAnnunciById_rawImgUrls,
|
||||||
getAnnunciListHandler,
|
getAnnunciListHandler,
|
||||||
getAnnunciMetaByCod,
|
|
||||||
getAnnuncioData,
|
getAnnuncioData,
|
||||||
getAnnunciRicerca,
|
getAnnunciRicerca,
|
||||||
getCodici_AnnunciHandler,
|
getCodici_AnnunciHandler,
|
||||||
|
|
@ -58,6 +57,7 @@ export const annunciRouter = createTRPCRouter({
|
||||||
cod: z.string(),
|
cod: z.string(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getAnnuncioData({ ...input });
|
return await getAnnuncioData({ ...input });
|
||||||
}),
|
}),
|
||||||
|
|
@ -83,16 +83,6 @@ export const annunciRouter = createTRPCRouter({
|
||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
return await getAnnunciById_rawImgUrls({ ...input });
|
return await getAnnunciById_rawImgUrls({ ...input });
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getAnnuncioMeta: publicProcedure
|
|
||||||
.input(
|
|
||||||
z.object({
|
|
||||||
cod: z.string(),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.query(async ({ input }) => {
|
|
||||||
return await getAnnunciMetaByCod({ ...input });
|
|
||||||
}),
|
|
||||||
getCodici: publicProcedure.query(async () => {
|
getCodici: publicProcedure.query(async () => {
|
||||||
return await getCodici_AnnunciHandler();
|
return await getCodici_AnnunciHandler();
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
|
import type { Result } from "~/lib/result";
|
||||||
import { getStorageUrl } from "~/lib/storage_utils";
|
import { getStorageUrl } from "~/lib/storage_utils";
|
||||||
import type {
|
import type {
|
||||||
Annunci,
|
Annunci,
|
||||||
|
|
@ -72,8 +73,48 @@ export const getCodici_AnnunciHandler = async (): Promise<string[]> => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AnnuncioData = Awaited<ReturnType<typeof getAnnuncioData>>;
|
export type AnnuncioData = Pick<
|
||||||
export const getAnnuncioData = async ({ cod }: { cod: string }) => {
|
Annunci,
|
||||||
|
| "anno"
|
||||||
|
| "accessori"
|
||||||
|
| "caratteristiche"
|
||||||
|
| "categorie"
|
||||||
|
| "desc_en"
|
||||||
|
| "desc_it"
|
||||||
|
| "titolo_it"
|
||||||
|
| "titolo_en"
|
||||||
|
| "stato"
|
||||||
|
| "id"
|
||||||
|
| "codice"
|
||||||
|
| "comune"
|
||||||
|
| "consegna"
|
||||||
|
| "external_videos"
|
||||||
|
| "prezzo"
|
||||||
|
| "numero_camere"
|
||||||
|
| "numero_bagni"
|
||||||
|
| "numero_balconi"
|
||||||
|
| "numero_postiauto"
|
||||||
|
| "numero_box"
|
||||||
|
| "numero_vani"
|
||||||
|
| "numero_terrazzi"
|
||||||
|
| "mq"
|
||||||
|
| "piano"
|
||||||
|
| "piano_palazzo"
|
||||||
|
| "lat_secondario"
|
||||||
|
| "lon_secondario"
|
||||||
|
> & {
|
||||||
|
tipo: string;
|
||||||
|
media_updated_at: string | null;
|
||||||
|
images: Pick<ImagesRefs, "img" | "thumb">[];
|
||||||
|
videos: Pick<VideosRefs, "video" | "thumb">[];
|
||||||
|
ogUrl: string;
|
||||||
|
ogImage: string;
|
||||||
|
};
|
||||||
|
export const getAnnuncioData = async ({
|
||||||
|
cod,
|
||||||
|
}: {
|
||||||
|
cod: string;
|
||||||
|
}): Promise<Result<AnnuncioData>> => {
|
||||||
try {
|
try {
|
||||||
const annuncio = await db
|
const annuncio = await db
|
||||||
.selectFrom("annunci")
|
.selectFrom("annunci")
|
||||||
|
|
@ -107,43 +148,18 @@ export const getAnnuncioData = async ({ cod }: { cod: string }) => {
|
||||||
"piano_palazzo",
|
"piano_palazzo",
|
||||||
"lat_secondario",
|
"lat_secondario",
|
||||||
"lon_secondario",
|
"lon_secondario",
|
||||||
"media_updated_at",
|
|
||||||
])
|
])
|
||||||
.select((_eb) => [withImages(), withVideos()])
|
.select((_eb) => [withImages(), withVideos()])
|
||||||
.where("annunci.web", "=", true)
|
.where("annunci.web", "=", true)
|
||||||
.where("tipo", "is not", null)
|
.where("tipo", "is not", null)
|
||||||
.where("codice", "=", cod)
|
.where("codice", "=", cod)
|
||||||
.executeTakeFirstOrThrow(
|
|
||||||
() => new Error(`Annuncio non trovato - codice: ${cod}`),
|
|
||||||
);
|
|
||||||
const { tipo, ...rest } = annuncio;
|
|
||||||
if (tipo === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { tipo, ...rest };
|
|
||||||
} catch (e) {
|
|
||||||
throw new TRPCError({
|
|
||||||
cause: (e as Error).cause,
|
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
|
||||||
message: `Errore query getAnnunciByCod: ${(e as Error).message}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
export const getAnnunciMetaByCod = async ({ cod }: { cod: string }) => {
|
|
||||||
try {
|
|
||||||
const annuncio = await db
|
|
||||||
.selectFrom("annunci")
|
|
||||||
.select((_eb) => [
|
|
||||||
"titolo_it",
|
|
||||||
"desc_it",
|
|
||||||
withImages(),
|
|
||||||
"media_updated_at",
|
|
||||||
])
|
|
||||||
.where("annunci.web", "=", true)
|
|
||||||
.where("codice", "=", cod)
|
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
if (!annuncio) {
|
if (!annuncio) {
|
||||||
return null;
|
return { success: false, message: "Annuncio non trovato" };
|
||||||
|
}
|
||||||
|
const { tipo, media_updated_at, ...rest } = annuncio;
|
||||||
|
if (tipo === null) {
|
||||||
|
return { success: false, message: "Annuncio non trovato" };
|
||||||
}
|
}
|
||||||
const ogImage =
|
const ogImage =
|
||||||
annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
|
annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
|
||||||
|
|
@ -156,17 +172,21 @@ export const getAnnunciMetaByCod = async ({ cod }: { cod: string }) => {
|
||||||
host: env.BASE_URL,
|
host: env.BASE_URL,
|
||||||
})
|
})
|
||||||
: `${env.BASE_URL}/og.jpg`;
|
: `${env.BASE_URL}/og.jpg`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
description: annuncio.desc_it || "",
|
success: true,
|
||||||
ogImage,
|
data: {
|
||||||
|
...rest,
|
||||||
|
media_updated_at: media_updated_at?.toISOString() || null,
|
||||||
|
tipo,
|
||||||
ogUrl: `${env.BASE_URL}/annuncio/${cod}`,
|
ogUrl: `${env.BASE_URL}/annuncio/${cod}`,
|
||||||
title: annuncio.titolo_it || "",
|
ogImage,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
cause: (e as Error).cause,
|
||||||
code: "INTERNAL_SERVER_ERROR",
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
message: `Errore query getAnnunciMetaByCod: ${(e as Error).message}`,
|
message: `Errore query getAnnunciByCod: ${(e as Error).message}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -493,7 +513,7 @@ export const getProprietarioDataHandler = async ({
|
||||||
"lon",
|
"lon",
|
||||||
"numero",
|
"numero",
|
||||||
"locatore",
|
"locatore",
|
||||||
"tipo_locatore"
|
"tipo_locatore",
|
||||||
])
|
])
|
||||||
.where("id", "=", annuncioId)
|
.where("id", "=", annuncioId)
|
||||||
.executeTakeFirstOrThrow(() => {
|
.executeTakeFirstOrThrow(() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue