Added base project
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>Game Board</h1>
|
||||
<p>Game board page content goes here</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>Game Selection</h1>
|
||||
<p>Game selection page content goes here</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,65 +0,0 @@
|
||||
<script setup>
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900 to-slate-900 flex items-center justify-center px-4">
|
||||
<div class="max-w-4xl w-full">
|
||||
<div class="text-center mb-16">
|
||||
<div class="mb-6">
|
||||
<div class="text-7xl mb-4">🎰</div>
|
||||
<h1 class="text-6xl font-bold text-white mb-2">Bisca</h1>
|
||||
<p class="text-xl text-purple-200">The Traditional Portuguese Card Game Online</p>
|
||||
</div>
|
||||
<p class="text-lg text-slate-300 max-w-2xl mx-auto">
|
||||
Challenge players from around the world in this classic card game. Master the rules, build your strategy, and climb the leaderboards.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-12 max-w-2xl mx-auto">
|
||||
<div class="group relative bg-gradient-to-br from-blue-500 to-blue-600 rounded-2xl p-8 hover:shadow-2xl cursor-pointer overflow-hidden"
|
||||
@click="router.push('/game-selection')">
|
||||
<div class="absolute inset-0 bg-white opacity-0 group-hover:opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="text-5xl mb-4">🎮</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-2">Play Game</h2>
|
||||
<p class="text-blue-100 text-sm">Start a new match and test your skills</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group relative bg-gradient-to-br from-amber-500 to-amber-600 rounded-2xl p-8 hover:shadow-2xl cursor-pointer overflow-hidden"
|
||||
@click="router.push('/leader-boards')">
|
||||
<div class="absolute inset-0 bg-white opacity-0 group-hover:opacity-10"></div>
|
||||
<div class="relative z-10">
|
||||
<div class="text-5xl mb-4">🏆</div>
|
||||
<h2 class="text-2xl font-bold text-white mb-2">Leaderboards</h2>
|
||||
<p class="text-amber-100 text-sm">Compete globally and see the rankings</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<button
|
||||
@click="router.push('/login')"
|
||||
class="px-8 py-3 bg-white text-slate-900 font-bold rounded-lg hover:bg-slate-100 shadow-lg"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
<button
|
||||
@click="router.push('/register')"
|
||||
class="px-8 py-3 border-2 border-white text-white font-bold rounded-lg hover:bg-white hover:text-slate-900"
|
||||
>
|
||||
Create Account
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-16 pt-8 border-t border-slate-700 text-center text-slate-400 text-sm">
|
||||
<p>Play anonymously or sign in to compete with others and earn rewards</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>LeaderBoards</h1>
|
||||
<p>LeaderBoards page content goes here</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>Profile</h1>
|
||||
<p>Login page content goes here</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>Profile</h1>
|
||||
<p>Profile page content goes here</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -1,8 +0,0 @@
|
||||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<h1>Profile</h1>
|
||||
<p>Register page content goes here</p>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped></style>`
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div class="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
|
||||
<div class="w-full max-w-md space-y-8">
|
||||
<div>
|
||||
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-gray-900">
|
||||
Sign in to your account
|
||||
</h2>
|
||||
<p class="mt-2 text-center text-sm text-gray-600">
|
||||
Enter your credentials to access your account
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form class="mt-8 space-y-6" @submit.prevent="handleSubmit">
|
||||
<div class="space-y-4 rounded-md shadow-sm">
|
||||
<div>
|
||||
<label for="email" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email address
|
||||
</label>
|
||||
<Input id="email" v-model="formData.email" type="email" autocomplete="email" required
|
||||
placeholder="[email protected]" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-gray-700 mb-1">
|
||||
Password
|
||||
</label>
|
||||
<Input id="password" v-model="formData.password" type="password" autocomplete="current-password"
|
||||
required placeholder="••••••••" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<Button type="submit" class="w-full"> Sign in </Button>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-sm">
|
||||
<span class="text-gray-600">Don't have an account? </span>
|
||||
<a href="#" class="font-medium text-blue-600 hover:text-blue-500">
|
||||
Sign up
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
const formData = ref({
|
||||
email: 'pa@mail.pt',
|
||||
password: '123'
|
||||
})
|
||||
|
||||
|
||||
const handleSubmit = async () => {
|
||||
|
||||
toast.promise(authStore.login(formData.value), {
|
||||
loading: 'Calling API',
|
||||
success: (data) => {
|
||||
return `Login Sucessfull - ${data?.name}`
|
||||
},
|
||||
error: (data) => `[API] Error saving game - ${data?.response?.data?.message}`,
|
||||
})
|
||||
|
||||
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="max-w-2xl mx-auto py-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">Laravel Tester</h2>
|
||||
|
||||
<div class="space-y-4 mt-10">
|
||||
<div class="flex gap-4">
|
||||
<Select v-model="selectedType" @update:modelValue="handleFiltersChange">
|
||||
<SelectTrigger class="w-[180px]">
|
||||
<SelectValue placeholder="Select type" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="3">Bisca 3</SelectItem>
|
||||
<SelectItem value="9">Bisca 9</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<Select v-model="selectedStatus" @update:modelValue="handleFiltersChange">
|
||||
<SelectTrigger class="w-[180px]">
|
||||
<SelectValue placeholder="Select status" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="Pending">Pending</SelectItem>
|
||||
<SelectItem value="Playing">Playing</SelectItem>
|
||||
<SelectItem value="Ended">Ended</SelectItem>
|
||||
<SelectItem value="Interrupted">Interrupted</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>
|
||||
<div class="flex items-center gap-2 cursor-pointer" @click="toggleSort('type')">
|
||||
Type
|
||||
<ArrowUpDown class="h-4 w-4" />
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<div class="flex items-center gap-2 cursor-pointer" @click="toggleSort('status')">
|
||||
Status
|
||||
<ArrowUpDown class="h-4 w-4" />
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead>Created By</TableHead>
|
||||
<TableHead>Winner</TableHead>
|
||||
<TableHead>
|
||||
<div class="flex items-center gap-2 cursor-pointer" @click="toggleSort('total_time')">
|
||||
Total Time
|
||||
<ArrowUpDown class="h-4 w-4" />
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableHead>
|
||||
<div class="flex items-center gap-2 cursor-pointer" @click="toggleSort('created_at')">
|
||||
Began At
|
||||
<ArrowUpDown class="h-4 w-4" />
|
||||
</div>
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="game in games" :key="game.id">
|
||||
<TableCell>{{ game.id }}</TableCell>
|
||||
<TableCell>
|
||||
<Badge :variant="game.type === '3' ? 'default' : 'secondary'">
|
||||
{{ game.type === '3' ? 'Bisca 3' : 'Bisca 9' }}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge :variant="getStatusVariant(game.status)">
|
||||
{{ game.status }}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{{ game.created_by?.name }}</TableCell>
|
||||
<TableCell>{{ game.winner?.name || '-' }}</TableCell>
|
||||
<TableCell>{{ game.total_time ? `${game.total_time}s` : '-' }}</TableCell>
|
||||
<TableCell>{{ formatDate(game.began_at) }}</TableCell>
|
||||
</TableRow>
|
||||
<TableRow v-if="!games?.length">
|
||||
<TableCell colspan="7" class="text-center h-24">No games found</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mb-20">
|
||||
<Button variant="outline" @click="loadMore" :disabled="loading">
|
||||
<Loader2 v-if="loading" class="mr-2 h-4 w-4 animate-spin" />
|
||||
Load More
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from '@/components/ui/table'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from '@/components/ui/select'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { ArrowUpDown, Loader2 } from 'lucide-vue-next'
|
||||
import { useAPIStore } from '@/stores/api';
|
||||
|
||||
|
||||
const apiStore = useAPIStore()
|
||||
const games = ref([])
|
||||
|
||||
const loading = ref(false)
|
||||
const selectedType = ref('')
|
||||
const selectedStatus = ref('')
|
||||
const sortField = ref('began_at')
|
||||
const sortDirection = ref('desc')
|
||||
|
||||
|
||||
const fetchData = async (resetPagination = false) => {
|
||||
loading.value = true
|
||||
|
||||
apiStore.gameQueryParameters.filters = {
|
||||
type: selectedType.value,
|
||||
status: selectedStatus.value,
|
||||
sort_by: sortField.value,
|
||||
sort_direction: sortDirection.value
|
||||
}
|
||||
|
||||
const response = await apiStore.getGames(resetPagination)
|
||||
loading.value = false
|
||||
games.value = response.data.data
|
||||
}
|
||||
|
||||
const handleFiltersChange = async () => {
|
||||
await fetchData(true)
|
||||
}
|
||||
|
||||
const toggleSort = (field) => {
|
||||
if (sortField.value === field) {
|
||||
sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc'
|
||||
} else {
|
||||
sortField.value = field
|
||||
sortDirection.value = 'desc'
|
||||
}
|
||||
handleFiltersChange()
|
||||
}
|
||||
|
||||
const loadMore = async () => {
|
||||
loading.value = true
|
||||
apiStore.gameQueryParameters.page++
|
||||
const response = await apiStore.getGames()
|
||||
games.value.push(...response.data.data)
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
const getStatusVariant = (status) => {
|
||||
const variants = {
|
||||
Pending: 'secondary',
|
||||
Playing: 'default',
|
||||
Ended: 'success',
|
||||
Interrupted: 'destructive'
|
||||
}
|
||||
return variants[status] || 'default'
|
||||
}
|
||||
|
||||
|
||||
|
||||
const formatDate = (date) => {
|
||||
if (!date) return ''
|
||||
return format(new Date(date), 'PPp')
|
||||
}
|
||||
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchData()
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="max-w-2xl mx-auto py-8">
|
||||
<h2 class="text-2xl font-bold text-gray-900 mb-6">WebSocket Tester</h2>
|
||||
|
||||
<div class="mb-6 flex items-center space-x-2">
|
||||
<div class="flex items-center">
|
||||
<div class="h-2.5 w-2.5 rounded-full mr-2 animate-pulse"
|
||||
:class="{ 'bg-green-500': socket.connected, 'bg-red-500': !socket.connected, }"></div>
|
||||
<span class="text-sm text-gray-600"> {{ (!socket.connected) ? 'Not' : '' }} Connected</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="space-y-6">
|
||||
<div class="space-y-2">
|
||||
<label for="message" class="block text-sm font-medium text-gray-700">
|
||||
Message:
|
||||
</label>
|
||||
<div class="flex space-x-4">
|
||||
<input type="text" id="message" v-model="message"
|
||||
class="flex-1 px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
||||
placeholder="Type your message here...">
|
||||
<Button @click.prevent="send" type="submit">Send </Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="responseData" class="space-y-2 mt-8">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label for="response" class="block text-sm font-medium text-gray-700">
|
||||
Response
|
||||
</label>
|
||||
<span class="text-xs text-gray-500">Real-time updates</span>
|
||||
</div>
|
||||
<textarea :value="responseData" id="response" rows="5"
|
||||
class="mt-1 block w-full px-3 py-2 bg-gray-50 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm font-mono"
|
||||
readonly></textarea>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, inject } from 'vue'
|
||||
import Button from '@/components/ui/button/Button.vue';
|
||||
|
||||
const socket = inject('socket')
|
||||
|
||||
const message = ref('DAD Intermediate Submission')
|
||||
const responseData = ref('')
|
||||
|
||||
const send = () => {
|
||||
socket.emit('echo', message.value)
|
||||
}
|
||||
|
||||
socket.on('echo', (message) => {
|
||||
responseData.value = message
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user