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,
|
Star,
|
||||||
TrafficCone,
|
TrafficCone,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { type RefObject, useEffect, useRef, useState } from "react";
|
import { type RefObject, useEffect, useRef, useState } from "react";
|
||||||
import { useIntersection } from "react-use";
|
import { useIntersection } from "react-use";
|
||||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
|
||||||
import {
|
import {
|
||||||
Carousel,
|
Carousel,
|
||||||
CarouselContent,
|
CarouselContent,
|
||||||
|
|
@ -216,7 +216,7 @@ export const CardAnnuncio = ({
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{images?.map((img, idx) => (
|
{images?.map((img, idx) => (
|
||||||
<CarouselItem key={`${img.img}key`}>
|
<CarouselItem key={`${img.img}key`}>
|
||||||
<ImageFlbk
|
<Image
|
||||||
alt={t.card.alt_immagine}
|
alt={t.card.alt_immagine}
|
||||||
blurDataURL={getStorageUrl({
|
blurDataURL={getStorageUrl({
|
||||||
storageId: img.thumb,
|
storageId: img.thumb,
|
||||||
|
|
@ -298,7 +298,7 @@ export const CardAnnuncio = ({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{images && images.length > 0 && images[0] ? (
|
{images && images.length > 0 && images[0] ? (
|
||||||
<ImageFlbk
|
<Image
|
||||||
alt={t.card.alt_immagine}
|
alt={t.card.alt_immagine}
|
||||||
blurDataURL={getStorageUrl({
|
blurDataURL={getStorageUrl({
|
||||||
storageId: images[0].thumb,
|
storageId: images[0].thumb,
|
||||||
|
|
@ -448,7 +448,7 @@ export const CarouselAnnuncio = ({
|
||||||
)}
|
)}
|
||||||
key={`${img.img}-cF`}
|
key={`${img.img}-cF`}
|
||||||
>
|
>
|
||||||
<ImageFlbk
|
<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-[25rem]",
|
||||||
|
|
@ -536,7 +536,7 @@ export const CarouselAnnuncio = ({
|
||||||
<CarouselContent>
|
<CarouselContent>
|
||||||
{immagini?.map((img, idx) => (
|
{immagini?.map((img, idx) => (
|
||||||
<CarouselItem key={`${img.img}-cD`}>
|
<CarouselItem key={`${img.img}-cD`}>
|
||||||
<ImageFlbk
|
<Image
|
||||||
alt={`carousel-img-${idx}`}
|
alt={`carousel-img-${idx}`}
|
||||||
className="mx-auto h-[90vh] w-full rounded-md bg-[#e6e9ec] object-contain sm:w-[80vw]"
|
className="mx-auto h-[90vh] w-full rounded-md bg-[#e6e9ec] object-contain sm:w-[80vw]"
|
||||||
height={1080}
|
height={1080}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { CircleCheck, ExternalLink, Trash2 } from "lucide-react";
|
import { CircleCheck, ExternalLink, Trash2 } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { getStorageUrl } from "~/lib/storage_utils";
|
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 type { CompleteUserData } from "~/server/services/user.service";
|
||||||
import { api } from "~/utils/api";
|
import { api } from "~/utils/api";
|
||||||
import { Confirm } from "../confirm";
|
import { Confirm } from "../confirm";
|
||||||
import { ImageFlbk } from "../ImageWithFallback";
|
|
||||||
import { LoadingPage } from "../loading";
|
import { LoadingPage } from "../loading";
|
||||||
import { LoadingSpinner } from "../spinner";
|
import { LoadingSpinner } from "../spinner";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
|
|
@ -146,7 +146,7 @@ export const DocumentiUploadSection = ({
|
||||||
{isPending ? (
|
{isPending ? (
|
||||||
<LoadingSpinner className="size-24 stroke-neutral-300" />
|
<LoadingSpinner className="size-24 stroke-neutral-300" />
|
||||||
) : (
|
) : (
|
||||||
<ImageFlbk
|
<Image
|
||||||
alt={title}
|
alt={title}
|
||||||
className="size-24 rounded-md border object-cover"
|
className="size-24 rounded-md border object-cover"
|
||||||
height={96}
|
height={96}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { ExternalLink } from "lucide-react";
|
import { ExternalLink } from "lucide-react";
|
||||||
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ImageFlbk } from "~/components/ImageWithFallback";
|
|
||||||
import { AnnuncioActions } from "~/components/servizio/annuncio_actions";
|
import { AnnuncioActions } from "~/components/servizio/annuncio_actions";
|
||||||
import { Interactions } from "~/components/servizio/interactions";
|
import { Interactions } from "~/components/servizio/interactions";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
|
|
@ -52,7 +52,7 @@ export const BasicAnnuncioCard = ({
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-4">
|
<CardContent className="px-4">
|
||||||
<div className="flex w-full flex-col gap-4 md:flex-row md:gap-10">
|
<div className="flex w-full flex-col gap-4 md:flex-row md:gap-10">
|
||||||
<ImageFlbk
|
<Image
|
||||||
alt={data.codice}
|
alt={data.codice}
|
||||||
className="h-auto max-h-72 w-full rounded-md bg-[#e6e9ec] object-contain sm:max-w-xs"
|
className="h-auto max-h-72 w-full rounded-md bg-[#e6e9ec] object-contain sm:max-w-xs"
|
||||||
height={1080}
|
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 { TOKEN_CONFIG } from "~/server/auth/configs";
|
||||||
import { verifyToken } from "~/server/auth/jwt";
|
import { verifyToken } from "~/server/auth/jwt";
|
||||||
|
|
||||||
|
const STALE_KEY = "stale-resource";
|
||||||
export const apisMiddleware = async (req: NextRequest) => {
|
export const apisMiddleware = async (req: NextRequest) => {
|
||||||
const { pathname, searchParams } = req.nextUrl;
|
const { pathname, searchParams } = req.nextUrl;
|
||||||
|
|
||||||
|
|
@ -68,16 +69,28 @@ export const apisMiddleware = async (req: NextRequest) => {
|
||||||
|
|
||||||
// Handle failed responses
|
// Handle failed responses
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error(
|
// console.error(
|
||||||
`Storage API error: ${response.status} for ${storageUrl}, referrer: ${req.referrer}, url: ${req.url}`,
|
// `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
|
// Return fallback for image requests
|
||||||
if (isImageRequest || isNextImageRequest) {
|
if (isImageRequest || isNextImageRequest) {
|
||||||
const url = req.nextUrl.clone();
|
const url = req.nextUrl.clone();
|
||||||
url.pathname = "/fallback-image.png";
|
url.pathname = "/fallback-image.png";
|
||||||
url.search = "";
|
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
|
// Return fallback for video requests
|
||||||
|
|
@ -85,10 +98,32 @@ export const apisMiddleware = async (req: NextRequest) => {
|
||||||
const url = req.nextUrl.clone();
|
const url = req.nextUrl.clone();
|
||||||
url.pathname = "/fallback-video.png";
|
url.pathname = "/fallback-video.png";
|
||||||
url.search = "";
|
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
|
// Get response data
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ import {
|
||||||
type CaratteristicheFiltered,
|
type CaratteristicheFiltered,
|
||||||
filteredCaratteristiche,
|
filteredCaratteristiche,
|
||||||
} from "~/hooks/schedaAnnuncioUtils";
|
} from "~/hooks/schedaAnnuncioUtils";
|
||||||
|
import { useStaleImageReload } from "~/hooks/staleImage";
|
||||||
import { useMediaQuery } from "~/hooks/use-media-query";
|
import { useMediaQuery } from "~/hooks/use-media-query";
|
||||||
import { handleConsegna } from "~/lib/annuncio_details";
|
import { handleConsegna } from "~/lib/annuncio_details";
|
||||||
import { replaceWithBr } from "~/lib/newlineToBr";
|
import { replaceWithBr } from "~/lib/newlineToBr";
|
||||||
|
|
@ -106,6 +107,7 @@ const AnnuncioDettaglio: NextPage<AnnuncioProps> = ({
|
||||||
const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
const AnnuncioView = ({ cod, flag }: Omit<AnnuncioProps, "meta">) => {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const { locale } = useTranslation();
|
const { locale } = useTranslation();
|
||||||
|
useStaleImageReload();
|
||||||
|
|
||||||
const { data, isLoading } = api.annunci.getAnnuncio.useQuery(
|
const { data, isLoading } = api.annunci.getAnnuncio.useQuery(
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue