Add subscription install modal (#42009)
* Install modal * Add connect button * Small product card * Add install functionality * Add no subscription error * Fix error notice loading * Connect style * Add success state * Fix admin urls * Add error message to failed install events * Add changefile(s) from automation for the following project(s): woocommerce --------- Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
parent
0903a53664
commit
2964800f27
|
@ -1,3 +1,8 @@
|
|||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { ADMIN_URL } from '../../utils/admin-settings';
|
||||
|
||||
export const DEFAULT_TAB_KEY = 'discover';
|
||||
export const MARKETPLACE_HOST = 'https://woo.com';
|
||||
export const MARKETPLACE_PATH = '/extensions';
|
||||
|
@ -11,3 +16,4 @@ export const MARKETPLACE_CART_PATH = MARKETPLACE_HOST + '/cart/';
|
|||
export const MARKETPLACE_COLLABORATION_PATH =
|
||||
MARKETPLACE_HOST +
|
||||
'/document/managing-woocommerce-com-subscriptions/#transfer-a-woocommerce-com-subscription';
|
||||
export const WP_ADMIN_PLUGIN_LIST_URL = ADMIN_URL + '/plugins.php';
|
||||
|
|
|
@ -56,7 +56,12 @@
|
|||
@media screen and (min-width: $break-small) {
|
||||
.woocommerce-marketplace {
|
||||
&__header-account-modal {
|
||||
max-width: 350px;
|
||||
max-width: $modal-min-width;
|
||||
|
||||
&.has-size-medium {
|
||||
max-width: $modal-width-medium;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -233,9 +233,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__my-subscriptions .components-button.is-link {
|
||||
text-decoration: none;
|
||||
padding: 6px 12px;
|
||||
.woocommerce-marketplace__my-subscriptions .components-button {
|
||||
&.is-link {
|
||||
text-decoration: none;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__my-subscriptions
|
||||
|
@ -300,6 +302,7 @@
|
|||
box-shadow: 0 2px 6px 0 rgba($gray-100, 0.05);
|
||||
border: 1px solid var(--gutenberg-gray-100, #f0f0f0);
|
||||
padding-right: $grid-unit-15;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
|
@ -338,3 +341,52 @@
|
|||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.woocommerce-marketplace__header-account-modal-overlay {
|
||||
.components-modal__header {
|
||||
padding-bottom: $grid-unit-20;
|
||||
|
||||
.components-modal__header-heading {
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
}
|
||||
.components-notice {
|
||||
padding: $grid-unit-15 $grid-unit-20;
|
||||
border-left: none;
|
||||
margin: $grid-unit-20 0;
|
||||
|
||||
&.is-warning {
|
||||
background-color: var(--wp-yellow-yellow-0, #fcf9e8);
|
||||
align-items: start;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
/* stylelint-disable-next-line function-url-quotes */
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20Z' stroke='%23614200' stroke-width='1.5'/%3E%3Cpath d='M13 7H11V13H13V7Z' fill='%23614200'/%3E%3Cpath d='M13 15H11V17H13V15Z' fill='%23614200'/%3E%3C/svg%3E");
|
||||
margin-right: $grid-unit-15;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.components-notice__content {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.components-button-group .components-button {
|
||||
&.is-primary {
|
||||
box-shadow: none;
|
||||
}
|
||||
&.is-secondary {
|
||||
box-shadow: inset 0 0 0 1px var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__product-card {
|
||||
margin: $grid-unit-20 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
import { Subscription } from './types';
|
||||
import { RefreshButton } from './table/actions/refresh-button';
|
||||
import Notices from './notices';
|
||||
import InstallModal from './table/actions/install-modal';
|
||||
|
||||
export default function MySubscriptions(): JSX.Element {
|
||||
const { subscriptions, isLoading } = useContext( SubscriptionsContext );
|
||||
|
@ -58,6 +59,7 @@ export default function MySubscriptions(): JSX.Element {
|
|||
if ( ! wccomSettings?.isConnected ) {
|
||||
return (
|
||||
<div className="woocommerce-marketplace__my-subscriptions--connect">
|
||||
<InstallModal />
|
||||
<div className="woocommerce-marketplace__my-subscriptions__icon" />
|
||||
<h2 className="woocommerce-marketplace__my-subscriptions__header">
|
||||
{ __( 'Manage your subscriptions', 'woocommerce' ) }
|
||||
|
@ -77,6 +79,7 @@ export default function MySubscriptions(): JSX.Element {
|
|||
|
||||
return (
|
||||
<div className="woocommerce-marketplace__my-subscriptions">
|
||||
<InstallModal />
|
||||
<section className="woocommerce-marketplace__my-subscriptions__notices">
|
||||
<Notices />
|
||||
</section>
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button } from '@wordpress/components';
|
||||
import { __ } from '@wordpress/i18n';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { getAdminSetting } from '../../../../../utils/admin-settings';
|
||||
|
||||
interface RenewProps {
|
||||
variant?: Button.ButtonVariant;
|
||||
install?: string;
|
||||
}
|
||||
|
||||
export default function ConnectAccountButton( props: RenewProps ) {
|
||||
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
|
||||
|
||||
const url = new URL( wccomSettings?.connectURL ?? '' );
|
||||
if ( props.install ) {
|
||||
url.searchParams.set( 'install', props.install );
|
||||
}
|
||||
return (
|
||||
<Button href={ url.href } variant={ props.variant ?? 'secondary' }>
|
||||
{ __( 'Connect Account', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Button, ButtonGroup, Modal, Notice } from '@wordpress/components';
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { getNewPath, navigateTo, useQuery } from '@woocommerce/navigation';
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Subscription } from '../../types';
|
||||
import { getAdminSetting } from '../../../../../utils/admin-settings';
|
||||
import Install from './install';
|
||||
import { SubscriptionsContext } from '../../../../contexts/subscriptions-context';
|
||||
import { MARKETPLACE_PATH, WP_ADMIN_PLUGIN_LIST_URL } from '../../../constants';
|
||||
import ConnectAccountButton from './connect-account-button';
|
||||
import ProductCard from '../../../product-card/product-card';
|
||||
import { addNotice, subscriptionToProduct } from '../../../../utils/functions';
|
||||
import { NoticeStatus } from '../../../../contexts/types';
|
||||
|
||||
export default function InstallModal() {
|
||||
const query = useQuery();
|
||||
const installingProductKey = query?.install;
|
||||
|
||||
const wccomSettings = getAdminSetting( 'wccomHelper', {} );
|
||||
const isConnected = !! wccomSettings?.isConnected;
|
||||
|
||||
const [ showModal, setShowModal ] = useState< boolean >( false );
|
||||
const [ isInstalled, setIsInstalled ] = useState< boolean >( false );
|
||||
|
||||
const { subscriptions, isLoading } = useContext( SubscriptionsContext );
|
||||
|
||||
const subscription: Subscription | undefined = subscriptions.find(
|
||||
( s: Subscription ) => s.product_key === installingProductKey
|
||||
);
|
||||
|
||||
const removeInstallQuery = useCallback( () => {
|
||||
navigateTo( {
|
||||
url: getNewPath(
|
||||
{
|
||||
...query,
|
||||
install: undefined,
|
||||
},
|
||||
MARKETPLACE_PATH,
|
||||
{}
|
||||
),
|
||||
} );
|
||||
}, [ query ] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( isLoading ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If subscriptions loaded, but we don't have a subscription for the product key, show an error.
|
||||
if (
|
||||
installingProductKey &&
|
||||
isConnected &&
|
||||
! isLoading &&
|
||||
! subscription
|
||||
) {
|
||||
addNotice(
|
||||
installingProductKey,
|
||||
sprintf(
|
||||
/* translators: %s is the product key */
|
||||
__(
|
||||
'Could not find subscription with product key %s.',
|
||||
'woocommerce'
|
||||
),
|
||||
installingProductKey
|
||||
),
|
||||
NoticeStatus.Error
|
||||
);
|
||||
removeInstallQuery();
|
||||
} else {
|
||||
setShowModal( !! installingProductKey );
|
||||
}
|
||||
}, [
|
||||
isConnected,
|
||||
isLoading,
|
||||
installingProductKey,
|
||||
removeInstallQuery,
|
||||
subscription,
|
||||
] );
|
||||
|
||||
useEffect( () => {
|
||||
if ( subscription && subscription.local.installed ) {
|
||||
setIsInstalled( true );
|
||||
}
|
||||
}, [ subscription ] );
|
||||
|
||||
const onClose = () => {
|
||||
removeInstallQuery();
|
||||
setShowModal( false );
|
||||
};
|
||||
|
||||
const modalTitle = () => {
|
||||
if ( isInstalled ) {
|
||||
return __( 'You are ready to go!', 'woocommerce' );
|
||||
}
|
||||
|
||||
return __( 'Add to store', 'woocommerce' );
|
||||
};
|
||||
|
||||
const modalContent = () => {
|
||||
if ( ! isConnected ) {
|
||||
return (
|
||||
<Notice status="warning" isDismissible={ false }>
|
||||
{ __(
|
||||
'In order to install a product, you need to first connect your account.',
|
||||
'woocommerce'
|
||||
) }
|
||||
</Notice>
|
||||
);
|
||||
} else if ( subscription ) {
|
||||
const installContent = isInstalled
|
||||
? __(
|
||||
'Keep the momentum going and start setting up your extension.',
|
||||
'woocommerce'
|
||||
)
|
||||
: __(
|
||||
'Would you like to install this extension?',
|
||||
'woocommerce'
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<p className="woocommerce-marketplace__header-account-modal-text">
|
||||
{ installContent }
|
||||
</p>
|
||||
<ProductCard
|
||||
product={ subscriptionToProduct( subscription ) }
|
||||
small={ true }
|
||||
tracksData={ {
|
||||
position: 1,
|
||||
group: 'subscriptions',
|
||||
label: 'install',
|
||||
} }
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
const modalButtons = () => {
|
||||
const buttons = [];
|
||||
if ( isInstalled ) {
|
||||
buttons.push(
|
||||
<Button
|
||||
variant="secondary"
|
||||
href={ subscription?.documentation_url }
|
||||
target="_blank"
|
||||
className="woocommerce-marketplace__header-account-modal-button"
|
||||
key={ 'docs' }
|
||||
>
|
||||
{ __( 'View docs', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
buttons.push(
|
||||
<Button
|
||||
variant="primary"
|
||||
href={ WP_ADMIN_PLUGIN_LIST_URL }
|
||||
className="woocommerce-marketplace__header-account-modal-button"
|
||||
key={ 'plugin-list' }
|
||||
>
|
||||
{ __( 'View in Plugins', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
} else {
|
||||
buttons.push(
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={ onClose }
|
||||
className="woocommerce-marketplace__header-account-modal-button"
|
||||
key={ 'cancel' }
|
||||
>
|
||||
{ __( 'Cancel', 'woocommerce' ) }
|
||||
</Button>
|
||||
);
|
||||
|
||||
if ( ! isConnected ) {
|
||||
buttons.push(
|
||||
<ConnectAccountButton
|
||||
variant="primary"
|
||||
install={ installingProductKey }
|
||||
key={ 'connect' }
|
||||
/>
|
||||
);
|
||||
} else if ( subscription ) {
|
||||
buttons.push(
|
||||
<Install
|
||||
subscription={ subscription }
|
||||
variant="primary"
|
||||
onError={ onClose }
|
||||
key={ 'install' }
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<ButtonGroup className="woocommerce-marketplace__header-account-modal-button-group">
|
||||
{ buttons }
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
if ( ! showModal ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={ modalTitle() }
|
||||
onRequestClose={ onClose }
|
||||
focusOnMount={ true }
|
||||
className="woocommerce-marketplace__header-account-modal has-size-medium"
|
||||
style={ { borderRadius: 4 } }
|
||||
overlayClassName="woocommerce-marketplace__header-account-modal-overlay"
|
||||
>
|
||||
{ modalContent() }
|
||||
{ modalButtons() }
|
||||
</Modal>
|
||||
);
|
||||
}
|
|
@ -22,6 +22,9 @@ import { NoticeStatus } from '../../../../contexts/types';
|
|||
|
||||
interface InstallProps {
|
||||
subscription: Subscription;
|
||||
variant?: Button.ButtonVariant;
|
||||
onSuccess?: () => void;
|
||||
onError?: () => void;
|
||||
}
|
||||
|
||||
export default function Install( props: InstallProps ) {
|
||||
|
@ -76,6 +79,10 @@ export default function Install( props: InstallProps ) {
|
|||
product_id: props.subscription.product_id,
|
||||
product_current_version: props.subscription.version,
|
||||
} );
|
||||
|
||||
if ( props.onSuccess ) {
|
||||
props.onSuccess();
|
||||
}
|
||||
} )
|
||||
.catch( ( error ) => {
|
||||
loadSubscriptions( false ).then( () => {
|
||||
|
@ -101,19 +108,24 @@ export default function Install( props: InstallProps ) {
|
|||
}
|
||||
);
|
||||
stopInstall();
|
||||
|
||||
if ( props.onError ) {
|
||||
props.onError();
|
||||
}
|
||||
} );
|
||||
|
||||
recordEvent( 'marketplace_product_install_failed', {
|
||||
product_zip_slug: props.subscription.zip_slug,
|
||||
product_id: props.subscription.product_id,
|
||||
product_current_version: props.subscription.version,
|
||||
error_message: error?.data?.message,
|
||||
} );
|
||||
} );
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="link"
|
||||
variant={ props.variant ?? 'link' }
|
||||
isBusy={ loading }
|
||||
disabled={ loading }
|
||||
onClick={ install }
|
||||
|
|
|
@ -59,6 +59,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.is-small {
|
||||
padding: $medium-gap;
|
||||
|
||||
.woocommerce-marketplace__product-card__description,
|
||||
.woocommerce-marketplace__product-card__price {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__product-card__content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__product-card__details {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.woocommerce-marketplace__product-card__title {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
outline: 1.5px solid var(--wp-admin-theme-color);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export interface ProductCardProps {
|
|||
product?: Product;
|
||||
isLoading?: boolean;
|
||||
tracksData: ProductTracksData;
|
||||
small?: boolean;
|
||||
}
|
||||
|
||||
function ProductCard( props: ProductCardProps ): JSX.Element {
|
||||
|
@ -102,6 +103,7 @@ function ProductCard( props: ProductCardProps ): JSX.Element {
|
|||
`woocommerce-marketplace__product-card--${ type }`,
|
||||
{
|
||||
'is-loading': isLoading,
|
||||
'is-small': props.small,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -33,3 +33,8 @@ $wp-gray-0: $gray-0;
|
|||
$wp-gray-50: $gray-50;
|
||||
$wp-gray-60: $gray-60;
|
||||
$skeleton-loader-color: #f0f0f0;
|
||||
|
||||
// Modal
|
||||
$modal-min-width: 350px;
|
||||
$modal-width-small: 384px;
|
||||
$modal-width-medium: 512px;
|
||||
|
|
|
@ -364,6 +364,23 @@ const removeNotice = ( productKey: string ) => {
|
|||
dispatch( noticeStore ).removeNotice( productKey );
|
||||
};
|
||||
|
||||
const subscriptionToProduct = ( subscription: Subscription ): Product => {
|
||||
return {
|
||||
id: subscription.product_id,
|
||||
title: subscription.product_name,
|
||||
image: '',
|
||||
type: subscription.product_type as ProductType,
|
||||
description: '',
|
||||
vendorName: '',
|
||||
vendorUrl: '',
|
||||
icon: subscription.product_icon,
|
||||
url: subscription.product_url,
|
||||
price: -1,
|
||||
averageRating: 0,
|
||||
reviewsCount: 0,
|
||||
};
|
||||
};
|
||||
|
||||
// Append UTM parameters to a URL, being aware of existing query parameters
|
||||
const appendURLParams = (
|
||||
url: string,
|
||||
|
@ -412,4 +429,5 @@ export {
|
|||
removeNotice,
|
||||
renewUrl,
|
||||
subscribeUrl,
|
||||
subscriptionToProduct,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add subscription install modal.
|
|
@ -684,14 +684,15 @@ class WC_Helper {
|
|||
/**
|
||||
* Get helper redirect URL.
|
||||
*
|
||||
* @param array $args Query args.
|
||||
* @param bool $redirect_to_wc_admin Whether to redirect to WC Admin.
|
||||
* @param array $args Query args.
|
||||
* @param bool $redirect_to_wc_admin Whether to redirect to WC Admin.
|
||||
* @param string $install_product_key Optional Product key to install.
|
||||
* @return string
|
||||
*/
|
||||
private static function get_helper_redirect_url( $args = array(), $redirect_to_wc_admin = false ) {
|
||||
private static function get_helper_redirect_url( $args = array(), $redirect_to_wc_admin = false, $install_product_key = '' ) {
|
||||
global $current_screen;
|
||||
if ( true === $redirect_to_wc_admin && 'woocommerce_page_wc-addons' === $current_screen->id ) {
|
||||
return add_query_arg(
|
||||
$new_url = add_query_arg(
|
||||
array(
|
||||
'page' => 'wc-admin',
|
||||
'tab' => 'my-subscriptions',
|
||||
|
@ -699,6 +700,15 @@ class WC_Helper {
|
|||
),
|
||||
admin_url( 'admin.php' )
|
||||
);
|
||||
if ( ! empty( $install_product_key ) ) {
|
||||
$new_url = add_query_arg(
|
||||
array(
|
||||
'install' => $install_product_key,
|
||||
),
|
||||
$new_url
|
||||
);
|
||||
}
|
||||
return $new_url;
|
||||
}
|
||||
|
||||
return add_query_arg(
|
||||
|
@ -727,6 +737,10 @@ class WC_Helper {
|
|||
$redirect_url_args['redirect-to-wc-admin'] = 1;
|
||||
}
|
||||
|
||||
if ( isset( $_GET['install'] ) ) {
|
||||
$redirect_url_args['install'] = sanitize_text_field( wp_unslash( $_GET['install'] ) );
|
||||
}
|
||||
|
||||
$redirect_uri = add_query_arg(
|
||||
$redirect_url_args,
|
||||
admin_url( 'admin.php' )
|
||||
|
@ -795,7 +809,8 @@ class WC_Helper {
|
|||
'page' => 'wc-addons',
|
||||
'section' => 'helper',
|
||||
),
|
||||
isset( $_GET['redirect-to-wc-admin'] )
|
||||
isset( $_GET['redirect-to-wc-admin'] ),
|
||||
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
|
||||
)
|
||||
);
|
||||
die();
|
||||
|
@ -858,7 +873,8 @@ class WC_Helper {
|
|||
'section' => 'helper',
|
||||
'wc-helper-status' => 'helper-connected',
|
||||
),
|
||||
isset( $_GET['redirect-to-wc-admin'] )
|
||||
isset( $_GET['redirect-to-wc-admin'] ),
|
||||
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
|
||||
)
|
||||
);
|
||||
die();
|
||||
|
@ -884,7 +900,8 @@ class WC_Helper {
|
|||
'section' => 'helper',
|
||||
'wc-helper-status' => 'helper-disconnected',
|
||||
),
|
||||
isset( $_GET['redirect-to-wc-admin'] )
|
||||
isset( $_GET['redirect-to-wc-admin'] ),
|
||||
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
|
||||
);
|
||||
|
||||
self::disconnect();
|
||||
|
@ -911,7 +928,8 @@ class WC_Helper {
|
|||
'filter' => self::get_current_filter(),
|
||||
'wc-helper-status' => 'helper-refreshed',
|
||||
),
|
||||
isset( $_GET['redirect-to-wc-admin'] )
|
||||
isset( $_GET['redirect-to-wc-admin'] ),
|
||||
isset( $_GET['install'] ) ? sanitize_text_field( wp_unslash( $_GET['install'] ) ) : ''
|
||||
);
|
||||
|
||||
wp_safe_redirect( $redirect_uri );
|
||||
|
|
Loading…
Reference in New Issue