From cbfa7bf43a811329cb04c16027a694da61fd1607 Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Thu, 3 Mar 2022 18:46:53 -0300 Subject: [PATCH] Chore: Migrate RoomInfoView to Typescript (#3778) * Chore: Migrate RoomInfoView to Typescript * tweak in avatar * tweak with SubscriptionType * minor tweak package * Chore: Migrate RoomInfoView to Typescript * tweak in avatar * tweak with SubscriptionType * minor tweak package * react.reactelement | null * minor tweak * minor tweak livechatvisitor * remove console.log * Tweaks --- app/containers/Avatar/interfaces.ts | 4 +- app/definitions/ILivechatVisitor.ts | 2 + app/definitions/ISubscription.ts | 2 +- app/definitions/IVisitor.ts | 24 --- app/definitions/rest/v1/omnichannel.ts | 8 +- app/stacks/MasterDetailStack/types.ts | 1 + app/stacks/types.ts | 3 +- app/views/LivechatEditView.tsx | 9 +- .../RoomInfoView/{Channel.js => Channel.tsx} | 12 +- app/views/RoomInfoView/CustomFields.js | 23 --- app/views/RoomInfoView/CustomFields.tsx | 22 +++ .../RoomInfoView/{Direct.js => Direct.tsx} | 29 ++-- app/views/RoomInfoView/{Item.js => Item.tsx} | 25 ++- .../{Livechat.js => Livechat.tsx} | 62 ++++--- app/views/RoomInfoView/Timezone.js | 27 --- app/views/RoomInfoView/Timezone.tsx | 34 ++++ .../RoomInfoView/{index.js => index.tsx} | 159 ++++++++++++------ .../RoomInfoView/{styles.js => styles.ts} | 0 package.json | 3 +- yarn.lock | 5 + 20 files changed, 249 insertions(+), 205 deletions(-) delete mode 100644 app/definitions/IVisitor.ts rename app/views/RoomInfoView/{Channel.js => Channel.tsx} (79%) delete mode 100644 app/views/RoomInfoView/CustomFields.js create mode 100644 app/views/RoomInfoView/CustomFields.tsx rename app/views/RoomInfoView/{Direct.js => Direct.tsx} (60%) rename app/views/RoomInfoView/{Item.js => Item.tsx} (67%) rename app/views/RoomInfoView/{Livechat.js => Livechat.tsx} (55%) delete mode 100644 app/views/RoomInfoView/Timezone.js create mode 100644 app/views/RoomInfoView/Timezone.tsx rename app/views/RoomInfoView/{index.js => index.tsx} (70%) rename app/views/RoomInfoView/{styles.js => styles.ts} (100%) diff --git a/app/containers/Avatar/interfaces.ts b/app/containers/Avatar/interfaces.ts index ddec5b276..3bc5dd85e 100644 --- a/app/containers/Avatar/interfaces.ts +++ b/app/containers/Avatar/interfaces.ts @@ -1,3 +1,5 @@ +import React from 'react'; + import { TGetCustomEmoji } from '../../definitions/IEmoji'; export interface IAvatar { @@ -9,7 +11,7 @@ export interface IAvatar { size?: number; borderRadius?: number; type?: string; - children?: JSX.Element; + children?: React.ReactElement | null; user?: { id?: string; token?: string; diff --git a/app/definitions/ILivechatVisitor.ts b/app/definitions/ILivechatVisitor.ts index 847d3b26c..8a942909a 100644 --- a/app/definitions/ILivechatVisitor.ts +++ b/app/definitions/ILivechatVisitor.ts @@ -32,6 +32,8 @@ export interface ILivechatVisitor extends IRocketChatRecord { ip?: string; host?: string; visitorEmails?: IVisitorEmail[]; + livechatData?: any; + utc?: number; } export interface ILivechatVisitorDTO { diff --git a/app/definitions/ISubscription.ts b/app/definitions/ISubscription.ts index dee63ecb5..1332fc442 100644 --- a/app/definitions/ISubscription.ts +++ b/app/definitions/ISubscription.ts @@ -38,7 +38,7 @@ export interface ISubscription { _updatedAt?: string; // from server v?: IVisitor; f: boolean; - t: string; // TODO: we need to review this type later + t: SubscriptionType; // TODO: we need to review this type later ts: string | Date; ls: Date; name: string; diff --git a/app/definitions/IVisitor.ts b/app/definitions/IVisitor.ts deleted file mode 100644 index 637513567..000000000 --- a/app/definitions/IVisitor.ts +++ /dev/null @@ -1,24 +0,0 @@ -export interface IVisitorEmail { - address: string; -} - -export interface IVisitorPhone { - phoneNumber: string; -} - -export interface IVisitor { - _id?: string; - token: string; - username: string; - updatedAt?: Date; - name: string; - department?: string; - phone?: IVisitorPhone[]; - visitorEmails?: IVisitorEmail[]; - customFields?: { - [key: string]: any; - }; - livechatData: { - [key: string]: any; - }; -} diff --git a/app/definitions/rest/v1/omnichannel.ts b/app/definitions/rest/v1/omnichannel.ts index 3a7d6bcec..7b5dcb64d 100644 --- a/app/definitions/rest/v1/omnichannel.ts +++ b/app/definitions/rest/v1/omnichannel.ts @@ -20,11 +20,7 @@ export type OmnichannelEndpoints = { }; 'livechat/visitors.info': { GET: (params: { visitorId: string }) => { - visitor: { - visitorEmails: Array<{ - address: string; - }>; - }; + visitor: ILivechatVisitor; }; }; 'livechat/room.onHold': { @@ -77,7 +73,7 @@ export type OmnichannelEndpoints = { POST: (params: { upsert: string[]; remove: string[] }) => void; }; 'livechat/department/:departmentId/?includeAgents=false': { - GET: () => PaginatedResult<{ department: ILivechatDepartment[] }>; + GET: () => PaginatedResult<{ department: ILivechatDepartment }>; }; 'livechat/departments.available-by-unit/:id': { GET: (params: PaginatedRequest<{ text: string }>) => PaginatedResult<{ diff --git a/app/stacks/MasterDetailStack/types.ts b/app/stacks/MasterDetailStack/types.ts index 0db458cd9..00fa276a2 100644 --- a/app/stacks/MasterDetailStack/types.ts +++ b/app/stacks/MasterDetailStack/types.ts @@ -39,6 +39,7 @@ export type ModalStackParamList = { member: any; rid: string; t: SubscriptionType; + showCloseModal?: boolean; }; SelectListView: { data: any; diff --git a/app/stacks/types.ts b/app/stacks/types.ts index a1d8dcda6..278f1d55c 100644 --- a/app/stacks/types.ts +++ b/app/stacks/types.ts @@ -53,10 +53,11 @@ export type ChatsStackParamList = { isRadio?: boolean; }; RoomInfoView: { - room: ISubscription; + room?: ISubscription; member: any; rid: string; t: SubscriptionType; + showCloseModal?: boolean; }; RoomInfoEditView: { rid: string; diff --git a/app/views/LivechatEditView.tsx b/app/views/LivechatEditView.tsx index 426ae7fb7..842dfc768 100644 --- a/app/views/LivechatEditView.tsx +++ b/app/views/LivechatEditView.tsx @@ -18,7 +18,7 @@ import { getUserSelector } from '../selectors/login'; import Button from '../containers/Button'; import SafeAreaView from '../containers/SafeAreaView'; import { MultiSelect } from '../containers/UIKit/MultiSelect'; -import { IVisitor } from '../definitions/IVisitor'; +import { ILivechatVisitor } from '../definitions/ILivechatVisitor'; import { ITagsOmnichannel } from '../definitions/ITagsOmnichannel'; import { IApplicationState, ISubscription } from '../definitions'; import { ChatsStackParamList } from '../stacks/types'; @@ -55,15 +55,18 @@ interface IField { } interface IInputs { - [key: string]: string | string[] | undefined; + livechatData: { + [key: string]: any; + }; name: string; email: string; phone?: string; topic: string; tag: string[]; + [key: string]: any; } -type TParams = IVisitor & IInputs; +type TParams = ILivechatVisitor & IInputs; interface ILivechat extends ISubscription { // Param dynamic depends on server diff --git a/app/views/RoomInfoView/Channel.js b/app/views/RoomInfoView/Channel.tsx similarity index 79% rename from app/views/RoomInfoView/Channel.js rename to app/views/RoomInfoView/Channel.tsx index ea4a584c7..3a1a03442 100644 --- a/app/views/RoomInfoView/Channel.js +++ b/app/views/RoomInfoView/Channel.tsx @@ -1,43 +1,35 @@ import React from 'react'; -import PropTypes from 'prop-types'; import I18n from '../../i18n'; +import { ISubscription } from '../../definitions'; import Item from './Item'; -const Channel = ({ room, theme }) => { +const Channel = ({ room }: { room: ISubscription }) => { const { description, topic, announcement } = room; return ( <> ); }; -Channel.propTypes = { - room: PropTypes.object, - theme: PropTypes.string -}; export default Channel; diff --git a/app/views/RoomInfoView/CustomFields.js b/app/views/RoomInfoView/CustomFields.js deleted file mode 100644 index 777cfdf80..000000000 --- a/app/views/RoomInfoView/CustomFields.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import Item from './Item'; - -const CustomFields = ({ customFields, theme }) => { - if (customFields) { - return Object.keys(customFields).map(title => { - if (!customFields[title]) { - return; - } - return ; - }); - } - - return null; -}; -CustomFields.propTypes = { - customFields: PropTypes.object, - theme: PropTypes.string -}; - -export default CustomFields; diff --git a/app/views/RoomInfoView/CustomFields.tsx b/app/views/RoomInfoView/CustomFields.tsx new file mode 100644 index 000000000..0034261bd --- /dev/null +++ b/app/views/RoomInfoView/CustomFields.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +import Item from './Item'; + +const CustomFields = ({ customFields }: { customFields: { [key: string]: string } }) => { + if (customFields) { + return ( + <> + {Object.keys(customFields).map((title: string) => { + if (!customFields[title]) { + return null; + } + return ; + })} + + ); + } + + return null; +}; + +export default CustomFields; diff --git a/app/views/RoomInfoView/Direct.js b/app/views/RoomInfoView/Direct.tsx similarity index 60% rename from app/views/RoomInfoView/Direct.js rename to app/views/RoomInfoView/Direct.tsx index cc6d8f393..b9e9e0da7 100644 --- a/app/views/RoomInfoView/Direct.js +++ b/app/views/RoomInfoView/Direct.tsx @@ -1,15 +1,17 @@ import React from 'react'; import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; +import { useTheme } from '../../theme'; import Timezone from './Timezone'; import CustomFields from './CustomFields'; import styles from './styles'; -const Roles = ({ roles, theme }) => - roles && roles.length ? ( +const Roles = ({ roles }: { roles: string[] }) => { + const { theme } = useTheme(); + + if (roles && roles.length) { {I18n.t('Roles')} @@ -21,23 +23,18 @@ const Roles = ({ roles, theme }) => ) : null )} - - ) : null; -Roles.propTypes = { - roles: PropTypes.array, - theme: PropTypes.string + ; + } + + return null; }; -const Direct = ({ roomUser, theme }) => ( +const Direct = ({ roomUser }: { roomUser: any }) => ( <> - - - + + + ); -Direct.propTypes = { - roomUser: PropTypes.object, - theme: PropTypes.string -}; export default Direct; diff --git a/app/views/RoomInfoView/Item.js b/app/views/RoomInfoView/Item.tsx similarity index 67% rename from app/views/RoomInfoView/Item.js rename to app/views/RoomInfoView/Item.tsx index 2b8d19f9d..7296f6542 100644 --- a/app/views/RoomInfoView/Item.js +++ b/app/views/RoomInfoView/Item.tsx @@ -1,25 +1,32 @@ import React from 'react'; import { Text, View } from 'react-native'; -import PropTypes from 'prop-types'; import Markdown from '../../containers/markdown'; import { themes } from '../../constants/colors'; +import { useTheme } from '../../theme'; import styles from './styles'; -const Item = ({ label, content, theme, testID }) => - content ? ( +interface IItem { + label?: string; + content?: string; + testID?: string; +} + +const Item = ({ label, content, testID }: IItem) => { + const { theme } = useTheme(); + + if (!content) { + return null; + } + + return ( {label} - ) : null; -Item.propTypes = { - label: PropTypes.string, - content: PropTypes.string, - theme: PropTypes.string, - testID: PropTypes.string + ); }; export default Item; diff --git a/app/views/RoomInfoView/Livechat.js b/app/views/RoomInfoView/Livechat.tsx similarity index 55% rename from app/views/RoomInfoView/Livechat.js rename to app/views/RoomInfoView/Livechat.tsx index 8635bdddd..ddc630979 100644 --- a/app/views/RoomInfoView/Livechat.js +++ b/app/views/RoomInfoView/Livechat.tsx @@ -1,15 +1,17 @@ import React, { useEffect, useState } from 'react'; import { StyleSheet, Text } from 'react-native'; -import PropTypes from 'prop-types'; import RocketChat from '../../lib/rocketchat'; -import { withTheme } from '../../theme'; +import { useTheme } from '../../theme'; import sharedStyles from '../Styles'; import { themes } from '../../constants/colors'; import I18n from '../../i18n'; +import { ISubscription } from '../../definitions'; +import { ILivechatVisitorModified } from './index'; import CustomFields from './CustomFields'; import Item from './Item'; import Timezone from './Timezone'; +import { ILivechatDepartment } from '../../definitions/ILivechatDepartment'; const styles = StyleSheet.create({ title: { @@ -19,20 +21,19 @@ const styles = StyleSheet.create({ } }); -const Title = ({ title, theme }) => {title}; -Title.propTypes = { - title: PropTypes.string, - theme: PropTypes.string -}; +const Title = ({ title, theme }: { title: string; theme: string }) => ( + {title} +); -const Livechat = ({ room, roomUser, theme }) => { - const [department, setDepartment] = useState({}); +const Livechat = ({ room, roomUser }: { room: ISubscription; roomUser: ILivechatVisitorModified }) => { + const [department, setDepartment] = useState({} as ILivechatDepartment); + const { theme } = useTheme(); - const getDepartment = async id => { + const getDepartment = async (id: string) => { if (id) { const result = await RocketChat.getDepartmentInfo(id); if (result.success) { - setDepartment(result.department); + setDepartment(result.department as ILivechatDepartment); } } }; @@ -50,37 +51,34 @@ const Livechat = ({ room, roomUser, theme }) => { return ( <> - <Timezone utcOffset={roomUser.utc} theme={theme} /> - <Item label={I18n.t('Username')} content={roomUser.username} theme={theme} /> + <Timezone utcOffset={roomUser.utc} /> + <Item label={I18n.t('Username')} content={roomUser.username} /> <Item label={I18n.t('Email')} content={roomUser.visitorEmails?.map(email => email.address).reduce((ret, item) => `${ret}${item}\n`)} - theme={theme} /> <Item label={I18n.t('Phone')} content={roomUser.phone?.map(phone => phone.phoneNumber).reduce((ret, item) => `${ret}${item}\n`)} - theme={theme} /> - <Item label={I18n.t('IP')} content={roomUser.ip} theme={theme} /> - <Item label={I18n.t('OS')} content={roomUser.os} theme={theme} /> - <Item label={I18n.t('Browser')} content={roomUser.browser} theme={theme} /> - <CustomFields customFields={roomUser.livechatData} theme={theme} /> + <Item label={I18n.t('IP')} content={roomUser.ip} /> + <Item label={I18n.t('OS')} content={roomUser.os} /> + <Item label={I18n.t('Browser')} content={roomUser.browser} /> + <CustomFields customFields={roomUser.livechatData} /> <Title title={I18n.t('Conversation')} theme={theme} /> - <Item label={I18n.t('Agent')} content={room.servedBy?.username} theme={theme} /> - <Item label={I18n.t('Facebook')} content={room.facebook?.page.name} theme={theme} /> - <Item label={I18n.t('SMS')} content={room.sms && 'SMS Enabled'} theme={theme} /> - <Item label={I18n.t('Topic')} content={room.topic} theme={theme} /> - <Item label={I18n.t('Tags')} content={room.tags?.join(', ')} theme={theme} /> - <Item label={I18n.t('Department')} content={department.name} theme={theme} /> - <CustomFields customFields={room.livechatData} theme={theme} /> + <Item label={I18n.t('Agent')} content={room.servedBy?.username} /> + {/* TODO: Will be deprecated */} + {/* @ts-ignore */} + <Item label={I18n.t('Facebook')} content={room.facebook?.page.name} /> + {/* TODO: Will be deprecated */} + {/* @ts-ignore */} + <Item label={I18n.t('SMS')} content={room.sms && 'SMS Enabled'} /> + <Item label={I18n.t('Topic')} content={room.topic} /> + <Item label={I18n.t('Tags')} content={room.tags?.join(', ')} /> + <Item label={I18n.t('Department')} content={department.name} /> + <CustomFields customFields={room.livechatData} /> </> ); }; -Livechat.propTypes = { - room: PropTypes.object, - roomUser: PropTypes.object, - theme: PropTypes.string -}; -export default withTheme(Livechat); +export default Livechat; diff --git a/app/views/RoomInfoView/Timezone.js b/app/views/RoomInfoView/Timezone.js deleted file mode 100644 index e40c4c23b..000000000 --- a/app/views/RoomInfoView/Timezone.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import moment from 'moment'; - -import I18n from '../../i18n'; -import Item from './Item'; - -const Timezone = ({ utcOffset, Message_TimeFormat, theme }) => - utcOffset ? ( - <Item - label={I18n.t('Timezone')} - content={`${moment().utcOffset(utcOffset).format(Message_TimeFormat)} (UTC ${utcOffset})`} - theme={theme} - /> - ) : null; -Timezone.propTypes = { - utcOffset: PropTypes.number, - Message_TimeFormat: PropTypes.string, - theme: PropTypes.string -}; - -const mapStateToProps = state => ({ - Message_TimeFormat: state.settings.Message_TimeFormat -}); - -export default connect(mapStateToProps)(Timezone); diff --git a/app/views/RoomInfoView/Timezone.tsx b/app/views/RoomInfoView/Timezone.tsx new file mode 100644 index 000000000..cf2e68f9c --- /dev/null +++ b/app/views/RoomInfoView/Timezone.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import moment from 'moment'; + +import { IApplicationState } from '../../definitions'; +import I18n from '../../i18n'; +import Item from './Item'; +import { TSettingsValues } from '../../reducers/settings'; + +interface ITimezone { + utcOffset?: number; + Message_TimeFormat?: TSettingsValues; +} + +const Timezone = ({ utcOffset, Message_TimeFormat }: ITimezone) => { + if (!utcOffset) { + return null; + } + + return ( + <Item + label={I18n.t('Timezone')} + content={`${moment() + .utcOffset(utcOffset) + .format(Message_TimeFormat as string)} (UTC ${utcOffset})`} + /> + ); +}; + +const mapStateToProps = (state: IApplicationState) => ({ + Message_TimeFormat: state.settings.Message_TimeFormat +}); + +export default connect(mapStateToProps)(Timezone); diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.tsx similarity index 70% rename from app/views/RoomInfoView/index.js rename to app/views/RoomInfoView/index.tsx index 6c677e3cb..1266e5741 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.tsx @@ -1,10 +1,12 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { ScrollView, Text, View } from 'react-native'; import { BorderlessButton } from 'react-native-gesture-handler'; import { connect } from 'react-redux'; import UAParser from 'ua-parser-js'; import isEmpty from 'lodash/isEmpty'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { CompositeNavigationProp, RouteProp } from '@react-navigation/native'; +import { Observable, Subscription } from 'rxjs'; import { CustomIcon } from '../../lib/Icons'; import Status from '../../containers/Status'; @@ -28,9 +30,22 @@ import Livechat from './Livechat'; import Channel from './Channel'; import Direct from './Direct'; import styles from './styles'; +import { ChatsStackParamList } from '../../stacks/types'; +import { MasterDetailInsideStackParamList } from '../../stacks/MasterDetailStack/types'; +import { SubscriptionType, TSubscriptionModel, ISubscription, IUser, IApplicationState } from '../../definitions'; +import { ILivechatVisitor } from '../../definitions/ILivechatVisitor'; -const getRoomTitle = (room, type, name, username, statusText, theme) => - type === 'd' ? ( +interface IGetRoomTitle { + room: ISubscription; + type: SubscriptionType; + name?: string; + username: string; + statusText?: string; + theme: string; +} + +const getRoomTitle = ({ room, type, name, username, statusText, theme }: IGetRoomTitle) => + type === SubscriptionType.DIRECT ? ( <> <Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]}> {name} @@ -60,28 +75,57 @@ const getRoomTitle = (room, type, name, username, statusText, theme) => </View> ); -class RoomInfoView extends React.Component { - static propTypes = { - navigation: PropTypes.object, - route: PropTypes.object, - rooms: PropTypes.array, - theme: PropTypes.string, - isMasterDetail: PropTypes.bool, - jitsiEnabled: PropTypes.bool, - editRoomPermission: PropTypes.array, - editOmnichannelContact: PropTypes.array, - editLivechatRoomCustomfields: PropTypes.array, - roles: PropTypes.array - }; +interface IRoomInfoViewProps { + navigation: CompositeNavigationProp< + StackNavigationProp<ChatsStackParamList, 'RoomInfoView'>, + StackNavigationProp<MasterDetailInsideStackParamList> + >; + route: RouteProp<ChatsStackParamList, 'RoomInfoView'>; + rooms: string[]; + theme: string; + isMasterDetail: boolean; + jitsiEnabled: boolean; + editRoomPermission?: string[]; + editOmnichannelContact?: string[]; + editLivechatRoomCustomfields?: string[]; + roles: { [key: string]: string }; +} - constructor(props) { +interface IUserParsed extends IUser { + parsedRoles?: string[]; +} + +export interface ILivechatVisitorModified extends ILivechatVisitor { + os?: string; + browser?: string; +} + +interface IRoomInfoViewState { + room: ISubscription; + // TODO: Could be IUserParsed or ILivechatVisitorModified + roomUser: any; + showEdit: boolean; +} + +class RoomInfoView extends React.Component<IRoomInfoViewProps, IRoomInfoViewState> { + private rid: string; + + private t: SubscriptionType; + + private unsubscribeFocus?: () => void; + + private subscription?: Subscription; + + private roomObservable?: Observable<TSubscriptionModel>; + + constructor(props: IRoomInfoViewProps) { super(props); const room = props.route.params?.room; const roomUser = props.route.params?.member; this.rid = props.route.params?.rid; this.t = props.route.params?.t; this.state = { - room: room || { rid: this.rid, t: this.t }, + room: (room || { rid: this.rid, t: this.t }) as any, roomUser: roomUser || {}, showEdit: false }; @@ -120,14 +164,14 @@ class RoomInfoView extends React.Component { const showCloseModal = route.params?.showCloseModal; navigation.setOptions({ headerLeft: showCloseModal ? () => <HeaderButton.CloseModal navigation={navigation} /> : undefined, - title: t === 'd' ? I18n.t('User_Info') : I18n.t('Room_Info'), + title: t === SubscriptionType.DIRECT ? I18n.t('User_Info') : I18n.t('Room_Info'), headerRight: showEdit ? () => ( <HeaderButton.Container> <HeaderButton.Item iconName='edit' onPress={() => { - const isLivechat = t === 'l'; + const isLivechat = t === SubscriptionType.OMNICHANNEL; logEvent(events[`RI_GO_${isLivechat ? 'LIVECHAT' : 'RI'}_EDIT`]); navigation.navigate(isLivechat ? 'LivechatEditView' : 'RoomInfoEditView', { rid, room, roomUser }); }} @@ -135,21 +179,21 @@ class RoomInfoView extends React.Component { /> </HeaderButton.Container> ) - : null + : undefined }); }; get isDirect() { const { room } = this.state; - return room.t === 'd'; + return room.t === SubscriptionType.DIRECT; } get isLivechat() { const { room } = this.state; - return room.t === 'l'; + return room.t === SubscriptionType.OMNICHANNEL; } - getRoleDescription = id => { + getRoleDescription = (id: string) => { const { roles } = this.props; return roles[id]; }; @@ -157,23 +201,26 @@ class RoomInfoView extends React.Component { loadVisitor = async () => { const { room } = this.state; try { - const result = await RocketChat.getVisitorInfo(room?.visitor?._id); - if (result.success) { - const { visitor } = result; - if (visitor.userAgent) { - const ua = new UAParser(); - ua.setUA(visitor.userAgent); - visitor.os = `${ua.getOS().name} ${ua.getOS().version}`; - visitor.browser = `${ua.getBrowser().name} ${ua.getBrowser().version}`; + if (room.visitor?._id) { + const result = await RocketChat.getVisitorInfo(room.visitor._id); + if (result.success) { + const { visitor } = result; + const params: { os?: string; browser?: string } = {}; + if (visitor.userAgent) { + const ua = new UAParser(); + ua.setUA(visitor.userAgent); + params.os = `${ua.getOS().name} ${ua.getOS().version}`; + params.browser = `${ua.getBrowser().name} ${ua.getBrowser().version}`; + } + this.setState({ roomUser: { ...visitor, ...params } }, () => this.setHeader()); } - this.setState({ roomUser: visitor }, () => this.setHeader()); } } catch (error) { // Do nothing } }; - parseRoles = roleArray => + parseRoles = (roleArray: string[]) => Promise.all( roleArray.map(async role => { const description = await this.getRoleDescription(role); @@ -191,11 +238,12 @@ class RoomInfoView extends React.Component { if (result.success) { const { user } = result; const { roles } = user; + const parsedRoles: { parsedRoles?: string[] } = {}; if (roles && roles.length) { - user.parsedRoles = await this.parseRoles(roles); + parsedRoles.parsedRoles = await this.parseRoles(roles); } - this.setState({ roomUser: user }); + this.setState({ roomUser: { ...user, ...parsedRoles } }); } } catch { // do nothing @@ -218,9 +266,10 @@ class RoomInfoView extends React.Component { loadRoom = async () => { const { room: roomState } = this.state; const { route, editRoomPermission, editOmnichannelContact, editLivechatRoomCustomfields } = this.props; - let room = route.params?.room; - if (room && room.observe) { - this.roomObservable = room.observe(); + let room = route.params?.room as any; + const roomModel = room as TSubscriptionModel; + if (roomModel && roomModel.observe) { + this.roomObservable = roomModel.observe(); this.subscription = this.roomObservable.subscribe(changes => { this.setState({ room: changes }, () => this.setHeader()); }); @@ -245,7 +294,7 @@ class RoomInfoView extends React.Component { }; createDirect = () => - new Promise(async (resolve, reject) => { + new Promise<void>(async (resolve, reject) => { const { route } = this.props; // We don't need to create a direct @@ -309,12 +358,12 @@ class RoomInfoView extends React.Component { RocketChat.callJitsi(room); }; - renderAvatar = (room, roomUser) => { + renderAvatar = (room: ISubscription, roomUser: IUserParsed) => { const { theme } = this.props; return ( <Avatar text={room.name || roomUser.username} style={styles.avatar} type={this.t} size={100} rid={room?.rid}> - {this.t === 'd' && roomUser._id ? ( + {this.t === SubscriptionType.DIRECT && roomUser._id ? ( <View style={[sharedStyles.status, { backgroundColor: themes[theme].auxiliaryBackground }]}> <Status size={20} id={roomUser._id} /> </View> @@ -323,7 +372,7 @@ class RoomInfoView extends React.Component { ); }; - renderButton = (onPress, iconName, text) => { + renderButton = (onPress: () => void, iconName: string, text: string) => { const { theme } = this.props; const onActionPress = async () => { @@ -359,14 +408,15 @@ class RoomInfoView extends React.Component { renderContent = () => { const { room, roomUser } = this.state; - const { theme } = this.props; if (this.isDirect) { - return <Direct roomUser={roomUser} theme={theme} />; - } else if (this.t === 'l') { - return <Livechat room={room} roomUser={roomUser} theme={theme} />; + return <Direct roomUser={roomUser} />; } - return <Channel room={room} theme={theme} />; + + if (this.t === SubscriptionType.OMNICHANNEL) { + return <Livechat room={room} roomUser={roomUser} />; + } + return <Channel room={room} />; }; render() { @@ -379,7 +429,14 @@ class RoomInfoView extends React.Component { <View style={[styles.avatarContainer, { backgroundColor: themes[theme].auxiliaryBackground }]}> {this.renderAvatar(room, roomUser)} <View style={styles.roomTitleContainer}> - {getRoomTitle(room, this.t, roomUser?.name, roomUser?.username, roomUser?.statusText, theme)} + {getRoomTitle({ + room, + type: this.t, + name: roomUser?.name, + username: roomUser?.username, + statusText: roomUser?.statusText, + theme + })} </View> {this.renderButtons()} </View> @@ -390,10 +447,10 @@ class RoomInfoView extends React.Component { } } -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ rooms: state.room.rooms, isMasterDetail: state.app.isMasterDetail, - jitsiEnabled: state.settings.Jitsi_Enabled || false, + jitsiEnabled: (state.settings.Jitsi_Enabled as boolean) || false, editRoomPermission: state.permissions['edit-room'], editOmnichannelContact: state.permissions['edit-omnichannel-contact'], editLivechatRoomCustomfields: state.permissions['edit-livechat-room-customfields'], diff --git a/app/views/RoomInfoView/styles.js b/app/views/RoomInfoView/styles.ts similarity index 100% rename from app/views/RoomInfoView/styles.js rename to app/views/RoomInfoView/styles.ts diff --git a/package.json b/package.json index f3fc666cc..7bda1c04b 100644 --- a/package.json +++ b/package.json @@ -143,8 +143,8 @@ "@rocket.chat/eslint-config": "^0.4.0", "@storybook/addon-storyshots": "5.3.21", "@storybook/react-native": "5.3.25", - "@types/bytebuffer": "^5.0.43", "@testing-library/react-native": "^9.0.0", + "@types/bytebuffer": "^5.0.43", "@types/ejson": "^2.1.3", "@types/jest": "^26.0.24", "@types/lodash": "^4.14.171", @@ -157,6 +157,7 @@ "@types/react-redux": "^7.1.18", "@types/react-test-renderer": "^17.0.1", "@types/semver": "^7.3.9", + "@types/ua-parser-js": "^0.7.36", "@types/url-parse": "^1.4.6", "@typescript-eslint/eslint-plugin": "^4.28.3", "@typescript-eslint/parser": "^4.28.5", diff --git a/yarn.lock b/yarn.lock index 69ec7d91c..8b576f36e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4534,6 +4534,11 @@ resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== +"@types/ua-parser-js@^0.7.36": + version "0.7.36" + resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190" + integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ== + "@types/uglify-js@*": version "3.9.2" resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.9.2.tgz#01992579debba674e1e359cd6bcb1a1d0ab2e02b"