implemented toasts for error messages
This commit is contained in:
parent
79deed25bc
commit
cecda6194d
5 changed files with 86 additions and 5 deletions
|
@ -17,6 +17,7 @@
|
||||||
"dotenv-webpack": "^1.7.0",
|
"dotenv-webpack": "^1.7.0",
|
||||||
"jquery": "^3.4.1",
|
"jquery": "^3.4.1",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.15",
|
||||||
|
"luxon": "^1.21.3",
|
||||||
"node-sass": "^4.13.0",
|
"node-sass": "^4.13.0",
|
||||||
"popper.js": "^1.16.0",
|
"popper.js": "^1.16.0",
|
||||||
"ramda": "^0.26.1",
|
"ramda": "^0.26.1",
|
||||||
|
|
11
src/App.vue
11
src/App.vue
|
@ -3,22 +3,27 @@
|
||||||
<AddItem v-if="addModalOpen" @close="closeAddModal()" isModal="true"/>
|
<AddItem v-if="addModalOpen" @close="closeAddModal()" isModal="true"/>
|
||||||
<Navbar @addClicked="openAddModal()"/>
|
<Navbar @addClicked="openAddModal()"/>
|
||||||
<router-view/>
|
<router-view/>
|
||||||
|
<div aria-live="polite" aria-atomic="true" class="d-flex justify-content-end align-items-start fixed-top mx-1 my-5 py-3" style="min-height: 200px;">
|
||||||
|
<Toast v-for="toast in toasts" :key="toast" :title="toast.title" :message="toast.message" :color="toast.color" @close="removeToast(toast.key)"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
import AddItem from '@/components/AddItem';
|
import AddItem from '@/components/AddItem';
|
||||||
import { mapState } from 'vuex';
|
import Toast from './components/Toast';
|
||||||
|
import { mapState, mapMutations } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
components: { Navbar, AddItem },
|
components: { Toast, Navbar, AddItem },
|
||||||
computed: mapState(['loadedItems', 'layout']),
|
computed: mapState(['loadedItems', 'layout', 'toasts']),
|
||||||
data: () => ({
|
data: () => ({
|
||||||
addModalOpen: false
|
addModalOpen: false
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapMutations(['removeToast']),
|
||||||
openAddModal() {
|
openAddModal() {
|
||||||
this.addModalOpen = true;
|
this.addModalOpen = true;
|
||||||
},
|
},
|
||||||
|
|
48
src/components/Toast.vue
Normal file
48
src/components/Toast.vue
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<template>
|
||||||
|
<div class="toast" :class="color && ('border-' + color)" role="alert" ref="toast" data-autohide="false">
|
||||||
|
<div class="toast-header">
|
||||||
|
<strong class="mr-auto pr-3">{{ title }}</strong>
|
||||||
|
<small>{{ displayTime }}</small>
|
||||||
|
<button type="button" class="ml-2 mb-1 close" @click="close()">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body" v-html="message">{{ message }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import $ from 'jquery';
|
||||||
|
import 'bootstrap/js/dist/toast';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Toast',
|
||||||
|
props: ['title', 'message', 'color'],
|
||||||
|
data: () => ({
|
||||||
|
creationTime: DateTime.local(),
|
||||||
|
displayTime: 'just now',
|
||||||
|
timer: undefined
|
||||||
|
}),
|
||||||
|
mounted() {
|
||||||
|
const { toast } = this.$refs;
|
||||||
|
$(toast).toast('show');
|
||||||
|
this.timer = setInterval(this.updateDisplayTime, 1000);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
const { toast } = this.$refs;
|
||||||
|
$(toast).toast('hide');
|
||||||
|
window.setTimeout(() => {
|
||||||
|
this.$emit('close');
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
updateDisplayTime() {
|
||||||
|
this.displayTime = this.creationTime.toRelative();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -12,16 +12,32 @@ const axios = AxiosBootstrap.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
axios.interceptors.response.use(response => response, error => {
|
axios.interceptors.response.use(response => response, error => {
|
||||||
|
console.log('error interceptor fired');
|
||||||
console.error(error); // todo: toast error
|
console.error(error); // todo: toast error
|
||||||
|
console.log(Object.entries(error));
|
||||||
|
|
||||||
|
if (error.isAxiosError) {
|
||||||
|
const message = `
|
||||||
|
<h3>A HTTP ${error.config.method} request failed.</h3>
|
||||||
|
<p>url: ${error.config.url}</p>
|
||||||
|
<p>timeout: ${!!error.request.timeout}</p>
|
||||||
|
<p>response-body: ${error.response && error.response.body}</p>
|
||||||
|
`;
|
||||||
|
store.commit('createToast', {title: 'HTTP Error', message, color: 'danger'});
|
||||||
|
} else {
|
||||||
|
store.commit('createToast', {title: 'Unknown Error', message: error.toString(), color: 'danger'});
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const store = new Vuex.Store({
|
const store = new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
|
keyIncrement: 0,
|
||||||
events: [],
|
events: [],
|
||||||
layout: 'cards',
|
layout: 'cards',
|
||||||
loadedItems: [],
|
loadedItems: [],
|
||||||
loadedBoxes: [],
|
loadedBoxes: [],
|
||||||
|
toasts: []
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getEventSlug: state => state.route && state.route.params.event? state.route.params.event : state.events.length ? state.events[0].slug : '36C3',
|
getEventSlug: state => state.route && state.route.params.event? state.route.params.event : state.events.length ? state.events[0].slug : '36C3',
|
||||||
|
@ -54,6 +70,13 @@ const store = new Vuex.Store({
|
||||||
},
|
},
|
||||||
appendItem(state, item) {
|
appendItem(state, item) {
|
||||||
state.loadedItems.push(item);
|
state.loadedItems.push(item);
|
||||||
|
},
|
||||||
|
createToast(state, { title, message, color }) {
|
||||||
|
state.toasts.push({ title, message, color, key: state.keyIncrement });
|
||||||
|
state.keyIncrement += 1;
|
||||||
|
},
|
||||||
|
removeToast(state, key) {
|
||||||
|
state.toasts = state.toasts.filter(toast => toast.key !== key);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -103,5 +126,4 @@ export default store;
|
||||||
store.dispatch('loadEvents').then(() =>{
|
store.dispatch('loadEvents').then(() =>{
|
||||||
store.dispatch('loadEventItems');
|
store.dispatch('loadEventItems');
|
||||||
store.dispatch('loadBoxes');
|
store.dispatch('loadBoxes');
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
|
@ -5074,6 +5074,11 @@ lru-cache@^5.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
yallist "^3.0.2"
|
yallist "^3.0.2"
|
||||||
|
|
||||||
|
luxon@^1.21.3:
|
||||||
|
version "1.21.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.21.3.tgz#f1d5c2a7e855d039836cf4954f883ecac8fc4727"
|
||||||
|
integrity sha512-lLRwNcNnkZLuv13A1FUuZRZmTWF7ro2ricYvb0L9cvBYHPvZhQdKwrYnZzi103D2XKmlVmxWpdn2wfIiOt2YEw==
|
||||||
|
|
||||||
make-dir@^2.0.0:
|
make-dir@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||||
|
|
Loading…
Reference in a new issue