Set up a global notification handler & Error view & Modal component

This commit is contained in:
Bubka 2023-09-28 11:27:45 +02:00
parent e37c0c9ea5
commit 73e36edd9c
7 changed files with 198 additions and 12 deletions

View File

@ -1,8 +1,5 @@
import '/resources/js_vue3/assets/app.scss';
// import { createApp } from 'vue'
// import { i18nVue } from 'laravel-vue-i18n'
// import { createPinia } from 'pinia'
import Notifications from '@kyvg/vue3-notification'
import App from './App.vue'
import router from './router'
@ -13,7 +10,7 @@ const app = createApp(App)
// Immutable app properties provided by the laravel blade view
const $2fauth = {
prefix: '2fauth_',
config: window.appConfig, //{"proxyAuth":false,"proxyLogoutUrl":false,"subdirectory":""}
config: window.appConfig,
version: window.appVersion,
isDemoApp: window.isDemoApp,
isTestingApp: window.isTestingApp,
@ -21,13 +18,17 @@ const $2fauth = {
}
app.provide('2fauth', readonly($2fauth))
// Stores
const pinia = createPinia()
pinia.use(({ store }) => {
store.$2fauth = $2fauth;
});
app.use(pinia)
// Router
app.use(router)
// Localization
app.use(i18nVue, {
lang: document.documentElement.lang.substring(0, 2),
resolve: async lang => {
@ -39,26 +40,41 @@ app.use(i18nVue, {
})
app.use(Notifications)
// Components registration
import ResponsiveWidthWrapper from '@/layouts/ResponsiveWidthWrapper.vue'
import FormWrapper from '@/layouts/FormWrapper.vue'
import Footer from '@/layouts/Footer.vue'
import Modal from '@/layouts/Modal.vue'
import VueButton from '@/components/formElements/Button.vue'
import FieldError from '@/components/formElements/FieldError.vue'
import FormField from '@/components/formElements/FormField.vue'
import FormPasswordField from '@/components/formElements/FormPasswordField.vue'
// import FormSelect from './FormSelect'
// import FormSwitch from './FormSwitch'
// import FormToggle from './FormToggle'
// import FormCheckbox from './FormCheckbox'
import FormButtons from '@/components/formElements/FormButtons.vue'
// import Kicker from './Kicker'
// import SettingTabs from './SettingTabs'
// Components registration
app
.component('FontAwesomeIcon', FontAwesomeIcon)
.component('ResponsiveWidthWrapper', ResponsiveWidthWrapper)
.component('FormWrapper', FormWrapper)
.component('VueFooter', Footer)
.component('Modal', Modal)
.component('VueButton', VueButton)
.component('FieldError', FieldError)
.component('FormField', FormField)
.component('FormPasswordField', FormPasswordField)
.component('FormButtons', FormButtons)
// Global error handling
import { useNotifyStore } from '@/stores/notify'
app.config.errorHandler = (err, instance, info) => {
useNotifyStore().error(err)
}
// App mounting
app.mount('#app')
app.mount('#app')

View File

@ -0,0 +1,52 @@
<script setup>
const { notify } = useNotification()
const props = defineProps({
modelValue: Boolean,
closable: {
type: Boolean,
default: true
},
})
const emit = defineEmits(['modalClosed'])
const isActive = computed({
get() {
return props.modelValue
},
set(value) {
emit('modalClosed')
}
})
function closeModal(event) {
if (event) {
notify({ clean: true })
isActive.value = false
}
}
</script>
<template>
<div class="modal modal-otp" v-bind:class="{ 'is-active': isActive }">
<div class="modal-background" @click.stop="closeModal"></div>
<div class="modal-content">
<section class="section">
<div class="columns is-centered">
<div class="column is-three-quarters">
<div class="modal-slot box has-text-centered is-shadowless">
<slot></slot>
</div>
</div>
</div>
</section>
</div>
<div v-if="props.closable" class="fullscreen-footer">
<!-- Close button -->
<button id="btnClose" class="button is-rounded" :class="{'is-dark' : false}" @click.stop="closeModal">
{{ $t('commons.close') }}
</button>
</div>
</div>
</template>

View File

@ -25,10 +25,11 @@ import SettingsOptions from '../views/settings/Options.vue'
// import SettingsWebAuthn from './views/settings/WebAuthn.vue'
// import EditCredential from './views/settings/Credentials/Edit.vue'
// import GeneratePAT from './views/settings/PATokens/Create.vue'
// import Errors from './views/Error.vue'
import Errors from '../views/Error.vue'
import About from '../views/About.vue'
import authGuard from './middlewares/authGuard'
import noEmptyError from './middlewares/noEmptyError'
const router = createRouter({
history: createWebHistory('/'),
@ -61,8 +62,8 @@ const router = createRouter({
{ path: '/webauthn/lost', name: 'webauthn.lost', component: WebauthnLost, meta: { disabledWithAuthProxy: true, showAbout: true } },
// { path: '/webauthn/recover', name: 'webauthn.recover', component: WebauthnRecover, meta: { disabledWithAuthProxy: true, showAbout: true } },
{ path: '/about', name: 'about',component: About, meta: { showAbout: true } },
// { path: '/error', name: 'genericError',component: Errors, props: true },
{ path: '/about', name: 'about', component: About, meta: { showAbout: true } },
{ path: '/error', name: 'genericError', component: Errors, meta: { middlewares: [noEmptyError], err: null } },
// { path: '/404', name: '404',component: Errors, props: true },
// { path: '*', redirect: { name: '404' } },

View File

@ -0,0 +1,8 @@
export default function noEmptyError({ to, next }) {
next()
if (to.params.err == undefined) {
// return to home if no err object is provided to prevent an empty error message
next({ name: 'accounts' });
}
else next()
}

67
resources/js_vue3/stores/notify.js vendored Normal file
View File

@ -0,0 +1,67 @@
import { defineStore } from 'pinia'
import router from '@/router'
const { notify } = useNotification()
export const useNotifyStore = defineStore({
id: 'notify',
state: () => {
return {
err: null,
message: null,
originalMessage: null,
debug: null,
}
},
getters: {
},
actions: {
parseError(err) {
this.$reset
this.err = err
// Hnalde axios response error
if (err.response) {
if (err.response.status === 407) {
this.message = trans('errors.auth_proxy_failed'),
this.originalMessage = trans('errors.auth_proxy_failed_legend')
}
else if (err.response.status === 403) {
this.message = trans('errors.unauthorized'),
this.originalMessage = trans('errors.unauthorized_legend')
}
else if(err.response.data) {
this.message = err.response.data.message,
this.originalMessage = err.response.data.originalMessage ?? null
this.debug = err.response.data.debug ?? null
}
} else {
this.message = err.message
this.debug = err.stack ?? null
}
// else if (err.request) {
//
},
error(err) {
this.parseError(err)
router.push({ name: 'genericError' })
},
info(notification) {
notify({ type: 'is-success', ...notification})
},
warn(notification) {
notify({ type: 'is-warning', ...notification})
},
alert(notification) {
notify({ type: 'is-danger', ...notification})
},
},
})

View File

@ -0,0 +1,41 @@
<script setup>
import { useNotifyStore } from '@/stores/notify'
const errorHandler = useNotifyStore()
const router = useRouter()
const route = useRoute()
const showModal = true
const showDebug = computed(() => process.env.NODE_ENV === 'development')
const props = defineProps({
closable: {
type: Boolean,
default: true
}
})
function exit() {
window.history.length > 1 && route.name !== '404' ? router.go(-1) : router.push({ name: 'accounts' })
}
</script>
<template>
<div class="error-message">
<modal v-model="showModal" :closable="props.closable" @modal-closed="exit">
<div class="error-message" v-if="$route.name == '404'">
<p class="error-404"></p>
<p>{{ $t('errors.resource_not_found') }}</p>
</div>
<div v-else>
<p class="error-generic"></p>
<p>{{ $t('errors.error_occured') }} </p>
<p v-if="errorHandler.message" class="has-text-grey-lighter">{{ errorHandler.message }}</p>
<p v-if="errorHandler.originalMessage" class="has-text-grey-lighter">{{ errorHandler.originalMessage }}</p>
<p v-if="showDebug && errorHandler.debug" class="is-size-7 is-family-code"><br>{{ errorHandler.debug }}
</p>
</div>
</modal>
</div>
</template>

View File

@ -4,15 +4,16 @@
// import { useStorage } from '@vueuse/core'
import Form from '@/components/formElements/Form'
import { useUserStore } from '@/stores/user'
import { useNotifyStore } from '@/stores/notify'
import { useAppSettingsStore } from '@/stores/appSettings'
// import { useRouter } from 'vue-router';
// import { useNotification } from "@kyvg/vue3-notification";
// import { trans } from 'laravel-vue-i18n';
const $2fauth = inject('2fauth')
const { notify } = useNotification()
const router = useRouter()
const user = useUserStore()
const notify = useNotifyStore()
const appSettings = useAppSettingsStore()
const showWebauthnForm = user.preferences.useWebauthnOnly ? true : useStorage($2fauth.prefix + 'showWebauthnForm', true)
const form = reactive(new Form({
@ -43,10 +44,10 @@
})
.catch(error => {
if( error.response.status === 401 ) {
notify({ type: 'is-danger', text: trans('auth.forms.authentication_failed'), duration:-1 })
notify.alert({text: trans('auth.forms.authentication_failed'), duration:5 })
}
else if( error.response.status !== 422 ) {
router.push({ name: 'genericError', params: { err: error.response } });
notify.error(error)
}
});
}