Added AdminX Embeddable signup forms UI (#17972)

refs https://github.com/TryGhost/Product/issues/3819
This commit is contained in:
Peter Zimon 2023-09-07 07:49:18 +03:00 committed by GitHub
parent 8c91662a47
commit b91714f80e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 194 additions and 23 deletions

View File

@ -51,6 +51,18 @@ export const WithHint: Story = {
}
};
export const Monospace: Story = {
render: function Component(args) {
const [, updateArgs] = useArgs();
return <TextArea {...args} onChange={e => updateArgs({value: e.target.value})} />;
},
args: {
title: 'Code',
fontStyle: 'mono',
value: `<html><body><h1>✨</h1></body></html>`
}
};
export const Resizeable: Story = {
args: {
title: 'Description',

View File

@ -5,6 +5,7 @@ import Hint from '../Hint';
import clsx from 'clsx';
type ResizeOptions = 'both' | 'vertical' | 'horizontal' | 'none';
type FontStyles = 'sans' | 'mono';
interface TextAreaProps {
inputRef?: React.RefObject<HTMLTextAreaElement>;
@ -17,6 +18,8 @@ interface TextAreaProps {
placeholder?: string;
hint?: React.ReactNode;
clearBg?: boolean;
fontStyle?: FontStyles;
className?: string;
onChange?: (event: React.ChangeEvent<HTMLTextAreaElement>) => void;
}
@ -31,6 +34,8 @@ const TextArea: React.FC<TextAreaProps> = ({
placeholder,
hint,
clearBg = true,
fontStyle = 'sans',
className,
onChange,
...props
}) => {
@ -40,7 +45,9 @@ const TextArea: React.FC<TextAreaProps> = ({
'peer order-2 rounded-sm border px-3 py-2',
clearBg ? 'bg-transparent' : 'bg-grey-75',
error ? 'border-red' : 'border-grey-500 hover:border-grey-700 focus:border-grey-800',
title && 'mt-2'
title && 'mt-2',
fontStyle === 'mono' && 'font-mono text-sm',
className
);
switch (resize) {

View File

@ -118,7 +118,7 @@ export const CompletePage: Story = {
args: {
size: 'full',
footer: <></>,
noPadding: true,
padding: false,
children: <>
<ModalPage heading='Hey there full page'>
<p>This is a full page in a modal</p>

View File

@ -25,7 +25,7 @@ export interface ModalProps {
leftButtonProps?: ButtonProps;
buttonsDisabled?: boolean;
footer?: boolean | React.ReactNode;
noPadding?: boolean;
padding?: boolean;
onOk?: () => void;
onCancel?: () => void;
topRightContent?: 'close' | React.ReactNode;
@ -49,7 +49,7 @@ const Modal: React.FC<ModalProps> = ({
footer,
leftButtonProps,
buttonsDisabled,
noPadding = false,
padding = true,
onOk,
okColor = 'black',
onCancel,
@ -139,61 +139,61 @@ const Modal: React.FC<ModalProps> = ({
'fixed inset-0 z-40 h-[100vh] w-[100vw]'
);
let padding = '';
let paddingClasses = '';
switch (size) {
case 'sm':
modalClasses += ' max-w-[480px] ';
backdropClasses += ' p-[8vmin]';
padding = 'p-8';
paddingClasses = 'p-8';
break;
case 'md':
modalClasses += ' max-w-[720px] ';
backdropClasses += ' p-[8vmin]';
padding = 'p-8';
paddingClasses = 'p-8';
break;
case 'lg':
modalClasses += ' max-w-[1020px] ';
backdropClasses += ' p-[4vmin]';
padding = 'p-8';
paddingClasses = 'p-8';
break;
case 'xl':
modalClasses += ' max-w-[1240px] ';
backdropClasses += ' p-[3vmin]';
padding = 'p-10';
paddingClasses = 'p-10';
break;
case 'full':
modalClasses += ' h-full ';
backdropClasses += ' p-[3vmin]';
padding = 'p-10';
paddingClasses = 'p-10';
break;
case 'bleed':
modalClasses += ' h-full ';
padding = 'p-10';
paddingClasses = 'p-10';
break;
default:
backdropClasses += ' p-[8vmin]';
padding = 'p-8';
paddingClasses = 'p-8';
break;
}
if (noPadding) {
padding = 'p-0';
if (!padding) {
paddingClasses = 'p-0';
}
let footerClasses = clsx(
`${padding} ${stickyFooter ? 'py-6' : 'pt-0'}`,
`${paddingClasses} ${stickyFooter ? 'py-6' : 'pt-0'}`,
'flex w-full items-center justify-between'
);
let contentClasses = clsx(
padding,
paddingClasses,
((size === 'full' || size === 'bleed') && 'grow')
);

View File

@ -205,7 +205,7 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
afterClose={afterClose}
animate={false}
footer={false}
noPadding={true}
padding={false}
size={size}
testId={testId}
title=''

View File

@ -54,8 +54,9 @@ const Sidebar: React.FC = () => {
<SettingNavItem navid='access' title="Access" onClick={handleSectionClick} />
<SettingNavItem navid='tiers' title="Tiers" onClick={handleSectionClick} />
{hasTipsAndDonations && <SettingNavItem navid='tips-or-donations' title="Tips or donations" onClick={handleSectionClick} />}
<SettingNavItem navid='analytics' title="Analytics" onClick={handleSectionClick} />
<SettingNavItem navid='embed-signup-form' title="Embeddable signup form" onClick={handleSectionClick} />
{hasRecommendations && <SettingNavItem navid='recommendations' title="Recommendations" onClick={handleSectionClick} />}
<SettingNavItem navid='analytics' title="Analytics" onClick={handleSectionClick} />
</SettingNavSection>
<SettingNavSection title="Email newsletters">

View File

@ -7,6 +7,7 @@ import ChangeThemeModal from '../settings/site/ThemeModal';
import CustomIntegrationModal from '../settings/advanced/integrations/CustomIntegrationModal';
import DesignModal from '../settings/site/DesignModal';
import EditRecommendationModal from '../settings/site/recommendations/EditRecommendationModal';
import EmbedSignupFormModal from '../settings/membership/EmbedSignupFormModal';
import FirstpromoterModal from '../settings/advanced/integrations/FirstPromoterModal';
import HistoryModal from '../settings/advanced/HistoryModal';
import InviteUserModal from '../settings/general/InviteUserModal';
@ -79,7 +80,8 @@ const modalPaths: {[key: string]: React.FC<NiceModalHocProps & RoutingModalProps
'integrations/show/:id': CustomIntegrationModal,
'recommendations/add': AddRecommendationModal,
'recommendations/:id': EditRecommendationModal,
'announcement-bar/edit': AnnouncementBarModal
'announcement-bar/edit': AnnouncementBarModal,
'embed-signup-form/show': EmbedSignupFormModal
};
function getHashPath(urlPath: string | undefined) {

View File

@ -0,0 +1,24 @@
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';
const EmbedSignupForm: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {updateRoute} = useRouting();
const openPreviewModal = () => {
updateRoute('embed-signup-form/show');
};
return (
<SettingGroup
customButtons={<Button color='green' label='Embed' link onClick={openPreviewModal}/>}
description="Grow your audience from anywhere on the web"
keywords={keywords}
navid='embed-signup-form'
testId='embed-signup-form'
title="Embeddable signup form"
/>
);
};
export default EmbedSignupForm;

View File

@ -0,0 +1,122 @@
import Button from '../../../admin-x-ds/global/Button';
import ColorIndicator from '../../../admin-x-ds/global/form/ColorIndicator';
import Form from '../../../admin-x-ds/global/form/Form';
import Heading from '../../../admin-x-ds/global/Heading';
import Modal from '../../../admin-x-ds/global/modal/Modal';
import MultiSelect from '../../../admin-x-ds/global/form/MultiSelect';
import NiceModal from '@ebay/nice-modal-react';
import Radio from '../../../admin-x-ds/global/form/Radio';
import TextArea from '../../../admin-x-ds/global/form/TextArea';
import useRouting from '../../../hooks/useRouting';
const Preview: React.FC = () => {
return (
<div className='rounded-md bg-grey-100 text-grey-600'>
preview
</div>
);
};
const Sidebar: React.FC = () => {
return (
<div className='flex h-full flex-col justify-between'>
<div>
<Heading className='mb-4' level={4}>Embed signup form</Heading>
<Form>
<Radio
id='embed-layout'
options={[
{
label: 'Branded',
value: 'branded'
},
{
label: 'Minimal',
value: 'minimal'
}
]}
selectedOption='branded'
title='Layout'
onSelect={() => {}}
/>
<ColorIndicator
isExpanded={false}
swatches={[
{
hex: '#08090c',
title: 'Dark'
},
{
hex: '#ffffff',
title: 'Light'
},
{
hex: '#ffdd00',
title: 'Accent'
}
]}
swatchSize='lg'
title='Background color'
onSwatchChange={() => {}}
onTogglePicker={() => {}}
/>
<MultiSelect
hint='Will be applied to all members signing up via this form'
options={[
{
label: 'Steph',
value: 'steph'
},
{
label: 'Klay',
value: 'klay'
},
{
label: 'Loons',
value: 'loons'
}
]}
placeholder='Pick one or more labels (optional)'
title='Labels at signup'
values={[]}
onChange={() => {}}
/>
<TextArea
className='text-grey-800'
clearBg={false}
fontStyle='mono'
hint={`Paste this code onto any website where you'd like your signup to appear.`}
title='Embed code'
value={`<div style="height: 40vmin;min-height: 360px"><script src="https://cdn.jsdelivr.net/ghost/signup-form@~0.1/umd/signup-form.min.js" data-background-color="#F1F3F4" data-text-color="#000000" data-button-color="#d74780" data-button-text-color="#FFFFFF" data-title="Zimo&#039;s Secret Volcano Lair" data-description="You Know, I Have One Simple Request, And That Is To Have Sharks With Frickin&#039; Laser Beams Attached To Their Heads!" data-site="http://localhost:2368" async></script></div>`}
/>
</Form>
</div>
<Button className='self-end' color='black' label='Copy code' />
</div>
);
};
const EmbedSignupFormModal = NiceModal.create(() => {
const {updateRoute} = useRouting();
return (
<Modal
afterClose={() => {
updateRoute('embed-signup-form');
}}
cancelLabel=''
footer={false}
size={1120}
testId='embed-signup-form'
title=''
topRightContent='close'
>
<div className='grid grid-cols-[5.5fr_2.5fr] gap-6 pb-8'>
<Preview />
<Sidebar />
</div>
</Modal>
);
});
export default EmbedSignupFormModal;

View File

@ -1,5 +1,6 @@
import Access from './Access';
import Analytics from './Analytics';
import EmbedSignupForm from './EmbedSignupForm';
import Portal from './Portal';
import React from 'react';
import Recommendations from '../site/Recommendations';
@ -13,8 +14,9 @@ const searchKeywords = {
access: ['default', 'access', 'subscription', 'post', 'membership'],
tiers: ['tiers', 'payment', 'paid'],
tips: ['tip', 'donation', 'one time', 'payment'],
analytics: ['analytics', 'tracking', 'privacy', 'membership'],
recommendations: ['recommendation', 'recommend', 'blogroll']
embedSignupForm: ['signup', 'form', 'embed'],
recommendations: ['recommendation', 'recommend', 'blogroll'],
analytics: ['analytics', 'tracking', 'privacy', 'membership']
};
const MembershipSettings: React.FC = () => {
@ -27,8 +29,9 @@ const MembershipSettings: React.FC = () => {
<Access keywords={searchKeywords.access} />
<Tiers keywords={searchKeywords.tiers} />
{hasTipsAndDonations && <TipsOrDonations keywords={searchKeywords.tips} />}
<Analytics keywords={searchKeywords.analytics} />
<EmbedSignupForm keywords={searchKeywords.embedSignupForm} />
{hasRecommendations && <Recommendations keywords={searchKeywords.recommendations} />}
<Analytics keywords={searchKeywords.analytics} />
</SettingSection>
);
};

View File

@ -274,7 +274,7 @@ const ChangeThemeModal = NiceModal.create(() => {
}}
cancelLabel=''
footer={false}
noPadding={true}
padding={false}
size='full'
testId='theme-modal'
title=''