Added AdminX Embeddable signup forms UI (#17972)
refs https://github.com/TryGhost/Product/issues/3819
This commit is contained in:
parent
8c91662a47
commit
b91714f80e
@ -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',
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
|
@ -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')
|
||||
);
|
||||
|
||||
|
@ -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=''
|
||||
|
@ -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">
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
@ -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's Secret Volcano Lair" data-description="You Know, I Have One Simple Request, And That Is To Have Sharks With Frickin' 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;
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -274,7 +274,7 @@ const ChangeThemeModal = NiceModal.create(() => {
|
||||
}}
|
||||
cancelLabel=''
|
||||
footer={false}
|
||||
noPadding={true}
|
||||
padding={false}
|
||||
size='full'
|
||||
testId='theme-modal'
|
||||
title=''
|
||||
|
Loading…
Reference in New Issue
Block a user