mirror of https://github.com/jitsi/jitsi-meet
feat(invite): be able to call numbers from the invite dialog (#2555)
* feat(invite): be able to call numbers from the invite dialog The major changes: - Remove DialOutDialog, its views, redux hooks, css, and images. Its main functionality has been moved into AddPeopleDialog. - Modify the AppPeopleDialog styling a bit so it is wider. - Add phone numbers to AddPeopleDialog search results. Phone numbers are validated in parallel with the request for people and then appended to the result. The validation includes an ajax to validate the number is recognized as dialable by the server. The trigger for the validation is essentially if the entered input is numbers only. - AddPeopleDialog holds onto the full object representation of an item selected in MultiSelectAutocomplete. This is so selected items can be removed on successful invite, leaving only unsuccessful items. - More granular error handling on invite so individual invitees can be removed from the selected items list. * squash: change load state, new regex for numbers * squash: change strings, auto prepend 1 if no country code, add reminderspull/2597/head jitsi-meet_2852
parent
ff8386e931
commit
4e4713c3e2
@ -1,81 +0,0 @@ |
||||
/** |
||||
* The dialog content element. |
||||
*/ |
||||
.dial-out-content { |
||||
margin-top: 5px; |
||||
|
||||
/** |
||||
* Wrap the contents in flex so items can be aligned on the same line. |
||||
*/ |
||||
.form-control { |
||||
display: flex; |
||||
} |
||||
|
||||
/** |
||||
* The style of the flag icon. |
||||
*/ |
||||
.dial-out-flag-icon { |
||||
position: absolute; |
||||
left: 5px; |
||||
top: 50%; |
||||
transform: translate(0, -50%); |
||||
} |
||||
|
||||
/** |
||||
* The style of the dial code element. |
||||
*/ |
||||
.dial-out-code { |
||||
margin-bottom: 0; |
||||
padding-left: 25px; |
||||
} |
||||
|
||||
/** |
||||
* The dial-out dialog error element. |
||||
*/ |
||||
.dial-out-error { |
||||
color: $errorColor; |
||||
} |
||||
|
||||
/** |
||||
* The style of the dial input element. |
||||
*/ |
||||
.dial-out-input { |
||||
display: inline-block; |
||||
flex: 1; |
||||
margin-left: 5px; |
||||
} |
||||
|
||||
/** |
||||
* Re-styling the default dropdown inside the dial-out-content. |
||||
*/ |
||||
.dropdown { |
||||
position: relative; |
||||
width: 65px; |
||||
} |
||||
|
||||
/** |
||||
* Re-styling the default form-control inside the dial-out-content. |
||||
*/ |
||||
.form-control { |
||||
margin-bottom: 8px; |
||||
} |
||||
|
||||
.dropdown { |
||||
position: relative; |
||||
|
||||
input { |
||||
padding-left: 16px; |
||||
|
||||
&:read-only { |
||||
color: inherit; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.dropdown-trigger-icon { |
||||
position: absolute; |
||||
right: 0; |
||||
top: 50%; |
||||
transform: translate(0, -50%); |
||||
} |
||||
} |
@ -1,35 +0,0 @@ |
||||
.flag-icon-background { |
||||
background-size: contain; |
||||
background-position: 50%; |
||||
background-repeat: no-repeat; |
||||
} |
||||
.flag-icon { |
||||
background-size: contain; |
||||
background-position: 50%; |
||||
background-repeat: no-repeat; |
||||
position: relative; |
||||
display: inline-block; |
||||
width: 1.33333333em; |
||||
line-height: 1em; |
||||
} |
||||
.flag-icon:before { |
||||
content: "\00a0"; |
||||
} |
||||
.flag-icon-au { |
||||
background-image: url(../images/countries/au.svg); |
||||
} |
||||
.flag-icon-ca { |
||||
background-image: url(../images/countries/ca.svg); |
||||
} |
||||
.flag-icon-de { |
||||
background-image: url(../images/countries/de.svg); |
||||
} |
||||
.flag-icon-gb { |
||||
background-image: url(../images/countries/gb.svg); |
||||
} |
||||
.flag-icon-fr { |
||||
background-image: url(../images/countries/fr.svg); |
||||
} |
||||
.flag-icon-us { |
||||
background-image: url(../images/countries/us.svg); |
||||
} |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 934 B |
Before Width: | Height: | Size: 220 B |
Before Width: | Height: | Size: 301 B |
Before Width: | Height: | Size: 956 B |
Before Width: | Height: | Size: 6.1 KiB |
@ -1,46 +0,0 @@ |
||||
/** |
||||
* The type of the action which signals a check for a dial-out phone number has |
||||
* succeeded. |
||||
* |
||||
* { |
||||
* type: PHONE_NUMBER_CHECKED, |
||||
* response: Object |
||||
* } |
||||
*/ |
||||
export const PHONE_NUMBER_CHECKED |
||||
= Symbol('PHONE_NUMBER_CHECKED'); |
||||
|
||||
/** |
||||
* The type of the action which signals a cancel of the dial-out operation. |
||||
* |
||||
* { |
||||
* type: DIAL_OUT_CANCELED, |
||||
* response: Object |
||||
* } |
||||
*/ |
||||
export const DIAL_OUT_CANCELED |
||||
= Symbol('DIAL_OUT_CANCELED'); |
||||
|
||||
/** |
||||
* The type of the action which signals a request for dial-out country codes has |
||||
* succeeded. |
||||
* |
||||
* { |
||||
* type: DIAL_OUT_CODES_UPDATED, |
||||
* response: Object |
||||
* } |
||||
*/ |
||||
export const DIAL_OUT_CODES_UPDATED |
||||
= Symbol('DIAL_OUT_CODES_UPDATED'); |
||||
|
||||
/** |
||||
* The type of the action which signals a failure in some of dial-out service |
||||
* requests. |
||||
* |
||||
* { |
||||
* type: DIAL_OUT_SERVICE_FAILED, |
||||
* response: Object |
||||
* } |
||||
*/ |
||||
export const DIAL_OUT_SERVICE_FAILED |
||||
= Symbol('DIAL_OUT_SERVICE_FAILED'); |
@ -1,102 +0,0 @@ |
||||
// @flow
|
||||
|
||||
import { |
||||
DIAL_OUT_CANCELED, |
||||
DIAL_OUT_CODES_UPDATED, |
||||
DIAL_OUT_SERVICE_FAILED, |
||||
PHONE_NUMBER_CHECKED |
||||
} from './actionTypes'; |
||||
|
||||
declare var $: Function; |
||||
declare var config: Object; |
||||
|
||||
/** |
||||
* Dials the given number. |
||||
* |
||||
* @returns {Function} |
||||
*/ |
||||
export function cancel() { |
||||
return { |
||||
type: DIAL_OUT_CANCELED |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Dials the given number. |
||||
* |
||||
* @param {string} dialNumber - The number to dial. |
||||
* @returns {Function} |
||||
*/ |
||||
export function dial(dialNumber: string) { |
||||
return (dispatch: Dispatch<*>, getState: Function) => { |
||||
const { conference } = getState()['features/base/conference']; |
||||
|
||||
conference.dial(dialNumber); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Sends an ajax request for dial-out country codes. |
||||
* |
||||
* @param {string} dialNumber - The dial number to check for validity. |
||||
* @returns {Function} |
||||
*/ |
||||
export function checkDialNumber(dialNumber: string) { |
||||
return (dispatch: Dispatch<*>, getState: Function) => { |
||||
const { dialOutAuthUrl } = getState()['features/base/config']; |
||||
|
||||
if (!dialOutAuthUrl) { |
||||
// no auth url, let's say it is valid
|
||||
const response = {}; |
||||
|
||||
response.allow = true; |
||||
dispatch({ |
||||
type: PHONE_NUMBER_CHECKED, |
||||
response |
||||
}); |
||||
|
||||
return; |
||||
} |
||||
|
||||
const fullUrl = `${dialOutAuthUrl}?phone=${dialNumber}`; |
||||
|
||||
$.getJSON(fullUrl) |
||||
.then(response => |
||||
dispatch({ |
||||
type: PHONE_NUMBER_CHECKED, |
||||
response |
||||
})) |
||||
.catch(error => |
||||
dispatch({ |
||||
type: DIAL_OUT_SERVICE_FAILED, |
||||
error |
||||
})); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Sends an ajax request for dial-out country codes. |
||||
* |
||||
* @returns {Function} |
||||
*/ |
||||
export function updateDialOutCodes() { |
||||
return (dispatch: Dispatch<*>, getState: Function) => { |
||||
const { dialOutCodesUrl } = getState()['features/base/config']; |
||||
|
||||
if (!dialOutCodesUrl) { |
||||
return; |
||||
} |
||||
|
||||
$.getJSON(dialOutCodesUrl) |
||||
.then(response => |
||||
dispatch({ |
||||
type: DIAL_OUT_CODES_UPDATED, |
||||
response |
||||
})) |
||||
.catch(error => |
||||
dispatch({ |
||||
type: DIAL_OUT_SERVICE_FAILED, |
||||
error |
||||
})); |
||||
}; |
||||
} |
@ -1,39 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
|
||||
/** |
||||
* Implements a React {@link Component} to render a country flag icon. |
||||
*/ |
||||
export default class CountryIcon extends Component { |
||||
/** |
||||
* {@code CountryIcon}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The css style class name. |
||||
*/ |
||||
className: PropTypes.string, |
||||
|
||||
/** |
||||
* The 2-letter country code. |
||||
*/ |
||||
countryCode: PropTypes.string |
||||
}; |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const iconClassName |
||||
= `flag-icon flag-icon-${ |
||||
this.props.countryCode} flag-icon-squared ${ |
||||
this.props.className}`;
|
||||
|
||||
return <span className = { iconClassName } />; |
||||
} |
||||
} |
@ -1,248 +0,0 @@ |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
import { Dialog } from '../../base/dialog'; |
||||
|
||||
import { cancel, checkDialNumber, dial } from '../actions'; |
||||
import DialOutNumbersForm from './DialOutNumbersForm'; |
||||
|
||||
/** |
||||
* Implements a React {@link Component} which allows the user to dial out from |
||||
* the conference. |
||||
*/ |
||||
class DialOutDialog extends Component { |
||||
/** |
||||
* {@code DialOutDialog} component's property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The redux state representing the list of dial-out codes. |
||||
*/ |
||||
_dialOutCodes: PropTypes.array, |
||||
|
||||
/** |
||||
* Property indicating if a dial number is allowed. |
||||
*/ |
||||
_isDialNumberAllowed: PropTypes.bool, |
||||
|
||||
/** |
||||
* The function performing the cancel action. |
||||
*/ |
||||
cancel: PropTypes.func, |
||||
|
||||
/** |
||||
* The function performing the phone number validity check. |
||||
*/ |
||||
checkDialNumber: PropTypes.func, |
||||
|
||||
/** |
||||
* The function performing the dial action. |
||||
*/ |
||||
dial: PropTypes.func, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code DialOutNumbersForm} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
/** |
||||
* The number to dial. |
||||
*/ |
||||
dialNumber: '', |
||||
|
||||
/** |
||||
* Indicates if the dial input is currently empty. |
||||
*/ |
||||
isDialInputEmpty: true |
||||
}; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDialNumberChange = this._onDialNumberChange.bind(this); |
||||
this._onCancel = this._onCancel.bind(this); |
||||
this._onSubmit = this._onSubmit.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { _isDialNumberAllowed } = this.props; |
||||
|
||||
return ( |
||||
<Dialog |
||||
okDisabled = { this.state.isDialInputEmpty |
||||
|| !_isDialNumberAllowed } |
||||
okTitleKey = 'dialOut.dial' |
||||
onCancel = { this._onCancel } |
||||
onSubmit = { this._onSubmit } |
||||
titleKey = 'dialOut.dialOut' |
||||
width = 'small'> |
||||
{ this._renderContent() } |
||||
</Dialog> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Formats the dial number in a way to remove all non digital characters |
||||
* from it (including spaces, brackets, dash, dot, etc.). |
||||
* |
||||
* @param {string} dialNumber - The phone number to format. |
||||
* @private |
||||
* @returns {string} - The formatted phone number. |
||||
*/ |
||||
_formatDialNumber(dialNumber) { |
||||
return dialNumber.replace(/\D/g, ''); |
||||
} |
||||
|
||||
/** |
||||
* Renders the dialog content. |
||||
* |
||||
* @returns {ReactElement} |
||||
* @private |
||||
*/ |
||||
_renderContent() { |
||||
const { _isDialNumberAllowed } = this.props; |
||||
|
||||
return ( |
||||
<div className = 'dial-out-content'> |
||||
{ _isDialNumberAllowed ? '' : this._renderErrorMessage() } |
||||
<DialOutNumbersForm |
||||
onChange = { this._onDialNumberChange } /> |
||||
</div>); |
||||
} |
||||
|
||||
/** |
||||
* Renders the error message to display if the dial phone number is not |
||||
* allowed. |
||||
* |
||||
* @returns {ReactElement} |
||||
* @private |
||||
*/ |
||||
_renderErrorMessage() { |
||||
const { t } = this.props; |
||||
|
||||
return ( |
||||
<div className = 'dial-out-error'> |
||||
{ t('dialOut.phoneNotAllowed') } |
||||
</div>); |
||||
} |
||||
|
||||
/** |
||||
* Cancel the dial out. |
||||
* |
||||
* @private |
||||
* @returns {boolean} - Returns true to indicate that the dialog should be |
||||
* closed. |
||||
*/ |
||||
_onCancel() { |
||||
this.props.cancel(); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Dials the number. |
||||
* |
||||
* @private |
||||
* @returns {boolean} - Returns true to indicate that the dialog should be |
||||
* closed. |
||||
*/ |
||||
_onSubmit() { |
||||
if (this.props._isDialNumberAllowed) { |
||||
this.props.dial(this.state.dialNumber); |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Updates the dialNumber and check for validity. |
||||
* |
||||
* @param {string} dialCode - The dial code value. |
||||
* @param {string} dialInput - The dial input value. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onDialNumberChange(dialCode, dialInput) { |
||||
let formattedDialInput, formattedNumber; |
||||
|
||||
// if there are no dial out codes it is possible they are disabled
|
||||
// so we get the input as is, it can be just a sip address
|
||||
if (this.props._dialOutCodes) { |
||||
// We remove all starting zeros from the dial input before attaching
|
||||
// it to the country code.
|
||||
formattedDialInput = dialInput.replace(/^(0+)/, ''); |
||||
|
||||
const dialNumber = `${dialCode}${formattedDialInput}`; |
||||
|
||||
formattedNumber = this._formatDialNumber(dialNumber); |
||||
|
||||
this.props.checkDialNumber(formattedNumber); |
||||
} else { |
||||
formattedNumber = formattedDialInput = dialInput; |
||||
} |
||||
|
||||
this.setState({ |
||||
dialNumber: formattedNumber, |
||||
isDialInputEmpty: !formattedDialInput |
||||
|| formattedDialInput.length === 0 |
||||
}); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code DialOutDialog}'s props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _isDialNumberAllowed: boolean |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
const { dialOutCodes, isDialNumberAllowed } = state['features/dial-out']; |
||||
|
||||
return { |
||||
/** |
||||
* List of dial-out codes. |
||||
* |
||||
* @private |
||||
* @type {array} |
||||
*/ |
||||
_dialOutCodes: dialOutCodes, |
||||
|
||||
/** |
||||
* Property indicating if a dial number is allowed. |
||||
* |
||||
* @private |
||||
* @type {boolean} |
||||
*/ |
||||
_isDialNumberAllowed: isDialNumberAllowed |
||||
}; |
||||
} |
||||
|
||||
export default translate( |
||||
connect(_mapStateToProps, { |
||||
cancel, |
||||
checkDialNumber, |
||||
dial |
||||
})(DialOutDialog)); |
@ -1,369 +0,0 @@ |
||||
import { DropdownMenuStateless as DropdownMenu } from '@atlaskit/dropdown-menu'; |
||||
import { FieldTextStateless as TextField } from '@atlaskit/field-text'; |
||||
import ChevronDownIcon from '@atlaskit/icon/glyph/chevron-down'; |
||||
import PropTypes from 'prop-types'; |
||||
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
||||
|
||||
import { translate } from '../../base/i18n'; |
||||
|
||||
import { updateDialOutCodes } from '../actions'; |
||||
import CountryIcon from './CountryIcon'; |
||||
|
||||
/** |
||||
* The default value of the country if the fetch service is unavailable. |
||||
* |
||||
* @type {{ |
||||
* code: string, |
||||
* dialCode: string, |
||||
* name: string |
||||
* }} |
||||
*/ |
||||
const DEFAULT_COUNTRY = { |
||||
code: 'US', |
||||
dialCode: '+1', |
||||
name: 'United States' |
||||
}; |
||||
|
||||
/** |
||||
* React {@code Component} responsible for fetching and displaying dial-out |
||||
* country codes, as well as dialing a phone number. |
||||
* |
||||
* @extends Component |
||||
*/ |
||||
class DialOutNumbersForm extends Component { |
||||
/** |
||||
* {@code DialOutNumbersForm}'s property types. |
||||
* |
||||
* @static |
||||
*/ |
||||
static propTypes = { |
||||
/** |
||||
* The redux state representing the list of dial-out codes. |
||||
*/ |
||||
_dialOutCodes: PropTypes.array, |
||||
|
||||
/** |
||||
* The function called on every dial input change. |
||||
*/ |
||||
onChange: PropTypes.func, |
||||
|
||||
/** |
||||
* Invoked to obtain translated strings. |
||||
*/ |
||||
t: PropTypes.func, |
||||
|
||||
/** |
||||
* Invoked to send an ajax request for dial-out codes. |
||||
*/ |
||||
updateDialOutCodes: PropTypes.func |
||||
}; |
||||
|
||||
/** |
||||
* Initializes a new {@code DialOutNumbersForm} instance. |
||||
* |
||||
* @param {Object} props - The read-only properties with which the new |
||||
* instance is to be initialized. |
||||
*/ |
||||
constructor(props) { |
||||
super(props); |
||||
|
||||
this.state = { |
||||
dialInput: '', |
||||
|
||||
/** |
||||
* Whether or not the dropdown should be open. |
||||
* |
||||
* @type {boolean} |
||||
*/ |
||||
isDropdownOpen: false, |
||||
|
||||
/** |
||||
* The selected country. |
||||
* |
||||
* @type {Object} |
||||
*/ |
||||
selectedCountry: DEFAULT_COUNTRY |
||||
}; |
||||
|
||||
/** |
||||
* The internal reference to the DOM/HTML element backing the React |
||||
* {@code Component} text input. |
||||
* |
||||
* @private |
||||
* @type {HTMLInputElement} |
||||
*/ |
||||
this._dialInputElem = null; |
||||
|
||||
// Bind event handlers so they are only bound once for every instance.
|
||||
this._onDropdownTriggerInputChange |
||||
= this._onDropdownTriggerInputChange.bind(this); |
||||
this._onInputChange = this._onInputChange.bind(this); |
||||
this._onOpenChange = this._onOpenChange.bind(this); |
||||
this._onSelect = this._onSelect.bind(this); |
||||
this._setDialInputElement = this._setDialInputElement.bind(this); |
||||
} |
||||
|
||||
/** |
||||
* Dispatches a request for dial out codes if not already present in the |
||||
* redux store. If dial out codes are present, sets a default code to |
||||
* display in the dropdown trigger. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentDidMount() { |
||||
const dialOutCodes = this.props._dialOutCodes; |
||||
|
||||
if (dialOutCodes) { |
||||
this._setDefaultCode(dialOutCodes); |
||||
} else { |
||||
this.props.updateDialOutCodes(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Monitors for dial out code updates and sets a default code to display in |
||||
* the dropdown trigger if not already set. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {void} |
||||
*/ |
||||
componentWillReceiveProps(nextProps) { |
||||
if (!this.state.selectedCountry && nextProps._dialOutCodes) { |
||||
this._setDefaultCode(nextProps._dialOutCodes); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Implements React's {@link Component#render()}. |
||||
* |
||||
* @inheritdoc |
||||
* @returns {ReactElement} |
||||
*/ |
||||
render() { |
||||
const { t, _dialOutCodes } = this.props; |
||||
|
||||
return ( |
||||
<div className = 'form-control'> |
||||
{ _dialOutCodes ? this._createDropdownMenu( |
||||
this._formatCountryCodes(_dialOutCodes)) : null } |
||||
<div className = 'dial-out-input'> |
||||
<TextField |
||||
autoFocus = { true } |
||||
isLabelHidden = { true } |
||||
label = { 'dial-out-input-field' } |
||||
onChange = { this._onInputChange } |
||||
placeholder = { t('dialOut.enterPhone') } |
||||
ref = { this._setDialInputElement } |
||||
shouldFitContainer = { true } |
||||
value = { this.state.dialInput } /> |
||||
</div> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Creates a {@code DropdownMenu} instance. |
||||
* |
||||
* @param {Array} items - The content to display within the dropdown. |
||||
* @returns {ReactElement} |
||||
*/ |
||||
_createDropdownMenu(items) { |
||||
const { code, dialCode } = this.state.selectedCountry; |
||||
|
||||
return ( |
||||
<div className = 'dropdown-container'> |
||||
<DropdownMenu |
||||
isOpen = { this.state.isDropdownOpen } |
||||
items = { [ { items } ] } |
||||
onItemActivated = { this._onSelect } |
||||
onOpenChange = { this._onOpenChange } |
||||
shouldFitContainer = { false }> |
||||
{ this._createDropdownTrigger(dialCode, code) } |
||||
</DropdownMenu> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Creates a React {@code Component} with a readonly HTMLInputElement as a |
||||
* trigger for displaying the dropdown menu. The {@code Component} will also |
||||
* display the currently selected number. |
||||
* |
||||
* @param {string} dialCode - The +xx dial code. |
||||
* @param {string} countryCode - The country 2 letter code. |
||||
* @private |
||||
* @returns {ReactElement} |
||||
*/ |
||||
_createDropdownTrigger(dialCode, countryCode) { |
||||
return ( |
||||
<div className = 'dropdown'> |
||||
<CountryIcon |
||||
className = 'dial-out-flag-icon' |
||||
countryCode = { `${countryCode}` } /> |
||||
{ /** |
||||
* FIXME Replace TextField with AtlasKit Button when an issue |
||||
* with icons shrinking due to button text is fixed. |
||||
*/ } |
||||
<TextField |
||||
className = 'input-control dial-out-code' |
||||
isLabelHidden = { true } |
||||
isReadOnly = { true } |
||||
label = 'dial-out-code' |
||||
onChange = { this._onDropdownTriggerInputChange } |
||||
type = 'text' |
||||
value = { dialCode || '' } /> |
||||
<span className = 'dropdown-trigger-icon'> |
||||
<ChevronDownIcon |
||||
label = 'expand' |
||||
size = 'small' /> |
||||
</span> |
||||
</div> |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Transforms the passed in numbers object into an array of objects that can |
||||
* be parsed by {@code DropdownMenu}. |
||||
* |
||||
* @param {Object} countryCodes - The list of country codes. |
||||
* @private |
||||
* @returns {Array<Object>} |
||||
*/ |
||||
_formatCountryCodes(countryCodes) { |
||||
return countryCodes.map(country => { |
||||
const countryIcon |
||||
= <CountryIcon countryCode = { `${country.code}` } />; |
||||
const countryElement |
||||
= <span>{countryIcon} { country.name }</span>; |
||||
|
||||
return { |
||||
content: `${country.dialCode}`, |
||||
country, |
||||
elemBefore: countryElement |
||||
}; |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Updates the dialNumber when changes to the dial text or code happen. |
||||
* |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onDialNumberChange() { |
||||
const { dialCode } = this.state.selectedCountry; |
||||
|
||||
this.props.onChange(dialCode, this.state.dialInput); |
||||
} |
||||
|
||||
/** |
||||
* This is a no-op function used to stub out TextField's onChange in order |
||||
* to prevent TextField from printing prop type validation errors. TextField |
||||
* is used as a trigger for the dropdown in {@code DialOutNumbersForm} to |
||||
* get the desired AtlasKit input look for the UI. |
||||
* |
||||
* @returns {void} |
||||
*/ |
||||
_onDropdownTriggerInputChange() { |
||||
// Intentionally left empty.
|
||||
} |
||||
|
||||
/** |
||||
* Updates the dialInput state when the input changes. |
||||
* |
||||
* @param {Object} e - The event notifying us of the change. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onInputChange(e) { |
||||
this.setState({ |
||||
dialInput: e.target.value |
||||
}, () => { |
||||
this._onDialNumberChange(); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Sets the internal state to either open or close the dropdown. If the |
||||
* dropdown is disabled, the state will always be set to false. |
||||
* |
||||
* @param {Object} dropdownEvent - The even returned from clicking on the |
||||
* dropdown trigger. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onOpenChange(dropdownEvent) { |
||||
this.setState({ |
||||
isDropdownOpen: dropdownEvent.isOpen |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Updates the internal state of the currently selected country code. |
||||
* |
||||
* @param {Object} selection - Event from choosing an dropdown option. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_onSelect(selection) { |
||||
this.setState({ |
||||
isDropdownOpen: false, |
||||
selectedCountry: selection.item.country |
||||
}, () => { |
||||
this._onDialNumberChange(); |
||||
|
||||
this._dialInputElem.focus(); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Updates the internal state of the currently selected number by defaulting |
||||
* to the first available number. |
||||
* |
||||
* @param {Object} countryCodes - The list of country codes to choose from |
||||
* for setting a default code. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setDefaultCode(countryCodes) { |
||||
this.setState({ |
||||
selectedCountry: countryCodes[0] |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Sets the internal reference to the DOM/HTML element backing the React |
||||
* {@code Component} dial input. |
||||
* |
||||
* @param {HTMLInputElement} input - The DOM/HTML element for this |
||||
* {@code Component}'s text input. |
||||
* @private |
||||
* @returns {void} |
||||
*/ |
||||
_setDialInputElement(input) { |
||||
this._dialInputElem = input; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Maps (parts of) the Redux state to the associated |
||||
* {@code DialOutNumbersForm}'s props. |
||||
* |
||||
* @param {Object} state - The Redux state. |
||||
* @private |
||||
* @returns {{ |
||||
* _dialOutCodes: Object |
||||
* }} |
||||
*/ |
||||
function _mapStateToProps(state) { |
||||
const { dialOutCodes } = state['features/dial-out']; |
||||
|
||||
return { |
||||
_dialOutCodes: dialOutCodes |
||||
}; |
||||
} |
||||
|
||||
export default translate( |
||||
connect(_mapStateToProps, { updateDialOutCodes })(DialOutNumbersForm)); |
@ -1 +0,0 @@ |
||||
export { default as DialOutDialog } from './DialOutDialog'; |
@ -1,4 +0,0 @@ |
||||
export * from './actions'; |
||||
export * from './components'; |
||||
|
||||
import './reducer'; |
@ -1,53 +0,0 @@ |
||||
import { |
||||
ReducerRegistry |
||||
} from '../base/redux'; |
||||
|
||||
import { |
||||
DIAL_OUT_CANCELED, |
||||
DIAL_OUT_CODES_UPDATED, |
||||
DIAL_OUT_SERVICE_FAILED, |
||||
PHONE_NUMBER_CHECKED |
||||
} from './actionTypes'; |
||||
|
||||
const DEFAULT_STATE = { |
||||
dialOutCodes: null, |
||||
error: null, |
||||
isDialNumberAllowed: true |
||||
}; |
||||
|
||||
ReducerRegistry.register( |
||||
'features/dial-out', |
||||
(state = DEFAULT_STATE, action) => { |
||||
switch (action.type) { |
||||
case DIAL_OUT_CANCELED: { |
||||
// if we have already downloaded codes fill them in default state
|
||||
// to skip another ajax query
|
||||
return { |
||||
...DEFAULT_STATE, |
||||
dialOutCodes: state.dialOutCodes |
||||
}; |
||||
} |
||||
case DIAL_OUT_CODES_UPDATED: { |
||||
return { |
||||
...state, |
||||
error: null, |
||||
dialOutCodes: action.response |
||||
}; |
||||
} |
||||
case DIAL_OUT_SERVICE_FAILED: { |
||||
return { |
||||
...state, |
||||
error: action.error |
||||
}; |
||||
} |
||||
case PHONE_NUMBER_CHECKED: { |
||||
return { |
||||
...state, |
||||
error: null, |
||||
isDialNumberAllowed: action.response.allow |
||||
}; |
||||
} |
||||
} |
||||
|
||||
return state; |
||||
}); |
Loading…
Reference in new issue