// @flow import { Component } from 'react'; import { createInviteDialogEvent, sendAnalytics } from '../../../analytics'; import { NOTIFICATION_TIMEOUT_TYPE, showNotification } from '../../../notifications'; import { invite } from '../../actions'; import { INVITE_TYPES } from '../../constants'; import { getInviteResultsForQuery, getInviteTypeCounts, isAddPeopleEnabled, isDialOutEnabled, isSipInviteEnabled } from '../../functions'; import logger from '../../logger'; export type Props = { /** * Whether or not to show Add People functionality. */ _addPeopleEnabled: boolean, /** * Whether or not call flows are enabled. */ _callFlowsEnabled: boolean, /** * The URL for validating if a phone number can be called. */ _dialOutAuthUrl: string, /** * Whether or not to show Dial Out functionality. */ _dialOutEnabled: boolean, /** * Whether or not to allow sip invites. */ _sipInviteEnabled: boolean, /** * The JWT token. */ _jwt: string, /** * The query types used when searching people. */ _peopleSearchQueryTypes: Array, /** * The URL pointing to the service allowing for people search. */ _peopleSearchUrl: string, /** * The Redux dispatch function. */ dispatch: Function }; export type State = { /** * Indicating that an error occurred when adding people to the call. */ addToCallError: boolean, /** * Indicating that we're currently adding the new people to the * call. */ addToCallInProgress: boolean, /** * The list of invite items. */ inviteItems: Array, }; /** * Implements an abstract dialog to invite people to the conference. */ export default class AbstractAddPeopleDialog extends Component { /** * Constructor of the component. * * @inheritdoc */ constructor(props: P) { super(props); this._query = this._query.bind(this); } /** * Retrieves the notification display name for the invitee. * * @param {Object} invitee - The invitee object. * @returns {string} */ _getDisplayName(invitee) { if (invitee.type === INVITE_TYPES.PHONE) { return invitee.number; } if (invitee.type === INVITE_TYPES.SIP) { return invitee.address; } return invitee.name; } /** * Invite people and numbers to the conference. The logic works by inviting * numbers, people/rooms, sip endpoints and videosipgw in parallel. All invitees are * stored in an array. As each invite succeeds, the invitee is removed * from the array. After all invites finish, close the modal if there are * no invites left to send. If any are left, that means an invite failed * and an error state should display. * * @param {Array} invitees - The items to be invited. * @returns {Promise>} */ _invite(invitees) { const inviteTypeCounts = getInviteTypeCounts(invitees); sendAnalytics(createInviteDialogEvent( 'clicked', 'inviteButton', { ...inviteTypeCounts, inviteAllowed: this._isAddDisabled() })); if (this._isAddDisabled()) { return Promise.resolve([]); } this.setState({ addToCallInProgress: true }); const { _callFlowsEnabled, dispatch } = this.props; return dispatch(invite(invitees)) .then(invitesLeftToSend => { this.setState({ addToCallInProgress: false }); // If any invites are left that means something failed to send // so treat it as an error. if (invitesLeftToSend.length) { const erroredInviteTypeCounts = getInviteTypeCounts(invitesLeftToSend); logger.error(`${invitesLeftToSend.length} invites failed`, erroredInviteTypeCounts); sendAnalytics(createInviteDialogEvent( 'error', 'invite', { ...erroredInviteTypeCounts })); this.setState({ addToCallError: true }); } else if (!_callFlowsEnabled) { const invitedCount = invitees.length; let notificationProps; if (invitedCount >= 3) { notificationProps = { titleArguments: { name: this._getDisplayName(invitees[0]), count: invitedCount - 1 }, titleKey: 'notify.invitedThreePlusMembers' }; } else if (invitedCount === 2) { notificationProps = { titleArguments: { first: this._getDisplayName(invitees[0]), second: this._getDisplayName(invitees[1]) }, titleKey: 'notify.invitedTwoMembers' }; } else if (invitedCount) { notificationProps = { titleArguments: { name: this._getDisplayName(invitees[0]) }, titleKey: 'notify.invitedOneMember' }; } if (notificationProps) { dispatch( showNotification(notificationProps, NOTIFICATION_TIMEOUT_TYPE.SHORT)); } } return invitesLeftToSend; }); } /** * Indicates if the Add button should be disabled. * * @private * @returns {boolean} - True to indicate that the Add button should * be disabled, false otherwise. */ _isAddDisabled() { return !this.state.inviteItems.length || this.state.addToCallInProgress; } _query: (?string) => Promise>; /** * Performs a people and phone number search request. * * @param {string} query - The search text. * @private * @returns {Promise} */ _query(query = '') { const { _addPeopleEnabled: addPeopleEnabled, _dialOutAuthUrl: dialOutAuthUrl, _dialOutEnabled: dialOutEnabled, _jwt: jwt, _peopleSearchQueryTypes: peopleSearchQueryTypes, _peopleSearchUrl: peopleSearchUrl, _sipInviteEnabled: sipInviteEnabled } = this.props; const options = { addPeopleEnabled, dialOutAuthUrl, dialOutEnabled, jwt, peopleSearchQueryTypes, peopleSearchUrl, sipInviteEnabled }; return getInviteResultsForQuery(query, options); } } /** * Maps (parts of) the Redux state to the props of this component. * * @param {Object} state - The Redux state. * @private * @returns {{ * _addPeopleEnabled: boolean, * _dialOutAuthUrl: string, * _dialOutEnabled: boolean, * _jwt: string, * _peopleSearchQueryTypes: Array, * _peopleSearchUrl: string * }} */ export function _mapStateToProps(state: Object) { const { callFlowsEnabled, dialOutAuthUrl, peopleSearchQueryTypes, peopleSearchUrl } = state['features/base/config']; return { _addPeopleEnabled: isAddPeopleEnabled(state), _callFlowsEnabled: callFlowsEnabled, _dialOutAuthUrl: dialOutAuthUrl, _dialOutEnabled: isDialOutEnabled(state), _jwt: state['features/base/jwt'].jwt, _peopleSearchQueryTypes: peopleSearchQueryTypes, _peopleSearchUrl: peopleSearchUrl, _sipInviteEnabled: isSipInviteEnabled(state) }; }