feat: Custom phone input in pre-chat form (#7275)

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Sivin Varghese
2023-06-26 10:26:06 +05:30
committed by GitHub
parent 996325f35b
commit 1176e5eb8a
7 changed files with 436 additions and 15 deletions

View File

@@ -7,9 +7,14 @@ import store from '../widget/store';
import App from '../widget/App.vue';
import ActionCableConnector from '../widget/helpers/actionCable';
import i18n from '../widget/i18n';
import { isPhoneE164OrEmpty } from 'shared/helpers/Validators';
import {
startsWithPlus,
isPhoneNumberValidWithDialCode,
} from 'shared/helpers/Validators';
import router from '../widget/router';
import { domPurifyConfig } from '../shared/helpers/HTMLSanitizer';
const PhoneInput = () => import('../widget/components/Form/PhoneInput');
Vue.use(VueI18n);
Vue.use(Vuelidate);
Vue.use(VueDOMPurifyHTML, domPurifyConfig);
@@ -19,8 +24,18 @@ const i18nConfig = new VueI18n({
messages: i18n,
});
Vue.use(VueFormulate, {
library: {
phoneInput: {
classification: 'number',
component: PhoneInput,
slotProps: {
component: ['placeholder', 'hasErrorInPhoneInput'],
},
},
},
rules: {
isPhoneE164OrEmpty: ({ value }) => isPhoneE164OrEmpty(value),
startsWithPlus: ({ value }) => startsWithPlus(value),
isValidPhoneNumber: ({ value }) => isPhoneNumberValidWithDialCode(value),
},
classes: {
outer: 'mb-4 wrapper',

View File

@@ -3,11 +3,13 @@
"arrow-right-outline": "M13.267 4.209a.75.75 0 0 0-1.034 1.086l6.251 5.955H3.75a.75.75 0 0 0 0 1.5h14.734l-6.251 5.954a.75.75 0 0 0 1.034 1.087l7.42-7.067a.996.996 0 0 0 .3-.58.758.758 0 0 0-.001-.29.995.995 0 0 0-.3-.578l-7.419-7.067Z",
"attach-outline": "M11.772 3.743a6 6 0 0 1 8.66 8.302l-.19.197-8.8 8.798-.036.03a3.723 3.723 0 0 1-5.489-4.973.764.764 0 0 1 .085-.13l.054-.06.086-.088.142-.148.002.003 7.436-7.454a.75.75 0 0 1 .977-.074l.084.073a.75.75 0 0 1 .074.976l-.073.084-7.594 7.613a2.23 2.23 0 0 0 3.174 3.106l8.832-8.83A4.502 4.502 0 0 0 13 4.644l-.168.16-.013.014-9.536 9.536a.75.75 0 0 1-1.133-.977l.072-.084 9.549-9.55h.002Z",
"checkmark-outline": "M4.53 12.97a.75.75 0 0 0-1.06 1.06l4.5 4.5a.75.75 0 0 0 1.06 0l11-11a.75.75 0 0 0-1.06-1.06L8.5 16.94l-3.97-3.97Z",
"chevron-down-outline": "M4.22 8.47a.75.75 0 0 1 1.06 0L12 15.19l6.72-6.72a.75.75 0 1 1 1.06 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L4.22 9.53a.75.75 0 0 1 0-1.06Z",
"chevron-left-outline": "M15.53 4.22a.75.75 0 0 1 0 1.06L8.81 12l6.72 6.72a.75.75 0 1 1-1.06 1.06l-7.25-7.25a.75.75 0 0 1 0-1.06l7.25-7.25a.75.75 0 0 1 1.06 0Z",
"chevron-right-outline": "M8.293 4.293a1 1 0 0 0 0 1.414L14.586 12l-6.293 6.293a1 1 0 1 0 1.414 1.414l7-7a1 1 0 0 0 0-1.414l-7-7a1 1 0 0 0-1.414 0Z",
"dismiss-outline": "m4.397 4.554.073-.084a.75.75 0 0 1 .976-.073l.084.073L12 10.939l6.47-6.47a.75.75 0 1 1 1.06 1.061L13.061 12l6.47 6.47a.75.75 0 0 1 .072.976l-.073.084a.75.75 0 0 1-.976.073l-.084-.073L12 13.061l-6.47 6.47a.75.75 0 0 1-1.06-1.061L10.939 12l-6.47-6.47a.75.75 0 0 1-.072-.976l.073-.084-.073.084Z",
"document-outline": "M18.5 20a.5.5 0 0 1-.5.5H6a.5.5 0 0 1-.5-.5V4a.5.5 0 0 1 .5-.5h6V8a2 2 0 0 0 2 2h4.5v10Zm-5-15.379L17.378 8.5H14a.5.5 0 0 1-.5-.5V4.621Zm5.914 3.793-5.829-5.828c-.026-.026-.058-.046-.085-.07a2.072 2.072 0 0 0-.219-.18c-.04-.027-.086-.045-.128-.068-.071-.04-.141-.084-.216-.116a1.977 1.977 0 0 0-.624-.138C12.266 2.011 12.22 2 12.172 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9.828a2 2 0 0 0-.586-1.414Z",
"emoji-outline": "M12 1.999c5.524 0 10.002 4.478 10.002 10.002 0 5.523-4.478 10.001-10.002 10.001-5.524 0-10.002-4.478-10.002-10.001C1.998 6.477 6.476 1.999 12 1.999Zm0 1.5a8.502 8.502 0 1 0 0 17.003A8.502 8.502 0 0 0 12 3.5ZM8.462 14.784A4.491 4.491 0 0 0 12 16.502a4.492 4.492 0 0 0 3.535-1.714.75.75 0 1 1 1.177.93A5.991 5.991 0 0 1 12 18.002a5.991 5.991 0 0 1-4.716-2.29.75.75 0 0 1 1.178-.928ZM9 8.75a1.25 1.25 0 1 1 0 2.499A1.25 1.25 0 0 1 9 8.75Zm6 0a1.25 1.25 0 1 1 0 2.499 1.25 1.25 0 0 1 0-2.499Z",
"globe-outline": "M12 1.999c5.524 0 10.002 4.478 10.002 10.002 0 5.523-4.478 10.001-10.002 10.001-5.524 0-10.002-4.478-10.002-10.001C1.998 6.477 6.476 1.999 12 1.999ZM14.939 16.5H9.06c.652 2.414 1.786 4.002 2.939 4.002s2.287-1.588 2.939-4.002Zm-7.43 0H4.785a8.532 8.532 0 0 0 4.094 3.411c-.522-.82-.953-1.846-1.27-3.015l-.102-.395Zm11.705 0h-2.722c-.324 1.335-.792 2.5-1.373 3.411a8.528 8.528 0 0 0 3.91-3.127l.185-.283ZM7.094 10H3.735l-.005.017a8.525 8.525 0 0 0-.233 1.984c0 1.056.193 2.067.545 3h3.173a20.847 20.847 0 0 1-.123-5Zm8.303 0H8.603a18.966 18.966 0 0 0 .135 5h6.524a18.974 18.974 0 0 0 .135-5Zm4.868 0h-3.358c.062.647.095 1.317.095 2a20.3 20.3 0 0 1-.218 3h3.173a8.482 8.482 0 0 0 .544-3c0-.689-.082-1.36-.236-2ZM8.88 4.09l-.023.008A8.531 8.531 0 0 0 4.25 8.5h3.048c.314-1.752.86-3.278 1.583-4.41ZM12 3.499l-.116.005C10.62 3.62 9.396 5.622 8.83 8.5h6.342c-.566-2.87-1.783-4.869-3.045-4.995L12 3.5Zm3.12.59.107.175c.669 1.112 1.177 2.572 1.475 4.237h3.048a8.533 8.533 0 0 0-4.339-4.29l-.291-.121Z",
"link-outline": "M9.25 7a.75.75 0 0 1 .11 1.492l-.11.008H7a3.5 3.5 0 0 0-.206 6.994L7 15.5h2.25a.75.75 0 0 1 .11 1.492L9.25 17H7a5 5 0 0 1-.25-9.994L7 7h2.25ZM17 7a5 5 0 0 1 .25 9.994L17 17h-2.25a.75.75 0 0 1-.11-1.492l.11-.008H17a3.5 3.5 0 0 0 .206-6.994L17 8.5h-2.25a.75.75 0 0 1-.11-1.492L14.75 7H17ZM7 11.25h10a.75.75 0 0 1 .102 1.493L17 12.75H7a.75.75 0 0 1-.102-1.493L7 11.25h10H7Z",
"more-vertical-outline": "M12 7.75a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5ZM12 13.75a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5ZM10.25 18a1.75 1.75 0 1 0 3.5 0 1.75 1.75 0 0 0-3.5 0Z",
"open-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v11.5c0 .966.783 1.75 1.75 1.75h11.5a1.75 1.75 0 0 0 1.75-1.75v-4a.75.75 0 0 1 1.5 0v4A3.25 3.25 0 0 1 17.75 21H6.25A3.25 3.25 0 0 1 3 17.75V6.25A3.25 3.25 0 0 1 6.25 3h4a.75.75 0 0 1 0 1.5h-4ZM13 3.75a.75.75 0 0 1 .75-.75h6.5a.75.75 0 0 1 .75.75v6.5a.75.75 0 0 1-1.5 0V5.56l-5.22 5.22a.75.75 0 0 1-1.06-1.06l5.22-5.22h-4.69a.75.75 0 0 1-.75-.75Z",

View File

@@ -1,11 +1,22 @@
export const isPhoneE164 = value => !!value.match(/^\+[1-9]\d{1,14}$/);
export const isPhoneNumberValid = (value, dialCode) => {
const number = value.replace(dialCode, '');
return !!number.match(/^[0-9]{1,14}$/);
};
export const isPhoneE164OrEmpty = value => isPhoneE164(value) || value === '';
export const isPhoneNumberValidWithDialCode = value => {
const number = value.replace(/^\+/, ''); // Remove the '+' sign
return !!number.match(/^[1-9]\d{4,}$/); // Validate the phone number with minimum 5 digits
};
export const startsWithPlus = value => value.startsWith('+');
export const shouldBeUrl = (value = '') =>
value ? value.startsWith('http') : true;
export const isValidPassword = value => {
const containsUppercase = /[A-Z]/.test(value);
const containsLowercase = /[a-z]/.test(value);
@@ -20,7 +31,9 @@ export const isValidPassword = value => {
containsSpecialCharacter
);
};
export const isNumber = value => /^\d+$/.test(value);
export const isDomain = value => {
if (value !== '') {
const domainRegex = /^([\p{L}0-9]+(-[\p{L}0-9]+)*\.)+[a-z]{2,}$/gmu;

View File

@@ -1,12 +1,56 @@
import { shouldBeUrl } from '../Validators';
import { isValidPassword } from '../Validators';
import { isNumber } from '../Validators';
import { isDomain } from '../Validators';
import {
shouldBeUrl,
isPhoneNumberValidWithDialCode,
isPhoneE164OrEmpty,
isPhoneE164,
startsWithPlus,
isValidPassword,
isPhoneNumberValid,
isNumber,
isDomain,
} from '../Validators';
describe('#shouldBeUrl', () => {
it('should return correct url', () => {
expect(shouldBeUrl('http')).toEqual(true);
});
it('should return wrong url', () => {
expect(shouldBeUrl('')).toEqual(true);
expect(shouldBeUrl('abc')).toEqual(false);
});
});
describe('#isPhoneE164', () => {
it('should return correct phone number', () => {
expect(isPhoneE164('+1234567890')).toEqual(true);
});
it('should return wrong phone number', () => {
expect(isPhoneE164('1234567890')).toEqual(false);
expect(isPhoneE164('12345678A9')).toEqual(false);
expect(isPhoneE164('+12345678901234567890')).toEqual(false);
});
});
describe('#isPhoneE164OrEmpty', () => {
it('should return correct phone number', () => {
expect(isPhoneE164OrEmpty('+1234567890')).toEqual(true);
expect(isPhoneE164OrEmpty('')).toEqual(true);
});
it('should return wrong phone number', () => {
expect(isPhoneE164OrEmpty('1234567890')).toEqual(false);
expect(isPhoneE164OrEmpty('12345678A9')).toEqual(false);
expect(isPhoneE164OrEmpty('+12345678901234567890')).toEqual(false);
});
});
describe('#isPhoneNumberValid', () => {
it('should return correct phone number', () => {
expect(isPhoneNumberValid('1234567890', '+91')).toEqual(true);
});
it('should return wrong phone number', () => {
expect(isPhoneNumberValid('12345A67890', '+1')).toEqual(false);
expect(isPhoneNumberValid('12345A6789120', '+1')).toEqual(false);
});
});
describe('#isValidPassword', () => {
@@ -51,3 +95,23 @@ describe('#isDomain', () => {
expect(isDomain('https://test.in')).toEqual(false);
});
});
describe('#isPhoneNumberValidWithDialCode', () => {
it('should return correct phone number', () => {
expect(isPhoneNumberValidWithDialCode('+123456789')).toEqual(true);
expect(isPhoneNumberValidWithDialCode('+12345')).toEqual(true);
});
it('should return wrong phone number', () => {
expect(isPhoneNumberValidWithDialCode('+123')).toEqual(false);
expect(isPhoneNumberValidWithDialCode('+1234')).toEqual(false);
});
});
describe('#startsWithPlus', () => {
it('should return correct phone number', () => {
expect(startsWithPlus('+123456789')).toEqual(true);
});
it('should return wrong phone number', () => {
expect(startsWithPlus('123456789')).toEqual(false);
});
});

View File

@@ -0,0 +1,310 @@
<template>
<div class="phone-input--wrap relative mt-2">
<div
class="phone-input rounded w-full flex items-center justify-start outline-none border border-solid"
:class="inputHasError"
>
<div
class="country-emoji--wrap h-full cursor-pointer flex items-center justify-between px-2 py-2"
:class="dropdownClass"
@click="toggleCountryDropdown"
>
<h5 v-if="activeCountry.emoji" class="text-xl mb-0">
{{ activeCountry.emoji }}
</h5>
<fluent-icon v-else icon="globe" class="fluent-icon" size="20" />
<fluent-icon icon="chevron-down" class="fluent-icon" size="12" />
</div>
<span
v-if="activeDialCode"
class="py-2 pr-0 pl-2 text-base"
:class="$dm('text-slate-700', 'dark:text-slate-50')"
>
{{ activeDialCode }}
</span>
<input
:value="phoneNumber"
type="phoneInput"
class="border-0 w-full py-2 pl-2 pr-3 leading-tight outline-none h-full rounded-r"
name="phoneNumber"
:placeholder="placeholder"
:class="inputLightAndDarkModeColor"
@input="onChange"
@blur="context.blurHandler"
/>
</div>
<div
v-if="showDropdown"
ref="dropdown"
v-on-clickaway="closeDropdown"
:class="dropdownBackgroundClass"
class="country-dropdown h-48 overflow-y-auto z-10 absolute top-12 px-0 pt-0 pl-1 pr-1 pb-1 rounded shadow-lg"
>
<div class="sticky top-0" :class="dropdownBackgroundClass">
<input
ref="searchbar"
v-model="searchCountry"
type="text"
placeholder="Search country"
class="dropdown-search h-8 text-sm mb-1 mt-1 w-full rounded py-2 px-3 outline-none border border-solid"
:class="[$dm('bg-slate-50', 'dark:bg-slate-600'), inputBorderColor]"
/>
</div>
<div
v-for="(country, index) in items"
ref="dropdownItem"
:key="index"
class="country-dropdown--item h-8 flex items-center cursor-pointer rounded py-2 px-2"
:class="[
dropdownItemClass,
country.id === activeCountryCode ? activeDropdownItemClass : '',
index === selectedIndex ? focusedDropdownItemClass : '',
]"
@click="onSelectCountry(country)"
>
<span v-if="country.emoji" class="mr-2 text-xl">{{
country.emoji
}}</span>
<span class="truncate text-sm leading-5">
{{ country.name }}
</span>
<span class="ml-2 text-xs">{{ country.dial_code }}</span>
</div>
<div v-if="items.length === 0">
<span
class="text-sm mt-4 justify-center text-center flex"
:class="$dm('text-slate-700', 'dark:text-slate-50')"
>
{{ this.$t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.DROPDOWN_EMPTY') }}
</span>
</div>
</div>
</div>
</template>
<script>
import { mixin as clickaway } from 'vue-clickaway';
import countries from 'shared/constants/countries.js';
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
import mentionSelectionKeyboardMixin from 'dashboard/components/widgets/mentions/mentionSelectionKeyboardMixin.js';
import FormulateInputMixin from '@braid/vue-formulate/src/FormulateInputMixin';
import darkModeMixin from 'widget/mixins/darkModeMixin';
export default {
components: {
FluentIcon,
},
mixins: [
mentionSelectionKeyboardMixin,
FormulateInputMixin,
darkModeMixin,
clickaway,
],
props: {
placeholder: {
type: String,
default: '',
},
hasErrorInPhoneInput: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedIndex: -1,
showDropdown: false,
searchCountry: '',
activeCountryCode: '',
activeDialCode: '',
phoneNumber: '',
};
},
computed: {
countries() {
return [
{
name: this.dropdownFirstItemName,
dial_code: '',
emoji: '',
id: '',
},
...countries,
];
},
dropdownFirstItemName() {
return this.activeCountryCode ? 'Clear selection' : 'Select Country';
},
dropdownClass() {
return `${this.$dm('bg-slate-100', 'dark:bg-slate-700')} ${this.$dm(
'text-slate-700',
'dark:text-slate-50'
)}`;
},
dropdownBackgroundClass() {
return `${this.$dm('bg-white', 'dark:bg-slate-700')} ${this.$dm(
'text-slate-700',
'dark:text-slate-50'
)}`;
},
dropdownItemClass() {
return `${this.$dm('text-slate-700', 'dark:text-slate-50')} ${this.$dm(
'hover:bg-slate-50',
'dark:hover:bg-slate-600'
)}`;
},
activeDropdownItemClass() {
return `active ${this.$dm('bg-slate-100', 'dark:bg-slate-800')}`;
},
focusedDropdownItemClass() {
return `focus ${this.$dm('bg-slate-50', 'dark:bg-slate-600')}`;
},
inputHasError() {
return this.hasErrorInPhoneInput
? `border-red-200 hover:border-red-300 focus:border-red-300 ${this.inputLightAndDarkModeColor}`
: `hover:border-black-300 focus:border-black-300 ${this.inputLightAndDarkModeColor} ${this.inputBorderColor}`;
},
inputBorderColor() {
return `${this.$dm('border-black-200', 'dark:border-black-500')}`;
},
inputLightAndDarkModeColor() {
return `${this.$dm('bg-white', 'dark:bg-slate-600')} ${this.$dm(
'text-slate-700',
'dark:text-slate-50'
)}`;
},
items() {
return this.countries.filter(country => {
const { name, dial_code, id } = country;
const search = this.searchCountry.toLowerCase();
return (
name.toLowerCase().includes(search) ||
dial_code.toLowerCase().includes(search) ||
id.toLowerCase().includes(search)
);
});
},
activeCountry() {
return (
this.countries.find(country => country.id === this.activeCountryCode) ||
''
);
},
},
methods: {
setContextValue(code) {
// This function is used to set the context value.
// The context value is used to set the value of the phone number field in the pre-chat form.
this.context.model = `${code}${this.phoneNumber}`;
},
dynamicallySetCountryCode(value) {
// This function is used to set the country code dynamically.
// The country and dial code is used to set from the value of the phone number field in the pre-chat form.
if (!value) return;
// check the number first four digit and check weather it is available in the countries array or not.
const country = countries.find(code => value.startsWith(code.dial_code));
if (country) {
// if it is available then set the country code and dial code.
this.activeCountryCode = country.id;
this.activeDialCode = country.dial_code;
// set the phone number without dial code.
this.phoneNumber = value.replace(country.dial_code, '');
}
},
onChange(e) {
this.phoneNumber = e.target.value;
this.dynamicallySetCountryCode(this.phoneNumber);
// This function is used to set the context value when the user types in the phone number field.
this.setContextValue(this.activeDialCode);
},
dropdownItem() {
// This function is used to get all the items in the dropdown.
return Array.from(
this.$refs.dropdown.querySelectorAll(
'div.country-dropdown div.country-dropdown--item'
)
);
},
focusedOrActiveItem(className) {
// This function is used to get the focused or active item in the dropdown.
return Array.from(
this.$refs.dropdown.querySelectorAll(
`div.country-dropdown div.country-dropdown--item.${className}`
)
);
},
handleKeyboardEvent(e) {
if (this.showDropdown) {
this.processKeyDownEvent(e);
this.scrollToFocusedOrActiveItem(this.focusedOrActiveItem('focus'));
}
},
onSelect() {
this.onSelectCountry(this.items[this.selectedIndex]);
},
scrollToFocusedOrActiveItem(item) {
// This function is used to scroll the dropdown to the focused or active item.
const focusedOrActiveItem = item;
if (focusedOrActiveItem.length > 0) {
const dropdown = this.$refs.dropdown;
const dropdownHeight = dropdown.clientHeight;
const itemTop = focusedOrActiveItem[0].offsetTop;
const itemHeight = focusedOrActiveItem[0].offsetHeight;
const scrollPosition = itemTop - dropdownHeight / 2 + itemHeight / 2;
dropdown.scrollTo({
top: scrollPosition,
behavior: 'auto',
});
}
},
onSelectCountry(country) {
this.activeCountryCode = country.id;
this.searchCountry = '';
this.activeDialCode = country.dial_code ? country.dial_code : '';
this.setContextValue(country.dial_code);
this.closeDropdown();
},
toggleCountryDropdown() {
this.showDropdown = !this.showDropdown;
this.selectedIndex = -1;
if (this.showDropdown) {
this.$nextTick(() => {
this.$refs.searchbar.focus();
// This is used to scroll the dropdown to the active item.
this.scrollToFocusedOrActiveItem(this.focusedOrActiveItem('active'));
});
}
},
closeDropdown() {
this.selectedIndex = -1;
this.showDropdown = false;
},
},
};
</script>
<style lang="scss" scoped>
@import '~widget/assets/scss/variables.scss';
.phone-input--wrap {
.phone-input {
height: 2.8rem;
input:placeholder-shown {
text-overflow: ellipsis;
}
}
.country-emoji--wrap {
border-bottom-left-radius: 0.18rem;
border-top-left-radius: 0.18rem;
min-width: 3.6rem;
width: 3.6rem;
}
.country-dropdown {
min-width: 6rem;
max-width: 14.8rem;
width: 100%;
}
}
</style>

View File

@@ -22,10 +22,14 @@
:label-class="context => labelClass(context)"
:input-class="context => inputClass(context)"
:validation-messages="{
isPhoneE164OrEmpty: $t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.VALID_ERROR'),
startsWithPlus: $t(
'PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.DIAL_CODE_VALID_ERROR'
),
isValidPhoneNumber: $t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.VALID_ERROR'),
email: $t('PRE_CHAT_FORM.FIELDS.EMAIL_ADDRESS.VALID_ERROR'),
required: $t('PRE_CHAT_FORM.REQUIRED'),
}"
:has-error-in-phone-input="hasErrorInPhoneInput"
/>
<FormulateInput
v-if="!hasActiveCampaign"
@@ -64,6 +68,7 @@ import { isEmptyObject } from 'widget/helpers/utils';
import routerMixin from 'widget/mixins/routerMixin';
import darkModeMixin from 'widget/mixins/darkModeMixin';
import configMixin from 'widget/mixins/configMixin';
export default {
components: {
CustomButton,
@@ -79,6 +84,7 @@ export default {
data() {
return {
locale: this.$root.$i18n.locale,
hasErrorInPhoneInput: false,
message: '',
formValues: {},
labels: {
@@ -143,7 +149,10 @@ export default {
.filter(field => field.enabled)
.map(field => ({
...field,
type: this.findFieldType(field.type),
type:
field.name === 'phoneNumber'
? 'phoneInput'
: this.findFieldType(field.type),
}));
},
conversationCustomAttributes() {
@@ -202,6 +211,9 @@ export default {
if (classification === 'box' && type === 'checkbox') {
return '';
}
if (type === 'phoneInput') {
this.hasErrorInPhoneInput = hasErrors;
}
if (!hasErrors) {
return `${this.inputStyles} hover:border-black-300 focus:border-black-300 ${this.isInputDarkOrLightMode} ${this.inputBorderColor}`;
}
@@ -224,12 +236,9 @@ export default {
return this.formValues[name] || null;
},
getValidation({ type, name }) {
if (!this.isContactFieldRequired(name)) {
return '';
}
const validations = {
emailAddress: 'email',
phoneNumber: 'isPhoneE164OrEmpty',
phoneNumber: 'startsWithPlus|isValidPhoneNumber',
url: 'url',
date: 'date',
text: null,
@@ -238,11 +247,17 @@ export default {
checkbox: false,
};
const validationKeys = Object.keys(validations);
const validation = 'bail|required';
const isRequired = this.isContactFieldRequired(name);
const validation = isRequired ? 'bail|required' : 'bail|optional';
if (validationKeys.includes(name) || validationKeys.includes(type)) {
const validationType = validations[type] || validations[name];
return validationType ? `${validation}|${validationType}` : validation;
const validationString = validationType
? `${validation}|${validationType}`
: validation;
return validationString;
}
return '';
},
findFieldType(type) {

View File

@@ -66,7 +66,9 @@
"LABEL": "Phone Number",
"PLACEHOLDER": "Please enter your phone number",
"REQUIRED_ERROR": "Phone Number is required",
"VALID_ERROR": "Phone number should be of E.164 format eg: +1415555555"
"DIAL_CODE_VALID_ERROR": "Please select a country code",
"VALID_ERROR": "Please enter a valid phone number",
"DROPDOWN_EMPTY": "No results found"
},
"MESSAGE": {
"LABEL": "Message",