diff --git a/src/ONYXKEYS.js b/src/ONYXKEYS.js index 0d77fc437c7..713dfa3df45 100755 --- a/src/ONYXKEYS.js +++ b/src/ONYXKEYS.js @@ -5,6 +5,9 @@ export default { // Holds information about the users account that is logging in ACCOUNT: 'account', + // Holds the reportID for the report between the user and their account manager + ACCOUNT_MANAGER_REPORT_ID: 'accountManagerReportID', + // Boolean flag only true when first set NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER: 'isFirstTimeNewExpensifyUser', diff --git a/src/components/ArchivedReportFooter.js b/src/components/ArchivedReportFooter.js index 1b6958fc2e6..c271b0f8c0b 100644 --- a/src/components/ArchivedReportFooter.js +++ b/src/components/ArchivedReportFooter.js @@ -70,6 +70,7 @@ const ArchivedReportFooter = (props) => { policyName: `${ReportUtils.getPolicyName(props.report, props.policies)}`, })} shouldRenderHTML={archiveReason !== CONST.REPORT.ARCHIVE_REASON.DEFAULT} + shouldShowIcon /> ); }; diff --git a/src/components/Banner.js b/src/components/Banner.js index 87808c22af2..3b5a8d66e3a 100644 --- a/src/components/Banner.js +++ b/src/components/Banner.js @@ -1,6 +1,7 @@ import React, {memo} from 'react'; import PropTypes from 'prop-types'; -import {View} from 'react-native'; +import {View, Pressable} from 'react-native'; +import compose from '../libs/compose'; import Hoverable from './Hoverable'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; @@ -9,17 +10,45 @@ import Text from './Text'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; import getButtonState from '../libs/getButtonState'; +import Tooltip from './Tooltip'; +import withLocalize, {withLocalizePropTypes} from './withLocalize'; const propTypes = { /** Text to display in the banner. */ text: PropTypes.string.isRequired, + /** Should this component render the left-aligned exclamation icon? */ + shouldShowIcon: PropTypes.bool, + + /** Should this component render a close button? */ + shouldShowCloseButton: PropTypes.bool, + /** Should this component render the text as HTML? */ shouldRenderHTML: PropTypes.bool, + + /** Callback called when the close button is pressed */ + onClose: PropTypes.func, + + /** Callback called when the message is pressed */ + onPress: PropTypes.func, + + // eslint-disable-next-line react/forbid-prop-types + containerStyles: PropTypes.arrayOf(PropTypes.object), + + // eslint-disable-next-line react/forbid-prop-types + textStyles: PropTypes.arrayOf(PropTypes.object), + + ...withLocalizePropTypes, }; const defaultProps = { shouldRenderHTML: false, + shouldShowIcon: false, + shouldShowCloseButton: false, + onClose: () => {}, + onPress: () => {}, + containerStyles: [], + textStyles: [], }; const Banner = props => ( @@ -32,19 +61,35 @@ const Banner = props => ( styles.borderRadiusNormal, isHovered ? styles.activeComponentBG : styles.hoveredComponentBG, styles.breakAll, + ...props.containerStyles, ]} > - - + + {props.shouldShowIcon && ( + + + + )} + { + props.shouldRenderHTML + ? + : {props.text} + } - { - props.shouldRenderHTML - ? - : {props.text} - } + {props.shouldShowCloseButton && ( + + + + + + )} )} @@ -54,4 +99,7 @@ Banner.propTypes = propTypes; Banner.defaultProps = defaultProps; Banner.displayName = 'Banner'; -export default memo(Banner); +export default compose( + withLocalize, + memo, +)(Banner); diff --git a/src/languages/en.js b/src/languages/en.js index 0c01680f2c8..1b7e384a92d 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -211,6 +211,7 @@ export default { beginningOfChatHistoryPolicyExpenseChatPartOne: 'Collaboration between ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' and ', beginningOfChatHistoryPolicyExpenseChatPartThree: ' starts here! šŸŽ‰ This is the place to chat, request money and settle up.', + chatWithAccountManager: 'Chat with your account manager here', }, newMessages: 'New messages', reportTypingIndicator: { diff --git a/src/languages/es.js b/src/languages/es.js index 5fcaca9d0ee..9fee9c421a5 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -211,6 +211,7 @@ export default { beginningOfChatHistoryPolicyExpenseChatPartOne: 'Ā”La colaboraciĆ³n entre ', beginningOfChatHistoryPolicyExpenseChatPartTwo: ' y ', beginningOfChatHistoryPolicyExpenseChatPartThree: ' empieza aquĆ­! :tada: Este es el lugar donde chatear, pedir dinero y pagar.', + chatWithAccountManager: 'Chatea con tu gestor de cuenta aquĆ­', }, newMessages: 'Mensajes nuevos', reportTypingIndicator: { diff --git a/src/libs/Navigation/DeprecatedCustomActions.js b/src/libs/Navigation/DeprecatedCustomActions.js index 1c192d60f93..eb10d9c2f82 100644 --- a/src/libs/Navigation/DeprecatedCustomActions.js +++ b/src/libs/Navigation/DeprecatedCustomActions.js @@ -102,16 +102,23 @@ function pushDrawerRoute(route) { const screenRoute = {type: 'route', name: newScreenName}; const history = _.map(state.history ? [...state.history] : [screenRoute], () => screenRoute); - // Force drawer to close - // https://github.com/react-navigation/react-navigation/blob/94ab791cae5061455f036cd3f6bc7fa63167e7c7/packages/routers/src/DrawerRouter.tsx#L142 - const hasDrawerhistory = _.find(state.history || [], h => h.type === 'drawer'); - if (!hasDrawerhistory || currentState.type !== 'drawer') { + const drawerHistoryItem = _.find(state.history || [], h => h.type === 'drawer'); + const isDrawerClosed = drawerHistoryItem && drawerHistoryItem.status === 'closed'; + if (!drawerHistoryItem || currentState.type !== 'drawer') { + // Add the drawer item to the navigation history to control if the drawer should be in open or closed state history.push({ type: 'drawer', - // If current state is not from drawer navigator then always use closed status to close the drawer + // If current state is not from drawer navigator then always force the drawer to close by using closed status + // https://github.com/react-navigation/react-navigation/blob/94ab791cae5061455f036cd3f6bc7fa63167e7c7/packages/routers/src/DrawerRouter.tsx#L142 status: currentState.type !== 'drawer' || currentState.default === 'open' ? 'closed' : 'open', }); + } else if (isDrawerClosed) { + // Keep the drawer closed if it's already closed + history.push({ + type: 'drawer', + status: 'closed', + }); } return CommonActions.reset({ diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 2107b4eb5ed..b76237b06b3 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -30,6 +30,8 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/ import OfflineIndicator from '../../components/OfflineIndicator'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import withDrawerState, {withDrawerPropTypes} from '../../components/withDrawerState'; +import Banner from '../../components/Banner'; +import withLocalize from '../../components/withLocalize'; import reportPropTypes from '../reportPropTypes'; const propTypes = { @@ -112,11 +114,14 @@ class ReportScreen extends React.Component { this.onSubmitComment = this.onSubmitComment.bind(this); this.updateViewportOffsetTop = this.updateViewportOffsetTop.bind(this); + this.chatWithAccountManager = this.chatWithAccountManager.bind(this); + this.dismissBanner = this.dismissBanner.bind(this); this.removeViewportResizeListener = () => {}; this.state = { skeletonViewContainerHeight: 0, viewportOffsetTop: 0, + isBannerVisible: true, }; } @@ -191,6 +196,14 @@ class ReportScreen extends React.Component { this.setState({viewportOffsetTop}); } + dismissBanner() { + this.setState({isBannerVisible: false}); + } + + chatWithAccountManager() { + Navigation.navigate(ROUTES.getReportRoute(this.props.accountManagerReportID)); + } + render() { if (!this.props.isSidebarLoaded) { return null; @@ -236,6 +249,16 @@ class ReportScreen extends React.Component { onNavigationMenuButtonClicked={() => Navigation.navigate(ROUTES.HOME)} /> + {this.props.accountManagerReportID && ReportUtils.isConciergeChatReport(this.props.report) && this.state.isBannerVisible && ( + + )}