diff --git a/lang/main.json b/lang/main.json index 67c6635af..ac25283ee 100644 --- a/lang/main.json +++ b/lang/main.json @@ -5,6 +5,7 @@ "copyInvite": "Copy meeting invitation", "copyLink": "Copy meeting link", "copyStream": "Copy live streaming link", + "contacts": "contacts", "countryNotSupported": "We do not support this destination yet.", "countryReminder": "Calling outside the US? Please make sure you start with the country code!", "defaultEmail": "Your Default Email", @@ -16,19 +17,14 @@ "inviteMoreMailSubject": "Join {{appName}} meeting", "inviteMorePrompt": "Invite more people", "linkCopied": "Link copied to clipboard", - "loading": "Searching for people and phone numbers", - "loadingNumber": "Validating phone number", - "loadingPeople": "Searching for people to invite", "noResults": "No matching search results", - "noValidNumbers": "Please enter a phone number", "outlookEmail": "Outlook Email", - "searchNumbers": "Add phone numbers", - "searchPeople": "Search for people", - "searchPeopleAndNumbers": "Search for people or add their phone numbers", + "phoneNumbers": "phone numbers", + "searching": "Searching...", "shareInvite": "Share meeting invitation", "shareLink": "Share the meeting link to invite others", "shareStream": "Share the live streaming link", - "sip": "SIP: {{address}}", + "sipAddresses": "sip addresses", "telephone": "Telephone: {{number}}", "title": "Invite people to this meeting", "yahooEmail": "Yahoo Email" @@ -379,6 +375,7 @@ "inviteLiveStream": "To view the live stream of this meeting, click this link: {{url}}", "invitePhone": "To join by phone instead, tap this: {{number}},,{{conferenceID}}#\n", "invitePhoneAlternatives": "Looking for a different dial-in number?\nSee meeting dial-in numbers: {{url}}\n\n\nIf also dialing-in through a room phone, join without connecting to audio: {{silentUrl}}", + "inviteSipEndpoint": "To join using the SIP address, enter this: {{sipUri}}", "inviteURLFirstPartGeneral": "You are invited to join a meeting.", "inviteURLFirstPartPersonal": "{{name}} is inviting you to a meeting.\n", "inviteURLSecondPart": "\nJoin the meeting:\n{{url}}\n", diff --git a/react/features/invite/actions.any.js b/react/features/invite/actions.any.js index a4424b952..d14329e86 100644 --- a/react/features/invite/actions.any.js +++ b/react/features/invite/actions.any.js @@ -215,7 +215,7 @@ export function updateDialInNumbers() { getDialInNumbers(dialInNumbersUrl, room, mucURL), getDialInConferenceID(dialInConfCodeUrl, room, mucURL) ]) - .then(([ dialInNumbers, { conference, id, message } ]) => { + .then(([ dialInNumbers, { conference, id, message, sipUri } ]) => { if (!conference || !id) { return Promise.reject(message); } @@ -223,7 +223,8 @@ export function updateDialInNumbers() { dispatch({ type: UPDATE_DIAL_IN_NUMBERS_SUCCESS, conferenceID: id, - dialInNumbers + dialInNumbers, + sipUri }); }) .catch(error => { diff --git a/react/features/invite/components/add-people-dialog/web/InviteContactsForm.js b/react/features/invite/components/add-people-dialog/web/InviteContactsForm.js index 0682caa5f..acdb21942 100644 --- a/react/features/invite/components/add-people-dialog/web/InviteContactsForm.js +++ b/react/features/invite/components/add-people-dialog/web/InviteContactsForm.js @@ -57,6 +57,8 @@ class InviteContactsForm extends AbstractAddPeopleDialog { _resourceClient: Object; + _translations: Object; + state = { addToCallError: false, addToCallInProgress: false, @@ -86,6 +88,16 @@ class InviteContactsForm extends AbstractAddPeopleDialog { makeQuery: this._query, parseResults: this._parseQueryResults }; + + + const { t } = props; + + this._translations = { + _dialOutEnabled: t('addPeople.phoneNumbers'), + _addPeopleEnabled: t('addPeople.contacts'), + _sipInviteEnabled: t('addPeople.sipAddresses') + }; + } /** @@ -118,30 +130,29 @@ class InviteContactsForm extends AbstractAddPeopleDialog { _addPeopleEnabled, _dialOutEnabled, _isVpaas, + _sipInviteEnabled, t } = this.props; const footerText = this._renderFooterText(); let isMultiSelectDisabled = this.state.addToCallInProgress; - let placeholder; - let loadingMessage; - let noMatches; + const loadingMessage = 'addPeople.searching'; + const noMatches = 'addPeople.noResults'; - if (_addPeopleEnabled && _dialOutEnabled) { - loadingMessage = 'addPeople.loading'; - noMatches = 'addPeople.noResults'; - placeholder = 'addPeople.searchPeopleAndNumbers'; - } else if (_addPeopleEnabled) { - loadingMessage = 'addPeople.loadingPeople'; - noMatches = 'addPeople.noResults'; - placeholder = 'addPeople.searchPeople'; - } else if (_dialOutEnabled) { - loadingMessage = 'addPeople.loadingNumber'; - noMatches = 'addPeople.noValidNumbers'; - placeholder = 'addPeople.searchNumbers'; - } else { + const features = { + _dialOutEnabled, + _addPeopleEnabled, + _sipInviteEnabled + }; + + const computedPlaceholder = Object.keys(features) + .filter(v => Boolean(features[v])) + .map(v => this._translations[v]) + .join(', '); + + const placeholder = computedPlaceholder ? `${t('dialog.add')} ${computedPlaceholder}` : t('addPeople.disabled'); + + if (!computedPlaceholder) { isMultiSelectDisabled = true; - noMatches = 'addPeople.noResults'; - placeholder = 'addPeople.disabled'; } return ( @@ -156,7 +167,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog { noMatchesFound = { t(noMatches) } onItemSelected = { this._onItemSelected } onSelectionChange = { this._onSelectionChange } - placeholder = { t(placeholder) } + placeholder = { placeholder } ref = { this._setMultiSelectElement } resourceClient = { this._resourceClient } shouldFitContainer = { true } diff --git a/react/features/invite/constants.js b/react/features/invite/constants.js index 1b8b11e92..32af28c33 100644 --- a/react/features/invite/constants.js +++ b/react/features/invite/constants.js @@ -47,7 +47,7 @@ export const OUTGOING_CALL_START_SOUND_ID = 'OUTGOING_CALL_START_SOUND_ID'; * Regex for matching sip addresses. */ // eslint-disable-next-line max-len -export const SIP_ADDRESS_REGEX = /^[a-zA-Z]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-.]+)(?::(\d+))?((?:;[^\s=?>;]+(?:=[^\s?;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/; +export const SIP_ADDRESS_REGEX = /^[+a-zA-Z0-9]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-.]+)(?::(\d+))?((?:;[^\s=?>;]+(?:=[^\s?;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/; /** * Different invite types mapping diff --git a/react/features/invite/functions.js b/react/features/invite/functions.js index 2ced54cd5..b182ff84d 100644 --- a/react/features/invite/functions.js +++ b/react/features/invite/functions.js @@ -301,6 +301,14 @@ export function getInviteText({ invite = `${invite}\n${dial}\n${moreNumbers}`; } + if (dialIn.sipUri) { + const sipText = t('info.inviteSipEndpoint', { + sipUri: dialIn.sipUri + }); + + invite = `${invite}\n${sipText}`; + } + return invite; } diff --git a/react/features/invite/reducer.js b/react/features/invite/reducer.js index b43e3e2b0..73be613bb 100644 --- a/react/features/invite/reducer.js +++ b/react/features/invite/reducer.js @@ -60,6 +60,7 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => { ...state, conferenceID: action.conferenceID, numbers: action.dialInNumbers, + sipUri: action.sipUri, numbersEnabled: true, numbersFetched: true }; diff --git a/react/features/lobby/components/AbstractLobbyScreen.js b/react/features/lobby/components/AbstractLobbyScreen.js index 50766057a..446edb507 100644 --- a/react/features/lobby/components/AbstractLobbyScreen.js +++ b/react/features/lobby/components/AbstractLobbyScreen.js @@ -46,6 +46,11 @@ export type Props = { */ _passwordJoinFailed: boolean, + /** + * True if the password field should be available for lobby participants. + */ + _renderPassword: boolean, + /** * The Redux dispatch function. */ @@ -365,6 +370,7 @@ export function _mapStateToProps(state: Object): $Shape { const localParticipant = getLocalParticipant(state); const participantId = localParticipant?.id; const { knocking, passwordJoinFailed } = state['features/lobby']; + const { iAmSipGateway } = state['features/base/config']; return { _knocking: knocking, @@ -372,6 +378,7 @@ export function _mapStateToProps(state: Object): $Shape { _participantEmail: localParticipant?.email, _participantId: participantId, _participantName: localParticipant?.name, - _passwordJoinFailed: passwordJoinFailed + _passwordJoinFailed: passwordJoinFailed, + _renderPassword: !iAmSipGateway }; } diff --git a/react/features/lobby/components/native/LobbyScreen.js b/react/features/lobby/components/native/LobbyScreen.js index 55afb2fef..b4c5711e2 100644 --- a/react/features/lobby/components/native/LobbyScreen.js +++ b/react/features/lobby/components/native/LobbyScreen.js @@ -208,7 +208,7 @@ class LobbyScreen extends AbstractLobbyScreen { * @inheritdoc */ _renderStandardButtons() { - const { _knocking, t } = this.props; + const { _knocking, _renderPassword, t } = this.props; return ( <> @@ -223,7 +223,7 @@ class LobbyScreen extends AbstractLobbyScreen { { t('lobby.knockButton') } } - { t('lobby.enterPasswordButton') } - + } ); } diff --git a/react/features/lobby/components/web/LobbyScreen.js b/react/features/lobby/components/web/LobbyScreen.js index adae11227..7a4ee5792 100644 --- a/react/features/lobby/components/web/LobbyScreen.js +++ b/react/features/lobby/components/web/LobbyScreen.js @@ -152,7 +152,7 @@ class LobbyScreen extends AbstractLobbyScreen { * @inheritdoc */ _renderStandardButtons() { - const { _knocking, t } = this.props; + const { _knocking, _renderPassword, t } = this.props; return ( <> @@ -163,12 +163,12 @@ class LobbyScreen extends AbstractLobbyScreen { type = 'primary'> { t('lobby.knockButton') } } - { t('lobby.enterPasswordButton') } - + } ); }