Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update UI to show fraud states #29680

Merged
merged 12 commits into from
Oct 23, 2023
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1244,7 +1244,7 @@ const CONST = {
BANK: 'Expensify Card',
FRAUD_TYPES: {
DOMAIN: 'domain',
INDIVIDUAL: 'individal',
INDIVIDUAL: 'individual',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙌

NONE: 'none',
},
STATE: {
Expand Down
7 changes: 6 additions & 1 deletion src/components/DotIndicatorMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import _ from 'underscore';
import PropTypes from 'prop-types';
import {View} from 'react-native';
import styles from '../styles/styles';
import stylePropTypes from '../styles/stylePropTypes';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import themeColors from '../styles/themes/default';
Expand All @@ -25,11 +26,15 @@ const propTypes = {
// Additional styles to apply to the container */
// eslint-disable-next-line react/forbid-prop-types
style: PropTypes.arrayOf(PropTypes.object),

// Additional styles to apply to the text
textStyles: stylePropTypes,
};

const defaultProps = {
messages: {},
style: [],
textStyles: [],
};

function DotIndicatorMessage(props) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR, but since we're adding changes to this component, let's use prop destructuring, it's documented as a best practice now

// Bad
function UserInfo(props) {
    return (
        <View>
            <Text>Name: {props.name}</Text>
            <Text>Email: {props.email}</Text>
        </View>
}

UserInfo.defaultProps = {
    name: 'anonymous';
}

// Good
function UserInfo({ name = 'anonymous', email }) {
    return (
        <View>
            <Text>Name: {name}</Text>
            <Text>Email: {email}</Text>
        </View>
    );
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that we need to update this in the scope of the current task

I believe that the best way is to apply it with typescript migration

Expand Down Expand Up @@ -64,7 +69,7 @@ function DotIndicatorMessage(props) {
{_.map(sortedMessages, (message, i) => (
<Text
key={i}
style={styles.offlineFeedback.text}
style={[styles.offlineFeedback.text, ...props.textStyles]}
>
{message}
</Text>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Illustrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import TreasureChest from '../../../assets/images/simple-illustrations/simple-il
import ThumbsUpStars from '../../../assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
import Hands from '../../../assets/images/product-illustrations/home-illustration-hands.svg';
import HandEarth from '../../../assets/images/simple-illustrations/simple-illustration__handearth.svg';
import SmartScan from '../../../assets/images/product-illustrations/simple-illustration__smartscan.svg';

export {
Abracadabra,
Expand Down Expand Up @@ -96,4 +97,5 @@ export {
ThumbsUpStars,
Hands,
HandEarth,
SmartScan,
};
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,10 @@ export default {
virtualCardNumber: 'Virtual card number',
physicalCardNumber: 'Physical card number',
reportFraud: 'Report virtual card fraud',
reviewTransaction: 'Review transaction',
suspiciousBannerTitle: 'Suspicious transaction',
suspiciousBannerDescription: 'We noticed suspicious transaction on your card. Tap below to review.',
cardLocked: "Your card is temporarily locked while our team reviews your company's account.",
cardDetails: {
cardNumber: 'Virtual card number',
expiration: 'Expiration',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,10 @@ export default {
virtualCardNumber: 'Número de la tarjeta virtual',
physicalCardNumber: 'Número de la tarjeta física',
reportFraud: 'Reportar fraude con la tarjeta virtual',
reviewTransaction: 'Revisar transacción',
suspiciousBannerTitle: 'Transacción sospechosa',
suspiciousBannerDescription: 'Hemos detectado una transacción sospechosa en la tarjeta. Haga click abajo para revisarla.',
cardLocked: 'La tarjeta está temporalmente bloqueada mientras nuestro equipo revisa la cuenta de tu empresa.',
cardDetails: {
cardNumber: 'Número de tarjeta virtual',
expiration: 'Expiración',
Expand Down
32 changes: 32 additions & 0 deletions src/pages/settings/Wallet/DangerCardSection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import styles from '../../../styles/styles';
import * as Illustrations from '../../../components/Icon/Illustrations';
import Text from '../../../components/Text';

const propTypes = {
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
};

function DangerCardSection({title, description}) {
return (
<View style={[styles.pageWrapper, styles.walletDangerSection]}>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.w100]}>
<View style={[styles.flexShrink1]}>
<Text style={[styles.walletDangerSectionTitle, styles.mb1]}>{title}</Text>
<Text styles={[styles.walletDangerSectionText]}>{description}</Text>
</View>
<View>
<Illustrations.SmartScan />
</View>
</View>
</View>
);
}

DangerCardSection.propTypes = propTypes;
DangerCardSection.displayName = 'DangerCardSection';

export default DangerCardSection;
132 changes: 85 additions & 47 deletions src/pages/settings/Wallet/ExpensifyCardPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import CardDetails from './WalletPage/CardDetails';
import MenuItem from '../../../components/MenuItem';
import CONST from '../../../CONST';
import assignedCardPropTypes from './assignedCardPropTypes';
import theme from '../../../styles/themes/default';
import DotIndicatorMessage from '../../../components/DotIndicatorMessage';
import * as Link from '../../../libs/actions/Link';
import DangerCardSection from './DangerCardSection';

const propTypes = {
/* Onyx Props */
Expand Down Expand Up @@ -63,6 +67,9 @@ function ExpensifyCardPage({
setShouldShowCardDetails(true);
};

const hasDetectedDomainFraud = _.some(domainCards, (card) => card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN);
const hasDetectedIndividualFraud = _.some(domainCards, (card) => card.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
Expand All @@ -75,67 +82,98 @@ function ExpensifyCardPage({
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_WALLET)}
/>
<ScrollView contentContainerStyle={safeAreaPaddingBottomStyle}>
<View style={[styles.flex1, styles.mb4, styles.mt4]}>
<View style={[styles.flex1, styles.mb9, styles.mt9]}>
<CardPreview />
</View>

<MenuItemWithTopDescription
description={translate('cardPage.availableSpend')}
title={formattedAvailableSpendAmount}
interactive={false}
titleStyle={styles.newKansasLarge}
/>
{!_.isEmpty(virtualCard) && (
{hasDetectedDomainFraud ? (
<DotIndicatorMessage
style={[styles.pageWrapper]}
textStyle={[styles.walletLockedMessage]}
messages={{0: translate('cardPage.cardLocked')}}
type="error"
/>
) : null}

{hasDetectedIndividualFraud && !hasDetectedDomainFraud ? (
<>
{shouldShowCardDetails ? (
<CardDetails
// This is just a temporary mock, it will be replaced in this issue https://github.com/orgs/Expensify/projects/58?pane=issue&itemId=33286617
pan="1234123412341234"
expiration="11/02/2024"
cvv="321"
domain={domain}
/>
) : (
<MenuItemWithTopDescription
description={translate('cardPage.virtualCardNumber')}
title={CardUtils.maskCard(virtualCard.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardNumber}
shouldShowRightComponent
rightComponent={
<Button
medium
text={translate('cardPage.cardDetails.revealDetails')}
onPress={handleRevealDetails}
/>
}
/>
)}
<DangerCardSection
title={translate('cardPage.suspiciousBannerTitle')}
description={translate('cardPage.suspiciousBannerDescription')}
/>
<MenuItemWithTopDescription
title={translate('cardPage.reportFraud')}
title={translate('cardPage.reviewTransaction')}
titleStyle={styles.walletCardMenuItem}
icon={Expensicons.Flag}
icon={Expensicons.MagnifyingGlass}
iconFill={theme.icon}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.SETTINGS_REPORT_FRAUD.getRoute(domain))}
brickRoadIndicator="error"
onPress={() => Link.openOldDotLink('inbox')}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should leave a note that this won't work in DEV and staging too, only in production (I wonder if buildOldDotURL is supposed to add environment to URL, since there is no dev/staging version of oldDot)

Screen.Recording.2023-10-19.at.17.06.52.mov

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should leave a note that this won't work in DEV and staging too, only in production (I wonder if buildOldDotURL is supposed to add environment to URL, since there is no dev/staging version of oldDot)

@0xmiroslav Internal employees do have a DEV/staging Old Dot

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah you were tagging @eVoloshchak

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'll have to change the profile pic, this isn't the first time this happens 😅

/>
</>
)}
{!_.isEmpty(physicalCard) && (
) : null}

{!hasDetectedDomainFraud ? (
<>
<MenuItemWithTopDescription
description={translate('cardPage.physicalCardNumber')}
title={CardUtils.maskCard(physicalCard.lastFourPAN)}
description={translate('cardPage.availableSpend')}
title={formattedAvailableSpendAmount}
interactive={false}
titleStyle={styles.walletCardMenuItem}
/>
<MenuItem
title={translate('reportCardLostOrDamaged.report')}
icon={Expensicons.Flag}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.SETTINGS_WALLET_REPORT_CARD_LOST_OR_DAMAGED.getRoute(domain))}
titleStyle={styles.newKansasLarge}
/>
{!_.isEmpty(virtualCard) && (
<>
{shouldShowCardDetails ? (
<CardDetails
// This is just a temporary mock, it will be replaced in this issue https://github.com/orgs/Expensify/projects/58?pane=issue&itemId=33286617
pan="1234123412341234"
expiration="11/02/2024"
cvv="321"
domain={domain}
/>
) : (
<MenuItemWithTopDescription
description={translate('cardPage.virtualCardNumber')}
title={CardUtils.maskCard(virtualCard.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardNumber}
shouldShowRightComponent
rightComponent={
<Button
medium
text={translate('cardPage.cardDetails.revealDetails')}
onPress={handleRevealDetails}
/>
}
/>
)}
<MenuItemWithTopDescription
title={translate('cardPage.reportFraud')}
titleStyle={styles.walletCardMenuItem}
icon={Expensicons.Flag}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.SETTINGS_REPORT_FRAUD.getRoute(domain))}
/>
</>
)}
{!_.isEmpty(physicalCard) && (
<>
<MenuItemWithTopDescription
description={translate('cardPage.physicalCardNumber')}
title={CardUtils.maskCard(physicalCard.lastFourPAN)}
interactive={false}
titleStyle={styles.walletCardMenuItem}
/>
<MenuItem
title={translate('reportCardLostOrDamaged.report')}
icon={Expensicons.Flag}
shouldShowRightIcon
onPress={() => Navigation.navigate(ROUTES.SETTINGS_WALLET_REPORT_CARD_LOST_OR_DAMAGED.getRoute(domain))}
/>
</>
)}
</>
)}
) : null}
</ScrollView>
{physicalCard.state === CONST.EXPENSIFY_CARD.STATE.NOT_ACTIVATED && (
<Button
Expand Down
Loading
Loading