continuer cleaner page admin
This commit is contained in:
parent
5fd32ef333
commit
4a26259891
@ -1,14 +0,0 @@
|
|||||||
/* eslint-env node */
|
|
||||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended',
|
|
||||||
'@vue/eslint-config-prettier/skip-formatting'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
eslint.config.cjs
Normal file
20
eslint.config.cjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
// require('@rushstack/eslint-patch/modern-module-resolution')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: [
|
||||||
|
'plugin:vue/vue3-essential',
|
||||||
|
'eslint:recommended',
|
||||||
|
'@vue/eslint-config-prettier/skip-formatting',
|
||||||
|
'plugin:@typescript-eslint/recommended'
|
||||||
|
],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
parser: '@typescript-eslint/parser'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint/eslint-plugin'
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
7523
package-lock.json
generated
7523
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
17
package.json
17
package.json
@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build",
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest",
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
|
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
|
||||||
@ -15,26 +15,33 @@
|
|||||||
"@tailwindcss/container-queries": "^0.1.1",
|
"@tailwindcss/container-queries": "^0.1.1",
|
||||||
"@tailwindcss/forms": "^0.5.6",
|
"@tailwindcss/forms": "^0.5.6",
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
|
"lucide-vue-next": "^0.479.0",
|
||||||
"mqtt": "^5.3.3",
|
"mqtt": "^5.3.3",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rushstack/eslint-patch": "^1.3.3",
|
"@rushstack/eslint-patch": "^1.10.5",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
||||||
|
"@typescript-eslint/parser": "^8.24.1",
|
||||||
"@vitejs/plugin-vue": "^4.3.4",
|
"@vitejs/plugin-vue": "^4.3.4",
|
||||||
"@vue/eslint-config-prettier": "^8.0.0",
|
"@vue/eslint-config-prettier": "^8.0.0",
|
||||||
|
"@vue/eslint-config-standard": "^9.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^14.4.0",
|
||||||
"@vue/test-utils": "^2.4.1",
|
"@vue/test-utils": "^2.4.1",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^9.20.1",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.32.0",
|
||||||
"jsdom": "^22.1.0",
|
"jsdom": "^22.1.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"sass": "^1.68.0",
|
"sass": "^1.68.0",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
"vite": "^4.4.9",
|
"vite": "^4.4.9",
|
||||||
"vitest": "^0.34.4"
|
"vitest": "^0.34.4",
|
||||||
|
"vue-tsc": "^2.2.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,19 +29,6 @@ a {
|
|||||||
/*}*/
|
/*}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
body {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
padding: 0 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Example CSS for headings with additional styles */
|
/* Example CSS for headings with additional styles */
|
||||||
h1 {
|
h1 {
|
||||||
|
|||||||
9
src/dtos/auth.dto.ts
Normal file
9
src/dtos/auth.dto.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export class AuthDto {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
public constructor(form: FormData) {
|
||||||
|
this.username = form.get("username") as string;
|
||||||
|
this.password = form.get("password") as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/interfaces/auth.ts
Normal file
6
src/interfaces/auth.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface AuthResponse {
|
||||||
|
id: number;
|
||||||
|
roles: string[];
|
||||||
|
token: string;
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
7
src/interfaces/game.ts
Normal file
7
src/interfaces/game.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface Game {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
thumbnail: any;
|
||||||
|
active: boolean
|
||||||
|
}
|
||||||
@ -1,113 +0,0 @@
|
|||||||
import {createRouter, createWebHistory} from 'vue-router'
|
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
import {useAuthStore} from '@/stores/auth';
|
|
||||||
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
name: 'home',
|
|
||||||
component: HomeView,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
name: 'member',
|
|
||||||
component: () => import('../views/Member.vue'),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
name: 'dashboard',
|
|
||||||
component: () => import('../components/TheWelcome.vue'),
|
|
||||||
meta: {requiresAuth: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/upload',
|
|
||||||
name: 'upload',
|
|
||||||
component: () => import('../views/games/UploadView.vue'),
|
|
||||||
meta: {requiresAuth: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/events',
|
|
||||||
name: 'events',
|
|
||||||
component: () => import('../views/mqtt/Events.vue'),
|
|
||||||
meta: {requiresAuth: true}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/games/:gameId',
|
|
||||||
name: 'game',
|
|
||||||
component: () => import('../views/games/GameView.vue'),
|
|
||||||
meta: {requiresAuth: true,},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/games',
|
|
||||||
name: 'games',
|
|
||||||
component: () => import('../views/games/GamesView.vue'),
|
|
||||||
meta: {requiresAuth: true,}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/games',
|
|
||||||
name: 'games',
|
|
||||||
component: () => import('../views/games/GamesView.vue'),
|
|
||||||
meta: {requiresAuth: true,}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/login',
|
|
||||||
name: 'login',
|
|
||||||
component: () => import('../views/auths/Login.vue'),
|
|
||||||
meta: {requiresAuth: false,}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/sign-up',
|
|
||||||
name: 'sign-up',
|
|
||||||
component: () => import('../views/auths/SignUp.vue'),
|
|
||||||
meta: {requiresAuth: false,}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
name: 'limited',
|
|
||||||
component: () => import('../views/Limited.vue'),
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/close',
|
|
||||||
name: 'close',
|
|
||||||
component: () => import('../views/players/Close.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/action',
|
|
||||||
name: 'action',
|
|
||||||
component: () => import('../views/players/QrAction.vue'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/:catchAll(.*)',
|
|
||||||
name: 'shit',
|
|
||||||
component: () => import('../views/NotFound.vue'),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
if (to.meta.requiresAuth === undefined)
|
|
||||||
next();
|
|
||||||
|
|
||||||
if (useAuthStore().isAuth() === to.meta.requiresAuth) {
|
|
||||||
// If the condition is met, allow access to the route
|
|
||||||
next();
|
|
||||||
} else if (to.meta.requiresAuth) {
|
|
||||||
console.error('sneaky')
|
|
||||||
// If the condition is not met, redirect to another route
|
|
||||||
next('/login'); // Redirect to the login page
|
|
||||||
} else {
|
|
||||||
console.error('sneaky')
|
|
||||||
next('/')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
||||||
110
src/router/index.ts
Normal file
110
src/router/index.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
|
||||||
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
|
component: HomeView,
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'member',
|
||||||
|
component: () => import('../views/Member.vue'),
|
||||||
|
meta: { requiresAuth: true },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
redirect: '/games'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/games',
|
||||||
|
name: 'games',
|
||||||
|
component: () => import('../views/games/GamesView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/upload',
|
||||||
|
name: 'upload',
|
||||||
|
component: () => import('../views/games/UploadView.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/events',
|
||||||
|
name: 'events',
|
||||||
|
component: () => import('../views/mqtt/Events.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/games/:gameId',
|
||||||
|
name: 'game',
|
||||||
|
component: () => import('../views/games/GameView.vue'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'auth',
|
||||||
|
component: () => import('../views/auths/Auth.vue'),
|
||||||
|
meta: { requiresAuth: false },
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: () => import('../views/auths/Login.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/sign-up',
|
||||||
|
name: 'sign-up',
|
||||||
|
component: () => import('../views/auths/SignUp.vue'),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
name: 'limited',
|
||||||
|
component: () => import('../views/Limited.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/close',
|
||||||
|
name: 'close',
|
||||||
|
component: () => import('../views/players/Close.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/action',
|
||||||
|
name: 'action',
|
||||||
|
component: () => import('../views/players/QrAction.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/:catchAll(.*)',
|
||||||
|
name: 'shit',
|
||||||
|
component: () => import('../views/NotFound.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
if (to.meta.requiresAuth === undefined)
|
||||||
|
next()
|
||||||
|
|
||||||
|
if (useAuthStore().isAuth() === to.meta.requiresAuth) {
|
||||||
|
// If the condition is met, allow access to the route
|
||||||
|
next()
|
||||||
|
} else if (to.meta.requiresAuth) {
|
||||||
|
console.error('sneaky')
|
||||||
|
// If the condition is not met, redirect to another route
|
||||||
|
next('/login') // Redirect to the login page
|
||||||
|
} else {
|
||||||
|
console.error('sneaky')
|
||||||
|
next('/')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
13
src/services/auth.service.ts
Normal file
13
src/services/auth.service.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { AuthDto } from '@/dtos/auth.dto'
|
||||||
|
import { BaseService } from '@/services/base-service'
|
||||||
|
|
||||||
|
export class AuthService extends BaseService {
|
||||||
|
|
||||||
|
public async login(data: AuthDto): Promise<Response> {
|
||||||
|
return this.post('login', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async signup(dto: AuthDto): Promise<Response> {
|
||||||
|
return this.post('signup', dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/services/base-service.ts
Normal file
44
src/services/base-service.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
export class BaseService {
|
||||||
|
protected apiUrl: string | undefined = import.meta.env.VITE_CONJUREOS_HOST
|
||||||
|
protected baseHeaders: Record<string, string> = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'API-Version': '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async get<T>(path: string, headers?: HeadersInit): Promise<T> {
|
||||||
|
return await (await fetch(this.apiUrl + path, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
...this.baseHeaders
|
||||||
|
}
|
||||||
|
})).json() as T
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async post<T extends object>(path: string, body: T, headers?: HeadersInit): Promise<Response> {
|
||||||
|
return await fetch(this.apiUrl + path, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(body) as any,
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
...this.baseHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async put<T>(path: string, body: T, headers?: HeadersInit): Promise<Response> {
|
||||||
|
return await fetch(this.apiUrl + path, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: {
|
||||||
|
...headers,
|
||||||
|
...this.baseHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/services/game.service.ts
Normal file
53
src/services/game.service.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { BaseService } from './base-service'
|
||||||
|
import { Game } from '../interfaces/game'
|
||||||
|
|
||||||
|
export class GameService extends BaseService {
|
||||||
|
|
||||||
|
public getGames(): Promise<Game[]> {
|
||||||
|
return this.get<Game[]>("games")
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGame(gameId: string): Promise<Game> {
|
||||||
|
return this.get<Game>(`games/${gameId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async upload(game: Game) : Promise<void> {
|
||||||
|
return this.post<any>(`games`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update(game: Game) : Promise<void> {
|
||||||
|
return this.put<any>(`games`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async activate(gameId: string): Promise<void> {
|
||||||
|
return this.post<any>(`games/${gameId}/activate`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deactivate(gameId: string): Promise<void> {
|
||||||
|
return this.post<any>(`games/${gameId}/deactivate`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async download(gameId: string): Promise<void> {
|
||||||
|
return this.get<any>(`games/${gameId}/download`).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async downloadAll(): Promise<void> {
|
||||||
|
return this.get<any>(`games/download`).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async patch(): Promise<void> {
|
||||||
|
return this.put<any>(`games/patch`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async minor(): Promise<void> {
|
||||||
|
return this.put<any>(`games/minor`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async major(): Promise<void> {
|
||||||
|
return this.put<any>(`games/major`, null).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async metadata(): Promise<void> {
|
||||||
|
return this.post<any>(`games/metadata`, null).then();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/shims-vue.d.ts
vendored
Normal file
6
src/shims-vue.d.ts
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
declare module "*.vue" {
|
||||||
|
import type {DefineComponent} from "vue";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const component: DefineComponent<object, object, any>;
|
||||||
|
export default component;
|
||||||
|
}
|
||||||
@ -1,28 +0,0 @@
|
|||||||
import {defineStore} from "pinia";
|
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
const key = "AUTH"
|
|
||||||
export const useAuthStore = defineStore('auth', () => {
|
|
||||||
/** @type {(undefined | any)} */
|
|
||||||
const auth = ref(JSON.parse(localStorage.getItem(key)) || undefined)
|
|
||||||
|
|
||||||
|
|
||||||
/** @param {(undefined | string)} auth */
|
|
||||||
function set(auth) {
|
|
||||||
this.auth = auth
|
|
||||||
if (auth)
|
|
||||||
localStorage.setItem(key, JSON.stringify(auth))
|
|
||||||
else
|
|
||||||
localStorage.removeItem(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isAuth() {
|
|
||||||
return !!this.auth?.token
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAuth() {
|
|
||||||
return this.auth
|
|
||||||
}
|
|
||||||
|
|
||||||
return {auth, getAuth, set, isAuth}
|
|
||||||
})
|
|
||||||
29
src/stores/auth.ts
Normal file
29
src/stores/auth.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { Ref, ref } from 'vue'
|
||||||
|
import { AuthResponse } from '@/interfaces/auth'
|
||||||
|
|
||||||
|
const key = 'AUTH'
|
||||||
|
export const useAuthStore = defineStore('auth', () => {
|
||||||
|
|
||||||
|
const localStorageAuth = localStorage.getItem(key)
|
||||||
|
const auth: Ref<AuthResponse | null> = ref(localStorageAuth ? JSON.parse(localStorageAuth) : null)
|
||||||
|
|
||||||
|
|
||||||
|
function set(_auth: AuthResponse) {
|
||||||
|
auth.value = _auth
|
||||||
|
if (_auth)
|
||||||
|
localStorage.setItem(key, JSON.stringify(_auth))
|
||||||
|
else
|
||||||
|
localStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAuth() {
|
||||||
|
return !!auth.value?.token
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuth() {
|
||||||
|
return auth.value
|
||||||
|
}
|
||||||
|
|
||||||
|
return { auth, getAuth, set, isAuth }
|
||||||
|
})
|
||||||
@ -3,18 +3,17 @@ import {ref} from "vue";
|
|||||||
|
|
||||||
export const useErrorStore = defineStore('error', () => {
|
export const useErrorStore = defineStore('error', () => {
|
||||||
/** @type {(string[])} */
|
/** @type {(string[])} */
|
||||||
const errors = ref([])
|
const errors = ref([] as any[])
|
||||||
|
|
||||||
|
|
||||||
/** @param {(undefined | string)} error */
|
function unshift(error: string) {
|
||||||
function unshift(error) {
|
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
this.errors.unshift(error)
|
errors.value.unshift(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function getErrors() {
|
function getErrors() {
|
||||||
return this.errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
return { errors, getErrors, unshift}
|
return { errors, getErrors, unshift}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
import {defineStore} from 'pinia';
|
|
||||||
import {ref} from 'vue';
|
|
||||||
|
|
||||||
export const useGamelistStore = defineStore('gamelist', () => {
|
|
||||||
/** @type {(undefined | any[])} */
|
|
||||||
const list = ref(undefined)
|
|
||||||
|
|
||||||
/** @param {(undefined | string[])} list */
|
|
||||||
function set(list) {
|
|
||||||
this.list = list
|
|
||||||
}
|
|
||||||
|
|
||||||
return {list, set}
|
|
||||||
})
|
|
||||||
13
src/stores/gamelist.ts
Normal file
13
src/stores/gamelist.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Game } from '@/interfaces/game'
|
||||||
|
|
||||||
|
export const useGamelistStore = defineStore('gamelist', () => {
|
||||||
|
const list = ref([] as Game[])
|
||||||
|
|
||||||
|
function set(_list: Game[]) {
|
||||||
|
list.value = _list
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list, set }
|
||||||
|
})
|
||||||
@ -1,30 +1,28 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {RouterLink, RouterView, useRoute} from 'vue-router'
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
|
|
||||||
import {storeToRefs} from 'pinia';
|
import { storeToRefs } from 'pinia'
|
||||||
import {useAuthStore} from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import router from '@/router';
|
|
||||||
import Errors from '@/components/Errors.vue'
|
import Errors from '@/components/Errors.vue'
|
||||||
import {ref} from 'vue';
|
|
||||||
|
|
||||||
const authStr = useAuthStore()
|
const authStr = useAuthStore()
|
||||||
|
|
||||||
const {auth} = storeToRefs(authStr)
|
const { auth } = storeToRefs(authStr)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
<img alt="Conjure logo" class="logo" src="@/assets/logo_conjure_dark.png" width="2228" height="349"/>
|
<img alt="Conjure logo" class="logo" src="@/assets/logo_conjure_dark.png" width="2228" height="349" />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<RouterView/>
|
<RouterView />
|
||||||
<footer>
|
<footer>
|
||||||
<router-link v-if="auth" to="/"
|
<router-link v-if="auth" to="/"
|
||||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded w-full">
|
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded w-full">
|
||||||
To dashboard
|
To dashboard
|
||||||
</router-link>
|
</router-link>
|
||||||
<Errors/>
|
<Errors />
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {RouterLink, RouterView, useRoute} from 'vue-router'
|
import {RouterLink, RouterView } from 'vue-router'
|
||||||
|
|
||||||
import {storeToRefs} from 'pinia';
|
import {storeToRefs} from 'pinia';
|
||||||
import {useAuthStore} from '@/stores/auth';
|
import {useAuthStore} from '@/stores/auth';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import Errors from '@/components/Errors.vue'
|
import Errors from '@/components/Errors.vue'
|
||||||
import {ref} from 'vue';
|
import { LogOutIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
const authStr = useAuthStore()
|
const authStr = useAuthStore()
|
||||||
|
|
||||||
@ -14,53 +14,48 @@ const {auth} = storeToRefs(authStr)
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
authStr.set(undefined)
|
authStr.set(undefined)
|
||||||
router.push('/')
|
router.push('/')
|
||||||
|
location.reload()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<header class="flex flex-row justify-between items-center mb-8">
|
||||||
<RouterLink to="/">
|
<div class="flex flex-row items-center mb-8 gap-10">
|
||||||
<img alt="Conjure logo" class="logo" src="@/assets/logo_conjure_dark.png" width="2228" height="349"/>
|
<RouterLink to="/">
|
||||||
</RouterLink>
|
<img alt="Conjure logo" class="logo max-w-lg" src="@/assets/logo_conjure_dark.png"/>
|
||||||
<nav v-if="auth">
|
</RouterLink>
|
||||||
<RouterLink to="/games">Games</RouterLink>
|
<nav>
|
||||||
<RouterLink to="/upload">Upload</RouterLink>
|
<RouterLink to="/games">Games</RouterLink>
|
||||||
</nav>
|
<RouterLink to="/upload">Upload</RouterLink>
|
||||||
<nav v-else>
|
</nav>
|
||||||
<RouterLink to="/login">Login</RouterLink>
|
</div>
|
||||||
<RouterLink to="/sign-up">Sign up</RouterLink>
|
<button id="logout" class="p-2 rounded-full border hover:border-gray-300" @click="logout()" v-if="auth">
|
||||||
</nav>
|
<LogOutIcon class="m-2" />
|
||||||
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<RouterView/>
|
<RouterView/>
|
||||||
<footer>
|
<footer>
|
||||||
<button @click="logout()" v-if="auth">Logout</button>
|
|
||||||
<Errors/>
|
<Errors/>
|
||||||
</footer>
|
</footer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.logo {
|
|
||||||
user-drag: none;
|
|
||||||
-webkit-user-drag: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
header {
|
header {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
max-height: 100dvh;
|
max-height: 100dvh;
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 0 auto 2rem;
|
margin: 0 auto 2rem;
|
||||||
|
user-drag: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
@ -80,6 +75,11 @@ nav a {
|
|||||||
border-left: 1px solid var(--color-border);
|
border-left: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#logout {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
nav a:first-of-type {
|
nav a:first-of-type {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
@ -90,11 +90,6 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
header {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
padding-right: calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
margin: 0 2rem 0 0;
|
margin: 0 2rem 0 0;
|
||||||
|
|||||||
115
src/views/auths/Auth.vue
Normal file
115
src/views/auths/Auth.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
|
import Errors from '@/components/Errors.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div id="auth">
|
||||||
|
<div id="auth-content">
|
||||||
|
<header>
|
||||||
|
<RouterLink to="/">
|
||||||
|
<img alt="Conjure logo" class="logo" src="@/assets/logo_conjure_dark.png" width="2228" height="349"/>
|
||||||
|
</RouterLink>
|
||||||
|
<nav>
|
||||||
|
<RouterLink to="/login">Login</RouterLink>
|
||||||
|
<RouterLink to="/sign-up">Sign up</RouterLink>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<RouterView/>
|
||||||
|
<footer>
|
||||||
|
<Errors/>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.logo {
|
||||||
|
user-drag: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
line-height: 1.5;
|
||||||
|
max-height: 100dvh;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a.router-link-exact-active {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a.router-link-exact-active:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-left: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav a:first-of-type {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
padding-right: calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 0 2rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .wrapper {
|
||||||
|
display: flex;
|
||||||
|
place-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
text-align: left;
|
||||||
|
margin-left: -1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#auth {
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#auth-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,74 +1,66 @@
|
|||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import {useErrorStore} from '@/stores/errors'
|
|
||||||
import {useAuthStore} from '@/stores/auth'
|
|
||||||
import router from '@/router'
|
|
||||||
import {ref} from 'vue'
|
|
||||||
import Loader from '@/components/Loader.vue'
|
|
||||||
|
|
||||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
import { useErrorStore } from '@/stores/errors'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
import router from '@/router'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import Loader from '@/components/Loader.vue'
|
||||||
|
import { AuthService } from '@/services/auth.service'
|
||||||
|
import { AuthDto } from '@/dtos/auth.dto'
|
||||||
|
|
||||||
const errorStore = useErrorStore()
|
const errorStore = useErrorStore()
|
||||||
|
const authService = new AuthService();
|
||||||
const isLoginIn = ref(false)
|
const isLoginIn = ref(false)
|
||||||
const login = (form) => {
|
const login = async (form: HTMLFormElement) => {
|
||||||
const formData = new FormData(form)
|
const formData = new FormData(form);
|
||||||
isLoginIn.value = true
|
isLoginIn.value = true;
|
||||||
|
|
||||||
const myHeaders = new Headers();
|
const dto = new AuthDto(formData);
|
||||||
myHeaders.append("API-Version", 1);
|
const response = await authService.login(dto).catch((error) => {
|
||||||
fetch(apiHost + 'login', {
|
isLoginIn.value = false;
|
||||||
method: 'POST',
|
errorStore.unshift(error);
|
||||||
body: formData,
|
|
||||||
headers: myHeaders
|
|
||||||
})
|
})
|
||||||
.then((response) => {
|
|
||||||
isLoginIn.value = false
|
isLoginIn.value = false;
|
||||||
if (response.status !== 200)
|
if (!response) {
|
||||||
return response.text().then(error => {
|
return;
|
||||||
throw new Error(error)
|
}
|
||||||
}
|
|
||||||
)
|
const result = await response.json();
|
||||||
return response.text()
|
useAuthStore().set(result)
|
||||||
})
|
router.push('/')
|
||||||
.then((result) => {
|
|
||||||
useAuthStore().set(JSON.parse(result))
|
|
||||||
router.push('/')
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
isLoginIn.value = false
|
|
||||||
errorStore.unshift(error)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
<form ref="loginForm" enctype="multipart/form-data" @submit.prevent="login($refs.loginForm)">
|
<form ref="loginForm" enctype="multipart/form-data" @submit.prevent="login($refs.loginForm as HTMLFormElement)">
|
||||||
<label for="username">username</label>
|
<label for="username">Username</label>
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||||
name="username"
|
name="username"
|
||||||
id="username"
|
id="username"
|
||||||
/>
|
/>
|
||||||
<label for="password">password</label>
|
<label for="password">Password</label>
|
||||||
<input
|
<input
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||||
name="password"
|
name="password"
|
||||||
id="password"
|
id="password"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
v-if="!isLoginIn"
|
v-if="!isLoginIn"
|
||||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
<span class="loader" v-else>
|
<span class="loader" v-else>
|
||||||
<Loader :variant="2"/>
|
<Loader :variant="2" />
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -1,41 +1,42 @@
|
|||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import {useErrorStore} from '@/stores/errors'
|
import { useErrorStore } from '@/stores/errors'
|
||||||
import {ref} from "vue";
|
import { ref } from 'vue'
|
||||||
import {usePlayerAuthStore} from '@/stores/player-auth';
|
import { usePlayerAuthStore } from '@/stores/player-auth'
|
||||||
import router from '@/router';
|
import router from '@/router'
|
||||||
|
|
||||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
||||||
|
|
||||||
const errorStore = useErrorStore()
|
const errorStore = useErrorStore()
|
||||||
|
|
||||||
const isLoginIn = ref(false)
|
const isLoginIn = ref(false)
|
||||||
const signup = (form) => {
|
const signup = (form: HTMLFormElement) => {
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form)
|
||||||
isLoginIn.value = true
|
isLoginIn.value = true
|
||||||
const myHeaders = new Headers();
|
const myHeaders = new Headers()
|
||||||
myHeaders.append("API-Version", 1);
|
myHeaders.append('API-Version', "1")
|
||||||
fetch(apiHost + 'signup', {
|
fetch(apiHost + 'signup', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: myHeaders
|
headers: myHeaders
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status !== 200)
|
if (response.status !== 200)
|
||||||
return response.text().then(error => {
|
return response.text().then(error => {
|
||||||
throw new Error(error)
|
throw new Error(error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return response.text()
|
return response.text()
|
||||||
})
|
})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
isLoginIn.value = false
|
isLoginIn.value = false
|
||||||
usePlayerAuthStore().set(JSON.parse(result))
|
console.log(result)
|
||||||
router.push('/')
|
usePlayerAuthStore().set(JSON.parse(result))
|
||||||
})
|
router.push('/')
|
||||||
.catch((error) => {
|
})
|
||||||
isLoginIn.value = false
|
.catch((error) => {
|
||||||
errorStore.unshift(error)
|
isLoginIn.value = false
|
||||||
});
|
errorStore.unshift(error)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -43,18 +44,18 @@ const signup = (form) => {
|
|||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<h1>Sign up</h1>
|
<h1>Sign up</h1>
|
||||||
<form ref="signupForm" enctype="multipart/form-data" @submit.prevent="signup($refs.signupForm)">
|
<form ref="signupForm" enctype="multipart/form-data" @submit.prevent="signup($refs.signupForm as HTMLFormElement)">
|
||||||
<label for="username">username</label>
|
<label for="username">Username</label>
|
||||||
<input required type="text"
|
<input required type="text"
|
||||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||||
name="username" id="username"/>
|
name="username" id="username" />
|
||||||
<label for="password">password</label>
|
<label for="password">Password</label>
|
||||||
<input required type="password"
|
<input required type="password"
|
||||||
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
class="border border-primary rounded-lg px-3 py-2 bg-transparent focus:outline-none focus:border-accent"
|
||||||
name="password" id="password"/>
|
name="password" id="password" />
|
||||||
<button
|
<button
|
||||||
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
class="bg-transparent text-primary font-semibold hover:text-white py-2 px-4 border border-gray-500 hover:border-transparent rounded"
|
||||||
type="submit">
|
type="submit">
|
||||||
Signup
|
Signup
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -6,90 +6,53 @@ import {useRoute} from 'vue-router';
|
|||||||
import {useAuthStore} from '@/stores/auth';
|
import {useAuthStore} from '@/stores/auth';
|
||||||
import {storeToRefs} from 'pinia';
|
import {storeToRefs} from 'pinia';
|
||||||
|
|
||||||
const errorStore = useErrorStore()
|
const errorStore = useErrorStore();
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore();
|
||||||
|
const { auth } = storeToRefs(authStore);
|
||||||
const {auth} = storeToRefs(authStore)
|
const apiHost = import.meta.env.VITE_CONJUREOS_HOST;
|
||||||
|
|
||||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const gameId = ref(route.params.gameId);
|
const gameId = ref(route.params.gameId);
|
||||||
const game = ref(undefined)
|
const game = ref(undefined);
|
||||||
const isActivating = ref(false)
|
const isActivating = ref(false);
|
||||||
console.log(route, route.meta)
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const myHeaders = new Headers();
|
|
||||||
myHeaders.append("API-Version", 1);
|
|
||||||
fetch(apiHost + 'games/' + gameId.value, {
|
fetch(apiHost + 'games/' + gameId.value, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: myHeaders
|
headers: { 'API-Version': 1 },
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
game.value = result
|
game.value = result;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
errorStore.unshift(error)
|
errorStore.unshift(error);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
/**
|
function toggleActivation(state) {
|
||||||
*
|
isActivating.value = true;
|
||||||
*/
|
fetch(`${apiHost}games/${gameId.value}/${state ? 'activate' : 'deactivate'}`, {
|
||||||
function activate() {
|
method: 'POST',
|
||||||
fetch(apiHost + 'games/' + gameId.value + '/activate', {
|
headers: {
|
||||||
method: 'POST', headers: {
|
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${auth.value.token}`,
|
'Authorization': `Bearer ${auth.value.token}`,
|
||||||
'API-Version': 1,
|
'API-Version': 1,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response.status !== 204)
|
if (response.status !== 204) return response.json().then((errorBody) => { throw new Error(errorBody); });
|
||||||
return response.json().then((errorBody) => {
|
return true;
|
||||||
throw new Error(errorBody);
|
})
|
||||||
});
|
.then(() => {
|
||||||
return true
|
game.value = { ...game.value, active: state };
|
||||||
})
|
isActivating.value = false;
|
||||||
.then((result) => {
|
})
|
||||||
game.value = {...game.value, active: true}
|
.catch((error) => {
|
||||||
isActivating.value = false
|
errorStore.unshift(error);
|
||||||
})
|
isActivating.value = false;
|
||||||
.catch((error) => {
|
});
|
||||||
errorStore.unshift(error)
|
|
||||||
isActivating.value = false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function deactivate() {
|
|
||||||
isActivating.value = true
|
|
||||||
fetch(apiHost + 'games/' + gameId.value + '/deactivate', {
|
|
||||||
method: 'POST', headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${auth.value.token}`,
|
|
||||||
'API-Version': 1,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status !== 204)
|
|
||||||
return response.json().then((errorBody) => {
|
|
||||||
throw new Error(errorBody);
|
|
||||||
});
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
.then((result) => {
|
|
||||||
game.value = {...game.value, active: false}
|
|
||||||
isActivating.value = false
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
errorStore.unshift(error)
|
|
||||||
isActivating.value = false
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@ -97,22 +60,22 @@ function deactivate() {
|
|||||||
<template>
|
<template>
|
||||||
<article>
|
<article>
|
||||||
<loader v-if="game === undefined"></loader>
|
<loader v-if="game === undefined"></loader>
|
||||||
<template v-else>
|
<div v-else>
|
||||||
<img v-if="game.image" :src="'data:image/png;base64,'+game.image" alt="thumbnail"/>
|
<img v-if="game.image" :src="'data:image/png;base64,'+game.image" alt="thumbnail"/>
|
||||||
<h1>{{ game.game }}</h1>
|
<h1>{{ game.game }}</h1>
|
||||||
<p>{{ game.description }}</p>
|
<p>{{ game.description }}</p>
|
||||||
<loader :variant="2" v-if="isActivating"></loader>
|
<loader :variant="2" v-if="isActivating"></loader>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button v-if="game.active" @click="deactivate()"
|
<button
|
||||||
class="bg-transparent font-bold text-primary underline underline-offset-8 hover:no-underline hover:text-primary py-2 px-4 border border-transparent hover:border-transparent rounded hover:bg-primary">
|
:class="game.active ? 'bg-red-500 text-white hover:bg-red-700' : 'bg-green-500 text-white hover:bg-green-700'"
|
||||||
deactivate
|
class="font-bold py-2 px-4 my-2 rounded"
|
||||||
</button>
|
@click="toggleActivation(!game.active)"
|
||||||
<button v-else @click="activate()"
|
:disabled="isActivating"
|
||||||
class="bg-transparent font-bold text-primary underline underline-offset-8 hover:no-underline hover:text-primary py-2 px-4 border border-transparent hover:border-transparent rounded hover:bg-primary">
|
>
|
||||||
activate
|
{{ game.active ? 'Deactivate' : 'Activate' }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -122,24 +85,6 @@ article {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
background-color: rgba(var(--vt-c-payne), 0.6);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 2fr;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 6rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -1,60 +1,45 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {onMounted} from 'vue';
|
import { onMounted } from 'vue'
|
||||||
import {useGamelistStore} from '@/stores/gamelist';
|
import { useGamelistStore } from '@/stores/gamelist'
|
||||||
import {storeToRefs} from 'pinia';
|
import { storeToRefs } from 'pinia'
|
||||||
import {useErrorStore} from '@/stores/errors'
|
import { useErrorStore } from '@/stores/errors'
|
||||||
import Loader from '@/components/Loader.vue';
|
import Loader from '@/components/Loader.vue'
|
||||||
import {RouterLink} from 'vue-router';
|
import { RouterLink } from 'vue-router'
|
||||||
|
import { GameService } from '@/services/game.service'
|
||||||
|
import { DownloadIcon } from 'lucide-vue-next'
|
||||||
|
|
||||||
const errorStore = useErrorStore()
|
const errorStore = useErrorStore()
|
||||||
|
|
||||||
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
const apiHost = import.meta.env.VITE_CONJUREOS_HOST
|
||||||
const gamelistStore = useGamelistStore()
|
const gamelistStore = useGamelistStore()
|
||||||
|
const { list: gamelist } = storeToRefs(gamelistStore)
|
||||||
|
const gamesService = new GameService()
|
||||||
|
|
||||||
const {list: gamelist} = storeToRefs(gamelistStore)
|
onMounted(async () => {
|
||||||
|
const games = await gamesService.getGames()
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error)
|
||||||
|
errorStore.unshift(error)
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
if (games) {
|
||||||
const myHeaders = new Headers();
|
gamelistStore.set(games)
|
||||||
myHeaders.append("API-Version", 1);
|
}
|
||||||
fetch(apiHost + 'games', {
|
|
||||||
method: 'GET',
|
|
||||||
headers: myHeaders
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((result) => {
|
|
||||||
gamelistStore.set(result)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
errorStore.unshift(error)
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
function download(name) {
|
function download(name) {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a')
|
||||||
a.href = apiHost + 'games/' + name + '/download';
|
a.href = apiHost + 'games/' + name + '/download'
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
// Programmatically click the anchor element to start the download
|
document.body.removeChild(a)
|
||||||
a.click();
|
|
||||||
|
|
||||||
// Clean up by removing the anchor element
|
|
||||||
document.body.removeChild(a);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadAll() {
|
function downloadAll() {
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a')
|
||||||
a.href = apiHost + 'games/download';
|
a.href = apiHost + 'games/download'
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
// Programmatically click the anchor element to start the download
|
document.body.removeChild(a)
|
||||||
a.click();
|
|
||||||
|
|
||||||
// Clean up by removing the anchor element
|
|
||||||
document.body.removeChild(a);
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -62,63 +47,45 @@ function downloadAll() {
|
|||||||
<article>
|
<article>
|
||||||
<loader v-if="gamelist === undefined"></loader>
|
<loader v-if="gamelist === undefined"></loader>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<header>
|
<header class="flex flex-row justify-between items-center">
|
||||||
|
<h1 class="text-foreground">My Games</h1>
|
||||||
<button
|
<button
|
||||||
class="bg-transparent font-bold text-foreground underline underline-offset-8 hover:no-underline py-2 px-4 border border-primary rounded hover:bg-primary"
|
class="bg-transparent font-bold text-foreground py-2 px-4 border border-primary rounded hover:bg-primary"
|
||||||
@click="downloadAll()"
|
@click="downloadAll()"
|
||||||
>
|
>
|
||||||
Download all
|
Download All
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
<ul>
|
<ul class="flex flex-col gap-4">
|
||||||
<li class="game rounded-lg p-4" v-for="(item) in gamelist" :key="item.id">
|
<li v-for="item in gamelist" :key="item.id" class="game-item border border-gray-300 rounded-lg p-4 flex items-center gap-4 hover:border-2">
|
||||||
<img v-if="item.thumbnail" :src="'data:image/png;base64,'+item.thumbnail" alt="thumbnail"/>
|
<RouterLink :to="'/games/' + item.id" class="flex-grow flex items-center gap-4 p-2 rounded">
|
||||||
<span v-else></span>
|
<img v-if="item.thumbnail" :src="'data:image/png;base64,' + item.thumbnail" alt="thumbnail" class="h-16" />
|
||||||
<div class="flex flex-col">
|
<div>
|
||||||
<p>{{ item.description }}</p>
|
<h2 class="font-bold">{{ item.game }}</h2>
|
||||||
<i class="ml-auto" v-if="item.active">active</i>
|
<p class="text-primary">{{ item.description }}</p>
|
||||||
</div>
|
<i v-if="item.active" class="text-green-500 pt-3">Active</i>
|
||||||
<h2>{{ item.game }}</h2>
|
</div>
|
||||||
<div>
|
</RouterLink>
|
||||||
<RouterLink :to="'/games/' + item.id"
|
<button
|
||||||
class="bg-transparent font-bold text-foreground underline underline-offset-8 hover:no-underline hover:text-foreground py-2 px-4 border border-transparent hover:border-transparent rounded hover:bg-primary">
|
class="p-2 rounded-full hover:bg-gray-200 text-green-500 hover:text-green-700"
|
||||||
open
|
@click="download(item.id)"
|
||||||
</RouterLink>
|
>
|
||||||
<button
|
<DownloadIcon />
|
||||||
class="bg-transparent font-bold text-foreground underline underline-offset-8 hover:no-underline hover:text-foreground py-2 px-4 border border-transparent hover:border-transparent rounded hover:bg-primary"
|
</button>
|
||||||
type="button" @click="download(item.id + '.conj')">Download
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
</article>
|
</article>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped>
|
||||||
|
.game-item:hover {
|
||||||
|
margin: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
article {
|
article {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
align-items: flex-end;
|
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
ul {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
li {
|
|
||||||
background-color: rgba(var(--vt-c-payne), 0.6);
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 2fr;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-height: 6rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default {
|
|||||||
submitForm() {
|
submitForm() {
|
||||||
const form = this.$refs.uploadForm
|
const form = this.$refs.uploadForm
|
||||||
const formData = new FormData(form)
|
const formData = new FormData(form)
|
||||||
console.log("Upload")
|
console.log("Upload " + JSON.stringify(authStr.getAuth()))
|
||||||
|
|
||||||
fetch(apiHost + 'games', {
|
fetch(apiHost + 'games', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
35
tsconfig.json
Normal file
35
tsconfig.json
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"types": ["vite/client"],
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM"
|
||||||
|
],
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue"
|
||||||
|
],
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "./tsconfig.node.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
11
tsconfig.node.json
Normal file
11
tsconfig.node.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"vite.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -1,25 +1,26 @@
|
|||||||
import {fileURLToPath, URL} from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
import {defineConfig} from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports,no-undef
|
||||||
process.env.VUE_APP_VERSION = require('./package.json').version
|
process.env.VUE_APP_VERSION = require('./package.json').version
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue()
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
terserOptions: {
|
terserOptions: {
|
||||||
compress: {
|
compress: {
|
||||||
drop_console: false,
|
drop_console: false
|
||||||
},
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user