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

Enable IOUPreview for bill splits #18143

Merged
merged 42 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
570bc0c
locate iouReportID in ReportActionItem to simplify onyx report retrieval
Julesssss Apr 27, 2023
dc1fe5b
pas requestReportID to IOUAction comp as string
Julesssss Apr 27, 2023
0cc25d7
Merge branch 'main' into jules-iouPreview-showForBillSplit
Julesssss Apr 28, 2023
70579f1
always show iouPreview component
Julesssss Apr 28, 2023
2de4b4b
implement launching of the chat participants page from the IOUPreview
Julesssss Apr 28, 2023
e60913e
IOUPreview now launches either the IOUDetailsPage or ParticipantsPage
Julesssss Apr 28, 2023
2fb9ab4
Merge branch 'main' into jules-iouPreview-showForBillSplit
Julesssss May 2, 2023
7836e1f
improve iouPreview callback naming
Julesssss May 2, 2023
605fce5
for group split IOUPreview, use currency and total from action
Julesssss May 2, 2023
c89d438
don't show IOUPreview paid icon for splits
Julesssss May 2, 2023
0d21ffc
add isBillSplit prop to IOUPreview to selectively show content
Julesssss May 2, 2023
808d427
show correct amount for split IOUPreview
Julesssss May 2, 2023
467959f
remove unused formatted 'send $amount' string
Julesssss May 3, 2023
fd92e3c
rename split amount string format
Julesssss May 3, 2023
10f1d6a
display IOU type in IOUPreview component
Julesssss May 3, 2023
5d778a6
refactor and simplify iou format strings
Julesssss May 3, 2023
ef66bd7
replace temporary isGroupSplit condition
Julesssss May 3, 2023
dd89377
refactor getAvatars to OptionListUtils helper function
Julesssss May 3, 2023
8042ffb
document OptionListUtils helper function and rename
Julesssss May 3, 2023
87f468e
display split participant avatars in IOUPreview
Julesssss May 3, 2023
2937138
simplify IOUPreview participant avatar logic
Julesssss May 3, 2023
b3f0d25
remove 'x paid y' from IOUPreview
Julesssss May 3, 2023
c741b6b
display IOU description in IOUPreview
Julesssss May 3, 2023
74f6ca0
re-enable pending conversation message
Julesssss May 3, 2023
5a27113
show the IOUPreview component for all IOUActions
Julesssss May 3, 2023
ab25d1a
clean up iouReportID logic
Julesssss May 3, 2023
23b7cad
always show the clickable IOUPreview styles
Julesssss May 3, 2023
7625547
show tooltip for split IOUPreview component
Julesssss May 3, 2023
b5dd4f2
Merge branch 'main' into jules-iouPreview-showForBillSplit
Julesssss May 3, 2023
b3f1805
remove unnecessary props and const from IOUPreview
Julesssss May 3, 2023
c12892a
apply formatting fixes
Julesssss May 3, 2023
46e52b9
update missed iouRequest translation
Julesssss May 3, 2023
81eb920
simplify reportActionItem logic for detecting iouReportId and improve…
Julesssss May 4, 2023
df7f304
uncapitalize the IOUPreview messages
Julesssss May 4, 2023
bd7569e
improve and simplify amount formatting logic
Julesssss May 4, 2023
2645cec
Merge branch 'main' into jules-iouPreview-showForBillSplit
Julesssss May 4, 2023
af90d08
apply lint fixes and improve IOUPreview documentation
Julesssss May 4, 2023
689ae2b
remove trailing space
Julesssss May 4, 2023
3dcf988
merge main and resolve conflicts with new currency util function
Julesssss May 5, 2023
166d5d6
capitalise request/split string in certain cases to avoid duplicate s…
Julesssss May 5, 2023
ac3083b
revert back to showing a single IOUPreview
Julesssss May 5, 2023
721d353
simplify logic and resolve 'line too long' lint error
Julesssss May 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/components/MoneyRequestConfirmationList.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withOnyx} from 'react-native-onyx';
import Str from 'expensify-common/lib/str';
import _ from 'underscore';
import styles from '../styles/styles';
import * as OptionsListUtils from '../libs/OptionsListUtils';
Expand Down Expand Up @@ -105,10 +106,10 @@ class MoneyRequestConfirmationList extends Component {
*/
getSplitOrRequestOptions() {
return [{
text: this.props.translate(
this.props.hasMultipleParticipants ? 'iou.split' : 'iou.request',
text: Str.recapitalize(this.props.translate(
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming from #18823:
This change was overlooked. There's another case of letter needs to be uppercased as well as first letter.
i.e. "Request CA$6.00"
This could have been caught earlier if we had tested this copy change in Spanish.

this.props.hasMultipleParticipants ? 'iou.splitAmount' : 'iou.requestAmount',
{amount: CurrencyUtils.convertToDisplayString(this.props.iouAmount, this.props.iou.selectedCurrencyCode)},
),
)),
value: this.props.hasMultipleParticipants ? CONST.IOU.MONEY_REQUEST_TYPE.SPLIT : CONST.IOU.MONEY_REQUEST_TYPE.REQUEST,
}];
}
Expand Down
29 changes: 17 additions & 12 deletions src/components/ReportActionItem/IOUAction.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const propTypes = {
/** The ID of the associated chatReport */
chatReportID: PropTypes.string.isRequired,

/** The ID of the associated request report */
requestReportID: PropTypes.string.isRequired,

/** Is this IOUACTION the most recent? */
isMostRecentIOUReportAction: PropTypes.bool.isRequired,

Expand Down Expand Up @@ -66,14 +69,16 @@ const defaultProps = {
};

const IOUAction = (props) => {
const launchDetailsModal = () => {
Navigation.navigate(ROUTES.getIouDetailsRoute(props.chatReportID, props.action.originalMessage.IOUReportID));
const hasMultipleParticipants = props.chatReport.participants.length > 1;
const onIOUPreviewPressed = () => {
if (hasMultipleParticipants) {
Navigation.navigate(ROUTES.getReportParticipantsRoute(props.chatReportID));
} else {
Navigation.navigate(ROUTES.getIouDetailsRoute(props.chatReportID, props.action.originalMessage.IOUReportID));
}
};

const shouldShowIOUPreview = (
props.isMostRecentIOUReportAction
&& Boolean(props.action.originalMessage.IOUReportID)
&& props.chatReport.hasOutstandingIOU) || props.action.originalMessage.type === 'pay';
const shouldShowIOUPreview = props.isMostRecentIOUReportAction || props.action.originalMessage.type === 'pay';
Copy link
Contributor Author

Choose a reason for hiding this comment

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

While we could include 'pay' in the array, it doesn't really make sense because it is not a IOURequest. Also, we're going to completely remove this condition with my next PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

We can do that


let shouldShowPendingConversionMessage = false;
if (
Expand All @@ -93,21 +98,21 @@ const IOUAction = (props) => {
action={props.action}
chatReportID={props.chatReportID}
contextMenuAnchor={props.contextMenuAnchor}
shouldAllowViewDetails={Boolean(props.action.originalMessage.IOUReportID)}
onViewDetailsPressed={launchDetailsModal}
onViewDetailsPressed={onIOUPreviewPressed}
checkIfContextMenuActive={props.checkIfContextMenuActive}
isHovered={props.isHovered}
/>
{shouldShowIOUPreview && (
Julesssss marked this conversation as resolved.
Show resolved Hide resolved
<IOUPreview
iouReportID={props.action.originalMessage.IOUReportID.toString()}
iouReportID={props.requestReportID}
chatReportID={props.chatReportID}
isBillSplit={hasMultipleParticipants}
Copy link
Contributor

Choose a reason for hiding this comment

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

Coming from #18671.
We were using participants.length to determine if action is a split action, so the check would fail if you delete all of the participants from the workspace.
This was fixed by checking the original type of the action
const isSplitBillAction = lodashGet(props.action, 'originalMessage.type', '') === CONST.IOU.REPORT_ACTION_TYPE.SPLIT;

action={props.action}
contextMenuAnchor={props.contextMenuAnchor}
checkIfContextMenuActive={props.checkIfContextMenuActive}
shouldShowPendingConversionMessage={shouldShowPendingConversionMessage}
onPayButtonPressed={launchDetailsModal}
onPreviewPressed={launchDetailsModal}
onPayButtonPressed={onIOUPreviewPressed}
onPreviewPressed={onIOUPreviewPressed}
containerStyles={[
styles.cursorPointer,
props.isHovered
Expand All @@ -131,7 +136,7 @@ export default compose(
key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`,
},
iouReport: {
key: ({action}) => `${ONYXKEYS.COLLECTION.REPORT}${action.originalMessage.IOUReportID}`,
key: ({requestReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${requestReportID}`,
},
reportActions: {
key: ({chatReportID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`,
Expand Down
83 changes: 28 additions & 55 deletions src/components/ReportActionItem/IOUPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
Pressable,
} from 'react-native';
import PropTypes from 'prop-types';
import Str from 'expensify-common/lib/str';
import {withOnyx} from 'react-native-onyx';
import lodashGet from 'lodash/get';
import _ from 'underscore';
Expand All @@ -26,7 +25,7 @@ import ControlSelection from '../../libs/ControlSelection';
import * as DeviceCapabilities from '../../libs/DeviceCapabilities';
import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes';
import {showContextMenuForReport} from '../ShowContextMenuContext';
import * as ReportUtils from '../../libs/ReportUtils';
import * as OptionsListUtils from '../../libs/OptionsListUtils';
import Button from '../Button';
import * as CurrencyUtils from '../../libs/CurrencyUtils';

Expand Down Expand Up @@ -80,16 +79,12 @@ const propTypes = {
hasOutstandingIOU: PropTypes.bool,
}),

/** True if this is this IOU is a split instead of a 1:1 request */
isBillSplit: PropTypes.bool.isRequired,

/** True if the IOU Preview card is hovered */
isHovered: PropTypes.bool,

/** All of the personal details for everyone */
personalDetails: PropTypes.objectOf(PropTypes.shape({

/** This is either the user's full name, or their login if full name is an empty string */
displayName: PropTypes.string.isRequired,
})),

/** Session info for the currently logged in user. */
session: PropTypes.shape({
/** Currently logged in user email */
Expand Down Expand Up @@ -122,7 +117,6 @@ const defaultProps = {
walletTerms: {},
pendingAction: null,
isHovered: false,
personalDetails: {},
session: {
email: null,
},
Expand All @@ -137,30 +131,21 @@ const IOUPreview = (props) => {
if (props.iouReport.total === 0) {
return null;
}

const sessionEmail = lodashGet(props.session, 'email', null);
const managerEmail = props.iouReport.managerEmail || '';
const ownerEmail = props.iouReport.ownerEmail || '';

// When displaying within a IOUDetailsModal we cannot guarentee that participants are included in the originalMessage data
// Because an IOUPreview of type split can never be rendered within the IOUDetailsModal, manually building the email array is only needed for non-billSplit ious
const participantEmails = props.isBillSplit ? props.action.originalMessage.participants : [managerEmail, ownerEmail];
pecanoro marked this conversation as resolved.
Show resolved Hide resolved
const participantAvatars = OptionsListUtils.getAvatarsForLogins(participantEmails);

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

const managerName = ReportUtils.getDisplayNameForParticipant(managerEmail, true);
const ownerName = ReportUtils.getDisplayNameForParticipant(ownerEmail, true);
const managerAvatar = {
source: ReportUtils.getAvatar(lodashGet(props.personalDetails, [managerEmail, 'avatar']), managerEmail),
type: CONST.ICON_TYPE_AVATAR,
name: managerEmail,
};
const ownerAvatar = {
source: ReportUtils.getAvatar(lodashGet(props.personalDetails, [ownerEmail, 'avatar']), ownerEmail),
type: CONST.ICON_TYPE_AVATAR,
name: ownerEmail,
};
const cachedTotal = props.iouReport.total && props.iouReport.currency
? CurrencyUtils.convertToDisplayString(props.iouReport.total, props.iouReport.currency)
: '';
const avatarTooltip = [Str.removeSMSDomain(managerEmail), Str.removeSMSDomain(ownerEmail)];
// Get request formatting options, as long as currency is provided
const requestAmount = props.isBillSplit ? props.action.originalMessage.amount : props.iouReport.total;
const requestCurrency = props.isBillSplit ? lodashGet(props.action, 'originalMessage.currency', CONST.CURRENCY.USD) : props.iouReport.currency;

const showContextMenu = (event) => {
// Use action and shouldHidePayButton props to check if we are in IOUDetailsModal,
Expand Down Expand Up @@ -191,51 +176,42 @@ const IOUPreview = (props) => {
needsOffscreenAlphaCompositing
>
<View style={[styles.iouPreviewBox, ...props.containerStyles]}>
<Text>
{props.isBillSplit ? props.translate('iou.split') : props.translate('iou.cash')}
</Text>
<View style={[styles.flexRow]}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={styles.h1}>
{cachedTotal}
{CurrencyUtils.convertToDisplayString(requestAmount, requestCurrency)}
</Text>
{!props.iouReport.hasOutstandingIOU && (
{!props.iouReport.hasOutstandingIOU && !props.isBillSplit && (
<View style={styles.iouPreviewBoxCheckmark}>
<Icon src={Expensicons.Checkmark} fill={themeColors.iconSuccessFill} />
</View>
)}
</View>
<View style={styles.iouPreviewBoxAvatar}>
<MultipleAvatars
icons={[managerAvatar, ownerAvatar]}
icons={participantAvatars}
secondAvatarStyle={[
styles.secondAvatarInline,
props.isHovered
? styles.iouPreviewBoxAvatarHover
: undefined,
]}
avatarTooltips={avatarTooltip}
avatarTooltips={participantEmails}
/>
</View>
</View>
{isCurrentUserManager
? (
<Text>
{props.iouReport.hasOutstandingIOU
? props.translate('iou.youowe', {owner: ownerName})
: props.translate('iou.youpaid', {owner: ownerName})}
</Text>
) : (
<>
<Text>
{props.iouReport.hasOutstandingIOU
? props.translate('iou.owesyou', {manager: managerName})
: props.translate('iou.paidyou', {manager: managerName})}
</Text>
{props.shouldShowPendingConversionMessage && (
<Text style={[styles.textLabel, styles.colorMuted]}>
{props.translate('iou.pendingConversionMessage')}
</Text>
)}
</>
)}

{!isCurrentUserManager && props.shouldShowPendingConversionMessage && (
<Text style={[styles.textLabel, styles.colorMuted]}>
{props.translate('iou.pendingConversionMessage')}
</Text>
)}

<Text>{lodashGet(props.action, 'originalMessage.comment', '')}</Text>
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should use decoded comment here for html characters

Suggested change
<Text>{lodashGet(props.action, 'originalMessage.comment', '')}</Text>
<Text>{Str.htmlDecode(lodashGet(props.action, 'originalMessage.comment', ''))}</Text>


{(isCurrentUserManager
&& !props.shouldHidePayButton
&& props.iouReport.stateNum === CONST.REPORT.STATE_NUM.PROCESSING && (
Expand Down Expand Up @@ -278,9 +254,6 @@ IOUPreview.displayName = 'IOUPreview';
export default compose(
withLocalize,
withOnyx({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS,
},
iouReport: {
key: ({iouReportID}) => `${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`,
},
Expand Down
26 changes: 6 additions & 20 deletions src/components/ReportActionItem/IOUQuote.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Text from '../Text';
import Icon from '../Icon';
import * as Expensicons from '../Icon/Expensicons';
import styles from '../../styles/styles';
import themeColors from '../../styles/themes/default';
import reportActionPropTypes from '../../pages/home/report/reportActionPropTypes';
import withLocalize, {withLocalizePropTypes} from '../withLocalize';
import ControlSelection from '../../libs/ControlSelection';
Expand All @@ -25,9 +24,6 @@ const propTypes = {
/** Popover context menu anchor, used for showing context menu */
contextMenuAnchor: PropTypes.shape({current: PropTypes.elementType}),

/** Whether it is allowed to view details. */
shouldAllowViewDetails: PropTypes.bool,

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

Expand All @@ -42,7 +38,6 @@ const propTypes = {

const defaultProps = {
contextMenuAnchor: null,
shouldAllowViewDetails: false,
isHovered: false,
onViewDetailsPressed: () => {},
checkIfContextMenuActive: () => {},
Expand All @@ -53,9 +48,7 @@ const IOUQuote = props => (
{_.map(props.action.message, (fragment, index) => (
<Pressable
key={`iouQuote-${props.action.reportActionID}-${index}`}
onPress={props.shouldAllowViewDetails
? props.onViewDetailsPressed
: () => {}}
onPress={props.onViewDetailsPressed}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={event => showContextMenuForReport(
Expand All @@ -65,27 +58,20 @@ const IOUQuote = props => (
props.action,
props.checkIfContextMenuActive,
)}
style={[styles.flexRow, styles.justifyContentBetween,
props.shouldAllowViewDetails
? undefined
: styles.cursorDefault,
]}
focusable={props.shouldAllowViewDetails}
style={[styles.flexRow, styles.justifyContentBetween]}
focusable
>
<Text style={[styles.flex1, styles.mr2]}>
<Text style={props.shouldAllowViewDetails && styles.chatItemMessageLink}>
<Text style={styles.chatItemMessageLink}>
{/* Get first word of IOU message */}
{fragment.text.split(' ')[0]}
</Text>
<Text style={[styles.chatItemMessage, props.shouldAllowViewDetails
? styles.cursorPointer
: styles.cursorDefault]}
>
<Text style={[styles.chatItemMessage, styles.cursorPointer]}>
{/* Get remainder of IOU message */}
{fragment.text.substring(fragment.text.indexOf(' '))}
</Text>
</Text>
<Icon src={Expensicons.ArrowRight} fill={props.shouldAllowViewDetails ? StyleUtils.getIconFillColor(getButtonState(props.isHovered)) : themeColors.transparent} />
<Icon src={Expensicons.ArrowRight} fill={StyleUtils.getIconFillColor(getButtonState(props.isHovered))} />
</Pressable>
))}
</View>
Expand Down
10 changes: 3 additions & 7 deletions src/languages/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ export default {
iou: {
amount: 'Amount',
cash: 'Cash',
split: 'Split',
participants: 'Participants',
splitBill: 'Split bill',
requestMoney: 'Request money',
Expand All @@ -317,13 +318,8 @@ export default {
settleExpensify: 'Pay with Expensify',
settleElsewhere: 'I\'ll settle up elsewhere',
settlePaypalMe: 'Pay with PayPal.me',
request: ({amount}) => `Request ${amount}`,
youowe: ({owner}) => `You owe ${owner}`,
youpaid: ({owner}) => `You paid ${owner}`,
owesyou: ({manager}) => `${manager} owes you`,
paidyou: ({manager}) => `${manager} paid you`,
split: ({amount}) => `Split ${amount}`,
send: ({amount}) => `Send ${amount}`,
requestAmount: ({amount}) => `request ${amount}`,
splitAmount: ({amount}) => `split ${amount}`,
noReimbursableExpenses: 'This report has an invalid amount',
pendingConversionMessage: 'Total will update when you\'re back online',
error: {
Expand Down
10 changes: 3 additions & 7 deletions src/languages/es.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ export default {
iou: {
amount: 'Importe',
cash: 'Efectivo',
split: 'Dividir',
participants: 'Participantes',
splitBill: 'Dividir factura',
requestMoney: 'Pedir dinero',
Expand All @@ -316,13 +317,8 @@ export default {
settleExpensify: 'Pagar con Expensify',
settleElsewhere: 'Voy a pagar de otra forma',
settlePaypalMe: 'Pagar con PayPal.me',
request: ({amount}) => `Solicitar ${amount}`,
youowe: ({owner}) => `Le debes a ${owner}`,
youpaid: ({owner}) => `Le pagaste a ${owner}`,
owesyou: ({manager}) => `${manager} te debe`,
paidyou: ({manager}) => `${manager} te pagó`,
split: ({amount}) => `Dividir ${amount}`,
send: ({amount}) => `Enviar ${amount}`,
requestAmount: ({amount}) => `solicitar ${amount}`,
splitAmount: ({amount}) => `dividir ${amount}`,
noReimbursableExpenses: 'El monto de este informe es inválido',
pendingConversionMessage: 'El total se actualizará cuando estés online',
error: {
Expand Down
Loading