Migrated to nuxt-js frontend framework

This commit is contained in:
Hubert Van De Walle 2020-04-17 18:57:05 +02:00
parent 8fe229b3d6
commit 1917e2e9fc
32 changed files with 8446 additions and 15 deletions

View File

@ -0,0 +1,9 @@
CREATE TABLE `Tags`
(
`id` int PRIMARY KEY AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`note_id` int NOT NULL
);
ALTER TABLE `Tags`
ADD FOREIGN KEY (`note_id`) REFERENCES `Notes` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT;

View File

@ -38,10 +38,10 @@ fun Application.module() {
log.debug(kodein.container.tree.bindings.description()) log.debug(kodein.container.tree.bindings.description())
// TODO, clean this (migration) // TODO, clean this (migration)
val feature: Feature by kodein.instance() val feature by kodein.instance<Feature>()
feature.execute() feature.execute()
val controllers: Set<KodeinController> by kodein.instance() val controllers by kodein.instance<Set<KodeinController>>()
routing { routing {
controllers.forEach { controllers.forEach {

View File

@ -39,21 +39,24 @@ class NoteController(kodein: Kodein) : KodeinController(kodein) {
.find { Users.email eq email } .find { Users.email eq email }
?: return@post call.respond(HttpStatusCode.Forbidden, ApiError.DeletedUserError) ?: return@post call.respond(HttpStatusCode.Forbidden, ApiError.DeletedUserError)
val transaction = db.useTransaction {
val note = Note { val note = Note {
this.title = body.title this.title = body.title
this.content = body.content this.content = body.content
this.user = user this.user = user
}
db.sequenceOf(Notes).add(note)
body.tags.forEach { tagName ->
val tag = Tag {
this.name = tagName
this.note = note
} }
db.sequenceOf(Tags).add(tag)
db.sequenceOf(Notes).add(note)
body.tags.forEach { tagName ->
val tag = Tag {
this.name = tagName
this.note = note
}
db.sequenceOf(Tags).add(tag)
}
1
} }
return@post call.respond(Response("created")) return@post call.respond(Response("created"))

13
frontend/.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

90
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,90 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp

28
frontend/api/api.js Normal file
View File

@ -0,0 +1,28 @@
import axios from 'axios'
import {mapState} from "vuex";
const state = mapState(['token'])
const apiClient = axios.create({
baseURL: `http://localhost:5000`,
withCredentials: false,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
axios.interceptors.request.use(
config => {
const token = state.token;
if (token) {
config.headers['Authorization'] = 'Bearer ' + token;
}
return config;
},
error => {
Promise.reject(error)
});
export default apiClient

View File

@ -0,0 +1,7 @@
# ASSETS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).

View File

@ -0,0 +1,4 @@
// Ref: https://github.com/nuxt-community/vuetify-module#customvariables
//
// The variables you want to modify
// $font-size-root: 20px;

View File

@ -0,0 +1,61 @@
<template>
<v-card flat>
<v-card-text>
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-text-field
v-model="username"
:rules="usernameRules"
label="Username"
required
prepend-icon="mdi-account"
></v-text-field>
<v-text-field
v-model="password"
:rules="passwordRules"
label="Password"
required
prepend-icon="mdi-lock"
type="password"
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn
:disabled="!valid"
color="success"
@click="submit"
>
Login
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "LoginForm",
methods: {
submit() {
}
},
data: () => ({
valid: false,
username: '',
usernameRules: [
v => !!v || 'Name is required',
],
password: '',
passwordRules: [
v => !!v || 'Password is required',
]
}),
}
</script>

View File

@ -0,0 +1,55 @@
<template>
<v-card height="100%">
<v-card-title>{{ title }}</v-card-title>
<v-card-text>
<slot></slot>
</v-card-text>
<div class="tags">
<v-chip v-for="tag in tags" :key="tag" active color="secondary">{{ tag }}</v-chip>
</div>
<v-divider class="mt-auto"></v-divider>
<v-card-actions>
<v-btn
text
color="deep-purple accent-4" dark>
<v-icon left>mdi-eye</v-icon>
View
</v-btn>
<v-spacer/>
<v-btn
text
color="deep-purple accent-4">
<v-icon left>mdi-pencil</v-icon>
Edit
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "Note",
props: {
title: {
type: String,
required: true
},
tags: {
type: Array
}
}
}
</script>
<style>
.tags {
padding: 16px;
}
.tags .v-chip {
margin: 4px 8px 4px 0;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<v-card flat>
<v-card-text>
<v-form
ref="form"
v-model="valid"
lazy-validation
>
<v-text-field
v-model="username"
:rules="usernameRules"
label="Username"
required
prepend-icon="mdi-account"
></v-text-field>
<v-text-field
v-model="email"
:rules="emailRules"
label="Email"
required
prepend-icon="mdi-at"
></v-text-field>
<v-text-field
v-model="password"
:rules="passwordRules"
label="Password"
required
prepend-icon="mdi-lock"
type="password"
></v-text-field>
<v-text-field
v-model="confirm"
:rules="confirmRules"
label="Confirm your password"
required
prepend-icon="mdi-lock"
type="password"
></v-text-field>
</v-form>
</v-card-text>
<v-divider/>
<v-card-actions>
<v-spacer/>
<v-btn
:disabled="!valid"
color="success"
@click="submit"
>
Register
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "RegisterForm",
methods: {
submit() {
}
},
data: () => ({
valid: false,
username: '',
usernameRules: [
v => !!v || 'Name is required',
],
email: '',
emailRules: [
v => !!v || 'Email is required',
v => /.+@.+/.test(v) || 'E-mail must be valid',
],
password: '',
passwordRules: [
v => !!v || 'Password is required',
v => v.length >= 6 || 'Password must be longer than 6 characters',
],
confirm: '',
confirmRules: [
v => !!v || 'Password is required'
//v => confirmEquals() || 'Both passwords must be equals',
]
}),
}
</script>

View File

@ -0,0 +1,19 @@
<template>
<v-app>
<v-content>
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col cols="12" lg="6" md="8">
<nuxt/>
</v-col>
</v-row>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
name: "centered"
}
</script>

View File

@ -0,0 +1,32 @@
<template>
<v-app>
<v-app-bar
fixed
app
color="primary"
dark
>
<v-toolbar-title>Notes</v-toolbar-title>
<v-spacer/>
<v-btn to="/notes" text rounded>My notes</v-btn>
<v-btn to="/account" text rounded>Account</v-btn>
</v-app-bar>
<v-content>
<v-container>
<nuxt/>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
}
</script>
<style>
html {
overflow-y: auto
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<v-app>
<v-content>
<nuxt/>
</v-content>
</v-app>
</template>
<script>
export default {}
</script>
<style>
html {
overflow-y: auto
}
</style>

View File

@ -0,0 +1,44 @@
<template>
<v-app dark>
<h1 v-if="error.statusCode === 404">
{{ pageNotFound }}
</h1>
<h1 v-else>
{{ otherError }}
</h1>
<NuxtLink to="/">
Home page
</NuxtLink>
</v-app>
</template>
<script>
export default {
layout: 'empty',
props: {
error: {
type: Object,
default: null
}
},
data() {
return {
pageNotFound: '404 Not Found',
otherError: 'An error occurred'
}
},
head() {
const title =
this.error.statusCode === 404 ? this.pageNotFound : this.otherError
return {
title
}
}
}
</script>
<style scoped>
h1 {
font-size: 20px;
}
</style>

33
frontend/layouts/home.vue Normal file
View File

@ -0,0 +1,33 @@
<template>
<v-app>
<v-app-bar
fixed
app
color="primary"
dark
prominent
>
<v-toolbar-title>Notes</v-toolbar-title>
<v-spacer/>
<v-btn to="/notes" text rounded>My notes</v-btn>
<v-btn to="/account" text rounded>Account</v-btn>
</v-app-bar>
<v-content>
<v-container>
<nuxt/>
</v-container>
</v-content>
</v-app>
</template>
<script>
export default {
}
</script>
<style>
html {
overflow-y: auto
}
</style>

89
frontend/nuxt.config.js Normal file
View File

@ -0,0 +1,89 @@
import colors from 'vuetify/es5/util/colors'
export default {
mode: 'universal',
/*
** Headers of the page
*/
head: {
titleTemplate: '%s - ' + process.env.npm_package_name,
title: process.env.npm_package_name || '',
meta: [
{charset: 'utf-8'},
{name: 'viewport', content: 'width=device-width, initial-scale=1'},
{hid: 'description', name: 'description', content: process.env.npm_package_description || ''}
],
link: [
{rel: 'icon', type: 'image/x-icon', href: '/favicon.ico'}
]
},
/*
** Customize the progress-bar color
*/
loading: {color: '#fff'},
/*
** Global CSS
*/
css: [],
/*
** Plugins to load before mounting the App
*/
plugins: [],
/*
** Nuxt.js dev-modules
*/
buildModules: [
// Doc: https://github.com/nuxt-community/eslint-module
'@nuxtjs/vuetify'
],
/*
** Nuxt.js modules
*/
modules: [
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
// Doc: https://github.com/nuxt-community/dotenv-module
'@nuxtjs/dotenv'
],
/*
** Axios module configuration
** See https://axios.nuxtjs.org/options
*/
axios: {},
/*
** vuetify module configuration
** https://github.com/nuxt-community/vuetify-module
*/
vuetify: {
customVariables: ['~/assets/variables.scss'],
theme: {
dark: false,
themes: {
dark: {
primary: colors.blue.darken2,
accent: colors.grey.darken3,
secondary: colors.amber.darken3,
info: colors.teal.lighten1,
warning: colors.amber.base,
error: colors.deepOrange.accent4,
success: colors.green.accent3
},
light: {
primary: colors.blue.darken3,
secondary: colors.teal.lighten1,
accent: colors.deepOrange.accent2
}
}
}
},
/*
** Build configuration
*/
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
}
}
}

23
frontend/package.json Normal file
View File

@ -0,0 +1,23 @@
{
"name": "Notes",
"version": "1.0.0",
"description": "",
"author": "Hubert Van De Walle",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
},
"dependencies": {
"@nuxtjs/axios": "^5.3.6",
"@nuxtjs/dotenv": "^1.4.0",
"masonry-layout": "^4.2.2",
"nuxt": "^2.0.0"
},
"devDependencies": {
"@nuxtjs/vuetify": "^1.0.0"
}
}

View File

@ -0,0 +1,59 @@
<template>
<v-card color="primary" dark>
<v-btn icon to="/">
<v-icon>mdi-arrow-left</v-icon>
</v-btn>
<v-card-title class="text-center justify-center py-6">
<h1 class="font-weight-bold display-2">Account</h1>
</v-card-title>
<v-tabs
v-model="tab"
background-color="transparent"
dark
grow
>
<v-tab
v-for="tab in tabs"
:key="tab"
>
{{ tab }}
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item
v-for="tab in tabs"
:key="tab"
>
<v-card flat>
<v-card-text>
<LoginForm v-if="tab === 'Login'"></LoginForm>
<RegisterForm v-else></RegisterForm>
</v-card-text>
</v-card>
</v-tab-item>
</v-tabs-items>
</v-card>
</template>
<script>
import LoginForm from "~/components/LoginForm";
import RegisterForm from "~/components/RegisterForm";
export default {
name: "centered",
layout: "centered",
data: () => ({
tab: 0,
tabs: ["Login", "Register"]
}),
head: () => ({
title: "Account"
}),
components: {
LoginForm,
RegisterForm
}
}
</script>

62
frontend/pages/create.vue Normal file
View File

@ -0,0 +1,62 @@
<template>
<v-card>
<v-toolbar
flat
color="secondary"
class="mt-12"
dark
>
<v-toolbar-title>Create a note</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-text-field label="Title" outlined value="My new note"></v-text-field>
<v-textarea
label="Text"
outlined
counter
loader-height="5"
spellcheck="false"
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse"
></v-textarea>
<v-combobox
outlined
v-model="tags"
:items="possibleTags"
:delimiters="delimiters"
label="Tags"
multiple
chips
deletable-chips
></v-combobox>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="success"
depressed
>
Create
</v-btn>
</v-card-actions>
</v-card>
</template>
<script>
export default {
name: "create",
data: () => ({
delimiters: [" ", ","],
tags: [],
possibleTags: ["Dev", "Ephec", "Java"]
})
}
</script>
<style scoped>
</style>

23
frontend/pages/index.vue Normal file
View File

@ -0,0 +1,23 @@
<template>
<h1>slt</h1>
</template>
<script>
export default {
title: 'Home',
layout: 'home',
data: () => ({
}),
head: () => ({
title: "Home"
})
}
</script>
<style>
body {
background-color: #1565c0;
}
</style>

77
frontend/pages/new.vue Normal file
View File

@ -0,0 +1,77 @@
<template>
<v-card flat>
<v-toolbar
color="primary"
dark
extended
prominent
>
<v-app-bar-nav-icon></v-app-bar-nav-icon>
<v-toolbar-title>Notes</v-toolbar-title>
</v-toolbar>
<v-card
class="mx-auto"
max-width="700"
style="margin-top: -64px;"
>
<v-toolbar flat>
<v-toolbar-title class="grey--text">Create a note</v-toolbar-title>
</v-toolbar>
<v-divider></v-divider>
<v-card-text>
<v-text-field label="Title" value="My new note"></v-text-field>
<v-textarea
label="Text"
counter
loader-height="5"
spellcheck="false"
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse"
></v-textarea>
<v-combobox
v-model="tags"
:items="possibleTags"
:delimiters="delimiters"
label="Tags"
multiple
chips
deletable-chips
></v-combobox>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-btn
color="success"
class="mx-auto"
depressed
block
>
Create
</v-btn>
</v-card-actions>
</v-card>
</v-card>
</template>
<script>
export default {
name: "new",
layout: "empty",
data: () => ({
delimiters: [" ", ","],
tags: [],
possibleTags: ["Dev", "Ephec", "Java"]
})
}
</script>
<style scoped>
</style>

106
frontend/pages/notes.vue Normal file
View File

@ -0,0 +1,106 @@
<template>
<v-container fluid>
<v-row dense
align-content="stretch"
align="stretch"
>
<v-col
v-for="(note, index) in notes"
:key="index"
cols="12" md="6" xl="3"
class="viewer">
<Note
class="item"
:title="note.title"
:tags="note.tags"
>
{{ note.content }}
</Note>
</v-col>
</v-row>
<v-btn to="/create" fab fixed bottom right large color="accent">
<v-icon>mdi-plus</v-icon>
</v-btn>
</v-container>
</template>
<script>
import Note from '@/components/Note'
export default {
name: "notes",
title: "Notes",
components: {
Note,
},
data: () => ({
selector: ".viewer",
options: {
columnWidth: "200",
percentPosition: true,
gutter: 0,
itemSelector: ".item"
},
notes: [
{
title: "Elec analogique",
tags: ["Ephec"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab est in rerum."
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
{
title: "Melvin fait du ski",
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab est in rerum."
},
{
title: "Daniel étudie elec",
tags: ["Ephec", "Ephec", "Ephec", "Ephec", "Ephec", "Ephec"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab est in rerum."
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
{
title: "Daniel étudie elec",
tags: ["Ephec", "Ephec", "Ephec", "Ephec", "Ephec", "Ephec"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab est in rerum."
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
{
title: "Portfolio",
tags: ["Ephec", "Dev"],
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Deserunt doloremque harum iste itaque laboriosam modi placeat quia sequi. Accusamus consectetur dolor doloribus eaque et incidunt ipsa iste, iure magnam maxime minus, modi nam odit possimus quam recusandae reprehenderit sit totam?"
},
]
})
}
</script>

View File

@ -0,0 +1,13 @@
export function setToken(token) {
if (process.browser && token) {
localStorage.setItem('token', token)
}
}
export function clearToken() {
if (process.browser) {
localStorage.removeItem('token')
}
}
export default {setToken, clearToken}

View File

@ -0,0 +1,19 @@
import apiClient from '@/api'
export default {
async login({username, password}) {
try {
const {data} = await apiClient.post('/signin', {
username,
password
})
return {token: data["access_token"]}
} catch (e) {
if (e.response && e.response.status === 401)
return Promise.reject({invalid: true})
else
return Promise.reject({error: true})
}
}
}

View File

@ -0,0 +1,20 @@
import apiClient from '@/api'
export default {
async register({username, email, password}) {
try {
await apiClient.post('/signup', {
username,
email,
password
})
return {success: true}
} catch (e) {
if (e.response && e.response.status === 409)
return Promise.reject({exists: true})
else
return Promise.reject({error: true})
}
}
}

11
frontend/static/README.md Normal file
View File

@ -0,0 +1,11 @@
# STATIC
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).

BIN
frontend/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
frontend/static/v.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

After

Width:  |  Height:  |  Size: 539 B

27
frontend/store/index.js Normal file
View File

@ -0,0 +1,27 @@
import {clearToken, setToken} from "@/services/LocalStorageService";
export const state = () => ({
token: ''
})
export const mutations = {
setToken(state, token) {
state.token = token
setToken(token)
},
clearToken(state) {
state.token = null
clearToken()
},
initToken(state) {
state.token = localStorage.getItem('token')
}
}
export const actions = {}
export const getters = {
isLoggedIn(state) {
return state.token !== null && state.token !== ''
}
}

7396
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff