aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHJ <spam@hjkos.com>2018-12-06 17:39:38 +0000
committerHJ <spam@hjkos.com>2018-12-06 17:39:38 +0000
commit0cb3c4e0569e24d6f3553ed0edece19a44267917 (patch)
tree663f5315d576d8dd4a6f305df4ce2f64293207e8 /src
parent3fa9b39150c318972511882239304bc08f6f57ad (diff)
parent8987c3025dea851627af83327913cc17aadaf497 (diff)
Merge branch 'better_errors_on_registration' into 'develop'
Registration form: Client side validation + better display of server validation errors See merge request pleroma/pleroma-fe!399
Diffstat (limited to 'src')
-rw-r--r--src/components/registration/registration.js84
-rw-r--r--src/components/registration/registration.vue146
-rw-r--r--src/i18n/en.json10
-rw-r--r--src/i18n/ru.json10
-rw-r--r--src/modules/errors.js12
-rw-r--r--src/modules/users.js47
6 files changed, 236 insertions, 73 deletions
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index f7f8a720..e5ead8bc 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -1,57 +1,61 @@
-import oauthApi from '../../services/new_api/oauth.js'
+import { validationMixin } from 'vuelidate'
+import { required, sameAs } from 'vuelidate/lib/validators'
+import { mapActions, mapState } from 'vuex'
const registration = {
+ mixins: [validationMixin],
data: () => ({
- user: {},
- error: false,
- registering: false
+ user: {
+ email: '',
+ fullname: '',
+ username: '',
+ password: '',
+ confirm: ''
+ }
}),
+ validations: {
+ user: {
+ email: { required },
+ username: { required },
+ fullname: { required },
+ password: { 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')
- }
},
computed: {
- termsofservice () { return this.$store.state.instance.tos },
- token () { return this.$route.params.token }
+ token () { return this.$route.params.token },
+ ...mapState({
+ registrationOpen: (state) => state.instance.registrationOpen,
+ signedIn: (state) => !!state.users.currentUser,
+ isPending: (state) => state.users.signUpPending,
+ serverValidationErrors: (state) => state.users.signUpErrors,
+ termsOfService: (state) => state.instance.tos
+ })
},
methods: {
- submit () {
- this.registering = true
+ ...mapActions(['signUp']),
+ async submit () {
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.error = data.error
- })
- }
+
+ this.$v.$touch()
+
+ if (!this.$v.$invalid) {
+ try {
+ await this.signUp(this.user)
+ this.$router.push('/main/friends')
+ } catch (error) {
+ console.warn('Registration failed: ' + error)
}
- )
+ }
}
}
}
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 087cab6b..8cb1392b 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -7,50 +7,90 @@
<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">
+ <ul>
+ <li v-if="!$v.user.username.required">
+ <span>{{$t('registration.validations.username_required')}}</span>
+ </li>
+ </ul>
</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">
+
+ <div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
+ <label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
+ <input :disabled="isPending" v-model.trim='$v.user.fullname.$model' 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-error" v-if="$v.user.fullname.$dirty">
+ <ul>
+ <li v-if="!$v.user.fullname.required">
+ <span>{{$t('registration.validations.fullname_required')}}</span>
+ </li>
+ </ul>
</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-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_confirmation'>{{$t('registration.password_confirm')}}</label>
- <input :disabled="registering" v-model='user.confirm' class='form-control' id='password_confirmation' type='password'>
+ <div class="form-error" v-if="$v.user.email.$dirty">
+ <ul>
+ <li v-if="!$v.user.email.required">
+ <span>{{$t('registration.validations.email_required')}}</span>
+ </li>
+ </ul>
</div>
- <!--
+
<div class='form-group'>
- <label for='captcha'>Captcha</label>
- <img src='/qvittersimplesecurity/captcha.jpg' alt='captcha' class='captcha'>
- <input :disabled="registering" v-model='user.captcha' placeholder='Enter captcha' type='test' class='form-control' id='captcha'>
+ <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">
+ <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 }">
+ <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">
+ <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' v-if='token' >
<label for='token'>{{$t('registration.token')}}</label>
<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 class='terms-of-service' v-html="termsOfService">
</div>
</div>
- <div v-if="error" class='form-group'>
- <div class='alert error'>{{error}}</div>
+ <div v-if="serverValidationErrors.length" class='form-group'>
+ <div class='alert error'>
+ <span v-for="error in serverValidationErrors">{{error}}</span>
+ </div>
</div>
</form>
</div>
@@ -60,6 +100,7 @@
<script src="./registration.js"></script>
<style lang="scss">
@import '../../_variables.scss';
+$validations-cRed: #f04124;
.registration-form {
display: flex;
@@ -89,6 +130,55 @@
flex-direction: column;
padding: 0.3em 0.0em 0.3em;
line-height:24px;
+ margin-bottom: 1em;
+ }
+
+ @keyframes shakeError {
+ 0% {
+ transform: translateX(0); }
+ 15% {
+ transform: translateX(0.375rem); }
+ 30% {
+ transform: translateX(-0.375rem); }
+ 45% {
+ transform: translateX(0.375rem); }
+ 60% {
+ transform: translateX(-0.375rem); }
+ 75% {
+ transform: translateX(0.375rem); }
+ 90% {
+ transform: translateX(-0.375rem); }
+ 100% {
+ transform: translateX(0); } }
+
+ .form-group--error {
+ animation-name: shakeError;
+ animation-duration: .6s;
+ animation-timing-function: ease-in-out;
+ }
+
+ .form-group--error .form--label {
+ color: $validations-cRed;
+ color: var(--cRed, $validations-cRed);
+ }
+
+ .form-error {
+ margin-top: -0.7em;
+ text-align: left;
+
+ span {
+ font-size: 12px;
+ }
+ }
+
+ .form-error ul {
+ list-style: none;
+ padding: 0 0 0 5px;
+ margin-top: 0;
+
+ li::before {
+ content: "• ";
+ }
}
form textarea {
@@ -102,8 +192,6 @@
}
.btn {
- //align-self: flex-start;
- //width: 10em;
margin-top: 0.6em;
height: 28px;
}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 8fd546ef..d9c9995a 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": "cannot be left blank",
+ "fullname_required": "cannot be left blank",
+ "email_required": "cannot be left blank",
+ "password_required": "cannot be left blank",
+ "password_confirmation_required": "cannot be left 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..075cc6cc 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -55,7 +55,15 @@
"fullname": "Отображаемое имя",
"password_confirm": "Подтверждение пароля",
"registration": "Регистрация",
- "token": "Код приглашения"
+ "token": "Код приглашения",
+ "validations": {
+ "username_required": "не должно быть пустым",
+ "fullname_required": "не должно быть пустым",
+ "email_required": "не должен быть пустым",
+ "password_required": "не должен быть пустым",
+ "password_confirmation_required": "не должно быть пустым",
+ "password_confirmation_match": "должно совпадать с паролем"
+ }
},
"settings": {
"attachmentRadius": "Прикреплённые файлы",
diff --git a/src/modules/errors.js b/src/modules/errors.js
new file mode 100644
index 00000000..c809e1b5
--- /dev/null
+++ b/src/modules/errors.js
@@ -0,0 +1,12 @@
+import { capitalize } from 'lodash'
+
+export function humanizeErrors (errors) {
+ return Object.entries(errors).reduce((errs, [k, val]) => {
+ let message = val.reduce((acc, message) => {
+ let key = capitalize(k.replace(/_/g, ' '))
+ return acc + [key, message].join(' ') + '. '
+ }, '')
+ return [...errs, message]
+ }, [])
+}
+
diff --git a/src/modules/users.js b/src/modules/users.js
index 8630ee0d..6d966c3b 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,6 +1,8 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { compact, map, each, merge } from 'lodash'
import { set } from 'vue'
+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 +48,28 @@ export const mutations = {
setColor (state, { user: {id}, highlighted }) {
const user = state.usersObject[id]
set(user, 'highlight', highlighted)
+ },
+ signUpPending (state) {
+ state.signUpPending = true
+ state.signUpErrors = []
+ },
+ signUpSuccess (state) {
+ state.signUpPending = false
+ },
+ signUpFailure (state, errors) {
+ state.signUpPending = false
+ state.signUpErrors = errors
}
}
export const defaultState = {
+ loggingIn: false,
lastLoginName: false,
currentUser: false,
- loggingIn: false,
users: [],
- usersObject: {}
+ usersObject: {},
+ signUpPending: false,
+ signUpErrors: []
}
const users = {
@@ -80,6 +95,34 @@ const users = {
store.commit('setUserForStatus', status)
})
},
+ async signUp (store, userInfo) {
+ store.commit('signUpPending')
+
+ let rootState = store.rootState
+
+ let response = await rootState.api.backendInteractor.register(userInfo)
+ if (response.ok) {
+ const data = {
+ oauth: rootState.oauth,
+ instance: rootState.instance.server
+ }
+ let app = await oauthApi.getOrCreateApp(data)
+ let result = await oauthApi.getTokenWithCredentials({
+ app,
+ instance: data.instance,
+ username: userInfo.username,
+ password: userInfo.password
+ })
+ store.commit('signUpSuccess')
+ store.commit('setToken', result.access_token)
+ store.dispatch('loginUser', result.access_token)
+ } else {
+ let data = await response.json()
+ let errors = humanizeErrors(JSON.parse(data.error))
+ store.commit('signUpFailure', errors)
+ throw Error(errors)
+ }
+ },
logout (store) {
store.commit('clearCurrentUser')
store.commit('setToken', false)