agora-appbuilder-core 4.1.8-beta.5 → 4.1.8-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.1.8-beta.5",
3
+ "version": "4.1.8-beta.7",
4
4
  "description": "React Native template for RTE app builder",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -77,8 +77,8 @@ const DefaultConfig = {
77
77
  CHAT_ORG_NAME: '',
78
78
  CHAT_APP_NAME: '',
79
79
  CHAT_URL: '',
80
- CLI_VERSION: '3.1.8-beta.5',
81
- CORE_VERSION: '4.1.8-beta.5',
80
+ CLI_VERSION: '3.1.8-beta.7',
81
+ CORE_VERSION: '4.1.8-beta.7',
82
82
  DISABLE_LANDSCAPE_MODE: false,
83
83
  STT_AUTO_START: false,
84
84
  CLOUD_RECORDING_AUTO_START: false,
@@ -89,7 +89,8 @@ const DefaultConfig = {
89
89
  AI_LAYOUT: 'LAYOUT_TYPE_1',
90
90
  SDK_CODEC: 'vp8',
91
91
  ENABLE_WAITING_ROOM_AUTO_APPROVAL: false,
92
- ENABLE_WAITING_ROOM_AUTO_REQUEST: false
92
+ ENABLE_WAITING_ROOM_AUTO_REQUEST: false,
93
+ ENABLE_TEXT_TRACKS: true,
93
94
  };
94
95
 
95
96
  module.exports = DefaultConfig;
@@ -177,6 +177,7 @@ interface ConfigInterface {
177
177
  SDK_CODEC: string;
178
178
  ENABLE_WAITING_ROOM_AUTO_APPROVAL: boolean;
179
179
  ENABLE_WAITING_ROOM_AUTO_REQUEST: boolean;
180
+ ENABLE_TEXT_TRACKS: boolean;
180
181
  }
181
182
  declare var $config: ConfigInterface;
182
183
  declare module 'customization' {
@@ -103,7 +103,7 @@ import {
103
103
  toolbarItemTranscriptText,
104
104
  toolbarItemVirtualBackgroundText,
105
105
  toolbarItemWhiteboardText,
106
- toolbarItemManageTranscriptText,
106
+ toolbarItemManageTextTracksText,
107
107
  } from '../language/default-labels/videoCallScreenLabels';
108
108
  import {LogSource, logger} from '../logger/AppBuilderLogger';
109
109
  import {useModal} from '../utils/useModal';
@@ -116,7 +116,7 @@ import {
116
116
  InviteToolbarItem,
117
117
  ScreenshareToolbarItem,
118
118
  } from './controls/toolbar-items';
119
- import ViewSTTTranscriptModal from './stt-transcript/ViewSTTTranscriptModal';
119
+ import ViewTextTracksModal from './text-tracks/ViewTextTracksModal';
120
120
 
121
121
  export const useToggleWhiteboard = () => {
122
122
  const {
@@ -278,7 +278,9 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
278
278
  const viewRecordingsLabel = useString<boolean>(
279
279
  toolbarItemViewRecordingText,
280
280
  )();
281
- const viewSTTLabel = useString<boolean>(toolbarItemManageTranscriptText)();
281
+ const viewTextTracksLabel = useString<boolean>(
282
+ toolbarItemManageTextTracksText,
283
+ )();
282
284
  const moreButtonLabel = useString(toolbarItemMoreText)();
283
285
  const virtualBackgroundLabel = useString(toolbarItemVirtualBackgroundText)();
284
286
  const chatLabel = useString(toolbarItemChatText)();
@@ -297,9 +299,9 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
297
299
  toggle: toggleVRModal,
298
300
  } = useModal();
299
301
  const {
300
- modalOpen: isSTTTranscriptModalOpen,
301
- setModalOpen: setSTTTranscriptModalOpen,
302
- toggle: toggleSTTTranscriptModal,
302
+ modalOpen: isTextTrackModalOpen,
303
+ setModalOpen: setTextTrackModalOpen,
304
+ toggle: toggleTextTrackModal,
303
305
  } = useModal();
304
306
  const moreBtnRef = useRef(null);
305
307
  const {width: globalWidth, height: globalHeight} = useWindowDimensions();
@@ -814,20 +816,20 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
814
816
  });
815
817
  }
816
818
 
817
- // 13. Transcripts to download
818
- const canAccessAllTranscripts =
819
- useControlPermissionMatrix('viewAllTranscripts');
819
+ // 13. Text-tracks to download
820
+ const canAccessAllTextTracks =
821
+ useControlPermissionMatrix('viewAllTextTracks');
820
822
 
821
- if (canAccessAllTranscripts) {
823
+ if (canAccessAllTextTracks) {
822
824
  actionMenuitems.push({
823
- componentName: 'view-all-transcripts',
825
+ componentName: 'view-all-text-tracks',
824
826
  order: 13,
825
827
  icon: 'transcript',
826
828
  iconColor: $config.SECONDARY_ACTION_COLOR,
827
829
  textColor: $config.FONT_COLOR,
828
- title: viewSTTLabel,
830
+ title: viewTextTracksLabel,
829
831
  onPress: () => {
830
- toggleSTTTranscriptModal();
832
+ toggleTextTrackModal();
831
833
  },
832
834
  });
833
835
  }
@@ -978,8 +980,8 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
978
980
  )}
979
981
  </>
980
982
  )}
981
- {canAccessAllTranscripts && isSTTTranscriptModalOpen ? (
982
- <ViewSTTTranscriptModal setModalOpen={setSTTTranscriptModalOpen} />
983
+ {canAccessAllTextTracks && isTextTrackModalOpen ? (
984
+ <ViewTextTracksModal setModalOpen={setTextTrackModalOpen} />
983
985
  ) : (
984
986
  <></>
985
987
  )}
@@ -51,7 +51,6 @@ const GenericPopup: React.FC<ConfirmationModalProps> = ({
51
51
  onCancel && onCancel();
52
52
  };
53
53
  const handleConfirm = () => {
54
- setVisible(false);
55
54
  onConfirm();
56
55
  };
57
56
 
@@ -151,7 +151,7 @@ const TableFooter: React.FC<TableFooterProps> = ({
151
151
 
152
152
  export {TableHeader, TableFooter, TableBody};
153
153
 
154
- const style = StyleSheet.create({
154
+ export const style = StyleSheet.create({
155
155
  scrollgrow: {
156
156
  flexGrow: 1,
157
157
  },
@@ -249,7 +249,6 @@ const style = StyleSheet.create({
249
249
  flex: 1,
250
250
  alignSelf: 'center',
251
251
  justifyContent: 'center',
252
- // height: 100,
253
252
  gap: 10,
254
253
  },
255
254
  tpreview: {
@@ -275,6 +274,8 @@ const style = StyleSheet.create({
275
274
  tactions: {
276
275
  display: 'flex',
277
276
  flexDirection: 'row',
277
+ alignItems: 'center',
278
+ justifyContent: 'flex-end',
278
279
  },
279
280
  tlink: {
280
281
  color: $config.PRIMARY_ACTION_BRAND_COLOR,
@@ -382,4 +383,21 @@ const style = StyleSheet.create({
382
383
  pl15: {
383
384
  paddingLeft: 15,
384
385
  },
386
+ // icon celles
387
+ tdIconCell: {
388
+ flex: 0,
389
+ flexShrink: 0,
390
+ alignItems: 'flex-start',
391
+ justifyContent: 'center',
392
+ minWidth: 50,
393
+ paddingRight: 50 + 12,
394
+ },
395
+ thIconCell: {
396
+ flex: 0,
397
+ flexShrink: 0,
398
+ alignSelf: 'stretch',
399
+ justifyContent: 'center',
400
+ minWidth: 50,
401
+ paddingHorizontal: 12,
402
+ },
385
403
  });
@@ -15,7 +15,7 @@ export type ControlPermissionKey =
15
15
  | 'participantControl'
16
16
  | 'screenshareControl'
17
17
  | 'settingsControl'
18
- | 'viewAllTranscripts';
18
+ | 'viewAllTextTracks';
19
19
 
20
20
  /**
21
21
  * ControlPermissionRule defines the properties used to evaluate permission rules.
@@ -36,10 +36,11 @@ export const controlPermissionMatrix: Record<
36
36
  settingsControl: ({preference}) => !preference.disableSettings,
37
37
  screenshareControl: ({preference}) =>
38
38
  $config.SCREEN_SHARING && !preference.disableScreenShare,
39
- viewAllTranscripts: ({isHost}) =>
39
+ viewAllTextTracks: ({isHost}) =>
40
40
  isHost &&
41
41
  $config.ENABLE_STT &&
42
42
  $config.ENABLE_MEETING_TRANSCRIPT &&
43
+ $config.ENABLE_TEXT_TRACKS &&
43
44
  isWeb(),
44
45
  };
45
46
 
@@ -0,0 +1,285 @@
1
+ import React, {useEffect, useState} from 'react';
2
+ import {View, Text, Linking, TouchableOpacity, StyleSheet} from 'react-native';
3
+ import {downloadRecording, getDuration, getRecordedDateTime} from './utils';
4
+ import IconButtonWithToolTip from '../../atoms/IconButton';
5
+ import Tooltip from '../../atoms/Tooltip';
6
+ import Clipboard from '../../subComponents/Clipboard';
7
+ import Spacer from '../../atoms/Spacer';
8
+ import PlatformWrapper from '../../utils/PlatformWrapper';
9
+ import {useFetchSTTTranscript} from '../text-tracks/useFetchSTTTranscript';
10
+ import {style} from '../common/data-table';
11
+ import {FetchRecordingData} from '../../subComponents/recording/useRecording';
12
+ import ImageIcon from '../../atoms/ImageIcon';
13
+ import TextTrackItemRow from './TextTrackItemRow';
14
+
15
+ interface RecordingItemRowProps {
16
+ item: FetchRecordingData['recordings'][0];
17
+ onDeleteAction: (id: string) => void;
18
+ onTextTrackDownload: (textTrackLink: string) => void;
19
+ }
20
+ export default function RecordingItemRow({
21
+ item,
22
+ onDeleteAction,
23
+ onTextTrackDownload,
24
+ }: RecordingItemRowProps) {
25
+ const [expanded, setIsExpanded] = useState(false);
26
+
27
+ const [date, time] = getRecordedDateTime(item.created_at);
28
+ const recordingStatus = item.status;
29
+
30
+ const {sttRecState, getSTTsForRecording} = useFetchSTTTranscript();
31
+ const {
32
+ status,
33
+ error,
34
+ data: {stts = []},
35
+ } = sttRecState;
36
+ console.log('supriya sttRecState', sttRecState);
37
+ useEffect(() => {
38
+ if (expanded) {
39
+ if (item.id) {
40
+ getSTTsForRecording(item.id);
41
+ }
42
+ }
43
+ }, [expanded, item.id, getSTTsForRecording]);
44
+
45
+ if (
46
+ recordingStatus === 'STOPPING' ||
47
+ recordingStatus === 'STARTED' ||
48
+ (recordingStatus === 'INPROGRESS' && !item?.download_url)
49
+ ) {
50
+ return (
51
+ <View key={item.id} style={style.pt12}>
52
+ <View style={[style.infotextContainer, style.captionContainer]}>
53
+ <ImageIcon
54
+ iconSize={20}
55
+ iconType="plain"
56
+ name="info"
57
+ tintColor={$config.SEMANTIC_NEUTRAL}
58
+ />
59
+ <Text style={[style.captionText]}>
60
+ Current recording is ongoing. Once it concludes, we'll generate the
61
+ link
62
+ </Text>
63
+ </View>
64
+ </View>
65
+ );
66
+ }
67
+
68
+ // Collapsible Row
69
+ return (
70
+ <View>
71
+ {/* ========== PARENT ROW ========== */}
72
+ <View style={style.tbrow} key={item.id}>
73
+ <View style={style.tdIconCell}>
74
+ <IconButtonWithToolTip
75
+ hoverEffect={true}
76
+ hoverEffectStyle={style.iconButtonHoverEffect}
77
+ containerStyle={style.iconButton}
78
+ iconProps={{
79
+ name: expanded ? 'arrow-up' : 'arrow-down',
80
+ iconType: 'plain',
81
+ iconSize: 20,
82
+ tintColor: `${$config.FONT_COLOR}`,
83
+ }}
84
+ onPress={() => setIsExpanded(prev => !prev)}
85
+ />
86
+ </View>
87
+ <View style={[style.td, style.plzero]}>
88
+ <Text style={style.ttime}>
89
+ {date}
90
+ <br />
91
+ <Text style={style.ttime}>{time}</Text>
92
+ </Text>
93
+ </View>
94
+ <View style={[style.td]}>
95
+ <Text style={style.ttime}>
96
+ {getDuration(item.created_at, item.ended_at)}
97
+ </Text>
98
+ </View>
99
+ <View style={style.td}>
100
+ {!item.download_url ? (
101
+ <View style={(style.tactions, {marginTop: 0})}>
102
+ <Text style={style.placeHolder}>{'No recording found'}</Text>
103
+ </View>
104
+ ) : item?.download_url?.length > 0 ? (
105
+ <View style={style.tactions}>
106
+ <View>
107
+ {item?.download_url?.map((link: string, i: number) => (
108
+ <View
109
+ style={[
110
+ style.tactions,
111
+ //if recording contains multiple parts then we need to add some space each row
112
+ i >= 1 ? {marginTop: 8} : {},
113
+ ]}>
114
+ <View>
115
+ <IconButtonWithToolTip
116
+ hoverEffect={true}
117
+ hoverEffectStyle={style.iconButtonHoverEffect}
118
+ containerStyle={style.iconButton}
119
+ iconProps={{
120
+ name: 'download',
121
+ iconType: 'plain',
122
+ iconSize: 20,
123
+ tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
124
+ }}
125
+ onPress={() => {
126
+ downloadRecording(link);
127
+ }}
128
+ />
129
+ </View>
130
+ <View style={style.pl10}>
131
+ <IconButtonWithToolTip
132
+ hoverEffect={true}
133
+ hoverEffectStyle={style.iconButtonHoverEffect}
134
+ containerStyle={style.iconButton}
135
+ iconProps={{
136
+ name: 'link-share',
137
+ iconType: 'plain',
138
+ iconSize: 20,
139
+ tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
140
+ }}
141
+ onPress={async () => {
142
+ if (await Linking.canOpenURL(link)) {
143
+ await Linking.openURL(link);
144
+ }
145
+ }}
146
+ />
147
+ </View>
148
+ <View style={[style.pl10]}>
149
+ <Tooltip
150
+ isClickable
151
+ placement="left"
152
+ toolTipMessage="Link Copied"
153
+ onPress={() => {
154
+ Clipboard.setString(link);
155
+ }}
156
+ toolTipIcon={
157
+ <>
158
+ <ImageIcon
159
+ iconType="plain"
160
+ name="tick-fill"
161
+ tintColor={$config.SEMANTIC_SUCCESS}
162
+ iconSize={20}
163
+ />
164
+ <Spacer size={8} horizontal={true} />
165
+ </>
166
+ }
167
+ fontSize={12}
168
+ renderContent={() => {
169
+ return (
170
+ <PlatformWrapper>
171
+ {(isHovered: boolean) => (
172
+ <TouchableOpacity
173
+ style={[
174
+ isHovered
175
+ ? style.iconButtonHoverEffect
176
+ : {},
177
+ style.iconShareLink,
178
+ ]}
179
+ onPress={() => {
180
+ Clipboard.setString(link);
181
+ }}>
182
+ <ImageIcon
183
+ iconType="plain"
184
+ name="copy-link"
185
+ iconSize={20}
186
+ tintColor={$config.SECONDARY_ACTION_COLOR}
187
+ />
188
+ </TouchableOpacity>
189
+ )}
190
+ </PlatformWrapper>
191
+ );
192
+ }}
193
+ />
194
+ </View>
195
+ </View>
196
+ ))}
197
+ </View>
198
+ <View style={[style.pl10]}>
199
+ <IconButtonWithToolTip
200
+ hoverEffect={true}
201
+ hoverEffectStyle={style.iconButtonHoverEffect}
202
+ containerStyle={style.iconButton}
203
+ iconProps={{
204
+ name: 'delete',
205
+ iconType: 'plain',
206
+ iconSize: 20,
207
+ tintColor: `${$config.SEMANTIC_ERROR}`,
208
+ }}
209
+ onPress={() => {
210
+ onDeleteAction && onDeleteAction(item.id);
211
+ }}
212
+ />
213
+ </View>
214
+ </View>
215
+ ) : (
216
+ <View style={(style.tactions, {marginTop: 0})}>
217
+ <Text style={style.placeHolder}>No recordings found</Text>
218
+ </View>
219
+ )}
220
+ </View>
221
+ </View>
222
+ {/* ========== CHILDREN ROW ========== */}
223
+ {expanded && (
224
+ <View style={expanedStyles.expandedContainer}>
225
+ <View>
226
+ <Text style={expanedStyles.expandedHeaderText}>STT's</Text>
227
+ </View>
228
+ <View style={expanedStyles.expandedHeaderBody}>
229
+ {status === 'idle' || status === 'pending' ? (
230
+ <Text style={style.ttime}>Fetching text-tracks....</Text>
231
+ ) : status === 'rejected' ? (
232
+ <Text style={style.ttime}>
233
+ {error?.message ||
234
+ 'There was an error while fetching the text-tracks'}
235
+ </Text>
236
+ ) : status === 'resolved' && stts?.length === 0 ? (
237
+ <Text style={style.ttime}>
238
+ There are no STT's for this recording
239
+ </Text>
240
+ ) : (
241
+ <>
242
+ <Text style={style.ttime}>Found {stts.length} text tracks</Text>
243
+ <View>
244
+ {stts.map(item => (
245
+ <TextTrackItemRow
246
+ key={item.id}
247
+ item={item}
248
+ onTextTrackDownload={onTextTrackDownload}
249
+ />
250
+ ))}
251
+ </View>
252
+ </>
253
+ )}
254
+ </View>
255
+ </View>
256
+ )}
257
+ </View>
258
+ );
259
+ }
260
+
261
+ const expanedStyles = StyleSheet.create({
262
+ expandedContainer: {
263
+ display: 'flex',
264
+ flexDirection: 'column',
265
+ gap: 5,
266
+ color: $config.FONT_COLOR,
267
+ borderColor: $config.CARD_LAYER_3_COLOR,
268
+ backgroundColor: $config.CARD_LAYER_2_COLOR,
269
+ paddingHorizontal: 12,
270
+ paddingVertical: 15,
271
+ borderRadius: 5,
272
+ },
273
+ expandedHeaderText: {
274
+ fontSize: 15,
275
+ lineHeight: 32,
276
+ fontWeight: '500',
277
+ color: $config.FONT_COLOR,
278
+ },
279
+ expandedHeaderBody: {
280
+ display: 'flex',
281
+ flexDirection: 'row',
282
+ justifyContent: 'space-between',
283
+ alignItems: 'flex-start',
284
+ },
285
+ });
@@ -1,30 +1,65 @@
1
1
  import React, {useState, useEffect} from 'react';
2
2
  import {View, Text} from 'react-native';
3
- import {style} from './style';
4
- import {RTableHeader, RTableBody, RTableFooter} from './recording-table';
5
- import {useRecording} from '../../subComponents/recording/useRecording';
3
+ import {
4
+ APIStatus,
5
+ FetchRecordingData,
6
+ useRecording,
7
+ } from '../../subComponents/recording/useRecording';
6
8
  import events from '../../rtm-events-api';
7
9
  import {EventNames} from '../../rtm-events';
10
+ import {style, TableBody, TableHeader} from '../common/data-table';
11
+ import Loading from '../../subComponents/Loading';
12
+ import ImageIcon from '../../atoms/ImageIcon';
13
+ import RecordingItemRow from './RecordingItemRow';
14
+ import GenericPopup from '../common/GenericPopup';
15
+ import {downloadS3Link} from '../../utils/common';
16
+
17
+ function EmptyRecordingState() {
18
+ return (
19
+ <View style={style.infotextContainer}>
20
+ <View>
21
+ <ImageIcon
22
+ iconType="plain"
23
+ name="info"
24
+ tintColor={'#777777'}
25
+ iconSize={32}
26
+ />
27
+ </View>
28
+ <View>
29
+ <Text style={[style.infoText, style.pt10, style.pl10]}>
30
+ No recording found for this meeting
31
+ </Text>
32
+ </View>
33
+ </View>
34
+ );
35
+ }
36
+
37
+ const headers = ['', 'Date/Time', 'Duration', 'Actions'];
38
+ const defaultPageNumber = 1;
8
39
 
9
40
  function RecordingsDateTable(props) {
10
- const [state, setState] = React.useState({
41
+ const [state, setState] = React.useState<{
42
+ status: APIStatus;
43
+ data: {
44
+ recordings: FetchRecordingData['recordings'];
45
+ pagination: FetchRecordingData['pagination'];
46
+ };
47
+ error: Error;
48
+ }>({
11
49
  status: 'idle',
12
50
  data: {
13
- pagination: {},
14
51
  recordings: [],
52
+ pagination: {total: 0, limit: 10, page: defaultPageNumber},
15
53
  },
16
54
  error: null,
17
55
  });
18
- const {
19
- status,
20
- data: {pagination, recordings},
21
- error,
22
- } = state;
56
+
57
+ const [currentPage, setCurrentPage] = useState(defaultPageNumber);
23
58
 
24
59
  const {fetchRecordings} = useRecording();
25
60
 
26
- const defaultPageNumber = 1;
27
- const [currentPage, setCurrentPage] = useState(defaultPageNumber);
61
+ // message for any download‐error popup
62
+ const [errorSnack, setErrorSnack] = React.useState<string | undefined>();
28
63
 
29
64
  const onRecordingDeleteCallback = () => {
30
65
  setCurrentPage(defaultPageNumber);
@@ -38,7 +73,7 @@ function RecordingsDateTable(props) {
38
73
  };
39
74
  }, []);
40
75
 
41
- const getRecordings = pageNumber => {
76
+ const getRecordings = (pageNumber: number) => {
42
77
  setState(prev => ({...prev, status: 'pending'}));
43
78
  fetchRecordings(pageNumber).then(
44
79
  response =>
@@ -47,8 +82,13 @@ function RecordingsDateTable(props) {
47
82
  status: 'resolved',
48
83
  data: {
49
84
  recordings: response?.recordings || [],
50
- pagination: response?.pagination || {},
85
+ pagination: response?.pagination || {
86
+ total: 0,
87
+ limit: 10,
88
+ page: defaultPageNumber,
89
+ },
51
90
  },
91
+ error: null,
52
92
  })),
53
93
  error => setState(prev => ({...prev, status: 'rejected', error})),
54
94
  );
@@ -58,26 +98,49 @@ function RecordingsDateTable(props) {
58
98
  getRecordings(currentPage);
59
99
  }, [currentPage]);
60
100
 
61
- if (status === 'rejected') {
101
+ if (state.status === 'rejected') {
62
102
  return (
63
103
  <Text style={[style.ttime, style.pv10, style.ph20]}>
64
- {error?.message}
104
+ {state.error?.message}
65
105
  </Text>
66
106
  );
67
107
  }
108
+ const onTextTrackDownload = (textTrackLink: string) => {
109
+ downloadS3Link(textTrackLink).catch((err: Error) => {
110
+ setErrorSnack(err.message || 'Download failed');
111
+ });
112
+ };
113
+
68
114
  return (
69
115
  <View style={style.ttable}>
70
- <RTableHeader />
71
- <RTableBody
72
- status={status}
73
- recordings={recordings}
74
- onDeleteAction={props?.onDeleteAction}
75
- />
76
- <RTableFooter
77
- currentPage={currentPage}
78
- setCurrentPage={setCurrentPage}
79
- pagination={pagination}
116
+ <TableHeader columns={headers} firstCellStyle={style.thIconCell} />
117
+ <TableBody
118
+ status={state.status}
119
+ items={state.data.recordings}
120
+ loadingComponent={
121
+ <Loading background="transparent" text="Fetching recordingss.." />
122
+ }
123
+ renderRow={item => (
124
+ <RecordingItemRow
125
+ key={item.id}
126
+ item={item}
127
+ onDeleteAction={props?.onDeleteAction}
128
+ onTextTrackDownload={onTextTrackDownload}
129
+ />
130
+ )}
131
+ emptyComponent={<EmptyRecordingState />}
80
132
  />
133
+ {/** ERROR POPUP **/}
134
+ {errorSnack && (
135
+ <GenericPopup
136
+ title="Error"
137
+ variant="error"
138
+ message={errorSnack}
139
+ visible={true}
140
+ setVisible={() => setErrorSnack(undefined)}
141
+ onConfirm={() => setErrorSnack(undefined)}
142
+ />
143
+ )}
81
144
  </View>
82
145
  );
83
146
  }