feat: improve input validation for register form
This commit is contained in:
parent
f7eb160509
commit
05e054f501
4 changed files with 58 additions and 7 deletions
6
src/helpers/isEmail.ts
Normal file
6
src/helpers/isEmail.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
export function isEmail(email: string): Boolean {
|
||||||
|
const format = /^.+@.+$/
|
||||||
|
const match = email.match(format)
|
||||||
|
|
||||||
|
return match === null ? false : match.length > 0
|
||||||
|
}
|
|
@ -34,7 +34,6 @@
|
||||||
"email": "E-mail address",
|
"email": "E-mail address",
|
||||||
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
|
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
|
||||||
"password": "Password",
|
"password": "Password",
|
||||||
"passwordRepeat": "Retype your password",
|
|
||||||
"passwordPlaceholder": "e.g. •••••••••••",
|
"passwordPlaceholder": "e.g. •••••••••••",
|
||||||
"forgotPassword": "Forgot your password?",
|
"forgotPassword": "Forgot your password?",
|
||||||
"resetPassword": "Reset your password",
|
"resetPassword": "Reset your password",
|
||||||
|
@ -50,7 +49,10 @@
|
||||||
"authenticating": "Authenticating…",
|
"authenticating": "Authenticating…",
|
||||||
"openIdStateError": "State does not match, refusing to continue!",
|
"openIdStateError": "State does not match, refusing to continue!",
|
||||||
"openIdGeneralError": "An error occured while authenticating against the third party.",
|
"openIdGeneralError": "An error occured while authenticating against the third party.",
|
||||||
"logout": "Logout"
|
"logout": "Logout",
|
||||||
|
"emailInvalid": "Please enter a valid email address.",
|
||||||
|
"usernameRequired": "Please provide a username.",
|
||||||
|
"passwordRequired": "Please provide a password."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "Settings",
|
"title": "Settings",
|
||||||
|
|
|
@ -16,6 +16,8 @@ import {
|
||||||
faCocktail,
|
faCocktail,
|
||||||
faCoffee,
|
faCoffee,
|
||||||
faCog,
|
faCog,
|
||||||
|
faEye,
|
||||||
|
faEyeSlash,
|
||||||
faEllipsisH,
|
faEllipsisH,
|
||||||
faEllipsisV,
|
faEllipsisV,
|
||||||
faExclamation,
|
faExclamation,
|
||||||
|
@ -87,6 +89,8 @@ library.add(faCocktail)
|
||||||
library.add(faCoffee)
|
library.add(faCoffee)
|
||||||
library.add(faCog)
|
library.add(faCog)
|
||||||
library.add(faComments)
|
library.add(faComments)
|
||||||
|
library.add(faEye)
|
||||||
|
library.add(faEyeSlash)
|
||||||
library.add(faEllipsisH)
|
library.add(faEllipsisH)
|
||||||
library.add(faEllipsisV)
|
library.add(faEllipsisV)
|
||||||
library.add(faExclamation)
|
library.add(faExclamation)
|
||||||
|
|
|
@ -18,8 +18,12 @@
|
||||||
v-focus
|
v-focus
|
||||||
v-model="credentials.username"
|
v-model="credentials.username"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
|
@focusout="validateUsername"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help is-danger" v-if="!usernameValid">
|
||||||
|
{{ $t('user.auth.usernameRequired') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
|
<label class="label" for="email">{{ $t('user.auth.email') }}</label>
|
||||||
|
@ -33,12 +37,16 @@
|
||||||
type="email"
|
type="email"
|
||||||
v-model="credentials.email"
|
v-model="credentials.email"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
|
@focusout="validateEmail"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help is-danger" v-if="!emailValid">
|
||||||
|
{{ $t('user.auth.emailInvalid') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
|
<label class="label" for="password">{{ $t('user.auth.password') }}</label>
|
||||||
<div class="control">
|
<div class="control is-relative">
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
id="password"
|
id="password"
|
||||||
|
@ -49,8 +57,12 @@
|
||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
v-model="credentials.password"
|
v-model="credentials.password"
|
||||||
@keyup.enter="submit"
|
@keyup.enter="submit"
|
||||||
|
@focusout="validatePassword"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="help is-danger" v-if="!passwordValid">
|
||||||
|
{{ $t('user.auth.passwordRequired') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label" for="passwordValidation">{{ $t('user.auth.passwordRepeat') }}</label>
|
<label class="label" for="passwordValidation">{{ $t('user.auth.passwordRepeat') }}</label>
|
||||||
|
@ -76,6 +88,7 @@
|
||||||
id="register-submit"
|
id="register-submit"
|
||||||
@click="submit"
|
@click="submit"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
|
:disabled="!everythingValid"
|
||||||
>
|
>
|
||||||
{{ $t('user.auth.register') }}
|
{{ $t('user.auth.register') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
|
@ -89,12 +102,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import {useDebounceFn} from '@vueuse/core'
|
||||||
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
|
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import {store} from '@/store'
|
import {store} from '@/store'
|
||||||
import Message from '@/components/misc/message'
|
import Message from '@/components/misc/message'
|
||||||
|
import {isEmail} from '@/helpers/isEmail'
|
||||||
|
|
||||||
// FIXME: use the `beforeEnter` hook of vue-router
|
// FIXME: use the `beforeEnter` hook of vue-router
|
||||||
// Check if the user is already logged in, if so, redirect them to the homepage
|
// Check if the user is already logged in, if so, redirect them to the homepage
|
||||||
|
@ -111,20 +126,44 @@ const credentials = reactive({
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
})
|
})
|
||||||
const passwordValidation = ref('')
|
|
||||||
|
|
||||||
const loading = computed(() => store.state.loading)
|
const loading = computed(() => store.state.loading)
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
|
const DEBOUNCE_TIME = 100
|
||||||
|
|
||||||
|
// debouncing to prevent error messages when clicking on the log in button
|
||||||
|
const emailValid = ref(true)
|
||||||
|
const validateEmail = useDebounceFn(() => {
|
||||||
|
emailValid.value = isEmail(credentials.email)
|
||||||
|
}, DEBOUNCE_TIME)
|
||||||
|
|
||||||
|
const usernameValid = ref(true)
|
||||||
|
const validateUsername = useDebounceFn(() => {
|
||||||
|
usernameValid.value = credentials.username !== ''
|
||||||
|
}, DEBOUNCE_TIME)
|
||||||
|
|
||||||
|
const passwordValid = ref(true)
|
||||||
|
const validatePassword = useDebounceFn(() => {
|
||||||
|
passwordValid.value = credentials.password !== ''
|
||||||
|
}, DEBOUNCE_TIME)
|
||||||
|
|
||||||
|
const everythingValid = computed(() => {
|
||||||
|
return credentials.username !== '' &&
|
||||||
|
credentials.email !== '' &&
|
||||||
|
credentials.password !== '' &&
|
||||||
|
emailValid.value &&
|
||||||
|
usernameValid.value &&
|
||||||
|
passwordValid.value
|
||||||
|
})
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
if (credentials.password !== passwordValidation.value) {
|
if (!everythingValid.value) {
|
||||||
errorMessage.value = t('user.auth.passwordsDontMatch')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await store.dispatch('auth/register', toRaw(credentials))
|
await store.dispatch('auth/register', toRaw(credentials))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
Loading…
Reference in a new issue