diff --git a/src/CONST.js b/src/CONST.js index 41c37009e08d..b88f0ec2a863 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2202,6 +2202,8 @@ const CONST = { PATHS_TO_TREAT_AS_EXTERNAL: [ 'NewExpensify.dmg', ], + + CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', }; export default CONST; diff --git a/src/components/MenuItem.js b/src/components/MenuItem.js index 85101babf5cb..c3e823715a33 100644 --- a/src/components/MenuItem.js +++ b/src/components/MenuItem.js @@ -1,8 +1,6 @@ import _ from 'underscore'; import React from 'react'; -import { - View, Pressable, -} from 'react-native'; +import {View} from 'react-native'; import Text from './Text'; import styles from '../styles/styles'; import * as StyleUtils from '../styles/StyleUtils'; @@ -18,9 +16,14 @@ import colors from '../styles/colors'; import variables from '../styles/variables'; import MultipleAvatars from './MultipleAvatars'; import * as defaultWorkspaceAvatars from './Icon/WorkspaceDefaultAvatars'; +import PressableWithSecondaryInteraction from './PressableWithSecondaryInteraction'; +import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; +import * as DeviceCapabilities from '../libs/DeviceCapabilities'; +import ControlSelection from '../libs/ControlSelection'; const propTypes = { ...menuItemPropTypes, + ...windowDimensionsPropTypes, }; const defaultProps = { @@ -46,11 +49,13 @@ const defaultProps = { subtitle: undefined, iconType: CONST.ICON_TYPE_ICON, onPress: () => {}, + onSecondaryInteraction: undefined, interactive: true, fallbackIcon: Expensicons.FallbackAvatar, brickRoadIndicator: '', floatRightAvatars: [], shouldStackHorizontally: false, + shouldBlockSelection: false, }; const MenuItem = (props) => { @@ -71,7 +76,7 @@ const MenuItem = (props) => { ]); return ( - { if (props.disabled) { return; @@ -83,12 +88,16 @@ const MenuItem = (props) => { props.onPress(e); }} + onPressIn={() => props.shouldBlockSelection && props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={ControlSelection.unblock} + onSecondaryInteraction={props.onSecondaryInteraction} style={({hovered, pressed}) => ([ props.style, StyleUtils.getButtonBackgroundColorStyle(getButtonState(props.focused || hovered, pressed, props.success, props.disabled, props.interactive), true), ..._.isArray(props.wrapperStyle) ? props.wrapperStyle : [props.wrapperStyle], ])} disabled={props.disabled} + ref={props.forwardedRef} > {({hovered, pressed}) => ( <> @@ -211,12 +220,14 @@ const MenuItem = (props) => { )} - + ); }; MenuItem.propTypes = propTypes; MenuItem.defaultProps = defaultProps; MenuItem.displayName = 'MenuItem'; - -export default MenuItem; +export default withWindowDimensions(React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +))); diff --git a/src/components/MenuItemList.js b/src/components/MenuItemList.js index e442d3766893..fe7d40b582f8 100644 --- a/src/components/MenuItemList.js +++ b/src/components/MenuItemList.js @@ -3,6 +3,8 @@ import _ from 'underscore'; import PropTypes from 'prop-types'; import MenuItem from './MenuItem'; import menuItemPropTypes from './menuItemPropTypes'; +import * as ReportActionContextMenu from '../pages/home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../pages/home/report/ContextMenu/ContextMenuActions'; const propTypes = { /** An array of props that are pass to individual MenuItem components */ @@ -12,17 +14,38 @@ const defaultProps = { menuItems: [], }; -const MenuItemList = props => ( - <> - {_.map(props.menuItems, menuItemProps => ( - - ))} - -); +const MenuItemList = (props) => { + let popoverAnchor; + + /** + * Handle the secondary interaction for a menu item. + * + * @param {*} link the menu item link or function to get the link + * @param {Event} e the interaction event + */ + const secondaryInteraction = (link, e) => { + if (typeof link === 'function') { + link().then(url => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, url, popoverAnchor)); + } else if (!_.isEmpty(link)) { + ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, link, popoverAnchor); + } + }; + + return ( + <> + {_.map(props.menuItems, menuItemProps => ( + secondaryInteraction(menuItemProps.link, e) : undefined} + ref={el => popoverAnchor = el} + shouldBlockSelection={Boolean(menuItemProps.link)} + // eslint-disable-next-line react/jsx-props-no-spreading + {...menuItemProps} + /> + ))} + + ); +}; MenuItemList.displayName = 'MenuItemList'; MenuItemList.propTypes = propTypes; diff --git a/src/components/PressableWithSecondaryInteraction/index.js b/src/components/PressableWithSecondaryInteraction/index.js index f3ea25a471fd..412755a4ccef 100644 --- a/src/components/PressableWithSecondaryInteraction/index.js +++ b/src/components/PressableWithSecondaryInteraction/index.js @@ -4,6 +4,7 @@ import {Pressable} from 'react-native'; import * as pressableWithSecondaryInteractionPropTypes from './pressableWithSecondaryInteractionPropTypes'; import styles from '../../styles/styles'; import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; +import * as StyleUtils from '../../styles/StyleUtils'; /** * This is a special Pressable that calls onSecondaryInteraction when LongPressed, or right-clicked. @@ -11,6 +12,7 @@ import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; class PressableWithSecondaryInteraction extends Component { constructor(props) { super(props); + this.executeSecondaryInteraction = this.executeSecondaryInteraction.bind(this); this.executeSecondaryInteractionOnContextMenu = this.executeSecondaryInteractionOnContextMenu.bind(this); } @@ -25,11 +27,28 @@ class PressableWithSecondaryInteraction extends Component { this.pressableRef.removeEventListener('contextmenu', this.executeSecondaryInteractionOnContextMenu); } + /** + * @param {Event} e - the secondary interaction event + */ + executeSecondaryInteraction(e) { + if (DeviceCapabilities.hasHoverSupport()) { + return; + } + if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) { + this.pressableRef.blur(); + } + this.props.onSecondaryInteraction(e); + } + /** * @param {contextmenu} e - A right-click MouseEvent. * https://developer.mozilla.org/en-US/docs/Web/API/Element/contextmenu_event */ executeSecondaryInteractionOnContextMenu(e) { + if (!this.props.onSecondaryInteraction) { + return; + } + e.stopPropagation(); if (this.props.preventDefaultContentMenu) { e.preventDefault(); @@ -54,17 +73,9 @@ class PressableWithSecondaryInteraction extends Component { // On Web, Text does not support LongPress events thus manage inline mode with styling instead of using Text. return ( { - if (DeviceCapabilities.hasHoverSupport()) { - return; - } - if (this.props.withoutFocusOnSecondaryInteraction && this.pressableRef) { - this.pressableRef.blur(); - } - this.props.onSecondaryInteraction(e); - }} + onLongPress={this.props.onSecondaryInteraction ? this.executeSecondaryInteraction : undefined} onPressOut={this.props.onPressOut} onPress={this.props.onPress} ref={el => this.pressableRef = el} diff --git a/src/components/PressableWithSecondaryInteraction/index.native.js b/src/components/PressableWithSecondaryInteraction/index.native.js index c51671ba835c..744c92e4f81b 100644 --- a/src/components/PressableWithSecondaryInteraction/index.native.js +++ b/src/components/PressableWithSecondaryInteraction/index.native.js @@ -19,6 +19,9 @@ const PressableWithSecondaryInteraction = (props) => { ref={props.forwardedRef} onPress={props.onPress} onLongPress={(e) => { + if (!props.onSecondaryInteraction) { + return; + } e.preventDefault(); HapticFeedback.longPress(); props.onSecondaryInteraction(e); diff --git a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js index bffe11bc4cd8..0964edaa9506 100644 --- a/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js +++ b/src/components/PressableWithSecondaryInteraction/pressableWithSecondaryInteractionPropTypes.js @@ -1,4 +1,5 @@ import PropTypes from 'prop-types'; +import stylePropTypes from '../../styles/stylePropTypes'; const propTypes = { /** The function that should be called when this pressable is pressed */ @@ -11,13 +12,19 @@ const propTypes = { onPressOut: PropTypes.func, /** The function that should be called when this pressable is LongPressed or right-clicked. */ - onSecondaryInteraction: PropTypes.func.isRequired, + onSecondaryInteraction: PropTypes.func, /** The children which should be contained in this wrapper component. */ - children: PropTypes.node.isRequired, + children: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.node, + ]).isRequired, /** The ref to the search input (may be null on small screen widths) */ - forwardedRef: PropTypes.func, + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), /** Prevent the default ContextMenu on web/Desktop */ preventDefaultContentMenu: PropTypes.bool, @@ -34,6 +41,9 @@ const propTypes = { /** Disable focus trap for the element on secondary interaction */ withoutFocusOnSecondaryInteraction: PropTypes.bool, + + /** Used to apply styles to the Pressable */ + style: stylePropTypes, }; const defaultProps = { diff --git a/src/components/menuItemPropTypes.js b/src/components/menuItemPropTypes.js index c1759d127cfe..78486cb5a848 100644 --- a/src/components/menuItemPropTypes.js +++ b/src/components/menuItemPropTypes.js @@ -88,6 +88,18 @@ const propTypes = { /** Prop to identify if we should load avatars vertically instead of diagonally */ shouldStackHorizontally: PropTypes.bool, + + /** The function that should be called when this component is LongPressed or right-clicked. */ + onSecondaryInteraction: PropTypes.func, + + /** Flag to indicate whether or not text selection should be disabled from long-pressing the menu item. */ + shouldBlockSelection: PropTypes.bool, + + /** The ref to the menu item */ + forwardedRef: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.object, + ]), }; export default propTypes; diff --git a/src/libs/actions/Link.js b/src/libs/actions/Link.js index c38f901bb391..2cccfc447f6b 100644 --- a/src/libs/actions/Link.js +++ b/src/libs/actions/Link.js @@ -34,33 +34,35 @@ function showGrowlIfOffline() { } /** - * @param {String} url + * @param {String} [url] the url path + * @param {String} [shortLivedAuthToken] + * + * @returns {Promise} */ -function openOldDotLink(url) { - /** - * @param {String} [shortLivedAuthToken] - * @returns {Promise} - */ - function buildOldDotURL(shortLivedAuthToken) { - const hasHashParams = url.indexOf('#') !== -1; - const hasURLParams = url.indexOf('?') !== -1; +function buildOldDotURL(url, shortLivedAuthToken) { + const hasHashParams = url.indexOf('#') !== -1; + const hasURLParams = url.indexOf('?') !== -1; - const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : ''; - const emailParam = `email=${encodeURIComponent(currentUserEmail)}`; + const authTokenParam = shortLivedAuthToken ? `authToken=${shortLivedAuthToken}` : ''; + const emailParam = `email=${encodeURIComponent(currentUserEmail)}`; - const params = _.compact([authTokenParam, emailParam]).join('&'); + const params = _.compact([authTokenParam, emailParam]).join('&'); - return Environment.getOldDotEnvironmentURL() - .then((environmentURL) => { - const oldDotDomain = Url.addTrailingForwardSlash(environmentURL); + return Environment.getOldDotEnvironmentURL() + .then((environmentURL) => { + const oldDotDomain = Url.addTrailingForwardSlash(environmentURL); - // If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters. - return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`; - }); - } + // If the URL contains # or ?, we can assume they don't need to have the `?` token to start listing url parameters. + return `${oldDotDomain}${url}${hasHashParams || hasURLParams ? '&' : '?'}${params}`; + }); +} +/** + * @param {String} url the url path + */ +function openOldDotLink(url) { if (isNetworkOffline) { - buildOldDotURL().then(oldDotURL => Linking.openURL(oldDotURL)); + buildOldDotURL(url).then(oldDotURL => Linking.openURL(oldDotURL)); return; } @@ -69,11 +71,11 @@ function openOldDotLink(url) { API.makeRequestWithSideEffects( 'OpenOldDotLink', {}, {}, ).then((response) => { - buildOldDotURL(response.shortLivedAuthToken).then((oldDotUrl) => { + buildOldDotURL(url, response.shortLivedAuthToken).then((oldDotUrl) => { Linking.openURL(oldDotUrl); }); }).catch(() => { - buildOldDotURL().then((oldDotUrl) => { + buildOldDotURL(url).then((oldDotUrl) => { Linking.openURL(oldDotUrl); }); }); @@ -92,6 +94,7 @@ function openExternalLink(url, shouldSkipCustomSafariLogic = false) { } export { + buildOldDotURL, openOldDotLink, openExternalLink, }; diff --git a/src/pages/GetAssistancePage.js b/src/pages/GetAssistancePage.js index e2115c6d7814..89544b25dcfe 100644 --- a/src/pages/GetAssistancePage.js +++ b/src/pages/GetAssistancePage.js @@ -57,6 +57,7 @@ const GetAssistancePage = (props) => { shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: CONST.NEWHELP_URL, }]; // If the user is eligible for calls with their Guide, add the 'Schedule a setup call' item at the second position in the list diff --git a/src/pages/ReimbursementAccount/Enable2FAPrompt.js b/src/pages/ReimbursementAccount/Enable2FAPrompt.js index 51c62b8957c1..cb7bc1f004d3 100644 --- a/src/pages/ReimbursementAccount/Enable2FAPrompt.js +++ b/src/pages/ReimbursementAccount/Enable2FAPrompt.js @@ -13,31 +13,36 @@ import themeColors from '../../styles/themes/default'; const propTypes = { ...withLocalizePropTypes, }; -const Enable2FAPrompt = props => ( -
{ - Link.openOldDotLink(encodeURI(`settings?param={"section":"account","action":"enableTwoFactorAuth","exitTo":"${ROUTES.getBankAccountRoute()}","isFromNewDot":"true"}`)); +const Enable2FAPrompt = (props) => { + const secureYourAccountUrl = encodeURI(`settings?param={"section":"account","action":"enableTwoFactorAuth","exitTo":"${ROUTES.getBankAccountRoute()}","isFromNewDot":"true"}`); + + return ( +
{ + Link.openOldDotLink(secureYourAccountUrl); + }, + icon: Expensicons.Shield, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + iconFill: themeColors.success, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(secureYourAccountUrl), }, - icon: Expensicons.Shield, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - iconFill: themeColors.success, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - > - - - {props.translate('validationStep.enable2FAText')} - - -
-); + ]} + > + + + {props.translate('validationStep.enable2FAText')} + + +
+ ); +}; Enable2FAPrompt.propTypes = propTypes; Enable2FAPrompt.displayName = 'Enable2FAPrompt'; diff --git a/src/pages/settings/AboutPage/AboutPage.js b/src/pages/settings/AboutPage/AboutPage.js index 7c7840fdbbdb..b4a816ea5740 100644 --- a/src/pages/settings/AboutPage/AboutPage.js +++ b/src/pages/settings/AboutPage/AboutPage.js @@ -19,6 +19,8 @@ import * as Report from '../../../libs/actions/Report'; import * as Link from '../../../libs/actions/Link'; import getPlatformSpecificMenuItems from './getPlatformSpecificMenuItems'; import compose from '../../../libs/compose'; +import * as ReportActionContextMenu from '../../home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../../home/report/ContextMenu/ContextMenuActions'; const propTypes = { ...withLocalizePropTypes, @@ -26,6 +28,8 @@ const propTypes = { }; const AboutPage = (props) => { + let popoverAnchor; + const platformSpecificMenuItems = getPlatformSpecificMenuItems(props.isSmallScreenWidth); const menuItems = [ @@ -44,6 +48,7 @@ const AboutPage = (props) => { action: () => { Link.openExternalLink(CONST.GITHUB_URL); }, + link: CONST.GITHUB_URL, }, { translationKey: 'initialSettingsPage.aboutPage.viewOpenJobs', @@ -52,6 +57,7 @@ const AboutPage = (props) => { action: () => { Link.openExternalLink(CONST.UPWORK_URL); }, + link: CONST.UPWORK_URL, }, { translationKey: 'initialSettingsPage.aboutPage.reportABug', @@ -107,6 +113,10 @@ const AboutPage = (props) => { icon={item.icon} iconRight={item.iconRight} onPress={() => item.action()} + shouldBlockSelection={Boolean(item.link)} + onSecondaryInteraction={!_.isEmpty(item.link) + ? e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor) : undefined} + ref={el => popoverAnchor = el} shouldShowRightIcon /> ))} diff --git a/src/pages/settings/AppDownloadLinks.js b/src/pages/settings/AppDownloadLinks.js index 9b47bdb644ef..8acd2e95dafb 100644 --- a/src/pages/settings/AppDownloadLinks.js +++ b/src/pages/settings/AppDownloadLinks.js @@ -11,12 +11,9 @@ import compose from '../../libs/compose'; import MenuItem from '../../components/MenuItem'; import styles from '../../styles/styles'; import * as Link from '../../libs/actions/Link'; -import PressableWithSecondaryInteraction from '../../components/PressableWithSecondaryInteraction'; -import ControlSelection from '../../libs/ControlSelection'; import withWindowDimensions, {windowDimensionsPropTypes} from '../../components/withWindowDimensions'; -import * as DeviceCapabilities from '../../libs/DeviceCapabilities'; import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; -import * as ContextMenuActions from '../home/report/ContextMenu/ContextMenuActions'; +import {CONTEXT_MENU_TYPES} from '../home/report/ContextMenu/ContextMenuActions'; const propTypes = { ...withLocalizePropTypes, @@ -56,21 +53,6 @@ const AppDownloadLinksPage = (props) => { }, ]; - /** - * Show the ReportActionContextMenu modal popover. - * - * @param {Object} [event] - A press event. - * @param {String} [selection] - Copied content. - */ - const showPopover = (event, selection) => { - ReportActionContextMenu.showContextMenu( - ContextMenuActions.CONTEXT_MENU_TYPES.LINK, - event, - selection, - popoverAnchor, - ); - }; - return ( { /> {_.map(menuItems, item => ( - props.isSmallScreenWidth && DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onSecondaryInteraction={e => showPopover(e, item.link)} - ref={el => popoverAnchor = el} + onPress={() => item.action()} + onSecondaryInteraction={e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, popoverAnchor)} onKeyDown={(event) => { event.target.blur(); }} - > - item.action()} - shouldShowRightIcon - /> - + ref={el => popoverAnchor = el} + title={props.translate(item.translationKey)} + icon={item.icon} + iconRight={item.iconRight} + shouldBlockSelection + shouldShowRightIcon + /> ))} diff --git a/src/pages/settings/InitialSettingsPage.js b/src/pages/settings/InitialSettingsPage.js index 71578843b7a3..ef001593a7c0 100755 --- a/src/pages/settings/InitialSettingsPage.js +++ b/src/pages/settings/InitialSettingsPage.js @@ -36,6 +36,8 @@ import * as Link from '../../libs/actions/Link'; import OfflineWithFeedback from '../../components/OfflineWithFeedback'; import * as UserUtils from '../../libs/UserUtils'; import policyMemberPropType from '../policyMemberPropType'; +import * as ReportActionContextMenu from '../home/report/ContextMenu/ReportActionContextMenu'; +import {CONTEXT_MENU_TYPES} from '../home/report/ContextMenu/ContextMenuActions'; const propTypes = { /* Onyx Props */ @@ -117,6 +119,8 @@ class InitialSettingsPage extends React.Component { constructor(props) { super(props); + this.popoverAnchor = React.createRef(); + this.getWalletBalance = this.getWalletBalance.bind(this); this.getDefaultMenuItems = this.getDefaultMenuItems.bind(this); this.getMenuItem = this.getMenuItem.bind(this); @@ -203,6 +207,7 @@ class InitialSettingsPage extends React.Component { action: () => { Link.openExternalLink(CONST.NEWHELP_URL); }, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, + link: CONST.NEWHELP_URL, }, { translationKey: 'initialSettingsPage.about', @@ -236,6 +241,9 @@ class InitialSettingsPage extends React.Component { brickRoadIndicator={item.brickRoadIndicator} floatRightAvatars={item.floatRightAvatars} shouldStackHorizontally={item.shouldStackHorizontally} + ref={this.popoverAnchor} + shouldBlockSelection={Boolean(item.link)} + onSecondaryInteraction={!_.isEmpty(item.link) ? e => ReportActionContextMenu.showContextMenu(CONTEXT_MENU_TYPES.LINK, e, item.link, this.popoverAnchor.current) : undefined} /> ); } diff --git a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js b/src/pages/workspace/bills/WorkspaceBillsFirstSection.js index f97b6ba946ae..15397ea96776 100644 --- a/src/pages/workspace/bills/WorkspaceBillsFirstSection.js +++ b/src/pages/workspace/bills/WorkspaceBillsFirstSection.js @@ -42,6 +42,7 @@ const defaultProps = { const WorkspaceBillsFirstSection = (props) => { const emailDomain = Str.extractEmailDomain(props.session.email); + const manageYourBillsUrl = `reports?policyID=${props.policyID}&from=all&type=bill&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`; return (
{ { title: props.translate('workspace.bills.viewAllBills'), onPress: () => ( - Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=bill&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`) + Link.openOldDotLink(manageYourBillsUrl) ), icon: Expensicons.Bill, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(manageYourBillsUrl), }, ]} containerStyles={[styles.cardSection]} diff --git a/src/pages/workspace/bills/WorkspaceBillsVBAView.js b/src/pages/workspace/bills/WorkspaceBillsVBAView.js index 3731a9cc7b8f..dcb625777c7d 100644 --- a/src/pages/workspace/bills/WorkspaceBillsVBAView.js +++ b/src/pages/workspace/bills/WorkspaceBillsVBAView.js @@ -17,30 +17,35 @@ const propTypes = { ...withLocalizePropTypes, }; -const WorkspaceBillsVBAView = props => ( - <> - +const WorkspaceBillsVBAView = (props) => { + const reportsUrl = `reports?policyID=${props.policyID}&from=all&type=bill&showStates=Processing,Approved&isAdvancedFilterMode=true`; -
Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=bill&showStates=Processing,Approved&isAdvancedFilterMode=true`), - icon: Expensicons.Bill, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - > - - {props.translate('workspace.bills.VBACopy')} - -
- -); + return ( + <> + + +
Link.openOldDotLink(reportsUrl), + icon: Expensicons.Bill, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(reportsUrl), + }, + ]} + > + + {props.translate('workspace.bills.VBACopy')} + +
+ + ); +}; WorkspaceBillsVBAView.propTypes = propTypes; WorkspaceBillsVBAView.displayName = 'WorkspaceBillsVBAView'; diff --git a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js index a9a551f23131..4c1213800bac 100644 --- a/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js +++ b/src/pages/workspace/card/WorkspaceCardVBAWithECardView.js @@ -13,31 +13,40 @@ const propTypes = { ...withLocalizePropTypes, }; +const MENU_LINKS = { + ISSUE_AND_MANAGE_CARDS: 'domain_companycards', + RECONCILE_CARDS: encodeURI('domain_companycards?param={"section":"cardReconciliation"}'), + SETTLEMENT_FREQUENCY: encodeURI('domain_companycards?param={"section":"configureSettings"}'), +}; + const WorkspaceCardVBAWithECardView = (props) => { const menuItems = [ { title: props.translate('workspace.common.issueAndManageCards'), - onPress: () => Link.openOldDotLink('domain_companycards'), + onPress: () => Link.openOldDotLink(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), icon: Expensicons.ExpensifyCard, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(MENU_LINKS.ISSUE_AND_MANAGE_CARDS), }, { title: props.translate('workspace.common.reconcileCards'), - onPress: () => Link.openOldDotLink(encodeURI('domain_companycards?param={"section":"cardReconciliation"}')), + onPress: () => Link.openOldDotLink(MENU_LINKS.RECONCILE_CARDS), icon: Expensicons.ReceiptSearch, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(MENU_LINKS.RECONCILE_CARDS), }, { title: props.translate('workspace.common.settlementFrequency'), - onPress: () => Link.openOldDotLink(encodeURI('domain_companycards?param={"section":"configureSettings"}')), + onPress: () => Link.openOldDotLink(MENU_LINKS.SETTLEMENT_FREQUENCY), icon: Expensicons.Gear, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(MENU_LINKS.SETTLEMENT_FREQUENCY), }, ]; diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js b/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js index 4d027542b155..9f3c84d28ff1 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js +++ b/src/pages/workspace/invoices/WorkspaceInvoicesFirstSection.js @@ -16,39 +16,46 @@ const propTypes = { ...withLocalizePropTypes, }; -const WorkspaceInvoicesFirstSection = props => ( -
Link.openOldDotLink(encodeURI('reports?param={"createInvoice":true}')), - icon: Expensicons.Send, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - { - title: props.translate('workspace.invoices.viewAllInvoices'), - onPress: () => ( - Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`) - ), - icon: Expensicons.Invoice, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - containerStyles={[styles.cardSection]} - > - - - {props.translate('workspace.invoices.invoiceFirstSectionCopy')} - - -
-); +const WorkspaceInvoicesFirstSection = (props) => { + const sendInvoiceUrl = encodeURI('reports?param={"createInvoice":true}'); + const viewAllInvoicesUrl = `reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Open,Processing,Approved,Reimbursed,Archived&isAdvancedFilterMode=true`; + + return ( +
Link.openOldDotLink(sendInvoiceUrl), + icon: Expensicons.Send, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(sendInvoiceUrl), + }, + { + title: props.translate('workspace.invoices.viewAllInvoices'), + onPress: () => ( + Link.openOldDotLink(viewAllInvoicesUrl) + ), + icon: Expensicons.Invoice, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewAllInvoicesUrl), + }, + ]} + containerStyles={[styles.cardSection]} + > + + + {props.translate('workspace.invoices.invoiceFirstSectionCopy')} + + +
+ ); +}; WorkspaceInvoicesFirstSection.propTypes = propTypes; WorkspaceInvoicesFirstSection.displayName = 'WorkspaceInvoicesFirstSection'; diff --git a/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js b/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js index c88a61eef18b..c4f50bb9d49d 100644 --- a/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js +++ b/src/pages/workspace/invoices/WorkspaceInvoicesVBAView.js @@ -17,30 +17,35 @@ const propTypes = { ...withLocalizePropTypes, }; -const WorkspaceInvoicesVBAView = props => ( - <> - +const WorkspaceInvoicesVBAView = (props) => { + const viewUnpaidInvoicesUrl = `reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Processing&isAdvancedFilterMode=true`; -
Link.openOldDotLink(`reports?policyID=${props.policyID}&from=all&type=invoice&showStates=Processing&isAdvancedFilterMode=true`), - icon: Expensicons.Hourglass, - shouldShowRightIcon: true, - iconRight: Expensicons.NewWindow, - wrapperStyle: [styles.cardMenuItem], - }, - ]} - > - - {props.translate('workspace.invoices.unlockVBACopy')} - -
- -); + return ( + <> + + +
Link.openOldDotLink(viewUnpaidInvoicesUrl), + icon: Expensicons.Hourglass, + shouldShowRightIcon: true, + iconRight: Expensicons.NewWindow, + wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewUnpaidInvoicesUrl), + }, + ]} + > + + {props.translate('workspace.invoices.unlockVBACopy')} + +
+ + ); +}; WorkspaceInvoicesVBAView.propTypes = propTypes; WorkspaceInvoicesVBAView.displayName = 'WorkspaceInvoicesVBAView'; diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js index 20f443bf5671..3928816fb686 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseSection.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseSection.js @@ -57,6 +57,7 @@ class WorkspaceReimburseSection extends React.Component { render() { const achState = lodashGet(this.props.reimbursementAccount, 'achData.state', ''); const hasVBA = achState === BankAccount.STATE.OPEN; + const reimburseReceiptsUrl = `reports?policyID=${this.props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`; if (this.props.network.isOffline) { return ( @@ -93,11 +94,12 @@ class WorkspaceReimburseSection extends React.Component { menuItems={[ { title: this.props.translate('workspace.reimburse.reimburseReceipts'), - onPress: () => Link.openOldDotLink(`reports?policyID=${this.props.policy.id}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`), + onPress: () => Link.openOldDotLink(reimburseReceiptsUrl), icon: Expensicons.Bank, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(reimburseReceiptsUrl), }, ]} > diff --git a/src/pages/workspace/reimburse/WorkspaceReimburseView.js b/src/pages/workspace/reimburse/WorkspaceReimburseView.js index d103121b9e2a..ccb40d053e7f 100644 --- a/src/pages/workspace/reimburse/WorkspaceReimburseView.js +++ b/src/pages/workspace/reimburse/WorkspaceReimburseView.js @@ -214,6 +214,8 @@ class WorkspaceReimburseView extends React.Component { } render() { + const viewAllReceiptsUrl = `expenses?policyIDList=${this.props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`; + return ( <>
Link.openOldDotLink(`expenses?policyIDList=${this.props.policy.id}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`), + onPress: () => Link.openOldDotLink(viewAllReceiptsUrl), icon: Expensicons.Receipt, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL(viewAllReceiptsUrl), }, ]} > diff --git a/src/pages/workspace/travel/WorkspaceTravelVBAView.js b/src/pages/workspace/travel/WorkspaceTravelVBAView.js index 9c79b55c90b4..79b256136028 100644 --- a/src/pages/workspace/travel/WorkspaceTravelVBAView.js +++ b/src/pages/workspace/travel/WorkspaceTravelVBAView.js @@ -8,6 +8,7 @@ import * as Illustrations from '../../../components/Icon/Illustrations'; import Section from '../../../components/Section'; import * as Link from '../../../libs/actions/Link'; import * as Report from '../../../libs/actions/Report'; +import CONST from '../../../CONST'; const propTypes = { ...withLocalizePropTypes, @@ -25,6 +26,7 @@ const WorkspaceTravelVBAView = props => ( shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: () => Link.buildOldDotURL('domain_companycards'), }, { title: props.translate('workspace.travel.bookTravelWithConcierge'), @@ -37,11 +39,12 @@ const WorkspaceTravelVBAView = props => ( }, { title: props.translate('requestorStep.learnMore'), - onPress: () => Link.openExternalLink('https://community.expensify.com/discussion/7066/introducing-concierge-travel'), + onPress: () => Link.openExternalLink(CONST.CONCIERGE_TRAVEL_URL), icon: Expensicons.Info, shouldShowRightIcon: true, iconRight: Expensicons.NewWindow, wrapperStyle: [styles.cardMenuItem], + link: CONST.CONCIERGE_TRAVEL_URL, }, ]} > diff --git a/src/styles/stylePropTypes.js b/src/styles/stylePropTypes.js index 4c7e825a8848..edc5d0383a75 100644 --- a/src/styles/stylePropTypes.js +++ b/src/styles/stylePropTypes.js @@ -3,4 +3,5 @@ import PropTypes from 'prop-types'; export default PropTypes.oneOfType([ PropTypes.object, PropTypes.arrayOf(PropTypes.object), + PropTypes.func, ]);