Version 1.0 #102

Merged
FernandoJVideira merged 163 commits from develop into master 2026-01-03 14:49:00 +00:00
Showing only changes of commit 432d3c0b88 - Show all commits
+109 -40
View File
@@ -23,6 +23,11 @@ const gObserverTarget = ref(null);
const gObserver = ref(null); const gObserver = ref(null);
const gLoading = ref(false); const gLoading = ref(false);
const gPage = ref(1); const gPage = ref(1);
const openMatches = ref([])
const mObserverTarget = ref(null);
const mObserver = ref(null);
const mLoading = ref(false);
const mPage = ref(1);
const showHostModal = ref(false) const showHostModal = ref(false)
const showJoinModal = ref(false) const showJoinModal = ref(false)
const selectedGame = ref(null) const selectedGame = ref(null)
@@ -38,6 +43,21 @@ const lbObserverTarget = ref(null)
const lbObserver = ref(null) const lbObserver = ref(null)
const lbMorePages = ref(true) const lbMorePages = ref(true)
const publicStats = ref(null) const publicStats = ref(null)
const isLoggedIn = useAuthStore().isLoggedIn
const setupMatchesObserver = () => {
if (mObserver.value) mObserver.value.disconnect();
mObserver.value = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !mLoading.value && openMatches.value.length > 0) {
getPendingMatches();
}
}, { threshold: 0.1 });
if (mObserverTarget.value) {
mObserver.value.observe(mObserverTarget.value);
}
}
const setupGamesObserver = () => { const setupGamesObserver = () => {
if (gObserver.value) gObserver.value.disconnect(); if (gObserver.value) gObserver.value.disconnect();
@@ -67,14 +87,30 @@ const getPendingGames = async (mode = selectedMode.value) => {
const newGames = response.data.data; const newGames = response.data.data;
openGames.value = [...openGames.value, ...newGames]; openGames.value = [...openGames.value, ...newGames];
gPage.value++;
} catch (error) {
console.error('Failed to fetch games:', error);
} finally { } finally {
gLoading.value = false; gLoading.value = false;
} }
} }
const getPendingMatches = async (mode = selectedMode.value) => {
if (mLoading.value) return;
mLoading.value = true;
try {
const response = await axios.get(`${API_BASE_URL}/matches/open`, {
params: {
type: mode,
page: mPage.value
}
});
const newMatches = response.data.data;
openMatches.value = [...openMatches.value, ...newMatches];
} finally {
mLoading.value = false;
}
}
const fetchPublicStats = async () => { const fetchPublicStats = async () => {
try { try {
const response = await apiStore.getPublicStats() const response = await apiStore.getPublicStats()
@@ -145,36 +181,32 @@ const setupLeaderboardObserver = () => {
const clickMode = async (mode) => { const clickMode = async (mode) => {
if (selectedMode.value === mode) return; if (selectedMode.value === mode) return;
openGames.value = [];
selectedMode.value = mode; selectedMode.value = mode;
openGames.value = [];
gPage.value = 1; gPage.value = 1;
await getPendingGames(); openMatches.value = [];
mPage.value = 1;
await Promise.all([getPendingGames(), getPendingMatches()]);
setupGamesObserver(); setupGamesObserver();
setupMatchesObserver();
} }
const startSingle = () => { const startSingle = () => {
router.push({ name: 'bisca' + selectedMode.value }); 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 = () => { const toggleReady = () => {
isReady.value = !isReady.value isReady.value = !isReady.value
} }
onMounted(async () => { onMounted(async () => {
await fetchPublicStats(); await fetchPublicStats();
await getPendingGames(); if (isLoggedIn) {
await Promise.all([getPendingGames(), getPendingMatches()]);
await nextTick(); await nextTick();
setupGamesObserver(); setupGamesObserver();
setupMatchesObserver();
}
}) })
const hostGame = async () => { const hostGame = async () => {
@@ -227,7 +259,7 @@ const joinGame = async (game) => {
<template> <template>
<div class="min-h-screen bg-cover bg-center transition-all duration-700"> <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="flex flex-col justify-center items-center gap-5 pt-10 pb-10 px-4">
<Card v-if="publicStats" class="w-full max-w-2xl border-[#a855f7] bg-[#f8f0ff] shadow-sm"> <Card v-if="publicStats" class="w-full max-w-2xl border-[#a855f7] bg-[#f8f0ff] shadow-sm">
<CardContent class="p-4"> <CardContent class="p-4">
@@ -293,50 +325,87 @@ const joinGame = async (game) => {
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
<Card class="w-full max-w-2xl"> <Card class="w-full max-w-2xl flex flex-col">
<CardHeader> <CardHeader>
<CardTitle class="text-3xl font-bold text-center"> <CardTitle class="text-3xl font-bold text-center">Multi Player</CardTitle>
Multi Player <CardDescription class="text-center">Go one on one with another player!</CardDescription>
</CardTitle>
<CardDescription class="text-center">
Go one on one with another player!
</CardDescription>
</CardHeader> </CardHeader>
<CardContent class="space-y-6">
<label class="text-sm font-medium">Open games (oldest first)</label> <CardContent class="flex-1 flex flex-col space-y-6">
<div class="flex flex-col flex-1">
<div class="flex justify-between items-end mb-2">
<label class="text-sm font-bold uppercase tracking-wider text-muted-foreground">Open
Matches</label>
<Button v-if="isLoggedIn" @click="showHostModal()" size="sm" variant="outline"
class="border-purple-500 text-purple-500 hover:bg-purple-500 hover:text-white transition-all duration-300 font-bold">
Host
</Button>
</div>
<div class="rounded-lg border bg-card text-card-foreground shadow-sm"> <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"> <div v-if="openGames.length === 0"
class="p-6 text-center text-sm text-muted-foreground">
No open macthes yet. Try hosting one!
</div>
<div v-else class="max-h-[350px] overflow-y-auto custom-scrollbar p-1">
<div v-for="(match, index) in openMatches" :key="match.id"
class="flex items-center justify-between p-3 mb-2 border border-transparent rounded-lg cursor-pointer 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-red-100 text-red-700">
{{ index + 1 }}
</div>
<div>
<div class="font-medium text-sm">Match ID: {{ match.id }}</div>
<div class="text-[10px] text-muted-foreground">Type: {{ match.type }}
</div>
</div>
</div>
</div>
<div ref="mObserverTarget" class="h-10 flex items-center justify-center">
<span v-if="mLoading" class="text-xs text-purple-500 animate-pulse">Loading
matches...</span>
</div>
</div>
</div>
</div>
<div class="flex flex-col flex-1">
<div class="flex justify-between items-end mb-2">
<label class="text-sm font-medium uppercase tracking-wider text-muted-foreground">Open
Games</label>
<Button v-if="isLoggedIn" @click="hostGame()" size="sm" variant="outline"
class="border-purple-500 text-purple-500 hover:bg-purple-500 hover:text-white transition-all duration-300 font-bold">
Host
</Button>
</div>
<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! No open games yet. Try hosting one!
</div> </div>
<div v-else class="max-h-[350px] overflow-y-auto custom-scrollbar p-1"> <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="joinGame(game)" <div v-for="(game, index) in openGames" :key="game.id" @click="joinGame(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"> class="flex items-center justify-between p-3 mb-2 border border-transparent rounded-lg cursor-pointer transition-all hover:bg-muted/50 hover:border-purple-500">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div <div
class="flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold bg-green-100 text-green-700"> class="flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold bg-green-100 text-green-700">
{{ index + 1 }} {{ index + 1 }}
</div> </div>
<div> <div>
<div class="font-medium text-sm">Started: {{ game.began_at }}</div> <div class="font-medium text-sm">Host: {{ game.player1?.nickname ||
<div class="text-xs text-muted-foreground">host: {{ 'Anonymous' }}</div>
game.player1?.nickname || 'Anonymous' }}</div> <div class="text-[10px] text-muted-foreground">{{ game.began_at }}</div>
</div> </div>
</div> </div>
</div> </div>
<div ref="gObserverTarget" class="h-10 flex items-center justify-center"> <div ref="gObserverTarget" class="h-10 flex items-center justify-center">
<span v-if="gLoading" class="text-xs text-purple-500 animate-pulse">Loading <span v-if="gLoading" class="text-xs text-purple-500 animate-pulse">Loading
more...</span> games...</span>
</div> </div>
</div> </div>
</div> </div>
<div class="flex justify-center">
<Button @click="hostGame()" size="lg" variant="secondary"
class="hover:bg-purple-500 hover:text-slate-200">
Host Game
</Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>