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:
parent
c88f4f24d3
commit
9016d76d42
7 changed files with 118 additions and 119 deletions
|
@ -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'
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
Loading…
Add table
Reference in a new issue