Skip to content

Commit

Permalink
cherry pick PR changes
Browse files Browse the repository at this point in the history
  • Loading branch information
JKobrynski committed Sep 26, 2023
1 parent cfad43f commit 8adb129
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 10 deletions.
21 changes: 18 additions & 3 deletions src/components/MenuItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'underscore';
import React, {useEffect, useMemo} from 'react';
import {View} from 'react-native';
import ExpensiMark from 'expensify-common/lib/ExpensiMark';
import PressableWithFeedback from './Pressable/PressableWithFeedback';
import Text from './Text';
import styles from '../styles/styles';
import themeColors from '../styles/themes/default';
Expand Down Expand Up @@ -49,6 +50,8 @@ const defaultProps = {
iconHeight: undefined,
description: undefined,
iconRight: Expensicons.ArrowRight,
onIconRightPress: undefined,
iconRightAccessibilityLabel: undefined,
iconStyles: [],
iconFill: undefined,
secondaryIconFill: undefined,
Expand Down Expand Up @@ -77,6 +80,8 @@ const defaultProps = {
numberOfLinesTitle: 1,
shouldGreyOutWhenDisabled: true,
shouldRenderAsHTML: false,
rightComponent: undefined,
shouldShowRightComponent: false,
};

const MenuItem = React.forwardRef((props, ref) => {
Expand Down Expand Up @@ -131,6 +136,8 @@ const MenuItem = React.forwardRef((props, ref) => {
return '';
}, [props.title, props.shouldRenderAsHTML, props.shouldParseTitle, html]);

const hasPressableRightComponent = props.onIconRightPress || props.iconRight || (props.rightComponent && props.shouldShowRightComponent);

return (
<Hoverable>
{(isHovered) => (
Expand Down Expand Up @@ -295,7 +302,7 @@ const MenuItem = React.forwardRef((props, ref) => {
</View>
</View>
</View>
<View style={[styles.flexRow, styles.menuItemTextContainer, styles.pointerEventsNone]}>
<View style={[styles.flexRow, styles.menuItemTextContainer, !hasPressableRightComponent && styles.pointerEventsNone]}>
{Boolean(props.badgeText) && (
<Badge
text={props.badgeText}
Expand Down Expand Up @@ -333,13 +340,21 @@ const MenuItem = React.forwardRef((props, ref) => {
</View>
)}
{Boolean(props.shouldShowRightIcon) && (
<View style={[styles.popoverMenuIcon, styles.pointerEventsAuto, props.disabled && styles.cursorDisabled]}>
<PressableWithFeedback
style={[styles.popoverMenuIcon, styles.pointerEventsAuto, props.disabled && styles.cursorDisabled]}
accessibilityRole={CONST.ACCESSIBILITY_ROLE.BUTTON}
accessibilityLabel={props.iconRightAccessibilityLabel ? props.iconRightAccessibilityLabel : ''}
accessible={!props.onIconRightPress}
disabled={!props.onIconRightPress}
onPress={props.onIconRightPress}
>
<Icon
src={props.iconRight}
fill={StyleUtils.getIconFillColor(getButtonState(props.focused || isHovered, pressed, props.success, props.disabled, props.interactive))}
/>
</View>
</PressableWithFeedback>
)}
{props.shouldShowRightComponent && props.rightComponent}
{props.shouldShowSelectedState && <SelectCircle isChecked={props.isSelected} />}
</View>
</>
Expand Down
12 changes: 12 additions & 0 deletions src/components/menuItemPropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ const propTypes = {
/** Overrides the icon for shouldShowRightIcon */
iconRight: PropTypes.elementType,

/** Function to fire when the right icon has been pressed */
onIconRightPress: PropTypes.func,

/** accessibilityLabel for the right icon when it's pressable */
iconRightAccessibilityLabel: PropTypes.string,

/** A description text to show under the title */
description: PropTypes.string,

Expand Down Expand Up @@ -147,6 +153,12 @@ const propTypes = {

/** Should render the content in HTML format */
shouldRenderAsHTML: PropTypes.bool,

/** Component to be displayed on the right */
rightComponent: PropTypes.node,

/** Should render component on the right */
shouldShowRightComponent: PropTypes.bool,
};

export default propTypes;
8 changes: 8 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,14 @@ export default {
setDefaultFailure: 'Something went wrong. Please chat with Concierge for further assistance.',
},
addBankAccountFailure: 'An unexpected error occurred while trying to add your bank account. Please try again.',
cardDetails: {
cardNumber: 'Virtual card number',
expiration: 'Expiration',
cvv: 'CVV',
address: 'Address',
revealDetails: 'Reveal details',
copyCardNumber: 'Copy card number',
},
},
cardPage: {
expensifyCard: 'Expensify Card',
Expand Down
8 changes: 8 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,14 @@ export default {
setDefaultFailure: 'No se ha podido configurar el método de pago.',
},
addBankAccountFailure: 'Ocurrió un error inesperado al intentar añadir la cuenta bancaria. Inténtalo de nuevo.',
cardDetails: {
cardNumber: 'Número de tarjeta virtual',
expiration: 'Expiración',
cvv: 'CVV',
address: 'Dirección',
revealDetails: 'Revelar detalles',
copyCardNumber: 'Copiar número de la tarjeta',
},
},
cardPage: {
expensifyCard: 'Tarjeta Expensify',
Expand Down
38 changes: 31 additions & 7 deletions src/pages/settings/Wallet/ExpensifyCardPage.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';
import React from 'react';
import React, {useState} from 'react';
import {ScrollView, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
Expand All @@ -16,6 +16,8 @@ import * as CurrencyUtils from '../../../libs/CurrencyUtils';
import Navigation from '../../../libs/Navigation/Navigation';
import styles from '../../../styles/styles';
import * as CardUtils from '../../../libs/CardUtils';
import Button from '../../../components/Button';
import CardDetails from './WalletPage/CardDetails';

const propTypes = {
/* Onyx Props */
Expand Down Expand Up @@ -45,12 +47,18 @@ function ExpensifyCardPage({
const virtualCard = _.find(domainCards, (card) => card.isVirtual) || {};
const physicalCard = _.find(domainCards, (card) => !card.isVirtual) || {};

const [shouldShowCardDetails, setShouldShowCardDetails] = useState(false);

if (_.isEmpty(virtualCard) && _.isEmpty(physicalCard)) {
return <NotFoundPage />;
}

const formattedAvailableSpendAmount = CurrencyUtils.convertToDisplayString(physicalCard.availableSpend || virtualCard.availableSpend || 0);

const handleRevealDetails = () => {
setShouldShowCardDetails(true);
};

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
Expand All @@ -73,13 +81,29 @@ function ExpensifyCardPage({
interactive={false}
titleStyle={styles.newKansasLarge}
/>
{!_.isEmpty(physicalCard) && (
<MenuItemWithTopDescription
description={translate('cardPage.virtualCardNumber')}
title={CardUtils.maskCard(virtualCard.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardNumber}
{shouldShowCardDetails ? (
<CardDetails
pan="1234123412341234"
expiration="11/02/2024"
cvv="321"
/>
) : (
!_.isEmpty(physicalCard) && (
<MenuItemWithTopDescription
description={translate('cardPage.virtualCardNumber')}
title={CardUtils.maskCard(virtualCard.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardNumber}
shouldShowRightComponent
rightComponent={
<Button
medium
text={translate('walletPage.cardDetails.revealDetails')}
onPress={handleRevealDetails}
/>
}
/>
)
)}
{!_.isEmpty(physicalCard) && (
<MenuItemWithTopDescription
Expand Down
110 changes: 110 additions & 0 deletions src/pages/settings/Wallet/WalletPage/CardDetails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import * as Expensicons from '../../../../components/Icon/Expensicons';
import MenuItemWithTopDescription from '../../../../components/MenuItemWithTopDescription';
import Clipboard from '../../../../libs/Clipboard';
import useLocalize from '../../../../hooks/useLocalize';
import usePrivatePersonalDetails from '../../../../hooks/usePrivatePersonalDetails';
import ONYXKEYS from '../../../../ONYXKEYS';

const propTypes = {
/** Card number */
pan: PropTypes.string,

/** Card expiration date */
expiration: PropTypes.string,

/** 3 digit code */
cvv: PropTypes.string,

/** User's private personal details */
privatePersonalDetails: PropTypes.shape({
/** User's home address */
address: PropTypes.shape({
street: PropTypes.string,
city: PropTypes.string,
state: PropTypes.string,
zip: PropTypes.string,
country: PropTypes.string,
}),
}),
};

const defaultProps = {
pan: '',
expiration: '',
cvv: '',
privatePersonalDetails: {
address: {
street: '',
street2: '',
city: '',
state: '',
zip: '',
country: '',
},
},
};

function CardDetails({pan, expiration, cvv, privatePersonalDetails}) {
usePrivatePersonalDetails();
const {translate} = useLocalize();

/**
* Formats an address object into an easily readable string
*
* @returns {String}
*/
const getFormattedAddress = () => {
const address = privatePersonalDetails.address || {};
const [street1, street2] = (address.street || '').split('\n');
const formatted = [street1, street2, address.city, address.state, address.zip, address.country].join(', ');

// Remove the last comma of the address
return formatted.trim().replace(/,$/, '');
};

const handleCopyToClipboard = () => {
Clipboard.setString(pan);
};

return (
<>
<MenuItemWithTopDescription
description={translate('walletPage.cardDetails.cardNumber')}
title={pan}
iconRight={Expensicons.Copy}
shouldShowRightIcon
interactive={false}
onIconRightPress={handleCopyToClipboard}
iconRightAccessibilityLabel={translate('walletPage.cardDetails.copyCardNumber')}
/>
<MenuItemWithTopDescription
description={translate('walletPage.cardDetails.expiration')}
title={expiration}
interactive={false}
/>
<MenuItemWithTopDescription
description={translate('walletPage.cardDetails.cvv')}
title={cvv}
interactive={false}
/>
<MenuItemWithTopDescription
description={translate('walletPage.cardDetails.address')}
title={getFormattedAddress()}
interactive={false}
/>
</>
);
}

CardDetails.displayName = 'CardDetails';
CardDetails.propTypes = propTypes;
CardDetails.defaultProps = defaultProps;

export default withOnyx({
privatePersonalDetails: {
key: ONYXKEYS.PRIVATE_PERSONAL_DETAILS,
},
})(CardDetails);

0 comments on commit 8adb129

Please sign in to comment.