فهرست منبع

refactor(webapp): clean up error handling, reduce code duplication

Nils Wisiol 3 سال پیش
والد
کامیت
7c88b9be4d

+ 15 - 36
www/webapp/src/components/DonateDirectDebitForm.vue

@@ -20,17 +20,7 @@
       <v-btn depressed outlined block :to="{name: 'home'}">Done</v-btn>
     </v-alert>
     <v-form v-if="!done" @submit.prevent="donate" ref="form">
-      <v-alert :value="!!(errors && errors.length)" type="error">
-        <div v-if="errors.length > 1">
-          <li v-for="error of errors" :key="error.message" >
-            <b>{{ error.message }}</b>
-            {{ error }}
-          </li>
-        </div>
-        <div v-else>
-          {{ errors[0] }}
-        </div>
-      </v-alert>
+      <error-alert v-bind:errors="errors"></error-alert>
 
       <v-radio-group
           v-model="interval"
@@ -111,6 +101,8 @@
 <script>
   import axios from 'axios';
   import {email_pattern} from '../validation';
+  import {digestError} from "../utils";
+  import ErrorAlert from '@/components/ErrorAlert';
 
   const HTTP = axios.create({
     baseURL: '/api/v1/',
@@ -120,6 +112,9 @@
 
   export default {
     name: 'DonateDirectDebitForm',
+    components: {
+      ErrorAlert,
+    },
     data: () => ({
       valid: false,
       working: false,
@@ -171,7 +166,7 @@
           return;
         }
         this.working = true;
-        this.errors = [];
+        this.errors.splice(0, this.errors.length);
         try {
           let response = await HTTP.post('donation/', {
             amount: this.amount,
@@ -184,32 +179,16 @@
           });
           this.mref = response.data.mref;
           this.done = true;
-        } catch (error) {
-          if (error.response) {
-            // status is not 2xx
-            if (error.response.status < 500 && typeof error.response.data === 'object') {
-              // 3xx or 4xx
-              let extracted = false;
-              if ('email' in error.response.data) {
-                this.email_errors = [error.response.data.email[0]];
-                extracted = true;
-              }
-              if ('amount' in error.response.data) {
-                this.amount_errors = [error.response.data.amount[0]];
-                extracted = true;
-              }
-              // TODO extract more errors
-              if (!extracted) {
-                this.errors = [error.response];
-              }
+        } catch (ex) {
+          let errors = digestError(ex);
+          for (const c in errors) {
+            if (c === 'email') {
+              this.email_errors = errors[c];
+            } else if (c === 'amount') {
+              this.amount_errors = errors[c];
             } else {
-              // 5xx
-              this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
+              this.errors.push(...errors[c]);
             }
-          } else if (error.request) {
-            this.errors = ['Cannot contact our servers. Are you offline?'];
-          } else {
-            this.errors = [error.message];
           }
         }
         this.working = false;

+ 24 - 0
www/webapp/src/components/ErrorAlert.vue

@@ -0,0 +1,24 @@
+<template>
+  <v-alert :value="!!(errors && errors.length)" type="error" style="overflow: auto;">
+    <div v-if="errors.length > 1">
+      <li v-for="(error, idx) of errors" :key="idx" >
+        {{ error }}
+      </li>
+    </div>
+    <div v-else-if="errors.length == 1">
+      {{ errors[0] }}
+    </div>
+  </v-alert>
+</template>
+
+<script>
+export default {
+  name: 'ErrorAlert',
+  props: {
+    errors: {type: Array},
+  },
+};
+</script>
+
+<style>
+</style>

+ 62 - 5
www/webapp/src/utils.js

@@ -7,14 +7,16 @@ export const HTTP = axios.create({
   },
 });
 
+function clearToken() {
+  store.commit('logout');
+  HTTP.defaults.headers.common.Authorization = '';
+  sessionStorage.removeItem('token');
+}
+
 export async function logout() {
   await withWorking(undefined, () => HTTP
       .post('auth/logout/')
-      .then(() => {
-        store.commit('logout');
-        HTTP.defaults.headers.common.Authorization = '';
-        sessionStorage.removeItem('token');
-      })
+      .then(clearToken)
   );
 }
 
@@ -32,3 +34,58 @@ export async function withWorking(errorHandler, action, ...params) {
     store.commit('working', false);
   }
 }
+
+function _digestError(error) {
+  if (error.response) {
+    // The request was made and the server responded with a status code
+    // that falls out of the range of 2xx
+    if (error.response.status < 500) {
+      // 3xx or 4xx
+      if (error.response.status === 401) {
+        if (sessionStorage.getItem('token')) {
+          setTimeout(() => clearToken());
+          return ['Session expired. Please login again.']
+        } else {
+          return ['You are not logged in.'];
+        }
+      } else if (error.response.status === 413) {
+        return ['Too much data. Try to reduce the length of your inputs.'];
+      } else if ('data' in error.response) {
+        let data = error.response.data;
+        if (typeof data === 'object') {
+          if ('detail' in data) {
+            return [data.detail];
+          } else if ('non_field_errors' in data) {
+            return data.non_field_errors;
+          } else {
+            return data;
+          }
+        } else {
+          return [data];
+        }
+      } else {
+        return ["Server returned an empty response."];
+      }
+    } else {
+      // 5xx
+      if (error.response.status === 500) {
+        return ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
+      } else {
+        return ['Something went wrong at the server, but we currently do not know why. Please try again later, and contact us if the problem persists for a longer time.'];
+      }
+    }
+  } else if (error.request) {
+    return [`Cannot contact servers at ${HTTP.baseURL ? HTTP.baseURL : window.location.hostname}. Are you offline?`];
+  } else {
+    return [error.message];
+  }
+}
+
+export function digestError(error) {
+  let e = _digestError(error);
+  if (e.constructor === Array) {
+    return {undefined: e};
+  } else {
+    return e;
+  }
+}

+ 11 - 26
www/webapp/src/views/ChangeEmail.vue

@@ -22,17 +22,7 @@
                             <v-toolbar-title>Change Account Email Address</v-toolbar-title>
                         </v-toolbar>
                         <v-card-text>
-                            <v-alert :value="!!(errors && errors.length)" type="error">
-                                <div v-if="errors.length > 1">
-                                    <li v-for="error of errors" :key="error.message">
-                                        <b>{{ error.message }}</b>
-                                        {{ error }}
-                                    </li>
-                                </div>
-                                <div v-else>
-                                    {{ errors[0] }}
-                                </div>
-                            </v-alert>
+                            <error-alert v-bind:errors="errors"></error-alert>
                             <v-alert v-if="done" type="success">
                                 <p>
                                     Please check your new email address for messages. If the new email address is not
@@ -100,9 +90,14 @@
 <script>
   import { HTTP, withWorking } from '@/utils';
   import {email_pattern} from '../validation';
+  import {digestError} from "../utils";
+  import ErrorAlert from "@/components/ErrorAlert";
 
   export default {
     name: 'ChangeEmail',
+    components: {
+      ErrorAlert,
+    },
     data: () => ({
       valid: false,
       working: false,
@@ -145,7 +140,7 @@
           return;
         }
         this.working = true;
-        this.errors = [];
+        this.errors.splice(0, this.errors.length);
         try {
           await HTTP.post('auth/account/change-email/', {
             email: this.email,
@@ -153,20 +148,10 @@
             new_email: this.new_email
           });
           this.done = true;
-        } catch (error) {
-          if (error.response) {
-            // status is not 2xx
-            if (error.response.status < 500 && typeof error.response.data === 'object') {
-              // 3xx or 4xx
-              this.errors = [error.response.data.detail];
-            } else {
-              // 5xx
-              this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
-            }
-          } else if (error.request) {
-            this.errors = ['Cannot contact our servers. Are you offline?'];
-          } else {
-            this.errors = [error.message];
+        } catch (ex) {
+          let errors = digestError(ex);
+          for (const c in errors) {
+            this.errors.push(...errors[c]);
           }
         }
         this.working = false;

+ 12 - 16
www/webapp/src/views/Confirmation.vue

@@ -21,17 +21,7 @@
             <v-toolbar-title class="capitalize">{{ this.$route.params.action | replace(/-/g, ' ') }} Confirmation</v-toolbar-title>
           </v-toolbar>
           <v-card-text>
-            <v-alert :value="!!(errors && errors.length)" type="error">
-              <div v-if="errors.length > 1">
-                <li v-for="error of errors" :key="error.message" >
-                  <b>{{ error.message }}</b>
-                  {{ error }}
-                </li>
-              </div>
-              <div v-else-if="errors.length == 1">
-                {{ errors[0].detail || errors[0][0] || errors[0]}}
-              </div>
-            </v-alert>
+            <error-alert v-bind:errors="errors"></error-alert>
             <v-form @submit.prevent="confirm" class="mb-4" v-model="valid" ref="form">
               <div
                       :is="this.actionHandler"
@@ -67,6 +57,8 @@
   import GenericActionHandler from '@/components/GenericActionHandler.vue';
   import ActivateAccountActionHandler from '@/components/ActivateAccountActionHandler.vue';
   import ResetPasswordActionHandler from '@/components/ResetPasswordActionHandler.vue';
+  import {digestError} from "../utils";
+  import ErrorAlert from '@/components/ErrorAlert';
 
   const HTTP = axios.create({
     baseURL: '/api/v1/',
@@ -79,6 +71,7 @@
       GenericActionHandler,
       ActivateAccountActionHandler,
       ResetPasswordActionHandler,
+      ErrorAlert,
     },
     data: () => ({
       actionHandler: null,
@@ -96,20 +89,23 @@
     methods: {
       async confirm() {
         this.post_response = {}
-        this.errors = []
+        this.clearErrors();
         this.working = true
         let action = this.$route.params.action
         try {
           this.post_response = await HTTP.post(`v/${action}/${this.$route.params.code}/`, this.post_payload);
           this.success = true
-        } catch (error) {
-          this.post_response = error.response
-          this.errors.push(error.response.data)
+        } catch (ex) {
+          let errors = digestError(ex);
+          this.post_response = ex.response
+          for (const c in errors) {
+            this.errors.push(...errors[c])
+          }
         }
         this.working = false
       },
       clearErrors() {
-        this.errors = []
+        this.errors.splice(0, this.errors.length);
       }
     },
     filters: {

+ 15 - 42
www/webapp/src/views/CrudList.vue

@@ -93,13 +93,7 @@
                               :indeterminate="true"
                       />
 
-                      <v-alert
-                              :value="createDialogError"
-                              type="error"
-                              style="overflow: auto"
-                      >
-                        {{ errors[errors.length - 1] }}
-                      </v-alert>
+                      <error-alert v-if="createDialogError" v-bind:errors="errors"></error-alert>
 
                       <v-alert
                               :value="createDialogSuccess"
@@ -286,12 +280,7 @@
                 >
                   {{ texts.destroyWarning(destroyDialogItem) }}
                 </v-alert>
-                <v-alert
-                  :value="!!destroyDialogError"
-                  type="error"
-                >
-                  {{ errors[errors.length - 1] }}
-                </v-alert>
+                <error-alert v-if="destroyDialogError" v-bind:errors="errors"></error-alert>
 
                 <v-card-text>
                   {{ texts.destroy(destroyDialogItem) }}
@@ -334,7 +323,7 @@
 </template>
 
 <script>
-import { HTTP, withWorking } from '@/utils';
+import { HTTP, withWorking, digestError } from '@/utils';
 import RRSetType from '@/components/Field/RRSetType';
 import TimeAgo from '@/components/Field/TimeAgo';
 import Checkbox from '@/components/Field/Checkbox';
@@ -345,6 +334,7 @@ import Record from '@/components/Field/Record';
 import RecordList from '@/components/Field/RecordList';
 import Switchbox from '@/components/Field/Switchbox';
 import TTL from '@/components/Field/TTL';
+import ErrorAlert from '@/components/ErrorAlert'
 
 const filter = function (obj, predicate) {
   const result = {};
@@ -358,9 +348,6 @@ const filter = function (obj, predicate) {
   return result;
 };
 
-// safely access deeply nested objects
-const safeget = (path, object) => path.reduce((xs, x) => ((xs && xs[x]) ? xs[x] : null), object);
-
 export default {
   name: 'CrudList',
   components: {
@@ -374,6 +361,7 @@ export default {
     Record,
     RecordList,
     TTL,
+    ErrorAlert,
   },
   data() { return {
     createDialog: false,
@@ -614,33 +602,18 @@ export default {
      * Handle the error e by displaying it to the user.
      * @param e
      */
-    error(e) {
-      if (safeget(['response', 'data', 'detail'], e)) {
-        e = e.response.data.detail;
-      } else if (safeget(['response', 'status'], e) == 413) {
-        e = 'Too much data. Try to reduce the length of your inputs.';
-      } else if (safeget(['response', 'data'], e)) {
-        e = safeget(['response', 'data'], e);
-      }
-      if (!safeget(['response'], e) && safeget(['request'], e)) {
-        e = `Cannot reach server at ${HTTP.baseURL ? HTTP.baseURL : window.location.hostname}. Are you offline?`;
-      }
-      this.errors.push(e);
-      if (this.destroyDialog) {
-        this.destroyDialogError = e;
-      } else if (this.createDialog) {
-        // see if e contains field-specific errors
-        if (Object.keys(e).every(key => Object.prototype.hasOwnProperty.call(this.columns, key))) {
-          // assume we obtained field-specific error(s),
-          // so let's assign them to the input fields
-          for (const c in e) {
-            this.columns[c].createErrors = e[c];
-          }
+    error(ex) {
+      this.errors.splice(0, this.errors.length);
+      let errors = digestError(ex);
+      for (const c in errors) {
+        if (this.columns[c] !== undefined) {
+          this.columns[c].createErrors = errors[c]
         } else {
-          this.createDialogError = true;
+          this.errors.push(...errors[c])
+          this.createDialogError = this.createDialog;
+          this.destroyDialogError = this.destroyDialog;
+          this.snackbar = !this.createDialog && !this.destroyDialog;
         }
-      } else {
-        this.snackbar = true;
       }
     },
     /** *

+ 9 - 31
www/webapp/src/views/DeleteAccount.vue

@@ -22,17 +22,7 @@
                             <v-toolbar-title>Delete Account</v-toolbar-title>
                         </v-toolbar>
                         <v-card-text>
-                            <v-alert :value="!!(errors && errors.length)" type="error">
-                                <div v-if="errors.length > 1">
-                                    <li v-for="error of errors" :key="error.message">
-                                        <b>{{ error.message }}</b>
-                                        {{ error }}
-                                    </li>
-                                </div>
-                                <div v-else>
-                                    {{ errors[0] }}
-                                </div>
-                            </v-alert>
+                            <error-alert v-bind:errors="errors"></error-alert>
                             <v-alert v-if="done" type="success">
                                 <p>
                                     Please check your mail box for further instructions to delete your account.
@@ -83,10 +73,12 @@
 </template>
 
 <script>
-  import { HTTP, withWorking } from '@/utils';
+  import { HTTP, withWorking, digestError } from '@/utils';
+  import ErrorAlert from "../components/ErrorAlert";
 
   export default {
     name: 'DeleteAccount',
+    components: {ErrorAlert},
     data: () => ({
       valid: false,
       working: false,
@@ -101,10 +93,6 @@
       /* password field */
       password: '',
       password_errors: [],
-
-      /* email field */
-      new_email: '',
-      email_errors: [],
     }),
     mounted() {
       this.initialFocus();
@@ -125,27 +113,17 @@
           return;
         }
         this.working = true;
-        this.errors = [];
+        this.errors.splice(0, this.errors.length);
         try {
           await HTTP.post('auth/account/delete/', {
             email: this.email,
             password: this.password,
           });
           this.done = true;
-        } catch (error) {
-          if (error.response) {
-            // status is not 2xx
-            if (error.response.status < 500 && typeof error.response.data === 'object') {
-              // 3xx or 4xx
-              this.errors = [error.response.data.detail];
-            } else {
-              // 5xx
-              this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
-            }
-          } else if (error.request) {
-            this.errors = ['Cannot contact our servers. Are you offline?'];
-          } else {
-            this.errors = [error.message];
+        } catch (ex) {
+          let errors = digestError(ex);
+          for (const c in errors) {
+            this.errors.push(...errors[c])
           }
         }
         this.working = false;

+ 13 - 30
www/webapp/src/views/DynSetup.vue

@@ -21,17 +21,7 @@
             <v-toolbar-title>Domain Registration Completed</v-toolbar-title>
           </v-toolbar>
           <v-card-text>
-            <v-alert :value="!!(errors && errors.length)" type="error">
-              <div v-if="errors.length > 1">
-                <li v-for="error of errors" :key="error.message" >
-                  <b>{{ error.message }}</b>
-                  {{ error }}
-                </li>
-              </div>
-              <div v-else>
-                {{ errors[0] }}
-              </div>
-            </v-alert>
+            <error-alert v-bind:errors="errors"></error-alert>
             <p>
               Congratulations, you are now the owner of <span class="fixed-width">{{ $route.params.domain }}</span>!
             </p>
@@ -179,6 +169,8 @@
 
 <script>
   import axios from 'axios';
+  import {digestError} from "../utils";
+  import ErrorAlert from "@/components/ErrorAlert";
 
   const HTTP = axios.create({
     baseURL: '/api/v1/',
@@ -188,6 +180,9 @@
 
   export default {
     name: 'DynSetup',
+    components: {
+      ErrorAlert,
+    },
     data: () => ({
       working: false,
       domain: undefined,
@@ -213,7 +208,7 @@
     methods: {
       async check() {
         this.working = true;
-        this.errors = [];
+        this.errors.splice(0, this.errors.length);
         try {
           let responseDomain = await HTTP.get(`domains/${this.domain}/`, {headers: {'Authorization': `Token ${this.token}`}});
           this.lastChanged = responseDomain.data.published;
@@ -242,25 +237,13 @@
           return this.errorHandler(error);
         }
       },
-      errorHandler(error) {
-        if (error.response) {
-          // status is not 2xx
-          if (error.response.status < 500) {
-            // 3xx or 4xx
-            if (error.response.status === 404) {
-              return null;
-            }
-            this.errors = error.response;
-          } else {
-            // 5xx
-            this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
-          }
-        } else if (error.request) {
-          this.errors = ['Cannot contact our servers. Are you offline?'];
-        } else {
-          this.errors = [error.message];
+      errorHandler(ex) {
+        if (ex.response && ex.response.status === 404)
+          return;
+        let errors = digestError(ex);
+        for (const c in errors) {
+          this.errors.push(...errors[c])
         }
-        throw this.errors
       },
     },
   };

+ 13 - 38
www/webapp/src/views/Login.vue

@@ -25,23 +25,7 @@
               <v-toolbar-title>Log In</v-toolbar-title>
             </v-toolbar>
             <v-card-text>
-              <v-alert
-                :value="!!errors && !!errors.length"
-                type="error"
-              >
-                <div v-if="errors.length > 1">
-                  <li
-                    v-for="error of errors"
-                    :key="error.message"
-                  >
-                    <b>{{ error.message }}</b>
-                    {{ error }}
-                  </li>
-                </div>
-                <div v-else>
-                  {{ errors[0] }}
-                </div>
-              </v-alert>
+              <error-alert v-bind:errors="errors"></error-alert>
               <v-text-field
                 v-model="email"
                 label="Email"
@@ -103,10 +87,14 @@
 </template>
 
 <script>
-import { HTTP } from '../utils';
+import { HTTP, digestError } from '../utils';
+import ErrorAlert from "@/components/ErrorAlert";
 
 export default {
   name: 'Login',
+  components: {
+    ErrorAlert,
+  },
   data: () => ({
     valid: false,
     working: false,
@@ -125,7 +113,7 @@ export default {
   methods: {
     async login() {
       this.working = true;
-      this.errors = [];
+      this.errors.splice(0, this.errors.length);
       try {
         const response = await HTTP.post('auth/login/', {
           email: this.email,
@@ -141,27 +129,14 @@ export default {
         } else {
           this.$router.replace({ name: 'domains' });
         }
-      } catch (error) {
-        if (error.response) {
-          // The request was made and the server responded with a status code
-          // that falls out of the range of 2xx
-          if (error.response.status < 500 && typeof error.response.data === 'object') {
-            // 3xx or 4xx
-            if ('email' in error.response.data) {
-              this.email_errors = [error.response.data.email[0]];
-            } else if ('non_field_errors' in error.response.data) {
-              this.errors = error.response.data.non_field_errors;
-            } else {
-              this.errors = [error.response.data.detail];
-            }
+      } catch (ex) {
+        let errors = digestError(ex);
+        for (const c in errors) {
+          if (c === 'email') {
+            this.email_errors = errors.email;
           } else {
-            // 5xx
-            this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
+            this.errors.push(...errors[c])
           }
-        } else if (error.request) {
-          this.errors = ['Cannot contact our servers. Are you offline?'];
-        } else {
-          this.errors = [error.message];
         }
       }
       this.working = false;

+ 15 - 38
www/webapp/src/views/ResetPassword.vue

@@ -22,17 +22,7 @@
                             <v-toolbar-title>Reset Account Password</v-toolbar-title>
                         </v-toolbar>
                         <v-card-text>
-                            <v-alert :value="!!(errors && errors.length)" type="error">
-                                <div v-if="errors.length > 1">
-                                    <li v-for="error of errors" :key="error.message">
-                                        <b>{{ error.message }}</b>
-                                        {{ error }}
-                                    </li>
-                                </div>
-                                <div v-else>
-                                    {{ errors[0] }}
-                                </div>
-                            </v-alert>
+                            <error-alert v-bind:errors="errors"></error-alert>
                             <v-alert v-if="done" type="success">
                                 <p>
                                     We received the password reset request. If an account with this email address exists
@@ -129,6 +119,8 @@
 <script>
   import axios from 'axios';
   import {email_pattern} from '../validation';
+  import {digestError} from "../utils";
+  import ErrorAlert from '@/components/ErrorAlert';
 
   const HTTP = axios.create({
     baseURL: '/api/v1/',
@@ -137,6 +129,9 @@
 
   export default {
     name: 'ResetPassword',
+    components: {
+      ErrorAlert,
+    },
     data: () => ({
       valid: false,
       working: false,
@@ -184,7 +179,7 @@
           return;
         }
         this.working = true;
-        this.errors = [];
+        this.errors.splice(0, this.errors.length);
         try {
           await HTTP.post('auth/account/reset-password/', {
             email: this.email,
@@ -194,34 +189,16 @@
             },
           });
           this.done = true;
-        } catch (error) {
-          if (error.response) {
-            // status is not 2xx
-            if (error.response.status < 500 && typeof error.response.data === 'object') {
-              // 3xx or 4xx
-              let extracted = false;
-              this.getCaptcha(true);
-              if ('captcha' in error.response.data) {
-                if ('non_field_errors' in error.response.data.captcha) {
-                  this.captcha_errors = [error.response.data.captcha.non_field_errors[0]];
-                  extracted = true;
-                }
-                if ('solution' in error.response.data.captcha) {
-                  this.captcha_errors = error.response.data.captcha.solution;
-                  extracted = true;
-                }
-              }
-              if (!extracted) {
-                this.errors = error.response;
-              }
+        } catch (ex) {
+          this.getCaptcha(true);
+          let errors = digestError(ex);
+          for (const c in errors) {
+            if (c === 'captcha') {
+              this.captcha_errors.push(...(errors[c]['non_field_errors'] ?? []));
+              this.captcha_errors.push(...(errors[c]['solution'] ?? []));
             } else {
-              // 5xx
-              this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
+              this.errors.push(...errors[c]);
             }
-          } else if (error.request) {
-            this.errors = ['Cannot contact our servers. Are you offline?'];
-          } else {
-            this.errors = [error.message];
           }
         }
         this.working = false;

+ 21 - 46
www/webapp/src/views/SignUp.vue

@@ -23,17 +23,7 @@
               <v-toolbar-title>Create new Account</v-toolbar-title>
             </v-toolbar>
             <v-card-text>
-              <v-alert :value="!!(errors && errors.length)" type="error">
-                <div v-if="errors.length > 1">
-                  <li v-for="error of errors" :key="error.message" >
-                    <b>{{ error.message }}</b>
-                    {{ error }}
-                  </li>
-                </div>
-                <div v-else>
-                  {{ errors[0] }}
-                </div>
-              </v-alert>
+              <error-alert v-bind:errors="errors"></error-alert>
 
               <v-text-field
                       v-model="email"
@@ -170,6 +160,8 @@
 <script>
   import axios from 'axios';
   import {domain_pattern, email_pattern} from '../validation';
+  import {digestError} from "../utils";
+  import ErrorAlert from "@/components/ErrorAlert";
 
   const LOCAL_PUBLIC_SUFFIXES = process.env.VUE_APP_LOCAL_PUBLIC_SUFFIXES.split(' ');
 
@@ -181,6 +173,9 @@
 
   export default {
     name: 'SignUp',
+    components: {
+      ErrorAlert,
+    },
     data: () => ({
       valid: false,
       working: false,
@@ -249,7 +244,7 @@
           return;
         }
         this.working = true;
-        this.errors = [];
+        this.errors.splice(0, this.errors.length);
         let domain = this.domain === '' ? undefined : this.domain.toLowerCase();
         if (domain && this.domainType == 'dynDNS') {
            domain += '.' + this.LOCAL_PUBLIC_SUFFIXES[0];
@@ -265,42 +260,22 @@
             domain: domain,
           });
           this.$router.push({name: 'welcome', params: domain !== '' ? {domain: domain} : {}});
-        } catch (error) {
-          if (error.response) {
-            // status is not 2xx
-            if (error.response.status < 500 && typeof error.response.data === 'object') {
-              // 3xx or 4xx
-              let extracted = false;
-              this.getCaptcha(true);
-              if ('captcha' in error.response.data) {
-                if ('non_field_errors' in error.response.data.captcha) {
-                  this.captcha_errors = [error.response.data.captcha.non_field_errors[0]];
-                  extracted = true;
-                }
-                if ('solution' in error.response.data.captcha) {
-                  this.captcha_errors = error.response.data.captcha.solution;
-                  extracted = true;
-                }
-              }
-              if ('domain' in error.response.data) {
-                this.domain_errors = [error.response.data.domain[0]];
-                extracted = true;
-              }
-              if ('email' in error.response.data) {
-                this.email_errors = [error.response.data.email[0]];
-                extracted = true;
-              }
-              if (!extracted) {
-                this.errors = error.response;
-              }
+        } catch (ex) {
+          this.getCaptcha(true);
+          let errors = digestError(ex);
+          for (const c in errors) {
+            if (c === undefined) {
+              this.errors.push(...errors[c]);
+            } else if (c === 'captcha') {
+              this.captcha_errors.push(...(errors[c]['non_field_errors'] ?? []));
+              this.captcha_errors.push(...(errors[c]['solution'] ?? []));
+            } else if (c === 'domain') {
+              this.domain_errors.push(...errors[c]);
+            } else if (c === 'email') {
+              this.email_errors.push(...errors[c]);
             } else {
-              // 5xx
-              this.errors = ['Something went wrong at the server, but we currently do not know why. The support was already notified.'];
+              this.errors.push(...errors[c]);
             }
-          } else if (error.request) {
-            this.errors = ['Cannot contact our servers. Are you offline?'];
-          } else {
-            this.errors = [error.message];
           }
         }
         this.working = false;