import React from "react"; import { WidgetState } from "widgets/BaseWidget"; import { RenderModes, WidgetType } from "constants/WidgetConstants"; import PhoneInputComponent, { PhoneInputComponentProps } from "../component"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { ValidationTypes, ValidationResponse, } from "constants/WidgetValidation"; import { createMessage, FIELD_REQUIRED_ERROR, } from "@appsmith/constants/messages"; import { DerivedPropertiesMap } from "utils/WidgetFactory"; import { getCountryCode, ISDCodeDropdownOptions, } from "../component/ISDCodeDropdown"; import { AutocompleteDataType } from "utils/autocomplete/TernServer"; import _ from "lodash"; import BaseInputWidget from "widgets/BaseInputWidget"; import derivedProperties from "./parsedDerivedProperties"; import { BaseInputWidgetProps } from "widgets/BaseInputWidget/widget"; import { mergeWidgetConfig } from "utils/helpers"; import { AsYouType, CountryCode, parseIncompletePhoneNumber, } from "libphonenumber-js"; import * as Sentry from "@sentry/react"; import log from "loglevel"; export function defaultValueValidation( value: any, props: PhoneInputWidgetProps, _?: any, ): ValidationResponse { const STRING_ERROR_MESSAGE = "This value must be string"; const EMPTY_ERROR_MESSAGE = ""; if (_.isObject(value)) { return { isValid: false, parsed: JSON.stringify(value, null, 2), messages: [STRING_ERROR_MESSAGE], }; } let parsed = value; if (!_.isString(value)) { try { parsed = _.toString(value); } catch (e) { return { isValid: false, parsed: "", messages: [STRING_ERROR_MESSAGE], }; } } return { isValid: _.isString(parsed), parsed: parsed, messages: [EMPTY_ERROR_MESSAGE], }; } class PhoneInputWidget extends BaseInputWidget< PhoneInputWidgetProps, WidgetState > { static getPropertyPaneConfig() { return mergeWidgetConfig( [ { sectionName: "General", children: [ { propertyName: "allowDialCodeChange", label: "Allow country code change", helpText: "Search by country", controlType: "SWITCH", isJSConvertible: false, isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.BOOLEAN }, }, { helpText: "Changes the country code", propertyName: "dialCode", label: "Default Country Code", enableSearch: true, dropdownHeight: "195px", controlType: "DROP_DOWN", placeholderText: "Search by code or country name", options: ISDCodeDropdownOptions, isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.TEXT, }, }, { helpText: "Sets the default text of the widget. The text is updated if the default text changes", propertyName: "defaultText", label: "Default Text", controlType: "INPUT_TEXT", placeholderText: "John Doe", isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.FUNCTION, params: { fn: defaultValueValidation, expected: { type: "string", example: `000 0000`, autocompleteDataType: AutocompleteDataType.STRING, }, }, }, }, { propertyName: "allowFormatting", label: "Enable Formatting", helpText: "Formats the phone number as per the country selected", controlType: "SWITCH", isJSConvertible: true, isBindProperty: true, isTriggerProperty: false, validation: { type: ValidationTypes.BOOLEAN }, }, ], }, ], super.getPropertyPaneConfig(), ); } static getDerivedPropertiesMap(): DerivedPropertiesMap { return { isValid: `{{(() => {${derivedProperties.isValid}})()}}`, }; } static getMetaPropertiesMap(): Record { return _.merge(super.getMetaPropertiesMap(), { value: undefined, }); } getFormattedPhoneNumber(value: string) { const countryCode = getCountryCode(this.props.dialCode); let formattedValue; if (!value) { formattedValue = value; } else if (this.props.allowFormatting) { formattedValue = new AsYouType(countryCode as CountryCode).input(value); } else { formattedValue = parseIncompletePhoneNumber(value); } return formattedValue; } componentDidMount() { //format the defaultText and store it in text if (!!this.props.text) { try { const formattedValue = this.getFormattedPhoneNumber(this.props.text); this.props.updateWidgetMetaProperty("value", this.props.text); this.props.updateWidgetMetaProperty("text", formattedValue); } catch (e) { log.error(e); Sentry.captureException(e); } } } componentDidUpdate(prevProps: PhoneInputWidgetProps) { if ( this.props.renderMode === RenderModes.CANVAS && prevProps.dialCode !== this.props.dialCode ) { this.onISDCodeChange(this.props.dialCode); } if (prevProps.allowFormatting !== this.props.allowFormatting) { const formattedValue = this.getFormattedPhoneNumber(this.props.value); this.props.updateWidgetMetaProperty("text", formattedValue); } // When the default text changes if ( prevProps.text !== this.props.text && this.props.text === this.props.defaultText ) { const formattedValue = this.getFormattedPhoneNumber(this.props.text); this.props.updateWidgetMetaProperty( "value", parseIncompletePhoneNumber(formattedValue), ); this.props.updateWidgetMetaProperty("text", formattedValue); // If defaultText property has changed, reset isDirty to false this.props.updateWidgetMetaProperty("isDirty", false); } } onISDCodeChange = (dialCode?: string) => { const countryCode = getCountryCode(dialCode); if (this.props.renderMode === RenderModes.CANVAS) { super.updateWidgetProperty("dialCode", dialCode); super.updateWidgetProperty("countryCode", countryCode); } else { this.props.updateWidgetMetaProperty("dialCode", dialCode); this.props.updateWidgetMetaProperty("countryCode", countryCode); } if (this.props.value && this.props.allowFormatting) { const formattedValue = this.getFormattedPhoneNumber(this.props.value); this.props.updateWidgetMetaProperty("text", formattedValue); } }; onValueChange = (value: string) => { let formattedValue; // Don't format, as value is typed, when user is deleting if (value && value.length > this.props.text.length) { formattedValue = this.getFormattedPhoneNumber(value); } else { formattedValue = value; } this.props.updateWidgetMetaProperty( "value", parseIncompletePhoneNumber(formattedValue), ); this.props.updateWidgetMetaProperty("text", formattedValue, { triggerPropertyName: "onTextChanged", dynamicString: this.props.onTextChanged, event: { type: EventType.ON_TEXT_CHANGE, }, }); if (!this.props.isDirty) { this.props.updateWidgetMetaProperty("isDirty", true); } if (value === this.props.defaultText) { this.props.updateWidgetMetaProperty("isDirty", true); } }; handleFocusChange = (focusState: boolean) => { super.handleFocusChange(focusState); }; handleKeyDown = ( e: | React.KeyboardEvent | React.KeyboardEvent, ) => { super.handleKeyDown(e); }; getPageView() { const value = this.props.text ?? ""; const isInvalid = "isValid" in this.props && !this.props.isValid && !!this.props.isDirty; const countryCode = this.props.countryCode; const conditionalProps: Partial = {}; conditionalProps.errorMessage = this.props.errorMessage; if (this.props.isRequired && value.length === 0) { conditionalProps.errorMessage = createMessage(FIELD_REQUIRED_ERROR); } return ( ); } static getWidgetType(): WidgetType { return "PHONE_INPUT_WIDGET"; } } export interface PhoneInputWidgetProps extends BaseInputWidgetProps { dialCode?: string; countryCode?: CountryCode; defaultText?: string; allowDialCodeChange: boolean; } export default PhoneInputWidget;