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
|
||||
*/
|
||||
import { PaymentRecommendations } from '../payments';
|
||||
import { ShippingRecommendations } from '../shipping';
|
||||
import { EmbeddedBodyProps } from './embedded-body-props';
|
||||
import './style.scss';
|
||||
|
||||
|
@ -21,6 +22,7 @@ function isWPPage(
|
|||
|
||||
const EMBEDDED_BODY_COMPONENT_LIST: React.ElementType[] = [
|
||||
PaymentRecommendations,
|
||||
ShippingRecommendations,
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export type EmbeddedBodyProps = {
|
||||
page: string;
|
||||
tab: string;
|
||||
zone_id?: 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