- Migrate from tailwindcss v3.3 to v4.1.18 - Replace @tailwind directives with @import 'tailwindcss' - Move custom colors to @theme block in globals.css - Convert custom utilities to @utility syntax - Update PostCSS config to use @tailwindcss/postcss - Remove autoprefixer (now built-in)
95 lines
3.7 KiB
TypeScript
95 lines
3.7 KiB
TypeScript
'use client';
|
|
|
|
import React from 'react';
|
|
import { motion } from 'framer-motion';
|
|
import { Sparkles, AlertCircle, Loader2 } from 'lucide-react';
|
|
|
|
interface BottleSkeletonCardProps {
|
|
name?: string;
|
|
imageUrl?: string;
|
|
processingStatus: 'pending' | 'analyzing' | 'error';
|
|
onClick?: () => void;
|
|
}
|
|
|
|
export default function BottleSkeletonCard({
|
|
name,
|
|
imageUrl,
|
|
processingStatus,
|
|
onClick
|
|
}: BottleSkeletonCardProps) {
|
|
const isError = processingStatus === 'error';
|
|
const isPending = processingStatus === 'pending';
|
|
const isAnalyzing = processingStatus === 'analyzing';
|
|
|
|
return (
|
|
<motion.div
|
|
initial={{ opacity: 0, scale: 0.95 }}
|
|
animate={{ opacity: 1, scale: 1 }}
|
|
onClick={onClick}
|
|
className={`relative bg-zinc-900 rounded-2xl border overflow-hidden cursor-pointer transition-all ${isError
|
|
? 'border-red-500/50 hover:border-red-500'
|
|
: 'border-zinc-800 hover:border-orange-600/50'
|
|
}`}
|
|
>
|
|
{/* Image */}
|
|
<div className="aspect-3/4 bg-zinc-950 relative overflow-hidden">
|
|
{imageUrl ? (
|
|
<img
|
|
src={imageUrl}
|
|
alt="Bottle preview"
|
|
className={`w-full h-full object-cover ${isError ? 'opacity-50 grayscale' : 'opacity-60'}`}
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex items-center justify-center">
|
|
<div className="w-16 h-24 bg-zinc-800 rounded-lg animate-pulse" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Processing Overlay */}
|
|
<div className={`absolute inset-0 flex flex-col items-center justify-center ${isError ? 'bg-red-950/50' : 'bg-black/40'
|
|
}`}>
|
|
{isError ? (
|
|
<>
|
|
<AlertCircle size={32} className="text-red-500 mb-2" />
|
|
<span className="text-xs font-bold text-red-400">Fehler</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
{isPending ? (
|
|
<Loader2 size={28} className="text-orange-500 animate-spin mb-2" />
|
|
) : (
|
|
<Sparkles size={28} className="text-orange-500 animate-pulse mb-2" />
|
|
)}
|
|
<span className="text-xs font-bold text-zinc-300">
|
|
{isPending ? 'Warten...' : 'KI analysiert...'}
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* Status Badge */}
|
|
<div className={`absolute top-2 right-2 px-2 py-1 rounded-lg text-[9px] font-black uppercase tracking-widest ${isError
|
|
? 'bg-red-500/20 text-red-400'
|
|
: isAnalyzing
|
|
? 'bg-orange-500/20 text-orange-400'
|
|
: 'bg-zinc-800/80 text-zinc-500'
|
|
}`}>
|
|
{isError ? 'Fehler' : isAnalyzing ? '✨ Analyse' : 'Warte'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div className="p-3">
|
|
{/* Skeleton Name */}
|
|
<div className="h-4 bg-zinc-800 rounded-sm animate-pulse mb-2 w-3/4" />
|
|
|
|
{/* Skeleton Details */}
|
|
<div className="flex gap-2">
|
|
<div className="h-3 bg-zinc-800/50 rounded-sm animate-pulse w-12" />
|
|
<div className="h-3 bg-zinc-800/50 rounded-sm animate-pulse w-8" />
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
);
|
|
}
|