Skip to content

Commit

Permalink
Merge pull request #2394 from Expensify/jules-iouDetailsModal
Browse files Browse the repository at this point in the history
IOU Details Modal
  • Loading branch information
marcaaron authored May 18, 2021
2 parents f1c770f + 2edf67d commit fc080b7
Show file tree
Hide file tree
Showing 22 changed files with 660 additions and 128 deletions.
2 changes: 1 addition & 1 deletion src/Expensify.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Onyx.init({
[ONYXKEYS.SESSION]: {loading: false, shouldShowComposeInput: true},
[ONYXKEYS.ACCOUNT]: CONST.DEFAULT_ACCOUNT_DATA,
[ONYXKEYS.NETWORK]: {isOffline: false},
[ONYXKEYS.IOU]: {loading: false},
[ONYXKEYS.IOU]: {loading: false, error: false, creatingIOUTransaction: false},
},
registerStorageEventListener: (onStorageEvent) => {
listenToStorageEvents(onStorageEvent);
Expand Down
3 changes: 3 additions & 0 deletions src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export default {
getIouRequestRoute: reportID => `iou/request/${reportID}`,
IOU_BILL: 'iou/split/:reportID',
getIouSplitRoute: reportID => `iou/split/${reportID}`,
IOU_DETAILS: 'iou/details',
IOU_DETAILS_WITH_IOU_REPORT_ID: 'iou/details/:chatReportID/:iouReportID/',
getIouDetailsRoute: (chatReportID, iouReportID) => `iou/details/${chatReportID}/${iouReportID}`,
SEARCH: 'search',
SET_PASSWORD_WITH_VALIDATE_CODE: 'setpassword/:accountID/:validateCode',
DETAILS: 'details',
Expand Down
68 changes: 68 additions & 0 deletions src/components/ReportActionItemIOUAction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import ReportActionItemIOUQuote from './ReportActionItemIOUQuote';
import ReportActionPropTypes from '../pages/home/report/ReportActionPropTypes';
import ReportActionItemIOUPreview from './ReportActionItemIOUPreview';
import Navigation from '../libs/Navigation/Navigation';
import ROUTES from '../ROUTES';

const propTypes = {
/** All the data of the action */
action: PropTypes.shape(ReportActionPropTypes).isRequired,

/** The associated chatReport */
chatReportID: PropTypes.number.isRequired,

/** Should render the preview Component? */
shouldDisplayPreview: PropTypes.bool.isRequired,

/* Onyx Props */
/** ChatReport associated with iouReport */
chatReport: PropTypes.shape({
/** The participants of this report */
participants: PropTypes.arrayOf(PropTypes.string),
}),
};

const defaultProps = {
chatReport: {},
};

const ReportActionItemIOUAction = ({
action,
chatReportID,
shouldDisplayPreview,
chatReport,
}) => {
const launchDetailsModal = () => {
Navigation.navigate(ROUTES.getIouDetailsRoute(chatReportID, action.originalMessage.IOUReportID));
};
const hasMultipleParticipants = chatReport.participants.length >= 2;
return (
<>
<ReportActionItemIOUQuote
action={action}
shouldShowViewDetailsLink={!hasMultipleParticipants}
onViewDetailsPressed={launchDetailsModal}
/>
{shouldDisplayPreview && (
<ReportActionItemIOUPreview
iouReportID={action.originalMessage.IOUReportID}
onPayButtonPressed={launchDetailsModal}
/>
)}
</>
);
};

ReportActionItemIOUAction.propTypes = propTypes;
ReportActionItemIOUAction.defaultProps = defaultProps;
ReportActionItemIOUAction.displayName = 'ReportActionItemIOUAction';

export default withOnyx({
chatReport: {
key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
},
})(ReportActionItemIOUAction);
142 changes: 73 additions & 69 deletions src/components/ReportActionItemIOUPreview.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import React from 'react';
import {View, TouchableOpacity} from 'react-native';
import {View, TouchableOpacity, Text} from 'react-native';
import PropTypes from 'prop-types';
import Str from 'expensify-common/lib/str';
import {withOnyx} from 'react-native-onyx';
import _ from 'underscore';
import lodashGet from 'lodash/get';
import Str from 'expensify-common/lib/str';
import compose from '../libs/compose';
import styles from '../styles/styles';
import ONYXKEYS from '../ONYXKEYS';
import ReportActionItemIOUQuote from './ReportActionItemIOUQuote';
import ReportActionPropTypes from '../pages/home/report/ReportActionPropTypes';
import Text from './Text';
import MultipleAvatars from './MultipleAvatars';
import styles from '../styles/styles';
import withLocalize, {withLocalizePropTypes} from './withLocalize';

const propTypes = {
/** All the data of the action */
action: PropTypes.shape(ReportActionPropTypes).isRequired,
/** Additional logic for displaying the pay button */
shouldHidePayButton: PropTypes.bool,

/** Is this the most recent IOU Action? */
isMostRecentIOUReportAction: PropTypes.bool.isRequired,
/** Callback for the Pay/Settle button */
onPayButtonPressed: PropTypes.func,

/** Whether there is an outstanding amount in IOU */
hasOutstandingIOU: PropTypes.bool.isRequired,
/** The active IOUReport, used for Onyx subscription */
/* eslint-disable-next-line react/no-unused-prop-types */
iouReportID: PropTypes.number,

/* Onyx Props */

Expand All @@ -34,6 +33,9 @@ const propTypes = {

/** Outstanding amount of this transaction */
cachedTotal: PropTypes.string,

/** Does the report have an outstanding IOU that needs to be paid? */
hasOutstandingIOU: PropTypes.bool,
}),

/** All of the personal details for everyone */
Expand All @@ -48,20 +50,30 @@ const propTypes = {
/** Currently logged in user email */
email: PropTypes.string,
}).isRequired,

...withLocalizePropTypes,
};

const defaultProps = {
iou: {},
iouReportID: undefined,
shouldHidePayButton: false,
onPayButtonPressed: null,
};

const ReportActionItemIOUPreview = ({
action,
isMostRecentIOUReportAction,
hasOutstandingIOU,
iou,
personalDetails,
session,
shouldHidePayButton,
onPayButtonPressed,
translate,
}) => {
const sessionEmail = lodashGet(session, 'email', null);

// Pay button should only be visible to the manager of the report.
const isCurrentUserManager = iou.managerEmail === sessionEmail;

const managerName = lodashGet(
personalDetails,
[iou.managerEmail, 'displayName'],
Expand All @@ -74,51 +86,40 @@ const ReportActionItemIOUPreview = ({
);
const managerAvatar = lodashGet(personalDetails, [iou.managerEmail, 'avatar'], '');
const ownerAvatar = lodashGet(personalDetails, [iou.ownerEmail, 'avatar'], '');
const sessionEmail = lodashGet(session, 'email', null);
const cachedTotal = iou.cachedTotal ? iou.cachedTotal.replace(/[()]/g, '') : '';

// Pay button should be visible to manager person in the report
// Check if the currently logged in user is the manager.
const isCurrentUserManager = iou.managerEmail === sessionEmail;

return (
<View>
<ReportActionItemIOUQuote action={action} />
{isMostRecentIOUReportAction
&& hasOutstandingIOU
&& !_.isEmpty(iou) && (
<View style={styles.iouPreviewBox}>
<View style={styles.flexRow}>
<View style={styles.flex1}>
<Text style={styles.h1}>{cachedTotal}</Text>
<Text style={styles.mt2}>
{managerName}
{' owes '}
{ownerName}
</Text>
</View>
<View style={styles.iouPreviewBoxAvatar}>
<MultipleAvatars
avatarImageURLs={[managerAvatar, ownerAvatar]}
secondAvatarStyle={[styles.secondAvatarInline]}
/>
</View>
</View>
{isCurrentUserManager && (
<TouchableOpacity
style={[styles.buttonSmall, styles.buttonSuccess, styles.mt4]}
>
<Text
style={[
styles.buttonSmallText,
styles.buttonSuccessText,
]}
>
Pay
</Text>
</TouchableOpacity>
)}
</View>
<View style={styles.iouPreviewBox}>
<View style={styles.flexRow}>
<View style={styles.flex1}>
<Text style={styles.h1}>{cachedTotal}</Text>
<Text style={styles.mt2}>
{iou.hasOutstandingIOU
? translate('iou.owes', {manager: managerName, owner: ownerName})
: translate('iou.paid', {manager: managerName, owner: ownerName})}
</Text>
</View>
<View style={styles.iouPreviewBoxAvatar}>
<MultipleAvatars
avatarImageURLs={[managerAvatar, ownerAvatar]}
secondAvatarStyle={[styles.secondAvatarInline]}
/>
</View>
</View>
{isCurrentUserManager && !shouldHidePayButton && (
<TouchableOpacity
style={[styles.buttonSmall, styles.buttonSuccess, styles.mt4]}
onPress={onPayButtonPressed}
>
<Text
style={[
styles.buttonSmallText,
styles.buttonSuccessText,
]}
>
{translate('iou.pay')}
</Text>
</TouchableOpacity>
)}
</View>
);
Expand All @@ -128,14 +129,17 @@ ReportActionItemIOUPreview.propTypes = propTypes;
ReportActionItemIOUPreview.defaultProps = defaultProps;
ReportActionItemIOUPreview.displayName = 'ReportActionItemIOUPreview';

export default withOnyx({
iou: {
key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`,
},
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
},
session: {
key: ONYXKEYS.SESSION,
},
})(ReportActionItemIOUPreview);
export default compose(
withLocalize,
withOnyx({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
},
iou: {
key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`,
},
session: {
key: ONYXKEYS.SESSION,
},
}),
)(ReportActionItemIOUPreview);
51 changes: 40 additions & 11 deletions src/components/ReportActionItemIOUQuote.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,58 @@
import React from 'react';
import {View} from 'react-native';
import {View, Text} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'underscore';
import styles from '../styles/styles';
import ReportActionPropTypes from '../pages/home/report/ReportActionPropTypes';
import RenderHTML from './RenderHTML';
import withLocalize, {withLocalizePropTypes} from './withLocalize';

const propTypes = {
/** All the data of the action */
action: PropTypes.shape(ReportActionPropTypes).isRequired,

/** Should the View Details link be displayed? */
shouldShowViewDetailsLink: PropTypes.bool,

/** Callback invoked when View Details is pressed */
onViewDetailsPressed: PropTypes.func,

...withLocalizePropTypes,
};

const defaultProps = {
shouldShowViewDetailsLink: false,
onViewDetailsPressed: () => {},
};

const ReportActionItemIOUQuote = ({action}) => (
const ReportActionItemIOUQuote = ({
action,
shouldShowViewDetailsLink,
onViewDetailsPressed,
translate,
}) => (
<View style={[styles.chatItemMessage]}>
{_.map(action.message, (fragment, index) => {
const viewDetails = '<br /><a href="#">View Details</a>';
const html = `<blockquote>${fragment.text}${viewDetails}</blockquote>`;
return (
<RenderHTML key={`iouQuote-${action.sequenceNumber}-${index}`} html={html} />
);
})}
{_.map(action.message, (fragment, index) => (
<View key={`iouQuote-${action.sequenceNumber}-${index}`}>
<View style={[styles.blockquote]}>
<Text style={[styles.chatItemMessage]}>
{fragment.text}
</Text>
{shouldShowViewDetailsLink && (
<Text
style={[styles.chatItemMessageLink]}
onPress={onViewDetailsPressed}
>
{translate('iou.viewDetails')}
</Text>
)}
</View>
</View>
))}
</View>
);

ReportActionItemIOUQuote.propTypes = propTypes;
ReportActionItemIOUQuote.defaultProps = defaultProps;
ReportActionItemIOUQuote.displayName = 'ReportActionItemIOUQuote';

export default ReportActionItemIOUQuote;
export default withLocalize(ReportActionItemIOUQuote);
36 changes: 36 additions & 0 deletions src/components/ReportTransaction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Text} from 'react-native';
import styles from '../styles/styles';
import ReportActionPropTypes from '../pages/home/report/ReportActionPropTypes';
import ReportActionItemSingle from '../pages/home/report/ReportActionItemSingle';

const propTypes = {
/** The chatReport which the transaction is associated with */
/* eslint-disable-next-line react/no-unused-prop-types */
chatReportID: PropTypes.number.isRequired,

/** ID for the IOU report */
/* eslint-disable-next-line react/no-unused-prop-types */
iouReportID: PropTypes.number.isRequired,

/** The report action which we are displaying */
action: PropTypes.shape(ReportActionPropTypes).isRequired,
};

const ReportTransaction = ({
action,
}) => (
<ReportActionItemSingle
action={action}
wrapperStyles={[styles.reportTransaction]}
>
<Text style={[styles.chatItemMessage]}>
{action.message[0].text}
</Text>
</ReportActionItemSingle>
);

ReportTransaction.displayName = 'ReportTransaction';
ReportTransaction.propTypes = propTypes;
export default ReportTransaction;
Loading

0 comments on commit fc080b7

Please sign in to comment.