Better styling for client-side validation. Add I18n for validation errors.

This commit is contained in:
raeno 2018-12-05 19:17:29 +04:00
parent 2b903f790d
commit f9ff839b1a
5 changed files with 85 additions and 23 deletions

View file

@ -1,5 +1,5 @@
import { validationMixin } from 'vuelidate' import { validationMixin } from 'vuelidate'
import { required } from 'vuelidate/lib/validators' import { required, sameAs, email } from 'vuelidate/lib/validators'
import { mapActions, mapState } from 'vuex' import { mapActions, mapState } from 'vuex'
import { SIGN_UP } from '../../mutation_types' import { SIGN_UP } from '../../mutation_types'
@ -16,24 +16,29 @@ const registration = {
}), }),
validations: { validations: {
user: { user: {
email: { required }, email: { required, email },
username: { required }, username: { required },
password: { required }, password: { required },
confirm: { required } confirm: {
required,
sameAsPassword: sameAs('password')
}
} }
}, },
created () { 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') this.$router.push('/main/all')
} }
// Seems like this doesn't work at first page open for some reason // // Seems like this doesn't work at first page open for some reason
if (this.$store.state.instance.registrationOpen && this.token) { // if (this.$store.state.instance.registrationOpen && this.token) {
this.$router.push('/registration') // this.$router.push('/registration')
} // }
}, },
computed: { computed: {
token () { return this.$route.params.token }, token () { return this.$route.params.token },
...mapState({ ...mapState({
registrationOpen: (state) => state.instance.registrationOpen,
signedIn: (state) => !!state.users.currentUser,
isPending: (state) => state.users[SIGN_UP.isPending], isPending: (state) => state.users[SIGN_UP.isPending],
serverValidationErrors: (state) => state.users[SIGN_UP.errors], serverValidationErrors: (state) => state.users[SIGN_UP.errors],
termsofservice: (state) => state.instance.tos termsofservice: (state) => state.instance.tos
@ -41,14 +46,19 @@ const registration = {
}, },
methods: { methods: {
...mapActions(['signUp']), ...mapActions(['signUp']),
submit () { async submit () {
this.user.nickname = this.user.username this.user.nickname = this.user.username
this.user.token = this.token this.user.token = this.token
this.$v.$touch() this.$v.$touch()
if (!this.$v.$invalid) { 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)
}
} }
} }
} }

View file

@ -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'> <input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' placeholder='e.g. lain'>
</div> </div>
<div class="form-error" v-if="$v.user.username.$dirty"> <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>
<div class='form-group'> <div class='form-group'>
@ -25,7 +29,14 @@
<input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email"> <input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email">
</div> </div>
<div class="form-error" v-if="$v.user.email.$dirty"> <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>
<div class='form-group'> <div class='form-group'>
@ -38,7 +49,11 @@
<input :disabled="isPending" v-model='user.password' class='form-control' id='sign-up-password' type='password'> <input :disabled="isPending" v-model='user.password' class='form-control' id='sign-up-password' type='password'>
</div> </div>
<div class="form-error" v-if="$v.user.password.$dirty"> <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>
<div class='form-group' :class="{ 'form-group--error': $v.user.confirm.$error }"> <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'> <input :disabled="isPending" v-model='user.confirm' class='form-control' id='sign-up-password-confirmation' type='password'>
</div> </div>
<div class="form-error" v-if="$v.user.confirm.$dirty"> <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>
<!-- <!--
<div class='form-group'> <div class='form-group'>
@ -132,6 +154,17 @@
span { span {
font-size: 12px; font-size: 12px;
} }
}
.form-error ul {
list-style: none;
padding: 0 0 0 5px;
margin-top: 0;
li::before {
content: "• ";
}
} }
form textarea { form textarea {

View file

@ -72,7 +72,15 @@
"fullname": "Display name", "fullname": "Display name",
"password_confirm": "Password confirmation", "password_confirm": "Password confirmation",
"registration": "Registration", "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": { "settings": {
"attachmentRadius": "Attachments", "attachmentRadius": "Attachments",

View file

@ -55,7 +55,15 @@
"fullname": "Отображаемое имя", "fullname": "Отображаемое имя",
"password_confirm": "Подтверждение пароля", "password_confirm": "Подтверждение пароля",
"registration": "Регистрация", "registration": "Регистрация",
"token": "Код приглашения" "token": "Код приглашения",
"validations": {
"username_required": "не должно быть пустым",
"email_required": "не должен быть пустым",
"email_valid": "должен быть корректный email адрес",
"password_required": "не должен быть пустым",
"password_confirmation_required": "не должно быть пустым",
"password_confirmation_match": "должно совпадать с паролем"
}
}, },
"settings": { "settings": {
"attachmentRadius": "Прикреплённые файлы", "attachmentRadius": "Прикреплённые файлы",

View file

@ -52,13 +52,14 @@ export const mutations = {
}, },
[SIGN_UP.PENDING] (state) { [SIGN_UP.PENDING] (state) {
state[SIGN_UP.isPending] = true state[SIGN_UP.isPending] = true
state[SIGN_UP.errors] = []
}, },
[SIGN_UP.SUCCESS] (state) { [SIGN_UP.SUCCESS] (state) {
state[SIGN_UP.isPending] = false state[SIGN_UP.isPending] = false
}, },
[SIGN_UP.FAILURE] (state, errors) { [SIGN_UP.FAILURE] (state, errors) {
state[SIGN_UP.isPending] = false 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) { async signUp (store, userInfo) {
store.commit(SIGN_UP.PENDING) 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) { if (response.ok) {
const data = { const data = {
oauth: store.state.oauth, oauth: rootState.oauth,
instance: store.state.instance.server instance: rootState.instance.server
} }
let app = await oauthApi.getOrCreateApp(data) let app = await oauthApi.getOrCreateApp(data)
let result = await oauthApi.getTokenWithCredentials({ let result = await oauthApi.getTokenWithCredentials({
app, app,
instance: data.instance, instance: data.instance,
username: this.user.username, username: userInfo.username,
password: this.user.password password: userInfo.password
}) })
store.commit(SIGN_UP.SUCCESS) store.commit(SIGN_UP.SUCCESS)
store.commit('setToken', result.access_token) store.commit('setToken', result.access_token)
store.dispatch('loginUser', result.access_token) store.dispatch('loginUser', result.access_token)
this.$router.push('/main/friends')
} else { } else {
let data = await response.json() let data = await response.json()
let errors = humanizeErrors(JSON.parse(data.error)) let errors = humanizeErrors(JSON.parse(data.error))
store.commit(SIGN_UP.FAILURE, errors) store.commit(SIGN_UP.FAILURE, errors)
throw Error(errors)
} }
}, },
logout (store) { logout (store) {