0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added static version of Offers list and new screens to AdminX (#18812)

This commit is contained in:
Sodbileg Gansukh 2023-11-01 18:19:14 +08:00 committed by GitHub
parent 62ecb1ebd8
commit 7904d1c832
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 188 additions and 4 deletions

View file

@ -0,0 +1 @@
<svg viewBox="-0.75 -0.75 24 24" xmlns="http://www.w3.org/2000/svg" height="24" width="24"><path d="m2.109375 20.390625 18.28125 -18.28125Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M14.765625 17.578125a2.8125 2.8125 0 1 0 5.625 0 2.8125 2.8125 0 1 0 -5.625 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path><path d="M2.109375 4.921875a2.8125 2.8125 0 1 0 5.625 0 2.8125 2.8125 0 1 0 -5.625 0Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"></path></svg>

After

Width:  |  Height:  |  Size: 645 B

View file

@ -109,7 +109,7 @@ const Sidebar: React.FC = () => {
<SettingNavItem icon='emailfield' keywords={membershipSearchKeywords.embedSignupForm} navid='embed-signup-form' title="Embeddable signup form" onClick={handleSectionClick} />
{hasRecommendations && <SettingNavItem icon='heart' keywords={membershipSearchKeywords.recommendations} navid='recommendations' title="Recommendations" onClick={handleSectionClick} />}
<SettingNavItem icon='baseline-chart' keywords={membershipSearchKeywords.analytics} navid='analytics' title="Analytics" onClick={handleSectionClick} />
{hasOffers && <SettingNavItem icon='ellipsis' keywords={membershipSearchKeywords.offers} navid='offers' title="Offers" onClick={handleSectionClick} />}
{hasOffers && <SettingNavItem icon='discount' keywords={membershipSearchKeywords.offers} navid='offers' title="Offers" onClick={handleSectionClick} />}
</SettingNavSection>
<SettingNavSection keywords={Object.values(emailSearchKeywords).flat()} title="Email newsletter">

View file

@ -17,7 +17,7 @@ const Offers: React.FC<{ keywords: string[] }> = ({keywords}) => {
return (
<SettingGroup
customButtons={<Button color='green' disabled={!checkStripeEnabled(settings, config)} label='Manage offers' link linkWithPadding onClick={openModal}/>}
description='Grow your audience by providing fixed or percentage discounts. [Learn more]'
description={<>Grow your audience by providing fixed or percentage discounts. <a className='text-green' href="https://ghost.org/help/offers" rel="noopener noreferrer" target="_blank">Learn more</a></>}
keywords={keywords}
navid='offers'
testId='offers'

View file

@ -1,9 +1,126 @@
import Form from '../../../../admin-x-ds/global/form/Form';
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import Select from '../../../../admin-x-ds/global/form/Select';
import TextArea from '../../../../admin-x-ds/global/form/TextArea';
import TextField from '../../../../admin-x-ds/global/form/TextField';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useRouting from '../../../../hooks/useRouting';
import {ReactComponent as CheckIcon} from '../../../../admin-x-ds/assets/icons/check.svg';
import {PreviewModalContent} from '../../../../admin-x-ds/global/modal/PreviewModal';
import {useEffect} from 'react';
interface OfferType {
title: string;
description: string;
}
const ButtonSelect: React.FC<{type: OfferType, checked: boolean}> = ({type, checked}) => {
const checkboxClass = checked ? 'bg-black text-white' : 'border border-grey-300';
return (
<button className='text-left' type='button'>
<div className='flex gap-3'>
<div className={`mt-0.5 flex h-4 w-4 items-center justify-center rounded-full ${checkboxClass}`}>
{checked ? <CheckIcon className='w-2 stroke-[4]' /> : null}
</div>
<div className='flex flex-col'>
<span>{type.title}</span>
<span className='text-sm'>{type.description}</span>
</div>
</div>
</button>
);
};
const Sidebar: React.FC = () => {
const typeOptions = [
{title: 'Discount', description: 'Offer a special reduced price'},
{title: 'Free trial', description: 'Give free access for a limited time'}
];
const tierCadenceOptions = [
{value: '1', label: 'Bronze — Monthly'},
{value: '2', label: 'Bronze — Yearly'},
{value: '3', label: 'Silver — Monthly'},
{value: '4', label: 'Silver — Yearly'},
{value: '5', label: 'Gold — Monthly'},
{value: '6', label: 'Gold — Yearly'}
];
const amountOptions = [
{value: '1', label: '%'},
{value: '2', label: 'USD'}
];
const durationOptions = [
{value: '1', label: 'First-payment'},
{value: '2', label: 'Multiple-months'},
{value: '3', label: 'Forever'}
];
return (
<div className='pt-7'>
<Form>
<TextField
hint='Visible to members on Stripe Checkout page.'
placeholder='Black Friday'
title='Name'
/>
<section className='mt-4'>
<h2 className='mb-4 text-lg'>Offer details</h2>
<div className='flex flex-col gap-6'>
<div className='flex flex-col gap-4 rounded-md border border-grey-200 p-4'>
<ButtonSelect checked={true} type={typeOptions[0]} />
<ButtonSelect checked={false} type={typeOptions[1]} />
</div>
<Select
options={tierCadenceOptions}
selectedOption={tierCadenceOptions[0]}
title='Tier — Cadence'
onSelect={() => {}}
/>
<div className='relative'>
<TextField title='Amount off' type='number' />
<div className='absolute bottom-0 right-1.5 z-10 w-10'>
<Select
clearBg={true}
controlClasses={{menu: 'w-20 right-0'}}
options={amountOptions}
selectedOption={amountOptions[0]}
onSelect={() => {}}
/>
</div>
</div>
<Select
options={durationOptions}
selectedOption={durationOptions[0]}
title='Duration'
onSelect={() => {}}
/>
</div>
</section>
<section className='mt-4'>
<h2 className='mb-4 text-lg'>Portal Settings</h2>
<div className='flex flex-col gap-6'>
<TextField
placeholder='Black Friday Special'
title='Display title'
/>
<TextField
placeholder='black-friday'
title='Offer code'
/>
<TextArea
placeholder='Take advantage of this limited-time offer.'
title='Display description'
/>
</div>
</section>
</Form>
</div>
);
};
const AddOfferModal = () => {
const modal = useModal();
const {updateRoute} = useRouting();
@ -16,7 +133,9 @@ const AddOfferModal = () => {
}
}, [hasOffers, modal, updateRoute]);
return <PreviewModalContent sidebar={<></>} title='Add Offer' />;
const sidebar = <Sidebar />;
return <PreviewModalContent deviceSelector={false} okLabel='Publish' sidebar={sidebar} size='full' title='Offer' />;
};
export default NiceModal.create(AddOfferModal);

View file

@ -1,9 +1,46 @@
import Button from '../../../../admin-x-ds/global/Button';
import Modal from '../../../../admin-x-ds/global/modal/Modal';
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import TabView, {Tab} from '../../../../admin-x-ds/global/TabView';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useRouting from '../../../../hooks/useRouting';
import {useEffect} from 'react';
export type OfferType = 'percent' | 'fixed' | 'trial';
const OfferCard: React.FC<{name: string, type: OfferType}> = ({name, type}) => {
let discountColor = '';
switch (type) {
case 'percent':
discountColor = 'text-green';
break;
case 'fixed':
discountColor = 'text-blue';
break;
case 'trial':
discountColor = 'text-pink';
break;
default:
break;
}
return <div className='flex flex-col items-center gap-6 border border-transparent bg-grey-100 p-5 text-center transition-all hover:border-grey-100 hover:bg-grey-75 hover:shadow-sm dark:bg-grey-950 dark:hover:border-grey-800'>
<h2 className='text-[1.6rem]'>{name}</h2>
<div className=''>
<div className='flex gap-3 text-sm uppercase leading-none'>
<span className={`font-semibold ${discountColor}`}>10% off</span>
<span className='text-grey-700 line-through'>$5</span>
</div>
<span className='text-3xl font-bold'>$4</span>
</div>
<div className='flex flex-col items-center text-xs'>
<span className='font-medium'>Bronze monthly  First payment</span>
<a className='text-grey-700 hover:underline' href="/ghost/#/members">4 redemptions</a>
</div>
</div>;
};
const OffersModal = () => {
const modal = useModal();
const {updateRoute} = useRouting();
@ -16,7 +53,34 @@ const OffersModal = () => {
}
}, [hasOffers, modal, updateRoute]);
return <Modal title='Offers' />;
let offersTabs: Tab[] = [
{id: 'active', title: 'Active'},
{id: 'archived', title: 'Archived'}
];
return <Modal cancelLabel='' header={false} size='lg'>
<div className='pt-6'>
<header>
<div className='flex items-center justify-between'>
<TabView
border={false}
selectedTab='active'
tabs={offersTabs}
width='wide'
onTabChange={() => {}}
/>
<Button color='green' icon='add' iconColorClass='green' label='New offer' link={true} size='sm' />
</div>
<h1 className='mt-12 border-b border-b-grey-300 pb-2.5 text-3xl'>Active offers</h1>
</header>
<div className='mt-8 grid grid-cols-3 gap-6'>
<OfferCard name='Black friday' type='percent' />
<OfferCard name='Buy this right now' type='fixed' />
<OfferCard name='Desperate Sale!' type='trial' />
</div>
<a className='absolute bottom-10 text-sm' href="https://ghost.org/help/offers" rel="noopener noreferrer" target="_blank"> Learn about offers in Ghost</a>
</div>
</Modal>;
};
export default NiceModal.create(OffersModal);