faszom
This commit is contained in:
parent
bd6f4f3827
commit
53e8aa9a9c
@ -0,0 +1 @@
|
|||||||
|
VUE_APP_API_LOCATION=http://localhost:5000/
|
@ -0,0 +1 @@
|
|||||||
|
VUE_APP_API_LOCATION=https://spoton.k8s.kmlabz.com/api/
|
108
package-lock.json
generated
108
package-lock.json
generated
@ -1105,6 +1105,61 @@
|
|||||||
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
|
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@nuxt/opencollective": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-XG7rUdXG9fcafu9KTDIYjJSkRO38EwjlKYIb5TQ/0WDbiTUTtUtgncMscKOYzfsY86kGs05pAuMOR+3Fi0aN3A==",
|
||||||
|
"requires": {
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"consola": "^2.15.0",
|
||||||
|
"node-fetch": "^2.6.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"@soda/friendly-errors-webpack-plugin": {
|
"@soda/friendly-errors-webpack-plugin": {
|
||||||
"version": "1.7.1",
|
"version": "1.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.1.tgz",
|
||||||
@ -2472,6 +2527,14 @@
|
|||||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
|
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.0.tgz",
|
||||||
|
"integrity": "sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"babel-eslint": {
|
"babel-eslint": {
|
||||||
"version": "10.1.0",
|
"version": "10.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
|
||||||
@ -2696,6 +2759,23 @@
|
|||||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
|
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"bootstrap": {
|
||||||
|
"version": "4.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.5.3.tgz",
|
||||||
|
"integrity": "sha512-o9ppKQioXGqhw8Z7mah6KdTYpNQY//tipnkxppWhPbiSWdD+1raYsnhwEZjkTHYbGee4cVQ0Rx65EhOY/HNLcQ=="
|
||||||
|
},
|
||||||
|
"bootstrap-vue": {
|
||||||
|
"version": "2.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bootstrap-vue/-/bootstrap-vue-2.19.0.tgz",
|
||||||
|
"integrity": "sha512-IjAXUSrRU5Qu9x3uwUcoj6LtysKbCVeWoJOsODyI/WokStUr95M+tTIajXUjIrB/Nsk0fS+RNvZnm2sWeNFrhg==",
|
||||||
|
"requires": {
|
||||||
|
"@nuxt/opencollective": "^0.3.2",
|
||||||
|
"bootstrap": ">=4.5.3 <5.0.0",
|
||||||
|
"popper.js": "^1.16.1",
|
||||||
|
"portal-vue": "^2.1.7",
|
||||||
|
"vue-functional-data-merge": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"brace-expansion": {
|
"brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
@ -3545,6 +3625,11 @@
|
|||||||
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
|
"integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"consola": {
|
||||||
|
"version": "2.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.0.tgz",
|
||||||
|
"integrity": "sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ=="
|
||||||
|
},
|
||||||
"console-browserify": {
|
"console-browserify": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
||||||
@ -5435,8 +5520,7 @@
|
|||||||
"follow-redirects": {
|
"follow-redirects": {
|
||||||
"version": "1.13.0",
|
"version": "1.13.0",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
|
||||||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
|
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"for-in": {
|
"for-in": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
@ -7440,6 +7524,11 @@
|
|||||||
"lower-case": "^1.1.1"
|
"lower-case": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||||
|
},
|
||||||
"node-forge": {
|
"node-forge": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||||
@ -8105,6 +8194,16 @@
|
|||||||
"ts-pnp": "^1.1.6"
|
"ts-pnp": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"popper.js": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
|
||||||
|
},
|
||||||
|
"portal-vue": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/portal-vue/-/portal-vue-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g=="
|
||||||
|
},
|
||||||
"portfinder": {
|
"portfinder": {
|
||||||
"version": "1.0.28",
|
"version": "1.0.28",
|
||||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||||
@ -10861,6 +10960,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"vue-functional-data-merge": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-leT4kdJVQyeZNY1kmnS1xiUlQ9z1B/kdBFCILIjYYQDqZgLqCLa0UhjSSeRX6c3mUe6U5qYeM8LrEqkHJ1B4LA=="
|
||||||
|
},
|
||||||
"vue-hot-reload-api": {
|
"vue-hot-reload-api": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.21.0",
|
||||||
|
"bootstrap": "^4.5.3",
|
||||||
|
"bootstrap-vue": "^2.19.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-router": "^3.2.0",
|
"vue-router": "^3.2.0",
|
||||||
|
68
src/App.vue
68
src/App.vue
@ -1,32 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app" v-if="$store.state.appReady">
|
||||||
<div id="nav">
|
|
||||||
<router-link to="/">Home</router-link> |
|
<navbar/>
|
||||||
<router-link to="/about">About</router-link>
|
|
||||||
</div>
|
<div id="content">
|
||||||
<router-view/>
|
<router-view/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="footer">
|
||||||
|
<footer-nav/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div id="app-loader" v-else>
|
||||||
|
<loading-screen/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FooterNav from "@/components/FooterNav";
|
||||||
|
import Navbar from "@/components/Navbar";
|
||||||
|
import LoadingScreen from "@/components/LoadingScreen";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
components: {
|
||||||
|
Navbar,
|
||||||
|
FooterNav,
|
||||||
|
LoadingScreen
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
// The basic app is created... Currently showing a loading screen (as soon as mounted)
|
||||||
|
this.$store.dispatch('storeUserData','testuser').then(() => {
|
||||||
|
this.$store.dispatch('setAppReady');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
#content {
|
||||||
|
margin-top: 70px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
display: flex;
|
||||||
-webkit-font-smoothing: antialiased;
|
flex-direction: column;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
min-height: 100vh;
|
||||||
text-align: center;
|
|
||||||
color: #2c3e50;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav {
|
#footer {
|
||||||
padding: 30px;
|
padding: 1em;
|
||||||
|
margin-top: auto;
|
||||||
|
background: #E9ECEF;
|
||||||
}
|
}
|
||||||
|
|
||||||
#nav a {
|
#app-loader {
|
||||||
font-weight: bold;
|
height: 100%;
|
||||||
color: #2c3e50;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav a.router-link-exact-active {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
134
src/api/index.js
134
src/api/index.js
@ -0,0 +1,134 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
const API_BASE_URL = process.env.VUE_APP_API_LOCATION
|
||||||
|
const LOCAL_STORAGE_KEY = "JWT"
|
||||||
|
|
||||||
|
const COMMON_ERROR_CODES = {
|
||||||
|
403: "Access Denied",
|
||||||
|
401: "Authentication failed",
|
||||||
|
400: "Invalid Request",
|
||||||
|
417: "Invalid Request"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new class {
|
||||||
|
|
||||||
|
_setupHTTPObject() {
|
||||||
|
const token = localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||||
|
|
||||||
|
let headers = {}
|
||||||
|
if (token) {
|
||||||
|
headers = {'Authorization': token}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.http = axios.create({
|
||||||
|
baseURL: API_BASE_URL,
|
||||||
|
timeout: 15000, // 15 sec, mert szar a mobilnet
|
||||||
|
headers: headers
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._setupHTTPObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
get haveToken() {
|
||||||
|
return !!localStorage.getItem(LOCAL_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
_performApiCall(method, url, data, precheckToken, expectedStatus, errorTexts = COMMON_ERROR_CODES) {
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
if (precheckToken && !this.haveToken) {
|
||||||
|
return reject({
|
||||||
|
status: null,
|
||||||
|
text: "Not logged in",
|
||||||
|
data: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.http.request({
|
||||||
|
url, method, data,
|
||||||
|
validateStatus(status) {
|
||||||
|
return status === expectedStatus;
|
||||||
|
}
|
||||||
|
}).then((response) => {
|
||||||
|
|
||||||
|
return resolve(response.data);
|
||||||
|
|
||||||
|
}).catch((error) => {
|
||||||
|
|
||||||
|
if (!error.response) { // Network error (CORS?)
|
||||||
|
|
||||||
|
return reject({
|
||||||
|
status: null,
|
||||||
|
text: "Network error",
|
||||||
|
data: null
|
||||||
|
})
|
||||||
|
|
||||||
|
} else { // Server side error
|
||||||
|
|
||||||
|
return reject({
|
||||||
|
status: error.response.status,
|
||||||
|
text: errorTexts[error.response.status] || "Network or server error",
|
||||||
|
data: error.response.data
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
performLogin(name, password) {
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
this._performApiCall('post', '/auth/login', {name, password}, false, 201, {
|
||||||
|
401: "Invalid credentials",
|
||||||
|
...COMMON_ERROR_CODES
|
||||||
|
}).then((data) => {
|
||||||
|
|
||||||
|
localStorage.setItem(LOCAL_STORAGE_KEY, data.token)
|
||||||
|
this._setupHTTPObject() // Update JWT token memes
|
||||||
|
return resolve({name})
|
||||||
|
|
||||||
|
}).catch(reject)
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
performLogout() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
this._performApiCall('delete', '/auth/login', null, true, 204).then(() => {
|
||||||
|
|
||||||
|
localStorage.removeItem(LOCAL_STORAGE_KEY)
|
||||||
|
this._setupHTTPObject() // Update JWT token memes
|
||||||
|
return resolve(null)
|
||||||
|
|
||||||
|
}).catch(reject)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getMyInfo() {
|
||||||
|
return this._performApiCall('get', '/auth/me', null, true, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllLists() {
|
||||||
|
return this._performApiCall('get', '/lists', null, true, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
getList(id) {
|
||||||
|
return this._performApiCall('get', `/lists/${id}`, null, true, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
getTrackFromList(listid, trackid) {
|
||||||
|
return this._performApiCall('get', `/lists/${listid}/${trackid}`, null, true, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/components/FooterNav.vue
Normal file
20
src/components/FooterNav.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<template>
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col class="text-center">
|
||||||
|
<div>Listen to your favorite songs <b>on Spot</b>ify.</div>
|
||||||
|
<div class="text-muted">Copyright 2020 onSpot Team.</div>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "FooterNav"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="hello">
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br>
|
|
||||||
check out the
|
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
|
||||||
</p>
|
|
||||||
<h3>Installed CLI Plugins</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Essential Links</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
|
||||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
|
||||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
|
||||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
|
||||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Ecosystem</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'HelloWorld',
|
|
||||||
props: {
|
|
||||||
msg: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
h3 {
|
|
||||||
margin: 40px 0 0;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,13 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
$END$
|
<div id="loader-content" class="text-center">
|
||||||
|
<p>
|
||||||
|
<b-spinner variant="success" type="grow" />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
onSpot is loading...
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "LoadingScreen"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
#loader-content {
|
||||||
|
margin: 45vh auto 0 auto;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
38
src/components/Navbar.vue
Normal file
38
src/components/Navbar.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<b-navbar toggleable="sm" type="dark" variant="dark" fixed="top">
|
||||||
|
|
||||||
|
<b-navbar-brand>
|
||||||
|
<b>onSpot</b>
|
||||||
|
</b-navbar-brand>
|
||||||
|
|
||||||
|
|
||||||
|
<b-navbar-toggle target="nav-text-collapse"></b-navbar-toggle>
|
||||||
|
|
||||||
|
<b-collapse id="nav-text-collapse" is-nav>
|
||||||
|
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item v-if="$store.getters.isLoggedIn" to="/">My Collections</b-nav-item>
|
||||||
|
<b-nav-item v-else to="/login">Login</b-nav-item>
|
||||||
|
<b-nav-item to="/about">About</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
|
||||||
|
<b-navbar-nav class="ml-auto" v-if="$store.getters.isLoggedIn">
|
||||||
|
<b-nav-item-dropdown :text="$store.state.userdata.name" right>
|
||||||
|
<b-dropdown-text>Lorem Ipsum dolor sit amaet</b-dropdown-text>
|
||||||
|
<b-dropdown-item href="#">Logout</b-dropdown-item>
|
||||||
|
</b-nav-item-dropdown>
|
||||||
|
</b-navbar-nav>
|
||||||
|
|
||||||
|
</b-collapse>
|
||||||
|
</b-navbar>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Navbar"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
16
src/main.js
16
src/main.js
@ -1,9 +1,19 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import App from './App.vue'
|
import App from '@/App.vue'
|
||||||
import router from './router'
|
import router from '@/router'
|
||||||
import store from './store'
|
import store from '@/store'
|
||||||
|
import api from "@/api";
|
||||||
|
|
||||||
|
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
|
||||||
|
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css'
|
||||||
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
Vue.prototype.$api = api
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue)
|
||||||
|
Vue.use(IconsPlugin)
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import VueRouter from 'vue-router'
|
import VueRouter from 'vue-router'
|
||||||
import Home from '../views/Home.vue'
|
import Home from '@/views/Home.vue'
|
||||||
|
import Login from "@/views/Login";
|
||||||
|
|
||||||
|
|
||||||
|
import store from '@/store'
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
|
||||||
@ -8,7 +12,12 @@ const routes = [
|
|||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: Home
|
component: Home,
|
||||||
|
meta: {
|
||||||
|
allowVisit(authorized) {
|
||||||
|
return authorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/about',
|
||||||
@ -16,7 +25,22 @@ const routes = [
|
|||||||
// route level code-splitting
|
// route level code-splitting
|
||||||
// this generates a separate chunk (about.[hash].js) for this route
|
// this generates a separate chunk (about.[hash].js) for this route
|
||||||
// which is lazy-loaded when the route is visited.
|
// which is lazy-loaded when the route is visited.
|
||||||
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
|
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue'),
|
||||||
|
meta: {
|
||||||
|
allowVisit() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'Login',
|
||||||
|
component: Login,
|
||||||
|
meta: {
|
||||||
|
allowVisit(authorized) {
|
||||||
|
return !authorized;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -26,4 +50,24 @@ const router = new VueRouter({
|
|||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
|
||||||
|
const authorized = store.getters.isLoggedIn;
|
||||||
|
|
||||||
|
const visitAllowed = to.matched.some(record => record.meta.allowVisit(authorized))
|
||||||
|
|
||||||
|
if (visitAllowed) {
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authorized) {
|
||||||
|
next({name: 'Home'})
|
||||||
|
} else {
|
||||||
|
next({name: 'Login'})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
@ -5,11 +5,32 @@ Vue.use(Vuex)
|
|||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
|
userdata: {
|
||||||
|
name: null
|
||||||
|
},
|
||||||
|
appReady: false
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
|
storeUserData(state, username) {
|
||||||
|
state.userdata.name = username;
|
||||||
|
},
|
||||||
|
setAppReady(state) {
|
||||||
|
state.appReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
storeUserData({commit}, username) {
|
||||||
|
commit('storeUserData', username);
|
||||||
},
|
},
|
||||||
modules: {
|
setAppReady({commit}) {
|
||||||
|
commit('setAppReady');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modules: {},
|
||||||
|
getters: {
|
||||||
|
isLoggedIn(state) {
|
||||||
|
return !!state.userdata.name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<b-container>
|
||||||
<div class="about">
|
<div class="about">
|
||||||
<h1>This is an about page</h1>
|
<h1>This is an about page</h1>
|
||||||
</div>
|
</div>
|
||||||
|
</b-container>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<b-container>
|
||||||
<div class="home">
|
<div class="home">
|
||||||
<img alt="Vue logo" src="../assets/logo.png">
|
<h1>home</h1>
|
||||||
<HelloWorld msg="Welcome to Your Vue.js App"/>
|
|
||||||
</div>
|
</div>
|
||||||
|
</b-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// @ is an alias to /src
|
|
||||||
import HelloWorld from '@/components/HelloWorld.vue'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
components: {
|
components: {
|
||||||
HelloWorld
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
75
src/views/Login.vue
Normal file
75
src/views/Login.vue
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<template>
|
||||||
|
<b-container>
|
||||||
|
<b-row>
|
||||||
|
<b-col class="mx-auto" cols="12" lg="4" sm="8">
|
||||||
|
<b-card class="mt-5">
|
||||||
|
<div class="my-4 text-center">
|
||||||
|
<b-img src="@/assets/musicbrainz.svg" id="provider-banner"/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
|
||||||
|
<b-form @submit.prevent="performLogin" v-if="true">
|
||||||
|
<b-form-group
|
||||||
|
id="input-group-1"
|
||||||
|
label="Username:"
|
||||||
|
label-for="input-1"
|
||||||
|
>
|
||||||
|
<b-form-input
|
||||||
|
id="input-1"
|
||||||
|
v-model="form.username"
|
||||||
|
required
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
></b-form-input>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<b-form-group id="input-group-2" label="Password:" label-for="input-2"
|
||||||
|
description="Yes, the same one you use on MusicBrainz"
|
||||||
|
>
|
||||||
|
<b-form-input
|
||||||
|
id="input-2"
|
||||||
|
v-model="form.password"
|
||||||
|
required
|
||||||
|
placeholder=""
|
||||||
|
autocomplete="off"
|
||||||
|
type="password"
|
||||||
|
></b-form-input>
|
||||||
|
</b-form-group>
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
<b-button type="submit" variant="primary">Login</b-button>
|
||||||
|
</div>
|
||||||
|
</b-form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</b-card>
|
||||||
|
</b-col>
|
||||||
|
</b-row>
|
||||||
|
</b-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Login",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: {
|
||||||
|
username: "",
|
||||||
|
password: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
performLogin() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#provider-banner {
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
</style>
|
30
vue.config.js
Normal file
30
vue.config.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
module.exports = {
|
||||||
|
publicPath: "/",
|
||||||
|
chainWebpack: config => {
|
||||||
|
config.module
|
||||||
|
.rule('vue')
|
||||||
|
.use('vue-loader')
|
||||||
|
.loader('vue-loader')
|
||||||
|
.tap(options => {
|
||||||
|
options.transformAssetUrls = {
|
||||||
|
img: 'src',
|
||||||
|
image: 'xlink:href',
|
||||||
|
'b-avatar': 'src',
|
||||||
|
'b-img': 'src',
|
||||||
|
'b-img-lazy': ['src', 'blank-src'],
|
||||||
|
'b-card': 'img-src',
|
||||||
|
'b-card-img': 'src',
|
||||||
|
'b-card-img-lazy': ['src', 'blank-src'],
|
||||||
|
'b-carousel-slide': 'img-src',
|
||||||
|
'b-embed': 'src'
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
}),
|
||||||
|
config.plugin('html')
|
||||||
|
.tap(args => {
|
||||||
|
args[0].title = "onSpot"
|
||||||
|
return args
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user