Use themes REST API - CYS intro page (#40552)
* Use themes REST API * Add changefile(s) from automation for the following project(s): woocommerce * Use browse_all.href and set default value to /:admin-dir/themes.php * Fix lint * Fix tests * Fix tests --------- Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Chi-Hsuan Huang <chihsuan.tw@gmail.com>
This commit is contained in:
parent
fbb294f543
commit
a99a52947e
|
@ -11,7 +11,7 @@ import {
|
|||
} from '@woocommerce/navigation';
|
||||
import { OPTIONS_STORE_NAME } from '@woocommerce/data';
|
||||
import { dispatch } from '@wordpress/data';
|
||||
import { addQueryArgs } from '@wordpress/url';
|
||||
import { getAdminLink } from '@woocommerce/settings';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
|
@ -64,8 +64,10 @@ const redirectToWooHome = () => {
|
|||
window.location.href = getNewPath( {}, '/', {} );
|
||||
};
|
||||
|
||||
const redirectToThemes = () => {
|
||||
window.location.href = addQueryArgs( 'themes.php' );
|
||||
const redirectToThemes = ( _context: customizeStoreStateMachineContext ) => {
|
||||
window.location.href =
|
||||
_context?.intro?.themeData?._links?.browse_all?.href ??
|
||||
getAdminLink( 'themes.php' );
|
||||
};
|
||||
|
||||
const markTaskComplete = async () => {
|
||||
|
@ -116,7 +118,14 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
context: {
|
||||
intro: {
|
||||
hasErrors: false,
|
||||
themeCards: [] as ThemeCard[],
|
||||
themeData: {
|
||||
themes: [] as ThemeCard[],
|
||||
_links: {
|
||||
browse_all: {
|
||||
href: getAdminLink( 'themes.php' ),
|
||||
},
|
||||
},
|
||||
},
|
||||
activeTheme: '',
|
||||
activeThemeHasMods: false,
|
||||
customizeStoreTaskCompleted: false,
|
||||
|
@ -184,7 +193,7 @@ export const customizeStoreStateMachineDefinition = createMachine( {
|
|||
onDone: {
|
||||
target: 'intro',
|
||||
actions: [
|
||||
'assignThemeCards',
|
||||
'assignThemeData',
|
||||
'assignActiveThemeHasMods',
|
||||
'assignCustomizeStoreCompleted',
|
||||
],
|
||||
|
|
|
@ -8,20 +8,28 @@ import { recordEvent } from '@woocommerce/tracks';
|
|||
* Internal dependencies
|
||||
*/
|
||||
import { customizeStoreStateMachineEvents } from '..';
|
||||
import { customizeStoreStateMachineContext } from '../types';
|
||||
import { ThemeCard } from './types';
|
||||
import {
|
||||
customizeStoreStateMachineContext,
|
||||
RecommendThemesAPIResponse,
|
||||
} from '../types';
|
||||
import { events } from './';
|
||||
|
||||
export const assignThemeCards = assign<
|
||||
export const assignThemeData = assign<
|
||||
customizeStoreStateMachineContext,
|
||||
customizeStoreStateMachineEvents // this is actually the wrong type for the event but I still don't know how to type this properly
|
||||
>( {
|
||||
intro: ( context, event ) => {
|
||||
const themeCards = (
|
||||
event as DoneInvokeEvent< { themeCards: ThemeCard[] } >
|
||||
).data.themeCards;
|
||||
const apiResponse = (
|
||||
event as DoneInvokeEvent< {
|
||||
themeData: RecommendThemesAPIResponse;
|
||||
} >
|
||||
).data.themeData;
|
||||
|
||||
// type coercion workaround for now
|
||||
return { ...context.intro, themeCards };
|
||||
return {
|
||||
...context.intro,
|
||||
themeData: apiResponse,
|
||||
};
|
||||
},
|
||||
} );
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ type ModalStatus = keyof typeof MODAL_COMPONENTS;
|
|||
|
||||
export const Intro: CustomizeStoreComponent = ( { sendEvent, context } ) => {
|
||||
const {
|
||||
intro: { themeCards, activeThemeHasMods, customizeStoreTaskCompleted },
|
||||
intro: { themeData, activeThemeHasMods, customizeStoreTaskCompleted },
|
||||
} = context;
|
||||
|
||||
const isJetpackOffline = false;
|
||||
|
@ -157,16 +157,16 @@ export const Intro: CustomizeStoreComponent = ( { sendEvent, context } ) => {
|
|||
</p>
|
||||
|
||||
<div className="woocommerce-customize-store-theme-cards">
|
||||
{ themeCards?.map( ( themeCard ) => (
|
||||
{ themeData.themes?.map( ( theme ) => (
|
||||
<ThemeCard
|
||||
key={ themeCard.slug }
|
||||
slug={ themeCard.slug }
|
||||
description={ themeCard.description }
|
||||
image={ themeCard.image }
|
||||
name={ themeCard.name }
|
||||
colorPalettes={ themeCard.colorPalettes }
|
||||
link={ themeCard?.link }
|
||||
isActive={ themeCard.isActive }
|
||||
key={ theme.slug }
|
||||
slug={ theme.slug }
|
||||
description={ theme.description }
|
||||
thumbnail_url={ theme.thumbnail_url }
|
||||
name={ theme.name }
|
||||
color_palettes={ theme.color_palettes }
|
||||
link_url={ theme?.link_url }
|
||||
is_active={ theme.is_active }
|
||||
/>
|
||||
) ) }
|
||||
</div>
|
||||
|
|
|
@ -7,63 +7,16 @@ import { resolveSelect } from '@wordpress/data';
|
|||
import { ONBOARDING_STORE_NAME } from '@woocommerce/data';
|
||||
// @ts-ignore No types for this exist yet.
|
||||
import { store as coreStore } from '@wordpress/core-data';
|
||||
import apiFetch from '@wordpress/api-fetch';
|
||||
|
||||
// placeholder xstate async service that returns a set of theme cards
|
||||
export const fetchThemeCards = async () => {
|
||||
return [
|
||||
{
|
||||
slug: 'twentytwentyone',
|
||||
name: 'Twenty Twenty One',
|
||||
description: 'The default theme for WordPress.',
|
||||
isActive: true,
|
||||
image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/pub/twentytwentyone/screenshot.png',
|
||||
colorPalettes: [],
|
||||
},
|
||||
{
|
||||
slug: 'twentytwenty',
|
||||
name: 'Twenty Twenty',
|
||||
description: 'The previous default theme for WordPress.',
|
||||
image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/pub/twentytwenty/screenshot.png',
|
||||
colorPalettes: [],
|
||||
},
|
||||
{
|
||||
slug: 'tsubaki',
|
||||
name: 'Tsubaki',
|
||||
description:
|
||||
'Tsubaki puts the spotlight on your products and your customers. This theme leverages WooCommerce to provide you with intuitive product navigation and the patterns you need to master digital merchandising.',
|
||||
image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/premium/tsubaki/screenshot.png',
|
||||
colorPalettes: [],
|
||||
},
|
||||
{
|
||||
slug: 'winkel',
|
||||
name: 'Winkel',
|
||||
description:
|
||||
'Winkel is a minimal, product-focused theme featuring Payments block. Its clean, cool look combined with a simple layout makes it perfect for showcasing fashion items – clothes, shoes, and accessories.',
|
||||
image: 'https://i0.wp.com/s2.wp.com/wp-content/themes/pub/winkel/screenshot.png',
|
||||
colorPalettes: [
|
||||
{
|
||||
title: 'Default',
|
||||
primary: '#ffffff',
|
||||
secondary: '#676767',
|
||||
},
|
||||
{
|
||||
title: 'Charcoal',
|
||||
primary: '#1f2527',
|
||||
secondary: '#9fd3e8',
|
||||
},
|
||||
{
|
||||
title: 'Rainforest',
|
||||
primary: '#eef4f7',
|
||||
secondary: '#35845d',
|
||||
},
|
||||
{
|
||||
title: 'Ruby Wine',
|
||||
primary: '#ffffff',
|
||||
secondary: '#c8133e',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const themes = await apiFetch( {
|
||||
path: '/wc-admin/onboarding/themes/recommended',
|
||||
method: 'GET',
|
||||
} );
|
||||
|
||||
return themes;
|
||||
};
|
||||
|
||||
export const fetchIntroData = async () => {
|
||||
|
@ -98,11 +51,11 @@ export const fetchIntroData = async () => {
|
|||
hasModifiedPages;
|
||||
const customizeStoreTaskCompleted = ( await getTask( 'customize-store' ) )
|
||||
?.isComplete;
|
||||
const themeCards = await fetchThemeCards();
|
||||
const themeData = await fetchThemeCards();
|
||||
|
||||
return {
|
||||
activeThemeHasMods,
|
||||
customizeStoreTaskCompleted,
|
||||
themeCards,
|
||||
themeData,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -28,13 +28,21 @@ describe( 'Intro Banners', () => {
|
|||
intro: {
|
||||
hasErrors: false,
|
||||
activeTheme: '',
|
||||
themeCards: [],
|
||||
themeData: {
|
||||
themes: [],
|
||||
_links: {
|
||||
browse_all: {
|
||||
href: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
activeThemeHasMods: false,
|
||||
customizeStoreTaskCompleted: false,
|
||||
currentThemeIsAiGenerated: false,
|
||||
},
|
||||
themeConfiguration: {},
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
/>
|
||||
);
|
||||
|
@ -56,13 +64,21 @@ describe( 'Intro Banners', () => {
|
|||
intro: {
|
||||
hasErrors: false,
|
||||
activeTheme: '',
|
||||
themeCards: [],
|
||||
themeData: {
|
||||
themes: [],
|
||||
_links: {
|
||||
browse_all: {
|
||||
href: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
activeThemeHasMods: false,
|
||||
customizeStoreTaskCompleted: false,
|
||||
currentThemeIsAiGenerated: false,
|
||||
},
|
||||
themeConfiguration: {},
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -26,13 +26,21 @@ describe( 'Intro Modals', () => {
|
|||
intro: {
|
||||
hasErrors: false,
|
||||
activeTheme: '',
|
||||
themeCards: [],
|
||||
themeData: {
|
||||
themes: [],
|
||||
_links: {
|
||||
browse_all: {
|
||||
href: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
activeThemeHasMods: true,
|
||||
customizeStoreTaskCompleted: false,
|
||||
currentThemeIsAiGenerated: false,
|
||||
},
|
||||
themeConfiguration: {},
|
||||
} }
|
||||
currentState={ 'intro' }
|
||||
parentMachine={ null as unknown as AnyInterpreter }
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -13,31 +13,31 @@ import { ColorPalettes } from './color-palettes';
|
|||
export const ThemeCard = ( {
|
||||
slug,
|
||||
description,
|
||||
image,
|
||||
thumbnail_url,
|
||||
name,
|
||||
colorPalettes = [],
|
||||
link = '',
|
||||
isActive = false,
|
||||
color_palettes = [],
|
||||
link_url = '',
|
||||
is_active = false,
|
||||
}: TypeThemeCard ) => {
|
||||
return (
|
||||
<div className="theme-card" key={ slug }>
|
||||
<div>
|
||||
{ link ? (
|
||||
<Link href={ link }>
|
||||
<img src={ image } alt={ description } />
|
||||
{ link_url ? (
|
||||
<Link href={ link_url }>
|
||||
<img src={ thumbnail_url } alt={ description } />
|
||||
</Link>
|
||||
) : (
|
||||
<img src={ image } alt={ description } />
|
||||
<img src={ thumbnail_url } alt={ description } />
|
||||
) }
|
||||
</div>
|
||||
<div className="theme-card__info">
|
||||
<h2 className="theme-card__title">{ name }</h2>
|
||||
{ colorPalettes && (
|
||||
<ColorPalettes colorPalettes={ colorPalettes } />
|
||||
{ color_palettes && (
|
||||
<ColorPalettes colorPalettes={ color_palettes } />
|
||||
) }
|
||||
</div>
|
||||
<div>
|
||||
{ isActive && (
|
||||
{ is_active && (
|
||||
<span className="theme-card__active">
|
||||
{ __( 'Active theme', 'woocommerce' ) }
|
||||
</span>
|
||||
|
|
|
@ -10,8 +10,8 @@ export type ThemeCard = {
|
|||
slug: string;
|
||||
name: string;
|
||||
description: string;
|
||||
image: string;
|
||||
isActive: boolean;
|
||||
link?: string;
|
||||
colorPalettes: ColorPalette[];
|
||||
thumbnail_url: string;
|
||||
is_active: boolean;
|
||||
link_url?: string;
|
||||
color_palettes: ColorPalette[];
|
||||
};
|
||||
|
|
|
@ -20,11 +20,20 @@ export type CustomizeStoreComponentMeta = {
|
|||
component: CustomizeStoreComponent;
|
||||
};
|
||||
|
||||
export type RecommendThemesAPIResponse = {
|
||||
themes: ThemeCard[];
|
||||
_links: {
|
||||
browse_all?: {
|
||||
href: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type customizeStoreStateMachineContext = {
|
||||
themeConfiguration: Record< string, unknown >; // placeholder for theme configuration until we know what it looks like
|
||||
intro: {
|
||||
hasErrors: boolean;
|
||||
themeCards: ThemeCard[];
|
||||
themeData: RecommendThemesAPIResponse;
|
||||
activeTheme: string;
|
||||
activeThemeHasMods: boolean;
|
||||
customizeStoreTaskCompleted: boolean;
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: update
|
||||
|
||||
Use the newly added themes REST API on the CYS intro page
|
Loading…
Reference in New Issue