0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Used Intl.NumberFormat to determine currency symbol (#128)

refs https://github.com/TryGhost/Team/issues/473
refs https://github.com/TryGhost/Ghost/pull/12700/commits/006cf434

Ghost no longer sends back currency symbols from the API, so we calculate the currency
symbol using `Intl.NumberFormat`.

We've also renamed the `currency` property to `currency_symbol` - as it does not store a currency.

Depending on currency and locale, currency symbols can be the currency ISO code (e.g. AED).
In order to style these differently we add a different class to the element.
This commit is contained in:
Fabien 'egg' O'Carroll 2021-02-25 09:58:19 +00:00 committed by GitHub
parent b751c1c41e
commit ccde2519a8
6 changed files with 22 additions and 32 deletions

View file

@ -241,10 +241,12 @@ function Checkbox({name, onPlanSelect, isChecked, disabled}) {
); );
} }
function PriceLabel({currency, price}) { function PriceLabel({currencySymbol, price}) {
const isSymbol = currencySymbol.length !== 3;
const currencyClass = isSymbol ? 'gh-portal-plan-currency gh-portal-plan-currency-symbol' : 'gh-portal-plan-currency gh-portal-plan-currency-code';
return ( return (
<div className='gh-portal-plan-pricelabel'> <div className='gh-portal-plan-pricelabel'>
<span className='gh-portal-plan-currency'>{currency}</span> <span className={currencyClass}>{currencySymbol}</span>
<span className='gh-portal-plan-price'>{price}</span> <span className='gh-portal-plan-price'>{price}</span>
</div> </div>
); );
@ -254,7 +256,7 @@ function PlanOptions({plans, selectedPlan, onPlanSelect, changePlan}) {
const hasMonthlyPlan = plans.some(({name}) => { const hasMonthlyPlan = plans.some(({name}) => {
return name === 'Monthly'; return name === 'Monthly';
}); });
return plans.map(({name, currency, price, discount}, i) => { return plans.map(({name, currency_symbol: currencySymbol, price, discount}, i) => {
const isChecked = selectedPlan === name; const isChecked = selectedPlan === name;
const classes = (isChecked ? 'gh-portal-plan-section checked' : 'gh-portal-plan-section'); const classes = (isChecked ? 'gh-portal-plan-section checked' : 'gh-portal-plan-section');
const planDetails = {}; const planDetails = {};
@ -279,7 +281,7 @@ function PlanOptions({plans, selectedPlan, onPlanSelect, changePlan}) {
<div className={classes} key={name} onClick={e => onPlanSelect(e, name)}> <div className={classes} key={name} onClick={e => onPlanSelect(e, name)}>
<Checkbox name={name} isChecked={isChecked} onPlanSelect={onPlanSelect} /> <Checkbox name={name} isChecked={isChecked} onPlanSelect={onPlanSelect} />
<h4 className='gh-portal-plan-name'>{displayName || name}</h4> <h4 className='gh-portal-plan-name'>{displayName || name}</h4>
<PriceLabel name={name} currency={currency} price={price} /> <PriceLabel name={name} currencySymbol={currencySymbol} price={price} />
<div className='gh-portal-plan-featurewrapper'> <div className='gh-portal-plan-featurewrapper'>
<div className='gh-portal-plan-feature'> <div className='gh-portal-plan-feature'>
{planDetails.feature} {planDetails.feature}

View file

@ -6,9 +6,9 @@ const setup = (overrides = {}) => {
const mockOnPlanSelectFn = jest.fn(); const mockOnPlanSelectFn = jest.fn();
const props = { const props = {
plans: [ plans: [
{type: 'free', price: 'Decide later', name: 'Free'}, {type: 'free', price: 'Decide later', currency_symbol: '$', name: 'Free'},
{type: 'month', price: 12, currency: '$', name: 'Monthly'}, {type: 'month', price: 12, currency_symbol: '$', name: 'Monthly'},
{type: 'year', price: 110, currency: '$', name: 'Yearly'} {type: 'year', price: 110, currency_symbol: '$', name: 'Yearly'}
], ],
selectedPlan: 'Monthly', selectedPlan: 'Monthly',
onPlanSelect: mockOnPlanSelectFn onPlanSelect: mockOnPlanSelectFn

View file

@ -132,8 +132,8 @@ const UserHeader = ({member, brandColor}) => {
const PaidAccountActions = ({member, site, openUpdatePlan, onEditBilling}) => { const PaidAccountActions = ({member, site, openUpdatePlan, onEditBilling}) => {
const PlanLabel = ({plan, isComplimentary}) => { const PlanLabel = ({plan, isComplimentary}) => {
const {amount = 0, currency_symbol: currencySymbol = '$', interval} = plan; const {amount = 0, currency, interval} = plan;
let label = `${currencySymbol}${amount / 100}/${interval}`; let label = `${Intl.NumberFormat('en', {currency, style: 'currency'}).format(amount / 100)}/${interval}`;
if (isComplimentary) { if (isComplimentary) {
label = `Complimentary (${label})`; label = `Complimentary (${label})`;
} }

View file

@ -266,20 +266,20 @@ class SignupPage extends React.Component {
{ {
type: 'month', type: 'month',
price: plans.monthly, price: plans.monthly,
currency: plans.currency_symbol, currency_symbol: plans.currency_symbol,
name: 'Monthly' name: 'Monthly'
}, },
{ {
type: 'year', type: 'year',
price: plans.yearly, price: plans.yearly,
currency: plans.currency_symbol, currency_symbol: plans.currency_symbol,
name: 'Yearly', name: 'Yearly',
discount discount
} }
]; ];
if (allowSelfSignup && (portalPlans === undefined || portalPlans.includes('free'))) { if (allowSelfSignup && (portalPlans === undefined || portalPlans.includes('free'))) {
plansData.push({type: 'free', price: 0, currency: plans.currency_symbol, name: 'Free'}); plansData.push({type: 'free', price: 0, currency_symbol: plans.currency_symbol, name: 'Free'});
} }
if (isStripeConfigured && pageQuery !== 'free') { if (isStripeConfigured && pageQuery !== 'free') {

View file

@ -8,8 +8,7 @@ export const site = {
plans: { plans: {
monthly: 5, monthly: 5,
yearly: 15, yearly: 15,
currency: 'USD', currency: 'USD'
currency_symbol: '$'
}, },
allow_self_signup: true, allow_self_signup: true,
is_stripe_configured: true, is_stripe_configured: true,
@ -52,8 +51,7 @@ export const member = {
nickname: 'Yearly', nickname: 'Yearly',
interval: 'year', interval: 'year',
amount: 1500, amount: 1500,
currency: 'USD', currency: 'USD'
currency_symbol: '$'
}, },
status: 'active', status: 'active',
start_date: '2019-05-01T11:42:40.000Z', start_date: '2019-05-01T11:42:40.000Z',
@ -82,8 +80,7 @@ export const member = {
nickname: 'Complimentary', nickname: 'Complimentary',
amount: 0, amount: 0,
interval: 'year', interval: 'year',
currency: 'USD', currency: 'USD'
currency_symbol: '$'
}, },
status: 'active', status: 'active',
start_date: '2020-09-03T11:12:37.000Z', start_date: '2020-09-03T11:12:37.000Z',
@ -112,8 +109,7 @@ export const member = {
nickname: 'Yearly', nickname: 'Yearly',
interval: 'year', interval: 'year',
amount: 500, amount: 500,
currency: 'USD', currency: 'USD'
currency_symbol: '$'
}, },
status: 'active', status: 'active',
start_date: '2019-05-01T11:42:40.000Z', start_date: '2019-05-01T11:42:40.000Z',

View file

@ -124,13 +124,13 @@ export function getSitePlans({site = {}, includeFree = true, pageQuery} = {}) {
{ {
type: 'month', type: 'month',
price: plans.monthly, price: plans.monthly,
currency: plans.currency_symbol, currency_symbol: getCurrencySymbol(plans.currency),
name: 'Monthly' name: 'Monthly'
}, },
{ {
type: 'year', type: 'year',
price: plans.yearly, price: plans.yearly,
currency: plans.currency_symbol, currency_symbol: getCurrencySymbol(plans.currency),
name: 'Yearly', name: 'Yearly',
discount discount
} }
@ -140,7 +140,7 @@ export function getSitePlans({site = {}, includeFree = true, pageQuery} = {}) {
plansData.push({ plansData.push({
type: 'free', type: 'free',
price: 0, price: 0,
currency: plans.currency_symbol, currency_symbol: getCurrencySymbol(plans.currency),
name: 'Free' name: 'Free'
}); });
} }
@ -188,15 +188,7 @@ export const getSiteDomain = ({site}) => {
}; };
export const getCurrencySymbol = (currency) => { export const getCurrencySymbol = (currency) => {
const CURRENCY_SYMBOLS = { return Intl.NumberFormat('en', {currency, style: 'currency'}).format(0).replace(/[\d\s.]/g, '');
USD: '$',
AUD: '$',
CAD: '$',
GBP: '£',
EUR: '€',
INR: '₹'
};
return CURRENCY_SYMBOLS[currency] || '';
}; };
export const createPopupNotification = ({type, status, autoHide, duration, closeable, state, message, meta = {}}) => { export const createPopupNotification = ({type, status, autoHide, duration, closeable, state, message, meta = {}}) => {