mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Admin x custom integrations UI (#17747)
refs. https://github.com/TryGhost/Product/issues/3729 - added static new custom integration modal - added static custom integration edit modal - refined built-in integration UI
This commit is contained in:
parent
9e25058934
commit
efc9a53fd2
21 changed files with 418 additions and 48 deletions
|
@ -0,0 +1 @@
|
|||
<svg viewBox="0 0 46 43"><title>integration</title><g stroke="currentColor" fill="none" fill-rule="evenodd" stroke-width="1.5px"><path d="M-1-3h48v48H-1z" stroke="none"></path><g stroke-linecap="round" stroke-linejoin="round"><path d="M32.932 6.574c.713.428 1.069 1.057 1.068 1.888v9.278l-11 7.076-11-7.076V8.462c0-.831.355-1.46 1.068-1.888l8.8-5.28c.755-.453 1.51-.453 2.264 0l8.8 5.28zM23 13.816v11"></path><path d="M34 31.416l-11-6.6 11-7.076 10 6.426c.669.435 1.002 1.052 1 1.85v8.124c.002.798-.331 1.415-1 1.85l-8.8 5.66c-.793.51-1.587.51-2.38 0L23 35.34V24.816m11 6.6V42M23 24.816V35.34l-9.8 6.31c-.793.51-1.587.51-2.38 0l-8.8-5.66c-.678-.43-1.018-1.047-1.02-1.85v-8.124c-.002-.798.331-1.415 1-1.85l10-6.426 11 7.076-11 6.6m0 0L1.262 24.974M12 31.416V42m11-28.184L12.282 7.384m21.436 0L23 13.816m21.738 11.158L34 31.416"></path></g></g></svg>
|
After Width: | Height: | Size: 848 B |
|
@ -78,8 +78,10 @@ const Button: React.FC<ButtonProps> = ({
|
|||
|
||||
styles += ` ${className}`;
|
||||
|
||||
const iconClasses = label && icon ? 'mr-1.5' : '';
|
||||
|
||||
const buttonChildren = <>
|
||||
{icon && <Icon colorClass={iconColorClass} name={icon} size={size === 'sm' ? 'sm' : 'md'} />}
|
||||
{icon && <Icon className={iconClasses} colorClass={iconColorClass} name={icon} size={size === 'sm' ? 'sm' : 'md'} />}
|
||||
{(label && hideLabel) ? <span className="sr-only">{label}</span> : label}
|
||||
</>;
|
||||
const buttonElement = React.createElement(tag, {className: styles,
|
||||
|
|
|
@ -3,6 +3,7 @@ import type {Meta, StoryObj} from '@storybook/react';
|
|||
|
||||
import Table from './Table';
|
||||
import TableCell from './TableCell';
|
||||
import TableHead from './TableHead';
|
||||
import TableRow from './TableRow';
|
||||
|
||||
const meta = {
|
||||
|
@ -13,6 +14,10 @@ const meta = {
|
|||
|
||||
const tableRows = (
|
||||
<>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Jamie Larson</TableCell>
|
||||
<TableCell>jamie@example.com</TableCell>
|
||||
|
|
19
apps/admin-x-settings/src/admin-x-ds/global/TableHead.tsx
Normal file
19
apps/admin-x-settings/src/admin-x-ds/global/TableHead.tsx
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Heading from './Heading';
|
||||
import React, {HTMLProps} from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const TableHead: React.FC<HTMLProps<HTMLTableCellElement>> = ({className, children, ...props}) => {
|
||||
const tableCellClasses = clsx(
|
||||
'!py-3 !pl-0 !pr-6 align-top',
|
||||
props.onClick && 'hover:cursor-pointer',
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<td className={tableCellClasses} {...props}>
|
||||
<Heading level={6}>{children}</Heading>
|
||||
</td>
|
||||
);
|
||||
};
|
||||
|
||||
export default TableHead;
|
|
@ -28,7 +28,7 @@ const TableRow: React.FC<TableRowProps> = ({id, action, hideActions, className,
|
|||
'group',
|
||||
bgOnHover && 'hover:bg-gradient-to-r hover:from-white hover:to-grey-50',
|
||||
onClick && 'cursor-pointer',
|
||||
separator ? 'border-b border-grey-100 last-of-type:border-b-transparent hover:border-grey-200' : 'border-y border-transparent hover:border-grey-200 first-of-type:hover:border-t-transparent',
|
||||
separator ? 'border-b border-grey-100 last-of-type:border-b-transparent hover:border-grey-200' : 'border-y border-transparent first-of-type:hover:border-t-transparent',
|
||||
className
|
||||
);
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ const Select: React.FC<SelectProps> = ({
|
|||
{title && <Heading grey={selectedOption || !prompt ? true : false} htmlFor={id} useLabelTag={true}>{title}</Heading>}
|
||||
<div className={containerClasses}>
|
||||
<select className={selectClasses} disabled={disabled} id={id} value={selectedOption} onChange={handleOptionChange}>
|
||||
{prompt && <option className={optionClasses} value="">{prompt}</option>}
|
||||
{prompt && <option className={optionClasses} value="" disabled selected>{prompt}</option>}
|
||||
{options.map(option => (
|
||||
'options' in option ?
|
||||
<optgroup key={option.label} label={option.label}>
|
||||
|
|
|
@ -186,3 +186,16 @@ export const Dirty: Story = {
|
|||
children: <p>Simulates if there were unsaved changes of a form. Click on Cancel</p>
|
||||
}
|
||||
};
|
||||
|
||||
export const FormSheet: Story = {
|
||||
args: {
|
||||
onOk: () => {
|
||||
alert('Clicked OK!');
|
||||
},
|
||||
onCancel: undefined,
|
||||
size: 'sm',
|
||||
title: 'Form sheet',
|
||||
formSheet: true,
|
||||
children: <p>Slightly differently styled modal that can be used to display small forms <em>inside other modals</em>. Use it sparingly!</p>
|
||||
}
|
||||
};
|
|
@ -37,6 +37,7 @@ export interface ModalProps {
|
|||
scrolling?: boolean;
|
||||
dirty?: boolean;
|
||||
animate?: boolean;
|
||||
formSheet?: boolean;
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({
|
||||
|
@ -60,7 +61,8 @@ const Modal: React.FC<ModalProps> = ({
|
|||
stickyFooter = false,
|
||||
scrolling = true,
|
||||
dirty = false,
|
||||
animate = true
|
||||
animate = true,
|
||||
formSheet = false
|
||||
}) => {
|
||||
const modal = useModal();
|
||||
const {setGlobalDirtyState} = useGlobalDirtyState();
|
||||
|
@ -126,8 +128,10 @@ const Modal: React.FC<ModalProps> = ({
|
|||
}
|
||||
|
||||
let modalClasses = clsx(
|
||||
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-x-hidden rounded bg-white shadow-xl',
|
||||
animate && 'animate-modal-in',
|
||||
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-x-hidden rounded bg-white',
|
||||
formSheet ? 'shadow-md' : 'shadow-xl',
|
||||
(animate && !formSheet) && 'animate-modal-in',
|
||||
formSheet && 'animate-modal-in-reverse',
|
||||
scrolling ? 'overflow-y-auto' : 'overflow-y-hidden'
|
||||
);
|
||||
|
||||
|
@ -237,7 +241,8 @@ const Modal: React.FC<ModalProps> = ({
|
|||
<div className={backdropClasses} id='modal-backdrop' onClick={handleBackdropClick}>
|
||||
<div className={clsx(
|
||||
'pointer-events-none fixed inset-0 z-0',
|
||||
backDrop && 'bg-[rgba(98,109,121,0.2)] backdrop-blur-[3px]'
|
||||
(backDrop && !formSheet) && 'bg-[rgba(98,109,121,0.2)] backdrop-blur-[3px]',
|
||||
formSheet && 'bg-[rgba(98,109,121,0.05)]'
|
||||
)}></div>
|
||||
<section className={modalClasses} data-testid={testId} style={modalStyles}>
|
||||
<div className={contentClasses}>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import AddIntegrationModal from '../settings/advanced/integrations/AddIntegrationModal';
|
||||
import AddNewsletterModal from '../settings/email/newsletters/AddNewsletterModal';
|
||||
import AmpModal from '../settings/advanced/integrations/AmpModal';
|
||||
import ChangeThemeModal from '../settings/site/ThemeModal';
|
||||
import CustomIntegrationModal from '../settings/advanced/integrations/CustomIntegrationModal';
|
||||
import DesignModal from '../settings/site/DesignModal';
|
||||
import FirstpromoterModal from '../settings/advanced/integrations/FirstPromoterModal';
|
||||
import HistoryModal from '../settings/advanced/HistoryModal';
|
||||
|
@ -120,6 +122,10 @@ const handleNavigation = (scroll: boolean = true) => {
|
|||
NiceModal.show(FirstpromoterModal);
|
||||
} else if (pathName === 'integrations/pintura') {
|
||||
NiceModal.show(PinturaModal);
|
||||
} else if (pathName === 'integrations/add') {
|
||||
NiceModal.show(AddIntegrationModal);
|
||||
} else if (pathName === 'integrations/show/custom/:id') { // TODO: move this to modalRoutes
|
||||
NiceModal.show(CustomIntegrationModal);
|
||||
}
|
||||
|
||||
if (scroll) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Button from '../../../admin-x-ds/global/Button';
|
||||
import ConfirmationModal from '../../../admin-x-ds/global/modal/ConfirmationModal';
|
||||
import Icon from '../../../admin-x-ds/global/Icon';
|
||||
import List from '../../../admin-x-ds/global/List';
|
||||
import ListItem from '../../../admin-x-ds/global/ListItem';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
|
@ -17,13 +18,24 @@ import {ReactComponent as ZapierIcon} from '../../../assets/icons/zapier.svg';
|
|||
import {useCreateWebhook, useDeleteWebhook, useEditWebhook} from '../../../api/webhooks';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
|
||||
const IntegrationItem: React.FC<{icon?: React.ReactNode, title: string, detail: string, action: () => void; disabled?: boolean; testId?: string}> = ({
|
||||
interface IntegrationItemProps {
|
||||
icon?: React.ReactNode,
|
||||
title: string,
|
||||
detail: string,
|
||||
action: () => void;
|
||||
disabled?: boolean;
|
||||
testId?: string;
|
||||
custom?: boolean;
|
||||
}
|
||||
|
||||
const IntegrationItem: React.FC<IntegrationItemProps> = ({
|
||||
icon,
|
||||
title,
|
||||
detail,
|
||||
action,
|
||||
disabled,
|
||||
testId
|
||||
testId,
|
||||
custom = false
|
||||
}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
|
@ -35,11 +47,16 @@ const IntegrationItem: React.FC<{icon?: React.ReactNode, title: string, detail:
|
|||
}
|
||||
};
|
||||
|
||||
return <ListItem
|
||||
action={disabled ?
|
||||
const buttons = custom ?
|
||||
<Button color='red' label='Delete' link onClick={() => {}} />
|
||||
:
|
||||
(disabled ?
|
||||
<Button icon='lock-locked' label='Upgrade' link onClick={handleClick} /> :
|
||||
<Button color='green' label='Configure' link onClick={handleClick} />
|
||||
}
|
||||
);
|
||||
|
||||
return <ListItem
|
||||
action={buttons}
|
||||
avatar={icon}
|
||||
className={disabled ? 'opacity-50 saturate-0' : ''}
|
||||
detail={detail}
|
||||
|
@ -122,28 +139,47 @@ const CustomIntegrations: React.FC<{integrations: Integration[]}> = ({integratio
|
|||
const {mutateAsync: createWebhook} = useCreateWebhook();
|
||||
const {mutateAsync: editWebhook} = useEditWebhook();
|
||||
const {mutateAsync: deleteWebhook} = useDeleteWebhook();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const openCustomIntegrationModal = () => {
|
||||
updateRoute('integrations/show/custom/:id');
|
||||
};
|
||||
|
||||
return (
|
||||
<List>
|
||||
{integrations.map(integration => (
|
||||
<IntegrationItem action={() => {
|
||||
NiceModal.show(ConfirmationModal, {
|
||||
title: 'TEST API actions',
|
||||
prompt: <>
|
||||
<IntegrationItem
|
||||
action={() => {
|
||||
NiceModal.show(ConfirmationModal, {
|
||||
title: 'TEST API actions',
|
||||
prompt: <>
|
||||
Webhooks (will not update until you close and reopen this modal)
|
||||
<pre><code>{JSON.stringify(integration.webhooks)}</code></pre>
|
||||
<pre><code>{JSON.stringify(integration.webhooks)}</code></pre>
|
||||
|
||||
<Button label='Create integration' onClick={() => createIntegration({name: 'Test'})} />
|
||||
<Button label='Update integration' onClick={() => editIntegration({...integration, name: integration.name + '*'})} />
|
||||
<Button label='Delete integration' onClick={() => deleteIntegration(integration.id)} />
|
||||
<Button label='Create webhook' onClick={() => createWebhook({integration_id: integration.id, event: 'post.edited', name: 'Test', target_url: 'https://test.com'})} />
|
||||
<Button label='Update webhook' onClick={() => editWebhook({...integration.webhooks[0], name: integration.webhooks[0].name + '*'})} />
|
||||
<Button label='Delete webhook' onClick={() => deleteWebhook(integration.webhooks[0].id)} />
|
||||
</>,
|
||||
onOk: modal => modal?.remove()
|
||||
});
|
||||
}} detail={integration.description || 'No description'} title={integration.name} />)
|
||||
<Button label='Create integration' onClick={() => createIntegration({name: 'Test'})} />
|
||||
<Button label='Update integration' onClick={() => editIntegration({...integration, name: integration.name + '*'})} />
|
||||
<Button label='Delete integration' onClick={() => deleteIntegration(integration.id)} />
|
||||
<Button label='Create webhook' onClick={() => createWebhook({integration_id: integration.id, event: 'post.edited', name: 'Test', target_url: 'https://test.com'})} />
|
||||
<Button label='Update webhook' onClick={() => editWebhook({...integration.webhooks[0], name: integration.webhooks[0].name + '*'})} />
|
||||
<Button label='Delete webhook' onClick={() => deleteWebhook(integration.webhooks[0].id)} />
|
||||
</>,
|
||||
onOk: modal => modal?.remove()
|
||||
});
|
||||
}}
|
||||
detail={integration.description || 'No description'}
|
||||
icon={<Icon className='w-8' name='integration' />}
|
||||
title={integration.name}
|
||||
custom
|
||||
/>)
|
||||
)}
|
||||
|
||||
<IntegrationItem
|
||||
action={openCustomIntegrationModal}
|
||||
detail='This is just a static placeholder to open the custom modal'
|
||||
icon={<Icon className='w-8' name='integration' />} // Should be custom icon when uploaded
|
||||
title='Custom integration modal'
|
||||
custom
|
||||
/>
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
@ -151,6 +187,7 @@ const CustomIntegrations: React.FC<{integrations: Integration[]}> = ({integratio
|
|||
const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const [selectedTab, setSelectedTab] = useState<'built-in' | 'custom'>('built-in');
|
||||
const {data: {integrations} = {integrations: []}} = useBrowseIntegrations();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
|
@ -167,7 +204,7 @@ const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
|||
|
||||
const buttons = (
|
||||
<Button color='green' label='Add custom integration' link={true} onClick={() => {
|
||||
// showInviteModal();
|
||||
updateRoute('integrations/add');
|
||||
}} />
|
||||
);
|
||||
|
||||
|
|
|
@ -18,13 +18,13 @@ const APIKeyField: React.FC<APIKeyFieldProps> = ({label, text = '', hint, onRege
|
|||
};
|
||||
|
||||
return <>
|
||||
<div className='p-0 py-1 pr-4 text-grey-600'>{label}</div>
|
||||
<div className='group relative overflow-hidden rounded p-1 hover:bg-grey-100'>
|
||||
<div className='p-0 py-1 pr-4 text-sm text-grey-600'>{label}</div>
|
||||
<div className='group relative overflow-hidden rounded p-1 text-sm hover:bg-grey-50'>
|
||||
{text}
|
||||
{hint}
|
||||
<div className='invisible absolute right-0 top-[50%] flex translate-y-[-50%] gap-1 group-hover:visible'>
|
||||
{onRegenerate && <Button color='grey' label='Regenerate' size='sm' onClick={onRegenerate} />}
|
||||
<Button color='black' label={copied ? 'Copied' : 'Copy'} size='sm' onClick={copyText} />
|
||||
<div className='invisible absolute right-0 top-[50%] flex translate-y-[-50%] gap-1 bg-white pl-1 text-sm group-hover:visible'>
|
||||
{onRegenerate && <Button color='outline' label='Regenerate' size='sm' onClick={onRegenerate} />}
|
||||
<Button color='outline' label={copied ? 'Copied' : 'Copy'} size='sm' onClick={copyText} />
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import Form from '../../../../admin-x-ds/global/form/Form';
|
||||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
|
||||
interface AddIntegrationModalProps {}
|
||||
|
||||
const AddIntegrationModal: React.FC<AddIntegrationModalProps> = () => {
|
||||
// const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
return <Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
okColor='black'
|
||||
okLabel='Add'
|
||||
size='sm'
|
||||
testId='add-integration-modal'
|
||||
title='Add integration'
|
||||
onOk={async () => {}}
|
||||
>
|
||||
<div className='mt-5'>
|
||||
<Form
|
||||
marginBottom={false}
|
||||
marginTop={false}
|
||||
>
|
||||
<TextField
|
||||
placeholder='Custom integration'
|
||||
title='Name'
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>;
|
||||
};
|
||||
|
||||
export default NiceModal.create(AddIntegrationModal);
|
|
@ -38,9 +38,8 @@ const AmpModal = NiceModal.create(() => {
|
|||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Save'
|
||||
okLabel='Save & close'
|
||||
testId='amp-modal'
|
||||
title=''
|
||||
onOk={async () => {
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
import APIKeys from './APIKeys';
|
||||
import Button from '../../../../admin-x-ds/global/Button';
|
||||
import Form from '../../../../admin-x-ds/global/form/Form';
|
||||
import ImageUpload from '../../../../admin-x-ds/global/form/ImageUpload';
|
||||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import Table from '../../../../admin-x-ds/global/Table';
|
||||
import TableCell from '../../../../admin-x-ds/global/TableCell';
|
||||
import TableHead from '../../../../admin-x-ds/global/TableHead';
|
||||
import TableRow from '../../../../admin-x-ds/global/TableRow';
|
||||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
import WebhookModal from './WebhookModal';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {getGhostPaths} from '../../../../utils/helpers';
|
||||
|
||||
interface CustomIntegrationModalProps {}
|
||||
|
||||
const CustomIntegrationModal: React.FC<CustomIntegrationModalProps> = () => {
|
||||
// const modal = useModal();
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
const integrationTitle = 'A custom integration';
|
||||
const regenerated = false;
|
||||
|
||||
return <Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
okColor='black'
|
||||
okLabel='Save & close'
|
||||
size='md'
|
||||
testId='custom-integration-modal'
|
||||
title={integrationTitle}
|
||||
stickyFooter
|
||||
onOk={async () => {}}
|
||||
>
|
||||
<div className='mt-7 flex w-full gap-7'>
|
||||
<div>
|
||||
<ImageUpload
|
||||
height='120px'
|
||||
id='custom-integration-icon'
|
||||
width='120px'
|
||||
onDelete={() => {}}
|
||||
onImageClick={() => {}}
|
||||
onUpload={() => {}}
|
||||
>
|
||||
Upload icon
|
||||
</ImageUpload>
|
||||
</div>
|
||||
<div className='flex grow flex-col'>
|
||||
<Form>
|
||||
<TextField title='Title' />
|
||||
<TextField title='Description' />
|
||||
<div>
|
||||
<APIKeys keys={[
|
||||
{
|
||||
label: 'Content API key',
|
||||
text: '[content key here]',
|
||||
hint: regenerated ? <div className='text-green'>Content API Key was successfully regenerated</div> : undefined
|
||||
// onRegenerate: handleRegenerate
|
||||
},
|
||||
{
|
||||
label: 'Admin API key',
|
||||
text: '[api key here]',
|
||||
hint: regenerated ? <div className='text-green'>Admin API Key was successfully regenerated</div> : undefined
|
||||
// onRegenerate: handleRegenerate
|
||||
},
|
||||
{
|
||||
label: 'API URL',
|
||||
text: window.location.origin + getGhostPaths().subdir
|
||||
}
|
||||
]} />
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Table>
|
||||
<TableRow bgOnHover={false}>
|
||||
<TableHead>1 webhook</TableHead>
|
||||
<TableHead>Last triggered</TableHead>
|
||||
<TableHead />
|
||||
</TableRow>
|
||||
<TableRow
|
||||
action={
|
||||
<Button color='red' label='Delete' link onClick={() => {}} />
|
||||
}
|
||||
hideActions
|
||||
onClick={() => {
|
||||
NiceModal.show(WebhookModal);
|
||||
}}
|
||||
>
|
||||
<TableCell className='w-1/2'>
|
||||
<div className='text-sm font-semibold'>Rebuild on post published</div>
|
||||
<div className='grid grid-cols-[max-content_1fr] gap-x-1 text-xs leading-snug'>
|
||||
<span className='text-grey-600'>Event:</span>
|
||||
<span>Post published</span>
|
||||
<span className='text-grey-600'>URL:</span>
|
||||
<span>https://example.com</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className='w-1/2 text-sm'>
|
||||
Tue Aug 15 2023 13:03:33
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow bgOnHover={false} separator={false}>
|
||||
<TableCell colSpan={3}>
|
||||
<Button
|
||||
color='green'
|
||||
icon='add'
|
||||
iconColorClass='text-green'
|
||||
label='Add webhook'
|
||||
size='sm'
|
||||
link
|
||||
onClick={() => {
|
||||
NiceModal.show(WebhookModal);
|
||||
}} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
</Modal>;
|
||||
};
|
||||
|
||||
export default NiceModal.create(CustomIntegrationModal);
|
|
@ -13,7 +13,7 @@ import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
|||
const FirstpromoterModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
|
||||
|
||||
const {settings} = useGlobalData();
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
|
||||
|
@ -48,9 +48,8 @@ const FirstpromoterModal = NiceModal.create(() => {
|
|||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Save'
|
||||
okLabel='Save & close'
|
||||
testId='firstpromoter-modal'
|
||||
title=''
|
||||
onOk={async () => {
|
||||
|
|
|
@ -5,15 +5,20 @@ import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
|||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import Toggle from '../../../../admin-x-ds/global/form/Toggle';
|
||||
import pinturaScreenshot from '../../../../assets/images/pintura-screenshot.png';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {ReactComponent as Icon} from '../../../../assets/icons/pintura.svg';
|
||||
import {useState} from 'react';
|
||||
|
||||
const PinturaModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Save'
|
||||
|
|
|
@ -4,16 +4,20 @@ import IntegrationHeader from './IntegrationHeader';
|
|||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
import useRouting from '../../../../hooks/useRouting';
|
||||
import {ReactComponent as Icon} from '../../../../assets/icons/slack.svg';
|
||||
|
||||
const SlackModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
cancelLabel=''
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
okColor='black'
|
||||
okLabel='Save'
|
||||
okLabel='Save & close'
|
||||
title=''
|
||||
onOk={() => {
|
||||
modal.remove();
|
||||
|
|
|
@ -27,9 +27,8 @@ const UnsplashModal = NiceModal.create(() => {
|
|||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Close'
|
||||
okLabel='Save & close'
|
||||
testId='unsplash-modal'
|
||||
title=''
|
||||
onOk={() => {
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import Form from '../../../../admin-x-ds/global/form/Form';
|
||||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import Select from '../../../../admin-x-ds/global/form/Select';
|
||||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
|
||||
interface WebhookModalProps {}
|
||||
|
||||
const WebhookModal: React.FC<WebhookModalProps> = () => {
|
||||
return <Modal
|
||||
okColor='black'
|
||||
okLabel='Add'
|
||||
size='sm'
|
||||
testId='webhook-modal'
|
||||
title='Add webhook'
|
||||
formSheet
|
||||
onOk={async () => {}}
|
||||
>
|
||||
<div className='mt-5'>
|
||||
<Form
|
||||
marginBottom={false}
|
||||
marginTop={false}
|
||||
>
|
||||
<TextField
|
||||
placeholder='Custom webhook'
|
||||
title='Name'
|
||||
/>
|
||||
<Select
|
||||
options={[
|
||||
{
|
||||
label: 'Global',
|
||||
options: [{label: 'Site changed', value: ''}]
|
||||
},
|
||||
{
|
||||
label: 'Posts',
|
||||
options: [
|
||||
{label: 'Post created', value: ''},
|
||||
{label: 'Post deleted', value: ''},
|
||||
{label: 'Post updated', value: ''},
|
||||
{label: 'Post published', value: ''},
|
||||
{label: 'Published post updated', value: ''},
|
||||
{label: 'Post unpublished', value: ''},
|
||||
{label: 'Post scheduled', value: ''},
|
||||
{label: 'Post unscheduled', value: ''},
|
||||
{label: 'Tag added to post', value: ''},
|
||||
{label: 'Tag removed from post', value: ''}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Pages',
|
||||
options: [
|
||||
{label: 'Page created', value: ''},
|
||||
{label: 'Page deleted', value: ''},
|
||||
{label: 'Page updated', value: ''},
|
||||
{label: 'Page published', value: ''},
|
||||
{label: 'Published page updated', value: ''},
|
||||
{label: 'Page unpublished', value: ''},
|
||||
{label: 'Tag added to page', value: ''},
|
||||
{label: 'Tag removed from page', value: ''}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Tags',
|
||||
options: [
|
||||
{label: 'Tag created', value: ''},
|
||||
{label: 'Tag deleted', value: ''},
|
||||
{label: 'Tag updated', value: ''}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Members',
|
||||
options: [
|
||||
{label: 'Members created', value: ''},
|
||||
{label: 'Members deleted', value: ''},
|
||||
{label: 'Members updated', value: ''}
|
||||
]
|
||||
}
|
||||
]}
|
||||
prompt='Select an event'
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
<TextField
|
||||
placeholder='https://example.com'
|
||||
title='Target URL'
|
||||
/>
|
||||
<TextField
|
||||
placeholder='Psst...'
|
||||
title='Secret'
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>;
|
||||
};
|
||||
|
||||
export default NiceModal.create(WebhookModal);
|
|
@ -66,6 +66,9 @@ const ZapierModal = NiceModal.create(() => {
|
|||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Close'
|
||||
|
@ -93,15 +96,16 @@ const ZapierModal = NiceModal.create(() => {
|
|||
<List className='mt-6'>
|
||||
{zapierTemplates.map(template => (
|
||||
<ListItem
|
||||
action={<Button color='green' href={template.url} label='Use this Zap' tag='a' target='_blank' link />}
|
||||
action={<Button className='whitespace-nowrap text-sm font-semibold text-[#FF4A00]' href={template.url} label='Use this Zap' tag='a' target='_blank' link unstyled />}
|
||||
avatar={<>
|
||||
<img className='h-10 w-10 object-contain' role='presentation' src={`${adminRoot}${template.ghostImage}`} />
|
||||
<ArrowRightIcon className='h-4 w-4' />
|
||||
<img className='h-10 w-10 object-contain' role='presentation' src={`${adminRoot}${template.appImage}`} />
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.ghostImage}`} />
|
||||
<ArrowRightIcon className='h-3 w-3' />
|
||||
<img className='h-8 w-8 object-contain' role='presentation' src={`${adminRoot}${template.appImage}`} />
|
||||
</>}
|
||||
bgOnHover={false}
|
||||
className='flex items-center gap-3 py-2'
|
||||
title={template.title}
|
||||
title={<span className='text-sm'>{template.title}</span>}
|
||||
hideActions
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
|
|
|
@ -160,6 +160,14 @@ module.exports = {
|
|||
'100%': {
|
||||
transform: 'translateY(0px)'
|
||||
}
|
||||
},
|
||||
modalInReverse: {
|
||||
'0%': {
|
||||
transform: 'translateY(-32px)'
|
||||
},
|
||||
'100%': {
|
||||
transform: 'translateY(0px)'
|
||||
}
|
||||
}
|
||||
},
|
||||
animation: {
|
||||
|
@ -170,7 +178,8 @@ module.exports = {
|
|||
'fade-out': 'fadeOut 0.15s ease forwards',
|
||||
'setting-highlight-fade-out': 'fadeOut 0.2s 1.4s ease forwards',
|
||||
'modal-backdrop-in': 'fadeIn 0.15s ease forwards',
|
||||
'modal-in': 'modalIn 0.25s ease forwards'
|
||||
'modal-in': 'modalIn 0.25s ease forwards',
|
||||
'modal-in-reverse': 'modalInReverse 0.25s ease forwards'
|
||||
},
|
||||
spacing: {
|
||||
px: '1px',
|
||||
|
|
Loading…
Add table
Reference in a new issue