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:
Dan Ribbens
2025-02-19 10:10:29 -05:00
committed by GitHub
parent cd48904798
commit 618624e110
7 changed files with 76 additions and 9 deletions

View File

@@ -185,6 +185,7 @@ export const Button: React.FC<Props> = (props) => {
className={disabled && !enableSubMenu ? `${baseClass}--popup-disabled` : ''}
disabled={disabled && !enableSubMenu}
horizontalAlign="right"
id={`${id}-popup`}
noBackground
render={({ close }) => SubMenuPopupContent({ close: () => close() })}
size="large"

View File

@@ -19,6 +19,7 @@ const baseClass = 'date-time-picker'
const DatePicker: React.FC<Props> = (props) => {
const {
id,
displayFormat: customDisplayFormat,
maxDate,
maxTime,
@@ -113,7 +114,7 @@ const DatePicker: React.FC<Props> = (props) => {
}, [i18n.language, i18n.dateFNS])
return (
<div className={classes}>
<div className={classes} id={id}>
<div className={`${baseClass}__icon-wrap`}>
{dateTimePickerProps.selected && (
<button

View File

@@ -1,6 +1,7 @@
import type { DayPickerProps, SharedProps, TimePickerProps } from 'payload'
export type Props = {
id?: string
onChange?: (val: Date) => void
placeholder?: string
readOnly?: boolean

View File

@@ -25,6 +25,7 @@ export type PopupProps = {
disabled?: boolean
forceOpen?: boolean
horizontalAlign?: 'center' | 'left' | 'right'
id?: string
initActive?: boolean
noBackground?: boolean
onToggleOpen?: (active: boolean) => void
@@ -37,6 +38,7 @@ export type PopupProps = {
export const Popup: React.FC<PopupProps> = (props) => {
const {
id,
boundingRef,
button,
buttonClassName,
@@ -168,7 +170,7 @@ export const Popup: React.FC<PopupProps> = (props) => {
.join(' ')
return (
<div className={classes}>
<div className={classes} id={id}>
<div className={`${baseClass}__trigger-wrap`}>
{showOnHover ? (
<div

View File

@@ -36,6 +36,7 @@ import { buildUpcomingColumns } from './buildUpcomingColumns.js'
const baseClass = 'schedule-publish'
type Props = {
defaultType?: PublishType
slug: string
}
@@ -44,7 +45,7 @@ const defaultLocaleOption = {
value: 'all',
}
export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
export const ScheduleDrawer: React.FC<Props> = ({ slug, defaultType }) => {
const { toggleModal } = useModal()
const {
config: {
@@ -60,7 +61,7 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
const { id, collectionSlug, globalSlug, title } = useDocumentInfo()
const { i18n, t } = useTranslation()
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 [timezone, setTimezone] = React.useState<string>(defaultTimezone)
const [locale, setLocale] = React.useState<{ label: string; value: string }>(defaultLocaleOption)
@@ -313,8 +314,9 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
</li>
</ul>
<br />
<FieldLabel label={t('general:time')} required />
<FieldLabel label={t('general:time')} path={'time'} required />
<DatePickerField
id="time"
minDate={new Date()}
onChange={(e) => onChangeDate(e)}
pickerAppearance="dayAndTime"
@@ -343,7 +345,13 @@ export const ScheduleDrawer: React.FC<Props> = ({ slug }) => {
</React.Fragment>
)}
<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')}
</Button>
{processing ? <span>{t('general:saving')}</span> : null}

View File

@@ -73,7 +73,10 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
entityConfig?.versions?.drafts.schedulePublish
const canSchedulePublish = Boolean(
scheduledPublishEnabled && hasPublishPermission && (globalSlug || (collectionSlug && id)),
scheduledPublishEnabled &&
hasPublishPermission &&
(globalSlug || (collectionSlug && id)) &&
!modified,
)
const operation = useOperation()
@@ -209,7 +212,10 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
<React.Fragment>
{canSchedulePublish && (
<PopupList.ButtonGroup key="schedule-publish">
<PopupList.Button onClick={() => [toggleModal(drawerSlug), close()]}>
<PopupList.Button
id="schedule-publish"
onClick={() => [toggleModal(drawerSlug), close()]}
>
{t('version:schedulePublish')}
</PopupList.Button>
</PopupList.ButtonGroup>
@@ -230,7 +236,12 @@ export function PublishButton({ label: labelProp }: PublishButtonClientProps) {
>
{localization ? defaultLabel : label}
</FormSubmit>
{canSchedulePublish && isModalOpen(drawerSlug) && <ScheduleDrawer slug={drawerSlug} />}
{canSchedulePublish && isModalOpen(drawerSlug) && (
<ScheduleDrawer
defaultType={!hasNewerVersions ? 'unpublish' : 'publish'}
slug={drawerSlug}
/>
)}
</React.Fragment>
)
}

View File

@@ -743,6 +743,49 @@ describe('Versions', () => {
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', () => {
beforeAll(() => {
url = new AdminUrlUtil(serverURL, localizedCollectionSlug)