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 { 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<unknown>;
inputRef?: React.Ref<RNTextInput>;
testID?: string;
onFocus?: (e: NativeSyntheticEvent<TextInputFocusEventData>) => void;
}

View File

@ -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<ViewStyle>;
inputStyle?: StyleProp<TextStyle>;
inputRef?: React.Ref<unknown>;
inputRef?: React.Ref<RNTextInput>;
testID?: string;
iconLeft?: string;
iconRight?: string;

View File

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

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;
}
const ThemedTextInput = React.forwardRef(({ style, theme, ...props }: IThemedTextInput, ref: any) => (
const ThemedTextInput = React.forwardRef<TextInput, IThemedTextInput>(({ style, theme, ...props }, ref) => (
<TextInput
ref={ref}
style={[{ color: themes[theme].titleText }, style, styles.input]}

View File

@ -1,6 +1,6 @@
import { StackNavigationOptions } from '@react-navigation/stack';
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 { encryptionDecodeKey } from '../actions/encryption';
@ -37,7 +37,7 @@ interface IE2EEnterYourPasswordViewState {
type TE2EEnterYourPasswordViewProps = IBaseScreen<E2EEnterYourPasswordStackParamList, 'E2EEnterYourPasswordView'>;
class E2EEnterYourPasswordView extends React.Component<TE2EEnterYourPasswordViewProps, IE2EEnterYourPasswordViewState> {
private passwordInput?: TextInput;
private passwordInput?: RNTextInput;
static navigationOptions = ({ navigation }: Pick<TE2EEnterYourPasswordViewProps, 'navigation'>): StackNavigationOptions => ({
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 }]}
testID='e2e-enter-your-password-view'>
<TextInput
inputRef={(e: TextInput) => {
inputRef={(e: RNTextInput) => {
this.passwordInput = e;
}}
placeholder={I18n.t('Password')}

View File

@ -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 ? <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text> : 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<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 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]}
/>
<Text style={[styles.label, { color: themes[theme].titleText }]}>{I18n.t('Tags')}</Text>
<MultiSelect
options={tagParam.map(tag => ({ 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) => (
<TextInput
label={key}
defaultValue={value}
@ -270,7 +329,7 @@ const LivechatEditView = ({ user, navigation, route, theme, editOmnichannelConta
onChangeText={text => 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
</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,
user: getUserSelector(state),
editOmnichannelContact: state.permissions['edit-omnichannel-contact'],