Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8a7cf8e
feat(web): implement Phase 5 games view wireframe
metacurb Oct 10, 2025
0c761da
fix(web): improve games view UX and implement game joining
metacurb Oct 13, 2025
6dda636
fix(web): correct Button import path in game detail page
metacurb Oct 13, 2025
f0800ea
fix(web): correct API import path in game detail page
metacurb Oct 13, 2025
aac9cb0
fix(web): correct game detail page logic for all scenarios
metacurb Oct 13, 2025
76ec7c8
fix(web): resolve game access issues and improve View Game button logic
metacurb Oct 13, 2025
97ef701
feat(web): enable viewing finished games for players and spectators
metacurb Oct 13, 2025
34d9811
feat(web): allow spectators to view all games (active and finished)
metacurb Oct 13, 2025
665c44c
fix(web): remove 'Game Access' screen - all games are now viewable
metacurb Oct 13, 2025
adf2972
fix(web): resolve JSX syntax error in game detail page
metacurb Oct 13, 2025
5116c17
fix(web): completely rewrite game detail page to resolve JSX syntax eโ€ฆ
metacurb Oct 13, 2025
4f00652
feat(web): update games list layout to max-w-md with single column
metacurb Oct 13, 2025
8888036
feat(web): add basic component tests for Phase 5 games components
metacurb Oct 13, 2025
140131e
fix(web): resolve all test failures for Phase 5 games components
metacurb Oct 13, 2025
591473a
style(web): clean up code formatting for Phase 5 games components
metacurb Oct 13, 2025
0d4092f
chore: linting
metacurb Oct 13, 2025
b2f156a
refactor(web): simplify game view logic to unified component
metacurb Oct 13, 2025
3c240f4
fix(web): consistent newName state handling in Change Name modal
metacurb Oct 13, 2025
941cc71
test(api): update tests to match new game access behavior
metacurb Oct 13, 2025
8035963
chore: add pre-commit hooks with husky and lint-staged
metacurb Oct 13, 2025
3599e41
fix(web): resolve SSR issue with window.location.origin in ShareGameMโ€ฆ
metacurb Oct 13, 2025
3d365b1
chore: add husky pre-commit hook configuration
metacurb Oct 13, 2025
2af6f97
fix(web): prevent stale name display in Header change name modal
metacurb Oct 13, 2025
61de0be
fix(web): resolve linting errors in games components
metacurb Oct 13, 2025
4455638
fix(web): resolve CreateGameButton not refreshing games list after crโ€ฆ
metacurb Oct 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm exec lint-staged
313 changes: 313 additions & 0 deletions .spec/phase5-games-view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
# ๐ŸŽฎ Phase 5 โ€” Games View Wireframe Spec

> **Goal:** Build the authenticated user's games dashboard with header, options menu, active games list, and game creation flow. Focus on wireframe structure with clay tablet styling.

---

## ๐ŸŽฏ Vision

**Games dashboard for authenticated users:**

1. **Header:** Logo left + Options menu right (user name, change name, how to play, clear data)
2. **Create Game:** Simple button with 5 waiting games limit
3. **Games List:** Cards showing opponent, status, turn indicator, actions
4. **Empty State:** Centered create button when no games exist

**Tech Stack:**

- Existing clay tablet theme from Phase 4
- Reusable Button component
- API integration with existing endpoints
- Mobile-first responsive design

---

## ๐Ÿ“‹ Implementation Plan

### Step 1: Create Header Component

**File:** `apps/web/components/games/Header.tsx`

**Features:**

- Logo on left ("GAME OF UR" text for now)
- Options menu icon on right (hamburger/user icon)
- Dropdown/modal with user info and actions
- Clay tablet styling consistent with homepage

**Options Menu Content:**

- User display name at top
- "Change Name" option
- "How to Play" option (reuse existing component)
- "Clear User Data" option (logout)

### Step 2: Create Games List Components

**Files:**

- `apps/web/components/games/GamesList.tsx` - Main container
- `apps/web/components/games/GameCard.tsx` - Individual game display

**GamesList Features:**

- Fetch user games using `api.getUserGames(secret)`
- Loading and error states
- Responsive grid layout

**GameCard Features:**

- Opponent name (player1 or player2, whoever isn't current user)
- Game status badge (waiting, active, finished)
- "Your turn" indicator if `currentTurn === userId`
- "View Game" button (navigate to `/games/[id]`)
- "Forfeit" button (call `api.forfeitGame()`)

### Step 3: Create Game Creation Components

**Files:**

- `apps/web/components/games/CreateGameButton.tsx`
- `apps/web/components/games/ShareGameModal.tsx`

**CreateGameButton Features:**

- Check current "waiting" games count
- Limit: Maximum 5 games in "waiting" status
- Show error if limit reached
- On success: Show shareable link modal

**ShareGameModal Features:**

- Display game link (e.g., `https://yourdomain.com/games/[gameId]`)
- Copy to clipboard functionality
- Instructions for sharing

### Step 4: Create Empty State

**File:** `apps/web/components/games/EmptyState.tsx`

**Features:**

- Centered message: "No active games yet"
- Large "Create New Game" button
- Clay tablet styling

### Step 5: Refactor Games Page

**File:** `apps/web/app/games/page.tsx`

**Layout Structure:**

```
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Header (Logo + Options Menu) โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚
โ”‚ Create New Game Button (top) โ”‚
โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Game Card 1 โ”‚ โ”‚
โ”‚ โ”‚ - Opponent: Player Name โ”‚ โ”‚
โ”‚ โ”‚ - Status: Active โ”‚ โ”‚
โ”‚ โ”‚ - Your Turn! โ”‚ โ”‚
โ”‚ โ”‚ [View Game] [Forfeit] โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ Game Card 2 (waiting) โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
```

**Features:**

- User authentication check (redirect to `/` if no user)
- Integrate all components
- State management for games list
- Refresh after create/forfeit actions

---

## ๐Ÿ”ง Technical Implementation

### API Integration

**Endpoints (already implemented):**

- `GET /games` - Get user's games list
- `POST /games` - Create new game
- `POST /games/:id/forfeit` - Forfeit game

**Response Structure:**

```typescript
GetGamesListResponse = Array<{
id: string
status: 'waiting' | 'active' | 'finished'
player1: { id: string; displayName: string }
player2: { id: string; displayName: string } | null
currentTurn: string | null
winnerId: string | null
}>
```

### Business Logic

**Waiting Games Limit:**

- Count games where `status === 'waiting'`
- If count >= 5, disable create button
- Show tooltip: "Maximum 5 waiting games allowed"

**Game Card Logic:**

- Determine opponent: if `currentUserId === player1.id` then opponent is `player2`, else `player1`
- Show "Waiting for opponent" if `player2 === null`
- Show "Your turn" badge if `currentTurn === currentUserId`
- Disable forfeit for finished games

**Options Menu Actions:**

- Change Name: Open modal with input, call `api.updateUser()`
- How to Play: Reuse `HowToPlay` component from landing
- Clear User Data: `localStorage.removeItem('urUser')`, redirect to `/`

### State Management

- Use React state (defer React Query for now)
- Fetch games on mount
- Refresh games list after create/forfeit
- Handle loading and error states

---

## ๐ŸŽจ Styling Guidelines

### Clay Tablet Theme

- Reuse existing clay tablet theme from Phase 4
- Use existing Button component for actions
- Use existing `.card` class for game cards
- Apply noisy texture background to games page
- Maintain amber/brown color palette

### Mobile-First Design

- Responsive grid layout for game cards
- Touch-friendly button sizes
- Proper spacing for mobile devices
- Sticky header if needed

### Component Styling

- Header: Full width, fixed height
- Game cards: Consistent card styling with clay theme
- Buttons: Use existing Button component
- Modals: Clay-themed overlays

---

## ๐Ÿ“ฑ User Experience Flow

### New User Journey

1. User creates account on homepage
2. Redirected to `/games` page
3. Sees empty state with "Create New Game" button
4. Clicks create โ†’ sees shareable link modal
5. Shares link with friend

### Existing User Journey

1. User visits `/games` page
2. Sees list of active games
3. Can create new game (if under limit)
4. Can view existing games
5. Can forfeit games if needed

### Game Creation Flow

1. Click "Create New Game" button
2. Check waiting games count
3. If under limit: Create game via API
4. Show shareable link modal
5. User copies link to share
6. Games list refreshes to show new game

---

## โœ… Testing Checklist

### Component Testing

- [ ] Header displays logo and opens options menu
- [ ] Options menu shows user name and action buttons
- [ ] Games list fetches and displays user games correctly
- [ ] Game cards show opponent name and status
- [ ] "Your turn" indicator appears correctly
- [ ] Create game button creates game and shows share link
- [ ] Waiting games limit (5) is enforced
- [ ] Forfeit button works and updates list
- [ ] Empty state shows when no games exist
- [ ] Shareable link is copyable

### User Flow Testing

- [ ] Change name updates user display name
- [ ] Clear user data logs out and redirects
- [ ] Mobile responsive layout works
- [ ] Error states are handled gracefully
- [ ] Loading states show appropriately

### API Integration Testing

- [ ] Games list loads correctly
- [ ] Game creation works
- [ ] Game forfeit works
- [ ] Error handling for API failures
- [ ] Authentication works properly

---

## ๐Ÿšซ Not Included (Future Phases)

- Real-time game updates (polling/websockets)
- Game board interface (Phase 6)
- Turn timers and notifications
- Game history/replay
- Pagination for games list
- Advanced game filtering/sorting

---

## โญ๏ธ Next Steps After This Phase

**Phase 6: Game Board Interface**

- Individual game view (`/games/[id]`)
- 3D board rendering with React Three Fiber
- Dice rolling and piece movement
- Move validation and game state updates

**Phase 7: Async Multiplayer**

- Turn timeouts and notifications
- Background polling for updates
- Game state synchronization

---

## ๐Ÿ“ Implementation Tasks

- [ ] Create Header component with logo and options menu
- [ ] Create GamesList component that fetches and displays games
- [ ] Create GameCard component for individual game display
- [ ] Create CreateGameButton with 5 waiting games limit
- [ ] Create ShareGameModal for displaying shareable link
- [ ] Create EmptyState component for no games
- [ ] Refactor /games page to integrate all components
- [ ] Add user authentication check and redirect logic
- [ ] Test complete games flow: create, list, forfeit, share
- [ ] Update README with Phase 5 completion
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ game-of-ur/
โ”‚ โ”œโ”€ phase1-users.md
โ”‚ โ”œโ”€ phase2-game-creation.md
โ”‚ โ”œโ”€ phase3-game-rules.md
โ”‚ โ””โ”€ phase4-homepage.md
โ”‚ โ”œโ”€ phase4-homepage.md
โ”‚ โ””โ”€ phase5-games-view.md
โ”‚
โ”œโ”€ turbo.json
โ”œโ”€ docker-compose.yml # Full local stack (web + api + postgres)
Expand Down Expand Up @@ -153,8 +154,8 @@ Each spec describes _exactly_ what should be implemented before moving forward.
| **Phase 1** | Device identity and persistence (deviceSecret model) | โœ… Done |
| **Phase 2** | Game creation and joining logic | โœ… Done |
| **Phase 3** | Game rules, dice rolls, and move logic | โœ… Done |
| **Phase 4** | Homepage redesign with 3D clay tablet aesthetic | โณ Next |
| **Phase 5** | Games view wireframe and active games dashboard | ๐Ÿ”œ |
| **Phase 4** | Homepage redesign with 3D clay tablet aesthetic | โœ… Done |
| **Phase 5** | Games view wireframe and active games dashboard | โณ Next |
| **Phase 6** | Game board interface and 3D board rendering | ๐Ÿ”œ |
| **Phase 7** | Async multiplayer and turn timeouts | ๐Ÿ”œ |
| **Phase 8** | Polish, UX, and visual improvements | ๐Ÿ”œ |
Expand Down
6 changes: 3 additions & 3 deletions apps/api/src/games/games.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ describe('Games E2E', () => {
expect(joinRes.body.currentTurn).toBe(player1Id)
})

it('prevents unauthorized access to game', async () => {
it('allows access to game for joining purposes', async () => {
// Create third player
const player3Res = await request(app.getHttpServer())
.post('/users')
Expand All @@ -97,11 +97,11 @@ describe('Games E2E', () => {

const player3Secret = player3Res.body.secret

// Try to access game without being part of it
// Try to access game without being part of it (should succeed for joining)
await request(app.getHttpServer())
.get(`/games/${gameId}`)
.set('Authorization', `Bearer ${player3Secret}`)
.expect(400)
.expect(200)
})
})

Expand Down
7 changes: 3 additions & 4 deletions apps/api/src/games/games.service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,14 @@ describe('GamesService', () => {
)
})

it('should throw BadRequestException if user is not part of the game', async () => {
it('should return game even if user is not part of the game', async () => {
const gameId = 'game-1'
const game = { ...mockGame, player1Id: 'user-3', player2Id: 'user-4' }

mockRepository.findOne.mockResolvedValue(game)

await expect(service.getById(TEST_CORRELATION_ID, gameId, mockUser1.id)).rejects.toThrow(
BadRequestException,
)
const result = await service.getById(TEST_CORRELATION_ID, gameId, mockUser1.id)
expect(result).toEqual(game)
})
})

Expand Down
5 changes: 0 additions & 5 deletions apps/api/src/games/games.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,11 +154,6 @@ export class GamesService {
throw new NotFoundException('Game not found')
}

if (game.player1Id !== userId && game.player2Id !== userId) {
this.logger.warn({ correlationId, gameId, userId }, 'User not authorized to view game')
throw new BadRequestException('You are not part of this game')
}

this.logger.debug({ correlationId, gameId, userId }, 'Game retrieved successfully')
return game
}
Expand Down
Loading