feat(web): add reusable ConfirmDialog and polish delete interactions
Extracts reusable ConfirmDialog component, refactors ship delete to show the ship name in confirmation, and improves interaction consistency. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
35
web/src/components/ui/ConfirmDialog.tsx
Normal file
35
web/src/components/ui/ConfirmDialog.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import Modal from './Modal';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onConfirm: () => void;
|
||||||
|
title: string;
|
||||||
|
message: string;
|
||||||
|
confirmLabel?: string;
|
||||||
|
danger?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ConfirmDialog({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onConfirm,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
confirmLabel = 'Confirm',
|
||||||
|
danger = false,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<Modal open={open} onClose={onClose} title={title}>
|
||||||
|
<p style={{ margin: 'var(--spacing-md) 0', lineHeight: 1.5 }}>{message}</p>
|
||||||
|
<div className="modal-actions">
|
||||||
|
<button className="btn-secondary" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button className={danger ? 'btn-danger' : 'btn-primary'} onClick={onConfirm}>
|
||||||
|
{confirmLabel}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import TopBar from '../components/layout/TopBar';
|
|||||||
import PageContainer from '../components/layout/PageContainer';
|
import PageContainer from '../components/layout/PageContainer';
|
||||||
import ShipCard from '../components/ships/ShipCard';
|
import ShipCard from '../components/ships/ShipCard';
|
||||||
import CreateShipModal from '../components/ships/CreateShipModal';
|
import CreateShipModal from '../components/ships/CreateShipModal';
|
||||||
|
import ConfirmDialog from '../components/ui/ConfirmDialog';
|
||||||
import { useShipsList } from '../store/use-ships-list';
|
import { useShipsList } from '../store/use-ships-list';
|
||||||
|
|
||||||
export default function ShipListPage() {
|
export default function ShipListPage() {
|
||||||
@@ -14,9 +15,9 @@ export default function ShipListPage() {
|
|||||||
fetchShips();
|
fetchShips();
|
||||||
}, [fetchShips]);
|
}, [fetchShips]);
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const pendingShipName = pendingDelete
|
||||||
setPendingDelete(id);
|
? ships.find((s) => s.id === pendingDelete)?.name ?? 'this ship'
|
||||||
};
|
: '';
|
||||||
|
|
||||||
const confirmDelete = async () => {
|
const confirmDelete = async () => {
|
||||||
if (!pendingDelete) return;
|
if (!pendingDelete) return;
|
||||||
@@ -42,33 +43,21 @@ export default function ShipListPage() {
|
|||||||
|
|
||||||
<div className="ship-list">
|
<div className="ship-list">
|
||||||
{ships.map((ship) => (
|
{ships.map((ship) => (
|
||||||
<ShipCard key={ship.id} ship={ship} onDelete={handleDelete} />
|
<ShipCard key={ship.id} ship={ship} onDelete={(id) => setPendingDelete(id)} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CreateShipModal open={showCreate} onClose={() => setShowCreate(false)} />
|
<CreateShipModal open={showCreate} onClose={() => setShowCreate(false)} />
|
||||||
|
|
||||||
{/* Simple delete confirmation */}
|
<ConfirmDialog
|
||||||
{pendingDelete && (
|
open={!!pendingDelete}
|
||||||
<div className="modal-overlay" onClick={() => setPendingDelete(null)}>
|
onClose={() => setPendingDelete(null)}
|
||||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
onConfirm={confirmDelete}
|
||||||
<div className="modal-header">
|
title="Delete Ship?"
|
||||||
<h2 className="modal-title">Delete Ship?</h2>
|
message={`Permanently delete "${pendingShipName}" and all its weapons?`}
|
||||||
</div>
|
confirmLabel="Delete"
|
||||||
<p style={{ margin: 'var(--spacing-md) 0' }}>
|
danger
|
||||||
This will permanently delete this ship and all its weapons.
|
/>
|
||||||
</p>
|
|
||||||
<div className="modal-actions">
|
|
||||||
<button className="btn-secondary" onClick={() => setPendingDelete(null)}>
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
<button className="btn-danger" onClick={confirmDelete}>
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user