diff --git a/package.json b/package.json index 0d914b77..e68e3107 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,8 @@ "build": "sharetribe-scripts build", "format": "prettier --write '**/*.{js,css}'", "format-ci": "prettier --list-different '**/*.{js,css}'", - "test": "sharetribe-scripts test --env=jsdom", - "test-ci": "sharetribe-scripts test --env=jsdom --runInBand", + "test": "export REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD&&sharetribe-scripts test --env=jsdom", + "test-ci": "export REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD&&sharetribe-scripts test --env=jsdom --runInBand", "eject": "sharetribe-scripts eject", "start": "node server/index.js", "dev-server": "export REACT_APP_ENV=development REACT_APP_CANONICAL_ROOT_URL=http://localhost:4000&&yarn run build&&nodemon --watch server server/index.js", diff --git a/src/components/BookingBreakdown/BookingBreakdown.example.js b/src/components/BookingBreakdown/BookingBreakdown.example.js index 92769f50..83e020bd 100644 --- a/src/components/BookingBreakdown/BookingBreakdown.example.js +++ b/src/components/BookingBreakdown/BookingBreakdown.example.js @@ -1,10 +1,13 @@ import Decimal from 'decimal.js'; import { types } from '../../util/sdkLoader'; import * as propTypes from '../../util/propTypes'; +import config from '../../config'; import BookingBreakdown from './BookingBreakdown'; const { UUID, Money } = types; +const CURRENCY = config.currency; + const exampleBooking = attributes => { return { id: new UUID('example-booking'), @@ -41,14 +44,14 @@ export const Checkout = { props: { userRole: 'customer', transaction: exampleTransaction({ - payinTotal: new Money(9000, 'USD'), - payoutTotal: new Money(9000, 'USD'), + payinTotal: new Money(9000, CURRENCY), + payoutTotal: new Money(9000, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(2), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(9000, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(9000, CURRENCY), reversal: false, }, ], @@ -65,14 +68,14 @@ export const CustomerOrder = { props: { userRole: 'customer', transaction: exampleTransaction({ - payinTotal: new Money(9000, 'USD'), - payoutTotal: new Money(9000, 'USD'), + payinTotal: new Money(9000, CURRENCY), + payoutTotal: new Money(9000, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(2), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(9000, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(9000, CURRENCY), reversal: false, }, ], @@ -89,20 +92,20 @@ export const ProviderSale = { props: { userRole: 'provider', transaction: exampleTransaction({ - payinTotal: new Money(9000, 'USD'), - payoutTotal: new Money(7000, 'USD'), + payinTotal: new Money(9000, CURRENCY), + payoutTotal: new Money(7000, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(2), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(9000, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(9000, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -119,20 +122,20 @@ export const ProviderSaleZeroCommission = { props: { userRole: 'provider', transaction: exampleTransaction({ - payinTotal: new Money(9000, 'USD'), - payoutTotal: new Money(9000, 'USD'), + payinTotal: new Money(9000, CURRENCY), + payoutTotal: new Money(9000, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(2), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(9000, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(9000, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(0, 'USD'), - lineTotal: new Money(0, 'USD'), + unitPrice: new Money(0, CURRENCY), + lineTotal: new Money(0, CURRENCY), reversal: false, }, ], @@ -149,20 +152,20 @@ export const ProviderSaleSingleNight = { props: { userRole: 'provider', transaction: exampleTransaction({ - payinTotal: new Money(4500, 'USD'), - payoutTotal: new Money(2500, 'USD'), + payinTotal: new Money(4500, CURRENCY), + payoutTotal: new Money(2500, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -180,20 +183,20 @@ export const ProviderSalePreauthorized = { userRole: 'provider', transaction: exampleTransaction({ lastTransition: propTypes.TX_TRANSITION_PREAUTHORIZE, - payinTotal: new Money(4500, 'USD'), - payoutTotal: new Money(2500, 'USD'), + payinTotal: new Money(4500, CURRENCY), + payoutTotal: new Money(2500, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -211,20 +214,20 @@ export const ProviderSaleAccepted = { userRole: 'provider', transaction: exampleTransaction({ lastTransition: propTypes.TX_TRANSITION_ACCEPT, - payinTotal: new Money(4500, 'USD'), - payoutTotal: new Money(2500, 'USD'), + payinTotal: new Money(4500, CURRENCY), + payoutTotal: new Money(2500, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -242,20 +245,20 @@ export const ProviderSaleDeclined = { userRole: 'provider', transaction: exampleTransaction({ lastTransition: propTypes.TX_TRANSITION_DECLINE, - payinTotal: new Money(4500, 'USD'), - payoutTotal: new Money(2500, 'USD'), + payinTotal: new Money(4500, CURRENCY), + payoutTotal: new Money(2500, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -273,20 +276,20 @@ export const ProviderSaleAutoDeclined = { userRole: 'provider', transaction: exampleTransaction({ lastTransition: propTypes.TX_TRANSITION_AUTO_DECLINE, - payinTotal: new Money(4500, 'USD'), - payoutTotal: new Money(2500, 'USD'), + payinTotal: new Money(4500, CURRENCY), + payoutTotal: new Money(2500, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -304,20 +307,20 @@ export const ProviderSaleDelivered = { userRole: 'provider', transaction: exampleTransaction({ lastTransition: propTypes.TX_TRANSITION_MARK_DELIVERED, - payinTotal: new Money(4500, 'USD'), - payoutTotal: new Money(2500, 'USD'), + payinTotal: new Money(4500, CURRENCY), + payoutTotal: new Money(2500, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, ], @@ -335,35 +338,35 @@ export const ProviderSaleCanceled = { userRole: 'provider', transaction: exampleTransaction({ lastTransition: propTypes.TX_TRANSITION_CANCEL, - payinTotal: new Money(0, 'USD'), - payoutTotal: new Money(0, 'USD'), + payinTotal: new Money(0, CURRENCY), + payoutTotal: new Money(0, CURRENCY), lineItems: [ { code: 'line-item/night', quantity: new Decimal(1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(4500, CURRENCY), reversal: false, }, { code: 'line-item/night', quantity: new Decimal(-1), - unitPrice: new Money(4500, 'USD'), - lineTotal: new Money(-4500, 'USD'), + unitPrice: new Money(4500, CURRENCY), + lineTotal: new Money(-4500, CURRENCY), reversal: true, }, { code: 'line-item/provider-commission', quantity: new Decimal(1), - unitPrice: new Money(-2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(-2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: false, }, { code: 'line-item/provider-commission', quantity: new Decimal(-1), - unitPrice: new Money(2000, 'USD'), - lineTotal: new Money(-2000, 'USD'), + unitPrice: new Money(2000, CURRENCY), + lineTotal: new Money(-2000, CURRENCY), reversal: true, }, ], diff --git a/src/components/CurrencyInputField/CurrencyInputField.example.js b/src/components/CurrencyInputField/CurrencyInputField.example.js index 440ca64d..252d6184 100644 --- a/src/components/CurrencyInputField/CurrencyInputField.example.js +++ b/src/components/CurrencyInputField/CurrencyInputField.example.js @@ -9,17 +9,22 @@ import { currencyConfig } from '../../util/test-data'; import * as validators from '../../util/validators'; import CurrencyInputField, { CurrencyInput } from './CurrencyInputField'; -const defaultConfig = { +const currencyConfigUSD = { ...currencyConfig, currency: 'USD', }; +const currencyConfigEUR = { + ...currencyConfig, + currency: 'EUR', +}; + // eslint-disable-next-line no-console const onChange = price => console.log('CurrencyInput - value:', price); // Different locales need to be initialized before their currency formatting is in use const CurrencyInputWithIntl = ({ locale, ...rest }) => { - if (locale === 'en-US') { + if (locale === 'en') { addLocaleData([...en]); } else { addLocaleData([...fi]); @@ -38,22 +43,39 @@ CurrencyInputWithIntl.propTypes = { locale: string.isRequired, }; -// Empty field with en-US locale -export const EmptyWithEnUS = { +export const EmptyWithEnUSD = { component: CurrencyInputWithIntl, props: { - currencyConfig: defaultConfig, - locale: 'en-US', + currencyConfig: currencyConfigUSD, + locale: 'en', }, group: 'custom inputs', }; -// Default value with fi-FI locale -export const defaultValueWithFiFI = { +export const defaultValueWithEnUSD = { component: CurrencyInputWithIntl, props: { - currencyConfig: { ...defaultConfig, currency: 'EUR' }, - locale: 'fi-FI', + currencyConfig: currencyConfigUSD, + locale: 'en', + defaultValue: 9999.99, + }, + group: 'custom inputs', +}; + +export const EmptyWithFiEUR = { + component: CurrencyInputWithIntl, + props: { + currencyConfig: currencyConfigEUR, + locale: 'fi', + }, + group: 'custom inputs', +}; + +export const defaultValueWithFiEUR = { + component: CurrencyInputWithIntl, + props: { + currencyConfig: currencyConfigEUR, + locale: 'fi', defaultValue: 9999.99, }, group: 'custom inputs', @@ -71,7 +93,7 @@ const FormComponent = props => { id={`${form}.price`} label="Set price:" placeholder="Type in amount in EUR..." - currencyConfig={{ ...defaultConfig, currency: 'EUR' }} + currencyConfig={currencyConfigEUR} validate={required} /> diff --git a/src/components/CurrencyInputField/CurrencyInputField.js b/src/components/CurrencyInputField/CurrencyInputField.js index a7d83832..5ff34370 100644 --- a/src/components/CurrencyInputField/CurrencyInputField.js +++ b/src/components/CurrencyInputField/CurrencyInputField.js @@ -51,8 +51,15 @@ class CurrencyInputComponent extends Component { constructor(props) { super(props); const { currencyConfig, defaultValue, input, intl } = props; - const initialValue = - input.value instanceof types.Money ? convertMoneyToNumber(input.value) : defaultValue; + const initialValueIsMoney = input.value instanceof types.Money; + + if (initialValueIsMoney && input.value.currency !== currencyConfig.currency) { + const e = new Error('Value currency different from marketplace currency'); + log.error(e, 'currency-input-invalid-currency', { currencyConfig, inputValue: input.value }); + throw e; + } + + const initialValue = initialValueIsMoney ? convertMoneyToNumber(input.value) : defaultValue; const hasInitialValue = typeof initialValue === 'number' && !isNaN(initialValue); // We need to handle number format - some locales use dots and some commas as decimal separator @@ -82,11 +89,7 @@ class CurrencyInputComponent extends Component { usesComma, }; } catch (e) { - log.error(e, 'currency-input-init-failed', { - currencyConfig: currencyConfig, - defaultValue: defaultValue, - initialValue: initialValue, - }); + log.error(e, 'currency-input-init-failed', { currencyConfig, defaultValue, initialValue }); throw e; } diff --git a/src/components/EditListingPricingPanel/EditListingPricingPanel.css b/src/components/EditListingPricingPanel/EditListingPricingPanel.css index 72841e01..f78ec93d 100644 --- a/src/components/EditListingPricingPanel/EditListingPricingPanel.css +++ b/src/components/EditListingPricingPanel/EditListingPricingPanel.css @@ -20,3 +20,7 @@ margin-bottom: 44px; } } + +.priceCurrencyInvalid { + color: var(--failColor); +} diff --git a/src/components/EditListingPricingPanel/EditListingPricingPanel.js b/src/components/EditListingPricingPanel/EditListingPricingPanel.js index e07aab3f..29e94fc6 100644 --- a/src/components/EditListingPricingPanel/EditListingPricingPanel.js +++ b/src/components/EditListingPricingPanel/EditListingPricingPanel.js @@ -6,6 +6,8 @@ import { createSlug } from '../../util/urlHelpers'; import { NamedLink } from '../../components'; import { EditListingPricingForm } from '../../containers'; import { ensureListing } from '../../util/data'; +import { types } from '../../util/sdkLoader'; +import config from '../../config'; import css from './EditListingPricingPanel.css'; @@ -40,19 +42,29 @@ const EditListingPricingPanel = props => { ); + const priceCurrencyValid = + price instanceof types.Money ? price.currency === config.currency : true; + const form = priceCurrencyValid ? ( + + ) : ( +
+ +
+ ); + return (

{panelTitle}

- + {form}
); }; diff --git a/src/translations/en.json b/src/translations/en.json index 35ef6050..d82a0808 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -141,6 +141,7 @@ "EditListingPricingForm.priceRequired": "You need to add a valid price.", "EditListingPricingForm.updateFailed": "Failed to update listing. Please try again.", "EditListingPricingPanel.createListingTitle": "How much does it cost?", + "EditListingPricingPanel.listingPriceCurrencyInvalid": "Listing currency is different from the marketplace currency. You cannot edit the price.", "EditListingPricingPanel.title": "Edit the pricing of {listingTitle}", "EditListingWizard.saveEditDescription": "Save description", "EditListingWizard.saveEditLocation": "Save location", diff --git a/src/util/currency.js b/src/util/currency.js index b1827458..0874c1b8 100644 --- a/src/util/currency.js +++ b/src/util/currency.js @@ -1,7 +1,6 @@ import { has, trimEnd } from 'lodash'; import Decimal from 'decimal.js'; import { types } from './sdkLoader'; -import config from '../config'; import { subUnitDivisors } from './currencyConfig'; // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER @@ -205,9 +204,6 @@ export const convertMoneyToNumber = value => { if (!(value instanceof types.Money)) { throw new Error('Value must be a Money type'); } - if (value.currency !== config.currency) { - throw new Error('Given currency different from marketplace currency'); - } const subUnitDivisorAsDecimal = convertDivisorToDecimal(unitDivisor(value.currency)); let amount; @@ -245,9 +241,6 @@ export const formatMoney = (intl, value) => { if (!(value instanceof types.Money)) { throw new Error('Value must be a Money type'); } - if (value.currency !== config.currency) { - throw new Error('Given currency different from marketplace currency'); - } const valueAsNumber = convertMoneyToNumber(value); // See: https://github.com/yahoo/react-intl/wiki/API#formatnumber diff --git a/src/util/currency.test.js b/src/util/currency.test.js index 7e055fc8..cd357319 100644 --- a/src/util/currency.test.js +++ b/src/util/currency.test.js @@ -238,13 +238,6 @@ describe('currency utils', () => { const value = null; expect(() => formatMoney(intl, value)).toThrowError('Value must be a Money type'); }); - it('complains about mismatching currencies', () => { - const intl = null; - const value = new types.Money(100, 'EUR'); - expect(() => formatMoney(intl, value)).toThrowError( - 'Given currency different from marketplace currency' - ); - }); // No test for that actual formatting for now. It depends on the // locale, and it doesn't really make sense to test the fake intl