From f9ff839b1af7cdae2bc9ff5090844ea6b1fac6ac Mon Sep 17 00:00:00 2001
From: raeno <just.raeno@gmail.com>
Date: Wed, 5 Dec 2018 19:17:29 +0400
Subject: [PATCH] Better styling for client-side validation. Add I18n for
 validation errors.

---
 src/components/registration/registration.js  | 30 +++++++++-----
 src/components/registration/registration.vue | 41 ++++++++++++++++++--
 src/i18n/en.json                             | 10 ++++-
 src/i18n/ru.json                             | 10 ++++-
 src/modules/users.js                         | 17 ++++----
 5 files changed, 85 insertions(+), 23 deletions(-)

diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index 69474585..67c052f1 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,5 +1,5 @@
 import { validationMixin } from 'vuelidate'
-import { required } from 'vuelidate/lib/validators'
+import { required, sameAs, email } from 'vuelidate/lib/validators'
 import { mapActions, mapState } from 'vuex'
 import { SIGN_UP } from '../../mutation_types'
 
@@ -16,24 +16,29 @@ const registration = {
   }),
   validations: {
     user: {
-      email: { required },
+      email: { required, email },
       username: { required },
       password: { required },
-      confirm: { required }
+      confirm: {
+        required,
+        sameAsPassword: sameAs('password')
+      }
     }
   },
   created () {
-    if ((!this.$store.state.instance.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) {
+    if ((!this.registrationOpen && !this.token) || this.signedIn) {
       this.$router.push('/main/all')
     }
-    // Seems like this doesn't work at first page open for some reason
-    if (this.$store.state.instance.registrationOpen && this.token) {
-      this.$router.push('/registration')
-    }
+    // // Seems like this doesn't work at first page open for some reason
+    // if (this.$store.state.instance.registrationOpen && this.token) {
+    //   this.$router.push('/registration')
+    // }
   },
   computed: {
     token () { return this.$route.params.token },
     ...mapState({
+      registrationOpen: (state) => state.instance.registrationOpen,
+      signedIn: (state) => !!state.users.currentUser,
       isPending: (state) => state.users[SIGN_UP.isPending],
       serverValidationErrors: (state) => state.users[SIGN_UP.errors],
       termsofservice: (state) => state.instance.tos
@@ -41,14 +46,19 @@ const registration = {
   },
   methods: {
     ...mapActions(['signUp']),
-    submit () {
+    async submit () {
       this.user.nickname = this.user.username
       this.user.token = this.token
 
       this.$v.$touch()
 
       if (!this.$v.$invalid) {
-        this.signUp(this.user)
+        try {
+          await this.signUp(this.user)
+          this.$router.push('/main/friends')
+        } catch (error) {
+          console.log("Registration failed: " + error)
+        }
       }
     }
   }
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 73200990..5f6357a2 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -12,7 +12,11 @@
               <input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' placeholder='e.g. lain'>
             </div>
             <div class="form-error" v-if="$v.user.username.$dirty">
-              <span class="error-required" v-if="!$v.user.username.required">Username is required.</span>
+              <ul>
+                <li v-if="!$v.user.username.required">
+                  <span>{{$t('registration.validations.username_required')}}</span>
+                </li>
+              </ul>
             </div>
 
             <div class='form-group'>
@@ -25,7 +29,14 @@
               <input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email">
             </div>
             <div class="form-error" v-if="$v.user.email.$dirty">
-              <span class="error-required" v-if="!$v.user.email.required">Email is required.</span>
+              <ul>
+                <li v-if="!$v.user.email.required">
+                  <span>{{$t('registration.validations.email_required')}}</span>
+                </li>
+                <li v-if="!$v.user.email.email">
+                  <span>{{$t('registration.validations.email_valid')}}</span>
+                </li>
+              </ul>
             </div>
 
             <div class='form-group'>
@@ -38,7 +49,11 @@
               <input :disabled="isPending" v-model='user.password' class='form-control' id='sign-up-password' type='password'>
             </div>
             <div class="form-error" v-if="$v.user.password.$dirty">
-              <span class="error-required" v-if="!$v.user.password.required">Password is required.</span>
+              <ul>
+                <li v-if="!$v.user.password.required">
+                  <span>{{$t('registration.validations.password_required')}}</span>
+                </li>
+              </ul>
             </div>
 
             <div class='form-group' :class="{ 'form-group--error': $v.user.confirm.$error }">
@@ -46,7 +61,14 @@
               <input :disabled="isPending" v-model='user.confirm' class='form-control' id='sign-up-password-confirmation' type='password'>
             </div>
             <div class="form-error" v-if="$v.user.confirm.$dirty">
-              <span class="error-required" v-if="!$v.user.confirm.required">Password confirmation is required.</span>
+              <ul>
+                <li v-if="!$v.user.confirm.required">
+                  <span>{{$t('registration.validations.password_confirmation_required')}}</span>
+                </li>
+                <li v-if="!$v.user.confirm.sameAsPassword">
+                  <span>{{$t('registration.validations.password_confirmation_match')}}</span>
+                </li>
+              </ul>
             </div>
             <!--
             <div class='form-group'>
@@ -132,6 +154,17 @@
     span {
       font-size: 12px;
     }
+
+  }
+
+  .form-error ul {
+    list-style: none;
+    padding: 0 0 0 5px;
+    margin-top: 0;
+
+    li::before {
+      content: "• ";
+    }
   }
 
   form textarea {
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 8fd546ef..6e13993f 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -72,7 +72,15 @@
     "fullname": "Display name",
     "password_confirm": "Password confirmation",
     "registration": "Registration",
-    "token": "Invite token"
+    "token": "Invite token",
+    "validations": {
+      "username_required": "username should not be blank",
+      "email_required": "should not be blank",
+      "email_valid": "should be valid email",
+      "password_required": "should not be blank",
+      "password_confirmation_required": "should not be blank",
+      "password_confirmation_match": "should be the same as password"
+    }
   },
   "settings": {
     "attachmentRadius": "Attachments",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 9c28ccf4..de2774ef 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -55,7 +55,15 @@
     "fullname": "Отображаемое имя",
     "password_confirm": "Подтверждение пароля",
     "registration": "Регистрация",
-    "token": "Код приглашения"
+    "token": "Код приглашения",
+    "validations": {
+      "username_required": "не должно быть пустым",
+      "email_required": "не должен быть пустым",
+      "email_valid": "должен быть корректный email адрес",
+      "password_required": "не должен быть пустым",
+      "password_confirmation_required": "не должно быть пустым",
+      "password_confirmation_match": "должно совпадать с паролем"
+    }
   },
   "settings": {
     "attachmentRadius": "Прикреплённые файлы",
diff --git a/src/modules/users.js b/src/modules/users.js
index 572a62c6..5ed8e46b 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -52,13 +52,14 @@ export const mutations = {
   },
   [SIGN_UP.PENDING] (state) {
     state[SIGN_UP.isPending] = true
+    state[SIGN_UP.errors] = []
   },
   [SIGN_UP.SUCCESS] (state) {
     state[SIGN_UP.isPending] = false
   },
   [SIGN_UP.FAILURE] (state, errors) {
     state[SIGN_UP.isPending] = false
-    state[SIGN_UP.errors] = [...state[SIGN_UP.errors], ...errors]
+    state[SIGN_UP.errors] = errors
   }
 }
 
@@ -97,27 +98,29 @@ const users = {
     async signUp (store, userInfo) {
       store.commit(SIGN_UP.PENDING)
 
-      let response = await store.rootState.api.backendInteractor.register(userInfo)
+      let rootState = store.rootState
+
+      let response = await rootState.api.backendInteractor.register(userInfo)
       if (response.ok) {
         const data = {
-          oauth: store.state.oauth,
-          instance: store.state.instance.server
+          oauth: rootState.oauth,
+          instance: rootState.instance.server
         }
         let app = await oauthApi.getOrCreateApp(data)
         let result = await oauthApi.getTokenWithCredentials({
           app,
           instance: data.instance,
-          username: this.user.username,
-          password: this.user.password
+          username: userInfo.username,
+          password: userInfo.password
         })
         store.commit(SIGN_UP.SUCCESS)
         store.commit('setToken', result.access_token)
         store.dispatch('loginUser', result.access_token)
-        this.$router.push('/main/friends')
       } else {
         let data = await response.json()
         let errors = humanizeErrors(JSON.parse(data.error))
         store.commit(SIGN_UP.FAILURE, errors)
+        throw Error(errors)
       }
     },
     logout (store) {