Add client validation for registration form
* also extract registration logic to users.js module
This commit is contained in:
parent
02e000b53e
commit
0029313775
6 changed files with 147 additions and 1035 deletions
|
@ -30,6 +30,7 @@
|
|||
"vue-router": "^3.0.1",
|
||||
"vue-template-compiler": "^2.3.4",
|
||||
"vue-timeago": "^3.1.2",
|
||||
"vuelidate": "^0.7.4",
|
||||
"vuex": "^3.0.1",
|
||||
"whatwg-fetch": "^2.0.3"
|
||||
},
|
||||
|
|
|
@ -1,12 +1,27 @@
|
|||
import oauthApi from '../../services/new_api/oauth.js'
|
||||
import { humanizeErrors } from '../../modules/errors'
|
||||
import { validationMixin } from 'vuelidate'
|
||||
import { required } from 'vuelidate/lib/validators'
|
||||
import { mapActions, mapState } from 'vuex'
|
||||
import { SIGN_UP } from "../../mutation_types"
|
||||
|
||||
const registration = {
|
||||
mixins: [validationMixin],
|
||||
data: () => ({
|
||||
user: {},
|
||||
errors: [],
|
||||
registering: false
|
||||
user: {
|
||||
email: '',
|
||||
username: '',
|
||||
password: '',
|
||||
confirm: ''
|
||||
},
|
||||
clientValidationFailed: false
|
||||
}),
|
||||
validations: {
|
||||
user: {
|
||||
email: { required },
|
||||
username: { required },
|
||||
password: { required },
|
||||
confirm: { required }
|
||||
}
|
||||
},
|
||||
created () {
|
||||
if ((!this.$store.state.instance.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) {
|
||||
this.$router.push('/main/all')
|
||||
|
@ -17,43 +32,24 @@ const registration = {
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
termsofservice () { return this.$store.state.instance.tos },
|
||||
token () { return this.$route.params.token }
|
||||
token () { return this.$route.params.token },
|
||||
...mapState({
|
||||
isPending: (state) => state.users[SIGN_UP.isPending],
|
||||
serverValidationErrors: (state) => state.users[SIGN_UP.errors],
|
||||
termsofservice: 'instance.tos',
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['signUp']),
|
||||
submit () {
|
||||
this.registering = true
|
||||
this.user.nickname = this.user.username
|
||||
this.user.token = this.token
|
||||
this.$store.state.api.backendInteractor.register(this.user).then(
|
||||
(response) => {
|
||||
if (response.ok) {
|
||||
const data = {
|
||||
oauth: this.$store.state.oauth,
|
||||
instance: this.$store.state.instance.server
|
||||
}
|
||||
oauthApi.getOrCreateApp(data).then((app) => {
|
||||
oauthApi.getTokenWithCredentials(
|
||||
{
|
||||
app,
|
||||
instance: data.instance,
|
||||
username: this.user.username,
|
||||
password: this.user.password
|
||||
})
|
||||
.then((result) => {
|
||||
this.$store.commit('setToken', result.access_token)
|
||||
this.$store.dispatch('loginUser', result.access_token)
|
||||
this.$router.push('/main/friends')
|
||||
})
|
||||
})
|
||||
} else {
|
||||
this.registering = false
|
||||
response.json().then((data) => {
|
||||
this.errors = humanizeErrors(JSON.parse(data.error))
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
this.$v.$touch()
|
||||
|
||||
if (!this.$v.$invalid) {
|
||||
this.signUp(this.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,29 +7,46 @@
|
|||
<form v-on:submit.prevent='submit(user)' class='registration-form'>
|
||||
<div class='container'>
|
||||
<div class='text-fields'>
|
||||
<div class='form-group'>
|
||||
<label for='username'>{{$t('login.username')}}</label>
|
||||
<input :disabled="registering" v-model='user.username' class='form-control' id='username' placeholder='e.g. lain'>
|
||||
<div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
|
||||
<label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
|
||||
<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-group'>
|
||||
<label for='fullname'>{{$t('registration.fullname')}}</label>
|
||||
<input :disabled="registering" v-model='user.fullname' class='form-control' id='fullname' placeholder='e.g. Lain Iwakura'>
|
||||
<div class="form-error" v-if="$v.user.username.$dirty">
|
||||
<span class="error-required" v-if="!$v.user.username.required">Username is required.</span>
|
||||
</div>
|
||||
|
||||
<div class='form-group'>
|
||||
<label for='email'>{{$t('registration.email')}}</label>
|
||||
<input :disabled="registering" v-model='user.email' class='form-control' id='email' type="email">
|
||||
<label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
|
||||
<input :disabled="isPending" v-model='user.fullname' class='form-control' id='sign-up-fullname' placeholder='e.g. Lain Iwakura'>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='bio'>{{$t('registration.bio')}}</label>
|
||||
<input :disabled="registering" v-model='user.bio' class='form-control' id='bio'>
|
||||
|
||||
<div class='form-group' :class="{ 'form-group--error': $v.user.email.$error }">
|
||||
<label class='form--label' for='email'>{{$t('registration.email')}}</label>
|
||||
<input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email">
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<label for='password'>{{$t('login.password')}}</label>
|
||||
<input :disabled="registering" v-model='user.password' class='form-control' id='password' type='password'>
|
||||
<div class="form-error" v-if="$v.user.email.$dirty">
|
||||
<span class="error-required" v-if="!$v.user.email.required">Email is required.</span>
|
||||
</div>
|
||||
|
||||
<div class='form-group'>
|
||||
<label for='password_confirmation'>{{$t('registration.password_confirm')}}</label>
|
||||
<input :disabled="registering" v-model='user.confirm' class='form-control' id='password_confirmation' type='password'>
|
||||
<label class='form--label' for='bio'>{{$t('registration.bio')}}</label>
|
||||
<input :disabled="isPending" v-model='user.bio' class='form-control' id='bio'>
|
||||
</div>
|
||||
|
||||
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
|
||||
<label class='form--label' for='sign-up-password'>{{$t('login.password')}}</label>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class='form-group' :class="{ 'form-group--error': $v.user.confirm.$error }">
|
||||
<label class='form--label' for='sign-up-password-confirmation'>{{$t('registration.password_confirm')}}</label>
|
||||
<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>
|
||||
</div>
|
||||
<!--
|
||||
<div class='form-group'>
|
||||
|
@ -43,15 +60,18 @@
|
|||
<input disabled='true' v-model='token' class='form-control' id='token' type='text'>
|
||||
</div>
|
||||
<div class='form-group'>
|
||||
<button :disabled="registering" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
|
||||
<button :disabled="isPending" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class='terms-of-service' v-html="termsofservice">
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="errors.length" class='form-group'>
|
||||
<div v-if="clientValidationFailed">
|
||||
<span>Form is invalid</span>
|
||||
</div>
|
||||
<div v-if="serverValidationErrors.length" class='form-group'>
|
||||
<div class='alert error'>
|
||||
<span v-for="error in errors">{{error}}</span>
|
||||
<span v-for="error in serverValidationErrors">{{error}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -91,6 +111,27 @@
|
|||
flex-direction: column;
|
||||
padding: 0.3em 0.0em 0.3em;
|
||||
line-height:24px;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.form-group--error {
|
||||
animation-name: shakeError;
|
||||
animation-duration: .6s;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
.form-group--error .form--label {
|
||||
color: #f04124;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
margin-top: -0.7em;
|
||||
margin-bottom: 0.5em;
|
||||
|
||||
text-align: left;
|
||||
span {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
form textarea {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||
import { compact, map, each, merge } from 'lodash'
|
||||
import { set } from 'vue'
|
||||
import { SIGN_UP } from '../mutation_types'
|
||||
import oauthApi from '../services/new_api/oauth'
|
||||
import {humanizeErrors} from './errors'
|
||||
|
||||
// TODO: Unify with mergeOrAdd in statuses.js
|
||||
export const mergeOrAdd = (arr, obj, item) => {
|
||||
|
@ -46,15 +49,26 @@ export const mutations = {
|
|||
setColor (state, { user: {id}, highlighted }) {
|
||||
const user = state.usersObject[id]
|
||||
set(user, 'highlight', highlighted)
|
||||
},
|
||||
[SIGN_UP.PENDING] (state) {
|
||||
state[SIGN_UP.isPending] = true
|
||||
},
|
||||
[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]
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultState = {
|
||||
lastLoginName: false,
|
||||
currentUser: false,
|
||||
loggingIn: false,
|
||||
users: [],
|
||||
usersObject: {}
|
||||
usersObject: {},
|
||||
sign_up_pending: false,
|
||||
sign_up_errors: []
|
||||
}
|
||||
|
||||
const users = {
|
||||
|
@ -80,6 +94,32 @@ const users = {
|
|||
store.commit('setUserForStatus', status)
|
||||
})
|
||||
},
|
||||
async signUp (store, userInfo) {
|
||||
store.commit(SIGN_UP.PENDING)
|
||||
|
||||
let response = await store.rootState.api.backendInteractor.register(userInfo)
|
||||
if (response.ok) {
|
||||
const data = {
|
||||
oauth: store.state.oauth,
|
||||
instance: store.state.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
|
||||
})
|
||||
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)
|
||||
}
|
||||
},
|
||||
logout (store) {
|
||||
store.commit('clearCurrentUser')
|
||||
store.commit('setToken', false)
|
||||
|
|
7
src/mutation_types.js
Normal file
7
src/mutation_types.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export const SIGN_UP = {
|
||||
SUCCESS: 'SIGN_UP_SUCCESS',
|
||||
FAILURE: 'SIGN_UP_FAILURE',
|
||||
PENDING: 'SIGN_UP_PENDING',
|
||||
isPending: 'sign_up_pending',
|
||||
errors: 'sign_up_errors'
|
||||
}
|
Loading…
Reference in a new issue