nice update

This commit is contained in:
gpatruno
2026-07-16 20:23:58 +02:00
parent a0d7163464
commit 9106c8b873
47 changed files with 5183 additions and 1732 deletions
+106 -33
View File
@@ -1,25 +1,28 @@
import { useEffect, useState } from 'react';
import { useEffect, useMemo, useState } from 'react';
import { Link, Navigate } from 'react-router-dom';
import { api, formatCredits } from '../api';
import { useAuth } from '../AuthContext';
import ItemTile from '../components/ItemTile';
export default function Dashboard() {
const { user, loading } = useAuth();
const { user, loading, patchUser } = useAuth();
const [inventory, setInventory] = useState([]);
const [cases, setCases] = useState([]);
const [error, setError] = useState('');
const [sort, setSort] = useState('recent');
const [selected, setSelected] = useState([]);
const [busy, setBusy] = useState(false);
const load = async () => {
const inv = await api.inventory();
setInventory(inv.inventory);
};
useEffect(() => {
if (!user || user.role === 'admin') return;
let cancelled = false;
(async () => {
try {
const [inv, cs] = await Promise.all([api.inventory(), api.cases()]);
if (!cancelled) {
setInventory(inv.inventory);
setCases(cs.cases);
}
await load();
} catch (err) {
if (!cancelled) setError(err.message);
}
@@ -29,6 +32,46 @@ export default function Dashboard() {
};
}, [user]);
const sorted = useMemo(() => {
const list = [...inventory];
if (sort === 'value') {
list.sort((a, b) => b.item.marketValue - a.item.marketValue);
} else {
list.sort((a, b) => new Date(b.obtainedAt) - new Date(a.obtainedAt));
}
return list;
}, [inventory, sort]);
const selectedValue = useMemo(
() =>
inventory
.filter((inv) => selected.includes(inv.id))
.reduce((sum, inv) => sum + inv.item.marketValue, 0),
[inventory, selected]
);
const toggle = (id) => {
setSelected((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
);
};
const sell = async () => {
if (!selected.length) return;
setBusy(true);
setError('');
try {
const data = await api.sellInventory(selected);
patchUser({ balance: data.user.balance });
setSelected([]);
await load();
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
};
if (loading) return <p className="muted">Loading</p>;
if (!user) return <Navigate to="/login" replace />;
if (user.role === 'admin') return <Navigate to="/admin" replace />;
@@ -39,42 +82,72 @@ export default function Dashboard() {
<div className="section-head">
<div>
<h1>Inventory</h1>
<p className="muted">Your drops and loot.</p>
<p className="muted">Select items to sell at market value.</p>
</div>
<div className="balance-pill">{formatCredits(user.balance)} cr</div>
</div>
<div className="inv-toolbar">
<div className="sort-toggle" role="group" aria-label="Sort inventory">
<button
type="button"
className={sort === 'recent' ? 'active' : ''}
onClick={() => setSort('recent')}
>
Most recent
</button>
<button
type="button"
className={sort === 'value' ? 'active' : ''}
onClick={() => setSort('value')}
>
Highest value
</button>
</div>
<div className="row-actions">
{selected.length > 0 && (
<>
<span className="muted">
{selected.length} selected · {formatCredits(selectedValue)} cr
</span>
<button
type="button"
className="btn ghost"
onClick={() => setSelected([])}
disabled={busy}
>
Clear
</button>
<button type="button" className="btn" onClick={sell} disabled={busy}>
Sell
</button>
</>
)}
</div>
</div>
{error && <div className="error">{error}</div>}
{inventory.length === 0 ? (
<div className="empty">Empty for now open a case below.</div>
<div className="empty">
Empty for now {' '}
<Link to="/cases" style={{ color: 'var(--accent)' }}>
open a case
</Link>
.
</div>
) : (
<div className="grid">
{inventory.map((inv) => (
<ItemTile key={inv.id} item={inv.item} />
{sorted.map((inv) => (
<ItemTile
key={inv.id}
item={inv.item}
selected={selected.includes(inv.id)}
onClick={() => toggle(inv.id)}
/>
))}
</div>
)}
</section>
<section className="section">
<div className="section-head">
<div>
<h2>Cases</h2>
<p className="muted">Pick a crate to open.</p>
</div>
</div>
<div className="grid case-grid">
{cases.map((c) => (
<Link key={c.id} to={`/cases/${c.id}`} className="case-tile">
<div className="case-visual" style={{ color: 'var(--accent)' }}>
</div>
<div className="case-name">{c.name}</div>
<div className="case-meta">{formatCredits(c.price)} cr</div>
<div className="case-meta">{c.items.length} possible drops</div>
</Link>
))}
</div>
</section>
</div>
);
}