feat: FRO-2 Implement Login Component (#8)
Reviewed-on: #8 Reviewed-by: lq64 <lq@blackhole.local> Co-authored-by: Janis <janis.e.20@gmx.de> Co-committed-by: Janis <janis.e.20@gmx.de>
This commit is contained in:
4
public/env.js
Normal file
4
public/env.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
window.__RUNTIME_CONFIG__ = {
|
||||||
|
API_URL: "http://localhost:9000"
|
||||||
|
};
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
globalThis.__RUNTIME_CONFIG__ = {
|
window.__RUNTIME_CONFIG__ = {
|
||||||
API_URL: "${API_URL}"
|
API_URL: "${API_URL}"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
19
src/composables/useUserInfo.ts
Normal file
19
src/composables/useUserInfo.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import {ref, type Ref} from 'vue'
|
||||||
|
|
||||||
|
export const useUserInfo = defineStore('userInfo', () => {
|
||||||
|
const username: Ref<string | null> = ref(null);
|
||||||
|
const userId: Ref<number | null> = ref(null);
|
||||||
|
|
||||||
|
function setUserInfo(name: string, id: number) {
|
||||||
|
username.value = name;
|
||||||
|
userId.value = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearUserInfo() {
|
||||||
|
username.value = null;
|
||||||
|
userId.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { username, userId, setUserInfo, clearUserInfo };
|
||||||
|
});
|
||||||
15
src/main.ts
15
src/main.ts
@@ -11,17 +11,18 @@ import 'quasar/dist/quasar.css'
|
|||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import VueAxios from 'vue-axios'
|
import VueAxios from 'vue-axios'
|
||||||
|
import {useUserInfo} from "@/composables/useUserInfo.ts";
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
app.use(pinia)
|
||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(Quasar, {
|
app.use(Quasar, {
|
||||||
plugins: {
|
plugins: {
|
||||||
Notify
|
Notify
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
app.use(pinia)
|
|
||||||
app.use(VueAxios, axios)
|
app.use(VueAxios, axios)
|
||||||
app.use(Particles, {
|
app.use(Particles, {
|
||||||
init: async engine => {
|
init: async engine => {
|
||||||
@@ -29,4 +30,16 @@ app.use(Particles, {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
res => res,
|
||||||
|
err => {
|
||||||
|
if (err.response?.status === 401) {
|
||||||
|
const info = useUserInfo();
|
||||||
|
info.clearUserInfo();
|
||||||
|
router.replace({name: 'login'});
|
||||||
|
}
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -1,49 +1,60 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
import LoginView from '../views/LoginView.vue'
|
import LoginView from '../views/LoginView.vue'
|
||||||
import MainMenuView from '../views/MainMenuView.vue'
|
import MainMenuView from '../views/MainMenuView.vue'
|
||||||
import createGameView from '../views/CreateGame.vue'
|
import createGameView from '../views/CreateGame.vue'
|
||||||
import joinGameView from "@/views/JoinGameView.vue";
|
import joinGameView from "@/views/JoinGameView.vue";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useUserInfo } from "@/composables/useUserInfo";
|
||||||
|
|
||||||
|
const api = window?.__RUNTIME_CONFIG__?.API_URL;
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'home',
|
name: 'mainmenu',
|
||||||
component: HomeView,
|
component: MainMenuView,
|
||||||
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
component: LoginView,
|
component: LoginView,
|
||||||
},
|
meta: { requiresAuth: false }
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
name: 'about',
|
|
||||||
// route level code-splitting
|
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
|
||||||
// which is lazy-loaded when the route is visited.
|
|
||||||
component: () => import('../views/AboutView.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/mainmenu',
|
|
||||||
name: 'mainmenu',
|
|
||||||
component: MainMenuView
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/create',
|
path: '/create',
|
||||||
name: 'create-Game',
|
name: 'create-Game',
|
||||||
component: createGameView
|
component: createGameView,
|
||||||
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/join',
|
path: '/join',
|
||||||
name: 'join-Game',
|
name: 'join-Game',
|
||||||
component: joinGameView
|
component: joinGameView,
|
||||||
},
|
meta: { requiresAuth: true }
|
||||||
|
}
|
||||||
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
const info = useUserInfo();
|
||||||
|
if (!to.meta.requiresAuth) return next();
|
||||||
|
try {
|
||||||
|
await axios.get(`${api}/userInfo`, { withCredentials: true }).then(
|
||||||
|
res => {
|
||||||
|
info.setUserInfo(res.data.username, res.data.userId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
next();
|
||||||
|
} catch (err) {
|
||||||
|
info.clearUserInfo();
|
||||||
|
next('/login');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import {type credentials, type token, type user} from '@/types/authTypes'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
import {ref, computed, type Ref} from 'vue'
|
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
|
||||||
const user: Ref<user | null> = ref(null)
|
|
||||||
const token = ref(localStorage.getItem('token') || null)
|
|
||||||
|
|
||||||
const isAuthenticated = computed(() => !!token.value)
|
|
||||||
|
|
||||||
async function login(credentials: credentials) {
|
|
||||||
const response = await fakeLoginApi(credentials)
|
|
||||||
|
|
||||||
token.value = response.token
|
|
||||||
user.value = response.user
|
|
||||||
|
|
||||||
localStorage.setItem('token', token.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
function setToken(newToken: string) {
|
|
||||||
token.value = newToken
|
|
||||||
localStorage.setItem('token', newToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout() {
|
|
||||||
token.value = null
|
|
||||||
user.value = null
|
|
||||||
localStorage.removeItem('token')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchUser() {
|
|
||||||
if (!token.value) return
|
|
||||||
|
|
||||||
const response = await fakeFetchUserApi(token.value)
|
|
||||||
user.value = response.user
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
token,
|
|
||||||
isAuthenticated,
|
|
||||||
login,
|
|
||||||
setToken,
|
|
||||||
logout,
|
|
||||||
fetchUser,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function fakeLoginApi(credentials: credentials): Promise<token> {
|
|
||||||
return {
|
|
||||||
token: 'abc123',
|
|
||||||
user: { id: 1, name: 'John Doe' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fakeFetchUserApi(token: string): Promise<{ user: user }> {
|
|
||||||
return {
|
|
||||||
user: { id: 1, name: 'John Doe' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,3 +45,5 @@ type WonInfo = {
|
|||||||
winner: PodiumPlayer | null
|
winner: PodiumPlayer | null
|
||||||
allPlayers: PodiumPlayer[]
|
allPlayers: PodiumPlayer[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type { GameInfo, LobbyInfo, TieInfo, TrumpInfo, WonInfo }
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
type token = {
|
|
||||||
token: string
|
|
||||||
user: user
|
|
||||||
}
|
|
||||||
type user = {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
type credentials = {
|
|
||||||
username: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type { token, user, credentials }
|
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
filled
|
filled
|
||||||
dark
|
dark
|
||||||
|
type="password"
|
||||||
label-color="white"
|
label-color="white"
|
||||||
color="white"
|
color="white"
|
||||||
v-model="password"
|
v-model="password"
|
||||||
@@ -60,8 +61,8 @@
|
|||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {useAuthStore} from "@/stores/auth.ts";
|
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
|
import {useUserInfo} from "@/composables/useUserInfo.ts";
|
||||||
|
|
||||||
const api = window?.__RUNTIME_CONFIG__?.API_URL;
|
const api = window?.__RUNTIME_CONFIG__?.API_URL;
|
||||||
|
|
||||||
@@ -71,14 +72,14 @@ const username = ref(null)
|
|||||||
const password = ref(null)
|
const password = ref(null)
|
||||||
const inProgress = ref(false)
|
const inProgress = ref(false)
|
||||||
const loginError = ref('')
|
const loginError = ref('')
|
||||||
|
const uInfo = useUserInfo()
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
if (inProgress.value) return
|
if (inProgress.value) return
|
||||||
inProgress.value = true
|
inProgress.value = true
|
||||||
loginError.value = ''
|
loginError.value = ''
|
||||||
axios.post(`${api}/login`, {username: username.value, password: password.value}).then(response => {
|
axios.post(`${api}/login`, {username: username.value, password: password.value}, {withCredentials: true}).then((response) => {
|
||||||
const auth = useAuthStore()
|
uInfo.setUserInfo(response.data.user.username, response.data.user.id)
|
||||||
auth.setToken(response.data.token)
|
|
||||||
router.push("/")
|
router.push("/")
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
loginError.value = 'Invalid username or password'
|
loginError.value = 'Invalid username or password'
|
||||||
@@ -107,11 +108,6 @@ const options = {
|
|||||||
},
|
},
|
||||||
"polygon": {
|
"polygon": {
|
||||||
"sides": 5
|
"sides": 5
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"src": "img/github.svg",
|
|
||||||
"width": 100,
|
|
||||||
"height": 100
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"opacity": {
|
"opacity": {
|
||||||
|
|||||||
Reference in New Issue
Block a user