Browse Source

feat(prejoin_page): Add ability for guest to join audio by phone

master
Vlad Piersec 4 years ago
parent
commit
b2895b7095

+ 75
- 0
css/_country-picker.scss View File

@@ -0,0 +1,75 @@
1
+.cpick {
2
+    border: 1px solid #A4B8D1;
3
+    color: #fff;
4
+    display: flex;
5
+    font-size: 15px;
6
+    height: 38px;
7
+    line-height: 24px;
8
+
9
+    &-selector {
10
+        align-items: center;
11
+        background-color: #283447;
12
+        border-right: 1px solid #A4B8D1;
13
+        cursor: pointer;
14
+        display: flex;
15
+        padding: 8px 10px;
16
+        position: relative;
17
+        width: 88px;
18
+    }
19
+
20
+    &-icon {
21
+        margin-right: 8px;
22
+        position: absolute;
23
+        right: 0;
24
+        top: 12px;
25
+
26
+        & > svg {
27
+            fill: #fff;
28
+        }
29
+    }
30
+
31
+    &-input {
32
+        padding: 8px;
33
+        background: #1C2025;
34
+        border: 0;
35
+        margin: 0;
36
+        color: #fff;
37
+        caret-color: #0376DA;
38
+        flex-grow: 1;
39
+    }
40
+
41
+    &-dropdown {
42
+        height: 190px;
43
+        overflow-y: auto;
44
+        width: 343px;
45
+    }
46
+
47
+    &-dropdown-entry {
48
+        align-items: center;
49
+        cursor: pointer;
50
+        display: flex;
51
+        height: 40px;
52
+        padding: 0 10px;
53
+
54
+        &:hover {
55
+            background-color: #66768b;
56
+        }
57
+
58
+        &-text {
59
+            color: #fff;
60
+            flex-grow: 1;
61
+            font-size: 15px;
62
+            line-height: 24px;
63
+            overflow: hidden;
64
+            text-overflow: ellipsis;
65
+            white-space: nowrap;
66
+
67
+        }
68
+    }
69
+}
70
+
71
+// Override @Atlaskit/inline-dialog styles
72
+.cpick-container > div > div:nth-child(2) > div > div {
73
+    outline: none;
74
+    padding: 8px 0 0 0;
75
+}

+ 182
- 0
css/_prejoin-dialog.scss View File

@@ -0,0 +1,182 @@
1
+.prejoin-dialog {
2
+    background: #1C2025;
3
+    box-shadow: 0px 2px 20px rgba(0, 0, 0, 0.5);
4
+    border-radius: 5px;
5
+    color: #fff;
6
+    height: 400px;
7
+    width: 375px;
8
+
9
+    &--small {
10
+        height: 300;
11
+        width: 400;
12
+    }
13
+
14
+    &-label {
15
+        font-size: 15px;
16
+        line-height: 24px;
17
+
18
+        &-num {
19
+            background: #2b3b4b;
20
+            border: 1px solid #A4B8D1;
21
+            border-radius: 50%;
22
+            color: #fff;
23
+            display: inline-block;
24
+            height: 24px;
25
+            margin-right: 8px;
26
+            width: 24px;
27
+        }
28
+    }
29
+
30
+    &-container {
31
+        align-items: center;
32
+        background: rgba(0,0,0,0.6);
33
+        display: flex;
34
+        height: 100vh;
35
+        justify-content: center;
36
+        left: 0;
37
+        position: absolute;
38
+        top: 0;
39
+        width: 100vw;
40
+        z-index: 3;
41
+    }
42
+
43
+    &-flag {
44
+        display: inline-block;
45
+        margin-right: 8px;
46
+        transform: scale(1.2);
47
+    }
48
+
49
+    &-title {
50
+        display: inline-block;
51
+        font-size: 24px;
52
+        line-height: 32px;
53
+    }
54
+
55
+    &-icon {
56
+        cursor: pointer;
57
+
58
+        > svg {
59
+            fill: #A4B8D1;
60
+        }
61
+    }
62
+
63
+    &-btn {
64
+        width: 309px;
65
+    }
66
+
67
+    &-dialin-container {
68
+        text-align: center;
69
+    }
70
+
71
+    &-delimiter {
72
+        background: #5f6266;
73
+        border: 0;
74
+        height: 1px;
75
+        margin: 0;
76
+        padding: 0;
77
+        width: 100%;
78
+
79
+        &-container {
80
+            margin: 16px 0 24px 0;
81
+            position: relative;
82
+        }
83
+
84
+        &-txt-container {
85
+            position: absolute;
86
+            text-align: center;
87
+            top: -8px;
88
+            width: 100%;
89
+        }
90
+
91
+        &-txt {
92
+            background: #1C2025;
93
+            color: #5f6266;
94
+            font-size: 11px;
95
+            text-transform: uppercase;
96
+            padding: 0 8px;
97
+        }
98
+    }
99
+}
100
+
101
+.prejoin-dialog-callout {
102
+    padding: 16px;
103
+
104
+    &-header {
105
+        display: flex;
106
+        justify-content: space-between;
107
+        margin-bottom: 24px;
108
+    }
109
+
110
+    &-picker {
111
+        margin: 8px 0 16px 0;
112
+    }
113
+}
114
+
115
+.prejoin-dialog-dialin {
116
+    text-align: center;
117
+
118
+    &-header {
119
+        align-items: center;
120
+        margin: 16px 0 32px 16px;
121
+        display: flex;
122
+    }
123
+
124
+    &-icon {
125
+        margin-right: 16px;
126
+    }
127
+
128
+    &-num {
129
+        background: #3e474f;
130
+        border-radius: 4px;
131
+        display: inline-block;
132
+        font-size: 15px;
133
+        line-height: 24px;
134
+        margin: 4px;
135
+        padding: 8px;
136
+
137
+        &-container {
138
+            min-height: 48px;
139
+            margin: 8px 0;
140
+        }
141
+    }
142
+
143
+    &-link {
144
+        color: #6FB1EA;
145
+        cursor: pointer;
146
+        display: inline-block;
147
+        font-size: 13px;
148
+        line-height: 20px;
149
+        margin-bottom: 24px;
150
+    }
151
+
152
+    &-spaced-label {
153
+        margin-bottom: 16px;
154
+        margin-top: 28px;
155
+    }
156
+
157
+    &-btns {
158
+        &> div {
159
+            margin-bottom: 16px;
160
+        }
161
+    }
162
+}
163
+
164
+.prejoin-dialog-calling {
165
+    padding: 16px;
166
+    text-align: center;
167
+
168
+    &-header {
169
+        text-align: right;
170
+    }
171
+
172
+    &-label {
173
+        font-size: 15px;
174
+        margin: 8px 0 16px 0;
175
+    }
176
+
177
+    &-number {
178
+        font-size: 19px;
179
+        line-height: 28px;
180
+        margin: 16px 0;
181
+    }
182
+}

+ 2
- 0
css/main.scss View File

@@ -91,5 +91,7 @@ $flagsImagePath: "../images/";
91 91
 @import 'audio-preview';
92 92
 @import 'video-preview';
93 93
 @import 'prejoin';
94
+@import 'prejoin-dialog';
95
+@import 'country-picker';
94 96
 
95 97
 /* Modules END */

+ 6
- 0
lang/main.json View File

@@ -489,6 +489,12 @@
489 489
         "dialInPin": "Dial into the meeting and enter PIN code:",
490 490
         "dialing": "Dialing",
491 491
         "doNotShow": "Don't show this again",
492
+        "errorDialOut": "Could not dial out",
493
+        "errorDialOutDisconnected": "Could not dial out. Disconnected",
494
+        "errorDialOutFailed": "Could not dial out. Call failed",
495
+        "errorDialOutStatus": "Error getting dial out status",
496
+        "errorStatusCode": "Error dialing out, status code: {{status}}",
497
+        "errorValidation": "Number validation failed",
492 498
         "iWantToDialIn": "I want to dial in",
493 499
         "joinAudioByPhone": "Join with phone audio",
494 500
         "joinMeeting": "Join meeting",

+ 20
- 0
react/features/base/config/functions.web.js View File

@@ -10,3 +10,23 @@ export * from './functions.any';
10 10
  */
11 11
 export function _cleanupConfig(config: Object) { // eslint-disable-line no-unused-vars
12 12
 }
13
+
14
+/**
15
+ * Returns the dial out url.
16
+ *
17
+ * @param {Object} state - The state of the app.
18
+ * @returns {string}
19
+ */
20
+export function getDialOutStatusUrl(state: Object): string {
21
+    return state['features/base/config'].guestDialOutStatusUrl;
22
+}
23
+
24
+/**
25
+ * Returns the dial out status url.
26
+ *
27
+ * @param {Object} state - The state of the app.
28
+ * @returns {string}
29
+ */
30
+export function getDialOutUrl(state: Object): string {
31
+    return state['features/base/config'].guestDialOutUrl;
32
+}

+ 5
- 2
react/features/base/util/openURLInBrowser.web.js View File

@@ -4,8 +4,11 @@
4 4
  * Opens URL in the browser.
5 5
  *
6 6
  * @param {string} url - The URL to be opened.
7
+ * @param {boolean} openInNewTab - If the link should be opened in a new tab.
7 8
  * @returns {void}
8 9
  */
9
-export function openURLInBrowser(url: string) {
10
-    window.open(url, '', 'noopener');
10
+export function openURLInBrowser(url: string, openInNewTab?: boolean) {
11
+    const target = openInNewTab ? '_blank' : '';
12
+
13
+    window.open(url, target, 'noopener');
11 14
 }

+ 2
- 1
react/features/invite/actions.any.js View File

@@ -165,9 +165,10 @@ export function updateDialInNumbers() {
165 165
         const state = getState();
166 166
         const { dialInConfCodeUrl, dialInNumbersUrl, hosts }
167 167
             = state['features/base/config'];
168
+        const { numbersFetched } = state['features/invite'];
168 169
         const mucURL = hosts && hosts.muc;
169 170
 
170
-        if (!dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
171
+        if (numbersFetched || !dialInConfCodeUrl || !dialInNumbersUrl || !mucURL) {
171 172
             // URLs for fetching dial in numbers not defined
172 173
             return;
173 174
         }

+ 67
- 0
react/features/invite/functions.js View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 import { i18next } from '../base/i18n';
4 4
 import { isLocalParticipantModerator } from '../base/participants';
5
+import { toState } from '../base/redux';
5 6
 import { doGetJSON, parseURIString } from '../base/util';
6 7
 
7 8
 import logger from './logger';
@@ -616,3 +617,69 @@ export function _decodeRoomURI(url: string) {
616 617
 
617 618
     return roomUrl;
618 619
 }
620
+
621
+/**
622
+ * Returns the stored conference id.
623
+ *
624
+ * @param {Object | Function} stateful - The Object or Function that can be
625
+ * resolved to a Redux state object with the toState function.
626
+ * @returns {string}
627
+ */
628
+export function getConferenceId(stateful: Object | Function) {
629
+    return toState(stateful)['features/invite'].conferenceID;
630
+}
631
+
632
+/**
633
+ * Returns the default dial in number from the store.
634
+ *
635
+ * @param {Object | Function} stateful - The Object or Function that can be
636
+ * resolved to a Redux state object with the toState function.
637
+ * @returns {string | null}
638
+ */
639
+export function getDefaultDialInNumber(stateful: Object | Function) {
640
+    return _getDefaultPhoneNumber(toState(stateful)['features/invite'].numbers);
641
+}
642
+
643
+/**
644
+ * Executes the dial out request.
645
+ *
646
+ * @param {string} url - The url for dialing out.
647
+ * @param {Object} body - The body of the request.
648
+ * @param {string} reqId - The unique request id.
649
+ * @returns {Object}
650
+ */
651
+export async function executeDialOutRequest(url: string, body: Object, reqId: string) {
652
+    const res = await fetch(url, {
653
+        method: 'POST',
654
+        headers: {
655
+            'Content-Type': 'application/json',
656
+            'request-id': reqId
657
+        },
658
+        body: JSON.stringify(body)
659
+    });
660
+
661
+    const json = await res.json();
662
+
663
+    return res.ok ? json : Promise.reject(json);
664
+}
665
+
666
+/**
667
+ * Executes the dial out status request.
668
+ *
669
+ * @param {string} url - The url for dialing out.
670
+ * @param {string} reqId - The unique request id used on the dial out request.
671
+ * @returns {Object}
672
+ */
673
+export async function executeDialOutStatusRequest(url: string, reqId: string) {
674
+    const res = await fetch(url, {
675
+        method: 'GET',
676
+        headers: {
677
+            'Content-Type': 'application/json',
678
+            'request-id': reqId
679
+        }
680
+    });
681
+
682
+    const json = await res.json();
683
+
684
+    return res.ok ? json : Promise.reject(json);
685
+}

+ 5
- 2
react/features/invite/reducer.js View File

@@ -20,6 +20,7 @@ const DEFAULT_STATE = {
20 20
      */
21 21
     calleeInfoVisible: false,
22 22
     numbersEnabled: true,
23
+    numbersFetched: false,
23 24
     pendingInviteRequests: []
24 25
 };
25 26
 
@@ -59,7 +60,8 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
59 60
                 ...state,
60 61
                 conferenceID: action.conferenceID,
61 62
                 numbers: action.dialInNumbers,
62
-                numbersEnabled: true
63
+                numbersEnabled: true,
64
+                numbersFetched: true
63 65
             };
64 66
         }
65 67
 
@@ -72,7 +74,8 @@ ReducerRegistry.register('features/invite', (state = DEFAULT_STATE, action) => {
72 74
             ...state,
73 75
             conferenceID: action.conferenceID,
74 76
             numbers: action.dialInNumbers,
75
-            numbersEnabled
77
+            numbersEnabled,
78
+            numbersFetched: true
76 79
         };
77 80
     }
78 81
     }

+ 15
- 0
react/features/prejoin/actionTypes.js View File

@@ -29,6 +29,21 @@ export const SET_DEVICE_STATUS = 'SET_DEVICE_STATUS';
29 29
  */
30 30
 export const SET_SKIP_PREJOIN = 'SET_SKIP_PREJOIN';
31 31
 
32
+/**
33
+ * Action type to set the country to dial out to.
34
+ */
35
+export const SET_DIALOUT_COUNTRY = 'SET_DIALOUT_COUNTRY';
36
+
37
+/**
38
+ * Action type to set the dial out number.
39
+ */
40
+export const SET_DIALOUT_NUMBER = 'SET_DIALOUT_NUMBER';
41
+
42
+/**
43
+ * Action type to set the dial out status while dialing.
44
+ */
45
+export const SET_DIALOUT_STATUS = 'SET_DIALOUT_STATUS';
46
+
32 47
 /**
33 48
  * Action type to set the visiblity of the 'JoinByPhone' dialog.
34 49
  */

+ 220
- 1
react/features/prejoin/actions.js View File

@@ -1,11 +1,17 @@
1 1
 // @flow
2 2
 
3
+import uuid from 'uuid';
4
+
5
+import { getRoomName } from '../base/conference';
3 6
 import {
4 7
     ADD_PREJOIN_AUDIO_TRACK,
5 8
     ADD_PREJOIN_CONTENT_SHARING_TRACK,
6 9
     ADD_PREJOIN_VIDEO_TRACK,
7 10
     PREJOIN_START_CONFERENCE,
8 11
     SET_DEVICE_STATUS,
12
+    SET_DIALOUT_COUNTRY,
13
+    SET_DIALOUT_NUMBER,
14
+    SET_DIALOUT_STATUS,
9 15
     SET_SKIP_PREJOIN,
10 16
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
11 17
     SET_PREJOIN_AUDIO_DISABLED,
@@ -15,9 +21,44 @@ import {
15 21
     SET_PREJOIN_VIDEO_DISABLED,
16 22
     SET_PREJOIN_VIDEO_MUTED
17 23
 } from './actionTypes';
24
+import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
18 25
 import { createLocalTrack } from '../base/lib-jitsi-meet';
19
-import { getAudioTrack, getVideoTrack } from './functions';
26
+import { openURLInBrowser } from '../base/util';
27
+import { executeDialOutRequest, executeDialOutStatusRequest, getDialInfoPageURL } from '../invite/functions';
20 28
 import logger from './logger';
29
+import { showErrorNotification } from '../notifications';
30
+
31
+import {
32
+    getFullDialOutNumber,
33
+    getAudioTrack,
34
+    getDialOutConferenceUrl,
35
+    getDialOutCountry,
36
+    getVideoTrack,
37
+    isJoinByPhoneDialogVisible
38
+} from './functions';
39
+
40
+const dialOutStatusToKeyMap = {
41
+    INITIATED: 'presenceStatus.calling',
42
+    RINGING: 'presenceStatus.ringing'
43
+};
44
+
45
+const DIAL_OUT_STATUS = {
46
+    INITIATED: 'INITIATED',
47
+    RINGING: 'RINGING',
48
+    CONNECTED: 'CONNECTED',
49
+    DISCONNECTED: 'DISCONNECTED',
50
+    FAILED: 'FAILED'
51
+};
52
+
53
+/**
54
+ * The time interval used between requests while polling for dial out status.
55
+ */
56
+const STATUS_REQ_FREQUENCY = 2000;
57
+
58
+/**
59
+ * The maximum number of retries while polling for dial out status.
60
+ */
61
+const STATUS_REQ_CAP = 45;
21 62
 
22 63
 /**
23 64
  * Action used to add an audio track to the store.
@@ -58,6 +99,129 @@ export function addPrejoinContentSharingTrack(value: Object) {
58 99
     };
59 100
 }
60 101
 
102
+/**
103
+ * Polls for status change after dial out.
104
+ * Changes dialog message based on response, closes the dialog if there is an error,
105
+ * joins the meeting when CONNECTED.
106
+ *
107
+ * @param {string} reqId - The request id used to correlate the dial out request with this one.
108
+ * @param {Function} onSuccess - Success handler.
109
+ * @param {Function} onFail - Fail handler.
110
+ * @param {number} count - The number of retried calls. When it hits STATUS_REQ_CAP it should no longer make requests.
111
+ * @returns {Function}
112
+ */
113
+function pollForStatus(
114
+        reqId: string,
115
+        onSuccess: Function,
116
+        onFail: Function,
117
+        count = 0) {
118
+    return async function(dispatch: Function, getState: Function) {
119
+        const state = getState();
120
+
121
+        try {
122
+            if (!isJoinByPhoneDialogVisible(state)) {
123
+                return;
124
+            }
125
+
126
+            const res = await executeDialOutStatusRequest(getDialOutStatusUrl(state), reqId);
127
+
128
+            switch (res) {
129
+            case DIAL_OUT_STATUS.INITIATED:
130
+            case DIAL_OUT_STATUS.RINGING: {
131
+                dispatch(setDialOutStatus(dialOutStatusToKeyMap[res]));
132
+
133
+                if (count < STATUS_REQ_CAP) {
134
+                    return setTimeout(() => {
135
+                        dispatch(pollForStatus(reqId, onSuccess, onFail, count + 1));
136
+                    }, STATUS_REQ_FREQUENCY);
137
+                }
138
+
139
+                return onFail();
140
+            }
141
+
142
+            case DIAL_OUT_STATUS.CONNECTED: {
143
+                return onSuccess();
144
+            }
145
+
146
+            case DIAL_OUT_STATUS.DISCONNECTED: {
147
+                dispatch(showErrorNotification({
148
+                    titleKey: 'prejoin.errorDialOutDisconnected'
149
+                }));
150
+
151
+                return onFail();
152
+            }
153
+
154
+            case DIAL_OUT_STATUS.FAILED: {
155
+                dispatch(showErrorNotification({
156
+                    titleKey: 'prejoin.errorDialOutFailed'
157
+                }));
158
+
159
+                return onFail();
160
+            }
161
+            }
162
+        } catch (err) {
163
+            dispatch(showErrorNotification({
164
+                titleKey: 'prejoin.errorDialOutStatus'
165
+            }));
166
+            logger.error('Error getting dial out status', err);
167
+            onFail();
168
+        }
169
+    };
170
+}
171
+
172
+
173
+/**
174
+ * Action used for joining the meeting with phone audio.
175
+ * A dial out connection is tried and a polling mechanism is used for getting the status.
176
+ * If the connection succeeds the `onSuccess` callback is executed.
177
+ * If the phone connection fails or the number is invalid the `onFail` callback is executed.
178
+ *
179
+ * @param {Function} onSuccess - Success handler.
180
+ * @param {Function} onFail - Fail handler.
181
+ * @returns {Function}
182
+ */
183
+export function dialOut(onSuccess: Function, onFail: Function) {
184
+    return async function(dispatch: Function, getState: Function) {
185
+        const state = getState();
186
+        const reqId = uuid.v4();
187
+        const url = getDialOutUrl(state);
188
+        const conferenceUrl = getDialOutConferenceUrl(state);
189
+        const phoneNumber = getFullDialOutNumber(state);
190
+        const countryCode = getDialOutCountry(state).code.toUpperCase();
191
+
192
+        const body = {
193
+            conferenceUrl,
194
+            countryCode,
195
+            name: phoneNumber,
196
+            phoneNumber
197
+        };
198
+
199
+        try {
200
+            await executeDialOutRequest(url, body, reqId);
201
+
202
+            dispatch(pollForStatus(reqId, onSuccess, onFail));
203
+        } catch (err) {
204
+            const notification = {
205
+                titleKey: 'prejoin.errorDialOut',
206
+                titleArguments: undefined
207
+            };
208
+
209
+            if (err.status) {
210
+                if (err.messageKey === 'validation.failed') {
211
+                    notification.titleKey = 'prejoin.errorValidation';
212
+                } else {
213
+                    notification.titleKey = 'prejoin.errorStatusCode';
214
+                    notification.titleArguments = { status: err.status };
215
+                }
216
+            }
217
+
218
+            dispatch(showErrorNotification(notification));
219
+            logger.error('Error dialing out', err);
220
+            onFail();
221
+        }
222
+    };
223
+}
224
+
61 225
 /**
62 226
  * Adds all the newly created tracks to store on init.
63 227
  *
@@ -121,6 +285,22 @@ export function joinConferenceWithoutAudio() {
121 285
     };
122 286
 }
123 287
 
288
+/**
289
+ * Opens an external page with all the dial in numbers.
290
+ *
291
+ * @returns {Function}
292
+ */
293
+export function openDialInPage() {
294
+    return function(dispatch: Function, getState: Function) {
295
+        const state = getState();
296
+        const locationURL = state['features/base/connection'].locationURL;
297
+        const roomName = getRoomName(state);
298
+        const dialInPage = getDialInfoPageURL(roomName, locationURL);
299
+
300
+        openURLInBrowser(dialInPage, true);
301
+    };
302
+}
303
+
124 304
 /**
125 305
  * Replaces the existing audio track with a new one.
126 306
  *
@@ -273,6 +453,45 @@ export function setDeviceStatusWarning(deviceStatusText: string) {
273 453
     };
274 454
 }
275 455
 
456
+/**
457
+ * Action used to set the dial out status.
458
+ *
459
+ * @param {string} value - The status.
460
+ * @returns {Object}
461
+ */
462
+function setDialOutStatus(value: string) {
463
+    return {
464
+        type: SET_DIALOUT_STATUS,
465
+        value
466
+    };
467
+}
468
+
469
+/**
470
+ * Action used to set the dial out country.
471
+ *
472
+ * @param {{ name: string, dialCode: string, code: string }} value - The country.
473
+ * @returns {Object}
474
+ */
475
+export function setDialOutCountry(value: Object) {
476
+    return {
477
+        type: SET_DIALOUT_COUNTRY,
478
+        value
479
+    };
480
+}
481
+
482
+/**
483
+ * Action used to set the dial out number.
484
+ *
485
+ * @param {string} value - The dial out number.
486
+ * @returns {Object}
487
+ */
488
+export function setDialOutNumber(value: string) {
489
+    return {
490
+        type: SET_DIALOUT_NUMBER,
491
+        value
492
+    };
493
+}
494
+
276 495
 /**
277 496
  * Sets the visibility of the prejoin page for future uses.
278 497
  *

+ 48
- 0
react/features/prejoin/components/Label.js View File

@@ -0,0 +1,48 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+type Props = {
6
+
7
+    /**
8
+     * The text for the Label.
9
+     */
10
+    children: React$Node,
11
+
12
+    /**
13
+     * The CSS class of the label.
14
+     */
15
+    className?: string,
16
+
17
+    /**
18
+     * The (round) number prefix for the Label.
19
+     */
20
+    number?: string | number,
21
+
22
+    /**
23
+     * The click handler.
24
+     */
25
+    onClick?: Function,
26
+};
27
+
28
+/**
29
+ *  Label for the dialogs.
30
+ *
31
+ *  @returns {ReactElement}
32
+ */
33
+function Label({ children, className, number, onClick }: Props) {
34
+    const containerClass = className
35
+        ? `prejoin-dialog-label ${className}`
36
+        : 'prejoin-dialog-label';
37
+
38
+    return (
39
+        <div
40
+            className = { containerClass }
41
+            onClick = { onClick }>
42
+            {number && <div className = 'prejoin-dialog-label-num'>{number}</div>}
43
+            <span>{children}</span>
44
+        </div>
45
+    );
46
+}
47
+
48
+export default Label;

+ 24
- 1
react/features/prejoin/components/Prejoin.js View File

@@ -25,6 +25,8 @@ import DeviceStatus from './preview/DeviceStatus';
25 25
 import ParticipantName from './preview/ParticipantName';
26 26
 import Preview from './preview/Preview';
27 27
 import { VideoSettingsButton, AudioSettingsButton } from '../../toolbox';
28
+import JoinByPhoneDialog from './dialogs/JoinByPhoneDialog';
29
+
28 30
 
29 31
 type Props = {
30 32
 
@@ -112,6 +114,8 @@ class Prejoin extends Component<Props, State> {
112 114
         this.state = {
113 115
             showJoinByPhoneButtons: false
114 116
         };
117
+
118
+        this._closeDialog = this._closeDialog.bind(this);
115 119
         this._showDialog = this._showDialog.bind(this);
116 120
         this._onCheckboxChange = this._onCheckboxChange.bind(this);
117 121
         this._onDropdownClose = this._onDropdownClose.bind(this);
@@ -174,6 +178,17 @@ class Prejoin extends Component<Props, State> {
174 178
         });
175 179
     }
176 180
 
181
+    _closeDialog: () => void;
182
+
183
+    /**
184
+     * Closes the join by phone dialog.
185
+     *
186
+     * @returns {undefined}
187
+     */
188
+    _closeDialog() {
189
+        this.props.setJoinByPhoneDialogVisiblity(false);
190
+    }
191
+
177 192
     _showDialog: () => void;
178 193
 
179 194
     /**
@@ -183,6 +198,7 @@ class Prejoin extends Component<Props, State> {
183 198
      */
184 199
     _showDialog() {
185 200
         this.props.setJoinByPhoneDialogVisiblity(true);
201
+        this._onDropdownClose();
186 202
     }
187 203
 
188 204
     /**
@@ -199,9 +215,11 @@ class Prejoin extends Component<Props, State> {
199 215
             joinConferenceWithoutAudio,
200 216
             name,
201 217
             hasJoinByPhoneButtons,
218
+            showDialog,
202 219
             t
203 220
         } = this.props;
204
-        const { _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
221
+
222
+        const { _closeDialog, _onCheckboxChange, _onDropdownClose, _onOptionsClick, _setName, _showDialog } = this;
205 223
         const { showJoinByPhoneButtons } = this.state;
206 224
 
207 225
         return (
@@ -271,6 +289,11 @@ class Prejoin extends Component<Props, State> {
271 289
                 </div>
272 290
 
273 291
                 { deviceStatusVisible && <DeviceStatus /> }
292
+                { showDialog && (
293
+                    <JoinByPhoneDialog
294
+                        joinConferenceWithoutAudio = { joinConferenceWithoutAudio }
295
+                        onClose = { _closeDialog } />
296
+                )}
274 297
             </div>
275 298
         );
276 299
     }

+ 33
- 0
react/features/prejoin/components/country-picker/CountryDropdown.js View File

@@ -0,0 +1,33 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { countries } from '../../utils';
5
+import CountryRow from './CountryRow';
6
+
7
+type Props = {
8
+
9
+    /**
10
+     * Click handler for a single entry.
11
+     */
12
+    onEntryClick: Function,
13
+};
14
+
15
+/**
16
+ * This component displays the dropdown for the country picker.
17
+ *
18
+ * @returns {ReactElement}
19
+ */
20
+function CountryDropdown({ onEntryClick }: Props) {
21
+    return (
22
+        <div className = 'cpick-dropdown'>
23
+            {countries.map(country => (
24
+                <CountryRow
25
+                    country = { country }
26
+                    key = { `${country.code}` }
27
+                    onEntryClick = { onEntryClick } />
28
+            ))}
29
+        </div>
30
+    );
31
+}
32
+
33
+export default CountryDropdown;

+ 250
- 0
react/features/prejoin/components/country-picker/CountryPicker.js View File

@@ -0,0 +1,250 @@
1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+import InlineDialog from '@atlaskit/inline-dialog';
5
+
6
+import { connect } from '../../../base/redux';
7
+import { setDialOutCountry, setDialOutNumber } from '../../actions';
8
+import { getDialOutCountry, getDialOutNumber } from '../../functions';
9
+import { getCountryFromDialCodeText } from '../../utils';
10
+
11
+import CountrySelector from './CountrySelector';
12
+import CountryDropDown from './CountryDropdown';
13
+
14
+const PREFIX_REG = /^(00)|\+/;
15
+
16
+type Props = {
17
+
18
+    /**
19
+     * The country to dial out to.
20
+     */
21
+    dialOutCountry: { name: string, dialCode: string, code: string },
22
+
23
+    /**
24
+     * The number to dial out to.
25
+     */
26
+    dialOutNumber: string,
27
+
28
+    /**
29
+     * Handler used when user presses 'Enter'.
30
+     */
31
+    onSubmit: Function,
32
+
33
+    /**
34
+     * Sets the dial out number.
35
+     */
36
+    setDialOutNumber: Function,
37
+
38
+    /**
39
+     * Sets the dial out country.
40
+     */
41
+    setDialOutCountry: Function,
42
+};
43
+
44
+type State = {
45
+
46
+    /**
47
+     * If the country picker is open or not.
48
+     */
49
+    isOpen: boolean,
50
+
51
+    /**
52
+     * The value of the input.
53
+     */
54
+    value: string
55
+}
56
+
57
+/**
58
+ * This component displays a country picker with an input for the phone number.
59
+ */
60
+class CountryPicker extends PureComponent<Props, State> {
61
+    /**
62
+     * A React ref to the HTML element containing the {@code input} instance.
63
+     */
64
+    inputRef: Object;
65
+
66
+    /**
67
+     * Initializes a new {@code CountryPicker} instance.
68
+     *
69
+     * @inheritdoc
70
+     */
71
+    constructor(props) {
72
+        super(props);
73
+
74
+        this.state = {
75
+            isOpen: false,
76
+            value: ''
77
+        };
78
+        this.inputRef = React.createRef();
79
+        this._onChange = this._onChange.bind(this);
80
+        this._onDropdownClose = this._onDropdownClose.bind(this);
81
+        this._onCountrySelectorClick = this._onCountrySelectorClick.bind(this);
82
+        this._onEntryClick = this._onEntryClick.bind(this);
83
+        this._onKeyPress = this._onKeyPress.bind(this);
84
+    }
85
+
86
+
87
+    /**
88
+     * Implements React's {@link Component#componentDidUnmount()}.
89
+     *
90
+     * @inheritdoc
91
+     */
92
+    componentDidMount() {
93
+        this.inputRef.current.focus();
94
+    }
95
+
96
+    /**
97
+     * Implements React's {@link Component#render()}.
98
+     *
99
+     * @inheritdoc
100
+     * @returns {ReactElement}
101
+     */
102
+    render() {
103
+        const { dialOutCountry, dialOutNumber } = this.props;
104
+        const { isOpen } = this.state;
105
+        const {
106
+            inputRef,
107
+            _onChange,
108
+            _onCountrySelectorClick,
109
+            _onDropdownClose,
110
+            _onKeyPress,
111
+            _onEntryClick
112
+        } = this;
113
+
114
+        return (
115
+            <div className = 'cpick-container'>
116
+                <InlineDialog
117
+                    content = { <CountryDropDown onEntryClick = { _onEntryClick } /> }
118
+                    isOpen = { isOpen }
119
+                    onClose = { _onDropdownClose }>
120
+                    <div className = 'cpick'>
121
+                        <CountrySelector
122
+                            country = { dialOutCountry }
123
+                            onClick = { _onCountrySelectorClick } />
124
+                        <input
125
+                            className = 'cpick-input'
126
+                            onChange = { _onChange }
127
+                            onKeyPress = { _onKeyPress }
128
+                            ref = { inputRef }
129
+                            value = { dialOutNumber } />
130
+                    </div>
131
+                </InlineDialog>
132
+            </div>
133
+        );
134
+    }
135
+
136
+    _onChange: (Object) => void;
137
+
138
+    /**
139
+     * Handles the input text change.
140
+     * Automatically updates the country from the 'CountrySelector' if a
141
+     * phone number prefix is entered (00 or +).
142
+     *
143
+     * @param {Object} e - The synthetic event.
144
+     * @returns {void}
145
+     */
146
+    _onChange({ target: { value } }) {
147
+        if (PREFIX_REG.test(value)) {
148
+            const textWithDialCode = value.replace(PREFIX_REG, '');
149
+
150
+            if (textWithDialCode.length >= 4) {
151
+                const country = getCountryFromDialCodeText(textWithDialCode);
152
+
153
+                if (country) {
154
+                    const rest = textWithDialCode.replace(country.dialCode, '');
155
+
156
+                    this.props.setDialOutCountry(country);
157
+                    this.props.setDialOutNumber(rest);
158
+
159
+                    return;
160
+                }
161
+            }
162
+        }
163
+
164
+        this.props.setDialOutNumber(value);
165
+    }
166
+
167
+    _onCountrySelectorClick: (Object) => void;
168
+
169
+    /**
170
+     * Click handler for country selector.
171
+     *
172
+     * @param {Object} e - The synthetic event.
173
+     * @returns {void}
174
+     */
175
+    _onCountrySelectorClick(e) {
176
+        e.stopPropagation();
177
+
178
+        this.setState({
179
+            isOpen: !this.setState.isOpen
180
+        });
181
+    }
182
+
183
+    _onDropdownClose: () => void;
184
+
185
+    /**
186
+     * Closes the dropdown.
187
+     *
188
+     * @returns {void}
189
+     */
190
+    _onDropdownClose() {
191
+        this.setState({
192
+            isOpen: false
193
+        });
194
+    }
195
+
196
+    _onEntryClick: (Object) => void;
197
+
198
+    /**
199
+     * Click handler for a single entry from the dropdown.
200
+     *
201
+     * @param {Object} country - The country used for dialing out.
202
+     * @returns {void}
203
+     */
204
+    _onEntryClick(country) {
205
+        this.props.setDialOutCountry(country);
206
+        this._onDropdownClose();
207
+    }
208
+
209
+    _onKeyPress: (Object) => void;
210
+
211
+    /**
212
+     * Handler for key presses.
213
+     *
214
+     * @param {Object} e - The synthetic event.
215
+     * @returns {void}
216
+     */
217
+    _onKeyPress(e) {
218
+        if (e.key === 'Enter') {
219
+            this.props.onSubmit();
220
+        }
221
+    }
222
+}
223
+
224
+/**
225
+ * Maps (parts of) the redux state to the React {@code Component} props.
226
+ *
227
+ * @param {Object} state - The redux state.
228
+ * @returns {Props}
229
+ */
230
+function mapStateToProps(state) {
231
+    return {
232
+        dialOutCountry: getDialOutCountry(state),
233
+        dialOutNumber: getDialOutNumber(state)
234
+    };
235
+}
236
+
237
+/**
238
+ * Maps redux actions to the props of the component.
239
+ *
240
+ * @type {{
241
+ *     setDialOutCountry: Function,
242
+ *     setDialOutNumber: Function
243
+ * }}
244
+ */
245
+const mapDispatchToProps = {
246
+    setDialOutCountry,
247
+    setDialOutNumber
248
+};
249
+
250
+export default connect(mapStateToProps, mapDispatchToProps)(CountryPicker);

+ 68
- 0
react/features/prejoin/components/country-picker/CountryRow.js View File

@@ -0,0 +1,68 @@
1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+type Props = {
6
+
7
+    /**
8
+     * Country of the entry.
9
+     */
10
+    country: { name: string, dialCode: string, code: string },
11
+
12
+    /**
13
+     * Entry click handler.
14
+     */
15
+    onEntryClick: Function,
16
+};
17
+
18
+/**
19
+ * This component displays a row from the country picker dropdown.
20
+ */
21
+class CountryRow extends PureComponent<Props> {
22
+    /**
23
+     * Initializes a new {@code CountryRow} instance.
24
+     *
25
+     * @param {Props} props - The props of the component.
26
+     */
27
+    constructor(props: Props) {
28
+        super(props);
29
+
30
+        this._onClick = this._onClick.bind(this);
31
+    }
32
+
33
+    /**
34
+     * Implements React's {@link Component#render()}.
35
+     *
36
+     * @inheritdoc
37
+     * @returns {ReactElement}
38
+     */
39
+    render() {
40
+        const {
41
+            country: { code, dialCode, name }
42
+        } = this.props;
43
+
44
+        return (
45
+            <div
46
+                className = 'cpick-dropdown-entry'
47
+                onClick = { this._onClick }>
48
+                <div className = { `prejoin-dialog-flag iti-flag ${code}` } />
49
+                <div className = 'cpick-dropdown-entry-text'>
50
+                    {`${name} (+${dialCode})`}
51
+                </div>
52
+            </div>
53
+        );
54
+    }
55
+
56
+    _onClick: () => void;
57
+
58
+    /**
59
+     * Click handler.
60
+     *
61
+     * @returns {void}
62
+     */
63
+    _onClick() {
64
+        this.props.onEntryClick(this.props.country);
65
+    }
66
+}
67
+
68
+export default CountryRow;

+ 39
- 0
react/features/prejoin/components/country-picker/CountrySelector.js View File

@@ -0,0 +1,39 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { Icon, IconArrowDown } from '../../../base/icons';
5
+
6
+type Props = {
7
+
8
+    /**
9
+     * Country object of the entry.
10
+     */
11
+    country: { name: string, dialCode: string, code: string },
12
+
13
+    /**
14
+     * Click handler for the selector.
15
+     */
16
+    onClick: Function,
17
+};
18
+
19
+/**
20
+ * This component displays the country selector with the flag.
21
+ *
22
+ * @returns {ReactElement}
23
+ */
24
+function CountrySelector({ country: { code, dialCode }, onClick }: Props) {
25
+    return (
26
+        <div
27
+            className = 'cpick-selector'
28
+            onClick = { onClick }>
29
+            <div className = { `prejoin-dialog-flag iti-flag ${code}` } />
30
+            <span>{`+${dialCode}`}</span>
31
+            <Icon
32
+                className = 'cpick-icon'
33
+                size = { 16 }
34
+                src = { IconArrowDown } />
35
+        </div>
36
+    );
37
+}
38
+
39
+export default CountrySelector;

+ 64
- 0
react/features/prejoin/components/dialogs/CallingDialog.js View File

@@ -0,0 +1,64 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { Avatar } from '../../../base/avatar';
5
+import { translate } from '../../../base/i18n';
6
+import { Icon, IconClose } from '../../../base/icons';
7
+import Label from '../Label';
8
+
9
+type Props = {
10
+
11
+    /**
12
+     * The phone number that is being called.
13
+     */
14
+    number: string,
15
+
16
+    /**
17
+     * Closes the dialog.
18
+     */
19
+    onClose: Function,
20
+
21
+    /**
22
+     * Handler used on hangup click.
23
+     */
24
+    onHangup: Function,
25
+
26
+    /**
27
+     * The status of the call.
28
+     */
29
+    status: string,
30
+
31
+    /**
32
+     * Used for translation.
33
+     */
34
+    t: Function,
35
+};
36
+
37
+/**
38
+ * Dialog displayed when the user gets called by the meeting.
39
+ *
40
+ * @param {Props} props - The props of the component.
41
+ * @returns {ReactElement}
42
+ */
43
+function CallingDialog(props: Props) {
44
+    const { number, onClose, status, t } = props;
45
+
46
+    return (
47
+        <div className = 'prejoin-dialog-calling'>
48
+            <div className = 'prejoin-dialog-calling-header'>
49
+                <Icon
50
+                    className = 'prejoin-dialog-icon'
51
+                    onClick = { onClose }
52
+                    size = { 24 }
53
+                    src = { IconClose } />
54
+            </div>
55
+            <Label className = 'prejoin-dialog-calling-label'>
56
+                {t(status)}
57
+            </Label>
58
+            <Avatar size = { 72 } />
59
+            <div className = 'prejoin-dialog-calling-number'>{number}</div>
60
+        </div>
61
+    );
62
+}
63
+
64
+export default translate(CallingDialog);

+ 121
- 0
react/features/prejoin/components/dialogs/DialInDialog.js View File

@@ -0,0 +1,121 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+import { translate } from '../../../base/i18n';
5
+import { Icon, IconArrowLeft } from '../../../base/icons';
6
+import { getCountryCodeFromPhone } from '../../utils';
7
+import ActionButton from '../buttons/ActionButton';
8
+import Label from '../Label';
9
+
10
+type Props = {
11
+
12
+    /**
13
+     * The number to call in order to join the conference.
14
+     */
15
+    number: string,
16
+
17
+    /**
18
+     * Handler used when clicking the back button.
19
+     */
20
+    onBack: Function,
21
+
22
+    /**
23
+     * Click handler for the text button.
24
+     */
25
+    onTextButtonClick: Function,
26
+
27
+    /**
28
+     * Click handler for primary button.
29
+     */
30
+    onPrimaryButtonClick: Function,
31
+
32
+    /**
33
+     * Click handler for the small additional text.
34
+     */
35
+    onSmallTextClick: Function,
36
+
37
+    /**
38
+     * The passCode of the conference.
39
+     */
40
+    passCode: string,
41
+
42
+    /**
43
+     * Used for translation.
44
+     */
45
+    t: Function,
46
+};
47
+
48
+/**
49
+ * This component displays the dialog whith all the information
50
+ * to join a meeting by calling it.
51
+ *
52
+ * @param {Props} props - The props of the component.
53
+ * @returns {React$Element}
54
+ */
55
+function DialinDialog(props: Props) {
56
+    const {
57
+        number,
58
+        onBack,
59
+        onPrimaryButtonClick,
60
+        onSmallTextClick,
61
+        onTextButtonClick,
62
+        passCode,
63
+        t
64
+    } = props;
65
+    const flagClassName = `prejoin-dialog-flag iti-flag ${getCountryCodeFromPhone(
66
+        number,
67
+    )}`;
68
+
69
+    return (
70
+        <div className = 'prejoin-dialog-dialin'>
71
+            <div className = 'prejoin-dialog-dialin-header'>
72
+                <Icon
73
+                    className = 'prejoin-dialog-icon prejoin-dialog-dialin-icon'
74
+                    onClick = { onBack }
75
+                    size = { 24 }
76
+                    src = { IconArrowLeft } />
77
+                <div className = 'prejoin-dialog-title'>
78
+                    {t('prejoin.dialInMeeting')}
79
+                </div>
80
+            </div>
81
+            <Label number = { 1 }>{ t('prejoin.dialInPin') }</Label>
82
+
83
+            <div className = 'prejoin-dialog-dialin-num-container'>
84
+                <div className = 'prejoin-dialog-dialin-num'>
85
+                    <div className = { flagClassName } />
86
+                    <span>{number}</span>
87
+                </div>
88
+                <div className = 'prejoin-dialog-dialin-num'>{passCode}</div>
89
+            </div>
90
+            <div>
91
+                <span
92
+                    className = 'prejoin-dialog-dialin-link'
93
+                    onClick = { onSmallTextClick }>
94
+                    {t('prejoin.viewAllNumbers')}
95
+                </span>
96
+            </div>
97
+            <div className = 'prejoin-dialog-delimiter' />
98
+            <Label
99
+                className = 'prejoin-dialog-dialin-spaced-label'
100
+                number = { 2 }>
101
+                {t('prejoin.connectedWithAudioQ')}
102
+            </Label>
103
+            <div className = 'prejoin-dialog-dialin-btns'>
104
+                <ActionButton
105
+                    className = 'prejoin-dialog-btn'
106
+                    onClick = { onPrimaryButtonClick }
107
+                    type = 'primary'>
108
+                    {t('prejoin.joinMeeting')}
109
+                </ActionButton>
110
+                <ActionButton
111
+                    className = 'prejoin-dialog-btn'
112
+                    onClick = { onTextButtonClick }
113
+                    type = 'text'>
114
+                    {t('dialog.Cancel')}
115
+                </ActionButton>
116
+            </div>
117
+        </div>
118
+    );
119
+}
120
+
121
+export default translate(DialinDialog);

+ 86
- 0
react/features/prejoin/components/dialogs/DialOutDialog.js View File

@@ -0,0 +1,86 @@
1
+// @flow
2
+
3
+import React from 'react';
4
+
5
+import { translate } from '../../../base/i18n';
6
+import { Icon, IconClose } from '../../../base/icons';
7
+import ActionButton from '../buttons/ActionButton';
8
+import CountryPicker from '../country-picker/CountryPicker';
9
+import Label from '../Label';
10
+
11
+type Props = {
12
+
13
+    /**
14
+     * Closes a dialog.
15
+     */
16
+    onClose: Function,
17
+
18
+    /**
19
+     * Submit handler.
20
+     */
21
+    onSubmit: Function,
22
+
23
+    /**
24
+     * Handler for text button.
25
+     */
26
+    onTextButtonClick: Function,
27
+
28
+    /**
29
+     * Used for translation.
30
+     */
31
+    t: Function,
32
+};
33
+
34
+/**
35
+ * This component displays the dialog from wich the user can enter the
36
+ * phone number in order to be called by the meeting.
37
+ *
38
+ * @param {Props} props - The props of the component.
39
+ * @returns {React$Element}
40
+ */
41
+function DialOutDialog(props: Props) {
42
+    const { onClose, onTextButtonClick, onSubmit, t } = props;
43
+
44
+    return (
45
+        <div className = 'prejoin-dialog-callout'>
46
+            <div className = 'prejoin-dialog-callout-header'>
47
+                <div className = 'prejoin-dialog-title'>
48
+                    {t('prejoin.startWithPhone')}
49
+                </div>
50
+                <Icon
51
+                    className = 'prejoin-dialog-icon'
52
+                    onClick = { onClose }
53
+                    size = { 24 }
54
+                    src = { IconClose } />
55
+            </div>
56
+            <Label>{t('prejoin.callMeAtNumber')}</Label>
57
+            <div className = 'prejoin-dialog-callout-picker'>
58
+                <CountryPicker onSubmit = { onSubmit } />
59
+            </div>
60
+            <ActionButton
61
+                className = 'prejoin-dialog-btn'
62
+                onClick = { onSubmit }
63
+                type = 'primary'>
64
+                {t('prejoin.callMe')}
65
+            </ActionButton>
66
+            <div className = 'prejoin-dialog-delimiter-container'>
67
+                <div className = 'prejoin-dialog-delimiter' />
68
+                <div className = 'prejoin-dialog-delimiter-txt-container'>
69
+                    <span className = 'prejoin-dialog-delimiter-txt'>
70
+                        {t('prejoin.or')}
71
+                    </span>
72
+                </div>
73
+            </div>
74
+            <div className = 'prejoin-dialog-dialin-container'>
75
+                <ActionButton
76
+                    className = 'prejoin-dialog-btn'
77
+                    onClick = { onTextButtonClick }
78
+                    type = 'text'>
79
+                    {t('prejoin.iWantToDialIn')}
80
+                </ActionButton>
81
+            </div>
82
+        </div>
83
+    );
84
+}
85
+
86
+export default translate(DialOutDialog);

+ 248
- 0
react/features/prejoin/components/dialogs/JoinByPhoneDialog.js View File

@@ -0,0 +1,248 @@
1
+// @flow
2
+
3
+import React, { PureComponent } from 'react';
4
+
5
+import { connect } from '../../../base/redux';
6
+import {
7
+    getConferenceId,
8
+    getDefaultDialInNumber,
9
+    updateDialInNumbers
10
+} from '../../../invite';
11
+import {
12
+    dialOut as dialOutAction,
13
+    joinConferenceWithoutAudio as joinConferenceWithoutAudioAction,
14
+    openDialInPage as openDialInPageAction
15
+} from '../../actions';
16
+import { getDialOutStatus, getFullDialOutNumber } from '../../functions';
17
+
18
+import CallingDialog from './CallingDialog';
19
+import DialInDialog from './DialInDialog';
20
+import DialOutDialog from './DialOutDialog';
21
+
22
+type Props = {
23
+
24
+    /**
25
+     * The number to call in order to join the conference.
26
+     */
27
+    dialInNumber: string,
28
+
29
+    /**
30
+     * The status of the call when the meeting calss the user.
31
+     */
32
+    dialOutStatus: string,
33
+
34
+    /**
35
+     * The action by which the meeting calls the user.
36
+     */
37
+    dialOut: Function,
38
+
39
+    /**
40
+     * The number the conference should call.
41
+     */
42
+    dialOutNumber: string,
43
+
44
+    /**
45
+     * Fetches conference dial in numbers & conference id
46
+     */
47
+    fetchConferenceDetails: Function,
48
+
49
+    /**
50
+     * Joins the conference without audio.
51
+     */
52
+    joinConferenceWithoutAudio: Function,
53
+
54
+    /**
55
+     * Closes the dialog.
56
+     */
57
+    onClose: Function,
58
+
59
+    /**
60
+     * Opens a web page with all the dial in numbers.
61
+     */
62
+    openDialInPage: Function,
63
+
64
+    /**
65
+     * The passCode of the conference used when joining a meeting by phone.
66
+     */
67
+    passCode: string,
68
+};
69
+
70
+type State = {
71
+
72
+    /**
73
+     * The dialout call is ongoing, 'CallingDialog' is shown;
74
+     */
75
+    isCalling: boolean,
76
+
77
+    /**
78
+     * If should show 'DialInDialog'.
79
+     */
80
+    showDialIn: boolean,
81
+
82
+    /**
83
+     * If should show 'DialOutDialog'.
84
+     */
85
+    showDialOut: boolean
86
+}
87
+
88
+/**
89
+ * This is the dialog shown when a user wants to join with phone audio.
90
+ */
91
+class JoinByPhoneDialog extends PureComponent<Props, State> {
92
+    /**
93
+     * Initializes a new {@code JoinByPhoneDialog} instance.
94
+     *
95
+     * @param {Props} props - The props of the component.
96
+     * @inheritdoc
97
+     */
98
+    constructor(props) {
99
+        super(props);
100
+
101
+        this.state = {
102
+            isCalling: false,
103
+            showDialOut: true,
104
+            showDialIn: false
105
+        };
106
+
107
+        this._dialOut = this._dialOut.bind(this);
108
+        this._showDialInDialog = this._showDialInDialog.bind(this);
109
+        this._showDialOutDialog = this._showDialOutDialog.bind(this);
110
+    }
111
+
112
+    _dialOut: () => void;
113
+
114
+    /**
115
+     * Meeting calls the user & shows the 'CallingDialog'.
116
+     *
117
+     * @returns {void}
118
+     */
119
+    _dialOut() {
120
+        const { dialOut, joinConferenceWithoutAudio } = this.props;
121
+
122
+        this.setState({
123
+            isCalling: true,
124
+            showDialOut: false,
125
+            showDialIn: false
126
+        });
127
+        dialOut(joinConferenceWithoutAudio, this._showDialOutDialog);
128
+    }
129
+
130
+    _showDialInDialog: () => void;
131
+
132
+    /**
133
+     * Shows the 'DialInDialog'.
134
+     *
135
+     * @returns {void}
136
+     */
137
+    _showDialInDialog() {
138
+        this.setState({
139
+            isCalling: false,
140
+            showDialOut: false,
141
+            showDialIn: true
142
+        });
143
+    }
144
+
145
+    _showDialOutDialog: () => void;
146
+
147
+    /**
148
+     * Shows the 'DialOutDialog'.
149
+     *
150
+     * @returns {void}
151
+     */
152
+    _showDialOutDialog() {
153
+        this.setState({
154
+            isCalling: false,
155
+            showDialOut: true,
156
+            showDialIn: false
157
+        });
158
+    }
159
+
160
+    /**
161
+     * Implements React's {@link Component#componentDidMount()}.
162
+     *
163
+     * @inheritdoc
164
+     */
165
+    componentDidMount() {
166
+        this.props.fetchConferenceDetails();
167
+    }
168
+
169
+    /**
170
+     * Implements React's {@link Component#render()}.
171
+     *
172
+     * @inheritdoc
173
+     * @returns {ReactElement}
174
+     */
175
+    render() {
176
+        const {
177
+            dialOutStatus,
178
+            dialInNumber,
179
+            dialOutNumber,
180
+            joinConferenceWithoutAudio,
181
+            passCode,
182
+            onClose,
183
+            openDialInPage
184
+        } = this.props;
185
+        const {
186
+            _dialOut,
187
+            _showDialInDialog,
188
+            _showDialOutDialog
189
+        } = this;
190
+        const { isCalling, showDialOut, showDialIn } = this.state;
191
+        const className = isCalling
192
+            ? 'prejoin-dialog prejoin-dialog--small'
193
+            : 'prejoin-dialog';
194
+
195
+        return (
196
+            <div className = 'prejoin-dialog-container'>
197
+                <div className = { className }>
198
+                    {showDialOut && (
199
+                        <DialOutDialog
200
+                            onClose = { onClose }
201
+                            onSubmit = { _dialOut }
202
+                            onTextButtonClick = { _showDialInDialog } />
203
+                    )}
204
+                    {showDialIn && (
205
+                        <DialInDialog
206
+                            number = { dialInNumber }
207
+                            onBack = { _showDialOutDialog }
208
+                            onPrimaryButtonClick = { joinConferenceWithoutAudio }
209
+                            onSmallTextClick = { openDialInPage }
210
+                            onTextButtonClick = { onClose }
211
+                            passCode = { passCode } />
212
+                    )}
213
+                    {isCalling && (
214
+                        <CallingDialog
215
+                            number = { dialOutNumber }
216
+                            onClose = { onClose }
217
+                            status = { dialOutStatus } />
218
+                    )}
219
+                </div>
220
+            </div>
221
+        );
222
+    }
223
+}
224
+
225
+/**
226
+ * Maps (parts of) the redux state to the React {@code Component} props.
227
+ *
228
+ * @param {Object} state - The redux state.
229
+ * @returns {Object}
230
+ */
231
+function mapStateToProps(state): Object {
232
+    return {
233
+        dialInNumber: getDefaultDialInNumber(state),
234
+        dialOutNumber: getFullDialOutNumber(state),
235
+        dialOutStatus: getDialOutStatus(state),
236
+        passCode: getConferenceId(state)
237
+    };
238
+}
239
+
240
+const mapDispatchToProps = {
241
+    dialOut: dialOutAction,
242
+    fetchConferenceDetails: updateDialInNumbers,
243
+    joinConferenceWithoutAudio: joinConferenceWithoutAudioAction,
244
+    openDialInPage: openDialInPageAction
245
+};
246
+
247
+
248
+export default connect(mapStateToProps, mapDispatchToProps)(JoinByPhoneDialog);

+ 56
- 1
react/features/prejoin/functions.js View File

@@ -1,5 +1,7 @@
1 1
 // @flow
2 2
 
3
+import { getRoomName } from '../base/conference';
4
+import { getDialOutStatusUrl, getDialOutUrl } from '../base/config/functions';
3 5
 
4 6
 /**
5 7
  * Mutes or unmutes a track.
@@ -27,7 +29,7 @@ function applyMuteOptionsToTrack(track, shouldMute) {
27 29
  * @returns {boolean}
28 30
  */
29 31
 export function areJoinByPhoneButtonsVisible(state: Object): boolean {
30
-    return state['features/prejoin'].buttonsVisible;
32
+    return Boolean(getDialOutUrl(state) && getDialOutStatusUrl(state));
31 33
 }
32 34
 
33 35
 /**
@@ -126,6 +128,59 @@ export function getDeviceStatusType(state: Object): string {
126 128
     return state['features/prejoin']?.deviceStatusType;
127 129
 }
128 130
 
131
+/**
132
+ * Returns the 'conferenceUrl' used for dialing out.
133
+ *
134
+ * @param {Object} state - The state of the app.
135
+ * @returns {string}
136
+ */
137
+export function getDialOutConferenceUrl(state: Object): string {
138
+    return `${getRoomName(state)}@${state['features/base/config'].hosts.muc}`;
139
+}
140
+
141
+/**
142
+ * Selector for getting the dial out country.
143
+ *
144
+ * @param {Object} state - The state of the app.
145
+ * @returns {Object}
146
+ */
147
+export function getDialOutCountry(state: Object): Object {
148
+    return state['features/prejoin'].dialOutCountry;
149
+}
150
+
151
+/**
152
+ * Selector for getting the dial out number (without prefix).
153
+ *
154
+ * @param {Object} state - The state of the app.
155
+ * @returns {string}
156
+ */
157
+export function getDialOutNumber(state: Object): string {
158
+    return state['features/prejoin'].dialOutNumber;
159
+}
160
+
161
+/**
162
+ * Selector for getting the dial out status while calling.
163
+ *
164
+ * @param {Object} state - The state of the app.
165
+ * @returns {string}
166
+ */
167
+export function getDialOutStatus(state: Object): string {
168
+    return state['features/prejoin'].dialOutStatus;
169
+}
170
+
171
+/**
172
+ * Returns the full dial out number (containing country code and +).
173
+ *
174
+ * @param {Object} state - The state of the app.
175
+ * @returns {string}
176
+ */
177
+export function getFullDialOutNumber(state: Object): string {
178
+    const dialOutNumber = getDialOutNumber(state);
179
+    const country = getDialOutCountry(state);
180
+
181
+    return `+${country.dialCode}${dialOutNumber}`;
182
+}
183
+
129 184
 /**
130 185
  * Selector for getting the prejoin video track.
131 186
  *

+ 38
- 5
react/features/prejoin/reducer.js View File

@@ -5,6 +5,9 @@ import {
5 5
     ADD_PREJOIN_CONTENT_SHARING_TRACK,
6 6
     ADD_PREJOIN_VIDEO_TRACK,
7 7
     SET_DEVICE_STATUS,
8
+    SET_DIALOUT_NUMBER,
9
+    SET_DIALOUT_COUNTRY,
10
+    SET_DIALOUT_STATUS,
8 11
     SET_JOIN_BY_PHONE_DIALOG_VISIBLITY,
9 12
     SET_SKIP_PREJOIN,
10 13
     SET_PREJOIN_AUDIO_DISABLED,
@@ -18,17 +21,26 @@ import {
18 21
 const DEFAULT_STATE = {
19 22
     audioDisabled: false,
20 23
     audioMuted: false,
21
-    videoMuted: false,
22
-    videoDisabled: false,
24
+    audioTrack: null,
25
+    contentSharingTrack: null,
26
+    country: '',
23 27
     deviceStatusText: 'prejoin.configuringDevices',
24 28
     deviceStatusType: 'ok',
29
+    dialOutCountry: {
30
+        name: 'United States',
31
+        dialCode: '1',
32
+        code: 'us'
33
+    },
34
+    dialOutNumber: '',
35
+    dialOutStatus: 'prejoin.dialing',
36
+    name: '',
37
+    rawError: '',
25 38
     showPrejoin: true,
26 39
     showJoinByPhoneDialog: false,
27 40
     userSelectedSkipPrejoin: false,
28 41
     videoTrack: null,
29
-    audioTrack: null,
30
-    contentSharingTrack: null,
31
-    rawError: ''
42
+    videoDisabled: false,
43
+    videoMuted: false
32 44
 };
33 45
 
34 46
 /**
@@ -114,6 +126,27 @@ ReducerRegistry.register(
114 126
             };
115 127
         }
116 128
 
129
+        case SET_DIALOUT_NUMBER: {
130
+            return {
131
+                ...state,
132
+                dialOutNumber: action.value
133
+            };
134
+        }
135
+
136
+        case SET_DIALOUT_COUNTRY: {
137
+            return {
138
+                ...state,
139
+                dialOutCountry: action.value
140
+            };
141
+        }
142
+
143
+        case SET_DIALOUT_STATUS: {
144
+            return {
145
+                ...state,
146
+                dialOutStatus: action.value
147
+            };
148
+        }
149
+
117 150
         case SET_JOIN_BY_PHONE_DIALOG_VISIBLITY: {
118 151
             return {
119 152
                 ...state,

+ 799
- 0
react/features/prejoin/utils.js View File

@@ -0,0 +1,799 @@
1
+// @flow
2
+
3
+export const countries = [
4
+    { name: 'Afghanistan',
5
+        dialCode: '93',
6
+        code: 'af' },
7
+    { name: 'Aland Islands',
8
+        dialCode: '358',
9
+        code: 'ax' },
10
+    { name: 'Albania',
11
+        dialCode: '355',
12
+        code: 'al' },
13
+    { name: 'Algeria',
14
+        dialCode: '213',
15
+        code: 'dz' },
16
+    { name: 'AmericanSamoa',
17
+        dialCode: '1684',
18
+        code: 'as' },
19
+    { name: 'Andorra',
20
+        dialCode: '376',
21
+        code: 'ad' },
22
+    { name: 'Angola',
23
+        dialCode: '244',
24
+        code: 'ao' },
25
+    { name: 'Anguilla',
26
+        dialCode: '1264',
27
+        code: 'ai' },
28
+    { name: 'Antarctica',
29
+        dialCode: '672',
30
+        code: 'aq' },
31
+    { name: 'Antigua and Barbuda',
32
+        dialCode: '1268',
33
+        code: 'ag' },
34
+    { name: 'Argentina',
35
+        dialCode: '54',
36
+        code: 'ar' },
37
+    { name: 'Armenia',
38
+        dialCode: '374',
39
+        code: 'am' },
40
+    { name: 'Aruba',
41
+        dialCode: '297',
42
+        code: 'aw' },
43
+    { name: 'Australia',
44
+        dialCode: '61',
45
+        code: 'au' },
46
+    { name: 'Austria',
47
+        dialCode: '43',
48
+        code: 'at' },
49
+    { name: 'Azerbaijan',
50
+        dialCode: '994',
51
+        code: 'az' },
52
+    { name: 'Bahamas',
53
+        dialCode: '1242',
54
+        code: 'bs' },
55
+    { name: 'Bahrain',
56
+        dialCode: '973',
57
+        code: 'bh' },
58
+    { name: 'Bangladesh',
59
+        dialCode: '880',
60
+        code: 'bd' },
61
+    { name: 'Barbados',
62
+        dialCode: '1246',
63
+        code: 'bb' },
64
+    { name: 'Belarus',
65
+        dialCode: '375',
66
+        code: 'by' },
67
+    { name: 'Belgium',
68
+        dialCode: '32',
69
+        code: 'be' },
70
+    { name: 'Belize',
71
+        dialCode: '501',
72
+        code: 'bz' },
73
+    { name: 'Benin',
74
+        dialCode: '229',
75
+        code: 'bj' },
76
+    { name: 'Bermuda',
77
+        dialCode: '1441',
78
+        code: 'bm' },
79
+    { name: 'Bhutan',
80
+        dialCode: '975',
81
+        code: 'bt' },
82
+    { name: 'Bolivia, Plurinational State of',
83
+        dialCode: '591',
84
+        code: 'bo' },
85
+    { name: 'Bosnia and Herzegovina',
86
+        dialCode: '387',
87
+        code: 'ba' },
88
+    { name: 'Botswana',
89
+        dialCode: '267',
90
+        code: 'bw' },
91
+    { name: 'Brazil',
92
+        dialCode: '55',
93
+        code: 'br' },
94
+    { name: 'British Indian Ocean Territory',
95
+        dialCode: '246',
96
+        code: 'io' },
97
+    { name: 'Brunei Darussalam',
98
+        dialCode: '673',
99
+        code: 'bn' },
100
+    { name: 'Bulgaria',
101
+        dialCode: '359',
102
+        code: 'bg' },
103
+    { name: 'Burkina Faso',
104
+        dialCode: '226',
105
+        code: 'bf' },
106
+    { name: 'Burundi',
107
+        dialCode: '257',
108
+        code: 'bi' },
109
+    { name: 'Cambodia',
110
+        dialCode: '855',
111
+        code: 'kh' },
112
+    { name: 'Cameroon',
113
+        dialCode: '237',
114
+        code: 'cm' },
115
+    { name: 'Canada',
116
+        dialCode: '1',
117
+        code: 'ca' },
118
+    { name: 'Cape Verde',
119
+        dialCode: '238',
120
+        code: 'cv' },
121
+    { name: 'Cayman Islands',
122
+        dialCode: ' 345',
123
+        code: 'ky' },
124
+    { name: 'Central African Republic',
125
+        dialCode: '236',
126
+        code: 'cf' },
127
+    { name: 'Chad',
128
+        dialCode: '235',
129
+        code: 'td' },
130
+    { name: 'Chile',
131
+        dialCode: '56',
132
+        code: 'cl' },
133
+    { name: 'China',
134
+        dialCode: '86',
135
+        code: 'cn' },
136
+    { name: 'Christmas Island',
137
+        dialCode: '61',
138
+        code: 'cx' },
139
+    { name: 'Cocos (Keeling) Islands',
140
+        dialCode: '61',
141
+        code: 'cc' },
142
+    { name: 'Colombia',
143
+        dialCode: '57',
144
+        code: 'co' },
145
+    { name: 'Comoros',
146
+        dialCode: '269',
147
+        code: 'km' },
148
+    { name: 'Congo',
149
+        dialCode: '242',
150
+        code: 'cg' },
151
+    {
152
+        name: 'Congo, The Democratic Republic of the Congo',
153
+        dialCode: '243',
154
+        code: 'cd'
155
+    },
156
+    { name: 'Cook Islands',
157
+        dialCode: '682',
158
+        code: 'ck' },
159
+    { name: 'Costa Rica',
160
+        dialCode: '506',
161
+        code: 'cr' },
162
+    { name: 'Cote d\'Ivoire',
163
+        dialCode: '225',
164
+        code: 'ci' },
165
+    { name: 'Croatia',
166
+        dialCode: '385',
167
+        code: 'hr' },
168
+    { name: 'Cuba',
169
+        dialCode: '53',
170
+        code: 'cu' },
171
+    { name: 'Cyprus',
172
+        dialCode: '357',
173
+        code: 'cy' },
174
+    { name: 'Czech Republic',
175
+        dialCode: '420',
176
+        code: 'cz' },
177
+    { name: 'Denmark',
178
+        dialCode: '45',
179
+        code: 'dk' },
180
+    { name: 'Djibouti',
181
+        dialCode: '253',
182
+        code: 'dj' },
183
+    { name: 'Dominica',
184
+        dialCode: '1767',
185
+        code: 'dm' },
186
+    { name: 'Dominican Republic',
187
+        dialCode: '1849',
188
+        code: 'do' },
189
+    { name: 'Ecuador',
190
+        dialCode: '593',
191
+        code: 'ec' },
192
+    { name: 'Egypt',
193
+        dialCode: '20',
194
+        code: 'eg' },
195
+    { name: 'El Salvador',
196
+        dialCode: '503',
197
+        code: 'sv' },
198
+    { name: 'Equatorial Guinea',
199
+        dialCode: '240',
200
+        code: 'gq' },
201
+    { name: 'Eritrea',
202
+        dialCode: '291',
203
+        code: 'er' },
204
+    { name: 'Estonia',
205
+        dialCode: '372',
206
+        code: 'ee' },
207
+    { name: 'Ethiopia',
208
+        dialCode: '251',
209
+        code: 'et' },
210
+    { name: 'Falkland Islands (Malvinas)',
211
+        dialCode: '500',
212
+        code: 'fk' },
213
+    { name: 'Faroe Islands',
214
+        dialCode: '298',
215
+        code: 'fo' },
216
+    { name: 'Fiji',
217
+        dialCode: '679',
218
+        code: 'fj' },
219
+    { name: 'Finland',
220
+        dialCode: '358',
221
+        code: 'fi' },
222
+    { name: 'France',
223
+        dialCode: '33',
224
+        code: 'fr' },
225
+    { name: 'French Guiana',
226
+        dialCode: '594',
227
+        code: 'gf' },
228
+    { name: 'French Polynesia',
229
+        dialCode: '689',
230
+        code: 'pf' },
231
+    { name: 'Gabon',
232
+        dialCode: '241',
233
+        code: 'ga' },
234
+    { name: 'Gambia',
235
+        dialCode: '220',
236
+        code: 'gm' },
237
+    { name: 'Georgia',
238
+        dialCode: '995',
239
+        code: 'ge' },
240
+    { name: 'Germany',
241
+        dialCode: '49',
242
+        code: 'de' },
243
+    { name: 'Ghana',
244
+        dialCode: '233',
245
+        code: 'gh' },
246
+    { name: 'Gibraltar',
247
+        dialCode: '350',
248
+        code: 'gi' },
249
+    { name: 'Greece',
250
+        dialCode: '30',
251
+        code: 'gr' },
252
+    { name: 'Greenland',
253
+        dialCode: '299',
254
+        code: 'gl' },
255
+    { name: 'Grenada',
256
+        dialCode: '1473',
257
+        code: 'gd' },
258
+    { name: 'Guadeloupe',
259
+        dialCode: '590',
260
+        code: 'gp' },
261
+    { name: 'Guam',
262
+        dialCode: '1671',
263
+        code: 'gu' },
264
+    { name: 'Guatemala',
265
+        dialCode: '502',
266
+        code: 'gt' },
267
+    { name: 'Guernsey',
268
+        dialCode: '44',
269
+        code: 'gg' },
270
+    { name: 'Guinea',
271
+        dialCode: '224',
272
+        code: 'gn' },
273
+    { name: 'Guinea-Bissau',
274
+        dialCode: '245',
275
+        code: 'gw' },
276
+    { name: 'Guyana',
277
+        dialCode: '595',
278
+        code: 'gy' },
279
+    { name: 'Haiti',
280
+        dialCode: '509',
281
+        code: 'ht' },
282
+    { name: 'Holy See (Vatican City State)',
283
+        dialCode: '379',
284
+        code: 'va' },
285
+    { name: 'Honduras',
286
+        dialCode: '504',
287
+        code: 'hn' },
288
+    { name: 'Hong Kong',
289
+        dialCode: '852',
290
+        code: 'hk' },
291
+    { name: 'Hungary',
292
+        dialCode: '36',
293
+        code: 'hu' },
294
+    { name: 'Iceland',
295
+        dialCode: '354',
296
+        code: 'is' },
297
+    { name: 'India',
298
+        dialCode: '91',
299
+        code: 'in' },
300
+    { name: 'Indonesia',
301
+        dialCode: '62',
302
+        code: 'id' },
303
+    {
304
+        name: 'Iran, Islamic Republic of Persian Gulf',
305
+        dialCode: '98',
306
+        code: 'ir'
307
+    },
308
+    { name: 'Iraq',
309
+        dialCode: '964',
310
+        code: 'iq' },
311
+    { name: 'Ireland',
312
+        dialCode: '353',
313
+        code: 'ie' },
314
+    { name: 'Isle of Man',
315
+        dialCode: '44',
316
+        code: 'im' },
317
+    { name: 'Israel',
318
+        dialCode: '972',
319
+        code: 'il' },
320
+    { name: 'Italy',
321
+        dialCode: '39',
322
+        code: 'it' },
323
+    { name: 'Jamaica',
324
+        dialCode: '1876',
325
+        code: 'jm' },
326
+    { name: 'Japan',
327
+        dialCode: '81',
328
+        code: 'jp' },
329
+    { name: 'Jersey',
330
+        dialCode: '44',
331
+        code: 'je' },
332
+    { name: 'Jordan',
333
+        dialCode: '962',
334
+        code: 'jo' },
335
+    { name: 'Kazakhstan',
336
+        dialCode: '77',
337
+        code: 'kz' },
338
+    { name: 'Kenya',
339
+        dialCode: '254',
340
+        code: 'ke' },
341
+    { name: 'Kiribati',
342
+        dialCode: '686',
343
+        code: 'ki' },
344
+    {
345
+        name: 'Korea, Democratic People\'s Republic of Korea',
346
+        dialCode: '850',
347
+        code: 'kp'
348
+    },
349
+    { name: 'Korea, Republic of South Korea',
350
+        dialCode: '82',
351
+        code: 'kr' },
352
+    { name: 'Kuwait',
353
+        dialCode: '965',
354
+        code: 'kw' },
355
+    { name: 'Kyrgyzstan',
356
+        dialCode: '996',
357
+        code: 'kg' },
358
+    { name: 'Laos',
359
+        dialCode: '856',
360
+        code: 'la' },
361
+    { name: 'Latvia',
362
+        dialCode: '371',
363
+        code: 'lv' },
364
+    { name: 'Lebanon',
365
+        dialCode: '961',
366
+        code: 'lb' },
367
+    { name: 'Lesotho',
368
+        dialCode: '266',
369
+        code: 'ls' },
370
+    { name: 'Liberia',
371
+        dialCode: '231',
372
+        code: 'lr' },
373
+    { name: 'Libyan Arab Jamahiriya',
374
+        dialCode: '218',
375
+        code: 'ly' },
376
+    { name: 'Liechtenstein',
377
+        dialCode: '423',
378
+        code: 'li' },
379
+    { name: 'Lithuania',
380
+        dialCode: '370',
381
+        code: 'lt' },
382
+    { name: 'Luxembourg',
383
+        dialCode: '352',
384
+        code: 'lu' },
385
+    { name: 'Macao',
386
+        dialCode: '853',
387
+        code: 'mo' },
388
+    { name: 'Macedonia',
389
+        dialCode: '389',
390
+        code: 'mk' },
391
+    { name: 'Madagascar',
392
+        dialCode: '261',
393
+        code: 'mg' },
394
+    { name: 'Malawi',
395
+        dialCode: '265',
396
+        code: 'mw' },
397
+    { name: 'Malaysia',
398
+        dialCode: '60',
399
+        code: 'my' },
400
+    { name: 'Maldives',
401
+        dialCode: '960',
402
+        code: 'mv' },
403
+    { name: 'Mali',
404
+        dialCode: '223',
405
+        code: 'ml' },
406
+    { name: 'Malta',
407
+        dialCode: '356',
408
+        code: 'mt' },
409
+    { name: 'Marshall Islands',
410
+        dialCode: '692',
411
+        code: 'mh' },
412
+    { name: 'Martinique',
413
+        dialCode: '596',
414
+        code: 'mq' },
415
+    { name: 'Mauritania',
416
+        dialCode: '222',
417
+        code: 'mr' },
418
+    { name: 'Mauritius',
419
+        dialCode: '230',
420
+        code: 'mu' },
421
+    { name: 'Mayotte',
422
+        dialCode: '262',
423
+        code: 'yt' },
424
+    { name: 'Mexico',
425
+        dialCode: '52',
426
+        code: 'mx' },
427
+    {
428
+        name: 'Micronesia, Federated States of Micronesia',
429
+        dialCode: '691',
430
+        code: 'fm'
431
+    },
432
+    { name: 'Moldova',
433
+        dialCode: '373',
434
+        code: 'md' },
435
+    { name: 'Monaco',
436
+        dialCode: '377',
437
+        code: 'mc' },
438
+    { name: 'Mongolia',
439
+        dialCode: '976',
440
+        code: 'mn' },
441
+    { name: 'Montenegro',
442
+        dialCode: '382',
443
+        code: 'me' },
444
+    { name: 'Montserrat',
445
+        dialCode: '1664',
446
+        code: 'ms' },
447
+    { name: 'Morocco',
448
+        dialCode: '212',
449
+        code: 'ma' },
450
+    { name: 'Mozambique',
451
+        dialCode: '258',
452
+        code: 'mz' },
453
+    { name: 'Myanmar',
454
+        dialCode: '95',
455
+        code: 'mm' },
456
+    { name: 'Namibia',
457
+        dialCode: '264',
458
+        code: 'na' },
459
+    { name: 'Nauru',
460
+        dialCode: '674',
461
+        code: 'nr' },
462
+    { name: 'Nepal',
463
+        dialCode: '977',
464
+        code: 'np' },
465
+    { name: 'Netherlands',
466
+        dialCode: '31',
467
+        code: 'nl' },
468
+    { name: 'Netherlands Antilles',
469
+        dialCode: '599',
470
+        code: 'an' },
471
+    { name: 'New Caledonia',
472
+        dialCode: '687',
473
+        code: 'nc' },
474
+    { name: 'New Zealand',
475
+        dialCode: '64',
476
+        code: 'nz' },
477
+    { name: 'Nicaragua',
478
+        dialCode: '505',
479
+        code: 'ni' },
480
+    { name: 'Niger',
481
+        dialCode: '227',
482
+        code: 'ne' },
483
+    { name: 'Nigeria',
484
+        dialCode: '234',
485
+        code: 'ng' },
486
+    { name: 'Niue',
487
+        dialCode: '683',
488
+        code: 'nu' },
489
+    { name: 'Norfolk Island',
490
+        dialCode: '672',
491
+        code: 'nf' },
492
+    { name: 'Northern Mariana Islands',
493
+        dialCode: '1670',
494
+        code: 'mp' },
495
+    { name: 'Norway',
496
+        dialCode: '47',
497
+        code: 'no' },
498
+    { name: 'Oman',
499
+        dialCode: '968',
500
+        code: 'om' },
501
+    { name: 'Pakistan',
502
+        dialCode: '92',
503
+        code: 'pk' },
504
+    { name: 'Palau',
505
+        dialCode: '680',
506
+        code: 'pw' },
507
+    { name: 'Palestinian Territory, Occupied',
508
+        dialCode: '970',
509
+        code: 'ps' },
510
+    { name: 'Panama',
511
+        dialCode: '507',
512
+        code: 'pa' },
513
+    { name: 'Papua New Guinea',
514
+        dialCode: '675',
515
+        code: 'pg' },
516
+    { name: 'Paraguay',
517
+        dialCode: '595',
518
+        code: 'py' },
519
+    { name: 'Peru',
520
+        dialCode: '51',
521
+        code: 'pe' },
522
+    { name: 'Philippines',
523
+        dialCode: '63',
524
+        code: 'ph' },
525
+    { name: 'Pitcairn',
526
+        dialCode: '872',
527
+        code: 'pn' },
528
+    { name: 'Poland',
529
+        dialCode: '48',
530
+        code: 'pl' },
531
+    { name: 'Portugal',
532
+        dialCode: '351',
533
+        code: 'pt' },
534
+    { name: 'Puerto Rico',
535
+        dialCode: '1939',
536
+        code: 'pr' },
537
+    { name: 'Qatar',
538
+        dialCode: '974',
539
+        code: 'qa' },
540
+    { name: 'Romania',
541
+        dialCode: '40',
542
+        code: 'ro' },
543
+    { name: 'Russia',
544
+        dialCode: '7',
545
+        code: 'ru' },
546
+    { name: 'Rwanda',
547
+        dialCode: '250',
548
+        code: 'rw' },
549
+    { name: 'Reunion',
550
+        dialCode: '262',
551
+        code: 're' },
552
+    { name: 'Saint Barthelemy',
553
+        dialCode: '590',
554
+        code: 'bl' },
555
+    {
556
+        name: 'Saint Helena, Ascension and Tristan Da Cunha',
557
+        dialCode: '290',
558
+        code: 'sh'
559
+    },
560
+    { name: 'Saint Kitts and Nevis',
561
+        dialCode: '1869',
562
+        code: 'kn' },
563
+    { name: 'Saint Lucia',
564
+        dialCode: '1758',
565
+        code: 'lc' },
566
+    { name: 'Saint Martin',
567
+        dialCode: '590',
568
+        code: 'mf' },
569
+    { name: 'Saint Pierre and Miquelon',
570
+        dialCode: '508',
571
+        code: 'pm' },
572
+    { name: 'Saint Vincent and the Grenadines',
573
+        dialCode: '1784',
574
+        code: 'vc' },
575
+    { name: 'Samoa',
576
+        dialCode: '685',
577
+        code: 'ws' },
578
+    { name: 'San Marino',
579
+        dialCode: '378',
580
+        code: 'sm' },
581
+    { name: 'Sao Tome and Principe',
582
+        dialCode: '239',
583
+        code: 'st' },
584
+    { name: 'Saudi Arabia',
585
+        dialCode: '966',
586
+        code: 'sa' },
587
+    { name: 'Senegal',
588
+        dialCode: '221',
589
+        code: 'sn' },
590
+    { name: 'Serbia',
591
+        dialCode: '381',
592
+        code: 'rs' },
593
+    { name: 'Seychelles',
594
+        dialCode: '248',
595
+        code: 'sc' },
596
+    { name: 'Sierra Leone',
597
+        dialCode: '232',
598
+        code: 'sl' },
599
+    { name: 'Singapore',
600
+        dialCode: '65',
601
+        code: 'sg' },
602
+    { name: 'Slovakia',
603
+        dialCode: '421',
604
+        code: 'sk' },
605
+    { name: 'Slovenia',
606
+        dialCode: '386',
607
+        code: 'si' },
608
+    { name: 'Solomon Islands',
609
+        dialCode: '677',
610
+        code: 'sb' },
611
+    { name: 'Somalia',
612
+        dialCode: '252',
613
+        code: 'so' },
614
+    { name: 'South Africa',
615
+        dialCode: '27',
616
+        code: 'za' },
617
+    { name: 'South Sudan',
618
+        dialCode: '211',
619
+        code: 'ss' },
620
+    {
621
+        name: 'South Georgia and the South Sandwich Islands',
622
+        dialCode: '500',
623
+        code: 'gs'
624
+    },
625
+    { name: 'Spain',
626
+        dialCode: '34',
627
+        code: 'es' },
628
+    { name: 'Sri Lanka',
629
+        dialCode: '94',
630
+        code: 'lk' },
631
+    { name: 'Sudan',
632
+        dialCode: '249',
633
+        code: 'sd' },
634
+    { name: 'Suriname',
635
+        dialCode: '597',
636
+        code: 'sr' },
637
+    { name: 'Svalbard and Jan Mayen',
638
+        dialCode: '47',
639
+        code: 'sj' },
640
+    { name: 'Swaziland',
641
+        dialCode: '268',
642
+        code: 'sz' },
643
+    { name: 'Sweden',
644
+        dialCode: '46',
645
+        code: 'se' },
646
+    { name: 'Switzerland',
647
+        dialCode: '41',
648
+        code: 'ch' },
649
+    { name: 'Syrian Arab Republic',
650
+        dialCode: '963',
651
+        code: 'sy' },
652
+    { name: 'Taiwan',
653
+        dialCode: '886',
654
+        code: 'tw' },
655
+    { name: 'Tajikistan',
656
+        dialCode: '992',
657
+        code: 'tj' },
658
+    {
659
+        name: 'Tanzania, United Republic of Tanzania',
660
+        dialCode: '255',
661
+        code: 'tz'
662
+    },
663
+    { name: 'Thailand',
664
+        dialCode: '66',
665
+        code: 'th' },
666
+    { name: 'Timor-Leste',
667
+        dialCode: '670',
668
+        code: 'tl' },
669
+    { name: 'Togo',
670
+        dialCode: '228',
671
+        code: 'tg' },
672
+    { name: 'Tokelau',
673
+        dialCode: '690',
674
+        code: 'tk' },
675
+    { name: 'Tonga',
676
+        dialCode: '676',
677
+        code: 'to' },
678
+    { name: 'Trinidad and Tobago',
679
+        dialCode: '1868',
680
+        code: 'tt' },
681
+    { name: 'Tunisia',
682
+        dialCode: '216',
683
+        code: 'tn' },
684
+    { name: 'Turkey',
685
+        dialCode: '90',
686
+        code: 'tr' },
687
+    { name: 'Turkmenistan',
688
+        dialCode: '993',
689
+        code: 'tm' },
690
+    { name: 'Turks and Caicos Islands',
691
+        dialCode: '1649',
692
+        code: 'tc' },
693
+    { name: 'Tuvalu',
694
+        dialCode: '688',
695
+        code: 'tv' },
696
+    { name: 'Uganda',
697
+        dialCode: '256',
698
+        code: 'ug' },
699
+    { name: 'Ukraine',
700
+        dialCode: '380',
701
+        code: 'ua' },
702
+    { name: 'United Arab Emirates',
703
+        dialCode: '971',
704
+        code: 'ae' },
705
+    { name: 'United Kingdom',
706
+        dialCode: '44',
707
+        code: 'gb' },
708
+    { name: 'United States',
709
+        dialCode: '1',
710
+        code: 'us' },
711
+    { name: 'Uruguay',
712
+        dialCode: '598',
713
+        code: 'uy' },
714
+    { name: 'Uzbekistan',
715
+        dialCode: '998',
716
+        code: 'uz' },
717
+    { name: 'Vanuatu',
718
+        dialCode: '678',
719
+        code: 'vu' },
720
+    {
721
+        name: 'Venezuela, Bolivarian Republic of Venezuela',
722
+        dialCode: '58',
723
+        code: 've'
724
+    },
725
+    { name: 'Vietnam',
726
+        dialCode: '84',
727
+        code: 'vn' },
728
+    { name: 'Virgin Islands, British',
729
+        dialCode: '1284',
730
+        code: 'vg' },
731
+    { name: 'Virgin Islands, U.S.',
732
+        dialCode: '1340',
733
+        code: 'vi' },
734
+    { name: 'Wallis and Futuna',
735
+        dialCode: '681',
736
+        code: 'wf' },
737
+    { name: 'Yemen',
738
+        dialCode: '967',
739
+        code: 'ye' },
740
+    { name: 'Zambia',
741
+        dialCode: '260',
742
+        code: 'zm' },
743
+    { name: 'Zimbabwe',
744
+        dialCode: '263',
745
+        code: 'zw' }
746
+];
747
+
748
+const countriesByCodeMap = countries.reduce((result, country) => {
749
+    result[country.dialCode] = country;
750
+
751
+    return result;
752
+}, {});
753
+
754
+/**
755
+ * Map between country dial codes and country objects.
756
+ *
757
+ */
758
+const codesByNumbersMap = countries.reduce((result, country) => {
759
+    result[country.dialCode] = country.code;
760
+
761
+    return result;
762
+}, {});
763
+
764
+/**
765
+ * Returns the corresponding country code from a phone number.
766
+ *
767
+ * @param {string} phoneNumber - The phone number.
768
+ * @returns {string}
769
+ */
770
+export function getCountryCodeFromPhone(phoneNumber: string): string {
771
+    const number = phoneNumber.replace(/[+.\s]/g, '');
772
+
773
+
774
+    for (let i = 4; i > 0; i--) {
775
+        const prefix = number.slice(0, i);
776
+
777
+        if (codesByNumbersMap[prefix]) {
778
+            return codesByNumbersMap[prefix];
779
+        }
780
+    }
781
+
782
+    return '';
783
+}
784
+
785
+/**
786
+ * Returns the corresponding country for a text starting with the dial code.
787
+ *
788
+ * @param {string} text - The text containing the dial code.
789
+ * @returns {Object}
790
+ */
791
+export function getCountryFromDialCodeText(text: string): Object {
792
+    return (
793
+        countriesByCodeMap[text.slice(0, 4)]
794
+        || countriesByCodeMap[text.slice(0, 3)]
795
+        || countriesByCodeMap[text.slice(0, 2)]
796
+        || countriesByCodeMap[text.slice(0, 1)]
797
+        || null
798
+    );
799
+}

Loading…
Cancel
Save