From abe4127d5842711ae9817026135fa18b43ec2a95 Mon Sep 17 00:00:00 2001 From: Leo Kewitz Date: Wed, 15 Jan 2025 13:17:55 +0100 Subject: [PATCH] fix: display approximated amount when paying expenses without quote (#10903) --- components/AmountWithExchangeRateInfo.js | 16 ++++---- components/Currency.tsx | 13 +++++- components/FormattedMoneyAmount.tsx | 4 ++ components/expenses/PayExpenseModal.tsx | 50 +++++++++++++++++------- components/expenses/graphql/fragments.ts | 24 ++++++++++++ 5 files changed, 84 insertions(+), 23 deletions(-) diff --git a/components/AmountWithExchangeRateInfo.js b/components/AmountWithExchangeRateInfo.js index d76fa139f8e..1d87c01e878 100644 --- a/components/AmountWithExchangeRateInfo.js +++ b/components/AmountWithExchangeRateInfo.js @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import { InfoCircle } from '@styled-icons/boxicons-regular/InfoCircle'; import { round } from 'lodash'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; -import styled from 'styled-components'; import { AmountPropTypeShape } from '../lib/prop-types'; import { cn } from '../lib/utils'; @@ -77,10 +76,6 @@ export const formatFxRateInfo = (intl, exchangeRate, { approximateCustomMessage, ); }; -const ContentContainer = styled.div` - white-space: nowrap; -`; - const AmountWithExchangeRateInfo = ({ amount: { exchangeRate, currency, value, valueInCents }, amountClassName, @@ -88,6 +83,8 @@ const AmountWithExchangeRateInfo = ({ invertIconPosition, warning, error, + amountWrapperClassName, + currencyCodeClassName, }) => { const intl = useIntl(); return ( @@ -98,16 +95,17 @@ const AmountWithExchangeRateInfo = ({ content={() => formatFxRateInfo(intl, exchangeRate, { warning, error })} > - - {exchangeRate?.isApproximate && `~ `} +
- +
@@ -119,6 +117,8 @@ AmountWithExchangeRateInfo.propTypes = { showCurrencyCode: PropTypes.bool, invertIconPosition: PropTypes.bool, amountClassName: PropTypes.object, + amountWrapperClassName: PropTypes.string, + currencyCodeClassName: PropTypes.string, warning: PropTypes.node, error: PropTypes.node, }; diff --git a/components/Currency.tsx b/components/Currency.tsx index ca99cbc4672..0245f11659f 100644 --- a/components/Currency.tsx +++ b/components/Currency.tsx @@ -16,6 +16,8 @@ type CurrencyProps = { /** How many numbers should we display after the comma. When `auto` is given, decimals are only displayed if necessary. */ precision?: number | 'auto'; className?: string; + /** Whether the amount is approximate, if true amount is prefixed by ~ */ + isApproximate?: boolean; }; /** @@ -23,7 +25,14 @@ type CurrencyProps = { * * ⚠️ Abbreviated mode is only for English at the moment. Abbreviated amount will not be internationalized. */ -const Currency = ({ value, currency, formatWithSeparators = false, precision = 0, className }: CurrencyProps) => { +const Currency = ({ + value, + currency, + formatWithSeparators = false, + precision = 0, + isApproximate = false, + className, +}: CurrencyProps) => { const { locale } = useIntl(); if (precision === 'auto') { precision = value % 100 === 0 ? 0 : 2; @@ -39,6 +48,7 @@ const Currency = ({ value, currency, formatWithSeparators = false, precision = 0 const floatAmount = value / 100; return ( + {isApproximate ? `~` : ''} {getCurrencySymbol(currency)} {floatAmount.toLocaleString(locale)} @@ -46,6 +56,7 @@ const Currency = ({ value, currency, formatWithSeparators = false, precision = 0 } else { return ( + {isApproximate ? `~` : ''} {formatCurrency(value, currency, { precision, locale })} ); diff --git a/components/FormattedMoneyAmount.tsx b/components/FormattedMoneyAmount.tsx index d6ef1d7b88d..71cfb497bb3 100644 --- a/components/FormattedMoneyAmount.tsx +++ b/components/FormattedMoneyAmount.tsx @@ -32,6 +32,8 @@ interface FormattedMoneyAmountProps { amountClassName?: string; /** Classnames for the currency code (eg. `USD`). Doesn't apply on interval */ currencyCodeClassName?: string; + /** Whether the amount is approximate, if true amount is prefixed by ~ */ + isApproximate?: boolean; } const DEFAULT_AMOUNT_CLASSES = ''; @@ -50,6 +52,7 @@ const FormattedMoneyAmount = ({ amountClassName, showCurrencyCode = true, currencyCodeClassName, + isApproximate, }: FormattedMoneyAmountProps) => { if (!currency) { return {EMPTY_AMOUNT_PLACEHOLDER}; @@ -65,6 +68,7 @@ const FormattedMoneyAmount = ({ precision={precision} formatWithSeparators={formatWithSeparators} className={cn(DEFAULT_AMOUNT_CLASSES, amountClassName)} + isApproximate={isApproximate} /> ); diff --git a/components/expenses/PayExpenseModal.tsx b/components/expenses/PayExpenseModal.tsx index 1f7645be715..06776522e2f 100644 --- a/components/expenses/PayExpenseModal.tsx +++ b/components/expenses/PayExpenseModal.tsx @@ -22,6 +22,7 @@ import { i18nTaxType } from '../../lib/i18n/taxes'; import { truncateMiddle } from '../../lib/utils'; import { getAmountWithoutTaxes, getTaxAmount } from './lib/utils'; +import AmountWithExchangeRateInfo from '../AmountWithExchangeRateInfo'; import FormattedMoneyAmount from '../FormattedMoneyAmount'; import { Box, Flex } from '../Grid'; import LoadingPlaceholder from '../LoadingPlaceholder'; @@ -304,15 +305,13 @@ const getInitialValues = (expense, host) => { ...DEFAULT_VALUES, ...getPayoutOptionValue(expense.payoutMethod, true, host), feesPayer: expense.feesPayer || DEFAULT_VALUES.feesPayer, - expenseAmountInHostCurrency: expense.currency === host.currency ? expense.amount : null, + expenseAmountInHostCurrency: + expense.currency === host.currency ? expense.amount : expense.amountInHostCurrency?.valueInCents, }; }; const calculateAmounts = ({ values, expense, quote, host, feesPayer }) => { - const expenseAmountInHostCurrency = { - valueInCents: values.expenseAmountInHostCurrency, - currency: host.currency, - }; + const expenseAmountInHostCurrency = expense.amountInHostCurrency; if (values.forceManual) { const totalAmount = { @@ -343,7 +342,14 @@ const calculateAmounts = ({ values, expense, quote, host, feesPayer }) => { expenseAmountInHostCurrency, }; } else { - return { expenseAmountInHostCurrency, totalAmount: expenseAmountInHostCurrency, paymentProcessorFee: null }; + const isMultiCurrency = expense.currency !== host.currency; + return { + amountInExpenseCurrency: { valueInCents: expense.amount, currency: expense.currency }, + expenseAmountInHostCurrency, + totalAmount: expenseAmountInHostCurrency, + paymentProcessorFee: null, + isMultiCurrency, + }; } }; @@ -377,7 +383,7 @@ const getHandleSubmit = (intl, currency, onSubmit) => async values => { }; type PayExpenseModalProps = { - expense: Expense; + expense: Expense & { amountInHostCurrency?: { valueInCents: number; currency?: string } }; collective: Pick; host: Pick; onClose: () => void; @@ -476,7 +482,8 @@ const PayExpenseModal = ({ onClose, onSubmit, expense, collective, host, error } ...formik.values, ...getPayoutOptionValue(payoutMethodType, item === 'AUTO', host), paymentProcessorFeeInHostCurrency: null, - expenseAmountInHostCurrency: expense.currency === host.currency ? expense.amount : null, + expenseAmountInHostCurrency: + expense.currency === host.currency ? expense.amount : expense.amountInHostCurrency?.valueInCents, feesPayer: !getCanCustomizeFeesPayer(expense, collective, isManualPayment, null) ? DEFAULT_VALUES.feesPayer // Reset fees payer if can't customize : formik.values.feesPayer, @@ -657,12 +664,21 @@ const PayExpenseModal = ({ onClose, onSubmit, expense, collective, host, error } - + {amounts.isMultiCurrency ? ( + + ) : ( + + )} {expense.taxes?.map(tax => ( @@ -723,6 +739,12 @@ const PayExpenseModal = ({ onClose, onSubmit, expense, collective, host, error } {quoteQuery.loading ? ( + ) : amounts.isMultiCurrency ? ( + ) : (