import { useEffect, useState } from 'react'; import { Navigate } from 'react-router-dom'; import { api, formatCredits } from '../api'; import { useAuth } from '../AuthContext'; function formatSkillValue(skill, value) { const prefix = skill.prefix || ''; if (skill.unit === 'cr-cents') { return `${prefix}${formatCredits(value)} cr`.replace(/^\+\+/, '+'); } if (skill.unit === '%') return `${prefix || '+'}${value}%`; if (skill.unit === 's') return `${prefix || '−'}${value}s`; if (skill.unit === ' osu') return `${prefix || '+'}${value} osu`; if (skill.prefix === '−') return `−${value}`; if (value === 0 && !prefix) return `${value}`; return `${prefix || '+'}${value}`; } export default function PrestigePage() { const { user, loading, patchUser, refresh } = useAuth(); const [data, setData] = useState(null); const [error, setError] = useState(''); const [message, setMessage] = useState(''); const [busy, setBusy] = useState(false); const load = async () => { const result = await api.prestige(); setData(result); }; useEffect(() => { if (!user || user.role === 'admin') return undefined; let cancelled = false; (async () => { try { const result = await api.prestige(); if (!cancelled) setData(result); } catch (err) { if (!cancelled) setError(err.message); } })(); return () => { cancelled = true; }; }, [user?.id, user?.role]); if (loading) return

Loading…

; if (!user) return ; if (user.role === 'admin') return ; if (!data && !error) return

Loading…

; if (data && data.pageVisible === false) { return ; } const cost = data?.prestige?.costCents ?? 0; const p = data?.prestige; const prBalance = p?.prBalance ?? user.prBalance ?? 0; const canClick = Boolean(data?.canPrestige) && !busy; const onPrestige = async () => { if (!canClick) return; setBusy(true); setError(''); setMessage(''); try { const result = await api.doPrestige(); patchUser(result.user); await refresh(); setMessage( `Prestige #${result.prestige.count} · +${result.prGranted} Pr. Balance, inventory, keys & catalog reset.` ); await load(); } catch (err) { setError(err.message); } finally { setBusy(false); } }; const onUpgrade = async (skillId) => { setBusy(true); setError(''); setMessage(''); try { const result = await api.prestigeUpgrade(skillId); patchUser(result.user); setMessage(`Upgraded ${skillId}.`); setData((prev) => prev ? { ...prev, prestige: result.prestige, skills: result.skills, } : prev ); } catch (err) { setError(err.message); } finally { setBusy(false); } }; const shortfall = Math.max(0, cost - (Number(user.balance) || 0)); const statusLabel = !data?.canAfford ? `Need ${formatCredits(shortfall)} cr more` : data?.lockedItems > 0 ? `Withdraw ${data.lockedItems} locked item${data.lockedItems === 1 ? '' : 's'} first` : `Ready · ~${data.previewPr} Pr`; return (

Prestige

Reset your progress for Pr, then spend Pr on permanent skills.

{p && (
Prestige {p.count}
)}
{prBalance} Pr
  1. 1
    Pay the cost

    {formatCredits(cost)} cr from your balance

  2. 2
    Earn Pr

    +{p?.prBaseReward ?? 1000} base, more if you are over the cost

  3. 3
    Upgrade skills

    Permanent bonuses — they survive every reset

{error &&
{error}
} {message &&
{message}
} {p && (
Fortune +{p.chanceBonus}%
Yield +{p.gainBonus}%
Polish +{p.wearBonus || 0}
Bulk +{p.openBonus || 0}
Discount −{p.caseDiscount || 0}%
Quality +{p.qualityBonus || 0}
noTime −{p.animReduction || 0}%
Refund {p.refundChance || 0}%
Respin {p.respinChance || 0}%
Vault slots +{p.vaultSlots || 0}
Offline Yield +{p.offlineYield || 0}%
Online Yield +{p.onlineYield || 0}%
)} {data?.lockedItems > 0 && (
Withdraw {data.lockedItems} locked item{data.lockedItems === 1 ? '' : 's'} from battles before prestiging.
)}

Reset now

{statusLabel}
Cost {formatCredits(cost)} cr
Your balance {formatCredits(user.balance)} cr
Est. Pr reward {data?.canAfford ? `~${data.previewPr} Pr` : '—'}

You keep

  • All Pr already earned
  • Skill-tree bonuses
  • Account & prestige count

You lose

  • Balance
  • Inventory & vault stakes
  • Case keys & catalog progress

Reward: +{p?.prBaseReward ?? 1000} Pr base. Every{' '} {formatCredits(p?.excessChunkCents ?? 0)} cr above the cost adds ~{p?.prPerExcessChunk ?? 1000}{' '} Pr.

Skill tree

Spend Pr on permanent branches. Costs rise with each purchase.

Core {prBalance} Pr
{(data?.skills || []).map((skill) => (

{skill.title}

{skill.description}

Lv {skill.level} · now{' '} {formatSkillValue(skill, skill.current)} → {formatSkillValue(skill, skill.nextValue)}
))}
{data?.history?.length > 0 && (

History

    {data.history.map((h) => (
  • Prestige reset {h.prGranted > 0 ? ` · +${h.prGranted} Pr` : ''}{' '} · {new Date(h.createdAt).toLocaleString()}
  • ))}
)}
); }