mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
UI improvements for Offers in AdminX (#19008)
This commit is contained in:
parent
d0fa385848
commit
3d945e539b
3 changed files with 51 additions and 19 deletions
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="-0.75 -0.75 24 24" height="24" width="24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M8.437481250000001 17.98875c-0.26370937499999997 0.263625 -0.621328125 0.41175 -0.99421875 0.41175 -0.37288125 0 -0.730509375 -0.148125 -0.99421875 -0.41175l-5.042812499999999 -5.041875c-0.13067812499999998 -0.13059375 -0.23433749999999998 -0.28565625 -0.3050625 -0.45628125 -0.070734375 -0.17071875 -0.10713750000000001 -0.35362499999999997 -0.10713750000000001 -0.53840625 0 -0.1846875 0.036403125 -0.3676875 0.10713750000000001 -0.5383125000000001 0.070725 -0.17071875 0.174384375 -0.32578124999999997 0.3050625 -0.45637500000000003L11.25 1.11376875c0.13059375 -0.13055624999999998 0.28575 -0.2341059375 0.45637500000000003 -0.304723125 0.17071875 -0.07061625 0.35362499999999997 -0.10692 0.5383125000000001 -0.106835625h5.041875c0.3729375 0 0.73059375 0.1481578125 0.9943124999999999 0.4118775 0.26371875 0.263728125 0.4119375 0.6214125 0.4119375 0.9943687499999999v5.042812499999999c-0.00009375 0.372703125 -0.148125 0.730125 -0.4115625 0.99375" stroke-width="1.5"></path><path stroke="currentColor" d="M15.1771875 4.56939375c-0.19415625 0 -0.3515625 -0.15739687500000002 -0.3515625 -0.3515625 0 -0.19415625 0.15740625 -0.3515625 0.3515625 -0.3515625" stroke-width="1.5"></path><path stroke="currentColor" d="M15.1771875 4.56939375c0.19415625 0 0.3515625 -0.15739687500000002 0.3515625 -0.3515625 0 -0.19415625 -0.15740625 -0.3515625 -0.3515625 -0.3515625" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m18.38709375 14.53125 -2.7234374999999997 3.631875c-0.06046875 0.08053125 -0.13753125 0.14709375000000002 -0.22593749999999999 0.19528125000000002 -0.0885 0.0481875 -0.1861875 0.07678125 -0.28668750000000004 0.08390625 -0.10040625 0.007031249999999999 -0.20118750000000002 -0.0075 -0.29559375000000004 -0.04265625 -0.0943125 -0.035250000000000004 -0.18 -0.090375 -0.25115625 -0.16153125000000002l-1.40625 -1.40625" stroke-width="1.5"></path><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M15.8803125 21.795937499999997c3.10659375 0 5.625 -2.51840625 5.625 -5.625s-2.51840625 -5.625 -5.625 -5.625 -5.625 2.51840625 -5.625 5.625 2.51840625 5.625 5.625 5.625Z" stroke-width="1.5"></path></svg>
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -1,11 +1,13 @@
|
|||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import {Button} from '@tryghost/admin-x-design-system';
|
||||
import {Icon} from '@tryghost/admin-x-design-system';
|
||||
import {Modal} from '@tryghost/admin-x-design-system';
|
||||
import {RoutingModalProps} from '@tryghost/admin-x-framework/routing';
|
||||
import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site';
|
||||
import {useBrowseOffersById} from '@tryghost/admin-x-framework/api/offers';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
|
||||
const OfferSuccess: React.FC<RoutingModalProps> = ({params}) => {
|
||||
// const {updateRoute} = useRouting();
|
||||
const {data: {offers: offerById = []} = {}} = useBrowseOffersById(params?.id ? params?.id : '');
|
||||
|
@ -34,16 +36,17 @@ const OfferSuccess: React.FC<RoutingModalProps> = ({params}) => {
|
|||
console.error('Failed to copy text: ', err);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return <Modal
|
||||
footer={false}
|
||||
height='full'
|
||||
size='lg'
|
||||
topRightContent='close'
|
||||
>
|
||||
<div className='flex h-full flex-col items-center justify-center text-center'>
|
||||
<h1 className='text-4xl'>Your new offer is live!</h1>
|
||||
<p className='mt-4 max-w-[510px] text-[1.6rem]'>You can share the link anywhere. In your newsletter, social media, a podcast, or in-person. It all just works.</p>
|
||||
<div className='-mt-6 flex h-full flex-col items-center justify-center text-center'>
|
||||
<Icon name='tags-check' size='xl' />
|
||||
<h1 className='mt-6 text-4xl'>Your new offer is live!</h1>
|
||||
<p className='mt-3 max-w-[510px] text-[1.6rem]'>You can share the link anywhere. In your newsletter, social media, a podcast, or in-person. It all just works.</p>
|
||||
<div className='mt-8 flex w-full max-w-md flex-col gap-8'>
|
||||
<Button color='green' label={isCopied ? 'Copied!' : 'Copy link'} fullWidth onClick={handleCopyClick} />
|
||||
<div className='flex items-center gap-4 text-xs font-medium before:h-px before:grow before:bg-grey-300 before:content-[""] after:h-px after:grow after:bg-grey-300 after:content-[""]'>OR</div>
|
||||
|
|
|
@ -3,9 +3,11 @@ import useFeatureFlag from '../../../../hooks/useFeatureFlag';
|
|||
import {Button, Modal, Tab, TabView} from '@tryghost/admin-x-design-system';
|
||||
import {Tier, getPaidActiveTiers, useBrowseTiers} from '@tryghost/admin-x-framework/api/tiers';
|
||||
import {currencyToDecimal, getSymbol} from '../../../../utils/currency';
|
||||
import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site';
|
||||
import {numberWithCommas} from '../../../../utils/helpers';
|
||||
import {useBrowseOffers} from '@tryghost/admin-x-framework/api/offers';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
export type OfferType = 'percent' | 'fixed' | 'trial';
|
||||
|
@ -82,6 +84,27 @@ const OfferCard: React.FC<{amount: number, cadence: string, currency: string, du
|
|||
);
|
||||
};
|
||||
|
||||
const CopyLinkButton: React.FC<{offerCode: string}> = ({offerCode}) => {
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
const {siteData} = useGlobalData();
|
||||
|
||||
const handleCopyClick = async () => {
|
||||
const offerLink = `${getHomepageUrl(siteData!)}${offerCode}`;
|
||||
try {
|
||||
await navigator.clipboard.writeText(offerLink);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000); // reset after 2 seconds
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to copy text: ', err);
|
||||
}
|
||||
};
|
||||
|
||||
return isCopied ?
|
||||
<span className='inline-block leading-[22px] text-green'>Copied</span> :
|
||||
<Button className='opacity-0 will-change-[opacity] group-hover:opacity-100' icon='hyperlink-circle' link={true} onClick={handleCopyClick} />;
|
||||
};
|
||||
|
||||
const OffersModal = () => {
|
||||
const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
|
@ -147,7 +170,7 @@ const OffersModal = () => {
|
|||
<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'>Redemptions</th>
|
||||
<th className='px-5 py-2.5 pr-0 text-xs font-normal text-grey-700'></th>
|
||||
<th className='min-w-[80px] px-5 py-2.5 pr-0 text-xs font-normal text-grey-700'></th>
|
||||
</tr>
|
||||
{allOffers.filter(offer => offer.status === selectedTab).map((offer) => {
|
||||
const offerTier = paidActiveTiers.find(tier => tier.id === offer?.tier.id);
|
||||
|
@ -159,13 +182,13 @@ const OffersModal = () => {
|
|||
const {discountColor, discountOffer, originalPriceWithCurrency, updatedPriceWithCurrency} = getOfferDiscount(offer.type, offer.amount, offer.cadence, offer.currency || 'USD', offerTier);
|
||||
|
||||
return (
|
||||
<tr className='border-b border-b-grey-200'>
|
||||
<td className='p-5 pl-0 font-semibold'>{offer?.name}</td>
|
||||
<td className='p-5 text-sm'>{offerTier.name} {getOfferCadence(offer.cadence)}</td>
|
||||
<td className='p-5 text-sm'><span className={`font-semibold uppercase ${discountColor}`}>{discountOffer}</span> — {getOfferDuration(offer.duration)}</td>
|
||||
<td className='p-5 text-sm'>{updatedPriceWithCurrency} <span className='text-grey-700 line-through'>{originalPriceWithCurrency}</span></td>
|
||||
<td className='p-5 text-sm'><a className='hover:underline' href={createRedemptionFilterUrl(offer.id ? offer.id : '')}>{offer.redemption_count}</a></td>
|
||||
<td className='p-5 pr-0 text-right text-sm leading-none'><Button icon='hyperlink-circle' link={true} /></td>
|
||||
<tr className='group border-b border-b-grey-200'>
|
||||
<td className='p-0 font-semibold'><a className='block cursor-pointer p-5 pl-0' onClick={() => handleOfferEdit(offer?.id ? offer.id : '')}>{offer?.name}</a></td>
|
||||
<td className='p-0 text-sm'><a className='block cursor-pointer p-5' onClick={() => handleOfferEdit(offer?.id ? offer.id : '')}>{offerTier.name} {getOfferCadence(offer.cadence)}</a></td>
|
||||
<td className='p-0 text-sm'><a className='block cursor-pointer p-5' onClick={() => handleOfferEdit(offer?.id ? offer.id : '')}><span className={`font-semibold uppercase ${discountColor}`}>{discountOffer}</span> — {getOfferDuration(offer.duration)}</a></td>
|
||||
<td className='p-0 text-sm'><a className='block cursor-pointer p-5' onClick={() => handleOfferEdit(offer?.id ? offer.id : '')}>{updatedPriceWithCurrency} <span className='text-grey-700 line-through'>{originalPriceWithCurrency}</span></a></td>
|
||||
<td className='p-0 text-sm'><a className='block cursor-pointer p-5 hover:underline' href={createRedemptionFilterUrl(offer.id ? offer.id : '')}>{offer.redemption_count}</a></td>
|
||||
<td className='min-w-[80px] p-5 pr-0 text-right text-sm leading-none'><CopyLinkButton offerCode={offer.code} /></td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
|
@ -194,13 +217,18 @@ const OffersModal = () => {
|
|||
<div className='pt-6'>
|
||||
<header>
|
||||
<div className='flex items-center justify-between'>
|
||||
<TabView
|
||||
border={false}
|
||||
selectedTab={selectedTab}
|
||||
tabs={offersTabs}
|
||||
width='wide'
|
||||
onTabChange={setSelectedTab}
|
||||
/>
|
||||
<div>
|
||||
{allOffers.some(offer => offer.hasOwnProperty('status') && offer.status === 'archived') ?
|
||||
<TabView
|
||||
border={false}
|
||||
selectedTab={selectedTab}
|
||||
tabs={offersTabs}
|
||||
width='wide'
|
||||
onTabChange={setSelectedTab}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<Button color='green' icon='add' iconColorClass='green' label='New offer' link={true} size='sm' onClick={() => updateRoute('offers/new')} />
|
||||
</div>
|
||||
<div className='mt-12 flex items-center justify-between border-b border-b-grey-300 pb-2.5'>
|
||||
|
|
Loading…
Add table
Reference in a new issue