Skip to content

Commit

Permalink
Move PaymentMethodLabel to a lib function and add it to OrderForm (i18n)
Browse files Browse the repository at this point in the history
The fact that we use these labels inside `<select/>` options prevent us
from implementing this as a React component as for now React does not
support having components inside `<option/>` tags, even if the component
returns only strings.

[This message](facebook/react#13586 (comment))
explains why its not supported (though it has been in the past) and why
it may not be in a near future.
  • Loading branch information
Betree committed Nov 16, 2018
1 parent f8681d2 commit 4f0fea0
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 147 deletions.
4 changes: 2 additions & 2 deletions src/components/EditPaymentMethod.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { defineMessages, FormattedMessage } from 'react-intl';
import withIntl from '../lib/withIntl';
import InputField from './InputField';
import { getCurrencySymbol, capitalize } from '../lib/utils';
import PaymentMethodLabel from './PaymentMethodLabel';
import { paymentMethodLabelWithIcon } from '../lib/payment_method_label';

class EditPaymentMethod extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -101,7 +101,7 @@ class EditPaymentMethod extends React.Component {
</label>
<Col sm={9}>
<div className="name col">
<PaymentMethodLabel paymentMethod={paymentMethod} />
{paymentMethodLabelWithIcon(intl, paymentMethod)}
</div>
{hasOrders && (
<div className="actions">
Expand Down
49 changes: 3 additions & 46 deletions src/components/OrderForm.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import moment from 'moment';
import PropTypes from 'prop-types';

import { pick, get } from 'lodash';
Expand Down Expand Up @@ -29,6 +28,7 @@ import {
import { getPaypal } from '../lib/paypal';
import { getStripeToken } from '../lib/stripe';
import { checkUserExistence, signin } from '../lib/api';
import { paymentMethodLabelWithIcon } from '../lib/payment_method_label';

class OrderForm extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -329,54 +329,11 @@ class OrderForm extends React.Component {
this.paymentMethodTypeOptions = paymentMethodTypeOptions;
};

paymentMethodName = pm => {
if (pm.type === 'virtualcard') {
return pm.name.replace('card from', 'Gift Card from');
} else {
return `${get(pm, 'data.brand', get(pm, 'type'))} ${pm.name}`;
}
};

paymentMethodExpiration = pm => {
/* The expiryDate field will show up for prepaid cards */
return pm.expiryDate
? `- exp ${moment(pm.expiryDate).format('MM/Y')}`
: get(pm, 'data.expMonth') || get(pm, 'data.expYear')
? `- exp ${get(pm, 'data.expMonth')}/${get(pm, 'data.expYear')}`
: '';
};

paymentMethodBalance = pm => {
/* Prepaid cards have their balance available */
if (pm.type === 'prepaid' || pm.type === 'virtualcard') {
if (pm.balance) {
return `(${formatCurrency(pm.balance, pm.currency)} left)`;
}
}
return '';
};

paymentMethodIcon = pm => {
if (pm.type === 'creditcard') {
return '💳';
}
if (pm.type === 'virtualcard') {
return '🎁';
}
return '';
};

paymentMethodsOptionsForCollective = (paymentMethods, collective) => {
const { intl } = this.props;
return paymentMethods.map(pm => {
const value = pm.uuid;
const expiration = this.paymentMethodExpiration(pm);
const balance = this.paymentMethodBalance(pm);
const name = this.paymentMethodName(pm);
const icon = this.paymentMethodIcon(pm);
/* Assemble all the pieces in one string */
const label = `${icon} \xA0\xA0${
collective.name
} - ${name} ${expiration} ${balance}`;
const label = paymentMethodLabelWithIcon(intl, pm, collective.name);
return { [value]: label };
});
};
Expand Down
99 changes: 0 additions & 99 deletions src/components/PaymentMethodLabel.js

This file was deleted.

121 changes: 121 additions & 0 deletions src/lib/payment_method_label.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* Functions for generating internationalized payment method labels.
*
* The fact that we use these labels inside `<select/>` options prevent us
* from implementing this as a React component as for now React does not
* support having components inside `<option/>` tags, even if the component
* returns only strings.
*
* [This message](https://github.com/facebook/react/issues/13586#issuecomment-419490956)
* explains why its not supported (though it has been in the past) and why
* it may not be in a near future.
*
*/

import { defineMessages } from 'react-intl';
import moment from 'moment';
import { get, padStart } from 'lodash';
import { formatCurrency } from './utils';

const messages = defineMessages({
virtualcard: {
id: 'paymentMethods.labelVirtualCard',
defaultMessage: '{name} {expiration} ({balance} left)',
description: 'Label for gift cards',
},
creditcard: {
id: 'paymentMethods.labelCreditCard',
defaultMessage: '{name} {expiration}',
description: 'Label for stripe credit cards',
},
prepaid: {
id: 'paymentMethods.labelPrepaid',
defaultMessage: '{name} ({balance} left)',
},
});

/**
* Generate a pretty string for payment method expiryDate or return an empty
* string if payment method has no expiry date.
* @param {PaymentMethod} pm
*/
function paymentMethodExpiration(pm) {
/* The expiryDate field will show up for prepaid cards */
return pm.expiryDate
? `- exp ${moment(pm.expiryDate).format('MM/Y')}`
: get(pm, 'data.expMonth') || get(pm, 'data.expYear')
? `- exp ${padStart(get(pm, 'data.expMonth'), 2, '0')}/${get(
pm,
'data.expYear',
)}`
: '';
}

/**
* Generate a pretty label for given payment method or return its name if type
* is unknown.
*
* @param {react-intl} intl the intl provider as given to your component by injectIntl
* @param {PaymentMethod} paymentMethod
* @param {string} collectiveName an optional name to prefix the payment method
*/
export function paymentMethodLabel(intl, paymentMethod, collectiveName = null) {
const { type, balance, currency, name, data } = paymentMethod;

let label = null;
if (type === 'virtualcard') {
label = intl.formatMessage(messages.virtualcard, {
name: name.replace('card from', 'Gift Card from'),
balance: formatCurrency(balance, currency),
expiration: paymentMethodExpiration(paymentMethod),
});
} else if (type === 'prepaid') {
label = intl.formatMessage(messages.prepaid, {
name: `${(data && data.brand) || type} ${name}`,
balance: formatCurrency(balance, currency),
});
} else if (type === 'creditcard') {
label = intl.formatMessage(messages.creditcard, {
name: `${(data && data.brand) || type} ${name}`,
expiration: paymentMethodExpiration(paymentMethod),
});
} else {
label = name;
}

return collectiveName ? `${collectiveName} - ${label}` : label;
}

/**
* Get the UTF8 icon associated with given payment method
* @param {PaymentMethod} paymentMethod
*/
export function paymentMethodUnicodeIcon(paymentMethod) {
switch (paymentMethod.type) {
case 'creditcard':
return '💳';
case 'virtualcard':
return '🎁';
case 'prepaid':
return '🎟️';
default:
return '💰';
}
}

/**
* Generate a label for given payment method as a string.
*
* @param {react-intl} intl the intl provider as given to your component by injectIntl
* @param {PaymentMethod} paymentMethod
* @param {string} collectiveName an optional name to prefix the payment method
*/
export function paymentMethodLabelWithIcon(
intl,
paymentMethod,
collectiveName = null,
) {
const icon = paymentMethodUnicodeIcon(paymentMethod);
const label = paymentMethodLabel(intl, paymentMethod, collectiveName);
return `${icon}\xA0\xA0${label}`;
}

0 comments on commit 4f0fea0

Please sign in to comment.