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 <alexalexandrejr@gmail.com>
Co-authored-by: Gerzon Z <gerzonc@icloud.com>
Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Reinaldo Neto 2022-02-07 12:18:37 -03:00 committed by GitHub
parent 6933278fd5
commit 0289ba716d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 61 deletions

View File

@ -1,5 +1,13 @@
import React from 'react'; 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 Touchable from 'react-native-platform-touchable';
import TextInput from '../presentation/TextInput'; import TextInput from '../presentation/TextInput';
@ -51,7 +59,7 @@ interface ISearchBox {
hasCancel?: boolean; hasCancel?: boolean;
onCancelPress?: Function; onCancelPress?: Function;
theme?: string; theme?: string;
inputRef?: React.Ref<unknown>; inputRef?: React.Ref<RNTextInput>;
testID?: string; testID?: string;
onFocus?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void; onFocus?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
} }

View File

@ -1,5 +1,5 @@
import React from 'react'; 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 Touchable from 'react-native-platform-touchable';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -59,7 +59,7 @@ export interface IRCTextInputProps extends TextInputProps {
loading?: boolean; loading?: boolean;
containerStyle?: StyleProp<ViewStyle>; containerStyle?: StyleProp<ViewStyle>;
inputStyle?: StyleProp<TextStyle>; inputStyle?: StyleProp<TextStyle>;
inputRef?: React.Ref<unknown>; inputRef?: React.Ref<RNTextInput>;
testID?: string; testID?: string;
iconLeft?: string; iconLeft?: string;
iconRight?: string; iconRight?: string;

View File

@ -22,8 +22,8 @@ interface IMultiSelect {
context?: number; context?: number;
loading?: boolean; loading?: boolean;
multiselect?: boolean; multiselect?: boolean;
onSearch: Function; onSearch?: () => void;
onClose: Function; onClose?: () => void;
inputStyle: object; inputStyle: object;
value?: any[]; value?: any[];
disabled?: boolean | object; disabled?: boolean | object;

View File

@ -0,0 +1,5 @@
export interface ITagsOmnichannel {
_id: string;
name: string;
departments: string[];
}

View File

@ -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;
};
}

View File

@ -15,7 +15,7 @@ interface IThemedTextInput extends IRCTextInputProps {
theme: string; theme: string;
} }
const ThemedTextInput = React.forwardRef(({ style, theme, ...props }: IThemedTextInput, ref: any) => ( const ThemedTextInput = React.forwardRef<TextInput, IThemedTextInput>(({ style, theme, ...props }, ref) => (
<TextInput <TextInput
ref={ref} ref={ref}
style={[{ color: themes[theme].titleText }, style, styles.input]} style={[{ color: themes[theme].titleText }, style, styles.input]}

View File

@ -1,6 +1,6 @@
import { StackNavigationOptions } from '@react-navigation/stack'; import { StackNavigationOptions } from '@react-navigation/stack';
import React from 'react'; import React from 'react';
import { ScrollView, StyleSheet, Text } from 'react-native'; import { ScrollView, StyleSheet, Text, TextInput as RNTextInput } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { encryptionDecodeKey } from '../actions/encryption'; import { encryptionDecodeKey } from '../actions/encryption';
@ -37,7 +37,7 @@ interface IE2EEnterYourPasswordViewState {
type TE2EEnterYourPasswordViewProps = IBaseScreen<E2EEnterYourPasswordStackParamList, 'E2EEnterYourPasswordView'>; type TE2EEnterYourPasswordViewProps = IBaseScreen<E2EEnterYourPasswordStackParamList, 'E2EEnterYourPasswordView'>;
class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordViewProps, IE2EEnterYourPasswordViewState> { class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordViewProps, IE2EEnterYourPasswordViewState> {
private passwordInput?: TextInput; private passwordInput?: RNTextInput;
static navigationOptions = ({ navigation }: Pick<TE2EEnterYourPasswordViewProps, 'navigation'>): StackNavigationOptions => ({ static navigationOptions = ({ navigation }: Pick<TE2EEnterYourPasswordViewProps, 'navigation'>): StackNavigationOptions => ({
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-enter-your-password-view-close' />, headerLeft: () => <HeaderButton.CloseModal navigation={navigation} testID='e2e-enter-your-password-view-close' />,
@ -76,7 +76,7 @@ class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordView
style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.container, { backgroundColor: themes[theme].backgroundColor }]}
testID='e2e-enter-your-password-view'> testID='e2e-enter-your-password-view'>
<TextInput <TextInput
inputRef={(e: TextInput) => { inputRef={(e: RNTextInput) => {
this.passwordInput = e; this.passwordInput = e;
}} }}
placeholder={I18n.t('Password')} placeholder={I18n.t('Password')}

View File

@ -1,6 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import { StackNavigationProp } from '@react-navigation/stack';
import { ScrollView, StyleSheet, Text } from 'react-native'; import { RouteProp } from '@react-navigation/native';
import { ScrollView, StyleSheet, Text, TextInput as RNTextInput } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit'; import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
@ -10,7 +11,6 @@ import TextInput from '../containers/TextInput';
import KeyboardView from '../presentation/KeyboardView'; import KeyboardView from '../presentation/KeyboardView';
import RocketChat from '../lib/rocketchat'; import RocketChat from '../lib/rocketchat';
import I18n from '../i18n'; import I18n from '../i18n';
import { LISTENER } from '../containers/Toast'; import { LISTENER } from '../containers/Toast';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import scrollPersistTaps from '../utils/scrollPersistTaps'; import scrollPersistTaps from '../utils/scrollPersistTaps';
@ -18,6 +18,10 @@ import { getUserSelector } from '../selectors/login';
import Button from '../containers/Button'; import Button from '../containers/Button';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { MultiSelect } from '../containers/UIKit/MultiSelect'; 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'; import sharedStyles from './Styles';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
@ -39,36 +43,87 @@ const styles = StyleSheet.create({
} }
}); });
const Title = ({ title, theme }) => interface ITitle {
title ? <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text> : null; title: string;
Title.propTypes = { theme: string;
title: PropTypes.string, }
theme: PropTypes.string
};
const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelContact, editLivechatRoomCustomfields }) => { interface IField {
const [customFields, setCustomFields] = useState({}); _id: string;
const [availableUserTags, setAvailableUserTags] = useState([]); 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<ChatsStackParamList, 'LivechatEditView'>;
route: RouteProp<ChatsStackParamList, 'LivechatEditView'>;
theme: string;
editOmnichannelContact: string[];
editLivechatRoomCustomfields: string[];
}
const Title = ({ title, theme }: ITitle) =>
title ? <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text> : null;
const LivechatEditView = ({
user,
navigation,
route,
theme,
editOmnichannelContact,
editLivechatRoomCustomfields
}: ILivechatEditViewProps) => {
const [customFields, setCustomFields] = useState<ICustomFields>({});
const [availableUserTags, setAvailableUserTags] = useState<string[]>([]);
const [permissions, setPermissions] = useState([]); const [permissions, setPermissions] = useState([]);
const params = {}; const params = {} as TParams;
const inputs = {}; const inputs = {} as IInputsRefs;
const livechat = route.params?.room ?? {}; const livechat = (route.params?.room ?? {}) as ILivechat;
const visitor = route.params?.roomUser ?? {}; const visitor = route.params?.roomUser ?? {};
const getCustomFields = async () => { const getCustomFields = async () => {
const result = await RocketChat.getCustomFields(); const result = await RocketChat.getCustomFields();
if (result.success && result.customFields?.length) { if (result.success && result.customFields?.length) {
const visitorCustomFields = result.customFields const visitorCustomFields = result.customFields
.filter(field => field.visibility !== 'hidden' && field.scope === 'visitor') .filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'visitor')
.map(field => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' })) .map((field: IField) => ({ [field._id]: (visitor.livechatData && visitor.livechatData[field._id]) || '' }))
.reduce((ret, field) => ({ ...field, ...ret })); .reduce((ret: IField, field: IField) => ({ ...field, ...ret }));
const livechatCustomFields = result.customFields const livechatCustomFields = result.customFields
.filter(field => field.visibility !== 'hidden' && field.scope === 'room') .filter((field: IField) => field.visibility !== 'hidden' && field.scope === 'room')
.map(field => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' })) .map((field: IField) => ({ [field._id]: (livechat.livechatData && livechat.livechatData[field._id]) || '' }))
.reduce((ret, field) => ({ ...field, ...ret })); .reduce((ret: IField, field: IField) => ({ ...field, ...ret }));
return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields }); return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields });
} }
@ -83,8 +138,8 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
setTags(uniqueArray); setTags(uniqueArray);
}, [availableUserTags]); }, [availableUserTags]);
const getTagsList = async agentDepartments => { const getTagsList = async (agentDepartments: string[]) => {
const tags = await RocketChat.getTagsList(); const tags: ITagsOmnichannel[] = await RocketChat.getTagsList();
const isAdmin = ['admin', 'livechat-manager'].find(role => user.roles.includes(role)); const isAdmin = ['admin', 'livechat-manager'].find(role => user.roles.includes(role));
const availableTags = tags const availableTags = tags
.filter(({ departments }) => isAdmin || departments.length === 0 || departments.some(i => agentDepartments.indexOf(i) > -1)) .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 getAgentDepartments = async () => {
const result = await RocketChat.getAgentDepartments(visitor?._id); const result = await RocketChat.getAgentDepartments(visitor?._id);
if (result.success) { if (result.success) {
const agentDepartments = result.departments.map(dept => dept.departmentId); const agentDepartments = result.departments.map((dept: { departmentId: string }) => dept.departmentId);
getTagsList(agentDepartments); getTagsList(agentDepartments);
} }
}; };
const submit = async () => { const submit = async () => {
const userData = { _id: visitor?._id }; const userData = { _id: visitor?._id } as TParams;
const { rid, sms } = livechat; const { rid } = livechat;
const roomData = { _id: rid }; const sms = livechat?.sms;
const roomData = { _id: rid } as TParams;
if (params.name) { if (params.name) {
userData.name = 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; params[key] = text;
}; };
@ -159,6 +216,9 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
}; };
useEffect(() => { useEffect(() => {
navigation.setOptions({
title: I18n.t('Edit')
});
getAgentDepartments(); getAgentDepartments();
getCustomFields(); getCustomFields();
getPermissions(); getPermissions();
@ -177,7 +237,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
defaultValue={visitor?.name} defaultValue={visitor?.name}
onChangeText={text => onChangeText('name', text)} onChangeText={text => onChangeText('name', text)}
onSubmitEditing={() => { onSubmitEditing={() => {
inputs.name.focus(); inputs.name?.focus();
}} }}
theme={theme} theme={theme}
editable={!!permissions[0]} editable={!!permissions[0]}
@ -190,7 +250,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
defaultValue={visitor?.visitorEmails && visitor?.visitorEmails[0]?.address} defaultValue={visitor?.visitorEmails && visitor?.visitorEmails[0]?.address}
onChangeText={text => onChangeText('email', text)} onChangeText={text => onChangeText('email', text)}
onSubmitEditing={() => { onSubmitEditing={() => {
inputs.phone.focus(); inputs.phone?.focus();
}} }}
theme={theme} theme={theme}
editable={!!permissions[0]} editable={!!permissions[0]}
@ -206,9 +266,9 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
const keys = Object.keys(customFields?.visitor || {}); const keys = Object.keys(customFields?.visitor || {});
if (keys.length > 0) { if (keys.length > 0) {
const key = keys[0]; const key = keys[0];
inputs[key].focus(); inputs[key]?.focus();
} else { } else {
inputs.topic.focus(); inputs.topic?.focus();
} }
}} }}
theme={theme} theme={theme}
@ -224,9 +284,9 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
onChangeText={text => onChangeText(key, text)} onChangeText={text => onChangeText(key, text)}
onSubmitEditing={() => { onSubmitEditing={() => {
if (array.length - 1 > index) { 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} theme={theme}
editable={!!permissions[0]} editable={!!permissions[0]}
@ -240,15 +300,14 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
}} }}
defaultValue={livechat?.topic} defaultValue={livechat?.topic}
onChangeText={text => onChangeText('topic', text)} onChangeText={text => onChangeText('topic', text)}
onSubmitEditing={() => inputs.tags.focus()}
theme={theme} theme={theme}
editable={!!permissions[1]} editable={!!permissions[1]}
/> />
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Tags')}</Text> <Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Tags')}</Text>
<MultiSelect <MultiSelect
options={tagParam.map(tag => ({ text: { text: tag }, value: tag }))} options={tagParam.map((tag: string) => ({ text: { text: tag }, value: tag }))}
onChange={({ value }) => { onChange={({ value }: { value: string[] }) => {
setTagParamSelected([...value]); setTagParamSelected([...value]);
}} }}
placeholder={{ text: I18n.t('Tags') }} placeholder={{ text: I18n.t('Tags') }}
@ -260,7 +319,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
inputStyle={styles.multiSelect} inputStyle={styles.multiSelect}
/> />
{Object.entries(customFields?.livechat || {}).map(([key, value], index, array) => ( {Object.entries(customFields?.livechat || {}).map(([key, value], index, array: any) => (
<TextInput <TextInput
label={key} label={key}
defaultValue={value} defaultValue={value}
@ -270,7 +329,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
onChangeText={text => onChangeText(key, text)} onChangeText={text => onChangeText(key, text)}
onSubmitEditing={() => { onSubmitEditing={() => {
if (array.length - 1 > index) { if (array.length - 1 > index) {
return inputs[array[index + 1]].focus(); return inputs[array[index + 1]]?.focus();
} }
submit(); submit();
}} }}
@ -285,19 +344,8 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
</KeyboardView> </KeyboardView>
); );
}; };
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, server: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
editOmnichannelContact: state.permissions['edit-omnichannel-contact'], editOmnichannelContact: state.permissions['edit-omnichannel-contact'],