diff --git a/client/my-sites/checkout/checkout-system-decider.js b/client/my-sites/checkout/checkout-system-decider.js
index f71ca4ed401f4..cec762dcd20b7 100644
--- a/client/my-sites/checkout/checkout-system-decider.js
+++ b/client/my-sites/checkout/checkout-system-decider.js
@@ -7,6 +7,7 @@ import debugFactory from 'debug';
import { CheckoutErrorBoundary } from '@automattic/composite-checkout';
import { useTranslate } from 'i18n-calypso';
import { StripeHookProvider } from '@automattic/calypso-stripe';
+import { getEmptyResponseCart } from '@automattic/shopping-cart';
/**
* Internal Dependencies
@@ -26,6 +27,8 @@ import CalypsoShoppingCartProvider from './calypso-shopping-cart-provider';
// otherwise we get `this is not defined` errors.
const wpcom = wp.undocumented();
+const emptyCart = getEmptyResponseCart();
+
const debug = debugFactory( 'calypso:checkout-system-decider' );
export default function CheckoutSystemDecider( {
@@ -39,7 +42,6 @@ export default function CheckoutSystemDecider( {
redirectTo,
isLoggedOutCart,
isNoSiteCart,
- cart: otherCart,
} ) {
const reduxDispatch = useDispatch();
const translate = useTranslate();
@@ -80,23 +82,14 @@ export default function CheckoutSystemDecider( {
[ reduxDispatch ]
);
- // We have to monitor the old cart manager in case it's waiting on a
- // requested change. To prevent race conditions, we will return undefined in
- // that case, which will cause the ShoppingCartProvider to enter a loading
- // state. We have to use null because CalypsoShoppingCartProvider assumes
- // undefined means to try for its own cartKey.
- const waitForOtherCartUpdates =
- otherCart?.hasPendingServerUpdates || ! otherCart?.hasLoadedFromServer;
const cartKey = useMemo(
() =>
- waitForOtherCartUpdates
- ? null
- : getCartKey( {
- selectedSite,
- isLoggedOutCart,
- isNoSiteCart,
- } ),
- [ waitForOtherCartUpdates, selectedSite, isLoggedOutCart, isNoSiteCart ]
+ getCartKey( {
+ selectedSite,
+ isLoggedOutCart,
+ isNoSiteCart,
+ } ),
+ [ selectedSite, isLoggedOutCart, isNoSiteCart ]
);
debug( 'cartKey is', cartKey );
@@ -110,8 +103,9 @@ export default function CheckoutSystemDecider( {
}
}
- const getCart = isLoggedOutCart || isNoSiteCart ? () => Promise.resolve( otherCart ) : undefined;
- debug( 'getCart being controlled by', { isLoggedOutCart, isNoSiteCart, otherCart } );
+ // If we do not have a site or user, we cannot fetch the initial cart from
+ // the server, so we'll just mock it as an empty cart here.
+ const getCart = isLoggedOutCart || isNoSiteCart ? () => Promise.resolve( emptyCart ) : undefined;
return (
<>
diff --git a/client/my-sites/checkout/composite-checkout/composite-checkout.tsx b/client/my-sites/checkout/composite-checkout/composite-checkout.tsx
index a1988f0e6a112..17b72bc5e4b44 100644
--- a/client/my-sites/checkout/composite-checkout/composite-checkout.tsx
+++ b/client/my-sites/checkout/composite-checkout/composite-checkout.tsx
@@ -232,6 +232,8 @@ export default function CompositeCheckout( {
isJetpackNotAtomic,
isPrivate,
siteSlug,
+ isLoggedOutCart,
+ isNoSiteCart,
} );
const {
diff --git a/client/my-sites/checkout/composite-checkout/hooks/use-prepare-products-for-cart.ts b/client/my-sites/checkout/composite-checkout/hooks/use-prepare-products-for-cart.ts
index 506c077bfbbce..557d92392984c 100644
--- a/client/my-sites/checkout/composite-checkout/hooks/use-prepare-products-for-cart.ts
+++ b/client/my-sites/checkout/composite-checkout/hooks/use-prepare-products-for-cart.ts
@@ -24,6 +24,8 @@ import { getProductsList, isProductsListFetching } from 'calypso/state/products-
import useFetchProductsIfNotLoaded from './use-fetch-products-if-not-loaded';
import doesValueExist from '../lib/does-value-exist';
import useStripProductsFromUrl from './use-strip-products-from-url';
+import getCartFromLocalStorage from '../lib/get-cart-from-local-storage';
+import { fillInSingleCartItemAttributes } from 'calypso/lib/cart-values';
const debug = debugFactory( 'calypso:composite-checkout:use-prepare-products-for-cart' );
@@ -46,6 +48,8 @@ export default function usePrepareProductsForCart( {
isJetpackNotAtomic,
isPrivate,
siteSlug,
+ isLoggedOutCart,
+ isNoSiteCart,
}: {
productAliasFromUrl: string | null | undefined;
purchaseId: string | number | null | undefined;
@@ -53,23 +57,20 @@ export default function usePrepareProductsForCart( {
isJetpackNotAtomic: boolean;
isPrivate: boolean;
siteSlug: string | undefined;
+ isLoggedOutCart?: boolean;
+ isNoSiteCart?: boolean;
} ): PreparedProductsForCart {
- const initializePreparedProductsState = (
- initialState: PreparedProductsForCart
- ): PreparedProductsForCart => ( {
- ...initialState,
- isLoading: !! productAliasFromUrl,
- } );
- const [ state, dispatch ] = useReducer(
- preparedProductsReducer,
- initialPreparedProductsState,
- initializePreparedProductsState
- );
+ const [ state, dispatch ] = useReducer( preparedProductsReducer, initialPreparedProductsState );
+
debug(
'preparing products for cart from url string',
productAliasFromUrl,
'and purchase id',
- originalPurchaseId
+ originalPurchaseId,
+ 'and isLoggedOutCart',
+ isLoggedOutCart,
+ 'and isNoSiteCart',
+ isNoSiteCart
);
useFetchProductsIfNotLoaded();
@@ -78,10 +79,18 @@ export default function usePrepareProductsForCart( {
isLoading: state.isLoading,
originalPurchaseId,
productAliasFromUrl,
+ isLoggedOutCart,
+ isNoSiteCart,
} );
+ debug( 'isLoading', state.isLoading );
+ debug( 'handler is', addHandler );
// Only one of these should ever operate. The others should bail if they
// think another hook will handle the data.
+ useAddProductsFromLocalStorage( {
+ dispatch,
+ addHandler,
+ } );
useAddProductFromSlug( {
productAliasFromUrl,
dispatch,
@@ -95,6 +104,7 @@ export default function usePrepareProductsForCart( {
dispatch,
addHandler,
} );
+ useNothingToAdd( { addHandler, dispatch } );
// Do not strip products from url until the URL has been parsed
const areProductsRetrievedFromUrl = ! state.isLoading && ! isInEditor;
@@ -130,32 +140,100 @@ function preparedProductsReducer(
}
}
-type AddHandler = 'addProductFromSlug' | 'addRenewalItems' | 'doNotAdd';
+type AddHandler = 'addProductFromSlug' | 'addRenewalItems' | 'doNotAdd' | 'addFromLocalStorage';
function chooseAddHandler( {
isLoading,
originalPurchaseId,
productAliasFromUrl,
+ isLoggedOutCart,
+ isNoSiteCart,
}: {
isLoading: boolean;
originalPurchaseId: string | number | null | undefined;
productAliasFromUrl: string | null | undefined;
+ isLoggedOutCart?: boolean;
+ isNoSiteCart?: boolean;
} ): AddHandler {
if ( ! isLoading ) {
return 'doNotAdd';
}
- if ( isLoading && originalPurchaseId ) {
+ if ( isLoggedOutCart || isNoSiteCart ) {
+ return 'addFromLocalStorage';
+ }
+
+ if ( originalPurchaseId ) {
return 'addRenewalItems';
}
- if ( isLoading && ! originalPurchaseId && productAliasFromUrl ) {
+ if ( ! originalPurchaseId && productAliasFromUrl ) {
return 'addProductFromSlug';
}
return 'doNotAdd';
}
+function useNothingToAdd( {
+ dispatch,
+ addHandler,
+}: {
+ dispatch: ( action: PreparedProductsAction ) => void;
+ addHandler: AddHandler;
+} ) {
+ useEffect( () => {
+ if ( addHandler !== 'doNotAdd' ) {
+ return;
+ }
+
+ debug( 'nothing to add' );
+ dispatch( { type: 'PRODUCTS_ADD', products: [] } );
+ }, [ addHandler, dispatch ] );
+}
+
+function useAddProductsFromLocalStorage( {
+ dispatch,
+ addHandler,
+}: {
+ dispatch: ( action: PreparedProductsAction ) => void;
+ addHandler: AddHandler;
+} ) {
+ const translate = useTranslate();
+ const products: Record<
+ string,
+ {
+ product_id: number;
+ product_slug: string;
+ }
+ > = useSelector( getProductsList );
+
+ useEffect( () => {
+ if ( addHandler !== 'addFromLocalStorage' ) {
+ return;
+ }
+ if ( Object.keys( products || {} ).length < 1 ) {
+ debug( 'waiting on products fetch' );
+ return;
+ }
+
+ const productsForCart: RequestCartProduct[] = getCartFromLocalStorage().map( ( product ) =>
+ fillInSingleCartItemAttributes( product, products )
+ );
+
+ if ( productsForCart.length < 1 ) {
+ debug( 'creating products from localStorage failed' );
+ dispatch( {
+ type: 'PRODUCTS_ADD_ERROR',
+ message: String( translate( 'I tried and failed to create products from signup' ) ),
+ } );
+ return;
+ }
+
+ debug( 'preparing products requested in localStorage', productsForCart );
+ dispatch( { type: 'PRODUCTS_ADD', products: productsForCart } );
+ }, [ addHandler, dispatch, translate, products ] );
+}
+
function useAddRenewalItems( {
originalPurchaseId,
productAlias,
diff --git a/client/my-sites/checkout/composite-checkout/lib/get-cart-from-local-storage.ts b/client/my-sites/checkout/composite-checkout/lib/get-cart-from-local-storage.ts
new file mode 100644
index 0000000000000..b5df51a84b0dc
--- /dev/null
+++ b/client/my-sites/checkout/composite-checkout/lib/get-cart-from-local-storage.ts
@@ -0,0 +1,14 @@
+/**
+ * External dependencies
+ */
+import type { RequestCartProduct } from '@automattic/shopping-cart';
+
+// Used by signup; see https://github.com/Automattic/wp-calypso/pull/44206
+// These products are likely missing product_id.
+export default function getCartFromLocalStorage(): Partial< RequestCartProduct >[] {
+ try {
+ return JSON.parse( window.localStorage.getItem( 'shoppingCart' ) || '[]' );
+ } catch ( err ) {
+ return [];
+ }
+}
diff --git a/client/my-sites/checkout/controller.jsx b/client/my-sites/checkout/controller.jsx
index d73277e162640..a3028cff2a516 100644
--- a/client/my-sites/checkout/controller.jsx
+++ b/client/my-sites/checkout/controller.jsx
@@ -21,7 +21,6 @@ import { canUserPurchaseGSuite } from 'calypso/lib/gsuite';
import { getRememberedCoupon } from 'calypso/lib/cart/actions';
import { setSectionMiddleware } from 'calypso/controller';
import { sites } from 'calypso/my-sites/controller';
-import CartData from 'calypso/components/data/cart';
import userFactory from 'calypso/lib/user';
import { getCurrentUser } from 'calypso/state/current-user/selectors';
import {
@@ -93,20 +92,18 @@ export function checkout( context, next ) {
}
context.primary = (
-
-
-
+
);
next();