diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/CreateNewCampaignModal.scss b/plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/CreateNewCampaignModal.scss similarity index 100% rename from plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/CreateNewCampaignModal.scss rename to plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/CreateNewCampaignModal.scss diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/CreateNewCampaignModal.test.tsx b/plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/CreateNewCampaignModal.test.tsx similarity index 100% rename from plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/CreateNewCampaignModal.test.tsx rename to plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/CreateNewCampaignModal.test.tsx diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/CreateNewCampaignModal.tsx b/plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/CreateNewCampaignModal.tsx similarity index 100% rename from plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/CreateNewCampaignModal.tsx rename to plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/CreateNewCampaignModal.tsx diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/index.ts b/plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/index.ts similarity index 100% rename from plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/CreateNewCampaignModal/index.ts rename to plugins/woocommerce-admin/client/marketing/components/CreateNewCampaignModal/index.ts diff --git a/plugins/woocommerce-admin/client/marketing/components/index.js b/plugins/woocommerce-admin/client/marketing/components/index.js index dc971c75160..5d1c1014754 100644 --- a/plugins/woocommerce-admin/client/marketing/components/index.js +++ b/plugins/woocommerce-admin/client/marketing/components/index.js @@ -8,3 +8,4 @@ export { PluginCardBody, SmartPluginCardBody } from './PluginCardBody'; export { CardHeaderTitle } from './CardHeaderTitle'; export { CardHeaderDescription } from './CardHeaderDescription'; export { CenteredSpinner } from './CenteredSpinner'; +export { CreateNewCampaignModal } from './CreateNewCampaignModal'; diff --git a/plugins/woocommerce-admin/client/marketing/hooks/index.ts b/plugins/woocommerce-admin/client/marketing/hooks/index.ts index a61a1a28ec7..3492d275ed0 100644 --- a/plugins/woocommerce-admin/client/marketing/hooks/index.ts +++ b/plugins/woocommerce-admin/client/marketing/hooks/index.ts @@ -1,4 +1,6 @@ +export { useIntroductionBanner } from './useIntroductionBanner'; export { useInstalledPlugins } from './useInstalledPlugins'; export { useRegisteredChannels } from './useRegisteredChannels'; export { useRecommendedChannels } from './useRecommendedChannels'; export { useCampaignTypes } from './useCampaignTypes'; +export { useCampaigns } from './useCampaigns'; diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/useCampaigns.ts b/plugins/woocommerce-admin/client/marketing/hooks/useCampaigns.ts similarity index 92% rename from plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/useCampaigns.ts rename to plugins/woocommerce-admin/client/marketing/hooks/useCampaigns.ts index 01765b61b62..6b1672142b6 100644 --- a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/useCampaigns.ts +++ b/plugins/woocommerce-admin/client/marketing/hooks/useCampaigns.ts @@ -27,13 +27,10 @@ type UseCampaignsType = { /** * Custom hook to get campaigns. * - * @param page Page number. First page is `1`. - * @param perPage Page size, i.e. number of records in one page. + * @param page Page number. Default is `1`. + * @param perPage Page size, i.e. number of records in one page. Default is `5`. */ -export const useCampaigns = ( - page: number, - perPage: number -): UseCampaignsType => { +export const useCampaigns = ( page = 1, perPage = 5 ): UseCampaignsType => { const { data: channels } = useRegisteredChannels(); return useSelect( diff --git a/plugins/woocommerce-admin/client/marketing/hooks/useIntroductionBanner.ts b/plugins/woocommerce-admin/client/marketing/hooks/useIntroductionBanner.ts new file mode 100644 index 00000000000..0eebcb471a1 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketing/hooks/useIntroductionBanner.ts @@ -0,0 +1,45 @@ +/** + * External dependencies + */ +import { useDispatch, useSelect } from '@wordpress/data'; +import { OPTIONS_STORE_NAME } from '@woocommerce/data'; +import { recordEvent } from '@woocommerce/tracks'; + +type UseIntroductionBanner = { + loading: boolean; + isIntroductionBannerDismissed: boolean; + dismissIntroductionBanner: () => void; +}; + +const OPTION_NAME_BANNER_DISMISSED = + 'woocommerce_marketing_overview_multichannel_banner_dismissed'; +const OPTION_VALUE_YES = 'yes'; + +export const useIntroductionBanner = (): UseIntroductionBanner => { + const { updateOptions } = useDispatch( OPTIONS_STORE_NAME ); + + const dismissIntroductionBanner = () => { + updateOptions( { + [ OPTION_NAME_BANNER_DISMISSED ]: OPTION_VALUE_YES, + } ); + recordEvent( 'marketing_multichannel_banner_dismissed', {} ); + }; + + const { loading, data } = useSelect( ( select ) => { + const { getOption, hasFinishedResolution } = + select( OPTIONS_STORE_NAME ); + + return { + loading: ! hasFinishedResolution( 'getOption', [ + OPTION_NAME_BANNER_DISMISSED, + ] ), + data: getOption( OPTION_NAME_BANNER_DISMISSED ), + }; + }, [] ); + + return { + loading, + isIntroductionBannerDismissed: data === OPTION_VALUE_YES, + dismissIntroductionBanner, + }; +}; diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.test.tsx b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.test.tsx index 66513850819..aaa59396728 100644 --- a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.test.tsx +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.test.tsx @@ -7,21 +7,23 @@ import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ -import { useCampaigns } from './useCampaigns'; -import { useCampaignTypes } from '~/marketing/hooks'; +import { useCampaignTypes, useCampaigns } from '~/marketing/hooks'; import { Campaigns } from './Campaigns'; -jest.mock( './useCampaigns', () => ( { - useCampaigns: jest.fn(), -} ) ); - jest.mock( '~/marketing/hooks', () => ( { + useCampaigns: jest.fn(), useCampaignTypes: jest.fn(), } ) ); -jest.mock( './CreateNewCampaignModal', () => ( { - CreateNewCampaignModal: () =>
Mocked CreateNewCampaignModal
, -} ) ); +jest.mock( '~/marketing/components', () => { + const originalModule = jest.requireActual( '~/marketing/components' ); + + return { + __esModule: true, + ...originalModule, + CreateNewCampaignModal: () =>
Mocked CreateNewCampaignModal
, + }; +} ); /** * Create a test campaign data object. diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.tsx b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.tsx index 2861c79a5d9..d3fdcf71bca 100644 --- a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.tsx +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Campaigns/Campaigns.tsx @@ -24,9 +24,11 @@ import { /** * Internal dependencies */ -import { CardHeaderTitle } from '~/marketing/components'; -import { useCampaigns } from './useCampaigns'; -import { CreateNewCampaignModal } from './CreateNewCampaignModal'; +import { + CardHeaderTitle, + CreateNewCampaignModal, +} from '~/marketing/components'; +import { useCampaigns } from '~/marketing/hooks'; import './Campaigns.scss'; const tableCaption = __( 'Campaigns', 'woocommerce' ); diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/Channels.tsx b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/Channels.tsx index 992726a366f..7427f3f15f1 100644 --- a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/Channels.tsx +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/Channels.tsx @@ -1,7 +1,13 @@ /** * External dependencies */ -import { Fragment, useState } from '@wordpress/element'; +import { + Fragment, + useState, + forwardRef, + useImperativeHandle, + useRef, +} from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { Card, @@ -32,72 +38,97 @@ type ChannelsProps = { onInstalledAndActivated?: () => void; }; -export const Channels: React.FC< ChannelsProps > = ( { - registeredChannels, - recommendedChannels, - onInstalledAndActivated, -} ) => { - const hasRegisteredChannels = registeredChannels.length >= 1; - +export type ChannelsRef = { /** - * State to collapse / expand the recommended channels. - * Initial state is expanded if there are no registered channels in first page load. + * Scroll into the "Add channels" section in the card. + * The section will be expanded, and the "Add channels" button will be in focus. */ - const [ expanded, setExpanded ] = useState( ! hasRegisteredChannels ); + scrollIntoAddChannels: () => void; +}; - return ( - - - - { __( 'Channels', 'woocommerce' ) } - - { ! hasRegisteredChannels && ( - - { __( - 'Start by adding a channel to your store', - 'woocommerce' - ) } - - ) } - +export const Channels = forwardRef< ChannelsRef, ChannelsProps >( + ( + { registeredChannels, recommendedChannels, onInstalledAndActivated }, + ref + ) => { + const hasRegisteredChannels = registeredChannels.length >= 1; - { /* Registered channels section. */ } - { registeredChannels.map( ( el, idx ) => { - return ( + /** + * State to collapse / expand the recommended channels. + * Initial state is expanded if there are no registered channels in first page load. + */ + const [ expanded, setExpanded ] = useState( ! hasRegisteredChannels ); + const addChannelsButtonRef = useRef< HTMLButtonElement >( null ); + + useImperativeHandle( + ref, + () => ( { + scrollIntoAddChannels: () => { + setExpanded( true ); + addChannelsButtonRef.current?.focus(); + addChannelsButtonRef.current?.scrollIntoView( { + block: 'center', + } ); + }, + } ), + [] + ); + + return ( + + + + { __( 'Channels', 'woocommerce' ) } + + { ! hasRegisteredChannels && ( + + { __( + 'Start by adding a channel to your store', + 'woocommerce' + ) } + + ) } + + + { /* Registered channels section. */ } + { registeredChannels.map( ( el, idx ) => ( { idx !== registeredChannels.length - 1 && ( ) } - ); - } ) } + ) ) } - { /* Recommended channels section. */ } - { recommendedChannels.length >= 1 && ( -
- { !! hasRegisteredChannels && ( - <> - - - - - - ) } - { !! expanded && - recommendedChannels.map( ( el, idx ) => { - return ( + > + { __( 'Add channels', 'woocommerce' ) } + + + + + ) } + { !! expanded && + recommendedChannels.map( ( el, idx ) => ( = ( { ) } - ); - } ) } -
- ) } -
- ); -}; + ) ) } + + ) } +
+ ); + } +); diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/index.ts b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/index.ts index da0d9c56072..f0e4fcc3762 100644 --- a/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/index.ts +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/Channels/index.ts @@ -1 +1,2 @@ export { Channels } from './Channels'; +export type { ChannelsRef } from './Channels'; diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/IntroductionBanner.scss b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/IntroductionBanner.scss new file mode 100644 index 00000000000..99ac6dc8ad4 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/IntroductionBanner.scss @@ -0,0 +1,54 @@ +.woocommerce-marketing-introduction-banner { + & > div { + display: flex; + flex-wrap: wrap; + } + + .woocommerce-marketing-introduction-banner-content { + flex: 1 0; + margin: 32px 20px 32px 40px; + + .woocommerce-marketing-introduction-banner-title { + font-size: 20px; + line-height: 28px; + margin-bottom: $gap-smaller; + } + + .woocommerce-marketing-introduction-banner-features { + color: $gray-700; + + svg { + fill: $studio-woocommerce-purple-50; + } + } + + .woocommerce-marketing-introduction-banner-buttons { + margin-top: $gap; + } + } + + .woocommerce-marketing-introduction-banner-illustration { + flex: 0 0 270px; + background: linear-gradient(90deg, rgba(247, 237, 247, 0) 5.31%, rgba(196, 152, 217, 0.12) 77.75%), + linear-gradient(90deg, rgba(247, 237, 247, 0) 22%, rgba(196, 152, 217, 0.12) 84.6%); + + .woocommerce-marketing-introduction-banner-image-placeholder { + width: 100%; + height: 100%; + background: center / contain no-repeat; + } + + .woocommerce-marketing-introduction-banner-close-button { + position: absolute; + top: $gap-small; + right: $gap; + padding: 0; + } + + img { + display: block; + width: 100%; + height: 100%; + } + } +} diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/IntroductionBanner.tsx b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/IntroductionBanner.tsx new file mode 100644 index 00000000000..829152f03dd --- /dev/null +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/IntroductionBanner.tsx @@ -0,0 +1,152 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useState } from '@wordpress/element'; +import { Card, Flex, FlexItem, FlexBlock, Button } from '@wordpress/components'; +import { Icon, trendingUp, megaphone, closeSmall } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import { CreateNewCampaignModal } from '~/marketing/components'; +import { + useRegisteredChannels, + useRecommendedChannels, +} from '~/marketing/hooks'; +import './IntroductionBanner.scss'; +import wooIconUrl from './woo.svg'; +import illustrationUrl from './illustration.svg'; + +type IntroductionBannerProps = { + onDismissClick: () => void; + onAddChannelsClick: () => void; +}; + +export const IntroductionBanner = ( { + onDismissClick, + onAddChannelsClick, +}: IntroductionBannerProps ) => { + const [ isModalOpen, setModalOpen ] = useState( false ); + const { data: dataRegistered } = useRegisteredChannels(); + const { data: dataRecommended } = useRecommendedChannels(); + + const showCreateCampaignButton = !! dataRegistered?.length; + + /** + * Boolean to display the "Add channels" button in the introduction banner. + * + * This depends on the number of registered channels, + * because if there are no registered channels, + * the Channels card will not have the "Add channels" toggle button, + * and it does not make sense to display the "Add channels" button in this introduction banner + * that will do nothing upon click. + * + * If there are registered channels and recommended channels, + * the Channels card will display the "Add channels" toggle button, + * and clicking on the "Add channels" button in this introduction banner + * will scroll to the button in Channels card. + */ + const showAddChannelsButton = + !! dataRegistered?.length && !! dataRecommended?.length; + + return ( + +
+
+ { __( + 'Reach new customers and increase sales without leaving WooCommerce', + 'woocommerce' + ) } +
+ + + + + + { __( + 'Reach customers on other sales channels', + 'woocommerce' + ) } + + + + + + + + { __( + 'Advertise with marketing campaigns', + 'woocommerce' + ) } + + + + + + { + + { __( 'Built by WooCommerce', 'woocommerce' ) } + + + + + { ( showCreateCampaignButton || showAddChannelsButton ) && ( + + { showCreateCampaignButton && ( + + ) } + { showAddChannelsButton && ( + + ) } + + ) } + { isModalOpen && ( + setModalOpen( false ) } + /> + ) } +
+
+ +
+
+ + ); +}; diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/illustration.svg b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/illustration.svg new file mode 100644 index 00000000000..3a00d63416c --- /dev/null +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/illustration.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/index.ts b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/index.ts new file mode 100644 index 00000000000..8ae35a6ca33 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/index.ts @@ -0,0 +1 @@ +export { IntroductionBanner } from './IntroductionBanner'; diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/woo.svg b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/woo.svg new file mode 100644 index 00000000000..fb4a4205043 --- /dev/null +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/IntroductionBanner/woo.svg @@ -0,0 +1,15 @@ + + +WooCommerce Logo + + + +image/svg+xml + + + + + + + + diff --git a/plugins/woocommerce-admin/client/marketing/overview-multichannel/MarketingOverviewMultichannel.tsx b/plugins/woocommerce-admin/client/marketing/overview-multichannel/MarketingOverviewMultichannel.tsx index 3922d1cba32..94784f01468 100644 --- a/plugins/woocommerce-admin/client/marketing/overview-multichannel/MarketingOverviewMultichannel.tsx +++ b/plugins/woocommerce-admin/client/marketing/overview-multichannel/MarketingOverviewMultichannel.tsx @@ -1,6 +1,7 @@ /** * External dependencies */ +import { useRef } from '@wordpress/element'; import { useUser } from '@woocommerce/data'; /** @@ -10,19 +11,28 @@ import '~/marketing/data'; import '~/marketing/data-multichannel'; import { CenteredSpinner } from '~/marketing/components'; import { + useIntroductionBanner, + useCampaigns, useRegisteredChannels, useRecommendedChannels, useCampaignTypes, } from '~/marketing/hooks'; import { getAdminSetting } from '~/utils/admin-settings'; +import { IntroductionBanner } from './IntroductionBanner'; import { Campaigns } from './Campaigns'; -import { Channels } from './Channels'; +import { Channels, ChannelsRef } from './Channels'; import { InstalledExtensions } from './InstalledExtensions'; import { DiscoverTools } from './DiscoverTools'; import { LearnMarketing } from './LearnMarketing'; import './MarketingOverviewMultichannel.scss'; export const MarketingOverviewMultichannel: React.FC = () => { + const { + loading: loadingIntroductionBanner, + isIntroductionBannerDismissed, + dismissIntroductionBanner, + } = useIntroductionBanner(); + const { loading: loadingCampaigns, meta: metaCampaigns } = useCampaigns(); const { loading: loadingCampaignTypes, data: dataCampaignTypes, @@ -36,8 +46,11 @@ export const MarketingOverviewMultichannel: React.FC = () => { const { loading: loadingRecommended, data: dataRecommended } = useRecommendedChannels(); const { currentUserCan } = useUser(); + const channelsRef = useRef< ChannelsRef >( null ); if ( + loadingIntroductionBanner || + ( loadingCampaigns && metaCampaigns?.total === undefined ) || ( loadingCampaignTypes && ! dataCampaignTypes ) || ( loadingRegistered && ! dataRegistered ) || ( loadingRecommended && ! dataRecommended ) @@ -45,6 +58,11 @@ export const MarketingOverviewMultichannel: React.FC = () => { return ; } + const shouldShowCampaigns = !! ( + dataRegistered?.length && + ( isIntroductionBannerDismissed || metaCampaigns?.total ) + ); + const shouldShowExtensions = getAdminSetting( 'allowMarketplaceSuggestions', false ) && currentUserCan( 'install_plugins' ); @@ -56,10 +74,19 @@ export const MarketingOverviewMultichannel: React.FC = () => { return (
- { !! dataRegistered?.length && } + { ! isIntroductionBannerDismissed && ( + { + channelsRef.current?.scrollIntoAddChannels(); + } } + /> + ) } + { shouldShowCampaigns && } { !! ( dataRegistered && dataRecommended ) && !! ( dataRegistered.length || dataRecommended.length ) && (