refactor: streamline new-game player selection ux
Consolidate new-game wizard steps into reusable components and remove legacy duplicate files. Replace inline player inputs with a chip-first flow and minimal name-entry modal, while improving compact-screen spacing and step-specific selection behavior. Made-with: Cursor
This commit is contained in:
@@ -80,6 +80,111 @@ npm run test:record
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
### Seed test matches via browser console
|
||||
|
||||
Copy/paste this snippet in your browser devtools console while the app is open. It inserts 10 sample matches (mix of 2/3 players, 8/9/10-ball, completed + active) into IndexedDB and reloads the page.
|
||||
|
||||
```js
|
||||
await (async () => {
|
||||
const DB_NAME = 'BSCScoreDB';
|
||||
const DB_VERSION = 1;
|
||||
const GAMES_STORE = 'games';
|
||||
|
||||
const openDb = () =>
|
||||
new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
|
||||
const now = Date.now();
|
||||
const minute = 60 * 1000;
|
||||
|
||||
const seedGames = [
|
||||
{ players: ['Alex', 'Ben'], gameType: '8-Ball', raceTo: 5, scores: [5, 2], status: 'completed', minutesAgo: 190 },
|
||||
{ players: ['Chris', 'Dana', 'Eli'], gameType: '8-Ball', raceTo: 6, scores: [4, 6, 2], status: 'completed', minutesAgo: 175 },
|
||||
{ players: ['Faye', 'Gus'], gameType: '9-Ball', raceTo: 7, scores: [7, 5], status: 'completed', minutesAgo: 160 },
|
||||
{ players: ['Hugo', 'Iris', 'Jade'], gameType: '9-Ball', raceTo: 4, scores: [2, 3, 1], status: 'active', minutesAgo: 145 },
|
||||
{ players: ['Kai', 'Lena'], gameType: '10-Ball', raceTo: 8, scores: [8, 6], status: 'completed', minutesAgo: 130 },
|
||||
{ players: ['Mona', 'Nico', 'Omar'], gameType: '10-Ball', raceTo: 5, scores: [5, 1, 4], status: 'completed', minutesAgo: 115 },
|
||||
{ players: ['Pia', 'Quin'], gameType: '8-Ball', raceTo: 4, scores: [1, 1], status: 'active', minutesAgo: 100 },
|
||||
{ players: ['Rex', 'Sara', 'Timo'], gameType: '9-Ball', raceTo: 3, scores: [3, 2, 0], status: 'completed', minutesAgo: 85 },
|
||||
{ players: ['Uma', 'Vik'], gameType: '10-Ball', raceTo: 6, scores: [2, 4], status: 'active', minutesAgo: 70 },
|
||||
{ players: ['Will', 'Xena', 'Yara'], gameType: '8-Ball', raceTo: 7, scores: [6, 4, 7], status: 'completed', minutesAgo: 55 },
|
||||
];
|
||||
|
||||
const gameRecords = seedGames.map((entry, index) => {
|
||||
const createdAtMs = now - entry.minutesAgo * minute;
|
||||
const updatedAtMs = createdAtMs + 15 * 1000;
|
||||
const id = now + index + 1;
|
||||
const isThreePlayer = entry.players.length === 3;
|
||||
const winnerIndex = entry.scores.indexOf(Math.max(...entry.scores));
|
||||
|
||||
const game = {
|
||||
id,
|
||||
gameType: entry.gameType,
|
||||
raceTo: entry.raceTo,
|
||||
status: entry.status,
|
||||
createdAt: new Date(createdAtMs).toISOString(),
|
||||
updatedAt: new Date(updatedAtMs).toISOString(),
|
||||
log: [
|
||||
{
|
||||
type: 'score_change',
|
||||
timestamp: new Date(createdAtMs + 5 * 1000).toISOString(),
|
||||
description: 'Seed data: score progression',
|
||||
},
|
||||
...(entry.status === 'completed'
|
||||
? [
|
||||
{
|
||||
type: 'game_complete',
|
||||
timestamp: new Date(updatedAtMs).toISOString(),
|
||||
description: `Winner: ${entry.players[winnerIndex]}`,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
undoStack: [],
|
||||
player1: entry.players[0],
|
||||
player2: entry.players[1],
|
||||
...(isThreePlayer ? { player3: entry.players[2] } : {}),
|
||||
score1: entry.scores[0],
|
||||
score2: entry.scores[1],
|
||||
...(isThreePlayer ? { score3: entry.scores[2] } : {}),
|
||||
breakRule: 'winnerbreak',
|
||||
breakOrder: isThreePlayer ? [1, 2, 3] : [1, 2],
|
||||
currentBreakerIdx: index % entry.players.length,
|
||||
};
|
||||
|
||||
return {
|
||||
id,
|
||||
game,
|
||||
lastModified: updatedAtMs,
|
||||
syncStatus: 'local',
|
||||
version: 1,
|
||||
createdAt: createdAtMs,
|
||||
};
|
||||
});
|
||||
|
||||
const db = await openDb();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const tx = db.transaction([GAMES_STORE], 'readwrite');
|
||||
const store = tx.objectStore(GAMES_STORE);
|
||||
|
||||
for (const record of gameRecords) {
|
||||
store.put(record);
|
||||
}
|
||||
|
||||
tx.oncomplete = () => resolve();
|
||||
tx.onerror = () => reject(tx.error);
|
||||
tx.onabort = () => reject(tx.error);
|
||||
});
|
||||
|
||||
console.log(`Inserted ${gameRecords.length} test matches into IndexedDB.`);
|
||||
window.location.reload();
|
||||
})();
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- **Record interactions**: Use Playwright codegen to capture clicks, form fills, and navigation
|
||||
|
||||
Reference in New Issue
Block a user