Add the collapsible Schedule section (#44563)
* Create schedule section within the pre-publish panel component * Add publish date time picket to the schedule section * Enhance schedule section title to show the selected date * Change the text of the publish button to schedule once the product is scheduled * Add changelog file * Fix linter errors * Set schedule text in pre publish button too * Fix timezone offset from getSiteSettingsTimezoneAbbreviation when the onboarding wizard is skipped
This commit is contained in:
parent
4061bbfc2b
commit
5af88543eb
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add the collapsible Schedule section
|
|
@ -14,6 +14,7 @@ import { MouseEvent } from 'react';
|
|||
import { useValidations } from '../../../../contexts/validation-context';
|
||||
import type { WPError } from '../../../../utils/get-product-error-message';
|
||||
import type { PublishButtonProps } from '../../publish-button';
|
||||
import { useProductScheduled } from '../../../../hooks/use-product-scheduled';
|
||||
|
||||
export function usePublish( {
|
||||
productType = 'product',
|
||||
|
@ -35,6 +36,8 @@ export function usePublish( {
|
|||
'id'
|
||||
);
|
||||
|
||||
const isScheduled = useProductScheduled( productType );
|
||||
|
||||
const { isSaving, isDirty } = useSelect(
|
||||
( select ) => {
|
||||
const {
|
||||
|
@ -42,8 +45,6 @@ export function usePublish( {
|
|||
isSavingEntityRecord,
|
||||
// @ts-expect-error There are no types for this.
|
||||
hasEditsForEntityRecord,
|
||||
// @ts-expect-error There are no types for this.
|
||||
getRawEntityRecord,
|
||||
} = select( 'core' );
|
||||
|
||||
return {
|
||||
|
@ -57,11 +58,6 @@ export function usePublish( {
|
|||
productType,
|
||||
productId
|
||||
),
|
||||
currentPost: getRawEntityRecord< boolean >(
|
||||
'postType',
|
||||
productType,
|
||||
productId
|
||||
),
|
||||
};
|
||||
},
|
||||
[ productId ]
|
||||
|
@ -146,10 +142,18 @@ export function usePublish( {
|
|||
}
|
||||
}
|
||||
|
||||
return {
|
||||
children: isPublished
|
||||
function getButtonText() {
|
||||
if ( isScheduled ) {
|
||||
return __( 'Schedule', 'woocommerce' );
|
||||
}
|
||||
|
||||
return isPublished
|
||||
? __( 'Update', 'woocommerce' )
|
||||
: __( 'Publish', 'woocommerce' ),
|
||||
: __( 'Publish', 'woocommerce' );
|
||||
}
|
||||
|
||||
return {
|
||||
children: getButtonText(),
|
||||
...props,
|
||||
isBusy,
|
||||
'aria-disabled': isDisabled,
|
||||
|
|
|
@ -15,6 +15,7 @@ import { store as productEditorUiStore } from '../../store/product-editor-ui';
|
|||
import { PrepublishButtonProps } from './types';
|
||||
import { useValidations } from '../../contexts/validation-context';
|
||||
import { TRACKS_SOURCE } from '../../constants';
|
||||
import { useProductScheduled } from '../../hooks/use-product-scheduled';
|
||||
|
||||
export function PrepublishButton( {
|
||||
productId,
|
||||
|
@ -49,6 +50,7 @@ export function PrepublishButton( {
|
|||
|
||||
const isBusy = isSaving || isValidating;
|
||||
const isDisabled = isBusy || ! isDirty;
|
||||
const isScheduled = useProductScheduled( productType );
|
||||
|
||||
return (
|
||||
<Button
|
||||
|
@ -61,7 +63,11 @@ export function PrepublishButton( {
|
|||
} }
|
||||
isBusy={ isBusy }
|
||||
aria-disabled={ isDisabled }
|
||||
children={ __( 'Publish', 'woocommerce' ) }
|
||||
children={
|
||||
isScheduled
|
||||
? __( 'Schedule', 'woocommerce' )
|
||||
: __( 'Publish', 'woocommerce' )
|
||||
}
|
||||
variant={ 'primary' }
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -15,6 +15,7 @@ import { PrepublishPanelProps } from './types';
|
|||
import { store as productEditorUiStore } from '../../store/product-editor-ui';
|
||||
import { TRACKS_SOURCE } from '../../constants';
|
||||
import { VisibilitySection } from './visibility-section';
|
||||
import { ScheduleSection } from './schedule-section';
|
||||
|
||||
export function PrepublishPanel( {
|
||||
productId,
|
||||
|
@ -60,6 +61,8 @@ export function PrepublishPanel( {
|
|||
<span>{ description }</span>
|
||||
</div>
|
||||
<VisibilitySection productType={ productType } />
|
||||
|
||||
<ScheduleSection postType={ productType } />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './schedule-section';
|
||||
export * from './types';
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { PanelBody } from '@wordpress/components';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import {
|
||||
DateSettings,
|
||||
dateI18n,
|
||||
getDate,
|
||||
__experimentalGetSettings as getSettings,
|
||||
} from '@wordpress/date';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { __, _x, isRTL, sprintf } from '@wordpress/i18n';
|
||||
import {
|
||||
// @ts-expect-error no exported member
|
||||
__experimentalPublishDateTimePicker as PublishDateTimePicker,
|
||||
} from '@wordpress/block-editor';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ScheduleSectionProps } from './types';
|
||||
import {
|
||||
getSiteSettingsTimezoneAbbreviation,
|
||||
isSameDay,
|
||||
isSiteSettingsTime12HourFormatted,
|
||||
isSiteSettingsTimezoneSameAsDateTimezone,
|
||||
} from '../../../utils';
|
||||
|
||||
export function getFormattedDateTime( value: string ) {
|
||||
const { formats } = getSettings() as DateSettings;
|
||||
|
||||
return dateI18n(
|
||||
sprintf(
|
||||
// translators: %s: Time of day the product is scheduled for.
|
||||
_x(
|
||||
'F j, Y %s',
|
||||
'product schedule full date format',
|
||||
'woocommerce'
|
||||
),
|
||||
formats.time
|
||||
),
|
||||
value,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function getFullScheduleLabel( dateAttribute: string ) {
|
||||
const timezoneAbbreviation = getSiteSettingsTimezoneAbbreviation();
|
||||
const formattedDate = getFormattedDateTime( dateAttribute );
|
||||
|
||||
return isRTL()
|
||||
? `${ timezoneAbbreviation } ${ formattedDate }`
|
||||
: `${ formattedDate } ${ timezoneAbbreviation }`;
|
||||
}
|
||||
|
||||
export function getScheduleLabel( dateAttribute?: string, now = new Date() ) {
|
||||
if ( ! dateAttribute ) {
|
||||
return __( 'Immediately', 'woocommerce' );
|
||||
}
|
||||
|
||||
// If the user timezone does not equal the site timezone then using words
|
||||
// like 'tomorrow' is confusing, so show the full date.
|
||||
if ( ! isSiteSettingsTimezoneSameAsDateTimezone( now ) ) {
|
||||
return getFullScheduleLabel( dateAttribute );
|
||||
}
|
||||
|
||||
const { formats } = getSettings() as DateSettings;
|
||||
const date = getDate( dateAttribute );
|
||||
|
||||
if ( isSameDay( date, now ) ) {
|
||||
return sprintf(
|
||||
// translators: %s: Time of day the product is scheduled for.
|
||||
__( 'Today at %s', 'woocommerce' ),
|
||||
dateI18n( formats.time, dateAttribute, undefined )
|
||||
);
|
||||
}
|
||||
|
||||
const tomorrow = new Date( now );
|
||||
tomorrow.setDate( tomorrow.getDate() + 1 );
|
||||
|
||||
if ( isSameDay( date, tomorrow ) ) {
|
||||
return sprintf(
|
||||
// translators: %s: Time of day the product is scheduled for.
|
||||
__( 'Tomorrow at %s', 'woocommerce' ),
|
||||
dateI18n( formats.time, dateAttribute, undefined )
|
||||
);
|
||||
}
|
||||
|
||||
if ( date.getFullYear() === now.getFullYear() ) {
|
||||
return dateI18n(
|
||||
sprintf(
|
||||
// translators: %s: Time of day the product is scheduled for.
|
||||
_x(
|
||||
'F j %s',
|
||||
'product schedule date format without year',
|
||||
'woocommerce'
|
||||
),
|
||||
formats.time
|
||||
),
|
||||
date,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
return getFormattedDateTime( dateAttribute );
|
||||
}
|
||||
|
||||
export function ScheduleSection( { postType }: ScheduleSectionProps ) {
|
||||
const [ editedDate, setDate, date ] = useEntityProp< string >(
|
||||
'postType',
|
||||
postType,
|
||||
'date_created'
|
||||
);
|
||||
|
||||
function handlePublishDateTimePickerChange( value: string ) {
|
||||
setDate( value );
|
||||
}
|
||||
|
||||
return (
|
||||
<PanelBody
|
||||
initialOpen={ false }
|
||||
// @ts-expect-error title does currently support this value
|
||||
title={ [
|
||||
__( 'Add:', 'woocommerce' ),
|
||||
<span className="editor-post-publish-panel__link" key="label">
|
||||
{ getScheduleLabel(
|
||||
editedDate === date ? undefined : editedDate
|
||||
) }
|
||||
</span>,
|
||||
] }
|
||||
>
|
||||
<PublishDateTimePicker
|
||||
currentDate={ editedDate }
|
||||
onChange={ handlePublishDateTimePickerChange }
|
||||
is12Hour={ isSiteSettingsTime12HourFormatted() }
|
||||
/>
|
||||
</PanelBody>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export type ScheduleSectionProps = {
|
||||
postType: string;
|
||||
};
|
|
@ -6,3 +6,4 @@ export { useVariationSwitcher as __experimentalUseVariationSwitcher } from './us
|
|||
export { default as __experimentalUseProductEntityProp } from './use-product-entity-prop';
|
||||
export { default as __experimentalUseProductMetadata } from './use-product-metadata';
|
||||
export { useProductTemplate as __experimentalUseProductTemplate } from './use-product-template';
|
||||
export { useProductScheduled as __experimentalUseProductScheduled } from './use-product-scheduled';
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './use-product-scheduled';
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { isInTheFuture } from '@wordpress/date';
|
||||
|
||||
export function useProductScheduled( postType: string ) {
|
||||
const [ date ] = useEntityProp< string >(
|
||||
'postType',
|
||||
postType,
|
||||
'date_created'
|
||||
);
|
||||
|
||||
return isInTheFuture( date );
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
TimezoneConfig,
|
||||
__experimentalGetSettings as getSettings,
|
||||
} from '@wordpress/date';
|
||||
|
||||
export function getSiteSettingsTimezoneAbbreviation() {
|
||||
const { timezone } = getSettings() as {
|
||||
timezone: TimezoneConfig & { offsetFormatted: string };
|
||||
};
|
||||
|
||||
if ( timezone.abbr && isNaN( Number( timezone.abbr ) ) ) {
|
||||
return timezone.abbr;
|
||||
}
|
||||
|
||||
const symbol = Number( timezone.offset ) < 0 ? '' : '+';
|
||||
return `UTC${ symbol }${ timezone.offsetFormatted ?? timezone.offset }`;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './get-site-settings-timezone-abbreviation';
|
||||
export * from './is-same-day';
|
||||
export * from './is-site-settings-time-12-hour-formatted';
|
||||
export * from './is-site-settings-timezone-same-as-date-timezone';
|
|
@ -0,0 +1,7 @@
|
|||
export function isSameDay( left: Date, right: Date ) {
|
||||
return (
|
||||
left.getDate() === right.getDate() &&
|
||||
left.getMonth() === right.getMonth() &&
|
||||
left.getFullYear() === right.getFullYear()
|
||||
);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
DateSettings,
|
||||
__experimentalGetSettings as getSettings,
|
||||
} from '@wordpress/date';
|
||||
|
||||
export function isSiteSettingsTime12HourFormatted() {
|
||||
const settings = getSettings() as DateSettings;
|
||||
|
||||
return /a(?!\\)/i.test(
|
||||
settings.formats.time
|
||||
.toLowerCase()
|
||||
.replace( /\\\\/g, '' )
|
||||
.split( '' )
|
||||
.reverse()
|
||||
.join( '' )
|
||||
);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import {
|
||||
DateSettings,
|
||||
__experimentalGetSettings as getSettings,
|
||||
} from '@wordpress/date';
|
||||
|
||||
export function isSiteSettingsTimezoneSameAsDateTimezone( date: Date ) {
|
||||
const { timezone } = getSettings() as DateSettings;
|
||||
|
||||
const siteOffset = Number( timezone.offset );
|
||||
const dateOffset = -1 * ( date.getTimezoneOffset() / 60 );
|
||||
return siteOffset === dateOffset;
|
||||
}
|
|
@ -23,6 +23,7 @@ import { hasAttributesUsedForVariations } from './has-attributes-used-for-variat
|
|||
import { isValidEmail } from './validate-email';
|
||||
|
||||
export * from './create-ordered-children';
|
||||
export * from './date';
|
||||
export * from './sort-fills-by-order';
|
||||
export * from './register-product-editor-block-type';
|
||||
export * from './init-block';
|
||||
|
|
Loading…
Reference in New Issue