Add launch your store success screen (#46103)

* Initial commit with LYS components

* Update CustomerFeedbackSimple component to support emoji value props

* Add confetti package in woocommerce components

* Add confetti usage

* Remove unnecessary files

* Update pnpm lock

* Changelogs

* Lint and temporarily comment out tests

* Lint css and rename image

* Various fixes

* Rename transitional to congrats

* Add copy link functionaility from Moon's code and move whatsnext component

* Rename components

* Move and renames

* Fix ref type

* Add temporary dynamic actions

* Remove confetti background

* Add header

* Update xstate actions for launch success page

* Add temporary spinner

* Combine congrats data fetching to a single action and service

* Add functioning dynamic actions list

* Temporarily remove test

* Cleanups

* More cleanups

* Small lint

* add url listener for content param

* Update comment on confetti package

* Remove lodash and replace with reduce

* Fix Woo Express condition

---------

Co-authored-by: rjchow <me@rjchow.com>
This commit is contained in:
Ilyas Foo 2024-04-05 10:44:57 +08:00 committed by GitHub
parent c551667646
commit 7e7be4f9d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 1067 additions and 29 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add confetti component and dependency from canvas-confetti package

View File

@ -97,6 +97,7 @@
"@wordpress/rich-text": "wp-6.0",
"@wordpress/url": "wp-6.0",
"@wordpress/viewport": "^4.20.0",
"canvas-confetti": "^1.9.2",
"classnames": "^2.3.2",
"core-js": "^3.34.0",
"d3-axis": "^1.0.12",
@ -148,6 +149,7 @@
"@testing-library/react": "12.1.3",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "13.5.0",
"@types/canvas-confetti": "^1.6.4",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.202",
"@types/prop-types": "^15.7.11",

View File

@ -0,0 +1,93 @@
/**
* External dependencies
*/
import confetti from 'canvas-confetti';
import { useEffect } from '@wordpress/element';
/**
* Note: This was copied over from https://github.com/Automattic/wp-calypso/blob/a39539547780871d0371a20fcf21c767a86a1010/packages/components/src/confetti/index.ts
* since there was problems with importing @automattic/components:2.1.0 due to versions of its dependencies breaking wc core.
* If we do not end up making further adjustments in this file that are not supported by the original implementation,
* we should consider replacing it with the npm package when we're able to.
*/
const COLORS = [
'#31CC9F',
'#618DF2',
'#6AB3D0',
'#B35EB1',
'#F2D76B',
'#FAA754',
'#E34C84',
];
type FireOptions = {
spread: number;
startVelocity?: number;
decay?: number;
scalar?: number;
};
function fireConfetti( colors: string[] ) {
const count = 60;
const scale = 2;
const defaults = {
origin: { y: 0.4 },
colors,
scalar: scale,
spread: 180,
gravity: 6,
};
function fire( particleRatio: number, opts: FireOptions ) {
confetti(
Object.assign( {}, defaults, opts, {
particleCount: Math.floor( count * particleRatio ),
startVelocity: opts.startVelocity
? scale * opts.startVelocity
: undefined,
spread: scale * opts.spread,
scalar: opts.scalar ? scale * opts.scalar : scale,
// counter react-modal very high z index, always render the confetti on top
zIndex: 1000000,
} )
);
}
fire( 0.25, {
spread: 26,
startVelocity: 55,
} );
fire( 0.2, {
spread: 60,
} );
fire( 0.35, {
spread: 100,
decay: 0.91,
scalar: 0.8,
} );
fire( 0.1, {
spread: 120,
startVelocity: 25,
decay: 0.92,
scalar: 1.2,
} );
fire( 0.1, {
spread: 120,
startVelocity: 45,
} );
}
export const ConfettiAnimation = ( {
trigger = true,
delay = 0,
colors = COLORS,
} ) => {
useEffect( () => {
if ( trigger ) {
setTimeout( () => fireConfetti( colors ), delay );
}
}, [ trigger, delay, colors ] );
return null;
};

View File

@ -113,3 +113,4 @@ export {
} from './product-section-layout';
export { DisplayState } from './display-state';
export { ProgressBar } from './progress-bar';
export { ConfettiAnimation } from './confetti-animation';

View File

@ -0,0 +1,4 @@
Significance: minor
Type: update
Add value props to CustomerFeedbackSimple component

View File

@ -6,10 +6,12 @@ import PropTypes from 'prop-types';
import { Button, Tooltip } from '@wordpress/components';
import { Text } from '@woocommerce/experimental';
import { __ } from '@wordpress/i18n';
import classNames from 'classnames';
type CustomerFeedbackSimpleProps = {
onSelect: ( score: number ) => void;
label: string;
selectedValue?: number | null;
};
/**
@ -26,10 +28,12 @@ type CustomerFeedbackSimpleProps = {
* @param {Object} props Component props.
* @param {Function} props.onSelect Function to call when the results are sent.
* @param {string} props.label Question to ask the customer.
* @param {number|null} [props.selectedValue] The default selected value.
*/
const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( {
onSelect,
label,
selectedValue,
} ) => {
const options = [
{
@ -76,6 +80,9 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( {
onClick={ () => {
onSelect( option.value );
} }
className={ classNames( {
'is-selected': selectedValue === option.value,
} ) }
>
{ option.emoji }
</Button>
@ -89,6 +96,7 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( {
CustomerFeedbackSimple.propTypes = {
onSelect: PropTypes.func.isRequired,
label: PropTypes.string.isRequired,
selectedValue: PropTypes.number,
};
export { CustomerFeedbackSimple };

View File

@ -1,21 +0,0 @@
/**
* External dependencies
*/
import classnames from 'classnames';
/**
* Internal dependencies
*/
import type { MainContentComponentProps } from '../xstate';
export const LaunchYourStoreSuccess = ( props: MainContentComponentProps ) => {
return (
<div
className={ classnames(
'launch-store-success-page__container',
props.className
) }
>
<p>Main Content - Site Launch Store Success Page</p>
</div>
);
};

View File

@ -0,0 +1,277 @@
/* eslint-disable @woocommerce/dependency-group */
/* eslint-disable @typescript-eslint/ban-ts-comment */
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { getSetting } from '@woocommerce/settings';
import { recordEvent } from '@woocommerce/tracks';
import { createInterpolateElement, useState } from '@wordpress/element';
import { Link, ConfettiAnimation } from '@woocommerce/components';
import { isInteger } from 'lodash';
import { closeSmall } from '@wordpress/icons';
import { CustomerFeedbackSimple } from '@woocommerce/customer-effort-score';
import { useCopyToClipboard } from '@wordpress/compose';
import { Button, TextareaControl, Icon, Dashicon } from '@wordpress/components';
/**
* Internal dependencies
*/
import './style.scss';
import WooLogo from '~/core-profiler/components/navigation/woologo';
import { navigateTo } from '@woocommerce/navigation';
export type CongratsProps = {
hasCompleteSurvey: boolean;
isWooExpress: boolean;
completeSurvey: () => void;
children?: React.ReactNode;
};
export const Congrats = ( {
hasCompleteSurvey,
isWooExpress,
completeSurvey,
children,
}: CongratsProps ) => {
const copyLink = __( 'Copy link', 'woocommerce' );
const copied = __( 'Copied!', 'woocommerce' );
const homeUrl: string = getSetting( 'homeUrl', '' );
const urlObject = new URL( homeUrl );
let hostname: string = urlObject?.hostname;
if ( urlObject?.port ) {
hostname += ':' + urlObject.port;
}
const [ isShowSurvey, setIsShowSurvey ] = useState< boolean >(
! hasCompleteSurvey
);
const [ emojiValue, setEmojiValue ] = useState< number | null >( null );
const [ feedbackText, setFeedbackText ] = useState< string >( '' );
const [ isShowThanks, setIsShowThanks ] = useState< boolean >( false );
const [ copyLinkText, setCopyLinkText ] = useState( copyLink );
const shouldShowComment = isInteger( emojiValue );
const copyClipboardRef = useCopyToClipboard< HTMLAnchorElement >(
homeUrl,
() => {
setCopyLinkText( copied );
setTimeout( () => {
setCopyLinkText( copyLink );
}, 2000 );
}
);
const sendData = () => {
const emojis = {
1: 'very_difficult',
2: 'difficult',
3: 'neutral',
4: 'good',
5: 'very_good',
} as const;
const emoji_value = emojiValue
? emojis[ emojiValue as keyof typeof emojis ]
: 'none';
recordEvent( 'launch_your_store_congrats_survey_complete', {
emoji: emoji_value,
feedback: feedbackText,
} );
setIsShowThanks( true );
completeSurvey();
};
return (
<div className="woocommerce-launch-store__congrats">
<ConfettiAnimation delay={ 1000 } />
<div className="woocommerce-launch-store__congrats-header-container">
<span className="woologo">
<WooLogo />
</span>
<Button
onClick={ () => {
navigateTo( { url: '/' } );
} }
className="back-to-home-button"
variant="link"
>
<Dashicon icon="arrow-left-alt2"></Dashicon>
<span>{ __( 'Back to Home', 'woocommerce' ) }</span>
</Button>
</div>
<div className="woocommerce-launch-store__congrats-content">
<h1 className="woocommerce-launch-store__congrats-heading">
{ __(
'Congratulations! Your store is now live',
'woocommerce'
) }
</h1>
<h2 className="woocommerce-launch-store__congrats-subheading">
{ __(
"You've successfully launched your store and are ready to start selling! We can't wait to see your business grow.",
'woocommerce'
) }
</h2>
<div className="woocommerce-launch-store__congrats-midsection-container">
<div className="woocommerce-launch-store__congrats-visit-store">
<p className="store-name">{ hostname }</p>
<div className="buttons-container">
<Button
className=""
variant="secondary"
ref={ copyClipboardRef }
onClick={ () => {
recordEvent(
'launch_you_store_congrats_copy_store_link_click'
);
} }
>
{ copyLinkText }
</Button>
<Button
className=""
variant="primary"
onClick={ () => {
recordEvent(
'launch_you_store_congrats_preview_store_click'
);
window.open( homeUrl, '_blank' );
} }
>
{ __( 'Visit your store', 'woocommerce' ) }
</Button>
</div>
</div>
{ isShowSurvey && <hr className="separator" /> }
{ isShowSurvey && (
<div className="woocommerce-launch-store__congrats-survey">
{ isShowThanks ? (
<div className="woocommerce-launch-store__congrats-thanks">
<p className="thanks-copy">
🙌{ ' ' }
{ __(
'We appreciate your feedback!',
'woocommerce'
) }
</p>
<Button
className="close-button"
label={ __( 'Close', 'woocommerce' ) }
icon={
<Icon
icon={ closeSmall }
viewBox="6 4 12 14"
/>
}
iconSize={ 14 }
size={ 24 }
onClick={ () => {
setIsShowThanks( false );
setIsShowSurvey( false );
} }
></Button>
</div>
) : (
<div className="woocommerce-launch-store__congrats-section_1">
<div className="woocommerce-launch-store__congrats-survey__selection">
<CustomerFeedbackSimple
label={ __(
'How was the experience of launching your store?',
'woocommerce'
) }
onSelect={ ( score ) =>
setEmojiValue( score )
}
selectedValue={ emojiValue }
/>
</div>
{ shouldShowComment && (
<div className="woocommerce-launch-store__congrats-survey__comment">
<label
className="comment-label"
htmlFor="launch-your-store-comment"
>
{ createInterpolateElement(
__(
'Why do you feel that way? <smallText>(optional)</smallText>',
'woocommerce'
),
{
smallText: (
<span className="small-text" />
),
}
) }
</label>
<TextareaControl
id="launch-your-store-comment"
value={ feedbackText }
onChange={ ( value ) => {
setFeedbackText( value );
} }
/>
<span className="privacy-text">
{ createInterpolateElement(
__(
'Your feedback will be only be shared with WooCommerce and treated in accordance with our <privacyLink>privacy policy</privacyLink>.',
'woocommerce'
),
{
privacyLink: (
<Link
href="https://automattic.com/privacy/"
type="external"
target="_blank"
>
<></>
</Link>
),
}
) }
</span>
</div>
) }
</div>
) }
{ shouldShowComment && ! isShowThanks && (
<div className="woocommerce-launch-store__congrats-section_2">
<div className="woocommerce-launch-store__congrats-buttons">
<Button
className=""
variant="tertiary"
onClick={ () => {
setEmojiValue( null );
} }
>
{ __( 'Cancel', 'woocommerce' ) }
</Button>
<Button
className=""
variant="primary"
onClick={ () => {
recordEvent(
isWooExpress
? 'launch_you_store_congrats_survey_click'
: 'launch_you_store_on_core_congrats_survey_click'
);
sendData();
} }
>
{ __( 'Send', 'woocommerce' ) }
</Button>
</div>
</div>
) }
</div>
) }
</div>
{ children }
</div>
</div>
);
};

View File

@ -0,0 +1,189 @@
/**
* External dependencies
*/
import { __ } from '@wordpress/i18n';
import { recordEvent } from '@woocommerce/tracks';
import { Button } from '@wordpress/components';
import { useMemo } from '@wordpress/element';
import type { TaskListType } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { ADMIN_URL } from '~/utils/admin-settings';
type WhatsNextProps = {
activePlugins: string[];
allTasklists: TaskListType[];
};
type Action = {
title: string;
description: string;
link: string;
linkText: string;
trackEvent: string;
};
const getActionsList = ( { activePlugins, allTasklists }: WhatsNextProps ) => {
const actions: Action[] = [];
const pick = ( action: Action, condition: boolean ) => {
if ( actions.length < 3 && condition ) {
actions.push( action );
}
};
const setupTasksCompletion = allTasklists
.find( ( { id } ) => id === 'setup' )
?.tasks?.reduce(
( acc: Record< string, boolean >, { id, isComplete } ) => {
acc[ id ] = isComplete || false;
return acc;
},
{}
);
const extendedTasksCompletion = allTasklists
.find( ( { id } ) => id === 'extended' )
?.tasks?.reduce(
( acc: Record< string, boolean >, { id, isComplete } ) => {
acc[ id ] = isComplete || false;
return acc;
},
{}
);
const isMarketingTaskCompleted = setupTasksCompletion?.marketing || false;
const isPaymentsTaskCompleted = setupTasksCompletion?.payments || false;
const isMobileTaskCompleted =
extendedTasksCompletion?.[ 'get-mobile-app' ] || false;
const isMailChimpActivated = activePlugins.includes(
'mailchimp-for-woocommerce'
);
const marketing = {
title: __( 'Promote your products', 'woocommerce' ),
description: __(
'Grow your customer base by promoting your products to millions of engaged shoppers.',
'woocommerce'
),
link: `${ ADMIN_URL }admin.php?page=wc-admin&task=marketing`,
linkText: __( 'Promote products', 'woocommerce' ),
trackEvent: 'launch_you_store_congrats_marketing_click',
};
const payments = {
title: __( 'Provide more ways to pay', 'woocommerce' ),
description: __(
'Give your shoppers more ways to pay by adding additional payment methods to your store.',
'woocommerce'
),
link: `${ ADMIN_URL }admin.php?page=wc-admin&task=payments`,
linkText: __( 'Add payment methods', 'woocommerce' ),
trackEvent: 'launch_you_store_congrats_payments_click',
};
const mailchimp = {
title: __( 'Build customer relationships', 'woocommerce' ),
description: __(
"Keep your shoppers up to date with what's new in your store and set up clever post-purchase automations.",
'woocommerce'
),
link: isMailChimpActivated
? `${ ADMIN_URL }admin.php?page=mailchimp-woocommerce`
: 'https://woo.com/products/mailchimp-for-woocommerce/?utm_source=launch_your_store&utm_medium=product',
linkText: isMailChimpActivated
? __( 'Manage Mailchimp', 'woocommerce' )
: __( 'Install Mailchimp', 'woocommerce' ),
trackEvent: 'launch_you_store_congrats_mailchimp_click',
};
const extensions = {
title: __( 'Power up your store', 'woocommerce' ),
description: __(
'Add extra features and functionality to your store with Woo extensions.',
'woocommerce'
),
link: `${ ADMIN_URL }admin.php?page=wc-admin&path=%2Fextensions`,
linkText: __( 'Add extensions', 'woocommerce' ),
trackEvent: 'launch_you_store_congrats_extensions_click',
};
const mobileApp = {
title: __( 'Manage your store on the go', 'woocommerce' ),
description: __(
'Manage your store anywhere with the free WooCommerce Mobile App.',
'woocommerce'
),
link: `${ ADMIN_URL }admin.php?page=wc-admin&mobileAppModal=true`,
linkText: __( 'Get the app', 'woocommerce' ),
trackEvent: 'launch_you_store_congrats_mobile_app_click',
};
const externalDocumentation = {
title: __( 'Help is on hand', 'woocommerce' ),
description: __(
"Detailed guides and our support team are always available if you're feeling stuck or need some guidance.",
'woocommerce'
),
link: `https://woo.com/documentation/woocommerce/?utm_source=launch_your_store&utm_medium=product`,
linkText: __( 'Explore support resources', 'woocommerce' ),
trackEvent: 'launch_you_store_congrats_external_documentation_click',
};
// Pick first three
pick( marketing, ! isMarketingTaskCompleted );
pick( payments, ! isPaymentsTaskCompleted );
pick( extensions, true ); // No condition yet
// Pick second three
pick( mobileApp, isMobileTaskCompleted );
pick( mailchimp, true ); // No condition yet
pick( externalDocumentation, true ); // No condition yet
// Pick last three
pick( payments, true );
pick( extensions, true );
pick( externalDocumentation, true );
return actions;
};
export const WhatsNext = ( {
activePlugins,
allTasklists,
}: WhatsNextProps ) => {
const actions = useMemo( () => {
return getActionsList( { activePlugins, allTasklists } );
}, [ activePlugins, allTasklists ] );
return (
<div className="woocommerce-launch-store__congrats-main-actions">
{ actions.map( ( item, index ) => (
<div
className="woocommerce-launch-store__congrats-action"
key={ index }
>
<div className="woocommerce-launch-store__congrats-action__content">
<h3>{ item.title }</h3>
<p>{ item.description }</p>
<Button
variant="link"
href={ item.link }
target={
item.link.indexOf( ADMIN_URL ) === -1
? '_blank'
: '_self'
}
onClick={ () => {
recordEvent( item.trackEvent );
} }
>
{ item.linkText }
</Button>
</div>
</div>
) ) }
</div>
);
};

View File

@ -0,0 +1,46 @@
/**
* External dependencies
*/
import { DoneActorEvent } from 'xstate5';
import { dispatch } from '@wordpress/data';
import { OPTIONS_STORE_NAME, TaskListType } from '@woocommerce/data';
/**
* Internal dependencies
*/
import type { MainContentMachineContext } from '../../../main-content/xstate';
export const assignCompleteSurvey = {
congratsScreen: ( { context }: { context: MainContentMachineContext } ) => {
dispatch( OPTIONS_STORE_NAME ).updateOptions( {
woocommerce_admin_launch_your_store_survey_completed: 'yes',
} );
return {
...context.congratsScreen,
hasCompleteSurvey: true,
};
},
};
export const assignCongratsData = {
congratsScreen: ( {
context,
event,
}: {
context: MainContentMachineContext;
event: DoneActorEvent< {
surveyCompleted: string | null;
tasklists: TaskListType[];
activePlugins: string[];
} >;
} ) => {
return {
...context.congratsScreen,
hasLoadedCongratsData: true,
hasCompleteSurvey: event.output.surveyCompleted === 'yes',
allTasklists: event.output.tasklists,
activePlugins: event.output.activePlugins,
};
},
};

View File

@ -0,0 +1,57 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import { Spinner } from '@woocommerce/components';
import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import type { MainContentComponentProps } from '../../xstate';
import { Congrats } from './Congrats';
export * as actions from './actions';
export * as services from './services';
export type events = { type: 'COMPLETE_SURVEY' };
import { WhatsNext } from './WhatsNext';
import { isWooExpress } from '~/utils/is-woo-express';
export const LaunchYourStoreSuccess = ( props: MainContentComponentProps ) => {
const completeSurvey = () => {
props.sendEventToMainContent( { type: 'COMPLETE_SURVEY' } );
};
// Temporary spinner until data load is moved to loading screen or somewhere else.
if ( ! props.context.congratsScreen.hasLoadedCongratsData ) {
return (
<div className="spinner-container">
<Spinner></Spinner>
</div>
);
}
return (
<div
className={ classnames(
'launch-store-success-page__container',
props.className
) }
>
<Congrats
hasCompleteSurvey={
props.context.congratsScreen.hasCompleteSurvey
}
isWooExpress={ isWooExpress() }
completeSurvey={ completeSurvey }
>
<h2 className="woocommerce-launch-store__congrats-main-actions-title">
{ __( "What's next?", 'woocommerce' ) }
</h2>
<WhatsNext
activePlugins={ props.context.congratsScreen.activePlugins }
allTasklists={ props.context.congratsScreen.allTasklists }
/>
</Congrats>
</div>
);
};

View File

@ -0,0 +1,28 @@
/**
* External dependencies
*/
import {
ONBOARDING_STORE_NAME,
OPTIONS_STORE_NAME,
PLUGINS_STORE_NAME,
} from '@woocommerce/data';
import { resolveSelect } from '@wordpress/data';
import { fromPromise } from 'xstate5';
export const fetchCongratsData = fromPromise( async () => {
const [ surveyCompleted, tasklists, activePlugins ] = await Promise.all( [
resolveSelect( OPTIONS_STORE_NAME ).getOption(
'woocommerce_admin_launch_your_store_survey_completed'
),
resolveSelect( ONBOARDING_STORE_NAME ).getTaskListsByIds( [
'setup',
'extended',
] ),
resolveSelect( PLUGINS_STORE_NAME ).getActivePlugins(),
] );
return {
surveyCompleted: surveyCompleted as string | null,
tasklists,
activePlugins,
};
} );

View File

@ -0,0 +1,292 @@
.launch-store-success-page__container {
background: #fff;
background-repeat: no-repeat;
background-position: 50% 0;
}
.launch-your-store-layout__content {
.spinner-container {
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
}
.woocommerce-launch-store__congrats {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
.woocommerce-launch-store__congrats-header-container {
min-height: 64px;
display: flex;
align-items: center;
justify-content: left;
.woologo {
padding-top: 8px;
svg {
margin-left: 38px;
margin-right: 24px;
width: 38px;
height: 23px;
}
}
.back-to-home-button {
padding: 0;
height: 24px;
text-decoration: none;
.dashicon {
padding-right: 22px;
}
span {
color: #3c434a;
font-size: 14px;
line-height: 20px;
letter-spacing: -0.15px;
font-weight: 500;
}
}
}
.woocommerce-launch-store__congrats-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
.woocommerce-launch-store__congrats-buttons {
display: flex;
gap: 8px;
button {
flex: 1;
}
}
}
.woocommerce-launch-store__congrats-heading {
color: $gray-900;
text-align: center;
font-feature-settings: "clig" off, "liga" off;
font-size: 32px;
font-style: normal;
font-weight: 400;
line-height: 60px; /* 187.5% */
letter-spacing: -0.32px;
margin: 0;
@media screen and (max-width: 600px) {
line-height: 38px;
padding-bottom: 8px;
}
}
.woocommerce-launch-store__congrats-subheading {
color: $gray-700;
text-align: center;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 150% */
letter-spacing: -0.1px;
margin: 4px 0 0;
max-width: 560px;
}
.woocommerce-launch-store__congrats-main-actions-title {
font-size: 20px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 120% */
margin-top: 50px;
margin-bottom: 40px;
}
.woocommerce-launch-store__congrats-main-actions {
display: flex;
gap: 40px;
flex-direction: row;
@media only screen and (max-width: 600px) {
flex-direction: column;
}
h3 {
margin: 0;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 150% */
letter-spacing: -0.32px;
}
.components-button {
text-decoration: none;
}
.woocommerce-launch-store__congrats-action {
display: flex;
}
.woocommerce-launch-store__congrats-action__content {
flex: 1;
}
.woocommerce-launch-store__congrats-action__content {
max-width: 250px;
padding-right: 2px;
p {
margin-top: 5px;
margin-bottom: 16px;
}
}
}
.woocommerce-launch-store__congrats-midsection-container {
margin: 50px 0;
border: 1px solid #dcdcde;
background: #fff;
border-radius: 4px;
max-width: 100%;
width: 650px;
.woocommerce-launch-store__congrats-thanks {
width: 100%;
display: flex;
align-items: center;
margin: 4px 0;
.thanks-copy {
flex-grow: 1;
font-size: 13px;
line-height: 20px;
margin: 0;
}
.close-button {
width: 24px;
height: 24px;
min-width: initial;
}
}
.woocommerce-launch-store__congrats-section_1 {
display: flex;
gap: 20px;
flex-direction: column;
width: 100%;
}
.woocommerce-launch-store__congrats-section_2 {
display: flex;
width: 100%;
justify-content: flex-end;
margin-bottom: 12px;
}
.woocommerce-launch-store__congrats-visit-store {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
padding: 16px 24px;
@media screen and (max-width: 600px) {
.buttons-container {
flex-direction: column;
justify-content: center;
button {
justify-content: center;
}
}
}
.store-name {
font-size: 16px;
font-weight: 500;
flex-grow: 1;
width: 353px;
}
.buttons-container {
display: flex;
gap: 12px;
}
}
hr.separator {
margin: 0;
border-top: 0;
border-bottom: 1px solid #f0f0f0;
}
.woocommerce-launch-store__congrats-survey {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 12px 24px;
gap: 12px;
}
.woocommerce-launch-store__congrats-survey__selection {
display: flex;
width: 100%;
}
}
.customer-feedback-simple__container {
width: 100%;
display: flex;
height: 32px;
p {
flex: 1;
font-size: 14px;
}
.components-button {
box-sizing: border-box;
height: 30px;
&.is-selected,
&:hover {
background-color: rgba(var(--wp-admin-theme-color--rgb), 0.1);
outline: 3px solid transparent;
}
}
}
.woocommerce-launch-store__congrats-survey__comment {
display: flex;
flex-direction: column;
gap: 8px;
color: rgb(30, 30, 30);
.comment-label {
display: block;
.small-text {
color: #757575;
}
}
.privacy-text {
font-size: 11px;
color: #757575;
}
.components-base-control .components-base-control__field {
margin-bottom: 0;
}
}
}

View File

@ -1,21 +1,32 @@
/**
* External dependencies
*/
import { fromCallback, setup } from 'xstate5';
import { assign, fromCallback, setup } from 'xstate5';
import React from 'react';
import { getQuery } from '@woocommerce/navigation';
import type { TaskListType } from '@woocommerce/data';
/**
* Internal dependencies
*/
import { LoadingPage } from './pages/loading';
import { LaunchYourStoreSuccess } from './pages/launch-store-success';
import { SitePreviewPage } from './pages/site-preview';
import type { LaunchYourStoreComponentProps } from '..';
import { createQueryParamsListener, updateQueryParams } from '../common';
import {
services as congratsServices,
events as congratsEvents,
actions as congratsActions,
LaunchYourStoreSuccess,
} from './pages/launch-store-success';
export type MainContentMachineContext = {
placeholder?: string; // remove this when we have some types to put here
congratsScreen: {
hasLoadedCongratsData: boolean;
hasCompleteSurvey: boolean;
allTasklists: TaskListType[];
activePlugins: string[];
};
};
export type MainContentComponentProps = LaunchYourStoreComponentProps & {
@ -23,11 +34,14 @@ export type MainContentComponentProps = LaunchYourStoreComponentProps & {
};
export type MainContentMachineEvents =
| { type: 'SHOW_LAUNCH_STORE_SUCCESS' }
| { type: 'SHOW_LOADING' };
| { type: 'EXTERNAL_URL_UPDATE' }
| { type: 'SHOW_LOADING' }
| congratsEvents;
const contentQueryParamListener = fromCallback( ( { sendBack } ) => {
return createQueryParamsListener( 'content', sendBack );
} );
export const mainContentMachine = setup( {
types: {} as {
context: MainContentMachineContext;
@ -52,11 +66,23 @@ export const mainContentMachine = setup( {
},
actors: {
contentQueryParamListener,
fetchCongratsData: congratsServices.fetchCongratsData,
},
} ).createMachine( {
id: 'mainContent',
initial: 'navigate',
context: {},
context: {
congratsScreen: {
hasLoadedCongratsData: false,
hasCompleteSurvey: false,
allTasklists: [],
activePlugins: [],
},
},
invoke: {
id: 'contentQueryParamListener',
src: 'contentQueryParamListener',
},
states: {
navigate: {
always: [
@ -86,6 +112,14 @@ export const mainContentMachine = setup( {
},
launchStoreSuccess: {
id: 'launchStoreSuccess',
invoke: [
{
src: 'fetchCongratsData',
onDone: {
actions: assign( congratsActions.assignCongratsData ),
},
},
],
entry: [
{
type: 'updateQueryParams',
@ -95,6 +129,11 @@ export const mainContentMachine = setup( {
meta: {
component: LaunchYourStoreSuccess,
},
on: {
COMPLETE_SURVEY: {
actions: assign( congratsActions.assignCompleteSurvey ),
},
},
},
loading: {
id: 'loading',

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add Launch Your Store success screen

View File

@ -220,6 +220,7 @@ class Options extends \WC_REST_Data_Controller {
'woocommerce_admin_customize_store_completed',
'woocommerce_admin_customize_store_completed_theme_id',
'woocommerce_admin_customize_store_survey_completed',
'woocommerce_admin_launch_your_store_survey_completed',
'woocommerce_coming_soon',
'woocommerce_store_pages_only',
'woocommerce_private_link',

View File

@ -716,6 +716,9 @@ importers:
'@wordpress/viewport':
specifier: ^4.20.0
version: 4.20.0(react@17.0.2)
canvas-confetti:
specifier: ^1.9.2
version: 1.9.2
classnames:
specifier: ^2.3.2
version: 2.3.2
@ -837,6 +840,9 @@ importers:
'@testing-library/user-event':
specifier: 13.5.0
version: 13.5.0(@testing-library/dom@8.11.3)
'@types/canvas-confetti':
specifier: ^1.6.4
version: 1.6.4
'@types/jest':
specifier: ^27.5.2
version: 27.5.2
@ -18823,6 +18829,10 @@ packages:
'@types/node': 16.18.68
'@types/responselike': 1.0.3
/@types/canvas-confetti@1.6.4:
resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==}
dev: true
/@types/cheerio@0.22.35:
resolution: {integrity: sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==}
dependencies:
@ -27989,6 +27999,10 @@ packages:
/caniuse-lite@1.0.30001568:
resolution: {integrity: sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==}
/canvas-confetti@1.9.2:
resolution: {integrity: sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==}
dev: false
/capital-case@1.0.4:
resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==}
dependencies: