feat: add shipping marketplace recommendations (https://github.com/woocommerce/woocommerce-admin/pull/7446)
This commit is contained in:
parent
3398e03999
commit
c1730d16f1
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: Add
|
||||||
|
|
||||||
|
Added shipping plugin recommendations to settings page (#7446).
|
|
@ -8,6 +8,7 @@ import QueryString, { parse } from 'qs';
|
||||||
* Internal dependencies
|
* Internal dependencies
|
||||||
*/
|
*/
|
||||||
import { PaymentRecommendations } from '../payments';
|
import { PaymentRecommendations } from '../payments';
|
||||||
|
import { ShippingRecommendations } from '../shipping';
|
||||||
import { EmbeddedBodyProps } from './embedded-body-props';
|
import { EmbeddedBodyProps } from './embedded-body-props';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
|
@ -21,6 +22,7 @@ function isWPPage(
|
||||||
|
|
||||||
const EMBEDDED_BODY_COMPONENT_LIST: React.ElementType[] = [
|
const EMBEDDED_BODY_COMPONENT_LIST: React.ElementType[] = [
|
||||||
PaymentRecommendations,
|
PaymentRecommendations,
|
||||||
|
ShippingRecommendations,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export type EmbeddedBodyProps = {
|
export type EmbeddedBodyProps = {
|
||||||
page: string;
|
page: string;
|
||||||
tab: string;
|
tab: string;
|
||||||
|
zone_id?: string;
|
||||||
section?: string;
|
section?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
.woocommerce-dismissable-list {
|
||||||
|
margin: 0 20px 10px 20px;
|
||||||
|
animation: isLoaded;
|
||||||
|
animation-duration: 250ms;
|
||||||
|
|
||||||
|
@media (min-width: #{ ($break-medium) }) {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-dismissable-list__controls {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
import { Button, Card, CardHeader } from '@wordpress/components';
|
||||||
|
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||||
|
import { EllipsisMenu } from '@woocommerce/components';
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { createContext, useContext } from '@wordpress/element';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import './dismissable-list.scss';
|
||||||
|
|
||||||
|
// using a context provider for the option name so that the option name prop doesn't need to be passed to the `DismissableListHeading` too
|
||||||
|
const OptionNameContext = createContext( '' );
|
||||||
|
|
||||||
|
export const DismissableListHeading: React.FC< {
|
||||||
|
onDismiss?: () => void;
|
||||||
|
} > = ( { children, onDismiss = () => null } ) => {
|
||||||
|
const { updateOptions } = useDispatch( OPTIONS_STORE_NAME );
|
||||||
|
const dismissOptionName = useContext( OptionNameContext );
|
||||||
|
|
||||||
|
const handleDismissClick = () => {
|
||||||
|
onDismiss();
|
||||||
|
updateOptions( {
|
||||||
|
[ dismissOptionName ]: 'yes',
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardHeader>
|
||||||
|
<div className="woocommerce-dismissable-list__header">
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<EllipsisMenu
|
||||||
|
label={ __( 'Task List Options', 'woocommerce-admin' ) }
|
||||||
|
renderContent={ () => (
|
||||||
|
<div className="woocommerce-dismissable-list__controls">
|
||||||
|
<Button onClick={ handleDismissClick }>
|
||||||
|
{ __( 'Hide this', 'woocommerce-admin' ) }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) }
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DismissableList: React.FC< {
|
||||||
|
dismissOptionName: string;
|
||||||
|
className?: string;
|
||||||
|
} > = ( { children, className, dismissOptionName } ) => {
|
||||||
|
const isVisible = useSelect( ( select ) => {
|
||||||
|
const { getOption, hasFinishedResolution } = select(
|
||||||
|
OPTIONS_STORE_NAME
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasFinishedResolving = hasFinishedResolution( 'getOption', [
|
||||||
|
dismissOptionName,
|
||||||
|
] );
|
||||||
|
|
||||||
|
const isDismissed = getOption( dismissOptionName ) === 'yes';
|
||||||
|
|
||||||
|
return hasFinishedResolving && ! isDismissed;
|
||||||
|
} );
|
||||||
|
|
||||||
|
if ( ! isVisible ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
size="medium"
|
||||||
|
className={ classNames(
|
||||||
|
'woocommerce-dismissable-list',
|
||||||
|
className
|
||||||
|
) }
|
||||||
|
>
|
||||||
|
<OptionNameContext.Provider value={ dismissOptionName }>
|
||||||
|
{ children }
|
||||||
|
</OptionNameContext.Provider>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,99 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { DismissableList, DismissableListHeading } from '../dismissable-list';
|
||||||
|
|
||||||
|
jest.mock( '@wordpress/data', () => ( {
|
||||||
|
...jest.requireActual( '@wordpress/data' ),
|
||||||
|
useSelect: jest.fn(),
|
||||||
|
useDispatch: jest.fn(),
|
||||||
|
} ) );
|
||||||
|
|
||||||
|
const DismissableListMock = ( { children } ) => (
|
||||||
|
<DismissableList dismissOptionName="dismissable_option_mock">
|
||||||
|
{ children }
|
||||||
|
<span>dismissible children</span>
|
||||||
|
</DismissableList>
|
||||||
|
);
|
||||||
|
|
||||||
|
describe( 'DismissableList', () => {
|
||||||
|
beforeEach( () => {
|
||||||
|
useSelect.mockImplementation( ( fn ) =>
|
||||||
|
fn( () => ( {
|
||||||
|
getOption: () => false,
|
||||||
|
hasFinishedResolution: () => true,
|
||||||
|
} ) )
|
||||||
|
);
|
||||||
|
useDispatch.mockReturnValue( { updateOptions: () => null } );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not render its children when the option is not resolved', () => {
|
||||||
|
useSelect.mockImplementation( ( fn ) =>
|
||||||
|
fn( () => ( {
|
||||||
|
getOption: () => false,
|
||||||
|
hasFinishedResolution: () => false,
|
||||||
|
} ) )
|
||||||
|
);
|
||||||
|
render( <DismissableListMock /> );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText( 'dismissible children' )
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not render its children when the option is dismissed', () => {
|
||||||
|
useSelect.mockImplementation( ( fn ) =>
|
||||||
|
fn( () => ( {
|
||||||
|
getOption: () => 'yes',
|
||||||
|
hasFinishedResolution: () => true,
|
||||||
|
} ) )
|
||||||
|
);
|
||||||
|
render( <DismissableListMock /> );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText( 'dismissible children' )
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'render its children', () => {
|
||||||
|
render( <DismissableListMock /> );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText( 'dismissible children' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should allow dismissing the option through the DismissableListHeading component', () => {
|
||||||
|
const handleDismissMock = jest.fn();
|
||||||
|
const updateOptionsMock = jest.fn();
|
||||||
|
useDispatch.mockReturnValue( { updateOptions: updateOptionsMock } );
|
||||||
|
render(
|
||||||
|
<DismissableListMock>
|
||||||
|
<DismissableListHeading onDismiss={ handleDismissMock }>
|
||||||
|
heading content mock
|
||||||
|
</DismissableListHeading>
|
||||||
|
</DismissableListMock>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText( 'heading content mock' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
userEvent.click( screen.getByTitle( 'Task List Options' ) );
|
||||||
|
userEvent.click( screen.getByText( 'Hide this' ) );
|
||||||
|
|
||||||
|
expect( handleDismissMock ).toHaveBeenCalled();
|
||||||
|
expect( updateOptionsMock ).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining( {
|
||||||
|
dismissable_option_mock: 'yes',
|
||||||
|
} )
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './shipping-recommendations-wrapper';
|
|
@ -0,0 +1,48 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { lazy, Suspense } from '@wordpress/element';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { EmbeddedBodyProps } from '../embedded-body-layout/embedded-body-props';
|
||||||
|
import RecommendationsEligibilityWrapper from '../settings-recommendations/recommendations-eligibility-wrapper';
|
||||||
|
|
||||||
|
const ShippingRecommendationsLoader = lazy(
|
||||||
|
() =>
|
||||||
|
import(
|
||||||
|
/* webpackChunkName: "shipping-recommendations" */ './shipping-recommendations'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ShippingRecommendations: React.FC< EmbeddedBodyProps > = ( {
|
||||||
|
page,
|
||||||
|
tab,
|
||||||
|
section,
|
||||||
|
zone_id,
|
||||||
|
} ) => {
|
||||||
|
if ( page !== 'wc-settings' ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( tab !== 'shipping' ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Boolean( section ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Boolean( zone_id ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RecommendationsEligibilityWrapper>
|
||||||
|
<Suspense fallback={ null }>
|
||||||
|
<ShippingRecommendationsLoader />
|
||||||
|
</Suspense>
|
||||||
|
</RecommendationsEligibilityWrapper>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,50 @@
|
||||||
|
.woocommerce-recommended-shipping-extensions {
|
||||||
|
&__more_options_cta {
|
||||||
|
// adding some breathing room between the "external" icon and the text
|
||||||
|
.gridicon {
|
||||||
|
margin-left: $gap-smallest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwriting the list's default colors/behavior
|
||||||
|
.woocommerce-list__item {
|
||||||
|
> .woocommerce-list__item-inner {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $white;
|
||||||
|
|
||||||
|
.woocommerce-list__item-title {
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $gray-900;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-pill {
|
||||||
|
margin-left: $gap-smallest;
|
||||||
|
margin-top: $gap-smallest;
|
||||||
|
margin-bottom: $gap-smallest;
|
||||||
|
padding: 2px 8px;
|
||||||
|
|
||||||
|
@media (min-width: #{ ($break-mobile) }) {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-after .components-button {
|
||||||
|
margin-left: $gap-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.woocommerce-list__item-text,
|
||||||
|
.woocommerce-recommended-shipping__header-heading {
|
||||||
|
max-width: 749px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
|
import { useState, Children } from '@wordpress/element';
|
||||||
|
import { Text } from '@woocommerce/experimental';
|
||||||
|
import { PLUGINS_STORE_NAME } from '@woocommerce/data';
|
||||||
|
import ExternalIcon from 'gridicons/dist/external';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore VisuallyHidden is present, it's just not typed
|
||||||
|
// eslint-disable-next-line @woocommerce/dependency-group
|
||||||
|
import { CardFooter, Button, VisuallyHidden } from '@wordpress/components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import { createNoticesFromResponse } from '../lib/notices';
|
||||||
|
import {
|
||||||
|
DismissableList,
|
||||||
|
DismissableListHeading,
|
||||||
|
} from '../settings-recommendations/dismissable-list';
|
||||||
|
import WooCommerceServicesItem from './woocommerce-services-item';
|
||||||
|
import './shipping-recommendations.scss';
|
||||||
|
|
||||||
|
const useInstallPlugin = () => {
|
||||||
|
const [ pluginsBeingSetup, setPluginsBeingSetup ] = useState<
|
||||||
|
Array< string >
|
||||||
|
>( [] );
|
||||||
|
|
||||||
|
const { installAndActivatePlugins } = useDispatch( PLUGINS_STORE_NAME );
|
||||||
|
|
||||||
|
const handleSetup = ( slugs: string[] ): PromiseLike< void > => {
|
||||||
|
if ( pluginsBeingSetup.length > 0 ) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
setPluginsBeingSetup( slugs );
|
||||||
|
|
||||||
|
return installAndActivatePlugins( slugs )
|
||||||
|
.then( () => {
|
||||||
|
setPluginsBeingSetup( [] );
|
||||||
|
} )
|
||||||
|
.catch( ( response: { errors: Record< string, string > } ) => {
|
||||||
|
createNoticesFromResponse( response );
|
||||||
|
setPluginsBeingSetup( [] );
|
||||||
|
|
||||||
|
return Promise.reject();
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
return [ pluginsBeingSetup, handleSetup ] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ShippingRecommendationsList: React.FC = ( { children } ) => (
|
||||||
|
<DismissableList
|
||||||
|
className="woocommerce-recommended-shipping-extensions"
|
||||||
|
dismissOptionName="woocommerce_settings_shipping_recommendations_hidden"
|
||||||
|
>
|
||||||
|
<DismissableListHeading>
|
||||||
|
<Text variant="title.small" as="p" size="20" lineHeight="28px">
|
||||||
|
{ __( 'Recommended shipping solutions', 'woocommerce-admin' ) }
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
className="woocommerce-recommended-shipping__header-heading"
|
||||||
|
variant="caption"
|
||||||
|
as="p"
|
||||||
|
size="12"
|
||||||
|
lineHeight="16px"
|
||||||
|
>
|
||||||
|
{ __(
|
||||||
|
'We recommend adding one of the following shipping extensions to your store. The extension will be installed and activated for you when you click "Get started".',
|
||||||
|
'woocommerce-admin'
|
||||||
|
) }
|
||||||
|
</Text>
|
||||||
|
</DismissableListHeading>
|
||||||
|
<ul className="woocommerce-list">
|
||||||
|
{ Children.map( children, ( item ) => (
|
||||||
|
<li className="woocommerce-list__item">{ item }</li>
|
||||||
|
) ) }
|
||||||
|
</ul>
|
||||||
|
<CardFooter>
|
||||||
|
<Button
|
||||||
|
className="woocommerce-recommended-shipping-extensions__more_options_cta"
|
||||||
|
href="https://woocommerce.com/product-category/woocommerce-extensions/shipping-methods/?utm_source=shipping_recommendations"
|
||||||
|
target="_blank"
|
||||||
|
isTertiary
|
||||||
|
>
|
||||||
|
{ __( 'See more options', 'woocommerce-admin' ) }
|
||||||
|
<VisuallyHidden>
|
||||||
|
{ __( '(opens in a new tab)', 'woocommerce-admin' ) }
|
||||||
|
</VisuallyHidden>
|
||||||
|
<ExternalIcon size={ 18 } />
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</DismissableList>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ShippingRecommendations: React.FC = () => {
|
||||||
|
const [ pluginsBeingSetup, setupPlugin ] = useInstallPlugin();
|
||||||
|
|
||||||
|
const activePlugins = useSelect< string[] >( ( select ) =>
|
||||||
|
select( PLUGINS_STORE_NAME ).getActivePlugins()
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( activePlugins.includes( 'woocommerce-services' ) ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ShippingRecommendationsList>
|
||||||
|
<WooCommerceServicesItem
|
||||||
|
pluginsBeingSetup={ pluginsBeingSetup }
|
||||||
|
onSetupClick={ setupPlugin }
|
||||||
|
/>
|
||||||
|
</ShippingRecommendationsList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ShippingRecommendations;
|
|
@ -0,0 +1,88 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { act, render, screen, waitFor } from '@testing-library/react';
|
||||||
|
import { useDispatch, useSelect } from '@wordpress/data';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import ShippingRecommendations from '../shipping-recommendations';
|
||||||
|
|
||||||
|
jest.mock( '@wordpress/data', () => ( {
|
||||||
|
...jest.requireActual( '@wordpress/data' ),
|
||||||
|
useSelect: jest.fn(),
|
||||||
|
useDispatch: jest.fn(),
|
||||||
|
} ) );
|
||||||
|
jest.mock( '../../settings-recommendations/dismissable-list', () => ( {
|
||||||
|
DismissableList: ( { children } ) => children,
|
||||||
|
DismissableListHeading: ( { children } ) => children,
|
||||||
|
} ) );
|
||||||
|
jest.mock( '../../lib/notices', () => ( {
|
||||||
|
createNoticesFromResponse: () => null,
|
||||||
|
} ) );
|
||||||
|
|
||||||
|
describe( 'ShippingRecommendations', () => {
|
||||||
|
beforeEach( () => {
|
||||||
|
useSelect.mockImplementation( ( fn ) =>
|
||||||
|
fn( () => ( {
|
||||||
|
getActivePlugins: () => [],
|
||||||
|
isJetpackConnected: () => false,
|
||||||
|
} ) )
|
||||||
|
);
|
||||||
|
useDispatch.mockReturnValue( {
|
||||||
|
installAndActivatePlugins: () => Promise.resolve(),
|
||||||
|
createSuccessNotice: () => null,
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should not render when WCS is already installed', () => {
|
||||||
|
useSelect.mockImplementation( ( fn ) =>
|
||||||
|
fn( () => ( {
|
||||||
|
getActivePlugins: () => [ 'woocommerce-services' ],
|
||||||
|
} ) )
|
||||||
|
);
|
||||||
|
render( <ShippingRecommendations /> );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText( 'Woocommerce Shipping' )
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'should render WCS when not installed', () => {
|
||||||
|
render( <ShippingRecommendations /> );
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText( 'Woocommerce Shipping' )
|
||||||
|
).toBeInTheDocument();
|
||||||
|
} );
|
||||||
|
|
||||||
|
it( 'allows to install WCS', async () => {
|
||||||
|
const installAndActivatePluginsMock = jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue( undefined );
|
||||||
|
const successNoticeMock = jest.fn();
|
||||||
|
useDispatch.mockReturnValue( {
|
||||||
|
installAndActivatePlugins: installAndActivatePluginsMock,
|
||||||
|
isJetpackConnected: () => false,
|
||||||
|
createSuccessNotice: successNoticeMock,
|
||||||
|
} );
|
||||||
|
render( <ShippingRecommendations /> );
|
||||||
|
|
||||||
|
expect( installAndActivatePluginsMock ).not.toHaveBeenCalled();
|
||||||
|
expect( successNoticeMock ).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
act( () => {
|
||||||
|
userEvent.click( screen.getByText( 'Get started' ) );
|
||||||
|
} );
|
||||||
|
|
||||||
|
expect( installAndActivatePluginsMock ).toHaveBeenCalled();
|
||||||
|
await waitFor( () => {
|
||||||
|
expect( successNoticeMock ).toHaveBeenCalledWith(
|
||||||
|
'🎉 WooCommerce Shipping is installed!',
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
} );
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120"><path fill="#7d57a4" d="M0 0h120v120H0z"/><path fill="#fff" d="M67.48 53.55c-1.19-.26-2.33.42-3.43 2.03-.87 1.26-1.45 2.56-1.74 3.91-.16.77-.24 1.58-.24 2.41 0 .97.19 1.96.58 2.99.48 1.26 1.13 1.96 1.93 2.12.8.16 1.69-.19 2.66-1.06 1.22-1.09 2.06-2.72 2.51-4.88.16-.77.24-1.58.24-2.41 0-.97-.19-1.96-.58-2.99-.48-1.25-1.12-1.96-1.93-2.12zm20.62 0c-1.19-.26-2.33.42-3.43 2.03-.87 1.26-1.45 2.56-1.74 3.91-.16.77-.24 1.58-.24 2.41 0 .97.19 1.96.58 2.99.48 1.26 1.13 1.96 1.93 2.12.8.16 1.69-.19 2.66-1.06 1.22-1.09 2.06-2.72 2.51-4.88.16-.77.24-1.58.24-2.41 0-.97-.19-1.96-.58-2.99-.48-1.25-1.12-1.96-1.93-2.12z"/><path fill="#fff" d="M92.76 40H27.24c-4.14 0-7.5 3.36-7.5 7.5v24.98c0 4.14 3.36 7.5 7.5 7.5h31.04l14.19 7.9-3.22-7.9h23.5c4.14 0 7.5-3.36 7.5-7.5V47.5c.01-4.14-3.35-7.5-7.49-7.5zM52.74 72.91c.06.84-.07 1.55-.38 2.16-.4.74-.98 1.13-1.75 1.19-.87.06-1.73-.35-2.6-1.22-3.06-3.14-5.49-7.81-7.28-14-2.12 4.21-3.71 7.37-4.75 9.48-1.93 3.72-3.59 5.62-4.97 5.72-.9.06-1.66-.69-2.29-2.26-1.69-4.3-3.5-12.63-5.44-24.97-.13-.86.05-1.6.52-2.21.47-.61 1.16-.95 2.06-1.02 1.67-.12 2.63.67 2.88 2.36 1.03 6.86 2.14 12.69 3.31 17.48l7.21-13.72c.66-1.24 1.48-1.9 2.47-1.97 1.44-.1 2.35.82 2.71 2.76.82 4.36 1.86 8.11 3.12 11.25.86-8.35 2.31-14.39 4.34-18.11.48-.9 1.21-1.39 2.17-1.46.77-.05 1.46.16 2.08.65.62.49.95 1.12 1 1.89.04.58-.07 1.1-.32 1.57-1.28 2.38-2.34 6.34-3.18 11.89-.82 5.34-1.13 9.53-.91 12.54zm20.2-5.16c-1.96 3.28-4.54 4.92-7.72 4.92-.58 0-1.18-.07-1.79-.19-2.32-.48-4.07-1.75-5.26-3.81-1.06-1.8-1.59-3.97-1.59-6.52 0-3.38.85-6.47 2.56-9.27 2-3.28 4.57-4.92 7.72-4.92.58 0 1.17.07 1.79.19 2.32.48 4.07 1.75 5.26 3.81 1.06 1.77 1.59 3.93 1.59 6.47-.01 3.38-.86 6.48-2.56 9.32zm20.62 0c-1.96 3.28-4.54 4.92-7.72 4.92-.58 0-1.17-.07-1.78-.19-2.32-.48-4.07-1.75-5.26-3.81-1.06-1.8-1.59-3.97-1.59-6.52 0-3.38.85-6.47 2.56-9.27 2-3.28 4.57-4.92 7.72-4.92.58 0 1.17.07 1.78.19 2.32.48 4.07 1.75 5.26 3.81 1.06 1.77 1.59 3.93 1.59 6.47 0 3.38-.86 6.48-2.56 9.32z"/></svg>
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,6 @@
|
||||||
|
.woocommerce-services-item {
|
||||||
|
&__logo {
|
||||||
|
width: 36px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* External dependencies
|
||||||
|
*/
|
||||||
|
import { __ } from '@wordpress/i18n';
|
||||||
|
import { useSelect, useDispatch } from '@wordpress/data';
|
||||||
|
import { Button, ExternalLink } from '@wordpress/components';
|
||||||
|
import { Pill } from '@woocommerce/components';
|
||||||
|
import { PLUGINS_STORE_NAME } from '@woocommerce/data';
|
||||||
|
import { getAdminLink, getSetting } from '@woocommerce/wc-admin-settings';
|
||||||
|
/**
|
||||||
|
* Internal dependencies
|
||||||
|
*/
|
||||||
|
import './woocommerce-services-item.scss';
|
||||||
|
import WooIcon from './woo-icon.svg';
|
||||||
|
|
||||||
|
const WooCommerceServicesItem: React.FC< {
|
||||||
|
pluginsBeingSetup: Array< string >;
|
||||||
|
onSetupClick: ( slugs: string[] ) => PromiseLike< void >;
|
||||||
|
} > = ( { onSetupClick, pluginsBeingSetup } ) => {
|
||||||
|
const wcAdminAssetUrl = getSetting( 'wcAdminAssetUrl', '' );
|
||||||
|
const { createSuccessNotice } = useDispatch( 'core/notices' );
|
||||||
|
|
||||||
|
const isSiteConnectedToJetpack = useSelect( ( select ) =>
|
||||||
|
select( PLUGINS_STORE_NAME ).isJetpackConnected()
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetupClick = () => {
|
||||||
|
onSetupClick( [ 'woocommerce-services' ] ).then( () => {
|
||||||
|
const actions = [];
|
||||||
|
if ( ! isSiteConnectedToJetpack ) {
|
||||||
|
actions.push( {
|
||||||
|
url: getAdminLink( 'plugins.php' ),
|
||||||
|
label: __(
|
||||||
|
'Finish the setup by connecting your store to Jetpack.',
|
||||||
|
'woocommerce-admin'
|
||||||
|
),
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
createSuccessNotice(
|
||||||
|
__(
|
||||||
|
'🎉 WooCommerce Shipping is installed!',
|
||||||
|
'woocommerce-admin'
|
||||||
|
),
|
||||||
|
{
|
||||||
|
actions,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="woocommerce-list__item-inner woocommerce-services-item">
|
||||||
|
<div className="woocommerce-list__item-before">
|
||||||
|
<img
|
||||||
|
className="woocommerce-services-item__logo"
|
||||||
|
src={ WooIcon }
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="woocommerce-list__item-text">
|
||||||
|
<span className="woocommerce-list__item-title">
|
||||||
|
{ __( 'Woocommerce Shipping', 'woocommerce-admin' ) }
|
||||||
|
<Pill>{ __( 'Recommended', 'woocommerce-admin' ) }</Pill>
|
||||||
|
</span>
|
||||||
|
<span className="woocommerce-list__item-content">
|
||||||
|
{ __(
|
||||||
|
'Print USPS and DHL Express labels straight from your WooCommerce dashboard and save on shipping.',
|
||||||
|
'woocommerce-admin'
|
||||||
|
) }
|
||||||
|
<br />
|
||||||
|
<ExternalLink href="https://woocommerce.com/woocommerce-shipping/">
|
||||||
|
{ __( 'Learn more', 'woocommerce-admin' ) }
|
||||||
|
</ExternalLink>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="woocommerce-list__item-after">
|
||||||
|
<Button
|
||||||
|
isSecondary
|
||||||
|
onClick={ handleSetupClick }
|
||||||
|
isBusy={ pluginsBeingSetup.includes(
|
||||||
|
'woocommerce-services'
|
||||||
|
) }
|
||||||
|
disabled={ pluginsBeingSetup.length > 0 }
|
||||||
|
>
|
||||||
|
{ __( 'Get started', 'woocommerce-admin' ) }
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WooCommerceServicesItem;
|
Loading…
Reference in New Issue