From 0289ba716d71b0fd8c0d58efd088c34950a5390f Mon Sep 17 00:00:00 2001 From: Reinaldo Neto <47038980+reinaldonetof@users.noreply.github.com> Date: Mon, 7 Feb 2022 12:18:37 -0300 Subject: [PATCH] Chore: Migrate LivechatEditView to Typescript (#3499) * Chore: Migrate LivechatEditView to Typescript * refactor: minor tweak * refactor: fix the interfaces for input * refactor: fix lint erros * minor tweak with new navigation types * function * iroom tweak * livechateditview tweak * TextInput tweak * refactor: update new types and interfaces for use ISubscription * refactor to default useState type * change the component name in SearchBox * changed state type Co-authored-by: AlexAlexandre Co-authored-by: Gerzon Z Co-authored-by: Diego Mello --- app/containers/SearchBox.tsx | 12 +- app/containers/TextInput.tsx | 4 +- app/containers/UIKit/MultiSelect/index.tsx | 4 +- app/definitions/ITagsOmnichannel.ts | 5 + app/definitions/IVisitor.ts | 24 +++ app/presentation/TextInput.tsx | 2 +- app/views/E2EEnterYourPasswordView.tsx | 6 +- ...vechatEditView.js => LivechatEditView.tsx} | 150 ++++++++++++------ 8 files changed, 146 insertions(+), 61 deletions(-) create mode 100644 app/definitions/ITagsOmnichannel.ts create mode 100644 app/definitions/IVisitor.ts rename app/views/{LivechatEditView.js => LivechatEditView.tsx} (67%) diff --git a/app/containers/SearchBox.tsx b/app/containers/SearchBox.tsx index 9ab236eb0..d30923e82 100644 --- a/app/containers/SearchBox.tsx +++ b/app/containers/SearchBox.tsx @@ -1,5 +1,13 @@ import React from 'react'; -import { NativeSyntheticEvent, StyleSheet, Text, TextInputFocusEventData, TextInputProps, View } from 'react-native'; +import { + NativeSyntheticEvent, + StyleSheet, + TextInput as RNTextInput, + Text, + TextInputFocusEventData, + TextInputProps, + View +} from 'react-native'; import Touchable from 'react-native-platform-touchable'; import TextInput from '../presentation/TextInput'; @@ -51,7 +59,7 @@ interface ISearchBox { hasCancel?: boolean; onCancelPress?: Function; theme?: string; - inputRef?: React.Ref; + inputRef?: React.Ref; testID?: string; onFocus?: (e: NativeSyntheticEvent) => void; } diff --git a/app/containers/TextInput.tsx b/app/containers/TextInput.tsx index 76cb1765c..f829bb909 100644 --- a/app/containers/TextInput.tsx +++ b/app/containers/TextInput.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { StyleProp, StyleSheet, Text, TextInputProps, TextStyle, View, ViewStyle } from 'react-native'; +import { StyleProp, StyleSheet, Text, TextInputProps, TextInput as RNTextInput, TextStyle, View, ViewStyle } from 'react-native'; import Touchable from 'react-native-platform-touchable'; import sharedStyles from '../views/Styles'; @@ -59,7 +59,7 @@ export interface IRCTextInputProps extends TextInputProps { loading?: boolean; containerStyle?: StyleProp; inputStyle?: StyleProp; - inputRef?: React.Ref; + inputRef?: React.Ref; testID?: string; iconLeft?: string; iconRight?: string; diff --git a/app/containers/UIKit/MultiSelect/index.tsx b/app/containers/UIKit/MultiSelect/index.tsx index d50dede31..bedac3257 100644 --- a/app/containers/UIKit/MultiSelect/index.tsx +++ b/app/containers/UIKit/MultiSelect/index.tsx @@ -22,8 +22,8 @@ interface IMultiSelect { context?: number; loading?: boolean; multiselect?: boolean; - onSearch: Function; - onClose: Function; + onSearch?: () => void; + onClose?: () => void; inputStyle: object; value?: any[]; disabled?: boolean | object; diff --git a/app/definitions/ITagsOmnichannel.ts b/app/definitions/ITagsOmnichannel.ts new file mode 100644 index 000000000..d0bb2c9b5 --- /dev/null +++ b/app/definitions/ITagsOmnichannel.ts @@ -0,0 +1,5 @@ +export interface ITagsOmnichannel { + _id: string; + name: string; + departments: string[]; +} diff --git a/app/definitions/IVisitor.ts b/app/definitions/IVisitor.ts new file mode 100644 index 000000000..637513567 --- /dev/null +++ b/app/definitions/IVisitor.ts @@ -0,0 +1,24 @@ +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/presentation/TextInput.tsx b/app/presentation/TextInput.tsx index 4d575fa8f..6b6e61a9e 100644 --- a/app/presentation/TextInput.tsx +++ b/app/presentation/TextInput.tsx @@ -15,7 +15,7 @@ interface IThemedTextInput extends IRCTextInputProps { theme: string; } -const ThemedTextInput = React.forwardRef(({ style, theme, ...props }: IThemedTextInput, ref: any) => ( +const ThemedTextInput = React.forwardRef(({ style, theme, ...props }, ref) => ( ; class E2EEnterYourPasswordView extends React.Component { - private passwordInput?: TextInput; + private passwordInput?: RNTextInput; static navigationOptions = ({ navigation }: Pick): StackNavigationOptions => ({ headerLeft: () => , @@ -76,7 +76,7 @@ class E2EEnterYourPasswordView extends React.Component { + inputRef={(e: RNTextInput) => { this.passwordInput = e; }} placeholder={I18n.t('Password')} diff --git a/app/views/LivechatEditView.js b/app/views/LivechatEditView.tsx similarity index 67% rename from app/views/LivechatEditView.js rename to app/views/LivechatEditView.tsx index 8a6108647..c77bc3999 100644 --- a/app/views/LivechatEditView.js +++ b/app/views/LivechatEditView.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; -import PropTypes from 'prop-types'; -import { ScrollView, StyleSheet, Text } from 'react-native'; +import { StackNavigationProp } from '@react-navigation/stack'; +import { RouteProp } from '@react-navigation/native'; +import { ScrollView, StyleSheet, Text, TextInput as RNTextInput } from 'react-native'; import { connect } from 'react-redux'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; @@ -10,7 +11,6 @@ import TextInput from '../containers/TextInput'; import KeyboardView from '../presentation/KeyboardView'; import RocketChat from '../lib/rocketchat'; import I18n from '../i18n'; - import { LISTENER } from '../containers/Toast'; import EventEmitter from '../utils/events'; import scrollPersistTaps from '../utils/scrollPersistTaps'; @@ -18,6 +18,10 @@ 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 { ITagsOmnichannel } from '../definitions/ITagsOmnichannel'; +import { IApplicationState, ISubscription } from '../definitions'; +import { ChatsStackParamList } from '../stacks/types'; import sharedStyles from './Styles'; const styles = StyleSheet.create({ @@ -39,36 +43,87 @@ const styles = StyleSheet.create({ } }); -const Title = ({ title, theme }) => - title ? {title} : null; -Title.propTypes = { - title: PropTypes.string, - theme: PropTypes.string -}; +interface ITitle { + title: string; + theme: string; +} -const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelContact, editLivechatRoomCustomfields }) => { - const [customFields, setCustomFields] = useState({}); - const [availableUserTags, setAvailableUserTags] = useState([]); +interface IField { + _id: string; + visibility: string; + scope: string; +} + +interface IInputs { + [key: string]: string | string[] | undefined; + name: string; + email: string; + phone?: string; + topic: string; + tag: string[]; +} + +type TParams = IVisitor & IInputs; + +interface ILivechat extends ISubscription { + // Param dynamic depends on server + sms?: string; +} + +interface IInputsRefs { + [index: string]: RNTextInput | null; + name: RNTextInput | null; + phone: RNTextInput | null; + topic: RNTextInput | null; +} + +interface ICustomFields { + visitor?: { [key: string]: string }; + livechat?: { [key: string]: string }; +} +interface ILivechatEditViewProps { + // TODO: Refactor when migrate models + user: any; + navigation: StackNavigationProp; + route: RouteProp; + theme: string; + editOmnichannelContact: string[]; + editLivechatRoomCustomfields: string[]; +} + +const Title = ({ title, theme }: ITitle) => + title ? {title} : null; + +const LivechatEditView = ({ + user, + navigation, + route, + theme, + editOmnichannelContact, + editLivechatRoomCustomfields +}: ILivechatEditViewProps) => { + const [customFields, setCustomFields] = useState({}); + const [availableUserTags, setAvailableUserTags] = useState([]); const [permissions, setPermissions] = useState([]); - const params = {}; - const inputs = {}; + const params = {} as TParams; + const inputs = {} as IInputsRefs; - const livechat = route.params?.room ?? {}; + const livechat = (route.params?.room ?? {}) as ILivechat; const visitor = route.params?.roomUser ?? {}; const getCustomFields = async () => { const result = await RocketChat.getCustomFields(); if (result.success && result.customFields?.length) { const visitorCustomFields = result.customFields - .filter(field => field.visibility !== 'hidden' && field.scope === 'visitor') - .map(field => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' })) - .reduce((ret, field) => ({ ...field, ...ret })); + .filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'visitor') + .map((field: IField) => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' })) + .reduce((ret: IField, field: IField) => ({ ...field, ...ret })); const livechatCustomFields = result.customFields - .filter(field => field.visibility !== 'hidden' && field.scope === 'room') - .map(field => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' })) - .reduce((ret, field) => ({ ...field, ...ret })); + .filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'room') + .map((field: IField) => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' })) + .reduce((ret: IField, field: IField) => ({ ...field, ...ret })); return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields }); } @@ -83,8 +138,8 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta setTags(uniqueArray); }, [availableUserTags]); - const getTagsList = async agentDepartments => { - const tags = await RocketChat.getTagsList(); + const getTagsList = async (agentDepartments: string[]) => { + const tags: ITagsOmnichannel[] = await RocketChat.getTagsList(); const isAdmin = ['admin', 'livechat-manager'].find(role => user.roles.includes(role)); const availableTags = tags .filter(({ departments }) => isAdmin || departments.length === 0 || departments.some(i => agentDepartments.indexOf(i) > -1)) @@ -95,16 +150,18 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta const getAgentDepartments = async () => { const result = await RocketChat.getAgentDepartments(visitor?._id); if (result.success) { - const agentDepartments = result.departments.map(dept => dept.departmentId); + const agentDepartments = result.departments.map((dept: { departmentId: string }) => dept.departmentId); getTagsList(agentDepartments); } }; const submit = async () => { - const userData = { _id: visitor?._id }; + const userData = { _id: visitor?._id } as TParams; - const { rid, sms } = livechat; - const roomData = { _id: rid }; + const { rid } = livechat; + const sms = livechat?.sms; + + const roomData = { _id: rid } as TParams; if (params.name) { userData.name = params.name; @@ -149,7 +206,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta } }; - const onChangeText = (key, text) => { + const onChangeText = (key: string, text: string) => { params[key] = text; }; @@ -159,6 +216,9 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta }; useEffect(() => { + navigation.setOptions({ + title: I18n.t('Edit') + }); getAgentDepartments(); getCustomFields(); getPermissions(); @@ -177,7 +237,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta defaultValue={visitor?.name} onChangeText={text => onChangeText('name', text)} onSubmitEditing={() => { - inputs.name.focus(); + inputs.name?.focus(); }} theme={theme} editable={!!permissions[0]} @@ -190,7 +250,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta defaultValue={visitor?.visitorEmails && visitor?.visitorEmails[0]?.address} onChangeText={text => onChangeText('email', text)} onSubmitEditing={() => { - inputs.phone.focus(); + inputs.phone?.focus(); }} theme={theme} editable={!!permissions[0]} @@ -206,9 +266,9 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta const keys = Object.keys(customFields?.visitor || {}); if (keys.length > 0) { const key = keys[0]; - inputs[key].focus(); + inputs[key]?.focus(); } else { - inputs.topic.focus(); + inputs.topic?.focus(); } }} theme={theme} @@ -224,9 +284,9 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta onChangeText={text => onChangeText(key, text)} onSubmitEditing={() => { if (array.length - 1 > index) { - return inputs[array[index + 1][0]].focus(); + return inputs[array[index + 1][0]]?.focus(); } - inputs.topic.focus(); + inputs.topic?.focus(); }} theme={theme} editable={!!permissions[0]} @@ -240,15 +300,14 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta }} defaultValue={livechat?.topic} onChangeText={text => onChangeText('topic', text)} - onSubmitEditing={() => inputs.tags.focus()} theme={theme} editable={!!permissions[1]} /> {I18n.t('Tags')} ({ text: { text: tag }, value: tag }))} - onChange={({ value }) => { + options={tagParam.map((tag: string) => ({ text: { text: tag }, value: tag }))} + onChange={({ value }: { value: string[] }) => { setTagParamSelected([...value]); }} placeholder={{ text: I18n.t('Tags') }} @@ -260,7 +319,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta inputStyle={styles.multiSelect} /> - {Object.entries(customFields?.livechat || {}).map(([key, value], index, array) => ( + {Object.entries(customFields?.livechat || {}).map(([key, value], index, array: any) => ( onChangeText(key, text)} onSubmitEditing={() => { if (array.length - 1 > index) { - return inputs[array[index + 1]].focus(); + return inputs[array[index + 1]]?.focus(); } submit(); }} @@ -285,19 +344,8 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta ); }; -LivechatEditView.propTypes = { - user: PropTypes.object, - navigation: PropTypes.object, - route: PropTypes.object, - theme: PropTypes.string, - editOmnichannelContact: PropTypes.array, - editLivechatRoomCustomfields: PropTypes.array -}; -LivechatEditView.navigationOptions = { - title: I18n.t('Edit') -}; -const mapStateToProps = state => ({ +const mapStateToProps = (state: IApplicationState) => ({ server: state.server.server, user: getUserSelector(state), editOmnichannelContact: state.permissions['edit-omnichannel-contact'],