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,7 +60,6 @@ $flagsImagePath: "../images/";
60 60
 @import 'filmstrip/vertical_filmstrip';
61 61
 @import 'filmstrip/vertical_filmstrip_overrides';
62 62
 @import 'unsupported-browser/main';
63
-@import 'modals/invite/add-people';
64 63
 @import 'deep-linking/main';
65 64
 @import 'transcription-subtitles';
66 65
 @import '_meetings_list.scss';

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

@@ -12,24 +12,6 @@
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 16
  * Styling shared video dialog errors.
35 17
  */

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

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

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

@@ -105,3 +105,11 @@ export interface ISwitchProps {
105 105
      */
106 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,6 +20,8 @@ interface IProps extends IInputProps {
20 20
     maxRows?: number;
21 21
     minRows?: number;
22 22
     name?: string;
23
+    onBlur?: (e: any) => void;
24
+    onFocus?: (event: React.FocusEvent) => void;
23 25
     onKeyPress?: (e: React.KeyboardEvent) => void;
24 26
     readOnly?: boolean;
25 27
     required?: boolean;
@@ -148,7 +150,9 @@ const Input = React.forwardRef<any, IProps>(({
148 150
     maxRows,
149 151
     minRows,
150 152
     name,
153
+    onBlur,
151 154
     onChange,
155
+    onFocus,
152 156
     onKeyPress,
153 157
     placeholder,
154 158
     readOnly = false,
@@ -208,7 +212,9 @@ const Input = React.forwardRef<any, IProps>(({
208 212
                         { ...(id ? { id } : {}) }
209 213
                         maxLength = { maxLength }
210 214
                         name = { name }
215
+                        onBlur = { onBlur }
211 216
                         onChange = { handleChange }
217
+                        onFocus = { onFocus }
212 218
                         onKeyPress = { onKeyPress }
213 219
                         placeholder = { placeholder }
214 220
                         readOnly = { readOnly }

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

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

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

@@ -27,8 +27,6 @@ import CopyMeetingLinkSection from './CopyMeetingLinkSection';
27 27
 import DialInLimit from './DialInLimit';
28 28
 import DialInSection from './DialInSection';
29 29
 import InviteByEmailSection from './InviteByEmailSection';
30
-// eslint-disable-next-line lines-around-comment
31
-// @ts-ignore
32 30
 import InviteContactsSection from './InviteContactsSection';
33 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,63 +1,93 @@
1
-// @flow
2
-
3
-import InlineMessage from '@atlaskit/inline-message';
1
+import { Theme } from '@mui/material';
2
+import { withStyles } from '@mui/styles';
4 3
 import React from 'react';
5 4
 import { connect } from 'react-redux';
6
-import type { Dispatch } from 'redux';
7 5
 
6
+import { IReduxState, IStore } from '../../../../app/types';
8 7
 import Avatar from '../../../../base/avatar/components/Avatar';
9 8
 import { translate } from '../../../../base/i18n/functions';
10 9
 import Icon from '../../../../base/icons/components/Icon';
11 10
 import { IconPhoneRinging } from '../../../../base/icons/svg';
12 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 14
 import { isVpaasMeeting } from '../../../../jaas/functions';
14
-import { hideAddPeopleDialog } from '../../../actions';
15
+import { hideAddPeopleDialog } from '../../../actions.web';
15 16
 import { INVITE_TYPES } from '../../../constants';
17
+import { IInviteSelectItem, IInvitee } from '../../../types';
16 18
 import AbstractAddPeopleDialog, {
17
-    type Props as AbstractProps,
18
-    type State,
19
+    IProps as AbstractProps,
20
+    IState,
19 21
     _mapStateToProps as _abstractMapStateToProps
20 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 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 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 59
      * The redux {@code dispatch} function.
38 60
      */
39
-    dispatch: Dispatch<any>,
61
+    dispatch: IStore['dispatch'];
40 62
 
41 63
     /**
42 64
      * Invoked to obtain translated strings.
43 65
      */
44
-    t: Function,
45
-};
66
+    t: Function;
67
+}
46 68
 
47 69
 /**
48 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 87
     state = {
58 88
         addToCallError: false,
59 89
         addToCallInProgress: false,
60
-        inviteItems: []
90
+        inviteItems: [] as IInviteSelectItem[]
61 91
     };
62 92
 
63 93
     /**
@@ -66,7 +96,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
66 96
      * @param {Object} props - The read-only properties with which the new
67 97
      * instance is to be initialized.
68 98
      */
69
-    constructor(props: Props) {
99
+    constructor(props: IProps) {
70 100
         super(props);
71 101
 
72 102
         // Bind event handlers so they are only bound once per instance.
@@ -99,11 +129,11 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
99 129
     /**
100 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 134
      * @returns {void}
105 135
      */
106
-    componentDidUpdate(prevProps, prevState) {
136
+    componentDidUpdate(prevProps: IProps, prevState: IState) {
107 137
         /**
108 138
          * Clears selected items from the multi select component on successful
109 139
          * invite.
@@ -133,7 +163,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
133 163
         const loadingMessage = 'addPeople.searching';
134 164
         const noMatches = 'addPeople.noResults';
135 165
 
136
-        const features = {
166
+        const features: { [key: string]: boolean; } = {
137 167
             _dialOutEnabled,
138 168
             _addPeopleEnabled,
139 169
             _sipInviteEnabled
@@ -152,9 +182,8 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
152 182
 
153 183
         return (
154 184
             <div
155
-                className = 'add-people-form-wrap'
185
+                className = { this.props.classes.formWrap }
156 186
                 onKeyDown = { this._onKeyDown }>
157
-                { this._renderErrorMessage() }
158 187
                 <MultiSelectAutocomplete
159 188
                     isDisabled = { isMultiSelectDisabled }
160 189
                     loadingMessage = { t(loadingMessage) }
@@ -172,21 +201,17 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
172 201
         );
173 202
     }
174 203
 
175
-    _invite: Array<Object> => Promise<*>;
176
-
177 204
     _isAddDisabled: () => boolean;
178 205
 
179
-    _onItemSelected: (Object) => Object;
180
-
181 206
     /**
182 207
      * Callback invoked when a selection has been made but before it has been
183 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 211
      * @private
187 212
      * @returns {Object} The item to display as selected in the input.
188 213
      */
189
-    _onItemSelected(item) {
214
+    _onItemSelected(item: IInviteSelectItem) {
190 215
         if (item.item.type === INVITE_TYPES.PHONE) {
191 216
             item.content = item.item.number;
192 217
         }
@@ -194,22 +219,19 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
194 219
         return item;
195 220
     }
196 221
 
197
-    _onSelectionChange: (Map<*, *>) => void;
198
-
199 222
     /**
200 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 226
      * @private
204 227
      * @returns {void}
205 228
      */
206
-    _onSelectionChange(selectedItems) {
229
+    _onSelectionChange(selectedItems: IInviteSelectItem[]) {
207 230
         this.setState({
208 231
             inviteItems: selectedItems
209 232
         });
210 233
     }
211 234
 
212
-    _onSubmit: () => void;
213 235
 
214 236
     /**
215 237
      * Submits the selection for inviting.
@@ -222,49 +244,43 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
222 244
         const invitees = inviteItems.map(({ item }) => item);
223 245
 
224 246
         this._invite(invitees)
225
-            .then(invitesLeftToSend => {
247
+            .then((invitesLeftToSend: IInvitee[]) => {
226 248
                 if (invitesLeftToSend.length) {
227 249
                     const unsentInviteIDs
228 250
                         = invitesLeftToSend.map(invitee =>
229 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 255
                     if (this._multiselect) {
235 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 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 268
      * @returns {void}
251 269
      */
252
-    _onSubmitKeyPress(e) {
270
+    _onSubmitKeyPress(e: React.KeyboardEvent) {
253 271
         if (e.key === ' ' || e.key === 'Enter') {
254 272
             e.preventDefault();
255 273
             this._onSubmit();
256 274
         }
257 275
     }
258 276
 
259
-    _onKeyDown: (Object) => void;
260
-
261 277
     /**
262 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 281
      * @returns {void}
266 282
      */
267
-    _onKeyDown(event) {
283
+    _onKeyDown(event: React.KeyboardEvent) {
268 284
         const { inviteItems } = this.state;
269 285
 
270 286
         if (event.key === 'Enter') {
@@ -275,20 +291,19 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
275 291
         }
276 292
     }
277 293
 
278
-    _parseQueryResults: (?Array<Object>) => Array<Object>;
279
-
280 294
     /**
281 295
      * Returns the avatar component for a user.
282 296
      *
283
-     * @param {Object} user - The user.
297
+     * @param {any} user - The user.
284 298
      * @param {string} className - The CSS class for the avatar component.
285 299
      * @private
286 300
      * @returns {ReactElement}
287 301
      */
288
-    _getAvatar(user, className = 'avatar-small') {
302
+    _getAvatar(user: any, className = 'avatar-small') {
289 303
         return (
290 304
             <Avatar
291 305
                 className = { className }
306
+                size = { 32 }
292 307
                 status = { user.status }
293 308
                 url = { user.avatar } />
294 309
         );
@@ -305,7 +320,7 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
305 320
      * @returns {Object[]} Configuration objects for items to display in the
306 321
      * search autocomplete.
307 322
      */
308
-    _parseQueryResults(response = []) {
323
+    _parseQueryResults(response: IInvitee[] = []) {
309 324
         const { t, _dialOutEnabled } = this.props;
310 325
 
311 326
         const userTypes = [ INVITE_TYPES.USER, INVITE_TYPES.VIDEO_ROOM, INVITE_TYPES.ROOM ];
@@ -394,10 +409,6 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
394 409
         ];
395 410
     }
396 411
 
397
-    _query: (string) => Promise<Array<Object>>;
398
-
399
-    _onClearItems: () => void;
400
-
401 412
     /**
402 413
      * Clears the selected items from state and form.
403 414
      *
@@ -410,16 +421,14 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
410 421
         this.setState({ inviteItems: [] });
411 422
     }
412 423
 
413
-    _onClearItemsKeyPress: () => void;
414
-
415 424
     /**
416 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 429
      * @returns {void}
421 430
      */
422
-    _onClearItemsKeyPress(e) {
431
+    _onClearItemsKeyPress(e: KeyboardEvent) {
423 432
         if (e.key === ' ' || e.key === 'Enter') {
424 433
             e.preventDefault();
425 434
             this._onClearItems();
@@ -433,76 +442,30 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
433 442
      */
434 443
     _renderFormActions() {
435 444
         const { inviteItems } = this.state;
436
-        const { t } = this.props;
445
+        const { t, classes } = this.props;
437 446
 
438 447
         if (!inviteItems.length) {
439 448
             return null;
440 449
         }
441 450
 
442 451
         return (
443
-            <div className = { `invite-more-dialog invite-buttons${this._isAddDisabled() ? ' disabled' : ''}` }>
444
-                <a
452
+            <div className = { classes.inviteButtons }>
453
+                <Button
445 454
                     aria-label = { t('dialog.Cancel') }
446
-                    className = 'invite-more-dialog invite-buttons-cancel'
455
+                    className = 'invite-button'
456
+                    label = { t('dialog.Cancel') }
447 457
                     onClick = { this._onClearItems }
448 458
                     onKeyPress = { this._onClearItemsKeyPress }
449 459
                     role = 'button'
450
-                    tabIndex = { 0 }>
451
-                    {t('dialog.Cancel')}
452
-                </a>
453
-                <a
460
+                    type = { BUTTON_TYPES.SECONDARY } />
461
+                <Button
454 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 466
                     onClick = { this._onSubmit }
457 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 469
             </div>
507 470
         );
508 471
     }
@@ -515,23 +478,19 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
515 478
      */
516 479
     _renderTelephoneIcon() {
517 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 486
      * Sets the instance variable for the multi select component
528 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 490
      * @private
532 491
      * @returns {void}
533 492
      */
534
-    _setMultiSelectElement(element) {
493
+    _setMultiSelectElement(element: MultiSelectAutocomplete) {
535 494
         this._multiselect = element;
536 495
     }
537 496
 }
@@ -540,15 +499,15 @@ class InviteContactsForm extends AbstractAddPeopleDialog<Props, State> {
540 499
  * Maps (parts of) the Redux state to the associated
541 500
  * {@code AddPeopleDialog}'s props.
542 501
  *
543
- * @param {Object} state - The Redux state.
502
+ * @param {IReduxState} state - The Redux state.
544 503
  * @private
545 504
  * @returns {Props}
546 505
  */
547
-function _mapStateToProps(state) {
506
+function _mapStateToProps(state: IReduxState) {
548 507
     return {
549 508
         ..._abstractMapStateToProps(state),
550 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,25 +1,16 @@
1
-// @flow
2
-
3 1
 import React from 'react';
4
-
5
-import { translate } from '../../../../base/i18n/functions';
2
+import { useTranslation } from 'react-i18next';
6 3
 
7 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 7
  * Component that represents the invitation section of the {@code AddPeopleDialog}.
19 8
  *
20 9
  * @returns {ReactElement$<any>}
21 10
  */
22
-function InviteContactsSection({ t }: Props) {
11
+function InviteContactsSection() {
12
+    const { t } = useTranslation();
13
+
23 14
     return (
24 15
         <>
25 16
             <span>{t('addPeople.addContacts')}</span>
@@ -29,4 +20,4 @@ function InviteContactsSection({ t }: Props) {
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,6 +1,20 @@
1
+import { MultiSelectItem } from '../base/ui/components/types';
2
+
1 3
 export interface IInvitee {
2 4
     address: string;
3
-    name: string;
5
+    allowed?: boolean;
6
+    id?: string;
7
+    name?: string;
4 8
     number: string;
9
+    originalEntry?: string;
10
+    phone?: string;
11
+    showCountryCodeReminder?: boolean;
5 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,4 +40,4 @@ export const NOTIFY_CLICK_MODE = {
40 40
 };
41 41
 
42 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