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:
parent
c0194b77eb
commit
40a2cd34d9
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user