cool prestige update

This commit is contained in:
gpatruno
2026-07-19 00:29:46 +02:00
parent d0f868ffa5
commit 3a8871cbae
27 changed files with 1165 additions and 353 deletions
+157 -3
View File
@@ -11,6 +11,8 @@ const PAGE_SIZE = 50;
export default function Dashboard() {
const { user, loading, patchUser } = useAuth();
const [inventory, setInventory] = useState([]);
const [vault, setVault] = useState(null);
const [vaultMessage, setVaultMessage] = useState('');
const [error, setError] = useState('');
const [sort, setSort] = useState('value');
const [page, setPage] = useState(1);
@@ -37,6 +39,7 @@ export default function Dashboard() {
const load = async () => {
const inv = await api.inventory();
setInventory(inv.inventory);
setVault(inv.vault || null);
};
useEffect(() => {
@@ -109,6 +112,7 @@ export default function Dashboard() {
return inventory.filter(
(inv) =>
!inv.favorite &&
!inv.staked &&
(inv.valueCents ?? inv.item?.valueCents ?? 0) < thresholdCents
);
}, [inventory, thresholdCents]);
@@ -118,10 +122,17 @@ export default function Dashboard() {
[inventory, selected]
);
const stakedItems = useMemo(() => inventory.filter((inv) => inv.staked), [inventory]);
const selectedHasFavorite = selectedItems.some((inv) => inv.favorite);
const selectedAllFavorite =
selectedItems.length > 0 && selectedItems.every((inv) => inv.favorite);
const selectedCanSell = selectedItems.some((inv) => !inv.favorite);
const selectedCanSell = selectedItems.some((inv) => !inv.favorite && !inv.staked);
const selectedStaked = selectedItems.filter((inv) => inv.staked);
const selectedUnstaked = selectedItems.filter((inv) => !inv.staked);
const vaultFree = Math.max(0, (vault?.slots ?? 3) - (vault?.used ?? 0));
const canStake = selectedUnstaked.length > 0 && selectedUnstaked.length <= vaultFree;
const canUnstake = selectedStaked.length > 0;
const belowValue = useMemo(
() =>
@@ -155,11 +166,78 @@ export default function Dashboard() {
};
const sell = async () => {
const ids = selectedItems.filter((inv) => !inv.favorite).map((inv) => inv.id);
const ids = selectedItems
.filter((inv) => !inv.favorite && !inv.staked)
.map((inv) => inv.id);
if (!ids.length) return;
await sellIds(ids);
};
const stakeSelected = async () => {
const ids = selectedUnstaked.map((inv) => inv.id);
if (!ids.length) return;
setBusy(true);
setError('');
setVaultMessage('');
try {
const data = await api.vaultStake(ids);
if (data.user) patchUser({ balance: data.user.balance });
setSelected([]);
setVaultMessage(`Staked ${ids.length} item${ids.length === 1 ? '' : 's'} in vault`);
await load();
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
};
const unstakeSelected = async () => {
const ids = selectedStaked.map((inv) => inv.id);
if (!ids.length) return;
setBusy(true);
setError('');
setVaultMessage('');
try {
const data = await api.vaultUnstake(ids);
if (data.user) patchUser({ balance: data.user.balance });
setSelected([]);
const yieldPart =
data.yieldCredited > 0
? ` · +${formatCredits(data.yieldCredited)} cr yield`
: '';
setVaultMessage(
`Unstaked ${ids.length} item${ids.length === 1 ? '' : 's'}${yieldPart}`
);
await load();
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
};
const claimVault = async () => {
setBusy(true);
setError('');
setVaultMessage('');
try {
const data = await api.vaultClaim();
if (data.user) patchUser({ balance: data.user.balance });
if (data.vault) setVault(data.vault);
setVaultMessage(
data.credited > 0
? `Vault yield +${formatCredits(data.credited)} cr`
: 'No vault yield ready yet'
);
await load();
} catch (err) {
setError(err.message);
} finally {
setBusy(false);
}
};
const setFavorite = async (favorite) => {
if (!selected.length) return;
setBusy(true);
@@ -303,10 +381,34 @@ export default function Dashboard() {
className="btn"
onClick={sell}
disabled={busy || !selectedCanSell}
title={selectedHasFavorite ? 'Favorites cannot be sold' : undefined}
title={
selectedHasFavorite || selectedStaked.length
? 'Favorites and vault items cannot be sold'
: undefined
}
>
Sell
</button>
{canStake && (
<button
type="button"
className="btn secondary"
onClick={stakeSelected}
disabled={busy}
>
Stake ({selectedUnstaked.length})
</button>
)}
{canUnstake && (
<button
type="button"
className="btn secondary"
onClick={unstakeSelected}
disabled={busy}
>
Unstake ({selectedStaked.length})
</button>
)}
</>
)}
</div>
@@ -381,6 +483,57 @@ export default function Dashboard() {
</div>
{error && <div className="error">{error}</div>}
{vaultMessage && <div className="ok-msg">{vaultMessage}</div>}
<div className="vault-panel admin-block">
<div className="section-head">
<div>
<h2>Vault</h2>
<p className="muted">
Stake up to {vault?.slots ?? 3} items. Online: 10% / 10 min · Offline: 1% / 10 min
(scales with wear &amp; rarity).
</p>
</div>
<button
type="button"
className="btn secondary"
onClick={claimVault}
disabled={busy || !(vault?.used > 0)}
>
Claim yield
</button>
</div>
<div className="vault-stats">
<span>
Slots <strong>{vault?.used ?? 0}/{vault?.slots ?? 3}</strong>
</span>
<span>
Online / tick{' '}
<strong className="gold">{formatCredits(vault?.onlinePerTick ?? 0)} cr</strong>
</span>
<span>
Offline / tick{' '}
<strong>{formatCredits(vault?.offlinePerTick ?? 0)} cr</strong>
</span>
</div>
{stakedItems.length > 0 ? (
<div className="grid vault-grid">
{stakedItems.map((inv) => (
<ItemTile
key={`vault-${inv.id}`}
item={inv.item}
favorite={inv.favorite}
staked
selected={selected.includes(inv.id)}
onClick={() => toggle(inv.id)}
/>
))}
</div>
) : (
<p className="muted">No items staked select items below and Stake.</p>
)}
</div>
{inventory.length === 0 ? (
<div className="empty">
Empty for now {' '}
@@ -397,6 +550,7 @@ export default function Dashboard() {
key={inv.id}
item={inv.item}
favorite={inv.favorite}
staked={inv.staked}
selected={selected.includes(inv.id)}
onClick={() => toggle(inv.id)}
/>