Add "Create a new campaign" modal (#37044)
This commit is contained in:
commit
7bf2cedc5c
|
@ -8,4 +8,5 @@ export const TYPES = {
|
|||
RECEIVE_RECOMMENDED_CHANNELS_ERROR:
|
||||
'RECEIVE_RECOMMENDED_CHANNELS_ERROR' as const,
|
||||
RECEIVE_CAMPAIGNS: 'RECEIVE_CAMPAIGNS' as const,
|
||||
RECEIVE_CAMPAIGN_TYPES: 'RECEIVE_CAMPAIGN_TYPES' as const,
|
||||
};
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { TYPES } from './action-types';
|
||||
import { isApiFetchError } from './guards';
|
||||
import {
|
||||
ApiFetchError,
|
||||
RegisteredChannel,
|
||||
RecommendedChannel,
|
||||
Campaign,
|
||||
CampaignType,
|
||||
} from './types';
|
||||
|
||||
export const receiveRegisteredChannelsSuccess = (
|
||||
|
@ -75,10 +77,29 @@ export const receiveCampaigns = ( response: CampaignsResponse ) => {
|
|||
};
|
||||
};
|
||||
|
||||
export const receiveCampaignTypes = (
|
||||
data: Array< CampaignType > | ApiFetchError
|
||||
) => {
|
||||
if ( isApiFetchError( data ) ) {
|
||||
return {
|
||||
type: TYPES.RECEIVE_CAMPAIGN_TYPES,
|
||||
payload: data,
|
||||
error: true as const,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: TYPES.RECEIVE_CAMPAIGN_TYPES,
|
||||
payload: data,
|
||||
error: false as const,
|
||||
};
|
||||
};
|
||||
|
||||
export type Action = ReturnType<
|
||||
| typeof receiveRegisteredChannelsSuccess
|
||||
| typeof receiveRegisteredChannelsError
|
||||
| typeof receiveRecommendedChannelsSuccess
|
||||
| typeof receiveRecommendedChannelsError
|
||||
| typeof receiveCampaigns
|
||||
| typeof receiveCampaignTypes
|
||||
>;
|
||||
|
|
|
@ -25,6 +25,10 @@ const initialState = {
|
|||
pages: undefined,
|
||||
total: undefined,
|
||||
},
|
||||
campaignTypes: {
|
||||
data: undefined,
|
||||
error: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
export const reducer: Reducer< State, Action > = (
|
||||
|
@ -80,6 +84,18 @@ export const reducer: Reducer< State, Action > = (
|
|||
},
|
||||
};
|
||||
|
||||
case TYPES.RECEIVE_CAMPAIGN_TYPES:
|
||||
return {
|
||||
...state,
|
||||
campaignTypes: action.error
|
||||
? {
|
||||
error: action.payload,
|
||||
}
|
||||
: {
|
||||
data: action.payload,
|
||||
},
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -12,12 +12,14 @@ import {
|
|||
receiveRecommendedChannelsSuccess,
|
||||
receiveRecommendedChannelsError,
|
||||
receiveCampaigns,
|
||||
receiveCampaignTypes,
|
||||
} from './actions';
|
||||
import { awaitResponseJson } from './controls';
|
||||
import {
|
||||
RegisteredChannel,
|
||||
RecommendedChannel,
|
||||
Campaign,
|
||||
CampaignType,
|
||||
ApiFetchError,
|
||||
} from './types';
|
||||
import { API_NAMESPACE } from './constants';
|
||||
|
@ -114,3 +116,17 @@ export function* getCampaigns( page: number, perPage: number ) {
|
|||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function* getCampaignTypes() {
|
||||
try {
|
||||
const data: CampaignType[] = yield apiFetch( {
|
||||
path: `${ API_NAMESPACE }/campaign-types`,
|
||||
} );
|
||||
|
||||
yield receiveCampaignTypes( data );
|
||||
} catch ( error ) {
|
||||
if ( isApiFetchError( error ) ) {
|
||||
yield receiveCampaignTypes( error );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,3 +17,7 @@ export const getRecommendedChannels = ( state: State ) => {
|
|||
export const getCampaigns = ( state: State ) => {
|
||||
return state.campaigns;
|
||||
};
|
||||
|
||||
export const getCampaignTypes = ( state: State ) => {
|
||||
return state.campaignTypes;
|
||||
};
|
||||
|
|
|
@ -72,8 +72,26 @@ export type CampaignsState = {
|
|||
total?: number;
|
||||
};
|
||||
|
||||
export type CampaignType = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
channel: {
|
||||
slug: string;
|
||||
name: string;
|
||||
};
|
||||
create_url: string;
|
||||
icon_url: string;
|
||||
};
|
||||
|
||||
export type CampaignTypesState = {
|
||||
data?: Array< CampaignType >;
|
||||
error?: ApiFetchError;
|
||||
};
|
||||
|
||||
export type State = {
|
||||
registeredChannels: RegisteredChannelsState;
|
||||
recommendedChannels: RecommendedChannelsState;
|
||||
campaigns: CampaignsState;
|
||||
campaignTypes: CampaignTypesState;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export { useInstalledPlugins } from './useInstalledPlugins';
|
||||
export { useRegisteredChannels } from './useRegisteredChannels';
|
||||
export { useRecommendedChannels } from './useRecommendedChannels';
|
||||
export { useCampaignTypes } from './useCampaignTypes';
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { useDispatch, useSelect } from '@wordpress/data';
|
||||
import { useCallback } from '@wordpress/element';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { STORE_KEY } from '~/marketing/data-multichannel/constants';
|
||||
import {
|
||||
CampaignTypesState,
|
||||
CampaignType as APICampaignType,
|
||||
ApiFetchError,
|
||||
} from '~/marketing/data-multichannel/types';
|
||||
import { CampaignType } from '~/marketing/types/CampaignType';
|
||||
|
||||
type UseCampaignTypes = {
|
||||
loading: boolean;
|
||||
data?: Array< CampaignType >;
|
||||
error?: ApiFetchError;
|
||||
refetch: () => void;
|
||||
};
|
||||
|
||||
const convert = ( campaignType: APICampaignType ): CampaignType => {
|
||||
return {
|
||||
id: campaignType.id,
|
||||
icon: campaignType.icon_url,
|
||||
name: campaignType.name,
|
||||
description: campaignType.description,
|
||||
createUrl: campaignType.create_url,
|
||||
channelName: campaignType.channel.name,
|
||||
channelSlug: campaignType.channel.slug,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCampaignTypes = (): UseCampaignTypes => {
|
||||
const { invalidateResolution } = useDispatch( STORE_KEY );
|
||||
|
||||
const refetch = useCallback( () => {
|
||||
invalidateResolution( 'getCampaignTypes', [] );
|
||||
}, [ invalidateResolution ] );
|
||||
|
||||
return useSelect< UseCampaignTypes >(
|
||||
( select ) => {
|
||||
const { hasFinishedResolution, getCampaignTypes } =
|
||||
select( STORE_KEY );
|
||||
const campaignTypesState = getCampaignTypes< CampaignTypesState >();
|
||||
|
||||
return {
|
||||
loading: ! hasFinishedResolution( 'getCampaignTypes', [] ),
|
||||
data: campaignTypesState.data?.map( convert ),
|
||||
error: campaignTypesState.error,
|
||||
refetch,
|
||||
};
|
||||
},
|
||||
[ refetch ]
|
||||
);
|
||||
};
|
|
@ -8,12 +8,21 @@ import userEvent from '@testing-library/user-event';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { useCampaigns } from './useCampaigns';
|
||||
import { useCampaignTypes } from '~/marketing/hooks';
|
||||
import { Campaigns } from './Campaigns';
|
||||
|
||||
jest.mock( './useCampaigns', () => ( {
|
||||
useCampaigns: jest.fn(),
|
||||
} ) );
|
||||
|
||||
jest.mock( '~/marketing/hooks', () => ( {
|
||||
useCampaignTypes: jest.fn(),
|
||||
} ) );
|
||||
|
||||
jest.mock( './CreateNewCampaignModal', () => ( {
|
||||
CreateNewCampaignModal: () => <div>Mocked CreateNewCampaignModal</div>,
|
||||
} ) );
|
||||
|
||||
/**
|
||||
* Create a test campaign data object.
|
||||
*/
|
||||
|
@ -38,6 +47,9 @@ describe( 'Campaigns component', () => {
|
|||
data: undefined,
|
||||
meta: undefined,
|
||||
} );
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
loading: true,
|
||||
} );
|
||||
|
||||
const { container } = render( <Campaigns /> );
|
||||
const tablePlaceholder = container.querySelector(
|
||||
|
@ -54,6 +66,9 @@ describe( 'Campaigns component', () => {
|
|||
data: undefined,
|
||||
meta: undefined,
|
||||
} );
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
loading: false,
|
||||
} );
|
||||
|
||||
render( <Campaigns /> );
|
||||
|
||||
|
@ -71,6 +86,9 @@ describe( 'Campaigns component', () => {
|
|||
total: 0,
|
||||
},
|
||||
} );
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
loading: false,
|
||||
} );
|
||||
|
||||
render( <Campaigns /> );
|
||||
|
||||
|
@ -88,6 +106,9 @@ describe( 'Campaigns component', () => {
|
|||
total: 1,
|
||||
},
|
||||
} );
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
loading: false,
|
||||
} );
|
||||
|
||||
const { container } = render( <Campaigns /> );
|
||||
|
||||
|
@ -114,6 +135,9 @@ describe( 'Campaigns component', () => {
|
|||
total: 6,
|
||||
},
|
||||
} );
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
loading: false,
|
||||
} );
|
||||
|
||||
render( <Campaigns /> );
|
||||
|
||||
|
@ -131,4 +155,29 @@ describe( 'Campaigns component', () => {
|
|||
// Campaign info in the second page.
|
||||
expect( screen.getByText( 'Campaign 6' ) ).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'renders a "Create new campaign" button in the card header, and upon clicking, displays the "Create a new campaign" modal', async () => {
|
||||
( useCampaigns as jest.Mock ).mockReturnValue( {
|
||||
loading: false,
|
||||
error: undefined,
|
||||
data: [ createTestCampaign( '1' ) ],
|
||||
meta: {
|
||||
total: 1,
|
||||
},
|
||||
} );
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
loading: false,
|
||||
} );
|
||||
|
||||
render( <Campaigns /> );
|
||||
|
||||
await userEvent.click(
|
||||
screen.getByRole( 'button', { name: 'Create new campaign' } )
|
||||
);
|
||||
|
||||
// Mocked CreateNewCampaignModal should be displayed.
|
||||
expect(
|
||||
screen.getByText( 'Mocked CreateNewCampaignModal' )
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardBody,
|
||||
|
@ -25,6 +26,7 @@ import {
|
|||
*/
|
||||
import { CardHeaderTitle } from '~/marketing/components';
|
||||
import { useCampaigns } from './useCampaigns';
|
||||
import { CreateNewCampaignModal } from './CreateNewCampaignModal';
|
||||
import './Campaigns.scss';
|
||||
|
||||
const tableCaption = __( 'Campaigns', 'woocommerce' );
|
||||
|
@ -53,6 +55,7 @@ const perPage = 5;
|
|||
*/
|
||||
export const Campaigns = () => {
|
||||
const [ page, setPage ] = useState( 1 );
|
||||
const [ isModalOpen, setModalOpen ] = useState( false );
|
||||
const { loading, data, meta } = useCampaigns( page, perPage );
|
||||
const total = meta?.total;
|
||||
|
||||
|
@ -159,6 +162,17 @@ export const Campaigns = () => {
|
|||
<CardHeaderTitle>
|
||||
{ __( 'Campaigns', 'woocommerce' ) }
|
||||
</CardHeaderTitle>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={ () => setModalOpen( true ) }
|
||||
>
|
||||
{ __( 'Create new campaign', 'woocommerce' ) }
|
||||
</Button>
|
||||
{ isModalOpen && (
|
||||
<CreateNewCampaignModal
|
||||
onRequestClose={ () => setModalOpen( false ) }
|
||||
/>
|
||||
) }
|
||||
</CardHeader>
|
||||
{ getContent() }
|
||||
{ total && total > perPage && (
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
.woocommerce-marketing-create-campaign-modal {
|
||||
max-width: 600px;
|
||||
|
||||
.components-modal__content {
|
||||
padding-bottom: $gap;
|
||||
}
|
||||
|
||||
.woocommerce-marketing-new-campaigns {
|
||||
padding-top: $gap-large;
|
||||
padding-bottom: $gap;
|
||||
|
||||
&__question-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.components-button.is-link {
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
.woocommerce_marketing_plugin_card_body {
|
||||
padding: $gap 0;
|
||||
}
|
||||
|
||||
.woocommerce-marketing-new-campaign-type {
|
||||
padding: $gap 0;
|
||||
|
||||
&__name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: #555d66;
|
||||
}
|
||||
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.woocommerce-marketing-add-channels {
|
||||
padding-top: $gap;
|
||||
border-top: solid 1px $gray-300;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
useCampaignTypes,
|
||||
useRecommendedChannels,
|
||||
useRegisteredChannels,
|
||||
} from '~/marketing/hooks';
|
||||
import { CreateNewCampaignModal } from './CreateNewCampaignModal';
|
||||
|
||||
jest.mock( '@woocommerce/components', () => {
|
||||
const originalModule = jest.requireActual( '@woocommerce/components' );
|
||||
|
||||
return {
|
||||
__esModule: true,
|
||||
...originalModule,
|
||||
Spinner: () => <div data-testid="spinner">Spinner</div>,
|
||||
};
|
||||
} );
|
||||
|
||||
jest.mock( '~/marketing/hooks', () => ( {
|
||||
useCampaignTypes: jest.fn(),
|
||||
useRecommendedChannels: jest.fn(),
|
||||
useRegisteredChannels: jest.fn(),
|
||||
} ) );
|
||||
|
||||
const google = {
|
||||
id: 'google-ads',
|
||||
icon: 'https://woocommerce.com/wp-content/uploads/2021/06/woo-GoogleListingsAds-jworee.png',
|
||||
name: 'Google Ads',
|
||||
description:
|
||||
'Boost your product listings with a campaign that is automatically optimized to meet your goals.',
|
||||
createUrl:
|
||||
'https://wc1.test/wp-admin/admin.php?page=wc-admin&path=/google/dashboard&subpath=/campaigns/create',
|
||||
channelName: 'Google Listings and Ads',
|
||||
channelSlug: 'google-listings-and-ads',
|
||||
};
|
||||
|
||||
const pinterest = {
|
||||
title: 'Pinterest for WooCommerce',
|
||||
description:
|
||||
'Grow your business on Pinterest! Use this official plugin to allow shoppers to Pin products while browsing your store, track conversions, and advertise on Pinterest.',
|
||||
url: 'https://woocommerce.com/products/pinterest-for-woocommerce/?utm_source=marketingtab&utm_medium=product&utm_campaign=wcaddons',
|
||||
direct_install: true,
|
||||
icon: 'https://woocommerce.com/wp-content/plugins/wccom-plugins/marketing-tab-rest-api/icons/pinterest.svg',
|
||||
product: 'pinterest-for-woocommerce',
|
||||
plugin: 'pinterest-for-woocommerce/pinterest-for-woocommerce.php',
|
||||
categories: [ 'marketing' ],
|
||||
subcategories: [ { slug: 'sales-channels', name: 'Sales channels' } ],
|
||||
tags: [
|
||||
{
|
||||
slug: 'built-by-woocommerce',
|
||||
name: 'Built by WooCommerce',
|
||||
},
|
||||
],
|
||||
show_extension_promotions: true,
|
||||
};
|
||||
|
||||
const amazon = {
|
||||
title: 'Amazon, eBay & Walmart Integration for WooCommerce',
|
||||
description:
|
||||
'Convert Woocommerce into a fully-featured omnichannel commerce platform, leveraging powerful automation and real-time sync to connect your brand with millions of new customers on the world\u2019s largest online marketplaces.',
|
||||
url: 'https://woocommerce.com/products/amazon-ebay-integration/?utm_source=marketingtab&utm_medium=product&utm_campaign=wcaddons',
|
||||
direct_install: false,
|
||||
icon: 'https://woocommerce.com/wp-content/plugins/wccom-plugins/marketing-tab-rest-api/icons/amazon-ebay.svg',
|
||||
product: 'amazon-ebay-integration',
|
||||
plugin: 'woocommerce-amazon-ebay-integration/woocommerce-amazon-ebay-integration.php',
|
||||
categories: [ 'marketing' ],
|
||||
subcategories: [ { slug: 'sales-channels', name: 'Sales channels' } ],
|
||||
tags: [],
|
||||
};
|
||||
|
||||
describe( 'CreateNewCampaignModal component', () => {
|
||||
it( 'renders new campaign types with recommended channels', async () => {
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
data: [ google ],
|
||||
} );
|
||||
( useRecommendedChannels as jest.Mock ).mockReturnValue( {
|
||||
data: [ pinterest, amazon ],
|
||||
} );
|
||||
( useRegisteredChannels as jest.Mock ).mockReturnValue( {
|
||||
refetch: jest.fn(),
|
||||
} );
|
||||
render( <CreateNewCampaignModal onRequestClose={ () => {} } /> );
|
||||
|
||||
expect( screen.getByText( 'Google Ads' ) ).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Boost your product listings with a campaign that is automatically optimized to meet your goals.'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Click button to expand recommended channels section.
|
||||
await userEvent.click(
|
||||
screen.getByRole( 'button', {
|
||||
name: 'Add channels for other campaign types',
|
||||
} )
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText( 'Pinterest for WooCommerce' )
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Amazon, eBay & Walmart Integration for WooCommerce'
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
} );
|
||||
|
||||
it( 'does not render recommended channels section when there are no recommended channels', async () => {
|
||||
( useCampaignTypes as jest.Mock ).mockReturnValue( {
|
||||
data: [ google ],
|
||||
} );
|
||||
( useRecommendedChannels as jest.Mock ).mockReturnValue( {
|
||||
data: [],
|
||||
} );
|
||||
( useRegisteredChannels as jest.Mock ).mockReturnValue( {
|
||||
refetch: jest.fn(),
|
||||
} );
|
||||
render( <CreateNewCampaignModal onRequestClose={ () => {} } /> );
|
||||
|
||||
// The expand button should not be there.
|
||||
expect(
|
||||
screen.queryByRole( 'button', {
|
||||
name: 'Add channels for other campaign types',
|
||||
} )
|
||||
).not.toBeInTheDocument();
|
||||
} );
|
||||
} );
|
|
@ -0,0 +1,156 @@
|
|||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
import { useState } from '@wordpress/element';
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
Icon,
|
||||
Flex,
|
||||
FlexBlock,
|
||||
FlexItem,
|
||||
} from '@wordpress/components';
|
||||
import { chevronUp, chevronDown, external } from '@wordpress/icons';
|
||||
import classnames from 'classnames';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import {
|
||||
useRecommendedChannels,
|
||||
useCampaignTypes,
|
||||
useRegisteredChannels,
|
||||
} from '~/marketing/hooks';
|
||||
import { SmartPluginCardBody } from '~/marketing/components';
|
||||
import './CreateNewCampaignModal.scss';
|
||||
|
||||
const isExternalURL = ( url: string ) =>
|
||||
new URL( url ).origin !== location.origin;
|
||||
|
||||
/**
|
||||
* Props for CreateNewCampaignModal, which is based on Modal.Props.
|
||||
*
|
||||
* Modal's title and children props are omitted because they are specified within the component
|
||||
* and not needed to be specified by the consumer.
|
||||
*/
|
||||
type CreateCampaignModalProps = Omit< Modal.Props, 'title' | 'children' >;
|
||||
|
||||
export const CreateNewCampaignModal = ( props: CreateCampaignModalProps ) => {
|
||||
const { className, ...restProps } = props;
|
||||
const [ collapsed, setCollapsed ] = useState( true );
|
||||
const { data: campaignTypes, refetch: refetchCampaignTypes } =
|
||||
useCampaignTypes();
|
||||
const { refetch: refetchRegisteredChannels } = useRegisteredChannels();
|
||||
const { data: recommendedChannels } = useRecommendedChannels();
|
||||
|
||||
const refetch = () => {
|
||||
refetchCampaignTypes();
|
||||
refetchRegisteredChannels();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
{ ...restProps }
|
||||
className={ classnames(
|
||||
className,
|
||||
'woocommerce-marketing-create-campaign-modal'
|
||||
) }
|
||||
title={ __( 'Create a new campaign', 'woocommerce' ) }
|
||||
>
|
||||
<div className="woocommerce-marketing-new-campaigns">
|
||||
<div className="woocommerce-marketing-new-campaigns__question-label">
|
||||
{ !! campaignTypes?.length
|
||||
? __(
|
||||
'Where would you like to promote your products?',
|
||||
'woocommerce'
|
||||
)
|
||||
: __( 'No campaign types found.', 'woocommerce' ) }
|
||||
</div>
|
||||
{ campaignTypes?.map( ( el ) => (
|
||||
<Flex
|
||||
key={ el.id }
|
||||
className="woocommerce-marketing-new-campaign-type"
|
||||
gap={ 4 }
|
||||
>
|
||||
<FlexItem>
|
||||
<img
|
||||
src={ el.icon }
|
||||
alt={ el.name }
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
</FlexItem>
|
||||
<FlexBlock>
|
||||
<Flex direction="column" gap={ 1 }>
|
||||
<FlexItem className="woocommerce-marketing-new-campaign-type__name">
|
||||
{ el.name }
|
||||
</FlexItem>
|
||||
<FlexItem className="woocommerce-marketing-new-campaign-type__description">
|
||||
{ el.description }
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
</FlexBlock>
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant="secondary"
|
||||
href={ el.createUrl }
|
||||
target={
|
||||
isExternalURL( el.createUrl )
|
||||
? '_blank'
|
||||
: '_self'
|
||||
}
|
||||
>
|
||||
<Flex gap={ 1 }>
|
||||
<FlexItem>
|
||||
{ __( 'Create', 'woocommerce' ) }
|
||||
</FlexItem>
|
||||
{ isExternalURL( el.createUrl ) && (
|
||||
<FlexItem>
|
||||
<Icon
|
||||
icon={ external }
|
||||
size={ 16 }
|
||||
/>
|
||||
</FlexItem>
|
||||
) }
|
||||
</Flex>
|
||||
</Button>
|
||||
</FlexItem>
|
||||
</Flex>
|
||||
) ) }
|
||||
</div>
|
||||
{ !! recommendedChannels?.length && (
|
||||
<div className="woocommerce-marketing-add-channels">
|
||||
<Flex direction="column">
|
||||
<FlexItem>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={ () => setCollapsed( ! collapsed ) }
|
||||
>
|
||||
{ __(
|
||||
'Add channels for other campaign types',
|
||||
'woocommerce'
|
||||
) }
|
||||
<Icon
|
||||
icon={ collapsed ? chevronDown : chevronUp }
|
||||
size={ 24 }
|
||||
/>
|
||||
</Button>
|
||||
</FlexItem>
|
||||
{ ! collapsed && (
|
||||
<FlexItem>
|
||||
{ recommendedChannels.map( ( el ) => (
|
||||
<SmartPluginCardBody
|
||||
key={ el.plugin }
|
||||
plugin={ el }
|
||||
onInstalledAndActivated={ refetch }
|
||||
/>
|
||||
) ) }
|
||||
</FlexItem>
|
||||
) }
|
||||
</Flex>
|
||||
</div>
|
||||
) }
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export { CreateNewCampaignModal } from './CreateNewCampaignModal';
|
|
@ -12,6 +12,7 @@ import { CenteredSpinner } from '~/marketing/components';
|
|||
import {
|
||||
useRegisteredChannels,
|
||||
useRecommendedChannels,
|
||||
useCampaignTypes,
|
||||
} from '~/marketing/hooks';
|
||||
import { getAdminSetting } from '~/utils/admin-settings';
|
||||
import { Campaigns } from './Campaigns';
|
||||
|
@ -22,26 +23,37 @@ import { LearnMarketing } from './LearnMarketing';
|
|||
import './MarketingOverviewMultichannel.scss';
|
||||
|
||||
export const MarketingOverviewMultichannel: React.FC = () => {
|
||||
const {
|
||||
loading: loadingCampaignTypes,
|
||||
data: dataCampaignTypes,
|
||||
refetch: refetchCampaignTypes,
|
||||
} = useCampaignTypes();
|
||||
const {
|
||||
loading: loadingRegistered,
|
||||
data: dataRegistered,
|
||||
refetch,
|
||||
refetch: refetchRegisteredChannels,
|
||||
} = useRegisteredChannels();
|
||||
const { loading: loadingRecommended, data: dataRecommended } =
|
||||
useRecommendedChannels();
|
||||
const { currentUserCan } = useUser();
|
||||
|
||||
const shouldShowExtensions =
|
||||
getAdminSetting( 'allowMarketplaceSuggestions', false ) &&
|
||||
currentUserCan( 'install_plugins' );
|
||||
|
||||
if (
|
||||
( loadingCampaignTypes && ! dataCampaignTypes ) ||
|
||||
( loadingRegistered && ! dataRegistered ) ||
|
||||
( loadingRecommended && ! dataRecommended )
|
||||
) {
|
||||
return <CenteredSpinner />;
|
||||
}
|
||||
|
||||
const shouldShowExtensions =
|
||||
getAdminSetting( 'allowMarketplaceSuggestions', false ) &&
|
||||
currentUserCan( 'install_plugins' );
|
||||
|
||||
const refetch = () => {
|
||||
refetchCampaignTypes();
|
||||
refetchRegisteredChannels();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="woocommerce-marketing-overview-multichannel">
|
||||
{ !! dataRegistered?.length && <Campaigns /> }
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export type CampaignType = {
|
||||
id: string;
|
||||
icon: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createUrl: string;
|
||||
channelName: string;
|
||||
channelSlug: string;
|
||||
};
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add "Create a new campaign" modal in Campaigns card in Multichannel Marketing page.
|
Loading…
Reference in New Issue