All checks were successful
Build and Deploy Next.js Blog to Production / 🔍 Code Quality Checks (push) Successful in 17s
Build and Deploy Next.js Blog to Production / 🏗️ Build and Push Docker Image (push) Successful in 30s
Build and Deploy Next.js Blog to Production / 🚀 Deploy to Production (push) Successful in 48s
71 lines
2.1 KiB
TypeScript
71 lines
2.1 KiB
TypeScript
'use client'
|
|
|
|
import Image from 'next/image'
|
|
import { useState } from 'react'
|
|
|
|
interface OptimizedImageProps {
|
|
src: string
|
|
alt: string
|
|
caption?: string
|
|
width?: number
|
|
height?: number
|
|
priority?: boolean
|
|
className?: string
|
|
}
|
|
|
|
export function OptimizedImage({
|
|
src,
|
|
alt,
|
|
caption,
|
|
width = 800,
|
|
height = 600,
|
|
priority = false,
|
|
className = '',
|
|
}: OptimizedImageProps) {
|
|
const [isLoading, setIsLoading] = useState(true)
|
|
const [hasError, setHasError] = useState(false)
|
|
|
|
if (hasError) {
|
|
return (
|
|
<span className="block my-8 rounded-lg border border-zinc-800 bg-zinc-900/50 p-8 text-center">
|
|
<span className="block text-zinc-400">Failed to load image</span>
|
|
{caption && <span className="block mt-2 text-sm text-zinc-500">{caption}</span>}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
const imageElement = (
|
|
<span className="block relative overflow-hidden rounded-lg border border-zinc-800 bg-zinc-900/50">
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
width={width}
|
|
height={height}
|
|
priority={priority}
|
|
style={{ maxWidth: '100%', height: 'auto' }}
|
|
className={`transition-opacity duration-300 ${isLoading ? 'opacity-0' : 'opacity-100'}`}
|
|
onLoad={() => setIsLoading(false)}
|
|
onError={() => setHasError(true)}
|
|
placeholder="blur"
|
|
blurDataURL="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='300'%3E%3Crect width='400' height='300' fill='%2318181b'/%3E%3C/svg%3E"
|
|
/>
|
|
{isLoading && (
|
|
<span className="absolute inset-0 flex items-center justify-center">
|
|
<span className="block h-8 w-8 animate-spin rounded-full border-2 border-emerald-500 border-t-transparent" />
|
|
</span>
|
|
)}
|
|
</span>
|
|
)
|
|
|
|
// Always use <span> to avoid invalid HTML nesting in <p> tags
|
|
// This prevents hydration mismatches between server and client
|
|
return (
|
|
<span className={`block my-8 ${className}`}>
|
|
{imageElement}
|
|
{caption && (
|
|
<span className="block mt-3 text-center text-sm text-zinc-400">{caption}</span>
|
|
)}
|
|
</span>
|
|
)
|
|
}
|