From 7e7be4f9d0dd7d39e52a9baa8b2418cd4c0a4edf Mon Sep 17 00:00:00 2001 From: Ilyas Foo Date: Fri, 5 Apr 2024 10:44:57 +0800 Subject: [PATCH] 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 --- .../changelog/add-lys-success-screen | 4 + packages/js/components/package.json | 2 + .../src/confetti-animation/index.ts | 93 ++++++ packages/js/components/src/index.ts | 1 + .../changelog/add-lys-success-screen | 4 + .../customer-feedback-simple.tsx | 14 +- .../pages/launch-store-success.tsx | 21 -- .../pages/launch-store-success/Congrats.tsx | 277 +++++++++++++++++ .../pages/launch-store-success/WhatsNext.tsx | 189 ++++++++++++ .../pages/launch-store-success/actions.tsx | 46 +++ .../pages/launch-store-success/index.tsx | 57 ++++ .../pages/launch-store-success/services.tsx | 28 ++ .../pages/launch-store-success/style.scss | 292 ++++++++++++++++++ .../hub/main-content/xstate.tsx | 49 ++- .../changelog/add-lys-success-screen | 4 + plugins/woocommerce/src/Admin/API/Options.php | 1 + pnpm-lock.yaml | 14 + 17 files changed, 1067 insertions(+), 29 deletions(-) create mode 100644 packages/js/components/changelog/add-lys-success-screen create mode 100644 packages/js/components/src/confetti-animation/index.ts create mode 100644 packages/js/customer-effort-score/changelog/add-lys-success-screen delete mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success.tsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/Congrats.tsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/WhatsNext.tsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/actions.tsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/index.tsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/services.tsx create mode 100644 plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/style.scss create mode 100644 plugins/woocommerce/changelog/add-lys-success-screen diff --git a/packages/js/components/changelog/add-lys-success-screen b/packages/js/components/changelog/add-lys-success-screen new file mode 100644 index 00000000000..707e1166ace --- /dev/null +++ b/packages/js/components/changelog/add-lys-success-screen @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add confetti component and dependency from canvas-confetti package diff --git a/packages/js/components/package.json b/packages/js/components/package.json index f83d55afca6..88d751a2572 100644 --- a/packages/js/components/package.json +++ b/packages/js/components/package.json @@ -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", diff --git a/packages/js/components/src/confetti-animation/index.ts b/packages/js/components/src/confetti-animation/index.ts new file mode 100644 index 00000000000..825461354ca --- /dev/null +++ b/packages/js/components/src/confetti-animation/index.ts @@ -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; +}; diff --git a/packages/js/components/src/index.ts b/packages/js/components/src/index.ts index 7c91e5121b9..5551a1d1b7f 100644 --- a/packages/js/components/src/index.ts +++ b/packages/js/components/src/index.ts @@ -113,3 +113,4 @@ export { } from './product-section-layout'; export { DisplayState } from './display-state'; export { ProgressBar } from './progress-bar'; +export { ConfettiAnimation } from './confetti-animation'; diff --git a/packages/js/customer-effort-score/changelog/add-lys-success-screen b/packages/js/customer-effort-score/changelog/add-lys-success-screen new file mode 100644 index 00000000000..bbaf356f0dc --- /dev/null +++ b/packages/js/customer-effort-score/changelog/add-lys-success-screen @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Add value props to CustomerFeedbackSimple component diff --git a/packages/js/customer-effort-score/src/components/customer-feedback-simple/customer-feedback-simple.tsx b/packages/js/customer-effort-score/src/components/customer-feedback-simple/customer-feedback-simple.tsx index bbabcfda706..a4e18d939da 100644 --- a/packages/js/customer-effort-score/src/components/customer-feedback-simple/customer-feedback-simple.tsx +++ b/packages/js/customer-effort-score/src/components/customer-feedback-simple/customer-feedback-simple.tsx @@ -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; }; /** @@ -23,13 +25,15 @@ type CustomerFeedbackSimpleProps = { * * Upon completion, the score and comments is sent to a callback function. * - * @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 {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 } @@ -89,6 +96,7 @@ const CustomerFeedbackSimple: React.FC< CustomerFeedbackSimpleProps > = ( { CustomerFeedbackSimple.propTypes = { onSelect: PropTypes.func.isRequired, label: PropTypes.string.isRequired, + selectedValue: PropTypes.number, }; export { CustomerFeedbackSimple }; diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success.tsx deleted file mode 100644 index bf9bb40e9eb..00000000000 --- a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * External dependencies - */ -import classnames from 'classnames'; - -/** - * Internal dependencies - */ -import type { MainContentComponentProps } from '../xstate'; -export const LaunchYourStoreSuccess = ( props: MainContentComponentProps ) => { - return ( -
-

Main Content - Site Launch Store Success Page

-
- ); -}; diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/Congrats.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/Congrats.tsx new file mode 100644 index 00000000000..92334483a45 --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/Congrats.tsx @@ -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 ( +
+ +
+ + + + +
+
+

+ { __( + 'Congratulations! Your store is now live', + 'woocommerce' + ) } +

+

+ { __( + "You've successfully launched your store and are ready to start selling! We can't wait to see your business grow.", + 'woocommerce' + ) } +

+
+
+

{ hostname }

+
+ + +
+
+ + { isShowSurvey &&
} + + { isShowSurvey && ( +
+ { isShowThanks ? ( +
+

+ 🙌{ ' ' } + { __( + 'We appreciate your feedback!', + 'woocommerce' + ) } +

+ +
+ ) : ( +
+
+ + setEmojiValue( score ) + } + selectedValue={ emojiValue } + /> +
+ { shouldShowComment && ( +
+ + { + setFeedbackText( value ); + } } + /> + + { createInterpolateElement( + __( + 'Your feedback will be only be shared with WooCommerce and treated in accordance with our privacy policy.', + 'woocommerce' + ), + { + privacyLink: ( + + <> + + ), + } + ) } + +
+ ) } +
+ ) } + { shouldShowComment && ! isShowThanks && ( +
+
+ + +
+
+ ) } +
+ ) } +
+ { children } +
+
+ ); +}; diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/WhatsNext.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/WhatsNext.tsx new file mode 100644 index 00000000000..53ea8959ccd --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/WhatsNext.tsx @@ -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 ( +
+ { actions.map( ( item, index ) => ( +
+
+

{ item.title }

+

{ item.description }

+ +
+
+ ) ) } +
+ ); +}; diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/actions.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/actions.tsx new file mode 100644 index 00000000000..263fffe0b02 --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/actions.tsx @@ -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, + }; + }, +}; diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/index.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/index.tsx new file mode 100644 index 00000000000..dffa5b6dbaf --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/index.tsx @@ -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 ( +
+ +
+ ); + } + + return ( +
+ +

+ { __( "What's next?", 'woocommerce' ) } +

+ +
+
+ ); +}; diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/services.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/services.tsx new file mode 100644 index 00000000000..8eec613181b --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/services.tsx @@ -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, + }; +} ); diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/style.scss b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/style.scss new file mode 100644 index 00000000000..69fcf75f112 --- /dev/null +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/pages/launch-store-success/style.scss @@ -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; + } + } +} diff --git a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/xstate.tsx b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/xstate.tsx index 937651072c8..3294cb47532 100644 --- a/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/xstate.tsx +++ b/plugins/woocommerce-admin/client/launch-your-store/hub/main-content/xstate.tsx @@ -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', diff --git a/plugins/woocommerce/changelog/add-lys-success-screen b/plugins/woocommerce/changelog/add-lys-success-screen new file mode 100644 index 00000000000..a0d652bfd31 --- /dev/null +++ b/plugins/woocommerce/changelog/add-lys-success-screen @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add Launch Your Store success screen diff --git a/plugins/woocommerce/src/Admin/API/Options.php b/plugins/woocommerce/src/Admin/API/Options.php index 51de1197a19..5ff8580a709 100644 --- a/plugins/woocommerce/src/Admin/API/Options.php +++ b/plugins/woocommerce/src/Admin/API/Options.php @@ -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', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a381238d58d..8736fa659f2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: