fix(ui): unsaved changes allows for scheduled publish missing changes (#11001)
### What? When you first edit a document and then open the Schedule publish drawer, you can schedule publish changes but the current changes made to the form won't be included. ### Why? The UX does not make it clear that the changes you have in the form are not actually going to be published. ### How? Instead of allowing that we just disable the Schedule Publish drawer toggler so that users are forced to save a draft first. In addition to the above, this change also passes a defaultType so that an already published document will default the radio type have "Unpublish" selected.
This commit is contained in:
@@ -185,6 +185,7 @@ export const Button: React.FC<Props> = (props) => {
|
|||||||
className={disabled && !enableSubMenu ? `${baseClass}--popup-disabled` : ''}
|
className={disabled && !enableSubMenu ? `${baseClass}--popup-disabled` : ''}
|
||||||
disabled={disabled && !enableSubMenu}
|
disabled={disabled && !enableSubMenu}
|
||||||
horizontalAlign="right"
|
horizontalAlign="right"
|
||||||
|
id={`${id}-popup`}
|
||||||
noBackground
|
noBackground
|
||||||
render={({ close }) => SubMenuPopupContent({ close: () => close() })}
|
render={({ close }) => SubMenuPopupContent({ close: () => close() })}
|
||||||
size="large"
|
size="large"
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const baseClass = 'date-time-picker'
|
|||||||
|
|
||||||
const DatePicker: React.FC<Props> = (props) => {
|
const DatePicker: React.FC<Props> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
displayFormat: customDisplayFormat,
|
displayFormat: customDisplayFormat,
|
||||||
maxDate,
|
maxDate,
|
||||||
maxTime,
|
maxTime,
|
||||||
@@ -113,7 +114,7 @@ const DatePicker: React.FC<Props> = (props) => {
|
|||||||
}, [i18n.language, i18n.dateFNS])
|
}, [i18n.language, i18n.dateFNS])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes} id={id}>
|
||||||
<div className={`${baseClass}__icon-wrap`}>
|
<div className={`${baseClass}__icon-wrap`}>
|
||||||
{dateTimePickerProps.selected && (
|
{dateTimePickerProps.selected && (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { DayPickerProps, SharedProps, TimePickerProps } from 'payload'
|
import type { DayPickerProps, SharedProps, TimePickerProps } from 'payload'
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
|
id?: string
|
||||||
onChange?: (val: Date) => void
|
onChange?: (val: Date) => void
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
readOnly?: boolean
|
readOnly?: boolean
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export type PopupProps = {
|
|||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
forceOpen?: boolean
|
forceOpen?: boolean
|
||||||
horizontalAlign?: 'center' | 'left' | 'right'
|
horizontalAlign?: 'center' | 'left' | 'right'
|
||||||
|
id?: string
|
||||||
initActive?: boolean
|
initActive?: boolean
|
||||||
noBackground?: boolean
|
noBackground?: boolean
|
||||||
onToggleOpen?: (active: boolean) => void
|
onToggleOpen?: (active: boolean) => void
|
||||||
@@ -37,6 +38,7 @@ export type PopupProps = {
|
|||||||
|
|
||||||
export const Popup: React.FC<PopupProps> = (props) => {
|
export const Popup: React.FC<PopupProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
|
id,
|
||||||
boundingRef,
|
boundingRef,
|
||||||
button,
|
button,
|
||||||
buttonClassName,
|
buttonClassName,
|
||||||
@@ -168,7 +170,7 @@ export const Popup: React.FC<PopupProps> = (props) => {
|
|||||||
.join(' ')
|
.join(' ')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<div className={classes} id={id}>
|
||||||
<div className={`${baseClass}__trigger-wrap`}>
|
<div className={`${baseClass}__trigger-wrap`}>
|
||||||
{showOnHover ? (
|
{showOnHover ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { buildUpcomingColumns } from './buildUpcomingColumns.js'
|
|||||||
const baseClass = 'schedule-publish'
|
const baseClass = 'schedule-publish'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
defaultType?: PublishType
|
||||||
slug: string
|
slug: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ const defaultLocaleOption = {
|
|||||||
value: 'all',
|
value: 'all',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
|
export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
|
||||||
const { toggleModal } = useModal()
|
const { toggleModal } = useModal()
|
||||||
const {
|
const {
|
||||||
config: {
|
config: {
|
||||||
@@ -60,7 +61,7 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
|
|||||||
const { id, collectionSlug, globalSlug, title } = useDocumentInfo()
|
const { id, collectionSlug, globalSlug, title } = useDocumentInfo()
|
||||||
const { i18n, t } = useTranslation()
|
const { i18n, t } = useTranslation()
|
||||||
const { schedulePublish } = useServerFunctions()
|
const { schedulePublish } = useServerFunctions()
|
||||||
const [type, setType] = React.useState<PublishType>('publish')
|
const [type, setType] = React.useState<PublishType>(defaultType || 'publish')
|
||||||
const [date, setDate] = React.useState<Date>()
|
const [date, setDate] = React.useState<Date>()
|
||||||
const [timezone, setTimezone] = React.useState<string>(defaultTimezone)
|
const [timezone, setTimezone] = React.useState<string>(defaultTimezone)
|
||||||
const [locale, setLocale] = React.useState<{ label: string; value: string }>(defaultLocaleOption)
|
const [locale, setLocale] = React.useState<{ label: string; value: string }>(defaultLocaleOption)
|
||||||
@@ -313,8 +314,9 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<br />
|
<br />
|
||||||
<FieldLabel label={t('general:time')} required />
|
<FieldLabel label={t('general:time')} path={'time'} required />
|
||||||
<DatePickerField
|
<DatePickerField
|
||||||
|
id="time"
|
||||||
minDate={new Date()}
|
minDate={new Date()}
|
||||||
onChange={(e) => onChangeDate(e)}
|
onChange={(e) => onChangeDate(e)}
|
||||||
pickerAppearance="dayAndTime"
|
pickerAppearance="dayAndTime"
|
||||||
@@ -343,7 +345,13 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
|
|||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
<div className={`${baseClass}__actions`}>
|
<div className={`${baseClass}__actions`}>
|
||||||
<Button buttonStyle="primary" disabled={processing} onClick={handleSave} type="button">
|
<Button
|
||||||
|
buttonStyle="primary"
|
||||||
|
disabled={processing}
|
||||||
|
id="scheduled-publish-save"
|
||||||
|
onClick={handleSave}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
{t('general:save')}
|
{t('general:save')}
|
||||||
</Button>
|
</Button>
|
||||||
{processing ? <span>{t('general:saving')}</span> : null}
|
{processing ? <span>{t('general:saving')}</span> : null}
|
||||||
|
|||||||
@@ -73,7 +73,10 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
|
|||||||
entityConfig?.versions?.drafts.schedulePublish
|
entityConfig?.versions?.drafts.schedulePublish
|
||||||
|
|
||||||
const canSchedulePublish = Boolean(
|
const canSchedulePublish = Boolean(
|
||||||
scheduledPublishEnabled && hasPublishPermission && (globalSlug || (collectionSlug && id)),
|
scheduledPublishEnabled &&
|
||||||
|
hasPublishPermission &&
|
||||||
|
(globalSlug || (collectionSlug && id)) &&
|
||||||
|
!modified,
|
||||||
)
|
)
|
||||||
|
|
||||||
const operation = useOperation()
|
const operation = useOperation()
|
||||||
@@ -209,7 +212,10 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
|
|||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{canSchedulePublish && (
|
{canSchedulePublish && (
|
||||||
<PopupList.ButtonGroup key="schedule-publish">
|
<PopupList.ButtonGroup key="schedule-publish">
|
||||||
<PopupList.Button onClick={() => [toggleModal(drawerSlug), close()]}>
|
<PopupList.Button
|
||||||
|
id="schedule-publish"
|
||||||
|
onClick={() => [toggleModal(drawerSlug), close()]}
|
||||||
|
>
|
||||||
{t('version:schedulePublish')}
|
{t('version:schedulePublish')}
|
||||||
</PopupList.Button>
|
</PopupList.Button>
|
||||||
</PopupList.ButtonGroup>
|
</PopupList.ButtonGroup>
|
||||||
@@ -230,7 +236,12 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
|
|||||||
>
|
>
|
||||||
{localization ? defaultLabel : label}
|
{localization ? defaultLabel : label}
|
||||||
</FormSubmit>
|
</FormSubmit>
|
||||||
{canSchedulePublish && isModalOpen(drawerSlug) && <ScheduleDrawer slug={drawerSlug} />}
|
{canSchedulePublish && isModalOpen(drawerSlug) && (
|
||||||
|
<ScheduleDrawer
|
||||||
|
defaultType={!hasNewerVersions ? 'unpublish' : 'publish'}
|
||||||
|
slug={drawerSlug}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -743,6 +743,49 @@ describe('Versions', () => {
|
|||||||
expect(versionsTabUpdated).toBeTruthy()
|
expect(versionsTabUpdated).toBeTruthy()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Scheduled publish', () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
url = new AdminUrlUtil(serverURL, draftCollectionSlug)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should schedule publish', async () => {
|
||||||
|
await page.goto(url.create)
|
||||||
|
await page.waitForURL(url.create)
|
||||||
|
await page.locator('#field-title').fill('scheduled publish')
|
||||||
|
await page.locator('#field-description').fill('scheduled publish description')
|
||||||
|
|
||||||
|
// schedule publish should not be available before document has been saved
|
||||||
|
await page.locator('#action-save-popup').click()
|
||||||
|
await expect(page.locator('#schedule-publish')).not.toBeVisible()
|
||||||
|
|
||||||
|
// save draft then try to schedule publish
|
||||||
|
await saveDocAndAssert(page)
|
||||||
|
await page.locator('#action-save-popup').click()
|
||||||
|
await page.locator('#schedule-publish').click()
|
||||||
|
|
||||||
|
// drawer should open
|
||||||
|
await expect(page.locator('.schedule-publish__drawer-header')).toBeVisible()
|
||||||
|
// nothing in scheduled
|
||||||
|
await expect(page.locator('.drawer__content')).toContainText('No upcoming events scheduled.')
|
||||||
|
|
||||||
|
// set date and time
|
||||||
|
await page.locator('.date-time-picker input').fill('Feb 21, 2050 12:00 AM')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
// save the scheduled publish
|
||||||
|
await page.locator('#scheduled-publish-save').click()
|
||||||
|
|
||||||
|
// delete the scheduled event after it was made
|
||||||
|
await page.locator('.cell-delete').locator('.btn').click()
|
||||||
|
|
||||||
|
// see toast deleted successfully
|
||||||
|
await expect(
|
||||||
|
page.locator('.payload-toast-item:has-text("Deleted successfully.")'),
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('Collections - publish specific locale', () => {
|
describe('Collections - publish specific locale', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
url = new AdminUrlUtil(serverURL, localizedCollectionSlug)
|
url = new AdminUrlUtil(serverURL, localizedCollectionSlug)
|
||||||
|
|||||||
Reference in New Issue
Block a user