[CYS on core] Update the WooCommerce Extensions Themes page to include references to the CYS (#45468)

* Introduce the new 'Design your own' button in the themes screen.

* unify the SCSS margins

* Add double quotes for woocommerce-marketplace__sub-header

* Add the CYS banner to the marketplace.

* Ensure each child in a list should have a unique 'key' prop

* Update the NoAIBanner component to direct users to the CYS flow.

* Add the customizeStoreDesignUrl const

* Ditch navigateOrParent

* Add the 'Browse the WordPress.org theme directory to discover more' link.

* Ensure the warning Modal is displayed whenever the user clicks on the 'Design your own' button and their current active theme is not TT4

* Remove the unnecessary fragment

* Add changefile(s) from automation for the following project(s): woocommerce

* Fix lint.

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Patricia Hillebrandt 2024-03-12 11:03:08 +01:00 committed by GitHub
parent 0f98ffee64
commit 4933b86cc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 289 additions and 144 deletions

View File

@ -2,8 +2,9 @@
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { addQueryArgs } from '@wordpress/url';
import classNames from 'classnames';
import { Button, Modal } from '@wordpress/components';
import { Button } from '@wordpress/components';
import { getNewPath } from '@woocommerce/navigation';
import { recordEvent } from '@woocommerce/tracks';
import interpolateComponents from '@automattic/interpolate-components';
@ -16,8 +17,9 @@ import { useSelect } from '@wordpress/data';
*/
import { Intro } from '.';
import { IntroSiteIframe } from './intro-site-iframe';
import { getAdminSetting } from '~/utils/admin-settings';
import { ADMIN_URL, getAdminSetting } from '~/utils/admin-settings';
import { navigateOrParent } from '../utils';
import { ThemeSwitchWarningModal } from '~/customize-store/intro/warning-modals';
export const BaseIntroBanner = ( {
bannerTitle,
@ -217,11 +219,7 @@ export const ThemeHasModsBanner = ( {
);
};
export const NoAIBanner = ( {
sendEvent,
}: {
sendEvent: React.ComponentProps< typeof Intro >[ 'sendEvent' ];
} ) => {
export const NoAIBanner = () => {
const [ isModalOpen, setIsModalOpen ] = useState( false );
interface Theme {
stylesheet?: string;
@ -232,6 +230,10 @@ export const NoAIBanner = ( {
}, [] );
const isDefaultTheme = currentTheme?.stylesheet === 'twentytwentyfour';
const customizeStoreDesignUrl = addQueryArgs( `${ ADMIN_URL }admin.php`, {
page: 'wc-admin',
path: '/customize-store/design',
} );
return (
<>
@ -247,56 +249,16 @@ export const NoAIBanner = ( {
if ( ! isDefaultTheme ) {
setIsModalOpen( true );
} else {
sendEvent( {
type: 'DESIGN_WITHOUT_AI',
} );
window.location.href = customizeStoreDesignUrl;
}
} }
showAIDisclaimer={ false }
/>
{ isModalOpen && (
<Modal
className={
'woocommerce-customize-store__theme-switch-warning-modal'
}
title={ __(
'Are you sure you want to design a new theme?',
'woocommerce'
) }
onRequestClose={ () => setIsModalOpen( false ) }
shouldCloseOnClickOutside={ false }
>
<p>
{ __(
'Your active theme will be changed and you could lose any changes youve made to it.',
'woocommerce'
) }
</p>
<div className="woocommerce-customize-store__theme-switch-warning-modal-footer">
<Button
onClick={ () => {
setIsModalOpen( false );
} }
variant="link"
>
{ __( 'Cancel', 'woocommerce' ) }
</Button>
<Button
onClick={ () => {
sendEvent( {
type: 'DESIGN_WITHOUT_AI',
} );
setIsModalOpen( false );
recordEvent(
'customize_your_store_agree_to_theme_switch_click'
);
} }
variant="primary"
>
{ __( 'Design a new theme', 'woocommerce' ) }
</Button>
</div>
</Modal>
<ThemeSwitchWarningModal
setIsModalOpen={ setIsModalOpen }
customizeStoreDesignUrl={ customizeStoreDesignUrl }
/>
) }
</>
);

View File

@ -194,3 +194,54 @@ export const StartOverWarningModal = ( {
</Modal>
);
};
export const ThemeSwitchWarningModal = ( {
setIsModalOpen,
customizeStoreDesignUrl,
}: {
setIsModalOpen: ( arg0: boolean ) => void;
customizeStoreDesignUrl: string;
} ) => {
return (
<Modal
className={
'woocommerce-customize-store__theme-switch-warning-modal'
}
title={ __(
'Are you sure you want to design a new theme?',
'woocommerce'
) }
onRequestClose={ () => setIsModalOpen( false ) }
shouldCloseOnClickOutside={ false }
>
<p>
{ __(
'Your active theme will be changed and you could lose any changes youve made to it.',
'woocommerce'
) }
</p>
<div className="woocommerce-customize-store__theme-switch-warning-modal-footer">
<Button
onClick={ () => {
setIsModalOpen( false );
} }
variant="link"
>
{ __( 'Cancel', 'woocommerce' ) }
</Button>
<Button
onClick={ () => {
window.location.href = customizeStoreDesignUrl;
setIsModalOpen( false );
recordEvent(
'customize_your_store_agree_to_theme_switch_click'
);
} }
variant="primary"
>
{ __( 'Design a new theme', 'woocommerce' ) }
</Button>
</div>
</Modal>
);
};

View File

@ -9,7 +9,7 @@ import { useQuery } from '@woocommerce/navigation';
*/
import './content.scss';
import { Product, ProductType, SearchResultType } from '../product-list/types';
import { getAdminSetting } from '../../../utils/admin-settings';
import { getAdminSetting } from '~/utils/admin-settings';
import Discover from '../discover/discover';
import Products from '../products/products';
import SearchResults from '../search-results/search-results';

View File

@ -12,6 +12,25 @@
color: $white;
height: 270px;
}
.woocommerce-customize-store-banner {
grid-column: 1 / -1;
display: block;
width: 100%;
margin: 16px 0;
}
.woocommerce-customize-store-banner-content {
margin-top: 63px;
}
&__browse-wp-theme-directory {
margin-top: 24px;
a {
text-decoration: none;
}
}
}
@media screen and (min-width: $breakpoint-medium) {

View File

@ -1,16 +1,26 @@
/**
* External dependencies
*/
import {
createInterpolateElement,
Fragment,
useEffect,
useState,
} from '@wordpress/element';
import classnames from 'classnames';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import './product-list-content.scss';
import '~/customize-store/intro/intro.scss';
import '~/customize-store/style.scss';
import ProductCard from '../product-card/product-card';
import { Product, ProductType } from '../product-list/types';
import { appendURLParams } from '../../utils/functions';
import { getAdminSetting } from '../../../utils/admin-settings';
import { ADMIN_URL, getAdminSetting } from '~/utils/admin-settings';
import { NoAIBanner } from '~/customize-store/intro/intro-banners';
export default function ProductListContent( props: {
products: Product[];
@ -28,54 +38,115 @@ export default function ProductListContent( props: {
props.className
);
const [ columns, setColumns ] = useState( 1 );
const updateColumns = () => {
const screenWidth = window.innerWidth;
if ( screenWidth >= 1920 ) {
setColumns( 4 );
} else if ( screenWidth >= 1024 ) {
setColumns( 3 );
} else if ( screenWidth >= 769 ) {
setColumns( 2 );
} else {
setColumns( 1 );
}
};
useEffect( () => {
updateColumns();
// Update columns on screen resize to adjust for responsive layout
window.addEventListener( 'resize', updateColumns );
return () => window.removeEventListener( 'resize', updateColumns );
}, [] );
const bannerPosition = columns * 2 - 1;
return (
<div className={ classes }>
{ props.products.map( ( product, index ) => (
<ProductCard
key={ product.id }
type={ props.type }
product={ {
id: product.id,
slug: product.slug,
title: product.title,
image: product.image,
type: product.type,
icon: product.icon,
vendorName: product.vendorName,
vendorUrl: product.vendorUrl
? appendURLParams( product.vendorUrl, [
[ 'utm_source', 'extensionsscreen' ],
[ 'utm_medium', 'product' ],
[ 'utm_campaign', 'wcaddons' ],
[ 'utm_content', 'devpartner' ],
] )
: '',
price: product.price,
url: appendURLParams(
product.url,
Object.entries( {
...wccomHelperSettings.inAppPurchaseURLParams,
...( props.productGroup !== undefined
? { utm_group: props.productGroup }
: {} ),
} )
<>
<div className={ classes }>
{ props.products.map( ( product, index ) => (
<Fragment key={ product.id }>
<ProductCard
key={ product.id }
type={ props.type }
product={ {
id: product.id,
slug: product.slug,
title: product.title,
image: product.image,
type: product.type,
icon: product.icon,
vendorName: product.vendorName,
vendorUrl: product.vendorUrl
? appendURLParams( product.vendorUrl, [
[
'utm_source',
'extensionsscreen',
],
[ 'utm_medium', 'product' ],
[ 'utm_campaign', 'wcaddons' ],
[ 'utm_content', 'devpartner' ],
] )
: '',
price: product.price,
url: appendURLParams(
product.url,
Object.entries( {
...wccomHelperSettings.inAppPurchaseURLParams,
...( props.productGroup !== undefined
? { utm_group: props.productGroup }
: {} ),
} )
),
averageRating: product.averageRating,
reviewsCount: product.reviewsCount,
description: product.description,
isInstallable: product.isInstallable,
} }
tracksData={ {
position: index + 1,
...( product.label && {
label: product.label,
} ),
...( props.group && { group: props.group } ),
...( props.searchTerm && {
searchTerm: props.searchTerm,
} ),
...( props.category && {
category: props.category,
} ),
} }
/>
{ index === bannerPosition && <NoAIBanner /> }
</Fragment>
) ) }
</div>
<div
className={
'woocommerce-marketplace__browse-wp-theme-directory'
}
>
<b>{ __( 'Didnt find a theme you like?', 'woocommerce' ) }</b>
{ createInterpolateElement(
__(
' Browse the <a>WordPress.org theme directory</a> to discover more.',
'woocommerce'
),
{
a: (
// eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href={
ADMIN_URL +
'theme-install.php?search=e-commerce'
}
/>
),
averageRating: product.averageRating,
reviewsCount: product.reviewsCount,
description: product.description,
isInstallable: product.isInstallable,
} }
tracksData={ {
position: index + 1,
...( product.label && { label: product.label } ),
...( props.group && { group: props.group } ),
...( props.searchTerm && {
searchTerm: props.searchTerm,
} ),
...( props.category && { category: props.category } ),
} }
/>
) ) }
</div>
}
) }
</div>
</>
);
}

View File

@ -7,3 +7,11 @@
margin-top: 24px;
}
}
.woocommerce-marketplace__sub-header {
display: flex;
.woocommerce-marketplace__customize-your-store-button {
margin: 16px 0 6px auto;
}
}

View File

@ -2,10 +2,12 @@
* External dependencies
*/
import { __, _n, sprintf } from '@wordpress/i18n';
import { useContext } from '@wordpress/element';
import { useContext, useState } from '@wordpress/element';
import { getNewPath, navigateTo, useQuery } from '@woocommerce/navigation';
import { Button } from '@wordpress/components';
import classnames from 'classnames';
import { addQueryArgs } from '@wordpress/url';
import { useSelect } from '@wordpress/data';
/**
* Internal dependencies
@ -18,6 +20,8 @@ import ProductLoader from '../product-loader/product-loader';
import NoResults from '../product-list-content/no-results';
import { Product, ProductType, SearchResultType } from '../product-list/types';
import { MARKETPLACE_ITEMS_PER_PAGE } from '../constants';
import { ADMIN_URL } from '~/utils/admin-settings';
import { ThemeSwitchWarningModal } from '~/customize-store/intro/warning-modals';
interface ProductsProps {
categorySelector?: boolean;
@ -39,15 +43,27 @@ const LABELS = {
},
};
export default function Products( props: ProductsProps ): JSX.Element {
export default function Products( props: ProductsProps ) {
const marketplaceContextValue = useContext( MarketplaceContext );
const { isLoading } = marketplaceContextValue;
const label = LABELS[ props.type ].label;
const singularLabel = LABELS[ props.type ].singularLabel;
const query = useQuery();
const category = query?.category;
const perPage = props.perPage ?? MARKETPLACE_ITEMS_PER_PAGE;
interface Theme {
stylesheet?: string;
}
const currentTheme = useSelect( ( select ) => {
return select( 'core' ).getCurrentTheme() as Theme;
}, [] );
const isDefaultTheme = currentTheme?.stylesheet === 'twentytwentyfour';
const [ isModalOpen, setIsModalOpen ] = useState( false );
const customizeStoreDesignUrl = addQueryArgs( `${ ADMIN_URL }admin.php`, {
page: 'wc-admin',
path: '/customize-store/design',
} );
// Only show the "View all" button when on search but not showing a specific section of results.
const showAllButton = props.showAllButton ?? false;
@ -92,53 +108,28 @@ export default function Products( props: ProductsProps ): JSX.Element {
baseContainerClass + 'button-' + label
);
function content() {
if ( isLoading ) {
return (
<>
{ props.categorySelector && (
<CategorySelector type={ props.type } />
) }
<ProductLoader hasTitle={ false } type={ props.type } />
</>
);
}
if ( products.length === 0 ) {
const type =
props.type === ProductType.extension
? SearchResultType.extension
: SearchResultType.theme;
if ( products.length === 0 ) {
const type =
props.type === ProductType.extension
? SearchResultType.extension
: SearchResultType.theme;
return <NoResults type={ type } showHeading={ false } />;
}
return <NoResults type={ type } showHeading={ false } />;
}
const productListClass = classnames(
showAllButton
? 'woocommerce-marketplace__product-list-content--collapsed'
: ''
);
const productListClass = classnames(
showAllButton
? 'woocommerce-marketplace__product-list-content--collapsed'
: ''
);
if ( isLoading ) {
return (
<>
{ props.categorySelector && (
<CategorySelector type={ props.type } />
) }
<ProductListContent
products={ products }
type={ props.type }
className={ productListClass }
searchTerm={ props.searchTerm }
category={ category }
/>
{ showAllButton && (
<Button
className={ viewAllButonClassName }
variant="secondary"
text={ __( 'View all', 'woocommerce' ) }
onClick={ () => showSection( props.type ) }
/>
) }
<ProductLoader hasTitle={ false } type={ props.type } />
</>
);
}
@ -148,7 +139,46 @@ export default function Products( props: ProductsProps ): JSX.Element {
<h2 className={ productListTitleClassName }>
{ isLoading ? ' ' : title }
</h2>
{ content() }
<div className="woocommerce-marketplace__sub-header">
{ props.categorySelector && (
<CategorySelector type={ props.type } />
) }
{ props.type === 'theme' && (
<Button
className="woocommerce-marketplace__customize-your-store-button"
variant="secondary"
text={ __( 'Design your own', 'woocommerce' ) }
onClick={ () => {
if ( ! isDefaultTheme ) {
setIsModalOpen( true );
} else {
window.location.href = customizeStoreDesignUrl;
}
} }
/>
) }
</div>
{ isModalOpen && (
<ThemeSwitchWarningModal
setIsModalOpen={ setIsModalOpen }
customizeStoreDesignUrl={ customizeStoreDesignUrl }
/>
) }
<ProductListContent
products={ products }
type={ props.type }
className={ productListClass }
searchTerm={ props.searchTerm }
category={ category }
/>
{ showAllButton && (
<Button
className={ viewAllButonClassName }
variant="secondary"
text={ __( 'View all', 'woocommerce' ) }
onClick={ () => showSection( props.type ) }
/>
) }
</div>
);
}

View File

@ -0,0 +1,4 @@
Significance: major
Type: add
Update the WooCommerce Extensions Theme page to include references to the Customize Your Store flow.