Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add introduction banner card into multichannel marketing page #37110

Merged
merged 38 commits into from Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b6eec1e
Add introduction banner card to marketing page.
ecgan Dec 26, 2022
82db71c
Add close button to introduction banner card.
ecgan Dec 27, 2022
a2419db
Display buttons in introduction banner with large illustration.
ecgan Dec 27, 2022
e1a82c5
Display and dismiss introduction banner with wp_options.
ecgan Dec 27, 2022
35375c6
Rename UseIntroductionBannerType to UseIntroductionBanner.
ecgan Dec 27, 2022
86cc118
Focus on Add Channels section when users click on Add Channels button.
ecgan Dec 27, 2022
f92f4d3
Code refactor with useIsLocationHashAddChannels.
ecgan Dec 27, 2022
7c9eedb
Merge branch 'feature/34909-marketing-create-campaign-modal' into fea…
ecgan Mar 6, 2023
a9b696d
Move CreateNewCampaignModal to shared components folder.
ecgan Mar 7, 2023
0cb2fb1
Display CreateNewCampaignModal upon button click in IntroductionBanner.
ecgan Mar 7, 2023
8fe3781
Use ref instead of location hash to scroll to add channels section.
ecgan Mar 7, 2023
42ac7e4
Better logic for displaying buttons in IntroductionBanner.
ecgan Mar 7, 2023
4068808
Merge branch 'feature/34909-marketing-create-campaign-modal' into fea…
ecgan Mar 7, 2023
1a28784
Add changelog.
ecgan Mar 7, 2023
66e46d8
Run lint:css-fix.
ecgan Mar 8, 2023
6d72c2b
Fix failed test for Campaigns component.
ecgan Mar 8, 2023
baf9408
Merge branch 'feature/34909-marketing-create-campaign-modal' into fea…
ecgan Mar 12, 2023
f769a8c
Merge branch 'trunk' into feature/34904-marketing-introduction-banner
ecgan Mar 16, 2023
7cc8e06
Merge branch 'trunk' into feature/34904-marketing-introduction-banner
ecgan Mar 19, 2023
4925342
Specify dependency for useSelect in useIntroductionBanner.
ecgan Mar 19, 2023
162d790
Update option name and value to make it more indicative.
ecgan Mar 19, 2023
99c6ebf
Remove the use of isOptionsUpdating in useIntroductionBanner.
ecgan Mar 19, 2023
1b65be0
Fix useSelect dependency in useIntroductionBanner.
ecgan Mar 19, 2023
65523e3
Fix spacing for close button in IntroductionBanner.
ecgan Mar 19, 2023
155211d
Remove unneeded Icon component.
ecgan Mar 19, 2023
6b75b5d
Rename variables to make them clearer.
ecgan Mar 19, 2023
c6a95ae
Rename callback props in IntroductionBanner to make them clearer.
ecgan Mar 19, 2023
458c16b
Move useCampaigns to shared hooks directory.
ecgan Mar 19, 2023
11683be
Set default parameter values for useCampaigns.
ecgan Mar 19, 2023
ddd287c
Show Campaigns card when banner is dismissed or campaigns total is tr…
ecgan Mar 19, 2023
38370cc
Remove unneeded CSS.
ecgan Mar 19, 2023
75c11a6
Simplify CSS to have less hardcoded width value.
ecgan Mar 19, 2023
5455abc
Use useImperativeHandle instead of exposing button ref in Channels.
ecgan Mar 19, 2023
2929990
Make shouldShowCampaigns boolean type.
ecgan Mar 20, 2023
7dd7994
Make scrollIntoView center on the "Add channels" button.
ecgan Mar 20, 2023
4e38969
Use one illustration instead of two in IntroductionBanner.
ecgan Mar 20, 2023
0300078
Remove unused illustration.svg file.
ecgan Mar 20, 2023
a2b02fc
Rename illustration-large.svg to illustration.svg.
ecgan Mar 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -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';
2 changes: 2 additions & 0 deletions 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';
Expand Up @@ -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(
Expand Down
@@ -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,
};
};
Expand Up @@ -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: () => <div>Mocked CreateNewCampaignModal</div>,
} ) );
jest.mock( '~/marketing/components', () => {
const originalModule = jest.requireActual( '~/marketing/components' );

return {
__esModule: true,
...originalModule,
CreateNewCampaignModal: () => <div>Mocked CreateNewCampaignModal</div>,
};
} );

/**
* Create a test campaign data object.
Expand Down
Expand Up @@ -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' );
Expand Down
@@ -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,
Expand Down Expand Up @@ -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 (
<Card className="woocommerce-marketing-channels-card">
<CardHeader>
<CardHeaderTitle>
{ __( 'Channels', 'woocommerce' ) }
</CardHeaderTitle>
{ ! hasRegisteredChannels && (
<CardHeaderDescription>
{ __(
'Start by adding a channel to your store',
'woocommerce'
) }
</CardHeaderDescription>
) }
</CardHeader>
export const Channels = forwardRef< ChannelsRef, ChannelsProps >(
(
{ registeredChannels, recommendedChannels, onInstalledAndActivated },
ref
) => {
const hasRegisteredChannels = registeredChannels.length >= 1;

/**
* 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 );

{ /* Registered channels section. */ }
{ registeredChannels.map( ( el, idx ) => {
return (
useImperativeHandle(
ref,
() => ( {
scrollIntoAddChannels: () => {
setExpanded( true );
addChannelsButtonRef.current?.focus();
addChannelsButtonRef.current?.scrollIntoView( {
block: 'center',
} );
},
} ),
[]
);

return (
<Card className="woocommerce-marketing-channels-card">
<CardHeader>
<CardHeaderTitle>
{ __( 'Channels', 'woocommerce' ) }
</CardHeaderTitle>
{ ! hasRegisteredChannels && (
<CardHeaderDescription>
{ __(
'Start by adding a channel to your store',
'woocommerce'
) }
</CardHeaderDescription>
) }
</CardHeader>

{ /* Registered channels section. */ }
{ registeredChannels.map( ( el, idx ) => (
<Fragment key={ el.slug }>
<RegisteredChannelCardBody registeredChannel={ el } />
{ idx !== registeredChannels.length - 1 && (
<CardDivider />
) }
</Fragment>
);
} ) }
) ) }

{ /* Recommended channels section. */ }
{ recommendedChannels.length >= 1 && (
<div>
{ !! hasRegisteredChannels && (
<>
<CardDivider />
<CardBody>
<Button
variant="link"
onClick={ () => setExpanded( ! expanded ) }
>
{ __( 'Add channels', 'woocommerce' ) }
<Icon
icon={
expanded ? chevronUp : chevronDown
{ /* Recommended channels section. */ }
{ recommendedChannels.length >= 1 && (
<div>
{ !! hasRegisteredChannels && (
<>
<CardDivider />
<CardBody>
<Button
ref={ addChannelsButtonRef }
variant="link"
onClick={ () =>
setExpanded( ! expanded )
}
size={ 24 }
/>
</Button>
</CardBody>
</>
) }
{ !! expanded &&
recommendedChannels.map( ( el, idx ) => {
return (
>
{ __( 'Add channels', 'woocommerce' ) }
<Icon
icon={
expanded
? chevronUp
: chevronDown
}
size={ 24 }
/>
</Button>
</CardBody>
</>
) }
{ !! expanded &&
recommendedChannels.map( ( el, idx ) => (
<Fragment key={ el.plugin }>
<SmartPluginCardBody
plugin={ el }
Expand All @@ -110,10 +141,10 @@ export const Channels: React.FC< ChannelsProps > = ( {
<CardDivider />
) }
</Fragment>
);
} ) }
</div>
) }
</Card>
);
};
) ) }
</div>
) }
</Card>
);
}
);
@@ -1 +1,2 @@
export { Channels } from './Channels';
export type { ChannelsRef } from './Channels';
@@ -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%;
}
}
}