feat: start refactoring the code to actually use vuex

This includes creating multiple stores as needed for components and removing all complex states from components (since all those states should be stored in vuex)
This commit is contained in:
Zephyrrus 2020-07-02 02:50:55 +03:00
parent f0753e1551
commit 720ffaf008
14 changed files with 317 additions and 181 deletions

View File

@ -65,6 +65,8 @@
</template>
<script>
import { mapState, mapGetters } from 'vuex';
export default {
props: {
isWhite: {
@ -76,12 +78,8 @@ export default {
return { hamburger: false };
},
computed: {
loggedIn() {
return this.$store.state.loggedIn;
},
config() {
return this.$store.state.config;
}
...mapGetters({ loggedIn: 'auth/isLoggedIn' }),
...mapState(['config'])
},
methods: {
logOut() {

View File

@ -12,22 +12,18 @@
<script>
import Navbar from '~/components/navbar/Navbar.vue';
import Footer from '~/components/footer/Footer';
import { mapState } from 'vuex';
export default {
components: {
Navbar,
Navbar,
Footer
},
computed: {
config() {
return this.$store.state.config;
},
alert() {
return this.$store.state.alert;
}
},
computed: mapState(['config', 'alert']),
watch: {
alert() {
if (!this.alert) return;
'alert.text'() {
console.log('aaaaaaaa');
if (!this.alert.text) return;
this.$buefy.toast.open({
duration: 3500,
@ -36,34 +32,40 @@ export default {
type: this.alert.error ? 'is-danger' : 'is-success'
});
setTimeout(() => {
this.$store.dispatch('alert', null);
}, 3500);
this.$store.dispatch('alert/clear', null);
}
},
mounted() {
console.log(`%c lolisafe %c v${this.config.version} %c`, 'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff', 'background:#ff015b; padding: 1px; border-radius: 0 3px 3px 0; color: #fff', 'background:transparent');
console.log(
`%c lolisafe %c v${this.config.version} %c`,
'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px; color: #fff',
'background:#ff015b; padding: 1px; border-radius: 0 3px 3px 0; color: #fff',
'background:transparent'
);
}
};
</script>
<style lang="scss">
html { overflow: hidden !important; }
.is-fullheight {
min-height: 100vh !important;
height: max-content;
}
.nuxt-app > .section {
min-height: auto !important;
height: auto !important;
}
@import "~/assets/styles/style.scss";
@import "~/assets/styles/icons.min.css";
html {
overflow: hidden !important;
}
.is-fullheight {
min-height: 100vh !important;
height: max-content;
}
.nuxt-app > .section {
min-height: auto !important;
height: auto !important;
}
@import '~/assets/styles/style.scss';
@import '~/assets/styles/icons.min.css';
</style>
<style lang="scss" scoped>
.default-body {
align-items: baseline !important;
}
.scroll-area {
height: 100vh;
}
.default-body {
align-items: baseline !important;
}
.scroll-area {
height: 100vh;
}
</style>

View File

@ -1,5 +1,5 @@
export default function({ store, redirect }) {
// If the user is not authenticated
if (!store.state.user) return redirect('/login');
if (!store.state.user.isAdmin) return redirect('/dashboard');
if (!store.state.auth.user) return redirect('/login');
if (!store.state.auth.user.isAdmin) return redirect('/dashboard');
}

View File

@ -1,6 +1,6 @@
export default function({ store, redirect }) {
// If the user is not authenticated
if (!store.state.loggedIn) {
if (!store.state.auth.loggedIn) {
return redirect('/login');
}
}

View File

@ -6,27 +6,55 @@
<Sidebar />
</div>
<div class="column">
<h2 class="subtitle">Your uploaded files</h2>
<nav class="level">
<div class="level-left">
<div class="level-item">
<h2 class="subtitle">Your uploaded files</h2>
</div>
</div>
<div class="level-right">
<div class="level-item">
<b-field>
<b-input
placeholder="Search"
type="search"/>
<p class="control">
<button
outlined
class="button is-primary">
Search
</button>
</p>
</b-field>
</div>
</div>
</nav>
<hr>
<Grid v-if="count"
:files="files"
:enableSearch="false"
class="grid" />
<b-loading :active="images.isLoading" />
<b-pagination
v-if="count > perPage"
:total="count"
:per-page="perPage"
:current.sync="current"
class="pagination"
icon-prev="icon-interface-arrow-left"
icon-next="icon-interface-arrow-right"
icon-pack="icon"
aria-next-label="Next page"
aria-previous-label="Previous page"
aria-page-label="Page"
aria-current-label="Current page" />
<Grid v-if="totalFiles"
:files="images.files"
:enableSearch="false"
class="grid">
<template v-slot:pagination>
<b-pagination
v-if="shouldPaginate"
:total="totalFiles"
:per-page="limit"
:current.sync="current"
range-before="2"
range-after="2"
class="pagination-slot"
icon-prev="icon-interface-arrow-left"
icon-next="icon-interface-arrow-right"
icon-pack="icon"
aria-next-label="Next page"
aria-previous-label="Previous page"
aria-page-label="Page"
aria-current-label="Current page" />
</template>
</Grid>
</div>
</div>
</div>
@ -34,6 +62,8 @@
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
import Sidebar from '~/components/sidebar/Sidebar.vue';
import Grid from '~/components/grid/Grid.vue';
@ -42,44 +72,34 @@ export default {
Sidebar,
Grid
},
middleware: 'auth',
middleware: ['auth', ({ store }) => {
store.dispatch('images/fetch');
}],
data() {
return {
files: [],
count: 0,
current: 1,
perPage: 30
current: 1
};
},
computed: {
...mapGetters({
totalFiles: 'images/getTotalFiles',
shouldPaginate: 'images/shouldPaginate',
limit: 'images/getLimit'
}),
...mapState(['images'])
},
metaInfo() {
return { title: 'Uploads' };
},
watch: {
current: 'getFiles'
},
async asyncData({ $axios, route }) {
const perPage = 30;
const current = 1; // current page
try {
const response = await $axios.$get(`files`, { params: { page: current, limit: perPage }});
return {
files: response.files || [],
count: response.count || 0,
current,
perPage
};
} catch (error) {
console.error(error);
return { files: [] };
}
current: 'fetchPaginate'
},
methods: {
async getFiles() {
// TODO: Cache a few pages once fetched
const response = await this.$axios.$get(`files`, { params: { page: this.current, limit: this.perPage }});
this.files = response.files;
this.count = response.count;
...mapActions({
fetch: 'images/fetch'
}),
fetchPaginate() {
this.fetch(this.current)
}
}
};
@ -89,4 +109,14 @@ export default {
div.grid {
margin-bottom: 1rem;
}
</style>
.pagination-slot {
padding: 1rem 0;
}
</style>
<style lang="scss">
.pagination-slot > .pagination-previous, .pagination-slot > .pagination-next {
display: none !important;
}
</style>

View File

@ -28,6 +28,8 @@
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import Logo from '~/components/logo/Logo.vue';
import Uploader from '~/components/uploader/Uploader.vue';
import Links from '~/components/home/links/Links.vue';
@ -43,12 +45,8 @@ export default {
return { albums: [] };
},
computed: {
loggedIn() {
return this.$store.state.loggedIn;
},
config() {
return this.$store.state.config;
}
...mapGetters({ loggedIn: 'auth/isLoggedIn' }),
...mapState(['config'])
}
};
</script>

View File

@ -63,6 +63,8 @@
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'Login',
data() {
@ -74,40 +76,31 @@ export default {
isLoading: false
};
},
computed: {
config() {
return this.$store.state.config;
}
},
computed: mapState(['config', 'auth']),
metaInfo() {
return { title: 'Login' };
},
created() {
if (this.auth.loggedIn) {
this.redirect();
}
},
methods: {
async login() {
if (this.isLoading) return;
if (!this.username || !this.password) {
this.$store.dispatch('alert', {
if (this.auth.isLoading) return;
const { username, password } = this;
if (!username || !password) {
this.$store.dispatch('alert/set', {
text: 'Please fill both fields before attempting to log in.',
error: true
});
return;
}
this.isLoading = true;
try {
const data = await this.$axios.$post(`auth/login`, {
username: this.username,
password: this.password
});
this.$axios.setToken(data.token, 'Bearer');
document.cookie = `token=${encodeURIComponent(data.token)}`;
this.$store.dispatch('login', { token: data.token, user: data.user });
await this.$store.dispatch('auth/login', { username, password });
if (this.auth.loggedIn) {
this.redirect();
} catch (error) {
//
} finally {
this.isLoading = false;
}
},
/*
@ -126,7 +119,7 @@ export default {
});
},*/
redirect() {
this.$store.commit('loggedIn', true);
console.log('redirect');
if (typeof this.$route.query.redirect !== 'undefined') {
this.$router.push(this.$route.query.redirect);
return;

View File

@ -42,6 +42,8 @@
</template>
<script>
import { mapState } from 'vuex';
export default {
name: 'Register',
data() {
@ -52,11 +54,7 @@ export default {
isLoading: false
};
},
computed: {
config() {
return this.$store.state.config;
}
},
computed: mapState(['config', 'auth']),
metaInfo() {
return { title: 'Register' };
},
@ -72,7 +70,7 @@ export default {
}
if (this.password !== this.rePassword) {
this.$store.dispatch('alert', {
text: 'Passwords don\'t match',
text: "Passwords don't match",
error: true
});
return;

View File

@ -1,21 +1,22 @@
export default function({ $axios, store }) {
$axios.setHeader('accept', 'application/vnd.lolisafe.json');
$axios.onRequest(config => {
if (store.state.token) {
config.headers.common['Authorization'] = `bearer ${store.state.token}`;
if (store.state.auth.token) {
config.headers.common['Authorization'] = `bearer ${store.state.auth.token}`;
}
});
$axios.onError(error => {
if (process.env.development) console.error('[AXIOS Error]', error);
if (process.browser) {
store.dispatch('alert', {
store.dispatch('alert/set', {
text: error.response.data.message,
error: true
});
if (error.response.data.message.indexOf('Token expired') !== -1) {
store.dispatch('logout');
store.dispatch('auth/logout');
}
}
});

26
src/site/store/alert.js Normal file
View File

@ -0,0 +1,26 @@
/* eslint-disable no-shadow */
const getDefaultState = () => ({
text: null,
error: false
});
export const state = getDefaultState;
export const actions = {
set({ commit }, data) {
commit('set', data);
},
clear({ commit }) {
commit('clear');
}
};
export const mutations = {
set(state, { text, error }) {
state.text = text;
state.error = error;
},
clear(state) {
Object.assign(state, getDefaultState());
}
};

61
src/site/store/auth.js Normal file
View File

@ -0,0 +1,61 @@
/* eslint-disable no-shadow */
// only used so I could keep the convention of naming the first param as "state" in mutations
const getDefaultState = () => ({
loggedIn: false,
isLoading: false,
user: null,
token: null
});
export const state = getDefaultState;
export const getters = {
isLoggedIn: state => state.loggedIn
};
export const actions = {
async verify({ commit, dispatch }) {
try {
const response = await this.$axios.$get('verify');
commit('loginSuccess', response);
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
}
},
async login({ commit, dispatch }, { username, password }) {
commit('loginRequest');
try {
const data = await this.$axios.$post(`auth/login`, { username, password });
this.$axios.setToken(data.token, 'Bearer');
commit('setToken', data.token);
commit('loginSuccess', { token: data.token, user: data.user });
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
}
},
logout({ commit }) {
commit('logout');
}
};
export const mutations = {
setToken(state, token) {
state.token = token;
},
loginRequest(state) {
state.isLoading = true;
},
loginSuccess(state, { user }) {
this.$cookies.set('token', state.token);
state.user = user;
state.loggedIn = true;
state.isLoading = false;
},
logout(state) {
this.$cookies.remove('token');
// reset state to default
Object.assign(state, getDefaultState());
}
};

19
src/site/store/config.js Normal file
View File

@ -0,0 +1,19 @@
/* eslint-disable no-shadow */
export const state = () => ({
development: true,
version: '4.0.0',
URL: 'http://localhost:8080',
baseURL: 'http://localhost:8080/api',
serviceName: '',
maxFileSize: 100,
chunkSize: 90,
maxLinksPerAlbum: 5,
publicMode: false,
userAccounts: false
});
export const mutations = {
set(state, config) {
Object.assign(state, config);
}
};

56
src/site/store/images.js Normal file
View File

@ -0,0 +1,56 @@
/* eslint-disable no-shadow */
export const state = () => ({
files: [],
isLoading: false,
pagination: {
page: 1,
limit: 30,
totalFiles: 0
}
});
export const getters = {
getTotalFiles: state => state.pagination.totalFiles,
getFetchedCount: state => state.files.length,
shouldPaginate: ({ pagination }) => pagination.totalFiles > pagination.limit,
getLimit: ({ pagination }) => pagination.limit
};
export const actions = {
async fetch({ commit, dispatch, state }, page) {
commit('setIsLoading');
page = page || 1;
try {
const response = await this.$axios.$get(`files`, { params: { limit: state.pagination.limit, page } });
commit('updateFiles', { files: response.files });
commit('updatePaginationMeta', { totalFiles: response.count, page });
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
}
},
async fetchById({ commit, dispatch }) {
try {
const response = await this.$axios.$get('verify');
commit('loginSuccess', response);
} catch (e) {
dispatch('alert/set', { text: e.message, error: true }, { root: true });
}
}
};
export const mutations = {
setIsLoading(state) {
state.isLoading = true;
},
updateFiles(state, { files }) {
state.files = files || [];
state.isLoading = false;
},
updatePaginationMeta(state, { page, totalFiles }) {
state.pagination.page = page || 1;
state.pagination.totalFiles = totalFiles || 0;
}
};

View File

@ -1,66 +1,20 @@
import config from '../../../dist/config.json';
export const state = () => ({
loggedIn: false,
user: null,
token: null,
config: null,
alert: null
});
/* eslint-disable no-shadow */
export const mutations = {
loggedIn(state, payload) {
state.loggedIn = payload;
},
user(state, payload) {
state.user = payload;
},
token(state, payload) {
state.token = payload;
},
config(state, payload) {
state.config = payload;
},
alert(state, payload) {
state.alert = payload;
}
};
export const actions = {
async nuxtClientInit({ commit, dispatch }, { app, req }) {
commit('config', config);
async nuxtClientInit({ commit, dispatch }) {
commit('config/set', config);
const cookies = this.$cookies.getAll();
if (!cookies.token) return dispatch('logout');
if (!cookies.token) return dispatch('auth/logout');
commit('token', cookies.token);
try {
const response = await this.$axios.$get('verify');
dispatch('login', {
token: cookies.token,
user: response.user
});
} catch (error) {
// dispatch('logout');
}
},
login({ commit }, { token, user }) {
this.$cookies.set('token', token);
commit('token', token);
commit('user', user);
commit('loggedIn', true);
},
logout({ commit }) {
this.$cookies.remove('token');
commit('token', null);
commit('user', null);
commit('loggedIn', false);
},
alert({ commit }, payload) {
commit('auth/setToken', cookies.token);
await dispatch('auth/verify');
}
/* alert({ commit }, payload) {
if (!payload) return commit('alert', null);
commit('alert', {
text: payload.text,
error: payload.error
});
}
} */
};