mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Simplified offers list screen in AdminX (#19190)
fixes ADM-40 - removed layout switch feature, and made the list layout default - removed the help link, because it will no longer be necessary on the list screen - fixed offers count in the table header
This commit is contained in:
parent
49eaee40f2
commit
63774463ce
1 changed files with 7 additions and 85 deletions
|
@ -89,42 +89,6 @@ export const CopyLinkButton: React.FC<{offerCode: string}> = ({offerCode}) => {
|
||||||
return <Tooltip containerClassName='group-hover:opacity-100 opacity-0 flex items-center -mr-1 justify-center leading-none w-5 h-5' content={isCopied ? 'Copied' : 'Copy link'} size='sm'><Button color='clear' hideLabel={true} icon={isCopied ? 'check-circle' : 'hyperlink-circle'} iconColorClass={isCopied ? 'text-green w-[14px] h-[14px]' : 'w-[18px] h-[18px]'} label={isCopied ? 'Copied' : 'Copy'} unstyled={true} onClick={handleCopyClick} /></Tooltip>;
|
return <Tooltip containerClassName='group-hover:opacity-100 opacity-0 flex items-center -mr-1 justify-center leading-none w-5 h-5' content={isCopied ? 'Copied' : 'Copy link'} size='sm'><Button color='clear' hideLabel={true} icon={isCopied ? 'check-circle' : 'hyperlink-circle'} iconColorClass={isCopied ? 'text-green w-[14px] h-[14px]' : 'w-[18px] h-[18px]'} label={isCopied ? 'Copied' : 'Copy'} unstyled={true} onClick={handleCopyClick} /></Tooltip>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const OfferCard: React.FC<{amount: number, cadence: string, currency: string, duration: string, name: string, code: string, offerId: string, offerTier: Tier | undefined, redemptionCount: number, type: OfferType, onClick: ()=>void}> = ({amount, cadence, currency, duration, name, code, offerId, offerTier, redemptionCount, type, onClick}) => {
|
|
||||||
let tierName = offerTier?.name + ' ' + getOfferCadence(cadence) + ' — ' + getOfferDuration(duration);
|
|
||||||
const {discountColor, discountOffer, originalPriceWithCurrency, updatedPriceWithCurrency} = getOfferDiscount(type, amount, cadence, currency || 'USD', offerTier);
|
|
||||||
|
|
||||||
const isTierArchived = offerTier?.active === false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`group relative ${isTierArchived ? '' : 'cursor-pointer'} border border-transparent bg-grey-100 p-5 transition-all ${!isTierArchived ? 'hover:border-grey-100 hover:bg-grey-75 hover:shadow-sm' : ''} dark:bg-grey-950 ${!isTierArchived ? 'dark:hover:border-grey-800' : ''}`} onClick={!isTierArchived ? onClick : () => {}}>
|
|
||||||
<div className='flex h-full flex-col gap-6'>
|
|
||||||
<div className={`${isTierArchived ? 'pointer-events-none opacity-50' : ''} flex grow items-baseline justify-between gap-2`}>
|
|
||||||
<h2 className='text-[1.6rem] font-semibold'>{name}</h2>
|
|
||||||
<span className={`-translate-y-px whitespace-nowrap text-xs font-semibold uppercase ${discountColor}`}>{discountOffer}</span>
|
|
||||||
</div>
|
|
||||||
<div className={`${isTierArchived ? 'pointer-events-none opacity-50' : ''} flex items-baseline gap-1`}>
|
|
||||||
<span className='text-3xl font-bold tracking-tight'>{updatedPriceWithCurrency}</span>
|
|
||||||
<span className='text-[1.6rem] font-medium text-grey-700 line-through'>{originalPriceWithCurrency}</span>
|
|
||||||
</div>
|
|
||||||
<div className='flex items-center justify-between'>
|
|
||||||
<div className={`${isTierArchived ? 'pointer-events-none opacity-50' : ''} flex flex-col items-start text-xs`}>
|
|
||||||
<span className='font-medium'>{tierName}</span>
|
|
||||||
<a className='text-grey-700 hover:underline' href={createRedemptionFilterUrl(offerId)}>{redemptionCount} redemptions</a>
|
|
||||||
</div>
|
|
||||||
{!isTierArchived ?
|
|
||||||
<CopyLinkButton offerCode={code} /> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isTierArchived ?
|
|
||||||
<div className='absolute left-4 top-4 whitespace-nowrap rounded-sm bg-black px-2 py-0.5 text-xs leading-normal text-white opacity-0 transition-all group-hover:opacity-100 dark:bg-grey-950'>This offer is disabled, because <br /> it is tied to an archived tier.</div> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OffersIndexModal = () => {
|
export const OffersIndexModal = () => {
|
||||||
const modal = useModal();
|
const modal = useModal();
|
||||||
const {updateRoute} = useRouting();
|
const {updateRoute} = useRouting();
|
||||||
|
@ -149,7 +113,6 @@ export const OffersIndexModal = () => {
|
||||||
{id: 'archived', title: 'Archived'}
|
{id: 'archived', title: 'Archived'}
|
||||||
];
|
];
|
||||||
const [selectedTab, setSelectedTab] = useState('active');
|
const [selectedTab, setSelectedTab] = useState('active');
|
||||||
const [selectedLayout, setSelectedLayout] = useState('card');
|
|
||||||
const [sortOption, setSortOption] = useState('date-added');
|
const [sortOption, setSortOption] = useState('date-added');
|
||||||
const [sortDirection, setSortDirection] = useState('desc');
|
const [sortDirection, setSortDirection] = useState('desc');
|
||||||
|
|
||||||
|
@ -179,43 +142,11 @@ export const OffersIndexModal = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const cardLayoutOutput = <div className='mt-8 grid grid-cols-1 gap-6 min-[600px]:grid-cols-2 min-[900px]:grid-cols-3'>
|
|
||||||
{sortedOffers.filter((offer) => {
|
|
||||||
const offerTier = allTiers?.find(tier => tier.id === offer?.tier.id);
|
|
||||||
//Check to filter out offers with archived offerTier
|
|
||||||
return (selectedTab === 'active' && (offer.status === 'active' && offerTier && offerTier.active === true)) ||
|
|
||||||
(selectedTab === 'archived' && (offer.status === 'archived' || (offerTier && offerTier.active === false)));
|
|
||||||
}).map((offer) => {
|
|
||||||
const offerTier = allTiers?.find(tier => tier.id === offer?.tier.id);
|
|
||||||
|
|
||||||
if (!offerTier) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<OfferCard
|
|
||||||
key={offer?.id}
|
|
||||||
amount={offer?.amount}
|
|
||||||
cadence={offer?.cadence}
|
|
||||||
code={offer?.code}
|
|
||||||
currency={offer?.currency || 'USD'}
|
|
||||||
duration={offer?.duration}
|
|
||||||
name={offer?.name}
|
|
||||||
offerId={offer?.id ? offer.id : ''}
|
|
||||||
offerTier={offerTier}
|
|
||||||
redemptionCount={offer?.redemption_count ? offer.redemption_count : 0}
|
|
||||||
type={offer?.type as OfferType}
|
|
||||||
onClick={() => handleOfferEdit(offer?.id ? offer.id : '')}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>;
|
|
||||||
|
|
||||||
const listLayoutOutput = <div className='overflow-x-auto'>
|
const listLayoutOutput = <div className='overflow-x-auto'>
|
||||||
<table className='m-0 w-full'>
|
<table className='m-0 w-full'>
|
||||||
{(selectedTab === 'active' && activeOffers.length > 0) || (selectedTab === 'archived' && archivedOffers.length > 0) ?
|
{(selectedTab === 'active' && activeOffers.length > 0) || (selectedTab === 'archived' && archivedOffers.length > 0) ?
|
||||||
<tr className='border-b border-b-grey-300'>
|
<tr className='border-b border-b-grey-300'>
|
||||||
<th className='px-5 py-2.5 pl-0 text-xs font-normal text-grey-700'>{sortedOffers.length} {sortedOffers.length > 1 ? 'offers' : 'offer'}</th>
|
<th className='px-5 py-2.5 pl-0 text-xs font-normal text-grey-700'>{selectedTab === 'active' ? activeOffers.length : archivedOffers.length} {selectedTab === 'active' ? (activeOffers.length !== 1 ? 'offers' : 'offer') : (archivedOffers.length !== 1 ? 'offers' : 'offer')}</th>
|
||||||
<th className='px-5 py-2.5 text-xs font-normal text-grey-700'>Tier</th>
|
<th className='px-5 py-2.5 text-xs font-normal text-grey-700'>Tier</th>
|
||||||
<th className='px-5 py-2.5 text-xs font-normal text-grey-700'>Terms</th>
|
<th className='px-5 py-2.5 text-xs font-normal text-grey-700'>Terms</th>
|
||||||
<th className='px-5 py-2.5 text-xs font-normal text-grey-700'>Price</th>
|
<th className='px-5 py-2.5 text-xs font-normal text-grey-700'>Price</th>
|
||||||
|
@ -264,20 +195,15 @@ export const OffersIndexModal = () => {
|
||||||
}}
|
}}
|
||||||
animate={false}
|
animate={false}
|
||||||
cancelLabel=''
|
cancelLabel=''
|
||||||
footer={
|
|
||||||
<div className='mx-8 flex w-full items-center justify-between'>
|
|
||||||
<a className='text-sm' href="https://ghost.org/help/offers" rel="noopener noreferrer" target="_blank">→ Learn about offers in Ghost</a>
|
|
||||||
<Button color='black' label='Close' onClick={() => {
|
|
||||||
modal.remove();
|
|
||||||
updateRoute('offers');
|
|
||||||
}} />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
header={false}
|
header={false}
|
||||||
height='full'
|
height='full'
|
||||||
size='lg'
|
size='lg'
|
||||||
testId='offers-modal'
|
testId='offers-modal'
|
||||||
stickyFooter
|
stickyFooter
|
||||||
|
onOk={() => {
|
||||||
|
modal.remove();
|
||||||
|
updateRoute('offers');
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className='pt-6'>
|
<div className='pt-6'>
|
||||||
<header>
|
<header>
|
||||||
|
@ -295,7 +221,7 @@ export const OffersIndexModal = () => {
|
||||||
</div>
|
</div>
|
||||||
<div className='mt-12 flex items-center justify-between border-b border-b-grey-300 pb-2.5'>
|
<div className='mt-12 flex items-center justify-between border-b border-b-grey-300 pb-2.5'>
|
||||||
<h1 className='text-3xl'>{offersTabs.find(tab => tab.id === selectedTab)?.title} offers</h1>
|
<h1 className='text-3xl'>{offersTabs.find(tab => tab.id === selectedTab)?.title} offers</h1>
|
||||||
<div className='flex gap-3'>
|
<div className='-mr-3'>
|
||||||
<SortMenu
|
<SortMenu
|
||||||
direction='desc'
|
direction='desc'
|
||||||
items={[
|
items={[
|
||||||
|
@ -312,10 +238,6 @@ export const OffersIndexModal = () => {
|
||||||
setSortOption(selectedOption);
|
setSortOption(selectedOption);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className='flex gap-3'>
|
|
||||||
<Button icon='layout-module-1' iconColorClass={selectedLayout === 'card' ? 'text-black' : 'text-grey-500'} link={true} size='sm' onClick={() => setSelectedLayout('card')} />
|
|
||||||
<Button icon='layout-headline' iconColorClass={selectedLayout === 'list' ? 'text-black' : 'text-grey-500'} link={true} size='sm' onClick={() => setSelectedLayout('list')} />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -331,7 +253,7 @@ export const OffersIndexModal = () => {
|
||||||
</NoValueLabel> :
|
</NoValueLabel> :
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
{selectedLayout === 'card' ? cardLayoutOutput : listLayoutOutput}
|
{listLayoutOutput}
|
||||||
</div>
|
</div>
|
||||||
</Modal>;
|
</Modal>;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue