* Add empty 'Customer Account' block

* Add edit to render the block on the editor

* Add Customer Account icon to @woocommerce/icons library

This icon is used in the Customer Account block.

* Use customerAccount icon in the Customer Account block

* Add Block Settings to Customer Account block

Add Typography and Color settings to the Customer Account block that can be used during Edit mode.

* Add Customer Account style icons

This adds the customer account style icons to be used by the customer account block.

* Render the block in the frontend

* Add Display setting to the Customer Account block

This setting allow users to choose if they want to include an icon with the customer account link.

* Add icon style selector to Display settings

This adds an icon style selector in which users can choose the customer account link icon.

* Display on the frontend depending on the icon style

* Rename attributes

* Refactor block rendering

* Improve styling

* Make the account link dynamic

* Add alignment menu

* Get dashboardUrl with getSettings and add fallback

* Add link to account settings

* Add tests

* Fix test

* Change display title

* Split css file

* Switch to SSR

* Add styles to the front end

* Address latest feedback

* Remove experimental flag

Co-authored-by: Alexandre Lara <allexandrelara@gmail.com>
This commit is contained in:
Alba Rincón 2022-12-22 16:01:01 +01:00 committed by GitHub
parent d6b3b26c01
commit a223af0ad5
18 changed files with 616 additions and 0 deletions

View File

@ -0,0 +1,31 @@
{
"name": "woocommerce/customer-account",
"version": "1.0.0",
"title": "Customer account",
"description": "A block that allows your customers to log in and out of their accounts in your store.",
"category": "woocommerce",
"keywords": [ "WooCommerce" ],
"supports": {
"align": true,
"color": {
"text": true
},
"typography": {
"fontSize": true,
"__experimentalFontFamily": true
}
},
"attributes": {
"displayStyle": {
"type": "string",
"default": "icon_and_text"
},
"iconStyle": {
"type": "string",
"default": "default"
}
},
"textdomain": "woo-gutenberg-products-block",
"apiVersion": 2,
"$schema": "https://schemas.wp.org/trunk/block.json"
}

View File

@ -0,0 +1,73 @@
/**
* External dependencies
*/
import { Icon } from '@wordpress/icons';
import {
customerAccountStyle,
customerAccountStyleAlt,
} from '@woocommerce/icons';
import { getSetting } from '@woocommerce/settings';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import { Attributes, DisplayStyle, IconStyle } from './types';
const AccountIcon = ( {
iconStyle,
displayStyle,
}: {
iconStyle: IconStyle;
displayStyle: DisplayStyle;
} ) => {
const icon =
iconStyle === IconStyle.ALT
? customerAccountStyleAlt
: customerAccountStyle;
return displayStyle === DisplayStyle.TEXT_ONLY ? null : (
<Icon className="icon" icon={ icon } size={ 16 } />
);
};
const Label = ( { displayStyle }: { displayStyle: DisplayStyle } ) => {
if ( displayStyle === DisplayStyle.ICON_ONLY ) {
return null;
}
const currentUserId = getSetting( 'currentUserId', null );
return (
<span className="label">
{ currentUserId
? __( 'My Account', 'woo-gutenberg-products-block' )
: __( 'Log in', 'woo-gutenberg-products-block' ) }
</span>
);
};
export const CustomerAccountBlock = ( {
attributes,
}: {
attributes: Attributes;
} ): JSX.Element => {
const { displayStyle, iconStyle } = attributes;
return (
<a
href={ getSetting(
'dashboardUrl',
getSetting( 'wpLoginUrl', '/wp-login.php' )
) }
>
<AccountIcon
iconStyle={ iconStyle }
displayStyle={ displayStyle }
/>
<Label displayStyle={ displayStyle } />
</a>
);
};
export default CustomerAccountBlock;

View File

@ -0,0 +1,43 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { Disabled } from '@wordpress/components';
import type { BlockEditProps } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import Block from './block';
import { Attributes } from './types';
import { BlockSettings } from './sidebar-settings';
import './editor.scss';
const Edit = ( {
attributes,
setAttributes,
}: BlockEditProps< Attributes > ) => {
const { className } = attributes;
const blockProps = useBlockProps( {
className: classNames( 'wc-block-customer-account', className ),
} );
return (
<>
<div { ...blockProps }>
<InspectorControls>
<BlockSettings
attributes={ attributes }
setAttributes={ setAttributes }
/>
</InspectorControls>
<Disabled>
<Block attributes={ attributes } />
</Disabled>
</div>
</>
);
};
export default Edit;

View File

@ -0,0 +1,7 @@
.wc-block-customer-account__icon-style-toggle {
width: 100%;
}
.account-link {
padding: 0 $gap $gap 52px;
}

View File

@ -0,0 +1,30 @@
/**
* External dependencies
*/
import { registerBlockType } from '@wordpress/blocks';
import { Icon } from '@wordpress/icons';
import { customerAccount } from '@woocommerce/icons';
/**
* Internal dependencies
*/
import metadata from './block.json';
import edit from './edit';
registerBlockType( metadata, {
icon: {
src: (
<Icon
icon={ customerAccount }
className="wc-block-editor-components-block-icon"
/>
),
},
attributes: {
...metadata.attributes,
},
edit,
save() {
return null;
},
} );

View File

@ -0,0 +1,160 @@
/**
* External dependencies
*/
import classNames from 'classnames';
import { Icon } from '@wordpress/icons';
import {
customerAccountStyle,
customerAccountStyleAlt,
} from '@woocommerce/icons';
import { InspectorControls } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';
import type { BlockAttributes } from '@wordpress/blocks';
import { getSetting } from '@woocommerce/settings';
import { createInterpolateElement } from '@wordpress/element';
import {
PanelBody,
SelectControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControl as ToggleGroupControl,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalToggleGroupControlOption as ToggleGroupControlOption,
ExternalLink,
} from '@wordpress/components';
/**
* Internal dependencies
*/
import { DisplayStyle, IconStyle } from './types';
interface BlockSettingsProps {
attributes: BlockAttributes;
setAttributes: ( attrs: BlockAttributes ) => void;
}
const AccountSettingsLink = () => {
const accountSettingsUrl = `${ getSetting(
'adminUrl'
) }admin.php?page=wc-settings&tab=account`;
const linkText = createInterpolateElement(
`<a>${ __(
'Manage account settings',
'woo-gutenberg-products-block'
) }</a>`,
{
a: <ExternalLink href={ accountSettingsUrl } />,
}
);
return <div className="account-link">{ linkText }</div>;
};
export const BlockSettings = ( {
attributes,
setAttributes,
}: BlockSettingsProps ) => {
const { displayStyle, iconStyle } = attributes;
const displayIconStyleSelector = [
DisplayStyle.ICON_ONLY,
DisplayStyle.ICON_AND_TEXT,
].includes( displayStyle );
return (
<InspectorControls key="inspector">
<AccountSettingsLink />
<PanelBody
title={ __(
'Display settings',
'woo-gutenberg-products-block'
) }
>
<SelectControl
className="customer-account-display-style"
label={ __(
'Icon options',
'woo-gutenberg-products-block'
) }
value={ displayStyle }
onChange={ ( value: DisplayStyle ) => {
setAttributes( { displayStyle: value } );
} }
help={ __(
'Choose if you want to include an icon with the customer account link.',
'woo-gutenberg-products-block'
) }
options={ [
{
value: DisplayStyle.ICON_AND_TEXT,
label: __(
'Icon and text',
'woo-gutenberg-products-block'
),
},
{
value: DisplayStyle.TEXT_ONLY,
label: __(
'Text-only',
'woo-gutenberg-products-block'
),
},
{
value: DisplayStyle.ICON_ONLY,
label: __(
'Icon-only',
'woo-gutenberg-products-block'
),
},
] }
/>
{ displayIconStyleSelector ? (
<ToggleGroupControl
label={ __(
'Display Style',
'woo-gutenberg-products-block'
) }
value={ iconStyle }
onChange={ ( value: IconStyle ) =>
setAttributes( {
iconStyle: value,
} )
}
className="wc-block-customer-account__icon-style-toggle"
>
<ToggleGroupControlOption
value={ IconStyle.DEFAULT }
label={
<Icon
icon={ customerAccountStyle }
size={ 16 }
className={ classNames(
'wc-block-customer-account__icon-option',
{
active:
iconStyle === IconStyle.DEFAULT,
}
) }
/>
}
/>
<ToggleGroupControlOption
value={ IconStyle.ALT }
label={
<Icon
icon={ customerAccountStyleAlt }
size={ 18 }
className={ classNames(
'wc-block-customer-account__icon-option',
{
active: iconStyle === IconStyle.ALT,
}
) }
/>
}
/>
</ToggleGroupControl>
) : null }
</PanelBody>
</InspectorControls>
);
};

View File

@ -0,0 +1,16 @@
.wp-block-woocommerce-customer-account {
a {
text-decoration: none !important;
align-items: center;
display: flex;
color: currentColor !important;
&:hover {
text-decoration: underline !important;
}
.icon + .label {
margin-left: $gap-smaller;
}
}
}

View File

@ -0,0 +1,16 @@
export interface Attributes {
className?: string;
displayStyle: DisplayStyle;
iconStyle: IconStyle;
}
export enum DisplayStyle {
ICON_AND_TEXT = 'icon_and_text',
TEXT_ONLY = 'text_only',
ICON_ONLY = 'icon_only',
}
export enum IconStyle {
DEFAULT = 'default',
ALT = 'alt',
}

View File

@ -13,3 +13,6 @@ export { default as woo } from './library/woo';
export { default as miniCart } from './library/mini-cart';
export { default as miniCartAlt } from './library/mini-cart-alt';
export { default as stacks } from './library/stacks';
export { default as customerAccount } from './library/customer-account';
export { default as customerAccountStyle } from './library/customer-account-style';
export { default as customerAccountStyleAlt } from './library/customer-account-style-alt';

View File

@ -0,0 +1,15 @@
/**
* External dependencies
*/
import { SVG } from '@wordpress/primitives';
const customerAccountStyleAlt = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
<path
d="M9 0C4.03579 0 0 4.03579 0 9C0 13.9642 4.03579 18 9 18C13.9642 18 18 13.9642 18 9C18 4.03579 13.9642 0 9 0ZM9 4.32C10.5347 4.32 11.7664 5.57056 11.7664 7.08638C11.7664 8.62109 10.5158 9.85277 9 9.85277C7.4653 9.85277 6.23362 8.60221 6.23362 7.08638C6.23362 5.57056 7.46526 4.32 9 4.32ZM9 10.7242C11.1221 10.7242 12.96 12.2021 13.7937 14.4189C12.5242 15.5559 10.8379 16.238 9 16.238C7.16207 16.238 5.49474 15.5369 4.20632 14.4189C5.05891 12.2021 6.87793 10.7242 9 10.7242Z"
fill="currentColor"
/>
</SVG>
);
export default customerAccountStyleAlt;

View File

@ -0,0 +1,17 @@
/**
* External dependencies
*/
import { SVG } from '@wordpress/primitives';
const customerAccountStyle = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.00009 8.34785C10.3096 8.34785 12.1819 6.47909 12.1819 4.17393C12.1819 1.86876 10.3096 0 8.00009 0C5.69055 0 3.81824 1.86876 3.81824 4.17393C3.81824 6.47909 5.69055 8.34785 8.00009 8.34785ZM0.333496 15.6522C0.333496 15.8444 0.489412 16 0.681933 16H15.3184C15.5109 16 15.6668 15.8444 15.6668 15.6522V14.9565C15.6668 12.1428 13.7821 9.73911 10.0912 9.73911H5.90931C2.21828 9.73911 0.333645 12.1428 0.333645 14.9565L0.333496 15.6522Z"
fill="currentColor"
/>
</SVG>
);
export default customerAccountStyle;

View File

@ -0,0 +1,18 @@
/**
* External dependencies
*/
import { SVG } from '@wordpress/primitives';
const customerAccount = (
<SVG xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
id="icon/action/account_circle_24px_2"
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM7.07 18.28C7.5 17.38 10.12 16.5 12 16.5C13.88 16.5 16.51 17.38 16.93 18.28C15.57 19.36 13.86 20 12 20C10.14 20 8.43 19.36 7.07 18.28ZM12 14.5C13.46 14.5 16.93 15.09 18.36 16.83C19.38 15.49 20 13.82 20 12C20 7.59 16.41 4 12 4C7.59 4 4 7.59 4 12C4 13.82 4.62 15.49 5.64 16.83C7.07 15.09 10.54 14.5 12 14.5ZM12 6C10.06 6 8.5 7.56 8.5 9.5C8.5 11.44 10.06 13 12 13C13.94 13 15.5 11.44 15.5 9.5C15.5 7.56 13.94 6 12 6ZM10.5 9.5C10.5 10.33 11.17 11 12 11C12.83 11 13.5 10.33 13.5 9.5C13.5 8.67 12.83 8 12 8C11.17 8 10.5 8.67 10.5 9.5Z"
/>
</SVG>
);
export default customerAccount;

View File

@ -39,6 +39,7 @@ export interface WooCommerceSharedSettings {
adminUrl: string;
countries: Record< string, string > | never[];
currency: WooCommerceSiteCurrency;
currentUserId: number;
currentUserIsAdmin: boolean;
homeUrl: string;
locale: WooCommerceSiteLocale;
@ -64,6 +65,7 @@ const defaults: WooCommerceSharedSettings = {
priceFormat: '%1$s%2$s',
thousandSeparator: ',',
},
currentUserId: 0,
currentUserIsAdmin: false,
homeUrl: '',
locale: {

View File

@ -62,6 +62,7 @@ const blocks = {
isExperimental: true,
},
'filter-wrapper': {},
'customer-account': {},
};
// Returns the entries for each block given a relative path (ie: `index.js`,

View File

@ -84,9 +84,11 @@ class AssetDataRegistry {
'adminUrl' => admin_url(),
'countries' => WC()->countries->get_countries(),
'currency' => $this->get_currency_data(),
'currentUserId' => get_current_user_id(),
'currentUserIsAdmin' => current_user_can( 'manage_woocommerce' ),
'homeUrl' => esc_url( home_url( '/' ) ),
'locale' => $this->get_locale_data(),
'dashboardUrl' => wc_get_account_endpoint_url( 'dashboard' ),
'orderStatuses' => $this->get_order_statuses(),
'placeholderImgSrc' => wc_placeholder_img_src(),
'productsSettings' => $this->get_products_settings(),

View File

@ -0,0 +1,109 @@
<?php
namespace Automattic\WooCommerce\Blocks\BlockTypes;
use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils;
/**
* CustomerAccount class.
*/
class CustomerAccount extends AbstractBlock {
const TEXT_ONLY = 'text_only';
const ICON_ONLY = 'icon_only';
const DISPLAY_ALT = 'alt';
/**
* Block name.
*
* @var string
*/
protected $block_name = 'customer-account';
/**
* Render the block.
*
* @param array $attributes Block attributes.
* @param string $content Block content.
* @param WP_Block $block Block instance.
*
* @return string Rendered block output.
*/
protected function render( $attributes, $content, $block ) {
$classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes );
$account_link = get_option( 'woocommerce_myaccount_page_id' ) ? wc_get_account_endpoint_url( 'dashboard' ) : wp_login_url();
$allowed_svg = array(
'svg' => array(
'class' => true,
'xmlns' => true,
'width' => true,
'height' => true,
'viewbox' => true,
),
'path' => array(
'd' => true,
'fill' => true,
),
);
return "<div class='wp-block-woocommerce-customer-account " . esc_attr( $classes_and_styles['classes'] ) . "' style='" . esc_attr( $classes_and_styles['styles'] ) . "'>
<a href='" . esc_attr( $account_link ) . "'>
" . wp_kses( $this->render_icon( $attributes ), $allowed_svg ) . "<span class='label'>" . wp_kses( $this->render_label( $attributes ), array() ) . '</span>
</a>
</div>';
}
/**
* Gets the icon to render depending on the iconStyle and displayStyle.
*
* @param array $attributes Block attributes.
*
* @return string Label to render on the block
*/
private function render_icon( $attributes ) {
if ( self::TEXT_ONLY === $attributes['displayStyle'] ) {
return '';
}
if ( self::DISPLAY_ALT === $attributes['iconStyle'] ) {
return '<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18" width="16" height="16">
<path
d="M9 0C4.03579 0 0 4.03579 0 9C0 13.9642 4.03579 18 9 18C13.9642 18 18 13.9642 18 9C18 4.03579 13.9642 0 9
0ZM9 4.32C10.5347 4.32 11.7664 5.57056 11.7664 7.08638C11.7664 8.62109 10.5158 9.85277 9 9.85277C7.4653
9.85277 6.23362 8.60221 6.23362 7.08638C6.23362 5.57056 7.46526 4.32 9 4.32ZM9 10.7242C11.1221 10.7242
12.96 12.2021 13.7937 14.4189C12.5242 15.5559 10.8379 16.238 9 16.238C7.16207 16.238 5.49474 15.5369
4.20632 14.4189C5.05891 12.2021 6.87793 10.7242 9 10.7242Z"
fill="currentColor"
/>
</svg>';
}
return '<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<path
d="M8.00009 8.34785C10.3096 8.34785 12.1819 6.47909 12.1819 4.17393C12.1819 1.86876 10.3096 0 8.00009 0C5.69055
0 3.81824 1.86876 3.81824 4.17393C3.81824 6.47909 5.69055 8.34785 8.00009 8.34785ZM0.333496 15.6522C0.333496
15.8444 0.489412 16 0.681933 16H15.3184C15.5109 16 15.6668 15.8444 15.6668 15.6522V14.9565C15.6668 12.1428
13.7821 9.73911 10.0912 9.73911H5.90931C2.21828 9.73911 0.333645 12.1428 0.333645 14.9565L0.333496 15.6522Z"
fill="currentColor"
/>
</svg>';
}
/**
* Gets the label to render depending on the displayStyle.
*
* @param array $attributes Block attributes.
*
* @return string Label to render on the block
*/
private function render_label( $attributes ) {
if ( self::ICON_ONLY === $attributes['displayStyle'] ) {
return '';
}
return get_current_user_id()
? __( 'My Account', 'woo-gutenberg-products-block' )
: __( 'Login', 'woo-gutenberg-products-block' );
}
}

View File

@ -202,6 +202,7 @@ final class BlockTypesController {
'MiniCartContents',
'ProductQuery',
'FilterWrapper',
'CustomerAccount',
];
$block_types = array_merge( $block_types, Cart::get_cart_block_types(), Checkout::get_checkout_block_types() );

View File

@ -0,0 +1,72 @@
/**
* External dependencies
*/
import {
switchUserToAdmin,
openDocumentSettingsSidebar,
} from '@wordpress/e2e-test-utils';
import { visitBlockPage } from '@woocommerce/blocks-test-utils';
const block = {
name: 'Customer account',
slug: 'woocommerce/customer-account',
class: '.wc-block-customer-account',
};
const SELECTORS = {
icon: '.wp-block-woocommerce-customer-account svg',
label: '.wp-block-woocommerce-customer-account .label',
iconToggle: '.wc-block-customer-account__icon-style-toggle',
displayDropdown: '.customer-account-display-style select',
};
describe( `${ block.name } Block`, () => {
beforeAll( async () => {
await switchUserToAdmin();
await visitBlockPage( `${ block.name } Block` );
} );
it( 'renders without crashing', async () => {
await expect( page ).toRenderBlock( block );
} );
describe( 'attributes', () => {
beforeEach( async () => {
await openDocumentSettingsSidebar();
await page.click( block.class );
} );
it( 'icon options can be set to Text-only', async () => {
await expect( page ).toSelect(
SELECTORS.displayDropdown,
'Text-only'
);
await expect( page ).not.toMatchElement( SELECTORS.iconToggle );
await expect( page ).not.toMatchElement( SELECTORS.icon );
await expect( page ).toMatchElement( SELECTORS.label );
} );
it( 'icon options can be set to Icon-only', async () => {
await expect( page ).toSelect(
SELECTORS.displayDropdown,
'Icon-only'
);
await expect( page ).toMatchElement( SELECTORS.iconToggle );
await expect( page ).toMatchElement( SELECTORS.icon );
await expect( page ).not.toMatchElement( SELECTORS.label );
} );
it( 'icon options can be set to Icon and text', async () => {
await expect( page ).toSelect(
SELECTORS.displayDropdown,
'Icon and text'
);
await expect( page ).toMatchElement( SELECTORS.iconToggle );
await expect( page ).toMatchElement( SELECTORS.icon );
await expect( page ).toMatchElement( SELECTORS.label );
} );
} );
} );