diff --git a/src/components/Select/FormsySelect.jsx b/src/components/Select/FormsySelect.jsx new file mode 100644 index 000000000..e87855570 --- /dev/null +++ b/src/components/Select/FormsySelect.jsx @@ -0,0 +1,33 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { HOC as hoc } from 'formsy-react' + +import Select from './Select' + +/** + * This component is a formsy wrapper for the Select component + * @param {Object} props Component props + */ +const FormsySelect = props => { + // when setValueOnly is set to true, formsy should submit the 'option.value' instead of the whole 'option' object + const { onChange, setValueOnly, options } = props + const selectedOption = props.getValue() + + const onSelectionChange = selectedOption => { + props.setValue(setValueOnly ? selectedOption.value : selectedOption) + onChange && onChange(selectedOption) + } + const value = setValueOnly + ? _.find(options, o => o.value === selectedOption) + : selectedOption + + return +} + +FormsySelect.PropTypes = { + onChange: PropTypes.func, + setValueOnly: PropTypes.bool, + options: PropTypes.array.isRequired +} + +export default hoc(FormsySelect) diff --git a/src/routes/settings/components/SettingsPanel.scss b/src/routes/settings/components/SettingsPanel.scss index 85e1255d5..da6a65df9 100644 --- a/src/routes/settings/components/SettingsPanel.scss +++ b/src/routes/settings/components/SettingsPanel.scss @@ -9,11 +9,13 @@ flex-grow: 1; margin: auto; margin-top: 20px; + margin-bottom: 260px; // gives space to show country selection list @media screen and (max-width: $screen-md - 1px) { padding: 0; margin: 0; max-width: none; + margin-bottom: 260px; // gives space to show country selection list in mobile view } } diff --git a/src/routes/settings/constants/settings.js b/src/routes/settings/constants/settings.js deleted file mode 100644 index a6a2de23c..000000000 --- a/src/routes/settings/constants/settings.js +++ /dev/null @@ -1,257 +0,0 @@ -export const COUNTRIES = -[ - { key: 'country', name: 'Afghanistan' }, - { key: 'country', name: 'Albania' }, - { key: 'country', name: 'Antarctica' }, - { key: 'country', name: 'Algeria' }, - { key: 'country', name: 'American Samoa' }, - { key: 'country', name: 'Andorra' }, - { key: 'country', name: 'Angola' }, - { key: 'country', name: 'Antigua and Barbuda' }, - { key: 'country', name: 'Azerbaijan' }, - { key: 'country', name: 'Argentina' }, - { key: 'country', name: 'Australia' }, - { key: 'country', name: 'Austria' }, - { key: 'country', name: 'Bahamas' }, - { key: 'country', name: 'Bahrain' }, - { key: 'country', name: 'Bangladesh' }, - { key: 'country', name: 'Armenia' }, - { key: 'country', name: 'Barbados' }, - { key: 'country', name: 'Belgium' }, - { key: 'country', name: 'Bermuda' }, - { key: 'country', name: 'Bhutan' }, - { key: 'country', name: 'Bolivia' }, - { key: 'country', name: 'Bosnia & Herzegowina' }, - { key: 'country', name: 'Botswana' }, - { key: 'country', name: 'Bouvet Island' }, - { key: 'country', name: 'Brazil' }, - { key: 'country', name: 'Belize' }, - { key: 'country', name: 'British Indian Ocean Territory' }, - { key: 'country', name: 'Solomon Islands' }, - { key: 'country', name: 'Virgin Islands (British)' }, - { key: 'country', name: 'Brunei Darussalam' }, - { key: 'country', name: 'Bulgaria' }, - { key: 'country', name: 'Myanmar' }, - { key: 'country', name: 'Burundi' }, - { key: 'country', name: 'Belarus' }, - { key: 'country', name: 'Cambodia' }, - { key: 'country', name: 'Cameroon' }, - { key: 'country', name: 'Canada' }, - { key: 'country', name: 'Cape Verde' }, - { key: 'country', name: 'Cayman Islands' }, - { key: 'country', name: 'Central African Republic' }, - { key: 'country', name: 'Sri Lanka' }, - { key: 'country', name: 'Chad' }, - { key: 'country', name: 'Chile' }, - { key: 'country', name: 'China' }, - { key: 'country', name: 'Taiwan' }, - { key: 'country', name: 'Christmas Island' }, - { key: 'country', name: 'Cocos (Keeling) Islands' }, - { key: 'country', name: 'Colombia' }, - { key: 'country', name: 'Comoros' }, - { key: 'country', name: 'Mayotte' }, - { key: 'country', name: 'Congo' }, - { key: 'country', name: 'Zaire' }, - { key: 'country', name: 'Cook Islands' }, - { key: 'country', name: 'Costa Rica' }, - { key: 'country', name: 'Croatia' }, - { key: 'country', name: 'Cuba' }, - { key: 'country', name: 'Cyprus' }, - { key: 'country', name: 'Czech Republic' }, - { key: 'country', name: 'Benin' }, - { key: 'country', name: 'Denmark' }, - { key: 'country', name: 'Dominica' }, - { key: 'country', name: 'Dominican Republic' }, - { key: 'country', name: 'Ecuador' }, - { key: 'country', name: 'El Salvador' }, - { key: 'country', name: 'Equatorial Guinea' }, - { key: 'country', name: 'Ethiopia' }, - { key: 'country', name: 'Eritrea' }, - { key: 'country', name: 'Estonia' }, - { key: 'country', name: 'Faroe Islands' }, - { key: 'country', name: 'Falkland Islands (Malvinas)' }, - { key: 'country', name: 'South Georgia and The S.Sandwich Is.' }, - { key: 'country', name: 'Fiji' }, - { key: 'country', name: 'Finland' }, - { key: 'country', name: 'Aland Islands' }, - { key: 'country', name: 'France' }, - { key: 'country', name: 'French Guiana' }, - { key: 'country', name: 'French Polynesia' }, - { key: 'country', name: 'French Southern Territories' }, - { key: 'country', name: 'Djibouti' }, - { key: 'country', name: 'Gabon' }, - { key: 'country', name: 'Georgia' }, - { key: 'country', name: 'Gambia' }, - { key: 'country', name: 'Palestine, State of' }, - { key: 'country', name: 'Germany' }, - { key: 'country', name: 'Ghana' }, - { key: 'country', name: 'Gibraltar' }, - { key: 'country', name: 'Kiribati' }, - { key: 'country', name: 'Greece' }, - { key: 'country', name: 'Greenland' }, - { key: 'country', name: 'Grenada' }, - { key: 'country', name: 'Guadeloupe' }, - { key: 'country', name: 'Guam' }, - { key: 'country', name: 'Guatemala' }, - { key: 'country', name: 'Guinea' }, - { key: 'country', name: 'Guyana' }, - { key: 'country', name: 'Haiti' }, - { key: 'country', name: 'Heard and Mc Donald Islands' }, - { key: 'country', name: 'Vatican City State (Holy See)' }, - { key: 'country', name: 'Honduras' }, - { key: 'country', name: 'Hong Kong' }, - { key: 'country', name: 'Hungary' }, - { key: 'country', name: 'Iceland' }, - { key: 'country', name: 'India' }, - { key: 'country', name: 'Indonesia' }, - { key: 'country', name: 'Iran' }, - { key: 'country', name: 'Iraq' }, - { key: 'country', name: 'Ireland' }, - { key: 'country', name: 'Israel' }, - { key: 'country', name: 'Italy' }, - { key: 'country', name: 'Cote D\'Ivoire' }, - { key: 'country', name: 'Jamaica' }, - { key: 'country', name: 'Japan' }, - { key: 'country', name: 'Kazakhstan' }, - { key: 'country', name: 'Jordan' }, - { key: 'country', name: 'Kenya' }, - { key: 'country', name: 'North Korea' }, - { key: 'country', name: 'South Korea' }, - { key: 'country', name: 'Kuwait' }, - { key: 'country', name: 'Kyrgyzstan' }, - { key: 'country', name: 'Lao People\'s Democratic Republic' }, - { key: 'country', name: 'Lebanon' }, - { key: 'country', name: 'Lesotho' }, - { key: 'country', name: 'Latvia' }, - { key: 'country', name: 'Liberia' }, - { key: 'country', name: 'Libya' }, - { key: 'country', name: 'Liechtenstein' }, - { key: 'country', name: 'Lithuania' }, - { key: 'country', name: 'Luxembourg' }, - { key: 'country', name: 'Macau' }, - { key: 'country', name: 'Madagascar' }, - { key: 'country', name: 'Malawi' }, - { key: 'country', name: 'Malaysia' }, - { key: 'country', name: 'Maldives' }, - { key: 'country', name: 'Mali' }, - { key: 'country', name: 'Malta' }, - { key: 'country', name: 'Martinique' }, - { key: 'country', name: 'Mauritania' }, - { key: 'country', name: 'Mauritius' }, - { key: 'country', name: 'Mexico' }, - { key: 'country', name: 'Monaco' }, - { key: 'country', name: 'Mongolia' }, - { key: 'country', name: 'Moldova, Republic of' }, - { key: 'country', name: 'Montenegro' }, - { key: 'country', name: 'Montserrat' }, - { key: 'country', name: 'Morocco' }, - { key: 'country', name: 'Mozambique' }, - { key: 'country', name: 'Oman' }, - { key: 'country', name: 'Namibia' }, - { key: 'country', name: 'Nauru' }, - { key: 'country', name: 'Nepal' }, - { key: 'country', name: 'Netherlands' }, - { key: 'country', name: 'Netherlands Antilles' }, - { key: 'country', name: 'Curacao' }, - { key: 'country', name: 'Aruba' }, - { key: 'country', name: 'Sint Maarten (Dutch part)' }, - { key: 'country', name: 'Bonaire, Sint Eustatius and Saba' }, - { key: 'country', name: 'New Caledonia' }, - { key: 'country', name: 'Vanuatu' }, - { key: 'country', name: 'New Zealand' }, - { key: 'country', name: 'Nicaragua' }, - { key: 'country', name: 'Niger' }, - { key: 'country', name: 'Nigeria' }, - { key: 'country', name: 'Niue' }, - { key: 'country', name: 'Norfolk Island' }, - { key: 'country', name: 'Norway' }, - { key: 'country', name: 'Northern Mariana Islands' }, - { key: 'country', name: 'United States Minor Outlying Islands' }, - { key: 'country', name: 'Micronesia, Federated States of' }, - { key: 'country', name: 'Marshall Islands' }, - { key: 'country', name: 'Palau' }, - { key: 'country', name: 'Pakistan' }, - { key: 'country', name: 'Panama' }, - { key: 'country', name: 'Papua New Guinea' }, - { key: 'country', name: 'Paraguay' }, - { key: 'country', name: 'Peru' }, - { key: 'country', name: 'Philippines' }, - { key: 'country', name: 'Pitcairn' }, - { key: 'country', name: 'Poland' }, - { key: 'country', name: 'Portugal' }, - { key: 'country', name: 'Guinea-Bissau' }, - { key: 'country', name: 'East Timor' }, - { key: 'country', name: 'Puerto Rico' }, - { key: 'country', name: 'Qatar' }, - { key: 'country', name: 'Reunion' }, - { key: 'country', name: 'Romania' }, - { key: 'country', name: 'Russia' }, - { key: 'country', name: 'Rwanda' }, - { key: 'country', name: 'Saint Barthelemy' }, - { key: 'country', name: 'St. Helena' }, - { key: 'country', name: 'Saint Kitts and Nevis' }, - { key: 'country', name: 'Anguilla' }, - { key: 'country', name: 'Saint Lucia' }, - { key: 'country', name: 'Saint Martin (French part)' }, - { key: 'country', name: 'St. Pierre and Miquelon' }, - { key: 'country', name: 'Saint Vincent and The Grenadines' }, - { key: 'country', name: 'San Marino' }, - { key: 'country', name: 'Sao Tome and Principe' }, - { key: 'country', name: 'Saudi Arabia' }, - { key: 'country', name: 'Senegal' }, - { key: 'country', name: 'Serbia' }, - { key: 'country', name: 'Seychelles' }, - { key: 'country', name: 'Sierra Leone' }, - { key: 'country', name: 'Singapore' }, - { key: 'country', name: 'Slovakia' }, - { key: 'country', name: 'Viet Nam' }, - { key: 'country', name: 'Slovenia' }, - { key: 'country', name: 'Somalia' }, - { key: 'country', name: 'South Africa' }, - { key: 'country', name: 'Zimbabwe' }, - { key: 'country', name: 'Spain' }, - { key: 'country', name: 'South Sudan' }, - { key: 'country', name: 'Sudan' }, - { key: 'country', name: 'Western Sahara' }, - { key: 'country', name: 'Suriname' }, - { key: 'country', name: 'Svalbard and Jan Mayen Islands' }, - { key: 'country', name: 'Swaziland' }, - { key: 'country', name: 'Sweden' }, - { key: 'country', name: 'Switzerland' }, - { key: 'country', name: 'Syria' }, - { key: 'country', name: 'Tajikistan' }, - { key: 'country', name: 'Thailand' }, - { key: 'country', name: 'Togo' }, - { key: 'country', name: 'Tokelau' }, - { key: 'country', name: 'Tonga' }, - { key: 'country', name: 'Trinidad and Tobago' }, - { key: 'country', name: 'United Arab Emirates' }, - { key: 'country', name: 'Tunisia' }, - { key: 'country', name: 'Turkey' }, - { key: 'country', name: 'Turkmenistan' }, - { key: 'country', name: 'Turks and Caicos Islands' }, - { key: 'country', name: 'Tuvalu' }, - { key: 'country', name: 'Uganda' }, - { key: 'country', name: 'Ukraine' }, - { key: 'country', name: 'Macedonia' }, - { key: 'country', name: 'Egypt' }, - { key: 'country', name: 'United Kingdom' }, - { key: 'country', name: 'Guernsey' }, - { key: 'country', name: 'Jersey' }, - { key: 'country', name: 'Isle of Man' }, - { key: 'country', name: 'Tanzania, United Republic of' }, - { key: 'country', name: 'United States' }, - { key: 'country', name: 'Virgin Islands (U.S.)' }, - { key: 'country', name: 'Burkina Faso' }, - { key: 'country', name: 'Uruguay' }, - { key: 'country', name: 'Uzbekistan' }, - { key: 'country', name: 'Venezuela' }, - { key: 'country', name: 'Wallis and Futuna Islands' }, - { key: 'country', name: 'Samoa' }, - { key: 'country', name: 'Yemen' }, - { key: 'country', name: 'Zambia' }, - { key: 'country', name: 'St. Kitts and Nevis' }, - { key: 'country', name: 'St. Lucia' }, - { key: 'country', name: 'St. Vincent and Grenadines' }, - { key: 'country', name: 'Palestine' } -] \ No newline at end of file diff --git a/src/routes/settings/helpers/settings.js b/src/routes/settings/helpers/settings.js index 90f22a674..d5ca1b074 100644 --- a/src/routes/settings/helpers/settings.js +++ b/src/routes/settings/helpers/settings.js @@ -5,14 +5,14 @@ import _ from 'lodash' // blank traits object which we can use if trait doesn't exist to create a new one export const blankTraits = { - 'connect_info': { // eslint-disable-line quote-props + 'connect_info': { // eslint-disable-line quote-props traitId: 'connect_info', categoryName: 'Connect User Information', traits: { data: [{}], }, }, - 'basic_info': { // eslint-disable-line quote-props + 'basic_info': { // eslint-disable-line quote-props traitId: 'basic_info', categoryName: 'Basic Info', traits: { @@ -20,7 +20,7 @@ export const blankTraits = { }, }, // to use for fallback on PROD for now - 'customer_info': { // eslint-disable-line quote-props + 'customer_info': { // eslint-disable-line quote-props traitId: 'customer_info', categoryName: 'Customer Information', traits: { @@ -33,11 +33,11 @@ export const blankTraits = { export const customerTraitId = 'connect_info' /** - * Format row member traits data to the format which can be rendered by the form + * Format row member traits data to the format which can be rendered by the form * on profile settings page. - * + * * @param {Array} traits list of member traits - * + * * @returns {Object} data formated for profile settings page form */ export const formatProfileSettings = (traits) => { @@ -56,8 +56,8 @@ export const formatProfileSettings = (traits) => { if (basicTrait) { const traitData = _.get(basicTrait, 'traits.data') if (traitData && traitData.length > 0) { + _.assign(data, _.pick(traitData[0], 'firstName', 'lastName')) data.photoUrl = traitData[0].photoURL - data.firstNLastName = `${traitData[0].firstName} ${traitData[0].lastName}` } } @@ -67,11 +67,11 @@ export const formatProfileSettings = (traits) => { /** * Applies profile settings from the form to row member traits data. * This method doesn't mutate traits. - * + * * @param {Array} traits list of member traits * @param {Object} profileSettings profile settings - * - * @returns {Array} updated member traits data + * + * @returns {Array} updated member traits data */ export const applyProfileSettingsToTraits = (traits, profileSettings) => { const existentTraits = [...traits] @@ -89,13 +89,16 @@ export const applyProfileSettingsToTraits = (traits, profileSettings) => { // TODO Revert to 'connect_info' again when PROD supports it if (trait.traitId === customerTraitId) { const updatedTrait = {...trait} - const updatedProps = _.omit(profileSettings, 'photoUrl') - + const updatedProps = _.omit(profileSettings, ['photoUrl', 'firstName', 'lastName']) + const existingProps = _.omit(_.get(trait, 'traits.data[0]'), ['firstName', 'lastName']) + const { firstName, lastName } = profileSettings + updatedTrait.traits = { ...trait.traits, data: [{ - ..._.get(trait, 'traits.data[0]'), - ...updatedProps + ...existingProps, + ...updatedProps, + firstNLastName: `${firstName} ${lastName}` }] } @@ -103,20 +106,18 @@ export const applyProfileSettingsToTraits = (traits, profileSettings) => { if (!updatedTrait.categoryName) { updatedTrait.categoryName = blankTraits[customerTraitId].categoryName } - + return updatedTrait } // to the `basic_info` we put just photoUrl, firstName and lastName if (trait.traitId === 'basic_info') { const updatedTrait = {...trait} - // get first and last name, if don't have should return `undefined` - const [, firstName, lastName] = profileSettings.firstNLastName ? profileSettings.firstNLastName.match(/([^\s]+)\s*(.*)/) : [] - const photoURL = profileSettings.photoUrl + const { photoUrl: photoURL, firstName, lastName } = profileSettings // define country similar to connect_info if not present for basic_info const country = _.get(trait, 'traits.data[0].country', profileSettings.country) - + // update only if new values are defined const updatedProps = _.omitBy({ photoURL, @@ -124,7 +125,7 @@ export const applyProfileSettingsToTraits = (traits, profileSettings) => { lastName, country, }, _.isUndefined) - + updatedTrait.traits = { ...trait.traits, data: [{ @@ -137,7 +138,7 @@ export const applyProfileSettingsToTraits = (traits, profileSettings) => { if (!updatedTrait.categoryName) { updatedTrait.categoryName = blankTraits['basic_info'].categoryName } - + return updatedTrait } diff --git a/src/routes/settings/routes/profile/components/ProfileSettingsForm.jsx b/src/routes/settings/routes/profile/components/ProfileSettingsForm.jsx index 880ffd1ff..9ac27ddc2 100644 --- a/src/routes/settings/routes/profile/components/ProfileSettingsForm.jsx +++ b/src/routes/settings/routes/profile/components/ProfileSettingsForm.jsx @@ -4,24 +4,21 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import FormsyForm from 'appirio-tech-react-components/components/Formsy' +import PhoneInput from 'appirio-tech-react-components/components/Formsy/PhoneInput' const TCFormFields = FormsyForm.Fields const Formsy = FormsyForm.Formsy import ProfileSettingsAvatar from './ProfileSettingsAvatar' -import SelectDropdown from '../../../../../components/SelectDropdown/SelectDropdown' -import { COUNTRIES } from '../../../constants/settings.js' +import FormsySelect from '../../../../../components/Select/FormsySelect' +import ISOCountries from '../../../../../helpers/ISOCountries' import './ProfileSettingsForm.scss' const companySizeRadioOptions = ['1-15', '16-50', '51-500', '500+'] -const countries = COUNTRIES.map(country => ({ - title: country.name, +const countries = _.orderBy(ISOCountries, ['name'], ['asc']).map(country => ({ + label: country.name, value: country.name, })) -countries.unshift({ - title: '- Select country -', - value: '', -}) class ProfileSettingsForm extends Component { constructor(props) { @@ -29,11 +26,48 @@ class ProfileSettingsForm extends Component { this.state = { valid: false, dirty: false, + businessPhoneValid: true, + countrySelected: null, } this.onSubmit = this.onSubmit.bind(this) this.onValid = this.onValid.bind(this) this.onInvalid = this.onInvalid.bind(this) this.onChange = this.onChange.bind(this) + this.onBusinessPhoneCountryChange = this.onBusinessPhoneCountryChange.bind(this) + this.onCountryChange = this.onCountryChange.bind(this) + } + + onCountryChange(country) { + // on country change, country code of business phone should change automatically + if (country && country.value && this.state.countrySelected !== country.value) { + this.setState({ + countrySelected: country.value, + }) + } + } + + onBusinessPhoneCountryChange({ country }) { + const { businessPhoneValid } = this.state + + if (country && country.code) { + if (this.state.countrySelected !== country.name && country.name) { + // when country code of business phone changes, the country selection should change automatically + this.refs.countrySelect.setValue(country.name) + this.setState({ + countrySelected: country.name, + }) + } + + if (!businessPhoneValid) { + this.setState({ + businessPhoneValid: true + }) + } + } else if (businessPhoneValid) { + this.setState({ + businessPhoneValid: false + }) + } } getField(label, name, isRequired=false) { @@ -43,15 +77,14 @@ class ProfileSettingsForm extends Component { // use same regexp as on server side matchRegexp: /^\+(?:[0-9] ?){6,14}[0-9]$/ } - } else if (name === 'firstNLastName') { - validations = { - // should have first and last name - matchRegexp: /([^\s])\s+([^\s]+)/ - } } + return ( - {label} + + {label} + {isRequired && * } + - {this.getField('First and last name', 'firstNLastName', true)} + {this.getField('First Name', 'firstName', true)} + {this.getField('Last Name', 'lastName', true)} {this.getField('Title', 'title', true)} - {this.getField('Business phone', 'businessPhone', true)} + + + Business Phone + * + + + this.state.businessPhoneValid + }} + ref="phoneInput" + wrapperClass={'input-container'} + name="businessPhone" + type="phone" + validationError="Invalid business phone" + showCheckMark + required + listCountry={ISOCountries} + forceCountry={this.state.countrySelected} + value={this.props.values.settings.businessPhone} + onChangeCountry={this.onBusinessPhoneCountryChange} + /> + Note: Changing the country code also updates your country selection + + {this.getField('Company name', 'companyName', true)} Company size @@ -122,9 +180,8 @@ class ProfileSettingsForm extends Component { value={this.props.values.settings.companySize} onChange={this.onFieldUpdate} options={companySizeRadioOptions.map((label) => ({option: label, label, value: label}))} - required /> - + Business address {this.getField('Address', 'address')} {this.getField('City', 'city')} @@ -146,15 +203,23 @@ class ProfileSettingsForm extends Component { - Country + + Country + * + - + Note: Changing the country also updates the country code of business phone. diff --git a/src/routes/settings/routes/profile/components/ProfileSettingsForm.scss b/src/routes/settings/routes/profile/components/ProfileSettingsForm.scss index bd6e6f8ea..7e020ac8f 100644 --- a/src/routes/settings/routes/profile/components/ProfileSettingsForm.scss +++ b/src/routes/settings/routes/profile/components/ProfileSettingsForm.scss @@ -111,6 +111,26 @@ .zip-input { max-width: 80px; } + + .phone-input-container .tc-label { + display: none; + } + + .phone-input-container .dropdown-wrap { + height: 38px; + } + + .phone-input-container .check-success-icon { + position: absolute; + top: 14px; + right: -20px; + width: 10px; + height: 10px; + } + + .phone-input-container > .input-container > input { + margin-bottom: 0; + } } .section-heading { @@ -141,3 +161,19 @@ } } } + +.requiredMarker { + color: $tc-red-100; + vertical-align: super; + font-size: 11px; +} + +.fieldLabelText { + vertical-align: top; +} + +.warningText { + font-size: 12px; + margin-top: 6px; + color: $tc-red-100; +} \ No newline at end of file