[NEW] Livechat (#2004)
* [WIP][NEW] Livechat info/actions * [IMPROVEMENT] RoomActionsView * [NEW] Visitor Navigation * [NEW] Get Department REST * [FIX] Borders * [IMPROVEMENT] Refactor RoomInfo View * [FIX] Error while navigate from mention -> roomInfo * [NEW] Livechat Fields * [NEW] Close Livechat * [WIP] Forward livechat * [NEW] Return inquiry * [WIP] Comment when close livechat * [WIP] Improve roomInfo * [IMPROVEMENT] Forward room * [FIX] Department picker * [FIX] Picker without results * [FIX] Superfluous argument * [FIX] Check permissions on RoomActionsView * [FIX] Livechat permissions * [WIP] Show edit to livechat * [I18N] Add pt-br translations * [WIP] Livechat Info * [IMPROVEMENT] Livechat info * [WIP] Livechat Edit * [WIP] Livechat edit * [WIP] Livechat Edit * [WIP] Livechat edit scroll * [FIX] Edit customFields * [FIX] Clean livechat customField * [FIX] Visitor Navigation * [NEW] Next input logic LivechatEdit * [FIX] Add livechat data to subscription * [FIX] Revert change * [NEW] Livechat user Status * [WIP] Livechat tags * [NEW] Edit livechat tags * [FIX] Prevent some crashes * [FIX] Forward * [FIX] Return Livechat error * [FIX] Prevent livechat info crash * [IMPROVEMENT] Use input style on forward chat * OnboardingSeparator -> OrSeparator * [FIX] Go to next input * [NEW] Added some icons * [NEW] Livechat close * [NEW] Forward Room Action * [FIX] Livechat edit style * [FIX] Change status logic * [CHORE] Remove unnecessary logic * [CHORE] Remove unnecessary code * [CHORE] Remove unecessary case * [FIX] Superfluous argument * [IMPROVEMENT] Submit livechat edit * [CHORE] Remove textInput type * [FIX] Livechat edit * [FIX] Livechat Edit * [FIX] Use same effect * [IMPROVEMENT] Tags input * [FIX] Add empty tag * Fix minor issues * Fix typo * insert livechat room data to our room object * review * add method calls server version Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
parent
0e4e174e25
commit
9e89316e2a
|
@ -31,7 +31,7 @@ export const ROOMS = createRequestTypes('ROOMS', [
|
||||||
'OPEN_SEARCH_HEADER',
|
'OPEN_SEARCH_HEADER',
|
||||||
'CLOSE_SEARCH_HEADER'
|
'CLOSE_SEARCH_HEADER'
|
||||||
]);
|
]);
|
||||||
export const ROOM = createRequestTypes('ROOM', ['SUBSCRIBE', 'UNSUBSCRIBE', 'LEAVE', 'DELETE', 'REMOVED', 'USER_TYPING']);
|
export const ROOM = createRequestTypes('ROOM', ['SUBSCRIBE', 'UNSUBSCRIBE', 'LEAVE', 'DELETE', 'REMOVED', 'CLOSE', 'FORWARD', 'USER_TYPING']);
|
||||||
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
export const APP = createRequestTypes('APP', ['START', 'READY', 'INIT', 'INIT_LOCAL_SETTINGS']);
|
||||||
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
export const MESSAGES = createRequestTypes('MESSAGES', ['REPLY_BROADCAST']);
|
||||||
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
export const CREATE_CHANNEL = createRequestTypes('CREATE_CHANNEL', [...defaultTypes]);
|
||||||
|
|
|
@ -30,6 +30,21 @@ export function deleteRoom(rid, t) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function closeRoom(rid) {
|
||||||
|
return {
|
||||||
|
type: types.ROOM.CLOSE,
|
||||||
|
rid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function forwardRoom(rid, transferData) {
|
||||||
|
return {
|
||||||
|
type: types.ROOM.FORWARD,
|
||||||
|
transferData,
|
||||||
|
rid
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function removedRoom() {
|
export function removedRoom() {
|
||||||
return {
|
return {
|
||||||
type: types.ROOM.REMOVED
|
type: types.ROOM.REMOVED
|
||||||
|
|
|
@ -68,6 +68,9 @@ export default {
|
||||||
LDAP_Enable: {
|
LDAP_Enable: {
|
||||||
type: 'valueAsBoolean'
|
type: 'valueAsBoolean'
|
||||||
},
|
},
|
||||||
|
Livechat_request_comment_when_closing_conversation: {
|
||||||
|
type: 'valueAsBoolean'
|
||||||
|
},
|
||||||
Jitsi_Enabled: {
|
Jitsi_Enabled: {
|
||||||
type: 'valueAsBoolean'
|
type: 'valueAsBoolean'
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@ import sharedStyles from '../views/Styles';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
import { loginRequest as loginRequestAction } from '../actions/login';
|
import { loginRequest as loginRequestAction } from '../actions/login';
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import OnboardingSeparator from './OnboardingSeparator';
|
import OrSeparator from './OrSeparator';
|
||||||
import Touch from '../utils/touch';
|
import Touch from '../utils/touch';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import random from '../utils/random';
|
import random from '../utils/random';
|
||||||
|
@ -252,12 +252,12 @@ class LoginServices extends React.PureComponent {
|
||||||
style={styles.options}
|
style={styles.options}
|
||||||
color={themes[theme].actionTintColor}
|
color={themes[theme].actionTintColor}
|
||||||
/>
|
/>
|
||||||
<OnboardingSeparator theme={theme} />
|
<OrSeparator theme={theme} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (length > 0 && separator) {
|
if (length > 0 && separator) {
|
||||||
return <OnboardingSeparator theme={theme} />;
|
return <OrSeparator theme={theme} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const DateSeparator = React.memo(({ theme }) => {
|
const OrSeparator = React.memo(({ theme }) => {
|
||||||
const line = { backgroundColor: themes[theme].borderColor };
|
const line = { backgroundColor: themes[theme].borderColor };
|
||||||
const text = { color: themes[theme].auxiliaryText };
|
const text = { color: themes[theme].auxiliaryText };
|
||||||
return (
|
return (
|
||||||
|
@ -36,8 +36,8 @@ const DateSeparator = React.memo(({ theme }) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
DateSeparator.propTypes = {
|
OrSeparator.propTypes = {
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DateSeparator;
|
export default OrSeparator;
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { Image, StyleSheet } from 'react-native';
|
import { Image, StyleSheet } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { CustomIcon } from '../lib/Icons';
|
import { CustomIcon } from '../lib/Icons';
|
||||||
import { themes } from '../constants/colors';
|
import { STATUS_COLORS, themes } from '../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
style: {
|
style: {
|
||||||
|
@ -15,7 +15,7 @@ const styles = StyleSheet.create({
|
||||||
});
|
});
|
||||||
|
|
||||||
const RoomTypeIcon = React.memo(({
|
const RoomTypeIcon = React.memo(({
|
||||||
type, size, isGroupChat, style, theme
|
type, size, isGroupChat, status, style, theme
|
||||||
}) => {
|
}) => {
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -36,7 +36,7 @@ const RoomTypeIcon = React.memo(({
|
||||||
}
|
}
|
||||||
return <CustomIcon name='at' size={13} style={[styles.style, styles.discussion, { color }]} />;
|
return <CustomIcon name='at' size={13} style={[styles.style, styles.discussion, { color }]} />;
|
||||||
} if (type === 'l') {
|
} if (type === 'l') {
|
||||||
return <CustomIcon name='omnichannel' size={13} style={[styles.style, styles.discussion, { color }]} />;
|
return <CustomIcon name='omnichannel' size={13} style={[styles.style, styles.discussion, { color: STATUS_COLORS[status] }]} />;
|
||||||
}
|
}
|
||||||
return <Image source={{ uri: 'lock' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
|
return <Image source={{ uri: 'lock' }} style={[styles.style, style, { width: size, height: size, tintColor: color }]} />;
|
||||||
});
|
});
|
||||||
|
@ -45,6 +45,7 @@ RoomTypeIcon.propTypes = {
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
isGroupChat: PropTypes.bool,
|
isGroupChat: PropTypes.bool,
|
||||||
|
status: PropTypes.string,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
style: PropTypes.object
|
style: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
|
@ -64,8 +64,10 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
inputRef: PropTypes.func,
|
inputRef: PropTypes.func,
|
||||||
testID: PropTypes.string,
|
testID: PropTypes.string,
|
||||||
iconLeft: PropTypes.string,
|
iconLeft: PropTypes.string,
|
||||||
|
iconRight: PropTypes.string,
|
||||||
placeholder: PropTypes.string,
|
placeholder: PropTypes.string,
|
||||||
left: PropTypes.element,
|
left: PropTypes.element,
|
||||||
|
onIconRightPress: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +92,19 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get iconRight() {
|
||||||
|
const { iconRight, onIconRightPress, theme } = this.props;
|
||||||
|
return (
|
||||||
|
<BorderlessButton onPress={onIconRightPress} style={[styles.iconContainer, styles.iconRight]}>
|
||||||
|
<CustomIcon
|
||||||
|
name={iconRight}
|
||||||
|
style={{ color: themes[theme].bodyText }}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
</BorderlessButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
get iconPassword() {
|
get iconPassword() {
|
||||||
const { showPassword } = this.state;
|
const { showPassword } = this.state;
|
||||||
const { testID, theme } = this.props;
|
const { testID, theme } = this.props;
|
||||||
|
@ -117,7 +132,7 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
render() {
|
render() {
|
||||||
const { showPassword } = this.state;
|
const { showPassword } = this.state;
|
||||||
const {
|
const {
|
||||||
label, left, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
|
label, left, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, iconRight, inputStyle, testID, placeholder, theme, ...inputProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { dangerColor } = themes[theme];
|
const { dangerColor } = themes[theme];
|
||||||
return (
|
return (
|
||||||
|
@ -140,7 +155,7 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
style={[
|
style={[
|
||||||
styles.input,
|
styles.input,
|
||||||
iconLeft && styles.inputIconLeft,
|
iconLeft && styles.inputIconLeft,
|
||||||
secureTextEntry && styles.inputIconRight,
|
(secureTextEntry || iconRight) && styles.inputIconRight,
|
||||||
{
|
{
|
||||||
backgroundColor: themes[theme].backgroundColor,
|
backgroundColor: themes[theme].backgroundColor,
|
||||||
borderColor: themes[theme].separatorColor,
|
borderColor: themes[theme].separatorColor,
|
||||||
|
@ -165,6 +180,7 @@ export default class RCTextInput extends React.PureComponent {
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
/>
|
/>
|
||||||
{iconLeft ? this.iconLeft : null}
|
{iconLeft ? this.iconLeft : null}
|
||||||
|
{iconRight ? this.iconRight : null}
|
||||||
{secureTextEntry ? this.iconPassword : null}
|
{secureTextEntry ? this.iconPassword : null}
|
||||||
{loading ? this.loading : null}
|
{loading ? this.loading : null}
|
||||||
{left}
|
{left}
|
||||||
|
|
|
@ -12,11 +12,13 @@ import styles from './styles';
|
||||||
|
|
||||||
const keyExtractor = item => item.value.toString();
|
const keyExtractor = item => item.value.toString();
|
||||||
|
|
||||||
const Chip = ({ item, onSelect, theme }) => (
|
const Chip = ({
|
||||||
|
item, onSelect, style, theme
|
||||||
|
}) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
key={item.value}
|
key={item.value}
|
||||||
onPress={() => onSelect(item)}
|
onPress={() => onSelect(item)}
|
||||||
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }]}
|
style={[styles.chip, { backgroundColor: themes[theme].auxiliaryBackground }, style]}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
|
@ -29,17 +31,21 @@ const Chip = ({ item, onSelect, theme }) => (
|
||||||
Chip.propTypes = {
|
Chip.propTypes = {
|
||||||
item: PropTypes.object,
|
item: PropTypes.object,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
const Chips = ({ items, onSelect, theme }) => (
|
const Chips = ({
|
||||||
|
items, onSelect, style, theme
|
||||||
|
}) => (
|
||||||
<View style={styles.chips}>
|
<View style={styles.chips}>
|
||||||
{items.map(item => <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} theme={theme} />)}
|
{items.map(item => <Chip key={keyExtractor(item)} item={item} onSelect={onSelect} style={style} theme={theme} />)}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
Chips.propTypes = {
|
Chips.propTypes = {
|
||||||
items: PropTypes.array,
|
items: PropTypes.array,
|
||||||
onSelect: PropTypes.func,
|
onSelect: PropTypes.func,
|
||||||
|
style: PropTypes.object,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Touchable from 'react-native-platform-touchable';
|
import Touchable from 'react-native-platform-touchable';
|
||||||
|
|
||||||
|
@ -9,16 +9,16 @@ import ActivityIndicator from '../../ActivityIndicator';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
|
||||||
const Input = ({
|
const Input = ({
|
||||||
children, open, theme, loading, inputStyle, disabled
|
children, onPress, theme, loading, inputStyle, placeholder, disabled
|
||||||
}) => (
|
}) => (
|
||||||
<Touchable
|
<Touchable
|
||||||
onPress={() => open(true)}
|
onPress={onPress}
|
||||||
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
style={[{ backgroundColor: themes[theme].backgroundColor }, inputStyle]}
|
||||||
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
background={Touchable.Ripple(themes[theme].bannerBackground)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
|
<View style={[styles.input, { borderColor: themes[theme].separatorColor }]}>
|
||||||
{children}
|
{placeholder ? <Text style={[styles.pickerText, { color: themes[theme].auxiliaryText }]}>{placeholder}</Text> : children}
|
||||||
{
|
{
|
||||||
loading
|
loading
|
||||||
? <ActivityIndicator style={[styles.loading, styles.icon]} />
|
? <ActivityIndicator style={[styles.loading, styles.icon]} />
|
||||||
|
@ -29,10 +29,11 @@ const Input = ({
|
||||||
);
|
);
|
||||||
Input.propTypes = {
|
Input.propTypes = {
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
open: PropTypes.func,
|
onPress: PropTypes.func,
|
||||||
theme: PropTypes.string,
|
theme: PropTypes.string,
|
||||||
inputStyle: PropTypes.object,
|
inputStyle: PropTypes.object,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
loading: PropTypes.bool
|
loading: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ export const MultiSelect = React.memo(({
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Input
|
<Input
|
||||||
open={onShow}
|
onPress={onShow}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
@ -150,7 +150,7 @@ export const MultiSelect = React.memo(({
|
||||||
const items = options.filter(option => selected.includes(option.value));
|
const items = options.filter(option => selected.includes(option.value));
|
||||||
button = (
|
button = (
|
||||||
<Input
|
<Input
|
||||||
open={onShow}
|
onPress={onShow}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -53,7 +53,6 @@ const User = React.memo(({
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={styles.titleContainer}
|
style={styles.titleContainer}
|
||||||
onPress={() => navToRoomInfo(navParam)}
|
onPress={() => navToRoomInfo(navParam)}
|
||||||
style={styles.titleContainer}
|
|
||||||
disabled={author._id === user.id}
|
disabled={author._id === user.id}
|
||||||
>
|
>
|
||||||
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
|
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||||
|
|
|
@ -85,6 +85,7 @@ export default {
|
||||||
Add_Server: 'Add Server',
|
Add_Server: 'Add Server',
|
||||||
Add_users: 'Add users',
|
Add_users: 'Add users',
|
||||||
Admin_Panel: 'Admin Panel',
|
Admin_Panel: 'Admin Panel',
|
||||||
|
Agent: 'Agent',
|
||||||
Alert: 'Alert',
|
Alert: 'Alert',
|
||||||
alert: 'alert',
|
alert: 'alert',
|
||||||
alerts: 'alerts',
|
alerts: 'alerts',
|
||||||
|
@ -133,7 +134,9 @@ export default {
|
||||||
Click_to_join: 'Click to Join!',
|
Click_to_join: 'Click to Join!',
|
||||||
Close: 'Close',
|
Close: 'Close',
|
||||||
Close_emoji_selector: 'Close emoji selector',
|
Close_emoji_selector: 'Close emoji selector',
|
||||||
|
Closing_chat: 'Closing chat',
|
||||||
Change_language_loading: 'Changing language.',
|
Change_language_loading: 'Changing language.',
|
||||||
|
Chat_closed_by_agent: 'Chat closed by agent',
|
||||||
Choose: 'Choose',
|
Choose: 'Choose',
|
||||||
Choose_from_library: 'Choose from library',
|
Choose_from_library: 'Choose from library',
|
||||||
Choose_file: 'Choose file',
|
Choose_file: 'Choose file',
|
||||||
|
@ -151,6 +154,7 @@ export default {
|
||||||
Continue_with: 'Continue with',
|
Continue_with: 'Continue with',
|
||||||
Copied_to_clipboard: 'Copied to clipboard!',
|
Copied_to_clipboard: 'Copied to clipboard!',
|
||||||
Copy: 'Copy',
|
Copy: 'Copy',
|
||||||
|
Conversation: 'Conversation',
|
||||||
Permalink: 'Permalink',
|
Permalink: 'Permalink',
|
||||||
Certificate_password: 'Certificate Password',
|
Certificate_password: 'Certificate Password',
|
||||||
Clear_cache: 'Clear local server cache',
|
Clear_cache: 'Clear local server cache',
|
||||||
|
@ -169,6 +173,7 @@ export default {
|
||||||
Default: 'Default',
|
Default: 'Default',
|
||||||
Default_browser: 'Default browser',
|
Default_browser: 'Default browser',
|
||||||
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
|
Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.',
|
||||||
|
Department: 'Department',
|
||||||
delete: 'delete',
|
delete: 'delete',
|
||||||
Delete: 'Delete',
|
Delete: 'Delete',
|
||||||
DELETE: 'DELETE',
|
DELETE: 'DELETE',
|
||||||
|
@ -196,6 +201,7 @@ export default {
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
EMAIL: 'EMAIL',
|
EMAIL: 'EMAIL',
|
||||||
email: 'e-mail',
|
email: 'e-mail',
|
||||||
|
Empty_title: 'Empty title',
|
||||||
Enable_Auto_Translate: 'Enable Auto-Translate',
|
Enable_Auto_Translate: 'Enable Auto-Translate',
|
||||||
Enable_notifications: 'Enable notifications',
|
Enable_notifications: 'Enable notifications',
|
||||||
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
||||||
|
@ -212,6 +218,10 @@ export default {
|
||||||
Forgot_password_If_this_email_is_registered: 'If this email is registered, we\'ll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.',
|
Forgot_password_If_this_email_is_registered: 'If this email is registered, we\'ll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.',
|
||||||
Forgot_password: 'Forgot your password?',
|
Forgot_password: 'Forgot your password?',
|
||||||
Forgot_Password: 'Forgot Password',
|
Forgot_Password: 'Forgot Password',
|
||||||
|
Forward: 'Forward',
|
||||||
|
Forward_Chat: 'Forward Chat',
|
||||||
|
Forward_to_department: 'Forward to department',
|
||||||
|
Forward_to_user: 'Forward to user',
|
||||||
Full_table: 'Click to see full table',
|
Full_table: 'Click to see full table',
|
||||||
Generate_New_Link: 'Generate New Link',
|
Generate_New_Link: 'Generate New Link',
|
||||||
Group_by_favorites: 'Group favorites',
|
Group_by_favorites: 'Group favorites',
|
||||||
|
@ -235,6 +245,7 @@ export default {
|
||||||
Message_HideType_subscription_role_removed: 'Role No Longer Defined',
|
Message_HideType_subscription_role_removed: 'Role No Longer Defined',
|
||||||
Message_HideType_room_archived: 'Room Archived',
|
Message_HideType_room_archived: 'Room Archived',
|
||||||
Message_HideType_room_unarchived: 'Room Unarchived',
|
Message_HideType_room_unarchived: 'Room Unarchived',
|
||||||
|
IP: 'IP',
|
||||||
In_app: 'In-app',
|
In_app: 'In-app',
|
||||||
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP',
|
IN_APP_AND_DESKTOP: 'IN-APP AND DESKTOP',
|
||||||
In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop',
|
In_App_and_Desktop_Alert_info: 'Displays a banner at the top of the screen when app is open, and displays a notification on desktop',
|
||||||
|
@ -260,6 +271,7 @@ export default {
|
||||||
Light: 'Light',
|
Light: 'Light',
|
||||||
License: 'License',
|
License: 'License',
|
||||||
Livechat: 'Livechat',
|
Livechat: 'Livechat',
|
||||||
|
Livechat_edit: 'Livechat edit',
|
||||||
Login: 'Login',
|
Login: 'Login',
|
||||||
Login_error: 'Your credentials were rejected! Please try again.',
|
Login_error: 'Your credentials were rejected! Please try again.',
|
||||||
Login_with: 'Login with',
|
Login_with: 'Login with',
|
||||||
|
@ -292,6 +304,7 @@ export default {
|
||||||
N_users: '{{n}} users',
|
N_users: '{{n}} users',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
Name: 'Name',
|
Name: 'Name',
|
||||||
|
Navigation_history: 'Navigation history',
|
||||||
Never: 'Never',
|
Never: 'Never',
|
||||||
New_Message: 'New Message',
|
New_Message: 'New Message',
|
||||||
New_Password: 'New Password',
|
New_Password: 'New Password',
|
||||||
|
@ -318,6 +331,7 @@ export default {
|
||||||
Notifications: 'Notifications',
|
Notifications: 'Notifications',
|
||||||
Notification_Duration: 'Notification Duration',
|
Notification_Duration: 'Notification Duration',
|
||||||
Notification_Preferences: 'Notification Preferences',
|
Notification_Preferences: 'Notification Preferences',
|
||||||
|
No_available_agents_to_transfer: 'No available agents to transfer',
|
||||||
Offline: 'Offline',
|
Offline: 'Offline',
|
||||||
Oops: 'Oops!',
|
Oops: 'Oops!',
|
||||||
Onboarding_description: 'A workspace is your team or organization’s space to collaborate. Ask the workspace admin for address to join or create one for your team.',
|
Onboarding_description: 'A workspace is your team or organization’s space to collaborate. Ask the workspace admin for address to join or create one for your team.',
|
||||||
|
@ -334,14 +348,17 @@ export default {
|
||||||
Open_Source_Communication: 'Open Source Communication',
|
Open_Source_Communication: 'Open Source Communication',
|
||||||
Open_your_authentication_app_and_enter_the_code: 'Open your authentication app and enter the code.',
|
Open_your_authentication_app_and_enter_the_code: 'Open your authentication app and enter the code.',
|
||||||
OR: 'OR',
|
OR: 'OR',
|
||||||
|
OS: 'OS',
|
||||||
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
|
Overwrites_the_server_configuration_and_use_room_config: 'Overwrites the server configuration and use room config',
|
||||||
Password: 'Password',
|
Password: 'Password',
|
||||||
Parent_channel_or_group: 'Parent channel or group',
|
Parent_channel_or_group: 'Parent channel or group',
|
||||||
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
|
Permalink_copied_to_clipboard: 'Permalink copied to clipboard!',
|
||||||
|
Phone: 'Phone',
|
||||||
Pin: 'Pin',
|
Pin: 'Pin',
|
||||||
Pinned_Messages: 'Pinned Messages',
|
Pinned_Messages: 'Pinned Messages',
|
||||||
pinned: 'pinned',
|
pinned: 'pinned',
|
||||||
Pinned: 'Pinned',
|
Pinned: 'Pinned',
|
||||||
|
Please_add_a_comment: 'Please add a comment',
|
||||||
Please_enter_your_password: 'Please enter your password',
|
Please_enter_your_password: 'Please enter your password',
|
||||||
Please_wait: 'Please wait.',
|
Please_wait: 'Please wait.',
|
||||||
Preferences: 'Preferences',
|
Preferences: 'Preferences',
|
||||||
|
@ -380,6 +397,7 @@ export default {
|
||||||
Reset_password: 'Reset password',
|
Reset_password: 'Reset password',
|
||||||
resetting_password: 'resetting password',
|
resetting_password: 'resetting password',
|
||||||
RESET: 'RESET',
|
RESET: 'RESET',
|
||||||
|
Return: 'Return',
|
||||||
Review_app_title: 'Are you enjoying this app?',
|
Review_app_title: 'Are you enjoying this app?',
|
||||||
Review_app_desc: 'Give us 5 stars on {{store}}',
|
Review_app_desc: 'Give us 5 stars on {{store}}',
|
||||||
Review_app_yes: 'Sure!',
|
Review_app_yes: 'Sure!',
|
||||||
|
@ -401,6 +419,7 @@ export default {
|
||||||
SAVE: 'SAVE',
|
SAVE: 'SAVE',
|
||||||
Save_Changes: 'Save Changes',
|
Save_Changes: 'Save Changes',
|
||||||
Save: 'Save',
|
Save: 'Save',
|
||||||
|
Saved: 'Saved',
|
||||||
saving_preferences: 'saving preferences',
|
saving_preferences: 'saving preferences',
|
||||||
saving_profile: 'saving profile',
|
saving_profile: 'saving profile',
|
||||||
saving_settings: 'saving settings',
|
saving_settings: 'saving settings',
|
||||||
|
@ -415,7 +434,9 @@ export default {
|
||||||
Select_Server: 'Select Server',
|
Select_Server: 'Select Server',
|
||||||
Select_Users: 'Select Users',
|
Select_Users: 'Select Users',
|
||||||
Select_a_Channel: 'Select a Channel',
|
Select_a_Channel: 'Select a Channel',
|
||||||
|
Select_a_Department: 'Select a Department',
|
||||||
Select_an_option: 'Select an option',
|
Select_an_option: 'Select an option',
|
||||||
|
Select_a_User: 'Select a User',
|
||||||
Send: 'Send',
|
Send: 'Send',
|
||||||
Send_audio_message: 'Send audio message',
|
Send_audio_message: 'Send audio message',
|
||||||
Send_crash_report: 'Send crash report',
|
Send_crash_report: 'Send crash report',
|
||||||
|
@ -453,6 +474,7 @@ export default {
|
||||||
Started_call: 'Call started by {{userBy}}',
|
Started_call: 'Call started by {{userBy}}',
|
||||||
Submit: 'Submit',
|
Submit: 'Submit',
|
||||||
Table: 'Table',
|
Table: 'Table',
|
||||||
|
Tags: 'Tags',
|
||||||
Take_a_photo: 'Take a photo',
|
Take_a_photo: 'Take a photo',
|
||||||
Take_a_video: 'Take a video',
|
Take_a_video: 'Take a video',
|
||||||
tap_to_change_status: 'tap to change status',
|
tap_to_change_status: 'tap to change status',
|
||||||
|
@ -488,6 +510,7 @@ export default {
|
||||||
Updating: 'Updating...',
|
Updating: 'Updating...',
|
||||||
Uploading: 'Uploading',
|
Uploading: 'Uploading',
|
||||||
Upload_file_question_mark: 'Upload file?',
|
Upload_file_question_mark: 'Upload file?',
|
||||||
|
User: 'User',
|
||||||
Users: 'Users',
|
Users: 'Users',
|
||||||
User_added_by: 'User {{userAdded}} added by {{userBy}}',
|
User_added_by: 'User {{userAdded}} added by {{userBy}}',
|
||||||
User_Info: 'User Info',
|
User_Info: 'User Info',
|
||||||
|
@ -518,8 +541,10 @@ export default {
|
||||||
Whats_your_2fa: 'What\'s your 2FA code?',
|
Whats_your_2fa: 'What\'s your 2FA code?',
|
||||||
Without_Servers: 'Without Servers',
|
Without_Servers: 'Without Servers',
|
||||||
Workspaces: 'Workspaces',
|
Workspaces: 'Workspaces',
|
||||||
|
Would_you_like_to_return_the_inquiry: 'Would you like to return the inquiry?',
|
||||||
Write_External_Permission_Message: 'Rocket Chat needs access to your gallery so you can save images.',
|
Write_External_Permission_Message: 'Rocket Chat needs access to your gallery so you can save images.',
|
||||||
Write_External_Permission: 'Gallery Permission',
|
Write_External_Permission: 'Gallery Permission',
|
||||||
|
Yes: 'Yes',
|
||||||
Yes_action_it: 'Yes, {{action}} it!',
|
Yes_action_it: 'Yes, {{action}} it!',
|
||||||
Yesterday: 'Yesterday',
|
Yesterday: 'Yesterday',
|
||||||
You_are_in_preview_mode: 'You are in preview mode',
|
You_are_in_preview_mode: 'You are in preview mode',
|
||||||
|
|
|
@ -89,6 +89,7 @@ export default {
|
||||||
Add_Reaction: 'Reagir',
|
Add_Reaction: 'Reagir',
|
||||||
Add_Server: 'Adicionar servidor',
|
Add_Server: 'Adicionar servidor',
|
||||||
Add_users: 'Adicionar usuário',
|
Add_users: 'Adicionar usuário',
|
||||||
|
Agent: 'Agente',
|
||||||
Alert: 'Alerta',
|
Alert: 'Alerta',
|
||||||
alert: 'alerta',
|
alert: 'alerta',
|
||||||
alerts: 'alertas',
|
alerts: 'alertas',
|
||||||
|
@ -135,7 +136,9 @@ export default {
|
||||||
Click_to_join: 'Clique para participar!',
|
Click_to_join: 'Clique para participar!',
|
||||||
Close: 'Fechar',
|
Close: 'Fechar',
|
||||||
Close_emoji_selector: 'Fechar seletor de emojis',
|
Close_emoji_selector: 'Fechar seletor de emojis',
|
||||||
|
Closing_chat: 'Fechando conversa',
|
||||||
Choose: 'Escolher',
|
Choose: 'Escolher',
|
||||||
|
Chat_closed_by_agent: 'Conversa fechada por agente',
|
||||||
Choose_from_library: 'Escolha da biblioteca',
|
Choose_from_library: 'Escolha da biblioteca',
|
||||||
Choose_file: 'Enviar arquivo',
|
Choose_file: 'Enviar arquivo',
|
||||||
Choose_where_you_want_links_be_opened: 'Escolha onde deseja que os links sejam abertos',
|
Choose_where_you_want_links_be_opened: 'Escolha onde deseja que os links sejam abertos',
|
||||||
|
@ -145,6 +148,7 @@ export default {
|
||||||
Confirm: 'Confirmar',
|
Confirm: 'Confirmar',
|
||||||
Connect: 'Conectar',
|
Connect: 'Conectar',
|
||||||
Connected: 'Conectado',
|
Connected: 'Conectado',
|
||||||
|
Conversation: 'Conversação',
|
||||||
connecting_server: 'conectando no servidor',
|
connecting_server: 'conectando no servidor',
|
||||||
Connecting: 'Conectando...',
|
Connecting: 'Conectando...',
|
||||||
Continue_with: 'Entrar com',
|
Continue_with: 'Entrar com',
|
||||||
|
@ -187,6 +191,7 @@ export default {
|
||||||
Email_or_password_field_is_empty: 'Email ou senha estão vazios',
|
Email_or_password_field_is_empty: 'Email ou senha estão vazios',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
email: 'e-mail',
|
email: 'e-mail',
|
||||||
|
Empty_title: 'Título vazio',
|
||||||
Enable_notifications: 'Habilitar notificações',
|
Enable_notifications: 'Habilitar notificações',
|
||||||
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
|
Everyone_can_access_this_channel: 'Todos podem acessar este canal',
|
||||||
Error_uploading: 'Erro subindo',
|
Error_uploading: 'Erro subindo',
|
||||||
|
@ -201,6 +206,10 @@ export default {
|
||||||
Forgot_password_If_this_email_is_registered: 'Se este e-mail estiver cadastrado, enviaremos instruções sobre como redefinir sua senha. Se você não receber um e-mail em breve, volte e tente novamente.',
|
Forgot_password_If_this_email_is_registered: 'Se este e-mail estiver cadastrado, enviaremos instruções sobre como redefinir sua senha. Se você não receber um e-mail em breve, volte e tente novamente.',
|
||||||
Forgot_password: 'Esqueceu sua senha?',
|
Forgot_password: 'Esqueceu sua senha?',
|
||||||
Forgot_Password: 'Esqueci minha senha',
|
Forgot_Password: 'Esqueci minha senha',
|
||||||
|
Forward: 'Encaminhar',
|
||||||
|
Forward_Chat: 'Encaminhar Conversa',
|
||||||
|
Forward_to_department: 'Encaminhar para departamento',
|
||||||
|
Forward_to_user: 'Encaminhar para usuário',
|
||||||
Full_table: 'Clique para ver a tabela completa',
|
Full_table: 'Clique para ver a tabela completa',
|
||||||
Generate_New_Link: 'Gerar novo convite',
|
Generate_New_Link: 'Gerar novo convite',
|
||||||
Group_by_favorites: 'Agrupar favoritos',
|
Group_by_favorites: 'Agrupar favoritos',
|
||||||
|
@ -223,6 +232,7 @@ export default {
|
||||||
Message_HideType_subscription_role_removed: 'Papel removido',
|
Message_HideType_subscription_role_removed: 'Papel removido',
|
||||||
Message_HideType_room_archived: 'Sala arquivada',
|
Message_HideType_room_archived: 'Sala arquivada',
|
||||||
Message_HideType_room_unarchived: 'Sala desarquivada',
|
Message_HideType_room_unarchived: 'Sala desarquivada',
|
||||||
|
IP: 'IP',
|
||||||
In_app: 'No app',
|
In_app: 'No app',
|
||||||
Invisible: 'Invisível',
|
Invisible: 'Invisível',
|
||||||
Invite: 'Convidar',
|
Invite: 'Convidar',
|
||||||
|
@ -269,6 +279,7 @@ export default {
|
||||||
N_users: '{{n}} usuários',
|
N_users: '{{n}} usuários',
|
||||||
name: 'nome',
|
name: 'nome',
|
||||||
Name: 'Nome',
|
Name: 'Nome',
|
||||||
|
Navigation_history: 'Histórico de navegação',
|
||||||
Never: 'Nunca',
|
Never: 'Nunca',
|
||||||
New_in_RocketChat_question_mark: 'Novo no Rocket.Chat?',
|
New_in_RocketChat_question_mark: 'Novo no Rocket.Chat?',
|
||||||
New_Message: 'Nova Mensagem',
|
New_Message: 'Nova Mensagem',
|
||||||
|
@ -289,6 +300,7 @@ export default {
|
||||||
Notify_active_in_this_room: 'Notificar usuários ativos nesta sala',
|
Notify_active_in_this_room: 'Notificar usuários ativos nesta sala',
|
||||||
Notify_all_in_this_room: 'Notificar todos nesta sala',
|
Notify_all_in_this_room: 'Notificar todos nesta sala',
|
||||||
Not_RC_Server: 'Este não é um servidor Rocket.Chat.\n{{contact}}',
|
Not_RC_Server: 'Este não é um servidor Rocket.Chat.\n{{contact}}',
|
||||||
|
No_available_agents_to_transfer: 'Nenhum agente disponível para transferência',
|
||||||
Offline: 'Offline',
|
Offline: 'Offline',
|
||||||
Oops: 'Ops!',
|
Oops: 'Ops!',
|
||||||
Onboarding_description: 'Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.',
|
Onboarding_description: 'Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.',
|
||||||
|
@ -305,6 +317,7 @@ export default {
|
||||||
Open_Source_Communication: 'Comunicação Open Source',
|
Open_Source_Communication: 'Comunicação Open Source',
|
||||||
Open_your_authentication_app_and_enter_the_code: 'Abra seu aplicativo de autenticação e digite o código.',
|
Open_your_authentication_app_and_enter_the_code: 'Abra seu aplicativo de autenticação e digite o código.',
|
||||||
OR: 'OU',
|
OR: 'OU',
|
||||||
|
OS: 'SO',
|
||||||
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
|
Overwrites_the_server_configuration_and_use_room_config: 'Substituir a configuração do servidor e usar a configuração da sala',
|
||||||
Password: 'Senha',
|
Password: 'Senha',
|
||||||
Parent_channel_or_group: 'Canal ou grupo pai',
|
Parent_channel_or_group: 'Canal ou grupo pai',
|
||||||
|
@ -315,6 +328,7 @@ export default {
|
||||||
Pinned: 'Mensagens Fixadas',
|
Pinned: 'Mensagens Fixadas',
|
||||||
Please_wait: 'Por favor, aguarde.',
|
Please_wait: 'Por favor, aguarde.',
|
||||||
Please_enter_your_password: 'Por favor, digite sua senha',
|
Please_enter_your_password: 'Por favor, digite sua senha',
|
||||||
|
Please_add_a_comment: 'Por favor, adicione um comentário',
|
||||||
Preferences: 'Preferências',
|
Preferences: 'Preferências',
|
||||||
Preferences_saved: 'Preferências salvas!',
|
Preferences_saved: 'Preferências salvas!',
|
||||||
Privacy_Policy: ' Política de Privacidade',
|
Privacy_Policy: ' Política de Privacidade',
|
||||||
|
@ -343,6 +357,7 @@ export default {
|
||||||
Reset_password: 'Resetar senha',
|
Reset_password: 'Resetar senha',
|
||||||
resetting_password: 'redefinindo senha',
|
resetting_password: 'redefinindo senha',
|
||||||
RESET: 'RESETAR',
|
RESET: 'RESETAR',
|
||||||
|
Return: 'Retornar',
|
||||||
Review_app_title: 'Você está gostando do app?',
|
Review_app_title: 'Você está gostando do app?',
|
||||||
Review_app_desc: 'Nos dê 5 estrelas na {{store}}',
|
Review_app_desc: 'Nos dê 5 estrelas na {{store}}',
|
||||||
Review_app_yes: 'Claro!',
|
Review_app_yes: 'Claro!',
|
||||||
|
@ -377,7 +392,9 @@ export default {
|
||||||
Select_Server: 'Selecionar Servidor',
|
Select_Server: 'Selecionar Servidor',
|
||||||
Select_Users: 'Selecionar Usuários',
|
Select_Users: 'Selecionar Usuários',
|
||||||
Select_a_Channel: 'Selecione um canal',
|
Select_a_Channel: 'Selecione um canal',
|
||||||
|
Select_a_Department: 'Selecione um Departamento',
|
||||||
Select_an_option: 'Selecione uma opção',
|
Select_an_option: 'Selecione uma opção',
|
||||||
|
Select_a_User: 'Selecione um Usuário',
|
||||||
Send: 'Enviar',
|
Send: 'Enviar',
|
||||||
Send_audio_message: 'Enviar mensagem de áudio',
|
Send_audio_message: 'Enviar mensagem de áudio',
|
||||||
Send_message: 'Enviar mensagem',
|
Send_message: 'Enviar mensagem',
|
||||||
|
@ -436,6 +453,7 @@ export default {
|
||||||
Updating: 'Atualizando...',
|
Updating: 'Atualizando...',
|
||||||
Uploading: 'Subindo arquivo',
|
Uploading: 'Subindo arquivo',
|
||||||
Upload_file_question_mark: 'Enviar arquivo?',
|
Upload_file_question_mark: 'Enviar arquivo?',
|
||||||
|
User: 'Usuário',
|
||||||
Users: 'Usuários',
|
Users: 'Usuários',
|
||||||
User_added_by: 'Usuário {{userAdded}} adicionado por {{userBy}}',
|
User_added_by: 'Usuário {{userAdded}} adicionado por {{userBy}}',
|
||||||
User_has_been_key: 'Usuário foi {{key}}!',
|
User_has_been_key: 'Usuário foi {{key}}!',
|
||||||
|
@ -479,8 +497,10 @@ export default {
|
||||||
Your_invite_link_will_never_expire: 'Seu link de convite nunca irá vencer.',
|
Your_invite_link_will_never_expire: 'Seu link de convite nunca irá vencer.',
|
||||||
Your_workspace: 'Sua workspace',
|
Your_workspace: 'Sua workspace',
|
||||||
You_will_not_be_able_to_recover_this_message: 'Você não será capaz de recuperar essa mensagem!',
|
You_will_not_be_able_to_recover_this_message: 'Você não será capaz de recuperar essa mensagem!',
|
||||||
|
Would_you_like_to_return_the_inquiry: 'Deseja retornar a consulta?',
|
||||||
Write_External_Permission_Message: 'Rocket Chat precisa de acesso à sua galeria para salvar imagens',
|
Write_External_Permission_Message: 'Rocket Chat precisa de acesso à sua galeria para salvar imagens',
|
||||||
Write_External_Permission: 'Acesso à Galeria',
|
Write_External_Permission: 'Acesso à Galeria',
|
||||||
|
Yes: 'Sim',
|
||||||
Crash_report_disclaimer: 'Nós não rastreamos o conteúdo das suas conversas. O relatório de erros apenas contém informações relevantes para identificarmos problemas e corrigí-los.',
|
Crash_report_disclaimer: 'Nós não rastreamos o conteúdo das suas conversas. O relatório de erros apenas contém informações relevantes para identificarmos problemas e corrigí-los.',
|
||||||
Type_message: 'Digitar mensagem',
|
Type_message: 'Digitar mensagem',
|
||||||
Room_search: 'Busca de sala',
|
Room_search: 'Busca de sala',
|
||||||
|
|
|
@ -168,6 +168,15 @@ const ChatsStack = createStackNavigator({
|
||||||
NotificationPrefView: {
|
NotificationPrefView: {
|
||||||
getScreen: () => require('./views/NotificationPreferencesView').default
|
getScreen: () => require('./views/NotificationPreferencesView').default
|
||||||
},
|
},
|
||||||
|
VisitorNavigationView: {
|
||||||
|
getScreen: () => require('./views/VisitorNavigationView').default
|
||||||
|
},
|
||||||
|
ForwardLivechatView: {
|
||||||
|
getScreen: () => require('./views/ForwardLivechatView').default
|
||||||
|
},
|
||||||
|
LivechatEditView: {
|
||||||
|
getScreen: () => require('./views/LivechatEditView').default
|
||||||
|
},
|
||||||
PickerView: {
|
PickerView: {
|
||||||
getScreen: () => require('./views/PickerView').default
|
getScreen: () => require('./views/PickerView').default
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,4 +13,14 @@ export default class Room extends Model {
|
||||||
@field('encrypted') encrypted;
|
@field('encrypted') encrypted;
|
||||||
|
|
||||||
@field('ro') ro;
|
@field('ro') ro;
|
||||||
|
|
||||||
|
@json('v', sanitizer) v;
|
||||||
|
|
||||||
|
@json('served_by', sanitizer) servedBy;
|
||||||
|
|
||||||
|
@field('department_id') departmentId;
|
||||||
|
|
||||||
|
@json('livechat_data', sanitizer) livechatData;
|
||||||
|
|
||||||
|
@json('tags', sanitizer) tags;
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,4 +97,14 @@ export default class Subscription extends Model {
|
||||||
@json('uids', sanitizer) uids;
|
@json('uids', sanitizer) uids;
|
||||||
|
|
||||||
@json('usernames', sanitizer) usernames;
|
@json('usernames', sanitizer) usernames;
|
||||||
|
|
||||||
|
@json('visitor', sanitizer) visitor;
|
||||||
|
|
||||||
|
@field('department_id') departmentId;
|
||||||
|
|
||||||
|
@json('served_by', sanitizer) servedBy;
|
||||||
|
|
||||||
|
@json('livechat_data', sanitizer) livechatData;
|
||||||
|
|
||||||
|
@json('tags', sanitizer) tags;
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,22 @@ export default schemaMigrations({
|
||||||
addColumns({
|
addColumns({
|
||||||
table: 'subscriptions',
|
table: 'subscriptions',
|
||||||
columns: [
|
columns: [
|
||||||
{ name: 'banner_closed', type: 'boolean', isOptional: true }
|
{ name: 'banner_closed', type: 'boolean', isOptional: true },
|
||||||
|
{ name: 'visitor', type: 'string', isOptional: true },
|
||||||
|
{ name: 'department_id', type: 'string', isOptional: true },
|
||||||
|
{ name: 'served_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'livechat_data', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tags', type: 'string', isOptional: true }
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
addColumns({
|
||||||
|
table: 'rooms',
|
||||||
|
columns: [
|
||||||
|
{ name: 'v', type: 'string', isOptional: true },
|
||||||
|
{ name: 'department_id', type: 'string', isOptional: true },
|
||||||
|
{ name: 'served_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'livechat_data', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tags', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
|
@ -43,7 +43,12 @@ export default appSchema({
|
||||||
{ name: 'hide_unread_status', type: 'boolean', isOptional: true },
|
{ name: 'hide_unread_status', type: 'boolean', isOptional: true },
|
||||||
{ name: 'sys_mes', type: 'string', isOptional: true },
|
{ name: 'sys_mes', type: 'string', isOptional: true },
|
||||||
{ name: 'uids', type: 'string', isOptional: true },
|
{ name: 'uids', type: 'string', isOptional: true },
|
||||||
{ name: 'usernames', type: 'string', isOptional: true }
|
{ name: 'usernames', type: 'string', isOptional: true },
|
||||||
|
{ name: 'visitor', type: 'string', isOptional: true },
|
||||||
|
{ name: 'department_id', type: 'string', isOptional: true },
|
||||||
|
{ name: 'served_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'livechat_data', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tags', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
@ -52,7 +57,12 @@ export default appSchema({
|
||||||
{ name: 'custom_fields', type: 'string' },
|
{ name: 'custom_fields', type: 'string' },
|
||||||
{ name: 'broadcast', type: 'boolean' },
|
{ name: 'broadcast', type: 'boolean' },
|
||||||
{ name: 'encrypted', type: 'boolean' },
|
{ name: 'encrypted', type: 'boolean' },
|
||||||
{ name: 'ro', type: 'boolean' }
|
{ name: 'ro', type: 'boolean' },
|
||||||
|
{ name: 'v', type: 'string', isOptional: true },
|
||||||
|
{ name: 'department_id', type: 'string', isOptional: true },
|
||||||
|
{ name: 'served_by', type: 'string', isOptional: true },
|
||||||
|
{ name: 'livechat_data', type: 'string', isOptional: true },
|
||||||
|
{ name: 'tags', type: 'string', isOptional: true }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
tableSchema({
|
||||||
|
|
|
@ -44,7 +44,12 @@ export default async(subscriptions = [], rooms = []) => {
|
||||||
autoTranslateLanguage: s.autoTranslateLanguage,
|
autoTranslateLanguage: s.autoTranslateLanguage,
|
||||||
lastMessage: s.lastMessage,
|
lastMessage: s.lastMessage,
|
||||||
usernames: s.usernames,
|
usernames: s.usernames,
|
||||||
uids: s.uids
|
uids: s.uids,
|
||||||
|
visitor: s.visitor,
|
||||||
|
departmentId: s.departmentId,
|
||||||
|
servedBy: s.servedBy,
|
||||||
|
livechatData: s.livechatData,
|
||||||
|
tags: s.tags
|
||||||
}));
|
}));
|
||||||
subscriptions = subscriptions.concat(existingSubs);
|
subscriptions = subscriptions.concat(existingSubs);
|
||||||
|
|
||||||
|
@ -65,7 +70,12 @@ export default async(subscriptions = [], rooms = []) => {
|
||||||
ro: r.ro,
|
ro: r.ro,
|
||||||
broadcast: r.broadcast,
|
broadcast: r.broadcast,
|
||||||
muted: r.muted,
|
muted: r.muted,
|
||||||
sysMes: r.sysMes
|
sysMes: r.sysMes,
|
||||||
|
v: r.v,
|
||||||
|
departmentId: r.departmentId,
|
||||||
|
servedBy: r.servedBy,
|
||||||
|
livechatData: r.livechatData,
|
||||||
|
tags: r.tags
|
||||||
}));
|
}));
|
||||||
rooms = rooms.concat(existingRooms);
|
rooms = rooms.concat(existingRooms);
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -35,6 +35,21 @@ export const merge = (subscription, room) => {
|
||||||
} else {
|
} else {
|
||||||
subscription.muted = [];
|
subscription.muted = [];
|
||||||
}
|
}
|
||||||
|
if (room.v) {
|
||||||
|
subscription.visitor = room.v;
|
||||||
|
}
|
||||||
|
if (room.departmentId) {
|
||||||
|
subscription.departmentId = room.departmentId;
|
||||||
|
}
|
||||||
|
if (room.servedBy) {
|
||||||
|
subscription.servedBy = room.servedBy;
|
||||||
|
}
|
||||||
|
if (room.livechatData) {
|
||||||
|
subscription.livechatData = room.livechatData;
|
||||||
|
}
|
||||||
|
if (room.tags) {
|
||||||
|
subscription.tags = room.tags;
|
||||||
|
}
|
||||||
subscription.sysMes = room.sysMes;
|
subscription.sysMes = room.sysMes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,12 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
lastMessage: s.lastMessage,
|
lastMessage: s.lastMessage,
|
||||||
roles: s.roles,
|
roles: s.roles,
|
||||||
usernames: s.usernames,
|
usernames: s.usernames,
|
||||||
uids: s.uids
|
uids: s.uids,
|
||||||
|
visitor: s.visitor,
|
||||||
|
departmentId: s.departmentId,
|
||||||
|
servedBy: s.servedBy,
|
||||||
|
livechatData: s.livechatData,
|
||||||
|
tags: s.tags
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
try {
|
try {
|
||||||
|
@ -98,10 +103,15 @@ const createOrUpdateSubscription = async(subscription, room) => {
|
||||||
// We have to create a plain obj so we can manipulate it on `merge`
|
// We have to create a plain obj so we can manipulate it on `merge`
|
||||||
// Can we do it in a better way?
|
// Can we do it in a better way?
|
||||||
room = {
|
room = {
|
||||||
customFields: r.customFields,
|
v: r.v,
|
||||||
broadcast: r.broadcast,
|
ro: r.ro,
|
||||||
|
tags: r.tags,
|
||||||
|
servedBy: r.servedBy,
|
||||||
encrypted: r.encrypted,
|
encrypted: r.encrypted,
|
||||||
ro: r.ro
|
broadcast: r.broadcast,
|
||||||
|
customFields: r.customFields,
|
||||||
|
departmentId: r.departmentId,
|
||||||
|
livechatData: r.livechatData
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
|
|
@ -757,6 +757,59 @@ const RocketChat = {
|
||||||
return this.sdk.get('rooms.info', { roomId });
|
return this.sdk.get('rooms.info', { roomId });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getVisitorInfo(visitorId) {
|
||||||
|
// RC 2.3.0
|
||||||
|
return this.sdk.get('livechat/visitors.info', { visitorId });
|
||||||
|
},
|
||||||
|
closeLivechat(rid, comment) {
|
||||||
|
// RC 0.29.0
|
||||||
|
return this.methodCall('livechat:closeRoom', rid, comment, { clientAction: true });
|
||||||
|
},
|
||||||
|
editLivechat(userData, roomData) {
|
||||||
|
// RC 0.55.0
|
||||||
|
return this.methodCall('livechat:saveInfo', userData, roomData);
|
||||||
|
},
|
||||||
|
returnLivechat(rid) {
|
||||||
|
// RC 0.72.0
|
||||||
|
return this.methodCall('livechat:returnAsInquiry', rid);
|
||||||
|
},
|
||||||
|
forwardLivechat(transferData) {
|
||||||
|
// RC 0.36.0
|
||||||
|
return this.methodCall('livechat:transfer', transferData);
|
||||||
|
},
|
||||||
|
getPagesLivechat(rid, offset) {
|
||||||
|
// RC 2.3.0
|
||||||
|
return this.sdk.get(`livechat/visitors.pagesVisited/${ rid }?count=50&offset=${ offset }`);
|
||||||
|
},
|
||||||
|
getDepartmentInfo(departmentId) {
|
||||||
|
// RC 2.2.0
|
||||||
|
return this.sdk.get(`livechat/department/${ departmentId }?includeAgents=false`);
|
||||||
|
},
|
||||||
|
getDepartments() {
|
||||||
|
// RC 2.2.0
|
||||||
|
return this.sdk.get('livechat/department');
|
||||||
|
},
|
||||||
|
usersAutoComplete(selector) {
|
||||||
|
// RC 2.4.0
|
||||||
|
return this.sdk.get('users.autocomplete', { selector });
|
||||||
|
},
|
||||||
|
getRoutingConfig() {
|
||||||
|
// RC 2.0.0
|
||||||
|
return this.methodCall('livechat:getRoutingConfig');
|
||||||
|
},
|
||||||
|
getTagsList() {
|
||||||
|
// RC 2.0.0
|
||||||
|
return this.methodCall('livechat:getTagsList');
|
||||||
|
},
|
||||||
|
getAgentDepartments(uid) {
|
||||||
|
// RC 2.4.0
|
||||||
|
return this.sdk.get(`livechat/agents/${ uid }/departments`);
|
||||||
|
},
|
||||||
|
getCustomFields() {
|
||||||
|
// RC 2.2.0
|
||||||
|
return this.sdk.get('livechat/custom-fields');
|
||||||
|
},
|
||||||
|
|
||||||
getUidDirectMessage(room) {
|
getUidDirectMessage(room) {
|
||||||
const { id: userId } = reduxStore.getState().login.user;
|
const { id: userId } = reduxStore.getState().login.user;
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ const TypeIcon = React.memo(({
|
||||||
if (type === 'd' && !isGroupChat) {
|
if (type === 'd' && !isGroupChat) {
|
||||||
return <Status style={styles.status} size={10} status={status} />;
|
return <Status style={styles.status} size={10} status={status} />;
|
||||||
}
|
}
|
||||||
return <RoomTypeIcon theme={theme} type={prid ? 'discussion' : type} isGroupChat={isGroupChat} />;
|
return <RoomTypeIcon theme={theme} type={prid ? 'discussion' : type} isGroupChat={isGroupChat} status={status} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
TypeIcon.propTypes = {
|
TypeIcon.propTypes = {
|
||||||
|
|
|
@ -209,12 +209,20 @@ RoomItem.defaultProps = {
|
||||||
getUserPresence: () => {}
|
getUserPresence: () => {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => ({
|
const mapStateToProps = (state, ownProps) => {
|
||||||
|
let status = 'offline';
|
||||||
|
const { id, type, visitor = {} } = ownProps;
|
||||||
|
if (state.meteor.connected) {
|
||||||
|
if (type === 'd') {
|
||||||
|
status = state.activeUsers[id]?.status || 'offline';
|
||||||
|
} else if (type === 'l' && visitor?.status) {
|
||||||
|
({ status } = visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
connected: state.meteor.connected,
|
connected: state.meteor.connected,
|
||||||
status:
|
status
|
||||||
state.meteor.connected && ownProps.type === 'd'
|
};
|
||||||
? state.activeUsers[ownProps.id] && state.activeUsers[ownProps.id].status
|
};
|
||||||
: 'offline'
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps)(RoomItem);
|
export default connect(mapStateToProps)(RoomItem);
|
||||||
|
|
|
@ -31,6 +31,18 @@ export default function(state = initialState, action) {
|
||||||
rid: action.rid,
|
rid: action.rid,
|
||||||
isDeleting: true
|
isDeleting: true
|
||||||
};
|
};
|
||||||
|
case ROOM.CLOSE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
rid: action.rid,
|
||||||
|
isDeleting: true
|
||||||
|
};
|
||||||
|
case ROOM.FORWARD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
rid: action.rid,
|
||||||
|
isDeleting: true
|
||||||
|
};
|
||||||
case ROOM.REMOVED:
|
case ROOM.REMOVED:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Alert } from 'react-native';
|
import { Alert } from 'react-native';
|
||||||
|
import prompt from 'react-native-prompt-android';
|
||||||
import {
|
import {
|
||||||
takeLatest, take, select, delay, race, put
|
takeLatest, take, select, delay, race, put
|
||||||
} from 'redux-saga/effects';
|
} from 'redux-saga/effects';
|
||||||
|
@ -9,6 +10,7 @@ import { removedRoom } from '../actions/room';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
|
import { showErrorAlert } from '../utils/info';
|
||||||
|
|
||||||
const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
||||||
const auth = yield select(state => state.login.isAuthenticated);
|
const auth = yield select(state => state.login.isAuthenticated);
|
||||||
|
@ -28,10 +30,8 @@ const watchUserTyping = function* watchUserTyping({ rid, status }) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemovedRoom = function* handleLeaveRoom({ result }) {
|
const handleRemovedRoom = function* handleRemovedRoom() {
|
||||||
if (result.success) {
|
|
||||||
yield Navigation.navigate('RoomsListView');
|
yield Navigation.navigate('RoomsListView');
|
||||||
}
|
|
||||||
// types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg
|
// types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg
|
||||||
const { timeout } = yield race({
|
const { timeout } = yield race({
|
||||||
deleteFinished: take(types.ROOM.REMOVED),
|
deleteFinished: take(types.ROOM.REMOVED),
|
||||||
|
@ -45,7 +45,9 @@ const handleRemovedRoom = function* handleLeaveRoom({ result }) {
|
||||||
const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
|
const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
|
||||||
try {
|
try {
|
||||||
const result = yield RocketChat.leaveRoom(rid, t);
|
const result = yield RocketChat.leaveRoom(rid, t);
|
||||||
yield handleRemovedRoom({ result });
|
if (result.success) {
|
||||||
|
yield handleRemovedRoom();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.data && e.data.errorType === 'error-you-are-last-owner') {
|
if (e.data && e.data.errorType === 'error-you-are-last-owner') {
|
||||||
Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType));
|
Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType));
|
||||||
|
@ -58,15 +60,65 @@ const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) {
|
||||||
const handleDeleteRoom = function* handleDeleteRoom({ rid, t }) {
|
const handleDeleteRoom = function* handleDeleteRoom({ rid, t }) {
|
||||||
try {
|
try {
|
||||||
const result = yield RocketChat.deleteRoom(rid, t);
|
const result = yield RocketChat.deleteRoom(rid, t);
|
||||||
yield handleRemovedRoom({ result });
|
if (result.success) {
|
||||||
|
yield handleRemovedRoom();
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_room') }));
|
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_room') }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCloseRoom = function* handleCloseRoom({ rid }) {
|
||||||
|
const requestComment = yield select(state => state.settings.Livechat_request_comment_when_closing_conversation);
|
||||||
|
|
||||||
|
const closeRoom = async(comment = '') => {
|
||||||
|
try {
|
||||||
|
await RocketChat.closeLivechat(rid, comment);
|
||||||
|
Navigation.navigate('RoomsListView');
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!requestComment) {
|
||||||
|
const comment = I18n.t('Chat_closed_by_agent');
|
||||||
|
return closeRoom(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt(
|
||||||
|
I18n.t('Closing_chat'),
|
||||||
|
I18n.t('Please_add_a_comment'),
|
||||||
|
[
|
||||||
|
{ text: I18n.t('Cancel'), onPress: () => { }, style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: I18n.t('Submit'),
|
||||||
|
onPress: comment => closeRoom(comment)
|
||||||
|
}
|
||||||
|
],
|
||||||
|
{
|
||||||
|
cancelable: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleForwardRoom = function* handleForwardRoom({ transferData }) {
|
||||||
|
try {
|
||||||
|
const result = yield RocketChat.forwardLivechat(transferData);
|
||||||
|
if (result === true) {
|
||||||
|
Navigation.navigate('RoomsListView');
|
||||||
|
} else {
|
||||||
|
showErrorAlert(I18n.t('No_available_agents_to_transfer'), I18n.t('Oops'));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e.reason, I18n.t('Oops'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.ROOM.USER_TYPING, watchUserTyping);
|
yield takeLatest(types.ROOM.USER_TYPING, watchUserTyping);
|
||||||
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
yield takeLatest(types.ROOM.LEAVE, handleLeaveRoom);
|
||||||
yield takeLatest(types.ROOM.DELETE, handleDeleteRoom);
|
yield takeLatest(types.ROOM.DELETE, handleDeleteRoom);
|
||||||
|
yield takeLatest(types.ROOM.CLOSE, handleCloseRoom);
|
||||||
|
yield takeLatest(types.ROOM.FORWARD, handleForwardRoom);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { View, StyleSheet } from 'react-native';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import OrSeparator from '../containers/OrSeparator';
|
||||||
|
import Input from '../containers/UIKit/MultiSelect/Input';
|
||||||
|
import { forwardRoom as forwardRoomAction } from '../actions/room';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
padding: 16
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const ForwardLivechatView = ({ forwardRoom, navigation, theme }) => {
|
||||||
|
const [departments, setDepartments] = useState([]);
|
||||||
|
const [departmentId, setDepartment] = useState();
|
||||||
|
const [users, setUsers] = useState([]);
|
||||||
|
const [userId, setUser] = useState();
|
||||||
|
const [room, setRoom] = useState();
|
||||||
|
|
||||||
|
const rid = navigation.getParam('rid');
|
||||||
|
|
||||||
|
const getDepartments = async() => {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getDepartments();
|
||||||
|
if (result.success) {
|
||||||
|
setDepartments(result.departments.map(department => ({ label: department.name, value: department._id })));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUsers = async(term = '') => {
|
||||||
|
try {
|
||||||
|
const { servedBy: { _id: agentId } = {} } = room;
|
||||||
|
const _id = agentId && { $ne: agentId };
|
||||||
|
const result = await RocketChat.usersAutoComplete({ conditions: { _id, status: { $ne: 'offline' }, statusLivechat: 'available' }, term });
|
||||||
|
if (result.success) {
|
||||||
|
const parsedUsers = result.items.map(user => ({ label: user.username, value: user._id }));
|
||||||
|
setUsers(parsedUsers);
|
||||||
|
return parsedUsers;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoom = async() => {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getRoomInfo(rid);
|
||||||
|
if (result.success) {
|
||||||
|
setRoom(result.room);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = () => {
|
||||||
|
const transferData = { roomId: rid };
|
||||||
|
|
||||||
|
if (!departmentId && !userId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
transferData.userId = userId;
|
||||||
|
} else {
|
||||||
|
transferData.departmentId = departmentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
forwardRoom(rid, transferData);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getRoom();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (room) {
|
||||||
|
getUsers();
|
||||||
|
getDepartments();
|
||||||
|
}
|
||||||
|
}, [room]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (departmentId || userId) {
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
}, [departmentId, userId]);
|
||||||
|
|
||||||
|
const onPressDepartment = () => {
|
||||||
|
navigation.navigate('PickerView', {
|
||||||
|
title: I18n.t('Forward_to_department'),
|
||||||
|
value: room?.departmentId,
|
||||||
|
data: departments,
|
||||||
|
onChangeValue: setDepartment,
|
||||||
|
goBack: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPressUser = () => {
|
||||||
|
navigation.navigate('PickerView', {
|
||||||
|
title: I18n.t('Forward_to_user'),
|
||||||
|
data: users,
|
||||||
|
onChangeValue: setUser,
|
||||||
|
onChangeText: getUsers,
|
||||||
|
goBack: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.container, { backgroundColor: themes[theme].auxiliaryBackground }]}>
|
||||||
|
<Input
|
||||||
|
onPress={onPressDepartment}
|
||||||
|
placeholder={I18n.t('Select_a_Department')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<OrSeparator theme={theme} />
|
||||||
|
<Input
|
||||||
|
onPress={onPressUser}
|
||||||
|
placeholder={I18n.t('Select_a_User')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
ForwardLivechatView.propTypes = {
|
||||||
|
forwardRoom: PropTypes.func,
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
ForwardLivechatView.navigationOptions = {
|
||||||
|
title: I18n.t('Forward_Chat')
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
forwardRoom: (rid, transferData) => dispatch(forwardRoomAction(rid, transferData))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(withTheme(ForwardLivechatView));
|
|
@ -0,0 +1,285 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Text, StyleSheet, ScrollView } from 'react-native';
|
||||||
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import TextInput from '../containers/TextInput';
|
||||||
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
|
||||||
|
import sharedStyles from './Styles';
|
||||||
|
import { LISTENER } from '../containers/Toast';
|
||||||
|
import EventEmitter from '../utils/events';
|
||||||
|
import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
|
import { getUserSelector } from '../selectors/login';
|
||||||
|
import Chips from '../containers/UIKit/MultiSelect/Chips';
|
||||||
|
import Button from '../containers/Button';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
padding: 16
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 20,
|
||||||
|
paddingVertical: 10,
|
||||||
|
...sharedStyles.textMedium
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = ({ title, theme }) => (title ? <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text> : null);
|
||||||
|
Title.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const LivechatEditView = ({ user, navigation, theme }) => {
|
||||||
|
const [customFields, setCustomFields] = useState({});
|
||||||
|
const [availableUserTags, setAvailableUserTags] = useState([]);
|
||||||
|
|
||||||
|
const params = {};
|
||||||
|
const inputs = {};
|
||||||
|
|
||||||
|
const livechat = navigation.getParam('room', {});
|
||||||
|
const visitor = navigation.getParam('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]: 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]: field, ...ret }));
|
||||||
|
|
||||||
|
return setCustomFields({ visitor: visitorCustomFields, livechat: livechatCustomFields });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const [tagParam, setTags] = useState(livechat?.tags || []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setTags([...tagParam, ...availableUserTags]);
|
||||||
|
}, [availableUserTags]);
|
||||||
|
|
||||||
|
const getTagsList = async(agentDepartments) => {
|
||||||
|
const tags = 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)))
|
||||||
|
.map(({ name }) => name);
|
||||||
|
setAvailableUserTags(availableTags);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAgentDepartments = async() => {
|
||||||
|
const result = await RocketChat.getAgentDepartments(visitor?._id);
|
||||||
|
if (result.success) {
|
||||||
|
const agentDepartments = result.departments.map(dept => dept.departmentId);
|
||||||
|
getTagsList(agentDepartments);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const submit = async() => {
|
||||||
|
const userData = { _id: visitor?._id };
|
||||||
|
|
||||||
|
const { rid, sms } = livechat;
|
||||||
|
const roomData = { _id: rid };
|
||||||
|
|
||||||
|
if (params.name) {
|
||||||
|
userData.name = params.name;
|
||||||
|
}
|
||||||
|
if (params.email) {
|
||||||
|
userData.email = params.email;
|
||||||
|
}
|
||||||
|
if (params.phone) {
|
||||||
|
userData.phone = params.phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
userData.livechatData = {};
|
||||||
|
Object.entries(customFields?.visitor || {}).forEach(([key]) => {
|
||||||
|
if (params[key] || params[key] === '') {
|
||||||
|
userData.livechatData[key] = params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (params.topic) {
|
||||||
|
roomData.topic = params.topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
roomData.tags = tagParam;
|
||||||
|
|
||||||
|
roomData.livechatData = {};
|
||||||
|
Object.entries(customFields?.livechat || {}).forEach(([key]) => {
|
||||||
|
if (params[key] || params[key] === '') {
|
||||||
|
roomData.livechatData[key] = params[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sms) {
|
||||||
|
delete userData.phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await RocketChat.editLivechat(userData, roomData);
|
||||||
|
if (error) {
|
||||||
|
EventEmitter.emit(LISTENER, { message: error });
|
||||||
|
} else {
|
||||||
|
EventEmitter.emit(LISTENER, { message: I18n.t('Saved') });
|
||||||
|
navigation.goBack();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onChangeText = (key, text) => { params[key] = text; };
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAgentDepartments();
|
||||||
|
getCustomFields();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KeyboardView
|
||||||
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
|
contentContainerStyle={sharedStyles.container}
|
||||||
|
keyboardVerticalOffset={128}
|
||||||
|
>
|
||||||
|
<ScrollView {...scrollPersistTaps}>
|
||||||
|
<SafeAreaView style={[sharedStyles.container, styles.container]} forceInset={{ vertical: 'never' }}>
|
||||||
|
<Title
|
||||||
|
title={visitor?.username}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={I18n.t('Name')}
|
||||||
|
defaultValue={visitor?.name}
|
||||||
|
onChangeText={text => onChangeText('name', text)}
|
||||||
|
onSubmitEditing={() => { inputs.name.focus(); }}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={I18n.t('Email')}
|
||||||
|
inputRef={(e) => { inputs.name = e; }}
|
||||||
|
defaultValue={visitor?.visitorEmails && visitor?.visitorEmails[0]?.address}
|
||||||
|
onChangeText={text => onChangeText('email', text)}
|
||||||
|
onSubmitEditing={() => { inputs.phone.focus(); }}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={I18n.t('Phone')}
|
||||||
|
inputRef={(e) => { inputs.phone = e; }}
|
||||||
|
defaultValue={visitor?.phone && visitor?.phone[0]?.phoneNumber}
|
||||||
|
onChangeText={text => onChangeText('phone', text)}
|
||||||
|
onSubmitEditing={() => {
|
||||||
|
const keys = Object.keys(customFields?.visitor || {});
|
||||||
|
if (keys.length > 0) {
|
||||||
|
const key = keys.pop();
|
||||||
|
inputs[key].focus();
|
||||||
|
} else {
|
||||||
|
inputs.topic.focus();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
{Object.entries(customFields?.visitor || {}).map(([key, value], index, array) => (
|
||||||
|
<TextInput
|
||||||
|
label={key}
|
||||||
|
defaultValue={value}
|
||||||
|
inputRef={(e) => { inputs[key] = e; }}
|
||||||
|
onChangeText={text => onChangeText(key, text)}
|
||||||
|
onSubmitEditing={() => {
|
||||||
|
if (array.length - 1 > index) {
|
||||||
|
return inputs[array[index + 1]].focus();
|
||||||
|
}
|
||||||
|
inputs.topic.focus();
|
||||||
|
}}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<Title
|
||||||
|
title={I18n.t('Conversation')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label={I18n.t('Topic')}
|
||||||
|
inputRef={(e) => { inputs.topic = e; }}
|
||||||
|
defaultValue={livechat?.topic}
|
||||||
|
onChangeText={text => onChangeText('topic', text)}
|
||||||
|
onSubmitEditing={() => inputs.tags.focus()}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextInput
|
||||||
|
inputRef={(e) => { inputs.tags = e; }}
|
||||||
|
label={I18n.t('Tags')}
|
||||||
|
iconRight='plus'
|
||||||
|
onIconRightPress={() => {
|
||||||
|
const lastText = inputs.tags._lastNativeText || '';
|
||||||
|
if (lastText.length) {
|
||||||
|
setTags([...tagParam.filter(t => t !== lastText), lastText]);
|
||||||
|
inputs.tags.clear();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onSubmitEditing={() => {
|
||||||
|
const keys = Object.keys(customFields?.livechat || {});
|
||||||
|
if (keys.length > 0) {
|
||||||
|
const key = keys.pop();
|
||||||
|
inputs[key].focus();
|
||||||
|
} else {
|
||||||
|
submit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Chips
|
||||||
|
items={tagParam.map(tag => ({ text: { text: tag }, value: tag }))}
|
||||||
|
onSelect={tag => setTags(tagParam.filter(t => t !== tag.value) || [])}
|
||||||
|
style={{ backgroundColor: themes[theme].backgroundColor }}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{Object.entries(customFields?.livechat || {}).map(([key, value], index, array) => (
|
||||||
|
<TextInput
|
||||||
|
label={key}
|
||||||
|
defaultValue={value}
|
||||||
|
inputRef={(e) => { inputs[key] = e; }}
|
||||||
|
onChangeText={text => onChangeText(key, text)}
|
||||||
|
onSubmitEditing={() => {
|
||||||
|
if (array.length - 1 > index) {
|
||||||
|
return inputs[array[index + 1]].focus();
|
||||||
|
}
|
||||||
|
submit();
|
||||||
|
}}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
title={I18n.t('Save')}
|
||||||
|
onPress={submit}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</SafeAreaView>
|
||||||
|
</ScrollView>
|
||||||
|
</KeyboardView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
LivechatEditView.propTypes = {
|
||||||
|
user: PropTypes.object,
|
||||||
|
navigation: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
LivechatEditView.navigationOptions = ({
|
||||||
|
title: I18n.t('Livechat_edit')
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
server: state.server.server,
|
||||||
|
user: getUserSelector(state)
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(withTheme(LivechatEditView));
|
|
@ -19,7 +19,7 @@ import { appStart as appStartAction } from '../actions';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
import Button from '../containers/Button';
|
import Button from '../containers/Button';
|
||||||
import TextInput from '../containers/TextInput';
|
import TextInput from '../containers/TextInput';
|
||||||
import OnboardingSeparator from '../containers/OnboardingSeparator';
|
import OrSeparator from '../containers/OrSeparator';
|
||||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS } from '../utils/deviceInfo';
|
||||||
|
@ -324,7 +324,7 @@ class NewServerView extends React.Component {
|
||||||
testID='new-server-view-button'
|
testID='new-server-view-button'
|
||||||
theme={theme}
|
theme={theme}
|
||||||
/>
|
/>
|
||||||
<OnboardingSeparator theme={theme} />
|
<OrSeparator theme={theme} />
|
||||||
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_join_open_description')}</Text>
|
<Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{I18n.t('Onboarding_join_open_description')}</Text>
|
||||||
<Button
|
<Button
|
||||||
title={I18n.t('Join_our_open_workspace')}
|
title={I18n.t('Join_our_open_workspace')}
|
||||||
|
|
|
@ -1,20 +1,38 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FlatList, StyleSheet } from 'react-native';
|
import {
|
||||||
|
View, FlatList, StyleSheet, Text
|
||||||
|
} from 'react-native';
|
||||||
|
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import { themedHeader } from '../utils/navigation';
|
import { themedHeader } from '../utils/navigation';
|
||||||
import { withTheme } from '../theme';
|
import { withTheme } from '../theme';
|
||||||
import { themes } from '../constants/colors';
|
import { themes } from '../constants/colors';
|
||||||
|
import debounce from '../utils/debounce';
|
||||||
import sharedStyles from './Styles';
|
import sharedStyles from './Styles';
|
||||||
|
|
||||||
import ListItem from '../containers/ListItem';
|
import ListItem from '../containers/ListItem';
|
||||||
import Check from '../containers/Check';
|
import Check from '../containers/Check';
|
||||||
import Separator from '../containers/Separator';
|
import Separator from '../containers/Separator';
|
||||||
|
import SearchBox from '../containers/SearchBox';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
check: {
|
check: {
|
||||||
marginHorizontal: 0
|
marginHorizontal: 0
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
width: '100%',
|
||||||
|
height: 56
|
||||||
|
},
|
||||||
|
noResult: {
|
||||||
|
fontSize: 16,
|
||||||
|
paddingVertical: 56,
|
||||||
|
...sharedStyles.textAlignCenter,
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
withoutBorder: {
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
borderTopWidth: 0
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -54,20 +72,46 @@ class PickerView extends React.PureComponent {
|
||||||
const data = props.navigation.getParam('data', []);
|
const data = props.navigation.getParam('data', []);
|
||||||
const value = props.navigation.getParam('value');
|
const value = props.navigation.getParam('value');
|
||||||
this.state = { data, value };
|
this.state = { data, value };
|
||||||
|
|
||||||
|
this.onSearch = props.navigation.getParam('onChangeText');
|
||||||
}
|
}
|
||||||
|
|
||||||
onChangeValue = (value) => {
|
onChangeValue = (value) => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
const goBack = navigation.getParam('goBack', true);
|
||||||
const onChange = navigation.getParam('onChangeValue', () => {});
|
const onChange = navigation.getParam('onChangeValue', () => {});
|
||||||
onChange(value);
|
onChange(value);
|
||||||
|
if (goBack) {
|
||||||
navigation.goBack();
|
navigation.goBack();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChangeText = debounce(async(text) => {
|
||||||
|
if (this.onSearch) {
|
||||||
|
const data = await this.onSearch(text);
|
||||||
|
this.setState({ data });
|
||||||
|
}
|
||||||
|
}, 300, true)
|
||||||
|
|
||||||
|
renderSearch() {
|
||||||
|
if (!this.onSearch) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.search}>
|
||||||
|
<SearchBox onChangeText={this.onChangeText} />
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { data, value } = this.state;
|
const { data, value } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{this.renderSearch()}
|
||||||
<FlatList
|
<FlatList
|
||||||
data={data}
|
data={data}
|
||||||
keyExtractor={item => item.value}
|
keyExtractor={item => item.value}
|
||||||
|
@ -75,20 +119,23 @@ class PickerView extends React.PureComponent {
|
||||||
<Item
|
<Item
|
||||||
item={item}
|
item={item}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
selected={(value || data[0]?.value) === item.value}
|
selected={!this.onSearch && (value || data[0]?.value) === item.value}
|
||||||
onItemPress={() => this.onChangeValue(item.value)}
|
onItemPress={() => this.onChangeValue(item.value)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
||||||
|
ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>}
|
||||||
contentContainerStyle={[
|
contentContainerStyle={[
|
||||||
sharedStyles.listContentContainer,
|
sharedStyles.listContentContainer,
|
||||||
{
|
{
|
||||||
backgroundColor: themes[theme].auxiliaryBackground,
|
backgroundColor: themes[theme].auxiliaryBackground,
|
||||||
borderColor: themes[theme].separatorColor
|
borderColor: themes[theme].separatorColor
|
||||||
}
|
},
|
||||||
|
!data.length && styles.withoutBorder
|
||||||
]}
|
]}
|
||||||
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import _ from 'lodash';
|
||||||
|
|
||||||
import Touch from '../../utils/touch';
|
import Touch from '../../utils/touch';
|
||||||
import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
|
import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
|
||||||
import { leaveRoom as leaveRoomAction } from '../../actions/room';
|
import { leaveRoom as leaveRoomAction, closeRoom as closeRoomAction } from '../../actions/room';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
|
@ -28,6 +28,7 @@ import { themedHeader } from '../../utils/navigation';
|
||||||
import { CloseModalButton } from '../../containers/HeaderButton';
|
import { CloseModalButton } from '../../containers/HeaderButton';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import Markdown from '../../containers/markdown';
|
import Markdown from '../../containers/markdown';
|
||||||
|
import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
|
||||||
|
|
||||||
class RoomActionsView extends React.Component {
|
class RoomActionsView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => {
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
|
@ -51,6 +52,7 @@ class RoomActionsView extends React.Component {
|
||||||
leaveRoom: PropTypes.func,
|
leaveRoom: PropTypes.func,
|
||||||
jitsiEnabled: PropTypes.bool,
|
jitsiEnabled: PropTypes.bool,
|
||||||
setLoadingInvite: PropTypes.func,
|
setLoadingInvite: PropTypes.func,
|
||||||
|
closeRoom: PropTypes.func,
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +71,9 @@ class RoomActionsView extends React.Component {
|
||||||
canViewMembers: false,
|
canViewMembers: false,
|
||||||
canAutoTranslate: false,
|
canAutoTranslate: false,
|
||||||
canAddUser: false,
|
canAddUser: false,
|
||||||
canInviteUser: false
|
canInviteUser: false,
|
||||||
|
canForwardGuest: false,
|
||||||
|
canReturnQueue: false
|
||||||
};
|
};
|
||||||
if (room && room.observe && room.rid) {
|
if (room && room.observe && room.rid) {
|
||||||
this.roomObservable = room.observe();
|
this.roomObservable = room.observe();
|
||||||
|
@ -117,6 +121,12 @@ class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
this.canAddUser();
|
this.canAddUser();
|
||||||
this.canInviteUser();
|
this.canInviteUser();
|
||||||
|
|
||||||
|
// livechat permissions
|
||||||
|
if (room.t === 'l') {
|
||||||
|
this.canForwardGuest();
|
||||||
|
this.canReturnQueue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,9 +196,32 @@ class RoomActionsView extends React.Component {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canForwardGuest = async() => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { rid } = room;
|
||||||
|
let result = true;
|
||||||
|
|
||||||
|
const transferLivechatGuest = 'transfer-livechat-guest';
|
||||||
|
const permissions = await RocketChat.hasPermission([transferLivechatGuest], rid);
|
||||||
|
if (!permissions[transferLivechatGuest]) {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ canForwardGuest: result });
|
||||||
|
}
|
||||||
|
|
||||||
|
canReturnQueue = async() => {
|
||||||
|
try {
|
||||||
|
const { returnQueue } = await RocketChat.getRoutingConfig();
|
||||||
|
this.setState({ canReturnQueue: returnQueue });
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
const {
|
const {
|
||||||
room, member, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate
|
room, member, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { jitsiEnabled } = this.props;
|
const { jitsiEnabled } = this.props;
|
||||||
const {
|
const {
|
||||||
|
@ -373,7 +406,42 @@ class RoomActionsView extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (t === 'l') {
|
} else if (t === 'l') {
|
||||||
sections[2].data = [notificationsAction];
|
sections[2].data = [];
|
||||||
|
|
||||||
|
sections[2].data.push({
|
||||||
|
icon: 'circle-cross',
|
||||||
|
name: I18n.t('Close'),
|
||||||
|
event: this.closeLivechat
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canForwardGuest) {
|
||||||
|
sections[2].data.push({
|
||||||
|
icon: 'reply',
|
||||||
|
name: I18n.t('Forward'),
|
||||||
|
route: 'ForwardLivechatView',
|
||||||
|
params: { rid }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canReturnQueue) {
|
||||||
|
sections[2].data.push({
|
||||||
|
icon: 'back',
|
||||||
|
name: I18n.t('Return'),
|
||||||
|
event: this.returnLivechat
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sections[2].data.push({
|
||||||
|
icon: 'reload',
|
||||||
|
name: I18n.t('Navigation_history'),
|
||||||
|
route: 'VisitorNavigationView',
|
||||||
|
params: { rid }
|
||||||
|
});
|
||||||
|
|
||||||
|
sections.push({
|
||||||
|
data: [notificationsAction],
|
||||||
|
renderItem: this.renderItem
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return sections;
|
return sections;
|
||||||
|
@ -384,6 +452,28 @@ class RoomActionsView extends React.Component {
|
||||||
return <View style={[styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
|
return <View style={[styles.separator, { backgroundColor: themes[theme].separatorColor }]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeLivechat = () => {
|
||||||
|
const { room: { rid } } = this.state;
|
||||||
|
const { closeRoom } = this.props;
|
||||||
|
|
||||||
|
closeRoom(rid);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnLivechat = () => {
|
||||||
|
const { room: { rid } } = this.state;
|
||||||
|
showConfirmationAlert({
|
||||||
|
message: I18n.t('Would_you_like_to_return_the_inquiry'),
|
||||||
|
callToAction: I18n.t('Yes'),
|
||||||
|
onPress: async() => {
|
||||||
|
try {
|
||||||
|
await RocketChat.returnLivechat(rid);
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e.reason, I18n.t('Oops'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateRoomMember = async() => {
|
updateRoomMember = async() => {
|
||||||
const { room } = this.state;
|
const { room } = this.state;
|
||||||
|
|
||||||
|
@ -485,7 +575,7 @@ class RoomActionsView extends React.Component {
|
||||||
? <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.fname}</Text>
|
? <Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{room.fname}</Text>
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} theme={theme} />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} status={room.visitor?.status} theme={theme} />
|
||||||
<Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{RocketChat.getRoomTitle(room)}</Text>
|
<Text style={[styles.roomTitle, { color: themes[theme].titleText }]} numberOfLines={1}>{RocketChat.getRoomTitle(room)}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -583,6 +673,7 @@ const mapStateToProps = state => ({
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t)),
|
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t)),
|
||||||
|
closeRoom: rid => dispatch(closeRoomAction(rid)),
|
||||||
setLoadingInvite: loading => dispatch(setLoadingAction(loading))
|
setLoadingInvite: loading => dispatch(setLoadingAction(loading))
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
import Item from './Item';
|
||||||
|
|
||||||
|
const Channel = ({ room, theme }) => {
|
||||||
|
const { description, topic, announcement } = room;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Item
|
||||||
|
label={I18n.t('Description')}
|
||||||
|
content={description || `__${ I18n.t('No_label_provided', { label: 'description' }) }__`}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Item
|
||||||
|
label={I18n.t('Topic')}
|
||||||
|
content={topic || `__${ I18n.t('No_label_provided', { label: 'topic' }) }__`}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Item
|
||||||
|
label={I18n.t('Announcement')}
|
||||||
|
content={announcement || `__${ I18n.t('No_label_provided', { label: 'announcement' }) }__`}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Item
|
||||||
|
label={I18n.t('Broadcast_Channel')}
|
||||||
|
content={room.broadcast && I18n.t('Broadcast_channel_Description')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Channel.propTypes = {
|
||||||
|
room: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Channel;
|
|
@ -0,0 +1,31 @@
|
||||||
|
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 (
|
||||||
|
<Item
|
||||||
|
label={title}
|
||||||
|
content={customFields[title]}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
CustomFields.propTypes = {
|
||||||
|
customFields: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomFields;
|
|
@ -0,0 +1,42 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
import Timezone from './Timezone';
|
||||||
|
import CustomFields from './CustomFields';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
|
||||||
|
const Roles = ({ roles, theme }) => (roles && roles.length ? (
|
||||||
|
<View style={styles.item}>
|
||||||
|
<Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Roles')}</Text>
|
||||||
|
<View style={styles.rolesContainer}>
|
||||||
|
{roles.map(role => (role ? (
|
||||||
|
<View style={[styles.roleBadge, { backgroundColor: themes[theme].auxiliaryBackground }]} key={role}>
|
||||||
|
<Text style={styles.role}>{role}</Text>
|
||||||
|
</View>
|
||||||
|
) : null))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
) : null);
|
||||||
|
Roles.propTypes = {
|
||||||
|
roles: PropTypes.array,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const Direct = ({ roomUser, theme }) => (
|
||||||
|
<>
|
||||||
|
<Roles roles={roomUser.parsedRoles} theme={theme} />
|
||||||
|
<Timezone utcOffset={roomUser.utcOffset} theme={theme} />
|
||||||
|
<CustomFields customFields={roomUser.customFields} theme={theme} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
Direct.propTypes = {
|
||||||
|
roomUser: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Direct;
|
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { View, Text } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import styles from './styles';
|
||||||
|
import Markdown from '../../containers/markdown';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
|
||||||
|
const Item = ({ label, content, theme }) => (
|
||||||
|
content ? (
|
||||||
|
<View style={styles.item}>
|
||||||
|
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>{label}</Text>
|
||||||
|
<Markdown
|
||||||
|
style={[styles.itemContent, { color: themes[theme].auxiliaryText }]}
|
||||||
|
msg={content}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : null
|
||||||
|
);
|
||||||
|
Item.propTypes = {
|
||||||
|
label: PropTypes.string,
|
||||||
|
content: PropTypes.string,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Item;
|
|
@ -0,0 +1,140 @@
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Text, StyleSheet } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
import { withTheme } from '../../theme';
|
||||||
|
import CustomFields from './CustomFields';
|
||||||
|
import Item from './Item';
|
||||||
|
import Timezone from './Timezone';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
import { themes } from '../../constants/colors';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
title: {
|
||||||
|
fontSize: 16,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
...sharedStyles.textMedium
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Title = ({ title, theme }) => <Text style={[styles.title, { color: themes[theme].titleText }]}>{title}</Text>;
|
||||||
|
Title.propTypes = {
|
||||||
|
title: PropTypes.string,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const Livechat = ({ room, roomUser, theme }) => {
|
||||||
|
const [department, setDepartment] = useState({});
|
||||||
|
|
||||||
|
|
||||||
|
const getDepartment = async(id) => {
|
||||||
|
if (id) {
|
||||||
|
const result = await RocketChat.getDepartmentInfo(id);
|
||||||
|
if (result.success) {
|
||||||
|
setDepartment(result.department);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRoom = () => {
|
||||||
|
if (room.departmentId) {
|
||||||
|
getDepartment(room.departmentId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => { getRoom(); }, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Title
|
||||||
|
title={I18n.t('User')}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Timezone
|
||||||
|
utcOffset={roomUser.utc}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<Item
|
||||||
|
label={I18n.t('Username')}
|
||||||
|
content={roomUser.username}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
<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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Livechat.propTypes = {
|
||||||
|
room: PropTypes.object,
|
||||||
|
roomUser: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withTheme(Livechat);
|
|
@ -0,0 +1,26 @@
|
||||||
|
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);
|
|
@ -3,19 +3,20 @@ import PropTypes from 'prop-types';
|
||||||
import { View, Text, ScrollView } from 'react-native';
|
import { View, Text, ScrollView } from 'react-native';
|
||||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import moment from 'moment';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import { SafeAreaView } from 'react-navigation';
|
import { SafeAreaView } from 'react-navigation';
|
||||||
|
import UAParser from 'ua-parser-js';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
import database from '../../lib/database';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import Status from '../../containers/Status';
|
import Status from '../../containers/Status';
|
||||||
import Avatar from '../../containers/Avatar';
|
import Avatar from '../../containers/Avatar';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
import database from '../../lib/database';
|
|
||||||
import RocketChat from '../../lib/rocketchat';
|
import RocketChat from '../../lib/rocketchat';
|
||||||
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
import RoomTypeIcon from '../../containers/RoomTypeIcon';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { CustomHeaderButtons, Item } from '../../containers/HeaderButton';
|
import { CustomHeaderButtons } from '../../containers/HeaderButton';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
import { themes } from '../../constants/colors';
|
import { themes } from '../../constants/colors';
|
||||||
|
@ -24,6 +25,11 @@ import { themedHeader } from '../../utils/navigation';
|
||||||
import { getUserSelector } from '../../selectors/login';
|
import { getUserSelector } from '../../selectors/login';
|
||||||
import Markdown from '../../containers/markdown';
|
import Markdown from '../../containers/markdown';
|
||||||
|
|
||||||
|
import Livechat from './Livechat';
|
||||||
|
import Channel from './Channel';
|
||||||
|
import Item from './Item';
|
||||||
|
import Direct from './Direct';
|
||||||
|
|
||||||
const PERMISSION_EDIT_ROOM = 'edit-room';
|
const PERMISSION_EDIT_ROOM = 'edit-room';
|
||||||
const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd'
|
const getRoomTitle = (room, type, name, username, statusText, theme) => (type === 'd'
|
||||||
? (
|
? (
|
||||||
|
@ -35,7 +41,7 @@ const getRoomTitle = (room, type, name, username, statusText, theme) => (type ==
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<View style={styles.roomTitleRow}>
|
<View style={styles.roomTitleRow}>
|
||||||
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' theme={theme} />
|
<RoomTypeIcon type={room.prid ? 'discussion' : room.t} key='room-info-type' status={room.visitor?.status} theme={theme} />
|
||||||
<Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'>{RocketChat.getRoomTitle(room)}</Text>
|
<Text testID='room-info-view-name' style={[styles.roomTitle, { color: themes[theme].titleText }]} key='room-info-name'>{RocketChat.getRoomTitle(room)}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -43,16 +49,22 @@ const getRoomTitle = (room, type, name, username, statusText, theme) => (type ==
|
||||||
|
|
||||||
class RoomInfoView extends React.Component {
|
class RoomInfoView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => {
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
const showEdit = navigation.getParam('showEdit');
|
|
||||||
const rid = navigation.getParam('rid');
|
|
||||||
const t = navigation.getParam('t');
|
const t = navigation.getParam('t');
|
||||||
|
const rid = navigation.getParam('rid');
|
||||||
|
const room = navigation.getParam('room');
|
||||||
|
const roomUser = navigation.getParam('roomUser');
|
||||||
|
const showEdit = navigation.getParam('showEdit', t === 'l');
|
||||||
return {
|
return {
|
||||||
title: t === 'd' ? I18n.t('User_Info') : I18n.t('Room_Info'),
|
title: t === 'd' ? I18n.t('User_Info') : I18n.t('Room_Info'),
|
||||||
...themedHeader(screenProps.theme),
|
...themedHeader(screenProps.theme),
|
||||||
headerRight: showEdit
|
headerRight: showEdit
|
||||||
? (
|
? (
|
||||||
<CustomHeaderButtons>
|
<CustomHeaderButtons>
|
||||||
<Item iconName='edit' onPress={() => navigation.navigate('RoomInfoEditView', { rid })} testID='room-info-view-edit-button' />
|
<Item
|
||||||
|
iconName='edit'
|
||||||
|
onPress={() => navigation.navigate(t === 'l' ? 'LivechatEditView' : 'RoomInfoEditView', { rid, room, roomUser })}
|
||||||
|
testID='room-info-view-edit-button'
|
||||||
|
/>
|
||||||
</CustomHeaderButtons>
|
</CustomHeaderButtons>
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
|
@ -66,7 +78,6 @@ class RoomInfoView extends React.Component {
|
||||||
token: PropTypes.string
|
token: PropTypes.string
|
||||||
}),
|
}),
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
Message_TimeFormat: PropTypes.string,
|
|
||||||
theme: PropTypes.string
|
theme: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,68 +89,42 @@ class RoomInfoView extends React.Component {
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.state = {
|
this.state = {
|
||||||
room: room || { rid: this.rid, t: this.t },
|
room: room || { rid: this.rid, t: this.t },
|
||||||
roomUser: roomUser || {},
|
roomUser: roomUser || {}
|
||||||
parsedRoles: []
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async componentDidMount() {
|
componentDidMount() {
|
||||||
const { roomUser, room: roomState } = this.state;
|
if (this.isDirect) {
|
||||||
if (this.t === 'd' && !_.isEmpty(roomUser)) {
|
this.loadUser();
|
||||||
return;
|
} else {
|
||||||
}
|
this.loadRoom();
|
||||||
|
|
||||||
if (this.t === 'd') {
|
|
||||||
try {
|
|
||||||
const roomUserId = RocketChat.getUidDirectMessage(roomState);
|
|
||||||
const result = await RocketChat.getUserInfo(roomUserId);
|
|
||||||
if (result.success) {
|
|
||||||
const { roles } = result.user;
|
|
||||||
let parsedRoles = [];
|
|
||||||
if (roles && roles.length) {
|
|
||||||
parsedRoles = await Promise.all(roles.map(async(role) => {
|
|
||||||
const description = await this.getRoleDescription(role);
|
|
||||||
return description;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
this.setState({ roomUser: result.user, parsedRoles });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
let room = navigation.getParam('room');
|
this.willFocusListener = navigation.addListener('willFocus', () => {
|
||||||
if (room && room.observe) {
|
if (this.isLivechat) {
|
||||||
this.roomObservable = room.observe();
|
this.loadVisitor();
|
||||||
this.subscription = this.roomObservable
|
}
|
||||||
.subscribe((changes) => {
|
|
||||||
this.setState({ room: changes });
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const result = await RocketChat.getRoomInfo(this.rid);
|
|
||||||
if (result.success) {
|
|
||||||
// eslint-disable-next-line prefer-destructuring
|
|
||||||
room = result.room;
|
|
||||||
this.setState({ room });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const permissions = await RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
|
||||||
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid && this.t !== 'l') {
|
|
||||||
navigation.setParams({ showEdit: true });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
if (this.subscription && this.subscription.unsubscribe) {
|
if (this.subscription && this.subscription.unsubscribe) {
|
||||||
this.subscription.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
|
if (this.willFocusListener && this.willFocusListener.remove) {
|
||||||
|
this.willFocusListener.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDirect() {
|
||||||
|
const { room } = this.state;
|
||||||
|
return room.t === 'd';
|
||||||
|
}
|
||||||
|
|
||||||
|
get isLivechat() {
|
||||||
|
const { room } = this.state;
|
||||||
|
return room.t === 'l';
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoleDescription = async(id) => {
|
getRoleDescription = async(id) => {
|
||||||
|
@ -154,86 +139,112 @@ class RoomInfoView extends React.Component {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadVisitor = async() => {
|
||||||
|
const { room } = this.state;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
|
||||||
|
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 }`;
|
||||||
|
}
|
||||||
|
this.setState({ roomUser: visitor });
|
||||||
|
navigation.setParams({ roomUser: visitor });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goRoom = async() => {
|
loadUser = async() => {
|
||||||
const { roomUser } = this.state;
|
const { room: roomState, roomUser } = this.state;
|
||||||
const { username } = roomUser;
|
|
||||||
|
if (_.isEmpty(roomUser)) {
|
||||||
|
try {
|
||||||
|
const roomUserId = RocketChat.getUidDirectMessage(roomState);
|
||||||
|
const result = await RocketChat.getUserInfo(roomUserId);
|
||||||
|
if (result.success) {
|
||||||
|
const { user } = result;
|
||||||
|
const { roles } = user;
|
||||||
|
if (roles && roles.length) {
|
||||||
|
user.parsedRoles = await Promise.all(roles.map(async(role) => {
|
||||||
|
const description = await this.getRoleDescription(role);
|
||||||
|
return description;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const room = await this.getDirect(user.username);
|
||||||
|
|
||||||
|
this.setState({ roomUser: user, room: { ...roomState, rid: room.rid } });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRoom = async() => {
|
||||||
const { navigation } = this.props;
|
const { navigation } = this.props;
|
||||||
|
let room = navigation.getParam('room');
|
||||||
|
if (room && room.observe) {
|
||||||
|
this.roomObservable = room.observe();
|
||||||
|
this.subscription = this.roomObservable
|
||||||
|
.subscribe((changes) => {
|
||||||
|
this.setState({ room: changes });
|
||||||
|
navigation.setParams({ room: changes });
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getRoomInfo(this.rid);
|
||||||
|
if (result.success) {
|
||||||
|
({ room } = result);
|
||||||
|
this.setState({ room });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = await RocketChat.hasPermission([PERMISSION_EDIT_ROOM], room.rid);
|
||||||
|
if (permissions[PERMISSION_EDIT_ROOM] && !room.prid) {
|
||||||
|
navigation.setParams({ showEdit: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDirect = async(username) => {
|
||||||
try {
|
try {
|
||||||
const result = await RocketChat.createDirectMessage(username);
|
const result = await RocketChat.createDirectMessage(username);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
return result.room;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
goRoom = async() => {
|
||||||
|
const { roomUser, room } = this.state;
|
||||||
|
const { navigation } = this.props;
|
||||||
|
try {
|
||||||
|
if (room.rid) {
|
||||||
await navigation.navigate('RoomsListView');
|
await navigation.navigate('RoomsListView');
|
||||||
const rid = result.room._id;
|
navigation.navigate('RoomView', { rid: room.rid, name: RocketChat.getRoomTitle(roomUser), t: 'd' });
|
||||||
navigation.navigate('RoomView', { rid, name: RocketChat.getRoomTitle(roomUser), t: 'd' });
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
videoCall = () => RocketChat.callJitsi(this.rid)
|
videoCall = () => {
|
||||||
|
const { room } = this.state;
|
||||||
isDirect = () => this.t === 'd'
|
RocketChat.callJitsi(room.rid);
|
||||||
|
|
||||||
renderItem = ({ label, content }) => {
|
|
||||||
const { theme } = this.props;
|
|
||||||
return (
|
|
||||||
<View style={styles.item}>
|
|
||||||
<Text accessibilityLabel={label} style={[styles.itemLabel, { color: themes[theme].titleText }]}>{label}</Text>
|
|
||||||
<Markdown
|
|
||||||
style={[styles.itemContent, { color: themes[theme].auxiliaryText }]}
|
|
||||||
msg={content || `__${ I18n.t('No_label_provided', { label: label.toLowerCase() }) }__`}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRole = (description) => {
|
|
||||||
const { theme } = this.props;
|
|
||||||
if (description) {
|
|
||||||
return (
|
|
||||||
<View style={[styles.roleBadge, { backgroundColor: themes[theme].auxiliaryBackground }]} key={description}>
|
|
||||||
<Text style={styles.role}>{ description }</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderRoles = () => {
|
|
||||||
const { parsedRoles } = this.state;
|
|
||||||
const { theme } = this.props;
|
|
||||||
if (parsedRoles && parsedRoles.length) {
|
|
||||||
return (
|
|
||||||
<View style={styles.item}>
|
|
||||||
<Text style={[styles.itemLabel, { color: themes[theme].titleText }]}>{I18n.t('Roles')}</Text>
|
|
||||||
<View style={styles.rolesContainer}>
|
|
||||||
{parsedRoles.map(role => this.renderRole(role))}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTimezone = () => {
|
|
||||||
const { roomUser } = this.state;
|
|
||||||
const { Message_TimeFormat } = this.props;
|
|
||||||
|
|
||||||
if (roomUser) {
|
|
||||||
const { utcOffset } = roomUser;
|
|
||||||
|
|
||||||
if (!utcOffset) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.renderItem({
|
|
||||||
label: I18n.t('Timezone'),
|
|
||||||
content: `${ moment().utcOffset(utcOffset).format(Message_TimeFormat) } (UTC ${ utcOffset })`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAvatar = (room, roomUser) => {
|
renderAvatar = (room, roomUser) => {
|
||||||
|
@ -254,37 +265,6 @@ class RoomInfoView extends React.Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderBroadcast = () => this.renderItem({
|
|
||||||
label: I18n.t('Broadcast_Channel'),
|
|
||||||
content: I18n.t('Broadcast_channel_Description')
|
|
||||||
});
|
|
||||||
|
|
||||||
renderCustomFields = () => {
|
|
||||||
const { roomUser } = this.state;
|
|
||||||
if (roomUser) {
|
|
||||||
const { customFields } = roomUser;
|
|
||||||
|
|
||||||
if (!roomUser.customFields) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
Object.keys(customFields).map((title) => {
|
|
||||||
if (!customFields[title]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<View style={styles.item} key={title}>
|
|
||||||
<Text style={styles.itemLabel}>{title}</Text>
|
|
||||||
<Text style={styles.itemContent}>{customFields[title]}</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderButton = (onPress, iconName, text) => {
|
renderButton = (onPress, iconName, text) => {
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
return (
|
return (
|
||||||
|
@ -309,37 +289,21 @@ class RoomInfoView extends React.Component {
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderChannel = () => {
|
renderContent = () => {
|
||||||
const { room } = this.state;
|
const { room, roomUser } = this.state;
|
||||||
const { description, topic, announcement } = room;
|
const { theme } = this.props;
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{this.renderItem({ label: I18n.t('Description'), content: description })}
|
|
||||||
{this.renderItem({ label: I18n.t('Topic'), content: topic })}
|
|
||||||
{this.renderItem({ label: I18n.t('Announcement'), content: announcement })}
|
|
||||||
{room.broadcast ? this.renderBroadcast() : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDirect = () => {
|
if (this.isDirect) {
|
||||||
const { roomUser } = this.state;
|
return <Direct roomUser={roomUser} theme={theme} />;
|
||||||
return (
|
} else if (this.t === 'l') {
|
||||||
<>
|
return <Livechat room={room} roomUser={roomUser} theme={theme} />;
|
||||||
{this.renderRoles()}
|
}
|
||||||
{this.renderTimezone()}
|
return <Channel room={room} theme={theme} />;
|
||||||
{this.renderCustomFields(roomUser._id)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { room, roomUser } = this.state;
|
const { room, roomUser } = this.state;
|
||||||
const { theme } = this.props;
|
const { theme } = this.props;
|
||||||
const isDirect = this.isDirect();
|
|
||||||
if (!room) {
|
|
||||||
return <View />;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={[styles.scroll, { backgroundColor: themes[theme].backgroundColor }]}>
|
<ScrollView style={[styles.scroll, { backgroundColor: themes[theme].backgroundColor }]}>
|
||||||
<StatusBar theme={theme} />
|
<StatusBar theme={theme} />
|
||||||
|
@ -348,12 +312,12 @@ class RoomInfoView extends React.Component {
|
||||||
forceInset={{ vertical: 'never' }}
|
forceInset={{ vertical: 'never' }}
|
||||||
testID='room-info-view'
|
testID='room-info-view'
|
||||||
>
|
>
|
||||||
<View style={[styles.avatarContainer, isDirect && styles.avatarContainerDirectRoom, { backgroundColor: themes[theme].auxiliaryBackground }]}>
|
<View style={[styles.avatarContainer, this.isDirect && styles.avatarContainerDirectRoom, { backgroundColor: themes[theme].auxiliaryBackground }]}>
|
||||||
{this.renderAvatar(room, roomUser)}
|
{this.renderAvatar(room, roomUser)}
|
||||||
<View style={styles.roomTitleContainer}>{ getRoomTitle(room, this.t, roomUser && roomUser.name, roomUser && roomUser.username, roomUser && roomUser.statusText, theme) }</View>
|
<View style={styles.roomTitleContainer}>{ getRoomTitle(room, this.t, roomUser?.name, roomUser?.username, roomUser?.statusText, theme) }</View>
|
||||||
{isDirect ? this.renderButtons() : null}
|
{this.isDirect ? this.renderButtons() : null}
|
||||||
</View>
|
</View>
|
||||||
{isDirect ? this.renderDirect() : this.renderChannel()}
|
{this.renderContent()}
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
@ -362,8 +326,7 @@ class RoomInfoView extends React.Component {
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
baseUrl: state.server.server,
|
baseUrl: state.server.server,
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state)
|
||||||
Message_TimeFormat: state.settings.Message_TimeFormat
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(RoomInfoView));
|
export default connect(mapStateToProps)(withTheme(RoomInfoView));
|
||||||
|
|
|
@ -29,7 +29,7 @@ const Icon = React.memo(({
|
||||||
}
|
}
|
||||||
|
|
||||||
let colorStyle = {};
|
let colorStyle = {};
|
||||||
if (type === 'd' && roomUserId) {
|
if (type === 'l') {
|
||||||
colorStyle = { color: STATUS_COLORS[status] };
|
colorStyle = { color: STATUS_COLORS[status] };
|
||||||
} else {
|
} else {
|
||||||
colorStyle = { color: isAndroid && theme === 'light' ? themes[theme].buttonText : themes[theme].auxiliaryText };
|
colorStyle = { color: isAndroid && theme === 'light' ? themes[theme].buttonText : themes[theme].auxiliaryText };
|
||||||
|
|
|
@ -8,7 +8,6 @@ import Header from './Header';
|
||||||
import RightButtons from './RightButtons';
|
import RightButtons from './RightButtons';
|
||||||
import { withTheme } from '../../../theme';
|
import { withTheme } from '../../../theme';
|
||||||
import RoomHeaderLeft from './RoomHeaderLeft';
|
import RoomHeaderLeft from './RoomHeaderLeft';
|
||||||
import { getUserSelector } from '../../../selectors/login';
|
|
||||||
|
|
||||||
class RoomHeaderView extends Component {
|
class RoomHeaderView extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -95,17 +94,15 @@ class RoomHeaderView extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
let status;
|
|
||||||
let statusText;
|
let statusText;
|
||||||
const { roomUserId, type } = ownProps;
|
let status = 'offline';
|
||||||
if (type === 'd') {
|
const { roomUserId, type, visitor = {} } = ownProps;
|
||||||
const user = getUserSelector(state);
|
|
||||||
if (user.id) {
|
if (state.meteor.connected) {
|
||||||
if (state.activeUsers[roomUserId] && state.meteor.connected) {
|
if (type === 'd' && state.activeUsers[roomUserId]) {
|
||||||
({ status, statusText } = state.activeUsers[roomUserId]);
|
({ status, statusText } = state.activeUsers[roomUserId]);
|
||||||
} else {
|
} else if (type === 'l' && visitor?.status) {
|
||||||
status = 'offline';
|
({ status } = visitor);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ const stateAttrsUpdate = [
|
||||||
'readOnly',
|
'readOnly',
|
||||||
'member'
|
'member'
|
||||||
];
|
];
|
||||||
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed'];
|
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'muted', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor'];
|
||||||
|
|
||||||
class RoomView extends React.Component {
|
class RoomView extends React.Component {
|
||||||
static navigationOptions = ({ navigation, screenProps }) => {
|
static navigationOptions = ({ navigation, screenProps }) => {
|
||||||
|
@ -87,6 +87,7 @@ class RoomView extends React.Component {
|
||||||
const goRoomActionsView = navigation.getParam('goRoomActionsView', () => {});
|
const goRoomActionsView = navigation.getParam('goRoomActionsView', () => {});
|
||||||
const unreadsCount = navigation.getParam('unreadsCount', null);
|
const unreadsCount = navigation.getParam('unreadsCount', null);
|
||||||
const roomUserId = navigation.getParam('roomUserId');
|
const roomUserId = navigation.getParam('roomUserId');
|
||||||
|
const visitor = navigation.getParam('visitor');
|
||||||
if (!rid) {
|
if (!rid) {
|
||||||
return {
|
return {
|
||||||
...themedHeader(screenProps.theme)
|
...themedHeader(screenProps.theme)
|
||||||
|
@ -104,6 +105,7 @@ class RoomView extends React.Component {
|
||||||
type={t}
|
type={t}
|
||||||
widthOffset={tmid ? 95 : 130}
|
widthOffset={tmid ? 95 : 130}
|
||||||
roomUserId={roomUserId}
|
roomUserId={roomUserId}
|
||||||
|
visitor={visitor}
|
||||||
goRoomActionsView={goRoomActionsView}
|
goRoomActionsView={goRoomActionsView}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
@ -291,6 +293,12 @@ class RoomView extends React.Component {
|
||||||
this.setReadOnly();
|
this.setReadOnly();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// If it's a livechat room
|
||||||
|
if (this.t === 'l') {
|
||||||
|
if (!isEqual(prevState.roomUpdate.visitor, roomUpdate.visitor)) {
|
||||||
|
navigation.setParams({ visitor: roomUpdate.visitor });
|
||||||
|
}
|
||||||
|
}
|
||||||
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) {
|
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) {
|
||||||
navigation.setParams({ name: RocketChat.getRoomTitle(room) });
|
navigation.setParams({ name: RocketChat.getRoomTitle(room) });
|
||||||
}
|
}
|
||||||
|
|
|
@ -421,7 +421,8 @@ class RoomsListView extends React.Component {
|
||||||
type: item.t,
|
type: item.t,
|
||||||
prid: item.prid,
|
prid: item.prid,
|
||||||
uids: item.uids,
|
uids: item.uids,
|
||||||
usernames: item.usernames
|
usernames: item.usernames,
|
||||||
|
visitor: item.visitor
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// unread
|
// unread
|
||||||
|
@ -548,6 +549,7 @@ class RoomsListView extends React.Component {
|
||||||
prid: item.prid,
|
prid: item.prid,
|
||||||
room: item,
|
room: item,
|
||||||
search: item.search,
|
search: item.search,
|
||||||
|
visitor: item.visitor,
|
||||||
roomUserId: this.getUidDirectMessage(item)
|
roomUserId: this.getUidDirectMessage(item)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -816,6 +818,7 @@ class RoomsListView extends React.Component {
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
getUserPresence={this.getUserPresence}
|
getUserPresence={this.getUserPresence}
|
||||||
isGroupChat={isGroupChat}
|
isGroupChat={isGroupChat}
|
||||||
|
visitor={item.visitor}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { FlatList, StyleSheet, Text } from 'react-native';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { withTheme } from '../theme';
|
||||||
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import { themes } from '../constants/colors';
|
||||||
|
import Separator from '../containers/Separator';
|
||||||
|
import openLink from '../utils/openLink';
|
||||||
|
import I18n from '../i18n';
|
||||||
|
import debounce from '../utils/debounce';
|
||||||
|
import sharedStyles from './Styles';
|
||||||
|
import ListItem from '../containers/ListItem';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
noResult: {
|
||||||
|
fontSize: 16,
|
||||||
|
paddingVertical: 56,
|
||||||
|
...sharedStyles.textAlignCenter,
|
||||||
|
...sharedStyles.textSemibold
|
||||||
|
},
|
||||||
|
withoutBorder: {
|
||||||
|
borderBottomWidth: 0,
|
||||||
|
borderTopWidth: 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Item = ({ item, theme }) => (
|
||||||
|
<ListItem
|
||||||
|
title={item.navigation?.page?.title || I18n.t('Empty_title')}
|
||||||
|
onPress={() => openLink(item.navigation?.page?.location?.href)}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Item.propTypes = {
|
||||||
|
item: PropTypes.object,
|
||||||
|
theme: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const VisitorNavigationView = ({ navigation, theme }) => {
|
||||||
|
let offset;
|
||||||
|
let total = 0;
|
||||||
|
const [pages, setPages] = useState([]);
|
||||||
|
|
||||||
|
const getPages = async() => {
|
||||||
|
const rid = navigation.getParam('rid');
|
||||||
|
if (rid) {
|
||||||
|
try {
|
||||||
|
const result = await RocketChat.getPagesLivechat(rid, offset);
|
||||||
|
if (result.success) {
|
||||||
|
setPages(result.pages);
|
||||||
|
offset = result.pages.length;
|
||||||
|
({ total } = result);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// do nothig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => { getPages(); }, []);
|
||||||
|
|
||||||
|
const onEndReached = debounce(() => {
|
||||||
|
if (pages.length <= total) {
|
||||||
|
getPages();
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FlatList
|
||||||
|
data={pages}
|
||||||
|
renderItem={({ item }) => <Item item={item} theme={theme} />}
|
||||||
|
ItemSeparatorComponent={() => <Separator theme={theme} />}
|
||||||
|
contentContainerStyle={[
|
||||||
|
sharedStyles.listContentContainer,
|
||||||
|
{
|
||||||
|
backgroundColor: themes[theme].auxiliaryBackground,
|
||||||
|
borderColor: themes[theme].separatorColor
|
||||||
|
},
|
||||||
|
!pages.length && styles.withoutBorder
|
||||||
|
]}
|
||||||
|
style={{ backgroundColor: themes[theme].auxiliaryBackground }}
|
||||||
|
ListEmptyComponent={() => <Text style={[styles.noResult, { color: themes[theme].titleText }]}>{I18n.t('No_results_found')}</Text>}
|
||||||
|
keyExtractor={item => item}
|
||||||
|
onEndReached={onEndReached}
|
||||||
|
onEndReachedThreshold={5}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
VisitorNavigationView.propTypes = {
|
||||||
|
theme: PropTypes.string,
|
||||||
|
navigation: PropTypes.object
|
||||||
|
};
|
||||||
|
VisitorNavigationView.navigationOptions = {
|
||||||
|
title: I18n.t('Navigation_history')
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withTheme(VisitorNavigationView);
|
|
@ -109,6 +109,7 @@
|
||||||
"rn-root-view": "^1.0.3",
|
"rn-root-view": "^1.0.3",
|
||||||
"rn-user-defaults": "^1.8.1",
|
"rn-user-defaults": "^1.8.1",
|
||||||
"semver": "7.3.2",
|
"semver": "7.3.2",
|
||||||
|
"ua-parser-js": "^0.7.21",
|
||||||
"url-parse": "^1.4.7",
|
"url-parse": "^1.4.7",
|
||||||
"use-deep-compare-effect": "^1.3.1"
|
"use-deep-compare-effect": "^1.3.1"
|
||||||
},
|
},
|
||||||
|
|
11
yarn.lock
11
yarn.lock
|
@ -2461,9 +2461,9 @@
|
||||||
prop-types "^15.7.2"
|
prop-types "^15.7.2"
|
||||||
|
|
||||||
"@react-native-community/async-storage@^1.9.0":
|
"@react-native-community/async-storage@^1.9.0":
|
||||||
version "1.9.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/@react-native-community/async-storage/-/async-storage-1.9.0.tgz#af26a8879bd2987970fbbe81a9623851d29a56f1"
|
resolved "https://registry.yarnpkg.com/@react-native-community/async-storage/-/async-storage-1.10.0.tgz#fd6a9737f3c227ef4e28858b8201ad793a022296"
|
||||||
integrity sha512-TlGMr02JcmY4huH1P7Mt7p6wJecosPpW+09+CwCFLn875IhpRqU2XiVA+BQppZOYfQdHUfUzIKyCBeXOlCEbEg==
|
integrity sha512-kPJwhUpBKLXGrBnUjx0JVSJvSEl5nPO+puJ3Uy9pMvika9uWeniRGZPQjUWWQimU5M7xhQ41d5I1OP82Q3Xx9A==
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-assign "^3.0.0"
|
deep-assign "^3.0.0"
|
||||||
|
|
||||||
|
@ -15862,6 +15862,11 @@ ua-parser-js@^0.7.18:
|
||||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.19.tgz#94151be4c0a7fb1d001af7022fdaca4642659e4b"
|
||||||
integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
|
integrity sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==
|
||||||
|
|
||||||
|
ua-parser-js@^0.7.21:
|
||||||
|
version "0.7.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
|
||||||
|
integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
|
||||||
|
|
||||||
uglify-es@^3.1.9:
|
uglify-es@^3.1.9:
|
||||||
version "3.3.9"
|
version "3.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
|
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
|
||||||
|
|
Loading…
Reference in New Issue