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,11 +1,15 @@
|
||||
import { useRef } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useGameStore } from '@/store';
|
||||
import { ConfirmModal } from '@/components/common/ConfirmModal';
|
||||
|
||||
export function SettingsPage() {
|
||||
const settings = useGameStore((s) => s.meta.settings);
|
||||
const companyName = useGameStore((s) => s.meta.companyName);
|
||||
const updateState = useGameStore((s) => s.updateState);
|
||||
const addNotification = useGameStore((s) => s.addNotification);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [showResetConfirm, setShowResetConfirm] = useState(false);
|
||||
const [importData, setImportData] = useState<{ data: unknown; name: string } | null>(null);
|
||||
|
||||
const toggleSound = () => {
|
||||
updateState({ meta: { ...useGameStore.getState().meta, settings: { ...settings, soundEnabled: !settings.soundEnabled } } });
|
||||
@@ -16,10 +20,8 @@ export function SettingsPage() {
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
if (confirm('Are you sure you want to reset all progress? This cannot be undone.')) {
|
||||
localStorage.removeItem('ai-tycoon-save');
|
||||
window.location.reload();
|
||||
}
|
||||
localStorage.removeItem('ai-tycoon-save');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleExport = () => {
|
||||
@@ -43,22 +45,24 @@ export function SettingsPage() {
|
||||
try {
|
||||
const data = JSON.parse(event.target?.result as string);
|
||||
if (!data.meta?.companyName) {
|
||||
alert('Invalid save file: missing company data.');
|
||||
addNotification({ title: 'Import Failed', message: 'Invalid save file: missing company data.', type: 'danger', tick: useGameStore.getState().meta.tickCount });
|
||||
return;
|
||||
}
|
||||
if (!confirm(`Import save for "${data.meta.companyName}"? This will replace your current game.`)) {
|
||||
return;
|
||||
}
|
||||
localStorage.setItem('ai-tycoon-save', JSON.stringify({ state: data }));
|
||||
window.location.reload();
|
||||
setImportData({ data, name: data.meta.companyName });
|
||||
} catch {
|
||||
alert('Failed to read save file. Make sure it is a valid AI Tycoon export.');
|
||||
addNotification({ title: 'Import Failed', message: 'Could not read save file. Make sure it is a valid AI Tycoon export.', type: 'danger', tick: useGameStore.getState().meta.tickCount });
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||
};
|
||||
|
||||
const confirmImport = () => {
|
||||
if (!importData) return;
|
||||
localStorage.setItem('ai-tycoon-save', JSON.stringify({ state: importData.data }));
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 max-w-2xl">
|
||||
<h2 className="text-2xl font-bold">Settings</h2>
|
||||
@@ -113,13 +117,34 @@ export function SettingsPage() {
|
||||
className="hidden"
|
||||
/>
|
||||
<button
|
||||
onClick={handleReset}
|
||||
onClick={() => setShowResetConfirm(true)}
|
||||
className="px-4 py-2 rounded bg-danger/20 hover:bg-danger/30 border border-danger/50 text-danger text-sm"
|
||||
>
|
||||
Reset Progress
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showResetConfirm && (
|
||||
<ConfirmModal
|
||||
title="Reset All Progress"
|
||||
message="This will permanently delete your save data and start a new game. This cannot be undone."
|
||||
confirmLabel="Reset Everything"
|
||||
danger
|
||||
onConfirm={handleReset}
|
||||
onCancel={() => setShowResetConfirm(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{importData && (
|
||||
<ConfirmModal
|
||||
title="Import Save"
|
||||
message={`Import save for "${importData.name}"? This will replace your current game.`}
|
||||
confirmLabel="Import"
|
||||
onConfirm={confirmImport}
|
||||
onCancel={() => setImportData(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user