diff --git a/frontend/src/components/Inputs/InputPasswordConfirmation.vue b/frontend/src/components/Inputs/InputPasswordConfirmation.vue index 56d58d9ad..900cc0a0a 100644 --- a/frontend/src/components/Inputs/InputPasswordConfirmation.vue +++ b/frontend/src/components/Inputs/InputPasswordConfirmation.vue @@ -8,7 +8,7 @@ containsLowercaseCharacter: true, containsUppercaseCharacter: true, containsNumericCharacter: true, - atLeastEightCharactera: true, + atLeastEightCharacters: true, atLeastOneSpecialCharater: true, noWhitespaceCharacters: true, }" diff --git a/frontend/src/components/Inputs/InputUsername.vue b/frontend/src/components/Inputs/InputUsername.vue index ce07a3a51..464ccec04 100644 --- a/frontend/src/components/Inputs/InputUsername.vue +++ b/frontend/src/components/Inputs/InputUsername.vue @@ -5,6 +5,7 @@ :name="name" :bails="!showAllErrors" :immediate="immediate" + vid="username" v-slot="{ errors, valid, validated, ariaInput, ariaMsg }" > @@ -41,10 +42,6 @@ export default { default: () => { return { required: true, - min: 3, - max: 20, - usernameAllowedChars: true, - usernameHyphens: true, } }, }, @@ -54,6 +51,7 @@ export default { value: { required: true, type: String }, showAllErrors: { type: Boolean, default: false }, immediate: { type: Boolean, default: false }, + unique: { type: Boolean, required: true }, }, data() { return { diff --git a/frontend/src/components/UserSettings/UserName.vue b/frontend/src/components/UserSettings/UserName.vue index 1661500f2..4fc1b2f3a 100644 --- a/frontend/src/components/UserSettings/UserName.vue +++ b/frontend/src/components/UserSettings/UserName.vue @@ -16,7 +16,7 @@
- + @@ -38,6 +38,8 @@ :name="$t('form.username')" :placeholder="$t('form.username-placeholder')" :showAllErrors="true" + :unique="true" + :rules="rules" /> @@ -48,7 +50,6 @@ :variant="disabled(invalid) ? 'light' : 'success'" @click="onSubmit" type="submit" - class="mt-4" :disabled="disabled(invalid)" > {{ $t('form.save') }} @@ -74,11 +75,20 @@ export default { return { showUserData: true, username: this.$store.state.username || '', + usernameUnique: false, + rules: { + required: true, + min: 3, + max: 20, + usernameAllowedChars: true, + usernameHyphens: true, + usernameUnique: true, + }, } }, methods: { cancelEdit() { - this.username = this.$store.state.username + this.username = this.$store.state.username || '' this.showUserData = true }, async onSubmit(event) { diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index a21117ac2..f254b93cc 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -89,6 +89,12 @@ export const queryOptIn = gql` } ` +export const checkUsername = gql` + query($username: String!) { + checkUsername(username: $username) + } +` + export const queryTransactionLink = gql` query($code: String!) { queryTransactionLink(code: $code) { diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index df4923da2..6da5db808 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -172,9 +172,9 @@ "gddCreationTime": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens einer Nachkommastelle sein", "gddSendAmount": "Das Feld {_field_} muss eine Zahl zwischen {min} und {max} mit höchstens zwei Nachkommastellen sein", "is-not": "Du kannst dir selbst keine Gradidos überweisen", - "usernmae-allowed-chars": "Der Nutzername darf nur aus Buchstaben (ohne Umlaute), Zahlen, Binde - oder Unterstrichen bestehen.", - "usernmae-hyphens": "Binde- oder Unterstriche müssen zwischen Buchstaben oder Zahlen stehen.", - "usernmae-unique": "Der Username ist bereits vergeben." + "username-allowed-chars": "Der Nutzername darf nur aus Buchstaben (ohne Umlaute), Zahlen, Binde - oder Unterstrichen bestehen.", + "username-hyphens": "Binde- oder Unterstriche müssen zwischen Buchstaben oder Zahlen stehen.", + "username-unique": "Der Nutzername ist bereits vergeben." }, "your_amount": "Dein Betrag" }, diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 0c035eee1..493346e13 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -172,9 +172,9 @@ "gddCreationTime": "The field {_field_} must be a number between {min} and {max} with at most one decimal place.", "gddSendAmount": "The {_field_} field must be a number between {min} and {max} with at most two digits after the decimal point", "is-not": "You cannot send Gradidos to yourself", - "usernmae-allowed-chars": "The username may online contain letters, numbers, hyphens or underscores.", - "usernmae-hyphens": "Hyphens or underscores must be in between letters or numbers.", - "usernmae-unique": "This username is already taken." + "username-allowed-chars": "The username may online contain letters, numbers, hyphens or underscores.", + "username-hyphens": "Hyphens or underscores must be in between letters or numbers.", + "username-unique": "This username is already taken." }, "your_amount": "Your amount" }, diff --git a/frontend/src/main.js b/frontend/src/main.js index 4809e490c..f31311ab2 100755 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -27,7 +27,7 @@ const filters = loadFilters(i18n) Vue.filter('amount', filters.amount) Vue.filter('GDD', filters.GDD) -loadAllRules(i18n) +loadAllRules(i18n, apolloProvider.defaultClient) addNavigationGuards(router, store, apolloProvider.defaultClient) diff --git a/frontend/src/validation-rules.js b/frontend/src/validation-rules.js index 54de80f0b..53b301676 100644 --- a/frontend/src/validation-rules.js +++ b/frontend/src/validation-rules.js @@ -1,8 +1,9 @@ import { configure, extend } from 'vee-validate' // eslint-disable-next-line camelcase import { required, email, min, max, is_not } from 'vee-validate/dist/rules' +import { checkUsername } from '@/graphql/queries' -export const loadAllRules = (i18nCallback) => { +export const loadAllRules = (i18nCallback, apollo) => { configure({ defaultMessage: (field, values) => { // eslint-disable-next-line @intlify/vue-i18n/no-dynamic-keys @@ -96,7 +97,7 @@ export const loadAllRules = (i18nCallback) => { message: (_, values) => i18nCallback.t('site.signup.one_number', values), }) - extend('atLeastEightCharactera', { + extend('atLeastEightCharacters', { validate(value) { return !!value.match(/.{8,}/) }, @@ -128,13 +129,33 @@ export const loadAllRules = (i18nCallback) => { validate(value) { return !!value.match(/^[a-zA-Z0-9_-]+$/) }, - message: (_, values) => i18nCallback.t('form.validation.usernmae-allowed-chars', values), + message: (_, values) => i18nCallback.t('form.validation.username-allowed-chars', values), }) extend('usernameHyphens', { validate(value) { return !!value.match(/^[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9])*$/) }, - message: (_, values) => i18nCallback.t('form.validation.usernmae-hyphens', values), + message: (_, values) => i18nCallback.t('form.validation.username-hyphens', values), + }) + + extend('usernameUnique', { + validate(value) { + if (value.match(/^(?=.{3,20}$)[a-zA-Z0-9]+(?:[_-][a-zA-Z0-9])*$/)) { + return apollo + .query({ + query: checkUsername, + variables: { username: value }, + }) + .then(({ data }) => { + return { + valid: data.checkUsername, + } + }) + } else { + return false + } + }, + message: (_, values) => i18nCallback.t('form.validation.username-unique', values), }) } diff --git a/frontend/test/testSetup.js b/frontend/test/testSetup.js index 9e844370f..347a6be4a 100644 --- a/frontend/test/testSetup.js +++ b/frontend/test/testSetup.js @@ -34,7 +34,7 @@ const i18nMock = { n: (value, format) => value, } -loadAllRules(i18nMock) +loadAllRules(i18nMock, { query: jest.fn().mockResolvedValue({ data: { checkUsername: true } }) }) global.localVue = createLocalVue()