0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-08 02:52:39 -05:00

Fixed duplicate modals issue in offers AdminX

refs https://github.com/TryGhost/Product/issues/4138

- we should follow the same pattern as change modal flow for the Offers modals
- with the change theme flow, only one modal is shown at a time to make it feel like navigating inside one container modal
- this removes the container modal from OffersContainerModal and puts back the Modal component to each screens, so there will be no modal on top of another
This commit is contained in:
Sodbileg Gansukh 2023-11-16 17:08:38 +08:00
parent 9016d76d42
commit 6de3e85bd0
6 changed files with 98 additions and 75 deletions

View file

@ -31,10 +31,9 @@ export const modalPaths: {[key: string]: ModalName} = {
'recommendations/edit': 'EditRecommendationModal',
'announcement-bar/edit': 'AnnouncementBarModal',
'embed-signup-form/show': 'EmbedSignupFormModal',
'offers/edit/*': 'OffersContainerModal',
// 'offers/new': 'AddOfferModal',
// 'offers/success/:id': 'OfferSuccess',
// 'offers/:id': 'EditOfferModal',
'offers/edit': 'OffersContainerModal',
'offers/edit/:id': 'OffersContainerModal',
'offers/new': 'OffersContainerModal',
about: 'AboutModal'
};

View file

@ -429,11 +429,12 @@ const AddOfferModal = () => {
cancelLabel='Cancel'
deviceSelector={false}
dirty={saveState === 'unsaved'}
height='full'
okColor={okProps.color}
okLabel='Publish'
preview={iframe}
sidebar={sidebar}
size='full'
size='lg'
title='Offer'
onCancel={cancelAddOffer}
onOk={async () => {

View file

@ -253,13 +253,15 @@ const EditOfferModal: React.FC<{id: string}> = ({id}) => {
href={href}
/>;
return offerById ? <PreviewModalContent deviceSelector={false}
return offerById ? <PreviewModalContent
deviceSelector={false}
dirty={saveState === 'unsaved'}
height='full'
okColor={okProps.color}
okLabel={okProps.label || 'Save'}
preview={iframe}
sidebar={sidebar}
size='full'
size='lg'
testId='offer-update-modal'
title='Offer'
onCancel={() => {

View file

@ -1,22 +1,27 @@
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 {Offer, useBrowseOffersById} from '@tryghost/admin-x-framework/api/offers';
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';
import {useRouting} from '@tryghost/admin-x-framework/routing';
const OfferSuccess: React.FC<{id: string}> = ({id}) => {
const {updateRoute} = useRouting();
const {data: {offers: offerById = []} = {}} = useBrowseOffersById(id ? id : '');
const [offer, setOffer] = useState<Offer>();
const [offerLink, setOfferLink] = useState<string>('');
const {siteData} = useGlobalData();
useEffect(() => {
if (offerById.length > 0) {
const offer = offerById[0];
const offerUrl = `${getHomepageUrl(siteData!)}${offer?.code}`;
const currentOffer = offerById[0];
const offerUrl = `${getHomepageUrl(siteData!)}${currentOffer?.code}`;
setOfferLink(offerUrl);
setOffer(currentOffer);
}
}, [offerById, siteData]);
@ -33,20 +38,43 @@ const OfferSuccess: React.FC<{id: string}> = ({id}) => {
}
};
return <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>
<div className='flex gap-2'>
<Button className='h-8 border border-grey-300' disabled={true} icon='twitter-x' iconColorClass='w-[14px] h-[14px]' size='sm' fullWidth />
<Button className='h-8 border border-grey-300' disabled={true} icon='facebook' size='sm' fullWidth />
<Button className='h-8 border border-grey-300' disabled={true} icon='linkedin' size='sm' fullWidth />
const handleTwitter = () => {
window.open(`https://twitter.com/intent/tweet?url=${encodeURI(offerLink)}&text=${encodeURIComponent(offer?.name || '')}`, '_blank');
};
const handleFacebook = () => {
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURI(offerLink)}`, '_blank');
};
const handleLinkedIn = () => {
window.open(`http://www.linkedin.com/shareArticle?mini=true&url=${encodeURI(offerLink)}&title=${encodeURIComponent(offer?.name || '')}`, '_blank');
};
return <Modal
afterClose={() => {
updateRoute('offers');
}}
animate={false}
footer={false}
height='full'
size='lg'
topRightContent='close'
>
<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>
<div className='flex gap-2'>
<Button className='h-8 border border-grey-300' icon='twitter-x' iconColorClass='w-[14px] h-[14px]' size='sm' fullWidth onClick={handleTwitter} />
<Button className='h-8 border border-grey-300' icon='facebook' size='sm' fullWidth onClick={handleFacebook} />
<Button className='h-8 border border-grey-300' icon='linkedin' size='sm' fullWidth onClick={handleLinkedIn} />
</div>
</div>
</div>
</div>;
</Modal>;
};
export default OfferSuccess;

View file

@ -2,60 +2,30 @@ import AddOfferModal from './AddOfferModal';
import EditOfferModal from './EditOfferModal';
import NiceModal from '@ebay/nice-modal-react';
import OfferSuccess from './OfferSuccess';
import {Button, Modal} from '@tryghost/admin-x-design-system';
import {OffersIndexModal} from './OffersIndex';
import {useRouting} from '@tryghost/admin-x-framework/routing';
import {useState} from 'react';
type OffersRouteHandlerProps = {
route: string;
setIsIndex: (value: boolean) => void;
};
const OffersRouteHandler: React.FC<OffersRouteHandlerProps> = ({route, setIsIndex}) => {
const OffersRouteHandler: React.FC<OffersRouteHandlerProps> = ({route}) => {
if (route === 'offers/new') {
setIsIndex(false);
return <AddOfferModal />;
} else if (route.startsWith('offers/edit/') && route.length > 'offers/edit/'.length) {
setIsIndex(false);
const offerId = route.split('/').pop();
return <EditOfferModal id={offerId ? offerId : ''} />;
} else if (route.startsWith('offers/success/') && route.length > 'offers/success/'.length) {
setIsIndex(false);
const offerId = route.split('/').pop();
return <OfferSuccess id={offerId ? offerId : ''} />;
} else {
setIsIndex(true);
return <OffersIndexModal />;
}
};
const OffersContainerModal = () => {
const {route, updateRoute} = useRouting();
const [isIndex, setIsIndex] = useState<boolean>(true);
return (
<Modal
afterClose={() => {
updateRoute('offers');
}}
cancelLabel=''
footer={
isIndex && <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={() => {
updateRoute('offers');
}} />
</div>
}
header={false}
height='full'
size='lg'
stickyFooter= {isIndex}
testId='offers-modal'
>
<OffersRouteHandler route={route} setIsIndex={setIsIndex} />
</Modal>
);
const {route} = useRouting();
return <OffersRouteHandler route={route} />;
};
export default NiceModal.create(OffersContainerModal);

View file

@ -1,5 +1,6 @@
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import {Button, Tab, TabView} from '@tryghost/admin-x-design-system';
import {Modal} 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';
@ -193,26 +194,48 @@ export const OffersIndexModal = () => {
})}
</table>;
return <div className='pt-6'>
<header>
<div className='flex items-center justify-between'>
<TabView
border={false}
selectedTab={selectedTab}
tabs={offersTabs}
width='wide'
onTabChange={setSelectedTab}
/>
<Button color='green' icon='add' iconColorClass='green' label='New offer' link={true} size='sm' onClick={() => updateRoute('offers/new')} />
return <Modal
afterClose={() => {
updateRoute('offers');
}}
animate={false}
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>
<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')} />
}
header={false}
height='full'
size='lg'
testId='offers-modal'
stickyFooter
>
<div className='pt-6'>
<header>
<div className='flex items-center justify-between'>
<TabView
border={false}
selectedTab={selectedTab}
tabs={offersTabs}
width='wide'
onTabChange={setSelectedTab}
/>
<Button color='green' icon='add' iconColorClass='green' label='New offer' link={true} size='sm' onClick={() => updateRoute('offers/new')} />
</div>
</div>
</header>
{selectedLayout === 'card' ? cardLayoutOutput : listLayoutOutput}
</div>;
<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>
{selectedLayout === 'card' ? cardLayoutOutput : listLayoutOutput}
</div>
</Modal>;
};