You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NumbersList.tsx 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import countries from 'i18n-iso-countries';
  2. import en from 'i18n-iso-countries/langs/en.json';
  3. import React, { useCallback, useMemo } from 'react';
  4. import { WithTranslation } from 'react-i18next';
  5. import { translate } from '../../../../base/i18n/functions';
  6. import Icon from '../../../../base/icons/components/Icon';
  7. import { IconSip } from '../../../../base/icons/svg';
  8. countries.registerLocale(en);
  9. interface INormalizedNumber {
  10. /**
  11. * The country code.
  12. */
  13. countryCode?: string;
  14. /**
  15. * The formatted number.
  16. */
  17. formattedNumber: string;
  18. /**
  19. * Whether the number is toll-free.
  20. */
  21. tollFree?: boolean;
  22. }
  23. interface INumbersMapping {
  24. [countryName: string]: Array<INormalizedNumber>;
  25. }
  26. interface IProps extends WithTranslation {
  27. /**
  28. * Whether or not numbers should include links with the telephone protocol.
  29. */
  30. clickableNumbers: boolean;
  31. /**
  32. * The conference ID for dialing in.
  33. */
  34. conferenceID: number | null;
  35. /**
  36. * The phone numbers to display. Can be an array of number Objects or an
  37. * object with countries as keys and an array of numbers as values.
  38. */
  39. numbers: INumbersMapping | null;
  40. }
  41. const NumbersList: React.FC<IProps> = ({ t, conferenceID, clickableNumbers, numbers: numbersMapping }) => {
  42. const renderFlag = useCallback((countryCode: string) => {
  43. if (countryCode) {
  44. return (
  45. <td className = 'flag-cell'>
  46. {countryCode === 'SIP' || countryCode === 'SIP_AUDIO_ONLY'
  47. ? <Icon src = { IconSip } />
  48. : <i className = { `flag iti-flag ${countryCode}` } />
  49. }
  50. </td>);
  51. }
  52. return null;
  53. }, []);
  54. const renderNumberLink = useCallback((number: string) => {
  55. if (clickableNumbers) {
  56. // Url encode # to %23, Android phone was cutting the # after
  57. // clicking it.
  58. // Seems that using ',' and '%23' works on iOS and Android.
  59. return (
  60. <a
  61. href = { `tel:${number},${conferenceID}%23` }
  62. key = { number } >
  63. {number}
  64. </a>
  65. );
  66. }
  67. return number;
  68. }, [ conferenceID, clickableNumbers ]);
  69. const renderNumbersList = useCallback((numbers: Array<INormalizedNumber>) => {
  70. const numbersListItems = numbers.map(number =>
  71. (<li
  72. className = 'dial-in-number'
  73. key = { number.formattedNumber }>
  74. {renderNumberLink(number.formattedNumber)}
  75. </li>));
  76. return (
  77. <ul className = 'numbers-list'>
  78. {numbersListItems}
  79. </ul>
  80. );
  81. }, []);
  82. const renderNumbersTollFreeList = useCallback((numbers: Array<INormalizedNumber>) => {
  83. const tollNumbersListItems = numbers.map(number =>
  84. (<li
  85. className = 'toll-free'
  86. key = { number.formattedNumber }>
  87. {number.tollFree ? t('info.dialInTollFree') : ''}
  88. </li>));
  89. return (
  90. <ul className = 'toll-free-list'>
  91. {tollNumbersListItems}
  92. </ul>
  93. );
  94. }, []);
  95. const renderNumbers = useMemo(() => {
  96. let numbers: INumbersMapping;
  97. if (!numbersMapping) {
  98. return;
  99. }
  100. if (Array.isArray(numbersMapping)) {
  101. numbers = numbersMapping.reduce(
  102. (resultNumbers: any, number: any) => {
  103. // The i18n-iso-countries package insists on upper case.
  104. const countryCode = number.countryCode.toUpperCase();
  105. let countryName;
  106. if (countryCode === 'SIP') {
  107. countryName = t('info.sip');
  108. } else if (countryCode === 'SIP_AUDIO_ONLY') {
  109. countryName = t('info.sipAudioOnly');
  110. } else {
  111. countryName = t(`countries:countries.${countryCode}`);
  112. // Some countries have multiple names as US ['United States of America', 'USA']
  113. // choose the first one if that is the case
  114. if (!countryName) {
  115. countryName = t(`countries:countries.${countryCode}.0`);
  116. }
  117. }
  118. if (resultNumbers[countryName]) {
  119. resultNumbers[countryName].push(number);
  120. } else {
  121. resultNumbers[countryName] = [ number ];
  122. }
  123. return resultNumbers;
  124. }, {});
  125. } else {
  126. numbers = {};
  127. for (const [ country, numbersArray ]
  128. of Object.entries(numbersMapping.numbers)) {
  129. if (Array.isArray(numbersArray)) {
  130. /* eslint-disable arrow-body-style */
  131. const formattedNumbers = numbersArray.map(number => ({
  132. formattedNumber: number
  133. }));
  134. /* eslint-enable arrow-body-style */
  135. numbers[country] = formattedNumbers;
  136. }
  137. }
  138. }
  139. const rows: [JSX.Element] = [] as unknown as [JSX.Element];
  140. Object.keys(numbers).forEach((countryName: string) => {
  141. const numbersArray: Array<INormalizedNumber> = numbers[countryName];
  142. const countryCode = numbersArray[0].countryCode
  143. || countries.getAlpha2Code(countryName, 'en')?.toUpperCase()
  144. || countryName;
  145. rows.push(
  146. <>
  147. <tr
  148. key = { countryName }>
  149. {renderFlag(countryCode)}
  150. <td className = 'country' >{countryName}</td>
  151. </tr>
  152. <tr>
  153. <td />
  154. <td className = 'numbers-list-column'>
  155. {renderNumbersList(numbersArray)}
  156. </td>
  157. <td className = 'toll-free-list-column' >
  158. {renderNumbersTollFreeList(numbersArray)}
  159. </td>
  160. </tr>
  161. </>
  162. );
  163. });
  164. return rows;
  165. }, [ numbersMapping ]);
  166. return (
  167. <table className = 'dial-in-numbers-list'>
  168. <tbody className = 'dial-in-numbers-body'>
  169. {renderNumbers}
  170. </tbody>
  171. </table>
  172. );
  173. };
  174. export default translate(NumbersList);