diff --git a/src/components/CachedImage.tsx b/src/components/CachedImage.tsx index f965ef4d..9f534f55 100644 --- a/src/components/CachedImage.tsx +++ b/src/components/CachedImage.tsx @@ -26,8 +26,9 @@ export function useCachedUrl(fetchUrl: string, cacheKey: string, fallbackToFetch return fallbackToFetch ? (resolved || fetchUrl) : resolved; } -export default function CachedImage({ src, cacheKey, style, onLoad, ...props }: CachedImageProps) { +export default function CachedImage({ src, cacheKey, style, onLoad, onError, ...props }: CachedImageProps) { const [inView, setInView] = useState(false); + const [fallbackSrc, setFallbackSrc] = useState(undefined); const imgRef = useRef(null); useEffect(() => { @@ -49,14 +50,34 @@ export default function CachedImage({ src, cacheKey, style, onLoad, ...props }: // URL upgrades within the same image — avoids the end-of-load flash. useEffect(() => { setLoaded(false); + setFallbackSrc(undefined); }, [cacheKey]); + const handleError = (e: React.SyntheticEvent) => { + if (onError) { + // Caller wants custom error handling (e.g. hide the element) + onError(e); + } else { + // Nullify the DOM-level handler first to prevent any infinite loop + e.currentTarget.onerror = null; + setFallbackSrc('/logo-psysonic.png'); + } + }; + + const isFallback = fallbackSrc !== undefined; + const finalSrc = fallbackSrc ?? (resolvedSrc || undefined); + + const fallbackStyle: React.CSSProperties = isFallback + ? { objectFit: 'contain', background: 'var(--bg-card, var(--ctp-surface0, #313244))', padding: '15%' } + : {}; + return ( { setLoaded(true); onLoad?.(e); }} + onError={handleError} {...props} /> ); diff --git a/src/utils/dynamicColors.ts b/src/utils/dynamicColors.ts index 2a98f890..bb4d3c8b 100644 --- a/src/utils/dynamicColors.ts +++ b/src/utils/dynamicColors.ts @@ -127,6 +127,8 @@ const MIN_CONTRAST = 4.5; */ export function extractCoverColors(imageUrl: string): Promise { if (!imageUrl) return Promise.resolve({ accent: '' }); + // Logo fallback has no meaningful color — skip extraction and use theme accent + if (imageUrl.includes('logo-psysonic')) return Promise.resolve({ accent: '' }); return new Promise(resolve => { const img = new Image();