Client - display page error if can not fetch app config

This commit is contained in:
Sam 2021-08-15 16:40:11 +02:00
parent 0b95b93e06
commit 9fc70fcf23
14 changed files with 174 additions and 3 deletions

View File

@ -1,30 +1,51 @@
<template>
<NavBar />
<div class="app-container">
<router-view />
<div v-if="appLoading" class="app-container">
<div class="app-loading">
<Loader />
</div>
</div>
<div v-else class="app-container">
<router-view v-if="appConfig" />
<NoConfig v-else />
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent, onBeforeMount } from 'vue'
import { computed, ComputedRef, defineComponent, onBeforeMount } from 'vue'
import { ROOT_STORE } from '@/store/constants'
import { useStore } from '@/use/useStore'
import { IAppConfig } from '@/store/modules/root/interfaces'
import Footer from '@/components/Footer.vue'
import Loader from '@/components/Common/Loader.vue'
import NavBar from '@/components/NavBar.vue'
import NoConfig from '@/components/NoConfig.vue'
export default defineComponent({
name: 'App',
components: {
Footer,
Loader,
NavBar,
NoConfig,
},
setup() {
const store = useStore()
const appConfig: ComputedRef<IAppConfig> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_CONFIG]
)
const appLoading: ComputedRef<boolean> = computed(
() => store.getters[ROOT_STORE.GETTERS.APP_LOADING]
)
onBeforeMount(() =>
store.dispatch(ROOT_STORE.ACTIONS.GET_APPLICATION_CONFIG)
)
return {
appConfig,
appLoading,
}
},
})
</script>
@ -33,5 +54,11 @@
@import '~@/scss/base';
.app-container {
height: $app-height;
.app-loading {
display: flex;
align-items: center;
height: 100%;
}
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<div class="loader" />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Loader',
})
</script>
<style scoped lang="scss">
@import '~@/scss/base';
.loader {
animation: spin 2s linear infinite;
border: 14px solid var(--app-loading-color);
border-top: 14px solid var(--app-loading-top-color);
border-radius: 50%;
height: 60px;
margin-left: 41%;
width: 60px;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<div id="no-config">
<div class="error-page">
<div class="error-img">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -51 512 512">
<g id="error">
<path
class="error-page-img"
d="M 0 0 C 0 11.300781 0 399.777344 0 410 L 512 410 C 512 402.324219 512 2.425781 512 0 Z M 370 71 L 370 30 L 411 30 L 411 71 Z M 30 30 L 340 30 L 340 71 L 30 71 Z M 482 380 L 30 380 L 30 101 L 482 101 Z M 441 71 L 441 30 L 482 30 L 482 71 Z M 441 71 "
/>
<path
class="error-page-img"
d="M 325.519531 297.070312 C 294.328125 265.878906 294.328125 215.125 325.519531 183.929688 L 304.304688 162.71875 C 261.417969 205.605469 261.417969 275.390625 304.304688 318.28125 Z M 325.519531 297.070312 "
/>
<path
class="error-page-img"
d="M 197.089844 180 L 237.089844 180 L 237.089844 220 L 197.089844 220 Z M 197.089844 180 "
/>
<path
class="error-page-img"
d="M 197.089844 261 L 237.089844 261 L 237.089844 301 L 197.089844 301 Z M 197.089844 261 "
/>
</g>
</svg>
</div>
<p class="error-message" v-html="$t('error.APP_ERROR')" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
export default defineComponent({
name: 'NoConfig',
setup() {
const { t } = useI18n()
return { t }
},
})
</script>
<style scoped lang="scss">
@import '~@/scss/base';
#no-config {
display: flex;
align-items: center;
justify-content: space-around;
padding: $default-padding;
height: 100%;
.error-page {
display: flex;
flex-direction: column;
align-items: center;
max-width: 100%;
.error-img {
width: 150px;
svg {
.error-page-img {
stroke: none;
fill-rule: nonzero;
fill: var(--app-color);
filter: drop-shadow(10px 10px 10px var(--app-shadow-color));
}
}
}
.error-message {
font-size: 1.2em;
text-align: center;
@media screen and (max-width: $medium-limit) {
font-size: 1em;
}
}
}
}
</style>

View File

@ -2,6 +2,7 @@
"ERROR": {
"UNKNOWN": "Error. Please try again or contact the administrator.",
"Invalid credentials": "Invalid credentials.",
"Network Error": "Network Error",
"Password and password confirmation don't match": "Password and password confirmation don't match.",
"Password: 8 characters required": "Password: 8 characters required.",
"Username: 3 to 12 characters required": "Username: 3 to 12 characters required.",

View File

@ -1,5 +1,6 @@
{
"UNKNOWN": "Error. Please try again or contact the administrator.",
"APP_ERROR": "The application seems encounter some issues.<br />Please try later or contact the administrator.",
"NOT_FOUND": {
"PAGE": "Page not found"
}

View File

@ -2,6 +2,7 @@
"ERROR": {
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
"Invalid credentials": "Identifiants invalides.",
"Network Error": "Erreur Réseau",
"Password and password confirmation don't match": "Les mots de passe saisis sont différents.",
"Password: 8 characters required": "8 caractères minimum pour le mot de passe.",
"Username: 3 to 12 characters required": "3 à 12 caractères requis pour le nom.",

View File

@ -1,5 +1,6 @@
{
"UNKNOWN": "Erreur. Veuillez réessayer ou contacter l'administrateur.",
"APP_ERROR": "L'application semble rencontrer quelques problèmes.<br />Veuillez réessayer plus tard ou contacter l'administrateur.",
"NOT_FOUND": {
"PAGE": "Page introuvable"
}

View File

@ -3,6 +3,8 @@
--app-color: #2c3e50;
--app-a-color: #40578a;
--app-shadow-color: lightgrey;
--app-loading-color: #f3f3f3;
--app-loading-top-color: var(--app-color);
--input-border-color: #9da3af;

View File

@ -10,6 +10,7 @@ export const actions: ActionTree<IRootState, IRootState> & IRootActions = {
context: ActionContext<IRootState, IRootState>
): void {
context.commit(ROOT_STORE.MUTATIONS.EMPTY_ERROR_MESSAGES)
context.commit(ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_LOADING, true)
authApi
.get('config')
.then((res) => {
@ -23,5 +24,8 @@ export const actions: ActionTree<IRootState, IRootState> & IRootActions = {
}
})
.catch((error) => handleError(context, error))
.finally(() =>
context.commit(ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_LOADING, false)
)
},
}

View File

@ -3,6 +3,8 @@ export enum RootActions {
}
export enum RootGetters {
APP_CONFIG = 'APP_CONFIG',
APP_LOADING = 'APP_LOADING',
ERROR_MESSAGES = 'ERROR_MESSAGES',
LANGUAGE = 'LANGUAGE',
}
@ -11,5 +13,6 @@ export enum RootMutations {
EMPTY_ERROR_MESSAGES = 'EMPTY_ERROR_MESSAGES',
SET_ERROR_MESSAGES = 'SET_ERROR_MESSAGES',
UPDATE_APPLICATION_CONFIG = 'UPDATE_APPLICATION_CONFIG',
UPDATE_APPLICATION_LOADING = 'UPDATE_APPLICATION_LOADING',
UPDATE_LANG = 'UPDATE_LANG',
}

View File

@ -4,6 +4,12 @@ import { ROOT_STORE } from '@/store/constants'
import { IRootState, IRootGetters } from '@/store/modules/root/interfaces'
export const getters: GetterTree<IRootState, IRootState> & IRootGetters = {
[ROOT_STORE.GETTERS.APP_CONFIG]: (state: IRootState) => {
return state.application.config
},
[ROOT_STORE.GETTERS.APP_LOADING]: (state: IRootState) => {
return state.appLoading
},
[ROOT_STORE.GETTERS.ERROR_MESSAGES]: (state: IRootState) => {
return state.errorMessages
},

View File

@ -28,6 +28,7 @@ export interface IRootState {
language: string
errorMessages: string | string[] | null
application: IApplication
appLoading: boolean
}
export interface IRootActions {
@ -37,6 +38,8 @@ export interface IRootActions {
}
export interface IRootGetters {
[ROOT_STORE.GETTERS.APP_CONFIG](state: IRootState): IAppConfig
[ROOT_STORE.GETTERS.APP_LOADING](state: IRootState): boolean
[ROOT_STORE.GETTERS.ERROR_MESSAGES](
state: IRootState
): string | string[] | null

View File

@ -20,6 +20,12 @@ export const mutations: MutationTree<IRootState> & TRootMutations = {
) {
state.application.config = config
},
[ROOT_STORE.MUTATIONS.UPDATE_APPLICATION_LOADING](
state: IRootState,
loading: boolean
) {
state.appLoading = loading
},
[ROOT_STORE.MUTATIONS.UPDATE_LANG](state: IRootState, language: string) {
state.language = language
},

View File

@ -6,4 +6,5 @@ export const state: IRootState = {
language: 'en',
errorMessages: null,
application: <IApplication>{},
appLoading: false,
}