implemented toasts for error messages

This commit is contained in:
busti 2019-12-27 02:06:04 +01:00
parent 79deed25bc
commit cecda6194d
5 changed files with 86 additions and 5 deletions

View file

@ -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",

View file

@ -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
View 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">&times;</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>

View file

@ -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');
} });
);

View file

@ -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"