diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx index f39315ec8e482..4ddfc540f2109 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/plans/plans-wrapper.tsx @@ -25,6 +25,7 @@ import { connect } from 'react-redux'; import QueryPlans from 'calypso/components/data/query-plans'; import { LoadingEllipsis } from 'calypso/components/loading-ellipsis'; import { useSite } from 'calypso/landing/stepper/hooks/use-site'; +import { getPlanCartItem } from 'calypso/lib/cart-values/cart-items'; import PlansFeaturesMain from 'calypso/my-sites/plans-features-main'; import PlanFAQ from 'calypso/my-sites/plans-features-main/components/plan-faq'; import StepWrapper from 'calypso/signup/step-wrapper'; @@ -92,11 +93,11 @@ const PlansWrapper: React.FC< Props > = ( props ) => { ? reduxHideFreePlan && 'plans-blog-onboarding' === plansIntent : reduxHideFreePlan; - const onSelectPlan = ( selectedPlan: any ) => { - if ( selectedPlan ) { + const onSelectPlan = ( cartItems?: MinimalRequestCartProduct[] | null ) => { + const planCartItem = getPlanCartItem( cartItems ); + if ( planCartItem ) { recordTracksEvent( 'calypso_signup_plan_select', { - product_slug: selectedPlan?.product_slug, - free_trial: selectedPlan?.free_trial, + product_slug: planCartItem?.product_slug, from_section: 'default', } ); } else { @@ -105,8 +106,8 @@ const PlansWrapper: React.FC< Props > = ( props ) => { } ); } - setPlanCartItem( selectedPlan ); - props.onSubmit?.( selectedPlan ); + setPlanCartItem( planCartItem ); + props.onSubmit?.( planCartItem ); }; const getPaidDomainName = () => { diff --git a/client/lib/cart-values/cart-items.ts b/client/lib/cart-values/cart-items.ts index 833997b99d9b1..d79a8a92ec055 100644 --- a/client/lib/cart-values/cart-items.ts +++ b/client/lib/cart-values/cart-items.ts @@ -44,6 +44,7 @@ import { isAkismetProduct, isWpcomEnterpriseGridPlan, is100Year, + PLAN_FREE, } from '@automattic/calypso-products'; import { isWpComProductRenewal as isRenewal } from '@automattic/wpcom-checkout'; import { getTld } from 'calypso/lib/domains'; @@ -898,3 +899,17 @@ export function hasStaleItem( cart: ObjectWithProducts ): boolean { ); } ); } + +export function getPlanCartItem( cartItems?: MinimalRequestCartProduct[] | null ) { + /** + * A null planCartItem corresponds to a free plan. It seems like this is case throughout the signup/plans + * onboarding codebase. There are, however, tests in client/signup/steps/plans/test/index.jsx that + * represent a free plan as a non null product. + * + * Additionally, free plans are, in fact, represented as products with product slugs elsewhere in the + * codebase. This is why we check for both cases here. When we conduct a more thorough investigation and + * determine that PLAN_FREE is no longer, in fact, used to represent free plans in signup/onboarding, we + * can remove PLAN_FREE check. p4TIVU-aLF-p2 + */ + return cartItems?.find( ( item ) => isPlan( item ) || item.product_slug === PLAN_FREE ) ?? null; +} diff --git a/client/lib/signup/flow-controller.ts b/client/lib/signup/flow-controller.ts index b956ab80bf9a5..feca38c4b625b 100644 --- a/client/lib/signup/flow-controller.ts +++ b/client/lib/signup/flow-controller.ts @@ -45,6 +45,7 @@ import { import { ProgressState } from 'calypso/state/signup/progress/schema'; import { getSignupProgress } from 'calypso/state/signup/progress/selectors'; import { getSiteSlug } from 'calypso/state/sites/selectors'; +import { getPlanCartItem } from '../cart-values/cart-items'; import type { Flow, Dependencies } from '../../signup/types'; const debug = debugModule( 'calypso:signup' ); @@ -442,7 +443,14 @@ export default class SignupFlowController { _getNeedsToGoThroughCheckout() { const progress = getSignupDependencyProgress( this._reduxStore.getState() ); - return !! progress?.plans?.cartItem; + + if ( ! progress?.plans?.cartItems ) { + return false; + } + + return ( + progress.plans.cartItems.length && Boolean( getPlanCartItem( progress.plans.cartItems ) ) + ); } _destination( dependencies: Dependencies ): string { diff --git a/client/lib/signup/step-actions/index.js b/client/lib/signup/step-actions/index.js index 50ce58832248c..5c304101ddf03 100644 --- a/client/lib/signup/step-actions/index.js +++ b/client/lib/signup/step-actions/index.js @@ -20,6 +20,7 @@ import { supportsPrivacyProtectionPurchase, planItem as getCartItemForPlan, marketplaceThemeProduct, + getPlanCartItem, } from 'calypso/lib/cart-values/cart-items'; import { getLocaleSlug } from 'calypso/lib/i18n-utils'; import { fetchSitesAndUser } from 'calypso/lib/signup/step-actions/fetch-sites-and-user'; @@ -507,12 +508,12 @@ function findMarketplacePlugin( state, pluginSlug, billingPeriod = '' ) { } export function addWithThemePlanToCart( callback, dependencies, stepProvidedItems, reduxStore ) { - const { cartItem } = stepProvidedItems; - + const { cartItems } = stepProvidedItems; + const planCartItem = getPlanCartItem( cartItems ); /** * If the user selected the free option, then we should abort the checkout part. */ - if ( ! cartItem ) { + if ( ! planCartItem ) { defer( callback ); return; } @@ -528,7 +529,7 @@ export function addWithThemePlanToCart( callback, dependencies, stepProvidedItem reduxStore.dispatch, themeSlug, dependencies.siteSlug, - cartItem + planCartItem ).then( () => {} ); } else { addPlanToCart( callback, dependencies, stepProvidedItems, reduxStore ); @@ -607,8 +608,8 @@ export function addPlanToCart( callback, dependencies, stepProvidedItems, reduxS // Note that we pull in emailItem to avoid race conditions from multiple step API functions // trying to fetch and update the cart simultaneously, as both of those actions are asynchronous. const { emailItem, siteSlug, plugin, billing_period: billingPeriod } = dependencies; - const { cartItem, lastKnownFlow } = stepProvidedItems; - if ( isEmpty( cartItem ) && isEmpty( emailItem ) ) { + const { cartItems, lastKnownFlow } = stepProvidedItems; + if ( isEmpty( cartItems ) && isEmpty( emailItem ) ) { // the user selected the free plan defer( callback ); @@ -620,9 +621,10 @@ export function addPlanToCart( callback, dependencies, stepProvidedItems, reduxS pluginItem = findMarketplacePlugin( reduxStore.getState(), plugin, billingPeriod ); } - const providedDependencies = { cartItem }; - const newCartItems = [ cartItem, emailItem, pluginItem ].filter( ( item ) => item ); - + const providedDependencies = { cartItems }; + const newCartItems = [ ...( cartItems ? cartItems : [] ), emailItem, pluginItem ].filter( + ( item ) => item + ); processItemCart( providedDependencies, newCartItems, callback, reduxStore, siteSlug, { lastKnownFlow, } ); @@ -1219,29 +1221,29 @@ export function isPlanFulfilled( stepName, defaultDependencies, nextProps ) { isDomainTransfer( signupDependencies.domainItem ); if ( isPaidPlan ) { - const cartItem = undefined; + const cartItems = undefined; submitSignupStep( - { stepName, cartItem, wasSkipped: true }, - { cartItem, ...dependenciesFromDefaults } + { stepName, cartItems, wasSkipped: true }, + { cartItems, ...dependenciesFromDefaults } ); recordExcludeStepEvent( stepName, sitePlanSlug ); - fulfilledDependencies.push( 'cartItem' ); + fulfilledDependencies.push( 'cartItems' ); } else if ( defaultDependencies && defaultDependencies.cartItem ) { - const cartItem = getCartItemForPlan( defaultDependencies.cartItem ); + const cartItems = [ getCartItemForPlan( defaultDependencies.cartItem ) ]; submitSignupStep( - { stepName, cartItem, wasSkipped: true }, - { cartItem, ...dependenciesFromDefaults } + { stepName, cartItems, wasSkipped: true }, + { cartItems, ...dependenciesFromDefaults } ); recordExcludeStepEvent( stepName, defaultDependencies.cartItem ); - fulfilledDependencies.push( 'cartItem' ); + fulfilledDependencies.push( 'cartItems' ); } else if ( isTransferSelectedInDomainTransferFlow ) { - const cartItem = null; + const cartItems = null; submitSignupStep( - { stepName, cartItem, wasSkipped: true }, - { cartItem, ...dependenciesFromDefaults } + { stepName, cartItems, wasSkipped: true }, + { cartItems, ...dependenciesFromDefaults } ); recordExcludeStepEvent( stepName, sitePlanSlug ); - fulfilledDependencies.push( 'cartItem' ); + fulfilledDependencies.push( 'cartItems' ); } if ( shouldExcludeStep( stepName, fulfilledDependencies ) ) { @@ -1270,7 +1272,7 @@ export function isNewOrExistingSiteFulfilled( stepName, defaultDependencies, nex } } -export const buildUpgradeFunction = ( planProps, cartItem ) => { +export const buildUpgradeFunction = ( planProps, cartItems ) => { const { additionalStepData, flowName, @@ -1282,11 +1284,12 @@ export const buildUpgradeFunction = ( planProps, cartItem ) => { goToNextStep, submitSignupStep, } = planProps; + const planCartItem = getPlanCartItem( cartItems ); - if ( cartItem ) { + if ( planCartItem ) { planProps.recordTracksEvent( 'calypso_signup_plan_select', { - product_slug: cartItem.product_slug, - free_trial: cartItem.free_trial, + product_slug: planCartItem.product_slug, + free_trial: planCartItem.free_trial, from_section: stepSectionName ? stepSectionName : 'default', } ); @@ -1295,9 +1298,9 @@ export const buildUpgradeFunction = ( planProps, cartItem ) => { // activated at the end of the checkout process. if ( flowName === 'ecommerce' && - planHasFeature( cartItem.product_slug, FEATURE_UPLOAD_THEMES_PLUGINS ) + planHasFeature( planCartItem.product_slug, FEATURE_UPLOAD_THEMES_PLUGINS ) ) { - cartItem.extra = Object.assign( cartItem.extra || {}, { + planCartItem.extra = Object.assign( planCartItem.extra || {}, { is_store_signup: true, } ); } @@ -1310,28 +1313,25 @@ export const buildUpgradeFunction = ( planProps, cartItem ) => { const step = { stepName, stepSectionName, - cartItem, + cartItems, ...additionalStepData, }; - if ( selectedSite && flowName === 'site-selected' && ! cartItem ) { - submitSignupStep( step, { - cartItem, - } ); + if ( selectedSite && flowName === 'site-selected' && ! planCartItem ) { + submitSignupStep( step, { cartItems } ); goToNextStep(); return; } const signupVals = { - cartItem, + cartItems, ...( themeSlugWithRepo && { themeSlugWithRepo } ), ...( launchSite && { comingSoon: 0 } ), }; - if ( cartItem && isEcommerce( cartItem ) ) { + if ( planCartItem && isEcommerce( planCartItem ) ) { signupVals.themeSlugWithRepo = 'pub/twentytwentytwo'; } - submitSignupStep( step, signupVals ); goToNextStep(); }; diff --git a/client/lib/signup/test/mocks/signup/config/steps.js b/client/lib/signup/test/mocks/signup/config/steps.js index 3e669fc8dc60c..06deedf87612f 100644 --- a/client/lib/signup/test/mocks/signup/config/steps.js +++ b/client/lib/signup/test/mocks/signup/config/steps.js @@ -73,7 +73,7 @@ export default { plans: { stepName: 'plans', dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem' ], + providesDependencies: [ 'cartItems' ], }, 'site-type': { diff --git a/client/lib/signup/test/step-actions.js b/client/lib/signup/test/step-actions.js index a2261dc917f59..1323d3c567e2e 100644 --- a/client/lib/signup/test/step-actions.js +++ b/client/lib/signup/test/step-actions.js @@ -202,7 +202,7 @@ describe( 'isPlanFulfilled()', () => { submitSignupStep, }; const defaultDependencies = { cartItem: 'testPlan' }; - const cartItem = { product_slug: defaultDependencies.cartItem }; + const cartItems = [ { product_slug: defaultDependencies.cartItem } ]; expect( flows.excludeStep ).not.toHaveBeenCalled(); expect( submitSignupStep ).not.toHaveBeenCalled(); @@ -210,8 +210,8 @@ describe( 'isPlanFulfilled()', () => { isPlanFulfilled( stepName, defaultDependencies, nextProps ); expect( submitSignupStep ).toHaveBeenCalledWith( - { stepName, cartItem, wasSkipped: true }, - { cartItem } + { stepName, cartItems, wasSkipped: true }, + { cartItems } ); expect( flows.excludeStep ).toHaveBeenCalledWith( stepName ); } ); @@ -236,8 +236,8 @@ describe( 'isPlanFulfilled()', () => { isPlanFulfilled( stepName, undefined, nextProps ); expect( submitSignupStep ).toHaveBeenCalledWith( - { stepName, cartItem: null, wasSkipped: true }, - { cartItem: null } + { stepName, cartItems: null, wasSkipped: true }, + { cartItems: null } ); expect( flows.excludeStep ).toHaveBeenCalledWith( stepName ); } ); diff --git a/client/my-sites/add-ons/hooks/use-add-ons.ts b/client/my-sites/add-ons/hooks/use-add-ons.ts index ef92e1b40ad89..3cdeea7108e3d 100644 --- a/client/my-sites/add-ons/hooks/use-add-ons.ts +++ b/client/my-sites/add-ons/hooks/use-add-ons.ts @@ -35,7 +35,7 @@ import useAddOnPrices from './use-add-on-prices'; import type { AddOnMeta } from '@automattic/data-stores'; // some memoization. executes far too many times -const useAddOns = ( siteId?: number ): ( AddOnMeta | null )[] => { +const useAddOns = ( siteId?: number, isInSignup = false ): ( AddOnMeta | null )[] => { const translate = useTranslate(); const addOnsActive = [ @@ -124,28 +124,28 @@ const useAddOns = ( siteId?: number ): ( AddOnMeta | null )[] => { // if upgrade is bought - show as manage // if upgrade is not bought - only show it if available storage and if it's larger than previously bought upgrade const { data: mediaStorage } = useMediaStorageQuery( siteId ); - const { billingTransactions, isLoading } = usePastBillingTransactions(); + const { billingTransactions, isLoading } = usePastBillingTransactions( isInSignup ); return useSelector( ( state ): ( AddOnMeta | null )[] => { // get the list of supported features const siteFeatures = getFeaturesBySiteId( state, siteId ); - const filter = getBillingTransactionFilters( state, 'past' ); - const filteredTransactions = - billingTransactions && filterTransactions( billingTransactions, filter, siteId ); - const spaceUpgradesPurchased: number[] = []; - if ( filteredTransactions?.length ) { - for ( const transaction of filteredTransactions ) { - transaction.items?.length && - spaceUpgradesPurchased.push( - ...transaction.items - .filter( ( item ) => item.wpcom_product_slug === PRODUCT_1GB_SPACE ) - .map( ( item ) => Number( item.licensed_quantity ) ) - ); + if ( billingTransactions && ! isInSignup ) { + const filter = getBillingTransactionFilters( state, 'past' ); + const filteredTransactions = filterTransactions( billingTransactions, filter, siteId ); + + if ( filteredTransactions?.length ) { + for ( const transaction of filteredTransactions ) { + transaction.items?.length && + spaceUpgradesPurchased.push( + ...transaction.items + .filter( ( item ) => item.wpcom_product_slug === PRODUCT_1GB_SPACE ) + .map( ( item ) => Number( item.licensed_quantity ) ) + ); + } } } - // Determine which Stats Add-On to show based on the site's commercial classification. const isSiteMarkedCommercial = getSiteOption( state, siteId, 'is_commercial' ); diff --git a/client/my-sites/plan-features-2023-grid/hooks/npm-ready/data-store/use-grid-plans.tsx b/client/my-sites/plan-features-2023-grid/hooks/npm-ready/data-store/use-grid-plans.tsx index f30005840c561..ac201d3b6e47e 100644 --- a/client/my-sites/plan-features-2023-grid/hooks/npm-ready/data-store/use-grid-plans.tsx +++ b/client/my-sites/plan-features-2023-grid/hooks/npm-ready/data-store/use-grid-plans.tsx @@ -71,12 +71,10 @@ export type UsePricingMetaForGridPlans = ( { planSlugs, withoutProRatedCredits, storageAddOns, - currencyCode, }: { planSlugs: PlanSlug[]; withoutProRatedCredits?: boolean; storageAddOns: ( AddOnMeta | null )[] | null; - currencyCode?: string | null; } ) => { [ planSlug: string ]: PricingMetaForGridPlan } | null; // TODO clk: move to types. will consume plan properties diff --git a/client/my-sites/plan-features-2023-grid/index.tsx b/client/my-sites/plan-features-2023-grid/index.tsx index 5d3bf7d5048ac..ca9cbb0f98df6 100644 --- a/client/my-sites/plan-features-2023-grid/index.tsx +++ b/client/my-sites/plan-features-2023-grid/index.tsx @@ -19,8 +19,10 @@ import { SlackLogo, TimeLogo, } from '@automattic/components'; +import { WpcomPlansUI } from '@automattic/data-stores'; import { isAnyHostingFlow } from '@automattic/onboarding'; import { MinimalRequestCartProduct } from '@automattic/shopping-cart'; +import { useSelect } from '@wordpress/data'; import classNames from 'classnames'; import { LocalizeProps, useTranslate } from 'i18n-calypso'; import { Component, ForwardedRef, forwardRef } from 'react'; @@ -55,7 +57,7 @@ import type { UsePricingMetaForGridPlans, } from './hooks/npm-ready/data-store/use-grid-plans'; import type { PlanActionOverrides } from './types'; -import type { DomainSuggestion } from '@automattic/data-stores'; +import type { DomainSuggestion, SelectedStorageOptionForPlans } from '@automattic/data-stores'; import type { IAppState } from 'calypso/state/types'; import './style.scss'; @@ -74,7 +76,7 @@ export interface PlanFeatures2023GridProps { siteId?: number | null; isLaunchPage?: boolean | null; isReskinned?: boolean; - onUpgradeClick?: ( cartItem?: MinimalRequestCartProduct | null ) => void; + onUpgradeClick?: ( cartItems?: MinimalRequestCartProduct[] | null ) => void; flowName?: string | null; paidDomainName?: string; wpcomFreeDomainSuggestion: DataResponse< DomainSuggestion >; // used to show a wpcom free domain in the Free plan column when a paid domain is picked. @@ -111,6 +113,7 @@ interface PlanFeatures2023GridType extends PlanFeatures2023GridProps { isPlanUpgradeCreditEligible: boolean; // temporary: element ref to scroll comparison grid into view once "Compare plans" button is clicked plansComparisonGridRef: ForwardedRef< HTMLDivElement >; + selectedStorageOptions?: SelectedStorageOptionForPlans; } export class PlanFeatures2023Grid extends Component< PlanFeatures2023GridType > { @@ -417,14 +420,33 @@ export class PlanFeatures2023Grid extends Component< PlanFeatures2023GridType > } handleUpgradeClick = ( planSlug: PlanSlug ) => { - const { onUpgradeClick: ownPropsOnUpgradeClick, gridPlansForFeaturesGrid } = this.props; - const { cartItemForPlan } = + const { + onUpgradeClick: ownPropsOnUpgradeClick, + gridPlansForFeaturesGrid, + selectedStorageOptions, + } = this.props; + const { cartItemForPlan, storageAddOnsForPlan } = gridPlansForFeaturesGrid.find( ( gridPlan ) => gridPlan.planSlug === planSlug ) ?? {}; - // TODO clk: Revisit. Could this suffice: `ownPropsOnUpgradeClick?.( cartItemForPlan )` + const selectedStorageOption = selectedStorageOptions?.[ planSlug ]; + const storageAddOn = storageAddOnsForPlan?.find( ( addOn ) => { + return selectedStorageOption && addOn + ? addOn.featureSlugs?.includes( selectedStorageOption ) + : false; + } ); + const storageAddOnCartItem = storageAddOn && { + product_slug: storageAddOn.productSlug, + quantity: storageAddOn.quantity, + volume: 1, + }; + + // TODO clk: Revisit. Could this suffice: `ownPropsOnUpgradeClick?.( cartItemForPlan )` if ( cartItemForPlan ) { - ownPropsOnUpgradeClick?.( cartItemForPlan ); + ownPropsOnUpgradeClick?.( [ + cartItemForPlan, + ...( storageAddOnCartItem ? [ storageAddOnCartItem ] : [] ), + ] ); return; } @@ -636,7 +658,6 @@ export class PlanFeatures2023Grid extends Component< PlanFeatures2023GridType > showUpgradeableStorage, storageOptions, } ); - const storageJSX = canUpgradeStorageForPlan ? ( ( gridPlans: props.gridPlansForFeaturesGrid, } ); + const selectedStorageOptions = useSelect( ( select ) => { + return select( WpcomPlansUI.store ).getSelectedStorageOptions(); + }, [] ); + // TODO clk: canUserManagePlan should be passed through props instead of being calculated here const canUserPurchasePlan = useSelector( ( state: IAppState ) => siteId @@ -824,6 +849,7 @@ export default forwardRef< HTMLDivElement, PlanFeatures2023GridProps >( manageHref={ manageHref } selectedSiteSlug={ selectedSiteSlug } translate={ translate } + selectedStorageOptions={ selectedStorageOptions } /> ); } diff --git a/client/my-sites/plans-comparison/plans-comparison.tsx b/client/my-sites/plans-comparison/plans-comparison.tsx index 827ff07d2e801..27bd14fab88c7 100644 --- a/client/my-sites/plans-comparison/plans-comparison.tsx +++ b/client/my-sites/plans-comparison/plans-comparison.tsx @@ -433,18 +433,20 @@ interface Props { selectedDomainConnection?: boolean; hideFreePlan?: boolean; purchaseId?: number | null; - onSelectPlan: ( item: Partial< CartItem > | null ) => void; + onSelectPlan: ( item: Partial< CartItem >[] | null ) => void; } -function planToCartItem( plan: WPComPlan ): Partial< CartItem > | null { +function planToCartItems( plan: WPComPlan ): Partial< CartItem >[] | null { if ( plan.type === TYPE_FREE || plan.type === TYPE_FLEXIBLE ) { return null; } - return { - product_slug: plan.getStoreSlug(), - product_id: plan.getProductId(), - }; + return [ + { + product_slug: plan.getStoreSlug(), + product_id: plan.getProductId(), + }, + ]; } export const PlansComparison: React.FunctionComponent< Props > = ( { @@ -538,7 +540,7 @@ export const PlansComparison: React.FunctionComponent< Props > = ( { isCurrentPlan={ sitePlan?.product_slug === plan.getStoreSlug() } manageHref={ manageHref } disabled={ isDomainConnectionDisabled || isMonthlyPlanDisabled } - onClick={ () => onSelectPlan( planToCartItem( plan ) ) } + onClick={ () => onSelectPlan( planToCartItems( plan ) ) } /> ); diff --git a/client/my-sites/plans-features-main/index.tsx b/client/my-sites/plans-features-main/index.tsx index 0e0fed28a6a46..eefb415e15729 100644 --- a/client/my-sites/plans-features-main/index.tsx +++ b/client/my-sites/plans-features-main/index.tsx @@ -25,7 +25,10 @@ import QuerySites from 'calypso/components/data/query-sites'; import FormattedHeader from 'calypso/components/formatted-header'; import { retargetViewPlans } from 'calypso/lib/analytics/ad-tracking'; import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; -import { planItem as getCartItemForPlan } from 'calypso/lib/cart-values/cart-items'; +import { + planItem as getCartItemForPlan, + getPlanCartItem, +} from 'calypso/lib/cart-values/cart-items'; import { isValidFeatureKey, FEATURES_LIST } from 'calypso/lib/plans/features-list'; import scrollIntoViewport from 'calypso/lib/scroll-into-viewport'; import PlanFeatures2023Grid from 'calypso/my-sites/plan-features-2023-grid'; @@ -98,7 +101,7 @@ export interface PlansFeaturesMainProps { basePlansPath?: string; selectedPlan?: PlanSlug; selectedFeature?: string; - onUpgradeClick?: ( cartItemForPlan?: MinimalRequestCartProduct | null ) => void; + onUpgradeClick?: ( cartItems?: MinimalRequestCartProduct[] | null ) => void; redirectToAddDomainFlow?: boolean; hidePlanTypeSelector?: boolean; paidDomainName?: string; @@ -217,7 +220,7 @@ const PlansFeaturesMain = ( { const [ masterbarHeight, setMasterbarHeight ] = useState( 0 ); const translate = useTranslate(); const plansComparisonGridRef = useRef< HTMLDivElement >( null ); - const storageAddOns = useAddOns( siteId ?? undefined ).filter( + const storageAddOns = useAddOns( siteId ?? undefined, isInSignup ).filter( ( addOn ) => addOn?.productSlug === PRODUCT_1GB_SPACE ); const currentPlan = useSelector( ( state: IAppState ) => getCurrentPlan( state, siteId ) ); @@ -284,7 +287,9 @@ const PlansFeaturesMain = ( { setIsFreePlanPaidDomainDialogOpen( ! isFreePlanPaidDomainDialogOpen ); }; - const handleUpgradeClick = ( cartItemForPlan?: { product_slug: string } | null ) => { + const handleUpgradeClick = ( cartItems?: MinimalRequestCartProduct[] | null ) => { + const cartItemForPlan = getPlanCartItem( cartItems ); + // `cartItemForPlan` is empty if Free plan is selected. Show `FreePlanPaidDomainDialog` // in that case and exit. `FreePlanPaidDomainDialog` takes over from there. // It only applies to main onboarding flow and the paid media flow at the moment. @@ -302,7 +307,7 @@ const PlansFeaturesMain = ( { } if ( onUpgradeClick ) { - onUpgradeClick( cartItemForPlan ); + onUpgradeClick( cartItems ); return; } @@ -576,7 +581,8 @@ const PlansFeaturesMain = ( { } } onPlanSelected={ () => { const cartItemForPlan = getCartItemForPlan( PLAN_PERSONAL ); - onUpgradeClick?.( cartItemForPlan ); + const cartItems = cartItemForPlan ? [ cartItemForPlan ] : null; + onUpgradeClick?.( cartItems ); } } /> ) } @@ -598,7 +604,8 @@ const PlansFeaturesMain = ( { } invalidateDomainSuggestionCache(); const cartItemForPlan = getCartItemForPlan( PLAN_PERSONAL ); - onUpgradeClick?.( cartItemForPlan ); + const cartItems = cartItemForPlan ? [ cartItemForPlan ] : null; + onUpgradeClick?.( cartItems ); } } /> ) } diff --git a/client/my-sites/plans/ecommerce-trial/wooexpress-plans/index.tsx b/client/my-sites/plans/ecommerce-trial/wooexpress-plans/index.tsx index 93ecee63357c9..0af87ce3e7f3d 100644 --- a/client/my-sites/plans/ecommerce-trial/wooexpress-plans/index.tsx +++ b/client/my-sites/plans/ecommerce-trial/wooexpress-plans/index.tsx @@ -12,6 +12,7 @@ import { hasTranslation } from '@wordpress/i18n'; import { useTranslate } from 'i18n-calypso'; import page from 'page'; import { useCallback, useMemo } from 'react'; +import { getPlanCartItem } from 'calypso/lib/cart-values/cart-items'; import { getTrialCheckoutUrl } from 'calypso/lib/trials/get-trial-checkout-url'; import PlansFeaturesMain from 'calypso/my-sites/plans-features-main'; import PlanIntervalSelector from 'calypso/my-sites/plans-features-main/components/plan-interval-selector'; @@ -96,8 +97,8 @@ export function WooExpressPlans( props: WooExpressPlansProps ) { ] ); const onUpgradeClick = useCallback( - ( cartItem?: MinimalRequestCartProduct | null ) => { - const upgradePlanSlug = cartItem?.product_slug ?? PLAN_FREE; + ( cartItems?: MinimalRequestCartProduct[] | null ) => { + const upgradePlanSlug = getPlanCartItem( cartItems )?.product_slug ?? PLAN_FREE; triggerTracksEvent?.( upgradePlanSlug ); diff --git a/client/my-sites/plans/trials/business-trial-plans/index.tsx b/client/my-sites/plans/trials/business-trial-plans/index.tsx index 519a6fe0b14a3..9cce26cde2e00 100644 --- a/client/my-sites/plans/trials/business-trial-plans/index.tsx +++ b/client/my-sites/plans/trials/business-trial-plans/index.tsx @@ -1,6 +1,7 @@ import { PLAN_FREE, getPlanPath, isBusinessPlan } from '@automattic/calypso-products'; import page from 'page'; import { useCallback } from 'react'; +import { getPlanCartItem } from 'calypso/lib/cart-values/cart-items'; import { getTrialCheckoutUrl } from 'calypso/lib/trials/get-trial-checkout-url'; import PlansFeaturesMain from 'calypso/my-sites/plans-features-main'; import type { MinimalRequestCartProduct } from '@automattic/shopping-cart'; @@ -15,8 +16,8 @@ export function BusinessTrialPlans( props: BusinessTrialPlansProps ) { const { siteId, siteSlug, triggerTracksEvent } = props; const onUpgradeClick = useCallback( - ( cartItem?: MinimalRequestCartProduct | null ) => { - const upgradePlanSlug = cartItem?.product_slug ?? PLAN_FREE; + ( cartItems?: MinimalRequestCartProduct[] | null ) => { + const upgradePlanSlug = getPlanCartItem( cartItems )?.product_slug ?? PLAN_FREE; triggerTracksEvent?.( upgradePlanSlug ); diff --git a/client/signup/config/flows.js b/client/signup/config/flows.js index 67a755adaa32a..52f37ffc29b18 100644 --- a/client/signup/config/flows.js +++ b/client/signup/config/flows.js @@ -32,7 +32,9 @@ function getCheckoutUrl( dependencies, localeSlug, flowName ) { } function dependenciesContainCartItem( dependencies ) { - return dependencies.cartItem || dependencies.domainItem; + // @TODO: cartItem is now deprecated. Remove dependencies.cartItem and + // dependencies.domainItem once all steps and flows have been updated to use cartItems + return dependencies.cartItem || dependencies.domainItem || dependencies.cartItems; } function getSiteDestination( dependencies ) { diff --git a/client/signup/config/steps-pure.js b/client/signup/config/steps-pure.js index 50f22685e4fd9..62a0327da57b8 100644 --- a/client/signup/config/steps-pure.js +++ b/client/signup/config/steps-pure.js @@ -117,7 +117,7 @@ export function generateSteps( { stepName: 'plans-site-selected', apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], props: { hideFreePlan: true, @@ -135,7 +135,7 @@ export function generateSteps( { stepName: 'plans-site-selected-legacy', apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], props: { hideEcommercePlan: true, @@ -266,7 +266,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug' ], optionalDependencies: [ 'emailItem', 'themeSlugWithRepo' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], fulfilledStepCallback: isPlanFulfilled, }, @@ -275,7 +275,7 @@ export function generateSteps( { apiRequestFunction: addWithThemePlanToCart, dependencies: [ 'siteSlug', 'theme' ], optionalDependencies: [ 'emailItem', 'themeSlugWithRepo' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], fulfilledStepCallback: isPlanFulfilled, }, @@ -284,7 +284,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug' ], optionalDependencies: [ 'emailItem', 'themeSlugWithRepo' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], fulfilledStepCallback: isPlanFulfilled, props: { hideFreePlan: true, @@ -300,7 +300,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug' ], optionalDependencies: [ 'emailItem', 'themeSlugWithRepo' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], fulfilledStepCallback: isPlanFulfilled, props: { showBiennialToggle: true, @@ -315,7 +315,7 @@ export function generateSteps( { 'plans-new': { stepName: 'plans', - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], fulfilledStepCallback: isPlanFulfilled, }, @@ -324,7 +324,7 @@ export function generateSteps( { stepName: 'plans-import', apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], fulfilledStepCallback: isPlanFulfilled, props: { @@ -337,7 +337,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_PERSONAL, @@ -349,7 +349,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_PREMIUM, @@ -361,7 +361,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_BUSINESS, @@ -373,7 +373,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug', 'plugin', 'billing_period' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_BUSINESS, @@ -385,7 +385,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_WPCOM_PRO, @@ -397,7 +397,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_WPCOM_STARTER, @@ -409,7 +409,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_ECOMMERCE, themeSlugWithRepo: 'pub/twentytwentytwo', @@ -421,7 +421,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], props: { isLaunchPage: true, @@ -432,7 +432,7 @@ export function generateSteps( { stepName: 'plans-store-nux', apiRequestFunction: addPlanToCart, dependencies: [ 'siteSlug', 'domainItem' ], - providesDependencies: [ 'cartItem' ], + providesDependencies: [ 'cartItems' ], }, 'mailbox-plan': { @@ -440,7 +440,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug', 'emailItem' ], - providesDependencies: [ 'themeSlugWithRepo', 'cartItem' ], + providesDependencies: [ 'themeSlugWithRepo', 'cartItems' ], optionalDependencies: [ 'themeSlugWithRepo' ], props: { useEmailOnboardingSubheader: true, @@ -686,7 +686,7 @@ export function generateSteps( { }, }, providesDependencies: [ 'siteId', 'siteSlug', 'domainItem', 'themeSlugWithRepo' ], - dependencies: [ 'cartItem', 'designType', 'domainItem', 'siteUrl', 'themeSlugWithRepo' ], + dependencies: [ 'designType', 'domainItem', 'siteUrl', 'themeSlugWithRepo' ], delayApiRequestUntilComplete: true, }, @@ -815,7 +815,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_PERSONAL_MONTHLY, @@ -827,7 +827,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_PREMIUM_MONTHLY, @@ -839,7 +839,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_BUSINESS_MONTHLY, @@ -851,7 +851,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_ECOMMERCE_MONTHLY, themeSlugWithRepo: 'pub/twentytwentytwo', @@ -863,7 +863,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_PERSONAL_2_YEARS, @@ -875,7 +875,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_PREMIUM_2_YEARS, @@ -887,7 +887,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], optionalDependencies: [ 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_BUSINESS_2_YEARS, @@ -899,7 +899,7 @@ export function generateSteps( { apiRequestFunction: addPlanToCart, fulfilledStepCallback: isPlanFulfilled, dependencies: [ 'siteSlug' ], - providesDependencies: [ 'cartItem', 'themeSlugWithRepo' ], + providesDependencies: [ 'cartItems', 'themeSlugWithRepo' ], defaultDependencies: { cartItem: PLAN_ECOMMERCE_2_YEARS, themeSlugWithRepo: 'pub/twentytwentytwo', diff --git a/client/signup/main.jsx b/client/signup/main.jsx index ee5215015c1eb..a6bf82a97914d 100644 --- a/client/signup/main.jsx +++ b/client/signup/main.jsx @@ -3,6 +3,7 @@ import { isDomainRegistration, isDomainTransfer, isDomainMapping, + isPlan, } from '@automattic/calypso-products'; import { isBlankCanvasDesign } from '@automattic/design-picker'; import { camelToSnakeCase } from '@automattic/js-utils'; @@ -95,7 +96,12 @@ import './style.scss'; const debug = debugModule( 'calypso:signup' ); function dependenciesContainCartItem( dependencies ) { - return !! ( dependencies && ( dependencies.cartItem || dependencies.domainItem ) ); + // @TODO: cartItem is now deprecated. Remove dependencies.cartItem and + // dependencies.domainItem once all steps and flows have been updated to use cartItems + return !! ( + dependencies && + ( dependencies.cartItem || dependencies.domainItem || dependencies.cartItems ) + ); } function addLoadingScreenClassNamesToBody() { @@ -480,12 +486,18 @@ class Signup extends Component { ( isNewishUser && dependencies && dependencies.siteSlug && existingSiteCount <= 1 ) ); const hasCartItems = dependenciesContainCartItem( dependencies ); + // @TODO: cartItem is now deprecated. Remove this once all steps and flows have been + // updated to use cartItems const cartItem = get( dependencies, 'cartItem' ); + const cartItems = get( dependencies, 'cartItems' ); const domainItem = get( dependencies, 'domainItem' ); const selectedDesign = get( dependencies, 'selectedDesign' ); const intent = get( dependencies, 'intent' ); const startingPoint = get( dependencies, 'startingPoint' ); const signupDomainOrigin = get( dependencies, 'signupDomainOrigin' ); + const planProductSlug = cartItems?.length + ? cartItems.find( ( item ) => isPlan( item ) )?.product_slug + : cartItem?.product_slug; const debugProps = { isNewishUser, @@ -511,7 +523,7 @@ class Signup extends Component { siteId, isNewUser, hasCartItems, - planProductSlug: hasCartItems && cartItem !== null ? cartItem.product_slug : undefined, + planProductSlug, domainProductSlug: undefined !== domainItem && domainItem.is_domain_registration ? domainItem.product_slug diff --git a/client/signup/steps/plans/index.jsx b/client/signup/steps/plans/index.jsx index 1f36df65c8211..63bd3b5102f38 100644 --- a/client/signup/steps/plans/index.jsx +++ b/client/signup/steps/plans/index.jsx @@ -59,8 +59,8 @@ export class PlansStep extends Component { this.unsubscribe(); } - onSelectPlan( cartItem ) { - buildUpgradeFunction( this.props, cartItem ); + onSelectPlan( cartItems ) { + buildUpgradeFunction( this.props, cartItems ); } getCustomerType() { @@ -144,7 +144,7 @@ export class PlansStep extends Component { this.onSelectPlan( cartItem ) } + onSelectPlan={ ( cartItems ) => this.onSelectPlan( cartItems ) } selectedSiteId={ selectedSite?.ID || undefined } selectedDomainConnection={ selectedDomainConnection } /> @@ -166,7 +166,7 @@ export class PlansStep extends Component { isInSignup={ true } isLaunchPage={ isLaunchPage } intervalType={ intervalType } - onUpgradeClick={ ( cartItem ) => this.onSelectPlan( cartItem ) } + onUpgradeClick={ ( cartItems ) => this.onSelectPlan( cartItems ) } customerType={ this.getCustomerType() } disableBloggerPlanWithNonBlogDomain={ disableBloggerPlanWithNonBlogDomain } // TODO clk investigate plansWithScroll={ this.state.isDesktop } diff --git a/client/signup/steps/plans/test/index.jsx b/client/signup/steps/plans/test/index.jsx index 32fb0ea44a276..d0a20187d79db 100644 --- a/client/signup/steps/plans/test/index.jsx +++ b/client/signup/steps/plans/test/index.jsx @@ -40,6 +40,15 @@ const props = { translate: ( string ) => string, }; +function getCartItems( overrides ) { + return [ + { + product_slug: PLAN_FREE, + ...overrides, + }, + ]; +} + describe( 'Plans basic tests', () => { test( 'should not blow up and have proper CSS class', () => { render( ); @@ -62,23 +71,22 @@ describe( 'Plans.onSelectPlan', () => { goToNextStep: jest.fn(), }; const comp = new PlansStep( myProps ); - comp.onSelectPlan( { product_slug: PLAN_FREE } ); + comp.onSelectPlan( getCartItems() ); expect( myProps.goToNextStep ).toHaveBeenCalled(); } ); test( 'Should call submitSignupStep with step details', () => { const submitSignupStep = jest.fn(); - + const cartItems = getCartItems(); const comp = new PlansStep( { ...tplProps, submitSignupStep } ); - const cartItem = { product_slug: PLAN_FREE }; - comp.onSelectPlan( cartItem ); + comp.onSelectPlan( cartItems ); expect( submitSignupStep ).toHaveBeenCalled(); const calls = submitSignupStep.mock.calls; const args = calls[ calls.length - 1 ]; expect( args[ 0 ].stepName ).toEqual( 'Step name' ); expect( args[ 0 ].stepSectionName ).toEqual( 'Step section name' ); - expect( args[ 0 ].cartItem ).toBe( cartItem ); + expect( args[ 0 ].cartItems ).toBe( cartItems ); expect( 'test' in args[ 0 ] ).toEqual( false ); } ); @@ -89,10 +97,8 @@ describe( 'Plans.onSelectPlan', () => { additionalStepData: { test: 23 }, submitSignupStep, }; - const comp = new PlansStep( myProps ); - const cartItem = { product_slug: PLAN_FREE }; - comp.onSelectPlan( cartItem ); + comp.onSelectPlan( getCartItems() ); expect( submitSignupStep ).toHaveBeenCalled(); const calls = submitSignupStep.mock.calls; @@ -103,20 +109,19 @@ describe( 'Plans.onSelectPlan', () => { test( 'Should call submitSignupStep with correct providedDependencies', () => { const submitSignupStep = jest.fn(); const comp = new PlansStep( { ...tplProps, submitSignupStep } ); - const cartItem = { product_slug: PLAN_FREE }; - comp.onSelectPlan( cartItem ); + const cartItems = getCartItems(); + comp.onSelectPlan( cartItems ); expect( submitSignupStep ).toHaveBeenCalled(); const calls = submitSignupStep.mock.calls; const args = calls[ calls.length - 1 ]; - expect( args[ 1 ].cartItem ).toBe( cartItem ); + expect( args[ 1 ].cartItems ).toBe( cartItems ); } ); test( 'Should call recordEvent when cartItem is specified', () => { const recordTracksEvent = jest.fn(); const comp = new PlansStep( { ...tplProps, recordTracksEvent } ); - const cartItem = { product_slug: PLAN_FREE, free_trial: false }; - comp.onSelectPlan( cartItem ); + comp.onSelectPlan( getCartItems( { free_trial: false } ) ); expect( recordTracksEvent ).toHaveBeenCalled(); @@ -143,11 +148,12 @@ describe( 'Plans.onSelectPlan', () => { ...tplProps, goToNextStep: jest.fn(), }; - const cartItem = { product_slug: plan }; + const cartItems = getCartItems( { product_slug: plan } ); + const [ planCartItem ] = cartItems; const comp = new PlansStep( myProps ); - comp.onSelectPlan( cartItem ); + comp.onSelectPlan( cartItems ); expect( myProps.goToNextStep ).toHaveBeenCalled(); - expect( cartItem.extra ).toEqual( { + expect( planCartItem.extra ).toEqual( { is_store_signup: true, } ); } @@ -167,11 +173,12 @@ describe( 'Plans.onSelectPlan', () => { flowName: 'signup', goToNextStep: jest.fn(), }; - const cartItem = { product_slug: plan }; + const cartItems = getCartItems( { product_slug: plan } ); + const [ planCartItem ] = cartItems; const comp = new PlansStep( myProps ); - comp.onSelectPlan( cartItem ); + comp.onSelectPlan( cartItems ); expect( myProps.goToNextStep ).toHaveBeenCalled(); - expect( cartItem.extra ).toEqual( undefined ); + expect( planCartItem.extra ).toEqual( undefined ); } ); @@ -183,10 +190,11 @@ describe( 'Plans.onSelectPlan', () => { designType: 'other', }, }; - const cartItem = { product_slug: PLAN_FREE }; + const cartItems = getCartItems(); + const [ planCartItem ] = cartItems; const comp = new PlansStep( myProps ); - comp.onSelectPlan( cartItem ); - expect( cartItem.extra ).toEqual( undefined ); + comp.onSelectPlan( cartItems ); + expect( planCartItem.extra ).toEqual( undefined ); } ); test.each( [ @@ -205,10 +213,11 @@ describe( 'Plans.onSelectPlan', () => { ] )( `Should not add is_store_signup to cartItem.extra when processing non-wp.com non-business plan (%s)`, ( plan ) => { - const cartItem = { product_slug: plan }; + const cartItems = getCartItems( { product_slug: plan } ); + const [ planCartItem ] = cartItems; const comp = new PlansStep( tplProps ); - comp.onSelectPlan( cartItem ); - expect( cartItem.extra ).toEqual( undefined ); + comp.onSelectPlan( cartItems ); + expect( planCartItem.extra ).toEqual( undefined ); } ); } ); diff --git a/client/signup/steps/site-or-domain/index.jsx b/client/signup/steps/site-or-domain/index.jsx index 89a1224991134..99be53dc6571d 100644 --- a/client/signup/steps/site-or-domain/index.jsx +++ b/client/signup/steps/site-or-domain/index.jsx @@ -208,7 +208,7 @@ class SiteOrDomain extends Component { ); this.props.submitSignupStep( { stepName: 'plans-site-selected', wasSkipped: true }, - { cartItem: null } + { cartItems: null } ); goToStep( 'user' ); } diff --git a/client/signup/steps/site-picker/site-picker-submit.jsx b/client/signup/steps/site-picker/site-picker-submit.jsx index b8ff950b3900a..9e7c39c21ddc4 100644 --- a/client/signup/steps/site-picker/site-picker-submit.jsx +++ b/client/signup/steps/site-picker/site-picker-submit.jsx @@ -29,7 +29,7 @@ export class SitePickerSubmit extends Component { if ( hasPaidPlan ) { this.props.submitSignupStep( { stepName: 'plans-site-selected', wasSkipped: true }, - { cartItem: null } + { cartItems: null } ); goToStep( 'user' ); diff --git a/client/state/signup/progress/actions.js b/client/state/signup/progress/actions.js index ffd1987e01cb0..60ac98e2fc95b 100644 --- a/client/state/signup/progress/actions.js +++ b/client/state/signup/progress/actions.js @@ -76,7 +76,7 @@ function recordSubmitStep( flow, stepName, providedDependencies, optionalProps ) } if ( - [ 'cart_item', 'domain_item', 'email_item' ].includes( propName ) && + [ 'cart_items', 'domain_item', 'email_item' ].includes( propName ) && typeof propValue !== 'string' ) { propValue = Object.entries( propValue || {} ) diff --git a/client/state/sites/hooks/use-billing-history.ts b/client/state/sites/hooks/use-billing-history.ts index 11ca6c648a3ec..6ccdd0bcf577e 100644 --- a/client/state/sites/hooks/use-billing-history.ts +++ b/client/state/sites/hooks/use-billing-history.ts @@ -13,7 +13,7 @@ const fetchPastBillingTransactions = () => apiVersion: '1.3', } ); -export const usePastBillingTransactions = () => { +export const usePastBillingTransactions = ( isInSignup: boolean ) => { const queryKey = [ billingTransactionsQueryKey ]; const { data, isLoading, error } = useQuery< BillingHistory, Error >( { @@ -25,5 +25,6 @@ export const usePastBillingTransactions = () => { billingTransactions: data?.billing_history || null, isLoading, error: error?.message || null, + enabled: ! isInSignup, }; }; diff --git a/packages/data-stores/src/index.ts b/packages/data-stores/src/index.ts index f7700d8770969..30f13aa7ef1c3 100644 --- a/packages/data-stores/src/index.ts +++ b/packages/data-stores/src/index.ts @@ -23,6 +23,7 @@ export * from './domain-suggestions/types'; export * from './plans/types'; export * from './theme'; export * from './user/types'; +export * from './wpcom-plans-ui/types'; export * from './queries/use-launchpad'; export * from './queries/use-all-domains-query'; export * from './queries/use-site-domains-query'; diff --git a/packages/data-stores/src/wpcom-plans-ui/reducer.ts b/packages/data-stores/src/wpcom-plans-ui/reducer.ts index 72f22852d8d88..cb98194395395 100644 --- a/packages/data-stores/src/wpcom-plans-ui/reducer.ts +++ b/packages/data-stores/src/wpcom-plans-ui/reducer.ts @@ -1,6 +1,6 @@ import { combineReducers } from '@wordpress/data'; import type { WpcomPlansUIAction } from './actions'; -import type { selectedStorageOptionForPlans } from './types'; +import type { SelectedStorageOptionForPlans } from './types'; import type { Reducer } from 'redux'; const showDomainUpsellDialog: Reducer< boolean | undefined, WpcomPlansUIAction > = ( @@ -16,7 +16,7 @@ const showDomainUpsellDialog: Reducer< boolean | undefined, WpcomPlansUIAction > }; const selectedStorageOptionForPlans: Reducer< - selectedStorageOptionForPlans | undefined, + SelectedStorageOptionForPlans | undefined, WpcomPlansUIAction > = ( state, action ) => { if ( action.type === 'WPCOM_PLANS_UI_SET_SELECTED_STORAGE_OPTION_FOR_PLAN' ) { diff --git a/packages/data-stores/src/wpcom-plans-ui/types.ts b/packages/data-stores/src/wpcom-plans-ui/types.ts index b22a0aa45b3df..c5b77e8c67e01 100644 --- a/packages/data-stores/src/wpcom-plans-ui/types.ts +++ b/packages/data-stores/src/wpcom-plans-ui/types.ts @@ -4,6 +4,6 @@ export interface DomainUpsellDialog { show: boolean; } -export interface selectedStorageOptionForPlans { +export interface SelectedStorageOptionForPlans { [ key: string ]: WPComStorageAddOnSlug; }