Browse Source

ref(invite): add people form (#13207)

factor2
Gabriel Borlea 2 years ago
parent
commit
aec86cecc0
No account linked to committer's email address

+ 0
- 1
css/main.scss View File

60
 @import 'filmstrip/vertical_filmstrip';
60
 @import 'filmstrip/vertical_filmstrip';
61
 @import 'filmstrip/vertical_filmstrip_overrides';
61
 @import 'filmstrip/vertical_filmstrip_overrides';
62
 @import 'unsupported-browser/main';
62
 @import 'unsupported-browser/main';
63
-@import 'modals/invite/add-people';
64
 @import 'deep-linking/main';
63
 @import 'deep-linking/main';
65
 @import 'transcription-subtitles';
64
 @import 'transcription-subtitles';
66
 @import '_meetings_list.scss';
65
 @import '_meetings_list.scss';

+ 0
- 18
css/modals/_dialog.scss View File

12
     }
12
     }
13
 }
13
 }
14
 
14
 
15
-/**
16
- * Styling inline dialog errors.
17
- */
18
-.inline-dialog-error {
19
-    margin-top: 16px;
20
-
21
-    &-text {
22
-        color: $dialogErrorText;
23
-        margin-bottom: 8px;
24
-        text-align: center;
25
-    }
26
-
27
-    &-button {
28
-        display: block;
29
-        margin: 16px auto 0 auto;
30
-    }
31
-}
32
-
33
 /**
15
 /**
34
  * Styling shared video dialog errors.
16
  * Styling shared video dialog errors.
35
  */
17
  */

+ 0
- 7
css/modals/invite/_add-people.scss View File

41
         }
41
         }
42
     }
42
     }
43
 }
43
 }
44
-
45
-/**
46
- * Styles errors in the MultiSelectAutocomplete.
47
- */
48
-.autocomplete-error {
49
-    min-width: 260px;
50
-}

+ 0
- 117
css/modals/invite/_invite_more.scss View File

1
 .invite-more {
1
 .invite-more {
2
-    &-container {
3
-        margin-bottom: 8px;
4
-        transition: margin-bottom 0.3s;
5
-
6
-        &.elevated {
7
-            margin-bottom: 36px;
8
-        }
9
-    }
10
-
11
-    &-content {
12
-        display: flex;
13
-        flex-direction: column;
14
-        align-items: center;
15
-        padding: 16px;
16
-        background: rgba(0, 0, 0, 0.7);
17
-        border-radius: 8px;
18
-        color: #fff;
19
-        font-size: 14px;
20
-        line-height: 24px;
21
-        font-weight: 600;
22
-    }
23
-
24
-    &-header {
25
-        max-width: 100%;
26
-        margin-bottom: 16px;
27
-        text-overflow: ellipsis;
28
-        overflow: hidden;
29
-        white-space: nowrap;
30
-    }
31
-
32
-    &-button {
33
-        display: flex;
34
-        max-width: 100%;
35
-        height: 40px;
36
-        box-sizing: border-box;
37
-        padding: 8px 16px;
38
-        background: #0376DA;
39
-        border-radius: 3px;
40
-        cursor: pointer;
41
-
42
-        @media (hover: hover) and (pointer: fine) {
43
-            &:hover {
44
-                background: #278ADF;
45
-            }
46
-        }
47
-
48
-        &-text {
49
-            margin-left: 8px;
50
-            text-overflow: ellipsis;
51
-            overflow: hidden;
52
-            white-space: nowrap;
53
-        }
54
-    }
55
     &-dialog {
2
     &-dialog {
56
         color: #fff;
3
         color: #fff;
57
         font-size: 15px;
4
         font-size: 15px;
65
             background: #5E6D7A;
12
             background: #5E6D7A;
66
         }
13
         }
67
 
14
 
68
-        &.email-container {
69
-            display: flex;
70
-            justify-content: space-between;
71
-            align-items: center;
72
-            padding: 8px 8px 8px 16px;
73
-            margin-top: 24px;
74
-            width: calc(100% - 26px);
75
-            height: 22px;
76
-
77
-            background: #2A3A4B;
78
-            border: 1px solid #5E6D7A;
79
-            border-radius: 3px;
80
-            cursor: pointer;
81
-
82
-            &.active {
83
-                border-radius: 3px 3px 0 0;
84
-            }
85
-        }
86
-
87
-        &.invite-buttons {
88
-            width: 100%;
89
-            text-align: right;
90
-            margin-top: 8px;
91
-
92
-            & > a {
93
-                display: inline-block;
94
-                height: 24px;
95
-                min-width: 48px;
96
-                border-radius: 3px;
97
-                text-align: center;
98
-                text-decoration: none;
99
-                cursor: pointer;
100
-            }
101
-
102
-            &-cancel {
103
-                margin-right: 16px;
104
-                padding: 7px 15px;
105
-                background: #2A3A4B;
106
-                border: 1px solid #5E6D7A;
107
-            }
108
-
109
-            &-add {
110
-                padding: 8px 16px;
111
-                background: #0376DA;
112
-            }
113
-
114
-            &.disabled {
115
-                & > a {
116
-                    pointer-events: none;
117
-                }
118
-            }
119
-        }
120
-
121
         &.stream {
15
         &.stream {
122
             display: flex;
16
             display: flex;
123
             justify-content: space-between;
17
             justify-content: space-between;
158
         }
52
         }
159
     }
53
     }
160
 }
54
 }
161
-
162
-.mobile-browser {
163
-    .invite-more-content {
164
-        font-size: 16px;
165
-    }
166
-
167
-    .invite-more-button {
168
-        height: 48px;
169
-        padding: 12px 16px;
170
-    }
171
-}

+ 62
- 46
react/features/base/react/components/web/InlineDialogFailure.tsx View File

1
-import React, { Component } from 'react';
2
-import { WithTranslation } from 'react-i18next';
1
+import React from 'react';
2
+import { useTranslation } from 'react-i18next';
3
+import { makeStyles } from 'tss-react/mui';
3
 
4
 
4
-import { translate } from '../../../i18n/functions';
5
+import { withPixelLineHeight } from '../../../styles/functions.web';
5
 import Button from '../../../ui/components/web/Button';
6
 import Button from '../../../ui/components/web/Button';
6
 
7
 
8
+const useStyles = makeStyles()(theme => {
9
+    return {
10
+        dialog: {
11
+            backgroundColor: theme.palette.ui01,
12
+            border: `1px solid ${theme.palette.ui04}`,
13
+            borderRadius: `${Number(theme.shape.borderRadius)}px`,
14
+            boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
15
+            color: theme.palette.text01,
16
+            ...withPixelLineHeight(theme.typography.bodyShortRegular),
17
+            padding: `${theme.spacing(3)} 10`,
18
+            '& .retry-button': {
19
+                margin: '16px auto 0 auto'
20
+            }
21
+        }
22
+    };
23
+});
24
+
7
 /**
25
 /**
8
  * The type of the React {@code Component} props of {@link InlineDialogFailure}.
26
  * The type of the React {@code Component} props of {@link InlineDialogFailure}.
9
  */
27
  */
10
-interface IProps extends WithTranslation {
28
+interface IProps {
11
 
29
 
12
     /**
30
     /**
13
      * Allows to retry the call that previously didn't succeed.
31
      * Allows to retry the call that previously didn't succeed.
22
 
40
 
23
 /**
41
 /**
24
  * Inline dialog that represents a failure and allows a retry.
42
  * Inline dialog that represents a failure and allows a retry.
43
+ *
44
+ * @returns {Element}
25
  */
45
  */
26
-class InlineDialogFailure extends Component<IProps> {
27
-    /**
28
-     * Renders the content of this component.
29
-     *
30
-     * @returns {ReactElement}
31
-     */
32
-    render() {
33
-        const { t, showSupportLink } = this.props;
46
+const InlineDialogFailure = ({
47
+    onRetry,
48
+    showSupportLink
49
+}: IProps) => {
50
+    const { t } = useTranslation();
51
+    const { classes } = useStyles();
34
 
52
 
35
-        const supportLink = interfaceConfig.SUPPORT_URL;
36
-        const supportString = t('inlineDialogFailure.supportMsg');
37
-        const supportLinkElem
38
-            = supportLink && showSupportLink
39
-                ? (
40
-                    <div className = 'inline-dialog-error-text'>
41
-                        <span>{ supportString.padEnd(supportString.length + 1) }
42
-                        </span>
43
-                        <span>
44
-                            <a
45
-                                href = { supportLink }
46
-                                rel = 'noopener noreferrer'
47
-                                target = '_blank'>
48
-                                { t('inlineDialogFailure.support') }
49
-                            </a>
50
-                        </span>
51
-                        <span>.</span>
52
-                    </div>
53
-                )
54
-                : null;
53
+    const supportLink = interfaceConfig.SUPPORT_URL;
54
+    const supportString = t('inlineDialogFailure.supportMsg');
55
+    const supportLinkElem = supportLink && showSupportLink
56
+        ? (
57
+            <div>
58
+                <span>{ supportString.padEnd(supportString.length + 1) }
59
+                </span>
60
+                <span>
61
+                    <a
62
+                        href = { supportLink }
63
+                        rel = 'noopener noreferrer'
64
+                        target = '_blank'>
65
+                        { t('inlineDialogFailure.support') }
66
+                    </a>
67
+                </span>
68
+                <span>.</span>
69
+            </div>
70
+        )
71
+        : null;
55
 
72
 
56
-        return (
57
-            <div className = 'inline-dialog-error'>
58
-                <div className = 'inline-dialog-error-text'>
59
-                    { t('inlineDialogFailure.msg') }
60
-                </div>
61
-                { supportLinkElem }
62
-                <Button
63
-                    className = 'inline-dialog-error-button'
64
-                    label = { t('inlineDialogFailure.retry') }
65
-                    onClick = { this.props.onRetry } />
73
+    return (
74
+        <div className = { classes.dialog }>
75
+            <div>
76
+                { t('inlineDialogFailure.msg') }
66
             </div>
77
             </div>
67
-        );
68
-    }
69
-}
78
+            { supportLinkElem }
79
+            <Button
80
+                className = 'retry-button'
81
+                label = { t('inlineDialogFailure.retry') }
82
+                onClick = { onRetry } />
83
+        </div>
84
+    );
85
+};
70
 
86
 
71
-export default translate(InlineDialogFailure);
87
+export default InlineDialogFailure;

react/features/base/react/components/web/MultiSelectAutocomplete.js → react/features/base/react/components/web/MultiSelectAutocomplete.tsx View File

1
-// @flow
2
-
3
-import AKInlineDialog from '@atlaskit/inline-dialog';
4
-import { MultiSelectStateless } from '@atlaskit/multi-select';
5
 import _debounce from 'lodash/debounce';
1
 import _debounce from 'lodash/debounce';
6
 import React, { Component } from 'react';
2
 import React, { Component } from 'react';
7
 
3
 
4
+import { MultiSelectItem } from '../../../ui/components/types';
5
+import MultiSelect from '../../../ui/components/web/MultiSelect';
8
 import logger from '../../logger';
6
 import logger from '../../logger';
9
 
7
 
10
 import InlineDialogFailure from './InlineDialogFailure';
8
 import InlineDialogFailure from './InlineDialogFailure';
13
  * The type of the React {@code Component} props of
11
  * The type of the React {@code Component} props of
14
  * {@link MultiSelectAutocomplete}.
12
  * {@link MultiSelectAutocomplete}.
15
  */
13
  */
16
-type Props = {
14
+interface IProps {
17
 
15
 
18
     /**
16
     /**
19
      * The default value of the selected item.
17
      * The default value of the selected item.
20
      */
18
      */
21
-    defaultValue: Array<Object>,
19
+    defaultValue?: Array<any>;
22
 
20
 
23
     /**
21
     /**
24
      * Optional footer to show as a last element in the results.
22
      * Optional footer to show as a last element in the results.
25
      * Should be of type {content: <some content>}.
23
      * Should be of type {content: <some content>}.
26
      */
24
      */
27
-    footer: Object,
25
+    footer?: any;
28
 
26
 
29
     /**
27
     /**
30
      * Indicates if the component is disabled.
28
      * Indicates if the component is disabled.
31
      */
29
      */
32
-    isDisabled: boolean,
30
+    isDisabled: boolean;
33
 
31
 
34
     /**
32
     /**
35
      * Text to display while a query is executing.
33
      * Text to display while a query is executing.
36
      */
34
      */
37
-    loadingMessage: string,
35
+    loadingMessage: string;
38
 
36
 
39
     /**
37
     /**
40
      * The text to show when no matches are found.
38
      * The text to show when no matches are found.
41
      */
39
      */
42
-    noMatchesFound: string,
40
+    noMatchesFound: string;
43
 
41
 
44
     /**
42
     /**
45
      * The function called immediately before a selection has been actually
43
      * The function called immediately before a selection has been actually
46
      * selected. Provides an opportunity to do any formatting.
44
      * selected. Provides an opportunity to do any formatting.
47
      */
45
      */
48
-    onItemSelected: Function,
46
+    onItemSelected: Function;
49
 
47
 
50
     /**
48
     /**
51
      * The function called when the selection changes.
49
      * The function called when the selection changes.
52
      */
50
      */
53
-    onSelectionChange: Function,
51
+    onSelectionChange: Function;
54
 
52
 
55
     /**
53
     /**
56
      * The placeholder text of the input component.
54
      * The placeholder text of the input component.
57
      */
55
      */
58
-    placeholder: string,
56
+    placeholder: string;
59
 
57
 
60
     /**
58
     /**
61
      * The service providing the search.
59
      * The service providing the search.
62
      */
60
      */
63
-    resourceClient: { makeQuery: Function, parseResults: Function },
61
+    resourceClient: { makeQuery: Function; parseResults: Function; };
64
 
62
 
65
     /**
63
     /**
66
      * Indicates if the component should fit the container.
64
      * Indicates if the component should fit the container.
67
      */
65
      */
68
-    shouldFitContainer: boolean,
66
+    shouldFitContainer: boolean;
69
 
67
 
70
     /**
68
     /**
71
      * Indicates if we should focus.
69
      * Indicates if we should focus.
72
      */
70
      */
73
-    shouldFocus: boolean,
71
+    shouldFocus: boolean;
74
 
72
 
75
     /**
73
     /**
76
      * Indicates whether the support link should be shown in case of an error.
74
      * Indicates whether the support link should be shown in case of an error.
77
      */
75
      */
78
-    showSupportLink: Boolean,
79
-};
76
+    showSupportLink: Boolean;
77
+}
80
 
78
 
81
 /**
79
 /**
82
  * The type of the React {@code Component} state of
80
  * The type of the React {@code Component} state of
83
  * {@link MultiSelectAutocomplete}.
81
  * {@link MultiSelectAutocomplete}.
84
  */
82
  */
85
-type State = {
83
+interface IState {
86
 
84
 
87
     /**
85
     /**
88
-     * Indicates if the dropdown is open.
86
+     * Indicates if there was an error.
89
      */
87
      */
90
-    isOpen: boolean,
88
+    error: boolean;
91
 
89
 
92
     /**
90
     /**
93
      * The text that filters the query result of the search.
91
      * The text that filters the query result of the search.
94
      */
92
      */
95
-    filterValue: string,
93
+    filterValue: string;
96
 
94
 
97
     /**
95
     /**
98
-     * Indicates if the component is currently loading results.
96
+     * Indicates if the dropdown is open.
99
      */
97
      */
100
-    loading: boolean,
98
+    isOpen: boolean;
101
 
99
 
102
     /**
100
     /**
103
-     * Indicates if there was an error.
101
+     * The list of result items.
104
      */
102
      */
105
-    error: boolean,
103
+    items: Array<MultiSelectItem>;
106
 
104
 
107
     /**
105
     /**
108
-     * The list of result items.
106
+     * Indicates if the component is currently loading results.
109
      */
107
      */
110
-    items: Array<Object>,
108
+    loading: boolean;
111
 
109
 
112
     /**
110
     /**
113
      * The list of selected items.
111
      * The list of selected items.
114
      */
112
      */
115
-    selectedItems: Array<Object>
116
-};
113
+    selectedItems: Array<MultiSelectItem>;
114
+}
117
 
115
 
118
 /**
116
 /**
119
  * A MultiSelect that is also auto-completing.
117
  * A MultiSelect that is also auto-completing.
120
  */
118
  */
121
-class MultiSelectAutocomplete extends Component<Props, State> {
119
+class MultiSelectAutocomplete extends Component<IProps, IState> {
122
     /**
120
     /**
123
      * Initializes a new {@code MultiSelectAutocomplete} instance.
121
      * Initializes a new {@code MultiSelectAutocomplete} instance.
124
      *
122
      *
125
      * @param {Object} props - The read-only properties with which the new
123
      * @param {Object} props - The read-only properties with which the new
126
      * instance is to be initialized.
124
      * instance is to be initialized.
127
      */
125
      */
128
-    constructor(props: Props) {
126
+    constructor(props: IProps) {
129
         super(props);
127
         super(props);
130
 
128
 
131
         const defaultValue = this.props.defaultValue || [];
129
         const defaultValue = this.props.defaultValue || [];
148
     /**
146
     /**
149
      * Sets the items to display as selected.
147
      * Sets the items to display as selected.
150
      *
148
      *
151
-     * @param {Array<Object>} selectedItems - The list of items to display as
149
+     * @param {Array<MultiSelectItem>} selectedItems - The list of items to display as
152
      * having been selected.
150
      * having been selected.
153
      * @returns {void}
151
      * @returns {void}
154
      */
152
      */
155
-    setSelectedItems(selectedItems: Array<Object> = []) {
153
+    setSelectedItems(selectedItems: Array<MultiSelectItem> = []) {
156
         this.setState({ selectedItems });
154
         this.setState({ selectedItems });
157
     }
155
     }
158
 
156
 
162
      * @returns {ReactElement}
160
      * @returns {ReactElement}
163
      */
161
      */
164
     render() {
162
     render() {
165
-        const shouldFitContainer = this.props.shouldFitContainer || false;
166
-        const shouldFocus = this.props.shouldFocus || false;
167
-        const isDisabled = this.props.isDisabled || false;
163
+        const autoFocus = this.props.shouldFocus || false;
164
+        const disabled = this.props.isDisabled || false;
168
         const placeholder = this.props.placeholder || '';
165
         const placeholder = this.props.placeholder || '';
169
         const noMatchesFound = this.props.noMatchesFound || '';
166
         const noMatchesFound = this.props.noMatchesFound || '';
167
+        const errorDialog = this._renderError();
170
 
168
 
171
         return (
169
         return (
172
             <div>
170
             <div>
173
-                <MultiSelectStateless
171
+                <MultiSelect
172
+                    autoFocus = { autoFocus }
173
+                    disabled = { disabled }
174
+                    error = { this.state.error }
175
+                    errorDialog = { errorDialog }
174
                     filterValue = { this.state.filterValue }
176
                     filterValue = { this.state.filterValue }
175
-                    footer = { this.props.footer }
176
-                    icon = { null }
177
-                    isDisabled = { isDisabled }
178
-                    isLoading = { this.state.loading }
179
                     isOpen = { this.state.isOpen }
177
                     isOpen = { this.state.isOpen }
180
                     items = { this.state.items }
178
                     items = { this.state.items }
181
-                    loadingMessage = { this.props.loadingMessage }
182
-                    noMatchesFound = { noMatchesFound }
179
+                    noMatchesText = { noMatchesFound }
183
                     onFilterChange = { this._onFilterChange }
180
                     onFilterChange = { this._onFilterChange }
184
                     onRemoved = { this._onSelectionChange }
181
                     onRemoved = { this._onSelectionChange }
185
                     onSelected = { this._onSelectionChange }
182
                     onSelected = { this._onSelectionChange }
186
                     placeholder = { placeholder }
183
                     placeholder = { placeholder }
187
-                    selectedItems = { this.state.selectedItems }
188
-                    shouldFitContainer = { shouldFitContainer }
189
-                    shouldFocus = { shouldFocus } />
190
-                { this._renderError() }
184
+                    selectedItems = { this.state.selectedItems } />
191
             </div>
185
             </div>
192
         );
186
         );
193
     }
187
     }
194
 
188
 
195
-    _onFilterChange: (string) => void;
196
 
189
 
197
     /**
190
     /**
198
      * Sets the state and sends a query on filter change.
191
      * Sets the state and sends a query on filter change.
201
      * @private
194
      * @private
202
      * @returns {void}
195
      * @returns {void}
203
      */
196
      */
204
-    _onFilterChange(filterValue) {
197
+    _onFilterChange(filterValue: string) {
205
         this.setState({
198
         this.setState({
206
             // Clean the error if the filterValue is empty.
199
             // Clean the error if the filterValue is empty.
207
             error: this.state.error && Boolean(filterValue),
200
             error: this.state.error && Boolean(filterValue),
215
         }
208
         }
216
     }
209
     }
217
 
210
 
218
-    _onRetry: () => void;
219
-
220
     /**
211
     /**
221
      * Retries the query on retry.
212
      * Retries the query on retry.
222
      *
213
      *
227
         this._sendQuery(this.state.filterValue);
218
         this._sendQuery(this.state.filterValue);
228
     }
219
     }
229
 
220
 
230
-    _onSelectionChange: (Object) => void;
231
-
232
     /**
221
     /**
233
      * Updates the selected items when a selection event occurs.
222
      * Updates the selected items when a selection event occurs.
234
      *
223
      *
235
-     * @param {Object} item - The selected item.
224
+     * @param {any} item - The selected item.
236
      * @private
225
      * @private
237
      * @returns {void}
226
      * @returns {void}
238
      */
227
      */
239
-    _onSelectionChange(item) {
228
+    _onSelectionChange(item: any) {
240
         const existing
229
         const existing
241
-            = this.state.selectedItems.find(k => k.value === item.value);
230
+            = this.state.selectedItems.find((k: any) => k.value === item.value);
242
         let selectedItems = this.state.selectedItems;
231
         let selectedItems = this.state.selectedItems;
243
 
232
 
244
         if (existing) {
233
         if (existing) {
265
         if (!this.state.error) {
254
         if (!this.state.error) {
266
             return null;
255
             return null;
267
         }
256
         }
268
-        const content = (
269
-            <div className = 'autocomplete-error'>
270
-                <InlineDialogFailure
271
-                    onRetry = { this._onRetry }
272
-                    showSupportLink = { this.props.showSupportLink } />
273
-            </div>
274
-        );
275
 
257
 
276
         return (
258
         return (
277
-            <AKInlineDialog
278
-                content = { content }
279
-                isOpen = { true } />
259
+
260
+            <InlineDialogFailure
261
+                onRetry = { this._onRetry }
262
+                showSupportLink = { this.props.showSupportLink } />
280
         );
263
         );
281
     }
264
     }
282
 
265
 
283
-    _sendQuery: (string) => void;
284
-
285
     /**
266
     /**
286
      * Sends a query to the resourceClient.
267
      * Sends a query to the resourceClient.
287
      *
268
      *
288
      * @param {string} filterValue - The string to use for the search.
269
      * @param {string} filterValue - The string to use for the search.
289
      * @returns {void}
270
      * @returns {void}
290
      */
271
      */
291
-    _sendQuery(filterValue) {
272
+    _sendQuery(filterValue: string) {
292
         if (!filterValue) {
273
         if (!filterValue) {
293
             return;
274
             return;
294
         }
275
         }
299
 
280
 
300
         const resourceClient = this.props.resourceClient || {
281
         const resourceClient = this.props.resourceClient || {
301
             makeQuery: () => Promise.resolve([]),
282
             makeQuery: () => Promise.resolve([]),
302
-            parseResults: results => results
283
+            parseResults: (results: any) => results
303
         };
284
         };
304
 
285
 
305
         resourceClient.makeQuery(filterValue)
286
         resourceClient.makeQuery(filterValue)
306
-            .then(results => {
287
+            .then((results: any) => {
307
                 if (this.state.filterValue !== filterValue) {
288
                 if (this.state.filterValue !== filterValue) {
308
                     this.setState({
289
                     this.setState({
309
                         error: false
290
                         error: false
311
 
292
 
312
                     return;
293
                     return;
313
                 }
294
                 }
314
-                const itemGroups = [
315
-                    {
316
-                        items: resourceClient.parseResults(results)
317
-                    }
318
-                ];
319
 
295
 
320
                 this.setState({
296
                 this.setState({
321
-                    items: itemGroups,
297
+                    items: resourceClient.parseResults(results),
322
                     isOpen: true,
298
                     isOpen: true,
323
                     loading: false,
299
                     loading: false,
324
                     error: false
300
                     error: false
325
                 });
301
                 });
326
             })
302
             })
327
-            .catch(error => {
303
+            .catch((error: Error) => {
328
                 logger.error('MultiSelectAutocomplete error in query', error);
304
                 logger.error('MultiSelectAutocomplete error in query', error);
329
 
305
 
330
                 this.setState({
306
                 this.setState({

+ 8
- 0
react/features/base/ui/components/types.ts View File

105
      */
105
      */
106
     onChange: (on?: boolean) => void;
106
     onChange: (on?: boolean) => void;
107
 }
107
 }
108
+
109
+export type MultiSelectItem = {
110
+    content: string;
111
+    description?: string;
112
+    elemBefore?: Element;
113
+    isDisabled?: boolean;
114
+    value: string;
115
+};

+ 6
- 0
react/features/base/ui/components/web/Input.tsx View File

20
     maxRows?: number;
20
     maxRows?: number;
21
     minRows?: number;
21
     minRows?: number;
22
     name?: string;
22
     name?: string;
23
+    onBlur?: (e: any) => void;
24
+    onFocus?: (event: React.FocusEvent) => void;
23
     onKeyPress?: (e: React.KeyboardEvent) => void;
25
     onKeyPress?: (e: React.KeyboardEvent) => void;
24
     readOnly?: boolean;
26
     readOnly?: boolean;
25
     required?: boolean;
27
     required?: boolean;
148
     maxRows,
150
     maxRows,
149
     minRows,
151
     minRows,
150
     name,
152
     name,
153
+    onBlur,
151
     onChange,
154
     onChange,
155
+    onFocus,
152
     onKeyPress,
156
     onKeyPress,
153
     placeholder,
157
     placeholder,
154
     readOnly = false,
158
     readOnly = false,
208
                         { ...(id ? { id } : {}) }
212
                         { ...(id ? { id } : {}) }
209
                         maxLength = { maxLength }
213
                         maxLength = { maxLength }
210
                         name = { name }
214
                         name = { name }
215
+                        onBlur = { onBlur }
211
                         onChange = { handleChange }
216
                         onChange = { handleChange }
217
+                        onFocus = { onFocus }
212
                         onKeyPress = { onKeyPress }
218
                         onKeyPress = { onKeyPress }
213
                         placeholder = { placeholder }
219
                         placeholder = { placeholder }
214
                         readOnly = { readOnly }
220
                         readOnly = { readOnly }

+ 175
- 0
react/features/base/ui/components/web/MultiSelect.tsx View File

1
+import React, { useCallback, useMemo, useRef } from 'react';
2
+import { makeStyles } from 'tss-react/mui';
3
+
4
+import { IconCloseLarge } from '../../../icons/svg';
5
+import { withPixelLineHeight } from '../../../styles/functions.web';
6
+import { MultiSelectItem } from '../types';
7
+
8
+import ClickableIcon from './ClickableIcon';
9
+import Input from './Input';
10
+
11
+interface IProps {
12
+    autoFocus?: boolean;
13
+    disabled?: boolean;
14
+    error?: boolean;
15
+    errorDialog?: JSX.Element | null;
16
+    filterValue?: string;
17
+    isOpen?: boolean;
18
+    items: MultiSelectItem[];
19
+    noMatchesText?: string;
20
+    onFilterChange?: (value: string) => void;
21
+    onRemoved: (item: any) => void;
22
+    onSelected: (item: any) => void;
23
+    placeholder?: string;
24
+    selectedItems?: MultiSelectItem[];
25
+}
26
+
27
+const useStyles = makeStyles()(theme => {
28
+    return {
29
+        container: {
30
+            position: 'relative'
31
+        },
32
+        items: {
33
+            '&.found': {
34
+                position: 'absolute',
35
+                boxShadow: '0px 5px 10px rgba(0, 0, 0, 0.75)'
36
+            },
37
+            marginTop: theme.spacing(2),
38
+            width: '100%',
39
+            backgroundColor: theme.palette.ui01,
40
+            border: `1px solid ${theme.palette.ui04}`,
41
+            borderRadius: `${Number(theme.shape.borderRadius)}px`,
42
+            ...withPixelLineHeight(theme.typography.bodyShortRegular),
43
+            zIndex: 2,
44
+            maxHeight: '400px',
45
+            overflowY: 'auto',
46
+            padding: '0'
47
+        },
48
+        listItem: {
49
+            boxSizing: 'border-box',
50
+            display: 'flex',
51
+            padding: `${theme.spacing(2)} ${theme.spacing(3)}`,
52
+            alignItems: 'center',
53
+            '& .content': {
54
+                // 38px because of the icon before the content
55
+                inlineSize: 'calc(100% - 38px)',
56
+                overflowWrap: 'break-word',
57
+                marginLeft: theme.spacing(2),
58
+                color: theme.palette.text01,
59
+                '&.with-remove': {
60
+                    // 60px because of the icon before the content and the remove button
61
+                    inlineSize: 'calc(100% - 60px)',
62
+                    marginRight: theme.spacing(2),
63
+                    '&.without-before': {
64
+                        marginLeft: 0,
65
+                        inlineSize: 'calc(100% - 38px)'
66
+                    }
67
+                },
68
+                '&.without-before': {
69
+                    marginLeft: 0,
70
+                    inlineSize: '100%'
71
+                }
72
+            },
73
+            '&.found': {
74
+                cursor: 'pointer',
75
+                padding: `10px ${theme.spacing(3)}`,
76
+                '&:hover': {
77
+                    backgroundColor: theme.palette.ui02
78
+                }
79
+            },
80
+            '&.disabled': {
81
+                cursor: 'not-allowed',
82
+                '&:hover': {
83
+                    backgroundColor: theme.palette.ui01
84
+                },
85
+                color: theme.palette.text03
86
+            }
87
+        },
88
+        errorMessage: {
89
+            position: 'absolute',
90
+            marginTop: theme.spacing(2),
91
+            width: '100%'
92
+        }
93
+    };
94
+});
95
+
96
+const MultiSelect = ({
97
+    autoFocus,
98
+    disabled,
99
+    error,
100
+    errorDialog,
101
+    placeholder,
102
+    items,
103
+    filterValue,
104
+    onFilterChange,
105
+    isOpen,
106
+    noMatchesText,
107
+    onSelected,
108
+    selectedItems,
109
+    onRemoved
110
+}: IProps) => {
111
+    const { classes } = useStyles();
112
+    const inputRef = useRef();
113
+    const selectItem = useCallback(item => () => onSelected(item), [ onSelected ]);
114
+    const removeItem = useCallback(item => () => onRemoved(item), [ onRemoved ]);
115
+    const foundItems = useMemo(() => (
116
+        <div className = { `${classes.items} found` }>
117
+            {
118
+                items.length > 0
119
+                    ? items.map(item => (
120
+                        <div
121
+                            className = { `${classes.listItem} ${item.isDisabled ? 'disabled' : ''} found` }
122
+                            key = { item.value }
123
+                            onClick = { item.isDisabled ? undefined : selectItem(item) }>
124
+                            {item.elemBefore}
125
+                            <div className = { `content ${item.elemBefore ? '' : 'without-before'}` }>
126
+                                {item.content}
127
+                                {item.description && <p>{item.description}</p>}
128
+                            </div>
129
+                        </div>
130
+                    ))
131
+                    : <div>{noMatchesText}</div>
132
+            }
133
+        </div>
134
+    ), [ items ]);
135
+
136
+    const errorMessageDialog = useMemo(() =>
137
+        error && <div className = { classes.errorMessage }>
138
+            { errorDialog }
139
+        </div>, [ error ]);
140
+
141
+    return (
142
+        <div className = { classes.container }>
143
+            <Input
144
+                autoFocus = { autoFocus }
145
+                disabled = { disabled }
146
+                onChange = { onFilterChange }
147
+                placeholder = { placeholder }
148
+                ref = { inputRef }
149
+                value = { filterValue ?? '' } />
150
+            {isOpen && foundItems}
151
+            { errorMessageDialog }
152
+            { selectedItems && selectedItems?.length > 0 && (
153
+                <div className = { classes.items }>
154
+                    { selectedItems.map(item => (
155
+                        <div
156
+                            className = { `${classes.listItem} ${item.isDisabled ? 'disabled' : ''}` }
157
+                            key = { item.value }>
158
+                            {item.elemBefore}
159
+                            <div className = { `content with-remove ${item.elemBefore ? '' : 'without-before'}` }>
160
+                                <p>{item.content}</p>
161
+                            </div>
162
+                            <ClickableIcon
163
+                                accessibilityLabel = { 'multi-select-unselect' }
164
+                                icon = { IconCloseLarge }
165
+                                id = 'modal-header-close-button'
166
+                                onClick = { removeItem(item) } />
167
+                        </div>
168
+                    ))}
169
+                </div>
170
+            )}
171
+        </div>
172
+    );
173
+};
174
+
175
+export default MultiSelect;

react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.ts → react/features/invite/components/add-people-dialog/AbstractAddPeopleDialog.tsx View File

3
 import { createInviteDialogEvent } from '../../../analytics/AnalyticsEvents';
3
 import { createInviteDialogEvent } from '../../../analytics/AnalyticsEvents';
4
 import { sendAnalytics } from '../../../analytics/functions';
4
 import { sendAnalytics } from '../../../analytics/functions';
5
 import { IReduxState } from '../../../app/types';
5
 import { IReduxState } from '../../../app/types';
6
-import { showNotification } from '../../../notifications/actions';
6
+import { showErrorNotification, showNotification } from '../../../notifications/actions';
7
 import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
7
 import { NOTIFICATION_TIMEOUT_TYPE } from '../../../notifications/constants';
8
 import { INotificationProps } from '../../../notifications/types';
8
 import { INotificationProps } from '../../../notifications/types';
9
 import { invite } from '../../actions';
9
 import { invite } from '../../actions';
10
 import { INVITE_TYPES } from '../../constants';
10
 import { INVITE_TYPES } from '../../constants';
11
 import {
11
 import {
12
-    GetInviteResultsOptions,
13
     getInviteResultsForQuery,
12
     getInviteResultsForQuery,
14
     getInviteTypeCounts,
13
     getInviteTypeCounts,
15
     isAddPeopleEnabled,
14
     isAddPeopleEnabled,
17
     isSipInviteEnabled
16
     isSipInviteEnabled
18
 } from '../../functions';
17
 } from '../../functions';
19
 import logger from '../../logger';
18
 import logger from '../../logger';
20
-import { IInvitee } from '../../types';
19
+import { IInviteSelectItem, IInvitee } from '../../types';
21
 
20
 
22
 export interface IProps {
21
 export interface IProps {
23
 
22
 
26
      */
25
      */
27
     _addPeopleEnabled: boolean;
26
     _addPeopleEnabled: boolean;
28
 
27
 
28
+    /**
29
+     * The app id of the user.
30
+     */
29
     _appId: string;
31
     _appId: string;
30
 
32
 
31
     /**
33
     /**
48
      */
50
      */
49
     _dialOutRegionUrl: string;
51
     _dialOutRegionUrl: string;
50
 
52
 
51
-    /**
53
+     /**
52
      * The JWT token.
54
      * The JWT token.
53
      */
55
      */
54
     _jwt: string;
56
     _jwt: string;
55
 
57
 
56
-    /**
58
+     /**
57
      * The query types used when searching people.
59
      * The query types used when searching people.
58
      */
60
      */
59
     _peopleSearchQueryTypes: Array<string>;
61
     _peopleSearchQueryTypes: Array<string>;
60
 
62
 
61
-    /**
63
+     /**
62
      * The URL pointing to the service allowing for people search.
64
      * The URL pointing to the service allowing for people search.
63
      */
65
      */
64
     _peopleSearchUrl: string;
66
     _peopleSearchUrl: string;
66
     /**
68
     /**
67
      * Whether or not to allow sip invites.
69
      * Whether or not to allow sip invites.
68
      */
70
      */
69
-    _sipInviteEnabled: boolean;
71
+     _sipInviteEnabled: boolean;
70
 
72
 
71
     /**
73
     /**
72
      * The Redux dispatch function.
74
      * The Redux dispatch function.
90
     /**
92
     /**
91
      * The list of invite items.
93
      * The list of invite items.
92
      */
94
      */
93
-    inviteItems: Array<Object>;
95
+    inviteItems: Array<IInviteSelectItem>;
94
 }
96
 }
95
 
97
 
96
 /**
98
 /**
97
  * Implements an abstract dialog to invite people to the conference.
99
  * Implements an abstract dialog to invite people to the conference.
98
  */
100
  */
99
-export default class AbstractAddPeopleDialog<P extends IProps, S extends IState>
100
-    extends Component<P, S> {
101
+export default class AbstractAddPeopleDialog<P extends IProps, S extends IState> extends Component<P, S> {
101
     /**
102
     /**
102
      * Constructor of the component.
103
      * Constructor of the component.
103
      *
104
      *
112
     /**
113
     /**
113
      * Retrieves the notification display name for the invitee.
114
      * Retrieves the notification display name for the invitee.
114
      *
115
      *
115
-     * @param {Object} invitee - The invitee object.
116
+     * @param {IInvitee} invitee - The invitee object.
116
      * @returns {string}
117
      * @returns {string}
117
      */
118
      */
118
     _getDisplayName(invitee: IInvitee) {
119
     _getDisplayName(invitee: IInvitee) {
124
             return invitee.address;
125
             return invitee.address;
125
         }
126
         }
126
 
127
 
127
-        return invitee.name;
128
+        return invitee.name ?? '';
128
     }
129
     }
129
 
130
 
130
     /**
131
     /**
135
      * no invites left to send. If any are left, that means an invite failed
136
      * no invites left to send. If any are left, that means an invite failed
136
      * and an error state should display.
137
      * and an error state should display.
137
      *
138
      *
138
-     * @param {Array<Object>} invitees - The items to be invited.
139
-     * @returns {Promise<Array<Object>>}
139
+     * @param {Array<IInvitee>} invitees - The items to be invited.
140
+     * @returns {Promise<Array<any>>}
140
      */
141
      */
141
     _invite(invitees: IInvitee[]) {
142
     _invite(invitees: IInvitee[]) {
142
         const inviteTypeCounts = getInviteTypeCounts(invitees);
143
         const inviteTypeCounts = getInviteTypeCounts(invitees);
176
                         'error', 'invite', {
177
                         'error', 'invite', {
177
                             ...erroredInviteTypeCounts
178
                             ...erroredInviteTypeCounts
178
                         }));
179
                         }));
179
-
180
-                    this.setState({
181
-                        addToCallError: true
182
-                    });
180
+                    dispatch(showErrorNotification({
181
+                        titleKey: 'addPeople.failedToAdd'
182
+                    }, NOTIFICATION_TIMEOUT_TYPE.MEDIUM));
183
                 } else if (!_callFlowsEnabled) {
183
                 } else if (!_callFlowsEnabled) {
184
                     const invitedCount = invitees.length;
184
                     const invitedCount = invitees.length;
185
                     let notificationProps: INotificationProps | undefined;
185
                     let notificationProps: INotificationProps | undefined;
188
                         notificationProps = {
188
                         notificationProps = {
189
                             titleArguments: {
189
                             titleArguments: {
190
                                 name: this._getDisplayName(invitees[0]),
190
                                 name: this._getDisplayName(invitees[0]),
191
-                                count: invitedCount - 1
191
+                                count: `${invitedCount - 1}`
192
                             },
192
                             },
193
                             titleKey: 'notify.invitedThreePlusMembers'
193
                             titleKey: 'notify.invitedThreePlusMembers'
194
                         };
194
                         };
250
             _peopleSearchUrl: peopleSearchUrl,
250
             _peopleSearchUrl: peopleSearchUrl,
251
             _sipInviteEnabled: sipInviteEnabled
251
             _sipInviteEnabled: sipInviteEnabled
252
         } = this.props;
252
         } = this.props;
253
-        const options: GetInviteResultsOptions = {
253
+        const options = {
254
             addPeopleEnabled,
254
             addPeopleEnabled,
255
             appId,
255
             appId,
256
             dialOutAuthUrl,
256
             dialOutAuthUrl,
292
 
292
 
293
     return {
293
     return {
294
         _addPeopleEnabled: isAddPeopleEnabled(state),
294
         _addPeopleEnabled: isAddPeopleEnabled(state),
295
-        _appId: state['features/base/jwt']?.tenant,
296
-        _callFlowsEnabled: callFlowsEnabled,
297
-        _dialOutAuthUrl: dialOutAuthUrl,
298
-        _dialOutRegionUrl: dialOutRegionUrl,
295
+        _appId: state['features/base/jwt']?.tenant ?? '',
296
+        _callFlowsEnabled: callFlowsEnabled ?? false,
297
+        _dialOutAuthUrl: dialOutAuthUrl ?? '',
298
+        _dialOutRegionUrl: dialOutRegionUrl ?? '',
299
         _dialOutEnabled: isDialOutEnabled(state),
299
         _dialOutEnabled: isDialOutEnabled(state),
300
-        _jwt: state['features/base/jwt'].jwt,
301
-        _peopleSearchQueryTypes: peopleSearchQueryTypes,
302
-        _peopleSearchUrl: peopleSearchUrl,
300
+        _jwt: state['features/base/jwt'].jwt ?? '',
301
+        _peopleSearchQueryTypes: peopleSearchQueryTypes ?? [],
302
+        _peopleSearchUrl: peopleSearchUrl ?? '',
303
         _sipInviteEnabled: isSipInviteEnabled(state)
303
         _sipInviteEnabled: isSipInviteEnabled(state)
304
     };
304
     };
305
 }
305
 }

+ 0
- 2
react/features/invite/components/add-people-dialog/web/AddPeopleDialog.tsx View File

27
 import DialInLimit from './DialInLimit';
27
 import DialInLimit from './DialInLimit';
28
 import DialInSection from './DialInSection';
28
 import DialInSection from './DialInSection';
29
 import InviteByEmailSection from './InviteByEmailSection';
29
 import InviteByEmailSection from './InviteByEmailSection';
30
-// eslint-disable-next-line lines-around-comment
31
-// @ts-ignore
32
 import InviteContactsSection from './InviteContactsSection';
30
 import InviteContactsSection from './InviteContactsSection';
33
 import LiveStreamSection from './LiveStreamSection';
31
 import LiveStreamSection from './LiveStreamSection';
34
 
32
 

react/features/invite/components/add-people-dialog/web/InviteContactsForm.js → react/features/invite/components/add-people-dialog/web/InviteContactsForm.tsx View File

1
-// @flow
2
-
3
-import InlineMessage from '@atlaskit/inline-message';
1
+import { Theme } from '@mui/material';
2
+import { withStyles } from '@mui/styles';
4
 import React from 'react';
3
 import React from 'react';
5
 import { connect } from 'react-redux';
4
 import { connect } from 'react-redux';
6
-import type { Dispatch } from 'redux';
7
 
5
 
6
+import { IReduxState, IStore } from '../../../../app/types';
8
 import Avatar from '../../../../base/avatar/components/Avatar';
7
 import Avatar from '../../../../base/avatar/components/Avatar';
9
 import { translate } from '../../../../base/i18n/functions';
8
 import { translate } from '../../../../base/i18n/functions';
10
 import Icon from '../../../../base/icons/components/Icon';
9
 import Icon from '../../../../base/icons/components/Icon';
11
 import { IconPhoneRinging } from '../../../../base/icons/svg';
10
 import { IconPhoneRinging } from '../../../../base/icons/svg';
12
 import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
11
 import MultiSelectAutocomplete from '../../../../base/react/components/web/MultiSelectAutocomplete';
12
+import Button from '../../../../base/ui/components/web/Button';
13
+import { BUTTON_TYPES } from '../../../../base/ui/constants.any';
13
 import { isVpaasMeeting } from '../../../../jaas/functions';
14
 import { isVpaasMeeting } from '../../../../jaas/functions';
14
-import { hideAddPeopleDialog } from '../../../actions';
15
+import { hideAddPeopleDialog } from '../../../actions.web';
15
 import { INVITE_TYPES } from '../../../constants';
16
 import { INVITE_TYPES } from '../../../constants';
17
+import { IInviteSelectItem, IInvitee } from '../../../types';
16
 import AbstractAddPeopleDialog, {
18
 import AbstractAddPeopleDialog, {
17
-    type Props as AbstractProps,
18
-    type State,
19
+    IProps as AbstractProps,
20
+    IState,
19
     _mapStateToProps as _abstractMapStateToProps
21
     _mapStateToProps as _abstractMapStateToProps
20
 } from '../AbstractAddPeopleDialog';
22
 } from '../AbstractAddPeopleDialog';
21
 
23
 
22
-declare var interfaceConfig: Object;
24
+const styles = (theme: Theme) => {
25
+    return {
26
+        formWrap: {
27
+            marginTop: theme.spacing(2)
28
+        },
29
+        inviteButtons: {
30
+            display: 'flex',
31
+            justifyContent: 'end',
32
+            marginTop: theme.spacing(2),
33
+            '& .invite-button': {
34
+                marginLeft: theme.spacing(2)
35
+            }
36
+        }
37
+    };
38
+};
39
+
23
 
40
 
24
-type Props = AbstractProps & {
41
+interface IProps extends AbstractProps {
25
 
42
 
26
     /**
43
     /**
27
      * The {@link JitsiMeetConference} which will be used to invite "room" participants.
44
      * The {@link JitsiMeetConference} which will be used to invite "room" participants.
28
      */
45
      */
29
-    _conference: Object,
46
+    _conference?: Object;
30
 
47
 
31
     /**
48
     /**
32
      * Whether the meeting belongs to JaaS user.
49
      * Whether the meeting belongs to JaaS user.
33
      */
50
      */
34
-    _isVpaas: boolean,
51
+    _isVpaas?: boolean;
52
+
53
+    /**
54
+     * Css classes.
55
+     */
56
+    classes: any;
35
 
57
 
36
     /**
58
     /**
37
      * The redux {@code dispatch} function.
59
      * The redux {@code dispatch} function.
38
      */
60
      */
39
-    dispatch: Dispatch<any>,
61
+    dispatch: IStore['dispatch'];
40
 
62
 
41
     /**
63
     /**
42
      * Invoked to obtain translated strings.
64
      * Invoked to obtain translated strings.
43
      */
65
      */
44
-    t: Function,
45
-};
66
+    t: Function;
67
+}
46
 
68
 
47
 /**
69
 /**
48
  * Form that enables inviting others to the call.
70
  * Form that enables inviting others to the call.
49
  */
71
  */
50
-class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
51
-    _multiselect = null;
72
+class InviteContactsForm extends AbstractAddPeopleDialog<IProps, IState> {
73
+    _multiselect: MultiSelectAutocomplete | null = null;
52
 
74
 
53
-    _resourceClient: Object;
75
+    _resourceClient: {
76
+        makeQuery: (query: string) => Promise<Array<any>>;
77
+        parseResults: Function;
78
+    };
54
 
79
 
55
-    _translations: Object;
80
+    _translations: {
81
+        [key: string]: string;
82
+        _addPeopleEnabled: string;
83
+        _dialOutEnabled: string;
84
+        _sipInviteEnabled: string;
85
+    };
56
 
86
 
57
     state = {
87
     state = {
58
         addToCallError: false,
88
         addToCallError: false,
59
         addToCallInProgress: false,
89
         addToCallInProgress: false,
60
-        inviteItems: []
90
+        inviteItems: [] as IInviteSelectItem[]
61
     };
91
     };
62
 
92
 
63
     /**
93
     /**
66
      * @param {Object} props - The read-only properties with which the new
96
      * @param {Object} props - The read-only properties with which the new
67
      * instance is to be initialized.
97
      * instance is to be initialized.
68
      */
98
      */
69
-    constructor(props: Props) {
99
+    constructor(props: IProps) {
70
         super(props);
100
         super(props);
71
 
101
 
72
         // Bind event handlers so they are only bound once per instance.
102
         // Bind event handlers so they are only bound once per instance.
99
     /**
129
     /**
100
      * React Component method that executes once component is updated.
130
      * React Component method that executes once component is updated.
101
      *
131
      *
102
-     * @param {Object} prevProps - The state object before the update.
103
-     * @param {Object} prevState - The state object before the update.
132
+     * @param {Props} prevProps - The props object before the update.
133
+     * @param {State} prevState - The state object before the update.
104
      * @returns {void}
134
      * @returns {void}
105
      */
135
      */
106
-    componentDidUpdate(prevProps, prevState) {
136
+    componentDidUpdate(prevProps: IProps, prevState: IState) {
107
         /**
137
         /**
108
          * Clears selected items from the multi select component on successful
138
          * Clears selected items from the multi select component on successful
109
          * invite.
139
          * invite.
133
         const loadingMessage = 'addPeople.searching';
163
         const loadingMessage = 'addPeople.searching';
134
         const noMatches = 'addPeople.noResults';
164
         const noMatches = 'addPeople.noResults';
135
 
165
 
136
-        const features = {
166
+        const features: { [key: string]: boolean; } = {
137
             _dialOutEnabled,
167
             _dialOutEnabled,
138
             _addPeopleEnabled,
168
             _addPeopleEnabled,
139
             _sipInviteEnabled
169
             _sipInviteEnabled
152
 
182
 
153
         return (
183
         return (
154
             <div
184
             <div
155
-                className = 'add-people-form-wrap'
185
+                className = { this.props.classes.formWrap }
156
                 onKeyDown = { this._onKeyDown }>
186
                 onKeyDown = { this._onKeyDown }>
157
-                { this._renderErrorMessage() }
158
                 <MultiSelectAutocomplete
187
                 <MultiSelectAutocomplete
159
                     isDisabled = { isMultiSelectDisabled }
188
                     isDisabled = { isMultiSelectDisabled }
160
                     loadingMessage = { t(loadingMessage) }
189
                     loadingMessage = { t(loadingMessage) }
172
         );
201
         );
173
     }
202
     }
174
 
203
 
175
-    _invite: Array<Object> => Promise<*>;
176
-
177
     _isAddDisabled: () => boolean;
204
     _isAddDisabled: () => boolean;
178
 
205
 
179
-    _onItemSelected: (Object) => Object;
180
-
181
     /**
206
     /**
182
      * Callback invoked when a selection has been made but before it has been
207
      * Callback invoked when a selection has been made but before it has been
183
      * set as selected.
208
      * set as selected.
184
      *
209
      *
185
-     * @param {Object} item - The item that has just been selected.
210
+     * @param {IInviteSelectItem} item - The item that has just been selected.
186
      * @private
211
      * @private
187
      * @returns {Object} The item to display as selected in the input.
212
      * @returns {Object} The item to display as selected in the input.
188
      */
213
      */
189
-    _onItemSelected(item) {
214
+    _onItemSelected(item: IInviteSelectItem) {
190
         if (item.item.type === INVITE_TYPES.PHONE) {
215
         if (item.item.type === INVITE_TYPES.PHONE) {
191
             item.content = item.item.number;
216
             item.content = item.item.number;
192
         }
217
         }
194
         return item;
219
         return item;
195
     }
220
     }
196
 
221
 
197
-    _onSelectionChange: (Map<*, *>) => void;
198
-
199
     /**
222
     /**
200
      * Handles a selection change.
223
      * Handles a selection change.
201
      *
224
      *
202
-     * @param {Array} selectedItems - The list of selected items.
225
+     * @param {Array<IInviteSelectItem>} selectedItems - The list of selected items.
203
      * @private
226
      * @private
204
      * @returns {void}
227
      * @returns {void}
205
      */
228
      */
206
-    _onSelectionChange(selectedItems) {
229
+    _onSelectionChange(selectedItems: IInviteSelectItem[]) {
207
         this.setState({
230
         this.setState({
208
             inviteItems: selectedItems
231
             inviteItems: selectedItems
209
         });
232
         });
210
     }
233
     }
211
 
234
 
212
-    _onSubmit: () => void;
213
 
235
 
214
     /**
236
     /**
215
      * Submits the selection for inviting.
237
      * Submits the selection for inviting.
222
         const invitees = inviteItems.map(({ item }) => item);
244
         const invitees = inviteItems.map(({ item }) => item);
223
 
245
 
224
         this._invite(invitees)
246
         this._invite(invitees)
225
-            .then(invitesLeftToSend => {
247
+            .then((invitesLeftToSend: IInvitee[]) => {
226
                 if (invitesLeftToSend.length) {
248
                 if (invitesLeftToSend.length) {
227
                     const unsentInviteIDs
249
                     const unsentInviteIDs
228
                         = invitesLeftToSend.map(invitee =>
250
                         = invitesLeftToSend.map(invitee =>
229
                             invitee.id || invitee.user_id || invitee.number);
251
                             invitee.id || invitee.user_id || invitee.number);
230
-                    const itemsToSelect
231
-                        = inviteItems.filter(({ item }) =>
232
-                            unsentInviteIDs.includes(item.id || item.user_id || item.number));
252
+                    const itemsToSelect = inviteItems.filter(({ item }) =>
253
+                        unsentInviteIDs.includes(item.id || item.user_id || item.number));
233
 
254
 
234
                     if (this._multiselect) {
255
                     if (this._multiselect) {
235
                         this._multiselect.setSelectedItems(itemsToSelect);
256
                         this._multiselect.setSelectedItems(itemsToSelect);
236
                     }
257
                     }
237
-                } else {
238
-                    this.props.dispatch(hideAddPeopleDialog());
239
                 }
258
                 }
240
-            });
259
+            })
260
+            .finally(() => this.props.dispatch(hideAddPeopleDialog()));
241
     }
261
     }
242
 
262
 
243
-    _onSubmitKeyPress: (Object) => void;
244
-
245
     /**
263
     /**
246
      * KeyPress handler for accessibility.
264
      * KeyPress handler for accessibility.
247
      *
265
      *
248
-     * @param {Object} e - The key event to handle.
266
+     * @param {KeyboardEvent} e - The key event to handle.
249
      *
267
      *
250
      * @returns {void}
268
      * @returns {void}
251
      */
269
      */
252
-    _onSubmitKeyPress(e) {
270
+    _onSubmitKeyPress(e: React.KeyboardEvent) {
253
         if (e.key === ' ' || e.key === 'Enter') {
271
         if (e.key === ' ' || e.key === 'Enter') {
254
             e.preventDefault();
272
             e.preventDefault();
255
             this._onSubmit();
273
             this._onSubmit();
256
         }
274
         }
257
     }
275
     }
258
 
276
 
259
-    _onKeyDown: (Object) => void;
260
-
261
     /**
277
     /**
262
      * Handles 'Enter' key in the form to trigger the invite.
278
      * Handles 'Enter' key in the form to trigger the invite.
263
      *
279
      *
264
-     * @param {Object} event - The key event.
280
+     * @param {KeyboardEvent} event - The key event.
265
      * @returns {void}
281
      * @returns {void}
266
      */
282
      */
267
-    _onKeyDown(event) {
283
+    _onKeyDown(event: React.KeyboardEvent) {
268
         const { inviteItems } = this.state;
284
         const { inviteItems } = this.state;
269
 
285
 
270
         if (event.key === 'Enter') {
286
         if (event.key === 'Enter') {
275
         }
291
         }
276
     }
292
     }
277
 
293
 
278
-    _parseQueryResults: (?Array<Object>) => Array<Object>;
279
-
280
     /**
294
     /**
281
      * Returns the avatar component for a user.
295
      * Returns the avatar component for a user.
282
      *
296
      *
283
-     * @param {Object} user - The user.
297
+     * @param {any} user - The user.
284
      * @param {string} className - The CSS class for the avatar component.
298
      * @param {string} className - The CSS class for the avatar component.
285
      * @private
299
      * @private
286
      * @returns {ReactElement}
300
      * @returns {ReactElement}
287
      */
301
      */
288
-    _getAvatar(user, className = 'avatar-small') {
302
+    _getAvatar(user: any, className = 'avatar-small') {
289
         return (
303
         return (
290
             <Avatar
304
             <Avatar
291
                 className = { className }
305
                 className = { className }
306
+                size = { 32 }
292
                 status = { user.status }
307
                 status = { user.status }
293
                 url = { user.avatar } />
308
                 url = { user.avatar } />
294
         );
309
         );
305
      * @returns {Object[]} Configuration objects for items to display in the
320
      * @returns {Object[]} Configuration objects for items to display in the
306
      * search autocomplete.
321
      * search autocomplete.
307
      */
322
      */
308
-    _parseQueryResults(response = []) {
323
+    _parseQueryResults(response: IInvitee[] = []) {
309
         const { t, _dialOutEnabled } = this.props;
324
         const { t, _dialOutEnabled } = this.props;
310
 
325
 
311
         const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
326
         const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
394
         ];
409
         ];
395
     }
410
     }
396
 
411
 
397
-    _query: (string) => Promise<Array<Object>>;
398
-
399
-    _onClearItems: () => void;
400
-
401
     /**
412
     /**
402
      * Clears the selected items from state and form.
413
      * Clears the selected items from state and form.
403
      *
414
      *
410
         this.setState({ inviteItems: [] });
421
         this.setState({ inviteItems: [] });
411
     }
422
     }
412
 
423
 
413
-    _onClearItemsKeyPress: () => void;
414
-
415
     /**
424
     /**
416
      * Clears the selected items from state and form.
425
      * Clears the selected items from state and form.
417
      *
426
      *
418
-     * @param {Object} e - The key event to handle.
427
+     * @param {KeyboardEvent} e - The key event to handle.
419
      *
428
      *
420
      * @returns {void}
429
      * @returns {void}
421
      */
430
      */
422
-    _onClearItemsKeyPress(e) {
431
+    _onClearItemsKeyPress(e: KeyboardEvent) {
423
         if (e.key === ' ' || e.key === 'Enter') {
432
         if (e.key === ' ' || e.key === 'Enter') {
424
             e.preventDefault();
433
             e.preventDefault();
425
             this._onClearItems();
434
             this._onClearItems();
433
      */
442
      */
434
     _renderFormActions() {
443
     _renderFormActions() {
435
         const { inviteItems } = this.state;
444
         const { inviteItems } = this.state;
436
-        const { t } = this.props;
445
+        const { t, classes } = this.props;
437
 
446
 
438
         if (!inviteItems.length) {
447
         if (!inviteItems.length) {
439
             return null;
448
             return null;
440
         }
449
         }
441
 
450
 
442
         return (
451
         return (
443
-            <div className = { `invite-more-dialog invite-buttons${this._isAddDisabled() ? ' disabled' : ''}` }>
444
-                <a
452
+            <div className = { classes.inviteButtons }>
453
+                <Button
445
                     aria-label = { t('dialog.Cancel') }
454
                     aria-label = { t('dialog.Cancel') }
446
-                    className = 'invite-more-dialog invite-buttons-cancel'
455
+                    className = 'invite-button'
456
+                    label = { t('dialog.Cancel') }
447
                     onClick = { this._onClearItems }
457
                     onClick = { this._onClearItems }
448
                     onKeyPress = { this._onClearItemsKeyPress }
458
                     onKeyPress = { this._onClearItemsKeyPress }
449
                     role = 'button'
459
                     role = 'button'
450
-                    tabIndex = { 0 }>
451
-                    {t('dialog.Cancel')}
452
-                </a>
453
-                <a
460
+                    type = { BUTTON_TYPES.SECONDARY } />
461
+                <Button
454
                     aria-label = { t('addPeople.add') }
462
                     aria-label = { t('addPeople.add') }
455
-                    className = 'invite-more-dialog invite-buttons-add'
463
+                    className = 'invite-button'
464
+                    disabled = { this._isAddDisabled() }
465
+                    label = { t('addPeople.add') }
456
                     onClick = { this._onSubmit }
466
                     onClick = { this._onSubmit }
457
                     onKeyPress = { this._onSubmitKeyPress }
467
                     onKeyPress = { this._onSubmitKeyPress }
458
-                    role = 'button'
459
-                    tabIndex = { 0 }>
460
-                    {t('addPeople.add')}
461
-                </a>
462
-            </div>
463
-        );
464
-    }
465
-
466
-    /**
467
-     * Renders the error message if the add doesn't succeed.
468
-     *
469
-     * @private
470
-     * @returns {ReactElement|null}
471
-     */
472
-    _renderErrorMessage() {
473
-        if (!this.state.addToCallError) {
474
-            return null;
475
-        }
476
-
477
-        const { t } = this.props;
478
-        const supportString = t('inlineDialogFailure.supportMsg');
479
-        const supportLink = interfaceConfig.SUPPORT_URL;
480
-
481
-        const supportLinkContent = supportLink ? (
482
-            <span>
483
-                <span>
484
-                    { supportString.padEnd(supportString.length + 1) }
485
-                </span>
486
-                <span>
487
-                    <a
488
-                        aria-label = { supportLink }
489
-                        href = { supportLink }
490
-                        rel = 'noopener noreferrer'
491
-                        target = '_blank'>
492
-                        { t('inlineDialogFailure.support') }
493
-                    </a>
494
-                </span>
495
-                <span>.</span>
496
-            </span>
497
-        ) : null;
498
-
499
-        return (
500
-            <div className = 'modal-dialog-form-error'>
501
-                <InlineMessage
502
-                    title = { t('addPeople.failedToAdd') }
503
-                    type = 'error'>
504
-                    { supportLinkContent }
505
-                </InlineMessage>
468
+                    role = 'button' />
506
             </div>
469
             </div>
507
         );
470
         );
508
     }
471
     }
515
      */
478
      */
516
     _renderTelephoneIcon() {
479
     _renderTelephoneIcon() {
517
         return (
480
         return (
518
-            <span className = 'add-telephone-icon'>
519
-                <Icon src = { IconPhoneRinging } />
520
-            </span>
481
+            <Icon src = { IconPhoneRinging } />
521
         );
482
         );
522
     }
483
     }
523
 
484
 
524
-    _setMultiSelectElement: (React$ElementRef<*> | null) => void;
525
-
526
     /**
485
     /**
527
      * Sets the instance variable for the multi select component
486
      * Sets the instance variable for the multi select component
528
      * element so it can be accessed directly.
487
      * element so it can be accessed directly.
529
      *
488
      *
530
-     * @param {Object} element - The DOM element for the component's dialog.
489
+     * @param {MultiSelectAutocomplete} element - The DOM element for the component's dialog.
531
      * @private
490
      * @private
532
      * @returns {void}
491
      * @returns {void}
533
      */
492
      */
534
-    _setMultiSelectElement(element) {
493
+    _setMultiSelectElement(element: MultiSelectAutocomplete) {
535
         this._multiselect = element;
494
         this._multiselect = element;
536
     }
495
     }
537
 }
496
 }
540
  * Maps (parts of) the Redux state to the associated
499
  * Maps (parts of) the Redux state to the associated
541
  * {@code AddPeopleDialog}'s props.
500
  * {@code AddPeopleDialog}'s props.
542
  *
501
  *
543
- * @param {Object} state - The Redux state.
502
+ * @param {IReduxState} state - The Redux state.
544
  * @private
503
  * @private
545
  * @returns {Props}
504
  * @returns {Props}
546
  */
505
  */
547
-function _mapStateToProps(state) {
506
+function _mapStateToProps(state: IReduxState) {
548
     return {
507
     return {
549
         ..._abstractMapStateToProps(state),
508
         ..._abstractMapStateToProps(state),
550
         _isVpaas: isVpaasMeeting(state)
509
         _isVpaas: isVpaasMeeting(state)
551
     };
510
     };
552
 }
511
 }
553
 
512
 
554
-export default translate(connect(_mapStateToProps)(InviteContactsForm));
513
+export default translate(connect(_mapStateToProps)(withStyles(styles)(InviteContactsForm)));

react/features/invite/components/add-people-dialog/web/InviteContactsSection.js → react/features/invite/components/add-people-dialog/web/InviteContactsSection.tsx View File

1
-// @flow
2
-
3
 import React from 'react';
1
 import React from 'react';
4
-
5
-import { translate } from '../../../../base/i18n/functions';
2
+import { useTranslation } from 'react-i18next';
6
 
3
 
7
 import InviteContactsForm from './InviteContactsForm';
4
 import InviteContactsForm from './InviteContactsForm';
8
 
5
 
9
-type Props = {
10
-
11
-    /**
12
-     * Invoked to obtain translated strings.
13
-     */
14
-    t: Function
15
-};
16
-
17
 /**
6
 /**
18
  * Component that represents the invitation section of the {@code AddPeopleDialog}.
7
  * Component that represents the invitation section of the {@code AddPeopleDialog}.
19
  *
8
  *
20
  * @returns {ReactElement$<any>}
9
  * @returns {ReactElement$<any>}
21
  */
10
  */
22
-function InviteContactsSection({ t }: Props) {
11
+function InviteContactsSection() {
12
+    const { t } = useTranslation();
13
+
23
     return (
14
     return (
24
         <>
15
         <>
25
             <span>{t('addPeople.addContacts')}</span>
16
             <span>{t('addPeople.addContacts')}</span>
29
     );
20
     );
30
 }
21
 }
31
 
22
 
32
-export default translate(InviteContactsSection);
23
+export default InviteContactsSection;

+ 15
- 1
react/features/invite/types.ts View File

1
+import { MultiSelectItem } from '../base/ui/components/types';
2
+
1
 export interface IInvitee {
3
 export interface IInvitee {
2
     address: string;
4
     address: string;
3
-    name: string;
5
+    allowed?: boolean;
6
+    id?: string;
7
+    name?: string;
4
     number: string;
8
     number: string;
9
+    originalEntry?: string;
10
+    phone?: string;
11
+    showCountryCodeReminder?: boolean;
5
     type: string;
12
     type: string;
13
+    user_id?: string;
14
+}
15
+
16
+export interface IInviteSelectItem extends MultiSelectItem {
17
+    filterValues?: string[];
18
+    item: IInvitee;
19
+    tag?: any;
6
 }
20
 }

+ 1
- 1
react/features/toolbox/constants.ts View File

40
 };
40
 };
41
 
41
 
42
 // Around 300 to be displayed above components like chat
42
 // Around 300 to be displayed above components like chat
43
-export const ZINDEX_DIALOG_PORTAL = 300;
43
+export const ZINDEX_DIALOG_PORTAL = 302;

Loading…
Cancel
Save