0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-17 23:44:39 -05:00

Unified Offers modals to a single modal (#19011)

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


<!-- Leave the line below if you'd like GitHub Copilot to generate a
summary from your commit -->
<!--
copilot:summary
-->
### <samp>🤖[[deprecated]](https://githubnext.com/copilot-for-prs-sunset)
Generated by Copilot at b2390e4</samp>

Refactored the offers modal logic and routing in the admin-x-settings
app. Moved the offers modal components from `modals.tsx` to
`SettingsRouter.tsx` and `OffersContainerModal.tsx`. Replaced
`NiceModal` with `useModal` hook and simplified props and imports.
Enabled sub-routes for creating, editing, and viewing offers.
This commit is contained in:
Ronald Langeveld 2023-11-16 13:56:06 +07:00 committed by GitHub
parent c88f4f24d3
commit 9016d76d42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 118 additions and 119 deletions

View file

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

View file

@ -4,13 +4,13 @@ import type {RoutingModalProps} from '@tryghost/admin-x-framework/routing';
import AboutModal from '../../settings/general/About';
import AddIntegrationModal from '../../settings/advanced/integrations/AddIntegrationModal';
import AddNewsletterModal from '../../settings/email/newsletters/AddNewsletterModal';
import AddOfferModal from '../../settings/growth/offers/AddOfferModal';
// import AddOfferModal from '../../settings/growth/offers/AddOfferModal';
import AddRecommendationModal from '../../settings/growth/recommendations/AddRecommendationModal';
import AmpModal from '../../settings/advanced/integrations/AmpModal';
import AnnouncementBarModal from '../../settings/site/AnnouncementBarModal';
import CustomIntegrationModal from '../../settings/advanced/integrations/CustomIntegrationModal';
import DesignAndThemeModal from '../../settings/site/DesignAndThemeModal';
import EditOfferModal from '../../settings/growth/offers/EditOfferModal';
// import EditOfferModal from '../../settings/growth/offers/EditOfferModal';
import EditRecommendationModal from '../../settings/growth/recommendations/EditRecommendationModal';
import EmbedSignupFormModal from '../../settings/growth/embedSignup/EmbedSignupFormModal';
import FirstpromoterModal from '../../settings/advanced/integrations/FirstPromoterModal';
@ -19,7 +19,8 @@ import InviteUserModal from '../../settings/general/InviteUserModal';
import NavigationModal from '../../settings/site/NavigationModal';
import NewsletterDetailModal from '../../settings/email/newsletters/NewsletterDetailModal';
import OfferSuccess from '../../settings/growth/offers/OfferSuccess';
import OffersModal from '../../settings/growth/offers/OffersModal';
// import OffersModal from '../../settings/growth/offers/OffersIndex';
import OffersContainerModal from '../../settings/growth/offers/OffersContainerModal';
import PinturaModal from '../../settings/advanced/integrations/PinturaModal';
import PortalModal from '../../settings/membership/portal/PortalModal';
import SlackModal from '../../settings/advanced/integrations/SlackModal';
@ -52,9 +53,10 @@ const modals = {
ZapierModal,
AnnouncementBarModal,
EmbedSignupFormModal,
OffersModal,
AddOfferModal,
EditOfferModal,
OffersContainerModal,
// OffersModal,
// AddOfferModal,
// EditOfferModal,
AboutModal,
OfferSuccess
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View file

@ -1,4 +1,3 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import PortalFrame from '../../membership/portal/PortalFrame';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useForm from '../../../../hooks/useForm';
@ -9,6 +8,7 @@ import {getTiersCadences} from '../../../../utils/getTiersCadences';
import {useAddOffer} from '@tryghost/admin-x-framework/api/offers';
import {useEffect, useState} from 'react';
import {useGlobalData} from '../../../providers/GlobalDataProvider';
import {useModal} from '@ebay/nice-modal-react';
import {useRouting} from '@tryghost/admin-x-framework/routing';
// we should replace this with a library
@ -282,7 +282,6 @@ const AddOfferModal = () => {
const response = await addOffer(dataset);
if (response && response.offers && response.offers.length > 0) {
modal.remove();
updateRoute(`offers/success/${response.offers[0].id}`);
}
},
@ -399,7 +398,6 @@ const AddOfferModal = () => {
}, [hasOffers, modal, updateRoute]);
const cancelAddOffer = () => {
modal.remove();
updateRoute('offers/edit');
};
@ -448,4 +446,4 @@ const AddOfferModal = () => {
}} />;
};
export default NiceModal.create(AddOfferModal);
export default AddOfferModal;

View file

@ -4,12 +4,12 @@ import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useForm, {ErrorMessages} from '../../../../hooks/useForm';
import {Button, ConfirmationModal, Form, PreviewModalContent, TextArea, TextField, showToast} from '@tryghost/admin-x-design-system';
import {Offer, useBrowseOffersById, useEditOffer} from '@tryghost/admin-x-framework/api/offers';
import {RoutingModalProps, useRouting} from '@tryghost/admin-x-framework/routing';
import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site';
import {getOfferPortalPreviewUrl, offerPortalPreviewUrlTypes} from '../../../../utils/getOffersPortalPreviewUrl';
import {useEffect, useState} from 'react';
import {useGlobalData} from '../../../providers/GlobalDataProvider';
import {useHandleError} from '@tryghost/admin-x-framework/hooks';
import {useRouting} from '@tryghost/admin-x-framework/routing';
const Sidebar: React.FC<{
clearError: (field: string) => void,
@ -166,7 +166,7 @@ const Sidebar: React.FC<{
);
};
const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
const EditOfferModal: React.FC<{id: string}> = ({id}) => {
const {siteData} = useGlobalData();
const modal = useModal();
const {updateRoute} = useRouting();
@ -183,7 +183,7 @@ const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
}
}, [hasOffers, modal, updateRoute]);
const {data: {offers: offerById = []} = {}} = useBrowseOffersById(params?.id ? params?.id : '');
const {data: {offers: offerById = []} = {}} = useBrowseOffersById(id ? id : '');
const {formState, saveState, updateForm, setFormState, handleSave, validate, errors, clearError, okProps} = useForm({
initialState: offerById[0],
@ -223,27 +223,6 @@ const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
validate={validate}
/>;
// {
// "id": "65541d87ac4bfaf85f35e773",
// "name": "apples",
// "code": "apples",
// "display_title": "apples",
// "display_description": "A new appple",
// "type": "percent",
// "cadence": "month",
// "amount": 30,
// "duration": "forever",
// "duration_in_months": null,
// "currency_restriction": false,
// "currency": null,
// "status": "active",
// "redemption_count": 0,
// "tier": {
// "id": "6535e75005fd81e1492d0cca",
// "name": "Ronald SQLite Dev"
// }
// }
useEffect(() => {
const dataset : offerPortalPreviewUrlTypes = {
name: formState?.name || '',
@ -284,7 +263,6 @@ const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
testId='offer-update-modal'
title='Offer'
onCancel={() => {
modal.remove();
updateRoute('offers/edit');
}}
onOk={async () => {
@ -297,4 +275,4 @@ const EditOfferModal: React.FC<RoutingModalProps> = ({params}) => {
}} /> : null;
};
export default NiceModal.create(EditOfferModal);
export default EditOfferModal;

View file

@ -1,16 +1,12 @@
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 : '');
const OfferSuccess: React.FC<{id: string}> = ({id}) => {
const {data: {offers: offerById = []} = {}} = useBrowseOffersById(id ? id : '');
const [offerLink, setOfferLink] = useState<string>('');
@ -37,27 +33,20 @@ const OfferSuccess: React.FC<RoutingModalProps> = ({params}) => {
}
};
return <Modal
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' 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 />
</div>
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 />
</div>
</div>
</Modal>;
</div>;
};
export default NiceModal.create(OfferSuccess);
export default OfferSuccess;

View file

@ -0,0 +1,61 @@
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}) => {
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>
);
};
export default NiceModal.create(OffersContainerModal);

View file

@ -1,6 +1,5 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import {Button, Modal, Tab, TabView} from '@tryghost/admin-x-design-system';
import {Button, 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';
@ -8,6 +7,7 @@ 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 {useModal} from '@ebay/nice-modal-react';
import {useRouting} from '@tryghost/admin-x-framework/routing';
export type OfferType = 'percent' | 'fixed' | 'trial';
@ -105,7 +105,7 @@ const CopyLinkButton: React.FC<{offerCode: string}> = ({offerCode}) => {
<Button className='opacity-0 will-change-[opacity] group-hover:opacity-100' icon='hyperlink-circle' link={true} onClick={handleCopyClick} />;
};
const OffersModal = () => {
export const OffersIndexModal = () => {
const modal = useModal();
const {updateRoute} = useRouting();
const hasOffers = useFeatureFlag('adminXOffers');
@ -133,8 +133,7 @@ const OffersModal = () => {
const handleOfferEdit = (id:string) => {
// TODO: implement
modal.remove();
updateRoute(`offers/${id}`);
updateRoute(`offers/edit/${id}`);
};
const cardLayoutOutput = <div className='mt-8 grid grid-cols-3 gap-6'>
@ -194,54 +193,26 @@ const OffersModal = () => {
})}
</table>;
return <Modal
afterClose={() => {
updateRoute('offers');
}}
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');
}} />
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')} />
</div>
}
header={false}
height='full'
size='lg'
testId='offers-modal'
stickyFooter
>
<div className='pt-6'>
<header>
<div className='flex items-center justify-between'>
<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 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 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>;
</div>
</header>
{selectedLayout === 'card' ? cardLayoutOutput : listLayoutOutput}
</div>;
};
export default NiceModal.create(OffersModal);