mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Added basic layout switch to the offers list in AdminX
refs https://github.com/TryGhost/Product/issues/4137 - separated logics to functions to use them on both card and list layouts - added toggle buttons that switch between layouts - created a very basic table for the list layout as a starting point
This commit is contained in:
parent
7799e0f47b
commit
5eb4e3330c
3 changed files with 83 additions and 30 deletions
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><title>layout-headline</title><path d="M2.109375 0.7003125h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 9.137812499999999h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 17.5753125h18.28125s1.40625 0 1.40625 1.40625v1.40625s0 1.40625 -1.40625 1.40625H2.109375s-1.40625 0 -1.40625 -1.40625v-1.40625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>
|
After Width: | Height: | Size: 995 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.75 -0.75 24 24" height="24" width="24"><defs></defs><title>layout-module-1</title><path d="M2.109375 0.7003125h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 13.356562499999999h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 0.7003125h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 13.356562499999999h5.625s1.40625 0 1.40625 1.40625v5.625s0 1.40625 -1.40625 1.40625h-5.625s-1.40625 0 -1.40625 -1.40625v-5.625s0 -1.40625 1.40625 -1.40625" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -15,12 +15,19 @@ const createRedemptionFilterUrl = (id: string): string => {
|
|||
return `${baseHref}?filter=${encodeURIComponent('offer_redemptions:' + id)}`;
|
||||
};
|
||||
|
||||
const OfferCard: React.FC<{amount: number, cadence: string, currency: string, duration: string, name: string, offerId: string, offerTier: Tier | undefined, redemptionCount: number, type: OfferType, onClick: ()=>void}> = ({amount, cadence, currency, duration, name, offerId, offerTier, redemptionCount, type, onClick}) => {
|
||||
const getOfferCadence = (cadence: string): string => {
|
||||
return cadence === 'month' ? 'monthly' : 'yearly';
|
||||
};
|
||||
|
||||
const getOfferDuration = (duration: string): string => {
|
||||
return (duration === 'once' ? 'First payment' : duration === 'repeating' ? 'Repeating' : 'Forever');
|
||||
};
|
||||
|
||||
const getOfferDiscount = (type: string, amount: number, cadence: string, currency: string, tier: Tier | undefined): {discountColor: string, discountOffer: string, originalPriceWithCurrency: string, updatedPriceWithCurrency: string} => {
|
||||
let discountColor = '';
|
||||
let discountOffer = '';
|
||||
const originalPrice = cadence === 'month' ? offerTier?.monthly_price ?? 0 : offerTier?.yearly_price ?? 0;
|
||||
const originalPrice = cadence === 'month' ? tier?.monthly_price ?? 0 : tier?.yearly_price ?? 0;
|
||||
let updatedPrice = originalPrice;
|
||||
let tierName = offerTier?.name + ' ' + (cadence === 'month' ? 'Monthly' : 'Yearly') + ' — ' + (duration === 'once' ? 'First payment' : duration === 'repeating' ? 'Repeating' : 'Forever');
|
||||
let originalPriceWithCurrency = getSymbol(currency) + numberWithCommas(currencyToDecimal(originalPrice));
|
||||
|
||||
switch (type) {
|
||||
|
@ -41,10 +48,22 @@ const OfferCard: React.FC<{amount: number, cadence: string, currency: string, du
|
|||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const updatedPriceWithCurrency = getSymbol(currency) + numberWithCommas(currencyToDecimal(updatedPrice));
|
||||
|
||||
return {
|
||||
discountColor,
|
||||
discountOffer,
|
||||
originalPriceWithCurrency,
|
||||
updatedPriceWithCurrency
|
||||
};
|
||||
};
|
||||
|
||||
const OfferCard: React.FC<{amount: number, cadence: string, currency: string, duration: string, name: string, offerId: string, offerTier: Tier | undefined, redemptionCount: number, type: OfferType, onClick: ()=>void}> = ({amount, cadence, currency, duration, name, 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);
|
||||
|
||||
return (
|
||||
<div className='flex cursor-pointer flex-col gap-6 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' onClick={onClick}>
|
||||
<div className='flex items-center justify-between'>
|
||||
|
@ -87,6 +106,7 @@ const OffersModal = () => {
|
|||
{id: 'archived', title: 'Archived'}
|
||||
];
|
||||
const [selectedTab, setSelectedTab] = useState('active');
|
||||
const [selectedLayout, setSelectedLayout] = useState('card');
|
||||
|
||||
const handleOfferEdit = (id:string) => {
|
||||
// TODO: implement
|
||||
|
@ -94,6 +114,54 @@ const OffersModal = () => {
|
|||
updateRoute(`offers/${id}`);
|
||||
};
|
||||
|
||||
const cardLayoutOutput = <div className='mt-8 grid grid-cols-3 gap-6'>
|
||||
{allOffers.filter(offer => offer.status === selectedTab).map((offer) => {
|
||||
const offerTier = paidActiveTiers.find(tier => tier.id === offer?.tier.id);
|
||||
|
||||
if (!offerTier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OfferCard
|
||||
key={offer?.id}
|
||||
amount={offer?.amount}
|
||||
cadence={offer?.cadence}
|
||||
currency={offer?.currency || 'USD'}
|
||||
duration={offer?.duration}
|
||||
name={offer?.name}
|
||||
offerId={offer?.id}
|
||||
offerTier={offerTier}
|
||||
redemptionCount={offer?.redemption_count}
|
||||
type={offer?.type as OfferType}
|
||||
onClick={() => handleOfferEdit(offer?.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>;
|
||||
|
||||
const listLayoutOutput = <table>
|
||||
{allOffers.filter(offer => offer.status === selectedTab).map((offer) => {
|
||||
const offerTier = paidActiveTiers.find(tier => tier.id === offer?.tier.id);
|
||||
|
||||
if (!offerTier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {discountColor, discountOffer, originalPriceWithCurrency, updatedPriceWithCurrency} = getOfferDiscount(offer.type, offer.amount, offer.cadence, offer.currency || 'USD', offerTier);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{offer?.name}</td>
|
||||
<td>{offerTier.name} {getOfferCadence(offer.cadence)}</td>
|
||||
<td><span className={`text-xs font-semibold uppercase ${discountColor}`}>{discountOffer}</span></td>
|
||||
<td>{updatedPriceWithCurrency}{originalPriceWithCurrency}</td>
|
||||
<td><a className='hover:underline' href={createRedemptionFilterUrl(offer.id)}>{offer.redemption_count}</a></td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</table>;
|
||||
|
||||
return <Modal
|
||||
afterClose={() => {
|
||||
updateRoute('offers');
|
||||
|
@ -109,6 +177,7 @@ const OffersModal = () => {
|
|||
</div>
|
||||
}
|
||||
header={false}
|
||||
height='full'
|
||||
size='lg'
|
||||
testId='offers-modal'
|
||||
stickyFooter
|
||||
|
@ -125,33 +194,15 @@ const OffersModal = () => {
|
|||
/>
|
||||
<Button color='green' icon='add' iconColorClass='green' label='New offer' link={true} size='sm' onClick={() => updateRoute('offers/new')} />
|
||||
</div>
|
||||
<h1 className='mt-12 border-b border-b-grey-300 pb-2.5 text-3xl'>{offersTabs.find(tab => tab.id === selectedTab)?.title} offers</h1>
|
||||
<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>
|
||||
<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>
|
||||
</header>
|
||||
<div className='mt-8 grid grid-cols-3 gap-6'>
|
||||
{allOffers.filter(offer => offer.status === selectedTab).map((offer) => {
|
||||
const offerTier = paidActiveTiers.find(tier => tier.id === offer?.tier.id);
|
||||
|
||||
if (!offerTier) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<OfferCard
|
||||
key={offer?.id}
|
||||
amount={offer?.amount}
|
||||
cadence={offer?.cadence}
|
||||
currency={offer?.currency || 'USD'}
|
||||
duration={offer?.duration}
|
||||
name={offer?.name}
|
||||
offerId={offer?.id}
|
||||
offerTier={offerTier}
|
||||
redemptionCount={offer?.redemption_count}
|
||||
type={offer?.type as OfferType}
|
||||
onClick={() => handleOfferEdit(offer?.id)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{selectedLayout === 'card' ? cardLayoutOutput : listLayoutOutput}
|
||||
</div>
|
||||
</Modal>;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue