mirror of
https://github.com/TryGhost/Ghost.git
synced 2023-12-13 21:00:40 +01:00
f00b342e83
fixes PROD-274 - corrected plural form of the label - added number formatting
102 lines
5.4 KiB
TypeScript
102 lines
5.4 KiB
TypeScript
import React from 'react';
|
|
import TopLevelGroup from '../../TopLevelGroup';
|
|
import {Button, withErrorBoundary} from '@tryghost/admin-x-design-system';
|
|
import {CopyLinkButton, createRedemptionFilterUrl, getOfferDiscount} from './offers/OffersIndex';
|
|
import {Offer, useBrowseOffers} from '@tryghost/admin-x-framework/api/offers';
|
|
import {Tier, getPaidActiveTiers, useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers';
|
|
import {checkStripeEnabled} from '@tryghost/admin-x-framework/api/settings';
|
|
import {numberWithCommas} from '../../../utils/helpers';
|
|
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
|
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
|
|
|
const OfferContainer: React.FC<{offerTitle: string, tier: Tier, cadence: string, redemptions: number, type: string, amount: number, currency: string, offerId: string, offerCode: string, goToOfferEdit: (offerId: string) => void}> = (
|
|
{offerTitle, tier, cadence, redemptions, type, amount, currency, offerId, offerCode, goToOfferEdit}) => {
|
|
const {discountColor, discountOffer} = getOfferDiscount(type, amount, cadence, currency || 'USD', tier);
|
|
return <div className='group flex h-full cursor-pointer flex-col justify-between gap-4 break-words rounded-sm border border-transparent bg-grey-100 p-5 transition-all hover:border-grey-100 hover:bg-grey-75 hover:shadow-sm dark:bg-grey-950 dark:hover:border-grey-800 min-[900px]:min-h-[187px]' onClick={() => goToOfferEdit(offerId)}>
|
|
<span className='text-[1.65rem] font-bold leading-tight tracking-tight'>{offerTitle}</span>
|
|
<div className='flex flex-col'>
|
|
<span className={`text-sm font-semibold uppercase ${discountColor}`}>{discountOffer}</span>
|
|
<div className='flex gap-1 text-xs'>
|
|
<span className='font-semibold'>{tier.name}</span>
|
|
<span className='text-grey-700'>{cadence === 'month' ? 'monthly' : 'yearly'}</span>
|
|
</div>
|
|
<div className='mt-2 flex items-end justify-between'>
|
|
<a className={`text-xs text-grey-700 hover:text-black ${redemptions === 0 ? 'pointer-events-none' : 'hover:underline'}`} href={createRedemptionFilterUrl(offerId)}>{numberWithCommas(redemptions)} {redemptions === 1 ? 'redemption' : 'redemptions'}</a>
|
|
<CopyLinkButton offerCode={offerCode} />
|
|
</div>
|
|
</div>
|
|
</div>;
|
|
};
|
|
|
|
const Offers: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|
const {updateRoute} = useRouting();
|
|
const {settings, config} = useGlobalData();
|
|
|
|
const {data: {offers: allOffers = []} = {}} = useBrowseOffers();
|
|
|
|
const {data: {tiers: allTiers} = {}} = useBrowseTiers();
|
|
const paidActiveTiers = getPaidActiveTiers(allTiers || []);
|
|
|
|
const activeOffers = allOffers
|
|
.map(offer => ({...offer, tier: paidActiveTiers.find(tier => tier.id === offer.tier.id)}))
|
|
.filter((offer): offer is Offer & {tier: Tier} => offer.status === 'active' && !!offer.tier);
|
|
|
|
activeOffers.sort((a, b) => {
|
|
const dateA = a.created_at ? new Date(a.created_at) : new Date(0);
|
|
const dateB = b.created_at ? new Date(b.created_at) : new Date(0);
|
|
return dateB.getTime() - dateA.getTime();
|
|
});
|
|
|
|
const latestThree = activeOffers.slice(0, 3);
|
|
|
|
const openOfferListModal = () => {
|
|
updateRoute('offers/edit');
|
|
};
|
|
|
|
const openAddModal = () => {
|
|
updateRoute('offers/new');
|
|
};
|
|
|
|
const goToOfferEdit = (offerId: string) => {
|
|
sessionStorage.setItem('editOfferPageSource', 'offers');
|
|
updateRoute(`offers/edit/${offerId}`);
|
|
};
|
|
|
|
return (
|
|
<TopLevelGroup
|
|
customButtons={<Button color='green' disabled={!checkStripeEnabled(settings, config)} label={allOffers.length > 0 ? 'Manage offers' : 'Add offer'} link linkWithPadding onClick={allOffers.length > 0 ? openOfferListModal : openAddModal}/>}
|
|
description={<>Create discounts & coupons to boost new subscriptions. {allOffers.length === 0 && <a className='text-green' href="https://ghost.org/help/offers" rel="noopener noreferrer" target="_blank">Learn more</a>}</>}
|
|
keywords={keywords}
|
|
navid='offers'
|
|
testId='offers'
|
|
title='Offers'
|
|
>
|
|
{latestThree.length > 0 ?
|
|
<div>
|
|
<div className='grid grid-cols-1 gap-4 min-[900px]:grid-cols-3'>
|
|
{latestThree.map(offer => (<OfferContainer
|
|
key={offer.id}
|
|
amount={offer.amount}
|
|
cadence={offer.cadence}
|
|
currency={offer.currency || 'USD'}
|
|
goToOfferEdit={goToOfferEdit}
|
|
offerCode={offer.code}
|
|
offerId={offer.id}
|
|
offerTitle={offer.name}
|
|
redemptions={offer.redemption_count}
|
|
tier={offer.tier}
|
|
type={offer.type}
|
|
/>))}
|
|
</div>
|
|
{allOffers.length > 3 && <div className='mt-4 border-t border-t-grey-200 pt-2'>
|
|
<span className='text-xs text-grey-700 dark:text-grey-600'>{allOffers.length} offers in total</span>
|
|
</div>}
|
|
</div> :
|
|
null
|
|
}
|
|
</TopLevelGroup>
|
|
);
|
|
};
|
|
|
|
export default withErrorBoundary(Offers, 'Portal settings');
|