Add core profiler user profile page (#38328)

This commit is contained in:
Chi-Hsuan Huang 2023-05-22 11:21:16 +08:00 committed by GitHub
parent 6d4014042b
commit 4547922f3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1509 additions and 122 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: add
Add onKeyDown and readOnlyWhenClosed options to experimentalSelectControl

View File

@ -105,4 +105,6 @@ Name | Type | Default | Description
`onInputChange` | Function | `() => null` | A callback that fires when the user input has changed `onInputChange` | Function | `() => null` | A callback that fires when the user input has changed
`onRemove` | Function | `() => null` | A callback that fires when a selected item has been removed `onRemove` | Function | `() => null` | A callback that fires when a selected item has been removed
`onSelect` | Function | `() => null` | A callback that fires when an item has been selected `onSelect` | Function | `() => null` | A callback that fires when an item has been selected
`selected` | Array or Item | `undefined` | An array of selected items or a single selected item `selected` | Array or Item | `undefined` | An array of selected items or a single selected item\
`onKeyDown` | Function | `() => null` | A callback that fires when a key is pressed
`readOnlyWhenClosed` | Boolean | `false` | Whether the input should be read-only when the menu is closed

View File

@ -58,6 +58,7 @@ export type SelectControlProps< ItemType > = {
) => void; ) => void;
onRemove?: ( item: ItemType ) => void; onRemove?: ( item: ItemType ) => void;
onSelect?: ( selected: ItemType ) => void; onSelect?: ( selected: ItemType ) => void;
onKeyDown?: ( e: KeyboardEvent ) => void;
onFocus?: ( data: { inputValue: string } ) => void; onFocus?: ( data: { inputValue: string } ) => void;
stateReducer?: ( stateReducer?: (
state: UseComboboxState< ItemType | null >, state: UseComboboxState< ItemType | null >,
@ -70,6 +71,8 @@ export type SelectControlProps< ItemType > = {
inputProps?: GetInputPropsOptions; inputProps?: GetInputPropsOptions;
suffix?: JSX.Element | null; suffix?: JSX.Element | null;
showToggleButton?: boolean; showToggleButton?: boolean;
readOnlyWhenClosed?: boolean;
/** /**
* This is a feature already implemented in downshift@7.0.0 through the * This is a feature already implemented in downshift@7.0.0 through the
* reducer. In order for us to use it this prop is added temporarily until * reducer. In order for us to use it this prop is added temporarily until
@ -118,6 +121,7 @@ function SelectControl< ItemType = DefaultItemType >( {
onRemove = () => null, onRemove = () => null,
onSelect = () => null, onSelect = () => null,
onFocus = () => null, onFocus = () => null,
onKeyDown = () => null,
stateReducer = ( state, actionAndChanges ) => actionAndChanges.changes, stateReducer = ( state, actionAndChanges ) => actionAndChanges.changes,
placeholder, placeholder,
selected, selected,
@ -126,6 +130,7 @@ function SelectControl< ItemType = DefaultItemType >( {
inputProps = {}, inputProps = {},
suffix = <SuffixIcon icon={ chevronDown } />, suffix = <SuffixIcon icon={ chevronDown } />,
showToggleButton = false, showToggleButton = false,
readOnlyWhenClosed = true,
__experimentalOpenMenuOnFocus = false, __experimentalOpenMenuOnFocus = false,
}: SelectControlProps< ItemType > ) { }: SelectControlProps< ItemType > ) {
const [ isFocused, setIsFocused ] = useState( false ); const [ isFocused, setIsFocused ] = useState( false );
@ -247,7 +252,7 @@ function SelectControl< ItemType = DefaultItemType >( {
onRemove( item ); onRemove( item );
}; };
const isReadOnly = ! isOpen && ! isFocused; const isReadOnly = readOnlyWhenClosed && ! isOpen && ! isFocused;
const selectedItemTags = multiple ? ( const selectedItemTags = multiple ? (
<SelectedItems <SelectedItems
@ -305,6 +310,7 @@ function SelectControl< ItemType = DefaultItemType >( {
setIsFocused( false ); setIsFocused( false );
} }
}, },
onKeyDown,
placeholder, placeholder,
disabled, disabled,
...inputProps, ...inputProps,

View File

@ -5,6 +5,10 @@ import { setLocaleData } from '@wordpress/i18n';
import { registerStore } from '@wordpress/data'; import { registerStore } from '@wordpress/data';
import 'regenerator-runtime/runtime'; import 'regenerator-runtime/runtime';
// Mock the config module to avoid errors like:
// Core Error: Could not find config value for key ${ key }. Please make sure that if you need it then it has a default value assigned in config/_shared.json.
jest.mock( '@automattic/calypso-config' );
// Due to the dependency @wordpress/compose which introduces the use of // Due to the dependency @wordpress/compose which introduces the use of
// ResizeObserver this global mock is required for some tests to work. // ResizeObserver this global mock is required for some tests to work.
global.ResizeObserver = require( 'resize-observer-polyfill' ); global.ResizeObserver = require( 'resize-observer-polyfill' );

View File

@ -0,0 +1,74 @@
.woocommerce-profiler-choice-container {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: $gap-large $gap;
border: 1px solid $gray-200;
border-radius: 2px;
width: 100%;
cursor: pointer;
&[data-selected] {
border: 2px solid var(--wp-admin-theme-color);
border-radius: 4px;
}
&:focus-visible {
border: 2px solid var(--wp-admin-theme-color);
padding: $gap-large $gap;
}
}
.woocommerce-profiler-choice {
display: flex;
flex-direction: row;
align-items: center;
.woocommerce-profiler-choice-input {
opacity: 0;
position: absolute;
}
label {
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: $gray-900;
display: flex;
position: relative;
}
input + label::before {
content: '';
width: 20px;
height: 20px;
border: 1px solid $gray-600;
border-radius: 16px;
margin-right: $gap;
display: inline-block;
}
input[data-selected] + label::before {
border-color: var(--wp-admin-theme-color);
}
input[data-selected] + label::after {
background-color: var(--wp-admin-theme-color);
align-self: center;
border-radius: 50%;
content: '';
position: absolute;
width: 14px;
height: 14px;
left: 4px;
}
}
.woocommerce-profiler-choice-sub-options {
margin-top: $gap-large;
width: 100%;
> div {
width: 100%;
}
}

View File

@ -0,0 +1,75 @@
/**
* External dependencies
*/
import classNames from 'classnames';
/**
* Internal dependencies
*/
import './choice.scss';
type Props = {
className?: string;
selected: boolean;
title: string;
name: string;
value: string;
onChange: ( value: string ) => void;
subOptionsComponent?: React.ReactNode;
};
export const Choice = ( {
className,
selected,
title,
name,
value,
onChange,
subOptionsComponent = null,
}: Props ) => {
const changeHandler = () => {
onChange( value );
};
const inputId = 'woocommerce-' + value.replace( /_/g, '-' );
return (
<div
role="radio"
className={ classNames(
'woocommerce-profiler-choice-container',
className
) }
onClick={ changeHandler }
onKeyDown={ ( e ) => {
if ( e.key === 'Enter' ) {
changeHandler();
}
} }
data-selected={ selected ? selected : null }
tabIndex={ 0 }
>
<div className="woocommerce-profiler-choice">
<input
className="woocommerce-profiler-choice-input"
id={ inputId }
name={ name }
type="radio"
value={ value }
checked={ !! selected }
onChange={ changeHandler }
data-selected={ selected ? selected : null }
// Stop the input from being focused when the parent div is clicked
tabIndex={ -1 }
></input>
<label htmlFor={ inputId } className="choice__title">
{ title }
</label>
</div>
{ selected && subOptionsComponent && (
<div className="woocommerce-profiler-choice-sub-options">
{ subOptionsComponent }
</div>
) }
</div>
);
};

View File

@ -1,7 +1,11 @@
.woocommerce-profiler-heading { .woocommerce-profiler-heading {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: 48px; margin-bottom: 48px;
width: 100%;
max-width: 550px; max-width: 550px;
width: 100%;
.woocommerce-profiler-heading__title { .woocommerce-profiler-heading__title {
font-style: normal; font-style: normal;
@ -31,4 +35,30 @@
color: $gray-800; color: $gray-800;
} }
} }
&.woocommerce-profiler__stepper-heading {
margin-top: 72px;
.woocommerce-profiler-heading__title {
margin-bottom: 0;
}
.woocommerce-profiler-heading__subtitle {
margin: 12px 0 0;
}
@media (max-width: #{ ($break-mobile) }) {
margin-top: 52px 0 40px;
.woocommerce-profiler-heading__title {
font-size: 32px;
line-height: 40px;
text-align: left;
}
.woocommerce-profiler-heading__subtitle {
text-align: left;
font-size: 16px;
color: $gray-700;
}
}
}
} }

View File

@ -0,0 +1,113 @@
.woocommerce-experimental-select-control {
&:hover,
ul:hover,
.woocommerce-experimental-select-control__input:hover,
.components-popover:hover,
.woocommerce-experimental-select-control__combo-box-wrapper:hover {
cursor: pointer;
}
&.has-selected-items {
.woocommerce-experimental-select-control__combox-box {
position: absolute;
width: 0;
z-index: -1;
}
}
.woocommerce-experimental-select-control__combox-box .woocommerce-experimental-select-control__input {
font-size: 13px;
font-weight: 400;
line-height: 16px;
}
.components-popover.woocommerce-experimental-select-control__popover-menu {
padding-bottom: 20px;
}
.woocommerce-experimental-select-control__selected-item {
background: $gray-100;
padding: 2px 5px 2px 8px;
}
.woocommerce-tag .woocommerce-tag__text {
background: none !important;
font-size: 12px;
line-height: 16px;
padding: 0 2px;
}
.woocommerce-experimental-select-control__combo-box-wrapper {
transition: box-shadow 0.25s linear;
border: 1px solid #bbb;
border-radius: 2px;
}
.woocommerce-experimental-select-control__popover-menu {
border: none;
left: 0 !important;
width: 100%;
}
.woocommerce-experimental-select-control__popover-menu-container {
overflow-y: initial;
list-style: none;
padding: 0;
width: 100% !important;
}
.components-popover {
transform: none !important;
.components-popover__content {
transform: none;
width: 100%;
border: 1px solid #ccc;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
border-radius: 2px;
overflow-x: hidden !important;
ul li {
&:hover {
background-color: #eff2fd !important;
cursor: pointer;
}
.components-base-control__field {
margin-bottom: 0 !important;
}
.components-checkbox-control__label {
cursor: pointer;
font-size: 13px;
font-weight: normal !important;
pointer-events: none;
}
.components-checkbox-control__input-container {
border: 1px solid #757575;
border-radius: 2px;
height: 20px !important;
padding: 1px;
width: 20px !important;
.components-checkbox-control__input[type='checkbox'] {
border: none;
height: 16px !important;
width: 16px !important;
&:checked {
border-radius: 2px;
}
}
svg {
height: 20px;
top: -1px;
width: 22px;
}
}
}
}
}
}

View File

@ -0,0 +1,97 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import {
__experimentalSelectControl as SelectControl,
selectControlStateChangeTypes,
} from '@woocommerce/components';
/**
* Internal dependencies
*/
import { renderMenu } from './render-menu';
import './multiple-selector.scss';
type Props = {
options: Array< { label: string; value: string } >;
onSelect: (
selectedOptions: Array< { label: string; value: string } >
) => void;
selectedOptions?: Array< { label: string; value: string } >;
placeholder?: string;
onOpenClose?: ( isOpen: boolean ) => void;
};
export const MultipleSelector = ( {
options,
onSelect,
selectedOptions = [],
placeholder = __( 'Select platforms', 'woocommerce' ),
onOpenClose = () => {},
}: Props ) => {
return (
<SelectControl
label=""
multiple
__experimentalOpenMenuOnFocus
readOnlyWhenClosed={ false }
items={ options }
getFilteredItems={ ( allItems ) => allItems }
selected={ selectedOptions }
inputProps={ {
'aria-readonly': true,
'aria-label': __(
'Use up and down arrow keys to navigate',
'woocommerce'
),
} }
onKeyDown={ ( e ) => {
if ( e.key.length <= 1 ) {
e.preventDefault();
return false;
}
} }
placeholder={ selectedOptions.length ? '' : placeholder }
stateReducer={ ( state, actionAndChanges ) => {
const { changes, type } = actionAndChanges;
switch ( type ) {
case selectControlStateChangeTypes.ControlledPropUpdatedSelectedItem:
return {
...changes,
inputValue: state.inputValue,
};
case selectControlStateChangeTypes.ItemClick:
return {
...changes,
isOpen: true,
inputValue: state.inputValue,
highlightedIndex: state.highlightedIndex,
};
default:
return changes;
}
} }
onSelect={ ( item ) => {
if ( ! item ) {
return;
}
const exist = selectedOptions.find(
( existingItem ) => existingItem.value === item.value
);
const updatedPlatforms = exist
? selectedOptions.filter(
( existingItem ) =>
existingItem.value !== item.value
)
: [ ...selectedOptions, item ];
onSelect( updatedPlatforms );
} }
onRemove={ ( item ) =>
onSelect( selectedOptions.filter( ( i ) => i !== item ) )
}
>
{ renderMenu( { selectedOptions, onOpenClose } ) }
</SelectControl>
);
};

View File

@ -0,0 +1,62 @@
/**
* External dependencies
*/
import { CheckboxControl } from '@wordpress/components';
import { useEffect } from '@wordpress/element';
import {
__experimentalSelectControlMenu as Menu,
__experimentalSelectControlMenuItem as MenuItem,
} from '@woocommerce/components';
import { ChildrenProps } from '@woocommerce/components/build-types/experimental-select-control/types';
type Props = {
selectedOptions: Array< { label: string; value: string } >;
onOpenClose: ( isOpen: boolean ) => void;
};
export const renderMenu =
( { selectedOptions, onOpenClose }: Props ) =>
( {
items,
highlightedIndex,
isOpen,
getItemProps,
getMenuProps,
}: ChildrenProps< {
label: string;
value: string;
} > ) => {
useEffect( () => {
onOpenClose( isOpen );
}, [ isOpen ] );
return (
<Menu
isOpen={ isOpen }
getMenuProps={ getMenuProps }
scrollIntoViewOnOpen={ true }
>
{ items.map( ( item, menuIndex ) => {
const isSelected = selectedOptions.includes( item );
return (
<MenuItem
key={ `${ item.value }` }
index={ menuIndex }
item={ item }
getItemProps={ getItemProps }
isActive={ highlightedIndex === menuIndex }
activeStyle={ {
backgroundColor: '#f6f7f7',
} }
>
<CheckboxControl
onChange={ () => {} }
checked={ isSelected }
label={ item.label }
/>
</MenuItem>
);
} ) }
</Menu>
);
};

View File

@ -22,7 +22,12 @@ import { initializeExPlat } from '@woocommerce/explat';
* Internal dependencies * Internal dependencies
*/ */
import { IntroOptIn } from './pages/IntroOptIn'; import { IntroOptIn } from './pages/IntroOptIn';
import { UserProfile } from './pages/UserProfile'; import {
UserProfile,
BusinessChoice,
SellingOnlineAnswer,
SellingPlatform,
} from './pages/UserProfile';
import { BusinessInfo } from './pages/BusinessInfo'; import { BusinessInfo } from './pages/BusinessInfo';
import { BusinessLocation } from './pages/BusinessLocation'; import { BusinessLocation } from './pages/BusinessLocation';
import { getCountryStateOptions } from './services/country'; import { getCountryStateOptions } from './services/country';
@ -76,9 +81,22 @@ export type ExtensionsEvent = {
}; };
}; };
// TODO: add types as we develop the pages
export type OnboardingProfile = {
business_choice: BusinessChoice;
selling_online_answer: SellingOnlineAnswer | null;
selling_platforms: SellingPlatform[] | null;
skip?: boolean;
};
export type CoreProfilerStateMachineContext = { export type CoreProfilerStateMachineContext = {
optInDataSharing: boolean; optInDataSharing: boolean;
userProfile: { foo: { bar: 'qux' }; skipped: false } | { skipped: true }; userProfile: {
businessChoice?: BusinessChoice;
sellingOnlineAnswer?: SellingOnlineAnswer | null;
sellingPlatforms?: SellingPlatform[] | null;
skipped?: boolean;
};
geolocatedLocation: { geolocatedLocation: {
location: string; location: string;
}; };
@ -126,6 +144,36 @@ const handleCountries = assign( {
}, },
} ); } );
const getOnboardingProfileOption = async () =>
resolveSelect( OPTIONS_STORE_NAME ).getOption(
'woocommerce_onboarding_profile'
);
const handleOnboardingProfileOption = assign( {
userProfile: (
_context,
event: DoneInvokeEvent< OnboardingProfile | undefined >
) => {
if ( ! event.data ) {
return {};
}
const {
business_choice: businessChoice,
selling_online_answer: sellingOnlineAnswer,
selling_platforms: sellingPlatforms,
...rest
} = event.data;
return {
...rest,
businessChoice,
sellingOnlineAnswer,
sellingPlatforms,
};
},
} );
const redirectToWooHome = () => { const redirectToWooHome = () => {
navigateTo( { url: getNewPath( {}, '/', {} ) } ); navigateTo( { url: getNewPath( {}, '/', {} ) } );
}; };
@ -148,6 +196,35 @@ const recordTracksIntroViewed = () => {
} ); } );
}; };
const recordTracksUserProfileViewed = () => {
recordEvent( 'storeprofiler_step_view', {
step: 'user_profile',
wc_version: getSetting( 'wcVersion' ),
} );
};
const recordTracksUserProfileCompleted = (
_context: CoreProfilerStateMachineContext,
event: Extract< UserProfileEvent, { type: 'USER_PROFILE_COMPLETED' } >
) => {
recordEvent( 'storeprofiler_step_complete', {
step: 'user_profile',
wc_version: getSetting( 'wcVersion' ),
} );
recordEvent( 'storeprofiler_user_profile', {
business_choice: event.payload.userProfile.businessChoice,
selling_online_answer: event.payload.userProfile.sellingOnlineAnswer,
selling_platforms: event.payload.userProfile.sellingPlatforms
? event.payload.userProfile.sellingPlatforms.join()
: null,
} );
};
const recordTracksUserProfileSkipped = () => {
recordEvent( 'storeprofiler_user_profile_skip' );
};
const recordTracksSkipBusinessLocationViewed = () => { const recordTracksSkipBusinessLocationViewed = () => {
recordEvent( 'storeprofiler_step_view', { recordEvent( 'storeprofiler_step_view', {
step: 'skip_business_location', step: 'skip_business_location',
@ -183,6 +260,22 @@ const updateTrackingOption = (
} ); } );
}; };
const updateOnboardingProfileOption = (
context: CoreProfilerStateMachineContext
) => {
const { businessChoice, sellingOnlineAnswer, sellingPlatforms, ...rest } =
context.userProfile;
return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
woocommerce_onboarding_profile: {
...rest,
business_choice: businessChoice,
selling_online_answer: sellingOnlineAnswer,
selling_platforms: sellingPlatforms,
},
} );
};
const updateBusinessLocation = ( countryAndState: string ) => { const updateBusinessLocation = ( countryAndState: string ) => {
return dispatch( OPTIONS_STORE_NAME ).updateOptions( { return dispatch( OPTIONS_STORE_NAME ).updateOptions( {
woocommerce_default_country: countryAndState, woocommerce_default_country: countryAndState,
@ -238,10 +331,14 @@ const coreProfilerMachineActions = {
recordTracksIntroCompleted, recordTracksIntroCompleted,
recordTracksIntroSkipped, recordTracksIntroSkipped,
recordTracksIntroViewed, recordTracksIntroViewed,
recordTracksUserProfileCompleted,
recordTracksUserProfileSkipped,
recordTracksUserProfileViewed,
recordTracksSkipBusinessLocationViewed, recordTracksSkipBusinessLocationViewed,
recordTracksSkipBusinessLocationCompleted, recordTracksSkipBusinessLocationCompleted,
assignOptInDataSharing, assignOptInDataSharing,
handleCountries, handleCountries,
handleOnboardingProfileOption,
redirectToWooHome, redirectToWooHome,
}; };
@ -249,6 +346,7 @@ const coreProfilerMachineServices = {
getAllowTrackingOption, getAllowTrackingOption,
getCountries, getCountries,
getExtensions, getExtensions,
getOnboardingProfileOption,
}; };
export const coreProfilerStateMachineDefinition = createMachine( { export const coreProfilerStateMachineDefinition = createMachine( {
id: 'coreProfiler', id: 'coreProfiler',
@ -296,7 +394,7 @@ export const coreProfilerStateMachineDefinition = createMachine( {
introOptIn: { introOptIn: {
on: { on: {
INTRO_COMPLETED: { INTRO_COMPLETED: {
target: 'userProfile', target: 'preUserProfile',
actions: [ actions: [
'assignOptInDataSharing', 'assignOptInDataSharing',
'updateTrackingOption', 'updateTrackingOption',
@ -328,10 +426,25 @@ export const coreProfilerStateMachineDefinition = createMachine( {
component: IntroOptIn, component: IntroOptIn,
}, },
}, },
preUserProfile: {
invoke: {
src: 'getOnboardingProfileOption',
onDone: [
{
actions: [ 'handleOnboardingProfileOption' ],
target: 'userProfile',
},
],
onError: {
target: 'userProfile',
},
},
},
userProfile: { userProfile: {
entry: [ 'recordTracksUserProfileViewed' ],
on: { on: {
USER_PROFILE_COMPLETED: { USER_PROFILE_COMPLETED: {
target: 'preBusinessInfo', target: 'postUserProfile',
actions: [ actions: [
assign( { assign( {
userProfile: ( context, event: UserProfileEvent ) => userProfile: ( context, event: UserProfileEvent ) =>
@ -340,7 +453,7 @@ export const coreProfilerStateMachineDefinition = createMachine( {
], ],
}, },
USER_PROFILE_SKIPPED: { USER_PROFILE_SKIPPED: {
target: 'preBusinessInfo', target: 'postUserProfile',
actions: [ actions: [
assign( { assign( {
userProfile: ( context, event: UserProfileEvent ) => userProfile: ( context, event: UserProfileEvent ) =>
@ -349,11 +462,36 @@ export const coreProfilerStateMachineDefinition = createMachine( {
], ],
}, },
}, },
exit: actions.choose( [
{
cond: ( _context, event ) =>
event.type === 'USER_PROFILE_COMPLETED',
actions: 'recordTracksUserProfileCompleted',
},
{
cond: ( _context, event ) =>
event.type === 'USER_PROFILE_SKIPPED',
actions: 'recordTracksUserProfileSkipped',
},
] ),
meta: { meta: {
progress: 40, progress: 40,
component: UserProfile, component: UserProfile,
}, },
}, },
postUserProfile: {
invoke: {
src: ( context ) => {
return updateOnboardingProfileOption( context );
},
onDone: {
target: 'preBusinessInfo',
},
onError: {
target: 'preBusinessInfo',
},
},
},
preBusinessInfo: { preBusinessInfo: {
always: [ always: [
// immediately transition to businessInfo without any events as long as geolocation parallel has completed // immediately transition to businessInfo without any events as long as geolocation parallel has completed

View File

@ -41,6 +41,7 @@ export const BusinessLocation = ( {
<Navigation percentage={ navigationProgress } /> <Navigation percentage={ navigationProgress } />
<div className="woocommerce-profiler-page__content woocommerce-profiler-business-location__content"> <div className="woocommerce-profiler-page__content woocommerce-profiler-business-location__content">
<Heading <Heading
className="woocommerce-profiler__stepper-heading"
title={ __( title={ __(
'Where is your business located?', 'Where is your business located?',
'woocommerce' 'woocommerce'
@ -74,9 +75,9 @@ export const BusinessLocation = ( {
showAllOnFocus showAllOnFocus
isSearchable isSearchable
/> />
<div className="woocommerce-profiler-go-to-mystore__button-container"> <div className="woocommerce-profiler-button-container woocommerce-profiler-go-to-mystore__button-container">
<Button <Button
className="woocommerce-profiler-go-to-mystore__button" className="woocommerce-profiler-button"
variant="primary" variant="primary"
disabled={ ! storeCountry.key } disabled={ ! storeCountry.key }
onClick={ () => { onClick={ () => {

View File

@ -1,41 +1,299 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { Button } from '@wordpress/components';
import { useState } from '@wordpress/element';
import { SelectControl } from '@woocommerce/components';
import { Icon, chevronDown } from '@wordpress/icons';
import classnames from 'classnames';
/** /**
* Internal dependencies * Internal dependencies
*/ */
import { UserProfileEvent } from '../index'; import { UserProfileEvent, CoreProfilerStateMachineContext } from '../index';
import { Navigation } from '../components/navigation/navigation';
import { Heading } from '../components/heading/heading';
import { Choice } from '../components/choice/choice';
import { MultipleSelector } from '../components/multiple-selector/multiple-selector';
const businessOptions = [
{
title: __( "I'm just starting my business", 'woocommerce' ),
value: 'im_just_starting_my_business' as const,
},
{
title: __( "I'm already selling", 'woocommerce' ),
value: 'im_already_selling' as const,
},
{
title: __( "I'm setting up a store for a client", 'woocommerce' ),
value: 'im_setting_up_a_store_for_a_client' as const,
},
];
const sellingOnlineOptions = [
{
label: __( "Yes, I'm selling online", 'woocommerce' ),
value: 'yes_im_selling_online' as const,
key: 'yes_im_selling_online' as const,
},
{
label: __( "No, I'm selling offline", 'woocommerce' ),
value: 'no_im_selling_offline' as const,
key: 'no_im_selling_offline' as const,
},
{
label: __( "I'm selling both online and offline", 'woocommerce' ),
value: 'im_selling_both_online_and_offline' as const,
key: 'im_selling_both_online_and_offline' as const,
},
];
const platformOptions = [
{
label: __( 'Amazon', 'woocommerce' ),
value: 'amazon' as const,
},
{
label: __( 'Adobe Commerce', 'woocommerce' ),
value: 'adobe_commerce' as const,
},
{
label: __( 'Big Cartel', 'woocommerce' ),
value: 'big_cartel' as const,
},
{
label: __( 'Big Commerce', 'woocommerce' ),
value: 'big_commerce' as const,
},
{
label: __( 'Ebay', 'woocommerce' ),
value: 'ebay' as const,
},
{
label: __( 'Ecwid', 'woocommerce' ),
value: 'ecwid' as const,
},
{
label: __( 'Etsy', 'woocommerce' ),
value: 'etsy' as const,
},
{
label: __( 'Facebook Marketplace', 'woocommerce' ),
value: 'facebook_marketplace' as const,
},
{
label: __( 'Google Shopping', 'woocommerce' ),
value: 'google_shopping' as const,
},
{
label: __( 'Pinterest', 'woocommerce' ),
value: 'pinterest' as const,
},
{
label: __( 'Shopify', 'woocommerce' ),
value: 'shopify' as const,
},
{
label: __( 'Square', 'woocommerce' ),
value: 'square' as const,
},
{
label: __( 'Squarespace', 'woocommerce' ),
value: 'squarespace' as const,
},
{
label: __( 'Wix', 'woocommerce' ),
value: 'wix' as const,
},
{
label: __( 'WordPress', 'woocommerce' ),
value: 'wordpress' as const,
},
];
export type BusinessChoice = typeof businessOptions[ 0 ][ 'value' ];
export type SellingOnlineAnswer = typeof sellingOnlineOptions[ 0 ][ 'value' ];
export type SellingPlatform = typeof platformOptions[ 0 ][ 'value' ];
export const UserProfile = ( { export const UserProfile = ( {
sendEvent, sendEvent,
navigationProgress,
context,
}: { }: {
sendEvent: ( event: UserProfileEvent ) => void; sendEvent: ( event: UserProfileEvent ) => void;
navigationProgress: number;
context: CoreProfilerStateMachineContext;
} ) => { } ) => {
const [ businessChoice, setBusinessChoice ] = useState< BusinessChoice >(
context.userProfile.businessChoice || 'im_just_starting_my_business'
);
const [ sellingOnlineAnswer, setSellingOnlineAnswer ] =
useState< SellingOnlineAnswer | null >(
context.userProfile.sellingOnlineAnswer || null
);
const [ sellingPlatforms, setSellingPlatforms ] =
useState< Array< SellingPlatform > | null >(
context.userProfile.sellingPlatforms || null
);
const [ isPlatformDropdownOpen, setIsPlatformDropdownOpen ] =
useState( false );
const renderAlreadySellingOptions = () => {
return (
<>
<div className="woocommerce-profiler-selling-online-question">
<p className="woocommerce-profiler-question-label">
{ __( 'Are you selling online?', 'woocommerce' ) }
</p>
<SelectControl
className="woocommerce-profiler-select-control__selling-online-question"
instanceId={ 1 }
label={ __( 'Select an option', 'woocommerce' ) }
autoComplete="new-password" // disable autocomplete and autofill
options={ sellingOnlineOptions }
excludeSelectedOptions={ false }
help={ <Icon icon={ chevronDown } /> }
onChange={ (
selectedOptionKey: typeof sellingOnlineAnswer
) => {
setSellingOnlineAnswer( selectedOptionKey );
} }
multiple={ false }
selected={ sellingOnlineAnswer }
/>
</div>
{ sellingOnlineAnswer &&
[
'yes_im_selling_online',
'im_selling_both_online_and_offline',
].includes( sellingOnlineAnswer ) && (
<div className="woocommerce-profiler-selling-platform">
<p className="woocommerce-profiler-question-label">
{ __(
'Which platform(s) are you currently using?',
'woocommerce'
) }
</p>
<MultipleSelector
options={ platformOptions }
selectedOptions={ platformOptions.filter(
( option ) =>
sellingPlatforms?.includes(
option.value
)
) }
onSelect={ ( items ) => {
setSellingPlatforms(
items.map(
( item ) =>
item.value as SellingPlatform
)
);
} }
onOpenClose={ setIsPlatformDropdownOpen }
/>
</div>
) }
</>
);
};
const onContinue = () => {
sendEvent( {
type: 'USER_PROFILE_COMPLETED',
payload: {
userProfile: {
businessChoice,
sellingOnlineAnswer:
businessChoice === 'im_already_selling'
? sellingOnlineAnswer
: null,
sellingPlatforms:
businessChoice === 'im_already_selling'
? sellingPlatforms
: null,
},
},
} );
};
return ( return (
<> <div
<div data-testid="core-profiler-user-profile">User Profile</div> className="woocommerce-profiler-user-profile"
<button data-testid="core-profiler-user-profile"
onClick={ () => >
sendEvent( { <Navigation
type: 'USER_PROFILE_COMPLETED', percentage={ navigationProgress }
payload: { skipText={ __( 'Skip this setup', 'woocommerce' ) }
userProfile: { onSkip={ () =>
foo: { bar: 'qux' },
skipped: false,
},
},
} )
}
>
Next
</button>
<button
onClick={ () =>
sendEvent( { sendEvent( {
type: 'USER_PROFILE_SKIPPED', type: 'USER_PROFILE_SKIPPED',
payload: { userProfile: { skipped: true } }, payload: { userProfile: { skipped: true } },
} ) } )
} }
/>
<div
className={ classnames(
'woocommerce-profiler-page__content woocommerce-profiler-user-profile__content',
{
'is-platform-selector-open': isPlatformDropdownOpen,
}
) }
> >
Skip <Heading
</button> className="woocommerce-profiler__stepper-heading"
</> title={ __(
'Which one of these best describes you?',
'woocommerce'
) }
subTitle={ __(
'Let us know where you are in your commerce journey so that we can tailor your Woo experience for you.',
'woocommerce'
) }
/>
<form className="woocommerce-user-profile-choices">
<fieldset>
<legend className="screen-reader-text">
{ __(
'Which one of these best describes you?',
'woocommerce'
) }
</legend>
{ businessOptions.map( ( { title, value } ) => {
return (
<Choice
key={ value }
name="user-profile-choice"
title={ title }
selected={ businessChoice === value }
value={ value }
onChange={ ( _value ) => {
setBusinessChoice(
_value as BusinessChoice
);
} }
subOptionsComponent={
value === 'im_already_selling'
? renderAlreadySellingOptions()
: null
}
/>
);
} ) }
</fieldset>
</form>
<div className="woocommerce-profiler-button-container">
<Button
className="woocommerce-profiler-button"
variant="primary"
onClick={ onContinue }
>
{ __( 'Continue', 'woocommerce' ) }
</Button>
</div>
</div>
</div>
); );
}; };

View File

@ -0,0 +1,129 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
/**
* External dependencies
*/
import { render, screen, fireEvent } from '@testing-library/react';
/**
* Internal dependencies
*/
import { UserProfile } from '../UserProfile';
import { CoreProfilerStateMachineContext } from '../..';
describe( 'UserProfile', () => {
let props: {
sendEvent: jest.Mock;
navigationProgress: number;
context: Pick< CoreProfilerStateMachineContext, 'userProfile' >;
};
beforeEach( () => {
props = {
sendEvent: jest.fn(),
navigationProgress: 0,
context: {
userProfile: {
businessChoice: 'im_just_starting_my_business',
sellingOnlineAnswer: null,
sellingPlatforms: null,
},
},
};
} );
it( 'should render user profile page', () => {
// @ts-ignore
render( <UserProfile { ...props } /> );
expect(
screen.getByText( /Which one of these best describes you?/i, {
selector: 'h1',
} )
).toBeInTheDocument();
expect(
screen.getByRole( 'button', {
name: /Continue/i,
} )
).toBeInTheDocument();
} );
it( 'should show online selling question when choosing "im_already_selling"', () => {
// @ts-ignore
render( <UserProfile { ...props } /> );
const radioInput = screen.getByLabelText< HTMLInputElement >(
"I'm already selling"
); // Replace with the label of your radio button
// Perform the radio button selection
fireEvent.click( radioInput );
// Assert the expected behavior
expect( radioInput.checked ).toBe( true );
const onlineSellingQuestion = screen.getByText(
/Are you selling online?/i
);
expect( onlineSellingQuestion ).toBeInTheDocument();
} );
it( 'should show online selling question when choosing "Yes, I\'m selling online"', () => {
render(
// @ts-ignore
<UserProfile
{ ...{
...props,
context: {
userProfile: {
businessChoice: 'im_already_selling',
sellingOnlineAnswer: 'yes_im_selling_online',
sellingPlatforms: null,
},
},
} }
/>
);
const platformSelector = screen.getByLabelText( /Select an option/i );
expect( platformSelector ).toBeInTheDocument();
} );
it( 'should call sendEvent with USER_PROFILE_COMPLETED event when button is clicked', () => {
render(
// @ts-ignore
<UserProfile { ...props } />
);
screen
.getByRole( 'button', {
name: /Continue/i,
} )
.click();
expect( props.sendEvent ).toHaveBeenCalledWith( {
type: 'USER_PROFILE_COMPLETED',
payload: {
userProfile: {
businessChoice: 'im_just_starting_my_business',
sellingOnlineAnswer: null,
sellingPlatforms: null,
},
},
} );
} );
it( 'should call sendEvent with USER_PROFILE_SKIPPED event when skip button is clicked', () => {
render(
// @ts-ignore
<UserProfile { ...props } />
);
screen
.getByRole( 'button', {
name: /Skip this setup/i,
} )
.click();
expect( props.sendEvent ).toHaveBeenCalledWith( {
type: 'USER_PROFILE_SKIPPED',
payload: {
userProfile: {
skipped: true,
},
},
} );
} );
} );

View File

@ -19,6 +19,85 @@
@include breakpoint( '<782px' ) { @include breakpoint( '<782px' ) {
padding: 0 20px; padding: 0 20px;
} }
.woocommerce-profiler-button-container {
width: 100%;
max-width: 404px;
@include breakpoint( '<782px' ) {
position: absolute;
bottom: 20px;
padding: 0 20px;
}
}
.woocommerce-profiler-button {
display: flex;
width: 100%;
justify-content: center;
padding: 10px 16px;
height: 48px;
font-size: 14px;
font-weight: 500;
}
.woocommerce-select-control__option {
font-size: 13px;
height: 40px;
min-height: initial;
&:hover {
background: #eff2fd;
color: $gray-900;
}
}
.woocommerce-select-control__listbox {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
border: 1px solid #ccc;
margin-top: 8px;
top: 40px;
}
.woocommerce-select-control__control {
height: 40px;
padding: 12px;
border: 1px solid #bbb;
label,
.woocommerce-select-control__control-input {
font-size: 13px;
cursor: pointer;
}
.components-base-control__label {
font-size: 13px;
line-height: 16px;
color: $gray-700;
}
&.with-value {
.woocommerce-select-control__control-input {
margin: 0;
color: $gray-900;
}
.components-base-control__label {
display: none;
}
}
&.is-active {
border: 1px solid var(--wp-admin-theme-color);
}
}
#woocommerce-select-control__listbox-0 {
top: 40px !important;
}
}
.woocommerce-profiler-select-control__country {
max-width: 404px;
margin: auto auto 32px auto;
} }
// Intro opt-in page // Intro opt-in page
@ -117,6 +196,11 @@
max-width: 514px; max-width: 514px;
margin-left: 10px; margin-left: 10px;
} }
.woocommerce-select-control__control-input {
font-size: 13px;
line-height: 16px;
}
} }
a { a {
@ -124,38 +208,8 @@
} }
} }
} }
.woocommerce-profiler-select-control__country {
max-width: 400px;
margin: auto auto 32px auto;
.woocommerce-select-control__option {
font-size: 13px;
&:hover {
background: #eff2fd;
}
}
.woocommerce-select-control__listbox {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
border: 1px solid #ccc;
margin-top: 8px;
}
.woocommerce-select-control__control { // Business location page
height: 40px;
padding: 12px;
border: 1px solid #bbb;
label,
input {
font-size: 13px;
color: var(--wp-components-color-foreground, #757575);
}
&.is-active {
border: 1px solid #3858e9;
}
}
#woocommerce-select-control__listbox-0 {
top: 40px !important;
}
}
.woocommerce-profiler-business-location { .woocommerce-profiler-business-location {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -169,42 +223,6 @@
width: 100%; width: 100%;
} }
.woocommerce-profiler-heading {
margin-top: 72px;
@include breakpoint( '<782px' ) {
margin-top: 52px;
margin-bottom: 40px;
h1 {
font-size: 32px;
line-height: 40px;
text-align: left;
}
h2 {
text-align: left;
font-size: 16px;
margin-bottom: 0;
}
}
}
.woocommerce-profiler-go-to-mystore__button-container {
width: 100%;
max-width: 400px;
@include breakpoint( '<782px' ) {
position: absolute;
bottom: 20px;
padding: 0 20px;
}
}
.woocommerce-profiler-go-to-mystore__button {
display: flex;
width: 100%;
justify-content: center;
padding: 10px 16px;
height: 48px;
font-size: 14px;
font-weight: 500;
}
.components-base-control__field { .components-base-control__field {
height: 40px; height: 40px;
} }
@ -232,6 +250,7 @@
} }
} }
// Loader page
.woocommerce-profiler-loader { .woocommerce-profiler-loader {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -283,3 +302,45 @@
} }
} }
} }
// User profile page
.woocommerce-profiler-user-profile {
.woocommerce-profiler-user-profile__content {
min-height: 630px;
&.is-platform-selector-open {
padding-bottom: 145px;
}
}
.woocommerce-user-profile-choices {
margin-bottom: 32px;
max-width: 404px;
width: 100%;
}
.woocommerce-profiler-heading__title {
max-width: 480px;
}
.woocommerce-user-profile-choices fieldset {
display: flex;
gap: 18px;
flex-direction: column;
}
.woocommerce-profiler-choice-sub-options {
.woocommerce-profiler-question-label {
text-transform: uppercase;
color: $gray-900;
font-weight: 500;
font-size: 11px;
line-height: 16px;
margin: 0 0 8px;
}
}
.woocommerce-profiler-selling-platform {
margin-top: 20px;
}
}

View File

@ -1955,7 +1955,7 @@ Object {
class="woocommerce-profiler-page__content woocommerce-profiler-business-location__content" class="woocommerce-profiler-page__content woocommerce-profiler-business-location__content"
> >
<div <div
class="woocommerce-profiler-heading" class="woocommerce-profiler-heading woocommerce-profiler__stepper-heading"
> >
<h1 <h1
class="woocommerce-profiler-heading__title" class="woocommerce-profiler-heading__title"
@ -2032,10 +2032,10 @@ Object {
</div> </div>
</div> </div>
<div <div
class="woocommerce-profiler-go-to-mystore__button-container" class="woocommerce-profiler-button-container woocommerce-profiler-go-to-mystore__button-container"
> >
<button <button
class="components-button woocommerce-profiler-go-to-mystore__button is-primary" class="components-button woocommerce-profiler-button is-primary"
disabled="" disabled=""
type="button" type="button"
> >
@ -2107,7 +2107,7 @@ Object {
class="woocommerce-profiler-page__content woocommerce-profiler-business-location__content" class="woocommerce-profiler-page__content woocommerce-profiler-business-location__content"
> >
<div <div
class="woocommerce-profiler-heading" class="woocommerce-profiler-heading woocommerce-profiler__stepper-heading"
> >
<h1 <h1
class="woocommerce-profiler-heading__title" class="woocommerce-profiler-heading__title"
@ -2184,10 +2184,10 @@ Object {
</div> </div>
</div> </div>
<div <div
class="woocommerce-profiler-go-to-mystore__button-container" class="woocommerce-profiler-button-container woocommerce-profiler-go-to-mystore__button-container"
> >
<button <button
class="components-button woocommerce-profiler-go-to-mystore__button is-primary" class="components-button woocommerce-profiler-button is-primary"
disabled="" disabled=""
type="button" type="button"
> >
@ -3033,16 +3033,179 @@ Object {
class="woocommerce-profile-wizard__container woocommerce-profile-wizard__step-userProfile" class="woocommerce-profile-wizard__container woocommerce-profile-wizard__step-userProfile"
> >
<div <div
class="woocommerce-profiler-user-profile"
data-testid="core-profiler-user-profile" data-testid="core-profiler-user-profile"
> >
User Profile <div
class="woocommerce-profiler-navigation-container"
>
<div
class="woocommerce-profiler-progress-bar progress-bar"
>
<div
class="woocommerce-profiler-progress-bar__container"
style="background-color: transparent;"
>
<div
class="woocommerce-profiler-progress-bar__filler"
style="width: 40%; display: inherit;"
/>
</div>
</div>
<div
class="woocommerce-profiler-navigation"
>
<div
class="woocommerce-profiler-navigation-col-left"
>
<span
class="woologo"
>
<svg
class="wc-icon wc-icon__woo-logo"
preserveAspectRatio="xMidYMid"
version="1.1"
viewBox="0 0 256 153"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m23.759 0h208.38c13.187 0 23.863 10.675 23.863 23.863v79.542c0 13.187-10.675 23.863-23.863 23.863h-74.727l10.257 25.118-45.109-25.118h-98.695c-13.187 0-23.863-10.675-23.863-23.863v-79.542c-0.10466-13.083 10.571-23.863 23.758-23.863z"
fill="#7f54b3"
/>
<path
d="m14.578 21.75c1.4569-1.9772 3.6423-3.0179 6.5561-3.226 5.3073-0.41626 8.3252 2.0813 9.0537 7.4927 3.226 21.75 6.7642 40.169 10.511 55.259l22.79-43.395c2.0813-3.9545 4.6829-6.0358 7.8049-6.2439 4.5789-0.3122 7.3886 2.6016 8.5333 8.7415 2.6016 13.841 5.9317 25.6 9.8862 35.59 2.7057-26.433 7.2846-45.476 13.737-57.236 1.561-2.9138 3.8504-4.3707 6.8683-4.5789 2.3935-0.20813 4.5789 0.52033 6.5561 2.0813 1.9772 1.561 3.0179 3.5382 3.226 5.9317 0.10406 1.8732-0.20813 3.4341-1.0407 4.9951-4.0585 7.4927-7.3886 20.085-10.094 37.567-2.6016 16.963-3.5382 30.179-2.9138 39.649 0.20813 2.6016-0.20813 4.8911-1.2488 6.8683-1.2488 2.2894-3.122 3.5382-5.5154 3.7463-2.7057 0.20813-5.5154-1.0406-8.2211-3.8504-9.678-9.8862-17.379-24.663-22.998-44.332-6.7642 13.32-11.759 23.311-14.985 29.971-6.1398 11.759-11.343 17.795-15.714 18.107-2.8098 0.20813-5.2033-2.1854-7.2846-7.1805-5.3073-13.633-11.031-39.961-17.171-78.985-0.41626-2.7057 0.20813-5.0992 1.665-6.9724zm223.64 16.338c-3.7463-6.5561-9.2618-10.511-16.65-12.072-1.9772-0.41626-3.8504-0.62439-5.6195-0.62439-9.9902 0-18.107 5.2033-24.455 15.61-5.4114 8.8455-8.1171 18.628-8.1171 29.346 0 8.013 1.665 14.881 4.9951 20.605 3.7463 6.5561 9.2618 10.511 16.65 12.072 1.9772 0.41626 3.8504 0.62439 5.6195 0.62439 10.094 0 18.211-5.2033 24.455-15.61 5.4114-8.9496 8.1171-18.732 8.1171-29.45 0.10406-8.1171-1.665-14.881-4.9951-20.501zm-13.112 28.826c-1.4569 6.8683-4.0585 11.967-7.9089 15.402-3.0179 2.7057-5.8276 3.8504-8.4293 3.3301-2.4976-0.52033-4.5789-2.7057-6.1398-6.7642-1.2488-3.226-1.8732-6.452-1.8732-9.4699 0-2.6016 0.20813-5.2033 0.72846-7.5967 0.93659-4.2667 2.7057-8.4293 5.5154-12.384 3.4341-5.0992 7.0764-7.1805 10.823-6.452 2.4976 0.52033 4.5789 2.7057 6.1398 6.7642 1.2488 3.226 1.8732 6.452 1.8732 9.4699 0 2.7057-0.20813 5.3073-0.72846 7.7008zm-52.033-28.826c-3.7463-6.5561-9.3659-10.511-16.65-12.072-1.9772-0.41626-3.8504-0.62439-5.6195-0.62439-9.9902 0-18.107 5.2033-24.455 15.61-5.4114 8.8455-8.1171 18.628-8.1171 29.346 0 8.013 1.665 14.881 4.9951 20.605 3.7463 6.5561 9.2618 10.511 16.65 12.072 1.9772 0.41626 3.8504 0.62439 5.6195 0.62439 10.094 0 18.211-5.2033 24.455-15.61 5.4114-8.9496 8.1171-18.732 8.1171-29.45 0-8.1171-1.665-14.881-4.9951-20.501zm-13.216 28.826c-1.4569 6.8683-4.0585 11.967-7.9089 15.402-3.0179 2.7057-5.8276 3.8504-8.4293 3.3301-2.4976-0.52033-4.5789-2.7057-6.1398-6.7642-1.2488-3.226-1.8732-6.452-1.8732-9.4699 0-2.6016 0.20813-5.2033 0.72846-7.5967 0.93658-4.2667 2.7057-8.4293 5.5154-12.384 3.4341-5.0992 7.0764-7.1805 10.823-6.452 2.4976 0.52033 4.5789 2.7057 6.1398 6.7642 1.2488 3.226 1.8732 6.452 1.8732 9.4699 0.10406 2.7057-0.20813 5.3073-0.72846 7.7008z"
fill="#fff"
/>
</svg>
</span>
</div>
<div
class="woocommerce-profiler-navigation-col-right"
>
<button
class="components-button woocommerce-profiler-navigation-skip-link is-link"
type="button"
>
Skip this setup
</button>
</div>
</div>
</div>
<div
class="woocommerce-profiler-page__content woocommerce-profiler-user-profile__content"
>
<div
class="woocommerce-profiler-heading woocommerce-profiler__stepper-heading"
>
<h1
class="woocommerce-profiler-heading__title"
>
Which one of these best describes you?
</h1>
<h2
class="woocommerce-profiler-heading__subtitle"
>
Let us know where you are in your commerce journey so that we can tailor your Woo experience for you.
</h2>
</div>
<form
class="woocommerce-user-profile-choices"
>
<fieldset>
<legend
class="screen-reader-text"
>
Which one of these best describes you?
</legend>
<div
class="woocommerce-profiler-choice-container"
data-selected="true"
role="radio"
tabindex="0"
>
<div
class="woocommerce-profiler-choice"
>
<input
checked=""
class="woocommerce-profiler-choice-input"
data-selected="true"
id="woocommerce-im-just-starting-my-business"
name="user-profile-choice"
tabindex="-1"
type="radio"
value="im_just_starting_my_business"
/>
<label
class="choice__title"
for="woocommerce-im-just-starting-my-business"
>
I'm just starting my business
</label>
</div>
</div>
<div
class="woocommerce-profiler-choice-container"
role="radio"
tabindex="0"
>
<div
class="woocommerce-profiler-choice"
>
<input
class="woocommerce-profiler-choice-input"
id="woocommerce-im-already-selling"
name="user-profile-choice"
tabindex="-1"
type="radio"
value="im_already_selling"
/>
<label
class="choice__title"
for="woocommerce-im-already-selling"
>
I'm already selling
</label>
</div>
</div>
<div
class="woocommerce-profiler-choice-container"
role="radio"
tabindex="0"
>
<div
class="woocommerce-profiler-choice"
>
<input
class="woocommerce-profiler-choice-input"
id="woocommerce-im-setting-up-a-store-for-a-client"
name="user-profile-choice"
tabindex="-1"
type="radio"
value="im_setting_up_a_store_for_a_client"
/>
<label
class="choice__title"
for="woocommerce-im-setting-up-a-store-for-a-client"
>
I'm setting up a store for a client
</label>
</div>
</div>
</fieldset>
</form>
<div
class="woocommerce-profiler-button-container"
>
<button
class="components-button woocommerce-profiler-button is-primary"
type="button"
>
Continue
</button>
</div>
</div>
</div> </div>
<button>
Next
</button>
<button>
Skip
</button>
</div> </div>
</div> </div>
</body>, </body>,
@ -3051,16 +3214,179 @@ Object {
class="woocommerce-profile-wizard__container woocommerce-profile-wizard__step-userProfile" class="woocommerce-profile-wizard__container woocommerce-profile-wizard__step-userProfile"
> >
<div <div
class="woocommerce-profiler-user-profile"
data-testid="core-profiler-user-profile" data-testid="core-profiler-user-profile"
> >
User Profile <div
class="woocommerce-profiler-navigation-container"
>
<div
class="woocommerce-profiler-progress-bar progress-bar"
>
<div
class="woocommerce-profiler-progress-bar__container"
style="background-color: transparent;"
>
<div
class="woocommerce-profiler-progress-bar__filler"
style="width: 40%; display: inherit;"
/>
</div>
</div>
<div
class="woocommerce-profiler-navigation"
>
<div
class="woocommerce-profiler-navigation-col-left"
>
<span
class="woologo"
>
<svg
class="wc-icon wc-icon__woo-logo"
preserveAspectRatio="xMidYMid"
version="1.1"
viewBox="0 0 256 153"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m23.759 0h208.38c13.187 0 23.863 10.675 23.863 23.863v79.542c0 13.187-10.675 23.863-23.863 23.863h-74.727l10.257 25.118-45.109-25.118h-98.695c-13.187 0-23.863-10.675-23.863-23.863v-79.542c-0.10466-13.083 10.571-23.863 23.758-23.863z"
fill="#7f54b3"
/>
<path
d="m14.578 21.75c1.4569-1.9772 3.6423-3.0179 6.5561-3.226 5.3073-0.41626 8.3252 2.0813 9.0537 7.4927 3.226 21.75 6.7642 40.169 10.511 55.259l22.79-43.395c2.0813-3.9545 4.6829-6.0358 7.8049-6.2439 4.5789-0.3122 7.3886 2.6016 8.5333 8.7415 2.6016 13.841 5.9317 25.6 9.8862 35.59 2.7057-26.433 7.2846-45.476 13.737-57.236 1.561-2.9138 3.8504-4.3707 6.8683-4.5789 2.3935-0.20813 4.5789 0.52033 6.5561 2.0813 1.9772 1.561 3.0179 3.5382 3.226 5.9317 0.10406 1.8732-0.20813 3.4341-1.0407 4.9951-4.0585 7.4927-7.3886 20.085-10.094 37.567-2.6016 16.963-3.5382 30.179-2.9138 39.649 0.20813 2.6016-0.20813 4.8911-1.2488 6.8683-1.2488 2.2894-3.122 3.5382-5.5154 3.7463-2.7057 0.20813-5.5154-1.0406-8.2211-3.8504-9.678-9.8862-17.379-24.663-22.998-44.332-6.7642 13.32-11.759 23.311-14.985 29.971-6.1398 11.759-11.343 17.795-15.714 18.107-2.8098 0.20813-5.2033-2.1854-7.2846-7.1805-5.3073-13.633-11.031-39.961-17.171-78.985-0.41626-2.7057 0.20813-5.0992 1.665-6.9724zm223.64 16.338c-3.7463-6.5561-9.2618-10.511-16.65-12.072-1.9772-0.41626-3.8504-0.62439-5.6195-0.62439-9.9902 0-18.107 5.2033-24.455 15.61-5.4114 8.8455-8.1171 18.628-8.1171 29.346 0 8.013 1.665 14.881 4.9951 20.605 3.7463 6.5561 9.2618 10.511 16.65 12.072 1.9772 0.41626 3.8504 0.62439 5.6195 0.62439 10.094 0 18.211-5.2033 24.455-15.61 5.4114-8.9496 8.1171-18.732 8.1171-29.45 0.10406-8.1171-1.665-14.881-4.9951-20.501zm-13.112 28.826c-1.4569 6.8683-4.0585 11.967-7.9089 15.402-3.0179 2.7057-5.8276 3.8504-8.4293 3.3301-2.4976-0.52033-4.5789-2.7057-6.1398-6.7642-1.2488-3.226-1.8732-6.452-1.8732-9.4699 0-2.6016 0.20813-5.2033 0.72846-7.5967 0.93659-4.2667 2.7057-8.4293 5.5154-12.384 3.4341-5.0992 7.0764-7.1805 10.823-6.452 2.4976 0.52033 4.5789 2.7057 6.1398 6.7642 1.2488 3.226 1.8732 6.452 1.8732 9.4699 0 2.7057-0.20813 5.3073-0.72846 7.7008zm-52.033-28.826c-3.7463-6.5561-9.3659-10.511-16.65-12.072-1.9772-0.41626-3.8504-0.62439-5.6195-0.62439-9.9902 0-18.107 5.2033-24.455 15.61-5.4114 8.8455-8.1171 18.628-8.1171 29.346 0 8.013 1.665 14.881 4.9951 20.605 3.7463 6.5561 9.2618 10.511 16.65 12.072 1.9772 0.41626 3.8504 0.62439 5.6195 0.62439 10.094 0 18.211-5.2033 24.455-15.61 5.4114-8.9496 8.1171-18.732 8.1171-29.45 0-8.1171-1.665-14.881-4.9951-20.501zm-13.216 28.826c-1.4569 6.8683-4.0585 11.967-7.9089 15.402-3.0179 2.7057-5.8276 3.8504-8.4293 3.3301-2.4976-0.52033-4.5789-2.7057-6.1398-6.7642-1.2488-3.226-1.8732-6.452-1.8732-9.4699 0-2.6016 0.20813-5.2033 0.72846-7.5967 0.93658-4.2667 2.7057-8.4293 5.5154-12.384 3.4341-5.0992 7.0764-7.1805 10.823-6.452 2.4976 0.52033 4.5789 2.7057 6.1398 6.7642 1.2488 3.226 1.8732 6.452 1.8732 9.4699 0.10406 2.7057-0.20813 5.3073-0.72846 7.7008z"
fill="#fff"
/>
</svg>
</span>
</div>
<div
class="woocommerce-profiler-navigation-col-right"
>
<button
class="components-button woocommerce-profiler-navigation-skip-link is-link"
type="button"
>
Skip this setup
</button>
</div>
</div>
</div>
<div
class="woocommerce-profiler-page__content woocommerce-profiler-user-profile__content"
>
<div
class="woocommerce-profiler-heading woocommerce-profiler__stepper-heading"
>
<h1
class="woocommerce-profiler-heading__title"
>
Which one of these best describes you?
</h1>
<h2
class="woocommerce-profiler-heading__subtitle"
>
Let us know where you are in your commerce journey so that we can tailor your Woo experience for you.
</h2>
</div>
<form
class="woocommerce-user-profile-choices"
>
<fieldset>
<legend
class="screen-reader-text"
>
Which one of these best describes you?
</legend>
<div
class="woocommerce-profiler-choice-container"
data-selected="true"
role="radio"
tabindex="0"
>
<div
class="woocommerce-profiler-choice"
>
<input
checked=""
class="woocommerce-profiler-choice-input"
data-selected="true"
id="woocommerce-im-just-starting-my-business"
name="user-profile-choice"
tabindex="-1"
type="radio"
value="im_just_starting_my_business"
/>
<label
class="choice__title"
for="woocommerce-im-just-starting-my-business"
>
I'm just starting my business
</label>
</div>
</div>
<div
class="woocommerce-profiler-choice-container"
role="radio"
tabindex="0"
>
<div
class="woocommerce-profiler-choice"
>
<input
class="woocommerce-profiler-choice-input"
id="woocommerce-im-already-selling"
name="user-profile-choice"
tabindex="-1"
type="radio"
value="im_already_selling"
/>
<label
class="choice__title"
for="woocommerce-im-already-selling"
>
I'm already selling
</label>
</div>
</div>
<div
class="woocommerce-profiler-choice-container"
role="radio"
tabindex="0"
>
<div
class="woocommerce-profiler-choice"
>
<input
class="woocommerce-profiler-choice-input"
id="woocommerce-im-setting-up-a-store-for-a-client"
name="user-profile-choice"
tabindex="-1"
type="radio"
value="im_setting_up_a_store_for_a_client"
/>
<label
class="choice__title"
for="woocommerce-im-setting-up-a-store-for-a-client"
>
I'm setting up a store for a client
</label>
</div>
</div>
</fieldset>
</form>
<div
class="woocommerce-profiler-button-container"
>
<button
class="components-button woocommerce-profiler-button is-primary"
type="button"
>
Continue
</button>
</div>
</div>
</div> </div>
<button>
Next
</button>
<button>
Skip
</button>
</div> </div>
</div>, </div>,
"debug": [Function], "debug": [Function],

View File

@ -14,14 +14,16 @@ import { createMachine } from 'xstate';
*/ */
import { CoreProfilerController } from '../'; import { CoreProfilerController } from '../';
jest.mock( '@automattic/calypso-config' );
// mock out the external dependencies which we don't want to test here // mock out the external dependencies which we don't want to test here
const actionOverrides = { const actionOverrides = {
updateTrackingOption: jest.fn(), updateTrackingOption: jest.fn(),
updateOnboardingProfileOption: jest.fn(),
recordTracksIntroCompleted: jest.fn(), recordTracksIntroCompleted: jest.fn(),
recordTracksIntroSkipped: jest.fn(), recordTracksIntroSkipped: jest.fn(),
recordTracksIntroViewed: jest.fn(), recordTracksIntroViewed: jest.fn(),
recordTracksUserProfileCompleted: jest.fn(),
recordTracksUserProfileSkipped: jest.fn(),
recordTracksUserProfileViewed: jest.fn(),
recordTracksSkipBusinessLocationViewed: jest.fn(), recordTracksSkipBusinessLocationViewed: jest.fn(),
recordTracksSkipBusinessLocationCompleted: jest.fn(), recordTracksSkipBusinessLocationCompleted: jest.fn(),
redirectToWooHome: jest.fn(), redirectToWooHome: jest.fn(),
@ -34,6 +36,7 @@ const servicesOverrides = {
.mockResolvedValue( [ .mockResolvedValue( [
{ code: 'US', name: 'United States', states: [] }, { code: 'US', name: 'United States', states: [] },
] ), ] ),
getOnboardingProfileOption: jest.fn().mockResolvedValue( {} ),
}; };
/** /**

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add core profiler user profile page