- Fix seed-products.ts TS error (implicit any) and BOM handling - Re-run full seed to restore correct Persian encoding in DB - Fix productService query→search param mismatch - Fix IngredientWiki hardcoded port 4000→use settingsStore - Restore seed-products-data.json from git after accidental corruption
75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
"use client";
|
|
import React from "react";
|
|
import { ImageOff, Sparkles } from "lucide-react";
|
|
import { cn } from "../lib/utils";
|
|
|
|
interface SafeImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
|
|
src?: string;
|
|
alt?: string;
|
|
className?: string;
|
|
imgClassName?: string;
|
|
fallbackText?: string;
|
|
}
|
|
|
|
export default function SafeImage({
|
|
src,
|
|
alt,
|
|
className,
|
|
imgClassName,
|
|
fallbackText = "در حال بروزرسانی تصویر...",
|
|
...props
|
|
}: SafeImageProps) {
|
|
const [error, setError] = React.useState(false);
|
|
const [loading, setLoading] = React.useState(true);
|
|
|
|
if (!src || error) {
|
|
return (
|
|
<div className={cn(
|
|
"flex flex-col items-center justify-center bg-white border border-medical-gray-100 p-4 text-center min-h-[120px] rounded-3xl shadow-inner relative overflow-hidden",
|
|
className
|
|
)}>
|
|
{/* Subtle Brand Pattern Background */}
|
|
<div className="absolute inset-0 opacity-[0.03] pointer-events-none">
|
|
<div className="grid grid-cols-6 h-full w-full">
|
|
{Array.from({length: 24}).map((_, i) => (
|
|
<div key={i} className="border border-canina-blue" />
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="w-16 h-16 bg-canina-blue rounded-2xl flex items-center justify-center text-white font-black text-3xl mb-4 shadow-xl shadow-canina-blue/20 italic transform -rotate-6">C</div>
|
|
<div className="relative">
|
|
<ImageOff className="w-5 h-5 text-medical-gray-200 mb-2" />
|
|
<Sparkles className="w-3 h-3 text-canina-blue absolute -top-1 -right-1 animate-pulse" />
|
|
</div>
|
|
<span className="text-[9px] font-black text-canina-blue/60 uppercase tracking-[0.2em] font-vazir leading-relaxed">
|
|
{fallbackText}
|
|
</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={cn("relative overflow-hidden group flex items-center justify-center", className)}>
|
|
{loading && (
|
|
<div className="absolute inset-0 bg-medical-gray-50 animate-pulse flex items-center justify-center z-10">
|
|
<Sparkles className="w-6 h-6 text-canina-blue/20" />
|
|
</div>
|
|
)}
|
|
<img
|
|
src={src}
|
|
alt={alt}
|
|
className={cn(
|
|
"transition-all duration-700 w-full h-full",
|
|
imgClassName,
|
|
loading ? "opacity-0 scale-110" : "opacity-100 scale-100"
|
|
)}
|
|
onLoad={() => setLoading(false)}
|
|
onError={() => setError(true)}
|
|
referrerPolicy="no-referrer"
|
|
{...props}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|