Files
aurorabot/panel/src/pages/components/ItemPreviewCard.tsx
syntaxbullet abca1922f2
Some checks failed
Deploy to Production / test (push) Failing after 34s
chore: change styling
2026-04-02 19:05:36 +02:00

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>
);
}