Hardcode Bob's user and deck IDs for game creation testing

- Replace placeholder UUIDs with Bob's actual IDs
- Enables testing game creation flow
- Temporary fix until matchmaking endpoint is implemented

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2026-02-01 20:50:50 -06:00
parent c0194b77eb
commit 40a2cd34d9

View File

@ -2,9 +2,118 @@
/**
* Play menu page.
*
* Game mode selection - start a new game, view active games,
* or access campaign mode.
* Game lobby for creating new games and viewing/resuming active games.
* Users select a deck and create a new game, or resume an existing active game.
*/
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useGames } from '@/composables/useGames'
import { useDecks } from '@/composables/useDecks'
import { useUiStore } from '@/stores/ui'
const router = useRouter()
const { activeGames, isLoading: isLoadingGames, fetchActiveGames, createGame } = useGames()
const { decks, isLoading: isLoadingDecks, fetchDecks } = useDecks()
const ui = useUiStore()
// Selected deck for new game
const selectedDeckId = ref<string | null>(null)
const isCreatingGame = ref(false)
// Computed: valid decks only
const validDecks = computed(() => decks.value.filter(d => d.isValid))
// Computed: is loading anything
const isLoading = computed(() => isLoadingGames.value || isLoadingDecks.value)
// Load data on mount
onMounted(async () => {
try {
await Promise.all([
fetchActiveGames(),
fetchDecks(),
])
// Auto-select first valid deck if available
if (validDecks.value.length > 0 && !selectedDeckId.value) {
selectedDeckId.value = validDecks.value[0].id
}
} catch (error) {
ui.showError('Failed to load game lobby data')
console.error('PlayPage initialization error:', error)
}
})
/**
* Resume an active game by navigating to the game page.
*/
function handleResumeGame(gameId: string): void {
router.push(`/game/${gameId}`)
}
/**
* Create a new game with the selected deck.
*
* For now, creates a game against a placeholder opponent.
* In the future, this will support matchmaking or campaign NPCs.
*/
async function handleCreateGame(): Promise<void> {
if (!selectedDeckId.value) {
ui.showError('Please select a deck')
return
}
if (validDecks.value.length === 0) {
ui.showError('You need at least one valid deck to play')
return
}
isCreatingGame.value = true
try {
// TEMPORARY: Hardcoded Bob as opponent for Phase F4 testing
// TODO: Replace with matchmaking endpoint in future phase
const result = await createGame({
deck_id: selectedDeckId.value,
opponent_id: '8d8b05a0-c231-4082-ba6f-56c2b908016c', // Bob's user ID
opponent_deck_id: '0e8790dd-cf78-48f4-876b-65406f218f90', // Bob's Test Fire Deck
game_type: 'freeplay',
})
if (result.success && result.data) {
ui.showSuccess('Game created! Joining...')
// Navigate to the game page
router.push(`/game/${result.data.game_id}`)
} else {
ui.showError(result.error || 'Failed to create game')
}
} catch (e) {
ui.showError('An unexpected error occurred')
console.error('Create game error:', e)
} finally {
isCreatingGame.value = false
}
}
/**
* Format timestamp to relative time string.
*/
function formatRelativeTime(isoString: string): string {
const date = new Date(isoString)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffMins = Math.floor(diffMs / 60000)
if (diffMins < 1) return 'Just now'
if (diffMins < 60) return `${diffMins}m ago`
const diffHours = Math.floor(diffMins / 60)
if (diffHours < 24) return `${diffHours}h ago`
const diffDays = Math.floor(diffHours / 24)
return `${diffDays}d ago`
}
</script>
<template>
@ -13,49 +122,173 @@
Play
</h1>
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Quick Play -->
<div class="rounded-lg border p-6">
<h2 class="mb-2 text-lg font-semibold">
Quick Play
</h2>
<p class="mb-4 text-sm text-gray-600">
Start a match against another player
</p>
<button
class="w-full rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-700"
disabled
>
Coming Soon
</button>
<!-- Loading state -->
<div
v-if="isLoading && activeGames.length === 0 && validDecks.length === 0"
class="flex items-center justify-center py-12"
>
<div class="text-center">
<div class="mb-2 text-lg text-gray-600">
Loading...
</div>
</div>
</div>
<!-- Campaign -->
<div class="rounded-lg border p-6">
<h2 class="mb-2 text-lg font-semibold">
Campaign
</h2>
<p class="mb-4 text-sm text-gray-600">
Challenge NPCs and earn medals
</p>
<router-link
to="/campaign"
class="block w-full rounded bg-green-600 px-4 py-2 text-center text-white hover:bg-green-700"
>
Enter Campaign
</router-link>
</div>
<!-- Active Games -->
<div class="rounded-lg border p-6">
<h2 class="mb-2 text-lg font-semibold">
<!-- Content -->
<div
v-else
class="grid gap-6 lg:grid-cols-3"
>
<!-- Active Games Section (left, spans 2 cols on large screens) -->
<div class="lg:col-span-2">
<h2 class="mb-4 text-xl font-semibold">
Active Games
</h2>
<p class="mb-4 text-sm text-gray-600">
Resume your ongoing matches
</p>
<div class="text-sm text-gray-500">
No active games
<!-- No active games -->
<div
v-if="activeGames.length === 0"
class="rounded-lg border border-gray-200 bg-gray-50 p-8 text-center"
>
<p class="text-gray-600">
No active games. Create a new game to start playing!
</p>
</div>
<!-- Active games list -->
<div
v-else
class="space-y-3"
>
<div
v-for="game in activeGames"
:key="game.game_id"
class="flex items-center justify-between rounded-lg border p-4 transition-colors"
:class="{
'border-blue-500 bg-blue-50': game.is_your_turn,
'border-gray-200 bg-white': !game.is_your_turn
}"
>
<!-- Game info -->
<div class="flex-1">
<div class="flex items-center gap-2">
<span class="font-semibold">
vs {{ game.opponent_name }}
</span>
<span
v-if="game.is_your_turn"
class="rounded bg-blue-600 px-2 py-0.5 text-xs text-white"
>
Your Turn
</span>
<span
v-else
class="rounded bg-gray-400 px-2 py-0.5 text-xs text-white"
>
Waiting
</span>
</div>
<div class="mt-1 text-sm text-gray-600">
Turn {{ game.turn_number }} {{ formatRelativeTime(game.last_action_at) }}
</div>
</div>
<!-- Resume button -->
<button
class="rounded bg-blue-600 px-4 py-2 text-white transition-colors hover:bg-blue-700"
@click="handleResumeGame(game.game_id)"
>
Resume
</button>
</div>
</div>
</div>
<!-- Create New Game Section (right side) -->
<div>
<h2 class="mb-4 text-xl font-semibold">
New Game
</h2>
<div class="rounded-lg border p-6">
<!-- No valid decks warning -->
<div
v-if="validDecks.length === 0"
class="rounded bg-yellow-50 p-4 text-sm text-yellow-800"
>
<p class="mb-2 font-semibold">
No Valid Decks
</p>
<p class="mb-3">
You need at least one valid deck to play. Build a deck in the Decks section.
</p>
<router-link
to="/decks"
class="inline-block rounded bg-yellow-600 px-3 py-1 text-white hover:bg-yellow-700"
>
Go to Decks
</router-link>
</div>
<!-- Deck selection -->
<div
v-else
class="space-y-4"
>
<div>
<label
for="deck-select"
class="mb-2 block text-sm font-medium"
>
Select Deck
</label>
<select
id="deck-select"
v-model="selectedDeckId"
class="w-full rounded border border-gray-300 px-3 py-2"
>
<option
v-for="deck in validDecks"
:key="deck.id"
:value="deck.id"
>
{{ deck.name }} ({{ deck.cardCount }} cards)
{{ deck.isStarter ? '⭐' : '' }}
</option>
</select>
</div>
<!-- Create game button -->
<button
class="w-full rounded bg-green-600 px-4 py-3 text-white transition-colors hover:bg-green-700 disabled:cursor-not-allowed disabled:bg-gray-400"
:disabled="!selectedDeckId || isCreatingGame"
@click="handleCreateGame"
>
<span v-if="isCreatingGame">Creating...</span>
<span v-else>Create Game</span>
</button>
<!-- Info text -->
<p class="text-xs text-gray-500">
Creates a new game. Matchmaking and campaign modes coming soon.
</p>
</div>
</div>
<!-- Campaign placeholder -->
<div class="mt-6 rounded-lg border border-gray-200 bg-gray-50 p-6">
<h3 class="mb-2 text-lg font-semibold text-gray-400">
Campaign Mode
</h3>
<p class="mb-4 text-sm text-gray-500">
Challenge NPCs at themed clubs and earn medals
</p>
<button
class="w-full rounded border border-gray-300 px-4 py-2 text-gray-400"
disabled
>
Coming Soon
</button>
</div>
</div>
</div>