refactor(panel): extract page sub-components from mega-files
Some checks failed
Deploy to Production / test (push) Failing after 33s
Some checks failed
Deploy to Production / test (push) Failing after 33s
Split ItemStudio (1863->388), Settings (1445->355), and Users (1062->164) into focused sub-components under pages/components/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
152
panel/src/pages/components/ItemPreviewCard.tsx
Normal file
152
panel/src/pages/components/ItemPreviewCard.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
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 border-t border-border 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 border-t border-border 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user