Add marketing extensions task to task list (https://github.com/woocommerce/woocommerce-admin/pull/7383)
* Add initial plugin list components and marketing task * Add marketing task styles * Fix action button alignment * Only allow specific plugin lists * Add button to activate already installed plugins * Record event when marketing plugin is installed * Update plugin list when plugins are installed or activated * Disable and set buttons as busy when installing/activating * Update data source to use v2 controller * Add changelog entry
This commit is contained in:
parent
f5de7ef892
commit
7167242dfb
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: Add
|
||||
|
||||
Add marketing extensions task to task list
|
|
@ -17,6 +17,7 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
*/
|
||||
import Appearance from './tasks/appearance';
|
||||
import { getCategorizedOnboardingProducts } from '../dashboard/utils';
|
||||
import { Marketing } from './tasks/Marketing';
|
||||
import { Products } from './tasks/products';
|
||||
import Shipping from './tasks/shipping';
|
||||
import Tax from './tasks/tax';
|
||||
|
@ -328,6 +329,28 @@ export function getAllTasks( {
|
|||
time: __( '1 minute', 'woocommerce-admin' ),
|
||||
type: 'setup',
|
||||
},
|
||||
{
|
||||
key: 'marketing',
|
||||
title: __( 'Set up marketing tools', 'woocommerce-admin' ),
|
||||
content: __(
|
||||
'Add recommended marketing tools to reach new customers and grow your business',
|
||||
'woocommerce-admin'
|
||||
),
|
||||
container: <Marketing />,
|
||||
onClick: () => {
|
||||
onTaskSelect( 'marketing' );
|
||||
updateQueryString( { task: 'marketing' } );
|
||||
},
|
||||
// @todo This should use the free extensions data store.
|
||||
completed:
|
||||
[].filter( ( plugin ) => installedPlugins.includes( plugin ) )
|
||||
.length > 0,
|
||||
visible:
|
||||
window.wcAdminFeatures &&
|
||||
window.wcAdminFeatures[ 'remote-extensions-list' ],
|
||||
time: __( '1 minute', 'woocommerce-admin' ),
|
||||
type: 'setup',
|
||||
},
|
||||
{
|
||||
key: 'appearance',
|
||||
title: __( 'Personalize my store', 'woocommerce-admin' ),
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
.woocommerce-task-marketing {
|
||||
.components-card__header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
h2 {
|
||||
color: $gray-900;
|
||||
margin-bottom: $gap-smaller;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
.woocommerce-plugin-list__plugin {
|
||||
display: flex;
|
||||
padding: $gap-large;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: $gap-small;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $gray-700;
|
||||
font-weight: 400;
|
||||
color: #3c434a;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-plugin-list__plugin-logo {
|
||||
margin-right: 45px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-plugin-list__plugin-text {
|
||||
max-width: 370px;
|
||||
margin-right: $gap;
|
||||
}
|
||||
|
||||
.woocommerce-plugin-list__plugin-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __, sprintf } from '@wordpress/i18n';
|
||||
import { Button } from '@wordpress/components';
|
||||
import { getAdminLink } from '@woocommerce/wc-admin-settings';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './Plugin.scss';
|
||||
|
||||
export type PluginProps = {
|
||||
isActive: boolean;
|
||||
isBusy?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isInstalled: boolean;
|
||||
description?: string;
|
||||
installAndActivate?: ( slug: string ) => void;
|
||||
imageUrl?: string;
|
||||
manageUrl?: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export const Plugin: React.FC< PluginProps > = ( {
|
||||
description,
|
||||
imageUrl,
|
||||
installAndActivate = () => {},
|
||||
isActive,
|
||||
isBusy,
|
||||
isDisabled,
|
||||
isInstalled,
|
||||
manageUrl,
|
||||
name,
|
||||
slug,
|
||||
} ) => {
|
||||
return (
|
||||
<div className="woocommerce-plugin-list__plugin">
|
||||
{ imageUrl && (
|
||||
<div className="woocommerce-plugin-list__plugin-logo">
|
||||
<img
|
||||
src={ imageUrl }
|
||||
alt={ sprintf(
|
||||
/* translators: %s = name of the plugin */
|
||||
__( '%s logo', 'woocommerce-admin' ),
|
||||
name
|
||||
) }
|
||||
/>
|
||||
</div>
|
||||
) }
|
||||
<div className="woocommerce-plugin-list__plugin-text">
|
||||
<Text variant="subtitle.small" as="h4">
|
||||
{ name }
|
||||
</Text>
|
||||
<Text variant="subtitle.small">{ description }</Text>
|
||||
</div>
|
||||
<div className="woocommerce-plugin-list__plugin-action">
|
||||
{ isActive && manageUrl && (
|
||||
<Button
|
||||
disabled={ isDisabled }
|
||||
isBusy={ isBusy }
|
||||
isSecondary
|
||||
href={ getAdminLink( manageUrl ) }
|
||||
>
|
||||
{ __( 'Manage', 'woocommmerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
{ isInstalled && ! isActive && (
|
||||
<Button
|
||||
disabled={ isDisabled }
|
||||
isBusy={ isBusy }
|
||||
isSecondary
|
||||
onClick={ () => installAndActivate( slug ) }
|
||||
>
|
||||
{ __( 'Activate', 'woocommmerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
{ ! isInstalled && (
|
||||
<Button
|
||||
disabled={ isDisabled }
|
||||
isBusy={ isBusy }
|
||||
isSecondary
|
||||
onClick={ () => installAndActivate( slug ) }
|
||||
>
|
||||
{ __( 'Install', 'woocommmerce-admin' ) }
|
||||
</Button>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
.woocommerce-plugin-list__title {
|
||||
padding: $gap-small 30px;
|
||||
background: $gray-200;
|
||||
margin-bottom: 1px;
|
||||
margin-top: 1px;
|
||||
position: relative;
|
||||
|
||||
h3 {
|
||||
font-weight: 500;
|
||||
color: $black;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Plugin, PluginProps } from './Plugin';
|
||||
import './PluginList.scss';
|
||||
|
||||
export type PluginListProps = {
|
||||
currentPlugin?: string | null;
|
||||
key: string;
|
||||
installAndActivate?: ( slug: string ) => void;
|
||||
plugins?: PluginProps[];
|
||||
title?: string;
|
||||
};
|
||||
|
||||
export const PluginList: React.FC< PluginListProps > = ( {
|
||||
currentPlugin,
|
||||
installAndActivate = () => {},
|
||||
plugins = [],
|
||||
title,
|
||||
} ) => {
|
||||
return (
|
||||
<div className="woocommerce-plugin-list">
|
||||
{ title && (
|
||||
<div className="woocommerce-plugin-list__title">
|
||||
<Text variant="sectionheading" as="h3">
|
||||
{ title }
|
||||
</Text>
|
||||
</div>
|
||||
) }
|
||||
{ plugins.map( ( plugin ) => {
|
||||
const {
|
||||
description,
|
||||
imageUrl,
|
||||
isActive,
|
||||
isInstalled,
|
||||
manageUrl,
|
||||
slug,
|
||||
name,
|
||||
} = plugin;
|
||||
return (
|
||||
<Plugin
|
||||
key={ slug }
|
||||
description={ description }
|
||||
manageUrl={ manageUrl }
|
||||
name={ name }
|
||||
imageUrl={ imageUrl }
|
||||
installAndActivate={ installAndActivate }
|
||||
isActive={ isActive }
|
||||
isBusy={ currentPlugin === slug }
|
||||
isDisabled={ !! currentPlugin }
|
||||
isInstalled={ isInstalled }
|
||||
slug={ slug }
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,175 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
import { Card, CardHeader, Spinner } from '@wordpress/components';
|
||||
import { PLUGINS_STORE_NAME, WCDataSelector } from '@woocommerce/data';
|
||||
import { recordEvent } from '@woocommerce/tracks';
|
||||
import { Text } from '@woocommerce/experimental';
|
||||
import { useEffect, useMemo, useState } from '@wordpress/element';
|
||||
import { useSelect, useDispatch } from '@wordpress/data';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import './Marketing.scss';
|
||||
import { createNoticesFromResponse } from '~/lib/notices';
|
||||
import { PluginList, PluginListProps } from './PluginList';
|
||||
import { PluginProps } from './Plugin';
|
||||
|
||||
type ExtensionList = {
|
||||
key: string;
|
||||
title: string;
|
||||
plugins: Extension[];
|
||||
};
|
||||
|
||||
type Extension = {
|
||||
description: string;
|
||||
key: string;
|
||||
image_url: string;
|
||||
manage_url: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
const ALLOWED_PLUGIN_LISTS = [ 'reach', 'grow' ];
|
||||
|
||||
export const Marketing: React.FC = () => {
|
||||
const [ fetchedExtensions, setFetchedExtensions ] = useState<
|
||||
ExtensionList[]
|
||||
>( [] );
|
||||
const [ currentPlugin, setCurrentPlugin ] = useState< string | null >(
|
||||
null
|
||||
);
|
||||
const [ isFetching, setIsFetching ] = useState( true );
|
||||
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
|
||||
const { activePlugins, installedPlugins } = useSelect(
|
||||
( select: WCDataSelector ) => {
|
||||
const { getActivePlugins, getInstalledPlugins } = select(
|
||||
PLUGINS_STORE_NAME
|
||||
);
|
||||
|
||||
return {
|
||||
activePlugins: getActivePlugins(),
|
||||
installedPlugins: getInstalledPlugins(),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const transformExtensionToPlugin = (
|
||||
extension: Extension
|
||||
): PluginProps => {
|
||||
const { description, image_url, key, manage_url, name } = extension;
|
||||
const slug = key.split( ':' )[ 0 ];
|
||||
return {
|
||||
description,
|
||||
slug,
|
||||
imageUrl: image_url,
|
||||
isActive: activePlugins.includes( slug ),
|
||||
isInstalled: installedPlugins.includes( slug ),
|
||||
manageUrl: manage_url,
|
||||
name,
|
||||
};
|
||||
};
|
||||
|
||||
useEffect( () => {
|
||||
apiFetch( {
|
||||
path: '/wc-admin/onboarding/free-extensions',
|
||||
} )
|
||||
.then( ( results: ExtensionList[] ) => {
|
||||
if ( results?.length ) {
|
||||
setFetchedExtensions( results );
|
||||
}
|
||||
setIsFetching( false );
|
||||
} )
|
||||
.catch( () => {
|
||||
// @todo Handle error checking.
|
||||
setIsFetching( false );
|
||||
} );
|
||||
}, [] );
|
||||
|
||||
const pluginLists: PluginListProps[] = useMemo( () => {
|
||||
return fetchedExtensions
|
||||
.map( ( list ) => {
|
||||
return {
|
||||
...list,
|
||||
plugins: list.plugins.map( ( extension ) =>
|
||||
transformExtensionToPlugin( extension )
|
||||
),
|
||||
};
|
||||
} )
|
||||
.filter( ( list ) => ALLOWED_PLUGIN_LISTS.includes( list.key ) );
|
||||
}, [ installedPlugins, activePlugins, fetchedExtensions ] );
|
||||
|
||||
const getInstalledMarketingPlugins = () => {
|
||||
const installed: string[] = [];
|
||||
pluginLists.forEach( ( list: PluginListProps ) => {
|
||||
return list.plugins?.forEach( ( plugin ) => {
|
||||
if ( plugin.isInstalled ) {
|
||||
installed.push( plugin.slug );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
return installed;
|
||||
};
|
||||
|
||||
const installAndActivate = ( slug: string ) => {
|
||||
setCurrentPlugin( slug );
|
||||
installAndActivatePlugins( [ slug ] )
|
||||
.then( ( response: { errors: Record< string, string > } ) => {
|
||||
recordEvent( 'tasklist_marketing_install', {
|
||||
selected_extension: slug,
|
||||
installed_extensions: getInstalledMarketingPlugins(),
|
||||
} );
|
||||
createNoticesFromResponse( response );
|
||||
setCurrentPlugin( null );
|
||||
} )
|
||||
.catch( ( response: { errors: Record< string, string > } ) => {
|
||||
createNoticesFromResponse( response );
|
||||
setCurrentPlugin( null );
|
||||
} );
|
||||
};
|
||||
|
||||
if ( isFetching ) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="woocommerce-task-marketing">
|
||||
<Card className="woocommerce-task-card">
|
||||
<CardHeader>
|
||||
<Text
|
||||
variant="title.small"
|
||||
as="h2"
|
||||
className="woocommerce-task-card__title"
|
||||
>
|
||||
{ __(
|
||||
'Recommended marketing extensions',
|
||||
'woocommerce-admin'
|
||||
) }
|
||||
</Text>
|
||||
<Text>
|
||||
{ __(
|
||||
'We recommend adding one of the following marketing tools for your store. The extension will be installed and activated for you when you click "Get started".',
|
||||
'woocommerce-admin'
|
||||
) }
|
||||
</Text>
|
||||
</CardHeader>
|
||||
{ pluginLists.map( ( list ) => {
|
||||
const { key, title, plugins } = list;
|
||||
return (
|
||||
<PluginList
|
||||
currentPlugin={ currentPlugin }
|
||||
installAndActivate={ installAndActivate }
|
||||
key={ key }
|
||||
plugins={ plugins }
|
||||
title={ title }
|
||||
/>
|
||||
);
|
||||
} ) }
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -69,4 +69,5 @@ export type PluginSelectors = {
|
|||
getInstalledPlugins: WPDataSelector< typeof getInstalledPlugins >;
|
||||
getRecommendedPlugins: WPDataSelector< typeof getRecommendedPlugins >;
|
||||
isJetpackConnected: WPDataSelector< typeof isJetpackConnected >;
|
||||
isPluginsRequesting: WPDataSelector< typeof isPluginsRequesting >;
|
||||
} & WPDataSelectors;
|
||||
|
|
|
@ -13,7 +13,7 @@ defined( 'ABSPATH' ) || exit;
|
|||
*/
|
||||
class DataSourcePoller {
|
||||
const DATA_SOURCES = array(
|
||||
'https://woocommerce.com/wp-json/wccom/obw-free-extensions/1.0/extensions.json',
|
||||
'https://woocommerce.com/wp-json/wccom/obw-free-extensions/2.0/extensions.json',
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue