153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
import { ImageIcon, CircleDollarSign, Gift, Zap } from "lucide-react";
|
|
import { cn } from "../../lib/utils";
|
|
import {
|
|
type Draft,
|
|
TYPE_META,
|
|
RARITY_META,
|
|
LOOT_TYPE_META,
|
|
} from "./ItemStudioTypes";
|
|
|
|
// ===== Item Preview Card =====
|
|
|
|
export function ItemPreviewCard({
|
|
draft,
|
|
previewImageSrc,
|
|
}: {
|
|
draft: Draft;
|
|
previewImageSrc: string | null;
|
|
}) {
|
|
const rarity = RARITY_META[draft.rarity];
|
|
const type = TYPE_META[draft.type];
|
|
const TypeIcon = type.icon;
|
|
|
|
const lootboxEffect = draft.effects.find((e) => e.kind === "LOOTBOX");
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
"bg-card border-2 rounded-xl overflow-hidden transition-all duration-300",
|
|
rarity.activeBorder
|
|
)}
|
|
>
|
|
{/* Image area */}
|
|
<div className="relative aspect-square bg-raised">
|
|
{previewImageSrc ? (
|
|
<img
|
|
src={previewImageSrc}
|
|
alt="Preview"
|
|
className="w-full h-full object-cover"
|
|
onError={(e) => {
|
|
e.currentTarget.style.opacity = "0.2";
|
|
}}
|
|
/>
|
|
) : (
|
|
<div className="w-full h-full flex flex-col items-center justify-center text-text-tertiary gap-2">
|
|
<ImageIcon className="w-10 h-10 opacity-25" />
|
|
<span className="text-xs opacity-40">No image</span>
|
|
</div>
|
|
)}
|
|
<span
|
|
className={cn(
|
|
"absolute top-3 right-3 px-2.5 py-1 rounded-full text-xs font-bold backdrop-blur-sm",
|
|
rarity.badgeBg,
|
|
rarity.text
|
|
)}
|
|
>
|
|
{draft.rarity}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Info */}
|
|
<div className="p-4 space-y-2">
|
|
<h4 className="text-base font-bold text-foreground leading-tight">
|
|
{draft.name.trim() ? (
|
|
draft.name
|
|
) : (
|
|
<span className="text-text-tertiary italic font-normal text-sm">
|
|
Item name...
|
|
</span>
|
|
)}
|
|
</h4>
|
|
|
|
<div className="flex items-center gap-1.5">
|
|
<TypeIcon className="w-3.5 h-3.5 text-text-tertiary" />
|
|
<span className="text-xs text-text-tertiary">{type.label}</span>
|
|
</div>
|
|
|
|
{draft.description.trim() && (
|
|
<p className="text-xs text-text-secondary line-clamp-3 leading-relaxed">
|
|
{draft.description}
|
|
</p>
|
|
)}
|
|
|
|
{draft.price && Number(draft.price) > 0 && (
|
|
<div className="flex items-center gap-1.5">
|
|
<CircleDollarSign className="w-3.5 h-3.5 text-gold" />
|
|
<span className="text-xs font-mono text-gold font-medium">
|
|
{parseInt(draft.price).toLocaleString()} coins
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Lootbox pool mini-preview */}
|
|
{lootboxEffect && lootboxEffect.pool.length > 0 && (
|
|
<div className="pt-2 mt-2 space-y-1.5">
|
|
<div className="flex items-center gap-1.5">
|
|
<Gift className="w-3.5 h-3.5 text-amber-400" />
|
|
<span className="text-xs text-amber-400 font-medium">
|
|
Lootbox · {lootboxEffect.pool.length} outcome
|
|
{lootboxEffect.pool.length !== 1 ? "s" : ""}
|
|
</span>
|
|
</div>
|
|
{/* Stacked bar */}
|
|
{(() => {
|
|
const total = lootboxEffect.pool.reduce(
|
|
(s, e) => s + Number(e.weight || 0),
|
|
0
|
|
);
|
|
return (
|
|
<div className="flex h-1.5 w-full rounded-full overflow-hidden gap-px bg-raised">
|
|
{lootboxEffect.pool.map((e) => {
|
|
const pct =
|
|
total > 0
|
|
? (Number(e.weight || 0) / total) * 100
|
|
: 0;
|
|
return (
|
|
<div
|
|
key={e._id}
|
|
style={{ width: `${pct}%` }}
|
|
className={cn(
|
|
"transition-all",
|
|
LOOT_TYPE_META[e.type].barColor
|
|
)}
|
|
title={`${LOOT_TYPE_META[e.type].label} ${pct.toFixed(1)}%`}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
)}
|
|
|
|
{(draft.effects.length > 0 || draft.consume) && !lootboxEffect && (
|
|
<div className="pt-2 mt-2 flex flex-wrap gap-1.5">
|
|
{draft.consume && (
|
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-red-500/10 text-red-400 text-xs">
|
|
Consumed on use
|
|
</span>
|
|
)}
|
|
{draft.effects.length > 0 && (
|
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-primary/10 text-primary text-xs">
|
|
<Zap className="w-3 h-3" />
|
|
{draft.effects.length} effect
|
|
{draft.effects.length !== 1 ? "s" : ""}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|