From be1c434143d4f8708c1280fb02c6321a4b768702 Mon Sep 17 00:00:00 2001 From: Barun Pandey Date: Sat, 27 Mar 2021 06:22:48 +0545 Subject: [PATCH 01/15] Add amount to OptionRow and configure PropTypes --- src/components/OptionsList.js | 12 ++++- src/components/OptionsSelector.js | 50 +++++++++++++------ src/pages/home/sidebar/OptionRow.js | 20 +++++++- .../OptionRowTitle/OptionRowTitleProps.js | 2 +- src/pages/home/sidebar/optionPropTypes.js | 2 +- 5 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/components/OptionsList.js b/src/components/OptionsList.js index 62b19b6f786b..69e43196f11f 100644 --- a/src/components/OptionsList.js +++ b/src/components/OptionsList.js @@ -67,6 +67,12 @@ const propTypes = { // Toggle between compact and default view of the option optionMode: PropTypes.oneOf(['compact', 'default']), + + // amount if iouTransaction row is to be shown + amount: PropTypes.string, + + // currency for iouTransaction + currency: PropTypes.string, }; const defaultProps = { @@ -85,6 +91,8 @@ const defaultProps = { innerRef: null, showTitleTooltip: false, optionMode: undefined, + amount: '', + currency: 'USD', }; class OptionsList extends Component { @@ -154,12 +162,14 @@ class OptionsList extends Component { showTitleTooltip={this.props.showTitleTooltip} hoverStyle={this.props.optionHoveredStyle} optionIsFocused={!this.props.disableFocusOptions - && this.props.focusedIndex === (index + section.indexOffset)} + && this.props.focusedIndex === (index + section.indexOffset)} onSelectRow={this.props.onSelectRow} isSelected={Boolean(_.find(this.props.selectedOptions, option => option.login === item.login))} showSelectedState={this.props.canSelectMultipleOptions} hideAdditionalOptionStates={this.props.hideAdditionalOptionStates} forceTextUnreadStyle={this.props.forceTextUnreadStyle} + amount={this.props.amount} + currency={this.props.currency} /> ); } diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js index 8e976eab31c7..b9f30e514fbb 100644 --- a/src/components/OptionsSelector.js +++ b/src/components/OptionsSelector.js @@ -12,6 +12,15 @@ const propTypes = { // Callback to fire when a row is tapped onSelectRow: PropTypes.func, + // amount if iouTransaction row is to be shown + amount: PropTypes.string, + + // currency for iouTransaction + currency: PropTypes.string, + + // if we should show search or not? + showSearch: PropTypes.bool, + // Sections for the section list sections: PropTypes.arrayOf(PropTypes.shape({ // Title of the section @@ -28,10 +37,10 @@ const propTypes = { })).isRequired, // Value in the search input field - value: PropTypes.string.isRequired, + value: PropTypes.string, // Callback fired when text changes - onChangeText: PropTypes.func.isRequired, + onChangeText: PropTypes.func, // Optional placeholder text for the selector placeholderText: PropTypes.string, @@ -72,6 +81,11 @@ const defaultProps = { hideAdditionalOptionStates: false, forceTextUnreadStyle: false, showTitleTooltip: false, + amount: '', + currency: 'USD', + showSearch: true, + onChangeText: () => {}, + value: '', }; class OptionsSelector extends Component { @@ -87,7 +101,9 @@ class OptionsSelector extends Component { } componentDidMount() { - this.textInput.focus(); + if (this.props.showSearch) { + this.textInput.focus(); + } } /** @@ -170,18 +186,20 @@ class OptionsSelector extends Component { render() { return ( - - this.textInput = el} - style={[styles.textInput]} - value={this.props.value} - onChangeText={this.props.onChangeText} - onKeyPress={this.handleKeyPress} - placeholder={this.props.placeholderText} - placeholderTextColor={themeColors.placeholderText} - /> - + { this.props.showSearch ? ( + + this.textInput = el} + style={[styles.textInput]} + value={this.props.value} + onChangeText={this.props.onChangeText} + onKeyPress={this.handleKeyPress} + placeholder={this.props.placeholderText} + placeholderTextColor={themeColors.placeholderText} + /> + + ) : null} this.list = el} optionHoveredStyle={styles.hoveredComponentBG} @@ -196,6 +214,8 @@ class OptionsSelector extends Component { hideAdditionalOptionStates={this.props.hideAdditionalOptionStates} forceTextUnreadStyle={this.props.forceTextUnreadStyle} showTitleTooltip={this.props.showTitleTooltip} + amount={this.props.amount} + currency={this.props.currency} /> ); diff --git a/src/pages/home/sidebar/OptionRow.js b/src/pages/home/sidebar/OptionRow.js index f9dfcc8f4727..6896c505cf2e 100644 --- a/src/pages/home/sidebar/OptionRow.js +++ b/src/pages/home/sidebar/OptionRow.js @@ -28,7 +28,13 @@ const propTypes = { optionIsFocused: PropTypes.bool.isRequired, // A function that is called when an option is selected. Selected option is passed as a param - onSelectRow: PropTypes.func.isRequired, + onSelectRow: PropTypes.func, + + // amount if iouTransaction row is to be shown + amount: PropTypes.string, + + // currency for iouTransaction + currency: PropTypes.string, // A flag to indicate whether to show additional optional states, such as pin and draft icons hideAdditionalOptionStates: PropTypes.bool, @@ -57,6 +63,9 @@ const defaultProps = { forceTextUnreadStyle: false, showTitleTooltip: false, mode: 'default', + onSelectRow: null, + amount: '', + currency: 'USD', }; const OptionRow = ({ @@ -70,6 +79,8 @@ const OptionRow = ({ forceTextUnreadStyle, showTitleTooltip, mode, + amount, + currency, }) => { const textStyle = optionIsFocused ? styles.sidebarLinkActiveText @@ -155,6 +166,13 @@ const OptionRow = ({ ) : null} + {amount ? ( + + + {`${currency}${amount}`} + + + ) : null} {showSelectedState && ( {isSelected && ( diff --git a/src/pages/home/sidebar/OptionRowTitle/OptionRowTitleProps.js b/src/pages/home/sidebar/OptionRowTitle/OptionRowTitleProps.js index 3bc7e93d15a2..ea1877c9cb8d 100644 --- a/src/pages/home/sidebar/OptionRowTitle/OptionRowTitleProps.js +++ b/src/pages/home/sidebar/OptionRowTitle/OptionRowTitleProps.js @@ -18,7 +18,7 @@ const propTypes = { text: PropTypes.string.isRequired, // List of particiapants of the report - participantsList: PropTypes.arrayOf(participantPropTypes).isRequired, + participantsList: PropTypes.arrayOf(participantPropTypes), // Text to show for tooltip tooltipText: PropTypes.string, diff --git a/src/pages/home/sidebar/optionPropTypes.js b/src/pages/home/sidebar/optionPropTypes.js index ec2881f790b5..fe39be125ab0 100644 --- a/src/pages/home/sidebar/optionPropTypes.js +++ b/src/pages/home/sidebar/optionPropTypes.js @@ -19,7 +19,7 @@ const optionPropTypes = PropTypes.shape({ alternateText: PropTypes.string.isRequired, // List of participants of the report - participantsList: PropTypes.arrayOf(participantPropTypes).isRequired, + participantsList: PropTypes.arrayOf(participantPropTypes), // The array URLs of the person's avatar icon: PropTypes.arrayOf(PropTypes.string), From d9c03583b78bf00745aee73c1aee3c9feac92c66 Mon Sep 17 00:00:00 2001 From: Barun Pandey Date: Sat, 27 Mar 2021 06:23:38 +0545 Subject: [PATCH 02/15] Add API.js calls and actions for IOU --- src/libs/API.js | 31 ++++++++++++++ src/libs/OptionsListUtils.js | 17 ++++++++ src/libs/actions/IOU.js | 82 +++++++++++++++++++++++++++++++++++- src/libs/actions/Report.js | 4 +- 4 files changed, 131 insertions(+), 3 deletions(-) diff --git a/src/libs/API.js b/src/libs/API.js index e6156fca7b7d..c9694f40268c 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -631,6 +631,35 @@ function GetIOUReport(parameters) { return Network.post(commandName, parameters); } +/** + * @param {Object} parameters + * @param {String} parameters.comment + * @param {Array} parameters.debtorEmail + * @param {String} parameters.currency + * @param {Number} parameters.amount + * @returns {Promise} + */ +function CreateIOUTransaction(parameters) { + const commandName = 'CreateIOUTransaction'; + requireParameters(['comment', 'debtorEmail', 'currency', 'amount'], parameters, commandName); + return Network.post(commandName, parameters); +} + +/** + * @param {Object} parameters + * @param {String} parameters.splits + * @param {String} parameters.currency + * @param {String} parameters.reportID + * @param {Number} parameters.amount + * @param {String} parameters.comment + * @returns {Promise} + */ +function CreateIOUSplit(parameters) { + const commandName = 'CreateIOUSplit'; + requireParameters(['splits', 'currency', 'amount', 'reportID'], parameters, commandName); + return Network.post(commandName, parameters); +} + export { getAuthToken, Authenticate, @@ -661,4 +690,6 @@ export { User_SecondaryLogin_Send, User_UploadAvatar, reauthenticate, + CreateIOUTransaction, + CreateIOUSplit, }; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 143203a45ba2..b030a7bb2131 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -347,6 +347,22 @@ function getNewChatOptions( }); } +/** + * Build the options for the New Chat view + * + * @param {Object} participant + * @returns {Array} + */ +function getDisplayOptionFromParticipant( + participant, +) { + return { + text: participant.displayName, + alternateText: participant.login, + icons: [participant.avatar], + }; +} + /** * Build the options for the New Group view * @@ -429,4 +445,5 @@ export { getSidebarOptions, getHeaderMessage, getPersonalDetailsForLogins, + getDisplayOptionFromParticipant, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 9226c90479ff..b3210b696185 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1,5 +1,7 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; +import * as API from '../API'; +import {getSimplifiedIOUReport} from './Report'; /** * Retrieve the users preferred currency @@ -13,7 +15,83 @@ function getPreferredCurrency() { }, 1600); } -// Re-enable the prefer-default-export lint when additional functions are added +/** + * Creates IOUTransaction for IOURequest + */ +function createIOUTransaction({ + comment, amount, currency, debtorEmail, +}) { + Onyx.merge(ONYXKEYS.IOU, {loading: true}); + let iouReportID = ''; + API.CreateIOUTransaction({ + comment, + amount, + currency, + debtorEmail, + }).then((data) => { + iouReportID = data.reportID; + return iouReportID; + }).then(reportID => API.Get({ + returnValueList: 'reportStuff', + reportIDList: reportID, + shouldLoadOptionalKeys: true, + includePinnedReports: true, + })).then((response) => { + if (response.jsonCode !== 200) { + throw new Error(response.message); + } + + const iouReportData = response.reports[iouReportID]; + if (!iouReportData) { + throw new Error(`No iouReportData found for reportID ${iouReportID}`); + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, + getSimplifiedIOUReport(iouReportData)); + + Onyx.merge(ONYXKEYS.IOU, {loading: false}); + }) + .catch((error) => { + console.debug(`[Report] Failed to populate IOU Collection: ${error.message}`); + }); +} + +/** + * Creates IOUSplit Transaction + * @param {Object} parameters + * @param {Array} parameters.participants + * @param {Array} parameters.splits + * @param {String} parameters.comment + * @param {String} parameters.amount + * @param {String} parameters.currency + */ +function createIOUSplit({ + participants, + comment, + amount, + currency, + splits, +}) { + API.CreateChatReport({ + emailList: participants.join(','), + }) + .then(data => data.reportID) + .then(reportID => API.CreateIOUSplit({ + splits: JSON.stringify(splits), + currency, + amount, + comment, + reportID, + })).then((res) => { + console.debug('Response from CreateIOUSplit', res); + }) + .catch((error) => { + console.debug(`Error: ${error.message}`); + }); +} + export { - getPreferredCurrency, // eslint-disable-line import/prefer-default-export + getPreferredCurrency, + createIOUTransaction, + createIOUSplit, }; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index c5d4c4586512..75aaf91b9cf7 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -583,11 +583,12 @@ function fetchOrCreateChatReport(participants) { if (participants.length < 2) { throw new Error('fetchOrCreateChatReport() must have at least two participants'); } - + console.debug('this is working?', participants.join(',')); API.CreateChatReport({ emailList: participants.join(','), }) .then((data) => { + console.debug('debug::::: data', data.reportID); if (data.jsonCode !== 200) { throw new Error(data.message); } @@ -908,4 +909,5 @@ export { broadcastUserIsTyping, togglePinnedState, updateCurrentlyViewedReportID, + getSimplifiedIOUReport, }; From 15f9b08595a975a779d84d298b7ae32830aa2e68 Mon Sep 17 00:00:00 2001 From: Barun Pandey Date: Sat, 27 Mar 2021 06:47:04 +0545 Subject: [PATCH 03/15] Add Confirmation views based on hasMultipleParticipants and clean up OptionRow logic --- src/components/OptionsList.js | 12 +- src/components/OptionsSelector.js | 10 - src/libs/API.js | 4 +- src/libs/OptionsListUtils.js | 37 ++- src/libs/actions/IOU.js | 56 +++-- src/libs/actions/Report.js | 3 +- src/pages/home/sidebar/OptionRow.js | 18 +- src/pages/home/sidebar/optionPropTypes.js | 3 + src/pages/iou/IOUModal.js | 84 ++++++- src/pages/iou/steps/IOUConfirmPage.js | 47 ---- .../steps/IOUConfirmPage/IOUConfirmRequest.js | 150 +++++++++++++ .../steps/IOUConfirmPage/IOUConfirmSplit.js | 211 ++++++++++++++++++ src/pages/iou/steps/IOUConfirmPage/index.js | 86 +++++++ 13 files changed, 599 insertions(+), 122 deletions(-) delete mode 100644 src/pages/iou/steps/IOUConfirmPage.js create mode 100644 src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js create mode 100644 src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js create mode 100644 src/pages/iou/steps/IOUConfirmPage/index.js diff --git a/src/components/OptionsList.js b/src/components/OptionsList.js index 69e43196f11f..62b19b6f786b 100644 --- a/src/components/OptionsList.js +++ b/src/components/OptionsList.js @@ -67,12 +67,6 @@ const propTypes = { // Toggle between compact and default view of the option optionMode: PropTypes.oneOf(['compact', 'default']), - - // amount if iouTransaction row is to be shown - amount: PropTypes.string, - - // currency for iouTransaction - currency: PropTypes.string, }; const defaultProps = { @@ -91,8 +85,6 @@ const defaultProps = { innerRef: null, showTitleTooltip: false, optionMode: undefined, - amount: '', - currency: 'USD', }; class OptionsList extends Component { @@ -162,14 +154,12 @@ class OptionsList extends Component { showTitleTooltip={this.props.showTitleTooltip} hoverStyle={this.props.optionHoveredStyle} optionIsFocused={!this.props.disableFocusOptions - && this.props.focusedIndex === (index + section.indexOffset)} + && this.props.focusedIndex === (index + section.indexOffset)} onSelectRow={this.props.onSelectRow} isSelected={Boolean(_.find(this.props.selectedOptions, option => option.login === item.login))} showSelectedState={this.props.canSelectMultipleOptions} hideAdditionalOptionStates={this.props.hideAdditionalOptionStates} forceTextUnreadStyle={this.props.forceTextUnreadStyle} - amount={this.props.amount} - currency={this.props.currency} /> ); } diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js index b9f30e514fbb..5bb54afefa10 100644 --- a/src/components/OptionsSelector.js +++ b/src/components/OptionsSelector.js @@ -12,12 +12,6 @@ const propTypes = { // Callback to fire when a row is tapped onSelectRow: PropTypes.func, - // amount if iouTransaction row is to be shown - amount: PropTypes.string, - - // currency for iouTransaction - currency: PropTypes.string, - // if we should show search or not? showSearch: PropTypes.bool, @@ -81,8 +75,6 @@ const defaultProps = { hideAdditionalOptionStates: false, forceTextUnreadStyle: false, showTitleTooltip: false, - amount: '', - currency: 'USD', showSearch: true, onChangeText: () => {}, value: '', @@ -214,8 +206,6 @@ class OptionsSelector extends Component { hideAdditionalOptionStates={this.props.hideAdditionalOptionStates} forceTextUnreadStyle={this.props.forceTextUnreadStyle} showTitleTooltip={this.props.showTitleTooltip} - amount={this.props.amount} - currency={this.props.currency} /> ); diff --git a/src/libs/API.js b/src/libs/API.js index c9694f40268c..3903e64a8ef8 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -636,7 +636,7 @@ function GetIOUReport(parameters) { * @param {String} parameters.comment * @param {Array} parameters.debtorEmail * @param {String} parameters.currency - * @param {Number} parameters.amount + * @param {String} parameters.amount * @returns {Promise} */ function CreateIOUTransaction(parameters) { @@ -650,7 +650,7 @@ function CreateIOUTransaction(parameters) { * @param {String} parameters.splits * @param {String} parameters.currency * @param {String} parameters.reportID - * @param {Number} parameters.amount + * @param {String} parameters.amount * @param {String} parameters.comment * @returns {Promise} */ diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index b030a7bb2131..e5efff289d18 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -350,17 +350,35 @@ function getNewChatOptions( /** * Build the options for the New Chat view * - * @param {Object} participant + * @param {Object} myPersonalDetail + * @param {String} amountText * @returns {Array} */ -function getDisplayOptionFromParticipant( - participant, +function getDisplayOptionFromMyPersonalDetail( + myPersonalDetail, + amountText, ) { - return { - text: participant.displayName, - alternateText: participant.login, - icons: [participant.avatar], - }; + return [{ + text: myPersonalDetail.displayName, + alternateText: myPersonalDetail.login, + icons: [myPersonalDetail.avatar], + descriptiveText: amountText, + }]; +} + +/** + * Build the options for the New Chat view + * + * @param {Array} participants + * @param {String} amountText + * @returns {Array} + */ +function getDisplayOptionsFromParticipants( + participants, amountText, +) { + return participants.map(participant => ({ + ...participant, descriptiveText: amountText, + })); } /** @@ -445,5 +463,6 @@ export { getSidebarOptions, getHeaderMessage, getPersonalDetailsForLogins, - getDisplayOptionFromParticipant, + getDisplayOptionFromMyPersonalDetail, + getDisplayOptionsFromParticipants, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index b3210b696185..f107b4220c25 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -16,7 +16,12 @@ function getPreferredCurrency() { } /** - * Creates IOUTransaction for IOURequest + * Creates IOUSplit Transaction + * @param {Object} parameters + * @param {String} parameters.amount + * @param {String} parameters.comment + * @param {String} parameters.currency + * @param {String} parameters.debtorEmail */ function createIOUTransaction({ comment, amount, currency, debtorEmail, @@ -28,29 +33,32 @@ function createIOUTransaction({ amount, currency, debtorEmail, - }).then((data) => { - iouReportID = data.reportID; - return iouReportID; - }).then(reportID => API.Get({ - returnValueList: 'reportStuff', - reportIDList: reportID, - shouldLoadOptionalKeys: true, - includePinnedReports: true, - })).then((response) => { - if (response.jsonCode !== 200) { - throw new Error(response.message); - } + }) + .then((data) => { + iouReportID = data.reportID; + return iouReportID; + }) + .then(reportID => API.Get({ + returnValueList: 'reportStuff', + reportIDList: reportID, + shouldLoadOptionalKeys: true, + includePinnedReports: true, + })) + .then((response) => { + if (response.jsonCode !== 200) { + throw new Error(response.message); + } - const iouReportData = response.reports[iouReportID]; - if (!iouReportData) { - throw new Error(`No iouReportData found for reportID ${iouReportID}`); - } + const iouReportData = response.reports[iouReportID]; + if (!iouReportData) { + throw new Error(`No iouReportData found for reportID ${iouReportID}`); + } - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, - getSimplifiedIOUReport(iouReportData)); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, + getSimplifiedIOUReport(iouReportData)); - Onyx.merge(ONYXKEYS.IOU, {loading: false}); - }) + Onyx.merge(ONYXKEYS.IOU, {loading: false}); + }) .catch((error) => { console.debug(`[Report] Failed to populate IOU Collection: ${error.message}`); }); @@ -72,6 +80,7 @@ function createIOUSplit({ currency, splits, }) { + Onyx.merge(ONYXKEYS.IOU, {loading: true}); API.CreateChatReport({ emailList: participants.join(','), }) @@ -82,11 +91,14 @@ function createIOUSplit({ amount, comment, reportID, - })).then((res) => { + })) + .then((res) => { console.debug('Response from CreateIOUSplit', res); + Onyx.merge(ONYXKEYS.IOU, {loading: false}); // placeholder }) .catch((error) => { console.debug(`Error: ${error.message}`); + Onyx.merge(ONYXKEYS.IOU, {loading: false}); }); } diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 75aaf91b9cf7..6cc8b3b3de28 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -583,12 +583,11 @@ function fetchOrCreateChatReport(participants) { if (participants.length < 2) { throw new Error('fetchOrCreateChatReport() must have at least two participants'); } - console.debug('this is working?', participants.join(',')); + API.CreateChatReport({ emailList: participants.join(','), }) .then((data) => { - console.debug('debug::::: data', data.reportID); if (data.jsonCode !== 200) { throw new Error(data.message); } diff --git a/src/pages/home/sidebar/OptionRow.js b/src/pages/home/sidebar/OptionRow.js index 6896c505cf2e..568ec4575831 100644 --- a/src/pages/home/sidebar/OptionRow.js +++ b/src/pages/home/sidebar/OptionRow.js @@ -30,12 +30,6 @@ const propTypes = { // A function that is called when an option is selected. Selected option is passed as a param onSelectRow: PropTypes.func, - // amount if iouTransaction row is to be shown - amount: PropTypes.string, - - // currency for iouTransaction - currency: PropTypes.string, - // A flag to indicate whether to show additional optional states, such as pin and draft icons hideAdditionalOptionStates: PropTypes.bool, @@ -64,8 +58,6 @@ const defaultProps = { showTitleTooltip: false, mode: 'default', onSelectRow: null, - amount: '', - currency: 'USD', }; const OptionRow = ({ @@ -79,8 +71,6 @@ const OptionRow = ({ forceTextUnreadStyle, showTitleTooltip, mode, - amount, - currency, }) => { const textStyle = optionIsFocused ? styles.sidebarLinkActiveText @@ -166,10 +156,10 @@ const OptionRow = ({ ) : null} - {amount ? ( + {option.descriptiveText ? ( - {`${currency}${amount}`} + {option.descriptiveText} ) : null} @@ -228,6 +218,10 @@ export default memo(OptionRow, (prevProps, nextProps) => { return false; } + if (prevProps.option.descriptiveText !== nextProps.option.descriptiveText) { + return false; + } + if (prevProps.option.hasDraftComment !== nextProps.option.hasDraftComment) { return false; } diff --git a/src/pages/home/sidebar/optionPropTypes.js b/src/pages/home/sidebar/optionPropTypes.js index fe39be125ab0..b2c9ee97aa73 100644 --- a/src/pages/home/sidebar/optionPropTypes.js +++ b/src/pages/home/sidebar/optionPropTypes.js @@ -24,6 +24,9 @@ const optionPropTypes = PropTypes.shape({ // The array URLs of the person's avatar icon: PropTypes.arrayOf(PropTypes.string), + // Descriptive text to be displayed besides selection element + descriptiveText: PropTypes.string, + // The type of option we have e.g. user or report type: PropTypes.string, diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 593addfda470..c9631f44509b 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -1,15 +1,18 @@ import React, {Component} from 'react'; import {View, TouchableOpacity} from 'react-native'; import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import _ from 'lodash'; import IOUAmountPage from './steps/IOUAmountPage'; import IOUParticipantsPage from './steps/IOUParticipantsPage'; import IOUConfirmPage from './steps/IOUConfirmPage'; import Header from '../../components/Header'; import styles from '../../styles/styles'; import Icon from '../../components/Icon'; -import {getPreferredCurrency} from '../../libs/actions/IOU'; +import {createIOUSplit, createIOUTransaction, getPreferredCurrency} from '../../libs/actions/IOU'; import {Close, BackArrow} from '../../components/Icon/Expensicons'; import Navigation from '../../libs/Navigation/Navigation'; +import ONYXKEYS from '../../ONYXKEYS'; /** * IOU modal for requesting money and splitting bills. @@ -17,6 +20,17 @@ import Navigation from '../../libs/Navigation/Navigation'; const propTypes = { // Is this new IOU for a single request or group bill split? hasMultipleParticipants: PropTypes.bool, + + /* Onyx Props */ + iousReport: PropTypes.objectOf(PropTypes.shape({ + currency: PropTypes.string, + managerEmail: PropTypes.string, + ownerEmail: PropTypes.string, + reportID: PropTypes.number, + transactions: PropTypes.arrayOf(PropTypes.shape({ + transactionID: PropTypes.string, + })), + })).isRequired, }; const defaultProps = { @@ -41,14 +55,17 @@ class IOUModal extends Component { this.navigateToNextStep = this.navigateToNextStep.bind(this); this.updateAmount = this.updateAmount.bind(this); this.currencySelected = this.currencySelected.bind(this); - + this.createTransaction = this.createTransaction.bind(this); + this.updateComment = this.updateComment.bind(this); this.addParticipants = this.addParticipants.bind(this); + this.state = { currentStepIndex: 0, participants: [], amount: '', selectedCurrency: 'USD', isAmountPageNextButtonDisabled: true, + comment: '', }; } @@ -56,6 +73,14 @@ class IOUModal extends Component { getPreferredCurrency(); } + componentDidUpdate(prevProps) { + // Dismiss modal when previous iousReport changes from the new iousReport + // This should occur the data changes from within the modal activity + if (!_.isEqual(prevProps.iousReport, this.props.iousReport)) { + return Navigation.dismissModal(); + } + } + /** * Retrieve title for current step, based upon current step and type of IOU * @@ -63,13 +88,14 @@ class IOUModal extends Component { */ getTitleForStep() { - if (this.state.currentStepIndex === 1) { + const currentStepIndex = this.state.currentStepIndex; + if (currentStepIndex === 1 || currentStepIndex === 2) { return `${this.props.hasMultipleParticipants ? 'Split' : 'Request'} $${this.state.amount}`; } - if (steps[this.state.currentStepIndex] === Steps.IOUAmount) { + if (steps[currentStepIndex] === Steps.IOUAmount) { return this.props.hasMultipleParticipants ? 'Split Bill' : 'Request Money'; } - return steps[this.state.currentStepIndex] || ''; + return steps[currentStepIndex] || ''; } /** @@ -102,6 +128,17 @@ class IOUModal extends Component { }); } + /** + * Update comment whenever user enters any new text + * + * @param {String} comment + */ + updateComment(comment) { + this.setState({ + comment, + }); + } + /** * Update amount with number or Backspace pressed. * Validate new amount with decimal number regex up to 6 digits and 2 decimal digit @@ -138,6 +175,28 @@ class IOUModal extends Component { this.setState({selectedCurrency}); } + closeModal() { + Navigation.dismissModal(); + } + + createTransaction({debtorEmail, splits, participants}) { + if (debtorEmail) { + return createIOUTransaction({ + comment: this.state.comment, + amount: this.state.amount, + currency: this.state.selectedCurrency, + debtorEmail, + }); + } + return createIOUSplit({ + comment: this.state.comment, + amount: this.state.amount, + currency: this.state.selectedCurrency, + splits, + participants, + }); + } + render() { const currentStep = steps[this.state.currentStepIndex]; return ( @@ -192,9 +251,13 @@ class IOUModal extends Component { )} {currentStep === Steps.IOUConfirm && ( console.debug('create IOU report')} + onConfirm={this.createTransaction} + hasMultipleParticipants={this.props.hasMultipleParticipants} participants={this.state.participants} iouAmount={this.state.amount} + comment={this.state.comment} + selectedCurrency={this.state.selectedCurrency} + onUpdateComment={this.updateComment} /> )} @@ -206,4 +269,11 @@ IOUModal.propTypes = propTypes; IOUModal.defaultProps = defaultProps; IOUModal.displayName = 'IOUModal'; -export default IOUModal; +export default withOnyx({ + iousReport: { + key: ONYXKEYS.COLLECTION.REPORT_IOUS, + }, + reportID: { + key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID, + }, +})(IOUModal); diff --git a/src/pages/iou/steps/IOUConfirmPage.js b/src/pages/iou/steps/IOUConfirmPage.js deleted file mode 100644 index 9f34f863b5cf..000000000000 --- a/src/pages/iou/steps/IOUConfirmPage.js +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../../../ONYXKEYS'; -import styles from '../../../styles/styles'; -import ButtonWithLoader from '../../../components/ButtonWithLoader'; - -const propTypes = { - // Callback to inform parent modal of success - onConfirm: PropTypes.func.isRequired, - - // IOU amount - iouAmount: PropTypes.number.isRequired, - - /* Onyx Props */ - - // Holds data related to IOU view state, rather than the underlying IOU data. - iou: PropTypes.shape({ - - // Whether or not the IOU step is loading (creating the IOU Report) - loading: PropTypes.bool, - }), -}; - -const defaultProps = { - iou: {}, -}; - -const IOUConfirmPage = props => ( - - - -); - -IOUConfirmPage.displayName = 'IOUConfirmPage'; -IOUConfirmPage.propTypes = propTypes; -IOUConfirmPage.defaultProps = defaultProps; - -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, -})(IOUConfirmPage); diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js new file mode 100644 index 000000000000..be04343396e8 --- /dev/null +++ b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js @@ -0,0 +1,150 @@ +import React, {Component} from 'react'; +import {View, TextInput} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import styles from '../../../../styles/styles'; +import Text from '../../../../components/Text'; +import ButtonWithLoader from '../../../../components/ButtonWithLoader'; +import themeColors from '../../../../styles/themes/default'; +import OptionsSelector from '../../../../components/OptionsSelector'; +import {getDisplayOptionsFromParticipants} from '../../../../libs/OptionsListUtils'; + +const propTypes = { + // Callback to inform parent modal of success + onConfirm: PropTypes.func.isRequired, + + // IOU amount + iouAmount: PropTypes.string.isRequired, + + // callback to update comment from IOUModal + onUpdateComment: PropTypes.func, + + // Selected currency from the user + // remove eslint disable after currency symbol is available + // eslint-disable-next-line react/no-unused-prop-types + selectedCurrency: PropTypes.string.isRequired, + + // comment value from IOUModal + comment: PropTypes.string, + + // Selected participants from IOUMOdal with login + participants: PropTypes.arrayOf(PropTypes.shape({ + login: PropTypes.string.isRequired, + alternateText: PropTypes.string, + hasDraftComment: PropTypes.bool, + icons: PropTypes.arrayOf(PropTypes.string), + searchText: PropTypes.string, + text: PropTypes.string, + keyForList: PropTypes.string, + isPinned: PropTypes.bool, + isUnread: PropTypes.bool, + reportID: PropTypes.number, + participantsList: PropTypes.arrayOf(PropTypes.object), + })).isRequired, + + /* Onyx Props */ + + // The personal details of the person who is logged in + myPersonalDetails: PropTypes.shape({ + + // Display name of the current user from their personal details + displayName: PropTypes.string, + + // Avatar URL of the current user from their personal details + avatar: PropTypes.string, + }).isRequired, + + // Holds data related to IOU view state, rather than the underlying IOU data. + iou: PropTypes.shape({ + + // Whether or not the IOU step is loading (creating the IOU Report) + loading: PropTypes.bool, + }), +}; + +const defaultProps = { + iou: {}, + onUpdateComment: null, + comment: '', +}; + + +class IOUConfirmRequestPage extends Component { + /** + * Returns the sections needed for the OptionsSelector + * + * @param {Boolean} maxParticipantsReached + * @returns {Array} + */ + getSections() { + const sections = []; + + // $ should be replaced by currency symbol once available + const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, + `$${this.props.iouAmount}`); + + sections.push({ + title: 'TO', + data: formattedParticipants, + shouldShow: true, + indexOffset: 0, + }); + return sections; + } + + render() { + const sections = this.getSections(); + return ( + + + + + WHAT'S IT FOR? + + + + + + + this.props.onConfirm({ + debtorEmail: this.props.participants[0].login, + })} + /> + + + ); + } +} + +IOUConfirmRequestPage.displayName = 'IOUConfirmRequestPage'; +IOUConfirmRequestPage.propTypes = propTypes; +IOUConfirmRequestPage.defaultProps = defaultProps; + +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + myPersonalDetails: { + key: ONYXKEYS.MY_PERSONAL_DETAILS, + }, + user: { + key: ONYXKEYS.USER, + }, +})(IOUConfirmRequestPage); diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js new file mode 100644 index 000000000000..92f6e58435c3 --- /dev/null +++ b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js @@ -0,0 +1,211 @@ +import React, {Component} from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import {TextInput} from 'react-native-gesture-handler'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import styles from '../../../../styles/styles'; +import Text from '../../../../components/Text'; +import themeColors from '../../../../styles/themes/default'; +import { + getDisplayOptionFromMyPersonalDetail, + getDisplayOptionsFromParticipants, +} from '../../../../libs/OptionsListUtils'; +import ButtonWithLoader from '../../../../components/ButtonWithLoader'; +import OptionsSelector from '../../../../components/OptionsSelector'; + +const propTypes = { + // Callback to inform parent modal of success + onConfirm: PropTypes.func.isRequired, + + // callback to update comment from IOUModal + onUpdateComment: PropTypes.func, + + // comment value from IOUModal + comment: PropTypes.string, + + // IOU amount + iouAmount: PropTypes.string.isRequired, + + // Selected currency from the user + // remove eslint disable after currency symbol is available + // eslint-disable-next-line react/no-unused-prop-types + selectedCurrency: PropTypes.string.isRequired, + + // Selected participants from IOUMOdal with login + participants: PropTypes.arrayOf(PropTypes.shape({ + login: PropTypes.string.isRequired, + alternateText: PropTypes.string, + hasDraftComment: PropTypes.bool, + icons: PropTypes.arrayOf(PropTypes.string), + searchText: PropTypes.string, + text: PropTypes.string, + keyForList: PropTypes.string, + isPinned: PropTypes.bool, + isUnread: PropTypes.bool, + reportID: PropTypes.number, + participantsList: PropTypes.arrayOf(PropTypes.object), + })).isRequired, + + /* Onyx Props */ + + // The personal details of the person who is logged in + myPersonalDetails: PropTypes.shape({ + + // Display name of the current user from their personal details + displayName: PropTypes.string, + + // Avatar URL of the current user from their personal details + avatar: PropTypes.string, + + // Primary login of the user + login: PropTypes.string, + }).isRequired, + + // Holds data related to IOU view state, rather than the underlying IOU data. + iou: PropTypes.shape({ + + // Whether or not the IOU step is loading (creating the IOU Report) + loading: PropTypes.bool, + }), +}; + +const defaultProps = { + iou: {}, + onUpdateComment: null, + comment: '', +}; + +class IOUConfirmSplitPage extends Component { + /** + * Returns the sections needed for the OptionsSelector + * + * @param {Boolean} maxParticipantsReached + * @returns {Array} + */ + getSections() { + const sections = []; + + // $ should be replaced by currency symbol once available + const individualAmountText = `$${this.calculateAmount(2)}`; + const formattedMyPersonalDetails = getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails, + individualAmountText); + const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, + individualAmountText); + + sections.push({ + title: 'WHO PAID?', + data: formattedMyPersonalDetails, + shouldShow: true, + indexOffset: 0, + }); + sections.push({ + title: 'WHO WAS THERE?', + data: formattedParticipants, + shouldShow: true, + indexOffset: 0, + }); + + return sections; + } + + /** + * Gets splits for the transaction + * + * @returns {Array} + */ + getSplits() { + const amountPerPerson = parseFloat(this.calculateAmount()); + const splits = this.props.participants.map(participant => ({ + email: participant.login, + amount: amountPerPerson, + })); + splits.push({email: this.props.myPersonalDetails.login, amount: amountPerPerson}); + return splits; + } + + /** + * Gets participants list for a report + * + * @returns {Array} + */ + getParticipants() { + const participants = this.props.participants.map(participant => participant.login); + participants.push(this.props.myPersonalDetails.login); + return participants; + } + + /** + * Returns selected options with all participant logins + * @returns {Array} + */ + getAllOptionsAsSelected() { + return [...this.props.participants, + getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails)]; + } + + /** + * Calculates amount for individual + * @param {Number} precision + * @returns {Number} + */ + calculateAmount(precision) { + const calculatedAmount = (this.props.iouAmount / (this.props.participants.length + 1)); + return precision ? calculatedAmount.toFixed(precision) : calculatedAmount; + } + + render() { + const sections = this.getSections(); + return ( + + + + + WHAT'S IT FOR? + + + + + + + this.props.onConfirm({ + splits: this.getSplits(), + participants: this.getParticipants(), + })} + /> + + + ); + } +} + +IOUConfirmSplitPage.displayName = 'IOUConfirmSplitPage'; +IOUConfirmSplitPage.propTypes = propTypes; +IOUConfirmSplitPage.defaultProps = defaultProps; + +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + myPersonalDetails: { + key: ONYXKEYS.MY_PERSONAL_DETAILS, + }, +})(IOUConfirmSplitPage); diff --git a/src/pages/iou/steps/IOUConfirmPage/index.js b/src/pages/iou/steps/IOUConfirmPage/index.js new file mode 100644 index 000000000000..0905720f5ae1 --- /dev/null +++ b/src/pages/iou/steps/IOUConfirmPage/index.js @@ -0,0 +1,86 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import ONYXKEYS from '../../../../ONYXKEYS'; +import IOUConfirmRequest from './IOUConfirmRequest'; +import IOUConfirmSplit from './IOUConfirmSplit'; + +const propTypes = { + // Callback to inform parent modal of success + onConfirm: PropTypes.func.isRequired, + + // Selected currency from the user + selectedCurrency: PropTypes.string.isRequired, + + // Should we request a single or multiple participant selection from user + hasMultipleParticipants: PropTypes.bool.isRequired, + + // IOU amount + iouAmount: PropTypes.string.isRequired, + + // optional comment + comment: PropTypes.string, + + // callback to update comment + onUpdateComment: PropTypes.func, + + // Selected participants from IOUMOdal with login + participants: PropTypes.arrayOf(PropTypes.shape({ + login: PropTypes.string.isRequired, + alternateText: PropTypes.string, + hasDraftComment: PropTypes.bool, + icons: PropTypes.arrayOf(PropTypes.string), + searchText: PropTypes.string, + text: PropTypes.string, + keyForList: PropTypes.string, + isPinned: PropTypes.bool, + isUnread: PropTypes.bool, + reportID: PropTypes.number, + })).isRequired, + + /* Onyx Props */ + + // Holds data related to IOU view state, rather than the underlying IOU data. + iou: PropTypes.shape({ + + // Whether or not the IOU step is loading (creating the IOU Report) + loading: PropTypes.bool, + }), +}; + +const defaultProps = { + iou: {}, + comment: '', + onUpdateComment: null, +}; + +const IOUConfirmPage = props => (props.hasMultipleParticipants + ? ( + + ) + : ( + + ) +); + +IOUConfirmPage.displayName = 'IOUConfirmPage'; +IOUConfirmPage.propTypes = propTypes; +IOUConfirmPage.defaultProps = defaultProps; + +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, +})(IOUConfirmPage); From ee72d6eb12230b9fa18a93d7d8cfaa426b8b5978 Mon Sep 17 00:00:00 2001 From: Barun Pandey Date: Tue, 30 Mar 2021 20:50:13 +0545 Subject: [PATCH 04/15] Manage split edge case and convert $ into cent --- src/libs/actions/IOU.js | 37 ++++++++++++-- src/libs/actions/Report.js | 1 + src/pages/iou/IOUModal.js | 50 +++++++++++++------ .../steps/IOUConfirmPage/IOUConfirmSplit.js | 44 +++++++++++----- 4 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index f107b4220c25..78a581c4caca 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -1,4 +1,5 @@ import Onyx from 'react-native-onyx'; +import _ from 'underscore'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; import {getSimplifiedIOUReport} from './Report'; @@ -24,7 +25,7 @@ function getPreferredCurrency() { * @param {String} parameters.debtorEmail */ function createIOUTransaction({ - comment, amount, currency, debtorEmail, + comment, amount, currency, debtorEmail, setIsTransactionComplete, }) { Onyx.merge(ONYXKEYS.IOU, {loading: true}); let iouReportID = ''; @@ -56,7 +57,7 @@ function createIOUTransaction({ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, getSimplifiedIOUReport(iouReportData)); - + setIsTransactionComplete(true); Onyx.merge(ONYXKEYS.IOU, {loading: false}); }) .catch((error) => { @@ -79,7 +80,9 @@ function createIOUSplit({ amount, currency, splits, + setIsTransactionComplete, }) { + let reportIDs = []; Onyx.merge(ONYXKEYS.IOU, {loading: true}); API.CreateChatReport({ emailList: participants.join(','), @@ -92,9 +95,33 @@ function createIOUSplit({ comment, reportID, })) - .then((res) => { - console.debug('Response from CreateIOUSplit', res); - Onyx.merge(ONYXKEYS.IOU, {loading: false}); // placeholder + .then((data) => { + console.debug(data); + Onyx.merge(ONYXKEYS.IOU, {loading: false}); + reportIDs = data.reportIDList; + return reportIDs; + }) + .then(reportIDList => API.Get({ + returnValueList: 'reportStuff', + reportIDList, + shouldLoadOptionalKeys: true, + includePinnedReports: true, + })) + .then(({reports}) => _.map(reports, getSimplifiedIOUReport)) + .then((iouReportObjects) => { + const reportIOUData = {}; + _.each(iouReportObjects, (iouReportObject) => { + if (!iouReportObject) { + return; + } + const iouReportKey = `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportObject.reportID}`; + reportIOUData[iouReportKey] = iouReportObject; + }); + return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, {...reportIOUData}); + }) + .then(() => { + setIsTransactionComplete(true); + Onyx.merge(ONYXKEYS.IOU, {loading: false}); }) .catch((error) => { console.debug(`Error: ${error.message}`); diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 6cc8b3b3de28..0e6785fa6a03 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -909,4 +909,5 @@ export { togglePinnedState, updateCurrentlyViewedReportID, getSimplifiedIOUReport, + getSimplifiedReportObject, }; diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index c9631f44509b..eece50d0c700 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -2,7 +2,6 @@ import React, {Component} from 'react'; import {View, TouchableOpacity} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; -import _ from 'lodash'; import IOUAmountPage from './steps/IOUAmountPage'; import IOUParticipantsPage from './steps/IOUParticipantsPage'; import IOUConfirmPage from './steps/IOUConfirmPage'; @@ -58,6 +57,7 @@ class IOUModal extends Component { this.createTransaction = this.createTransaction.bind(this); this.updateComment = this.updateComment.bind(this); this.addParticipants = this.addParticipants.bind(this); + this.setIsTransactionComplete = this.setIsTransactionComplete.bind(this); this.state = { currentStepIndex: 0, @@ -66,6 +66,7 @@ class IOUModal extends Component { selectedCurrency: 'USD', isAmountPageNextButtonDisabled: true, comment: '', + isTransactionComplete: false, }; } @@ -74,11 +75,21 @@ class IOUModal extends Component { } componentDidUpdate(prevProps) { - // Dismiss modal when previous iousReport changes from the new iousReport - // This should occur the data changes from within the modal activity - if (!_.isEqual(prevProps.iousReport, this.props.iousReport)) { + // Dismiss modal when the length of transactions for any iousReport changes + if (!prevProps.iousReport) { return; } + + if (this.state.isTransactionComplete === true) { return Navigation.dismissModal(); } + + // Object.keys(this.props.iousReport) + // .forEach((reportKey) => { + // const prevTransactions = lodashGet({...prevProps.iousReport[reportKey]}, 'transactions', null); + // const currentTransactions = lodashGet({...this.props.iousReport[reportKey]}, 'transactions', null); + // if (prevTransactions.length !== currentTransactions.length) { + // return Navigation.dismissModal(); + // } + // }); } /** @@ -98,6 +109,18 @@ class IOUModal extends Component { return steps[currentStepIndex] || ''; } + setIsTransactionComplete(isTransactionComplete) { + this.setState({ + isTransactionComplete, + }); + } + + addParticipants(participants) { + this.setState({ + participants, + }); + } + /** * Navigate to the next IOU step if possible */ @@ -122,12 +145,6 @@ class IOUModal extends Component { })); } - addParticipants(participants) { - this.setState({ - participants, - }); - } - /** * Update comment whenever user enters any new text * @@ -183,17 +200,23 @@ class IOUModal extends Component { if (debtorEmail) { return createIOUTransaction({ comment: this.state.comment, - amount: this.state.amount, + + // should send in cents to API + amount: this.state.amount * 100, currency: this.state.selectedCurrency, debtorEmail, + setIsTransactionComplete: this.setIsTransactionComplete, }); } return createIOUSplit({ comment: this.state.comment, - amount: this.state.amount, + + // should send in cents to API + amount: this.state.amount * 100, currency: this.state.selectedCurrency, splits, participants, + setIsTransactionComplete: this.setIsTransactionComplete, }); } @@ -273,7 +296,4 @@ export default withOnyx({ iousReport: { key: ONYXKEYS.COLLECTION.REPORT_IOUS, }, - reportID: { - key: ONYXKEYS.CURRENTLY_VIEWED_REPORTID, - }, })(IOUModal); diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js index 92f6e58435c3..c71d5f519136 100644 --- a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js +++ b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js @@ -86,12 +86,12 @@ class IOUConfirmSplitPage extends Component { getSections() { const sections = []; - // $ should be replaced by currency symbol once available - const individualAmountText = `$${this.calculateAmount(2)}`; const formattedMyPersonalDetails = getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails, - individualAmountText); + + // convert from cent to bigger form + `$${this.calculateAmount(true) / 100}`); const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, - individualAmountText); + `$${this.calculateAmount() / 100}`); sections.push({ title: 'WHO PAID?', @@ -115,12 +115,19 @@ class IOUConfirmSplitPage extends Component { * @returns {Array} */ getSplits() { - const amountPerPerson = parseFloat(this.calculateAmount()); const splits = this.props.participants.map(participant => ({ email: participant.login, - amount: amountPerPerson, + + // we should send in cents to API + amount: this.calculateAmount(), })); - splits.push({email: this.props.myPersonalDetails.login, amount: amountPerPerson}); + + splits.push({ + email: this.props.myPersonalDetails.login, + + // the user is default and we should send in cents to API + amount: this.calculateAmount(true), + }); return splits; } @@ -144,14 +151,27 @@ class IOUConfirmSplitPage extends Component { getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails)]; } + /** - * Calculates amount for individual - * @param {Number} precision + * Calculates the amount per user + * @param {Boolean} isDefaultUser * @returns {Number} */ - calculateAmount(precision) { - const calculatedAmount = (this.props.iouAmount / (this.props.participants.length + 1)); - return precision ? calculatedAmount.toFixed(precision) : calculatedAmount; + calculateAmount(isDefaultUser = false) { + // convert to cents before working with iouAmount to avoid + // javascript subtraction with decimal problem + const iouAmount = Math.round(parseFloat(this.props.iouAmount * 100)); + const totalParticipants = this.props.participants.length + 1; + + const amountPerPerson = Math.round(iouAmount / totalParticipants); + + const sumAmount = amountPerPerson * totalParticipants; + + const difference = iouAmount - sumAmount; + + if (!isDefaultUser) { return amountPerPerson; } + + return iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; } render() { From d02b119a440918cbb7110320667164521b8444d4 Mon Sep 17 00:00:00 2001 From: Barun Pandey Date: Tue, 30 Mar 2021 22:06:37 +0545 Subject: [PATCH 05/15] Remove extra lines --- src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js | 1 - src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js | 1 - 2 files changed, 2 deletions(-) diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js index be04343396e8..a80dd556d5e0 100644 --- a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js +++ b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js @@ -69,7 +69,6 @@ const defaultProps = { comment: '', }; - class IOUConfirmRequestPage extends Component { /** * Returns the sections needed for the OptionsSelector diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js index c71d5f519136..c769fe55679c 100644 --- a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js +++ b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js @@ -151,7 +151,6 @@ class IOUConfirmSplitPage extends Component { getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails)]; } - /** * Calculates the amount per user * @param {Boolean} isDefaultUser From f2a9c4da6d67d9256ce982cbd5ead12f26bd6ee8 Mon Sep 17 00:00:00 2001 From: Barun Pandey Date: Wed, 31 Mar 2021 23:10:54 +0545 Subject: [PATCH 06/15] Refactor code, revert changes in OptionsSelector --- src/components/OptionsSelector.js | 40 +++++++------------ .../steps/IOUConfirmPage/IOUConfirmRequest.js | 18 +++++---- .../steps/IOUConfirmPage/IOUConfirmSplit.js | 18 +++++---- src/styles/utilities/spacing.js | 4 ++ 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/components/OptionsSelector.js b/src/components/OptionsSelector.js index 5bb54afefa10..8e976eab31c7 100644 --- a/src/components/OptionsSelector.js +++ b/src/components/OptionsSelector.js @@ -12,9 +12,6 @@ const propTypes = { // Callback to fire when a row is tapped onSelectRow: PropTypes.func, - // if we should show search or not? - showSearch: PropTypes.bool, - // Sections for the section list sections: PropTypes.arrayOf(PropTypes.shape({ // Title of the section @@ -31,10 +28,10 @@ const propTypes = { })).isRequired, // Value in the search input field - value: PropTypes.string, + value: PropTypes.string.isRequired, // Callback fired when text changes - onChangeText: PropTypes.func, + onChangeText: PropTypes.func.isRequired, // Optional placeholder text for the selector placeholderText: PropTypes.string, @@ -75,9 +72,6 @@ const defaultProps = { hideAdditionalOptionStates: false, forceTextUnreadStyle: false, showTitleTooltip: false, - showSearch: true, - onChangeText: () => {}, - value: '', }; class OptionsSelector extends Component { @@ -93,9 +87,7 @@ class OptionsSelector extends Component { } componentDidMount() { - if (this.props.showSearch) { - this.textInput.focus(); - } + this.textInput.focus(); } /** @@ -178,20 +170,18 @@ class OptionsSelector extends Component { render() { return ( - { this.props.showSearch ? ( - - this.textInput = el} - style={[styles.textInput]} - value={this.props.value} - onChangeText={this.props.onChangeText} - onKeyPress={this.handleKeyPress} - placeholder={this.props.placeholderText} - placeholderTextColor={themeColors.placeholderText} - /> - - ) : null} + + this.textInput = el} + style={[styles.textInput]} + value={this.props.value} + onChangeText={this.props.onChangeText} + onKeyPress={this.handleKeyPress} + placeholder={this.props.placeholderText} + placeholderTextColor={themeColors.placeholderText} + /> + this.list = el} optionHoveredStyle={styles.hoveredComponentBG} diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js index a80dd556d5e0..a57c06aafeb6 100644 --- a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js +++ b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js @@ -7,8 +7,8 @@ import styles from '../../../../styles/styles'; import Text from '../../../../components/Text'; import ButtonWithLoader from '../../../../components/ButtonWithLoader'; import themeColors from '../../../../styles/themes/default'; -import OptionsSelector from '../../../../components/OptionsSelector'; import {getDisplayOptionsFromParticipants} from '../../../../libs/OptionsListUtils'; +import OptionsList from '../../../../components/OptionsList'; const propTypes = { // Callback to inform parent modal of success @@ -97,17 +97,19 @@ class IOUConfirmRequestPage extends Component { return ( - - - WHAT'S IT FOR? - - + + + WHAT'S IT FOR? + + + - + - - - WHAT'S IT FOR? - - + + + WHAT'S IT FOR? + + + - + Date: Thu, 1 Apr 2021 00:08:29 +0545 Subject: [PATCH 07/15] Remove temporary solution of using state to dismiss modal --- src/libs/actions/IOU.js | 7 +------ src/pages/iou/IOUModal.js | 31 ++++++++++--------------------- 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 78a581c4caca..1a79e7e55548 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -25,7 +25,7 @@ function getPreferredCurrency() { * @param {String} parameters.debtorEmail */ function createIOUTransaction({ - comment, amount, currency, debtorEmail, setIsTransactionComplete, + comment, amount, currency, debtorEmail, }) { Onyx.merge(ONYXKEYS.IOU, {loading: true}); let iouReportID = ''; @@ -57,7 +57,6 @@ function createIOUTransaction({ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, getSimplifiedIOUReport(iouReportData)); - setIsTransactionComplete(true); Onyx.merge(ONYXKEYS.IOU, {loading: false}); }) .catch((error) => { @@ -80,7 +79,6 @@ function createIOUSplit({ amount, currency, splits, - setIsTransactionComplete, }) { let reportIDs = []; Onyx.merge(ONYXKEYS.IOU, {loading: true}); @@ -96,8 +94,6 @@ function createIOUSplit({ reportID, })) .then((data) => { - console.debug(data); - Onyx.merge(ONYXKEYS.IOU, {loading: false}); reportIDs = data.reportIDList; return reportIDs; }) @@ -120,7 +116,6 @@ function createIOUSplit({ return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, {...reportIOUData}); }) .then(() => { - setIsTransactionComplete(true); Onyx.merge(ONYXKEYS.IOU, {loading: false}); }) .catch((error) => { diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index eece50d0c700..cf17e5d94b72 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -57,7 +57,6 @@ class IOUModal extends Component { this.createTransaction = this.createTransaction.bind(this); this.updateComment = this.updateComment.bind(this); this.addParticipants = this.addParticipants.bind(this); - this.setIsTransactionComplete = this.setIsTransactionComplete.bind(this); this.state = { currentStepIndex: 0, @@ -66,7 +65,6 @@ class IOUModal extends Component { selectedCurrency: 'USD', isAmountPageNextButtonDisabled: true, comment: '', - isTransactionComplete: false, }; } @@ -75,21 +73,19 @@ class IOUModal extends Component { } componentDidUpdate(prevProps) { - // Dismiss modal when the length of transactions for any iousReport changes + // Handles edge case when there are no previous user IOU report if (!prevProps.iousReport) { return; } - if (this.state.isTransactionComplete === true) { - return Navigation.dismissModal(); - } + // Dismiss modal when the length of transactions for any iousReport changes + Object.keys(this.props.iousReport) + .forEach((reportKey) => { + const prevTransactions = [...prevProps.iousReport[reportKey].transactions]; + const currentTransactions = [...this.props.iousReport[reportKey].transactions]; - // Object.keys(this.props.iousReport) - // .forEach((reportKey) => { - // const prevTransactions = lodashGet({...prevProps.iousReport[reportKey]}, 'transactions', null); - // const currentTransactions = lodashGet({...this.props.iousReport[reportKey]}, 'transactions', null); - // if (prevTransactions.length !== currentTransactions.length) { - // return Navigation.dismissModal(); - // } - // }); + if (prevTransactions.length !== currentTransactions.length) { + return Navigation.dismissModal(); + } + }); } /** @@ -109,12 +105,6 @@ class IOUModal extends Component { return steps[currentStepIndex] || ''; } - setIsTransactionComplete(isTransactionComplete) { - this.setState({ - isTransactionComplete, - }); - } - addParticipants(participants) { this.setState({ participants, @@ -216,7 +206,6 @@ class IOUModal extends Component { currency: this.state.selectedCurrency, splits, participants, - setIsTransactionComplete: this.setIsTransactionComplete, }); } From e1dcc446f5a68598dbdd058c4ced04d401a74670 Mon Sep 17 00:00:00 2001 From: barun1997 Date: Fri, 2 Apr 2021 10:53:43 +0545 Subject: [PATCH 08/15] Add edge cases to handle ComponentDidUpdate in IOUModal --- src/pages/iou/IOUModal.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index cf17e5d94b72..32d1020bda27 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -1,5 +1,6 @@ import React, {Component} from 'react'; import {View, TouchableOpacity} from 'react-native'; +import _ from 'underscore'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import IOUAmountPage from './steps/IOUAmountPage'; @@ -73,19 +74,10 @@ class IOUModal extends Component { } componentDidUpdate(prevProps) { - // Handles edge case when there are no previous user IOU report - if (!prevProps.iousReport) { return; } - - // Dismiss modal when the length of transactions for any iousReport changes - Object.keys(this.props.iousReport) - .forEach((reportKey) => { - const prevTransactions = [...prevProps.iousReport[reportKey].transactions]; - const currentTransactions = [...this.props.iousReport[reportKey].transactions]; - - if (prevTransactions.length !== currentTransactions.length) { - return Navigation.dismissModal(); - } - }); + // if the prevProps isn't equivalent to new prop, dismiss the modal + if (!_.isEqual(prevProps.iousReport, this.props.iousReport)) { + return Navigation.dismissModal(); + } } /** From 1ac4540074d61c9353d80c2734e843ac6548a07d Mon Sep 17 00:00:00 2001 From: barun1997 Date: Thu, 8 Apr 2021 20:05:31 +0545 Subject: [PATCH 09/15] Solve github actions bug --- .github/actions/checkDeployBlockers/index.js | 2315 +---------------- .../actions/reopenIssueWithComment/index.js | 2315 +---------------- 2 files changed, 32 insertions(+), 4598 deletions(-) diff --git a/.github/actions/checkDeployBlockers/index.js b/.github/actions/checkDeployBlockers/index.js index baae215e4076..38ff286a9db7 100644 --- a/.github/actions/checkDeployBlockers/index.js +++ b/.github/actions/checkDeployBlockers/index.js @@ -82,8 +82,6 @@ module.exports = run; const _ = __nccwpck_require__(4987); const lodashGet = __nccwpck_require__(6908); -const semverParse = __nccwpck_require__(5925); -const semverSatisfies = __nccwpck_require__(6055); const GITHUB_OWNER = 'Expensify'; const EXPENSIFY_CASH_REPO = 'Expensify.cash'; @@ -144,11 +142,6 @@ class GithubUtils { try { const versionRegex = new RegExp('([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9]+))?', 'g'); const tag = issue.body.match(versionRegex)[0].replace(/`/g, ''); - - // eslint-disable-next-line max-len - const compareURLRegex = new RegExp(`${EXPENSIFY_CASH_URL}/compare/${versionRegex.source}\\.\\.\\.${versionRegex.source}`, 'g'); - const comparisonURL = issue.body.match(compareURLRegex)[0]; - return { title: issue.title, url: issue.url, @@ -156,7 +149,6 @@ class GithubUtils { PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), tag, - comparisonURL, }; } catch (exception) { throw new Error(`Unable to find ${STAGING_DEPLOY_CASH_LABEL} issue with correct data.`); @@ -231,71 +223,6 @@ class GithubUtils { ); } - /** - * Generate a comparison URL between two versions following the semverLevel passed - * - * @param {String} repoSlug - The slug of the repository: / - * @param {String} tag - The tag to compare first the previous semverLevel - * @param {String} semverLevel - The semantic versioning MAJOR, MINOR, PATCH and BUILD - * @return {Promise} the url generated - * @throws {Error} If the request to the Github API fails. - */ - generateVersionComparisonURL(repoSlug, tag, semverLevel) { - return new Promise((resolve, reject) => { - const getComparisonURL = (previousTag, currentTag) => ( - `${EXPENSIFY_CASH_URL}/compare/${previousTag}...${currentTag}` - ); - - const [repoOwner, repoName] = repoSlug.split('/'); - const tagSemver = semverParse(tag); - - return this.octokit.repos.listTags({ - owner: repoOwner, - repo: repoName, - }) - .then(githubResponse => githubResponse.data.some(({name: repoTag}) => { - if (semverLevel === 'MAJOR' - && semverSatisfies(repoTag, `<${tagSemver.major}.x.x`, {includePrerelease: true}) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - - if (semverLevel === 'MINOR' - && semverSatisfies( - repoTag, - `<${tagSemver.major}.${tagSemver.minor}.x`, - {includePrerelease: true}, - ) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - - if (semverLevel === 'PATCH' - && semverSatisfies(repoTag, `<${tagSemver}`, {includePrerelease: true}) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - - if (semverLevel === 'BUILD' - && repoTag !== tagSemver.version - && semverSatisfies( - repoTag, - `<=${tagSemver.major}.${tagSemver.minor}.${tagSemver.patch}`, - {includePrerelease: true}, - ) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - return false; - })) - .catch(githubError => reject(githubError)); - }); - } - /** * Creates a new StagingDeployCash issue. * @@ -391,22 +318,16 @@ class GithubUtils { deployBlockers = [], resolvedDeployBlockers = [], ) { - return Promise.all([ - this.generateVersionComparisonURL(`${GITHUB_OWNER}/${EXPENSIFY_CASH_REPO}`, tag, 'PATCH'), - this.octokit.pulls.list({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - per_page: 100, - }), - ]) - .then(results => ({ - comparisonURL: results[0], - automergePRs: _.map( - _.filter(results[1].data, GithubUtils.isAutomergePullRequest), + return this.octokit.pulls.list({ + owner: GITHUB_OWNER, + repo: EXPENSIFY_CASH_REPO, + per_page: 100, + }) + .then(({data}) => { + const automergePRs = _.pluck( + _.filter(data, GithubUtils.isAutomergePullRequest), 'html_url', - ), - })) - .then(({comparisonURL, automergePRs}) => { + ); const sortedPRList = _.chain(PRList) .difference(automergePRs) .unique() @@ -418,8 +339,8 @@ class GithubUtils { ); // Tag version and comparison URL - let issueBody = `**Release Version:** \`${tag}\`\r\n`; - issueBody += `**Compare Changes:** ${comparisonURL}\r\n`; + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n`; // PR list if (!_.isEmpty(PRList)) { @@ -440,11 +361,13 @@ class GithubUtils { } issueBody += '\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; }) - // eslint-disable-next-line no-console - .catch(err => console.warn('Error generating comparison URL, continuing...', err)); + .catch(err => console.warn( + 'Error generating StagingDeployCash issue body!', + 'Automerge PRs may not be properly filtered out. Continuing...', + err, + )); } /** @@ -10790,2212 +10713,6 @@ if (!safer.constants) { module.exports = safer -/***/ }), - -/***/ 1532: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const ANY = Symbol('SemVer ANY') -// hoisted class for cyclic dependency -class Comparator { - static get ANY () { - return ANY - } - constructor (comp, options) { - options = parseOptions(options) - - if (comp instanceof Comparator) { - if (comp.loose === !!options.loose) { - return comp - } else { - comp = comp.value - } - } - - debug('comparator', comp, options) - this.options = options - this.loose = !!options.loose - this.parse(comp) - - if (this.semver === ANY) { - this.value = '' - } else { - this.value = this.operator + this.semver.version - } - - debug('comp', this) - } - - parse (comp) { - const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] - const m = comp.match(r) - - if (!m) { - throw new TypeError(`Invalid comparator: ${comp}`) - } - - this.operator = m[1] !== undefined ? m[1] : '' - if (this.operator === '=') { - this.operator = '' - } - - // if it literally is just '>' or '' then allow anything. - if (!m[2]) { - this.semver = ANY - } else { - this.semver = new SemVer(m[2], this.options.loose) - } - } - - toString () { - return this.value - } - - test (version) { - debug('Comparator.test', version, this.options.loose) - - if (this.semver === ANY || version === ANY) { - return true - } - - if (typeof version === 'string') { - try { - version = new SemVer(version, this.options) - } catch (er) { - return false - } - } - - return cmp(version, this.operator, this.semver, this.options) - } - - intersects (comp, options) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required') - } - - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false - } - } - - if (this.operator === '') { - if (this.value === '') { - return true - } - return new Range(comp.value, options).test(this.value) - } else if (comp.operator === '') { - if (comp.value === '') { - return true - } - return new Range(this.value, options).test(comp.semver) - } - - const sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>') - const sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<') - const sameSemVer = this.semver.version === comp.semver.version - const differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<=') - const oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, options) && - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<') - const oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, options) && - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>') - - return ( - sameDirectionIncreasing || - sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || - oppositeDirectionsGreaterThan - ) - } -} - -module.exports = Comparator - -const parseOptions = __nccwpck_require__(785) -const {re, t} = __nccwpck_require__(9523) -const cmp = __nccwpck_require__(5098) -const debug = __nccwpck_require__(427) -const SemVer = __nccwpck_require__(8088) -const Range = __nccwpck_require__(9828) - - -/***/ }), - -/***/ 9828: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -// hoisted class for cyclic dependency -class Range { - constructor (range, options) { - options = parseOptions(options) - - if (range instanceof Range) { - if ( - range.loose === !!options.loose && - range.includePrerelease === !!options.includePrerelease - ) { - return range - } else { - return new Range(range.raw, options) - } - } - - if (range instanceof Comparator) { - // just put it in the set and return - this.raw = range.value - this.set = [[range]] - this.format() - return this - } - - this.options = options - this.loose = !!options.loose - this.includePrerelease = !!options.includePrerelease - - // First, split based on boolean or || - this.raw = range - this.set = range - .split(/\s*\|\|\s*/) - // map the range to a 2d array of comparators - .map(range => this.parseRange(range.trim())) - // throw out any comparator lists that are empty - // this generally means that it was not a valid range, which is allowed - // in loose mode, but will still throw if the WHOLE range is invalid. - .filter(c => c.length) - - if (!this.set.length) { - throw new TypeError(`Invalid SemVer Range: ${range}`) - } - - // if we have any that are not the null set, throw out null sets. - if (this.set.length > 1) { - // keep the first one, in case they're all null sets - const first = this.set[0] - this.set = this.set.filter(c => !isNullSet(c[0])) - if (this.set.length === 0) - this.set = [first] - else if (this.set.length > 1) { - // if we have any that are *, then the range is just * - for (const c of this.set) { - if (c.length === 1 && isAny(c[0])) { - this.set = [c] - break - } - } - } - } - - this.format() - } - - format () { - this.range = this.set - .map((comps) => { - return comps.join(' ').trim() - }) - .join('||') - .trim() - return this.range - } - - toString () { - return this.range - } - - parseRange (range) { - range = range.trim() - - // memoize range parsing for performance. - // this is a very hot path, and fully deterministic. - const memoOpts = Object.keys(this.options).join(',') - const memoKey = `parseRange:${memoOpts}:${range}` - const cached = cache.get(memoKey) - if (cached) - return cached - - const loose = this.options.loose - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] - range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) - debug('hyphen replace', range) - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) - debug('comparator trim', range, re[t.COMPARATORTRIM]) - - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[t.TILDETRIM], tildeTrimReplace) - - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[t.CARETTRIM], caretTrimReplace) - - // normalize spaces - range = range.split(/\s+/).join(' ') - - // At this point, the range is completely trimmed and - // ready to be split into comparators. - - const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] - const rangeList = range - .split(' ') - .map(comp => parseComparator(comp, this.options)) - .join(' ') - .split(/\s+/) - // >=0.0.0 is equivalent to * - .map(comp => replaceGTE0(comp, this.options)) - // in loose mode, throw out any that are not valid comparators - .filter(this.options.loose ? comp => !!comp.match(compRe) : () => true) - .map(comp => new Comparator(comp, this.options)) - - // if any comparators are the null set, then replace with JUST null set - // if more than one comparator, remove any * comparators - // also, don't include the same comparator more than once - const l = rangeList.length - const rangeMap = new Map() - for (const comp of rangeList) { - if (isNullSet(comp)) - return [comp] - rangeMap.set(comp.value, comp) - } - if (rangeMap.size > 1 && rangeMap.has('')) - rangeMap.delete('') - - const result = [...rangeMap.values()] - cache.set(memoKey, result) - return result - } - - intersects (range, options) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required') - } - - return this.set.some((thisComparators) => { - return ( - isSatisfiable(thisComparators, options) && - range.set.some((rangeComparators) => { - return ( - isSatisfiable(rangeComparators, options) && - thisComparators.every((thisComparator) => { - return rangeComparators.every((rangeComparator) => { - return thisComparator.intersects(rangeComparator, options) - }) - }) - ) - }) - ) - }) - } - - // if ANY of the sets match ALL of its comparators, then pass - test (version) { - if (!version) { - return false - } - - if (typeof version === 'string') { - try { - version = new SemVer(version, this.options) - } catch (er) { - return false - } - } - - for (let i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version, this.options)) { - return true - } - } - return false - } -} -module.exports = Range - -const LRU = __nccwpck_require__(1196) -const cache = new LRU({ max: 1000 }) - -const parseOptions = __nccwpck_require__(785) -const Comparator = __nccwpck_require__(1532) -const debug = __nccwpck_require__(427) -const SemVer = __nccwpck_require__(8088) -const { - re, - t, - comparatorTrimReplace, - tildeTrimReplace, - caretTrimReplace -} = __nccwpck_require__(9523) - -const isNullSet = c => c.value === '<0.0.0-0' -const isAny = c => c.value === '' - -// take a set of comparators and determine whether there -// exists a version which can satisfy it -const isSatisfiable = (comparators, options) => { - let result = true - const remainingComparators = comparators.slice() - let testComparator = remainingComparators.pop() - - while (result && remainingComparators.length) { - result = remainingComparators.every((otherComparator) => { - return testComparator.intersects(otherComparator, options) - }) - - testComparator = remainingComparators.pop() - } - - return result -} - -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -const parseComparator = (comp, options) => { - debug('comp', comp, options) - comp = replaceCarets(comp, options) - debug('caret', comp) - comp = replaceTildes(comp, options) - debug('tildes', comp) - comp = replaceXRanges(comp, options) - debug('xrange', comp) - comp = replaceStars(comp, options) - debug('stars', comp) - return comp -} - -const isX = id => !id || id.toLowerCase() === 'x' || id === '*' - -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 -const replaceTildes = (comp, options) => - comp.trim().split(/\s+/).map((comp) => { - return replaceTilde(comp, options) - }).join(' ') - -const replaceTilde = (comp, options) => { - const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] - return comp.replace(r, (_, M, m, p, pr) => { - debug('tilde', comp, _, M, m, p, pr) - let ret - - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = `>=${M}.0.0 <${+M + 1}.0.0-0` - } else if (isX(p)) { - // ~1.2 == >=1.2.0 <1.3.0-0 - ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0` - } else if (pr) { - debug('replaceTilde pr', pr) - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${+m + 1}.0-0` - } else { - // ~1.2.3 == >=1.2.3 <1.3.0-0 - ret = `>=${M}.${m}.${p - } <${M}.${+m + 1}.0-0` - } - - debug('tilde return', ret) - return ret - }) -} - -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 -// ^1.2.3 --> >=1.2.3 <2.0.0-0 -// ^1.2.0 --> >=1.2.0 <2.0.0-0 -const replaceCarets = (comp, options) => - comp.trim().split(/\s+/).map((comp) => { - return replaceCaret(comp, options) - }).join(' ') - -const replaceCaret = (comp, options) => { - debug('caret', comp, options) - const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET] - const z = options.includePrerelease ? '-0' : '' - return comp.replace(r, (_, M, m, p, pr) => { - debug('caret', comp, _, M, m, p, pr) - let ret - - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0` - } else if (isX(p)) { - if (M === '0') { - ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0` - } else { - ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0` - } - } else if (pr) { - debug('replaceCaret pr', pr) - if (M === '0') { - if (m === '0') { - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${m}.${+p + 1}-0` - } else { - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${+m + 1}.0-0` - } - } else { - ret = `>=${M}.${m}.${p}-${pr - } <${+M + 1}.0.0-0` - } - } else { - debug('no pr') - if (M === '0') { - if (m === '0') { - ret = `>=${M}.${m}.${p - }${z} <${M}.${m}.${+p + 1}-0` - } else { - ret = `>=${M}.${m}.${p - }${z} <${M}.${+m + 1}.0-0` - } - } else { - ret = `>=${M}.${m}.${p - } <${+M + 1}.0.0-0` - } - } - - debug('caret return', ret) - return ret - }) -} - -const replaceXRanges = (comp, options) => { - debug('replaceXRanges', comp, options) - return comp.split(/\s+/).map((comp) => { - return replaceXRange(comp, options) - }).join(' ') -} - -const replaceXRange = (comp, options) => { - comp = comp.trim() - const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE] - return comp.replace(r, (ret, gtlt, M, m, p, pr) => { - debug('xRange', comp, ret, gtlt, M, m, p, pr) - const xM = isX(M) - const xm = xM || isX(m) - const xp = xm || isX(p) - const anyX = xp - - if (gtlt === '=' && anyX) { - gtlt = '' - } - - // if we're including prereleases in the match, then we need - // to fix this to -0, the lowest possible prerelease value - pr = options.includePrerelease ? '-0' : '' - - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0-0' - } else { - // nothing is forbidden - ret = '*' - } - } else if (gtlt && anyX) { - // we know patch is an x, because we have any x at all. - // replace X with 0 - if (xm) { - m = 0 - } - p = 0 - - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - gtlt = '>=' - if (xm) { - M = +M + 1 - m = 0 - p = 0 - } else { - m = +m + 1 - p = 0 - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<' - if (xm) { - M = +M + 1 - } else { - m = +m + 1 - } - } - - if (gtlt === '<') - pr = '-0' - - ret = `${gtlt + M}.${m}.${p}${pr}` - } else if (xm) { - ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0` - } else if (xp) { - ret = `>=${M}.${m}.0${pr - } <${M}.${+m + 1}.0-0` - } - - debug('xRange return', ret) - - return ret - }) -} - -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -const replaceStars = (comp, options) => { - debug('replaceStars', comp, options) - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[t.STAR], '') -} - -const replaceGTE0 = (comp, options) => { - debug('replaceGTE0', comp, options) - return comp.trim() - .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') -} - -// This function is passed to string.replace(re[t.HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0-0 -const hyphenReplace = incPr => ($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) => { - if (isX(fM)) { - from = '' - } else if (isX(fm)) { - from = `>=${fM}.0.0${incPr ? '-0' : ''}` - } else if (isX(fp)) { - from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}` - } else if (fpr) { - from = `>=${from}` - } else { - from = `>=${from}${incPr ? '-0' : ''}` - } - - if (isX(tM)) { - to = '' - } else if (isX(tm)) { - to = `<${+tM + 1}.0.0-0` - } else if (isX(tp)) { - to = `<${tM}.${+tm + 1}.0-0` - } else if (tpr) { - to = `<=${tM}.${tm}.${tp}-${tpr}` - } else if (incPr) { - to = `<${tM}.${tm}.${+tp + 1}-0` - } else { - to = `<=${to}` - } - - return (`${from} ${to}`).trim() -} - -const testSet = (set, version, options) => { - for (let i = 0; i < set.length; i++) { - if (!set[i].test(version)) { - return false - } - } - - if (version.prerelease.length && !options.includePrerelease) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (let i = 0; i < set.length; i++) { - debug(set[i].semver) - if (set[i].semver === Comparator.ANY) { - continue - } - - if (set[i].semver.prerelease.length > 0) { - const allowed = set[i].semver - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) { - return true - } - } - } - - // Version has a -pre, but it's not one of the ones we like. - return false - } - - return true -} - - -/***/ }), - -/***/ 8088: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const debug = __nccwpck_require__(427) -const { MAX_LENGTH, MAX_SAFE_INTEGER } = __nccwpck_require__(2293) -const { re, t } = __nccwpck_require__(9523) - -const parseOptions = __nccwpck_require__(785) -const { compareIdentifiers } = __nccwpck_require__(2463) -class SemVer { - constructor (version, options) { - options = parseOptions(options) - - if (version instanceof SemVer) { - if (version.loose === !!options.loose && - version.includePrerelease === !!options.includePrerelease) { - return version - } else { - version = version.version - } - } else if (typeof version !== 'string') { - throw new TypeError(`Invalid Version: ${version}`) - } - - if (version.length > MAX_LENGTH) { - throw new TypeError( - `version is longer than ${MAX_LENGTH} characters` - ) - } - - debug('SemVer', version, options) - this.options = options - this.loose = !!options.loose - // this isn't actually relevant for versions, but keep it so that we - // don't run into trouble passing this.options around. - this.includePrerelease = !!options.includePrerelease - - const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]) - - if (!m) { - throw new TypeError(`Invalid Version: ${version}`) - } - - this.raw = version - - // these are actually numbers - this.major = +m[1] - this.minor = +m[2] - this.patch = +m[3] - - if (this.major > MAX_SAFE_INTEGER || this.major < 0) { - throw new TypeError('Invalid major version') - } - - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { - throw new TypeError('Invalid minor version') - } - - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { - throw new TypeError('Invalid patch version') - } - - // numberify any prerelease numeric ids - if (!m[4]) { - this.prerelease = [] - } else { - this.prerelease = m[4].split('.').map((id) => { - if (/^[0-9]+$/.test(id)) { - const num = +id - if (num >= 0 && num < MAX_SAFE_INTEGER) { - return num - } - } - return id - }) - } - - this.build = m[5] ? m[5].split('.') : [] - this.format() - } - - format () { - this.version = `${this.major}.${this.minor}.${this.patch}` - if (this.prerelease.length) { - this.version += `-${this.prerelease.join('.')}` - } - return this.version - } - - toString () { - return this.version - } - - compare (other) { - debug('SemVer.compare', this.version, this.options, other) - if (!(other instanceof SemVer)) { - if (typeof other === 'string' && other === this.version) { - return 0 - } - other = new SemVer(other, this.options) - } - - if (other.version === this.version) { - return 0 - } - - return this.compareMain(other) || this.comparePre(other) - } - - compareMain (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - return ( - compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch) - ) - } - - comparePre (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) { - return -1 - } else if (!this.prerelease.length && other.prerelease.length) { - return 1 - } else if (!this.prerelease.length && !other.prerelease.length) { - return 0 - } - - let i = 0 - do { - const a = this.prerelease[i] - const b = other.prerelease[i] - debug('prerelease compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) - } - } while (++i) - } - - compareBuild (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - let i = 0 - do { - const a = this.build[i] - const b = other.build[i] - debug('prerelease compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) - } - } while (++i) - } - - // preminor will bump the version up to the next minor release, and immediately - // down to pre-release. premajor and prepatch work the same way. - inc (release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0 - this.patch = 0 - this.minor = 0 - this.major++ - this.inc('pre', identifier) - break - case 'preminor': - this.prerelease.length = 0 - this.patch = 0 - this.minor++ - this.inc('pre', identifier) - break - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0 - this.inc('patch', identifier) - this.inc('pre', identifier) - break - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) { - this.inc('patch', identifier) - } - this.inc('pre', identifier) - break - - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if ( - this.minor !== 0 || - this.patch !== 0 || - this.prerelease.length === 0 - ) { - this.major++ - } - this.minor = 0 - this.patch = 0 - this.prerelease = [] - break - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) { - this.minor++ - } - this.patch = 0 - this.prerelease = [] - break - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) { - this.patch++ - } - this.prerelease = [] - break - // This probably shouldn't be used publicly. - // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) { - this.prerelease = [0] - } else { - let i = this.prerelease.length - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++ - i = -2 - } - } - if (i === -1) { - // didn't increment anything - this.prerelease.push(0) - } - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) { - this.prerelease = [identifier, 0] - } - } else { - this.prerelease = [identifier, 0] - } - } - break - - default: - throw new Error(`invalid increment argument: ${release}`) - } - this.format() - this.raw = this.version - return this - } -} - -module.exports = SemVer - - -/***/ }), - -/***/ 5098: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const eq = __nccwpck_require__(1898) -const neq = __nccwpck_require__(6017) -const gt = __nccwpck_require__(4123) -const gte = __nccwpck_require__(5522) -const lt = __nccwpck_require__(194) -const lte = __nccwpck_require__(7520) - -const cmp = (a, op, b, loose) => { - switch (op) { - case '===': - if (typeof a === 'object') - a = a.version - if (typeof b === 'object') - b = b.version - return a === b - - case '!==': - if (typeof a === 'object') - a = a.version - if (typeof b === 'object') - b = b.version - return a !== b - - case '': - case '=': - case '==': - return eq(a, b, loose) - - case '!=': - return neq(a, b, loose) - - case '>': - return gt(a, b, loose) - - case '>=': - return gte(a, b, loose) - - case '<': - return lt(a, b, loose) - - case '<=': - return lte(a, b, loose) - - default: - throw new TypeError(`Invalid operator: ${op}`) - } -} -module.exports = cmp - - -/***/ }), - -/***/ 4309: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const SemVer = __nccwpck_require__(8088) -const compare = (a, b, loose) => - new SemVer(a, loose).compare(new SemVer(b, loose)) - -module.exports = compare - - -/***/ }), - -/***/ 1898: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const eq = (a, b, loose) => compare(a, b, loose) === 0 -module.exports = eq - - -/***/ }), - -/***/ 4123: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const gt = (a, b, loose) => compare(a, b, loose) > 0 -module.exports = gt - - -/***/ }), - -/***/ 5522: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const gte = (a, b, loose) => compare(a, b, loose) >= 0 -module.exports = gte - - -/***/ }), - -/***/ 194: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const lt = (a, b, loose) => compare(a, b, loose) < 0 -module.exports = lt - - -/***/ }), - -/***/ 7520: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const lte = (a, b, loose) => compare(a, b, loose) <= 0 -module.exports = lte - - -/***/ }), - -/***/ 6017: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const neq = (a, b, loose) => compare(a, b, loose) !== 0 -module.exports = neq - - -/***/ }), - -/***/ 5925: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const {MAX_LENGTH} = __nccwpck_require__(2293) -const { re, t } = __nccwpck_require__(9523) -const SemVer = __nccwpck_require__(8088) - -const parseOptions = __nccwpck_require__(785) -const parse = (version, options) => { - options = parseOptions(options) - - if (version instanceof SemVer) { - return version - } - - if (typeof version !== 'string') { - return null - } - - if (version.length > MAX_LENGTH) { - return null - } - - const r = options.loose ? re[t.LOOSE] : re[t.FULL] - if (!r.test(version)) { - return null - } - - try { - return new SemVer(version, options) - } catch (er) { - return null - } -} - -module.exports = parse - - -/***/ }), - -/***/ 6055: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const Range = __nccwpck_require__(9828) -const satisfies = (version, range, options) => { - try { - range = new Range(range, options) - } catch (er) { - return false - } - return range.test(version) -} -module.exports = satisfies - - -/***/ }), - -/***/ 2293: -/***/ ((module) => { - -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -const SEMVER_SPEC_VERSION = '2.0.0' - -const MAX_LENGTH = 256 -const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || - /* istanbul ignore next */ 9007199254740991 - -// Max safe segment length for coercion. -const MAX_SAFE_COMPONENT_LENGTH = 16 - -module.exports = { - SEMVER_SPEC_VERSION, - MAX_LENGTH, - MAX_SAFE_INTEGER, - MAX_SAFE_COMPONENT_LENGTH -} - - -/***/ }), - -/***/ 427: -/***/ ((module) => { - -const debug = ( - typeof process === 'object' && - process.env && - process.env.NODE_DEBUG && - /\bsemver\b/i.test(process.env.NODE_DEBUG) -) ? (...args) => console.error('SEMVER', ...args) - : () => {} - -module.exports = debug - - -/***/ }), - -/***/ 2463: -/***/ ((module) => { - -const numeric = /^[0-9]+$/ -const compareIdentifiers = (a, b) => { - const anum = numeric.test(a) - const bnum = numeric.test(b) - - if (anum && bnum) { - a = +a - b = +b - } - - return a === b ? 0 - : (anum && !bnum) ? -1 - : (bnum && !anum) ? 1 - : a < b ? -1 - : 1 -} - -const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a) - -module.exports = { - compareIdentifiers, - rcompareIdentifiers -} - - -/***/ }), - -/***/ 785: -/***/ ((module) => { - -// parse out just the options we care about so we always get a consistent -// obj with keys in a consistent order. -const opts = ['includePrerelease', 'loose', 'rtl'] -const parseOptions = options => - !options ? {} - : typeof options !== 'object' ? { loose: true } - : opts.filter(k => options[k]).reduce((options, k) => { - options[k] = true - return options - }, {}) -module.exports = parseOptions - - -/***/ }), - -/***/ 9523: -/***/ ((module, exports, __nccwpck_require__) => { - -const { MAX_SAFE_COMPONENT_LENGTH } = __nccwpck_require__(2293) -const debug = __nccwpck_require__(427) -exports = module.exports = {} - -// The actual regexps go on exports.re -const re = exports.re = [] -const src = exports.src = [] -const t = exports.t = {} -let R = 0 - -const createToken = (name, value, isGlobal) => { - const index = R++ - debug(index, value) - t[name] = index - src[index] = value - re[index] = new RegExp(value, isGlobal ? 'g' : undefined) -} - -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. - -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. - -createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') -createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+') - -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. - -createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*') - -// ## Main Version -// Three dot-separated numeric identifiers. - -createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + - `(${src[t.NUMERICIDENTIFIER]})\\.` + - `(${src[t.NUMERICIDENTIFIER]})`) - -createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + - `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + - `(${src[t.NUMERICIDENTIFIERLOOSE]})`) - -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. - -createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER] -}|${src[t.NONNUMERICIDENTIFIER]})`) - -createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE] -}|${src[t.NONNUMERICIDENTIFIER]})`) - -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. - -createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] -}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`) - -createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] -}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`) - -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. - -createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+') - -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. - -createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] -}(?:\\.${src[t.BUILDIDENTIFIER]})*))`) - -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. - -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. - -createToken('FULLPLAIN', `v?${src[t.MAINVERSION] -}${src[t.PRERELEASE]}?${ - src[t.BUILD]}?`) - -createToken('FULL', `^${src[t.FULLPLAIN]}$`) - -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] -}${src[t.PRERELEASELOOSE]}?${ - src[t.BUILD]}?`) - -createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`) - -createToken('GTLT', '((?:<|>)?=?)') - -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`) -createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`) - -createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + - `(?:${src[t.PRERELEASE]})?${ - src[t.BUILD]}?` + - `)?)?`) - -createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:${src[t.PRERELEASELOOSE]})?${ - src[t.BUILD]}?` + - `)?)?`) - -createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`) -createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) - -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -createToken('COERCE', `${'(^|[^\\d])' + - '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:$|[^\\d])`) -createToken('COERCERTL', src[t.COERCE], true) - -// Tilde ranges. -// Meaning is "reasonably at or greater than" -createToken('LONETILDE', '(?:~>?)') - -createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true) -exports.tildeTrimReplace = '$1~' - -createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`) -createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`) - -// Caret ranges. -// Meaning is "at least and backwards compatible with" -createToken('LONECARET', '(?:\\^)') - -createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true) -exports.caretTrimReplace = '$1^' - -createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`) -createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`) - -// A simple gt/lt/eq thing, or just "" to indicate "any version" -createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`) -createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`) - -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] -}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true) -exports.comparatorTrimReplace = '$1$2$3' - -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + - `\\s+-\\s+` + - `(${src[t.XRANGEPLAIN]})` + - `\\s*$`) - -createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + - `\\s+-\\s+` + - `(${src[t.XRANGEPLAINLOOSE]})` + - `\\s*$`) - -// Star ranges basically just allow anything at all. -createToken('STAR', '(<|>)?=?\\s*\\*') -// >=0.0.0 is like a star -createToken('GTE0', '^\\s*>=\\s*0\.0\.0\\s*$') -createToken('GTE0PRE', '^\\s*>=\\s*0\.0\.0-0\\s*$') - - -/***/ }), - -/***/ 1196: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -// A linked list to keep track of recently-used-ness -const Yallist = __nccwpck_require__(220) - -const MAX = Symbol('max') -const LENGTH = Symbol('length') -const LENGTH_CALCULATOR = Symbol('lengthCalculator') -const ALLOW_STALE = Symbol('allowStale') -const MAX_AGE = Symbol('maxAge') -const DISPOSE = Symbol('dispose') -const NO_DISPOSE_ON_SET = Symbol('noDisposeOnSet') -const LRU_LIST = Symbol('lruList') -const CACHE = Symbol('cache') -const UPDATE_AGE_ON_GET = Symbol('updateAgeOnGet') - -const naiveLength = () => 1 - -// lruList is a yallist where the head is the youngest -// item, and the tail is the oldest. the list contains the Hit -// objects as the entries. -// Each Hit object has a reference to its Yallist.Node. This -// never changes. -// -// cache is a Map (or PseudoMap) that matches the keys to -// the Yallist.Node object. -class LRUCache { - constructor (options) { - if (typeof options === 'number') - options = { max: options } - - if (!options) - options = {} - - if (options.max && (typeof options.max !== 'number' || options.max < 0)) - throw new TypeError('max must be a non-negative number') - // Kind of weird to have a default max of Infinity, but oh well. - const max = this[MAX] = options.max || Infinity - - const lc = options.length || naiveLength - this[LENGTH_CALCULATOR] = (typeof lc !== 'function') ? naiveLength : lc - this[ALLOW_STALE] = options.stale || false - if (options.maxAge && typeof options.maxAge !== 'number') - throw new TypeError('maxAge must be a number') - this[MAX_AGE] = options.maxAge || 0 - this[DISPOSE] = options.dispose - this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false - this[UPDATE_AGE_ON_GET] = options.updateAgeOnGet || false - this.reset() - } - - // resize the cache when the max changes. - set max (mL) { - if (typeof mL !== 'number' || mL < 0) - throw new TypeError('max must be a non-negative number') - - this[MAX] = mL || Infinity - trim(this) - } - get max () { - return this[MAX] - } - - set allowStale (allowStale) { - this[ALLOW_STALE] = !!allowStale - } - get allowStale () { - return this[ALLOW_STALE] - } - - set maxAge (mA) { - if (typeof mA !== 'number') - throw new TypeError('maxAge must be a non-negative number') - - this[MAX_AGE] = mA - trim(this) - } - get maxAge () { - return this[MAX_AGE] - } - - // resize the cache when the lengthCalculator changes. - set lengthCalculator (lC) { - if (typeof lC !== 'function') - lC = naiveLength - - if (lC !== this[LENGTH_CALCULATOR]) { - this[LENGTH_CALCULATOR] = lC - this[LENGTH] = 0 - this[LRU_LIST].forEach(hit => { - hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key) - this[LENGTH] += hit.length - }) - } - trim(this) - } - get lengthCalculator () { return this[LENGTH_CALCULATOR] } - - get length () { return this[LENGTH] } - get itemCount () { return this[LRU_LIST].length } - - rforEach (fn, thisp) { - thisp = thisp || this - for (let walker = this[LRU_LIST].tail; walker !== null;) { - const prev = walker.prev - forEachStep(this, fn, walker, thisp) - walker = prev - } - } - - forEach (fn, thisp) { - thisp = thisp || this - for (let walker = this[LRU_LIST].head; walker !== null;) { - const next = walker.next - forEachStep(this, fn, walker, thisp) - walker = next - } - } - - keys () { - return this[LRU_LIST].toArray().map(k => k.key) - } - - values () { - return this[LRU_LIST].toArray().map(k => k.value) - } - - reset () { - if (this[DISPOSE] && - this[LRU_LIST] && - this[LRU_LIST].length) { - this[LRU_LIST].forEach(hit => this[DISPOSE](hit.key, hit.value)) - } - - this[CACHE] = new Map() // hash of items by key - this[LRU_LIST] = new Yallist() // list of items in order of use recency - this[LENGTH] = 0 // length of items in the list - } - - dump () { - return this[LRU_LIST].map(hit => - isStale(this, hit) ? false : { - k: hit.key, - v: hit.value, - e: hit.now + (hit.maxAge || 0) - }).toArray().filter(h => h) - } - - dumpLru () { - return this[LRU_LIST] - } - - set (key, value, maxAge) { - maxAge = maxAge || this[MAX_AGE] - - if (maxAge && typeof maxAge !== 'number') - throw new TypeError('maxAge must be a number') - - const now = maxAge ? Date.now() : 0 - const len = this[LENGTH_CALCULATOR](value, key) - - if (this[CACHE].has(key)) { - if (len > this[MAX]) { - del(this, this[CACHE].get(key)) - return false - } - - const node = this[CACHE].get(key) - const item = node.value - - // dispose of the old one before overwriting - // split out into 2 ifs for better coverage tracking - if (this[DISPOSE]) { - if (!this[NO_DISPOSE_ON_SET]) - this[DISPOSE](key, item.value) - } - - item.now = now - item.maxAge = maxAge - item.value = value - this[LENGTH] += len - item.length - item.length = len - this.get(key) - trim(this) - return true - } - - const hit = new Entry(key, value, len, now, maxAge) - - // oversized objects fall out of cache automatically. - if (hit.length > this[MAX]) { - if (this[DISPOSE]) - this[DISPOSE](key, value) - - return false - } - - this[LENGTH] += hit.length - this[LRU_LIST].unshift(hit) - this[CACHE].set(key, this[LRU_LIST].head) - trim(this) - return true - } - - has (key) { - if (!this[CACHE].has(key)) return false - const hit = this[CACHE].get(key).value - return !isStale(this, hit) - } - - get (key) { - return get(this, key, true) - } - - peek (key) { - return get(this, key, false) - } - - pop () { - const node = this[LRU_LIST].tail - if (!node) - return null - - del(this, node) - return node.value - } - - del (key) { - del(this, this[CACHE].get(key)) - } - - load (arr) { - // reset the cache - this.reset() - - const now = Date.now() - // A previous serialized cache has the most recent items first - for (let l = arr.length - 1; l >= 0; l--) { - const hit = arr[l] - const expiresAt = hit.e || 0 - if (expiresAt === 0) - // the item was created without expiration in a non aged cache - this.set(hit.k, hit.v) - else { - const maxAge = expiresAt - now - // dont add already expired items - if (maxAge > 0) { - this.set(hit.k, hit.v, maxAge) - } - } - } - } - - prune () { - this[CACHE].forEach((value, key) => get(this, key, false)) - } -} - -const get = (self, key, doUse) => { - const node = self[CACHE].get(key) - if (node) { - const hit = node.value - if (isStale(self, hit)) { - del(self, node) - if (!self[ALLOW_STALE]) - return undefined - } else { - if (doUse) { - if (self[UPDATE_AGE_ON_GET]) - node.value.now = Date.now() - self[LRU_LIST].unshiftNode(node) - } - } - return hit.value - } -} - -const isStale = (self, hit) => { - if (!hit || (!hit.maxAge && !self[MAX_AGE])) - return false - - const diff = Date.now() - hit.now - return hit.maxAge ? diff > hit.maxAge - : self[MAX_AGE] && (diff > self[MAX_AGE]) -} - -const trim = self => { - if (self[LENGTH] > self[MAX]) { - for (let walker = self[LRU_LIST].tail; - self[LENGTH] > self[MAX] && walker !== null;) { - // We know that we're about to delete this one, and also - // what the next least recently used key will be, so just - // go ahead and set it now. - const prev = walker.prev - del(self, walker) - walker = prev - } - } -} - -const del = (self, node) => { - if (node) { - const hit = node.value - if (self[DISPOSE]) - self[DISPOSE](hit.key, hit.value) - - self[LENGTH] -= hit.length - self[CACHE].delete(hit.key) - self[LRU_LIST].removeNode(node) - } -} - -class Entry { - constructor (key, value, length, now, maxAge) { - this.key = key - this.value = value - this.length = length - this.now = now - this.maxAge = maxAge || 0 - } -} - -const forEachStep = (self, fn, node, thisp) => { - let hit = node.value - if (isStale(self, hit)) { - del(self, node) - if (!self[ALLOW_STALE]) - hit = undefined - } - if (hit) - fn.call(thisp, hit.value, hit.key, self) -} - -module.exports = LRUCache - - -/***/ }), - -/***/ 5327: -/***/ ((module) => { - -"use strict"; - -module.exports = function (Yallist) { - Yallist.prototype[Symbol.iterator] = function* () { - for (let walker = this.head; walker; walker = walker.next) { - yield walker.value - } - } -} - - -/***/ }), - -/***/ 220: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - -module.exports = Yallist - -Yallist.Node = Node -Yallist.create = Yallist - -function Yallist (list) { - var self = this - if (!(self instanceof Yallist)) { - self = new Yallist() - } - - self.tail = null - self.head = null - self.length = 0 - - if (list && typeof list.forEach === 'function') { - list.forEach(function (item) { - self.push(item) - }) - } else if (arguments.length > 0) { - for (var i = 0, l = arguments.length; i < l; i++) { - self.push(arguments[i]) - } - } - - return self -} - -Yallist.prototype.removeNode = function (node) { - if (node.list !== this) { - throw new Error('removing node which does not belong to this list') - } - - var next = node.next - var prev = node.prev - - if (next) { - next.prev = prev - } - - if (prev) { - prev.next = next - } - - if (node === this.head) { - this.head = next - } - if (node === this.tail) { - this.tail = prev - } - - node.list.length-- - node.next = null - node.prev = null - node.list = null - - return next -} - -Yallist.prototype.unshiftNode = function (node) { - if (node === this.head) { - return - } - - if (node.list) { - node.list.removeNode(node) - } - - var head = this.head - node.list = this - node.next = head - if (head) { - head.prev = node - } - - this.head = node - if (!this.tail) { - this.tail = node - } - this.length++ -} - -Yallist.prototype.pushNode = function (node) { - if (node === this.tail) { - return - } - - if (node.list) { - node.list.removeNode(node) - } - - var tail = this.tail - node.list = this - node.prev = tail - if (tail) { - tail.next = node - } - - this.tail = node - if (!this.head) { - this.head = node - } - this.length++ -} - -Yallist.prototype.push = function () { - for (var i = 0, l = arguments.length; i < l; i++) { - push(this, arguments[i]) - } - return this.length -} - -Yallist.prototype.unshift = function () { - for (var i = 0, l = arguments.length; i < l; i++) { - unshift(this, arguments[i]) - } - return this.length -} - -Yallist.prototype.pop = function () { - if (!this.tail) { - return undefined - } - - var res = this.tail.value - this.tail = this.tail.prev - if (this.tail) { - this.tail.next = null - } else { - this.head = null - } - this.length-- - return res -} - -Yallist.prototype.shift = function () { - if (!this.head) { - return undefined - } - - var res = this.head.value - this.head = this.head.next - if (this.head) { - this.head.prev = null - } else { - this.tail = null - } - this.length-- - return res -} - -Yallist.prototype.forEach = function (fn, thisp) { - thisp = thisp || this - for (var walker = this.head, i = 0; walker !== null; i++) { - fn.call(thisp, walker.value, i, this) - walker = walker.next - } -} - -Yallist.prototype.forEachReverse = function (fn, thisp) { - thisp = thisp || this - for (var walker = this.tail, i = this.length - 1; walker !== null; i--) { - fn.call(thisp, walker.value, i, this) - walker = walker.prev - } -} - -Yallist.prototype.get = function (n) { - for (var i = 0, walker = this.head; walker !== null && i < n; i++) { - // abort out of the list early if we hit a cycle - walker = walker.next - } - if (i === n && walker !== null) { - return walker.value - } -} - -Yallist.prototype.getReverse = function (n) { - for (var i = 0, walker = this.tail; walker !== null && i < n; i++) { - // abort out of the list early if we hit a cycle - walker = walker.prev - } - if (i === n && walker !== null) { - return walker.value - } -} - -Yallist.prototype.map = function (fn, thisp) { - thisp = thisp || this - var res = new Yallist() - for (var walker = this.head; walker !== null;) { - res.push(fn.call(thisp, walker.value, this)) - walker = walker.next - } - return res -} - -Yallist.prototype.mapReverse = function (fn, thisp) { - thisp = thisp || this - var res = new Yallist() - for (var walker = this.tail; walker !== null;) { - res.push(fn.call(thisp, walker.value, this)) - walker = walker.prev - } - return res -} - -Yallist.prototype.reduce = function (fn, initial) { - var acc - var walker = this.head - if (arguments.length > 1) { - acc = initial - } else if (this.head) { - walker = this.head.next - acc = this.head.value - } else { - throw new TypeError('Reduce of empty list with no initial value') - } - - for (var i = 0; walker !== null; i++) { - acc = fn(acc, walker.value, i) - walker = walker.next - } - - return acc -} - -Yallist.prototype.reduceReverse = function (fn, initial) { - var acc - var walker = this.tail - if (arguments.length > 1) { - acc = initial - } else if (this.tail) { - walker = this.tail.prev - acc = this.tail.value - } else { - throw new TypeError('Reduce of empty list with no initial value') - } - - for (var i = this.length - 1; walker !== null; i--) { - acc = fn(acc, walker.value, i) - walker = walker.prev - } - - return acc -} - -Yallist.prototype.toArray = function () { - var arr = new Array(this.length) - for (var i = 0, walker = this.head; walker !== null; i++) { - arr[i] = walker.value - walker = walker.next - } - return arr -} - -Yallist.prototype.toArrayReverse = function () { - var arr = new Array(this.length) - for (var i = 0, walker = this.tail; walker !== null; i++) { - arr[i] = walker.value - walker = walker.prev - } - return arr -} - -Yallist.prototype.slice = function (from, to) { - to = to || this.length - if (to < 0) { - to += this.length - } - from = from || 0 - if (from < 0) { - from += this.length - } - var ret = new Yallist() - if (to < from || to < 0) { - return ret - } - if (from < 0) { - from = 0 - } - if (to > this.length) { - to = this.length - } - for (var i = 0, walker = this.head; walker !== null && i < from; i++) { - walker = walker.next - } - for (; walker !== null && i < to; i++, walker = walker.next) { - ret.push(walker.value) - } - return ret -} - -Yallist.prototype.sliceReverse = function (from, to) { - to = to || this.length - if (to < 0) { - to += this.length - } - from = from || 0 - if (from < 0) { - from += this.length - } - var ret = new Yallist() - if (to < from || to < 0) { - return ret - } - if (from < 0) { - from = 0 - } - if (to > this.length) { - to = this.length - } - for (var i = this.length, walker = this.tail; walker !== null && i > to; i--) { - walker = walker.prev - } - for (; walker !== null && i > from; i--, walker = walker.prev) { - ret.push(walker.value) - } - return ret -} - -Yallist.prototype.splice = function (start, deleteCount, ...nodes) { - if (start > this.length) { - start = this.length - 1 - } - if (start < 0) { - start = this.length + start; - } - - for (var i = 0, walker = this.head; walker !== null && i < start; i++) { - walker = walker.next - } - - var ret = [] - for (var i = 0; walker && i < deleteCount; i++) { - ret.push(walker.value) - walker = this.removeNode(walker) - } - if (walker === null) { - walker = this.tail - } - - if (walker !== this.head && walker !== this.tail) { - walker = walker.prev - } - - for (var i = 0; i < nodes.length; i++) { - walker = insert(this, walker, nodes[i]) - } - return ret; -} - -Yallist.prototype.reverse = function () { - var head = this.head - var tail = this.tail - for (var walker = head; walker !== null; walker = walker.prev) { - var p = walker.prev - walker.prev = walker.next - walker.next = p - } - this.head = tail - this.tail = head - return this -} - -function insert (self, node, value) { - var inserted = node === self.head ? - new Node(value, null, node, self) : - new Node(value, node, node.next, self) - - if (inserted.next === null) { - self.tail = inserted - } - if (inserted.prev === null) { - self.head = inserted - } - - self.length++ - - return inserted -} - -function push (self, item) { - self.tail = new Node(item, self.tail, null, self) - if (!self.head) { - self.head = self.tail - } - self.length++ -} - -function unshift (self, item) { - self.head = new Node(item, null, self.head, self) - if (!self.tail) { - self.tail = self.head - } - self.length++ -} - -function Node (value, prev, next, list) { - if (!(this instanceof Node)) { - return new Node(value, prev, next, list) - } - - this.list = list - this.value = value - - if (prev) { - prev.next = this - this.prev = prev - } else { - this.prev = null - } - - if (next) { - next.prev = this - this.next = next - } else { - this.next = null - } -} - -try { - // add if support for Symbol.iterator is present - __nccwpck_require__(5327)(Yallist) -} catch (er) {} - - /***/ }), /***/ 4294: diff --git a/.github/actions/reopenIssueWithComment/index.js b/.github/actions/reopenIssueWithComment/index.js index a8ac89842010..26f9a663122e 100644 --- a/.github/actions/reopenIssueWithComment/index.js +++ b/.github/actions/reopenIssueWithComment/index.js @@ -53,8 +53,6 @@ reopenIssueWithComment() const _ = __nccwpck_require__(4987); const lodashGet = __nccwpck_require__(6908); -const semverParse = __nccwpck_require__(5925); -const semverSatisfies = __nccwpck_require__(6055); const GITHUB_OWNER = 'Expensify'; const EXPENSIFY_CASH_REPO = 'Expensify.cash'; @@ -115,11 +113,6 @@ class GithubUtils { try { const versionRegex = new RegExp('([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9]+))?', 'g'); const tag = issue.body.match(versionRegex)[0].replace(/`/g, ''); - - // eslint-disable-next-line max-len - const compareURLRegex = new RegExp(`${EXPENSIFY_CASH_URL}/compare/${versionRegex.source}\\.\\.\\.${versionRegex.source}`, 'g'); - const comparisonURL = issue.body.match(compareURLRegex)[0]; - return { title: issue.title, url: issue.url, @@ -127,7 +120,6 @@ class GithubUtils { PRList: this.getStagingDeployCashPRList(issue), deployBlockers: this.getStagingDeployCashDeployBlockers(issue), tag, - comparisonURL, }; } catch (exception) { throw new Error(`Unable to find ${STAGING_DEPLOY_CASH_LABEL} issue with correct data.`); @@ -202,71 +194,6 @@ class GithubUtils { ); } - /** - * Generate a comparison URL between two versions following the semverLevel passed - * - * @param {String} repoSlug - The slug of the repository: / - * @param {String} tag - The tag to compare first the previous semverLevel - * @param {String} semverLevel - The semantic versioning MAJOR, MINOR, PATCH and BUILD - * @return {Promise} the url generated - * @throws {Error} If the request to the Github API fails. - */ - generateVersionComparisonURL(repoSlug, tag, semverLevel) { - return new Promise((resolve, reject) => { - const getComparisonURL = (previousTag, currentTag) => ( - `${EXPENSIFY_CASH_URL}/compare/${previousTag}...${currentTag}` - ); - - const [repoOwner, repoName] = repoSlug.split('/'); - const tagSemver = semverParse(tag); - - return this.octokit.repos.listTags({ - owner: repoOwner, - repo: repoName, - }) - .then(githubResponse => githubResponse.data.some(({name: repoTag}) => { - if (semverLevel === 'MAJOR' - && semverSatisfies(repoTag, `<${tagSemver.major}.x.x`, {includePrerelease: true}) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - - if (semverLevel === 'MINOR' - && semverSatisfies( - repoTag, - `<${tagSemver.major}.${tagSemver.minor}.x`, - {includePrerelease: true}, - ) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - - if (semverLevel === 'PATCH' - && semverSatisfies(repoTag, `<${tagSemver}`, {includePrerelease: true}) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - - if (semverLevel === 'BUILD' - && repoTag !== tagSemver.version - && semverSatisfies( - repoTag, - `<=${tagSemver.major}.${tagSemver.minor}.${tagSemver.patch}`, - {includePrerelease: true}, - ) - ) { - resolve(getComparisonURL(repoTag, tagSemver)); - return true; - } - return false; - })) - .catch(githubError => reject(githubError)); - }); - } - /** * Creates a new StagingDeployCash issue. * @@ -362,22 +289,16 @@ class GithubUtils { deployBlockers = [], resolvedDeployBlockers = [], ) { - return Promise.all([ - this.generateVersionComparisonURL(`${GITHUB_OWNER}/${EXPENSIFY_CASH_REPO}`, tag, 'PATCH'), - this.octokit.pulls.list({ - owner: GITHUB_OWNER, - repo: EXPENSIFY_CASH_REPO, - per_page: 100, - }), - ]) - .then(results => ({ - comparisonURL: results[0], - automergePRs: _.map( - _.filter(results[1].data, GithubUtils.isAutomergePullRequest), + return this.octokit.pulls.list({ + owner: GITHUB_OWNER, + repo: EXPENSIFY_CASH_REPO, + per_page: 100, + }) + .then(({data}) => { + const automergePRs = _.pluck( + _.filter(data, GithubUtils.isAutomergePullRequest), 'html_url', - ), - })) - .then(({comparisonURL, automergePRs}) => { + ); const sortedPRList = _.chain(PRList) .difference(automergePRs) .unique() @@ -389,8 +310,8 @@ class GithubUtils { ); // Tag version and comparison URL - let issueBody = `**Release Version:** \`${tag}\`\r\n`; - issueBody += `**Compare Changes:** ${comparisonURL}\r\n`; + // eslint-disable-next-line max-len + let issueBody = `**Release Version:** \`${tag}\`\r\n**Compare Changes:** https://github.com/Expensify/Expensify.cash/compare/production...staging\r\n`; // PR list if (!_.isEmpty(PRList)) { @@ -411,11 +332,13 @@ class GithubUtils { } issueBody += '\r\ncc @Expensify/applauseleads\r\n'; - return issueBody; }) - // eslint-disable-next-line no-console - .catch(err => console.warn('Error generating comparison URL, continuing...', err)); + .catch(err => console.warn( + 'Error generating StagingDeployCash issue body!', + 'Automerge PRs may not be properly filtered out. Continuing...', + err, + )); } /** @@ -10761,2212 +10684,6 @@ if (!safer.constants) { module.exports = safer -/***/ }), - -/***/ 1532: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const ANY = Symbol('SemVer ANY') -// hoisted class for cyclic dependency -class Comparator { - static get ANY () { - return ANY - } - constructor (comp, options) { - options = parseOptions(options) - - if (comp instanceof Comparator) { - if (comp.loose === !!options.loose) { - return comp - } else { - comp = comp.value - } - } - - debug('comparator', comp, options) - this.options = options - this.loose = !!options.loose - this.parse(comp) - - if (this.semver === ANY) { - this.value = '' - } else { - this.value = this.operator + this.semver.version - } - - debug('comp', this) - } - - parse (comp) { - const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] - const m = comp.match(r) - - if (!m) { - throw new TypeError(`Invalid comparator: ${comp}`) - } - - this.operator = m[1] !== undefined ? m[1] : '' - if (this.operator === '=') { - this.operator = '' - } - - // if it literally is just '>' or '' then allow anything. - if (!m[2]) { - this.semver = ANY - } else { - this.semver = new SemVer(m[2], this.options.loose) - } - } - - toString () { - return this.value - } - - test (version) { - debug('Comparator.test', version, this.options.loose) - - if (this.semver === ANY || version === ANY) { - return true - } - - if (typeof version === 'string') { - try { - version = new SemVer(version, this.options) - } catch (er) { - return false - } - } - - return cmp(version, this.operator, this.semver, this.options) - } - - intersects (comp, options) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required') - } - - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false - } - } - - if (this.operator === '') { - if (this.value === '') { - return true - } - return new Range(comp.value, options).test(this.value) - } else if (comp.operator === '') { - if (comp.value === '') { - return true - } - return new Range(this.value, options).test(comp.semver) - } - - const sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>') - const sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<') - const sameSemVer = this.semver.version === comp.semver.version - const differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<=') - const oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, options) && - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<') - const oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, options) && - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>') - - return ( - sameDirectionIncreasing || - sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || - oppositeDirectionsGreaterThan - ) - } -} - -module.exports = Comparator - -const parseOptions = __nccwpck_require__(785) -const {re, t} = __nccwpck_require__(9523) -const cmp = __nccwpck_require__(5098) -const debug = __nccwpck_require__(427) -const SemVer = __nccwpck_require__(8088) -const Range = __nccwpck_require__(9828) - - -/***/ }), - -/***/ 9828: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -// hoisted class for cyclic dependency -class Range { - constructor (range, options) { - options = parseOptions(options) - - if (range instanceof Range) { - if ( - range.loose === !!options.loose && - range.includePrerelease === !!options.includePrerelease - ) { - return range - } else { - return new Range(range.raw, options) - } - } - - if (range instanceof Comparator) { - // just put it in the set and return - this.raw = range.value - this.set = [[range]] - this.format() - return this - } - - this.options = options - this.loose = !!options.loose - this.includePrerelease = !!options.includePrerelease - - // First, split based on boolean or || - this.raw = range - this.set = range - .split(/\s*\|\|\s*/) - // map the range to a 2d array of comparators - .map(range => this.parseRange(range.trim())) - // throw out any comparator lists that are empty - // this generally means that it was not a valid range, which is allowed - // in loose mode, but will still throw if the WHOLE range is invalid. - .filter(c => c.length) - - if (!this.set.length) { - throw new TypeError(`Invalid SemVer Range: ${range}`) - } - - // if we have any that are not the null set, throw out null sets. - if (this.set.length > 1) { - // keep the first one, in case they're all null sets - const first = this.set[0] - this.set = this.set.filter(c => !isNullSet(c[0])) - if (this.set.length === 0) - this.set = [first] - else if (this.set.length > 1) { - // if we have any that are *, then the range is just * - for (const c of this.set) { - if (c.length === 1 && isAny(c[0])) { - this.set = [c] - break - } - } - } - } - - this.format() - } - - format () { - this.range = this.set - .map((comps) => { - return comps.join(' ').trim() - }) - .join('||') - .trim() - return this.range - } - - toString () { - return this.range - } - - parseRange (range) { - range = range.trim() - - // memoize range parsing for performance. - // this is a very hot path, and fully deterministic. - const memoOpts = Object.keys(this.options).join(',') - const memoKey = `parseRange:${memoOpts}:${range}` - const cached = cache.get(memoKey) - if (cached) - return cached - - const loose = this.options.loose - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] - range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) - debug('hyphen replace', range) - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) - debug('comparator trim', range, re[t.COMPARATORTRIM]) - - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[t.TILDETRIM], tildeTrimReplace) - - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[t.CARETTRIM], caretTrimReplace) - - // normalize spaces - range = range.split(/\s+/).join(' ') - - // At this point, the range is completely trimmed and - // ready to be split into comparators. - - const compRe = loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] - const rangeList = range - .split(' ') - .map(comp => parseComparator(comp, this.options)) - .join(' ') - .split(/\s+/) - // >=0.0.0 is equivalent to * - .map(comp => replaceGTE0(comp, this.options)) - // in loose mode, throw out any that are not valid comparators - .filter(this.options.loose ? comp => !!comp.match(compRe) : () => true) - .map(comp => new Comparator(comp, this.options)) - - // if any comparators are the null set, then replace with JUST null set - // if more than one comparator, remove any * comparators - // also, don't include the same comparator more than once - const l = rangeList.length - const rangeMap = new Map() - for (const comp of rangeList) { - if (isNullSet(comp)) - return [comp] - rangeMap.set(comp.value, comp) - } - if (rangeMap.size > 1 && rangeMap.has('')) - rangeMap.delete('') - - const result = [...rangeMap.values()] - cache.set(memoKey, result) - return result - } - - intersects (range, options) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required') - } - - return this.set.some((thisComparators) => { - return ( - isSatisfiable(thisComparators, options) && - range.set.some((rangeComparators) => { - return ( - isSatisfiable(rangeComparators, options) && - thisComparators.every((thisComparator) => { - return rangeComparators.every((rangeComparator) => { - return thisComparator.intersects(rangeComparator, options) - }) - }) - ) - }) - ) - }) - } - - // if ANY of the sets match ALL of its comparators, then pass - test (version) { - if (!version) { - return false - } - - if (typeof version === 'string') { - try { - version = new SemVer(version, this.options) - } catch (er) { - return false - } - } - - for (let i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version, this.options)) { - return true - } - } - return false - } -} -module.exports = Range - -const LRU = __nccwpck_require__(1196) -const cache = new LRU({ max: 1000 }) - -const parseOptions = __nccwpck_require__(785) -const Comparator = __nccwpck_require__(1532) -const debug = __nccwpck_require__(427) -const SemVer = __nccwpck_require__(8088) -const { - re, - t, - comparatorTrimReplace, - tildeTrimReplace, - caretTrimReplace -} = __nccwpck_require__(9523) - -const isNullSet = c => c.value === '<0.0.0-0' -const isAny = c => c.value === '' - -// take a set of comparators and determine whether there -// exists a version which can satisfy it -const isSatisfiable = (comparators, options) => { - let result = true - const remainingComparators = comparators.slice() - let testComparator = remainingComparators.pop() - - while (result && remainingComparators.length) { - result = remainingComparators.every((otherComparator) => { - return testComparator.intersects(otherComparator, options) - }) - - testComparator = remainingComparators.pop() - } - - return result -} - -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -const parseComparator = (comp, options) => { - debug('comp', comp, options) - comp = replaceCarets(comp, options) - debug('caret', comp) - comp = replaceTildes(comp, options) - debug('tildes', comp) - comp = replaceXRanges(comp, options) - debug('xrange', comp) - comp = replaceStars(comp, options) - debug('stars', comp) - return comp -} - -const isX = id => !id || id.toLowerCase() === 'x' || id === '*' - -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 -const replaceTildes = (comp, options) => - comp.trim().split(/\s+/).map((comp) => { - return replaceTilde(comp, options) - }).join(' ') - -const replaceTilde = (comp, options) => { - const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] - return comp.replace(r, (_, M, m, p, pr) => { - debug('tilde', comp, _, M, m, p, pr) - let ret - - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = `>=${M}.0.0 <${+M + 1}.0.0-0` - } else if (isX(p)) { - // ~1.2 == >=1.2.0 <1.3.0-0 - ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0` - } else if (pr) { - debug('replaceTilde pr', pr) - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${+m + 1}.0-0` - } else { - // ~1.2.3 == >=1.2.3 <1.3.0-0 - ret = `>=${M}.${m}.${p - } <${M}.${+m + 1}.0-0` - } - - debug('tilde return', ret) - return ret - }) -} - -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 -// ^1.2.3 --> >=1.2.3 <2.0.0-0 -// ^1.2.0 --> >=1.2.0 <2.0.0-0 -const replaceCarets = (comp, options) => - comp.trim().split(/\s+/).map((comp) => { - return replaceCaret(comp, options) - }).join(' ') - -const replaceCaret = (comp, options) => { - debug('caret', comp, options) - const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET] - const z = options.includePrerelease ? '-0' : '' - return comp.replace(r, (_, M, m, p, pr) => { - debug('caret', comp, _, M, m, p, pr) - let ret - - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0` - } else if (isX(p)) { - if (M === '0') { - ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0` - } else { - ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0` - } - } else if (pr) { - debug('replaceCaret pr', pr) - if (M === '0') { - if (m === '0') { - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${m}.${+p + 1}-0` - } else { - ret = `>=${M}.${m}.${p}-${pr - } <${M}.${+m + 1}.0-0` - } - } else { - ret = `>=${M}.${m}.${p}-${pr - } <${+M + 1}.0.0-0` - } - } else { - debug('no pr') - if (M === '0') { - if (m === '0') { - ret = `>=${M}.${m}.${p - }${z} <${M}.${m}.${+p + 1}-0` - } else { - ret = `>=${M}.${m}.${p - }${z} <${M}.${+m + 1}.0-0` - } - } else { - ret = `>=${M}.${m}.${p - } <${+M + 1}.0.0-0` - } - } - - debug('caret return', ret) - return ret - }) -} - -const replaceXRanges = (comp, options) => { - debug('replaceXRanges', comp, options) - return comp.split(/\s+/).map((comp) => { - return replaceXRange(comp, options) - }).join(' ') -} - -const replaceXRange = (comp, options) => { - comp = comp.trim() - const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE] - return comp.replace(r, (ret, gtlt, M, m, p, pr) => { - debug('xRange', comp, ret, gtlt, M, m, p, pr) - const xM = isX(M) - const xm = xM || isX(m) - const xp = xm || isX(p) - const anyX = xp - - if (gtlt === '=' && anyX) { - gtlt = '' - } - - // if we're including prereleases in the match, then we need - // to fix this to -0, the lowest possible prerelease value - pr = options.includePrerelease ? '-0' : '' - - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0-0' - } else { - // nothing is forbidden - ret = '*' - } - } else if (gtlt && anyX) { - // we know patch is an x, because we have any x at all. - // replace X with 0 - if (xm) { - m = 0 - } - p = 0 - - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - gtlt = '>=' - if (xm) { - M = +M + 1 - m = 0 - p = 0 - } else { - m = +m + 1 - p = 0 - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<' - if (xm) { - M = +M + 1 - } else { - m = +m + 1 - } - } - - if (gtlt === '<') - pr = '-0' - - ret = `${gtlt + M}.${m}.${p}${pr}` - } else if (xm) { - ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0` - } else if (xp) { - ret = `>=${M}.${m}.0${pr - } <${M}.${+m + 1}.0-0` - } - - debug('xRange return', ret) - - return ret - }) -} - -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -const replaceStars = (comp, options) => { - debug('replaceStars', comp, options) - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[t.STAR], '') -} - -const replaceGTE0 = (comp, options) => { - debug('replaceGTE0', comp, options) - return comp.trim() - .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') -} - -// This function is passed to string.replace(re[t.HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0-0 -const hyphenReplace = incPr => ($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) => { - if (isX(fM)) { - from = '' - } else if (isX(fm)) { - from = `>=${fM}.0.0${incPr ? '-0' : ''}` - } else if (isX(fp)) { - from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}` - } else if (fpr) { - from = `>=${from}` - } else { - from = `>=${from}${incPr ? '-0' : ''}` - } - - if (isX(tM)) { - to = '' - } else if (isX(tm)) { - to = `<${+tM + 1}.0.0-0` - } else if (isX(tp)) { - to = `<${tM}.${+tm + 1}.0-0` - } else if (tpr) { - to = `<=${tM}.${tm}.${tp}-${tpr}` - } else if (incPr) { - to = `<${tM}.${tm}.${+tp + 1}-0` - } else { - to = `<=${to}` - } - - return (`${from} ${to}`).trim() -} - -const testSet = (set, version, options) => { - for (let i = 0; i < set.length; i++) { - if (!set[i].test(version)) { - return false - } - } - - if (version.prerelease.length && !options.includePrerelease) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (let i = 0; i < set.length; i++) { - debug(set[i].semver) - if (set[i].semver === Comparator.ANY) { - continue - } - - if (set[i].semver.prerelease.length > 0) { - const allowed = set[i].semver - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) { - return true - } - } - } - - // Version has a -pre, but it's not one of the ones we like. - return false - } - - return true -} - - -/***/ }), - -/***/ 8088: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const debug = __nccwpck_require__(427) -const { MAX_LENGTH, MAX_SAFE_INTEGER } = __nccwpck_require__(2293) -const { re, t } = __nccwpck_require__(9523) - -const parseOptions = __nccwpck_require__(785) -const { compareIdentifiers } = __nccwpck_require__(2463) -class SemVer { - constructor (version, options) { - options = parseOptions(options) - - if (version instanceof SemVer) { - if (version.loose === !!options.loose && - version.includePrerelease === !!options.includePrerelease) { - return version - } else { - version = version.version - } - } else if (typeof version !== 'string') { - throw new TypeError(`Invalid Version: ${version}`) - } - - if (version.length > MAX_LENGTH) { - throw new TypeError( - `version is longer than ${MAX_LENGTH} characters` - ) - } - - debug('SemVer', version, options) - this.options = options - this.loose = !!options.loose - // this isn't actually relevant for versions, but keep it so that we - // don't run into trouble passing this.options around. - this.includePrerelease = !!options.includePrerelease - - const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]) - - if (!m) { - throw new TypeError(`Invalid Version: ${version}`) - } - - this.raw = version - - // these are actually numbers - this.major = +m[1] - this.minor = +m[2] - this.patch = +m[3] - - if (this.major > MAX_SAFE_INTEGER || this.major < 0) { - throw new TypeError('Invalid major version') - } - - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { - throw new TypeError('Invalid minor version') - } - - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { - throw new TypeError('Invalid patch version') - } - - // numberify any prerelease numeric ids - if (!m[4]) { - this.prerelease = [] - } else { - this.prerelease = m[4].split('.').map((id) => { - if (/^[0-9]+$/.test(id)) { - const num = +id - if (num >= 0 && num < MAX_SAFE_INTEGER) { - return num - } - } - return id - }) - } - - this.build = m[5] ? m[5].split('.') : [] - this.format() - } - - format () { - this.version = `${this.major}.${this.minor}.${this.patch}` - if (this.prerelease.length) { - this.version += `-${this.prerelease.join('.')}` - } - return this.version - } - - toString () { - return this.version - } - - compare (other) { - debug('SemVer.compare', this.version, this.options, other) - if (!(other instanceof SemVer)) { - if (typeof other === 'string' && other === this.version) { - return 0 - } - other = new SemVer(other, this.options) - } - - if (other.version === this.version) { - return 0 - } - - return this.compareMain(other) || this.comparePre(other) - } - - compareMain (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - return ( - compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch) - ) - } - - comparePre (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) { - return -1 - } else if (!this.prerelease.length && other.prerelease.length) { - return 1 - } else if (!this.prerelease.length && !other.prerelease.length) { - return 0 - } - - let i = 0 - do { - const a = this.prerelease[i] - const b = other.prerelease[i] - debug('prerelease compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) - } - } while (++i) - } - - compareBuild (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } - - let i = 0 - do { - const a = this.build[i] - const b = other.build[i] - debug('prerelease compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) - } - } while (++i) - } - - // preminor will bump the version up to the next minor release, and immediately - // down to pre-release. premajor and prepatch work the same way. - inc (release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0 - this.patch = 0 - this.minor = 0 - this.major++ - this.inc('pre', identifier) - break - case 'preminor': - this.prerelease.length = 0 - this.patch = 0 - this.minor++ - this.inc('pre', identifier) - break - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0 - this.inc('patch', identifier) - this.inc('pre', identifier) - break - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) { - this.inc('patch', identifier) - } - this.inc('pre', identifier) - break - - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if ( - this.minor !== 0 || - this.patch !== 0 || - this.prerelease.length === 0 - ) { - this.major++ - } - this.minor = 0 - this.patch = 0 - this.prerelease = [] - break - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) { - this.minor++ - } - this.patch = 0 - this.prerelease = [] - break - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) { - this.patch++ - } - this.prerelease = [] - break - // This probably shouldn't be used publicly. - // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) { - this.prerelease = [0] - } else { - let i = this.prerelease.length - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++ - i = -2 - } - } - if (i === -1) { - // didn't increment anything - this.prerelease.push(0) - } - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) { - this.prerelease = [identifier, 0] - } - } else { - this.prerelease = [identifier, 0] - } - } - break - - default: - throw new Error(`invalid increment argument: ${release}`) - } - this.format() - this.raw = this.version - return this - } -} - -module.exports = SemVer - - -/***/ }), - -/***/ 5098: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const eq = __nccwpck_require__(1898) -const neq = __nccwpck_require__(6017) -const gt = __nccwpck_require__(4123) -const gte = __nccwpck_require__(5522) -const lt = __nccwpck_require__(194) -const lte = __nccwpck_require__(7520) - -const cmp = (a, op, b, loose) => { - switch (op) { - case '===': - if (typeof a === 'object') - a = a.version - if (typeof b === 'object') - b = b.version - return a === b - - case '!==': - if (typeof a === 'object') - a = a.version - if (typeof b === 'object') - b = b.version - return a !== b - - case '': - case '=': - case '==': - return eq(a, b, loose) - - case '!=': - return neq(a, b, loose) - - case '>': - return gt(a, b, loose) - - case '>=': - return gte(a, b, loose) - - case '<': - return lt(a, b, loose) - - case '<=': - return lte(a, b, loose) - - default: - throw new TypeError(`Invalid operator: ${op}`) - } -} -module.exports = cmp - - -/***/ }), - -/***/ 4309: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const SemVer = __nccwpck_require__(8088) -const compare = (a, b, loose) => - new SemVer(a, loose).compare(new SemVer(b, loose)) - -module.exports = compare - - -/***/ }), - -/***/ 1898: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const eq = (a, b, loose) => compare(a, b, loose) === 0 -module.exports = eq - - -/***/ }), - -/***/ 4123: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const gt = (a, b, loose) => compare(a, b, loose) > 0 -module.exports = gt - - -/***/ }), - -/***/ 5522: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const gte = (a, b, loose) => compare(a, b, loose) >= 0 -module.exports = gte - - -/***/ }), - -/***/ 194: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const lt = (a, b, loose) => compare(a, b, loose) < 0 -module.exports = lt - - -/***/ }), - -/***/ 7520: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const lte = (a, b, loose) => compare(a, b, loose) <= 0 -module.exports = lte - - -/***/ }), - -/***/ 6017: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const compare = __nccwpck_require__(4309) -const neq = (a, b, loose) => compare(a, b, loose) !== 0 -module.exports = neq - - -/***/ }), - -/***/ 5925: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const {MAX_LENGTH} = __nccwpck_require__(2293) -const { re, t } = __nccwpck_require__(9523) -const SemVer = __nccwpck_require__(8088) - -const parseOptions = __nccwpck_require__(785) -const parse = (version, options) => { - options = parseOptions(options) - - if (version instanceof SemVer) { - return version - } - - if (typeof version !== 'string') { - return null - } - - if (version.length > MAX_LENGTH) { - return null - } - - const r = options.loose ? re[t.LOOSE] : re[t.FULL] - if (!r.test(version)) { - return null - } - - try { - return new SemVer(version, options) - } catch (er) { - return null - } -} - -module.exports = parse - - -/***/ }), - -/***/ 6055: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -const Range = __nccwpck_require__(9828) -const satisfies = (version, range, options) => { - try { - range = new Range(range, options) - } catch (er) { - return false - } - return range.test(version) -} -module.exports = satisfies - - -/***/ }), - -/***/ 2293: -/***/ ((module) => { - -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -const SEMVER_SPEC_VERSION = '2.0.0' - -const MAX_LENGTH = 256 -const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || - /* istanbul ignore next */ 9007199254740991 - -// Max safe segment length for coercion. -const MAX_SAFE_COMPONENT_LENGTH = 16 - -module.exports = { - SEMVER_SPEC_VERSION, - MAX_LENGTH, - MAX_SAFE_INTEGER, - MAX_SAFE_COMPONENT_LENGTH -} - - -/***/ }), - -/***/ 427: -/***/ ((module) => { - -const debug = ( - typeof process === 'object' && - process.env && - process.env.NODE_DEBUG && - /\bsemver\b/i.test(process.env.NODE_DEBUG) -) ? (...args) => console.error('SEMVER', ...args) - : () => {} - -module.exports = debug - - -/***/ }), - -/***/ 2463: -/***/ ((module) => { - -const numeric = /^[0-9]+$/ -const compareIdentifiers = (a, b) => { - const anum = numeric.test(a) - const bnum = numeric.test(b) - - if (anum && bnum) { - a = +a - b = +b - } - - return a === b ? 0 - : (anum && !bnum) ? -1 - : (bnum && !anum) ? 1 - : a < b ? -1 - : 1 -} - -const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a) - -module.exports = { - compareIdentifiers, - rcompareIdentifiers -} - - -/***/ }), - -/***/ 785: -/***/ ((module) => { - -// parse out just the options we care about so we always get a consistent -// obj with keys in a consistent order. -const opts = ['includePrerelease', 'loose', 'rtl'] -const parseOptions = options => - !options ? {} - : typeof options !== 'object' ? { loose: true } - : opts.filter(k => options[k]).reduce((options, k) => { - options[k] = true - return options - }, {}) -module.exports = parseOptions - - -/***/ }), - -/***/ 9523: -/***/ ((module, exports, __nccwpck_require__) => { - -const { MAX_SAFE_COMPONENT_LENGTH } = __nccwpck_require__(2293) -const debug = __nccwpck_require__(427) -exports = module.exports = {} - -// The actual regexps go on exports.re -const re = exports.re = [] -const src = exports.src = [] -const t = exports.t = {} -let R = 0 - -const createToken = (name, value, isGlobal) => { - const index = R++ - debug(index, value) - t[name] = index - src[index] = value - re[index] = new RegExp(value, isGlobal ? 'g' : undefined) -} - -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. - -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. - -createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') -createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+') - -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. - -createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*') - -// ## Main Version -// Three dot-separated numeric identifiers. - -createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + - `(${src[t.NUMERICIDENTIFIER]})\\.` + - `(${src[t.NUMERICIDENTIFIER]})`) - -createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + - `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + - `(${src[t.NUMERICIDENTIFIERLOOSE]})`) - -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. - -createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NUMERICIDENTIFIER] -}|${src[t.NONNUMERICIDENTIFIER]})`) - -createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NUMERICIDENTIFIERLOOSE] -}|${src[t.NONNUMERICIDENTIFIER]})`) - -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. - -createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] -}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`) - -createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] -}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`) - -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. - -createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+') - -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. - -createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] -}(?:\\.${src[t.BUILDIDENTIFIER]})*))`) - -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. - -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. - -createToken('FULLPLAIN', `v?${src[t.MAINVERSION] -}${src[t.PRERELEASE]}?${ - src[t.BUILD]}?`) - -createToken('FULL', `^${src[t.FULLPLAIN]}$`) - -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] -}${src[t.PRERELEASELOOSE]}?${ - src[t.BUILD]}?`) - -createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`) - -createToken('GTLT', '((?:<|>)?=?)') - -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`) -createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`) - -createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + - `(?:${src[t.PRERELEASE]})?${ - src[t.BUILD]}?` + - `)?)?`) - -createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + - `(?:${src[t.PRERELEASELOOSE]})?${ - src[t.BUILD]}?` + - `)?)?`) - -createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`) -createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) - -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -createToken('COERCE', `${'(^|[^\\d])' + - '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + - `(?:$|[^\\d])`) -createToken('COERCERTL', src[t.COERCE], true) - -// Tilde ranges. -// Meaning is "reasonably at or greater than" -createToken('LONETILDE', '(?:~>?)') - -createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true) -exports.tildeTrimReplace = '$1~' - -createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`) -createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`) - -// Caret ranges. -// Meaning is "at least and backwards compatible with" -createToken('LONECARET', '(?:\\^)') - -createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true) -exports.caretTrimReplace = '$1^' - -createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`) -createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`) - -// A simple gt/lt/eq thing, or just "" to indicate "any version" -createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`) -createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`) - -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] -}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true) -exports.comparatorTrimReplace = '$1$2$3' - -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + - `\\s+-\\s+` + - `(${src[t.XRANGEPLAIN]})` + - `\\s*$`) - -createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + - `\\s+-\\s+` + - `(${src[t.XRANGEPLAINLOOSE]})` + - `\\s*$`) - -// Star ranges basically just allow anything at all. -createToken('STAR', '(<|>)?=?\\s*\\*') -// >=0.0.0 is like a star -createToken('GTE0', '^\\s*>=\\s*0\.0\.0\\s*$') -createToken('GTE0PRE', '^\\s*>=\\s*0\.0\.0-0\\s*$') - - -/***/ }), - -/***/ 1196: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - - -// A linked list to keep track of recently-used-ness -const Yallist = __nccwpck_require__(220) - -const MAX = Symbol('max') -const LENGTH = Symbol('length') -const LENGTH_CALCULATOR = Symbol('lengthCalculator') -const ALLOW_STALE = Symbol('allowStale') -const MAX_AGE = Symbol('maxAge') -const DISPOSE = Symbol('dispose') -const NO_DISPOSE_ON_SET = Symbol('noDisposeOnSet') -const LRU_LIST = Symbol('lruList') -const CACHE = Symbol('cache') -const UPDATE_AGE_ON_GET = Symbol('updateAgeOnGet') - -const naiveLength = () => 1 - -// lruList is a yallist where the head is the youngest -// item, and the tail is the oldest. the list contains the Hit -// objects as the entries. -// Each Hit object has a reference to its Yallist.Node. This -// never changes. -// -// cache is a Map (or PseudoMap) that matches the keys to -// the Yallist.Node object. -class LRUCache { - constructor (options) { - if (typeof options === 'number') - options = { max: options } - - if (!options) - options = {} - - if (options.max && (typeof options.max !== 'number' || options.max < 0)) - throw new TypeError('max must be a non-negative number') - // Kind of weird to have a default max of Infinity, but oh well. - const max = this[MAX] = options.max || Infinity - - const lc = options.length || naiveLength - this[LENGTH_CALCULATOR] = (typeof lc !== 'function') ? naiveLength : lc - this[ALLOW_STALE] = options.stale || false - if (options.maxAge && typeof options.maxAge !== 'number') - throw new TypeError('maxAge must be a number') - this[MAX_AGE] = options.maxAge || 0 - this[DISPOSE] = options.dispose - this[NO_DISPOSE_ON_SET] = options.noDisposeOnSet || false - this[UPDATE_AGE_ON_GET] = options.updateAgeOnGet || false - this.reset() - } - - // resize the cache when the max changes. - set max (mL) { - if (typeof mL !== 'number' || mL < 0) - throw new TypeError('max must be a non-negative number') - - this[MAX] = mL || Infinity - trim(this) - } - get max () { - return this[MAX] - } - - set allowStale (allowStale) { - this[ALLOW_STALE] = !!allowStale - } - get allowStale () { - return this[ALLOW_STALE] - } - - set maxAge (mA) { - if (typeof mA !== 'number') - throw new TypeError('maxAge must be a non-negative number') - - this[MAX_AGE] = mA - trim(this) - } - get maxAge () { - return this[MAX_AGE] - } - - // resize the cache when the lengthCalculator changes. - set lengthCalculator (lC) { - if (typeof lC !== 'function') - lC = naiveLength - - if (lC !== this[LENGTH_CALCULATOR]) { - this[LENGTH_CALCULATOR] = lC - this[LENGTH] = 0 - this[LRU_LIST].forEach(hit => { - hit.length = this[LENGTH_CALCULATOR](hit.value, hit.key) - this[LENGTH] += hit.length - }) - } - trim(this) - } - get lengthCalculator () { return this[LENGTH_CALCULATOR] } - - get length () { return this[LENGTH] } - get itemCount () { return this[LRU_LIST].length } - - rforEach (fn, thisp) { - thisp = thisp || this - for (let walker = this[LRU_LIST].tail; walker !== null;) { - const prev = walker.prev - forEachStep(this, fn, walker, thisp) - walker = prev - } - } - - forEach (fn, thisp) { - thisp = thisp || this - for (let walker = this[LRU_LIST].head; walker !== null;) { - const next = walker.next - forEachStep(this, fn, walker, thisp) - walker = next - } - } - - keys () { - return this[LRU_LIST].toArray().map(k => k.key) - } - - values () { - return this[LRU_LIST].toArray().map(k => k.value) - } - - reset () { - if (this[DISPOSE] && - this[LRU_LIST] && - this[LRU_LIST].length) { - this[LRU_LIST].forEach(hit => this[DISPOSE](hit.key, hit.value)) - } - - this[CACHE] = new Map() // hash of items by key - this[LRU_LIST] = new Yallist() // list of items in order of use recency - this[LENGTH] = 0 // length of items in the list - } - - dump () { - return this[LRU_LIST].map(hit => - isStale(this, hit) ? false : { - k: hit.key, - v: hit.value, - e: hit.now + (hit.maxAge || 0) - }).toArray().filter(h => h) - } - - dumpLru () { - return this[LRU_LIST] - } - - set (key, value, maxAge) { - maxAge = maxAge || this[MAX_AGE] - - if (maxAge && typeof maxAge !== 'number') - throw new TypeError('maxAge must be a number') - - const now = maxAge ? Date.now() : 0 - const len = this[LENGTH_CALCULATOR](value, key) - - if (this[CACHE].has(key)) { - if (len > this[MAX]) { - del(this, this[CACHE].get(key)) - return false - } - - const node = this[CACHE].get(key) - const item = node.value - - // dispose of the old one before overwriting - // split out into 2 ifs for better coverage tracking - if (this[DISPOSE]) { - if (!this[NO_DISPOSE_ON_SET]) - this[DISPOSE](key, item.value) - } - - item.now = now - item.maxAge = maxAge - item.value = value - this[LENGTH] += len - item.length - item.length = len - this.get(key) - trim(this) - return true - } - - const hit = new Entry(key, value, len, now, maxAge) - - // oversized objects fall out of cache automatically. - if (hit.length > this[MAX]) { - if (this[DISPOSE]) - this[DISPOSE](key, value) - - return false - } - - this[LENGTH] += hit.length - this[LRU_LIST].unshift(hit) - this[CACHE].set(key, this[LRU_LIST].head) - trim(this) - return true - } - - has (key) { - if (!this[CACHE].has(key)) return false - const hit = this[CACHE].get(key).value - return !isStale(this, hit) - } - - get (key) { - return get(this, key, true) - } - - peek (key) { - return get(this, key, false) - } - - pop () { - const node = this[LRU_LIST].tail - if (!node) - return null - - del(this, node) - return node.value - } - - del (key) { - del(this, this[CACHE].get(key)) - } - - load (arr) { - // reset the cache - this.reset() - - const now = Date.now() - // A previous serialized cache has the most recent items first - for (let l = arr.length - 1; l >= 0; l--) { - const hit = arr[l] - const expiresAt = hit.e || 0 - if (expiresAt === 0) - // the item was created without expiration in a non aged cache - this.set(hit.k, hit.v) - else { - const maxAge = expiresAt - now - // dont add already expired items - if (maxAge > 0) { - this.set(hit.k, hit.v, maxAge) - } - } - } - } - - prune () { - this[CACHE].forEach((value, key) => get(this, key, false)) - } -} - -const get = (self, key, doUse) => { - const node = self[CACHE].get(key) - if (node) { - const hit = node.value - if (isStale(self, hit)) { - del(self, node) - if (!self[ALLOW_STALE]) - return undefined - } else { - if (doUse) { - if (self[UPDATE_AGE_ON_GET]) - node.value.now = Date.now() - self[LRU_LIST].unshiftNode(node) - } - } - return hit.value - } -} - -const isStale = (self, hit) => { - if (!hit || (!hit.maxAge && !self[MAX_AGE])) - return false - - const diff = Date.now() - hit.now - return hit.maxAge ? diff > hit.maxAge - : self[MAX_AGE] && (diff > self[MAX_AGE]) -} - -const trim = self => { - if (self[LENGTH] > self[MAX]) { - for (let walker = self[LRU_LIST].tail; - self[LENGTH] > self[MAX] && walker !== null;) { - // We know that we're about to delete this one, and also - // what the next least recently used key will be, so just - // go ahead and set it now. - const prev = walker.prev - del(self, walker) - walker = prev - } - } -} - -const del = (self, node) => { - if (node) { - const hit = node.value - if (self[DISPOSE]) - self[DISPOSE](hit.key, hit.value) - - self[LENGTH] -= hit.length - self[CACHE].delete(hit.key) - self[LRU_LIST].removeNode(node) - } -} - -class Entry { - constructor (key, value, length, now, maxAge) { - this.key = key - this.value = value - this.length = length - this.now = now - this.maxAge = maxAge || 0 - } -} - -const forEachStep = (self, fn, node, thisp) => { - let hit = node.value - if (isStale(self, hit)) { - del(self, node) - if (!self[ALLOW_STALE]) - hit = undefined - } - if (hit) - fn.call(thisp, hit.value, hit.key, self) -} - -module.exports = LRUCache - - -/***/ }), - -/***/ 5327: -/***/ ((module) => { - -"use strict"; - -module.exports = function (Yallist) { - Yallist.prototype[Symbol.iterator] = function* () { - for (let walker = this.head; walker; walker = walker.next) { - yield walker.value - } - } -} - - -/***/ }), - -/***/ 220: -/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { - -"use strict"; - -module.exports = Yallist - -Yallist.Node = Node -Yallist.create = Yallist - -function Yallist (list) { - var self = this - if (!(self instanceof Yallist)) { - self = new Yallist() - } - - self.tail = null - self.head = null - self.length = 0 - - if (list && typeof list.forEach === 'function') { - list.forEach(function (item) { - self.push(item) - }) - } else if (arguments.length > 0) { - for (var i = 0, l = arguments.length; i < l; i++) { - self.push(arguments[i]) - } - } - - return self -} - -Yallist.prototype.removeNode = function (node) { - if (node.list !== this) { - throw new Error('removing node which does not belong to this list') - } - - var next = node.next - var prev = node.prev - - if (next) { - next.prev = prev - } - - if (prev) { - prev.next = next - } - - if (node === this.head) { - this.head = next - } - if (node === this.tail) { - this.tail = prev - } - - node.list.length-- - node.next = null - node.prev = null - node.list = null - - return next -} - -Yallist.prototype.unshiftNode = function (node) { - if (node === this.head) { - return - } - - if (node.list) { - node.list.removeNode(node) - } - - var head = this.head - node.list = this - node.next = head - if (head) { - head.prev = node - } - - this.head = node - if (!this.tail) { - this.tail = node - } - this.length++ -} - -Yallist.prototype.pushNode = function (node) { - if (node === this.tail) { - return - } - - if (node.list) { - node.list.removeNode(node) - } - - var tail = this.tail - node.list = this - node.prev = tail - if (tail) { - tail.next = node - } - - this.tail = node - if (!this.head) { - this.head = node - } - this.length++ -} - -Yallist.prototype.push = function () { - for (var i = 0, l = arguments.length; i < l; i++) { - push(this, arguments[i]) - } - return this.length -} - -Yallist.prototype.unshift = function () { - for (var i = 0, l = arguments.length; i < l; i++) { - unshift(this, arguments[i]) - } - return this.length -} - -Yallist.prototype.pop = function () { - if (!this.tail) { - return undefined - } - - var res = this.tail.value - this.tail = this.tail.prev - if (this.tail) { - this.tail.next = null - } else { - this.head = null - } - this.length-- - return res -} - -Yallist.prototype.shift = function () { - if (!this.head) { - return undefined - } - - var res = this.head.value - this.head = this.head.next - if (this.head) { - this.head.prev = null - } else { - this.tail = null - } - this.length-- - return res -} - -Yallist.prototype.forEach = function (fn, thisp) { - thisp = thisp || this - for (var walker = this.head, i = 0; walker !== null; i++) { - fn.call(thisp, walker.value, i, this) - walker = walker.next - } -} - -Yallist.prototype.forEachReverse = function (fn, thisp) { - thisp = thisp || this - for (var walker = this.tail, i = this.length - 1; walker !== null; i--) { - fn.call(thisp, walker.value, i, this) - walker = walker.prev - } -} - -Yallist.prototype.get = function (n) { - for (var i = 0, walker = this.head; walker !== null && i < n; i++) { - // abort out of the list early if we hit a cycle - walker = walker.next - } - if (i === n && walker !== null) { - return walker.value - } -} - -Yallist.prototype.getReverse = function (n) { - for (var i = 0, walker = this.tail; walker !== null && i < n; i++) { - // abort out of the list early if we hit a cycle - walker = walker.prev - } - if (i === n && walker !== null) { - return walker.value - } -} - -Yallist.prototype.map = function (fn, thisp) { - thisp = thisp || this - var res = new Yallist() - for (var walker = this.head; walker !== null;) { - res.push(fn.call(thisp, walker.value, this)) - walker = walker.next - } - return res -} - -Yallist.prototype.mapReverse = function (fn, thisp) { - thisp = thisp || this - var res = new Yallist() - for (var walker = this.tail; walker !== null;) { - res.push(fn.call(thisp, walker.value, this)) - walker = walker.prev - } - return res -} - -Yallist.prototype.reduce = function (fn, initial) { - var acc - var walker = this.head - if (arguments.length > 1) { - acc = initial - } else if (this.head) { - walker = this.head.next - acc = this.head.value - } else { - throw new TypeError('Reduce of empty list with no initial value') - } - - for (var i = 0; walker !== null; i++) { - acc = fn(acc, walker.value, i) - walker = walker.next - } - - return acc -} - -Yallist.prototype.reduceReverse = function (fn, initial) { - var acc - var walker = this.tail - if (arguments.length > 1) { - acc = initial - } else if (this.tail) { - walker = this.tail.prev - acc = this.tail.value - } else { - throw new TypeError('Reduce of empty list with no initial value') - } - - for (var i = this.length - 1; walker !== null; i--) { - acc = fn(acc, walker.value, i) - walker = walker.prev - } - - return acc -} - -Yallist.prototype.toArray = function () { - var arr = new Array(this.length) - for (var i = 0, walker = this.head; walker !== null; i++) { - arr[i] = walker.value - walker = walker.next - } - return arr -} - -Yallist.prototype.toArrayReverse = function () { - var arr = new Array(this.length) - for (var i = 0, walker = this.tail; walker !== null; i++) { - arr[i] = walker.value - walker = walker.prev - } - return arr -} - -Yallist.prototype.slice = function (from, to) { - to = to || this.length - if (to < 0) { - to += this.length - } - from = from || 0 - if (from < 0) { - from += this.length - } - var ret = new Yallist() - if (to < from || to < 0) { - return ret - } - if (from < 0) { - from = 0 - } - if (to > this.length) { - to = this.length - } - for (var i = 0, walker = this.head; walker !== null && i < from; i++) { - walker = walker.next - } - for (; walker !== null && i < to; i++, walker = walker.next) { - ret.push(walker.value) - } - return ret -} - -Yallist.prototype.sliceReverse = function (from, to) { - to = to || this.length - if (to < 0) { - to += this.length - } - from = from || 0 - if (from < 0) { - from += this.length - } - var ret = new Yallist() - if (to < from || to < 0) { - return ret - } - if (from < 0) { - from = 0 - } - if (to > this.length) { - to = this.length - } - for (var i = this.length, walker = this.tail; walker !== null && i > to; i--) { - walker = walker.prev - } - for (; walker !== null && i > from; i--, walker = walker.prev) { - ret.push(walker.value) - } - return ret -} - -Yallist.prototype.splice = function (start, deleteCount, ...nodes) { - if (start > this.length) { - start = this.length - 1 - } - if (start < 0) { - start = this.length + start; - } - - for (var i = 0, walker = this.head; walker !== null && i < start; i++) { - walker = walker.next - } - - var ret = [] - for (var i = 0; walker && i < deleteCount; i++) { - ret.push(walker.value) - walker = this.removeNode(walker) - } - if (walker === null) { - walker = this.tail - } - - if (walker !== this.head && walker !== this.tail) { - walker = walker.prev - } - - for (var i = 0; i < nodes.length; i++) { - walker = insert(this, walker, nodes[i]) - } - return ret; -} - -Yallist.prototype.reverse = function () { - var head = this.head - var tail = this.tail - for (var walker = head; walker !== null; walker = walker.prev) { - var p = walker.prev - walker.prev = walker.next - walker.next = p - } - this.head = tail - this.tail = head - return this -} - -function insert (self, node, value) { - var inserted = node === self.head ? - new Node(value, null, node, self) : - new Node(value, node, node.next, self) - - if (inserted.next === null) { - self.tail = inserted - } - if (inserted.prev === null) { - self.head = inserted - } - - self.length++ - - return inserted -} - -function push (self, item) { - self.tail = new Node(item, self.tail, null, self) - if (!self.head) { - self.head = self.tail - } - self.length++ -} - -function unshift (self, item) { - self.head = new Node(item, null, self.head, self) - if (!self.tail) { - self.tail = self.head - } - self.length++ -} - -function Node (value, prev, next, list) { - if (!(this instanceof Node)) { - return new Node(value, prev, next, list) - } - - this.list = list - this.value = value - - if (prev) { - prev.next = this - this.prev = prev - } else { - this.prev = null - } - - if (next) { - next.prev = this - this.next = next - } else { - this.next = null - } -} - -try { - // add if support for Symbol.iterator is present - __nccwpck_require__(5327)(Yallist) -} catch (er) {} - - /***/ }), /***/ 4294: From 39dba0c60acbbac278874f71891fdcc180e6fa0d Mon Sep 17 00:00:00 2001 From: barun1997 Date: Mon, 12 Apr 2021 00:25:43 +0545 Subject: [PATCH 10/15] Make IOUConfirmPage a single page and add creatingIOUTransaction to IOU Onyx --- src/libs/actions/IOU.js | 13 +- src/pages/iou/IOUModal.js | 34 ++-- .../IOUConfirmSplit.js => IOUConfirmPage.js} | 101 +++++++----- .../steps/IOUConfirmPage/IOUConfirmRequest.js | 151 ------------------ src/pages/iou/steps/IOUConfirmPage/index.js | 86 ---------- 5 files changed, 89 insertions(+), 296 deletions(-) rename src/pages/iou/steps/{IOUConfirmPage/IOUConfirmSplit.js => IOUConfirmPage.js} (67%) delete mode 100644 src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js delete mode 100644 src/pages/iou/steps/IOUConfirmPage/index.js diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 1a79e7e55548..6945162127e7 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -27,7 +27,7 @@ function getPreferredCurrency() { function createIOUTransaction({ comment, amount, currency, debtorEmail, }) { - Onyx.merge(ONYXKEYS.IOU, {loading: true}); + Onyx.merge(ONYXKEYS.IOU, {loading: true, creatingIOUTransaction: true, error: false}); let iouReportID = ''; API.CreateIOUTransaction({ comment, @@ -57,10 +57,11 @@ function createIOUTransaction({ Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, getSimplifiedIOUReport(iouReportData)); - Onyx.merge(ONYXKEYS.IOU, {loading: false}); + Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: false}); }) .catch((error) => { - console.debug(`[Report] Failed to populate IOU Collection: ${error.message}`); + Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: true}); + throw new Error(`[Report] Failed to populate IOU Collection: ${error.message}`); }); } @@ -81,7 +82,7 @@ function createIOUSplit({ splits, }) { let reportIDs = []; - Onyx.merge(ONYXKEYS.IOU, {loading: true}); + Onyx.merge(ONYXKEYS.IOU, {loading: true, creatingIOUTransaction: true, error: false}); API.CreateChatReport({ emailList: participants.join(','), }) @@ -116,11 +117,11 @@ function createIOUSplit({ return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, {...reportIOUData}); }) .then(() => { - Onyx.merge(ONYXKEYS.IOU, {loading: false}); + Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: false}); }) .catch((error) => { console.debug(`Error: ${error.message}`); - Onyx.merge(ONYXKEYS.IOU, {loading: false}); + Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: true}); }); } diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 32d1020bda27..63f9872a25c1 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -1,6 +1,5 @@ import React, {Component} from 'react'; import {View, TouchableOpacity} from 'react-native'; -import _ from 'underscore'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import IOUAmountPage from './steps/IOUAmountPage'; @@ -21,16 +20,20 @@ const propTypes = { // Is this new IOU for a single request or group bill split? hasMultipleParticipants: PropTypes.bool, - /* Onyx Props */ - iousReport: PropTypes.objectOf(PropTypes.shape({ - currency: PropTypes.string, - managerEmail: PropTypes.string, - ownerEmail: PropTypes.string, - reportID: PropTypes.number, - transactions: PropTypes.arrayOf(PropTypes.shape({ - transactionID: PropTypes.string, - })), - })).isRequired, + // Holds data related to IOU view state, rather than the underlying IOU data. + iou: PropTypes.shape({ + + // Whether or not the IOU step is loading (creating the IOU Report) + loading: PropTypes.bool, + + // Whether or not transaction creation has started + creatingIOUTransaction: PropTypes.bool, + + // Whether or not transaction creation has resulted to error + error: PropTypes.bool, + }).isRequired, + + }; const defaultProps = { @@ -62,6 +65,8 @@ class IOUModal extends Component { this.state = { currentStepIndex: 0, participants: [], + + // amount is currency in decimal format amount: '', selectedCurrency: 'USD', isAmountPageNextButtonDisabled: true, @@ -74,9 +79,9 @@ class IOUModal extends Component { } componentDidUpdate(prevProps) { - // if the prevProps isn't equivalent to new prop, dismiss the modal - if (!_.isEqual(prevProps.iousReport, this.props.iousReport)) { - return Navigation.dismissModal(); + // Successfully close the modal if transaction creation has ended and theree is no error + if (prevProps.iou.creatingIOUTransaction && !this.props.iou.creatingIOUTransaction && !this.props.iou.error) { + Navigation.dismissModal(); } } @@ -277,4 +282,5 @@ export default withOnyx({ iousReport: { key: ONYXKEYS.COLLECTION.REPORT_IOUS, }, + iou: {key: ONYXKEYS.IOU}, })(IOUModal); diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js b/src/pages/iou/steps/IOUConfirmPage.js similarity index 67% rename from src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js rename to src/pages/iou/steps/IOUConfirmPage.js index 6f7c8f25c1f6..6ccedbf525b7 100644 --- a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmSplit.js +++ b/src/pages/iou/steps/IOUConfirmPage.js @@ -3,16 +3,16 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import {withOnyx} from 'react-native-onyx'; import {TextInput} from 'react-native-gesture-handler'; -import ONYXKEYS from '../../../../ONYXKEYS'; -import styles from '../../../../styles/styles'; -import Text from '../../../../components/Text'; -import themeColors from '../../../../styles/themes/default'; +import ONYXKEYS from '../../../ONYXKEYS'; +import styles from '../../../styles/styles'; +import Text from '../../../components/Text'; +import themeColors from '../../../styles/themes/default'; import { getDisplayOptionFromMyPersonalDetail, getDisplayOptionsFromParticipants, -} from '../../../../libs/OptionsListUtils'; -import ButtonWithLoader from '../../../../components/ButtonWithLoader'; -import OptionsList from '../../../../components/OptionsList'; +} from '../../../libs/OptionsListUtils'; +import ButtonWithLoader from '../../../components/ButtonWithLoader'; +import OptionsList from '../../../components/OptionsList'; const propTypes = { // Callback to inform parent modal of success @@ -24,6 +24,9 @@ const propTypes = { // comment value from IOUModal comment: PropTypes.string, + // Should we request a single or multiple participant selection from user + hasMultipleParticipants: PropTypes.bool.isRequired, + // IOU amount iouAmount: PropTypes.string.isRequired, @@ -76,7 +79,7 @@ const defaultProps = { comment: '', }; -class IOUConfirmSplitPage extends Component { +class IOUConfirmPage extends Component { /** * Returns the sections needed for the OptionsSelector * @@ -86,26 +89,38 @@ class IOUConfirmSplitPage extends Component { getSections() { const sections = []; - const formattedMyPersonalDetails = getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails, - - // convert from cent to bigger form - `$${this.calculateAmount(true) / 100}`); - const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, - `$${this.calculateAmount() / 100}`); - - sections.push({ - title: 'WHO PAID?', - data: formattedMyPersonalDetails, - shouldShow: true, - indexOffset: 0, - }); - sections.push({ - title: 'WHO WAS THERE?', - data: formattedParticipants, - shouldShow: true, - indexOffset: 0, - }); - + if (this.props.hasMultipleParticipants) { + const formattedMyPersonalDetails = getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails, + + // convert from cent to bigger form + `$${this.calculateAmount(true) / 100}`); + const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, + `$${this.calculateAmount() / 100}`); + + sections.push({ + title: 'WHO PAID?', + data: formattedMyPersonalDetails, + shouldShow: true, + indexOffset: 0, + }); + sections.push({ + title: 'WHO WAS THERE?', + data: formattedParticipants, + shouldShow: true, + indexOffset: 0, + }); + } else { + // $ should be replaced by currency symbol once available + const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, + `$${this.props.iouAmount}`); + + sections.push({ + title: 'TO', + data: formattedParticipants, + shouldShow: true, + indexOffset: 0, + }); + } return sections; } @@ -183,9 +198,9 @@ class IOUConfirmSplitPage extends Component { disableArrowKeysActions hideAdditionalOptionStates forceTextUnreadStyle - canSelectMultipleOptions + canSelectMultipleOptions={this.props.hasMultipleParticipants} disableFocusOptions - selectedOptions={this.getAllOptionsAsSelected()} + selectedOptions={this.props.hasMultipleParticipants && this.getAllOptionsAsSelected()} /> @@ -205,11 +220,19 @@ class IOUConfirmSplitPage extends Component { this.props.onConfirm({ - splits: this.getSplits(), - participants: this.getParticipants(), - })} + text={this.props.hasMultipleParticipants ? 'Split' : `Request $${this.props.iouAmount}`} + onClick={() => { + if (this.props.hasMultipleParticipants) { + this.props.onConfirm({ + splits: this.getSplits(), + participants: this.getParticipants(), + }); + } else { + this.props.onConfirm({ + debtorEmail: this.props.participants[0].login, + }); + } + }} /> @@ -217,9 +240,9 @@ class IOUConfirmSplitPage extends Component { } } -IOUConfirmSplitPage.displayName = 'IOUConfirmSplitPage'; -IOUConfirmSplitPage.propTypes = propTypes; -IOUConfirmSplitPage.defaultProps = defaultProps; +IOUConfirmPage.displayName = 'IOUConfirmPage'; +IOUConfirmPage.propTypes = propTypes; +IOUConfirmPage.defaultProps = defaultProps; export default withOnyx({ iou: {key: ONYXKEYS.IOU}, @@ -229,4 +252,4 @@ export default withOnyx({ myPersonalDetails: { key: ONYXKEYS.MY_PERSONAL_DETAILS, }, -})(IOUConfirmSplitPage); +})(IOUConfirmPage); diff --git a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js b/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js deleted file mode 100644 index a57c06aafeb6..000000000000 --- a/src/pages/iou/steps/IOUConfirmPage/IOUConfirmRequest.js +++ /dev/null @@ -1,151 +0,0 @@ -import React, {Component} from 'react'; -import {View, TextInput} from 'react-native'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../../../../ONYXKEYS'; -import styles from '../../../../styles/styles'; -import Text from '../../../../components/Text'; -import ButtonWithLoader from '../../../../components/ButtonWithLoader'; -import themeColors from '../../../../styles/themes/default'; -import {getDisplayOptionsFromParticipants} from '../../../../libs/OptionsListUtils'; -import OptionsList from '../../../../components/OptionsList'; - -const propTypes = { - // Callback to inform parent modal of success - onConfirm: PropTypes.func.isRequired, - - // IOU amount - iouAmount: PropTypes.string.isRequired, - - // callback to update comment from IOUModal - onUpdateComment: PropTypes.func, - - // Selected currency from the user - // remove eslint disable after currency symbol is available - // eslint-disable-next-line react/no-unused-prop-types - selectedCurrency: PropTypes.string.isRequired, - - // comment value from IOUModal - comment: PropTypes.string, - - // Selected participants from IOUMOdal with login - participants: PropTypes.arrayOf(PropTypes.shape({ - login: PropTypes.string.isRequired, - alternateText: PropTypes.string, - hasDraftComment: PropTypes.bool, - icons: PropTypes.arrayOf(PropTypes.string), - searchText: PropTypes.string, - text: PropTypes.string, - keyForList: PropTypes.string, - isPinned: PropTypes.bool, - isUnread: PropTypes.bool, - reportID: PropTypes.number, - participantsList: PropTypes.arrayOf(PropTypes.object), - })).isRequired, - - /* Onyx Props */ - - // The personal details of the person who is logged in - myPersonalDetails: PropTypes.shape({ - - // Display name of the current user from their personal details - displayName: PropTypes.string, - - // Avatar URL of the current user from their personal details - avatar: PropTypes.string, - }).isRequired, - - // Holds data related to IOU view state, rather than the underlying IOU data. - iou: PropTypes.shape({ - - // Whether or not the IOU step is loading (creating the IOU Report) - loading: PropTypes.bool, - }), -}; - -const defaultProps = { - iou: {}, - onUpdateComment: null, - comment: '', -}; - -class IOUConfirmRequestPage extends Component { - /** - * Returns the sections needed for the OptionsSelector - * - * @param {Boolean} maxParticipantsReached - * @returns {Array} - */ - getSections() { - const sections = []; - - // $ should be replaced by currency symbol once available - const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, - `$${this.props.iouAmount}`); - - sections.push({ - title: 'TO', - data: formattedParticipants, - shouldShow: true, - indexOffset: 0, - }); - return sections; - } - - render() { - const sections = this.getSections(); - return ( - - - - - - WHAT'S IT FOR? - - - - - - - - this.props.onConfirm({ - debtorEmail: this.props.participants[0].login, - })} - /> - - - ); - } -} - -IOUConfirmRequestPage.displayName = 'IOUConfirmRequestPage'; -IOUConfirmRequestPage.propTypes = propTypes; -IOUConfirmRequestPage.defaultProps = defaultProps; - -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - myPersonalDetails: { - key: ONYXKEYS.MY_PERSONAL_DETAILS, - }, - user: { - key: ONYXKEYS.USER, - }, -})(IOUConfirmRequestPage); diff --git a/src/pages/iou/steps/IOUConfirmPage/index.js b/src/pages/iou/steps/IOUConfirmPage/index.js deleted file mode 100644 index 0905720f5ae1..000000000000 --- a/src/pages/iou/steps/IOUConfirmPage/index.js +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import ONYXKEYS from '../../../../ONYXKEYS'; -import IOUConfirmRequest from './IOUConfirmRequest'; -import IOUConfirmSplit from './IOUConfirmSplit'; - -const propTypes = { - // Callback to inform parent modal of success - onConfirm: PropTypes.func.isRequired, - - // Selected currency from the user - selectedCurrency: PropTypes.string.isRequired, - - // Should we request a single or multiple participant selection from user - hasMultipleParticipants: PropTypes.bool.isRequired, - - // IOU amount - iouAmount: PropTypes.string.isRequired, - - // optional comment - comment: PropTypes.string, - - // callback to update comment - onUpdateComment: PropTypes.func, - - // Selected participants from IOUMOdal with login - participants: PropTypes.arrayOf(PropTypes.shape({ - login: PropTypes.string.isRequired, - alternateText: PropTypes.string, - hasDraftComment: PropTypes.bool, - icons: PropTypes.arrayOf(PropTypes.string), - searchText: PropTypes.string, - text: PropTypes.string, - keyForList: PropTypes.string, - isPinned: PropTypes.bool, - isUnread: PropTypes.bool, - reportID: PropTypes.number, - })).isRequired, - - /* Onyx Props */ - - // Holds data related to IOU view state, rather than the underlying IOU data. - iou: PropTypes.shape({ - - // Whether or not the IOU step is loading (creating the IOU Report) - loading: PropTypes.bool, - }), -}; - -const defaultProps = { - iou: {}, - comment: '', - onUpdateComment: null, -}; - -const IOUConfirmPage = props => (props.hasMultipleParticipants - ? ( - - ) - : ( - - ) -); - -IOUConfirmPage.displayName = 'IOUConfirmPage'; -IOUConfirmPage.propTypes = propTypes; -IOUConfirmPage.defaultProps = defaultProps; - -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, -})(IOUConfirmPage); From ee4d2451ba9dc7df4207f916ef0622f7a30cb66e Mon Sep 17 00:00:00 2001 From: barun1997 Date: Mon, 12 Apr 2021 01:25:43 +0545 Subject: [PATCH 11/15] Fix styling issues --- src/components/OptionsList.js | 2 +- src/pages/iou/steps/IOUConfirmPage.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/OptionsList.js b/src/components/OptionsList.js index 091b7d6f1b2c..d6f81138c538 100644 --- a/src/components/OptionsList.js +++ b/src/components/OptionsList.js @@ -196,7 +196,7 @@ class OptionsList extends Component { render() { return ( - + {this.props.headerMessage ? ( diff --git a/src/pages/iou/steps/IOUConfirmPage.js b/src/pages/iou/steps/IOUConfirmPage.js index 6ccedbf525b7..9870dbeb216d 100644 --- a/src/pages/iou/steps/IOUConfirmPage.js +++ b/src/pages/iou/steps/IOUConfirmPage.js @@ -191,8 +191,8 @@ class IOUConfirmPage extends Component { render() { const sections = this.getSections(); return ( - - + + Date: Tue, 13 Apr 2021 10:02:55 +0545 Subject: [PATCH 12/15] Resolve comments --- src/components/IOUConfirmationList.js | 248 ++++++++++++++++++++++++ src/libs/API.js | 4 + src/libs/OptionsListUtils.js | 12 +- src/libs/actions/IOU.js | 103 ++++------ src/pages/home/sidebar/SidebarScreen.js | 4 +- src/pages/iou/IOUModal.js | 36 ++-- src/pages/iou/steps/IOUConfirmPage.js | 218 ++------------------- 7 files changed, 332 insertions(+), 293 deletions(-) create mode 100644 src/components/IOUConfirmationList.js diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js new file mode 100644 index 000000000000..91e0b38247f8 --- /dev/null +++ b/src/components/IOUConfirmationList.js @@ -0,0 +1,248 @@ +import React, {Component} from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import {TextInput} from 'react-native-gesture-handler'; +import {withOnyx} from 'react-native-onyx'; +import styles from '../styles/styles'; +import Text from './Text'; +import themeColors from '../styles/themes/default'; +import { + getIOUConfirmationOptionsFromMyPersonalDetail, + getIOUConfirmationOptionsFromParticipants, +} from '../libs/OptionsListUtils'; +import OptionsList from './OptionsList'; +import ButtonWithLoader from './ButtonWithLoader'; +import ONYXKEYS from '../ONYXKEYS'; + +const propTypes = { + // Callback to inform parent modal of success + onConfirm: PropTypes.func.isRequired, + + // callback to update comment from IOUModal + onUpdateComment: PropTypes.func, + + // comment value from IOUModal + comment: PropTypes.string, + + // Should we request a single or multiple participant selection from user + hasMultipleParticipants: PropTypes.bool.isRequired, + + // IOU amount + iouAmount: PropTypes.string.isRequired, + + // Selected currency from the user + // remove eslint disable after currency symbol is available + // eslint-disable-next-line react/no-unused-prop-types + selectedCurrency: PropTypes.string.isRequired, + + // Selected participants from IOUMOdal with login + participants: PropTypes.arrayOf(PropTypes.shape({ + login: PropTypes.string.isRequired, + alternateText: PropTypes.string, + hasDraftComment: PropTypes.bool, + icons: PropTypes.arrayOf(PropTypes.string), + searchText: PropTypes.string, + text: PropTypes.string, + keyForList: PropTypes.string, + isPinned: PropTypes.bool, + isUnread: PropTypes.bool, + reportID: PropTypes.number, + participantsList: PropTypes.arrayOf(PropTypes.object), + })).isRequired, + + /* Onyx Props */ + + // The personal details of the person who is logged in + myPersonalDetails: PropTypes.shape({ + + // Display name of the current user from their personal details + displayName: PropTypes.string, + + // Avatar URL of the current user from their personal details + avatar: PropTypes.string, + + // Primary login of the user + login: PropTypes.string, + }).isRequired, + + // Holds data related to IOU view state, rather than the underlying IOU data. + iou: PropTypes.shape({ + + // Whether or not the IOU step is loading (creating the IOU Report) + loading: PropTypes.bool, + }), +}; + +const defaultProps = { + iou: {}, + onUpdateComment: null, + comment: '', +}; + +class IOUConfirmationList extends Component { + /** + * Returns the sections needed for the OptionsSelector + * + * @param {Boolean} maxParticipantsReached + * @returns {Array} + */ + getSections() { + const sections = []; + + if (this.props.hasMultipleParticipants) { + const formattedMyPersonalDetails = getIOUConfirmationOptionsFromMyPersonalDetail( + this.props.myPersonalDetails, + + // convert from cent to bigger form + `$${this.calculateAmount(true) / 100}`, + ); + const formattedParticipants = getIOUConfirmationOptionsFromParticipants(this.props.participants, + `$${this.calculateAmount() / 100}`); + + sections.push({ + title: 'WHO PAID?', + data: formattedMyPersonalDetails, + shouldShow: true, + indexOffset: 0, + }); + sections.push({ + title: 'WHO WAS THERE?', + data: formattedParticipants, + shouldShow: true, + indexOffset: 0, + }); + } else { + // $ should be replaced by currency symbol once available + const formattedParticipants = getIOUConfirmationOptionsFromParticipants(this.props.participants, + `$${this.props.iouAmount}`); + + sections.push({ + title: 'TO', + data: formattedParticipants, + shouldShow: true, + indexOffset: 0, + }); + } + return sections; + } + + /** + * Gets splits for the transaction + * + * @returns {Array} + */ + getSplits() { + const splits = this.props.participants.map(participant => ({ + email: participant.login, + + // we should send in cents to API + amount: this.calculateAmount(), + })); + + splits.push({ + email: this.props.myPersonalDetails.login, + + // the user is default and we should send in cents to API + amount: this.calculateAmount(true), + }); + return splits; + } + + /** + * Gets participants list for a report + * + * @returns {Array} + */ + getParticipants() { + const participants = this.props.participants.map(participant => participant.login); + participants.push(this.props.myPersonalDetails.login); + return participants; + } + + /** + * Returns selected options with all participant logins + * @returns {Array} + */ + getAllOptionsAsSelected() { + return [...this.props.participants, + getIOUConfirmationOptionsFromMyPersonalDetail(this.props.myPersonalDetails)]; + } + + /** + * Calculates the amount per user + * @param {Boolean} isDefaultUser + * @returns {Number} + */ + calculateAmount(isDefaultUser = false) { + // convert to cents before working with iouAmount to avoid + // javascript subtraction with decimal problem + const iouAmount = Math.round(parseFloat(this.props.iouAmount * 100)); + const totalParticipants = this.props.participants.length + 1; + const amountPerPerson = Math.round(iouAmount / totalParticipants); + const sumAmount = amountPerPerson * totalParticipants; + const difference = iouAmount - sumAmount; + + if (!isDefaultUser) { return amountPerPerson; } + + return iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; + } + + render() { + const sections = this.getSections(); + return ( + + + + + + WHAT'S IT FOR? + + + + + + + + { + if (this.props.hasMultipleParticipants) { + this.props.onConfirm({ + splits: this.getSplits(), + }); + } else { + this.props.onConfirm({}); + } + }} + /> + + + ); + } +} + +IOUConfirmationList.displayName = 'IOUConfirmPage'; +IOUConfirmationList.propTypes = propTypes; +IOUConfirmationList.defaultProps = defaultProps; + +export default withOnyx({ + iou: {key: ONYXKEYS.IOU}, + myPersonalDetails: { + key: ONYXKEYS.MY_PERSONAL_DETAILS, + }, +})(IOUConfirmationList); diff --git a/src/libs/API.js b/src/libs/API.js index 75f825b614ac..8dafae4e8792 100644 --- a/src/libs/API.js +++ b/src/libs/API.js @@ -644,6 +644,8 @@ function GetIOUReport(parameters) { } /** + * Create a new IOUTransaction + * * @param {Object} parameters * @param {String} parameters.comment * @param {Array} parameters.debtorEmail @@ -658,6 +660,8 @@ function CreateIOUTransaction(parameters) { } /** + * Create a new IOU Split + * * @param {Object} parameters * @param {String} parameters.splits * @param {String} parameters.currency diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 3d366b719fa2..c970e7666aa5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -353,13 +353,13 @@ function getNewChatOptions( } /** - * Build the options for the New Chat view + * Build the IOUConfirmation options for showing MyPersonalDetail * * @param {Object} myPersonalDetail * @param {String} amountText * @returns {Array} */ -function getDisplayOptionFromMyPersonalDetail( +function getIOUConfirmationOptionsFromMyPersonalDetail( myPersonalDetail, amountText, ) { @@ -372,13 +372,13 @@ function getDisplayOptionFromMyPersonalDetail( } /** - * Build the options for the New Chat view + * Build the IOUConfirmationOptions for showing participants * * @param {Array} participants * @param {String} amountText * @returns {Array} */ -function getDisplayOptionsFromParticipants( +function getIOUConfirmationOptionsFromParticipants( participants, amountText, ) { return participants.map(participant => ({ @@ -473,6 +473,6 @@ export { getSidebarOptions, getHeaderMessage, getPersonalDetailsForLogins, - getDisplayOptionFromMyPersonalDetail, - getDisplayOptionsFromParticipants, + getIOUConfirmationOptionsFromMyPersonalDetail, + getIOUConfirmationOptionsFromParticipants, }; diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 6945162127e7..2e8aa913223a 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -16,6 +16,36 @@ function getPreferredCurrency() { }, 1600); } +function setNewIOUReportObjects(reportIds) { + return API.Get({ + returnValueList: 'reportStuff', + reportIDList: reportIds, + shouldLoadOptionalKeys: true, + includePinnedReports: true, + }) + .then(({reports}) => _.map(reports, getSimplifiedIOUReport)) + .then((iouReportObjects) => { + const reportIOUData = {}; + + if (iouReportObjects.length === 1) { + const iouReportKey = `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportObjects[0].reportID}`; + return Onyx.merge(iouReportKey, + getSimplifiedIOUReport(iouReportObjects[0])); + } + + _.each(iouReportObjects, (iouReportObject) => { + if (!iouReportObject) { + return; + } + const iouReportKey = `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportObject.reportID}`; + reportIOUData[iouReportKey] = iouReportObject; + }); + return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, {...reportIOUData}); + }) + .catch(() => Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: true})) + .finally(() => Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: false})); +} + /** * Creates IOUSplit Transaction * @param {Object} parameters @@ -28,65 +58,39 @@ function createIOUTransaction({ comment, amount, currency, debtorEmail, }) { Onyx.merge(ONYXKEYS.IOU, {loading: true, creatingIOUTransaction: true, error: false}); - let iouReportID = ''; API.CreateIOUTransaction({ comment, amount, currency, debtorEmail, }) - .then((data) => { - iouReportID = data.reportID; - return iouReportID; - }) - .then(reportID => API.Get({ - returnValueList: 'reportStuff', - reportIDList: reportID, - shouldLoadOptionalKeys: true, - includePinnedReports: true, - })) - .then((response) => { - if (response.jsonCode !== 200) { - throw new Error(response.message); - } - - const iouReportData = response.reports[iouReportID]; - if (!iouReportData) { - throw new Error(`No iouReportData found for reportID ${iouReportID}`); - } - - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportID}`, - getSimplifiedIOUReport(iouReportData)); - Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: false}); - }) - .catch((error) => { - Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: true}); - throw new Error(`[Report] Failed to populate IOU Collection: ${error.message}`); - }); + .then(data => data.reportID) + .then(reportID => setNewIOUReportObjects([reportID])); } /** * Creates IOUSplit Transaction * @param {Object} parameters - * @param {Array} parameters.participants * @param {Array} parameters.splits * @param {String} parameters.comment * @param {String} parameters.amount * @param {String} parameters.currency */ function createIOUSplit({ - participants, comment, amount, currency, splits, }) { - let reportIDs = []; Onyx.merge(ONYXKEYS.IOU, {loading: true, creatingIOUTransaction: true, error: false}); + API.CreateChatReport({ - emailList: participants.join(','), + emailList: splits.map(participant => participant.email).join(','), }) - .then(data => data.reportID) + .then((data) => { + console.debug(data); + return data.reportID; + }) .then(reportID => API.CreateIOUSplit({ splits: JSON.stringify(splits), currency, @@ -94,35 +98,8 @@ function createIOUSplit({ comment, reportID, })) - .then((data) => { - reportIDs = data.reportIDList; - return reportIDs; - }) - .then(reportIDList => API.Get({ - returnValueList: 'reportStuff', - reportIDList, - shouldLoadOptionalKeys: true, - includePinnedReports: true, - })) - .then(({reports}) => _.map(reports, getSimplifiedIOUReport)) - .then((iouReportObjects) => { - const reportIOUData = {}; - _.each(iouReportObjects, (iouReportObject) => { - if (!iouReportObject) { - return; - } - const iouReportKey = `${ONYXKEYS.COLLECTION.REPORT_IOUS}${iouReportObject.reportID}`; - reportIOUData[iouReportKey] = iouReportObject; - }); - return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, {...reportIOUData}); - }) - .then(() => { - Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: false}); - }) - .catch((error) => { - console.debug(`Error: ${error.message}`); - Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: true}); - }); + .then(data => data.reportIDList) + .then(reportIDList => setNewIOUReportObjects(reportIDList)); } export { diff --git a/src/pages/home/sidebar/SidebarScreen.js b/src/pages/home/sidebar/SidebarScreen.js index 358159fbe22f..e55bc68e3e72 100644 --- a/src/pages/home/sidebar/SidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen.js @@ -93,12 +93,12 @@ class SidebarScreen extends Component { { icon: ChatBubble, text: 'New Chat', - onSelected: () => Navigation.navigate(ROUTES.NEW_CHAT), + onSelected: () => Navigation.navigate(ROUTES.IOU_BILL), }, { icon: Users, text: 'New Group', - onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP), + onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST), }, ]} diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 63f9872a25c1..bf16b2746b29 100644 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -22,10 +22,6 @@ const propTypes = { // Holds data related to IOU view state, rather than the underlying IOU data. iou: PropTypes.shape({ - - // Whether or not the IOU step is loading (creating the IOU Report) - loading: PropTypes.bool, - // Whether or not transaction creation has started creatingIOUTransaction: PropTypes.bool, @@ -79,7 +75,7 @@ class IOUModal extends Component { } componentDidUpdate(prevProps) { - // Successfully close the modal if transaction creation has ended and theree is no error + // Successfully close the modal if transaction creation has ended and there is no error if (prevProps.iou.creatingIOUTransaction && !this.props.iou.creatingIOUTransaction && !this.props.iou.error) { Navigation.dismissModal(); } @@ -96,7 +92,7 @@ class IOUModal extends Component { if (currentStepIndex === 1 || currentStepIndex === 2) { return `${this.props.hasMultipleParticipants ? 'Split' : 'Request'} $${this.state.amount}`; } - if (steps[currentStepIndex] === Steps.IOUAmount) { + if (currentStepIndex === 0) { return this.props.hasMultipleParticipants ? 'Split Bill' : 'Request Money'; } return steps[currentStepIndex] || ''; @@ -179,30 +175,34 @@ class IOUModal extends Component { this.setState({selectedCurrency}); } - closeModal() { - Navigation.dismissModal(); - } - - createTransaction({debtorEmail, splits, participants}) { - if (debtorEmail) { - return createIOUTransaction({ + createTransaction({splits}) { + if (splits) { + return createIOUSplit({ comment: this.state.comment, // should send in cents to API amount: this.state.amount * 100, currency: this.state.selectedCurrency, - debtorEmail, - setIsTransactionComplete: this.setIsTransactionComplete, + splits, }); } - return createIOUSplit({ + + console.debug({ comment: this.state.comment, // should send in cents to API amount: this.state.amount * 100, currency: this.state.selectedCurrency, - splits, - participants, + debtorEmail: this.state.participants[0].login, + }); + + return createIOUTransaction({ + comment: this.state.comment, + + // should send in cents to API + amount: this.state.amount * 100, + currency: this.state.selectedCurrency, + debtorEmail: this.state.participants[0].login, }); } diff --git a/src/pages/iou/steps/IOUConfirmPage.js b/src/pages/iou/steps/IOUConfirmPage.js index 9870dbeb216d..650c9e160992 100644 --- a/src/pages/iou/steps/IOUConfirmPage.js +++ b/src/pages/iou/steps/IOUConfirmPage.js @@ -1,18 +1,6 @@ -import React, {Component} from 'react'; -import {View} from 'react-native'; +import React from 'react'; import PropTypes from 'prop-types'; -import {withOnyx} from 'react-native-onyx'; -import {TextInput} from 'react-native-gesture-handler'; -import ONYXKEYS from '../../../ONYXKEYS'; -import styles from '../../../styles/styles'; -import Text from '../../../components/Text'; -import themeColors from '../../../styles/themes/default'; -import { - getDisplayOptionFromMyPersonalDetail, - getDisplayOptionsFromParticipants, -} from '../../../libs/OptionsListUtils'; -import ButtonWithLoader from '../../../components/ButtonWithLoader'; -import OptionsList from '../../../components/OptionsList'; +import IOUConfirmationList from '../../../components/IOUConfirmationList'; const propTypes = { // Callback to inform parent modal of success @@ -50,206 +38,28 @@ const propTypes = { participantsList: PropTypes.arrayOf(PropTypes.object), })).isRequired, - /* Onyx Props */ - - // The personal details of the person who is logged in - myPersonalDetails: PropTypes.shape({ - - // Display name of the current user from their personal details - displayName: PropTypes.string, - - // Avatar URL of the current user from their personal details - avatar: PropTypes.string, - - // Primary login of the user - login: PropTypes.string, - }).isRequired, - - // Holds data related to IOU view state, rather than the underlying IOU data. - iou: PropTypes.shape({ - - // Whether or not the IOU step is loading (creating the IOU Report) - loading: PropTypes.bool, - }), }; const defaultProps = { - iou: {}, onUpdateComment: null, comment: '', }; -class IOUConfirmPage extends Component { - /** - * Returns the sections needed for the OptionsSelector - * - * @param {Boolean} maxParticipantsReached - * @returns {Array} - */ - getSections() { - const sections = []; - - if (this.props.hasMultipleParticipants) { - const formattedMyPersonalDetails = getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails, - - // convert from cent to bigger form - `$${this.calculateAmount(true) / 100}`); - const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, - `$${this.calculateAmount() / 100}`); - - sections.push({ - title: 'WHO PAID?', - data: formattedMyPersonalDetails, - shouldShow: true, - indexOffset: 0, - }); - sections.push({ - title: 'WHO WAS THERE?', - data: formattedParticipants, - shouldShow: true, - indexOffset: 0, - }); - } else { - // $ should be replaced by currency symbol once available - const formattedParticipants = getDisplayOptionsFromParticipants(this.props.participants, - `$${this.props.iouAmount}`); - - sections.push({ - title: 'TO', - data: formattedParticipants, - shouldShow: true, - indexOffset: 0, - }); - } - return sections; - } - - /** - * Gets splits for the transaction - * - * @returns {Array} - */ - getSplits() { - const splits = this.props.participants.map(participant => ({ - email: participant.login, - - // we should send in cents to API - amount: this.calculateAmount(), - })); - - splits.push({ - email: this.props.myPersonalDetails.login, - - // the user is default and we should send in cents to API - amount: this.calculateAmount(true), - }); - return splits; - } - - /** - * Gets participants list for a report - * - * @returns {Array} - */ - getParticipants() { - const participants = this.props.participants.map(participant => participant.login); - participants.push(this.props.myPersonalDetails.login); - return participants; - } - - /** - * Returns selected options with all participant logins - * @returns {Array} - */ - getAllOptionsAsSelected() { - return [...this.props.participants, - getDisplayOptionFromMyPersonalDetail(this.props.myPersonalDetails)]; - } - - /** - * Calculates the amount per user - * @param {Boolean} isDefaultUser - * @returns {Number} - */ - calculateAmount(isDefaultUser = false) { - // convert to cents before working with iouAmount to avoid - // javascript subtraction with decimal problem - const iouAmount = Math.round(parseFloat(this.props.iouAmount * 100)); - const totalParticipants = this.props.participants.length + 1; - - const amountPerPerson = Math.round(iouAmount / totalParticipants); - - const sumAmount = amountPerPerson * totalParticipants; - - const difference = iouAmount - sumAmount; - - if (!isDefaultUser) { return amountPerPerson; } - - return iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; - } +const IOUConfirmPage = props => ( + +); - render() { - const sections = this.getSections(); - return ( - - - - - - WHAT'S IT FOR? - - - - - - - - { - if (this.props.hasMultipleParticipants) { - this.props.onConfirm({ - splits: this.getSplits(), - participants: this.getParticipants(), - }); - } else { - this.props.onConfirm({ - debtorEmail: this.props.participants[0].login, - }); - } - }} - /> - - - ); - } -} IOUConfirmPage.displayName = 'IOUConfirmPage'; IOUConfirmPage.propTypes = propTypes; IOUConfirmPage.defaultProps = defaultProps; -export default withOnyx({ - iou: {key: ONYXKEYS.IOU}, - personalDetails: { - key: ONYXKEYS.PERSONAL_DETAILS, - }, - myPersonalDetails: { - key: ONYXKEYS.MY_PERSONAL_DETAILS, - }, -})(IOUConfirmPage); +export default IOUConfirmPage; From f8be3195133d59d59bea9829159818b986b175b3 Mon Sep 17 00:00:00 2001 From: barun1997 Date: Tue, 13 Apr 2021 10:05:18 +0545 Subject: [PATCH 13/15] Revert Sidebarscreen typo --- src/pages/home/sidebar/SidebarScreen.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/sidebar/SidebarScreen.js b/src/pages/home/sidebar/SidebarScreen.js index e55bc68e3e72..358159fbe22f 100644 --- a/src/pages/home/sidebar/SidebarScreen.js +++ b/src/pages/home/sidebar/SidebarScreen.js @@ -93,12 +93,12 @@ class SidebarScreen extends Component { { icon: ChatBubble, text: 'New Chat', - onSelected: () => Navigation.navigate(ROUTES.IOU_BILL), + onSelected: () => Navigation.navigate(ROUTES.NEW_CHAT), }, { icon: Users, text: 'New Group', - onSelected: () => Navigation.navigate(ROUTES.IOU_REQUEST), + onSelected: () => Navigation.navigate(ROUTES.NEW_GROUP), }, ]} From 6f8f7575b3cd5608456628e8ad1b491824453cab Mon Sep 17 00:00:00 2001 From: barun1997 Date: Wed, 14 Apr 2021 13:48:04 +0545 Subject: [PATCH 14/15] Make changes according to comments --- src/components/IOUConfirmationList.js | 40 +++++++++++++++------------ src/libs/actions/IOU.js | 2 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/components/IOUConfirmationList.js b/src/components/IOUConfirmationList.js index 91e0b38247f8..d9507ced11e4 100644 --- a/src/components/IOUConfirmationList.js +++ b/src/components/IOUConfirmationList.js @@ -18,10 +18,10 @@ const propTypes = { // Callback to inform parent modal of success onConfirm: PropTypes.func.isRequired, - // callback to update comment from IOUModal + // Callback to update comment from IOUModal onUpdateComment: PropTypes.func, - // comment value from IOUModal + // Comment value from IOUModal comment: PropTypes.string, // Should we request a single or multiple participant selection from user @@ -31,7 +31,7 @@ const propTypes = { iouAmount: PropTypes.string.isRequired, // Selected currency from the user - // remove eslint disable after currency symbol is available + // Remove eslint disable after currency symbol is available // eslint-disable-next-line react/no-unused-prop-types selectedCurrency: PropTypes.string.isRequired, @@ -93,9 +93,12 @@ class IOUConfirmationList extends Component { const formattedMyPersonalDetails = getIOUConfirmationOptionsFromMyPersonalDetail( this.props.myPersonalDetails, - // convert from cent to bigger form + // Convert from cent to bigger form + // USD is temporary and there must be support for other currencies in the future `$${this.calculateAmount(true) / 100}`, ); + + // Cents is temporary and there must be support for other currencies in the future const formattedParticipants = getIOUConfirmationOptionsFromParticipants(this.props.participants, `$${this.calculateAmount() / 100}`); @@ -112,7 +115,7 @@ class IOUConfirmationList extends Component { indexOffset: 0, }); } else { - // $ should be replaced by currency symbol once available + // $ Should be replaced by currency symbol once available const formattedParticipants = getIOUConfirmationOptionsFromParticipants(this.props.participants, `$${this.props.iouAmount}`); @@ -135,14 +138,16 @@ class IOUConfirmationList extends Component { const splits = this.props.participants.map(participant => ({ email: participant.login, - // we should send in cents to API + // We should send in cents to API + // Cents is temporary and there must be support for other currencies in the future amount: this.calculateAmount(), })); splits.push({ email: this.props.myPersonalDetails.login, - // the user is default and we should send in cents to API + // The user is default and we should send in cents to API + // USD is temporary and there must be support for other currencies in the future amount: this.calculateAmount(true), }); return splits; @@ -160,7 +165,7 @@ class IOUConfirmationList extends Component { } /** - * Returns selected options with all participant logins + * Returns selected options with all participant logins -- there is checkmark for every row in List for split flow * @returns {Array} */ getAllOptionsAsSelected() { @@ -174,26 +179,29 @@ class IOUConfirmationList extends Component { * @returns {Number} */ calculateAmount(isDefaultUser = false) { - // convert to cents before working with iouAmount to avoid - // javascript subtraction with decimal problem + // Convert to cents before working with iouAmount to avoid + // javascript subtraction with decimal problem -- when dealing with decimals, + // because they are encoded as IEEE 754 floating point numbers, some of the decimal + // numbers cannot be represented with perfect accuracy. + // Cents is temporary and there must be support for other currencies in the future const iouAmount = Math.round(parseFloat(this.props.iouAmount * 100)); const totalParticipants = this.props.participants.length + 1; const amountPerPerson = Math.round(iouAmount / totalParticipants); - const sumAmount = amountPerPerson * totalParticipants; - const difference = iouAmount - sumAmount; if (!isDefaultUser) { return amountPerPerson; } + const sumAmount = amountPerPerson * totalParticipants; + const difference = iouAmount - sumAmount; + return iouAmount !== sumAmount ? (amountPerPerson + difference) : amountPerPerson; } render() { - const sections = this.getSections(); return ( { if (this.props.hasMultipleParticipants) { - this.props.onConfirm({ - splits: this.getSplits(), - }); + this.props.onConfirm({splits: this.getSplits()}); } else { this.props.onConfirm({}); } diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 2e8aa913223a..cccdedbb8289 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -43,7 +43,7 @@ function setNewIOUReportObjects(reportIds) { return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT_IOUS, {...reportIOUData}); }) .catch(() => Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: true})) - .finally(() => Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false, error: false})); + .finally(() => Onyx.merge(ONYXKEYS.IOU, {loading: false, creatingIOUTransaction: false})); } /** From 90585a2d4a218c2fb900491ae89427420cc71d15 Mon Sep 17 00:00:00 2001 From: barun1997 Date: Wed, 14 Apr 2021 18:18:47 +0545 Subject: [PATCH 15/15] Set function name --- src/libs/actions/IOU.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index cccdedbb8289..f56871642da9 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -16,7 +16,12 @@ function getPreferredCurrency() { }, 1600); } -function setNewIOUReportObjects(reportIds) { +/** + * @param {Array} reportIds + * @returns {Promise} + * Gets the IOU Reports for new transaction + */ +function getIOUReportsForNewTransaction(reportIds) { return API.Get({ returnValueList: 'reportStuff', reportIDList: reportIds, @@ -65,7 +70,7 @@ function createIOUTransaction({ debtorEmail, }) .then(data => data.reportID) - .then(reportID => setNewIOUReportObjects([reportID])); + .then(reportID => getIOUReportsForNewTransaction([reportID])); } /** @@ -99,7 +104,7 @@ function createIOUSplit({ reportID, })) .then(data => data.reportIDList) - .then(reportIDList => setNewIOUReportObjects(reportIDList)); + .then(reportIDList => getIOUReportsForNewTransaction(reportIDList)); } export {