Merge remote-tracking branch 'origin/develop' into feature/profile-page
This commit is contained in:
Generated
+417
-469
File diff suppressed because it is too large
Load Diff
@@ -1,55 +1,55 @@
|
||||
<template>
|
||||
<div class="flex min-h-screen items-center justify-center 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>
|
||||
<div class="flex min-h-screen items-center justify-center 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]" :disabled="isLoading" />
|
||||
</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="••••••••" :disabled="isLoading" />
|
||||
</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>
|
||||
<Button type="submit" class="w-full" :disabled="isLoading">
|
||||
{{ isLoading ? 'Signing in...' : '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'
|
||||
@@ -58,27 +58,21 @@ const authStore = useAuthStore()
|
||||
const router = useRouter()
|
||||
|
||||
const formData = ref({
|
||||
email: 'pa@mail.pt',
|
||||
password: '123'
|
||||
email: 'pa@mail.pt',
|
||||
password: '123'
|
||||
})
|
||||
|
||||
const isLoading = ref(false)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
const user = await authStore.login(formData.value)
|
||||
|
||||
toast.success(`Login Successful - Welcome ${user?.name}!`)
|
||||
|
||||
setTimeout(() => {
|
||||
router.push('/')
|
||||
}, 500)
|
||||
} catch (error) {
|
||||
toast.error(`Login Failed - ${error?.response?.data?.message || error.message}`)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
const promise = authStore.login(formData.value)
|
||||
toast.promise(promise, {
|
||||
loading: 'Calling API',
|
||||
success: (user) => {
|
||||
router.push('/')
|
||||
return `Login Successful - ${user.name}`
|
||||
},
|
||||
error: (error) =>
|
||||
error.response?.data?.message
|
||||
})
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/stores/api.js
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from 'axios'
|
||||
import { inject, ref } from 'vue'
|
||||
@@ -17,15 +16,16 @@ export const useAPIStore = defineStore('api', () => {
|
||||
},
|
||||
})
|
||||
|
||||
// AUTH
|
||||
const postLogin = async (credentials) => {
|
||||
const response = await axios.post(`${API_BASE_URL}/login`, credentials)
|
||||
token.value = response.data.token
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
|
||||
const response = await axios.post(`${API_BASE_URL}/login`, credentials)
|
||||
|
||||
return response
|
||||
}
|
||||
if (!response.data.token) throw response
|
||||
|
||||
token.value = response.data.token
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
|
||||
|
||||
return response
|
||||
}
|
||||
const postLogout = async () => {
|
||||
await axios.post(`${API_BASE_URL}/logout`)
|
||||
token.value = undefined
|
||||
@@ -57,19 +57,11 @@ export const useAPIStore = defineStore('api', () => {
|
||||
return axios.get(`${API_BASE_URL}/games?${queryParams}`)
|
||||
}
|
||||
|
||||
const initToken = (storedToken) => {
|
||||
if (storedToken) {
|
||||
token.value = storedToken
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${storedToken}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
postLogin,
|
||||
postLogout,
|
||||
getAuthUser,
|
||||
getGames,
|
||||
gameQueryParameters,
|
||||
initToken,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// src/stores/auth.js
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useAPIStore } from './api'
|
||||
import axios from 'axios'
|
||||
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
const apiStore = useAPIStore()
|
||||
@@ -22,10 +20,8 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
token.value = jwtToken
|
||||
if (jwtToken) {
|
||||
localStorage.setItem('token', jwtToken)
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${jwtToken}`
|
||||
} else {
|
||||
localStorage.removeItem('token')
|
||||
delete axios.defaults.headers.common['Authorization']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,57 +30,18 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
}
|
||||
|
||||
const login = async (credentials) => {
|
||||
try {
|
||||
// postLogin already sets the token and axios headers, and returns the response
|
||||
const loginResponse = await apiStore.postLogin(credentials)
|
||||
|
||||
// Extract the token from the response
|
||||
const jwtToken = loginResponse.data.token
|
||||
|
||||
// Store token in auth store (for localStorage)
|
||||
const loginResp = await apiStore.postLogin(credentials)
|
||||
const userResp = await apiStore.getAuthUser()
|
||||
const jwtToken = loginResp.data.token
|
||||
setToken(jwtToken)
|
||||
|
||||
// Use the user data from login response (it's already there!)
|
||||
currentUser.value = loginResponse.data.user
|
||||
|
||||
return currentUser.value
|
||||
} catch (error) {
|
||||
// Clear everything on login failure
|
||||
setToken(null)
|
||||
currentUser.value = undefined
|
||||
throw error
|
||||
}
|
||||
currentUser.value = userResp.data
|
||||
return userResp.data
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
try {
|
||||
await apiStore.postLogout()
|
||||
} catch (error) {
|
||||
console.error('Logout API call failed:', error)
|
||||
} finally {
|
||||
currentUser.value = undefined
|
||||
setToken(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize auth from localStorage on app start
|
||||
const initAuth = async () => {
|
||||
if (token.value) {
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
|
||||
apiStore.initToken(token.value)
|
||||
|
||||
// Try to fetch current user if we don't have it
|
||||
if (!currentUser.value) {
|
||||
try {
|
||||
const userResponse = await apiStore.getAuthUser()
|
||||
currentUser.value = userResponse.data
|
||||
} catch (error) {
|
||||
console.error('Failed to restore user session:', error)
|
||||
// Clear invalid token
|
||||
setToken(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
await apiStore.postLogout()
|
||||
currentUser.value = undefined
|
||||
setToken(null)
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -96,6 +53,5 @@ export const useAuthStore = defineStore('auth', () => {
|
||||
getToken,
|
||||
login,
|
||||
logout,
|
||||
initAuth,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,15 +5,20 @@ export const useSocketStore = defineStore('socket', () => {
|
||||
const socket = inject('socket')
|
||||
|
||||
const handleConnection = () => {
|
||||
socket.on('connect', () => {
|
||||
console.log(`[Socket] Connected -- ${socket.id}`)
|
||||
})
|
||||
socket.on('disconnect', () => {
|
||||
console.log(`[Socket] Disconnected -- ${socket.id}`)
|
||||
})
|
||||
try {
|
||||
socket.on('connect', () => {
|
||||
console.log(`[Socket] Connected -- ${socket.id}`)
|
||||
})
|
||||
socket.on('disconnect', () => {
|
||||
console.log(`[Socket] Disconnected -- ${socket.id}`)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('[Socket] Connection error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
handleConnection,
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user