validated text input (in/valid border color + icon)
This commit is contained in:
@@ -163,7 +163,14 @@ input, textarea, input.form-control, textarea.form-control {
|
||||
input {
|
||||
height: 30px;
|
||||
border: 0;
|
||||
background: $white !important;
|
||||
}
|
||||
|
||||
&.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
&.is-invalid {
|
||||
border-color: $red-100 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t(settingsLabel) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
:class="{
|
||||
'is-valid': wasChanged && isValid,
|
||||
'is-invalid': wasChanged && !isValid
|
||||
}"
|
||||
>
|
||||
<input
|
||||
:value="value"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:class="{
|
||||
'is-invalid input-invalid': wasChanged && !isValid,
|
||||
'is-valid': wasChanged && isValid
|
||||
}"
|
||||
@keyup="handleChange"
|
||||
@blur="$emit('blur')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-for="issue in invalidIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ValidatedTextInput',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'update:value',
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isValid: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
settingsLabel: {
|
||||
type: String,
|
||||
},
|
||||
invalidIssues: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
wasChanged: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleChange ({ target: { value } }) {
|
||||
this.wasChanged = true;
|
||||
this.$emit('update:value', value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.label-line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -74,7 +74,7 @@ input {
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SelectList from '@/components/ui/selectList';
|
||||
import { GenericSettingMixin } from '../components/genericSettingMixin';
|
||||
|
||||
@@ -80,8 +80,8 @@ import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import CurrentPasswordInput from '../components/_currentPasswordInput';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
@@ -136,7 +136,7 @@ import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import checkIcon from '@/assets/svg/check.svg';
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -86,7 +86,7 @@ input {
|
||||
<script>
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SelectList from '@/components/ui/selectList';
|
||||
import { GenericSettingMixin } from '../components/genericSettingMixin';
|
||||
|
||||
@@ -113,9 +113,9 @@ input {
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import CurrentPasswordInput from '../components/_currentPasswordInput';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
|
||||
export default {
|
||||
components: { CurrentPasswordInput, SaveCancelButtons },
|
||||
|
||||
@@ -86,8 +86,8 @@ import axios from 'axios';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import CurrentPasswordInput from '../components/_currentPasswordInput';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
@@ -36,33 +36,12 @@
|
||||
{{ $t("changeEmailDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("email") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
:class="{ 'is-valid': validEmail }"
|
||||
>
|
||||
<input
|
||||
id="changeEmail"
|
||||
v-model="updates.newEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:class="{ 'is-invalid input-invalid': !validEmail }"
|
||||
@blur="restoreEmptyEmail()"
|
||||
>
|
||||
<div class="input-floating-checkmark">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon color check-icon"
|
||||
v-html="icons.checkIcon"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<validated-text-input
|
||||
v-model="updates.newEmail"
|
||||
settings-label="email"
|
||||
:is-valid="validEmail"
|
||||
@blur="restoreEmptyEmail()"
|
||||
/>
|
||||
|
||||
<current-password-input
|
||||
:show-forget-password="true"
|
||||
@@ -80,51 +59,6 @@
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.input-floating-checkmark {
|
||||
position: absolute;
|
||||
background: none !important;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-group.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
.input-group:not(.is-valid) {
|
||||
.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: $green-50;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -132,13 +66,13 @@ import axios from 'axios';
|
||||
import * as validator from 'validator';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import checkIcon from '@/assets/svg/check.svg';
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import CurrentPasswordInput from '../components/_currentPasswordInput';
|
||||
import CurrentPasswordInput from '../components/currentPasswordInput.vue';
|
||||
import ValidatedTextInput from '@/pages/settings/components/validatedTextInput.vue';
|
||||
|
||||
export default {
|
||||
components: { CurrentPasswordInput, SaveCancelButtons },
|
||||
components: { ValidatedTextInput, CurrentPasswordInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {
|
||||
@@ -146,9 +80,7 @@ export default {
|
||||
newEmail: '',
|
||||
password: '',
|
||||
},
|
||||
icons: Object.freeze({
|
||||
checkIcon,
|
||||
}),
|
||||
emailChanged: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -36,48 +36,20 @@
|
||||
{{ $t("changeUsernameDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("username") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
:class="{ 'is-valid': usernameValid }"
|
||||
>
|
||||
<input
|
||||
id="changeUsername"
|
||||
v-model="inputValue"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newUsername')"
|
||||
:class="{ 'is-invalid input-invalid': !usernameValid }"
|
||||
@keyup="inputChanged = true"
|
||||
@blur="restoreEmptyUsername()"
|
||||
>
|
||||
<div class="input-floating-checkmark">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon color check-icon"
|
||||
v-html="icons.checkIcon"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="issue in usernameIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
</div>
|
||||
<validated-text-input
|
||||
v-model="inputValue"
|
||||
settings-label="username"
|
||||
:is-valid="usernameValid"
|
||||
:invalid-issues="usernameIssues"
|
||||
@update:value="inputChanged = true"
|
||||
@blur="restoreEmptyUsername()"
|
||||
/>
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="usernameCannotSubmit"
|
||||
@saveClicked="changeUser('username', cleanedInputValue)"
|
||||
@cancelClicked="closeModal()"
|
||||
/>
|
||||
</div>
|
||||
<save-cancel-buttons
|
||||
:disable-save="usernameCannotSubmit"
|
||||
@saveClicked="changeUser('username', cleanedInputValue)"
|
||||
@cancelClicked="closeModal()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
@@ -86,49 +58,6 @@
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.input-floating-checkmark {
|
||||
position: absolute;
|
||||
background: none !important;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-group.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
.input-group:not(.is-valid) {
|
||||
.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: $green-50;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -136,23 +65,20 @@ import axios from 'axios';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import checkIcon from '@/assets/svg/check.svg';
|
||||
import { InlineSettingMixin } from '../components/inlineSettingMixin';
|
||||
import SaveCancelButtons from '../components/_saveCancelButtons';
|
||||
import SaveCancelButtons from '../components/saveCancelButtons.vue';
|
||||
import ValidatedTextInput from '@/pages/settings/components/validatedTextInput.vue';
|
||||
|
||||
// TODO extract usernameIssues/checks to a mixin to share between this and the authForm
|
||||
|
||||
export default {
|
||||
components: { SaveCancelButtons },
|
||||
components: { ValidatedTextInput, SaveCancelButtons },
|
||||
mixins: [InlineSettingMixin],
|
||||
data () {
|
||||
return {
|
||||
inputValue: '',
|
||||
inputChanged: false,
|
||||
usernameIssues: [],
|
||||
icons: Object.freeze({
|
||||
checkIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -200,20 +126,21 @@ export default {
|
||||
// this.localAuth.username = this.user.auth.local.username;
|
||||
this.user.flags.verifiedUsername = true;
|
||||
},
|
||||
validateUsername: debounce(function checkName (username) {
|
||||
validateUsername: debounce(async function checkName (username) {
|
||||
if (username.length <= 1 || username === this.user.auth.local.username) {
|
||||
this.usernameIssues = [];
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyUsername', {
|
||||
|
||||
const res = await this.$store.dispatch('auth:verifyUsername', {
|
||||
username,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
});
|
||||
|
||||
if (res.issues !== undefined) {
|
||||
this.usernameIssues = res.issues;
|
||||
} else {
|
||||
this.usernameIssues = [];
|
||||
}
|
||||
}, 500),
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user