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

Refactor of WorkspaceReimburseView to use new API #10692

Merged
merged 10 commits into from
Sep 1, 2022
4 changes: 4 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,10 @@ const CONST = {
ROOM_PREFIX: '#',
},

CUSTOM_UNITS: {
NAME_DISTANCE: 'Distance',
},

TERMS: {
CFPB_PREPAID: 'cfpb.gov/prepaid',
CFPB_COMPLAINT: 'cfpb.gov/complaint',
Expand Down
5 changes: 5 additions & 0 deletions src/libs/actions/Policy.js
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,10 @@ function generatePolicyID() {
return _.times(16, () => Math.floor(Math.random() * 16).toString(16)).join('').toUpperCase();
}

function openWorkspaceReimburseView(policyID) {
API.read('OpenWorkspaceReimburseView', {policyID});
}

export {
getPolicyList,
loadFullPolicy,
Expand All @@ -829,6 +833,7 @@ export {
subscribeToPolicyEvents,
clearDeleteMemberError,
clearAddMemberError,
openWorkspaceReimburseView,
generateDefaultWorkspaceName,
updateGeneralSettings,
clearWorkspaceGeneralSettingsErrors,
Expand Down
235 changes: 129 additions & 106 deletions src/pages/workspace/reimburse/WorkspaceReimburseView.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ import * as Policy from '../../../libs/actions/Policy';
import withFullPolicy from '../withFullPolicy';
import CONST from '../../../CONST';
import Button from '../../../components/Button';
import {withNetwork} from '../../../components/OnyxProvider';
import FullPageNotFoundView from '../../../components/BlockingViews/FullPageNotFoundView';
import OfflineWithFeedback from '../../../components/OfflineWithFeedback';
import * as ReimbursementAccount from '../../../libs/actions/ReimbursementAccount';
import networkPropTypes from '../../../components/networkPropTypes';

const propTypes = {
/** The policy ID currently being configured */
policyID: PropTypes.string.isRequired,

/** Does the user have a VBA in their account? */
hasVBA: PropTypes.bool.isRequired,

/** Policy values needed in the component */
policy: PropTypes.shape({
customUnits: PropTypes.objectOf(
Expand All @@ -49,8 +49,12 @@ const propTypes = {
}),
),
outputCurrency: PropTypes.string,
hasVBA: PropTypes.bool,
}).isRequired,

/** Information about the network */
network: networkPropTypes.isRequired,

...withLocalizePropTypes,
};

Expand Down Expand Up @@ -84,6 +88,35 @@ class WorkspaceReimburseView extends React.Component {
this.updateRateValueDebounced = _.debounce(this.updateRateValue.bind(this), 1000);
}

componentDidMount() {
Policy.openWorkspaceReimburseView(this.props.policyID);
}

componentDidUpdate(prevProps) {
if (prevProps.policy.customUnits !== this.props.policy.customUnits) {
const distanceCustomUnit = _.chain(lodashGet(this.props, 'policy.customUnits', []))
.values()
.findWhere({name: CONST.CUSTOM_UNITS.NAME_DISTANCE})
.value();

this.setState({
unitID: lodashGet(distanceCustomUnit, 'customUnitID', ''),
unitName: lodashGet(distanceCustomUnit, 'name', ''),
unitValue: lodashGet(distanceCustomUnit, 'attributes.unit', 'mi'),
rateID: lodashGet(distanceCustomUnit, 'rates[0].customUnitRateID', ''),
rateName: lodashGet(distanceCustomUnit, 'rates[0].name', ''),
rateValue: this.getRateDisplayValue(lodashGet(distanceCustomUnit, 'rates[0].rate', 0) / 100),
});
}
Comment on lines +96 to +110
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB: Why don't we use the props directly? I think it will be a bit hard to keep props and state in sync if we add more props in the future, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'm trying to have as little impact on this aspect of the page as possible and simply support the new API command. It's been worked on by a number of contributors based on what I've seen. I agree it could use some improvement in how it's written but I feel like that would be the domain of a follow-up issue.


const reconnecting = prevProps.network.isOffline && !this.props.network.isOffline;
if (!reconnecting) {
return;
}

Policy.openWorkspaceReimburseView(this.props.policyID);
}

getRateDisplayValue(value) {
const numValue = parseFloat(value);
if (Number.isNaN(numValue)) {
Expand All @@ -104,19 +137,6 @@ class WorkspaceReimburseView extends React.Component {
});
}

static getDerivedStateFromProps(props, state) {
const distanceCustomUnit = _.find(lodashGet(props, 'policy.customUnits', {}), unit => unit.name === 'Distance');
const unitValue = lodashGet(distanceCustomUnit, 'attributes.unit', 'mi');

if (unitValue !== state.unitValue) {
return {
unitValue,
};
}

return null;
}

setUnit(value) {
if (value === this.state.unitValue) {
return;
Expand Down Expand Up @@ -160,107 +180,109 @@ class WorkspaceReimburseView extends React.Component {
render() {
return (
<>
<Section
title={this.props.translate('workspace.reimburse.captureReceipts')}
icon={Illustrations.ReceiptYellow}
menuItems={[
{
title: this.props.translate('workspace.reimburse.viewAllReceipts'),
onPress: () => Link.openOldDotLink(`expenses?policyIDList=${this.props.policyID}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`),
icon: Expensicons.Receipt,
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
},
]}
>
<View style={[styles.mv4, styles.flexRow, styles.flexWrap]}>
<Text>
{this.props.translate('workspace.reimburse.captureNoVBACopyBeforeEmail')}
<CopyTextToClipboard
text="receipts@expensify.com"
textStyles={[styles.textBlue]}
/>
<Text>{this.props.translate('workspace.reimburse.captureNoVBACopyAfterEmail')}</Text>
</Text>
</View>
</Section>

<Section
title={this.props.translate('workspace.reimburse.trackDistance')}
icon={Illustrations.GpsTrackOrange}
>
<View style={[styles.mv4]}>
<Text>{this.props.translate('workspace.reimburse.trackDistanceCopy')}</Text>
</View>
<OfflineWithFeedback
errors={lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'errors'])}
pendingAction={lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'pendingAction'])}
onClose={() => Policy.removeUnitError(this.props.policyID, this.state.unitID)}
>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mv2]}>
<View style={[styles.rateCol]}>
<TextInput
label={this.props.translate('workspace.reimburse.trackDistanceRate')}
placeholder={this.state.outputCurrency}
onChangeText={value => this.setRate(value)}
value={this.state.rateValue}
autoCompleteType="off"
autoCorrect={false}
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}
onKeyPress={this.debounceUpdateOnCursorMove}
/>
</View>
<View style={[styles.unitCol]}>
<Picker
label={this.props.translate('workspace.reimburse.trackDistanceUnit')}
items={this.unitItems}
value={this.state.unitValue}
onInputChange={value => this.setUnit(value)}
/>
</View>
</View>
</OfflineWithFeedback>
</Section>

{!this.props.hasVBA && (
<FullPageNotFoundView shouldShow={_.isEmpty(this.props.policy)}>
<Section
title={this.props.translate('workspace.reimburse.unlockNextDayReimbursements')}
icon={Illustrations.JewelBoxGreen}
>
<View style={[styles.mv4]}>
<Text>{this.props.translate('workspace.reimburse.unlockNoVBACopy')}</Text>
</View>
<Button
text={this.props.translate('workspace.common.bankAccount')}
onPress={() => ReimbursementAccount.navigateToBankAccountRoute(this.props.policyID)}
icon={Expensicons.Bank}
style={[styles.mt4]}
iconStyles={[styles.buttonCTAIcon]}
shouldShowRightIcon
large
success
/>
</Section>
)}
{this.props.hasVBA && (
<Section
title={this.props.translate('workspace.reimburse.fastReimbursementsHappyMembers')}
icon={Illustrations.BankUserGreen}
title={this.props.translate('workspace.reimburse.captureReceipts')}
icon={Illustrations.ReceiptYellow}
menuItems={[
{
title: this.props.translate('workspace.reimburse.reimburseReceipts'),
onPress: () => Link.openOldDotLink(`reports?policyID=${this.props.policyID}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`),
icon: Expensicons.Bank,
title: this.props.translate('workspace.reimburse.viewAllReceipts'),
onPress: () => Link.openOldDotLink(`expenses?policyIDList=${this.props.policyID}&billableReimbursable=reimbursable&submitterEmail=%2B%2B`),
icon: Expensicons.Receipt,
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
},
]}
>
<View style={[styles.mv4, styles.flexRow, styles.flexWrap]}>
<Text>
{this.props.translate('workspace.reimburse.captureNoVBACopyBeforeEmail')}
<CopyTextToClipboard
text="receipts@expensify.com"
textStyles={[styles.textBlue]}
/>
<Text>{this.props.translate('workspace.reimburse.captureNoVBACopyAfterEmail')}</Text>
</Text>
</View>
</Section>

<Section
title={this.props.translate('workspace.reimburse.trackDistance')}
icon={Illustrations.GpsTrackOrange}
>
<View style={[styles.mv4]}>
<Text>{this.props.translate('workspace.reimburse.fastReimbursementsVBACopy')}</Text>
<Text>{this.props.translate('workspace.reimburse.trackDistanceCopy')}</Text>
</View>
<OfflineWithFeedback
errors={lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'errors'])}
pendingAction={lodashGet(this.props, ['policy', 'customUnits', this.state.unitID, 'pendingAction'])}
onClose={() => Policy.removeUnitError(this.props.policyID, this.state.unitID)}
>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.mv2]}>
<View style={[styles.rateCol]}>
<TextInput
label={this.props.translate('workspace.reimburse.trackDistanceRate')}
placeholder={this.state.outputCurrency}
onChangeText={value => this.setRate(value)}
value={this.state.rateValue}
autoCompleteType="off"
autoCorrect={false}
keyboardType={CONST.KEYBOARD_TYPE.DECIMAL_PAD}
onKeyPress={this.debounceUpdateOnCursorMove}
/>
</View>
<View style={[styles.unitCol]}>
<Picker
label={this.props.translate('workspace.reimburse.trackDistanceUnit')}
items={this.unitItems}
value={this.state.unitValue}
onInputChange={value => this.setUnit(value)}
/>
</View>
</View>
</OfflineWithFeedback>
</Section>
)}

{!this.props.hasVBA && (
<Section
title={this.props.translate('workspace.reimburse.unlockNextDayReimbursements')}
icon={Illustrations.JewelBoxGreen}
>
<View style={[styles.mv4]}>
<Text>{this.props.translate('workspace.reimburse.unlockNoVBACopy')}</Text>
</View>
<Button
text={this.props.translate('workspace.common.bankAccount')}
onPress={() => ReimbursementAccount.navigateToBankAccountRoute(this.props.policyID)}
icon={Expensicons.Bank}
style={[styles.mt4]}
iconStyles={[styles.buttonCTAIcon]}
shouldShowRightIcon
large
success
/>
</Section>
)}
{this.props.hasVBA && (
<Section
title={this.props.translate('workspace.reimburse.fastReimbursementsHappyMembers')}
icon={Illustrations.BankUserGreen}
menuItems={[
{
title: this.props.translate('workspace.reimburse.reimburseReceipts'),
onPress: () => Link.openOldDotLink(`reports?policyID=${this.props.policyID}&from=all&type=expense&showStates=Archived&isAdvancedFilterMode=true`),
icon: Expensicons.Bank,
shouldShowRightIcon: true,
iconRight: Expensicons.NewWindow,
},
]}
>
<View style={[styles.mv4]}>
<Text>{this.props.translate('workspace.reimburse.fastReimbursementsVBACopy')}</Text>
</View>
</Section>
)}
</FullPageNotFoundView>
</>
);
}
Expand All @@ -272,6 +294,7 @@ WorkspaceReimburseView.displayName = 'WorkspaceReimburseView';
export default compose(
withFullPolicy,
withLocalize,
withNetwork(),
withOnyx({
policy: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
Expand Down