validated text input (in/valid border color + icon)

This commit is contained in:
negue
2022-12-07 22:19:15 +01:00
parent e1b444ea63
commit 78093848d7
12 changed files with 138 additions and 187 deletions
+8 -1
View File
@@ -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),
},
};