Implement "return to cart" and "place order" buttons on checkout (https://github.com/woocommerce/woocommerce-blocks/pull/1926)

* Add back icon

* Add cart URL constant

* Add button components

* Implement button components into checkout

* Update checkout styles to match mockup incl updates to margins and padding

* Add options to control return to cart link

* Use checkout context

* Update snapshot

* Update context

* href

* Color/arrow styling

* Implement select instead of open URL field

* Add notice and updated settings control

* Show notice conditonally

* Store permalinks to avoid extra API requests, and get pages via API

* Update snapshots

* Fix double layout conflict

* Switch back to ID and add permalink via block setting

* snaps

* Fix snapshot; add default shape for pages

* Feedback

* Better undefined handling

* Update assets/js/blocks/cart-checkout/checkout/block.js

Co-Authored-By: Darren Ethier <darren@roughsmootheng.in>

Co-authored-by: Darren Ethier <darren@roughsmootheng.in>
This commit is contained in:
Mike Jolley 2020-03-16 16:38:24 +00:00 committed by GitHub
parent 3e7a2d41e4
commit 01602f90bf
25 changed files with 490 additions and 235 deletions

View File

@ -6,3 +6,5 @@ export { default as ProductName } from './product-name';
export { default as ProductPrice } from './product-price';
export { default as ProductSaleBadge } from './product-sale-badge';
export { default as ProductVariationData } from './product-variation-data';
export { default as PlaceOrderButton } from './place-order-button';
export { default as ReturnToCartButton } from './return-to-cart-button';

View File

@ -0,0 +1,24 @@
/**
* External dependencies
*/
import { useCheckoutContext } from '@woocommerce/base-context';
/**
* Internal dependencies
*/
import Button from '../button';
const PlaceOrderButton = () => {
const { submitLabel, onSubmit } = useCheckoutContext();
return (
<Button
className="wc-block-components-checkout-place-order-button"
onClick={ onSubmit }
>
{ submitLabel }
</Button>
);
};
export default PlaceOrderButton;

View File

@ -0,0 +1,25 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { CART_URL } from '@woocommerce/block-settings';
import { Icon, arrowBack } from '@woocommerce/icons';
/**
* Internal dependencies
*/
import './style.scss';
const ReturnToCartButton = ( { link } ) => {
return (
<a
href={ link || CART_URL }
className="wc-block-components-checkout-return-to-cart-button"
>
<Icon srcElement={ arrowBack } />
{ __( 'Return to Cart', 'woo-gutenberg-products-block' ) }
</a>
);
};
export default ReturnToCartButton;

View File

@ -0,0 +1,10 @@
.wc-block-components-checkout-return-to-cart-button {
color: $gray-60;
text-decoration: none !important;
svg {
vertical-align: middle;
margin-right: 0.25em;
display: inline-block;
}
}

View File

@ -8,7 +8,7 @@ $line-offset-from-circle-size: 8px;
.wc-block-checkout-form fieldset.wc-block-checkout-step {
position: relative;
border: none;
padding: 0 0 $gap-larger $gap-large;
padding: 0 $gap-larger $gap-larger $gap-larger;
background: none;
margin: 0;
@ -63,7 +63,7 @@ $line-offset-from-circle-size: 8px;
width: 1px;
background-color: $gray-10;
position: absolute;
left: 0;
left: $circle-size/2;
top: $circle-size + $line-offset-from-circle-size;
}
@ -73,7 +73,7 @@ $line-offset-from-circle-size: 8px;
position: absolute;
width: $circle-size;
height: $circle-size;
left: -$circle-size / 2;
left: 0;
top: 0;
background: $gray-20;
color: $white;

View File

@ -1,12 +1,3 @@
.wc-block-checkout-form {
margin-right: $gap-large;
max-width: 100%;
}
// Responsive media styles.
@include breakpoint( "<480px" ) {
.wc-block-checkout-form {
margin-left: $gap-smaller;
margin-right: $gap;
}
}

View File

@ -3,7 +3,7 @@
text-align: center;
list-style: none outside;
line-height: 1;
margin: 2em 0 1em;
margin: $gap-large 0;
}
.wc-block-components-checkout-policies__item {
list-style: none outside;
@ -18,5 +18,6 @@
a {
padding: 0 0.25em;
text-decoration: underline;
color: $gray-60;
}
}

View File

@ -2,7 +2,7 @@
margin: auto;
border: 2px solid $black;
border-radius: 5px;
padding: 10px;
padding: 8px;
.wc-block-component-express-checkout__title {
position: relative;
@ -50,7 +50,7 @@
display: flex;
align-items: center;
text-align: center;
padding: 0 ($gap-large+14px);
padding: 0 $gap-larger;
margin: $gap-large 0;
&::before {

View File

@ -18,13 +18,10 @@ import { EditorProvider } from '@woocommerce/base-context';
import FullCart from './full-cart';
import EmptyCart from './empty-cart';
/**
* Component to handle edit mode of "Cart Block".
*/
const CartEditor = ( { className, attributes, setAttributes } ) => {
const BlockSettings = ( { attributes, setAttributes } ) => {
const { isShippingCalculatorEnabled, isShippingCostHidden } = attributes;
const BlockSettings = () => (
return (
<InspectorControls>
<PanelBody
title={ __( 'Shipping rates', 'woo-gutenberg-products-block' ) }
@ -64,7 +61,13 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
</PanelBody>
</InspectorControls>
);
};
/**
* Component to handle edit mode of "Cart Block".
*/
const CartEditor = ( { className, attributes, setAttributes } ) => {
const { isShippingCalculatorEnabled, isShippingCostHidden } = attributes;
return (
<div className={ className }>
<ViewSwitcher
@ -87,7 +90,12 @@ const CartEditor = ( { className, attributes, setAttributes } ) => {
<>
{ currentView === 'full' && (
<>
{ SHIPPING_ENABLED && <BlockSettings /> }
{ SHIPPING_ENABLED && (
<BlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
/>
) }
<BlockErrorBoundary
header={ __(
'Cart Block Error',

View File

@ -62,13 +62,16 @@ const getProps = ( el ) => {
Object.keys( blockAttributes ).forEach( ( key ) => {
if ( typeof el.dataset[ key ] !== 'undefined' ) {
if (
el.dataset[ key ] === 'true' ||
el.dataset[ key ] === 'false'
) {
switch ( blockAttributes[ key ].type ) {
case 'boolean':
attributes[ key ] = el.dataset[ key ] !== 'false';
} else {
break;
case 'number':
attributes[ key ] = parseInt( el.dataset[ key ], 10 );
break;
default:
attributes[ key ] = el.dataset[ key ];
break;
}
} else {
attributes[ key ] = blockAttributes[ key ].default;

View File

@ -32,6 +32,14 @@ const blockAttributes = {
type: 'boolean',
default: true,
},
showReturnToCart: {
type: 'boolean',
default: true,
},
cartPageId: {
type: 'number',
default: 0,
},
};
export default blockAttributes;

View File

@ -11,6 +11,10 @@ import {
NoShipping,
Policies,
} from '@woocommerce/base-components/checkout';
import {
PlaceOrderButton,
ReturnToCartButton,
} from '@woocommerce/base-components/cart-checkout';
import TextInput from '@woocommerce/base-components/text-input';
import ShippingRatesControl from '@woocommerce/base-components/shipping-rates-control';
import CheckboxControl from '@woocommerce/base-components/checkbox-control';
@ -29,6 +33,7 @@ import {
SidebarLayout,
Main,
} from '@woocommerce/base-components/sidebar-layout';
import { getSetting } from '@woocommerce/settings';
/**
* Internal dependencies
@ -324,6 +329,20 @@ const Block = ( {
}
/>
</FormStep>
<div className="wc-block-checkout__actions">
{ attributes.showReturnToCart && (
<ReturnToCartButton
link={
getSetting(
'page-' +
attributes?.cartPageId,
false
)
}
/>
) }
<PlaceOrderButton />
</div>
{ attributes.showPolicyLinks && <Policies /> }
</CheckoutForm>
</Main>

View File

@ -2,7 +2,7 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { withFeedbackPrompt } from '@woocommerce/block-hocs';
import FeedbackPrompt from '@woocommerce/block-components/feedback-prompt';
import {
previewCart,
previewShippingRates,
@ -12,6 +12,7 @@ import {
PanelBody,
ToggleControl,
CheckboxControl,
SelectControl,
Notice,
} from '@wordpress/components';
import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary';
@ -19,9 +20,12 @@ import {
PRIVACY_URL,
TERMS_URL,
SHIPPING_METHODS_EXIST,
CHECKOUT_PAGE_ID,
} from '@woocommerce/block-settings';
import { useSelect } from '@wordpress/data';
import { getAdminLink } from '@woocommerce/settings';
import { __experimentalCreateInterpolateElement } from 'wordpress-element';
import { EditorProvider, useEditorContext } from '@woocommerce/base-context';
/**
* Internal dependencies
@ -29,9 +33,8 @@ import { __experimentalCreateInterpolateElement } from 'wordpress-element';
import Block from './block.js';
import './editor.scss';
const CheckoutEditor = ( { attributes, setAttributes } ) => {
const BlockSettings = ( { attributes, setAttributes } ) => {
const {
className,
useShippingAsBilling,
showCompanyField,
showAddress2Field,
@ -39,15 +42,50 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
requireCompanyField,
requirePhoneField,
showPolicyLinks,
showReturnToCart,
cartPageId,
} = attributes;
const { currentPostId } = useEditorContext();
const pages =
useSelect( ( select ) => {
return select( 'core' ).getEntityRecords( 'postType', 'page', {
status: 'publish',
orderby: 'title',
order: 'asc',
per_page: 100,
} );
}, [] ) || null;
return (
<div className={ className }>
<InspectorControls>
<PanelBody
title={ __(
'Form options',
{ currentPostId !== CHECKOUT_PAGE_ID && (
<Notice
className="wc-block-checkout__page-notice"
isDismissible={ false }
status="warning"
>
{ __experimentalCreateInterpolateElement(
__(
'If you would like to use this block as your default checkout you must update your <a>page settings in WooCommerce</a>.',
'woo-gutenberg-products-block'
),
{
a: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href={ getAdminLink(
'admin.php?page=wc-settings&tab=advanced'
) }
target="_blank"
rel="noopener noreferrer"
/>
),
}
) }
</Notice>
) }
<PanelBody
title={ __( 'Form options', 'woo-gutenberg-products-block' ) }
>
<p className="wc-block-checkout__controls-text">
{ __(
@ -56,10 +94,7 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
) }
</p>
<ToggleControl
label={ __(
'Company',
'woo-gutenberg-products-block'
) }
label={ __( 'Company', 'woo-gutenberg-products-block' ) }
checked={ showCompanyField }
onChange={ () =>
setAttributes( {
@ -149,13 +184,17 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
>
<p className="wc-block-checkout__controls-text">
{ __(
'Choose additional content to display on checkout.',
'Choose additional content to display.',
'woo-gutenberg-products-block'
) }
</p>
<ToggleControl
label={ __(
'Show links to terms and conditions and privacy policy',
'Show links to policies',
'woo-gutenberg-products-block'
) }
help={ __(
'Shows a list of links to your "terms and conditions" and "privacy policy" pages.',
'woo-gutenberg-products-block'
) }
checked={ showPolicyLinks }
@ -200,8 +239,71 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
) }
</Notice>
) }
<ToggleControl
label={ __(
'Show a "return to cart" link',
'woo-gutenberg-products-block'
) }
checked={ showReturnToCart }
onChange={ () =>
setAttributes( {
showReturnToCart: ! showReturnToCart,
} )
}
/>
{ showReturnToCart &&
( currentPostId !== CHECKOUT_PAGE_ID || cartPageId ) &&
pages && (
<SelectControl
label={ __(
'Link to',
'woo-gutenberg-products-block'
) }
value={ cartPageId }
options={ [
...[
{
label: __(
'WooCommerce Cart Page',
'woo-gutenberg-products-block'
),
value: '',
},
],
...Object.values( pages ).map( ( page ) => {
return {
label: page.title.raw,
value: parseInt( page.id, 10 ),
};
} ),
] }
onChange={ ( value ) =>
setAttributes( {
cartPageId: parseInt( value, 10 ),
} )
}
/>
) }
</PanelBody>
<FeedbackPrompt
text={ __(
'We are currently working on improving our cart and checkout blocks, providing merchants with the tools and customization options they need.',
'woo-gutenberg-products-block'
) }
/>
</InspectorControls>
);
};
const CheckoutEditor = ( { attributes, setAttributes } ) => {
const { className } = attributes;
return (
<EditorProvider>
<div className={ className }>
<BlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
/>
<BlockErrorBoundary
header={ __(
'Checkout Block Error',
@ -228,12 +330,8 @@ const CheckoutEditor = ( { attributes, setAttributes } ) => {
/>
</BlockErrorBoundary>
</div>
</EditorProvider>
);
};
export default withFeedbackPrompt(
__(
'We are currently working on improving our cart and checkout blocks, providing merchants with the tools and customization options they need.',
'woo-gutenberg-products-block'
)
)( CheckoutEditor );
export default CheckoutEditor;

View File

@ -13,3 +13,7 @@
padding-left: 52px;
margin-top: -12px;
}
.wc-block-checkout__page-notice {
margin: 0;
}

View File

@ -70,13 +70,16 @@ const getProps = ( el ) => {
Object.keys( blockAttributes ).forEach( ( key ) => {
if ( typeof el.dataset[ key ] !== 'undefined' ) {
if (
el.dataset[ key ] === 'true' ||
el.dataset[ key ] === 'false'
) {
switch ( blockAttributes[ key ].type ) {
case 'boolean':
attributes[ key ] = el.dataset[ key ] !== 'false';
} else {
break;
case 'number':
attributes[ key ] = parseInt( el.dataset[ key ], 10 );
break;
default:
attributes[ key ] = el.dataset[ key ];
break;
}
} else {
attributes[ key ] = blockAttributes[ key ].default;

View File

@ -83,6 +83,22 @@
}
}
.wc-block-checkout__actions {
display: flex;
justify-content: space-between;
align-items: center;
margin: $gap-large 0 $gap-large*2;
padding: 0 $gap-larger;
.wc-block-components-checkout-place-order-button {
width: 50%;
padding: 1em;
border-radius: 3px;
height: auto;
margin-left: auto;
}
}
@include breakpoint( ">480px" ) {
.wc-block-checkout__billing-fields,
.wc-block-checkout__shipping-fields {

View File

@ -4,11 +4,7 @@
import { Fragment } from '@wordpress/element';
import { InspectorControls } from '@wordpress/block-editor';
import { createHigherOrderComponent } from '@wordpress/compose';
/**
* Internal dependencies
*/
import FeedbackPrompt from './feedback-prompt.js';
import FeedbackPrompt from '@woocommerce/block-components/feedback-prompt';
/**
* Adds a feedback prompt with custom text to the editor sidebar.

View File

@ -1,5 +1,6 @@
export { default as Icon } from './icon';
export { default as arrowBack } from './library/arrow-back';
export { default as bill } from './library/bill';
export { default as card } from './library/card';
export { default as cart } from './library/cart';

View File

@ -0,0 +1,17 @@
/**
* External dependencies
*/
import { SVG } from 'wordpress-components';
const arrowBack = (
<SVG
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
>
<path d="M20 11H7.8l5.6-5.6L12 4l-8 8 8 8 1.4-1.4L7.8 13H20v-2z" />
</SVG>
);
export default arrowBack;

View File

@ -55,18 +55,27 @@ export const SHIPPING_METHODS_EXIST = getSetting(
);
const defaultPage = {
name: '',
url: '',
id: 0,
title: '',
permalink: '',
};
const storePages = getSetting( 'storePages', {
shop: defaultPage,
cart: defaultPage,
checkout: defaultPage,
privacy: defaultPage,
terms: defaultPage,
} );
export const SHOP_URL = storePages.shop.url;
export const CHECKOUT_URL = storePages.checkout.url;
export const PRIVACY_URL = storePages.privacy.url;
export const TERMS_URL = storePages.terms.url;
export const PRIVACY_PAGE_NAME = storePages.privacy.name;
export const TERMS_PAGE_NAME = storePages.terms.name;
export const SHOP_URL = storePages.shop.permalink;
export const CHECKOUT_PAGE_ID = storePages.checkout.id;
export const CHECKOUT_URL = storePages.checkout.permalink;
export const PRIVACY_URL = storePages.privacy.permalink;
export const PRIVACY_PAGE_NAME = storePages.privacy.title;
export const TERMS_URL = storePages.terms.permalink;
export const TERMS_PAGE_NAME = storePages.terms.title;
export const CART_PAGE_ID = storePages.cart.id;
export const CART_URL = storePages.cart.permalink;

View File

@ -103,6 +103,7 @@ class Assets {
$product_counts = wp_count_posts( 'product' );
$page_ids = [
'shop' => wc_get_page_id( 'shop' ),
'cart' => wc_get_page_id( 'cart' ),
'checkout' => wc_get_page_id( 'checkout' ),
'privacy' => wc_privacy_policy_page_id(),
'terms' => wc_terms_and_conditions_page_id(),
@ -142,27 +143,40 @@ class Assets {
],
'homeUrl' => esc_url( home_url( '/' ) ),
'storePages' => [
'shop' => $page_ids['shop'] ? [
'name' => get_the_title( $page_ids['shop'] ),
'url' => get_permalink( $page_ids['shop'] ),
] : false,
'checkout' => $page_ids['checkout'] ? [
'name' => get_the_title( $page_ids['checkout'] ),
'url' => get_permalink( $page_ids['checkout'] ),
] : false,
'privacy' => $page_ids['privacy'] ? [
'name' => get_the_title( $page_ids['privacy'] ),
'url' => get_permalink( $page_ids['privacy'] ),
] : false,
'terms' => $page_ids['terms'] ? [
'name' => get_the_title( $page_ids['terms'] ),
'url' => get_permalink( $page_ids['terms'] ),
] : false,
'shop' => self::format_page_resource( $page_ids['shop'] ),
'cart' => self::format_page_resource( $page_ids['cart'] ),
'checkout' => self::format_page_resource( $page_ids['checkout'] ),
'privacy' => self::format_page_resource( $page_ids['privacy'] ),
'terms' => self::format_page_resource( $page_ids['terms'] ),
],
]
);
}
/**
* Format a page object into a standard array of data.
*
* @param WP_Post|int $page Page object or ID.
* @return array
*/
protected static function format_page_resource( $page ) {
if ( is_numeric( $page ) ) {
$page = get_post( $page );
}
if ( ! $page ) {
return [
'id' => 0,
'title' => '',
'permalink' => false,
];
}
return [
'id' => $page->ID,
'title' => $page->post_title,
'permalink' => get_permalink( $page->ID ),
];
}
/**
* Get the file modified time as a cache buster if we're in dev mode.
*

View File

@ -50,6 +50,12 @@ class Checkout extends AbstractBlock {
$data_registry = Package::container()->get(
\Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry::class
);
if ( ! empty( $attributes['cartPageId'] ) && ! $data_registry->exists( 'page-' . $attributes['cartPageId'] ) ) {
$permalink = get_permalink( $attributes['cartPageId'] );
if ( $permalink ) {
$data_registry->add( 'page-' . $attributes['cartPageId'], get_permalink( $attributes['cartPageId'] ) );
}
}
if ( ! $data_registry->exists( 'allowedCountries' ) ) {
$data_registry->add( 'allowedCountries', WC()->countries->get_allowed_countries() );
}

View File

@ -2,6 +2,6 @@
exports[`Checkout Block can be created 1`] = `
"<!-- wp:woocommerce/checkout -->
<div class=\\"wp-block-woocommerce-checkout\\" data-use-shipping-as-billing=\\"true\\" data-show-company-field=\\"false\\" data-require-company-field=\\"false\\" data-show-address-2-field=\\"true\\" data-show-phone-field=\\"true\\" data-require-phone-field=\\"false\\" data-show-policy-links=\\"true\\">Checkout block coming soon to store near you</div>
<div class=\\"wp-block-woocommerce-checkout\\" data-use-shipping-as-billing=\\"true\\" data-show-company-field=\\"false\\" data-require-company-field=\\"false\\" data-show-address-2-field=\\"true\\" data-show-phone-field=\\"true\\" data-require-phone-field=\\"false\\" data-show-policy-links=\\"true\\" data-show-return-to-cart=\\"true\\" data-cart-page-id=\\"0\\">Checkout block coming soon to store near you</div>
<!-- /wp:woocommerce/checkout -->"
`;