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 { useSession } from "~/providers/SessionProvider";
|
||||
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
|
||||
import type { AnnuncioData } from "~/server/controllers/annunci.controller";
|
||||
import { generateSSGHelper } from "~/server/utils/ssgHelper";
|
||||
import { api } from "~/utils/api";
|
||||
|
||||
type AnnuncioProps = {
|
||||
cod: string;
|
||||
data: AnnuncioData;
|
||||
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.
|
||||
*/
|
||||
const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
||||
cod,
|
||||
data,
|
||||
flag,
|
||||
meta,
|
||||
}: AnnuncioProps) => {
|
||||
return (
|
||||
<main>
|
||||
<Head>
|
||||
<title>
|
||||
{meta ? `${cod} | ${meta.title}` : "Annuncio non trovato"}
|
||||
</title>
|
||||
<meta content={meta?.description} property="description" />
|
||||
<meta content={meta?.ogUrl} property="og:url" />
|
||||
<title>{`${data.codice} | ${data.titolo_it}`}</title>
|
||||
<meta content={data.desc_it || ""} property="description" />
|
||||
<meta content={data.ogUrl} property="og:url" />
|
||||
<meta content="website" property="og:type" />
|
||||
<meta
|
||||
content={meta ? `${cod} | ${meta.title}` : "Annuncio non trovato"}
|
||||
content={`${data.codice} | ${data.titolo_it}`}
|
||||
property="og:title"
|
||||
/>
|
||||
<meta content={meta?.description} property="og:description" />
|
||||
<meta content={meta?.ogImage} property="og:image" />
|
||||
<meta content={data.desc_it || ""} property="og:description" />
|
||||
<meta content={data.ogImage} property="og:image" />
|
||||
<meta content="1200" property="og:image:width" />
|
||||
<meta content="630" property="og:image:height" />
|
||||
<meta
|
||||
|
|
@ -111,37 +103,29 @@ const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
|||
property="og:logo"
|
||||
/>
|
||||
<meta content="Infoalloggi.it" property="og:site_name" />
|
||||
<link href={meta?.ogUrl || ""} rel="canonical" />
|
||||
<link href={data.ogUrl || ""} rel="canonical" />
|
||||
<link
|
||||
href={`${env.NEXT_PUBLIC_BASE_URL}/annuncio/${cod}`}
|
||||
href={`${env.NEXT_PUBLIC_BASE_URL}/annuncio/${data.codice}`}
|
||||
hrefLang="it"
|
||||
rel="alternate"
|
||||
/>
|
||||
<link
|
||||
href={`${env.NEXT_PUBLIC_BASE_URL}/en/annuncio/${cod}`}
|
||||
href={`${env.NEXT_PUBLIC_BASE_URL}/en/annuncio/${data.codice}`}
|
||||
hrefLang="en"
|
||||
rel="alternate"
|
||||
/>
|
||||
</Head>
|
||||
<AnnuncioView cod={cod} flag={flag} />
|
||||
<AnnuncioView data={data} flag={flag} />
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||
const AnnuncioView = ({ data, flag }: AnnuncioProps) => {
|
||||
const session = useSession();
|
||||
useStaleImageReload();
|
||||
|
||||
const { data, isLoading } = api.annunci.getAnnuncioData.useQuery(
|
||||
{
|
||||
cod: cod,
|
||||
},
|
||||
{ refetchOnMount: false },
|
||||
);
|
||||
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
if (isLoading || session.status === "pending") return <LoadingPage />;
|
||||
if (
|
||||
!data ||
|
||||
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">
|
||||
<CarouselAnnuncio
|
||||
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}
|
||||
/>
|
||||
|
||||
|
|
@ -221,21 +207,31 @@ export async function getStaticProps(
|
|||
const cod = context.params?.cod;
|
||||
if (typeof cod !== "string") {
|
||||
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({
|
||||
id: "ANNUNCIO_INTERACTIONS_DISABLED",
|
||||
});
|
||||
return {
|
||||
props: {
|
||||
cod,
|
||||
data: annuncio.data,
|
||||
//cod,
|
||||
flag,
|
||||
meta,
|
||||
//meta,
|
||||
trpcState: ssg.dehydrate(),
|
||||
},
|
||||
revalidate: 21600, // 6 hours - ensures fresh data 4x per day
|
||||
|
|
@ -674,7 +670,7 @@ const AnnuncioFooter = () => {
|
|||
coverImage={getStorageUrl({
|
||||
storageId: video.thumb,
|
||||
params: {
|
||||
cacheKey: media_updated_at?.toISOString(),
|
||||
cacheKey: media_updated_at || undefined,
|
||||
media: "image",
|
||||
},
|
||||
})}
|
||||
|
|
@ -682,7 +678,7 @@ const AnnuncioFooter = () => {
|
|||
videoSrc={getStorageUrl({
|
||||
storageId: video.video,
|
||||
params: {
|
||||
cacheKey: media_updated_at?.toISOString(),
|
||||
cacheKey: media_updated_at || undefined,
|
||||
media: "video",
|
||||
},
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
get_AnnunciPositionsHandler,
|
||||
getAnnunciById_rawImgUrls,
|
||||
getAnnunciListHandler,
|
||||
getAnnunciMetaByCod,
|
||||
getAnnuncioData,
|
||||
getAnnunciRicerca,
|
||||
getCodici_AnnunciHandler,
|
||||
|
|
@ -58,6 +57,7 @@ export const annunciRouter = createTRPCRouter({
|
|||
cod: z.string(),
|
||||
}),
|
||||
)
|
||||
|
||||
.query(async ({ input }) => {
|
||||
return await getAnnuncioData({ ...input });
|
||||
}),
|
||||
|
|
@ -83,16 +83,6 @@ export const annunciRouter = createTRPCRouter({
|
|||
.query(async ({ 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 () => {
|
||||
return await getCodici_AnnunciHandler();
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { TRPCError } from "@trpc/server";
|
||||
import { env } from "~/env";
|
||||
import type { Result } from "~/lib/result";
|
||||
import { getStorageUrl } from "~/lib/storage_utils";
|
||||
import type {
|
||||
Annunci,
|
||||
|
|
@ -72,8 +73,48 @@ export const getCodici_AnnunciHandler = async (): Promise<string[]> => {
|
|||
}
|
||||
};
|
||||
|
||||
export type AnnuncioData = Awaited<ReturnType<typeof getAnnuncioData>>;
|
||||
export const getAnnuncioData = async ({ cod }: { cod: string }) => {
|
||||
export type AnnuncioData = Pick<
|
||||
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 {
|
||||
const annuncio = await db
|
||||
.selectFrom("annunci")
|
||||
|
|
@ -107,43 +148,18 @@ export const getAnnuncioData = async ({ cod }: { cod: string }) => {
|
|||
"piano_palazzo",
|
||||
"lat_secondario",
|
||||
"lon_secondario",
|
||||
"media_updated_at",
|
||||
])
|
||||
.select((_eb) => [withImages(), withVideos()])
|
||||
.where("annunci.web", "=", true)
|
||||
.where("tipo", "is not", null)
|
||||
.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();
|
||||
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 =
|
||||
annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
|
||||
|
|
@ -156,17 +172,21 @@ export const getAnnunciMetaByCod = async ({ cod }: { cod: string }) => {
|
|||
host: env.BASE_URL,
|
||||
})
|
||||
: `${env.BASE_URL}/og.jpg`;
|
||||
|
||||
return {
|
||||
description: annuncio.desc_it || "",
|
||||
ogImage,
|
||||
success: true,
|
||||
data: {
|
||||
...rest,
|
||||
media_updated_at: media_updated_at?.toISOString() || null,
|
||||
tipo,
|
||||
ogUrl: `${env.BASE_URL}/annuncio/${cod}`,
|
||||
title: annuncio.titolo_it || "",
|
||||
ogImage,
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
throw new TRPCError({
|
||||
cause: (e as Error).cause,
|
||||
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",
|
||||
"numero",
|
||||
"locatore",
|
||||
"tipo_locatore"
|
||||
"tipo_locatore",
|
||||
])
|
||||
.where("id", "=", annuncioId)
|
||||
.executeTakeFirstOrThrow(() => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue