first commit
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, Navigate, useParams } from 'react-router-dom';
|
||||
import { api, formatCredits } from '../api';
|
||||
import { useAuth } from '../AuthContext';
|
||||
import CaseReel from '../components/CaseReel';
|
||||
import ItemTile from '../components/ItemTile';
|
||||
import { rarityColor } from '../rarities';
|
||||
|
||||
export default function CasePage() {
|
||||
const { id } = useParams();
|
||||
const { user, loading, updateBalance } = useAuth();
|
||||
const [crate, setCrate] = useState(null);
|
||||
const [error, setError] = useState('');
|
||||
const [spinning, setSpinning] = useState(false);
|
||||
const [winnerId, setWinnerId] = useState(null);
|
||||
const [wonItem, setWonItem] = useState(null);
|
||||
const [showResult, setShowResult] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const data = await api.case(id);
|
||||
if (!cancelled) setCrate(data.case);
|
||||
} catch (err) {
|
||||
if (!cancelled) setError(err.message);
|
||||
}
|
||||
})();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
if (loading) return <p className="muted">Loading…</p>;
|
||||
if (!user) return <Navigate to="/login" replace />;
|
||||
if (user.role === 'admin') return <Navigate to="/admin" replace />;
|
||||
|
||||
const canOpen =
|
||||
crate && !spinning && user.balance >= crate.price && crate.items.length > 0;
|
||||
|
||||
const openCase = async () => {
|
||||
if (!canOpen) return;
|
||||
setError('');
|
||||
setWonItem(null);
|
||||
setShowResult(false);
|
||||
setWinnerId(null);
|
||||
setSpinning(false);
|
||||
|
||||
try {
|
||||
const result = await api.openCase(crate.id);
|
||||
updateBalance(result.user.balance);
|
||||
setWonItem(result.item);
|
||||
setWinnerId(result.item.id);
|
||||
// start spin on next frame so reel rebuilds with winner
|
||||
requestAnimationFrame(() => setSpinning(true));
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="case-page">
|
||||
<Link to="/dashboard" className="muted">
|
||||
← Back to dashboard
|
||||
</Link>
|
||||
|
||||
{!crate && !error && <p className="muted">Loading case…</p>}
|
||||
{error && <div className="error">{error}</div>}
|
||||
|
||||
{crate && (
|
||||
<>
|
||||
<div className="case-hero">
|
||||
<h1>{crate.name}</h1>
|
||||
<p className="muted">
|
||||
Cost {formatCredits(crate.price)} cr · Balance {formatCredits(user.balance)} cr
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
className={`case-icon ${canOpen ? '' : 'disabled'}`}
|
||||
onClick={openCase}
|
||||
disabled={!canOpen}
|
||||
title="Open case"
|
||||
>
|
||||
▣
|
||||
</button>
|
||||
<button className="btn" type="button" onClick={openCase} disabled={!canOpen}>
|
||||
{spinning ? 'Opening…' : `Open for ${formatCredits(crate.price)} cr`}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{(spinning || winnerId) && (
|
||||
<CaseReel
|
||||
items={crate.items}
|
||||
winnerId={winnerId}
|
||||
spinning={spinning}
|
||||
onDone={() => {
|
||||
setSpinning(false);
|
||||
setShowResult(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showResult && wonItem && (
|
||||
<div
|
||||
className="won-banner"
|
||||
style={{ '--rarity': rarityColor(wonItem.rarity) }}
|
||||
>
|
||||
<div className="muted">You unboxed</div>
|
||||
<strong>{wonItem.name}</strong>
|
||||
<div style={{ color: rarityColor(wonItem.rarity) }}>{wonItem.rarity}</div>
|
||||
<div className="muted">{formatCredits(wonItem.marketValue)} cr</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section className="section">
|
||||
<div className="section-head">
|
||||
<div>
|
||||
<h2>Possible drops</h2>
|
||||
<p className="muted">Relative drop odds for this case.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid">
|
||||
{crate.items.map((item) => (
|
||||
<div key={item.id}>
|
||||
<ItemTile item={item} />
|
||||
<div className="item-meta" style={{ marginTop: '0.35rem' }}>
|
||||
{item.chance}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user