Refactored user store & moved popup logic from App to store

Cleaned up bits of all the components that use these stores.

User store now focuses around keeping track of the authorization token
and the response from /login. When a sucessfull login request is made we
save our new token and username & admin data to the with login(). Since
cookies aren't implemented yet we keep track of the auth_token to make
authroized requests back to the api later.
The username and admin data from within the body of the token is saved
and only cleared on logout().
Since we haven't implemented cookies we persist storage with
localStorage. Whenever we successfully decode and save a token body we
also save the token to localStorage. This is later used by
initFromLocalStorage() to hydrate the store on first page load.

Popup module is for opening and closing the popup, and now moved away
from a inline plugin in App entry. Now handles loading from &
updating query parameters type=movie | show.
The route listens checks if open every navigation and closes popup if it
is.
This commit is contained in:
2022-01-12 22:20:12 +01:00
parent d1cbbfffd8
commit b021882013
14 changed files with 723 additions and 685 deletions

View File

@@ -46,18 +46,13 @@
<router-view class="content" :key="$route.fullPath"></router-view>
<!-- Movie popup that will show above existing rendered content -->
<movie-popup
v-if="moviePopupIsVisible"
:id="popupID"
:type="popupType"
></movie-popup>
<movie-popup></movie-popup>
<darkmode-toggle />
</div>
</template>
<script>
import Vue from "vue";
import NavigationHeader from "@/components/NavigationHeader";
import NavigationIcons from "@/components/NavigationIcons";
import MoviePopup from "@/components/MoviePopup";
@@ -70,37 +65,6 @@ export default {
NavigationIcons,
MoviePopup,
DarkmodeToggle
},
data() {
return {
query: "",
moviePopupIsVisible: false,
popupID: 0,
popupType: "movie"
};
},
created() {
let that = this;
Vue.prototype.$popup = {
get isOpen() {
return that.moviePopupIsVisible;
},
open: (id, type) => {
this.popupID = id || this.popupID;
this.popupType = type || this.popupType;
this.moviePopupIsVisible = true;
console.log("opened");
},
close: () => {
this.moviePopupIsVisible = false;
console.log("closed");
}
};
const movieId = new URLSearchParams(window.location.search).get("movie");
if (movieId) {
this.$popup.open(movieId, "movie");
}
}
};
</script>
@@ -126,7 +90,8 @@ body {
transition: background-color 0.5s ease, color 0.5s ease;
* {
transition: background-color 0.5s ease, color 0.5s ease;
transition: background-color 0.5s ease, color 0.5s ease,
border-color 0.5s ease;
}
&.hidden {
@@ -158,20 +123,6 @@ img {
.wrapper {
position: relative;
}
// .header {
// position: fixed;
// z-index: 15;
// display: flex;
// flex-direction: column;
// @include tablet-min {
// width: calc(100% - 170px);
// margin-left: 95px;
// border-top: 0;
// border-bottom: 0;
// top: 0;
// }
// }
// router view transition
.fade-enter-active,

View File

@@ -1,24 +1,34 @@
import axios from 'axios'
import storage from '@/storage'
import config from '@/config.json'
import path from 'path'
import store from '@/store'
import axios from "axios";
import storage from "@/storage";
import config from "@/config.json";
import path from "path";
import store from "@/store";
const SEASONED_URL = config.SEASONED_URL
const ELASTIC_URL = config.ELASTIC_URL
const ELASTIC_INDEX = config.ELASTIC_INDEX
const token = () => store.getters["user/token"];
const plexId = () => store.getters["user/plexId"];
const AUTHORIZATION_HEADERS = () => {
return {
Authorization: token(),
"Content-Type": "application/json"
};
};
const SEASONED_URL = config.SEASONED_URL;
const ELASTIC_URL = config.ELASTIC_URL;
const ELASTIC_INDEX = config.ELASTIC_INDEX;
// TODO
// - Move autorization token and errors here?
const checkStatusAndReturnJson = (response) => {
const checkStatusAndReturnJson = response => {
if (!response.ok) {
throw resp
throw resp;
}
return response.json()
}
return response.json();
};
// - - - TMDB - - -
// - - - TMDB - - -
/**
* Fetches tmdb movie by id. Can optionally include cast credits in result object.
@@ -26,23 +36,31 @@ const checkStatusAndReturnJson = (response) => {
* @param {boolean} [credits=false] Include credits
* @returns {object} Tmdb response
*/
const getMovie = (id, checkExistance=false, credits=false, release_dates=false) => {
const url = new URL('v2/movie', SEASONED_URL)
url.pathname = path.join(url.pathname, id.toString())
const getMovie = (
id,
checkExistance = false,
credits = false,
release_dates = false
) => {
const url = new URL("v2/movie", SEASONED_URL);
url.pathname = path.join(url.pathname, id.toString());
if (checkExistance) {
url.searchParams.append('check_existance', true)
url.searchParams.append("check_existance", true);
}
if (credits) {
url.searchParams.append('credits', true)
url.searchParams.append("credits", true);
}
if(release_dates) {
url.searchParams.append('release_dates', true)
if (release_dates) {
url.searchParams.append("release_dates", true);
}
return fetch(url.href)
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
.then(resp => resp.json())
.catch(error => { console.error(`api error getting movie: ${id}`); throw error })
}
.catch(error => {
console.error(`api error getting movie: ${id}`);
throw error;
});
};
/**
* Fetches tmdb show by id. Can optionally include cast credits in result object.
@@ -50,20 +68,23 @@ const getMovie = (id, checkExistance=false, credits=false, release_dates=false)
* @param {boolean} [credits=false] Include credits
* @returns {object} Tmdb response
*/
const getShow = (id, checkExistance=false, credits=false) => {
const url = new URL('v2/show', SEASONED_URL)
url.pathname = path.join(url.pathname, id.toString())
const getShow = (id, checkExistance = false, credits = false) => {
const url = new URL("v2/show", SEASONED_URL);
url.pathname = path.join(url.pathname, id.toString());
if (checkExistance) {
url.searchParams.append('check_existance', true)
url.searchParams.append("check_existance", true);
}
if (credits) {
url.searchParams.append('credits', true)
url.searchParams.append("credits", true);
}
return fetch(url.href)
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
.then(resp => resp.json())
.catch(error => { console.error(`api error getting show: ${id}`); throw error })
}
.catch(error => {
console.error(`api error getting show: ${id}`);
throw error;
});
};
/**
* Fetches tmdb person by id. Can optionally include cast credits in result object.
@@ -71,17 +92,20 @@ const getShow = (id, checkExistance=false, credits=false) => {
* @param {boolean} [credits=false] Include credits
* @returns {object} Tmdb response
*/
const getPerson = (id, credits=false) => {
const url = new URL('v2/person', SEASONED_URL)
url.pathname = path.join(url.pathname, id.toString())
const getPerson = (id, credits = false) => {
const url = new URL("v2/person", SEASONED_URL);
url.pathname = path.join(url.pathname, id.toString());
if (credits) {
url.searchParams.append('credits', true)
url.searchParams.append("credits", true);
}
return fetch(url.href)
.then(resp => resp.json())
.catch(error => { console.error(`api error getting person: ${id}`); throw error })
}
.catch(error => {
console.error(`api error getting person: ${id}`);
throw error;
});
};
/**
* Fetches tmdb list by name.
@@ -89,41 +113,39 @@ const getPerson = (id, credits=false) => {
* @param {number} [page=1]
* @returns {object} Tmdb list response
*/
const getTmdbMovieListByName = (name, page=1) => {
const url = new URL('v2/movie/' + name, SEASONED_URL)
url.searchParams.append('page', page)
const headers = { authorization: storage.token }
const getTmdbMovieListByName = (name, page = 1) => {
const url = new URL("v2/movie/" + name, SEASONED_URL);
url.searchParams.append("page", page);
return fetch(url.href, { headers: headers })
.then(resp => resp.json())
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
}
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() }).then(resp =>
resp.json()
);
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
};
/**
* Fetches requested items.
* @param {number} [page=1]
* @returns {object} Request response
*/
const getRequests = (page=1) => {
const url = new URL('v2/request', SEASONED_URL)
url.searchParams.append('page', page)
const headers = { authorization: storage.token }
const getRequests = (page = 1) => {
const url = new URL("v2/request", SEASONED_URL);
url.searchParams.append("page", page);
return fetch(url.href, { headers: headers })
.then(resp => resp.json())
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
}
return fetch(url.href, {
headers: AUTHORIZATION_HEADERS()
}).then(resp => resp.json());
// .catch(error => { console.error(`api error getting list: ${name}, page: ${page}`); throw error })
};
const getUserRequests = (page = 1) => {
const url = new URL("v1/user/requests", SEASONED_URL);
url.searchParams.append("page", page);
const getUserRequests = (page=1) => {
const url = new URL('v1/user/requests', SEASONED_URL)
url.searchParams.append('page', page)
const headers = { authorization: localStorage.getItem('token') }
return fetch(url.href, { headers })
.then(resp => resp.json())
}
return fetch(url.href, {
headers: AUTHORIZATION_HEADERS()
}).then(resp => resp.json());
};
/**
* Fetches tmdb movies and shows by query.
@@ -131,24 +153,27 @@ const getUserRequests = (page=1) => {
* @param {number} [page=1]
* @returns {object} Tmdb response
*/
const searchTmdb = (query, page=1, adult=false, mediaType=null) => {
const url = new URL('v2/search', SEASONED_URL)
if (mediaType != null && ['movie', 'show', 'person'].includes(mediaType)) {
url.pathname += `/${mediaType}`
const searchTmdb = (query, page = 1, adult = false, mediaType = null) => {
const url = new URL("v2/search", SEASONED_URL);
if (mediaType != null && ["movie", "show", "person"].includes(mediaType)) {
url.pathname += `/${mediaType}`;
}
url.searchParams.append('query', query)
url.searchParams.append('page', page)
url.searchParams.append('adult', adult)
url.searchParams.append("query", query);
url.searchParams.append("page", page);
url.searchParams.append("adult", adult);
const headers = { authorization: localStorage.getItem('token') }
return fetch(url.href, { headers })
return fetch(url.href, {
headers: AUTHORIZATION_HEADERS()
})
.then(resp => resp.json())
.catch(error => { console.error(`api error searching: ${query}, page: ${page}`); throw error })
}
.catch(error => {
console.error(`api error searching: ${query}, page: ${page}`);
throw error;
});
};
// - - - Torrents - - -
// - - - Torrents - - -
/**
* Search for torrents by query
@@ -157,15 +182,18 @@ const searchTmdb = (query, page=1, adult=false, mediaType=null) => {
* @returns {object} Torrent response
*/
const searchTorrents = (query, authorization_token) => {
const url = new URL('/api/v1/pirate/search', SEASONED_URL)
url.searchParams.append('query', query)
const url = new URL("/api/v1/pirate/search", SEASONED_URL);
url.searchParams.append("query", query);
const headers = { authorization: storage.token }
return fetch(url.href, { headers: headers })
return fetch(url.href, {
headers: AUTHORIZATION_HEADERS()
})
.then(resp => resp.json())
.catch(error => { console.error(`api error searching torrents: ${query}`); throw error })
}
.catch(error => {
console.error(`api error searching torrents: ${query}`);
throw error;
});
};
/**
* Add magnet to download queue.
@@ -175,28 +203,27 @@ const searchTorrents = (query, authorization_token) => {
* @returns {object} Success/Failure response
*/
const addMagnet = (magnet, name, tmdb_id) => {
const url = new URL('v1/pirate/add', SEASONED_URL)
const url = new URL("v1/pirate/add", SEASONED_URL);
const body = JSON.stringify({
magnet: magnet,
name: name,
tmdb_id: tmdb_id
})
const headers = {
'Content-Type': 'application/json',
authorization: storage.token
}
});
return fetch(url.href, {
method: 'POST',
headers,
body
})
method: "POST",
headers: AUTHORIZATION_HEADERS(),
body
})
.then(resp => resp.json())
.catch(error => { console.error(`api error adding magnet: ${name} ${error}`); throw error })
}
.catch(error => {
console.error(`api error adding magnet: ${name} ${error}`);
throw error;
});
};
// - - - Plex/Request - - -
// - - - Plex/Request - - -
/**
* Request a movie or show from id. If authorization token is included the user will be linked
@@ -206,28 +233,19 @@ const addMagnet = (magnet, name, tmdb_id) => {
* @param {string} [authorization_token] To identify the requesting user
* @returns {object} Success/Failure response
*/
const request = (id, type, authorization_token=undefined) => {
const url = new URL('v2/request', SEASONED_URL)
// url.pathname = path.join(url.pathname, id.toString())
// url.searchParams.append('type', type)
const headers = {
'Authorization': authorization_token,
'Content-Type': 'application/json'
}
const body = {
id: id,
type: type
}
const request = (id, type) => {
const url = new URL("v2/request", SEASONED_URL);
return fetch(url.href, {
method: 'POST',
headers: headers,
body: JSON.stringify(body)
method: "POST",
headers: AUTHORIZATION_HEADERS(),
body: JSON.stringify({ id, type })
})
.then(resp => resp.json())
.catch(error => { console.error(`api error requesting: ${id}, type: ${type}`); throw error })
}
.then(resp => resp.json())
.catch(error => {
console.error(`api error requesting: ${id}, type: ${type}`);
throw error;
});
};
/**
* Check request status by tmdb id and type
@@ -235,186 +253,182 @@ const request = (id, type, authorization_token=undefined) => {
* @param {string} type
* @returns {object} Success/Failure response
*/
const getRequestStatus = (id, type, authorization_token=undefined) => {
const url = new URL('v2/request', SEASONED_URL)
url.pathname = path.join(url.pathname, id.toString())
url.searchParams.append('type', type)
const getRequestStatus = (id, type, authorization_token = undefined) => {
const url = new URL("v2/request", SEASONED_URL);
url.pathname = path.join(url.pathname, id.toString());
url.searchParams.append("type", type);
return fetch(url.href)
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
.then(resp => {
const status = resp.status;
if (status === 200) { return true }
else if (status === 404) { return false }
else {
console.error(`api error getting request status for id ${id} and type ${type}`)
if (status === 200) {
return true;
} else if (status === 404) {
return false;
} else {
console.error(
`api error getting request status for id ${id} and type ${type}`
);
}
})
.catch(err => Promise.reject(err))
}
.catch(err => Promise.reject(err));
};
const watchLink = (title, year, authorization_token=undefined) => {
const url = new URL('v1/plex/watch-link', SEASONED_URL)
url.searchParams.append('title', title)
url.searchParams.append('year', year)
const watchLink = (title, year) => {
const url = new URL("v1/plex/watch-link", SEASONED_URL);
url.searchParams.append("title", title);
url.searchParams.append("year", year);
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
.then(resp => resp.json())
.then(response => response.link);
};
const movieImages = id => {
const url = new URL(`v2/movie/${id}/images`, SEASONED_URL);
const headers = {
'Authorization': authorization_token,
'Content-Type': 'application/json'
}
"Content-Type": "application/json"
};
return fetch(url.href, { headers })
.then(resp => resp.json())
.then(response => response.link)
}
return fetch(url.href, { headers }).then(resp => resp.json());
};
// - - - Seasoned user endpoints - - -
const register = (username, password) => {
const url = new URL('v1/user', SEASONED_URL)
const url = new URL("v1/user", SEASONED_URL);
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
method: "POST",
headers: AUTHORIZATION_HEADERS(),
body: JSON.stringify({ username, password })
}
};
return fetch(url.href, options)
.then(resp => resp.json())
.catch(error => {
console.error('Unexpected error occured before receiving response. Error:', error)
console.error(
"Unexpected error occured before receiving response. Error:",
error
);
// TODO log to sentry the issue here
throw error
})
}
throw error;
});
};
const login = (username, password, throwError=false) => {
const url = new URL('v1/user/login', SEASONED_URL)
const login = (username, password, throwError = false) => {
const url = new URL("v1/user/login", SEASONED_URL);
const options = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
}
};
return fetch(url.href, options)
.then(resp => {
if (resp.status == 200)
return resp.json();
return fetch(url.href, options).then(resp => {
if (resp.status == 200) return resp.json();
if (throwError)
throw resp;
else
console.error("Error occured when trying to sign in.\nError:", resp);
})
}
if (throwError) throw resp;
else console.error("Error occured when trying to sign in.\nError:", resp);
});
};
const getSettings = () => {
const settingsExists = (value) => {
if (value instanceof Object && value.hasOwnProperty('settings'))
const settingsExists = value => {
if (value instanceof Object && value.hasOwnProperty("settings"))
return value;
throw "Settings does not exist in response object.";
}
const commitSettingsToStore = (response) => {
store.dispatch('userModule/setSettings', response.settings)
return response
}
};
const commitSettingsToStore = response => {
store.dispatch("user/setSettings", response.settings);
return response;
};
const url = new URL('v1/user/settings', SEASONED_URL)
const url = new URL("v1/user/settings", SEASONED_URL);
const authorization_token = localStorage.getItem('token')
const headers = authorization_token ? {
'Authorization': authorization_token,
'Content-Type': 'application/json'
} : {}
return fetch(url.href, { headers })
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
.then(resp => resp.json())
.then(settingsExists)
.then(commitSettingsToStore)
.then(response => response.settings)
.catch(error => { console.log('api error getting user settings'); throw error })
}
const updateSettings = (settings) => {
const url = new URL('v1/user/settings', SEASONED_URL)
const authorization_token = localStorage.getItem('token')
const headers = authorization_token ? {
'Authorization': authorization_token,
'Content-Type': 'application/json'
} : {}
.catch(error => {
console.log("api error getting user settings");
throw error;
});
};
const updateSettings = settings => {
const url = new URL("v1/user/settings", SEASONED_URL);
return fetch(url.href, {
method: 'PUT',
headers,
body: JSON.stringify(settings)
})
method: "PUT",
headers: AUTHORIZATION_HEADERS(),
body: JSON.stringify(settings)
})
.then(resp => resp.json())
.catch(error => { console.log('api error updating user settings'); throw error })
}
.catch(error => {
console.log("api error updating user settings");
throw error;
});
};
// - - - Authenticate with plex - - -
const linkPlexAccount = (username, password) => {
const url = new URL('v1/user/link_plex', SEASONED_URL)
const body = { username, password }
const headers = {
'Content-Type': 'application/json',
authorization: storage.token
}
const url = new URL("v1/user/link_plex", SEASONED_URL);
const body = { username, password };
return fetch(url.href, {
method: 'POST',
headers,
method: "POST",
headers: AUTHORIZATION_HEADERS(),
body: JSON.stringify(body)
})
.then(resp => resp.json())
.catch(error => { console.error(`api error linking plex account: ${username}`); throw error })
}
.then(resp => resp.json())
.catch(error => {
console.error(`api error linking plex account: ${username}`);
throw error;
});
};
const unlinkPlexAccount = (username, password) => {
const url = new URL('v1/user/unlink_plex', SEASONED_URL)
const headers = {
'Content-Type': 'application/json',
authorization: storage.token
}
const url = new URL("v1/user/unlink_plex", SEASONED_URL);
return fetch(url.href, {
method: 'POST',
headers
method: "POST",
headers: AUTHORIZATION_HEADERS()
})
.then(resp => resp.json())
.catch(error => { console.error(`api error unlinking plex account: ${username}`); throw error })
}
.then(resp => resp.json())
.catch(error => {
console.error(`api error unlinking plex account: ${username}`);
throw error;
});
};
// - - - User graphs - - -
const fetchChart = (urlPath, days, chartType) => {
const url = new URL('v1/user' + urlPath, SEASONED_URL)
url.searchParams.append('days', days)
url.searchParams.append('y_axis', chartType)
const url = new URL("v1/user" + urlPath, SEASONED_URL);
url.searchParams.append("days", days);
url.searchParams.append("y_axis", chartType);
const authorization_token = localStorage.getItem('token')
const headers = authorization_token ? {
'Authorization': authorization_token,
'Content-Type': 'application/json'
} : {}
return fetch(url.href, { headers })
return fetch(url.href, { headers: AUTHORIZATION_HEADERS() })
.then(resp => resp.json())
.catch(error => { console.log('api error fetching chart'); throw error })
}
.catch(error => {
console.log("api error fetching chart");
throw error;
});
};
// - - - Random emoji - - -
const getEmoji = () => {
const url = new URL('v1/emoji', SEASONED_URL)
const url = new URL("v1/emoji", SEASONED_URL);
return fetch(url.href)
.then(resp => resp.json())
.catch(error => { console.log('api error getting emoji'); throw error })
}
.catch(error => {
console.log("api error getting emoji");
throw error;
});
};
// - - - ELASTIC SEARCH - - -
// This elastic index contains titles mapped to ids. Lightning search
@@ -426,44 +440,41 @@ const getEmoji = () => {
* @param {string} query
* @returns {object} List of movies and shows matching query
*/
const elasticSearchMoviesAndShows = (query) => {
const url = new URL(path.join(ELASTIC_INDEX, '/_search'), ELASTIC_URL)
const headers = {
'Content-Type': 'application/json'
}
const elasticSearchMoviesAndShows = query => {
const url = new URL(path.join(ELASTIC_INDEX, "/_search"), ELASTIC_URL);
const body = {
"sort" : [
{ "popularity" : {"order" : "desc"}},
"_score"
],
"query": {
"bool": {
"should": [{
"match_phrase_prefix": {
"original_name": query
sort: [{ popularity: { order: "desc" } }, "_score"],
query: {
bool: {
should: [
{
match_phrase_prefix: {
original_name: query
}
},
{
match_phrase_prefix: {
original_title: query
}
}
},
{
"match_phrase_prefix": {
"original_title": query
}
}]
]
}
},
"size": 6
}
size: 22
};
return fetch(url.href, {
method: 'POST',
headers: headers,
method: "POST",
headers: AUTHORIZATION_HEADERS(),
body: JSON.stringify(body)
})
.then(resp => resp.json())
.catch(error => { console.log(`api error searching elasticsearch: ${query}`); throw error })
}
.catch(error => {
console.log(`api error searching elasticsearch: ${query}`);
throw error;
});
};
export {
getMovie,
@@ -477,6 +488,7 @@ export {
addMagnet,
request,
watchLink,
movieImages,
getRequestStatus,
linkPlexAccount,
unlinkPlexAccount,
@@ -487,4 +499,4 @@ export {
fetchChart,
getEmoji,
elasticSearchMoviesAndShows
}
};

View File

@@ -3,26 +3,31 @@
<section class="not-found">
<h1 class="not-found__title">Page Not Found</h1>
</section>
<seasoned-button class="button" @click="goBack">go back to previous page</seasoned-button>
<seasoned-button class="button" @click="goBack"
>go back to previous page</seasoned-button
>
</div>
</template>
<script>
import store from '@/store'
import SeasonedButton from '@/components/ui/SeasonedButton'
import { mapActions, mapGetters } from "vuex";
import SeasonedButton from "@/components/ui/SeasonedButton";
export default {
components: { SeasonedButton },
computed: {
...mapGetters("popup", ["isOpen"])
},
methods: {
...mapActions("popup", ["close"]),
goBack() {
this.$router.go(-1)
this.$router.go(-1);
}
},
created() {
if (this.$popup.isOpen == true)
this.$popup.close()
if (this.isOpen) this.close();
}
}
};
</script>
<style lang="scss" scoped>
@@ -47,7 +52,7 @@ export default {
.not-found {
display: flex;
height: calc(100vh - var(--header-size));
background: url('~assets/pulp-fiction.jpg') no-repeat 50% 50%;
background: url("~assets/pulp-fiction.jpg") no-repeat 50% 50%;
background-size: cover;
align-items: center;
flex-direction: column;

View File

@@ -1,64 +1,58 @@
<template>
<div class="movie-popup" @click="$popup.close()">
<div v-if="isOpen" class="movie-popup" @click="close">
<div class="movie-popup__box" @click.stop>
<movie :id="id" :type="type"></movie>
<button class="movie-popup__close" @click="$popup.close()"></button>
<button class="movie-popup__close" @click="close"></button>
</div>
<i class="loader"></i>
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
import Movie from "./Movie";
export default {
props: {
id: {
type: Number,
required: true
},
type: {
type: String,
required: true
components: { Movie },
computed: {
...mapGetters("popup", ["isOpen", "id", "type"])
},
watch: {
isOpen(value) {
value
? document.getElementsByTagName("body")[0].classList.add("no-scroll")
: document
.getElementsByTagName("body")[0]
.classList.remove("no-scroll");
}
},
components: { Movie },
methods: {
...mapActions("popup", ["close", "open"]),
checkEventForEscapeKey(event) {
if (event.keyCode == 27) {
this.$popup.close();
}
},
updateQueryParams(id = false) {
const params = new URLSearchParams(window.location.search);
if (params.has("movie")) {
params.delete("movie");
}
if (id) {
params.append("movie", id);
}
window.history.replaceState(
{},
"search",
`${window.location.protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : ""
}${window.location.pathname}${
params.toString().length ? `?${params}` : ""
}`
);
if (event.keyCode == 27) this.close();
}
},
created() {
this.updateQueryParams(this.id);
const params = new URLSearchParams(window.location.search);
let id = null;
let type = null;
if (params.has("movie")) {
id = Number(params.get("movie"));
type = "movie";
} else if (params.has("show")) {
id = Number(params.get("show"));
type = "show";
}
if (id && type) {
this.open({ id, type });
}
window.addEventListener("keyup", this.checkEventForEscapeKey);
document.getElementsByTagName("body")[0].classList.add("no-scroll");
},
beforeDestroy() {
this.updateQueryParams();
window.removeEventListener("keyup", this.checkEventForEscapeKey);
document.getElementsByTagName("body")[0].classList.remove("no-scroll");
}
};
</script>

View File

@@ -4,7 +4,7 @@
<img
class="movie-item__img"
ref="poster-image"
@click="openMoviePopup(movie.id, movie.type)"
@click="openMoviePopup"
:alt="posterAltText"
:data-src="poster"
src="~assets/placeholder.png"
@@ -27,6 +27,7 @@
</template>
<script>
import { mapActions } from "vuex";
import img from "../directives/v-image";
export default {
@@ -101,8 +102,12 @@ export default {
imageObserver.observe(poster);
},
methods: {
openMoviePopup(id, type) {
this.$popup.open(id, type);
...mapActions("popup", ["open"]),
openMoviePopup() {
this.open({
id: this.movie.id,
type: this.movie.type
});
}
}
};

View File

@@ -1,6 +1,6 @@
<template>
<section class="profile">
<div class="profile__content" v-if="userLoggedIn">
<div class="profile__content" v-if="loggedIn">
<header class="profile__header">
<h2 class="profile__title">{{ emoji }} Welcome {{ username }}</h2>
@@ -12,7 +12,7 @@
showActivity ? "hide activity" : "show activity"
}}</seasoned-button>
<seasoned-button @click="logOut">Log out</seasoned-button>
<seasoned-button @click="_logout">Log out</seasoned-button>
</div>
</header>
@@ -24,7 +24,7 @@
<results-list v-if="results" :results="results" />
</div>
<section class="not-found" v-if="!userLoggedIn">
<section class="not-found" v-if="!loggedIn">
<div class="not-found__content">
<h2 class="not-found__title">Authentication Request Failed</h2>
<router-link :to="{ name: 'signin' }" exact title="Sign in here">
@@ -36,21 +36,19 @@
</template>
<script>
import storage from "@/storage";
import store from "@/store";
import { mapGetters, mapActions } from "vuex";
import ListHeader from "@/components/ListHeader";
import ResultsList from "@/components/ResultsList";
import Settings from "@/components/Settings";
import Activity from "@/components/ActivityPage";
import SeasonedButton from "@/components/ui/SeasonedButton";
import { getEmoji, getUserRequests } from "@/api";
import { getEmoji, getUserRequests, getSettings } from "@/api";
export default {
components: { ListHeader, ResultsList, Settings, Activity, SeasonedButton },
data() {
return {
userLoggedIn: "",
emoji: "",
results: undefined,
totalResults: undefined,
@@ -59,16 +57,17 @@ export default {
};
},
computed: {
...mapGetters("user", ["loggedIn", "username", "settings"]),
resultCount() {
if (this.results === undefined) return;
const loadedResults = this.results.length;
const totalResults = this.totalResults < 10000 ? this.totalResults : "∞";
return `${loadedResults} of ${totalResults} results`;
},
username: () => store.getters["userModule/username"]
}
},
methods: {
...mapActions("user", ["logout", "updateSettings"]),
toggleSettings() {
this.showSettings = this.showSettings ? false : true;
@@ -98,16 +97,20 @@ export default {
this.showActivity = this.showActivity == true ? false : true;
this.updateQueryParams("activity", this.showActivity);
},
logOut() {
this.$router.push("logout");
_logout() {
this.logout();
this.$router.push("home");
}
},
created() {
if (!localStorage.getItem("token")) {
this.userLoggedIn = false;
} else {
this.userLoggedIn = true;
if (!this.settings) {
getSettings().then(resp => {
const { settings } = resp;
if (settings) updateSettings(settings);
});
}
if (this.loggedIn) {
this.showSettings = window.location.toString().includes("settings=true");
this.showActivity = window.location.toString().includes("activity=true");

View File

@@ -1,47 +1,77 @@
<template>
<section class="profile">
<div class="profile__content" v-if="userLoggedIn">
<section class='settings'>
<h3 class='settings__header'>Plex account</h3>
<div class="profile__content" v-if="loggedIn">
<section class="settings">
<h3 class="settings__header">Plex account</h3>
<div v-if="!hasPlexUser">
<span class="settings__info">Sign in to your plex account to get information about recently added movies and to see your watch history</span>
<div v-if="!plexId">
<span class="settings__info"
>Sign in to your plex account to get information about recently
added movies and to see your watch history</span
>
<form class="form">
<seasoned-input placeholder="plex username" icon="Email" :value.sync="plexUsername"/>
<seasoned-input placeholder="plex password" icon="Keyhole" type="password"
:value.sync="plexPassword" @submit="authenticatePlex" />
<seasoned-input
placeholder="plex username"
icon="Email"
:value.sync="plexUsername"
/>
<seasoned-input
placeholder="plex password"
icon="Keyhole"
type="password"
:value.sync="plexPassword"
@submit="authenticatePlex"
/>
<seasoned-button @click="authenticatePlex">link plex account</seasoned-button>
<seasoned-button @click="authenticatePlex"
>link plex account</seasoned-button
>
</form>
</div>
<div v-else>
<span class="settings__info">Awesome, your account is already authenticated with plex! Enjoy viewing your seasoned search history, plex watch history and real-time torrent download progress.</span>
<seasoned-button @click="unauthenticatePlex">un-link plex account</seasoned-button>
<span class="settings__info"
>Awesome, your account is already authenticated with plex! Enjoy
viewing your seasoned search history, plex watch history and
real-time torrent download progress.</span
>
<seasoned-button @click="unauthenticatePlex"
>un-link plex account</seasoned-button
>
</div>
<seasoned-messages :messages.sync="messages" />
<hr class='setting__divider'>
<hr class="setting__divider" />
<h3 class='settings__header'>Change password</h3>
<h3 class="settings__header">Change password</h3>
<form class="form">
<seasoned-input placeholder="new password" icon="Keyhole" type="password"
:value.sync="newPassword" />
<seasoned-input
placeholder="new password"
icon="Keyhole"
type="password"
:value.sync="newPassword"
/>
<seasoned-input placeholder="repeat new password" icon="Keyhole" type="password"
:value.sync="newPasswordRepeat" />
<seasoned-input
placeholder="repeat new password"
icon="Keyhole"
type="password"
:value.sync="newPasswordRepeat"
/>
<seasoned-button @click="changePassword">change password</seasoned-button>
<seasoned-button @click="changePassword"
>change password</seasoned-button
>
</form>
<hr class='setting__divider'>
<hr class="setting__divider" />
</section>
</div>
<section class="not-found" v-else>
<div class="not-found__content">
<h2 class="not-found__title">Authentication Request Failed</h2>
<router-link :to="{name: 'signin'}" exact title="Sign in here">
<router-link :to="{ name: 'signin' }" exact title="Sign in here">
<button class="not-found__button button">Sign In</button>
</router-link>
</div>
@@ -50,82 +80,68 @@
</template>
<script>
import store from '@/store'
import storage from '@/storage'
import SeasonedInput from '@/components/ui/SeasonedInput'
import SeasonedButton from '@/components/ui/SeasonedButton'
import SeasonedMessages from '@/components/ui/SeasonedMessages'
import { mapGetters, mapState, mapActions } from "vuex";
import storage from "@/storage";
import SeasonedInput from "@/components/ui/SeasonedInput";
import SeasonedButton from "@/components/ui/SeasonedButton";
import SeasonedMessages from "@/components/ui/SeasonedMessages";
import { getSettings, updateSettings, linkPlexAccount, unlinkPlexAccount } from '@/api'
import { linkPlexAccount, unlinkPlexAccount, getSettings } from "@/api";
export default {
components: { SeasonedInput, SeasonedButton, SeasonedMessages },
data(){
return{
userLoggedIn: '',
data() {
return {
messages: [],
plexUsername: null,
plexPassword: null,
newPassword: null,
newPasswordRepeat: null,
emoji: null
}
};
},
computed: {
hasPlexUser: function() {
return this.settings && this.settings['plex_userid']
},
settings: {
get: () => {
return store.getters['userModule/settings']
},
set: function(newSettings) {
store.dispatch('userModule/setSettings', newSettings)
}
}
...mapGetters("user", ["loggedIn", "plexId", "settings"])
},
methods: {
setValue(l, t) {
this[l] = t
},
...mapActions("user", ["login", "updateSettings"]),
changePassword() {
return
return;
},
created() {
if (!this.settings) {
console.log("settings does not exists.", this.settings);
getSettings().then(resp => {
const { settings } = resp;
if (settings) updateSettings(settings);
});
}
},
async authenticatePlex() {
let username = this.plexUsername
let password = this.plexPassword
let username = this.plexUsername;
let password = this.plexPassword;
const response = await linkPlexAccount(username, password)
const { success, message } = await linkPlexAccount(username, password);
this.messages.push({
type: response.success ? 'success' : 'error',
title: response.success ? 'Authenticated with plex' : 'Something went wrong',
message: response.message
})
if (response.success)
getSettings().then(settings => this.settings = settings)
type: success ? "success" : "error",
title: success ? "Authenticated with plex" : "Something went wrong",
message: message
});
},
async unauthenticatePlex() {
const response = await unlinkPlexAccount()
const response = await unlinkPlexAccount();
this.messages.push({
type: response.success ? 'success' : 'error',
title: response.success ? 'Unlinked plex account ' : 'Something went wrong',
type: response.success ? "success" : "error",
title: response.success
? "Unlinked plex account "
: "Something went wrong",
message: response.message
})
if (response.success)
getSettings().then(settings => this.settings = settings)
}
},
created(){
const token = localStorage.getItem('token') || false;
if (token){
this.userLoggedIn = true
});
}
}
}
};
</script>
<style lang="scss" scoped>
@@ -133,7 +149,7 @@ export default {
@import "./src/scss/media-queries";
a {
text-decoration: none;
text-decoration: none;
}
// DUPLICATE CODE
@@ -142,22 +158,22 @@ a {
margin-top: 1rem;
}
&__group{
justify-content: unset;
&__input-icon {
margin-top: 8px;
height: 22px;
width: 22px;
}
&-input {
padding: 10px 5px 10px 45px;
height: 40px;
font-size: 17px;
width: 75%;
@include desktop-min {
width: 400px;
}
}
&__group {
justify-content: unset;
&__input-icon {
margin-top: 8px;
height: 22px;
width: 22px;
}
&-input {
padding: 10px 5px 10px 45px;
height: 40px;
font-size: 17px;
width: 75%;
@include desktop-min {
width: 400px;
}
}
}
}
.settings {
@@ -167,32 +183,32 @@ a {
padding: 1rem;
}
&__header {
margin: 0;
line-height: 16px;
color: $text-color;
font-weight: 300;
margin-bottom: 20px;
text-transform: uppercase;
}
&__info {
display: block;
margin-bottom: 25px;
}
hr {
display: block;
height: 1px;
border: 0;
border-bottom: 1px solid $text-color-50;
margin-top: 30px;
margin-bottom: 70px;
margin-left: 20px;
width: 96%;
text-align: left;
}
span {
font-weight: 200;
size: 16px;
}
&__header {
margin: 0;
line-height: 16px;
color: $text-color;
font-weight: 300;
margin-bottom: 20px;
text-transform: uppercase;
}
&__info {
display: block;
margin-bottom: 25px;
}
hr {
display: block;
height: 1px;
border: 0;
border-bottom: 1px solid $text-color-50;
margin-top: 30px;
margin-bottom: 70px;
margin-left: 20px;
width: 96%;
text-align: left;
}
span {
font-weight: 200;
size: 16px;
}
}
</style>

View File

@@ -2,88 +2,94 @@
<section>
<h1>Sign in</h1>
<seasoned-input placeholder="username"
icon="Email"
type="email"
@enter="submit"
:value.sync="username" />
<seasoned-input placeholder="password" icon="Keyhole" type="password" :value.sync="password" @enter="submit"/>
<seasoned-input
placeholder="username"
icon="Email"
type="email"
@enter="submit"
:value.sync="username"
/>
<seasoned-input
placeholder="password"
icon="Keyhole"
type="password"
:value.sync="password"
@enter="submit"
/>
<seasoned-button @click="submit">sign in</seasoned-button>
<router-link class="link" to="/register">Don't have a user? Register here</router-link>
<router-link class="link" to="/register"
>Don't have a user? Register here</router-link
>
<seasoned-messages :messages.sync="messages"></seasoned-messages>
</section>
</template>
<script>
import { login } from '@/api'
import storage from '../storage'
import SeasonedInput from '@/components/ui/SeasonedInput'
import SeasonedButton from '@/components/ui/SeasonedButton'
import SeasonedMessages from '@/components/ui/SeasonedMessages'
import { parseJwt } from '@/utils'
import { mapActions } from "vuex";
import { login } from "@/api";
import storage from "../storage";
import SeasonedInput from "@/components/ui/SeasonedInput";
import SeasonedButton from "@/components/ui/SeasonedButton";
import SeasonedMessages from "@/components/ui/SeasonedMessages";
export default {
components: { SeasonedInput, SeasonedButton, SeasonedMessages },
data(){
return{
data() {
return {
messages: [],
username: null,
password: null
}
};
},
methods: {
setValue(l, t) {
this[l] = t
},
...mapActions("user", ["login"]),
submit() {
this.messages = [];
let username = this.username;
let password = this.password;
let { username, password } = this;
if (username == null || username.length == 0) {
this.messages.push({ type: 'error', title: 'Missing username' })
return
if (!username || username.length == 0) {
this.messages.push({ type: "error", title: "Missing username" });
return;
}
if (password == null || password.length == 0) {
this.messages.push({ type: 'error', title: 'Missing password' })
return
if (!password || password.length == 0) {
this.messages.push({ type: "error", title: "Missing password" });
return;
}
this.signin(username, password)
this.signin(username, password);
},
signin(username, password) {
login(username, password, true)
.then(data => {
if (data.success){
const jwtData = parseJwt(data.token)
localStorage.setItem('token', data.token);
localStorage.setItem('username', jwtData['username']);
localStorage.setItem('admin', jwtData['admin'] || false);
eventHub.$emit('setUserStatus');
this.$router.push({ name: 'profile' })
if (data.success && this.login(data.token)) {
this.$router.push({ name: "profile" });
}
})
.catch(error => {
if (error.status === 401) {
this.messages.push({ type: 'error', title: 'Access denied', message: 'Incorrect username or password' })
}
else {
this.messages.push({ type: 'error', title: 'Unexpected error', message: error.message })
this.messages.push({
type: "error",
title: "Access denied",
message: "Incorrect username or password"
});
} else {
this.messages.push({
type: "error",
title: "Unexpected error",
message: error.message
});
}
});
}
},
created(){
document.title = 'Sign in' + storage.pageTitlePostfix;
created() {
document.title = "Sign in" + storage.pageTitlePostfix;
storage.backTitle = document.title;
}
}
};
</script>
<style lang="scss" scoped>

View File

@@ -1,28 +1,29 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import axios from 'axios'
import router from './routes'
import store from './store'
import Vue from "vue";
import VueRouter from "vue-router";
import axios from "axios";
import router from "./routes";
import store from "./store";
import Toast from './plugins/Toast'
import DataTablee from 'vue-data-tablee'
import VModal from 'vue-js-modal'
import Toast from "./plugins/Toast";
import DataTablee from "vue-data-tablee";
import VModal from "vue-js-modal";
import App from './App.vue'
import App from "./App.vue";
window.eventHub = new Vue();
Vue.use(VueRouter)
Vue.use(Toast)
Vue.use(DataTablee)
Vue.use(VModal, { dialog: true })
Vue.use(VueRouter);
Vue.use(Toast);
Vue.use(DataTablee);
Vue.use(VModal, { dialog: true });
store.dispatch('darkmodeModule/findAndSetDarkmodeSupported')
store.dispatch("darkmodeModule/findAndSetDarkmodeSupported");
store.dispatch("user/initFromLocalStorage");
new Vue({
el: '#app',
el: "#app",
router,
store,
components: { App },
template: '<App />'
})
template: "<App />"
});

59
src/modules/popup.js Normal file
View File

@@ -0,0 +1,59 @@
const removeIncludedQueryParams = (params, key) => {
if (params.has(key)) params.delete(key);
return params;
};
const updateQueryParams = (id = null, type = null) => {
let params = new URLSearchParams(window.location.search);
params = removeIncludedQueryParams(params, "movie");
params = removeIncludedQueryParams(params, "show");
if (id && type === "movie") {
params.append("movie", id);
}
if (id && type === "show") {
params.append("show", id);
}
let url = `${window.location.protocol}//${window.location.hostname}${
window.location.port ? `:${window.location.port}` : ""
}${window.location.pathname}${params.toString().length ? `?${params}` : ""}`;
window.history.replaceState({}, "search", url);
};
export default {
namespaced: true,
state: {
id: null,
type: null,
open: false
},
getters: {
isOpen: state => state.open,
id: state => state.id,
type: state => state.type
},
mutations: {
SET_OPEN: (state, { id, type }) => {
state.id = id;
state.type = type;
state.open = true;
},
SET_CLOSE: state => {
state.id = null;
state.type = null;
state.open = false;
}
},
actions: {
open: ({ commit }, { id, type = "movie" }) => {
commit("SET_OPEN", { id, type });
updateQueryParams(id, type);
},
close: ({ commit }) => {
commit("SET_CLOSE");
updateQueryParams(); // reset
}
}
};

103
src/modules/user.js Normal file
View File

@@ -0,0 +1,103 @@
import { refreshToken } from "@/api";
import { parseJwt } from "@/utils";
function setLocalStorageByKey(key, value) {
if (value instanceof Object || value instanceof Array) {
value = JSON.stringify(value);
}
const buff = Buffer.from(value);
const encodedValue = buff.toString("base64");
localStorage.setItem(key, encodedValue);
}
function getLocalStorageByKey(key) {
const encodedValue = localStorage.getItem(key);
if (encodedValue == null) {
return null;
}
const buff = new Buffer(encodedValue, "base64");
const value = buff.toString("utf-8");
try {
return JSON.parse(value);
} catch {
return value;
}
}
export default {
namespaced: true,
state: {
token: null,
admin: false,
settings: null,
username: null
},
getters: {
username: state => state.username,
settings: state => state.settings,
token: state => state.token,
loggedIn: state => state && state.username !== null,
admin: state => state && state.admin !== null,
plexId: state => {
if (state && state.settings && state.settings.plex_userid)
return state.settings.plex_userid;
return null;
}
},
mutations: {
SET_TOKEN: (state, token) => (state.token = token),
SET_USERNAME: (state, username) => (state.username = username),
SET_SETTINGS: (state, settings) => (state.settings = settings),
SET_ADMIN: (state, admin) => (state.admin = admin),
LOGOUT: state => {
state.token = null;
state.username = null;
state.settings = null;
state.admin = false;
localStorage.removeItem("token");
}
},
actions: {
initFromLocalStorage: async ({ dispatch }) => {
const token = getLocalStorageByKey("token");
if (token) await dispatch("setupStateFromToken", token);
const settings = getLocalStorageByKey("settings");
if (settings) await dispatch("setSettings", settings);
},
setupStateFromToken: ({ commit }, token) => {
try {
const jwtData = parseJwt(token);
const { username, admin } = jwtData;
if (!username) {
return false;
}
commit("SET_TOKEN", token);
commit("SET_USERNAME", username);
commit("SET_ADMIN", admin != undefined ? true : false);
return true;
} catch (error) {
console.log("Unable to parse JWT, failed with error:", error);
return false;
}
},
setSettings: ({ commit }, settings) => {
if (!(settings instanceof Object)) {
throw "Parameter is not a object.";
}
commit("SET_SETTINGS", settings);
setLocalStorageByKey("settings", settings);
},
logout: ({ commit }) => commit("LOGOUT"),
login: async ({ dispatch }, token) => {
const loggedIn = await dispatch("setupStateFromToken", token);
if (loggedIn) setLocalStorageByKey("token", token);
return loggedIn;
}
}
};

View File

@@ -1,112 +0,0 @@
import { getSettings } from '@/api'
function setLocalStorageByKey(key, value) {
if (value instanceof Object || value instanceof Array) {
value = JSON.stringify(value)
}
const buff = Buffer.from(value)
const encodedValue = buff.toString('base64')
localStorage.setItem(key, encodedValue)
}
function getLocalStorageByKey(key) {
const encodedValue = localStorage.getItem(key)
if (encodedValue == null) {
return undefined
}
const buff = new Buffer(encodedValue, 'base64')
const value = buff.toString('utf-8')
try {
return JSON.parse(value)
} catch {
return value
}
}
const ifMissingSettingsAndTokenExistsFetchSettings =
() => getLocalStorageByKey('token') ? getSettings() : null
export default {
namespaced: true,
state: {
admin: false,
settings: undefined,
username: undefined,
plex_userid: undefined
},
getters: {
admin: (state) => {
return state.admin
},
settings: (state, foo, bar) => {
console.log('is this called?')
const settings = state.settings || getLocalStorageByKey('settings')
if (settings instanceof Object) {
return settings
}
ifMissingSettingsAndTokenExistsFetchSettings()
return undefined
},
username: (state) => {
const settings = state.settings || getLocalStorageByKey('settings')
if (settings instanceof Object && settings.hasOwnProperty('user_name')) {
return settings.user_name
}
ifMissingSettingsAndTokenExistsFetchSettings()
return undefined
},
plex_userid: (state) => {
const settings = state.settings || getLocalStorageByKey('settings')
console.log('plex_userid from store', settings)
if (settings instanceof Object && settings.hasOwnProperty('plex_userid')) {
return settings.plex_userid
}
ifMissingSettingsAndTokenExistsFetchSettings()
return undefined
},
isPlexAuthenticated: (state) => {
const settings = state.settings || getLocalStorageByKey('settings')
if (settings == null)
return false
const hasPlexId = settings['plex_userid']
return hasPlexId != null ? true : false
}
},
mutations: {
SET_ADMIN: (state, isAdmin) => {
state.admin = isAdmin
},
SET_USERNAME: (state, username) => {
state.username = username
console.log('username')
setLocalStorageByKey('username', username)
},
SET_SETTINGS: (state, settings) => {
state.settings = settings
console.log('settings')
setLocalStorageByKey('settings', settings)
}
},
actions: {
setAdmin: ({commit}, isAdmin) => {
if (!(isAdmin instanceof Object)) {
throw "Parameter is not a boolean value."
}
commit('SET_ADMIN', isAdmin)
},
setSettings: ({commit}, settings) => {
console.log('settings input', settings)
if (!(settings instanceof Object)) {
throw "Parameter is not a object."
}
commit('SET_SETTINGS', settings)
}
}
}

View File

@@ -66,17 +66,6 @@ let routes = [
path: "/404",
component: resolve => require(["./components/404.vue"], resolve)
},
{
name: "logout",
path: "/logout",
component: {
template: "<div></div>",
created() {
localStorage.clear();
this.$router.push({ name: "home" });
}
}
},
{
path: "*",
redirect: "/"
@@ -94,8 +83,12 @@ const router = new VueRouter({
linkActiveClass: "is-active"
});
const loggedIn = () => store.getters["user/loggedIn"];
const isOpen = () => store.getters["user/isOpen"];
router.beforeEach((to, from, next) => {
store.dispatch("documentTitle/updateTitle", to.name);
if (isOpen()) store.dispatch("popup/close");
// Toggle mobile nav
if (document.querySelector(".nav__hamburger--active")) {
@@ -106,7 +99,7 @@ router.beforeEach((to, from, next) => {
}
if (to.matched.some(record => record.meta.requiresAuth)) {
if (localStorage.getItem("token") == null) {
if (!loggedIn) {
next({ path: "/signin" });
}
}

View File

@@ -1,20 +1,22 @@
import Vue from 'vue'
import Vuex from 'vuex'
import Vue from "vue";
import Vuex from "vuex";
import darkmodeModule from './modules/darkmodeModule'
import documentTitle from './modules/documentTitle'
import torrentModule from './modules/torrentModule'
import userModule from './modules/userModule'
import darkmodeModule from "./modules/darkmodeModule";
import documentTitle from "./modules/documentTitle";
import torrentModule from "./modules/torrentModule";
import user from "./modules/user";
import popup from "./modules/popup";
Vue.use(Vuex)
Vue.use(Vuex);
const store = new Vuex.Store({
modules: {
darkmodeModule,
documentTitle,
torrentModule,
userModule
user,
popup
}
})
});
export default store
export default store;