mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 20:03:12 +03:00
Wired up embeddable signup form to Admin X (#18010)
refs https://github.com/TryGhost/Product/issues/3819 - Wired up embeddable signup form to admin x. - minus the colour picker, to add in the next commit. --- <!-- Leave the line below if you'd like GitHub Copilot to generate a summary from your commit --> <!-- copilot:summary --> ### <samp>🤖 Generated by Copilot at 9a3f1b9</samp> This pull request introduces a new feature that allows users to embed a signup form for their blog site on other websites. It adds a new component `EmbedSignupFormModal` that renders a modal with form customization and code copying options. It also updates the `Config` type and the `config.ts` file to store and access the necessary data for the embed code generation.
This commit is contained in:
parent
e9bff23aa9
commit
b0662d2cf9
@ -11,6 +11,11 @@ export type Config = {
|
||||
url: string
|
||||
version: string
|
||||
};
|
||||
signupForm: {
|
||||
url: string,
|
||||
version: string
|
||||
}
|
||||
blogUrl: string;
|
||||
labs: Record<string, boolean>;
|
||||
stripeDirect: boolean;
|
||||
hostSettings?: {
|
||||
|
@ -57,7 +57,7 @@ const UnsplashModal = () => import('../settings/advanced/integrations/UnsplashMo
|
||||
const UserDetailModal = () => import('../settings/general/UserDetailModal');
|
||||
const ZapierModal = () => import('../settings/advanced/integrations/ZapierModal');
|
||||
const AnnouncementBarModal = () => import('../settings/site/AnnouncementBarModal');
|
||||
const EmbedSignupFormModal = () => import('../settings/membership/EmbedSignupFormModal');
|
||||
const EmbedSignupFormModal = () => import('../settings/membership/embedSignup/EmbedSignupFormModal');
|
||||
|
||||
const modalPaths: {[key: string]: () => Promise<{default: React.FC<NiceModalHocProps & RoutingModalProps>}>} = {
|
||||
'design/edit/themes': ChangeThemeModal,
|
||||
|
@ -1,122 +0,0 @@
|
||||
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='hidden rounded-md bg-grey-100 text-grey-600 tablet:!visible tablet:!block'>
|
||||
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-1 gap-6 pb-8 md:grid-cols-[5.5fr_2.5fr]'>
|
||||
<Preview />
|
||||
<Sidebar />
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default EmbedSignupFormModal;
|
@ -1,6 +1,6 @@
|
||||
import Access from './Access';
|
||||
import Analytics from './Analytics';
|
||||
import EmbedSignupForm from './EmbedSignupForm';
|
||||
import EmbedSignupForm from './embedSignup/EmbedSignupForm';
|
||||
import Portal from './Portal';
|
||||
import React from 'react';
|
||||
import Recommendations from '../site/Recommendations';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Button from '../../../admin-x-ds/global/Button';
|
||||
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 SettingGroup from '../../../../admin-x-ds/settings/SettingGroup';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
|
||||
const EmbedSignupForm: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
@ -0,0 +1,122 @@
|
||||
import EmbedSignupPreview from './EmbedSignupPreview';
|
||||
import EmbedSignupSidebar, {SelectedLabelTypes} from './EmbedSignupSidebar';
|
||||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import useSettingGroup from '../../../../hooks/useSettingGroup';
|
||||
import {MultiSelectOption} from '../../../../admin-x-ds/global/form/MultiSelect';
|
||||
import {MultiValue} from 'react-select';
|
||||
import {generateCode} from '../../../../utils/generateEmbedCode';
|
||||
import {getSettingValues} from '../../../../api/settings';
|
||||
import {useBrowseLabels} from '../../../../api/labels';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
|
||||
const EmbedSignupFormModal = NiceModal.create(() => {
|
||||
let i18nEnabled = false;
|
||||
|
||||
const [selectedColor, setSelectedColor] = useState<string>('#08090c');
|
||||
const [selectedLabels, setSelectedLabels] = useState<SelectedLabelTypes[]>([]);
|
||||
const [selectedLayout, setSelectedLayout] = useState<string>('all-in-one');
|
||||
const [embedScript, setEmbedScript] = useState<string>('');
|
||||
const [isCopied, setIsCopied] = useState(false);
|
||||
|
||||
const {updateRoute} = useRouting();
|
||||
const {config} = useGlobalData();
|
||||
const {localSettings, siteData} = useSettingGroup();
|
||||
const [accentColor, title, description, locale, labs, icon] = getSettingValues<string>(localSettings, ['accent_color', 'title', 'description', 'locale', 'labs', 'icon']);
|
||||
const {data: labels} = useBrowseLabels();
|
||||
|
||||
if (labs) {
|
||||
i18nEnabled = JSON.parse(labs).i18n;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!siteData) {
|
||||
return;
|
||||
}
|
||||
const code = generateCode({
|
||||
preview: true,
|
||||
config: {
|
||||
blogUrl: siteData.url,
|
||||
signupForm: {
|
||||
url: config?.signupForm?.url,
|
||||
version: config?.signupForm?.version
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
accentColor: accentColor || '#d74780',
|
||||
title: title || '',
|
||||
locale: locale || 'en',
|
||||
icon: icon || '',
|
||||
description: description || ''
|
||||
},
|
||||
labels: selectedLabels.map(({label}) => ({name: label})),
|
||||
backgroundColor: selectedColor || '#08090c',
|
||||
layout: selectedLayout,
|
||||
i18nEnabled
|
||||
});
|
||||
|
||||
setEmbedScript(code);
|
||||
}, [siteData, accentColor, selectedLabels, config, title, selectedColor, selectedLayout, locale, i18nEnabled, icon, description]);
|
||||
|
||||
const handleCopyClick = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(embedScript);
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 2000); // reset after 2 seconds
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to copy text: ', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleColorToggle = (e:string) => {
|
||||
setSelectedColor(e);
|
||||
};
|
||||
|
||||
const addSelectedLabel = (selected: MultiValue<MultiSelectOption>) => {
|
||||
if (selected?.length) {
|
||||
const chosenLabels = selected?.map(({value}) => ({label: value, value: value}));
|
||||
setSelectedLabels(chosenLabels);
|
||||
} else {
|
||||
setSelectedLabels([]);
|
||||
}
|
||||
};
|
||||
|
||||
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'>
|
||||
<EmbedSignupPreview
|
||||
html={embedScript}
|
||||
style={selectedLayout}
|
||||
/>
|
||||
<EmbedSignupSidebar
|
||||
accentColor={accentColor}
|
||||
embedScript={embedScript}
|
||||
handleColorToggle={handleColorToggle}
|
||||
handleCopyClick={handleCopyClick}
|
||||
handleLabelClick={addSelectedLabel}
|
||||
handleLayoutSelect={setSelectedLayout}
|
||||
isCopied={isCopied}
|
||||
labels={labels?.labels || []}
|
||||
selectedColor={selectedColor}
|
||||
selectedLabels={selectedLabels}
|
||||
selectedLayout={selectedLayout}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default EmbedSignupFormModal;
|
@ -0,0 +1,74 @@
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
|
||||
type EmbedSignupPreviewProps = {
|
||||
html: string;
|
||||
style: string;
|
||||
};
|
||||
|
||||
const EmbedSignupPreview: React.FC<EmbedSignupPreviewProps> = ({html, style}) => {
|
||||
const [visibleIframeIndex, setVisibleIframeIndex] = useState(0);
|
||||
const iframes = [useRef<HTMLIFrameElement>(null), useRef<HTMLIFrameElement>(null)];
|
||||
|
||||
const updateIframeContent = (index: number) => {
|
||||
const iframe = iframes[index].current;
|
||||
|
||||
if (!iframe) {
|
||||
return;
|
||||
}
|
||||
|
||||
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
|
||||
if (!iframeDoc) {
|
||||
return;
|
||||
}
|
||||
|
||||
const docString = `
|
||||
<html>
|
||||
<head>
|
||||
<style>body, html {padding: 0; margin: 0; overflow: hidden;}</style>
|
||||
<style>${style}</style>
|
||||
</head>
|
||||
<body>${html}</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
iframeDoc.open();
|
||||
iframeDoc.write(docString);
|
||||
iframeDoc.close();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const invisibleIframeIndex = visibleIframeIndex === 0 ? 1 : 0;
|
||||
updateIframeContent(invisibleIframeIndex);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setVisibleIframeIndex(invisibleIframeIndex);
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [html, style]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<iframe
|
||||
ref={iframes[0]}
|
||||
// allowTransparency={true}
|
||||
className={`absolute h-full w-full transition-opacity duration-500 ${visibleIframeIndex !== 0 ? 'z-10 opacity-0' : 'z-20 opacity-100'}`}
|
||||
frameBorder="0"
|
||||
title="Signup Form Preview 1"
|
||||
></iframe>
|
||||
|
||||
<iframe
|
||||
ref={iframes[1]}
|
||||
// allowTransparency={true}
|
||||
className={`absolute h-full w-full transition-opacity duration-500 ${visibleIframeIndex !== 1 ? 'z-10 opacity-0' : 'z-20 opacity-100'}`}
|
||||
frameBorder="0"
|
||||
title="Signup Form Preview 2"
|
||||
></iframe>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedSignupPreview;
|
@ -0,0 +1,124 @@
|
||||
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 MultiSelect, {MultiSelectOption} from '../../../../admin-x-ds/global/form/MultiSelect';
|
||||
import Radio from '../../../../admin-x-ds/global/form/Radio';
|
||||
import React from 'react';
|
||||
import TextArea from '../../../../admin-x-ds/global/form/TextArea';
|
||||
import {Label} from '../../../../api/labels';
|
||||
import {MultiValue} from 'react-select';
|
||||
|
||||
export type SelectedLabelTypes = {
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type SidebarProps = {
|
||||
selectedColor?: string;
|
||||
accentColor?: string;
|
||||
handleColorToggle: (e: string) => void;
|
||||
labels?: Label[];
|
||||
handleLabelClick: (selected: MultiValue<MultiSelectOption>) => void;
|
||||
selectedLabels?: SelectedLabelTypes[];
|
||||
embedScript: string;
|
||||
handleLayoutSelect: React.Dispatch<React.SetStateAction<string>>;
|
||||
selectedLayout : string;
|
||||
handleCopyClick: () => void;
|
||||
isCopied: boolean;
|
||||
};
|
||||
|
||||
const EmbedSignupSidebar: React.FC<SidebarProps> = ({selectedLayout,
|
||||
accentColor,
|
||||
handleColorToggle,
|
||||
selectedColor,
|
||||
labels,
|
||||
selectedLabels,
|
||||
handleLabelClick,
|
||||
embedScript,
|
||||
handleLayoutSelect,
|
||||
handleCopyClick,
|
||||
isCopied}) => {
|
||||
const labelOptions = labels ? labels.map((l) => {
|
||||
return {
|
||||
label: l?.name,
|
||||
value: l?.name
|
||||
};
|
||||
}).filter(Boolean) : [];
|
||||
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: 'all-in-one'
|
||||
},
|
||||
{
|
||||
label: 'Minimal',
|
||||
value: 'minimal'
|
||||
}
|
||||
]}
|
||||
selectedOption={selectedLayout}
|
||||
title='Layout'
|
||||
onSelect={(value) => {
|
||||
handleLayoutSelect(value);
|
||||
}}
|
||||
/>
|
||||
{
|
||||
selectedLayout === 'all-in-one' &&
|
||||
<ColorIndicator
|
||||
isExpanded={false}
|
||||
swatches={[
|
||||
{
|
||||
hex: '#08090c',
|
||||
title: 'Dark'
|
||||
},
|
||||
{
|
||||
hex: '#ffffff',
|
||||
title: 'Light'
|
||||
},
|
||||
{
|
||||
hex: (accentColor || '#d74780'),
|
||||
title: 'Accent'
|
||||
}
|
||||
]}
|
||||
swatchSize='lg'
|
||||
title='Background color'
|
||||
value={selectedColor}
|
||||
onSwatchChange={(e) => {
|
||||
if (e) {
|
||||
handleColorToggle(e);
|
||||
}
|
||||
}}
|
||||
onTogglePicker={() => {}}
|
||||
/>
|
||||
}
|
||||
<MultiSelect
|
||||
hint='Will be applied to all members signing up via this form'
|
||||
options={labelOptions}
|
||||
placeholder='Pick one or more labels (optional)'
|
||||
title='Labels at signup'
|
||||
values={selectedLabels || []}
|
||||
onChange={handleLabelClick}
|
||||
/>
|
||||
<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={`${embedScript}`}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
<Button className='self-end' color={isCopied ? 'green' : 'black'} label={isCopied ? 'Copied!' : 'Copy code'} onClick={handleCopyClick} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmbedSignupSidebar;
|
11
apps/admin-x-settings/src/utils/escapeHtml.ts
Normal file
11
apps/admin-x-settings/src/utils/escapeHtml.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export function escapeHtml(unsafe:string) {
|
||||
if (!unsafe) {
|
||||
return '';
|
||||
}
|
||||
return unsafe
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
108
apps/admin-x-settings/src/utils/generateEmbedCode.ts
Normal file
108
apps/admin-x-settings/src/utils/generateEmbedCode.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import {escapeHtml} from './escapeHtml';
|
||||
import {textColorForBackgroundColor} from '@tryghost/color-utils';
|
||||
export type GenerateCodeOptions = {
|
||||
preview: boolean;
|
||||
config: {
|
||||
blogUrl: string;
|
||||
signupForm: {
|
||||
url: string;
|
||||
version: string;
|
||||
};
|
||||
};
|
||||
settings: {
|
||||
accentColor: string;
|
||||
icon?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
locale?: string;
|
||||
};
|
||||
labels: Array<{ name: string }>;
|
||||
backgroundColor: string;
|
||||
layout: string;
|
||||
i18nEnabled: boolean;
|
||||
};
|
||||
|
||||
type OptionsType = {
|
||||
site: string;
|
||||
'button-color': string;
|
||||
'button-text-color': string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
[key: string]: any; // This allows for computed properties like 'label-1', 'label-2', etc.
|
||||
};
|
||||
|
||||
export const generateCode = ({
|
||||
preview,
|
||||
config,
|
||||
settings,
|
||||
labels,
|
||||
backgroundColor,
|
||||
layout,
|
||||
i18nEnabled
|
||||
}: GenerateCodeOptions) => {
|
||||
const siteUrl = config.blogUrl;
|
||||
const scriptUrl = config.signupForm.url.replace('{version}', config.signupForm.version);
|
||||
|
||||
let options: OptionsType = {
|
||||
site: siteUrl,
|
||||
'button-color': settings.accentColor,
|
||||
'button-text-color': textColorForBackgroundColor(settings.accentColor).hex()
|
||||
};
|
||||
|
||||
if (i18nEnabled && settings.locale) {
|
||||
options.locale = settings.locale;
|
||||
}
|
||||
|
||||
for (const [i, label] of labels.entries()) {
|
||||
options[`label-${i + 1}`] = label.name;
|
||||
}
|
||||
|
||||
let style = 'min-height: 58px;max-width: 440px;margin: 0 auto;width: 100%';
|
||||
|
||||
if (layout === 'all-in-one') {
|
||||
if (settings.icon && settings.icon !== '') {
|
||||
options.icon = settings.icon.replace(/\/content\/images\//, '/content/images/size/w192h192/');
|
||||
}
|
||||
options.title = settings.title;
|
||||
options.description = settings.description;
|
||||
options['background-color'] = backgroundColor;
|
||||
options['text-color'] = textColorForBackgroundColor(backgroundColor).hex();
|
||||
|
||||
style = 'height: 40vmin;min-height: 360px';
|
||||
}
|
||||
|
||||
if (preview) {
|
||||
if (layout === 'minimal') {
|
||||
style = 'min-height: 58px; max-width: 440px;width: 100%;position: absolute; left: 50%; top:50%; transform: translate(-50%, -50%);';
|
||||
} else {
|
||||
style = 'height: 100vh';
|
||||
}
|
||||
}
|
||||
|
||||
let dataOptionsString = '';
|
||||
const preferredOrder = [
|
||||
'background-color',
|
||||
'text-color',
|
||||
'button-color',
|
||||
'button-text-color',
|
||||
'title',
|
||||
'description',
|
||||
'icon',
|
||||
'site',
|
||||
'locale'
|
||||
];
|
||||
const sortedKeys = Object.keys(options).sort((a, b) => {
|
||||
return preferredOrder.indexOf(a) - preferredOrder.indexOf(b);
|
||||
});
|
||||
for (const key of sortedKeys) {
|
||||
const value = options[key];
|
||||
dataOptionsString += ` data-${key}="${escapeHtml(value)}"`;
|
||||
}
|
||||
|
||||
const code = `<div style="${escapeHtml(style)}"><script src="${encodeURI(scriptUrl)}"${dataOptionsString} async></script></div>`;
|
||||
|
||||
if (preview && style === 'minimal') {
|
||||
return `<div style="position: absolute; z-index: -1; top: 0; left: 0; width: 100%; height: 100%; background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%), linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%);background-size: 16px 16px;background-position: 0 0, 8px 8px;;"></div>${code}`;
|
||||
}
|
||||
|
||||
return code;
|
||||
};
|
@ -0,0 +1,6 @@
|
||||
import {test} from '@playwright/test';
|
||||
// import {globalDataRequests, mockApi, responseFixtures} from '../../utils/e2e';
|
||||
|
||||
test.describe('Signup Embed', async () => {
|
||||
// TODO - currently having difficulty rendering the iframe in the test
|
||||
});
|
Loading…
Reference in New Issue
Block a user