Comprehensive UX polish: fix 19 friction points across all pages
CI / build-and-push (push) Successful in 33s
CI / build-and-push (push) Successful in 33s
Addresses broken interactions (notification bell, browser dialogs), missing feedback states (disabled buttons, pricing changes, paused indicator), unclear affordances (research queue, model tuning, funding requirements), and navigation gaps (hash routing, keyboard shortcuts, clickable dashboard cards, sidebar grouping, tutorial hints). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { useGameStore } from '@/store';
|
||||
import { formatMoney, formatPercent, FUNDING_ROUNDS } from '@ai-tycoon/shared';
|
||||
import { formatMoney, formatPercent, formatNumber, FUNDING_ROUNDS } from '@ai-tycoon/shared';
|
||||
import type { FundingRoundType } from '@ai-tycoon/shared';
|
||||
import { TrendingUp, DollarSign, PiggyBank, BarChart3, Rocket } from 'lucide-react';
|
||||
import { AreaChart, Area, XAxis, YAxis, ResponsiveContainer, LineChart, Line } from 'recharts';
|
||||
import { TrendingUp, DollarSign, PiggyBank, BarChart3, Rocket, Check, X as XIcon } from 'lucide-react';
|
||||
import { AreaChart, Area, XAxis, YAxis, ResponsiveContainer, LineChart, Line, Tooltip } from 'recharts';
|
||||
import { canRaiseFunding } from '@ai-tycoon/game-engine';
|
||||
import type { GameState } from '@ai-tycoon/shared';
|
||||
|
||||
@@ -15,6 +15,9 @@ export function FinancePage() {
|
||||
const infrastructure = useGameStore((s) => s.infrastructure);
|
||||
const talent = useGameStore((s) => s.talent);
|
||||
const raiseFunding = useGameStore((s) => s.raiseFunding);
|
||||
const totalRevenue = useGameStore((s) => s.economy.totalRevenue);
|
||||
const subscribers = useGameStore((s) => s.market.consumers.totalSubscribers);
|
||||
const reputationScore = useGameStore((s) => s.reputation.score);
|
||||
|
||||
const state = useGameStore.getState();
|
||||
const gameStateForFunding: GameState = {
|
||||
@@ -81,12 +84,22 @@ export function FinancePage() {
|
||||
</div>
|
||||
|
||||
<div className="bg-surface-900 border border-surface-700 rounded-xl p-4">
|
||||
<h3 className="text-sm font-medium text-surface-400 mb-4">Revenue vs Expenses</h3>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-sm font-medium text-surface-400">Revenue vs Expenses</h3>
|
||||
<div className="flex items-center gap-4 text-xs">
|
||||
<span className="flex items-center gap-1"><span className="w-2.5 h-2.5 rounded-full bg-success inline-block" />Revenue</span>
|
||||
<span className="flex items-center gap-1"><span className="w-2.5 h-2.5 rounded-full bg-danger inline-block" />Expenses</span>
|
||||
</div>
|
||||
</div>
|
||||
{history.length > 1 ? (
|
||||
<ResponsiveContainer width="100%" height={200}>
|
||||
<LineChart data={history}>
|
||||
<XAxis dataKey="tick" hide />
|
||||
<YAxis hide />
|
||||
<Tooltip
|
||||
contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }}
|
||||
formatter={(value: number, name: string) => [formatMoney(value), name === 'revenue' ? 'Revenue' : 'Expenses']}
|
||||
/>
|
||||
<Line type="monotone" dataKey="revenue" stroke="#22c55e" dot={false} strokeWidth={2} />
|
||||
<Line type="monotone" dataKey="expenses" stroke="#ef4444" dot={false} strokeWidth={2} />
|
||||
</LineChart>
|
||||
@@ -143,26 +156,43 @@ export function FinancePage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{fundingStatus.nextRound && (
|
||||
<div className="bg-surface-800 rounded-lg p-4 mb-4 border border-surface-600">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-semibold text-sm capitalize">
|
||||
{fundingStatus.nextRound === 'ipo' ? 'IPO' : fundingStatus.nextRound.replace('series', 'Series ')}
|
||||
</h4>
|
||||
{fundingStatus.canRaise ? (
|
||||
<button
|
||||
onClick={() => raiseFunding(fundingStatus.nextRound!)}
|
||||
className="flex items-center gap-1.5 bg-accent hover:bg-accent-dark text-white rounded-lg px-4 py-2 text-sm font-medium"
|
||||
>
|
||||
<Rocket size={14} />
|
||||
Raise {formatMoney(FUNDING_ROUNDS[fundingStatus.nextRound! as FundingRoundType].amount)}
|
||||
</button>
|
||||
) : (
|
||||
<span className="text-xs text-warning">{String(fundingStatus.reason ?? '')}</span>
|
||||
{fundingStatus.nextRound && (() => {
|
||||
const roundConfig = FUNDING_ROUNDS[fundingStatus.nextRound as FundingRoundType];
|
||||
const reqs = roundConfig.requirements;
|
||||
const checks = [
|
||||
...(reqs.minRevenue ? [{ label: `Total Revenue: ${formatMoney(totalRevenue)} / ${formatMoney(reqs.minRevenue)}`, met: totalRevenue >= reqs.minRevenue }] : []),
|
||||
...(reqs.minUsers ? [{ label: `Subscribers: ${formatNumber(subscribers)} / ${formatNumber(reqs.minUsers)}`, met: subscribers >= reqs.minUsers }] : []),
|
||||
...(reqs.minReputation ? [{ label: `Reputation: ${reputationScore} / ${reqs.minReputation}`, met: reputationScore >= reqs.minReputation }] : []),
|
||||
];
|
||||
return (
|
||||
<div className="bg-surface-800 rounded-lg p-4 mb-4 border border-surface-600">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h4 className="font-semibold text-sm capitalize">
|
||||
{fundingStatus.nextRound === 'ipo' ? 'IPO' : fundingStatus.nextRound.replace('series', 'Series ')}
|
||||
</h4>
|
||||
{fundingStatus.canRaise && (
|
||||
<button
|
||||
onClick={() => raiseFunding(fundingStatus.nextRound!)}
|
||||
className="flex items-center gap-1.5 bg-accent hover:bg-accent-dark text-white rounded-lg px-4 py-2 text-sm font-medium"
|
||||
>
|
||||
<Rocket size={14} />
|
||||
Raise {formatMoney(roundConfig.amount)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{checks.length > 0 && (
|
||||
<div className="space-y-1.5 mt-2">
|
||||
{checks.map((c, i) => (
|
||||
<div key={i} className="flex items-center gap-2 text-xs">
|
||||
{c.met ? <Check size={12} className="text-success shrink-0" /> : <XIcon size={12} className="text-danger shrink-0" />}
|
||||
<span className={c.met ? 'text-surface-400' : 'text-surface-200'}>{c.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
})()}
|
||||
|
||||
{funding.completedRounds.length === 0 ? (
|
||||
<p className="text-sm text-surface-500">No funding rounds completed yet.</p>
|
||||
|
||||
Reference in New Issue
Block a user