Version 1.0 #102
@@ -22,3 +22,5 @@
|
|||||||
Homestead.json
|
Homestead.json
|
||||||
Homestead.yaml
|
Homestead.yaml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
composer.lock
|
||||||
@@ -30,7 +30,11 @@ class GameController extends Controller
|
|||||||
$query->where("status", $request->status);
|
$query->where("status", $request->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
$query->orderBy("began_at", "desc");
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json($query->paginate(15));
|
return response()->json($query->paginate(15));
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+3
@@ -102,12 +102,15 @@
|
|||||||
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Queue\\Console\\MonitorCommand' => '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\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Queue\\Console\\RestartCommand' => '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\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Queue\\Console\\WorkCommand' => '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\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider',
|
||||||
'Illuminate\\Foundation\\Console\\RouteListCommand' => '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
|
POST http://localhost:8000/api/login
|
||||||
content-Type: application/json
|
Content-Type: application/json
|
||||||
Accept: application/json
|
Accept: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"email": "[email protected]",
|
"email": "[email protected]",
|
||||||
"password": "123"
|
"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,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
"iconLibrary": "lucide",
|
|
||||||
"aliases": {
|
"aliases": {
|
||||||
"components": "@/components",
|
"components": "@/components",
|
||||||
|
"composables": "@/composables",
|
||||||
"utils": "@/lib/utils",
|
"utils": "@/lib/utils",
|
||||||
"ui": "@/components/ui",
|
"ui": "@/components/ui",
|
||||||
"lib": "@/lib",
|
"lib": "@/lib"
|
||||||
"composables": "@/composables"
|
|
||||||
},
|
},
|
||||||
"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",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
|
"lucide-react": "^0.562.0",
|
||||||
"lucide-vue-next": "^0.554.0",
|
"lucide-vue-next": "^0.554.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"reka-ui": "^2.6.0",
|
"reka-ui": "^2.6.0",
|
||||||
|
|||||||
@@ -36,7 +36,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useBiscaStore } from '@/stores/bisca'
|
import { useBiscaStore } from '@/stores/bisca'
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
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-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--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 {
|
:root {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { clsx } from "clsx";
|
import { clsx } from "clsx";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
export function cn(...inputs) {
|
export function cn(...inputs) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const app = createApp(App)
|
|||||||
|
|
||||||
//app.provide('socket', io(wsConnection))
|
//app.provide('socket', io(wsConnection))
|
||||||
app.provide('serverBaseURL', `http://${apiDomain}`)
|
app.provide('serverBaseURL', `http://${apiDomain}`)
|
||||||
app.provide('apiBaseURL', `http://${apiDomain}/api`)
|
app.provide('apiBaseURL', `http://${apiDomain}/api/v1`)
|
||||||
|
|
||||||
app.use(createPinia())
|
app.use(createPinia())
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|||||||
@@ -1,85 +1,242 @@
|
|||||||
|
<script setup>
|
||||||
|
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 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 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>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="min-h-screen bg-cover bg-center transition-all duration-700">
|
||||||
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">
|
<div class="flex flex-col justify-center items-center gap-5 pt-10 px-4">
|
||||||
<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
|
<div class="relative w-full max-w-md bg-card rounded-lg border shadow-sm overflow-hidden">
|
||||||
disabled
|
<div class="relative h-14 flex items-center">
|
||||||
class="px-6 py-4 bg-gray-600 rounded-lg text-xl font-bold opacity-50 cursor-not-allowed"
|
|
||||||
>
|
|
||||||
Multiplayer
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<button @click="clickMode('9')"
|
||||||
v-else
|
class="h-full flex items-center justify-center font-semibold transition-all duration-500 ease-in-out border-r last:border-r-0"
|
||||||
class="bg-gray-800/90 p-8 rounded-xl border border-gray-600 shadow-2xl w-80 text-center animate-fade-in"
|
:class="{
|
||||||
>
|
'w-2/3 z-10 text-purple-600 dark:text-purple-400 bg-purple-500/10': selectedMode === '9',
|
||||||
<h2 class="text-2xl font-bold mb-6 text-emerald-400">Escolhe o Modo</h2>
|
'w-1/3 z-0 text-muted-foreground/40 bg-transparent': selectedMode !== '9'
|
||||||
|
}">
|
||||||
<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>Bisca de 9</span>
|
||||||
<span class="text-xs bg-purple-800 px-2 py-1 rounded">Épico</span>
|
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<button
|
<div class="flex flex-col items-center gap-5 w-[100vw]">
|
||||||
@click="showModeSelection = false"
|
<Card class="w-full max-w-2xl">
|
||||||
class="mt-6 text-gray-400 hover:text-white text-sm underline"
|
<CardHeader>
|
||||||
>
|
<CardTitle class="text-3xl font-bold text-center">
|
||||||
Voltar atrás
|
Single Player
|
||||||
</button>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const showModeSelection = 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 })
|
|
||||||
}
|
|
||||||
</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>
|
|
||||||
|
|||||||
Reference in New Issue
Block a user