Skip to content

Commit

Permalink
fix: display approximated amount when paying expenses without quote (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
kewitz authored Jan 15, 2025
1 parent ceacb2e commit abe4127
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 23 deletions.
16 changes: 8 additions & 8 deletions components/AmountWithExchangeRateInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -77,17 +76,15 @@ export const formatFxRateInfo = (intl, exchangeRate, { approximateCustomMessage,
);
};

const ContentContainer = styled.div`
white-space: nowrap;
`;

const AmountWithExchangeRateInfo = ({
amount: { exchangeRate, currency, value, valueInCents },
amountClassName,
showCurrencyCode,
invertIconPosition,
warning,
error,
amountWrapperClassName,
currencyCodeClassName,
}) => {
const intl = useIntl();
return (
Expand All @@ -98,16 +95,17 @@ const AmountWithExchangeRateInfo = ({
content={() => formatFxRateInfo(intl, exchangeRate, { warning, error })}
>
<Flex flexWrap="noWrap" alignItems="center" flexDirection={invertIconPosition ? 'row-reverse' : 'row'} gap="4px">
<ContentContainer>
{exchangeRate?.isApproximate && `~ `}
<div className={cn('flex flex-row gap-1 whitespace-nowrap', amountWrapperClassName)}>
<FormattedMoneyAmount
amount={valueInCents ?? Math.round(value * 100)}
currency={currency}
precision={2}
amountClassName={amountClassName || null}
showCurrencyCode={showCurrencyCode}
isApproximate={exchangeRate?.isApproximate}
currencyCodeClassName={currencyCodeClassName}
/>
</ContentContainer>
</div>
<InfoCircle size="1em" className={cn({ 'text-yellow-600': warning, 'text-red-600': error })} />
</Flex>
</StyledTooltip>
Expand All @@ -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,
};
Expand Down
13 changes: 12 additions & 1 deletion components/Currency.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,23 @@ 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;
};

/**
* Shows a money amount with the currency.
*
* ⚠️ 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;
Expand All @@ -39,13 +48,15 @@ const Currency = ({ value, currency, formatWithSeparators = false, precision = 0
const floatAmount = value / 100;
return (
<span className={cn('whitespace-nowrap', className)}>
{isApproximate ? `~` : ''}
{getCurrencySymbol(currency)}
{floatAmount.toLocaleString(locale)}
</span>
);
} else {
return (
<span className={cn('whitespace-nowrap', className)}>
{isApproximate ? `~` : ''}
{formatCurrency(value, currency, { precision, locale })}
</span>
);
Expand Down
4 changes: 4 additions & 0 deletions components/FormattedMoneyAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
Expand All @@ -50,6 +52,7 @@ const FormattedMoneyAmount = ({
amountClassName,
showCurrencyCode = true,
currencyCodeClassName,
isApproximate,
}: FormattedMoneyAmountProps) => {
if (!currency) {
return <span className={cn(DEFAULT_AMOUNT_CLASSES, amountClassName)}>{EMPTY_AMOUNT_PLACEHOLDER}</span>;
Expand All @@ -65,6 +68,7 @@ const FormattedMoneyAmount = ({
precision={precision}
formatWithSeparators={formatWithSeparators}
className={cn(DEFAULT_AMOUNT_CLASSES, amountClassName)}
isApproximate={isApproximate}
/>
);

Expand Down
50 changes: 36 additions & 14 deletions components/expenses/PayExpenseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
};
}
};

Expand Down Expand Up @@ -377,7 +383,7 @@ const getHandleSubmit = (intl, currency, onSubmit) => async values => {
};

type PayExpenseModalProps = {
expense: Expense;
expense: Expense & { amountInHostCurrency?: { valueInCents: number; currency?: string } };
collective: Pick<Account, 'currency'>;
host: Pick<Host, 'plan' | 'slug' | 'currency' | 'transferwise' | 'settings'>;
onClose: () => void;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -657,12 +664,21 @@ const PayExpenseModal = ({ onClose, onSubmit, expense, collective, host, error }
<FormattedMessage id="ExpenseAmount" defaultMessage="Expense amount" />
</Label>
<Amount>
<FormattedMoneyAmount
amount={amounts.expenseAmountInHostCurrency?.valueInCents}
amountClassName="font-medium"
currency={amounts.expenseAmountInHostCurrency?.currency}
currencyCodeClassName="text-muted-foreground"
/>
{amounts.isMultiCurrency ? (
<FormattedMoneyAmount
amount={amounts.amountInExpenseCurrency?.valueInCents}
currency={amounts.amountInExpenseCurrency?.currency}
amountClassName="font-medium"
currencyCodeClassName="text-muted-foreground"
/>
) : (
<FormattedMoneyAmount
amount={amounts.expenseAmountInHostCurrency?.valueInCents}
currency={amounts.expenseAmountInHostCurrency?.currency}
amountClassName="font-medium"
currencyCodeClassName="text-muted-foreground"
/>
)}
</Amount>
</AmountLine>
{expense.taxes?.map(tax => (
Expand Down Expand Up @@ -723,6 +739,12 @@ const PayExpenseModal = ({ onClose, onSubmit, expense, collective, host, error }
<Amount>
{quoteQuery.loading ? (
<LoadingPlaceholder height="16px" />
) : amounts.isMultiCurrency ? (
<AmountWithExchangeRateInfo
amount={amounts.expenseAmountInHostCurrency}
currencyCodeClassName="text-muted-foreground"
amountWrapperClassName="flex flex-row-reverse gap-1"
/>
) : (
<FormattedMoneyAmount
amount={amounts.totalAmount?.valueInCents}
Expand Down
24 changes: 24 additions & 0 deletions components/expenses/graphql/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,18 @@ export const expensePageExpenseFieldsFragment = gql`
toCurrency
}
}
amountInHostCurrency: amountV2(currencySource: HOST) {
valueInCents
currency
exchangeRate {
date
value
source
isApproximate
fromCurrency
toCurrency
}
}
createdAt
invoiceInfo
merchantId
Expand Down Expand Up @@ -667,6 +679,18 @@ export const expensesListFieldsFragment = gql`
toCurrency
}
}
amountInHostCurrency: amountV2(currencySource: HOST) {
valueInCents
currency
exchangeRate {
date
value
source
isApproximate
fromCurrency
toCurrency
}
}
currency
type
requiredLegalDocuments
Expand Down

0 comments on commit abe4127

Please sign in to comment.