feat: FRO-2 Implement Login Component

This commit is contained in:
2025-12-10 11:25:57 +01:00
parent f47b757398
commit d6270e6f47
8 changed files with 68 additions and 108 deletions

4
public/env.js Normal file
View File

@@ -0,0 +1,4 @@
window.__RUNTIME_CONFIG__ = {
API_URL: "http://localhost:9000"
};

View File

@@ -1,4 +1,4 @@
globalThis.__RUNTIME_CONFIG__ = { window.__RUNTIME_CONFIG__ = {
API_URL: "${API_URL}" API_URL: "${API_URL}"
}; };

View 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 };
});

View File

@@ -15,13 +15,13 @@ import VueAxios from 'vue-axios'
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 +29,14 @@ app.use(Particles, {
}, },
}) })
axios.interceptors.response.use(
res => res,
err => {
if (err.response?.status === 401) {
router.replace({name: 'login'});
}
return Promise.reject(err);
}
);
app.mount('#app') app.mount('#app')

View File

@@ -1,49 +1,59 @@
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) {
next('/login');
}
});
export default router export default router

View File

@@ -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' }
}
}

View File

@@ -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 }

View File

@@ -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,7 +61,6 @@
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";
const api = window?.__RUNTIME_CONFIG__?.API_URL; const api = window?.__RUNTIME_CONFIG__?.API_URL;
@@ -76,9 +76,7 @@ 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()
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 +105,6 @@ const options = {
}, },
"polygon": { "polygon": {
"sides": 5 "sides": 5
},
"image": {
"src": "img/github.svg",
"width": 100,
"height": 100
} }
}, },
"opacity": { "opacity": {