Merge pull request 'Build game lobby for single-player and multiplayer modes' (#76) from feature/home-page into develop
Reviewed-on: https://gitea.fernandovideira.com/SyntaxSquad/DADProject/pulls/76 Reviewed-by: Afonso Clerigo Mendes de Sousa <[email protected]> Reviewed-by: KZix <[email protected]>
This commit was merged in pull request #76.
This commit is contained in:
@@ -22,3 +22,5 @@
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
|
||||
composer.lock
|
||||
@@ -30,7 +30,11 @@ class GameController extends Controller
|
||||
$query->where("status", $request->status);
|
||||
}
|
||||
|
||||
$query->orderBy("began_at", "desc");
|
||||
if ($request->has("sort_direction") && in_array($request->sort_direction, ["asc", "desc"])) {
|
||||
$query->orderBy("began_at", $request->sort_direction);
|
||||
}else{
|
||||
$query->orderBy("began_at", "desc");
|
||||
}
|
||||
|
||||
return response()->json($query->paginate(15));
|
||||
}
|
||||
|
||||
Vendored
+3
@@ -102,12 +102,15 @@
|
||||
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PauseCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\ResumeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\ReloadCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||
|
||||
Generated
-9392
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,19 @@
|
||||
### Get All Students
|
||||
### Get All Students (Login)
|
||||
# @name login
|
||||
POST http://localhost:8000/api/login
|
||||
content-Type: application/json
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
|
||||
{
|
||||
"email": "[email protected]",
|
||||
"password": "123"
|
||||
}
|
||||
}
|
||||
|
||||
### Capture the token
|
||||
@token = {{login.response.body.token}}
|
||||
|
||||
### Get All matches
|
||||
GET http://localhost:8000/api/matches
|
||||
Content-Type: application/json
|
||||
Accept: application/json
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -9,13 +9,12 @@
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"composables": "@/composables",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"composables": "@/composables"
|
||||
"lib": "@/lib"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
Generated
-5208
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"lucide-react": "^0.562.0",
|
||||
"lucide-vue-next": "^0.554.0",
|
||||
"pinia": "^3.0.3",
|
||||
"reka-ui": "^2.6.0",
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
</li>
|
||||
</NavigationMenuContent>
|
||||
</NavigationMenuItem>
|
||||
|
||||
|
||||
<NavigationMenuItem v-if="!userLoggedIn">
|
||||
<NavigationMenuLink>
|
||||
<RouterLink to="/login">Login</RouterLink>
|
||||
</NavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
|
||||
|
||||
<NavigationMenuItem v-else>
|
||||
<NavigationMenuLink>
|
||||
<a href="/home" @click.prevent="logoutClickHandler" class="cursor-pointer">Logout</a>
|
||||
@@ -36,7 +36,6 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useBiscaStore } from '@/stores/bisca'
|
||||
import {
|
||||
NavigationMenu,
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="card"
|
||||
:class="
|
||||
cn(
|
||||
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="card-action"
|
||||
:class="
|
||||
cn(
|
||||
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,13 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-slot="card-content" :class="cn('px-6', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<p
|
||||
data-slot="card-description"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
:class="cn('flex items-center px-6 [.border-t]:pt-6', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-slot="card-header"
|
||||
:class="
|
||||
cn(
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,16 @@
|
||||
<script setup>
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const props = defineProps({
|
||||
class: { type: null, required: false },
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h3
|
||||
data-slot="card-title"
|
||||
:class="cn('leading-none font-semibold', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
export { default as Card } from "./Card.vue";
|
||||
export { default as CardAction } from "./CardAction.vue";
|
||||
export { default as CardContent } from "./CardContent.vue";
|
||||
export { default as CardDescription } from "./CardDescription.vue";
|
||||
export { default as CardFooter } from "./CardFooter.vue";
|
||||
export { default as CardHeader } from "./CardHeader.vue";
|
||||
export { default as CardTitle } from "./CardTitle.vue";
|
||||
@@ -39,6 +39,9 @@
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--radius-2xl: calc(var(--radius) + 8px);
|
||||
--radius-3xl: calc(var(--radius) + 12px);
|
||||
--radius-4xl: calc(var(--radius) + 16px);
|
||||
}
|
||||
|
||||
:root {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs) {
|
||||
return twMerge(clsx(inputs));
|
||||
|
||||
@@ -15,7 +15,7 @@ const app = createApp(App)
|
||||
|
||||
//app.provide('socket', io(wsConnection))
|
||||
app.provide('serverBaseURL', `http://${apiDomain}`)
|
||||
app.provide('apiBaseURL', `http://${apiDomain}/api`)
|
||||
app.provide('apiBaseURL', `http://${apiDomain}/api/v1`)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
@@ -1,85 +1,242 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center min-h-screen bg-green-900 text-white relative"
|
||||
>
|
||||
<h1 class="text-6xl font-bold mb-12 drop-shadow-lg">Bisca Game</h1>
|
||||
|
||||
<div v-if="!showModeSelection" class="flex flex-col gap-6 w-64">
|
||||
<button
|
||||
@click="showModeSelection = true"
|
||||
class="px-6 py-4 bg-emerald-600 hover:bg-emerald-500 rounded-lg text-xl font-bold shadow-lg transition-transform hover:scale-105"
|
||||
>
|
||||
Single Player
|
||||
</button>
|
||||
|
||||
<button
|
||||
disabled
|
||||
class="px-6 py-4 bg-gray-600 rounded-lg text-xl font-bold opacity-50 cursor-not-allowed"
|
||||
>
|
||||
Multiplayer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="bg-gray-800/90 p-8 rounded-xl border border-gray-600 shadow-2xl w-80 text-center animate-fade-in"
|
||||
>
|
||||
<h2 class="text-2xl font-bold mb-6 text-emerald-400">Escolhe o Modo</h2>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<button
|
||||
@click="startGame(3)"
|
||||
class="px-6 py-3 bg-blue-600 hover:bg-blue-500 rounded-lg font-bold transition-colors flex justify-between items-center"
|
||||
>
|
||||
<span>Bisca de 3</span>
|
||||
<span class="text-xs bg-blue-800 px-2 py-1 rounded">Clássico</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click="startGame(9)"
|
||||
class="px-6 py-3 bg-purple-600 hover:bg-purple-500 rounded-lg font-bold transition-colors flex justify-between items-center"
|
||||
>
|
||||
<span>Bisca de 9</span>
|
||||
<span class="text-xs bg-purple-800 px-2 py-1 rounded">Épico</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="showModeSelection = false"
|
||||
class="mt-6 text-gray-400 hover:text-white text-sm underline"
|
||||
>
|
||||
Voltar atrás
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from '@/components/ui/card'
|
||||
import { useAPIStore } from '@/stores/api'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const apiStore = useAPIStore()
|
||||
const router = useRouter()
|
||||
const showModeSelection = ref(false)
|
||||
const openGames = ref([])
|
||||
const selectedMode = ref('9')
|
||||
const observerTarget = ref(null);
|
||||
const observer = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const pageCounter = ref(1);
|
||||
const showHostModal = ref(false)
|
||||
const showJoinModal = ref(false)
|
||||
const selectedGame = ref(null)
|
||||
const isReady = ref(false)
|
||||
|
||||
const startGame = (type) => {
|
||||
// Navega para a rota correta baseada no tipo (3 ou 9)
|
||||
const routeName = type === 9 ? 'bisca9' : 'bisca3'
|
||||
router.push({ name: routeName })
|
||||
const setupObserver = () => {
|
||||
if (observer.value) observer.value.disconnect();
|
||||
|
||||
observer.value = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !isLoading.value && openGames.value.length > 0) {
|
||||
getPendingGames();
|
||||
}
|
||||
}, { threshold: 0.1 });
|
||||
|
||||
if (observerTarget.value) {
|
||||
observer.value.observe(observerTarget.value);
|
||||
}
|
||||
}
|
||||
|
||||
const getPendingGames = async (mode = selectedMode.value) => {
|
||||
if (isLoading.value) return;
|
||||
isLoading.value = true;
|
||||
|
||||
|
||||
apiStore.gameQueryParameters.page = pageCounter.value++;
|
||||
apiStore.gameQueryParameters.filters.sort_direction = "asc"
|
||||
apiStore.gameQueryParameters.filters.type = mode
|
||||
apiStore.gameQueryParameters.filters.status = 'Pending'
|
||||
try {
|
||||
const response = await apiStore.getGames();
|
||||
const newGames = response.data.data;
|
||||
openGames.value = [...openGames.value, ...newGames];
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
const clickMode = async (mode) => {
|
||||
if (selectedMode.value === mode) return;
|
||||
openGames.value = [];
|
||||
selectedMode.value = mode;
|
||||
pageCounter.value = 1;
|
||||
await getPendingGames();
|
||||
setupObserver();
|
||||
}
|
||||
|
||||
const startSingle = () => {
|
||||
router.push({ name: 'bisca' + selectedMode.value });
|
||||
}
|
||||
|
||||
const JoinGameModal = (game) => {
|
||||
selectedGame.value = game
|
||||
isReady.value = false
|
||||
showJoinModal.value = true
|
||||
}
|
||||
|
||||
const hostGameModal = () => {
|
||||
showHostModal.value = true
|
||||
}
|
||||
|
||||
const toggleReady = () => {
|
||||
isReady.value = !isReady.value
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getPendingGames();
|
||||
await nextTick();
|
||||
setupObserver();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.animate-fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="min-h-screen bg-cover bg-center transition-all duration-700">
|
||||
|
||||
<div class="flex flex-col justify-center items-center gap-5 pt-10 px-4">
|
||||
|
||||
<div class="relative w-full max-w-md bg-card rounded-lg border shadow-sm overflow-hidden">
|
||||
<div class="relative h-14 flex items-center">
|
||||
|
||||
<button @click="clickMode('9')"
|
||||
class="h-full flex items-center justify-center font-semibold transition-all duration-500 ease-in-out border-r last:border-r-0"
|
||||
:class="{
|
||||
'w-2/3 z-10 text-purple-600 dark:text-purple-400 bg-purple-500/10': selectedMode === '9',
|
||||
'w-1/3 z-0 text-muted-foreground/40 bg-transparent': selectedMode !== '9'
|
||||
}">
|
||||
<span>Bisca de 9</span>
|
||||
</button>
|
||||
|
||||
<button @click="clickMode('3')"
|
||||
class="h-full flex items-center justify-center font-semibold transition-all duration-500 ease-in-out"
|
||||
:class="{
|
||||
'w-2/3 z-10 text-purple-600 dark:text-purple-400 bg-purple-500/10': selectedMode === '3',
|
||||
'w-1/3 z-0 text-muted-foreground/40 bg-transparent': selectedMode !== '3'
|
||||
}">
|
||||
<span>Bisca de 3</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center gap-5 w-[100vw]">
|
||||
<Card class="w-full max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle class="text-3xl font-bold text-center">
|
||||
Single Player
|
||||
</CardTitle>
|
||||
<CardDescription class="text-center">
|
||||
Go against one of our bots!
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-6">
|
||||
<div class="flex justify-center">
|
||||
<Button @click="startSingle()" size="lg" variant="secondary"
|
||||
class="hover:bg-purple-500 hover:text-slate-200">
|
||||
Start Game
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card class="w-full max-w-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle class="text-3xl font-bold text-center">
|
||||
MultiPlayer
|
||||
</CardTitle>
|
||||
<CardDescription class="text-center">
|
||||
Go one on one with another player!
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-6">
|
||||
<label class="text-sm font-medium">Open games (oldest first)</label>
|
||||
<div class="rounded-lg border bg-card text-card-foreground shadow-sm">
|
||||
<div v-if="openGames.length === 0" class="p-6 text-center text-sm text-muted-foreground">
|
||||
No open games yet. Try hosting one!
|
||||
</div>
|
||||
<div v-else class="max-h-[350px] overflow-y-auto custom-scrollbar p-1">
|
||||
|
||||
<div v-for="(game, index) in openGames" :key="game.id" @click="JoinGameModal(game)"
|
||||
class="flex items-center justify-between p-3 mb-2 border border-transparent rounded-lg transition-all hover:bg-muted/50 hover:border-purple-500">
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold bg-green-100 text-green-700">
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-sm">Started: {{ game.began_at }}</div>
|
||||
<div class="text-xs text-muted-foreground">host: {{
|
||||
game.player1?.nickname || 'Anonymous' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ref="observerTarget" class="h-10 flex items-center justify-center">
|
||||
<span v-if="isLoading" class="text-xs text-purple-500 animate-pulse">Loading
|
||||
more...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<Button @click="hostGameModal()" size="lg" variant="secondary"
|
||||
class="hover:bg-purple-500 hover:text-slate-200">
|
||||
Host Game
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showHostModal"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
<Card class="w-full max-w-sm border-purple-500 shadow-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle class="text-center">Hosting Game</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-col items-center gap-6 py-10">
|
||||
<div class="relative">
|
||||
<div
|
||||
class="h-16 w-16 animate-spin rounded-full border-4 border-purple-500 border-t-transparent">
|
||||
</div>
|
||||
</div>
|
||||
<p class="animate-pulse text-lg font-medium text-muted-foreground">Waiting for opponents...</p>
|
||||
<Button variant="outline" @click="showHostModal = false">Cancel Hosting</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<div v-if="showJoinModal"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||
<Card class="w-full max-w-md border-purple-500 shadow-2xl">
|
||||
<CardHeader>
|
||||
<CardTitle>Join Game</CardTitle>
|
||||
<CardDescription v-if="selectedGame">
|
||||
Hosted by {{ selectedGame.player1?.nickname || 'Anonymous' }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="space-y-4">
|
||||
<div class="rounded-lg bg-muted p-4 text-sm">
|
||||
<p><strong>Game Type:</strong> Bisca de {{ selectedMode }}</p>
|
||||
<p><strong>Started at:</strong> {{ selectedGame?.began_at }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="isReady"
|
||||
class="flex items-center justify-center rounded-md bg-green-500/10 p-3 text-green-600 dark:text-green-400">
|
||||
<span class="animate-pulse font-semibold">Waiting for the host to start...</span>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-3 pt-4">
|
||||
<Button variant="destructive" class="flex-1" @click="showJoinModal = false">
|
||||
Leave
|
||||
</Button>
|
||||
|
||||
<Button class="flex-1 transition-all duration-300" :variant="isReady ? 'outline' : 'default'"
|
||||
:class="!isReady ? 'bg-purple-600 hover:bg-purple-700' : ''" @click="toggleReady">
|
||||
{{ isReady ? 'Cancel Ready' : 'Ready up' }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user