refactor: remove ImageWithFallback component and replace with Next.js Image in various components
This commit is contained in:
parent
a268afee74
commit
cfbeadb93b
7 changed files with 102 additions and 34 deletions
|
|
@ -1,19 +0,0 @@
|
|||
import Image from "next/image";
|
||||
import type { ComponentProps } from "react";
|
||||
|
||||
const fallbackImage = "/fallback-image.png";
|
||||
export const ImageFlbk = (props: ComponentProps<typeof Image>) => {
|
||||
return (
|
||||
<Image
|
||||
{...props}
|
||||
onError={(e) => {
|
||||
e.currentTarget.src = fallbackImage;
|
||||
}}
|
||||
onLoad={(e) => {
|
||||
if (e.currentTarget.naturalWidth === 0) {
|
||||
e.currentTarget.src = fallbackImage;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -8,10 +8,10 @@ import {
|
|||
Star,
|
||||
TrafficCone,
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { type RefObject, useEffect, useRef, useState } from "react";
|
||||
import { useIntersection } from "react-use";
|
||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
|
|
@ -216,7 +216,7 @@ export const CardAnnuncio = ({
|
|||
<CarouselContent>
|
||||
{images?.map((img, idx) => (
|
||||
<CarouselItem key={`${img.img}key`}>
|
||||
<ImageFlbk
|
||||
<Image
|
||||
alt={t.card.alt_immagine}
|
||||
blurDataURL={getStorageUrl({
|
||||
storageId: img.thumb,
|
||||
|
|
@ -298,7 +298,7 @@ export const CardAnnuncio = ({
|
|||
) : (
|
||||
<>
|
||||
{images && images.length > 0 && images[0] ? (
|
||||
<ImageFlbk
|
||||
<Image
|
||||
alt={t.card.alt_immagine}
|
||||
blurDataURL={getStorageUrl({
|
||||
storageId: images[0].thumb,
|
||||
|
|
@ -448,7 +448,7 @@ export const CarouselAnnuncio = ({
|
|||
)}
|
||||
key={`${img.img}-cF`}
|
||||
>
|
||||
<ImageFlbk
|
||||
<Image
|
||||
alt={img.img}
|
||||
className={cn(
|
||||
"aspect-square max-h-92 w-full rounded-md bg-[#e6e9ec] object-cover sm:max-h-80 xl:max-h-[25rem]",
|
||||
|
|
@ -536,7 +536,7 @@ export const CarouselAnnuncio = ({
|
|||
<CarouselContent>
|
||||
{immagini?.map((img, idx) => (
|
||||
<CarouselItem key={`${img.img}-cD`}>
|
||||
<ImageFlbk
|
||||
<Image
|
||||
alt={`carousel-img-${idx}`}
|
||||
className="mx-auto h-[90vh] w-full rounded-md bg-[#e6e9ec] object-contain sm:w-[80vw]"
|
||||
height={1080}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { CircleCheck, ExternalLink, Trash2 } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import toast from "react-hot-toast";
|
||||
import { getStorageUrl } from "~/lib/storage_utils";
|
||||
|
|
@ -8,7 +9,6 @@ import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
|
|||
import type { CompleteUserData } from "~/server/services/user.service";
|
||||
import { api } from "~/utils/api";
|
||||
import { Confirm } from "../confirm";
|
||||
import { ImageFlbk } from "../ImageWithFallback";
|
||||
import { LoadingPage } from "../loading";
|
||||
import { LoadingSpinner } from "../spinner";
|
||||
import { Button } from "../ui/button";
|
||||
|
|
@ -146,7 +146,7 @@ export const DocumentiUploadSection = ({
|
|||
{isPending ? (
|
||||
<LoadingSpinner className="size-24 stroke-neutral-300" />
|
||||
) : (
|
||||
<ImageFlbk
|
||||
<Image
|
||||
alt={title}
|
||||
className="size-24 rounded-md border object-cover"
|
||||
height={96}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { ExternalLink } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
||||
import { AnnuncioActions } from "~/components/servizio/annuncio_actions";
|
||||
import { Interactions } from "~/components/servizio/interactions";
|
||||
import { Button } from "~/components/ui/button";
|
||||
|
|
@ -52,7 +52,7 @@ export const BasicAnnuncioCard = ({
|
|||
</CardHeader>
|
||||
<CardContent className="px-4">
|
||||
<div className="flex w-full flex-col gap-4 md:flex-row md:gap-10">
|
||||
<ImageFlbk
|
||||
<Image
|
||||
alt={data.codice}
|
||||
className="h-auto max-h-72 w-full rounded-md bg-[#e6e9ec] object-contain sm:max-w-xs"
|
||||
height={1080}
|
||||
|
|
|
|||
50
apps/infoalloggi/src/hooks/staleImage.ts
Normal file
50
apps/infoalloggi/src/hooks/staleImage.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"use client";
|
||||
|
||||
import { deleteCookie } from "cookies-next";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const STALE_KEY = "stale-resource";
|
||||
const RELOAD_STORAGE_KEY = "stale-img-reload";
|
||||
const RELOAD_COOLDOWN_MS = 10 * 1000;
|
||||
|
||||
export const useStaleImageReload = () => {
|
||||
useEffect(() => {
|
||||
// Check for stale resource cookie
|
||||
const checkStaleCookie = () => {
|
||||
const hasStale = document.cookie.includes(`${STALE_KEY}=1`);
|
||||
|
||||
if (hasStale) {
|
||||
console.log("Stale resource cookie detected");
|
||||
// Clear the cookie
|
||||
deleteCookie(STALE_KEY);
|
||||
attemptReload();
|
||||
}
|
||||
};
|
||||
|
||||
// Check on mount
|
||||
checkStaleCookie();
|
||||
|
||||
// Also check periodically for dynamically loaded images
|
||||
const interval = setInterval(checkStaleCookie, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
};
|
||||
|
||||
function attemptReload() {
|
||||
const lastAttempt = sessionStorage.getItem(RELOAD_STORAGE_KEY);
|
||||
const now = Date.now();
|
||||
|
||||
if (!lastAttempt) {
|
||||
sessionStorage.setItem(RELOAD_STORAGE_KEY, now.toString());
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
const timeSinceLastAttempt = now - parseInt(lastAttempt, 10);
|
||||
|
||||
if (timeSinceLastAttempt > RELOAD_COOLDOWN_MS) {
|
||||
sessionStorage.setItem(RELOAD_STORAGE_KEY, now.toString());
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import { env } from "~/env";
|
|||
import { TOKEN_CONFIG } from "~/server/auth/configs";
|
||||
import { verifyToken } from "~/server/auth/jwt";
|
||||
|
||||
const STALE_KEY = "stale-resource";
|
||||
export const apisMiddleware = async (req: NextRequest) => {
|
||||
const { pathname, searchParams } = req.nextUrl;
|
||||
|
||||
|
|
@ -68,16 +69,28 @@ export const apisMiddleware = async (req: NextRequest) => {
|
|||
|
||||
// Handle failed responses
|
||||
if (!response.ok) {
|
||||
console.error(
|
||||
`Storage API error: ${response.status} for ${storageUrl}, referrer: ${req.referrer}, url: ${req.url}`,
|
||||
);
|
||||
// console.error(
|
||||
// `Storage API error: ${response.status} for ${storageUrl}, referrer: ${req.referrer}, url: ${req.url}`,
|
||||
// );
|
||||
// 404 = File not found (likely stale URL after media update)
|
||||
// 410 = Gone (explicitly removed)
|
||||
const isStaleResource =
|
||||
response.status === 404 || response.status === 410;
|
||||
|
||||
// Return fallback for image requests
|
||||
if (isImageRequest || isNextImageRequest) {
|
||||
const url = req.nextUrl.clone();
|
||||
url.pathname = "/fallback-image.png";
|
||||
url.search = "";
|
||||
return NextResponse.rewrite(url);
|
||||
const response = NextResponse.rewrite(url);
|
||||
if (isStaleResource) {
|
||||
response.cookies.set(STALE_KEY, "1", {
|
||||
maxAge: 5,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Return fallback for video requests
|
||||
|
|
@ -85,10 +98,32 @@ export const apisMiddleware = async (req: NextRequest) => {
|
|||
const url = req.nextUrl.clone();
|
||||
url.pathname = "/fallback-video.png";
|
||||
url.search = "";
|
||||
return NextResponse.rewrite(url);
|
||||
const response = NextResponse.rewrite(url);
|
||||
if (isStaleResource) {
|
||||
response.cookies.set(STALE_KEY, "1", {
|
||||
maxAge: 5,
|
||||
path: "/",
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
return new NextResponse("Storage API error", { status: response.status });
|
||||
// For non-media requests (e.g., documents), return JSON with reload hint
|
||||
if (isStaleResource) {
|
||||
return NextResponse.json(
|
||||
{ message: "Stale resource, please reload." },
|
||||
{ status: 410 },
|
||||
);
|
||||
}
|
||||
return new NextResponse(`Storage API error: ${response.status}`, {
|
||||
status: response.status,
|
||||
});
|
||||
}
|
||||
|
||||
//ignore if 204 no content, is the favicon request
|
||||
if (response.status === 204) {
|
||||
return new NextResponse(null, { status: 200 });
|
||||
}
|
||||
|
||||
// Get response data
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import {
|
|||
type CaratteristicheFiltered,
|
||||
filteredCaratteristiche,
|
||||
} from "~/hooks/schedaAnnuncioUtils";
|
||||
import { useStaleImageReload } from "~/hooks/staleImage";
|
||||
import { useMediaQuery } from "~/hooks/use-media-query";
|
||||
import { handleConsegna } from "~/lib/annuncio_details";
|
||||
import { replaceWithBr } from "~/lib/newlineToBr";
|
||||
|
|
@ -106,6 +107,7 @@ const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
|||
const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||
const session = useSession();
|
||||
const { locale } = useTranslation();
|
||||
useStaleImageReload();
|
||||
|
||||
const { data, isLoading } = api.annunci.getAnnuncio.useQuery(
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue