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.

SalesforceLinkDialog.tsx 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import React, { useCallback } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import { Platform, SafeAreaView, ScrollView, Text, View, ViewStyle } from 'react-native';
  4. import { useSelector } from 'react-redux';
  5. import { IReduxState } from '../../../app/types';
  6. import { IconSearch } from '../../../base/icons/svg';
  7. import JitsiScreen from '../../../base/modal/components/JitsiScreen';
  8. import LoadingIndicator from '../../../base/react/components/native/LoadingIndicator';
  9. import Button from '../../../base/ui/components/native/Button';
  10. import Input from '../../../base/ui/components/native/Input';
  11. import { BUTTON_TYPES } from '../../../base/ui/constants.native';
  12. import { navigate } from '../../../mobile/navigation/components/conference/ConferenceNavigationContainerRef';
  13. import { screen } from '../../../mobile/navigation/routes';
  14. import { CONTENT_HEIGHT_OFFSET, LIST_HEIGHT_OFFSET, NOTES_LINES, NOTES_MAX_LENGTH } from '../../constants';
  15. import { useSalesforceLinkDialog } from '../../useSalesforceLinkDialog';
  16. import { RecordItem } from './RecordItem';
  17. import styles from './styles';
  18. /**
  19. * Component that renders the Salesforce link dialog.
  20. *
  21. * @returns {React$Element<any>}
  22. */
  23. const SalesforceLinkDialog = () => {
  24. const { t } = useTranslation();
  25. const { clientHeight } = useSelector((state: IReduxState) => state['features/base/responsive-ui']);
  26. const {
  27. hasDetailsErrors,
  28. hasRecordsErrors,
  29. isLoading,
  30. linkMeeting,
  31. notes,
  32. records,
  33. searchTerm,
  34. selectedRecord,
  35. selectedRecordOwner,
  36. setNotes,
  37. setSearchTerm,
  38. setSelectedRecord,
  39. showNoResults,
  40. showSearchResults
  41. } = useSalesforceLinkDialog();
  42. const handlePress = useCallback(() => {
  43. navigate(screen.conference.main);
  44. selectedRecord && linkMeeting();
  45. }, [ navigate, linkMeeting ]);
  46. const renderSpinner = () => (
  47. <View style = { [ styles.recordsSpinner, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
  48. <LoadingIndicator />
  49. </View>
  50. );
  51. const renderDetailsErrors = () => (
  52. <Text style = { styles.detailsError }>
  53. {t('dialog.searchResultsDetailsError')}
  54. </Text>
  55. );
  56. const renderSelection = () => (
  57. <SafeAreaView>
  58. <ScrollView
  59. bounces = { false }
  60. style = { [ styles.selectedRecord, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
  61. <View style = { styles.recordInfo as ViewStyle }>
  62. <RecordItem { ...selectedRecord } />
  63. { selectedRecordOwner && <RecordItem { ...selectedRecordOwner } /> }
  64. { hasDetailsErrors && renderDetailsErrors() }
  65. </View>
  66. <Text style = { styles.addNote }>
  67. {t('dialog.addOptionalNote')}
  68. </Text>
  69. <Input
  70. customStyles = {{ container: styles.notes }}
  71. maxLength = { NOTES_MAX_LENGTH }
  72. minHeight = { Platform.OS === 'ios' && NOTES_LINES ? 20 * NOTES_LINES : undefined }
  73. multiline = { true }
  74. numberOfLines = { Platform.OS === 'ios' ? undefined : NOTES_LINES }
  75. /* eslint-disable-next-line react/jsx-no-bind */
  76. onChange = { value => setNotes(value) }
  77. placeholder = { t('dialog.addMeetingNote') }
  78. value = { notes } />
  79. </ScrollView>
  80. </SafeAreaView>
  81. );
  82. const renderRecordsSearch = () => (
  83. <View style = { styles.recordsSearchContainer as ViewStyle }>
  84. <Input
  85. icon = { IconSearch }
  86. maxLength = { NOTES_MAX_LENGTH }
  87. /* eslint-disable-next-line react/jsx-no-bind */
  88. onChange = { value => setSearchTerm(value) }
  89. placeholder = { t('dialog.searchInSalesforce') }
  90. value = { searchTerm ?? '' } />
  91. {(!isLoading && !hasRecordsErrors) && (
  92. <Text style = { styles.resultLabel }>
  93. {showSearchResults
  94. ? t('dialog.searchResults', { count: records.length })
  95. : t('dialog.recentlyUsedObjects')
  96. }
  97. </Text>
  98. )}
  99. </View>
  100. );
  101. const renderNoRecords = () => showNoResults && (
  102. <View style = { [ styles.noRecords, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
  103. <Text style = { styles.noRecordsText }>
  104. {t('dialog.searchResultsNotFound')}
  105. </Text>
  106. <Text style = { styles.noRecordsText }>
  107. {t('dialog.searchResultsTryAgain')}
  108. </Text>
  109. </View>
  110. );
  111. const renderRecordsError = () => (
  112. <View style = { [ styles.recordsError, { height: clientHeight - CONTENT_HEIGHT_OFFSET } ] as ViewStyle[] }>
  113. <Text style = { styles.recordsErrorText }>
  114. {t('dialog.searchResultsError')}
  115. </Text>
  116. </View>
  117. );
  118. const renderContent = () => {
  119. if (isLoading) {
  120. return renderSpinner();
  121. }
  122. if (hasRecordsErrors) {
  123. return renderRecordsError();
  124. }
  125. if (showNoResults) {
  126. return renderNoRecords();
  127. }
  128. if (selectedRecord) {
  129. return renderSelection();
  130. }
  131. return (
  132. <SafeAreaView>
  133. <ScrollView
  134. bounces = { false }
  135. style = { [ styles.recordList, { height: clientHeight - LIST_HEIGHT_OFFSET } ] as ViewStyle[] }>
  136. {records.map((item: any) => (
  137. <RecordItem
  138. key = { `record-${item.id}` }
  139. /* eslint-disable-next-line react/jsx-no-bind */
  140. onClick = { () => setSelectedRecord(item) }
  141. { ...item } />
  142. ))}
  143. </ScrollView>
  144. </SafeAreaView>
  145. );
  146. };
  147. return (
  148. <JitsiScreen style = { styles.salesforceDialogContainer }>
  149. <View>
  150. {!selectedRecord && renderRecordsSearch()}
  151. {renderContent()}
  152. </View>
  153. {
  154. selectedRecord
  155. && <View>
  156. <Button
  157. labelKey = 'dialog.Cancel'
  158. /* eslint-disable-next-line react/jsx-no-bind */
  159. onClick = { () => setSelectedRecord(null) }
  160. style = { styles.cancelButton }
  161. type = { BUTTON_TYPES.SECONDARY } />
  162. <Button
  163. labelKey = 'dialog.linkMeeting'
  164. onClick = { handlePress }
  165. style = { styles.linkButton }
  166. type = { BUTTON_TYPES.PRIMARY } />
  167. </View>
  168. }
  169. </JitsiScreen>
  170. );
  171. };
  172. export default SalesforceLinkDialog;