Add message after publishing a product to pre-publish panel (#44864)
* Add message after publishing product * add styles * Fix animation styles * Fix panel footer * Remove console.logs * Add changelog * Fix product url * Remove preview * Add post-publish-section and fix animation * Add hook useProductURL * Add postpublish * Refactor usePreview * Use PostPublishSection and PostPublishTitle * move style * modify styles * Fix footer * Fix copy * Add ts-ignore * Add CopyButtonProps * Fix productStatus * Rename isPublished
This commit is contained in:
parent
8ff89fa6db
commit
9865701cbb
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add message after publishing a product to pre-publish panel #44864
|
|
@ -14,6 +14,7 @@ import { MouseEvent } from 'react';
|
|||
*/
|
||||
import { useValidations } from '../../../../contexts/validation-context';
|
||||
import { WPError } from '../../../../utils/get-product-error-message';
|
||||
import { useProductURL } from '../../../../hooks/use-product-url';
|
||||
import { PreviewButtonProps } from '../../preview-button';
|
||||
|
||||
export function usePreview( {
|
||||
|
@ -36,11 +37,7 @@ export function usePreview( {
|
|||
'id'
|
||||
);
|
||||
|
||||
const [ permalink ] = useEntityProp< string >(
|
||||
'postType',
|
||||
productType,
|
||||
'permalink'
|
||||
);
|
||||
const { getProductURL } = useProductURL( productType );
|
||||
|
||||
const { hasEdits, isDisabled } = useSelect(
|
||||
( select ) => {
|
||||
|
@ -72,12 +69,6 @@ export function usePreview( {
|
|||
// @ts-expect-error There are no types for this.
|
||||
const { editEntityRecord, saveEditedEntityRecord } = useDispatch( 'core' );
|
||||
|
||||
let previewLink: URL | undefined;
|
||||
if ( typeof permalink === 'string' ) {
|
||||
previewLink = new URL( permalink );
|
||||
previewLink.searchParams.append( 'preview', 'true' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default anchor behaviour when the product has unsaved changes.
|
||||
* Before redirecting to the preview page all changes are saved and then the
|
||||
|
@ -157,7 +148,7 @@ export function usePreview( {
|
|||
'aria-disabled': ariaDisabled,
|
||||
// Note that the href is always passed for a11y support. So
|
||||
// the final rendered element is always an anchor.
|
||||
href: previewLink?.toString(),
|
||||
href: getProductURL( true ),
|
||||
variant: 'tertiary',
|
||||
onClick: handleClick,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
export * from './post-publish-section';
|
||||
export * from './post-publish-title';
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { createElement, useState, Fragment } from '@wordpress/element';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { Button, PanelBody, TextControl } from '@wordpress/components';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useCopyToClipboard } from '@wordpress/compose';
|
||||
import { Ref } from 'react';
|
||||
import { getNewPath } from '@woocommerce/navigation';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { useProductScheduled } from '../../../hooks/use-product-scheduled';
|
||||
import { CopyButtonProps, PostPublishSectionProps } from './types';
|
||||
import { TRACKS_SOURCE } from '../../../constants';
|
||||
import { useProductURL } from '../../../hooks/use-product-url';
|
||||
|
||||
export function PostPublishSection( { postType }: PostPublishSectionProps ) {
|
||||
const { getProductURL } = useProductURL( postType );
|
||||
const { isScheduled } = useProductScheduled( postType );
|
||||
|
||||
const [ showCopyConfirmation, setShowCopyConfirmation ] =
|
||||
useState< boolean >( false );
|
||||
|
||||
const productURL = getProductURL( isScheduled );
|
||||
|
||||
const CopyButton = ( { text, onCopy, children }: CopyButtonProps ) => {
|
||||
const ref = useCopyToClipboard(
|
||||
text,
|
||||
onCopy
|
||||
) as Ref< HTMLButtonElement >;
|
||||
return (
|
||||
<Button variant="secondary" ref={ ref }>
|
||||
{ children }
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const onCopyURL = () => {
|
||||
recordEvent( 'product_prepublish_panel', {
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'copy_product_url',
|
||||
} );
|
||||
setShowCopyConfirmation( true );
|
||||
setTimeout( () => {
|
||||
setShowCopyConfirmation( false );
|
||||
}, 4000 );
|
||||
};
|
||||
|
||||
const onSelectInput = ( event: React.FocusEvent< HTMLInputElement > ) => {
|
||||
event.target.select();
|
||||
};
|
||||
|
||||
return (
|
||||
<PanelBody>
|
||||
<p className="post-publish-section__postpublish-subheader">
|
||||
<strong>{ __( 'What’s next?', 'woocommerce' ) }</strong>
|
||||
</p>
|
||||
<div className="post-publish-section__postpublish-post-address-container">
|
||||
{ /* eslint-disable-next-line @typescript-eslint/ban-ts-comment */ }
|
||||
{ /* @ts-ignore TextControl requires an 'onChange' but it's not necessary here. */ }
|
||||
<TextControl
|
||||
className="post-publish-section__postpublish-post-address"
|
||||
readOnly
|
||||
label={ __( 'product address', 'woocommerce' ) }
|
||||
value={ productURL }
|
||||
onFocus={ onSelectInput }
|
||||
/>
|
||||
|
||||
<div className="post-publish-section__copy-button-wrap">
|
||||
<CopyButton text={ productURL } onCopy={ onCopyURL }>
|
||||
<>
|
||||
{ showCopyConfirmation
|
||||
? __( 'Copied!', 'woocommerce' )
|
||||
: __( 'Copy', 'woocommerce' ) }
|
||||
</>
|
||||
</CopyButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="post-publish-section__postpublish-buttons">
|
||||
{ ! isScheduled && (
|
||||
<Button variant="primary" href={ productURL }>
|
||||
{ __( 'View Product', 'woocommerce' ) }
|
||||
</Button>
|
||||
) }
|
||||
<Button
|
||||
variant={ isScheduled ? 'primary' : 'secondary' }
|
||||
href={ getNewPath( {}, '/add-product', {} ) }
|
||||
>
|
||||
{ __( 'Add New Product', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
</PanelBody>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { createElement, createInterpolateElement } from '@wordpress/element';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { PostPublishTitleProps } from './types';
|
||||
import { useProductURL } from '../../../hooks/use-product-url';
|
||||
import { useProductScheduled } from '../../../hooks/use-product-scheduled';
|
||||
|
||||
export function PostPublishTitle( {
|
||||
productType = 'product',
|
||||
}: PostPublishTitleProps ) {
|
||||
const { getProductURL } = useProductURL( productType );
|
||||
const { isScheduled, formattedDate } = useProductScheduled( productType );
|
||||
const [ editedProductName ] = useEntityProp< string >(
|
||||
'postType',
|
||||
productType,
|
||||
'name'
|
||||
);
|
||||
|
||||
const productURLString = getProductURL( false );
|
||||
const getPostPublishedTitle = () => {
|
||||
if ( isScheduled ) {
|
||||
return createInterpolateElement(
|
||||
sprintf(
|
||||
/* translators: %s is the date when the product will be published */
|
||||
__(
|
||||
'<productURL /> is now scheduled. It will go live on %s',
|
||||
'woocommerce'
|
||||
),
|
||||
formattedDate
|
||||
),
|
||||
{
|
||||
productURL: (
|
||||
<a
|
||||
className="woocommerce-product-list__product-name"
|
||||
href={ productURLString }
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ editedProductName }
|
||||
</a>
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
return createInterpolateElement(
|
||||
__( '<productURL /> is now live.', 'woocommerce' ),
|
||||
{
|
||||
productURL: (
|
||||
<a
|
||||
className="woocommerce-product-list__product-name"
|
||||
href={ productURLString }
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{ editedProductName }
|
||||
</a>
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="woocommerce-product-publish-panel__published">
|
||||
{ getPostPublishedTitle() }
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
export type PostPublishSectionProps = {
|
||||
postType: string;
|
||||
};
|
||||
|
||||
export type PostPublishTitleProps = {
|
||||
productType: string;
|
||||
};
|
||||
|
||||
export type CopyButtonProps = {
|
||||
text: string;
|
||||
onCopy: () => void;
|
||||
children: JSX.Element;
|
||||
};
|
|
@ -2,11 +2,14 @@
|
|||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { createElement } from '@wordpress/element';
|
||||
import { createElement, Fragment } from '@wordpress/element';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { useDispatch } from '@wordpress/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { closeSmall } from '@wordpress/icons';
|
||||
import classnames from 'classnames';
|
||||
import type { Product } from '@woocommerce/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -18,6 +21,7 @@ import { TRACKS_SOURCE } from '../../constants';
|
|||
import { VisibilitySection } from './visibility-section';
|
||||
import { ScheduleSection } from './schedule-section';
|
||||
import { ShowPrepublishChecksSection } from './show-prepublish-checks-section';
|
||||
import { PostPublishSection, PostPublishTitle } from './post-publish';
|
||||
|
||||
export function PrepublishPanel( {
|
||||
productType = 'product',
|
||||
|
@ -33,8 +37,17 @@ export function PrepublishPanel( {
|
|||
'date_created'
|
||||
);
|
||||
|
||||
const [ productStatus, , prevStatus ] = useEntityProp<
|
||||
Product[ 'status' ]
|
||||
>( 'postType', productType, 'status' );
|
||||
|
||||
const { closePrepublishPanel } = useDispatch( productEditorUiStore );
|
||||
|
||||
const isPublishedOrScheduled =
|
||||
productType === 'product' && prevStatus !== 'future'
|
||||
? productStatus === 'publish'
|
||||
: true;
|
||||
|
||||
if ( editedDate !== date ) {
|
||||
title = __( 'Are you ready to schedule this product?', 'woocommerce' );
|
||||
description = __(
|
||||
|
@ -43,9 +56,25 @@ export function PrepublishPanel( {
|
|||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="woocommerce-product-publish-panel">
|
||||
<div className="woocommerce-product-publish-panel__header">
|
||||
function getHeaderActions() {
|
||||
if ( isPublishedOrScheduled ) {
|
||||
return (
|
||||
<Button
|
||||
className="woocommerce-publish-panel-close"
|
||||
icon={ closeSmall }
|
||||
label={ __( 'Close panel', 'woocommerce' ) }
|
||||
onClick={ () => {
|
||||
recordEvent( 'product_prepublish_panel', {
|
||||
source: TRACKS_SOURCE,
|
||||
action: 'close',
|
||||
} );
|
||||
closePrepublishPanel();
|
||||
} }
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<PublishButton productType={ productType } />
|
||||
<Button
|
||||
variant={ 'secondary' }
|
||||
|
@ -59,14 +88,48 @@ export function PrepublishPanel( {
|
|||
>
|
||||
{ __( 'Cancel', 'woocommerce' ) }
|
||||
</Button>
|
||||
</div>
|
||||
<div className="woocommerce-product-publish-panel__title">
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getPanelTitle() {
|
||||
if ( isPublishedOrScheduled ) {
|
||||
return <PostPublishTitle productType={ productType } />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<h4>{ title }</h4>
|
||||
<span>{ description }</span>
|
||||
</div>
|
||||
<div className="woocommerce-product-publish-panel__content">
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getPanelSections() {
|
||||
if ( isPublishedOrScheduled ) {
|
||||
return <PostPublishSection postType={ productType } />;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<VisibilitySection productType={ productType } />
|
||||
<ScheduleSection postType={ productType } />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={ classnames( 'woocommerce-product-publish-panel', {
|
||||
'is-published': isPublishedOrScheduled,
|
||||
} ) }
|
||||
>
|
||||
<div className="woocommerce-product-publish-panel__header">
|
||||
{ getHeaderActions() }
|
||||
</div>
|
||||
<div className="woocommerce-product-publish-panel__title">
|
||||
{ getPanelTitle() }
|
||||
</div>
|
||||
<div className="woocommerce-product-publish-panel__content">
|
||||
{ getPanelSections() }
|
||||
</div>
|
||||
<div className="woocommerce-product-publish-panel__footer">
|
||||
<ShowPrepublishChecksSection />
|
||||
|
|
|
@ -7,23 +7,16 @@
|
|||
overflow: auto;
|
||||
position: fixed;
|
||||
background: $white;
|
||||
transform: translateX(+100%);
|
||||
animation: product-publish-panel__slide-in-animation 0.1s forwards;
|
||||
width: 100%;
|
||||
|
||||
@include break-medium() {
|
||||
top: $admin-bar-height;
|
||||
top: 0;
|
||||
width: $sidebar-width - $border-width;
|
||||
@include reduce-motion("animation");
|
||||
|
||||
body.is-fullscreen-mode & {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
// Keep it open on focus to avoid conflict with navigate-regions animation.
|
||||
[role="region"]:focus & {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
|
@ -32,6 +25,7 @@
|
|||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 $grid-unit-20;
|
||||
justify-content: right;
|
||||
|
||||
> button {
|
||||
flex: 1;
|
||||
|
@ -41,6 +35,11 @@
|
|||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.woocommerce-publish-panel-close {
|
||||
flex: unset;
|
||||
width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
|
@ -53,22 +52,77 @@
|
|||
}
|
||||
|
||||
&__content {
|
||||
// Ensure the pre-publish panel accounts for the header and footer height.
|
||||
min-height: calc( 100% - #{ $header-height + 200px } );
|
||||
min-height: calc( 100% - #{ $header-height + 220px } );
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding: 0 $grid-unit-20;
|
||||
padding: $gap $grid-unit-20 $gap-largest $grid-unit-20;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
min-height: $gap-largest + $gap-large;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.components-base-control__field {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.post-publish-section {
|
||||
&__postpublish-subheader {
|
||||
margin: 0 0 $gap-small;
|
||||
}
|
||||
&__postpublish-post-address-container {
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
margin-bottom: $gap-small;
|
||||
gap: $gap;
|
||||
}
|
||||
&__postpublish-post-address {
|
||||
flex: 1;
|
||||
}
|
||||
&__copy-button-wrap {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: $gap-smaller;
|
||||
}
|
||||
&__postpublish-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: $gap-small;
|
||||
.components-button {
|
||||
padding: $gap-smaller;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.is-published {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.woocommerce-product-publish-panel {
|
||||
&__header, &__title {
|
||||
border-bottom: $border-width solid $gray-200;
|
||||
font-weight: 500;
|
||||
}
|
||||
&__content {
|
||||
min-height: 180px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.interface-interface-skeleton__actions {
|
||||
transform: translateX(+100%);
|
||||
animation: product-publish-panel__slide-in-animation 0.1s forwards;
|
||||
@include break-medium() {
|
||||
// Keep it open on focus to avoid conflict with navigate-regions animation.
|
||||
[role="region"]:focus & {
|
||||
transform: translateX(0%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes product-publish-panel__slide-in-animation {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useEntityProp } from '@wordpress/core-data';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export function useProductURL( productType: string ) {
|
||||
const [ permalink ] = useEntityProp< string >(
|
||||
'postType',
|
||||
productType,
|
||||
'permalink'
|
||||
);
|
||||
const getProductURL = useCallback(
|
||||
( isPreview: boolean ) => {
|
||||
const productURL = new URL( permalink ) as URL | undefined;
|
||||
if ( isPreview ) {
|
||||
productURL?.searchParams.append( 'preview', 'true' );
|
||||
}
|
||||
return productURL?.toString() || '';
|
||||
},
|
||||
[ permalink ]
|
||||
);
|
||||
return { getProductURL };
|
||||
}
|
Loading…
Reference in New Issue