Skip to content

Commit

Permalink
Merge pull request #18148 from Expensify/yuwen-moneyRequestHeader
Browse files Browse the repository at this point in the history
[No QA] Create Money Request Header component
  • Loading branch information
bondydaa authored May 4, 2023
2 parents 75d03a4 + eadd538 commit 5f649d5
Show file tree
Hide file tree
Showing 8 changed files with 357 additions and 29 deletions.
1 change: 1 addition & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -962,6 +962,7 @@ const CONST = {
SMALL_SUBSCRIPT: 'small-subscript',
MID_SUBSCRIPT: 'mid-subscript',
LARGE_BORDERED: 'large-bordered',
HEADER: 'header',
},
OPTION_MODE: {
COMPACT: 'compact',
Expand Down
117 changes: 117 additions & 0 deletions src/components/AvatarWithDisplayName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
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.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}
/>
{!_.isEmpty(subtitle) && (
<Text
style={[
styles.sidebarLinkText,
styles.optionAlternateText,
styles.textLabelSupporting,
styles.pre,
]}
numberOfLines={1}
>
{subtitle}
</Text>
)}
</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),

...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.flex1]}>
{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
142 changes: 142 additions & 0 deletions src/components/MoneyRequestHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
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 = false; // TODO: use ReportUtils.isSettled(props.report.reportID) once that method is added
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, styles.pr1]}
shouldShowCloseButton={false}
shouldShowBackButton={props.isSmallScreenWidth}
onBackButtonPress={() => Navigation.navigate(ROUTES.HOME)}
/>
<View style={[styles.ph5, styles.pb5]}>
<Text style={[styles.textLabelSupporting, styles.lh16]}>{props.translate('common.to')}</Text>
<View style={[
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>
</View>
</View>
</View>
);
};

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

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

0 comments on commit 5f649d5

Please sign in to comment.