diff --git a/.env.example b/.env.example index e69de29bb..ab7a23c8a 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1 @@ +SENTRY_DSN= \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e91257bb5..447b71ee2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,13 +42,8 @@ And install dependencies Create a new project in [Sentry](https://sentry.io/for/react-native/) -Create file sentry under `sentry.js` in root of the project. -Add the following contents. - -``` -export const SENTRY_TOKEN_URL =; -``` +Add `SENTRY_DSN` value in `.env` file. If you want to supports native crashes, link the sentry SDK to your native projects. Run the command diff --git a/LICENSE b/LICENSE index 0bb21c0b6..46aa13b8d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017-2020 ThoughtWoot Inc. +Copyright (c) 2017-2021 Chatwoot Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index ea9310aab..182df20bc 100644 --- a/README.md +++ b/README.md @@ -61,4 +61,4 @@ If you want to self deploy the Chatwoot mobile app, please take a look at the [C If there's anything you'd like to chat about, please feel free to join our [Discord](https://discord.gg/cJXdrwS) chat! -_Chatwoot_ © 2017-2020, ThoughtWoot Inc - Released under the MIT License. +_Chatwoot_ © 2017-2020, Chatwoot Inc - Released under the MIT License. diff --git a/android/app/build.gradle b/android/app/build.gradle index c07b1a0bb..3f41ab339 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -134,8 +134,8 @@ android { applicationId "com.chatwoot.app" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 111 - versionName "1.0.11" + versionCode 113 + versionName "1.0.13" multiDexEnabled true } splits { @@ -235,3 +235,4 @@ task copyDownloadableDepsToLibs(type: Copy) { } apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) +apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" diff --git a/babel.config.js b/babel.config.js index f842b77fc..025a9dd62 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,30 @@ module.exports = { presets: ['module:metro-react-native-babel-preset'], + plugins: [ + [ + 'module-resolver', + { + root: ['.'], + extensions: [ + '.ios.ts', + '.android.ts', + '.ts', + '.ios.tsx', + '.android.tsx', + '.tsx', + '.jsx', + '.js', + '.json', + ], + alias: { + components: './src/components', + actions: './src/actions', + constants: './src/constants', + helpers: './src/helpers', + i18n: './src/i18n', + reducer: './src/reducer', + }, + }, + ], + ], }; diff --git a/index.js b/index.js index 752d24b0f..cbbf8159d 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,11 @@ import { AppRegistry } from 'react-native'; import * as Sentry from '@sentry/react-native'; - +import Config from 'react-native-config'; import { name as appName } from './app.json'; -import { SENTRY_TOKEN_URL } from './sentry'; - import App from './src/app'; AppRegistry.registerComponent(appName, () => App); - Sentry.init({ - dsn: SENTRY_TOKEN_URL, + dsn: Config.SENTRY_DSN, }); diff --git a/ios/Chatwoot.xcodeproj/project.pbxproj b/ios/Chatwoot.xcodeproj/project.pbxproj index 07ae90220..a9d55b97e 100644 --- a/ios/Chatwoot.xcodeproj/project.pbxproj +++ b/ios/Chatwoot.xcodeproj/project.pbxproj @@ -724,13 +724,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Chatwoot/Chatwoot.entitlements; - CURRENT_PROJECT_VERSION = 112; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 6C953F3RX2; ENABLE_BITCODE = NO; INFOPLIST_FILE = Chatwoot/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.0.12; + MARKETING_VERSION = 1.0.13; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -751,12 +751,12 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Chatwoot/Chatwoot.entitlements; - CURRENT_PROJECT_VERSION = 112; + CURRENT_PROJECT_VERSION = 113; DEVELOPMENT_TEAM = 6C953F3RX2; INFOPLIST_FILE = Chatwoot/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - MARKETING_VERSION = 1.0.12; + MARKETING_VERSION = 1.0.13; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d73c1251d..83fe66223 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -296,6 +296,12 @@ PODS: - React-cxxreact (= 0.63.3) - React-jsi (= 0.63.3) - React-jsinspector (0.63.3) + - react-native-config (1.4.2): + - react-native-config/App (= 1.4.2) + - react-native-config/App (1.4.2): + - React-Core + - react-native-document-picker (5.0.0): + - React-Core - react-native-image-picker (3.1.4): - React-Core - react-native-netinfo (5.9.10): @@ -443,6 +449,8 @@ DEPENDENCIES: - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) + - react-native-config (from `../node_modules/react-native-config`) + - react-native-document-picker (from `../node_modules/react-native-document-picker`) - react-native-image-picker (from `../node_modules/react-native-image-picker`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-safari-view (from `../node_modules/react-native-safari-view`) @@ -534,6 +542,10 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: :path: "../node_modules/react-native/ReactCommon/jsinspector" + react-native-config: + :path: "../node_modules/react-native-config" + react-native-document-picker: + :path: "../node_modules/react-native-document-picker" react-native-image-picker: :path: "../node_modules/react-native-image-picker" react-native-netinfo: @@ -634,6 +646,8 @@ SPEC CHECKSUMS: React-jsi: df07aa95b39c5be3e41199921509bfa929ed2b9d React-jsiexecutor: b56c03e61c0dd5f5801255f2160a815f4a53d451 React-jsinspector: 8e68ffbfe23880d3ee9bafa8be2777f60b25cbe2 + react-native-config: c98128a72bc2c3a1ca72caec0b021f0fa944aa29 + react-native-document-picker: ca0c8769c3848866970dc9164e809c6ea45cb5d6 react-native-image-picker: 28d8d1f5a643119876b8b2c7d4d1dcfb2b232dd4 react-native-netinfo: 30fb89fa913c342be82a887b56e96be6d71201dd react-native-safari-view: 955d7160d159241b8e9395d12d10ea0ef863dcdd @@ -669,4 +683,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 359eeed31941826235cdc4a3c6b998cb2e176764 -COCOAPODS: 1.10.0 +COCOAPODS: 1.10.1 diff --git a/package.json b/package.json index ccf2b9bc3..f9d1340a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chatwoot/mobile-app", - "version": "1.0.12", + "version": "1.0.13", "private": true, "scripts": { "clean": "rm -rf $TMPDIR/react-* && watchman watch-del-all && npm cache clean --force", @@ -42,7 +42,9 @@ "react-native-actions-sheet": "^0.4.2", "react-native-animatable": "^1.3.3", "react-native-background-color": "^0.0.8", + "react-native-config": "^1.4.2", "react-native-device-info": "^5.6.5", + "react-native-document-picker": "^5.0.0", "react-native-exception-handler": "^2.10.9", "react-native-gesture-handler": "^1.9.0", "react-native-hyperlink": "^0.0.19", @@ -63,7 +65,8 @@ "redux-persist": "^6.0.0", "redux-thunk": "^2.3.0", "tcomb-form-native": "^0.6.20", - "validator": "^13.5.2" + "validator": "^13.5.2", + "yarn": "^1.22.10" }, "devDependencies": { "@babel/core": "^7.8.4", @@ -71,6 +74,7 @@ "@bam.tech/react-native-make": "^3.0.0", "@react-native-community/eslint-config": "^1.1.0", "babel-jest": "^25.1.0", + "babel-plugin-module-resolver": "^4.1.0", "detox": "^17.4.6", "eslint": "^6.5.1", "eslint-plugin-detox": "^1.0.0", diff --git a/src/actions/conversation.js b/src/actions/conversation.js index 717f5b63d..02bd67922 100644 --- a/src/actions/conversation.js +++ b/src/actions/conversation.js @@ -475,3 +475,17 @@ export const assignConversation = ({ conversationId, assigneeId }) => async ( dispatch({ type: ASSIGN_CONVERSATION_ERROR }); } }; +export const unAssignConversation = ({ conversationId, assigneeId }) => async ( + dispatch, + getState, +) => { + dispatch({ type: ASSIGN_CONVERSATION }); + try { + const apiUrl = `conversations/${conversationId}/assignments?assignee_id=${assigneeId}`; + await axios.post(apiUrl); + dispatch({ type: ASSIGN_CONVERSATION_SUCCESS }); + dispatch(getConversationDetails({ conversationId })); + } catch (error) { + dispatch({ type: ASSIGN_CONVERSATION_ERROR }); + } +}; diff --git a/src/components/ConversationActionItem.js b/src/components/ConversationActionItem.js index ab31d563a..7e1aa7eea 100644 --- a/src/components/ConversationActionItem.js +++ b/src/components/ConversationActionItem.js @@ -57,7 +57,6 @@ const ConversationActionItem = ({ eva: { style, theme }, }) => { const isActive = availabilityStatus === 'online' ? true : false; - return ( onPressItem({ itemType })}> @@ -65,7 +64,7 @@ const ConversationActionItem = ({ {text} - {itemType === 'assignee' && ( + {itemType === 'assignee' && thumbnail !== '' && ( { const { style, theme } = eva; - const message = content.replace(/\[(@[\w_.]+)\]\(mention:\/\/user\/\d+\/[\w_.]+\)/gi, '$1'); + const message = content + ? content.replace(/\[(@[\w_.]+)\]\(mention:\/\/user\/\d+\/[\w_.]+\)/gi, '$1') + : ''; return ( {messageType === MESSAGE_TYPES.OUTGOING ? ( diff --git a/src/components/ConversationItem.js b/src/components/ConversationItem.js index 3107bc354..5ed07b171 100644 --- a/src/components/ConversationItem.js +++ b/src/components/ConversationItem.js @@ -91,7 +91,7 @@ const ConversationItemComponent = ({ {!typingUser ? ( - attachments && attachments.length ? ( + !content && attachments && attachments.length ? ( { + )} diff --git a/src/screens/ChatScreen/ChatScreen.js b/src/screens/ChatScreen/ChatScreen.js index e08f257c5..a638c9aab 100644 --- a/src/screens/ChatScreen/ChatScreen.js +++ b/src/screens/ChatScreen/ChatScreen.js @@ -1,21 +1,14 @@ -import React, { Component, createRef } from 'react'; -import { - Icon, - TopNavigation, - TopNavigationAction, - Spinner, - withStyles, -} from '@ui-kitten/components'; +import React, { Component } from 'react'; +import { Spinner, withStyles } from '@ui-kitten/components'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import ActionSheet from 'react-native-actions-sheet'; -import { View, SafeAreaView, SectionList, Linking, TouchableOpacity } from 'react-native'; - -import ChatMessage from '../../components/ChatMessage'; -import ChatMessageDate from '../../components/ChatMessageDate'; +import { View, SafeAreaView, SectionList, Linking } from 'react-native'; +import ChatMessage from './components/ChatMessage'; +import ChatMessageDate from './components/ChatMessageDate'; +import ReplyBox from './components/ReplyBox'; +import ChatHeader from './components/ChatHeader'; import ScrollToBottomButton from '../../components/ScrollToBottomButton'; import styles from './ChatScreen.style'; -import UserAvatar from '../../components/UserAvatar'; import { openURL } from '../../helpers/UrlHelper'; import { @@ -26,25 +19,9 @@ import { resetConversation, toggleTypingStatus, getConversationDetails, - toggleConversationStatus, } from '../../actions/conversation'; import { markNotificationAsRead } from '../../actions/notification'; -import { getGroupedConversation, getTypingUsersText, findUniqueMessages } from '../../helpers'; -import CustomText from '../../components/Text'; -import ConversationAction from '../ConversationAction/ConversationAction'; -import ReplyBox from './components/ReplyBox'; - -const BackIcon = (style) => ( - -); - -const MenuIcon = (style) => { - return ; -}; - -const BackAction = (props) => ; - -const actionSheetRef = createRef(); +import { getGroupedConversation, findUniqueMessages } from '../../helpers'; class ChatScreenComponent extends Component { static propTypes = { @@ -143,9 +120,6 @@ class ChatScreenComponent extends Component { const { navigation } = this.props; navigation.goBack(); }; - - renderLeftControl = () => ; - loadMoreMessages = () => { const { allMessages, isAllMessagesLoaded } = this.props; @@ -217,125 +191,6 @@ class ChatScreenComponent extends Component { } }; - renderTitle = () => { - const senderDetails = { - name: null, - thumbnail: null, - }; - - const { - conversationDetails, - route, - conversationTypingUsers, - eva: { style, theme }, - } = this.props; - - const { - params: { conversationId }, - } = route; - - const typingUser = getTypingUsersText({ - conversationTypingUsers, - conversationId, - }); - const { meta } = route.params; - if (meta) { - const { - sender: { name, thumbnail }, - channel, - } = meta; - senderDetails.name = name; - senderDetails.thumbnail = thumbnail; - senderDetails.channel = channel; - } - if (!senderDetails.name && conversationDetails) { - const { - meta: { - sender: { name, thumbnail }, - channel, - }, - } = conversationDetails; - senderDetails.name = name; - senderDetails.thumbnail = thumbnail; - senderDetails.channel = channel; - } - - if (senderDetails.name) { - return ( - - - - - - {senderDetails.name.length > 24 - ? ` ${senderDetails.name.substring(0, 20)}...` - : ` ${senderDetails.name}`} - - - {typingUser ? ( - - - {typingUser ? `${typingUser}` : ''} - - - ) : null} - - - ); - } - return null; - }; - - showActionSheet = () => { - actionSheetRef.current?.setModalVisible(); - }; - - renderRightControl = () => { - const { conversationDetails } = this.props; - - if (conversationDetails) { - return ; - } - return null; - }; - - renderTopNavigation = () => { - return ( - - ); - }; - - onPressAction = ({ itemType }) => { - actionSheetRef.current?.hide(); - const { conversationDetails, navigation, route } = this.props; - if (itemType === 'assignee') { - if (conversationDetails) { - navigation.navigate('AgentScreen', { conversationDetails }); - } - } - if (itemType === 'toggle_status') { - const { - params: { conversationId }, - } = route; - this.props.toggleConversationStatus({ conversationId }); - } - }; - - handleChoosePhoto = () => {}; - render() { const { allMessages, @@ -343,10 +198,12 @@ class ChatScreenComponent extends Component { eva: { style }, route, cannedResponses, + conversationTypingUsers, + conversationDetails, } = this.props; const { - params: { conversationId }, + params: { conversationId, meta }, } = route; const { showScrollToButton } = this.state; @@ -355,10 +212,17 @@ class ChatScreenComponent extends Component { const groupedConversationList = getGroupedConversation({ conversations: uniqueMessages, }); - const { conversationDetails } = this.props; + return ( - {this.renderTopNavigation()} + @@ -395,12 +259,6 @@ class ChatScreenComponent extends Component { - - - ); } @@ -416,8 +274,6 @@ function bindAction(dispatch) { dispatch(getConversationDetails({ conversationId })), sendMessage: ({ conversationId, message }) => dispatch(sendMessage({ conversationId, message })), - toggleConversationStatus: ({ conversationId, message }) => - dispatch(toggleConversationStatus({ conversationId })), markAllMessagesAsRead: ({ conversationId }) => dispatch(markMessagesAsRead({ conversationId })), toggleTypingStatus: ({ conversationId, typingStatus }) => dispatch(toggleTypingStatus({ conversationId, typingStatus })), diff --git a/src/screens/ChatScreen/ChatScreen.style.js b/src/screens/ChatScreen/ChatScreen.style.js index 0c4dbb99e..f16fad9ba 100644 --- a/src/screens/ChatScreen/ChatScreen.style.js +++ b/src/screens/ChatScreen/ChatScreen.style.js @@ -6,31 +6,6 @@ const styles = (theme) => ({ backgroundColor: theme['background-basic-color-1'], }, - keyboardView: { - flex: 1, - }, - headerView: { - flexDirection: 'row', - alignItems: 'center', - }, - avatarView: { - marginHorizontal: 4, - }, - titleView: { - marginHorizontal: 8, - }, - headerTitle: { - textTransform: 'capitalize', - fontWeight: theme['font-semi-bold'], - fontSize: theme['font-size-large'], - }, - subHeaderTitle: { - fontSize: theme['font-size-extra-small'], - color: theme['color-gray'], - paddingTop: 4, - paddingLeft: 4, - }, - container: { flex: 1, backgroundColor: theme['color-background'], diff --git a/src/screens/ChatScreen/components/Attachment.js b/src/screens/ChatScreen/components/Attachment.js index 035fe28a2..a4756190b 100644 --- a/src/screens/ChatScreen/components/Attachment.js +++ b/src/screens/ChatScreen/components/Attachment.js @@ -2,9 +2,11 @@ import React, { createRef } from 'react'; import { withStyles, Icon } from '@ui-kitten/components'; import { launchCamera, launchImageLibrary } from 'react-native-image-picker'; import ActionSheet from 'react-native-actions-sheet'; +import { Keyboard } from 'react-native'; +import DocumentPicker from 'react-native-document-picker'; import PropTypes from 'prop-types'; + import AttachmentActionItem from './AttachmentActionItem'; -import { Keyboard } from 'react-native'; const styles = (theme) => ({ button: { @@ -50,6 +52,28 @@ const Attachment = ({ conversationId, eva: { style, theme }, onSelectAttachment } }); }; + const openDocument = async () => { + try { + const res = await DocumentPicker.pick({ + type: [ + DocumentPicker.types.audio, + DocumentPicker.types.pdf, + DocumentPicker.types.plainText, + DocumentPicker.types.doc, + DocumentPicker.types.docx, + DocumentPicker.types.xls, + DocumentPicker.types.csv, + ], + }); + const attachement = { uri: res.uri, type: res.type, fileSize: res.size, fileName: res.name }; + onSelectAttachment({ attachement }); + } catch (err) { + if (DocumentPicker.isCancel(err)) { + } else { + throw err; + } + } + }; const onPressItem = ({ itemType }) => { actionSheetRef.current?.hide(); setTimeout(() => { @@ -59,6 +83,9 @@ const Attachment = ({ conversationId, eva: { style, theme }, onSelectAttachment if (itemType === 'upload_gallery') { openGallery(); } + if (itemType === 'upload_file') { + openDocument(); + } }, 500); }; @@ -86,6 +113,12 @@ const Attachment = ({ conversationId, eva: { style, theme }, onSelectAttachment itemType="upload_gallery" onPressItem={onPressItem} /> + ); diff --git a/src/screens/ChatScreen/components/CannedResponses.js b/src/screens/ChatScreen/components/CannedResponses.js index 80d4179b7..4d64b1793 100644 --- a/src/screens/ChatScreen/components/CannedResponses.js +++ b/src/screens/ChatScreen/components/CannedResponses.js @@ -13,7 +13,7 @@ const styles = (theme) => ({ borderTopColor: theme['color-border'], borderTopWidth: 1, }, - itemView: { + itemView: { flex: 1, flexDirection: 'row', paddingVertical: 8, diff --git a/src/components/ChatAttachmentItem.js b/src/screens/ChatScreen/components/ChatAttachmentItem.js similarity index 74% rename from src/components/ChatAttachmentItem.js rename to src/screens/ChatScreen/components/ChatAttachmentItem.js index 57eecdd65..0039eaf8b 100644 --- a/src/components/ChatAttachmentItem.js +++ b/src/screens/ChatScreen/components/ChatAttachmentItem.js @@ -6,10 +6,9 @@ import { withStyles, Icon } from '@ui-kitten/components'; const deviceWidth = Dimensions.get('window').width; const deviceHeight = Dimensions.get('window').height; -import ImageLoader from './ImageLoader'; - -import CustomText from './Text'; -import i18n from '../i18n'; +import ImageLoader from '../../../components/ImageLoader'; +import CustomText from '../../../components/Text'; +import i18n from '../../../i18n'; const styles = (theme) => ({ fileViewRight: { @@ -41,12 +40,23 @@ const styles = (theme) => ({ borderRadius: 8, borderTopRightRadius: 8, left: -4, + backgroundColor: theme['color-primary-default'], + padding: 2, }, imageViewRight: { borderRadius: 8, borderTopLeftRadius: 8, left: 4, + padding: 2, + backgroundColor: theme['color-primary-default'], + }, + privateMessageContainer: { + backgroundColor: theme['color-background-activity'], + color: theme['text-basic-color'], + borderWidth: 1, + borderColor: theme['color-border-activity'], + paddingTop: 8, }, imageLoader: { position: 'absolute', @@ -55,7 +65,6 @@ const styles = (theme) => ({ top: 0, bottom: 0, opacity: 0.7, - backgroundColor: theme['color-background-message'], justifyContent: 'center', alignItems: 'center', borderRadius: 8, @@ -120,6 +129,20 @@ const styles = (theme) => ({ textAlign: 'left', alignSelf: 'stretch', }, + attachmentText: { + fontSize: theme['text-primary-size'], + fontWeight: theme['font-medium'], + color: 'white', + paddingBottom: 4, + paddingLeft: 8, + }, + attachmentPrivateText: { + fontSize: theme['text-primary-size'], + fontWeight: theme['font-medium'], + color: theme['text-basic-color'], + paddingBottom: 4, + paddingLeft: 8, + }, }); const propTypes = { @@ -129,35 +152,45 @@ const propTypes = { }).isRequired, type: PropTypes.string, showAttachment: PropTypes.func, - attachment: PropTypes.arrayOf( - PropTypes.shape({ - file_type: PropTypes.string, - data_url: PropTypes.string, - }), - ), + message: PropTypes.shape({ + content: PropTypes.string, + private: PropTypes.bool, + attachments: PropTypes.arrayOf( + PropTypes.shape({ + file_type: PropTypes.string, + data_url: PropTypes.string, + }), + ), + }), }; const FileIcon = (style) => { return ; }; -const ChatAttachmentItemComponent = ({ - type, - attachment, - showAttachment, - eva: { style, theme }, -}) => { +const ChatAttachmentItemComponent = ({ type, showAttachment, message, eva: { style, theme } }) => { + const { attachments, content } = message; + const [imageLoading, onLoadImage] = useState(false); - const { file_type: fileType, data_url: dataUrl } = attachment[0]; + const { file_type: fileType, data_url: dataUrl } = attachments[0]; const fileName = dataUrl ? dataUrl.split('/').reverse()[0] : ''; return ( - {fileType !== 'file' ? ( + {fileType === 'image' ? ( showAttachment({ type: 'image', dataUrl })} - style={type === 'outgoing' ? style.imageViewRight : style.imageViewLeft}> + style={[ + type === 'outgoing' ? style.imageViewRight : style.imageViewLeft, + message.private && style.privateMessageContainer, + ]}> + {content && ( + + {content} + + )} + {imageLoading && } ) : ( diff --git a/src/screens/ChatScreen/components/ChatHeader.js b/src/screens/ChatScreen/components/ChatHeader.js new file mode 100644 index 000000000..a6920a9ee --- /dev/null +++ b/src/screens/ChatScreen/components/ChatHeader.js @@ -0,0 +1,180 @@ +import React, { createRef } from 'react'; +import { withStyles, Icon, TopNavigation, TopNavigationAction } from '@ui-kitten/components'; +import ActionSheet from 'react-native-actions-sheet'; +import { useNavigation } from '@react-navigation/native'; +import { useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; +import { TouchableOpacity, View } from 'react-native'; +import ConversationAction from '../../ConversationAction/ConversationAction'; +import UserAvatar from '../../../components/UserAvatar'; +import { getTypingUsersText } from '../../../helpers'; +import CustomText from '../../../components/Text'; +import { unAssignConversation, toggleConversationStatus } from '../../../actions/conversation'; + +const styles = (theme) => ({ + headerView: { + flexDirection: 'row', + alignItems: 'center', + }, + avatarView: { + marginHorizontal: 4, + }, + titleView: { + marginHorizontal: 8, + }, + headerTitle: { + textTransform: 'capitalize', + fontWeight: theme['font-semi-bold'], + fontSize: theme['font-size-large'], + }, + subHeaderTitle: { + fontSize: theme['font-size-extra-small'], + color: theme['color-gray'], + paddingTop: 4, + paddingLeft: 4, + }, +}); + +const BackIcon = (style) => ( + +); + +const MenuIcon = (style) => { + return ; +}; + +const BackAction = (props) => ; + +const propTypes = { + eva: PropTypes.shape({ + style: PropTypes.object, + theme: PropTypes.object, + }).isRequired, + conversationId: PropTypes.number, + onBackPress: PropTypes.func, + showConversationDetails: PropTypes.func, + conversationDetails: PropTypes.object, + conversationMetaDetails: PropTypes.object, + conversationTypingUsers: PropTypes.shape({}), +}; + +const ChatHeader = ({ + conversationDetails, + conversationMetaDetails, + conversationId, + conversationTypingUsers, + showConversationDetails, + eva: { style, theme }, + onBackPress, +}) => { + const navigation = useNavigation(); + const actionSheetRef = createRef(); + const dispatch = useDispatch(); + const renderTitle = () => { + const senderDetails = { + name: null, + thumbnail: null, + }; + const typingUser = getTypingUsersText({ + conversationTypingUsers, + conversationId, + }); + + if (conversationMetaDetails) { + const { + sender: { name, thumbnail }, + channel, + } = conversationMetaDetails; + senderDetails.name = name; + senderDetails.thumbnail = thumbnail; + senderDetails.channel = channel; + } + + if (senderDetails.name) { + return ( + + + + + + {senderDetails.name.length > 24 + ? ` ${senderDetails.name.substring(0, 20)}...` + : ` ${senderDetails.name}`} + + + {typingUser ? ( + + + {typingUser ? `${typingUser}` : ''} + + + ) : null} + + + ); + } + return null; + }; + + const showActionSheet = () => { + actionSheetRef.current?.setModalVisible(); + }; + + const renderLeftControl = () => ; + const renderRightControl = () => { + if (conversationDetails) { + return ; + } + return null; + }; + + const onPressAction = ({ itemType }) => { + actionSheetRef.current?.hide(); + + if (itemType === 'assignee') { + if (conversationDetails) { + navigation.navigate('AgentScreen', { conversationDetails }); + } + } + if (itemType === 'toggle_status') { + dispatch(toggleConversationStatus({ conversationId })); + } + if (itemType === 'unassign') { + dispatch( + unAssignConversation({ + conversationId: conversationDetails.id, + assigneeId: 0, + }), + ); + } + }; + return ( + + + + + + + ); +}; + +ChatHeader.propTypes = propTypes; + +const ChatHeaderItem = withStyles(ChatHeader, styles); +export default ChatHeaderItem; diff --git a/src/components/ChatMessage.js b/src/screens/ChatScreen/components/ChatMessage.js similarity index 91% rename from src/components/ChatMessage.js rename to src/screens/ChatScreen/components/ChatMessage.js index c3e265c2b..89146b2ba 100644 --- a/src/components/ChatMessage.js +++ b/src/screens/ChatScreen/components/ChatMessage.js @@ -2,14 +2,12 @@ import React from 'react'; import { View, Dimensions } from 'react-native'; import PropTypes from 'prop-types'; - import { withStyles } from '@ui-kitten/components'; -import CustomText from './Text'; - -import { messageStamp } from '../helpers/TimeHelper'; -import ChatAttachmentItem from '../components/ChatAttachmentItem'; -import ChatMessageItem from '../components/ChatMessageItem'; +import CustomText from '../../../components/Text'; +import { messageStamp } from '../../../helpers/TimeHelper'; +import ChatAttachmentItem from './ChatAttachmentItem'; +import ChatMessageItem from './ChatMessageItem'; const styles = (theme) => ({ message: { @@ -61,7 +59,12 @@ const MessageContentComponent = ({ message, type, showAttachment, created_at }) const { attachments } = message; return attachments ? ( - + ) : ( ); diff --git a/src/components/ChatMessageActionItem.js b/src/screens/ChatScreen/components/ChatMessageActionItem.js similarity index 97% rename from src/components/ChatMessageActionItem.js rename to src/screens/ChatScreen/components/ChatMessageActionItem.js index 88ddb369f..8508575c1 100644 --- a/src/components/ChatMessageActionItem.js +++ b/src/screens/ChatScreen/components/ChatMessageActionItem.js @@ -3,8 +3,7 @@ import { TouchableOpacity, View } from 'react-native'; import PropTypes from 'prop-types'; import { Divider, Icon, withStyles } from '@ui-kitten/components'; -import CustomText from './Text'; - +import CustomText from '../../../components/Text'; const styles = (theme) => ({ section: { justifyContent: 'space-between', diff --git a/src/components/ChatMessageDate.js b/src/screens/ChatScreen/components/ChatMessageDate.js similarity index 96% rename from src/components/ChatMessageDate.js rename to src/screens/ChatScreen/components/ChatMessageDate.js index 5e2fdb48f..ee3d664ab 100644 --- a/src/components/ChatMessageDate.js +++ b/src/screens/ChatScreen/components/ChatMessageDate.js @@ -2,7 +2,7 @@ import React from 'react'; import { View } from 'react-native'; import { withStyles } from '@ui-kitten/components'; import PropTypes from 'prop-types'; -import CustomText from './Text'; +import CustomText from '../../../components/Text'; const styles = (theme) => ({ mainView: { diff --git a/src/components/ChatMessageItem.js b/src/screens/ChatScreen/components/ChatMessageItem.js similarity index 95% rename from src/components/ChatMessageItem.js rename to src/screens/ChatScreen/components/ChatMessageItem.js index d2dade5ec..2004577c6 100644 --- a/src/components/ChatMessageItem.js +++ b/src/screens/ChatScreen/components/ChatMessageItem.js @@ -5,13 +5,13 @@ import { withStyles, Icon } from '@ui-kitten/components'; import Hyperlink from 'react-native-hyperlink'; import Clipboard from '@react-native-clipboard/clipboard'; import Markdown from 'react-native-markdown-display'; - import ActionSheet from 'react-native-actions-sheet'; -import CustomText from './Text'; -import { messageStamp } from '../helpers/TimeHelper'; -import { openURL } from '../helpers/UrlHelper'; + +import CustomText from '../../../components/Text'; +import { messageStamp } from '../../../helpers/TimeHelper'; +import { openURL } from '../../../helpers/UrlHelper'; import ChatMessageActionItem from './ChatMessageActionItem'; -import { showToast } from '../helpers/ToastHelper'; +import { showToast } from '../../../helpers/ToastHelper'; const LockIcon = (style) => { return ; @@ -214,4 +214,4 @@ const ChatMessageItemComponent = ({ type, message, eva: { style, theme }, create ChatMessageItemComponent.propTypes = propTypes; const ChatMessageItem = withStyles(ChatMessageItemComponent, styles); -export default ChatMessageItem; \ No newline at end of file +export default ChatMessageItem; diff --git a/src/screens/ConversationAction/ConversationAction.js b/src/screens/ConversationAction/ConversationAction.js index 3e27f7e12..296475768 100644 --- a/src/screens/ConversationAction/ConversationAction.js +++ b/src/screens/ConversationAction/ConversationAction.js @@ -16,7 +16,16 @@ const ConversationActionComponent = ({ eva: { style }, onPressAction, conversati const { meta: { assignee }, } = conversationDetails; - const assignedAgent = agents.find((item) => item.id === assignee.id); + + let assignedAgent = null; + if (assignee) { + assignedAgent = agents.find((item) => item.id === assignee.id); + } else { + assignedAgent = { + name: 'Select Agent', + thumbnail: '', + }; + } return ( @@ -28,11 +37,19 @@ const ConversationActionComponent = ({ eva: { style }, onPressAction, conversati thumbnail={assignedAgent.thumbnail} availabilityStatus={assignedAgent.availability_status} /> + {assignee && ( + + )} + ; -import ActionCable from '../../helpers/ActionCable'; -import { getPubSubToken, getUserDetails } from '../../helpers/AuthHelper'; -import { onLogOut } from '../../actions/auth'; -import HeaderBar from '../../components/HeaderBar'; -import { findUniqueConversations } from '../../helpers'; -import { clearAllDeliveredNotifications } from '../../helpers/PushHelper'; -import Empty from '../../components/Empty'; -import images from '../../constants/images'; - const wait = (timeout) => { return new Promise((resolve) => { setTimeout(resolve, timeout); diff --git a/src/screens/Language/LanguageScreen.js b/src/screens/Language/LanguageScreen.js index 3da1cbb10..a67539898 100644 --- a/src/screens/Language/LanguageScreen.js +++ b/src/screens/Language/LanguageScreen.js @@ -16,8 +16,10 @@ import { LANGUAGES } from '../../constants'; const LanguageScreenComponent = ({ eva: { style }, navigation }) => { const settings = useSelector((state) => state.settings); + const auth = useSelector((state) => state.auth); const localeValue = settings.localeValue || 'en'; + const isLoggedIn = auth.isLoggedIn; const dispatch = useDispatch(); @@ -25,6 +27,14 @@ const LanguageScreenComponent = ({ eva: { style }, navigation }) => { dispatch(setLocale(item)); }; + const onSubmitLanguage = () => { + if (isLoggedIn) { + navigation.dispatch(StackActions.replace('Tab')); + } else { + navigation.dispatch(StackActions.replace('Login')); + } + }; + const goBack = () => { navigation.goBack(); }; @@ -52,7 +62,7 @@ const LanguageScreenComponent = ({ eva: { style }, navigation }) => { style={style.languageButton} size="large" textStyle={style.languageButtonText} - onPress={() => navigation.dispatch(StackActions.replace('Tab'))} + onPress={() => onSubmitLanguage()} text={i18n.t('SETTINGS.SUBMIT')} /> diff --git a/src/screens/LoginScreen/LoginScreen.js b/src/screens/LoginScreen/LoginScreen.js index 5fd9c74ca..405c5cb2b 100644 --- a/src/screens/LoginScreen/LoginScreen.js +++ b/src/screens/LoginScreen/LoginScreen.js @@ -170,6 +170,11 @@ const LoginScreenComponent = ({ navigation, eva }) => { {i18n.t('LOGIN.CHANGE_URL')} + + navigate('Language')}> + {i18n.t('LOGIN.CHANGE_LANGUAGE')} + + diff --git a/src/screens/Notification/NotificationScreen.js b/src/screens/Notification/NotificationScreen.js index 2ec26e78e..acb2d84f1 100644 --- a/src/screens/Notification/NotificationScreen.js +++ b/src/screens/Notification/NotificationScreen.js @@ -141,22 +141,19 @@ class NotificationScreenComponent extends Component { const { primary_actor_id, primary_actor_type, - primary_actor: { id: conversationId, meta, messages }, + primary_actor: { id: conversationId, meta }, } = item; - const { navigation, selectConversation, loadInitialMessages } = this.props; + const { navigation, selectConversation } = this.props; this.props.markNotificationAsRead({ primaryActorId: primary_actor_id, primaryActorType: primary_actor_type, }); - selectConversation({ conversationId }); - loadInitialMessages({ messages }); navigation.navigate('ChatScreen', { conversationId, meta, - messages, }); }; diff --git a/yarn.lock b/yarn.lock index 6ee8ff8f7..c52d7a3b5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2117,6 +2117,17 @@ babel-plugin-jest-hoist@^26.2.0: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" +babel-plugin-module-resolver@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2" + integrity sha512-MlX10UDheRr3lb3P0WcaIdtCSRlxdQsB1sBqL7W0raF070bGl1HQQq5K3T2vf2XAYie+ww+5AKC/WrkjRO2knA== + dependencies: + find-babel-config "^1.2.0" + glob "^7.1.6" + pkg-up "^3.1.0" + reselect "^4.0.0" + resolve "^1.13.1" + babel-plugin-syntax-trailing-function-commas@^7.0.0-beta.0: version "7.0.0-beta.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz#aa213c1435e2bffeb6fca842287ef534ad05d5cf" @@ -3895,6 +3906,14 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" +find-babel-config@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/find-babel-config/-/find-babel-config-1.2.0.tgz#a9b7b317eb5b9860cda9d54740a8c8337a2283a2" + integrity sha512-jB2CHJeqy6a820ssiqwrKMeyC6nNdmrcgkKWJWmpoxpE8RKciYJXCcXRq1h2AzCo5I5BJeN2tkGEO3hLTuePRA== + dependencies: + json5 "^0.5.1" + path-exists "^3.0.0" + find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -4583,6 +4602,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-core-module@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -5765,6 +5791,11 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + json5@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" @@ -7075,6 +7106,13 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-up@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" + integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA== + dependencies: + find-up "^3.0.0" + please-upgrade-node@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942" @@ -7336,11 +7374,21 @@ react-native-background-color@^0.0.8: resolved "https://registry.yarnpkg.com/react-native-background-color/-/react-native-background-color-0.0.8.tgz#661f737156a18a86ece362126e9c97e33fa42ed2" integrity sha512-Or89KCBNyFsD0234g/MgWyRrhwtd5egHXdqUwal1e5FKpum8d/x1y5PQqF3OUFR0W/ztp5Vt3FaLc/LEY0Fy+Q== +react-native-config@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/react-native-config/-/react-native-config-1.4.2.tgz#cc39f22ed72c5cb06af1adf0ba70ef01593900b4" + integrity sha512-iTvCvThvFTH7SdiBcsxYtpLy86zglsh51ZBqFPPZ75tdrQMdlRnDNnb86kNjQf03KT0qs1nCMLRejfPMIim8iA== + react-native-device-info@^5.6.5: version "5.6.5" resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-5.6.5.tgz#3f18d7c38b21cfc475163351a9aae6e9037a43a0" integrity sha512-l457wv7gYXSRnm38AH+NpCgKYa1zxxfN0EL8mTBxo+TnasXtk0FZ86zWDLhm4eqpcSH8xpT/+z2MQrATfqCaoA== +react-native-document-picker@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/react-native-document-picker/-/react-native-document-picker-5.0.0.tgz#15c2557edd6c3ebc129b529e52556ec378c99853" + integrity sha512-VL1fcYyAx5d9/JlGI3hyuZm3JRGPHAYaU6rKD2Kn3K4b11xB4NdjVvtAiRRFEwDm8BCBKxDoTXRWoGYOhtefyQ== + react-native-eva-icons@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/react-native-eva-icons/-/react-native-eva-icons-1.3.1.tgz#1e6e019b0fd3cb1669db50bd6bbdaa6d89327593" @@ -7744,6 +7792,11 @@ requireindex@~1.1.0: resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.1.0.tgz#e5404b81557ef75db6e49c5a72004893fe03e162" integrity sha1-5UBLgVV+91225JxacgBIk/4D4WI= +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -7783,6 +7836,14 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.17.0, resolve@^1.3 dependencies: path-parse "^1.0.6" +resolve@^1.13.1: + version "1.20.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" + integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== + dependencies: + is-core-module "^2.2.0" + path-parse "^1.0.6" + restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -9434,3 +9495,8 @@ yargs@^15.1.0, yargs@^15.3.1: which-module "^2.0.0" y18n "^4.0.0" yargs-parser "^18.1.2" + +yarn@^1.22.10: + version "1.22.10" + resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.10.tgz#c99daa06257c80f8fa2c3f1490724e394c26b18c" + integrity sha512-IanQGI9RRPAN87VGTF7zs2uxkSyQSrSPsju0COgbsKQOOXr5LtcVPeyXWgwVa0ywG3d8dg6kSYKGBuYK021qeA==