error view with multiple states used in forms
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<form class="ui-form" @submit.prevent="onSubmit" @change="onChange">
|
||||
<slot v-bind="slotProps" />
|
||||
<slot v-if="loadingState === 'default'" v-bind="slotProps" />
|
||||
|
||||
<ui-error-view v-if="loadingState === 'error'" :error="loadingError" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -13,14 +15,6 @@
|
||||
name: 'uiForm',
|
||||
|
||||
props: {
|
||||
submit: {
|
||||
type: Function,
|
||||
default: () => { }
|
||||
},
|
||||
mapState: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
errorComponents: {
|
||||
type: Array,
|
||||
default: () => [ 'uiError' ]
|
||||
@@ -29,6 +23,8 @@
|
||||
|
||||
data: () => ({
|
||||
dirty: false,
|
||||
loadingState: 'default',
|
||||
loadingError: null,
|
||||
state: 'default',
|
||||
errors: [],
|
||||
slotProps: {
|
||||
@@ -46,6 +42,7 @@
|
||||
created()
|
||||
{
|
||||
this.slotProps.state = this.state;
|
||||
this.$emit('load', this);
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -72,7 +69,36 @@
|
||||
}
|
||||
},
|
||||
|
||||
// handles a promise
|
||||
// loads data on creation of the form
|
||||
load(promise)
|
||||
{
|
||||
this.loadingState = 'loading';
|
||||
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
promise
|
||||
.then(
|
||||
response =>
|
||||
{
|
||||
this.loadingState = 'default';
|
||||
resolve(response);
|
||||
},
|
||||
(error) =>
|
||||
{
|
||||
this.loadingState = 'error';
|
||||
this.loadingError = error;
|
||||
reject(error);
|
||||
}
|
||||
)
|
||||
.catch(exception =>
|
||||
{
|
||||
this.loadingState = 'error';
|
||||
this.loadingError = error;
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// handles a promise as result of the form submission
|
||||
handle(promise)
|
||||
{
|
||||
this.setState('loading');
|
||||
@@ -109,7 +135,7 @@
|
||||
// submits the form
|
||||
onSubmit(e)
|
||||
{
|
||||
this.submit(this, e);
|
||||
this.$emit('submit', this, e);
|
||||
},
|
||||
|
||||
|
||||
@@ -221,4 +247,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-form
|
||||
{
|
||||
min-height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<ui-form ref="form" class="user" v-slot="form" :submit="onSubmit">
|
||||
<ui-form ref="form" class="user" v-slot="form" @submit="onSubmit" @load="onLoad">
|
||||
|
||||
<ui-header-bar :title="model.name" title-empty="@user.name" :on-back="onBack">
|
||||
<ui-dropdown align="right">
|
||||
@@ -49,6 +49,7 @@
|
||||
name: 'app-settings-user',
|
||||
|
||||
data: () => ({
|
||||
loading: true,
|
||||
page: true,
|
||||
actions: [],
|
||||
model: {
|
||||
@@ -59,12 +60,6 @@
|
||||
|
||||
created()
|
||||
{
|
||||
UsersApi.getById(this.id).then(response =>
|
||||
{
|
||||
console.info(response);
|
||||
this.model = response;
|
||||
});
|
||||
|
||||
this.actions.push({
|
||||
name: 'Disable',
|
||||
icon: 'fth-minus-circle'
|
||||
@@ -93,6 +88,15 @@
|
||||
this.$router.go(-1);
|
||||
},
|
||||
|
||||
onLoad(form)
|
||||
{
|
||||
form.load(UsersApi.getById(this.id)).then(response =>
|
||||
{
|
||||
console.info(response);
|
||||
this.model = response;
|
||||
});
|
||||
},
|
||||
|
||||
onSubmit(form)
|
||||
{
|
||||
form.handle(new Promise(resolve =>
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
<template>
|
||||
<div class="page page-error" :class="{'theme-dark': dark }">
|
||||
<i class="page-error-icon fth-cloud-snow"></i>
|
||||
<p class="page-error-text">
|
||||
<strong class="page-error-headline" v-localize="{ key: errorDetails.headline, tokens: tokens }"></strong><br>
|
||||
<span v-localize:html="{ key: errorDetails.text, tokens: tokens }"></span>
|
||||
</p>
|
||||
<ui-button v-if="errorDetails.code === 404" class="page-error-button" type="light" :label="detailsText" @click="details = !details" />
|
||||
<ui-button v-if="errorDetails.code !== 404 && errorDetails.category === 4" class="page-error-button" type="light" label="@ui.back" @click="$router.go(-1)" />
|
||||
<!--<ui-button v-if="errorDetails.code !== 404 && errorDetails.category === 5" class="page-error-button" type="light" label="@ui.back" @click="$router.go(-1)" />-->
|
||||
<template v-if="errorDetails.code === 404 && details">
|
||||
<br><br>
|
||||
<div class="page-error-routes">
|
||||
<span>#</span>
|
||||
<span>Name</span>
|
||||
<span>Path</span>
|
||||
<template v-for="(route, index) in routes">
|
||||
<span>{{index + 1}}.</span>
|
||||
<span>{{route.name}}</span>
|
||||
<span>{{route.path}}</span>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
const KNOWN_ERRORS = [403, 404, 504];
|
||||
|
||||
export default {
|
||||
|
||||
props: {
|
||||
dark: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
error: {
|
||||
type: Error,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
path: null,
|
||||
routes: [],
|
||||
details: false,
|
||||
errorDetails: {
|
||||
category: 0,
|
||||
code: null,
|
||||
headline: null,
|
||||
text: null
|
||||
}
|
||||
}),
|
||||
|
||||
watch: {
|
||||
error: function (val)
|
||||
{
|
||||
this.rebuild();
|
||||
},
|
||||
'$route': function (val)
|
||||
{
|
||||
this.rebuild();
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
detailsText()
|
||||
{
|
||||
return this.details ? 'Hide' : 'Defined routes';
|
||||
},
|
||||
tokens()
|
||||
{
|
||||
return {
|
||||
code: this.errorDetails.code,
|
||||
path: this.path
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
mounted()
|
||||
{
|
||||
console.info(this.error.response);
|
||||
this.rebuild();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
rebuild()
|
||||
{
|
||||
if (this.error && this.error.response)
|
||||
{
|
||||
let errorKey = null;
|
||||
|
||||
const errorCode = this.error.response.status;
|
||||
const errorCategory = Math.round(errorCode / 100);
|
||||
|
||||
if (KNOWN_ERRORS.indexOf(errorCode) > -1)
|
||||
{
|
||||
errorKey = '@errors.http.' + errorCode;
|
||||
}
|
||||
else if (errorCategory === 4)
|
||||
{
|
||||
errorKey = '@errors.http.4xx';
|
||||
}
|
||||
else if (errorCategory === 5)
|
||||
{
|
||||
errorKey = '@errors.http.5xx';
|
||||
}
|
||||
|
||||
this.errorDetails.category = errorCategory;
|
||||
this.errorDetails.code = errorCode;
|
||||
this.errorDetails.headline = errorKey;
|
||||
this.errorDetails.text = errorKey + '_text';
|
||||
}
|
||||
|
||||
this.path = this.$router.options.base + this.$route.path.substring(1);
|
||||
this.routes = [];
|
||||
|
||||
this.$router.options.routes.forEach(route =>
|
||||
{
|
||||
this.routes.push({
|
||||
path: route.path,
|
||||
name: route.name
|
||||
});
|
||||
|
||||
if (route.children)
|
||||
{
|
||||
route.children.forEach(child =>
|
||||
{
|
||||
this.routes.push({
|
||||
path: route.path + '/' + child.path,
|
||||
name: child.name
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.page-error
|
||||
{
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--color-bg-light);
|
||||
color: var(--color-fg);
|
||||
text-align: center;
|
||||
padding: var(--padding);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.page-error-icon
|
||||
{
|
||||
font-size: 82px;
|
||||
color: var(--color-fg);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.page-error-text
|
||||
{
|
||||
font-size: var(--font-size);
|
||||
color: var(--color-fg-light);
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.page-error-headline
|
||||
{
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
font-size: var(--font-size-l);
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.page-error-button
|
||||
{
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.page-error-routes
|
||||
{
|
||||
display: grid;
|
||||
grid-template-columns: auto auto 1fr;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
border-top: 1px solid var(--color-line);
|
||||
border-left: 1px solid var(--color-line);
|
||||
margin-top: 30px;
|
||||
|
||||
span
|
||||
{
|
||||
border: 1px solid var(--color-line);
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
padding: 8px 10px 6px;
|
||||
font-family: Consolas;
|
||||
font-size: 12px;
|
||||
|
||||
&:nth-child(3n+1)
|
||||
{
|
||||
color: var(--color-fg-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="app-auth theme-dark">
|
||||
<div class="app-auth">
|
||||
<h1 class="app-auth-headline">zero</h1>
|
||||
<ui-form class="app-auth-inner theme-light" v-slot="form" :submit="onSubmit">
|
||||
<ui-form class="app-auth-inner" v-slot="form" @submit="onSubmit">
|
||||
<div>
|
||||
<h2 v-localize="'@login.headline'"></h2>
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="page not-found theme-dark">
|
||||
<i class="not-found-icon fth-cloud-snow"></i>
|
||||
<p class="not-found-text">
|
||||
<strong class="not-found-headline">Not found</strong><br>
|
||||
<div class="page page-error theme-dark">
|
||||
<i class="page-error-icon fth-cloud-snow"></i>
|
||||
<p class="page-error-text">
|
||||
<strong class="page-error-headline">Not found</strong><br>
|
||||
The requested resource could not be found
|
||||
<br>
|
||||
({{path}})
|
||||
</p>
|
||||
<ui-button class="not-found-button" type="light" :label="detailsText" @click="details = !details" />
|
||||
<ui-button class="page-error-button" type="light" :label="detailsText" @click="details = !details" />
|
||||
<template v-if="details">
|
||||
<br><br>
|
||||
<div class="not-found-routes">
|
||||
<div class="page-error-routes">
|
||||
<span>#</span>
|
||||
<span>Name</span>
|
||||
<span>Path</span>
|
||||
@@ -86,7 +86,7 @@
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.not-found
|
||||
.page-error
|
||||
{
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
@@ -101,21 +101,21 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.not-found-icon
|
||||
.page-error-icon
|
||||
{
|
||||
font-size: 82px;
|
||||
color: var(--color-fg);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.not-found-text
|
||||
.page-error-text
|
||||
{
|
||||
font-size: var(--font-size);
|
||||
color: var(--color-fg-light);
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
.not-found-headline
|
||||
.page-error-headline
|
||||
{
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
@@ -123,12 +123,12 @@
|
||||
color: var(--color-fg);
|
||||
}
|
||||
|
||||
.not-found-button
|
||||
.page-error-button
|
||||
{
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.not-found-routes
|
||||
.page-error-routes
|
||||
{
|
||||
display: grid;
|
||||
grid-template-columns: auto auto 1fr;
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace zero.Web.Controllers
|
||||
/// </summary>
|
||||
public async Task<IActionResult> GetById([FromQuery] string id)
|
||||
{
|
||||
await Task.Delay(500);
|
||||
return Json(Mapper.Map<User, UserEditModel>(await Api.GetUserById(id)));
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,21 @@
|
||||
}
|
||||
},
|
||||
|
||||
"errors": {
|
||||
"http": {
|
||||
"403": "Unauthorized",
|
||||
"403_text": "You are not allowed to view this resource",
|
||||
"404": "Not found",
|
||||
"404_text": "The requested resource could not be found<br>({path})",
|
||||
"4xx": "Client error",
|
||||
"4xx_text": "There was an internal client error (code: {code})",
|
||||
"504": "Timed out",
|
||||
"504_text": "The backoffice did not receive a timely response from the server",
|
||||
"5xx": "Server error",
|
||||
"5xx_text": "There was an internal server error (code: {code})"
|
||||
}
|
||||
},
|
||||
|
||||
"unsavedchanges": {
|
||||
"close": "Discard changes",
|
||||
"confirm": "Stay"
|
||||
|
||||
Reference in New Issue
Block a user