Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(invite) add email autocomplete in invite #14610

Merged
merged 13 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,17 @@ var config = {
// and will automatically redirect to the token service to get the token for the meeting.
// tokenAuthUrlAutoRedirect: false

// You can put an array of values to target different entity types in the invite dialog.
// Valid values are "phone", "room", "sip", "user", "videosipgw" and "email"
// peopleSearchQueryTypes: ["user", "email"],
// Directory endpoint which is called for invite dialog autocomplete
// peopleSearchUrl: "https://myservice.com/api/people",
// Endpoint which is called to send invitation requests
// inviteServiceUrl: "https://myservice.com/api/invite",

// For external entities (e. g. email), the localStorage key holding the token value for directory authentication
// peopleSearchTokenLocation: "mytoken",

// List of undocumented settings used in jitsi-meet
/**
_immediateReloadThreshold
Expand All @@ -1565,8 +1576,6 @@ var config = {
iAmRecorder
iAmSipGateway
microsoftApiApplicationClientID
peopleSearchQueryTypes
peopleSearchUrl
requireDisplayName
*/

Expand Down
9 changes: 8 additions & 1 deletion react/features/base/avatar/components/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export interface IProps {
*/
colorBase?: string;

/**
* Indicates the default icon for the avatar.
*/
defaultIcon?: string;

/**
* Display name of the entity to render an avatar for (if any). This is handy when we need
* an avatar for a non-participant entity (e.g. A recent list item).
Expand Down Expand Up @@ -112,6 +117,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
* @static
*/
static defaultProps = {
defaultIcon: IconUser,
dynamicColor: true
};

Expand Down Expand Up @@ -172,6 +178,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
_loadableAvatarUrlUseCORS,
className,
colorBase,
defaultIcon,
dynamicColor,
id,
size,
Expand Down Expand Up @@ -229,7 +236,7 @@ class Avatar<P extends IProps> extends PureComponent<P, IState> {
}

if (navigator.product !== 'ReactNative') {
avatarProps.iconUser = IconUser;
avatarProps.iconUser = defaultIcon;
}

return (
Expand Down
1 change: 1 addition & 0 deletions react/features/base/config/configType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@ export interface IConfig {
};
pcStatsInterval?: number;
peopleSearchQueryTypes?: string[];
peopleSearchTokenLocation?: string;
peopleSearchUrl?: string;
preferBosh?: boolean;
preferVisitor?: boolean;
Expand Down
35 changes: 21 additions & 14 deletions react/features/invite/actions.any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,19 @@ export function invite(
const { conference, password } = state['features/base/conference'];

if (typeof conference === 'undefined') {
// Only keep invitees which can get an invite request from Jitsi UI
const jitsiInvitees = invitees.filter(({ type }) => type !== INVITE_TYPES.EMAIL);

// Invite will fail before CONFERENCE_JOIN. The request will be
// cached in order to be executed on CONFERENCE_JOIN.
return new Promise(resolve => {
dispatch(addPendingInviteRequest({
invitees,
callback: (failedInvitees: any) => resolve(failedInvitees)
}));
});
if (jitsiInvitees.length) {
return new Promise(resolve => {
dispatch(addPendingInviteRequest({
invitees: jitsiInvitees,
callback: (failedInvitees: any) => resolve(failedInvitees)
}));
});
}
}

let allInvitePromises: Promise<any>[] = [];
Expand All @@ -112,24 +117,26 @@ export function invite(

// For each number, dial out. On success, remove the number from
// {@link invitesLeftToSend}.
const phoneInvitePromises = phoneNumbers.map(item => {
const numberToInvite = item.number;
const phoneInvitePromises = typeof conference === 'undefined'
? []
: phoneNumbers.map(item => {
const numberToInvite = item.number;

return conference.dial(numberToInvite)
return conference.dial(numberToInvite)
.then(() => {
invitesLeftToSend
= invitesLeftToSend.filter(
invitee => invitee !== item);
})
.catch((error: Error) =>
logger.error('Error inviting phone number:', error));
});
});

allInvitePromises = allInvitePromises.concat(phoneInvitePromises);

const usersAndRooms
= invitesLeftToSend.filter(
({ type }) => [ INVITE_TYPES.USER, INVITE_TYPES.ROOM ].includes(type));
({ type }) => [ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.ROOM ].includes(type));

if (usersAndRooms.length) {
// Send a request to invite all the rooms and users. On success,
Expand All @@ -139,12 +146,12 @@ export function invite(
(callFlowsEnabled
? inviteServiceCallFlowsUrl : inviteServiceUrl) ?? '',
inviteUrl,
jwt,
usersAndRooms)
usersAndRooms,
state)
.then(() => {
invitesLeftToSend
= invitesLeftToSend.filter(
({ type }) => ![ INVITE_TYPES.USER, INVITE_TYPES.ROOM ].includes(type));
({ type }) => ![ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.ROOM ].includes(type));
})
.catch(error => {
dispatch(setCalleeInfoVisible(false));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export interface IProps {
*/
_peopleSearchQueryTypes: Array<string>;

/**
* The localStorage key holding the alternative token for people directory.
*/
_peopleSearchTokenLocation: string;

/**
* The URL pointing to the service allowing for people search.
*/
Expand Down Expand Up @@ -254,6 +259,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
_jwt: jwt,
_peopleSearchQueryTypes: peopleSearchQueryTypes,
_peopleSearchUrl: peopleSearchUrl,
_peopleSearchTokenLocation: peopleSearchTokenLocation,
_region: region,
_sipInviteEnabled: sipInviteEnabled
} = this.props;
Expand All @@ -266,6 +272,7 @@ export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
jwt,
peopleSearchQueryTypes,
peopleSearchUrl,
peopleSearchTokenLocation,
region,
sipInviteEnabled
};
Expand Down Expand Up @@ -295,7 +302,8 @@ export function _mapStateToProps(state: IReduxState) {
dialOutAuthUrl,
dialOutRegionUrl,
peopleSearchQueryTypes,
peopleSearchUrl
peopleSearchUrl,
peopleSearchTokenLocation
} = state['features/base/config'];

return {
Expand All @@ -308,6 +316,7 @@ export function _mapStateToProps(state: IReduxState) {
_jwt: state['features/base/jwt'].jwt ?? '',
_peopleSearchQueryTypes: peopleSearchQueryTypes ?? [],
_peopleSearchUrl: peopleSearchUrl ?? '',
_peopleSearchTokenLocation: peopleSearchTokenLocation ?? '',
_region: getMeetingRegion(state),
_sipInviteEnabled: isSipInviteEnabled(state)
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Icon from '../../../../base/icons/components/Icon';
import {
IconCheck,
IconCloseCircle,
IconEnvelope,
IconPhoneRinging,
IconSearch,
IconShare
Expand Down Expand Up @@ -260,6 +261,12 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
key: item.id || item.user_id,
title: item.name
};
case INVITE_TYPES.EMAIL:
return {
avatar: item.avatar || IconEnvelope,
key: item.id || item.user_id,
title: item.name
};
default:
return null;
}
Expand All @@ -273,7 +280,11 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
* @returns {string}
*/
_keyExtractor(item: any) {
return item.type === INVITE_TYPES.USER ? item.id || item.user_id : item.number;
if (item.type === INVITE_TYPES.USER || item.type === INVITE_TYPES.EMAIL) {
return item.id || item.user_id;
}

return item.number;
}

/**
Expand Down Expand Up @@ -451,6 +462,7 @@ class AddPeopleDialog extends AbstractAddPeopleDialog<IProps, IState> {
selected = inviteItems.find(_.matchesProperty('number', item.number));
break;
case INVITE_TYPES.USER:
case INVITE_TYPES.EMAIL:
selected = item.id
? inviteItems.find(_.matchesProperty('id', item.id))
: inviteItems.find(_.matchesProperty('user_id', item.user_id));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { IReduxState, IStore } from '../../../../app/types';
import Avatar from '../../../../base/avatar/components/Avatar';
import { translate } from '../../../../base/i18n/functions';
import Icon from '../../../../base/icons/components/Icon';
import { IconPhoneRinging } from '../../../../base/icons/svg';
import { IconEnvelope, IconPhoneRinging, IconUser } from '../../../../base/icons/svg';
import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
import Button from '../../../../base/ui/components/web/Button';
import { BUTTON_TYPES } from '../../../../base/ui/constants.any';
Expand Down Expand Up @@ -302,9 +302,12 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
* @returns {ReactElement}
*/
_getAvatar(user: any, className = 'avatar-small') {
const defaultIcon = user.type === INVITE_TYPES.EMAIL ? IconEnvelope : IconUser;

return (
<Avatar
className = { className }
defaultIcon = { defaultIcon }
size = { 32 }
status = { user.status }
url = { user.avatar } />
Expand All @@ -325,7 +328,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
_parseQueryResults(response: IInvitee[] = []) {
const { t, _dialOutEnabled } = this.props;

const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.EMAIL, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
const users = response.filter(item => userTypes.includes(item.type));
const userDisplayItems: any = [];

Expand Down
1 change: 1 addition & 0 deletions react/features/invite/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const SIP_ADDRESS_REGEX = /^[+a-zA-Z0-9]+(?:([^\s>:@]+)(?::([^\s@>]+))?@)
* Different invite types mapping.
*/
export const INVITE_TYPES = {
EMAIL: 'email',
PHONE: 'phone',
ROOM: 'room',
SIP: 'sip',
Expand Down
Loading