Added feature flag for AdminX Offers (#18801)

closes https://github.com/TryGhost/Product/issues/4084

---

### <samp>🤖 Generated by Copilot at 9380e1f</samp>

This pull request adds a new UI for creating and managing offers for
members in the admin settings, which is controlled by an alpha feature
flag. It introduces new modal components for the offers UI, a new
sidebar item, new routes, and a new setting group. It also updates the
`labs.js` file to include the `adminXOffers` flag.
This commit is contained in:
Jono M 2023-10-31 08:41:35 +00:00 committed by GitHub
parent ab57071901
commit 3711260f9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 117 additions and 2 deletions

View File

@ -23,6 +23,7 @@ const Sidebar: React.FC = () => {
const {updateRoute} = useRouting();
const searchInputRef = useRef<HTMLInputElement | null>(null);
const {isAnyTextFieldFocused} = useFocusContext();
const hasOffers = useFeatureFlag('adminXOffers');
// Focus in on search field when pressing "/"
useEffect(() => {
@ -108,6 +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} />}
</SettingNavSection>
<SettingNavSection keywords={Object.values(emailSearchKeywords).flat()} title="Email newsletter">

View File

@ -62,6 +62,9 @@ const modalPaths: {[key: string]: ModalName} = {
'recommendations/edit': 'EditRecommendationModal',
'announcement-bar/edit': 'AnnouncementBarModal',
'embed-signup-form/show': 'EmbedSignupFormModal',
'offers/edit': 'OffersModal',
'offers/new': 'AddOfferModal',
'offers/:id': 'EditOfferModal',
about: 'AboutModal'
};

View File

@ -4,11 +4,13 @@ import type {RoutingModalProps} from '../RoutingProvider';
import AboutModal from '../../settings/general/About';
import AddIntegrationModal from '../../settings/advanced/integrations/AddIntegrationModal';
import AddNewsletterModal from '../../settings/email/newsletters/AddNewsletterModal';
import AddOfferModal from '../../settings/membership/offers/AddOfferModal';
import AddRecommendationModal from '../../settings/membership/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/membership/offers/EditOfferModal';
import EditRecommendationModal from '../../settings/membership/recommendations/EditRecommendationModal';
import EmbedSignupFormModal from '../../settings/membership/embedSignup/EmbedSignupFormModal';
import FirstpromoterModal from '../../settings/advanced/integrations/FirstPromoterModal';
@ -16,6 +18,7 @@ import HistoryModal from '../../settings/advanced/HistoryModal';
import InviteUserModal from '../../settings/general/InviteUserModal';
import NavigationModal from '../../settings/site/NavigationModal';
import NewsletterDetailModal from '../../settings/email/newsletters/NewsletterDetailModal';
import OffersModal from '../../settings/membership/offers/OffersModal';
import PinturaModal from '../../settings/advanced/integrations/PinturaModal';
import PortalModal from '../../settings/membership/portal/PortalModal';
import SlackModal from '../../settings/advanced/integrations/SlackModal';
@ -48,6 +51,9 @@ const modals = {
ZapierModal,
AnnouncementBarModal,
EmbedSignupFormModal,
OffersModal,
AddOfferModal,
EditOfferModal,
AboutModal
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} satisfies {[key: string]: ModalComponent<any>};

View File

@ -55,6 +55,10 @@ const features = [{
title: 'Editor emoji picker',
description: <>Trigger an emoji picker when typing <code>{':{search}'}</code> in the editor</>,
flag: 'editorEmojiPicker'
},{
title: 'AdminX Offers',
description: 'Enables the new offers UI in AdminX settings',
flag: 'adminXOffers'
}];
const AlphaFeatures: React.FC = () => {

View File

@ -1,6 +1,7 @@
import Access from './Access';
import Analytics from './Analytics';
import EmbedSignupForm from './embedSignup/EmbedSignupForm';
import Offers from './Offers';
import Portal from './Portal';
import React from 'react';
import Recommendations from './Recommendations';
@ -16,12 +17,14 @@ export const searchKeywords = {
tips: ['tip', 'donation', 'one time', 'payment'],
embedSignupForm: ['embeddable signup form', 'embeddable form', 'embeddable sign up form', 'embeddable sign up'],
recommendations: ['recommendations', 'recommend', 'blogroll'],
analytics: ['analytics', 'tracking', 'privacy', 'membership']
analytics: ['analytics', 'tracking', 'privacy', 'membership'],
offers: ['offers', 'discounts', 'coupons', 'promotions']
};
const MembershipSettings: React.FC = () => {
const hasTipsAndDonations = useFeatureFlag('tipsAndDonations');
const hasRecommendations = useFeatureFlag('recommendations');
const hasOffers = useFeatureFlag('adminXOffers');
return (
<SettingSection keywords={Object.values(searchKeywords).flat()} title='Membership'>
@ -32,6 +35,7 @@ const MembershipSettings: React.FC = () => {
<EmbedSignupForm keywords={searchKeywords.embedSignupForm} />
{hasRecommendations && <Recommendations keywords={searchKeywords.recommendations} />}
<Analytics keywords={searchKeywords.analytics} />
{hasOffers && <Offers keywords={searchKeywords.offers} />}
</SettingSection>
);
};

View File

@ -0,0 +1,29 @@
import Button from '../../../admin-x-ds/global/Button';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import useRouting from '../../../hooks/useRouting';
import {checkStripeEnabled} from '../../../api/settings';
import {useGlobalData} from '../../providers/GlobalDataProvider';
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
const Offers: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {updateRoute} = useRouting();
const {settings, config} = useGlobalData();
const openModal = () => {
updateRoute('offers/edit');
};
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]'
keywords={keywords}
navid='offers'
testId='offers'
title='Offers'
/>
);
};
export default withErrorBoundary(Offers, 'Portal settings');

View File

@ -0,0 +1,22 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useRouting from '../../../../hooks/useRouting';
import {PreviewModalContent} from '../../../../admin-x-ds/global/modal/PreviewModal';
import {useEffect} from 'react';
const AddOfferModal = () => {
const modal = useModal();
const {updateRoute} = useRouting();
const hasOffers = useFeatureFlag('adminXOffers');
useEffect(() => {
if (!hasOffers) {
modal.remove();
updateRoute('');
}
}, [hasOffers, modal, updateRoute]);
return <PreviewModalContent sidebar={<></>} title='Add Offer' />;
};
export default NiceModal.create(AddOfferModal);

View File

@ -0,0 +1,22 @@
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useRouting from '../../../../hooks/useRouting';
import {PreviewModalContent} from '../../../../admin-x-ds/global/modal/PreviewModal';
import {useEffect} from 'react';
const EditOfferModal = () => {
const modal = useModal();
const {updateRoute} = useRouting();
const hasOffers = useFeatureFlag('adminXOffers');
useEffect(() => {
if (!hasOffers) {
modal.remove();
updateRoute('');
}
}, [hasOffers, modal, updateRoute]);
return <PreviewModalContent sidebar={<></>} title='Edit Offer' />;
};
export default NiceModal.create(EditOfferModal);

View File

@ -0,0 +1,22 @@
import Modal from '../../../../admin-x-ds/global/modal/Modal';
import NiceModal, {useModal} from '@ebay/nice-modal-react';
import useFeatureFlag from '../../../../hooks/useFeatureFlag';
import useRouting from '../../../../hooks/useRouting';
import {useEffect} from 'react';
const OffersModal = () => {
const modal = useModal();
const {updateRoute} = useRouting();
const hasOffers = useFeatureFlag('adminXOffers');
useEffect(() => {
if (!hasOffers) {
modal.remove();
updateRoute('');
}
}, [hasOffers, modal, updateRoute]);
return <Modal title='Offers' />;
};
export default NiceModal.create(OffersModal);

View File

@ -43,7 +43,8 @@ const ALPHA_FEATURES = [
'importMemberTier',
'lexicalIndicators',
'listUnsubscribeHeader',
'editorEmojiPicker'
'editorEmojiPicker',
'adminXOffers'
];
module.exports.GA_KEYS = [...GA_FEATURES];