Commit f5676d14 by Jonathan Thomas

Removed fetch and replaced with axios (mostly due to simpler syntax, and support…

Removed fetch and replaced with axios (mostly due to simpler syntax, and support for upload progress events). Also added support for custom errors, for failed API calls, failed login, etc... Errors are stateful, and can be dismissed by the user.
parent efde96c4
......@@ -2502,6 +2502,14 @@
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
"dev": true
},
"axios": {
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
"requires": {
"follow-redirects": "^1.14.4"
}
},
"babel-eslint": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz",
......@@ -5546,8 +5554,7 @@
"follow-redirects": {
"version": "1.14.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.6.tgz",
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A==",
"dev": true
"integrity": "sha512-fhUl5EwSJbbl8AR+uYL2KQDxLkdSjZGR36xy46AO7cOMTrCMON6Sa28FmAnC2tRTDbd/Uuzz3aJBv7EBN7JH8A=="
},
"for-in": {
"version": "1.0.2",
......@@ -9412,6 +9419,14 @@
"tough-cookie": "~2.5.0",
"tunnel-agent": "^0.6.0",
"uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"require-directory": {
......@@ -11127,10 +11142,9 @@
"dev": true
},
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"v8-compile-cache": {
"version": "2.3.0",
......@@ -11968,6 +11982,14 @@
"requires": {
"ansi-colors": "^3.0.0",
"uuid": "^3.3.2"
},
"dependencies": {
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
}
}
},
"webpack-merge": {
......
......@@ -9,8 +9,10 @@
},
"dependencies": {
"@popperjs/core": "^2.11.0",
"axios": "^0.24.0",
"bootstrap": "^5.1.3",
"core-js": "^3.6.5",
"uuid": "^8.3.2",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0"
......
......@@ -29,20 +29,34 @@
<!--router content-->
<div class="container">
<!-- show any errors from vuex -->
<div v-for="error in errors" :key="error.id" class="alert alert-danger alert-dismissible fade show" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-exclamation-triangle-fill flex-shrink-0 me-2" viewBox="0 0 16 16" role="img" aria-label="Warning:">
<path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</svg>
{{ error.message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close" @click="removeError(error)"></button>
</div>
<!-- main router content -->
<router-view/>
</div>
</template>
<script>
import { mapGetters, mapActions, mapMutations } from 'vuex'
import { mapGetters, mapActions, mapMutations, mapState } from 'vuex'
export default {
methods: {
removeError(error) {
this.removeError(error)
},
...mapActions(['login']),
...mapMutations(['setUser'])
...mapMutations(['setUser','removeError'])
},
computed: {
...mapGetters(['isAuthenticated'])
...mapGetters(['isAuthenticated']),
...mapState(['errors'])
},
async created() {
// auto-login on subsequent visits
......
......@@ -19,7 +19,7 @@ export default {
video {
background-color: #000000;
width: 100%;
height: 100%;
height: 95%;
}
.export-btn {
float: right;
......
import {create} from "axios";
//import store from './index'
// Init a new axios instance, with auth
const instance = create({
baseURL: 'https://cloud.openshot.org'
});
// Set the AUTH token for any request
instance.interceptors.request.use(function (config) {
const token = localStorage.auth
config.headers.Authorization = token ? token : '';
return config;
});
// Handle authorization errors
instance.interceptors.response.use(
response => {
return response;
},
error => {
if (error.response.status === 401) {
//store.commit('addError', 'Invalid username or password, please login again')
}
return Promise.reject(error)
});
export { instance };
\ No newline at end of file
import { createStore } from 'vuex'
import { v4 as uuidv4 } from "uuid"
import { instance } from "./axios"
export default createStore({
state: {
counter: 1,
projects: {},
user: null
user: null,
errors: []
},
mutations: {
addError(state, message) {
let error = {'id': uuidv4(), 'message': message}
state.errors.push(error)
},
removeError(state, error) {
state.errors = state.errors.filter(err => err.id != error.id )
},
clearErrors(state) {
state.errors = []
},
increment(state, amount) {
state.counter += amount
},
......@@ -26,94 +40,41 @@ export default createStore({
},
actions: {
async login({commit}, auth) {
// Prepare headers & auth
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', auth);
// Prepare request
const url = process.env.VUE_APP_API_URL + "users/";
// Send response
const response = await fetch(url, {
method: 'GET',
headers
});
if (!response.ok) {
// Login failed
commit('setUser', null)
} else {
// Get user object
let response_json = await response.json();
let user_object = response_json.results[0]
try {
const response = await instance.get('/users/')
let user_object = response.data.results[0]
user_object.auth = auth
commit('setUser', user_object);
commit('setUser', user_object)
} catch(err) {
commit('setUser', null)
commit('addError', JSON.stringify(err.response.data))
}
},
async loadProjects({commit, state}) {
// Prepare headers & auth
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', state.user.auth);
// Prepare request
const url = process.env.VUE_APP_API_URL + "projects/";
async loadProjects({commit}) {
// Send response
const response = await fetch(url, {
method: 'GET',
headers
});
if (!response.ok) {
const message = `An error has occurred: ${response.status}`;
throw new Error(message);
try {
const response = await instance.get('/projects/')
commit('setProjects', response.data.results)
} catch(err) {
commit('addError', JSON.stringify(err.response.data))
}
let response_json = await response.json();
commit('setProjects', response_json.results);
},
async createProject({state, dispatch}, payload) {
// Prepare headers & auth
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', state.user.auth);
// Prepare request
const url = process.env.VUE_APP_API_URL + "projects/";
// Send response
const response = await fetch(url, {
method: 'POST',
headers,
body: JSON.stringify(payload)
});
if (!response.ok) {
const message = `An error has occurred: ${response.status}`;
throw new Error(message);
async createProject({dispatch, commit}, payload) {
try {
await instance.post('/projects/', payload)
dispatch('loadProjects')
} catch(err) {
commit('addError', JSON.stringify(err.response.data))
}
// reload projects
dispatch('loadProjects')
},
async deleteProject({state, dispatch}, id) {
// Prepare headers & auth
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', state.user.auth);
// Prepare request
const url = process.env.VUE_APP_API_URL + `projects/${id}/`;
async deleteProject({commit, dispatch}, id) {
// Send response
const response = await fetch(url, {
method: 'DELETE',
headers
});
if (!response.ok) {
const message = `An error has occurred: ${response.status}`;
throw new Error(message);
try {
await instance.delete(`projects/${id}/`)
dispatch('loadProjects')
} catch(err) {
commit('addError', JSON.stringify(err.response.data))
}
// reload projects
dispatch('loadProjects')
}
},
modules: {
......
<template>
<div class="row p-5 justify-content-center">
<div class="col-3">
<div class="col-xs-12 col-md-6 col-lg-4">
<div class="mb-3">
<label class="form-label">Username</label>
<input type="text" class="form-control" v-model="username">
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" class="form-control" v-model="password">
<input type="password" class="form-control" v-model="password" @keyup.enter="loginClick">
</div>
<button type="submit" class="btn btn-primary" @click="loginClick">Login</button>
</div>
......@@ -15,7 +15,7 @@
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { mapActions, mapGetters, mapMutations } from 'vuex'
export default {
name: "Login.vue",
......@@ -27,15 +27,22 @@ export default {
},
methods: {
async loginClick() {
// Clear existing errors
this.clearErrors()
let hash = btoa(`${this.username}:${this.password}`)
let auth = `Basic ${hash}`
localStorage.auth = auth
await this.login(auth)
if (this.isAuthenticated) {
localStorage.auth = auth
this.$router.push('/')
await this.$router.push('/')
} else {
localStorage.removeItem('auth')
}
},
...mapActions(['login'])
...mapActions(['login']),
...mapMutations(['clearErrors'])
},
computed: {
...mapGetters(['isAuthenticated'])
......
......@@ -12,7 +12,7 @@ export default {
// Logout user
this.setUser(null)
this.setProjects(null)
localStorage.auth = null
localStorage.removeItem('auth')
this.$router.push('/login')
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment