Use dismissible alerts instead of dialogs to notify about errors

This commit is contained in:
Lucas Verney 2018-11-07 23:18:45 +01:00
parent 22c68454ec
commit 14e28694e0
7 changed files with 98 additions and 97 deletions

View File

@ -57,7 +57,7 @@
</div> </div>
</v-toolbar> </v-toolbar>
<v-content> <v-content>
<ReportErrorModal v-model="hasReportError"></ReportErrorModal> <Alert :error="$t('reportDialog.unableToSendDescription')" :onDismiss="dismissError" v-if="hasReportError"></Alert>
<ShareMapViewModal v-model="isShareMapViewModalShown"></ShareMapViewModal> <ShareMapViewModal v-model="isShareMapViewModalShown"></ShareMapViewModal>
<ReportIssueModal v-model="isReportIssueModalShown"></ReportIssueModal> <ReportIssueModal v-model="isReportIssueModalShown"></ReportIssueModal>
<SearchModal v-model="isSearchModalShown"></SearchModal> <SearchModal v-model="isSearchModalShown"></SearchModal>
@ -71,14 +71,14 @@ import runtime from 'serviceworker-webpack-plugin/lib/runtime';
import { DELAY_BETWEEN_API_BATCH_REQUESTS } from '@/constants'; import { DELAY_BETWEEN_API_BATCH_REQUESTS } from '@/constants';
import ReportErrorModal from '@/components/ReportErrorModal.vue'; import Alert from '@/components/Alert.vue';
import ReportIssueModal from '@/components/ReportIssueModal.vue'; import ReportIssueModal from '@/components/ReportIssueModal.vue';
import SearchModal from '@/components/SearchModal.vue'; import SearchModal from '@/components/SearchModal.vue';
import ShareMapViewModal from '@/components/ShareMapViewModal.vue'; import ShareMapViewModal from '@/components/ShareMapViewModal.vue';
export default { export default {
components: { components: {
ReportErrorModal, Alert,
ReportIssueModal, ReportIssueModal,
SearchModal, SearchModal,
ShareMapViewModal, ShareMapViewModal,
@ -111,6 +111,9 @@ export default {
}; };
}, },
methods: { methods: {
dismissError() {
this.hasReportError = false;
},
exportGPX() { exportGPX() {
import('@/tools/exportGPX' /* webpackChunkName: "MapView" */).then((module) => { import('@/tools/exportGPX' /* webpackChunkName: "MapView" */).then((module) => {
const activityName = this.$t('misc.activityName'); const activityName = this.$t('misc.activityName');

78
src/components/Alert.vue Normal file
View File

@ -0,0 +1,78 @@
<template>
<div class="alert-wrapper">
<v-alert class="alert" type="error" v-model="showAlert" :dismissible="true" transition="slide-y-transition">
{{ error }}
<v-progress-linear
class="progress"
v-model="progressValue"
background-opacity="0"
color="rgba(0,0,0,0.3)"
></v-progress-linear>
</v-alert>
</div>
</template>
<script>
export default {
beforeDestroy() {
this.clearTimer();
},
data() {
return {
interval: null,
progressValue: 100,
showAlert: true,
};
},
methods: {
clearTimer() {
if (this.interval !== null) {
clearInterval(this.interval);
}
},
},
mounted() {
this.interval = setInterval(() => {
this.progressValue -= 4;
if (this.progressValue < 0) {
this.showAlert = false;
this.clearTimer();
}
}, 100);
},
props: {
error: String,
onDismiss: Function,
},
watch: {
showAlert(newAlert) {
if (!newAlert && this.onDismiss) {
this.onDismiss();
}
},
},
};
</script>
<style scoped>
.alert-wrapper {
position: absolute;
top: 0;
z-index: 9999;
width: 100%;
}
.alert {
margin-left: auto;
margin-right: auto;
margin-top: 0;
width: 50%;
}
.progress {
position: absolute;
bottom: 0;
left: 0;
margin-bottom: 0;
}
</style>

View File

@ -1,43 +0,0 @@
<template>
<div>
<p class="text-xs-center">{{ error }}</p>
<p class="text-xs-center">
<v-btn role="button" color="blue" dark @click="retryFunction">{{ $t('misc.retry') }}</v-btn>
</p>
<p>{{ $t('misc.or') }}</p>
<p>
<AddressInput
:label="$t('locationPicker.pickALocationManually')"
:onInput="onManualLocationPicker"
></AddressInput>
</p>
</div>
</template>
<script>
import { DEFAULT_ZOOM } from '@/constants';
import AddressInput from '@/components/AddressInput.vue';
export default {
components: {
AddressInput,
},
methods: {
onManualLocationPicker(value) {
this.$router.push({
name: 'SharedMap',
params: {
lat: value.latlng.lat,
lng: value.latlng.lng,
zoom: DEFAULT_ZOOM,
},
});
},
},
props: {
error: String,
retryFunction: Function,
},
};
</script>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<ReportErrorModal v-model="hasError"></ReportErrorModal> <Alert :error="$t('reportDialog.unableToSendDescription')" :onDismiss="dismissError" v-if="hasError"></Alert>
<v-bottom-sheet v-model="isActive" id="reportCardSheet"> <v-bottom-sheet v-model="isActive" id="reportCardSheet">
<v-card> <v-card>
<v-container fluid> <v-container fluid>
@ -25,7 +25,7 @@
import { REPORT_TYPES_ORDER } from '@/constants'; import { REPORT_TYPES_ORDER } from '@/constants';
import REPORT_TYPES from '@/report-types'; import REPORT_TYPES from '@/report-types';
import ReportErrorModal from '@/components/ReportErrorModal.vue'; import Alert from '@/components/Alert.vue';
import ReportTile from './ReportTile.vue'; import ReportTile from './ReportTile.vue';
export default { export default {
@ -33,7 +33,7 @@ export default {
window.removeEventListener('keydown', this.hideReportDialogOnEsc); window.removeEventListener('keydown', this.hideReportDialogOnEsc);
}, },
components: { components: {
ReportErrorModal, Alert,
ReportTile, ReportTile,
}, },
computed: { computed: {
@ -57,6 +57,9 @@ export default {
}; };
}, },
methods: { methods: {
dismissError() {
this.hasError = false;
},
hideReportDialogOnEsc(event) { hideReportDialogOnEsc(event) {
let isEscape = false; let isEscape = false;
if ('key' in event) { if ('key' in event) {

View File

@ -1,42 +0,0 @@
<template>
<Modal v-model="hasError">
<v-card>
<v-card-title class="subheading">{{ $t('reportDialog.unableToSendTitle') }}</v-card-title>
<v-card-text>{{ $t('reportDialog.unableToSendDescription') }} </v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="hasError = false" large role="button">
{{ $t('misc.ok') }}
</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</Modal>
</template>
<script>
import Modal from '@/components/Modal.vue';
export default {
components: {
Modal,
},
computed: {
hasError: {
get() {
return this.value;
},
set(val) {
this.$emit('input', val);
},
},
},
props: {
value: Boolean,
},
};
</script>

View File

@ -2,6 +2,7 @@
<v-container fluid fill-height class="no-padding"> <v-container fluid fill-height class="no-padding">
<v-layout row wrap fill-height> <v-layout row wrap fill-height>
<ReportCard></ReportCard> <ReportCard></ReportCard>
<Alert :error="error" v-if="error" :onDismiss="clearError"></Alert>
<v-flex xs12 fill-height v-if="mapCenter"> <v-flex xs12 fill-height v-if="mapCenter">
<Map <Map
:accuracy="currentLocation.hdop" :accuracy="currentLocation.hdop"
@ -36,10 +37,6 @@
</v-btn> </v-btn>
<ReportDialog v-model="isReportDialogVisible" :latLng="reportLatLng" :onHide="resetReportLatLng"></ReportDialog> <ReportDialog v-model="isReportDialogVisible" :latLng="reportLatLng" :onHide="resetReportLatLng"></ReportDialog>
</v-flex> </v-flex>
<v-flex xs12 sm6 offset-sm3 md4 offset-md4 fill-height v-else class="pa-3">
<LocationError :error="error" :retryFunction="initializePositionWatching" v-if="error"></LocationError>
<p class="text-xs-center" v-else>{{ $t('geolocation.fetching') }}</p>
</v-flex>
</v-layout> </v-layout>
</v-container> </v-container>
</template> </template>
@ -47,7 +44,7 @@
<script> <script>
import NoSleep from 'nosleep.js'; import NoSleep from 'nosleep.js';
import LocationError from '@/components/LocationError.vue'; import Alert from '@/components/Alert.vue';
import Map from '@/components/Map.vue'; import Map from '@/components/Map.vue';
import ReportCard from '@/components/ReportCard.vue'; import ReportCard from '@/components/ReportCard.vue';
import ReportDialog from '@/components/ReportDialog/index.vue'; import ReportDialog from '@/components/ReportDialog/index.vue';
@ -82,7 +79,7 @@ export default {
this.$store.dispatch('hideReportDetails'); this.$store.dispatch('hideReportDetails');
}, },
components: { components: {
LocationError, Alert,
Map, Map,
ReportCard, ReportCard,
ReportDialog, ReportDialog,
@ -181,6 +178,9 @@ export default {
}; };
}, },
methods: { methods: {
clearError() {
store.dispatch('setLocationError', { error: null });
},
createNotification() { createNotification() {
const $t = this.$t.bind(this); const $t = this.$t.bind(this);
this.notification = new Notification( this.notification = new Notification(

View File

@ -1,5 +1,6 @@
import Vue from 'vue'; import Vue from 'vue';
import Vuetify from 'vuetify/es5/components/Vuetify'; import Vuetify from 'vuetify/es5/components/Vuetify';
import VAlert from 'vuetify/es5/components/VAlert';
import VApp from 'vuetify/es5/components/VApp'; import VApp from 'vuetify/es5/components/VApp';
import VBadge from 'vuetify/es5/components/VBadge'; import VBadge from 'vuetify/es5/components/VBadge';
import VBottomSheet from 'vuetify/es5/components/VBottomSheet'; import VBottomSheet from 'vuetify/es5/components/VBottomSheet';
@ -22,6 +23,7 @@ import VToolbar from 'vuetify/es5/components/VToolbar';
Vue.use(Vuetify, { Vue.use(Vuetify, {
components: { components: {
VAlert,
VApp, VApp,
VBadge, VBadge,
VBottomSheet, VBottomSheet,