mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Wired data to tier preview in adminX (#17323)
refs https://github.com/TryGhost/Product/issues/3580
This commit is contained in:
parent
1851a36c0d
commit
4424f25970
3 changed files with 218 additions and 26 deletions
|
@ -18,11 +18,18 @@ interface TierDetailModalProps {
|
|||
tier?: Tier
|
||||
}
|
||||
|
||||
type TierFormState = Partial<Omit<Tier, 'monthly_price' | 'yearly_price' | 'trial_days'>> & {
|
||||
monthly_price: string;
|
||||
yearly_price: string;
|
||||
trial_days: string;
|
||||
};
|
||||
|
||||
const TierDetailModal: React.FC<TierDetailModalProps> = ({tier}) => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
const {update: updateTier, create: createTier} = useTiers();
|
||||
const {formState, updateForm, handleSave} = useForm({
|
||||
|
||||
const {formState, updateForm, handleSave} = useForm<TierFormState>({
|
||||
initialState: {
|
||||
...(tier || {}),
|
||||
monthly_price: tier?.monthly_price?.toString() || '',
|
||||
|
@ -135,7 +142,7 @@ const TierDetailModal: React.FC<TierDetailModalProps> = ({tier}) => {
|
|||
</Form>
|
||||
</div>
|
||||
<div className='sticky top-[77px] shrink-0 basis-[380px]'>
|
||||
<TierDetailPreview />
|
||||
<TierDetailPreview tier={formState} />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>;
|
||||
|
|
|
@ -1,9 +1,70 @@
|
|||
import Icon from '../../../../admin-x-ds/global/Icon';
|
||||
import React from 'react';
|
||||
import {Tier} from '../../../../types/api';
|
||||
import {getNonDecimal, getSymbol} from '../../../../utils/currency';
|
||||
|
||||
interface TierDetailPreviewProps {}
|
||||
export type TierFormState = Partial<Omit<Tier, 'monthly_price' | 'yearly_price' | 'trial_days'>> & {
|
||||
monthly_price: string;
|
||||
yearly_price: string;
|
||||
trial_days: string;
|
||||
};
|
||||
|
||||
interface TierDetailPreviewProps {
|
||||
tier: TierFormState
|
||||
}
|
||||
|
||||
const TrialDaysLabel: React.FC<{trialDays: number}> = ({trialDays}) => {
|
||||
if (!trialDays) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="relative -mr-1 -mt-1 whitespace-nowrap rounded-full px-2.5 py-1.5 text-sm font-semibold leading-none tracking-wide text-grey-900">
|
||||
<span className="absolute inset-0 block rounded-full bg-pink opacity-20"></span>
|
||||
{trialDays} days free
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const TierBenefits: React.FC<{benefits: string[]}> = ({benefits}) => {
|
||||
if (!benefits?.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{
|
||||
benefits.map((benefit) => {
|
||||
return (
|
||||
<div key={benefit} className="mt-4 w-full text-md leading-snug text-grey-900">
|
||||
<div className="mb-2 flex items-start">
|
||||
<Icon className="mr-[10px] mt-[3px] h-3.5 w-3.5 min-w-[14px] overflow-visible !stroke-[3px]" name='check' />
|
||||
<div>{benefit}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DiscountLabel: React.FC<{discount: number}> = ({discount}) => {
|
||||
if (!discount) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<span className="mt-1 text-sm font-semibold leading-none text-pink">{discount}% discount</span>
|
||||
);
|
||||
};
|
||||
|
||||
const TierDetailPreview: React.FC<TierDetailPreviewProps> = ({tier}) => {
|
||||
const name = tier?.name || '';
|
||||
const description = tier?.description || '';
|
||||
const monthlyPrice = getNonDecimal(parseFloat(tier?.monthly_price || '0'));
|
||||
const trialDays = parseFloat(tier?.trial_days || '0');
|
||||
const currency = tier?.currency || 'USD';
|
||||
const currencySymbol = currency ? getSymbol(currency) : '$';
|
||||
const benefits = tier?.benefits || [];
|
||||
|
||||
const TierDetailPreview: React.FC<TierDetailPreviewProps> = () => {
|
||||
return (
|
||||
<div className="-mt-[6px]">
|
||||
<div className="flex items-baseline justify-between">
|
||||
|
@ -13,35 +74,21 @@ const TierDetailPreview: React.FC<TierDetailPreviewProps> = () => {
|
|||
</div>
|
||||
<div className="flex-column relative flex min-h-[200px] min-w-[320px] max-w-[420px] items-start justify-stretch rounded border border-grey-200 bg-white p-8">
|
||||
<div className="min-h-[56px] w-full">
|
||||
<h4 className="-mt-1 mb-0 w-full break-words text-lg font-semibold leading-tight text-pink">Bronze</h4>
|
||||
<h4 className="-mt-1 mb-0 w-full break-words text-lg font-semibold leading-tight text-pink">{name}</h4>
|
||||
<div className="mt-4 flex w-full flex-row flex-wrap items-end justify-between gap-x-1 gap-y-[10px]">
|
||||
<div className="flex justify-center text-black">
|
||||
<span className="self-start text-[2.7rem] font-bold uppercase leading-[1.115]">$</span>
|
||||
<span className="text-[3.4rem] font-bold leading-none tracking-tight">42</span>
|
||||
<span className="ml-1 self-end text-[1.5rem] leading-snug text-grey-800">/year</span>
|
||||
<span className="self-start text-[2.7rem] font-bold uppercase leading-[1.115]">{currencySymbol}</span>
|
||||
<span className="text-[3.4rem] font-bold leading-none tracking-tight">{monthlyPrice}</span>
|
||||
<span className="ml-1 self-end text-[1.5rem] leading-snug text-grey-800">/month</span>
|
||||
</div>
|
||||
<span className="relative -mr-1 -mt-1 whitespace-nowrap rounded-full px-2.5 py-1.5 text-sm font-semibold leading-none tracking-wide text-grey-900">
|
||||
<span className="absolute inset-0 block rounded-full bg-pink opacity-20"></span>
|
||||
7 days free
|
||||
</span>
|
||||
<TrialDaysLabel trialDays={trialDays} />
|
||||
</div>
|
||||
<span className="mt-1 text-sm font-semibold leading-none text-pink">25% discount</span>
|
||||
<DiscountLabel discount={25} />
|
||||
</div>
|
||||
<div className="flex-column flex w-full flex-1">
|
||||
<div className="flex-1">
|
||||
<div className="mt-4 w-full text-[1.55rem] font-semibold leading-snug text-grey-900">Full access to premium content</div>
|
||||
<div className="mt-4 w-full text-md leading-snug text-grey-900">
|
||||
<div className="mb-2 flex items-start">
|
||||
<Icon className="mr-[10px] mt-[3px] h-3.5 w-3.5 min-w-[14px] overflow-visible !stroke-[3px]" name='check' />
|
||||
<div>Blogs that you can watch and listen to on your favorite podcast apps</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 w-full text-md leading-snug text-grey-900">
|
||||
<div className="mb-2 flex items-start">
|
||||
<Icon className="mr-[10px] mt-[3px] h-3.5 w-3.5 min-w-[14px] overflow-visible !stroke-[3px]" name='check' />
|
||||
<div>Blogs that you can watch and listen to on your favorite podcast apps</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 w-full text-[1.55rem] font-semibold leading-snug text-grey-900">{description}</div>
|
||||
<TierBenefits benefits={benefits} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
138
apps/admin-x-settings/src/utils/currency.ts
Normal file
138
apps/admin-x-settings/src/utils/currency.ts
Normal file
|
@ -0,0 +1,138 @@
|
|||
type CurrencyOption = {
|
||||
isoCode: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const currencies: CurrencyOption[] = [
|
||||
{isoCode: 'USD', name: 'United States dollar'},
|
||||
{isoCode: 'EUR', name: 'Euro'},
|
||||
{isoCode: 'GBP', name: 'Pound sterling'},
|
||||
{isoCode: 'AUD', name: 'Australian dollar'},
|
||||
{isoCode: 'CAD', name: 'Canadian dollar'},
|
||||
{isoCode: 'AED', name: 'United Arab Emirates dirham'},
|
||||
{isoCode: 'AFN', name: 'Afghan afghani'},
|
||||
{isoCode: 'ALL', name: 'Albanian lek'},
|
||||
{isoCode: 'AMD', name: 'Armenian dram'},
|
||||
{isoCode: 'ANG', name: 'Netherlands Antillean guilder'},
|
||||
{isoCode: 'AOA', name: 'Angolan kwanza'},
|
||||
{isoCode: 'ARS', name: 'Argentine peso'},
|
||||
{isoCode: 'AWG', name: 'Aruban florin'},
|
||||
{isoCode: 'AZN', name: 'Azerbaijani manat'},
|
||||
{isoCode: 'BAM', name: 'Bosnia and Herzegovina convertible mark'},
|
||||
{isoCode: 'BBD', name: 'Barbados dollar'},
|
||||
{isoCode: 'BDT', name: 'Bangladeshi taka'},
|
||||
{isoCode: 'BGN', name: 'Bulgarian lev'},
|
||||
{isoCode: 'BMD', name: 'Bermudian dollar'},
|
||||
{isoCode: 'BND', name: 'Brunei dollar'},
|
||||
{isoCode: 'BOB', name: 'Boliviano'},
|
||||
{isoCode: 'BRL', name: 'Brazilian real'},
|
||||
{isoCode: 'BSD', name: 'Bahamian dollar'},
|
||||
{isoCode: 'BWP', name: 'Botswana pula'},
|
||||
{isoCode: 'BZD', name: 'Belize dollar'},
|
||||
{isoCode: 'CDF', name: 'Congolese franc'},
|
||||
{isoCode: 'CHF', name: 'Swiss franc'},
|
||||
{isoCode: 'CNY', name: 'Chinese yuan'},
|
||||
{isoCode: 'COP', name: 'Colombian peso'},
|
||||
{isoCode: 'CRC', name: 'Costa Rican colon'},
|
||||
{isoCode: 'CVE', name: 'Cape Verdean escudo'},
|
||||
{isoCode: 'CZK', name: 'Czech koruna'},
|
||||
{isoCode: 'DKK', name: 'Danish krone'},
|
||||
{isoCode: 'DOP', name: 'Dominican peso'},
|
||||
{isoCode: 'DZD', name: 'Algerian dinar'},
|
||||
{isoCode: 'EGP', name: 'Egyptian pound'},
|
||||
{isoCode: 'ETB', name: 'Ethiopian birr'},
|
||||
{isoCode: 'FJD', name: 'Fiji dollar'},
|
||||
{isoCode: 'FKP', name: 'Falkland Islands pound'},
|
||||
{isoCode: 'GEL', name: 'Georgian lari'},
|
||||
{isoCode: 'GIP', name: 'Gibraltar pound'},
|
||||
{isoCode: 'GMD', name: 'Gambian dalasi'},
|
||||
{isoCode: 'GTQ', name: 'Guatemalan queztal'},
|
||||
{isoCode: 'GYD', name: 'Guyanese dollar'},
|
||||
{isoCode: 'HKD', name: 'Hong Kong dollar'},
|
||||
{isoCode: 'HNL', name: 'Honduran lempira'},
|
||||
{isoCode: 'HRK', name: 'Croation kuna'},
|
||||
{isoCode: 'HTG', name: 'Haitian gourde'},
|
||||
{isoCode: 'HUF', name: 'Hungarian forint'},
|
||||
{isoCode: 'IDR', name: 'Indonesian rupiah'},
|
||||
{isoCode: 'ILS', name: 'Israeli new shekel'},
|
||||
{isoCode: 'INR', name: 'Indian rupee'},
|
||||
{isoCode: 'ISK', name: 'Icelandic króna'},
|
||||
{isoCode: 'JMD', name: 'Jamaican dollar'},
|
||||
{isoCode: 'KES', name: 'Kenyan shilling'},
|
||||
{isoCode: 'KGS', name: 'Kyrgyzstani som'},
|
||||
{isoCode: 'KHR', name: 'Cambodian riel'},
|
||||
{isoCode: 'KYD', name: 'Cayman Islands dollar'},
|
||||
{isoCode: 'KZT', name: 'Kazakhstani tenge'},
|
||||
{isoCode: 'LAK', name: 'Lao kip'},
|
||||
{isoCode: 'LBP', name: 'Lebanese pound'},
|
||||
{isoCode: 'LKR', name: 'Sri Lankan rupee'},
|
||||
{isoCode: 'LRD', name: 'Liberian dollar'},
|
||||
{isoCode: 'LSL', name: 'Lesotho loti'},
|
||||
{isoCode: 'MAD', name: 'Moroccan dirham'},
|
||||
{isoCode: 'MDL', name: 'Moldovan leu'},
|
||||
{isoCode: 'MKD', name: 'Macedonian denar'},
|
||||
{isoCode: 'MMK', name: 'Myanmar kyat'},
|
||||
{isoCode: 'MNT', name: 'Mongolian tögrög'},
|
||||
{isoCode: 'MOP', name: 'Macanese pataca'},
|
||||
{isoCode: 'MRO', name: 'Mauritanian ouguiya'},
|
||||
{isoCode: 'MUR', name: 'Mauritian rupee'},
|
||||
{isoCode: 'MVR', name: 'Maldivian rufiyaa'},
|
||||
{isoCode: 'MWK', name: 'Malawian kwacha'},
|
||||
{isoCode: 'MXN', name: 'Mexican peso'},
|
||||
{isoCode: 'MYR', name: 'Malaysian ringgit'},
|
||||
{isoCode: 'MZN', name: 'Mozambican metical'},
|
||||
{isoCode: 'NAD', name: 'Namibian dollar'},
|
||||
{isoCode: 'NGN', name: 'Nigerian naira'},
|
||||
{isoCode: 'NIO', name: 'Nicaraguan córdoba'},
|
||||
{isoCode: 'NOK', name: 'Norwegian krone'},
|
||||
{isoCode: 'NPR', name: 'Nepalese rupee'},
|
||||
{isoCode: 'NZD', name: 'New Zealand dollar'},
|
||||
{isoCode: 'PAB', name: 'Panamanian balboa'},
|
||||
{isoCode: 'PEN', name: 'Peruvian sol'},
|
||||
{isoCode: 'PGK', name: 'Papua New Guinean kina'},
|
||||
{isoCode: 'PHP', name: 'Philippine peso'},
|
||||
{isoCode: 'PKR', name: 'Pakistani rupee'},
|
||||
{isoCode: 'PLN', name: 'Polish złoty'},
|
||||
{isoCode: 'QAR', name: 'Qatari riyal'},
|
||||
{isoCode: 'RON', name: 'Romanian leu'},
|
||||
{isoCode: 'RSD', name: 'Serbian dinar'},
|
||||
{isoCode: 'RUB', name: 'Russian ruble'},
|
||||
{isoCode: 'SAR', name: 'Saudi riyal'},
|
||||
{isoCode: 'SBD', name: 'Solomon Islands dollar'},
|
||||
{isoCode: 'SCR', name: 'Seychelles rupee'},
|
||||
{isoCode: 'SEK', name: 'Swedish krona'},
|
||||
{isoCode: 'SGD', name: 'Singapore dollar'},
|
||||
{isoCode: 'SHP', name: 'Saint Helena pound'},
|
||||
{isoCode: 'SLL', name: 'Sierra Leonean leone'},
|
||||
{isoCode: 'SOS', name: 'Somali shilling'},
|
||||
{isoCode: 'SRD', name: 'Surinamese dollar'},
|
||||
{isoCode: 'STD', name: 'São Tomé and Príncipe dobra'},
|
||||
{isoCode: 'SZL', name: 'Salvadoran colón'},
|
||||
{isoCode: 'THB', name: 'Thai baht'},
|
||||
{isoCode: 'TJS', name: 'Tajikistani somoni'},
|
||||
{isoCode: 'TOP', name: 'Tongan paʻanga'},
|
||||
{isoCode: 'TRY', name: 'Turkish lira'},
|
||||
{isoCode: 'TTD', name: 'Trinidad and Tobago dollar'},
|
||||
{isoCode: 'TWD', name: 'New Taiwan dollar'},
|
||||
{isoCode: 'TZS', name: 'Tanzanian shilling'},
|
||||
{isoCode: 'UAH', name: 'Ukrainian hryvnia'},
|
||||
{isoCode: 'UYU', name: 'Uruguayan peso'},
|
||||
{isoCode: 'UZS', name: 'Uzbekistan som'},
|
||||
{isoCode: 'WST', name: 'Samoan tala'},
|
||||
{isoCode: 'XCD', name: 'East Caribbean dollar'},
|
||||
{isoCode: 'YER', name: 'Yemeni rial'},
|
||||
{isoCode: 'ZAR', name: 'South African rand'},
|
||||
{isoCode: 'ZMW', name: 'Zambian kwacha'}
|
||||
];
|
||||
|
||||
export function getSymbol(currency: string): string {
|
||||
if (!currency) {
|
||||
return '';
|
||||
}
|
||||
return Intl.NumberFormat('en', {currency, style: 'currency'}).format(0).replace(/[\d\s.]/g, '');
|
||||
}
|
||||
|
||||
// We currently only support decimal currencies
|
||||
export function getNonDecimal(amount: number): number {
|
||||
return amount / 100;
|
||||
}
|
Loading…
Add table
Reference in a new issue