Version 1.0 #102
@@ -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,95 @@ 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">{{ new
|
||||||
|
Date(game.began_at).toLocaleDateString() }} • {{ new
|
||||||
|
Date(game.began_at).toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit' }) }}
|
||||||
|
</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">{{ new
|
||||||
|
Date(game.began_at).toLocaleDateString() }} • {{ new
|
||||||
|
Date(game.began_at).toLocaleTimeString([], {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit' }) }}</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>
|
||||||
|
|||||||
Reference in New Issue
Block a user