Updated integration modals buttons (#20502)
DES-27 Updated buttons in integrations from [Cancel] and [Save & close] to [Close] and [Save] to be consistent with the rest of the Settings UI.
This commit is contained in:
parent
c285b0a0f1
commit
fca8941740
@ -27,6 +27,7 @@ export interface ModalProps {
|
||||
cancelLabel?: string;
|
||||
leftButtonProps?: ButtonProps;
|
||||
buttonsDisabled?: boolean;
|
||||
okDisabled?: boolean;
|
||||
footer?: boolean | React.ReactNode;
|
||||
header?: boolean;
|
||||
padding?: boolean;
|
||||
@ -62,6 +63,7 @@ const Modal: React.FC<ModalProps> = ({
|
||||
header,
|
||||
leftButtonProps,
|
||||
buttonsDisabled,
|
||||
okDisabled,
|
||||
padding = true,
|
||||
onOk,
|
||||
okColor = 'black',
|
||||
@ -179,7 +181,7 @@ const Modal: React.FC<ModalProps> = ({
|
||||
color: okColor,
|
||||
className: 'min-w-[80px]',
|
||||
onClick: onOk,
|
||||
disabled: buttonsDisabled,
|
||||
disabled: buttonsDisabled || okDisabled,
|
||||
loading: okLoading
|
||||
});
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ const AmpModal = NiceModal.create(() => {
|
||||
const {settings} = useGlobalData();
|
||||
const [ampEnabled] = getSettingValues<boolean>(settings, ['amp']);
|
||||
const [ampId] = getSettingValues<string>(settings, ['amp_gtag_id']);
|
||||
const modal = NiceModal.useModal();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [trackingId, setTrackingId] = useState<string | null>('');
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
const handleError = useHandleError();
|
||||
const [okLabel, setOkLabel] = useState('Save');
|
||||
const [enabled, setEnabled] = useState<boolean>(!!ampEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
setEnabled(ampEnabled || false);
|
||||
@ -30,26 +30,36 @@ const AmpModal = NiceModal.create(() => {
|
||||
{key: 'amp_gtag_id', value: trackingId}
|
||||
];
|
||||
try {
|
||||
await editSettings(updates);
|
||||
setOkLabel('Saving...');
|
||||
await Promise.all([
|
||||
editSettings(updates),
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
})
|
||||
]);
|
||||
setOkLabel('Saved');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setTimeout(() => setOkLabel('Save'), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const isDirty = !(enabled === ampEnabled) || !(trackingId === ampId);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
dirty={!(enabled === ampEnabled) || !(trackingId === ampId)}
|
||||
okColor='black'
|
||||
okLabel='Save & close'
|
||||
cancelLabel='Close'
|
||||
dirty={isDirty}
|
||||
okColor={okLabel === 'Saved' ? 'green' : 'black'}
|
||||
okLabel={okLabel}
|
||||
testId='amp-modal'
|
||||
title=''
|
||||
onOk={async () => {
|
||||
await handleSave();
|
||||
modal.remove();
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
>
|
||||
<IntegrationHeader
|
||||
|
@ -28,7 +28,6 @@ const CustomIntegrationModalContent: React.FC<{integration: Integration}> = ({in
|
||||
await editIntegration(formState);
|
||||
},
|
||||
onSavedStateReset: () => {
|
||||
modal.remove();
|
||||
updateRoute('integrations');
|
||||
},
|
||||
onSaveError: handleError,
|
||||
@ -82,9 +81,10 @@ const CustomIntegrationModalContent: React.FC<{integration: Integration}> = ({in
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
buttonsDisabled={okProps.disabled}
|
||||
cancelLabel='Close'
|
||||
dirty={saveState === 'unsaved'}
|
||||
okColor={okProps.color}
|
||||
okLabel={okProps.label || 'Save & close'}
|
||||
okLabel={okProps.label || 'Save'}
|
||||
size='md'
|
||||
testId='custom-integration-modal'
|
||||
title={formState.name || 'Custom integration'}
|
||||
|
@ -10,18 +10,19 @@ import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
const FirstpromoterModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
|
||||
const {settings} = useGlobalData();
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
const handleError = useHandleError();
|
||||
|
||||
const [accountId, setAccountId] = useState<string | null>('');
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
const [firstPromoterEnabled] = getSettingValues<boolean>(settings, ['firstpromoter']);
|
||||
const [firstPromoterId] = getSettingValues<string>(settings, ['firstpromoter_id']);
|
||||
|
||||
const [okLabel, setOkLabel] = useState('Save');
|
||||
const [enabled, setEnabled] = useState<boolean>(!!firstPromoterEnabled);
|
||||
|
||||
useEffect(() => {
|
||||
setEnabled(firstPromoterEnabled || false);
|
||||
setAccountId(firstPromoterId || null);
|
||||
@ -38,8 +39,20 @@ const FirstpromoterModal = NiceModal.create(() => {
|
||||
value: accountId
|
||||
}
|
||||
];
|
||||
|
||||
await editSettings(updates);
|
||||
try {
|
||||
setOkLabel('Saving...');
|
||||
await Promise.all([
|
||||
editSettings(updates),
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
})
|
||||
]);
|
||||
setOkLabel('Saved');
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
} finally {
|
||||
setTimeout(() => setOkLabel('Save'), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@ -47,16 +60,15 @@ const FirstpromoterModal = NiceModal.create(() => {
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel='Close'
|
||||
dirty={enabled !== firstPromoterEnabled || accountId !== firstPromoterId}
|
||||
okColor='black'
|
||||
okLabel='Save & close'
|
||||
okColor={okLabel === 'Saved' ? 'green' : 'black'}
|
||||
okLabel={okLabel}
|
||||
testId='firstpromoter-modal'
|
||||
title=''
|
||||
onOk={async () => {
|
||||
try {
|
||||
await handleSave();
|
||||
updateRoute('integrations');
|
||||
modal.remove();
|
||||
} catch (e) {
|
||||
handleError(e);
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ import {useUploadFile} from '@tryghost/admin-x-framework/api/files';
|
||||
|
||||
const PinturaModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [uploadingState, setUploadingState] = useState({
|
||||
js: false,
|
||||
css: false
|
||||
@ -29,6 +27,29 @@ const PinturaModal = NiceModal.create(() => {
|
||||
setEnabled(pinturaEnabled || false);
|
||||
}, [pinturaEnabled]);
|
||||
|
||||
const [okLabel, setOkLabel] = useState('Save');
|
||||
const [enabled, setEnabled] = useState<boolean>(!!pinturaEnabled);
|
||||
|
||||
const handleToggleChange = async () => {
|
||||
const updates: Setting[] = [
|
||||
{key: 'pintura', value: (enabled)}
|
||||
];
|
||||
try {
|
||||
setOkLabel('Saving...');
|
||||
await Promise.all([
|
||||
editSettings(updates),
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
})
|
||||
]);
|
||||
setOkLabel('Saved');
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
} finally {
|
||||
setTimeout(() => setOkLabel('Save'), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const jsUploadRef = useRef<HTMLInputElement>(null);
|
||||
const cssUploadRef = useRef<HTMLInputElement>(null);
|
||||
const triggerUpload = (form: string) => {
|
||||
@ -70,23 +91,20 @@ const PinturaModal = NiceModal.create(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const isDirty = !(enabled === pinturaEnabled);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
cancelLabel=''
|
||||
okColor='black'
|
||||
okLabel='Save'
|
||||
cancelLabel='Close'
|
||||
dirty={isDirty}
|
||||
okColor={okLabel === 'Saved' ? 'green' : 'black'}
|
||||
okLabel={okLabel}
|
||||
testId='pintura-modal'
|
||||
title=''
|
||||
onOk={async () => {
|
||||
modal.remove();
|
||||
updateRoute('integrations');
|
||||
await editSettings([
|
||||
{key: 'pintura', value: enabled}
|
||||
]);
|
||||
}}
|
||||
onOk={handleToggleChange}
|
||||
>
|
||||
<IntegrationHeader
|
||||
detail='Advanced image editing'
|
||||
|
@ -10,9 +10,8 @@ import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
const SlackModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
|
||||
const {localSettings, updateSetting, handleSave, validate, errors, clearError} = useSettingGroup({
|
||||
const {localSettings, updateSetting, handleSave, validate, errors, clearError, okProps} = useSettingGroup({
|
||||
onValidate: () => {
|
||||
const newErrors: Record<string, string> = {};
|
||||
|
||||
@ -21,7 +20,8 @@ const SlackModal = NiceModal.create(() => {
|
||||
}
|
||||
|
||||
return newErrors;
|
||||
}
|
||||
},
|
||||
savingDelay: 500
|
||||
});
|
||||
const [slackUrl, slackUsername] = getSettingValues<string>(localSettings, ['slack_url', 'slack_username']);
|
||||
|
||||
@ -38,22 +38,22 @@ const SlackModal = NiceModal.create(() => {
|
||||
}
|
||||
};
|
||||
|
||||
const isDirty = localSettings.some(setting => setting.dirty);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
dirty={localSettings.some(setting => setting.dirty)}
|
||||
okColor='black'
|
||||
okLabel='Save & close'
|
||||
cancelLabel='Close'
|
||||
dirty={isDirty}
|
||||
okColor={okProps.color}
|
||||
okLabel={okProps.label || 'Save'}
|
||||
testId='slack-modal'
|
||||
title=''
|
||||
onOk={async () => {
|
||||
toast.remove();
|
||||
if (await handleSave()) {
|
||||
modal.remove();
|
||||
updateRoute('integrations');
|
||||
}
|
||||
await handleSave();
|
||||
}}
|
||||
>
|
||||
<IntegrationHeader
|
||||
|
@ -3,42 +3,58 @@ import NiceModal from '@ebay/nice-modal-react';
|
||||
import {Form, Modal, Toggle} from '@tryghost/admin-x-design-system';
|
||||
import {ReactComponent as Icon} from '../../../../assets/icons/unsplash.svg';
|
||||
import {Setting, getSettingValues, useEditSettings} from '@tryghost/admin-x-framework/api/settings';
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
import {useHandleError} from '@tryghost/admin-x-framework/hooks';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
const UnsplashModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
const {settings} = useGlobalData();
|
||||
const [unsplashEnabled] = getSettingValues<boolean>(settings, ['unsplash']);
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
const handleError = useHandleError();
|
||||
const [okLabel, setOkLabel] = useState('Save');
|
||||
const [enabled, setEnabled] = useState<boolean>(!!unsplashEnabled);
|
||||
|
||||
const handleToggleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
useEffect(() => {
|
||||
setEnabled(unsplashEnabled || false);
|
||||
}, [unsplashEnabled]);
|
||||
|
||||
const handleToggleChange = async () => {
|
||||
const updates: Setting[] = [
|
||||
{key: 'unsplash', value: (e.target.checked)}
|
||||
{key: 'unsplash', value: (enabled)}
|
||||
];
|
||||
try {
|
||||
await editSettings(updates);
|
||||
setOkLabel('Saving...');
|
||||
await Promise.all([
|
||||
editSettings(updates),
|
||||
new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
})
|
||||
]);
|
||||
setOkLabel('Saved');
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
} finally {
|
||||
setTimeout(() => setOkLabel('Save'), 1000);
|
||||
}
|
||||
};
|
||||
|
||||
const isDirty = !(enabled === unsplashEnabled);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
okColor='black'
|
||||
okLabel='Save & close'
|
||||
cancelLabel='Close'
|
||||
dirty={isDirty}
|
||||
okColor={okLabel === 'Saved' ? 'green' : 'black'}
|
||||
okLabel={okLabel}
|
||||
testId='unsplash-modal'
|
||||
title=''
|
||||
onOk={() => {
|
||||
modal.remove();
|
||||
updateRoute('integrations');
|
||||
}}
|
||||
onOk={handleToggleChange}
|
||||
>
|
||||
<IntegrationHeader
|
||||
detail='Beautiful, free photos'
|
||||
@ -48,11 +64,13 @@ const UnsplashModal = NiceModal.create(() => {
|
||||
<div className='mt-7'>
|
||||
<Form marginBottom={false} grouped>
|
||||
<Toggle
|
||||
checked={unsplashEnabled}
|
||||
checked={enabled}
|
||||
direction='rtl'
|
||||
hint={<>Enable <a className='text-green' href="https://unsplash.com" rel="noopener noreferrer" target="_blank">Unsplash</a> image integration for your posts</>}
|
||||
label='Enable Unsplash'
|
||||
onChange={handleToggleChange}
|
||||
onChange={(e) => {
|
||||
setEnabled(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
|
@ -51,7 +51,7 @@ test.describe('AMP integration', async () => {
|
||||
const ampToggle = ampModal.getByRole('switch');
|
||||
await ampToggle.click();
|
||||
|
||||
await ampModal.getByRole('button', {name: 'Cancel'}).click();
|
||||
await ampModal.getByRole('button', {name: 'Close'}).click();
|
||||
|
||||
await expect(page.getByTestId('confirmation-modal')).toHaveText(/leave/i);
|
||||
|
||||
|
@ -140,7 +140,7 @@ test.describe('Custom integrations', async () => {
|
||||
|
||||
await modal.getByLabel('Description').fill('Test description');
|
||||
|
||||
await modal.getByRole('button', {name: 'Cancel'}).click();
|
||||
await modal.getByRole('button', {name: 'Close'}).click();
|
||||
|
||||
await expect(page.getByTestId('confirmation-modal')).toHaveText(/leave/i);
|
||||
|
||||
@ -190,7 +190,8 @@ test.describe('Custom integrations', async () => {
|
||||
// Edit integration
|
||||
|
||||
await modal.getByLabel('Description').fill('Test description');
|
||||
await modal.getByRole('button', {name: 'Save & close'}).click();
|
||||
await modal.getByRole('button', {name: 'Save'}).click();
|
||||
await modal.getByRole('button', {name: 'Close'}).click();
|
||||
|
||||
await expect(integrationsSection).toHaveText(/Test description/);
|
||||
|
||||
|
@ -51,7 +51,7 @@ test.describe('First Promoter integration', async () => {
|
||||
const fpToggle = fpModal.getByRole('switch');
|
||||
await fpToggle.click();
|
||||
|
||||
await fpModal.getByRole('button', {name: 'Cancel'}).click();
|
||||
await fpModal.getByRole('button', {name: 'Close'}).click();
|
||||
|
||||
await expect(page.getByTestId('confirmation-modal')).toHaveText(/leave/i);
|
||||
|
||||
|
@ -23,14 +23,15 @@ test.describe('Slack integration', async () => {
|
||||
// Failing validation
|
||||
|
||||
await slackModal.getByLabel('Webhook URL').fill('badurl');
|
||||
await slackModal.getByRole('button', {name: 'Save & close'}).click();
|
||||
await slackModal.getByRole('button', {name: 'Save'}).click();
|
||||
await expect(slackModal).toContainText('The URL must be in a format like https://hooks.slack.com/services/<your personal key>');
|
||||
|
||||
// Successful save
|
||||
|
||||
await slackModal.getByLabel('Webhook URL').fill('https://hooks.slack.com/services/123456789/123456789/123456789');
|
||||
await slackModal.getByLabel('Username').fill('My site');
|
||||
await slackModal.getByRole('button', {name: 'Save & close'}).click();
|
||||
await slackModal.getByRole('button', {name: 'Save'}).click();
|
||||
await slackModal.getByRole('button', {name: 'Close'}).click();
|
||||
|
||||
await expect(slackModal).toHaveCount(0);
|
||||
|
||||
@ -59,7 +60,7 @@ test.describe('Slack integration', async () => {
|
||||
|
||||
await slackModal.getByLabel('Webhook URL').fill('https://hooks.slack.com/services/123456789/123456789/123456789');
|
||||
|
||||
await slackModal.getByRole('button', {name: 'Cancel'}).click();
|
||||
await slackModal.getByRole('button', {name: 'Close'}).click();
|
||||
|
||||
await expect(page.getByTestId('confirmation-modal')).toHaveText(/leave/i);
|
||||
|
||||
@ -90,7 +91,7 @@ test.describe('Slack integration', async () => {
|
||||
// Doesn't send the request when validation fails
|
||||
|
||||
await slackModal.getByLabel('Webhook URL').fill('badurl');
|
||||
await slackModal.getByRole('button', {name: 'Save & close'}).click();
|
||||
await slackModal.getByRole('button', {name: 'Save'}).click();
|
||||
await expect(slackModal).toContainText('The URL must be in a format like https://hooks.slack.com/services/<your personal key>');
|
||||
expect(lastApiRequests.testSlack).toBeUndefined();
|
||||
|
||||
|
@ -21,6 +21,8 @@ test.describe('Unsplash integration', async () => {
|
||||
const unsplashToggle = unsplashModal.getByRole('switch');
|
||||
await unsplashToggle.click();
|
||||
|
||||
await unsplashModal.getByRole('button', {name: 'Save'}).click();
|
||||
|
||||
expect(lastApiRequests.editSettings?.body).toEqual({
|
||||
settings: [
|
||||
{key: 'unsplash', value: false}
|
||||
|
Loading…
Reference in New Issue
Block a user