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

[No QA] Create Money Request Header component #18148

Merged
merged 25 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9a02fcc
Add MoneyRequestHeader and AvatarWithDisplayName components
yuwenmemon Apr 27, 2023
6832775
Get the avatar with display name rendering
yuwenmemon Apr 27, 2023
2d52d92
Get some rudimentary style in there, get workspace logo part working …
yuwenmemon Apr 27, 2023
48e99fd
Add some more styles
yuwenmemon Apr 28, 2023
8873246
Update Avatar logic for IOU vs. Expense Report
yuwenmemon Apr 28, 2023
b31c2d1
Initial styling pass
grgia May 1, 2023
f6d9b4b
Align the bottom half
grgia May 1, 2023
e7ff900
Merge pull request #18247 from Expensify/georgia-moneyRequestHeader-s…
yuwenmemon May 1, 2023
1867b7e
Revert ReportScreen.js back to normal
yuwenmemon May 1, 2023
c59467c
Hide close button
yuwenmemon May 1, 2023
9caca42
Fix some header alignment and get formatting looking better
yuwenmemon May 1, 2023
04e5b45
Style
yuwenmemon May 1, 2023
854de26
Back button functionality
yuwenmemon May 2, 2023
a4a9226
PR comments
yuwenmemon May 2, 2023
240871a
Fix conflicts
yuwenmemon May 2, 2023
e58f154
Remove policies param from getWorkspaceAvatar
yuwenmemon May 2, 2023
b6f5732
JSX Linting
yuwenmemon May 2, 2023
938d002
Update src/components/MoneyRequestHeader.js
yuwenmemon May 3, 2023
413a867
Update src/components/MoneyRequestHeader.js
yuwenmemon May 3, 2023
036d9e9
Remove ternary
yuwenmemon May 3, 2023
89c919a
Align tooltip
yuwenmemon May 3, 2023
ead5b8c
Get rid of flex1 for header
yuwenmemon May 3, 2023
acf1257
Remove another flex1
yuwenmemon May 3, 2023
7d53670
Add a flex
yuwenmemon May 3, 2023
eadd538
Fix JS error
yuwenmemon May 4, 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
1 change: 1 addition & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,7 @@ const CONST = {
SMALL_SUBSCRIPT: 'small-subscript',
MID_SUBSCRIPT: 'mid-subscript',
LARGE_BORDERED: 'large-bordered',
HEADER: 'header',
},
OPTION_MODE: {
COMPACT: 'compact',
Expand Down
118 changes: 118 additions & 0 deletions src/components/AvatarWithDisplayName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import React from 'react';
import {View} from 'react-native';
import _ from 'underscore';
import PropTypes from 'prop-types';
import CONST from '../CONST';
import reportPropTypes from '../pages/reportPropTypes';
import participantPropTypes from './participantPropTypes';
import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import styles from '../styles/styles';
import SubscriptAvatar from './SubscriptAvatar';
import * as ReportUtils from '../libs/ReportUtils';
import Avatar from './Avatar';
import DisplayNames from './DisplayNames';
import compose from '../libs/compose';
import * as OptionsListUtils from '../libs/OptionsListUtils';
import Text from './Text';

const propTypes = {
/** The report currently being looked at */
report: reportPropTypes,

/** The policies which the user has access to and which the report could be tied to */
policies: PropTypes.shape({
/** Name of the policy */
name: PropTypes.string,
}),

/** The size of the avatar */
size: PropTypes.oneOf(_.values(CONST.AVATAR_SIZE)),

/** Personal details of all the users */
personalDetails: PropTypes.objectOf(participantPropTypes),

...windowDimensionsPropTypes,
...withLocalizePropTypes,
};

const defaultProps = {
personalDetails: {},
policies: {},
report: null,
size: CONST.AVATAR_SIZE.DEFAULT,
};

const AvatarWithDisplayName = (props) => {
const title = ReportUtils.getDisplayNameForParticipant(props.report.ownerEmail, true);
const subtitle = ReportUtils.getChatRoomSubtitle(props.report, props.policies);
const isExpenseReport = ReportUtils.isExpenseReport(props.report);
const icons = ReportUtils.getIcons(props.report, props.personalDetails, props.policies);
const ownerPersonalDetails = OptionsListUtils.getPersonalDetailsForLogins([props.report.ownerEmail], props.personalDetails);
const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(ownerPersonalDetails, false);
return (
<View>
<View style={[styles.appContentHeaderTitle]}>
{Boolean(props.report && title) && (
<View
style={[
styles.flex1,
styles.flexRow,
styles.alignItemsCenter,
styles.justifyContentBetween,
]}
>
{isExpenseReport ? (
<SubscriptAvatar
mainAvatar={icons[0]}
secondaryAvatar={icons[1]}
mainTooltip={props.report.ownerEmail}
secondaryTooltip={subtitle}
size={props.size}
/>
) : (
<Avatar
size={props.size}
source={icons[0].source}
type={icons[0].type}
name={icons[0].name}
containerStyles={props.size === CONST.AVATAR_SIZE.SMALL ? styles.emptyAvatarSmall : styles.emptyAvatar}
/>
)}
<View style={[styles.flex1, styles.flexColumn]}>
<DisplayNames
fullTitle={title}
displayNamesWithTooltips={displayNamesWithTooltips}
tooltipEnabled
numberOfLines={1}
textStyles={[styles.headerText, styles.pre]}
shouldUseFullTitle={isExpenseReport}
/>
{subtitle ? (
<Text
style={[
styles.sidebarLinkText,
styles.optionAlternateText,
styles.textLabelSupporting,
styles.pre,
]}
numberOfLines={1}
>
{subtitle}
</Text>
) : null }
</View>
</View>
)}
</View>
</View>
);
};
AvatarWithDisplayName.propTypes = propTypes;
AvatarWithDisplayName.displayName = 'AvatarWithDisplayName';
AvatarWithDisplayName.defaultProps = defaultProps;

export default compose(
withWindowDimensions,
withLocalize,
)(AvatarWithDisplayName);
78 changes: 58 additions & 20 deletions src/components/HeaderWithCloseButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import ThreeDotsMenu, {ThreeDotsMenuItemPropTypes} from './ThreeDotsMenu';
import withDelayToggleButtonState, {withDelayToggleButtonStatePropTypes} from './withDelayToggleButtonState';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import withKeyboardState, {keyboardStatePropTypes} from './withKeyboardState';
import AvatarWithDisplayName from './AvatarWithDisplayName';
import iouReportPropTypes from '../pages/iouReportPropTypes';
import participantPropTypes from './participantPropTypes';

const propTypes = {
/** Title of the Header */
Expand Down Expand Up @@ -78,6 +81,25 @@ const propTypes = {
total: PropTypes.number,
}),

/** Whether we should show an avatar */
shouldShowAvatarWithDisplay: PropTypes.bool,

/** Report, if we're showing the details for one and using AvatarWithDisplay */
report: iouReportPropTypes,

/** Policies, if we're showing the details for a report and need info about it for AvatarWithDisplay */
policies: PropTypes.shape({
/** Name of the policy */
name: PropTypes.string,
}),

/** Policies, if we're showing the details for a report and need participant details for AvatarWithDisplay */
personalDetails: PropTypes.objectOf(participantPropTypes),

/** Additional styles to render on the container of this component */
// eslint-disable-next-line react/forbid-prop-types
containerStyles: PropTypes.arrayOf(PropTypes.object),
Comment on lines +88 to +101
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB but if these are only used when shouldShowAvatarWithDisplay is true then maybe that's a case for having a completely separate component HeaderWithCloseButtonAndAvavtar or something like that 🤷.


...withLocalizePropTypes,
...withDelayToggleButtonStatePropTypes,
...keyboardStatePropTypes,
Expand All @@ -97,13 +119,18 @@ const defaultProps = {
shouldShowThreeDotsButton: false,
shouldShowCloseButton: true,
shouldShowStepCounter: true,
shouldShowAvatarWithDisplay: false,
report: null,
policies: {},
personalDetails: {},
guidesCallTaskID: '',
stepCounter: null,
threeDotsMenuItems: [],
threeDotsAnchorPosition: {
top: 0,
left: 0,
},
containerStyles: [],
};

class HeaderWithCloseButton extends Component {
Expand All @@ -128,7 +155,7 @@ class HeaderWithCloseButton extends Component {

render() {
return (
<View style={[styles.headerBar, this.props.shouldShowBorderBottom && styles.borderBottom, this.props.shouldShowBackButton && styles.pl2]}>
<View style={[styles.headerBar, this.props.shouldShowBorderBottom && styles.borderBottom, this.props.shouldShowBackButton && styles.pl2, ...this.props.containerStyles]}>
<View style={[
styles.dFlex,
styles.flexRow,
Expand All @@ -138,26 +165,37 @@ class HeaderWithCloseButton extends Component {
styles.overflowHidden,
]}
>
{this.props.shouldShowBackButton && (
<Tooltip text={this.props.translate('common.back')}>
<Pressable
onPress={() => {
if (this.props.isKeyboardShown) {
Keyboard.dismiss();
}
this.props.onBackButtonPress();
}}
style={[styles.touchableButtonImage]}
>
<Icon src={Expensicons.BackArrow} />
</Pressable>
</Tooltip>
<View style={[styles.flexRow, styles.alignItemsCenter]}>
{this.props.shouldShowBackButton && (
<Tooltip text={this.props.translate('common.back')}>
<Pressable
onPress={() => {
if (this.props.isKeyboardShown) {
Keyboard.dismiss();
}
this.props.onBackButtonPress();
}}
style={[styles.touchableButtonImage]}
>
<Icon src={Expensicons.BackArrow} />
</Pressable>
</Tooltip>
)}
{this.props.shouldShowAvatarWithDisplay && (
<AvatarWithDisplayName
report={this.props.report}
policies={this.props.policies}
personalDetails={this.props.personalDetails}
/>
)}
</View>
{!this.props.shouldShowAvatarWithDisplay && (
<Header
title={this.props.title}
subtitle={this.props.stepCounter && this.props.shouldShowStepCounter ? this.props.translate('stepCounter', this.props.stepCounter) : this.props.subtitle}
/>
)}
<Header
title={this.props.title}
subtitle={this.props.stepCounter && this.props.shouldShowStepCounter ? this.props.translate('stepCounter', this.props.stepCounter) : this.props.subtitle}
/>
<View style={[styles.reportOptions, styles.flexRow, styles.pr5]}>
<View style={[styles.reportOptions, styles.flexRow]}>
{
this.props.shouldShowDownloadButton && (
<Tooltip text={this.props.translate('common.download')}>
Expand Down
143 changes: 143 additions & 0 deletions src/components/MoneyRequestHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
import lodashGet from 'lodash/get';
import HeaderWithCloseButton from './HeaderWithCloseButton';
import iouReportPropTypes from '../pages/iouReportPropTypes';
import withLocalize, {withLocalizePropTypes} from './withLocalize';
import * as ReportUtils from '../libs/ReportUtils';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
import participantPropTypes from './participantPropTypes';
import Avatar from './Avatar';
import styles from '../styles/styles';
import themeColors from '../styles/themes/default';
import CONST from '../CONST';
import withWindowDimensions from './withWindowDimensions';
import compose from '../libs/compose';
import Navigation from '../libs/Navigation/Navigation';
import ROUTES from '../ROUTES';
import Icon from './Icon';

const propTypes = {
/** The report currently being looked at */
report: iouReportPropTypes.isRequired,

/** The policies which the user has access to and which the report could be tied to */
policies: PropTypes.shape({
/** Name of the policy */
name: PropTypes.string,
}).isRequired,

/** Personal details so we can get the ones for the report participants */
personalDetails: PropTypes.objectOf(participantPropTypes).isRequired,

/** Whether we're viewing a report with a single transaction in it */
isSingleTransactionView: PropTypes.bool,

...withLocalizePropTypes,
};

const defaultProps = {
isSingleTransactionView: false,
};

const MoneyRequestHeader = (props) => {
const formattedAmount = props.numberFormat(props.report.total / 100, {
style: 'currency',
currency: props.report.currency,
});
const isSettled = /* ReportUtils.isSettled(props.report.reportID); */ false;
const isExpenseReport = ReportUtils.isExpenseReport(props.report);
const payeeName = isExpenseReport
? ReportUtils.getPolicyName(props.report, props.policies)
: ReportUtils.getDisplayNameForParticipant(props.report.managerEmail);
const payeeAvatar = isExpenseReport
? ReportUtils.getWorkspaceAvatar(props.report)
: ReportUtils.getAvatar(lodashGet(props.personalDetails, [props.report.managerEmail, 'avatar']), props.personalDetails);
return (
<View style={[
{backgroundColor: themeColors.highlightBG},
styles.pl0,
]}
>
<HeaderWithCloseButton
shouldShowAvatarWithDisplay
shouldShowThreeDotsButton={!isSettled}
threeDotsMenuItems={[{
icon: Expensicons.Trashcan,
text: props.translate('common.delete'),
onSelected: () => {},
}]}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton}
report={props.report}
policies={props.policies}
personalDetails={props.personalDetails}
containerStyles={[styles.pt5, styles.pb3]}
shouldShowCloseButton={false}
shouldShowBackButton={props.isSmallScreenWidth}
onBackButtonPress={() => Navigation.navigate(ROUTES.HOME)}
/>
<View style={[styles.ph5, styles.pb5]}>
<Text style={[styles.textLabelSupporting, styles.lh16]}>To</Text>
<View style={[
styles.flex1,
styles.flexRow,
styles.alignItemsCenter,
styles.justifyContentBetween,
styles.pv3,
]}
>
<View style={[
styles.flexRow,
styles.alignItemsCenter,
styles.justifyContentBetween,
]}
>
<Avatar
source={payeeAvatar}
type={isExpenseReport ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR}
name={payeeName}
size={CONST.AVATAR_SIZE.HEADER}
/>
<View style={[styles.flexColumn, styles.ml3]}>
<Text
style={[styles.headerText, styles.pre]}
numberOfLines={1}
>
{payeeName}
</Text>
{isExpenseReport && (
<Text
style={[styles.textLabelSupporting, styles.lh16, styles.pre]}
numberOfLines={1}
>
{props.translate('workspace.common.workspace')}
</Text>
)}
</View>
</View>
<View style={[styles.flexRow]}>
{!props.isSingleTransactionView && (
<Text style={[styles.newKansasLarge]}>{formattedAmount}</Text>
)}
{isSettled && (
<View style={styles.moneyRequestHeaderCheckmark}>
<Icon src={Expensicons.Checkmark} fill={themeColors.iconSuccessFill} />
</View>
)}
</View>
Copy link
Member

Choose a reason for hiding this comment

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

Can we use MenuItem instead of this?

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 think we have bigger plans for this component so I think we wanted the flexibility. cc @mountiny and @luacmartins who had a bigger hand in the high level design of this.

</View>
</View>
</View>
);
};

MoneyRequestHeader.displayName = 'MoneyRequestHeader';
MoneyRequestHeader.propTypes = propTypes;
MoneyRequestHeader.defaultProps = defaultProps;

export default compose(
withWindowDimensions,
withLocalize,
)(MoneyRequestHeader);
Loading