Add/user menu component (#39383)

User Menu: Create component and add to header

- Create initial component design
- We may need to return to implement the helper connection URLs once those are in place.
- Some PHPCS errors still exist - this is to be expected for now.

# Conflicts:
#	plugins/woocommerce-admin/client/marketplace/stylesheets/_variables.scss

Co-authored-by: And Finally <andfinally@users.noreply.github.com>
Co-authored-by: raicem <unalancem@gmail.com>
This commit is contained in:
Kyle Nel 2023-08-03 10:30:26 +02:00 committed by And Finally
parent 55b2bb33ac
commit a807040208
8 changed files with 378 additions and 28 deletions

View File

@ -0,0 +1,61 @@
/**
* External dependencies
*/
import { useState } from '@wordpress/element';
import { Button, ButtonGroup, Modal } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
export interface HeaderAccountModalProps {
setIsModalOpen: ( value: boolean ) => void;
disconnectURL: string;
}
export default function HeaderAccountModal(
props: HeaderAccountModalProps
): JSX.Element {
const { setIsModalOpen, disconnectURL } = props;
const [ isBusy, setIsBusy ] = useState( false );
const toggleIsBusy = () => setIsBusy( ! isBusy );
const closeModal = () => setIsModalOpen( false );
return (
<Modal
title={ __( 'Are you sure?', 'woocommerce' ) }
onRequestClose={ closeModal }
focusOnMount={ true }
className="woocommerce-marketplace__header-account-modal"
style={ { borderRadius: 4 } }
overlayClassName="woocommerce-marketplace__header-account-modal-overlay"
>
<p className="woocommerce-marketplace__header-account-modal-text">
{ __(
'Keep your your account connected to manage your subscriptions, get updates and support for your extensions and themes.',
'woocommerce'
) }
</p>
<ButtonGroup className="woocommerce-marketplace__header-account-modal-button-group">
<Button
variant="tertiary"
href={ disconnectURL }
onClick={ toggleIsBusy }
isBusy={ isBusy }
isDestructive={ true }
className="woocommerce-marketplace__header-account-modal-button"
>
{ __( 'Disconnect account', 'woocommerce' ) }
</Button>
<Button
variant="primary"
onClick={ closeModal }
className="woocommerce-marketplace__header-account-modal-button"
>
{ __( 'Keep connected', 'woocommerce' ) }
</Button>
</ButtonGroup>
</Modal>
);
}

View File

@ -0,0 +1,62 @@
@import '../../stylesheets/_variables.scss';
.woocommerce-marketplace {
&__menu-item span {
white-space: normal;
}
&__menu-avatar-image {
border-radius: 50%;
height: $grid-unit-30;
width: $grid-unit-30;
}
&__menu-icon {
flex-shrink: 0;
margin-right: $grid-unit-10;
}
&__menu-text {
display: flex;
flex-direction: column;
}
&__sub-text {
color: $gray-700;
font-size: 12px;
}
&__header-account-modal {
&__header-account-modal-text {
margin-bottom: $grid-unit-10;
}
}
&__header-account-modal-overlay {
// This is to ensure the modal is above the user menu popover.
z-index: 1000000;
}
&__header-account-modal-button-group {
display: inline-flex;
gap: $grid-unit-10;
justify-content: flex-end;
margin-top: $grid-unit-30;
width: 100%;
.woocommerce-marketplace__header-account-modal-button,
.woocommerce-marketplace__header-account-modal-button.is-primary {
border-radius: 2px;
box-shadow: none;
}
}
}
@media screen and (min-width: $break-small) {
.woocommerce-marketplace {
&__header-account-modal {
max-width: 350px;
}
}
}

View File

@ -1,32 +1,143 @@
/**
* External dependencies
*/
import { useState } from '@wordpress/element';
import { DropdownMenu, MenuGroup, MenuItem } from '@wordpress/components';
import { Icon, commentAuthorAvatar, external, linkOff } from '@wordpress/icons';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import './header-account.scss';
import { getAdminSetting } from '../../../utils/admin-settings';
import HeaderAccountModal from './header-account-modal';
export default function HeaderAccount() {
return (
<a
className="woocommerce-marketplace__header-account"
href="https://woocommerce.com/my-account/"
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
id="Union"
fillRule="evenodd"
clipRule="evenodd"
d="M7.25 16.4371C6.16445 15.2755 5.5 13.7153 5.5 12C5.5 8.41015 8.41015 5.5 12 5.5C15.5899 5.5 18.5 8.41015 18.5 12C18.5 13.7153 17.8356 15.2755 16.75 16.4371V16C16.75 14.4812 15.5188 13.25 14 13.25L10 13.25C8.48122 13.25 7.25 14.4812 7.25 16V16.4371ZM8.75 17.6304C9.70606 18.1835 10.8161 18.5 12 18.5C13.1839 18.5 14.2939 18.1835 15.25 17.6304V16C15.25 15.3096 14.6904 14.75 14 14.75L10 14.75C9.30964 14.75 8.75 15.3096 8.75 16V17.6304ZM4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12ZM14 10C14 11.1046 13.1046 12 12 12C10.8954 12 10 11.1046 10 10C10 8.89543 10.8954 8 12 8C13.1046 8 14 8.89543 14 10Z"
fill="black"
export default function HeaderAccount(): JSX.Element {
const [ isModalOpen, setIsModalOpen ] = useState( false );
const openModal = () => setIsModalOpen( true );
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
const isConnected = wccomSettings?.isConnected ?? false;
const connectionURL = wccomSettings?.connectURL ?? '';
const userEmail = wccomSettings?.userEmail;
const avatarURL = wccomSettings?.userAvatar ?? commentAuthorAvatar;
// This is a hack to prevent TypeScript errors. The MenuItem component passes these as an href prop to the underlying button
// component. That component is either an anchor with href if provided or a button that won't accept an href if no href is provided.
// Due to early erroring of TypeScript, it only takes the button version into account which doesn't accept href.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const accountURL: any = 'https://woocommerce.com/my-dashboard/';
const accountOrConnect = isConnected ? accountURL : connectionURL;
const avatar = () => {
if ( ! isConnected ) {
return commentAuthorAvatar;
}
return (
<img
src={ avatarURL }
alt=""
className="woocommerce-marketplace__menu-avatar-image"
/>
);
};
const connectionStatusText = isConnected
? __( 'Connected', 'woocommerce' )
: __( 'Not Connected', 'woocommerce' );
const connectionDetails = () => {
if ( isConnected ) {
return (
<>
<Icon
icon={ commentAuthorAvatar }
size={ 24 }
className="woocommerce-marketplace__menu-icon"
/>
<span className="woocommerce-marketplace__main-text">
{ userEmail }
</span>
</>
);
}
return (
<>
<Icon
icon={ commentAuthorAvatar }
size={ 24 }
className="woocommerce-marketplace__menu-icon"
/>
</svg>
</a>
<div className="woocommerce-marketplace__menu-text">
{ __( 'Connect account', 'woocommerce' ) }
<span className="woocommerce-marketplace__sub-text">
{ __(
'Manage your subscriptions, get updates and support for your extensions and themes.',
'woocommerce'
) }
</span>
</div>
</>
);
};
return (
<>
<DropdownMenu
className="woocommerce-marketplace__user-menu"
icon={ avatar() }
label={ __( 'User options', 'woocommerce' ) }
>
{ () => (
<>
<MenuGroup
className="woocommerce-layout__homescreen-display-options"
label={ connectionStatusText }
>
<MenuItem
className="woocommerce-marketplace__menu-item"
href={ accountOrConnect }
>
{ connectionDetails() }
</MenuItem>
<MenuItem href={ accountURL }>
<Icon
icon={ external }
size={ 24 }
className="woocommerce-marketplace__menu-icon"
/>
{ __(
'WooCommerce.com account',
'woocommerce'
) }
</MenuItem>
</MenuGroup>
{ isConnected && (
<MenuGroup className="woocommerce-layout__homescreen-display-options">
<MenuItem onClick={ openModal }>
<Icon
icon={ linkOff }
size={ 24 }
className="woocommerce-marketplace__menu-icon"
/>
{ __(
'Disconnect account',
'woocommerce'
) }
</MenuItem>
</MenuGroup>
) }
</>
) }
</DropdownMenu>
{ isModalOpen && (
<HeaderAccountModal
setIsModalOpen={ setIsModalOpen }
disconnectURL={ connectionURL }
/>
) }
</>
);
}

View File

@ -39,7 +39,6 @@
.woocommerce-marketplace__header-meta {
grid-area: mktpl-meta;
height: $grid-unit-30;
justify-self: end;
@media (width <= $breakpoint-medium) {
@ -55,3 +54,28 @@
padding: 0 $content-spacing-small;
}
}
.woocommerce-marketplace__search {
grid-area: mktpl-search;
background: #f0f0f0;
border-radius: 2px;
display: flex;
height: 40px;
margin-right: $medium-gap;
padding: 4px 8px 4px 12px;
input[type='search'] {
all: unset;
flex-grow: 1;
}
@media (width <= $breakpoint-medium) {
margin: $content-spacing-small;
}
}
.woocommerce-marketplace__search-button {
all: unset;
cursor: pointer;
}

View File

@ -2,7 +2,7 @@
.woocommerce-marketplace__search {
grid-area: mktpl-search;
background: $gutenberg-gray-100;
background: $gray-0;
border-radius: 2px;
display: flex;
height: 40px;

View File

@ -1,3 +1,5 @@
@import '@wordpress/base-styles/_colors.native.scss';
// Spacings
// Taken from base style system
// @wordpress/base-styles/_variables.scss
@ -23,10 +25,10 @@ $header-height-desktop: 89px;
$header-height-mobile: 129px;
// Colours
$gutenberg-gray-100: #f0f0f0;
$gutenberg-gray-300: $gray-300;
$gutenberg-gray-700: #757575;
$gutenberg-gray-100: $gray-0; // replaced with closest colour from _colors.native.scss
$gutenberg-gray-300: $gray-300; // anything above gray-100 is from the default _colors.scss
$gutenberg-gray-700: $gray-700;
$gutenberg-gray-900: $gray-900;
$mauve-light-12: #1a1523;
$mauve-light-12: $gray-900;
$woo-purple-50: #7f54b3;
$wp-gray-50: #646970;
$wp-gray-50: $gray-50;

View File

@ -0,0 +1,89 @@
<?php
/**
* WooCommerce Admin Helper - React admin interface
*
* @package WooCommerce\Admin\Helper
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper Class
*
* The main entry-point for all things related to the Helper.
* The Helper manages the connection between the store and
* an account on WooCommerce.com.
*/
class WC_Helper_Admin {
/**
* Loads the class, runs on init
*
* @return void
*/
public static function load() {
add_filter( 'woocommerce_admin_shared_settings', array( __CLASS__, 'add_marketplace_settings' ) );
}
/**
* Pushes settings onto the WooCommerce Admin global settings object (wcSettings).
*
* @param mixed $settings The settings object we're amending.
*
* @return mixed $settings
*/
public static function add_marketplace_settings( $settings ) {
$auth_user_data = WC_Helper_Options::get( 'auth_user_data', array() );
$auth_user_email = isset( $auth_user_data['email'] ) ? $auth_user_data['email'] : '';
$settings['wccomHelper'] = array(
'isConnected' => WC_Helper::is_site_connected(),
'connectURL' => self::get_connection_url(),
'userEmail' => $auth_user_email,
'userAvatar' => get_avatar_url( $auth_user_email, array( 'size' => '48' ) ),
);
return $settings;
}
/**
* Generates the URL for connecting or disconnecting the store to/from WooCommerce.com.
* Approach taken from existing helper code that isn't exposed.
*
* @return string
*/
public static function get_connection_url() {
// No active connection.
if ( ! WC_Helper::is_site_connected() ) {
$connect_url = add_query_arg(
array(
'page' => 'wc-addons',
'section' => 'helper',
'wc-helper-connect' => 1,
'wc-helper-nonce' => wp_create_nonce( 'connect' ),
),
admin_url( 'admin.php' )
);
// TODO: this may be needed to make the URL work.
// include WC_Helper::get_view_filename( 'html-oauth-start.php' );
return $connect_url;
}
$connect_url = add_query_arg(
array(
'page' => 'wc-addons',
'section' => 'helper',
'wc-helper-disconnect' => 1,
'wc-helper-nonce' => wp_create_nonce( 'disconnect' ),
),
admin_url( 'admin.php' )
);
return $connect_url;
}
}
WC_Helper_Admin::load();

View File

@ -58,6 +58,7 @@ class WC_Helper {
include_once dirname( __FILE__ ) . '/class-wc-helper-updater.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-plugin-info.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-compat.php';
include_once dirname( __FILE__ ) . '/class-wc-helper-admin.php';
}
/**