Files
AIHostingTycoon/apps/web/src/components/dev/ResourcesTab.tsx
T
josh 9c49a10b31
CI / build-and-push (push) Successful in 30s
Add floating dev/debug menu for QA testing (Ctrl+D)
Four-tab panel with resource manipulation, time controls, state inspection,
and event triggers to accelerate testing across all game systems.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-24 23:43:41 -04:00

142 lines
6.3 KiB
TypeScript

import { useState } from 'react';
import { useGameStore } from '@/store';
import { formatMoney } from '@ai-tycoon/shared';
function DevButton({ onClick, children, variant = 'default' }: {
onClick: () => void;
children: React.ReactNode;
variant?: 'default' | 'success';
}) {
const cls = variant === 'success'
? 'bg-emerald-500/20 hover:bg-emerald-500/30 border-emerald-500/50 text-emerald-400'
: 'bg-surface-800 hover:bg-surface-700 border-surface-600 text-surface-300';
return (
<button onClick={onClick} className={`px-2 py-1 rounded text-xs border ${cls}`}>
{children}
</button>
);
}
function DevInput({ value, onChange, placeholder, className = '' }: {
value: string;
onChange: (v: string) => void;
placeholder?: string;
className?: string;
}) {
return (
<input
type="number"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className={`bg-surface-800 border border-surface-600 rounded px-2 py-1 text-xs text-surface-100 w-24 ${className}`}
/>
);
}
export function ResourcesTab() {
const money = useGameStore((s) => s.economy.money);
const reputation = useGameStore((s) => s.reputation);
const researchPoints = useGameStore((s) => s.research.researchPoints);
const totalTrainingTokens = useGameStore((s) => s.data.totalTrainingTokens);
const [customMoney, setCustomMoney] = useState('');
const [customTokens, setCustomTokens] = useState('');
const [customRP, setCustomRP] = useState('');
const addMoney = (amount: number) => {
useGameStore.setState((s) => ({ economy: { ...s.economy, money: s.economy.money + amount } }));
};
const setMoney = (amount: number) => {
useGameStore.setState((s) => ({ economy: { ...s.economy, money: amount } }));
};
const setRepField = (field: 'safetyRecord' | 'publicPerception' | 'employeeSatisfaction' | 'regulatoryStanding', value: number) => {
const clamped = Math.max(0, Math.min(100, value));
useGameStore.setState((s) => ({ reputation: { ...s.reputation, [field]: clamped } }));
};
const addResearchPoints = (amount: number) => {
useGameStore.setState((s) => ({ research: { ...s.research, researchPoints: s.research.researchPoints + amount } }));
};
const addTokens = (amount: number) => {
useGameStore.setState((s) => ({ data: { ...s.data, totalTrainingTokens: s.data.totalTrainingTokens + amount } }));
};
return (
<div className="space-y-4">
<div>
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">
Money <span className="text-surface-400 normal-case">({formatMoney(money)})</span>
</div>
<div className="flex flex-wrap gap-1.5">
<DevButton onClick={() => addMoney(100_000)} variant="success">+100K</DevButton>
<DevButton onClick={() => addMoney(1_000_000)} variant="success">+1M</DevButton>
<DevButton onClick={() => addMoney(10_000_000)} variant="success">+10M</DevButton>
<DevButton onClick={() => addMoney(1_000_000_000)} variant="success">+1B</DevButton>
</div>
<div className="flex gap-1.5 mt-1.5">
<DevInput value={customMoney} onChange={setCustomMoney} placeholder="Amount" />
<DevButton onClick={() => { if (customMoney) setMoney(Number(customMoney)); }}>Set</DevButton>
<DevButton onClick={() => { if (customMoney) addMoney(Number(customMoney)); }} variant="success">Add</DevButton>
</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">
Reputation <span className="text-surface-400 normal-case">(score: {reputation.score})</span>
</div>
<div className="space-y-1">
{([
['safetyRecord', 'Safety Record', reputation.safetyRecord],
['publicPerception', 'Public Perception', reputation.publicPerception],
['employeeSatisfaction', 'Employee Satisfaction', reputation.employeeSatisfaction],
['regulatoryStanding', 'Regulatory Standing', reputation.regulatoryStanding],
] as const).map(([field, label, val]) => (
<div key={field} className="flex items-center gap-2">
<span className="text-xs text-surface-400 w-32">{label}</span>
<input
type="range"
min={0}
max={100}
value={Math.round(val)}
onChange={(e) => setRepField(field, Number(e.target.value))}
className="flex-1 h-1 accent-accent"
/>
<span className="text-xs font-mono text-surface-100 w-8 text-right">{Math.round(val)}</span>
</div>
))}
</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">
Research Points <span className="text-surface-400 normal-case">({researchPoints.toFixed(1)})</span>
</div>
<div className="flex gap-1.5">
<DevButton onClick={() => addResearchPoints(5)} variant="success">+5</DevButton>
<DevButton onClick={() => addResearchPoints(25)} variant="success">+25</DevButton>
<DevButton onClick={() => addResearchPoints(100)} variant="success">+100</DevButton>
<DevInput value={customRP} onChange={setCustomRP} placeholder="Amount" />
<DevButton onClick={() => { if (customRP) addResearchPoints(Number(customRP)); }} variant="success">Add</DevButton>
</div>
</div>
<div>
<div className="text-[10px] uppercase tracking-wider text-surface-500 mb-1.5 font-semibold">
Training Tokens <span className="text-surface-400 normal-case">({(totalTrainingTokens / 1e9).toFixed(1)}B)</span>
</div>
<div className="flex gap-1.5">
<DevButton onClick={() => addTokens(1_000_000_000)} variant="success">+1B</DevButton>
<DevButton onClick={() => addTokens(10_000_000_000)} variant="success">+10B</DevButton>
<DevButton onClick={() => addTokens(100_000_000_000)} variant="success">+100B</DevButton>
<DevInput value={customTokens} onChange={setCustomTokens} placeholder="Amount" />
<DevButton onClick={() => { if (customTokens) addTokens(Number(customTokens)); }} variant="success">Add</DevButton>
</div>
</div>
</div>
);
}