Repository: Automattic/wp-calypso
Pull request #101854: Refactor "Use your domain" to functional component
Author: login: ciampo
Labels: name: Janitorial
Created: 2025-03-25T17:52:50Z | Merged: 2025-03-28T12:28:49Z

<!--
Link a related issue to this PR. If the PR does not immediately resolve the issue,
for example, it requires a separate deployment to production, avoid
using the "fixes" keyword and instead attach the [Status] Fix Inbound label to
the linked issue.
-->

Related to #101833

## Proposed Changes

Refactor the React component from class to functional in preparation for the work highlighted in https://github.com/Automattic/wp-calypso/issues/100243#issuecomment-2751061385

## Why are these changes being made?
<!--
It's easy to see what a PR does but much harder to find out why it was made,
particularly when researching old changes in history. Record an explanation of
the motivation behind this change and how it will help.
-->

Refactoring to functional component will allow the usage of the necessary React hooks to complete #100243

## Testing Instructions

<!--
Add as many details as possible to help others reproduce the issue and test the fix.
"Before / After" screenshots can also be very helpful when the change is visual.
-->

Visit `/domains/add/use-your-domain/{SITE_SLUG}` and make sure everything works as on `trunk`.

> [!TIP]
> It's highly recommended to **review commit-by-commit** 

> [!NOTE]
> I couldn't find a way to visit this page _organically_. Who would be the best person / team to understand whether this whole branch of domain transfer steps can be deleted?

![Screenshot 2025-03-25 at 19 14 12](https://github.com/user-attachments/assets/b0ecc20b-d9f0-40eb-b719-e7d7ede79316)


## Pre-merge Checklist

<!--
Complete applicable items on this checklist **before** merging into trunk. Inapplicable items can be left unchecked.

Both the PR author and reviewer are responsible for ensuring the checklist is completed.
-->

- [ ] Has the general commit checklist been followed? (PCYsg-hS-p2)
- [ ] [Have you written new tests](https://wpcalypso.wordpress.com/devdocs/docs/testing/index.md) for your changes?
- [ ] Have you tested the feature in Simple (P9HQHe-k8-p2), Atomic (P9HQHe-jW-p2), and self-hosted Jetpack sites (PCYsg-g6b-p2)?
- [ ] Have you checked for TypeScript, React or other console errors?
- [ ] Have you used memoizing on expensive computations? More info in [Memoizing with create-selector](https://github.com/Automattic/wp-calypso/blob/trunk/packages/state-utils/src/create-selector/README.md) and [Using memoizing selectors](https://react-redux.js.org/api/hooks#using-memoizing-selectors) and [Our Approach to Data](https://github.com/Automattic/wp-calypso/blob/trunk/docs/our-approach-to-data.md)
- [ ] Have we added the "[Status] String Freeze" label as soon as any new strings were ready for translation (p4TIVU-5Jq-p2)?
    - [ ] For UI changes, have we tested the change in various languages (for example, ES, PT, FR, or DE)? The length of text and words vary significantly between languages. 
- [ ] For changes affecting Jetpack: Have we added the "[Status] Needs Privacy Updates" label if this pull request changes what data or activity we track or use (p4TIVU-aUh-p2)?



Diff:
diff --git a/client/components/domains/use-your-domain-step/index.jsx b/client/components/domains/use-your-domain-step/index.jsx
deleted file mode 100644
index ffc48d5438b1..000000000000
--- a/client/components/domains/use-your-domain-step/index.jsx
+++ /dev/null
@@ -1,469 +0,0 @@
-import { isPlan } from '@automattic/calypso-products';
-import page from '@automattic/calypso-router';
-import { Card, Button, Gridicon } from '@automattic/components';
-import { localizeUrl } from '@automattic/i18n-utils';
-import { withShoppingCart } from '@automattic/shopping-cart';
-import {
-	CALYPSO_HELP_WITH_HELP_CENTER,
-	INCOMING_DOMAIN_TRANSFER,
-	MAP_EXISTING_DOMAIN,
-} from '@automattic/urls';
-import { formatCurrency, localize } from 'i18n-calypso';
-import { get, isEmpty } from 'lodash';
-import PropTypes from 'prop-types';
-import { stringify } from 'qs';
-import { Component } from 'react';
-import { connect } from 'react-redux';
-import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
-import themesImage from 'calypso/assets/images/illustrations/themes.svg';
-import QueryProducts from 'calypso/components/data/query-products-list';
-import HeaderCake from 'calypso/components/header-cake';
-import {
-	isDomainBundledWithPlan,
-	isDomainMappingFree,
-	isNextDomainFree,
-} from 'calypso/lib/cart-values/cart-items';
-import {
-	getDomainPrice,
-	getDomainProductSlug,
-	getDomainTransferSalePrice,
-} from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
-import { recordTracksEvent } from 'calypso/state/analytics/actions';
-import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
-import {
-	DOMAINS_WITH_PLANS_ONLY,
-	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
-} from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
-import { getProductsList } from 'calypso/state/products-list/selectors';
-import { getSelectedSite } from 'calypso/state/ui/selectors';
-
-import './style.scss';
-
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildMapDomainUrl;
-	}
-
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
-	}
-
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
-		if (
-			isEmpty( domainProductSalePrice ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
-		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
-		}
-
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
-
-		return mappingProductPrice;
-	};
-
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
-		);
-	};
-
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
-
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
-
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
-	};
-
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
-
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
-			>
-				{ buttonText }
-			</Button>
-		);
-	};
-
-	renderSelectTransfer = () => {
-		const { translate } = this.props;
-
-		const image = migratingHostImage;
-		const title = translate( 'Transfer your domain away from your current registrar.' );
-		const reasons = [
-			translate(
-				'Domain registration and billing will be moved from your current provider to WordPress.com'
-			),
-			translate( 'Manage your domain and site from your WordPress.com dashboard' ),
-			translate( 'Extends registration by one year' ),
-			this.getTransferFreeText(),
-			this.getTransferSalePriceText(),
-			this.getTransferPriceText(),
-		];
-		const buttonText = translate( 'Transfer to WordPress.com' );
-		const learnMore = translate( '{{a}}Learn more about domain transfers{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
-
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToTransferDomainStep,
-			buttonText,
-			isPrimary: true,
-			learnMore,
-		} );
-	};
-
-	renderSelectMapping = () => {
-		const { translate } = this.props;
-		const image = themesImage;
-		const title = translate( 'Map your domain without moving it from your current registrar.' );
-		const reasons = [
-			translate( 'Domain registration and billing will remain at your current provider' ),
-			translate( 'Manage some domain settings at your current provider and some at WordPress.com' ),
-			// translate( 'Continue paying for your domain registration at your current provider' ),
-			translate( "Requires changes to the domain's DNS" ),
-			this.getMappingPriceText(),
-		];
-		const buttonText = translate( 'Map your domain' );
-		const learnMore = translate( '{{a}}Learn more about domain mapping{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
-
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToMapDomainStep,
-			buttonText,
-			isPrimary: false,
-			learnMore,
-		} );
-	};
-
-	render() {
-		const { isSignupStep, translate } = this.props;
-
-		const header = ! isSignupStep && (
-			<HeaderCake onClick={ this.goBack }>
-				{ this.props.translate( 'Use My Own Domain' ) }
-			</HeaderCake>
-		);
-
-		return (
-			<div className="use-your-domain-step">
-				{ header }
-				<QueryProducts />
-				<div className="use-your-domain-step__content">
-					{ this.renderSelectTransfer() }
-					{ this.renderSelectMapping() }
-				</div>
-				<p className="use-your-domain-step__footer">
-					{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
-						components: {
-							a: (
-								<a
-									href={ CALYPSO_HELP_WITH_HELP_CENTER }
-									target="_blank"
-									rel="noopener noreferrer"
-								/>
-							),
-						},
-					} ) }
-				</p>
-			</div>
-		);
-	}
-}
-
-const recordTransferButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_transfer_click', { domain_name } );
-
-const recordMappingButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name } );
-
-export default connect(
-	( state ) => ( {
-		currentUser: getCurrentUser( state ),
-		currencyCode: getCurrentUserCurrencyCode( state ),
-		domainsWithPlansOnly: currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY ),
-		primaryWithPlansOnly: currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS ),
-		selectedSite: getSelectedSite( state ),
-		productsList: getProductsList( state ),
-	} ),
-	{
-		errorNotice,
-		recordTransferButtonClickInUseYourDomain,
-		recordMappingButtonClickInUseYourDomain,
-	}
-)( withCartKey( withShoppingCart( localize( UseYourDomainStep ) ) ) );
diff --git a/client/components/domains/use-your-domain-step/index.tsx b/client/components/domains/use-your-domain-step/index.tsx
new file mode 100644
index 000000000000..4ede92fc6b15
--- /dev/null
+++ b/client/components/domains/use-your-domain-step/index.tsx
@@ -0,0 +1,313 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( ! phrase ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		domainProductSalePrice !== null &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = productsList?.domain_map?.cost;
+	if ( price && currencyCode ) {
+		const cost = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ) ||
+		isNextDomainFree( cart ) ||
+		isDomainBundledWithPlan( cart, searchQuery )
+	) {
+		mappingPriceText = translate(
+			'Free with your plan, but registration costs at your current provider still apply'
+		);
+	} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
+		mappingPriceText = translate(
+			'Included in annual paid plans, but registration costs at your current provider still apply'
+		);
+	}
+
+	const goToMapDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
+
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_mapping_click', {
+				domain_name: ANALYTICS_SECTION,
+			} )
+		);
+
+		const basePathForMapping = extractBasePath( basePath );
+		let mapDomainStepURL = `${ basePathForMapping }/mapping`;
+		if ( selectedSite ) {
+			const query = stringify( { initialQuery: searchQuery.trim() } );
+			mapDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+		}
+
+		page( mapDomainStepURL );
+	};
+
+	const goToTransferDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
+
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_transfer_click', {
+				domain_name: ANALYTICS_SECTION,
+			} )
+		);
+
+		const basePathForTransfer = extractBasePath( basePath );
+		let transferDomainStepURL = `${ basePathForTransfer }/transfer`;
+		if ( selectedSite ) {
+			const query = stringify( {
+				initialQuery: searchQuery.trim(),
+				useStandardBack: true,
+			} );
+			transferDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+		}
+
+		page( transferDomainStepURL );
+	};
+
+	return (
+		<div className="use-your-domain-step">
+			<HeaderCake onClick={ goBack }>{ translate( 'Use My Own Domain' ) }</HeaderCake>
+			<QueryProducts />
+			<div className="use-your-domain-step__content">
+				<UseYourDomainStepContent
+					image={ migratingHostImage }
+					title={ translate( 'Transfer your domain away from your current registrar.' ) }
+					reasons={ [
+						translate(
+							'Domain registration and billing will be moved from your current provider to WordPress.com'
+						),
+						translate( 'Manage your domain and site from your WordPress.com dashboard' ),
+						translate( 'Extends registration by one year' ),
+						domainProductFreeText,
+						transferSalePriceText,
+						transferPriceText,
+					] }
+					buttonText={ translate( 'Transfer to WordPress.com' ) }
+					onClick={ goToTransferDomainStep }
+					isPrimary
+					learnMore={ translate( '{{a}}Learn more about domain transfers{{/a}}', {
+						components: {
+							a: (
+								<a
+									href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
+									rel="noopener noreferrer"
+									target="_blank"
+								/>
+							),
+						},
+					} ) }
+				/>
+				<UseYourDomainStepContent
+					image={ themesImage }
+					title={ translate( 'Map your domain without moving it from your current registrar.' ) }
+					reasons={ [
+						translate( 'Domain registration and billing will remain at your current provider' ),
+						translate(
+							'Manage some domain settings at your current provider and some at WordPress.com'
+						),
+						translate( "Requires changes to the domain's DNS" ),
+						mappingPriceText,
+					] }
+					buttonText={ translate( 'Map your domain' ) }
+					onClick={ goToMapDomainStep }
+					isPrimary={ false }
+					learnMore={ translate( '{{a}}Learn more about domain mapping{{/a}}', {
+						components: {
+							a: (
+								<a
+									href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
+									rel="noopener noreferrer"
+									target="_blank"
+								/>
+							),
+						},
+					} ) }
+				/>
+			</div>
+			<p className="use-your-domain-step__footer">
+				{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+					components: {
+						a: (
+							<a href={ CALYPSO_HELP_WITH_HELP_CENTER } target="_blank" rel="noopener noreferrer" />
+						),
+					},
+				} ) }
+			</p>
+		</div>
+	);
+}
+
+export default UseYourDomainStep;
diff --git a/client/lib/cart-values/cart-items.ts b/client/lib/cart-values/cart-items.ts
index 38ffc1a7572a..50b7a8ab067c 100644
--- a/client/lib/cart-values/cart-items.ts
+++ b/client/lib/cart-values/cart-items.ts
@@ -52,6 +52,7 @@ import { isWpComProductRenewal as isRenewal } from '@automattic/wpcom-checkout';
 import { getTld } from 'calypso/lib/domains';
 import { domainProductSlugs } from 'calypso/lib/domains/constants';
 import type { WithCamelCaseSlug, WithSnakeCaseSlug } from '@automattic/calypso-products';
+import type { SiteDetails } from '@automattic/data-stores';
 import type {
 	ResponseCart,
 	ResponseCartProduct,
@@ -769,7 +770,7 @@ function hasSomeSlug( data: unknown ): data is WithSnakeCaseSlug | WithCamelCase
 
 export function shouldBundleDomainWithPlan(
 	withPlansOnly: boolean,
-	selectedSite: undefined | { plan: { product_slug: string } },
+	selectedSite: undefined | SiteDetails,
 	cart: ResponseCart,
 	suggestionOrCartItem: {
 		product_slug?: string;
@@ -790,7 +791,7 @@ export function shouldBundleDomainWithPlan(
 			! isDomainBeingUsedForPlan( cart, suggestionOrCartItem.domain_name ) && // a plan in cart
 			! isNextDomainFree( cart ) && // domain credit
 			! hasPlan( cart ) && // already a plan in cart
-			( ! selectedSite || ( selectedSite && selectedSite.plan.product_slug === 'free_plan' ) )
+			( ! selectedSite || selectedSite.plan?.product_slug === 'free_plan' )
 	); // site has a plan
 }
 
@@ -799,7 +800,7 @@ export function shouldBundleDomainWithPlan(
  * This function checks tells if user has to upgrade just to be able to pay for a domain.
  */
 export function hasToUpgradeToPayForADomain(
-	selectedSite: undefined | { plan: { product_slug?: string } },
+	selectedSite: undefined | SiteDetails,
 	cart: ObjectWithProducts,
 	domain?: string
 ): boolean {
@@ -807,7 +808,7 @@ export function hasToUpgradeToPayForADomain(
 		return false;
 	}
 
-	const sitePlanSlug = ( ( selectedSite || {} ).plan || {} ).product_slug;
+	const sitePlanSlug = selectedSite?.plan?.product_slug;
 	const isDotBlogDomain = 'blog'.startsWith( getTld( domain ) );
 
 	if ( sitePlanSlug && isWpComBloggerPlan( sitePlanSlug ) && ! isDotBlogDomain ) {
@@ -821,11 +822,11 @@ export function hasToUpgradeToPayForADomain(
 	return false;
 }
 
-export function isDomainMappingFree(
-	selectedSite: undefined | { plan: { product_slug: string } }
-): boolean {
+export function isDomainMappingFree( selectedSite: SiteDetails | null | undefined ): boolean {
 	return Boolean(
-		selectedSite && isPlan( selectedSite.plan ) && ! isBloggerPlan( selectedSite.plan.product_slug )
+		selectedSite?.plan &&
+			isPlan( selectedSite.plan ) &&
+			! isBloggerPlan( selectedSite.plan.product_slug )
 	);
 }
 
@@ -848,7 +849,7 @@ const isMonthlyOrFreeFlow = ( flowName: string | undefined ): boolean => {
 
 export function getDomainPriceRule(
 	withPlansOnly: boolean,
-	selectedSite: undefined | { plan: { product_slug: string } },
+	selectedSite: undefined | SiteDetails,
 	cart: ResponseCart,
 	suggestion: {
 		product_slug?: string;


Reviews:
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T17:56:14Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T07:01:46Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T07:03:09Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T07:04:26Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T07:06:30Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T07:09:19Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:36:12Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:36:40Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:43:01Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:43:56Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:45:17Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:49:49Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:50:46Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T08:52:35Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T11:21:28Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:40:01Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:42:05Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:52:13Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T14:56:34Z
user: login: sirbrillig
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T15:01:58Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T15:02:01Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-26T15:04:47Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T16:58:08Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:45:30Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:45:37Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:45:48Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:46:14Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T17:46:29Z
user: login: jsnajdr
body: I like this 🙂 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-28T11:06:42Z
user: login: jsnajdr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T11:08:51Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T11:48:46Z
user: login: ciampo
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-28T11:48:55Z

Review comments:
diff_hunk: @@ -0,0 +1,340 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: The `withShoppingCart` HOC that was used previously called the `useManagerClient` hook, which doesn't seem to be exported from the `@automattic/shopping-cart` package:

https://github.com/Automattic/wp-calypso/blob/6e6636e9633456a68032d28fafd6d734e08db379/packages/shopping-cart/src/with-shopping-cart.tsx#L17-L19

@sirbrillig Is it necessary to call `useManagerClient` here as well, or can we do without and just call `useShoppingCart` ?
created_at: 2025-03-25T17:56:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,469 +0,0 @@
-import { isPlan } from '@automattic/calypso-products';
-import page from '@automattic/calypso-router';
-import { Card, Button, Gridicon } from '@automattic/components';
-import { localizeUrl } from '@automattic/i18n-utils';
-import { withShoppingCart } from '@automattic/shopping-cart';
-import {
-	CALYPSO_HELP_WITH_HELP_CENTER,
-	INCOMING_DOMAIN_TRANSFER,
-	MAP_EXISTING_DOMAIN,
-} from '@automattic/urls';
-import { formatCurrency, localize } from 'i18n-calypso';
-import { get, isEmpty } from 'lodash';
-import PropTypes from 'prop-types';
-import { stringify } from 'qs';
-import { Component } from 'react';
-import { connect } from 'react-redux';
-import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
-import themesImage from 'calypso/assets/images/illustrations/themes.svg';
-import QueryProducts from 'calypso/components/data/query-products-list';
-import HeaderCake from 'calypso/components/header-cake';
-import {
-	isDomainBundledWithPlan,
-	isDomainMappingFree,
-	isNextDomainFree,
-} from 'calypso/lib/cart-values/cart-items';
-import {
-	getDomainPrice,
-	getDomainProductSlug,
-	getDomainTransferSalePrice,
-} from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
-import { recordTracksEvent } from 'calypso/state/analytics/actions';
-import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
-import {
-	DOMAINS_WITH_PLANS_ONLY,
-	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
-} from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
-import { getProductsList } from 'calypso/state/products-list/selectors';
-import { getSelectedSite } from 'calypso/state/ui/selectors';
-
-import './style.scss';
-
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
path: client/components/domains/use-your-domain-step/index.jsx
user: login: ciampo
body: I was able to remove a lot of props compared to the previous implementation:
- some of them came from `connect`ing the component to the Redux Global state, and were replaced by hooks;
- some of them are actually not currently used, and I guess were not cleaned up over time
created_at: 2025-03-26T07:01:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,469 +0,0 @@
-import { isPlan } from '@automattic/calypso-products';
-import page from '@automattic/calypso-router';
-import { Card, Button, Gridicon } from '@automattic/components';
-import { localizeUrl } from '@automattic/i18n-utils';
-import { withShoppingCart } from '@automattic/shopping-cart';
-import {
-	CALYPSO_HELP_WITH_HELP_CENTER,
-	INCOMING_DOMAIN_TRANSFER,
-	MAP_EXISTING_DOMAIN,
-} from '@automattic/urls';
-import { formatCurrency, localize } from 'i18n-calypso';
-import { get, isEmpty } from 'lodash';
-import PropTypes from 'prop-types';
-import { stringify } from 'qs';
-import { Component } from 'react';
-import { connect } from 'react-redux';
-import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
-import themesImage from 'calypso/assets/images/illustrations/themes.svg';
-import QueryProducts from 'calypso/components/data/query-products-list';
-import HeaderCake from 'calypso/components/header-cake';
-import {
-	isDomainBundledWithPlan,
-	isDomainMappingFree,
-	isNextDomainFree,
-} from 'calypso/lib/cart-values/cart-items';
-import {
-	getDomainPrice,
-	getDomainProductSlug,
-	getDomainTransferSalePrice,
-} from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
-import { recordTracksEvent } from 'calypso/state/analytics/actions';
-import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
-import {
-	DOMAINS_WITH_PLANS_ONLY,
-	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
-} from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
-import { getProductsList } from 'calypso/state/products-list/selectors';
-import { getSelectedSite } from 'calypso/state/ui/selectors';
-
-import './style.scss';
-
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
path: client/components/domains/use-your-domain-step/index.jsx
user: login: ciampo
body: Most of the old "state" wasn't actually used — in fact, `setState` is never called in this component. The only `state` usage that I kept in the new version is for storing the initial value of a prop.
created_at: 2025-03-26T07:03:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Added a `!` because `isEmpty` is not acting as a type guard for `domainProductSalePrice`
created_at: 2025-03-26T07:04:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: I didn't want to work on `formatCurrency` to prevent scope bloat, but I get the sense, from the number of warnings that I see in the browser console, that `formatCurrency` is often invoked with a `null` or `undefined` currency code. I wonder if we should change it so that it handles `null` or `undefined` internally
created_at: 2025-03-26T07:06:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,469 +0,0 @@
-import { isPlan } from '@automattic/calypso-products';
-import page from '@automattic/calypso-router';
-import { Card, Button, Gridicon } from '@automattic/components';
-import { localizeUrl } from '@automattic/i18n-utils';
-import { withShoppingCart } from '@automattic/shopping-cart';
-import {
-	CALYPSO_HELP_WITH_HELP_CENTER,
-	INCOMING_DOMAIN_TRANSFER,
-	MAP_EXISTING_DOMAIN,
-} from '@automattic/urls';
-import { formatCurrency, localize } from 'i18n-calypso';
-import { get, isEmpty } from 'lodash';
-import PropTypes from 'prop-types';
-import { stringify } from 'qs';
-import { Component } from 'react';
-import { connect } from 'react-redux';
-import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
-import themesImage from 'calypso/assets/images/illustrations/themes.svg';
-import QueryProducts from 'calypso/components/data/query-products-list';
-import HeaderCake from 'calypso/components/header-cake';
-import {
-	isDomainBundledWithPlan,
-	isDomainMappingFree,
-	isNextDomainFree,
-} from 'calypso/lib/cart-values/cart-items';
-import {
-	getDomainPrice,
-	getDomainProductSlug,
-	getDomainTransferSalePrice,
-} from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
-import { recordTracksEvent } from 'calypso/state/analytics/actions';
-import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
-import {
-	DOMAINS_WITH_PLANS_ONLY,
-	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
-} from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
-import { getProductsList } from 'calypso/state/products-list/selectors';
-import { getSelectedSite } from 'calypso/state/ui/selectors';
-
-import './style.scss';
-
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildMapDomainUrl;
-	}
-
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
-	}
-
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
-		if (
-			isEmpty( domainProductSalePrice ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
-		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
-		}
-
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
-
-		return mappingProductPrice;
-	};
-
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
-		);
-	};
-
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
-
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
-
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
-	};
-
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
-
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
path: client/components/domains/use-your-domain-step/index.jsx
user: login: ciampo
body: I removed the `busy` prop since, in the current implementation, it was always computing as `false`
created_at: 2025-03-26T07:09:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,318 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	// TODO: what about using
+	// useManagerClient( 'withShoppingCart' );
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
-		}
-
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
-
-		return mappingProductPrice;
-	};
-
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
 		);
-	};
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
+		isNextDomainFree( cart ) ||
+		isDomainBundledWithPlan( cart, searchQuery )
+	) {
+		mappingPriceText = translate(
+			'Free with your plan, but registration costs at your current provider still apply'
+		);
+	} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
+		mappingPriceText = translate(
+			'Included in annual paid plans, but registration costs at your current provider still apply'
+		);
+	}
 
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
+	const goToMapDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
+		recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name: analyticsSection } );
 
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
-	};
+		let mapDomainStepURL;
 
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
+		if ( mapDomainUrl ) {
+			mapDomainStepURL = mapDomainUrl;
+		} else {
+			const basePathForMapping = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			mapDomainStepURL = `${ basePathForMapping }/mapping`;
+			if ( selectedSite ) {
+				const query = stringify( { initialQuery: searchQuery.trim() } );
+				mapDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
-			>
-				{ buttonText }
-			</Button>
-		);
+		page( mapDomainStepURL );
 	};
 
-	renderSelectTransfer = () => {
-		const { translate } = this.props;
+	const goToTransferDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-		const image = migratingHostImage;
-		const title = translate( 'Transfer your domain away from your current registrar.' );
-		const reasons = [
-			translate(
-				'Domain registration and billing will be moved from your current provider to WordPress.com'
-			),
-			translate( 'Manage your domain and site from your WordPress.com dashboard' ),
-			translate( 'Extends registration by one year' ),
-			this.getTransferFreeText(),
-			this.getTransferSalePriceText(),
-			this.getTransferPriceText(),
-		];
-		const buttonText = translate( 'Transfer to WordPress.com' );
-		const learnMore = translate( '{{a}}Learn more about domain transfers{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
+		recordTracksEvent( 'calypso_use_your_domain_transfer_click', {
+			domain_name: analyticsSection,
 		} );
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToTransferDomainStep,
-			buttonText,
-			isPrimary: true,
-			learnMore,
-		} );
-	};
+		let transferDomainStepURL;
 
-	renderSelectMapping = () => {
-		const { translate } = this.props;
-		const image = themesImage;
-		const title = translate( 'Map your domain without moving it from your current registrar.' );
-		const reasons = [
-			translate( 'Domain registration and billing will remain at your current provider' ),
-			translate( 'Manage some domain settings at your current provider and some at WordPress.com' ),
-			// translate( 'Continue paying for your domain registration at your current provider' ),
-			translate( "Requires changes to the domain's DNS" ),
-			this.getMappingPriceText(),
-		];
-		const buttonText = translate( 'Map your domain' );
-		const learnMore = translate( '{{a}}Learn more about domain mapping{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
+		if ( transferDomainUrl ) {
+			transferDomainStepURL = transferDomainUrl;
+		} else {
+			const basePathForTransfer = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			transferDomainStepURL = `${ basePathForTransfer }/transfer`;
+
+			if ( selectedSite ) {
+				const query = stringify( {
+					initialQuery: searchQuery.trim(),
+					useStandardBack: true,
+				} );
+				transferDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToMapDomainStep,
-			buttonText,
-			isPrimary: false,
-			learnMore,
-		} );
+		page( transferDomainStepURL );
 	};
 
-	render() {
-		const { isSignupStep, translate } = this.props;
-
-		const header = ! isSignupStep && (
-			<HeaderCake onClick={ this.goBack }>
-				{ this.props.translate( 'Use My Own Domain' ) }
-			</HeaderCake>
-		);
-
-		return (
-			<div className="use-your-domain-step">
-				{ header }
-				<QueryProducts />
-				<div className="use-your-domain-step__content">
-					{ this.renderSelectTransfer() }
-					{ this.renderSelectMapping() }
-				</div>
-				<p className="use-your-domain-step__footer">
-					{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+	return (
+		<div className="use-your-domain-step">
+			{ ! isSignupStep && (
+				<HeaderCake onClick={ goBack }>{ translate( 'Use My Own Domain' ) }</HeaderCake>
+			) }
+			<QueryProducts />
+			<div className="use-your-domain-step__content">
+				<UseYourDomainStepContent
+					image={ migratingHostImage }
+					title={ translate( 'Transfer your domain away from your current registrar.' ) }
+					reasons={ [
+						translate(
+							'Domain registration and billing will be moved from your current provider to WordPress.com'
+						),
+						translate( 'Manage your domain and site from your WordPress.com dashboard' ),
+						translate( 'Extends registration by one year' ),
+						domainProductFreeText,
+						transferSalePriceText,
+						transferPriceText,
+					] }
+					buttonText={ translate( 'Transfer to WordPress.com' ) }
+					onClick={ goToTransferDomainStep }
+					isPrimary
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain transfers{{/a}}', {
 						components: {
 							a: (
 								<a
-									href={ CALYPSO_HELP_WITH_HELP_CENTER }
+									href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
+									rel="noopener noreferrer"
 									target="_blank"
+								/>
+							),
+						},
+					} ) }
+				/>
+				<UseYourDomainStepContent
+					image={ themesImage }
+					title={ translate( 'Map your domain without moving it from your current registrar.' ) }
+					reasons={ [
+						translate( 'Domain registration and billing will remain at your current provider' ),
+						translate(
+							'Manage some domain settings at your current provider and some at WordPress.com'
+						),
+						translate( "Requires changes to the domain's DNS" ),
+						mappingPriceText,
+					] }
+					buttonText={ translate( 'Map your domain' ) }
+					onClick={ goToMapDomainStep }
+					isPrimary={ false }
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain mapping{{/a}}', {
+						components: {
+							a: (
+								<a
+									href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
 									rel="noopener noreferrer"
+									target="_blank"
 								/>
 							),
 						},
 					} ) }
-				</p>
+				/>
 			</div>
-		);
-	}
+			<p className="use-your-domain-step__footer">
+				{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+					components: {
+						a: (
+							<a href={ CALYPSO_HELP_WITH_HELP_CENTER } target="_blank" rel="noopener noreferrer" />
+						),
+					},
+				} ) }
+			</p>
+		</div>
+	);
 }
 
-const recordTransferButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_transfer_click', { domain_name } );
-
-const recordMappingButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name } );
-
-export default connect(
-	( state ) => ( {
-		currentUser: getCurrentUser( state ),
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Removed in the new version as it was not used in code
created_at: 2025-03-26T08:36:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,318 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	// TODO: what about using
+	// useManagerClient( 'withShoppingCart' );
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
-		}
-
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
-
-		return mappingProductPrice;
-	};
-
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
 		);
-	};
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
+		isNextDomainFree( cart ) ||
+		isDomainBundledWithPlan( cart, searchQuery )
+	) {
+		mappingPriceText = translate(
+			'Free with your plan, but registration costs at your current provider still apply'
+		);
+	} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
+		mappingPriceText = translate(
+			'Included in annual paid plans, but registration costs at your current provider still apply'
+		);
+	}
 
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
+	const goToMapDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
+		recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name: analyticsSection } );
 
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
-	};
+		let mapDomainStepURL;
 
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
+		if ( mapDomainUrl ) {
+			mapDomainStepURL = mapDomainUrl;
+		} else {
+			const basePathForMapping = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			mapDomainStepURL = `${ basePathForMapping }/mapping`;
+			if ( selectedSite ) {
+				const query = stringify( { initialQuery: searchQuery.trim() } );
+				mapDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
-			>
-				{ buttonText }
-			</Button>
-		);
+		page( mapDomainStepURL );
 	};
 
-	renderSelectTransfer = () => {
-		const { translate } = this.props;
+	const goToTransferDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-		const image = migratingHostImage;
-		const title = translate( 'Transfer your domain away from your current registrar.' );
-		const reasons = [
-			translate(
-				'Domain registration and billing will be moved from your current provider to WordPress.com'
-			),
-			translate( 'Manage your domain and site from your WordPress.com dashboard' ),
-			translate( 'Extends registration by one year' ),
-			this.getTransferFreeText(),
-			this.getTransferSalePriceText(),
-			this.getTransferPriceText(),
-		];
-		const buttonText = translate( 'Transfer to WordPress.com' );
-		const learnMore = translate( '{{a}}Learn more about domain transfers{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
+		recordTracksEvent( 'calypso_use_your_domain_transfer_click', {
+			domain_name: analyticsSection,
 		} );
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToTransferDomainStep,
-			buttonText,
-			isPrimary: true,
-			learnMore,
-		} );
-	};
+		let transferDomainStepURL;
 
-	renderSelectMapping = () => {
-		const { translate } = this.props;
-		const image = themesImage;
-		const title = translate( 'Map your domain without moving it from your current registrar.' );
-		const reasons = [
-			translate( 'Domain registration and billing will remain at your current provider' ),
-			translate( 'Manage some domain settings at your current provider and some at WordPress.com' ),
-			// translate( 'Continue paying for your domain registration at your current provider' ),
-			translate( "Requires changes to the domain's DNS" ),
-			this.getMappingPriceText(),
-		];
-		const buttonText = translate( 'Map your domain' );
-		const learnMore = translate( '{{a}}Learn more about domain mapping{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
+		if ( transferDomainUrl ) {
+			transferDomainStepURL = transferDomainUrl;
+		} else {
+			const basePathForTransfer = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			transferDomainStepURL = `${ basePathForTransfer }/transfer`;
+
+			if ( selectedSite ) {
+				const query = stringify( {
+					initialQuery: searchQuery.trim(),
+					useStandardBack: true,
+				} );
+				transferDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToMapDomainStep,
-			buttonText,
-			isPrimary: false,
-			learnMore,
-		} );
+		page( transferDomainStepURL );
 	};
 
-	render() {
-		const { isSignupStep, translate } = this.props;
-
-		const header = ! isSignupStep && (
-			<HeaderCake onClick={ this.goBack }>
-				{ this.props.translate( 'Use My Own Domain' ) }
-			</HeaderCake>
-		);
-
-		return (
-			<div className="use-your-domain-step">
-				{ header }
-				<QueryProducts />
-				<div className="use-your-domain-step__content">
-					{ this.renderSelectTransfer() }
-					{ this.renderSelectMapping() }
-				</div>
-				<p className="use-your-domain-step__footer">
-					{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+	return (
+		<div className="use-your-domain-step">
+			{ ! isSignupStep && (
+				<HeaderCake onClick={ goBack }>{ translate( 'Use My Own Domain' ) }</HeaderCake>
+			) }
+			<QueryProducts />
+			<div className="use-your-domain-step__content">
+				<UseYourDomainStepContent
+					image={ migratingHostImage }
+					title={ translate( 'Transfer your domain away from your current registrar.' ) }
+					reasons={ [
+						translate(
+							'Domain registration and billing will be moved from your current provider to WordPress.com'
+						),
+						translate( 'Manage your domain and site from your WordPress.com dashboard' ),
+						translate( 'Extends registration by one year' ),
+						domainProductFreeText,
+						transferSalePriceText,
+						transferPriceText,
+					] }
+					buttonText={ translate( 'Transfer to WordPress.com' ) }
+					onClick={ goToTransferDomainStep }
+					isPrimary
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain transfers{{/a}}', {
 						components: {
 							a: (
 								<a
-									href={ CALYPSO_HELP_WITH_HELP_CENTER }
+									href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
+									rel="noopener noreferrer"
 									target="_blank"
+								/>
+							),
+						},
+					} ) }
+				/>
+				<UseYourDomainStepContent
+					image={ themesImage }
+					title={ translate( 'Map your domain without moving it from your current registrar.' ) }
+					reasons={ [
+						translate( 'Domain registration and billing will remain at your current provider' ),
+						translate(
+							'Manage some domain settings at your current provider and some at WordPress.com'
+						),
+						translate( "Requires changes to the domain's DNS" ),
+						mappingPriceText,
+					] }
+					buttonText={ translate( 'Map your domain' ) }
+					onClick={ goToMapDomainStep }
+					isPrimary={ false }
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain mapping{{/a}}', {
+						components: {
+							a: (
+								<a
+									href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
 									rel="noopener noreferrer"
+									target="_blank"
 								/>
 							),
 						},
 					} ) }
-				</p>
+				/>
 			</div>
-		);
-	}
+			<p className="use-your-domain-step__footer">
+				{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+					components: {
+						a: (
+							<a href={ CALYPSO_HELP_WITH_HELP_CENTER } target="_blank" rel="noopener noreferrer" />
+						),
+					},
+				} ) }
+			</p>
+		</div>
+	);
 }
 
-const recordTransferButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_transfer_click', { domain_name } );
-
-const recordMappingButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name } );
-
-export default connect(
-	( state ) => ( {
-		currentUser: getCurrentUser( state ),
-		currencyCode: getCurrentUserCurrencyCode( state ),
-		domainsWithPlansOnly: currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY ),
-		primaryWithPlansOnly: currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS ),
-		selectedSite: getSelectedSite( state ),
-		productsList: getProductsList( state ),
-	} ),
-	{
-		errorNotice,
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Also removed as I couldn't see any error notice being dispatched in the code
created_at: 2025-03-26T08:36:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
+		isNextDomainFree( cart ) ||
+		isDomainBundledWithPlan( cart, searchQuery )
+	) {
+		mappingPriceText = translate(
+			'Free with your plan, but registration costs at your current provider still apply'
+		);
+	} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
+		mappingPriceText = translate(
+			'Included in annual paid plans, but registration costs at your current provider still apply'
+		);
+	}
 
-		return mappingProductPrice;
-	};
+	const goToMapDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_mapping_click', {
+				domain_name: analyticsSection,
+			} )
 		);
-	};
 
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
+		let mapDomainStepURL;
 
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
+		if ( mapDomainUrl ) {
+			mapDomainStepURL = mapDomainUrl;
+		} else {
+			const basePathForMapping = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			mapDomainStepURL = `${ basePathForMapping }/mapping`;
+			if ( selectedSite ) {
+				const query = stringify( { initialQuery: searchQuery.trim() } );
+				mapDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
+		page( mapDomainStepURL );
 	};
 
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
+	const goToTransferDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
-			>
-				{ buttonText }
-			</Button>
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_transfer_click', {
+				domain_name: analyticsSection,
+			} )
 		);
-	};
-
-	renderSelectTransfer = () => {
-		const { translate } = this.props;
-
-		const image = migratingHostImage;
-		const title = translate( 'Transfer your domain away from your current registrar.' );
-		const reasons = [
-			translate(
-				'Domain registration and billing will be moved from your current provider to WordPress.com'
-			),
-			translate( 'Manage your domain and site from your WordPress.com dashboard' ),
-			translate( 'Extends registration by one year' ),
-			this.getTransferFreeText(),
-			this.getTransferSalePriceText(),
-			this.getTransferPriceText(),
-		];
-		const buttonText = translate( 'Transfer to WordPress.com' );
-		const learnMore = translate( '{{a}}Learn more about domain transfers{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToTransferDomainStep,
-			buttonText,
-			isPrimary: true,
-			learnMore,
-		} );
-	};
+		let transferDomainStepURL;
 
-	renderSelectMapping = () => {
-		const { translate } = this.props;
-		const image = themesImage;
-		const title = translate( 'Map your domain without moving it from your current registrar.' );
-		const reasons = [
-			translate( 'Domain registration and billing will remain at your current provider' ),
-			translate( 'Manage some domain settings at your current provider and some at WordPress.com' ),
-			// translate( 'Continue paying for your domain registration at your current provider' ),
-			translate( "Requires changes to the domain's DNS" ),
-			this.getMappingPriceText(),
-		];
-		const buttonText = translate( 'Map your domain' );
-		const learnMore = translate( '{{a}}Learn more about domain mapping{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
+		if ( transferDomainUrl ) {
+			transferDomainStepURL = transferDomainUrl;
+		} else {
+			const basePathForTransfer = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			transferDomainStepURL = `${ basePathForTransfer }/transfer`;
+
+			if ( selectedSite ) {
+				const query = stringify( {
+					initialQuery: searchQuery.trim(),
+					useStandardBack: true,
+				} );
+				transferDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToMapDomainStep,
-			buttonText,
-			isPrimary: false,
-			learnMore,
-		} );
+		page( transferDomainStepURL );
 	};
 
-	render() {
-		const { isSignupStep, translate } = this.props;
-
-		const header = ! isSignupStep && (
-			<HeaderCake onClick={ this.goBack }>
-				{ this.props.translate( 'Use My Own Domain' ) }
-			</HeaderCake>
-		);
-
-		return (
-			<div className="use-your-domain-step">
-				{ header }
-				<QueryProducts />
-				<div className="use-your-domain-step__content">
-					{ this.renderSelectTransfer() }
-					{ this.renderSelectMapping() }
-				</div>
-				<p className="use-your-domain-step__footer">
-					{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+	return (
+		<div className="use-your-domain-step">
+			{ ! isSignupStep && (
+				<HeaderCake onClick={ goBack }>{ translate( 'Use My Own Domain' ) }</HeaderCake>
+			) }
+			<QueryProducts />
+			<div className="use-your-domain-step__content">
+				<UseYourDomainStepContent
+					image={ migratingHostImage }
+					title={ translate( 'Transfer your domain away from your current registrar.' ) }
+					reasons={ [
+						translate(
+							'Domain registration and billing will be moved from your current provider to WordPress.com'
+						),
+						translate( 'Manage your domain and site from your WordPress.com dashboard' ),
+						translate( 'Extends registration by one year' ),
+						domainProductFreeText,
+						transferSalePriceText,
+						transferPriceText,
+					] }
+					buttonText={ translate( 'Transfer to WordPress.com' ) }
+					onClick={ goToTransferDomainStep }
+					isPrimary
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain transfers{{/a}}', {
 						components: {
 							a: (
 								<a
-									href={ CALYPSO_HELP_WITH_HELP_CENTER }
+									href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
+									rel="noopener noreferrer"
 									target="_blank"
+								/>
+							),
+						},
+					} ) }
+				/>
+				<UseYourDomainStepContent
+					image={ themesImage }
+					title={ translate( 'Map your domain without moving it from your current registrar.' ) }
+					reasons={ [
+						translate( 'Domain registration and billing will remain at your current provider' ),
+						translate(
+							'Manage some domain settings at your current provider and some at WordPress.com'
+						),
+						translate( "Requires changes to the domain's DNS" ),
+						mappingPriceText,
+					] }
+					buttonText={ translate( 'Map your domain' ) }
+					onClick={ goToMapDomainStep }
+					isPrimary={ false }
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain mapping{{/a}}', {
+						components: {
+							a: (
+								<a
+									href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
 									rel="noopener noreferrer"
+									target="_blank"
 								/>
 							),
 						},
 					} ) }
-				</p>
+				/>
 			</div>
-		);
-	}
+			<p className="use-your-domain-step__footer">
+				{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+					components: {
+						a: (
+							<a href={ CALYPSO_HELP_WITH_HELP_CENTER } target="_blank" rel="noopener noreferrer" />
+						),
+					},
+				} ) }
+			</p>
+		</div>
+	);
 }
 
-const recordTransferButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_transfer_click', { domain_name } );
-
-const recordMappingButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name } );
-
-export default connect(
-	( state ) => ( {
-		currentUser: getCurrentUser( state ),
-		currencyCode: getCurrentUserCurrencyCode( state ),
-		domainsWithPlansOnly: currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY ),
-		primaryWithPlansOnly: currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS ),
-		selectedSite: getSelectedSite( state ),
-		productsList: getProductsList( state ),
-	} ),
-	{
-		errorNotice,
-		recordTransferButtonClickInUseYourDomain,
-		recordMappingButtonClickInUseYourDomain,
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Moved to inline `dispatch` calls
created_at: 2025-03-26T08:43:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
+		isNextDomainFree( cart ) ||
+		isDomainBundledWithPlan( cart, searchQuery )
+	) {
+		mappingPriceText = translate(
+			'Free with your plan, but registration costs at your current provider still apply'
+		);
+	} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
+		mappingPriceText = translate(
+			'Included in annual paid plans, but registration costs at your current provider still apply'
+		);
+	}
 
-		return mappingProductPrice;
-	};
+	const goToMapDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_mapping_click', {
+				domain_name: analyticsSection,
+			} )
 		);
-	};
 
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
+		let mapDomainStepURL;
 
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
+		if ( mapDomainUrl ) {
+			mapDomainStepURL = mapDomainUrl;
+		} else {
+			const basePathForMapping = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			mapDomainStepURL = `${ basePathForMapping }/mapping`;
+			if ( selectedSite ) {
+				const query = stringify( { initialQuery: searchQuery.trim() } );
+				mapDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
+		page( mapDomainStepURL );
 	};
 
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
+	const goToTransferDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
-			>
-				{ buttonText }
-			</Button>
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_transfer_click', {
+				domain_name: analyticsSection,
+			} )
 		);
-	};
-
-	renderSelectTransfer = () => {
-		const { translate } = this.props;
-
-		const image = migratingHostImage;
-		const title = translate( 'Transfer your domain away from your current registrar.' );
-		const reasons = [
-			translate(
-				'Domain registration and billing will be moved from your current provider to WordPress.com'
-			),
-			translate( 'Manage your domain and site from your WordPress.com dashboard' ),
-			translate( 'Extends registration by one year' ),
-			this.getTransferFreeText(),
-			this.getTransferSalePriceText(),
-			this.getTransferPriceText(),
-		];
-		const buttonText = translate( 'Transfer to WordPress.com' );
-		const learnMore = translate( '{{a}}Learn more about domain transfers{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToTransferDomainStep,
-			buttonText,
-			isPrimary: true,
-			learnMore,
-		} );
-	};
+		let transferDomainStepURL;
 
-	renderSelectMapping = () => {
-		const { translate } = this.props;
-		const image = themesImage;
-		const title = translate( 'Map your domain without moving it from your current registrar.' );
-		const reasons = [
-			translate( 'Domain registration and billing will remain at your current provider' ),
-			translate( 'Manage some domain settings at your current provider and some at WordPress.com' ),
-			// translate( 'Continue paying for your domain registration at your current provider' ),
-			translate( "Requires changes to the domain's DNS" ),
-			this.getMappingPriceText(),
-		];
-		const buttonText = translate( 'Map your domain' );
-		const learnMore = translate( '{{a}}Learn more about domain mapping{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
+		if ( transferDomainUrl ) {
+			transferDomainStepURL = transferDomainUrl;
+		} else {
+			const basePathForTransfer = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			transferDomainStepURL = `${ basePathForTransfer }/transfer`;
+
+			if ( selectedSite ) {
+				const query = stringify( {
+					initialQuery: searchQuery.trim(),
+					useStandardBack: true,
+				} );
+				transferDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToMapDomainStep,
-			buttonText,
-			isPrimary: false,
-			learnMore,
-		} );
+		page( transferDomainStepURL );
 	};
 
-	render() {
-		const { isSignupStep, translate } = this.props;
-
-		const header = ! isSignupStep && (
-			<HeaderCake onClick={ this.goBack }>
-				{ this.props.translate( 'Use My Own Domain' ) }
-			</HeaderCake>
-		);
-
-		return (
-			<div className="use-your-domain-step">
-				{ header }
-				<QueryProducts />
-				<div className="use-your-domain-step__content">
-					{ this.renderSelectTransfer() }
-					{ this.renderSelectMapping() }
-				</div>
-				<p className="use-your-domain-step__footer">
-					{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+	return (
+		<div className="use-your-domain-step">
+			{ ! isSignupStep && (
+				<HeaderCake onClick={ goBack }>{ translate( 'Use My Own Domain' ) }</HeaderCake>
+			) }
+			<QueryProducts />
+			<div className="use-your-domain-step__content">
+				<UseYourDomainStepContent
+					image={ migratingHostImage }
+					title={ translate( 'Transfer your domain away from your current registrar.' ) }
+					reasons={ [
+						translate(
+							'Domain registration and billing will be moved from your current provider to WordPress.com'
+						),
+						translate( 'Manage your domain and site from your WordPress.com dashboard' ),
+						translate( 'Extends registration by one year' ),
+						domainProductFreeText,
+						transferSalePriceText,
+						transferPriceText,
+					] }
+					buttonText={ translate( 'Transfer to WordPress.com' ) }
+					onClick={ goToTransferDomainStep }
+					isPrimary
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain transfers{{/a}}', {
 						components: {
 							a: (
 								<a
-									href={ CALYPSO_HELP_WITH_HELP_CENTER }
+									href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
+									rel="noopener noreferrer"
 									target="_blank"
+								/>
+							),
+						},
+					} ) }
+				/>
+				<UseYourDomainStepContent
+					image={ themesImage }
+					title={ translate( 'Map your domain without moving it from your current registrar.' ) }
+					reasons={ [
+						translate( 'Domain registration and billing will remain at your current provider' ),
+						translate(
+							'Manage some domain settings at your current provider and some at WordPress.com'
+						),
+						translate( "Requires changes to the domain's DNS" ),
+						mappingPriceText,
+					] }
+					buttonText={ translate( 'Map your domain' ) }
+					onClick={ goToMapDomainStep }
+					isPrimary={ false }
+					isSubmitting={ submittingWhois }
+					learnMore={ translate( '{{a}}Learn more about domain mapping{{/a}}', {
+						components: {
+							a: (
+								<a
+									href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
 									rel="noopener noreferrer"
+									target="_blank"
 								/>
 							),
 						},
 					} ) }
-				</p>
+				/>
 			</div>
-		);
-	}
+			<p className="use-your-domain-step__footer">
+				{ translate( "Not sure what works best for you? {{a}}We're happy to help!{{/a}}", {
+					components: {
+						a: (
+							<a href={ CALYPSO_HELP_WITH_HELP_CENTER } target="_blank" rel="noopener noreferrer" />
+						),
+					},
+				} ) }
+			</p>
+		</div>
+	);
 }
 
-const recordTransferButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_transfer_click', { domain_name } );
-
-const recordMappingButtonClickInUseYourDomain = ( domain_name ) =>
-	recordTracksEvent( 'calypso_use_your_domain_mapping_click', { domain_name } );
-
-export default connect(
-	( state ) => ( {
-		currentUser: getCurrentUser( state ),
-		currencyCode: getCurrentUserCurrencyCode( state ),
-		domainsWithPlansOnly: currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY ),
-		primaryWithPlansOnly: currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS ),
-		selectedSite: getSelectedSite( state ),
-		productsList: getProductsList( state ),
-	} ),
-	{
-		errorNotice,
-		recordTransferButtonClickInUseYourDomain,
-		recordMappingButtonClickInUseYourDomain,
-	}
-)( withCartKey( withShoppingCart( localize( UseYourDomainStep ) ) ) );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: - `localize` => `useTranslate()`
- `withShoppingCart` => `useShoppingCart()`
- `withCartKey` => `useCartKey()`
created_at: 2025-03-26T08:43:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
+		isNextDomainFree( cart ) ||
+		isDomainBundledWithPlan( cart, searchQuery )
+	) {
+		mappingPriceText = translate(
+			'Free with your plan, but registration costs at your current provider still apply'
+		);
+	} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
+		mappingPriceText = translate(
+			'Included in annual paid plans, but registration costs at your current provider still apply'
+		);
+	}
 
-		return mappingProductPrice;
-	};
+	const goToMapDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderIllustration = ( image ) => {
-		return (
-			<div className="use-your-domain-step__option-illustration">
-				<img src={ image } alt="" />
-			</div>
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_mapping_click', {
+				domain_name: analyticsSection,
+			} )
 		);
-	};
 
-	renderOptionTitle = ( optionTitle ) => {
-		return <h3 className="use-your-domain-step__option-title">{ optionTitle }</h3>;
-	};
+		let mapDomainStepURL;
 
-	renderOptionReasons = ( optionReasons ) => {
-		return (
-			<div className="use-your-domain-step__option-reasons">
-				{ optionReasons.map( ( phrase, index ) => {
-					if ( isEmpty( phrase ) ) {
-						return;
-					}
+		if ( mapDomainUrl ) {
+			mapDomainStepURL = mapDomainUrl;
+		} else {
+			const basePathForMapping = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			mapDomainStepURL = `${ basePathForMapping }/mapping`;
+			if ( selectedSite ) {
+				const query = stringify( { initialQuery: searchQuery.trim() } );
+				mapDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-					return (
-						<div className="use-your-domain-step__option-reason" key={ index }>
-							<Gridicon icon="checkmark" size={ 18 } />
-							{ phrase }
-						</div>
-					);
-				} ) }
-			</div>
-		);
+		page( mapDomainStepURL );
 	};
 
-	renderOptionContent = ( content ) => {
-		const { image, title, reasons, onClick, buttonText, isPrimary, learnMore } = content;
-		return (
-			<Card className="use-your-domain-step__option" compact>
-				<div className="use-your-domain-step__option-inner-wrap">
-					<div className="use-your-domain-step__option-content">
-						{ this.renderIllustration( image ) }
-						{ this.renderOptionTitle( title ) }
-						{ this.renderOptionReasons( reasons ) }
-					</div>
-					<div className="use-your-domain-step__option-action">
-						{ this.renderOptionButton( { onClick, buttonText, isPrimary } ) }
-						<div className="use-your-domain-step__learn-more">{ learnMore }</div>
-					</div>
-				</div>
-			</Card>
-		);
-	};
+	const goToTransferDomainStep: React.MouseEventHandler = ( event ) => {
+		event.preventDefault();
 
-	renderOptionButton = ( buttonOptions ) => {
-		const { buttonText, onClick, isPrimary } = buttonOptions;
-		const { submittingAvailability, submittingWhois } = this.state;
-		const submitting = submittingAvailability || submittingWhois;
-		return (
-			<Button
-				className="use-your-domain-step__option-button"
-				primary={ isPrimary }
-				onClick={ onClick }
-				busy={ submitting }
-			>
-				{ buttonText }
-			</Button>
+		dispatch(
+			recordTracksEvent( 'calypso_use_your_domain_transfer_click', {
+				domain_name: analyticsSection,
+			} )
 		);
-	};
-
-	renderSelectTransfer = () => {
-		const { translate } = this.props;
-
-		const image = migratingHostImage;
-		const title = translate( 'Transfer your domain away from your current registrar.' );
-		const reasons = [
-			translate(
-				'Domain registration and billing will be moved from your current provider to WordPress.com'
-			),
-			translate( 'Manage your domain and site from your WordPress.com dashboard' ),
-			translate( 'Extends registration by one year' ),
-			this.getTransferFreeText(),
-			this.getTransferSalePriceText(),
-			this.getTransferPriceText(),
-		];
-		const buttonText = translate( 'Transfer to WordPress.com' );
-		const learnMore = translate( '{{a}}Learn more about domain transfers{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( INCOMING_DOMAIN_TRANSFER ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToTransferDomainStep,
-			buttonText,
-			isPrimary: true,
-			learnMore,
-		} );
-	};
+		let transferDomainStepURL;
 
-	renderSelectMapping = () => {
-		const { translate } = this.props;
-		const image = themesImage;
-		const title = translate( 'Map your domain without moving it from your current registrar.' );
-		const reasons = [
-			translate( 'Domain registration and billing will remain at your current provider' ),
-			translate( 'Manage some domain settings at your current provider and some at WordPress.com' ),
-			// translate( 'Continue paying for your domain registration at your current provider' ),
-			translate( "Requires changes to the domain's DNS" ),
-			this.getMappingPriceText(),
-		];
-		const buttonText = translate( 'Map your domain' );
-		const learnMore = translate( '{{a}}Learn more about domain mapping{{/a}}', {
-			components: {
-				a: (
-					<a
-						href={ localizeUrl( MAP_EXISTING_DOMAIN ) }
-						rel="noopener noreferrer"
-						target="_blank"
-					/>
-				),
-			},
-		} );
+		if ( transferDomainUrl ) {
+			transferDomainStepURL = transferDomainUrl;
+		} else {
+			const basePathForTransfer = basePath.endsWith( '/use-your-domain' )
+				? basePath.substring( 0, basePath.length - 16 )
+				: basePath;
+
+			transferDomainStepURL = `${ basePathForTransfer }/transfer`;
+
+			if ( selectedSite ) {
+				const query = stringify( {
+					initialQuery: searchQuery.trim(),
+					useStandardBack: true,
+				} );
+				transferDomainStepURL += `/${ selectedSite.slug }?${ query }`;
+			}
+		}
 
-		return this.renderOptionContent( {
-			image,
-			title,
-			reasons,
-			onClick: this.goToMapDomainStep,
-			buttonText,
-			isPrimary: false,
-			learnMore,
-		} );
+		page( transferDomainStepURL );
 	};
 
-	render() {
-		const { isSignupStep, translate } = this.props;
-
-		const header = ! isSignupStep && (
-			<HeaderCake onClick={ this.goBack }>
-				{ this.props.translate( 'Use My Own Domain' ) }
-			</HeaderCake>
-		);
-
-		return (
-			<div className="use-your-domain-step">
-				{ header }
-				<QueryProducts />
-				<div className="use-your-domain-step__content">
-					{ this.renderSelectTransfer() }
-					{ this.renderSelectMapping() }
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Refactored these two render functions (and the underlying `renderOptionContent`) to an internal `UseYourDomainStepContent` component
created_at: 2025-03-26T08:45:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Inverted the boolean check since we can't early return in the render function.
created_at: 2025-03-26T08:49:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Extracted the common `domainProductPrice` check, and moved the rest of the checks to a nested `if...else`
created_at: 2025-03-26T08:50:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Quick fix since `selectedSite` can be `undefined` OR `null`, but `isDomainMappingFree` doesn't accept `null`
created_at: 2025-03-26T08:52:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,340 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: As the comment says, that `useManagerClient` call is there only to throw a better error if you fail to render `ShoppingCartProvider` as a parent component. It's either:
```
withShoppingCart must be used inside a ShoppingCartProvider
```
or
```
useShoppingCart must be used inside a ShoppingCartProvider
```
Note that the error message matches the API that you really used.

Now when you're using `useShoppingCart` directly, the extra `useManagerClient` would have no function.
created_at: 2025-03-26T11:21:27Z
author_association: MEMBER
diff_hunk: @@ -1,469 +0,0 @@
-import { isPlan } from '@automattic/calypso-products';
-import page from '@automattic/calypso-router';
-import { Card, Button, Gridicon } from '@automattic/components';
-import { localizeUrl } from '@automattic/i18n-utils';
-import { withShoppingCart } from '@automattic/shopping-cart';
-import {
-	CALYPSO_HELP_WITH_HELP_CENTER,
-	INCOMING_DOMAIN_TRANSFER,
-	MAP_EXISTING_DOMAIN,
-} from '@automattic/urls';
-import { formatCurrency, localize } from 'i18n-calypso';
-import { get, isEmpty } from 'lodash';
-import PropTypes from 'prop-types';
-import { stringify } from 'qs';
-import { Component } from 'react';
-import { connect } from 'react-redux';
-import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
-import themesImage from 'calypso/assets/images/illustrations/themes.svg';
-import QueryProducts from 'calypso/components/data/query-products-list';
-import HeaderCake from 'calypso/components/header-cake';
-import {
-	isDomainBundledWithPlan,
-	isDomainMappingFree,
-	isNextDomainFree,
-} from 'calypso/lib/cart-values/cart-items';
-import {
-	getDomainPrice,
-	getDomainProductSlug,
-	getDomainTransferSalePrice,
-} from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
-import { recordTracksEvent } from 'calypso/state/analytics/actions';
-import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
-import {
-	DOMAINS_WITH_PLANS_ONLY,
-	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
-} from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
-import { getProductsList } from 'calypso/state/products-list/selectors';
-import { getSelectedSite } from 'calypso/state/ui/selectors';
-
-import './style.scss';
-
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
path: client/components/domains/use-your-domain-step/index.jsx
user: login: jsnajdr
body: In the Git history I see big refactorings (#25573 is an example) where large chunks of code were moved around between `use-your-domain-step`, `register-domain-step`, `transfer-domain-step` etc. That explains how many props and state fields got left behind unused.
created_at: 2025-03-26T14:40:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: This TS error can be fixed by changing the `if ( price )` condition to `if ( price && currencyCode )`. The `currencyCode` Redux state is initialized when the products list is finished fetching (triggered by `QueryProducts`).
created_at: 2025-03-26T14:42:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: You can remove the `isEmpty` check (it's lodash, so it's unwanted anyway) and replace it with `domainProductSalePrice !== null`.
created_at: 2025-03-26T14:52:13Z
author_association: MEMBER
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: You can add the `| null` type to the `isDomainMappingFree`, it's a change that makes sense.
created_at: 2025-03-26T14:56:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: sirbrillig
body: > I wonder if we should change it so that it handles null or undefined internally

I think this might be hard to do. How do you format a price if you don't know the currency? Better to not render a price at all if you don't know how but what to render in its place? That's very context specific and probably should be in the hands of the caller.

> This TS error can be fixed by changing the if ( price ) condition to if ( price && currencyCode )

I think this is the right solution.
created_at: 2025-03-26T15:01:58Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,316 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: This `isEmpty` call be eliminated in favor of `phrase === null`. The value (member of `reason` array) is always either `null` or a result of `translate()`.
created_at: 2025-03-26T15:02:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,316 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: This is the only usage of `get` in this file and can be easily replaced with `?.`. Let's completely remove Lodash from here!
created_at: 2025-03-26T15:04:47Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Thank you both for the advice — I'll go ahead and implement the suggested solution, even if it introduces a runtime change.

> Better to not render a price at all if you don't know how but what to render in its place?

That's a good point, but one that I see ignored repeatedly across the codebase (judging by the amount of browser console warnings) 😞 

I guess that the fact that `formatCurrency` still picks a default currency also doesn't help.
created_at: 2025-03-27T16:58:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,316 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: https://github.com/Automattic/wp-calypso/pull/101854/commits/f52f737944224af3059dfe29f5ad3d8ff3c5dcdb
created_at: 2025-03-27T17:45:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,316 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: Let's! https://github.com/Automattic/wp-calypso/pull/101854/commits/f52f737944224af3059dfe29f5ad3d8ff3c5dcdb
created_at: 2025-03-27T17:45:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: https://github.com/Automattic/wp-calypso/pull/101854/commits/f52f737944224af3059dfe29f5ad3d8ff3c5dcdb
created_at: 2025-03-27T17:45:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: https://github.com/Automattic/wp-calypso/pull/101854/commits/eb60d1d141393f978699ccc03e3f9fb3a770e4ee
created_at: 2025-03-27T17:46:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,442 +26,323 @@ import {
 	getDomainProductSlug,
 	getDomainTransferSalePrice,
 } from 'calypso/lib/domains';
-import withCartKey from 'calypso/my-sites/checkout/with-cart-key';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
 import { recordTracksEvent } from 'calypso/state/analytics/actions';
 import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
 import {
 	DOMAINS_WITH_PLANS_ONLY,
 	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
 } from 'calypso/state/current-user/constants';
-import { currentUserHasFlag, getCurrentUser } from 'calypso/state/current-user/selectors';
-import { errorNotice } from 'calypso/state/notices/actions';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
 import { getProductsList } from 'calypso/state/products-list/selectors';
 import { getSelectedSite } from 'calypso/state/ui/selectors';
 
 import './style.scss';
 
-const noop = () => {};
-
-class UseYourDomainStep extends Component {
-	static propTypes = {
-		analyticsSection: PropTypes.string.isRequired,
-		basePath: PropTypes.string,
-		cart: PropTypes.object,
-		domainsWithPlansOnly: PropTypes.bool,
-		primaryWithPlansOnly: PropTypes.bool,
-		goBack: PropTypes.func,
-		initialQuery: PropTypes.string,
-		isSignupStep: PropTypes.bool,
-		mapDomainUrl: PropTypes.string,
-		transferDomainUrl: PropTypes.string,
-		onRegisterDomain: PropTypes.func,
-		onTransferDomain: PropTypes.func,
-		onSave: PropTypes.func,
-		selectedSite: PropTypes.oneOfType( [ PropTypes.object, PropTypes.bool ] ),
-		forcePrecheck: PropTypes.bool,
-	};
-
-	static defaultProps = {
-		analyticsSection: 'domains',
-		onSave: noop,
-	};
-
-	state = this.getDefaultState();
-
-	getDefaultState() {
-		return {
-			authCodeValid: null,
-			domain: null,
-			domainsWithPlansOnly: false,
-			inboundTransferStatus: {},
-			precheck: get( this.props, 'forcePrecheck', false ),
-			searchQuery: this.props.initialQuery || '',
-			submittingAuthCodeCheck: false,
-			submittingAvailability: false,
-			submittingWhois: get( this.props, 'forcePrecheck', false ),
-			supportsPrivacy: false,
-		};
-	}
-
-	getMapDomainUrl() {
-		const { basePath, mapDomainUrl, selectedSite } = this.props;
-		if ( mapDomainUrl ) {
-			return mapDomainUrl;
-		}
-
-		let buildMapDomainUrl;
-		const basePathForMapping = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildMapDomainUrl = `${ basePathForMapping }/mapping`;
-		if ( selectedSite ) {
-			const query = stringify( { initialQuery: this.state.searchQuery.trim() } );
-			buildMapDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	isSubmitting: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	isSubmitting,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+						busy={ isSubmitting }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
 
-		return buildMapDomainUrl;
+type UseYourDomainStepProps = {
+	analyticsSection?: string;
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+	isSignupStep?: boolean;
+	mapDomainUrl?: string;
+	transferDomainUrl?: string;
+	forcePrecheck?: boolean;
+};
+
+function UseYourDomainStep( {
+	analyticsSection = 'domains',
+	basePath,
+	goBack,
+	initialQuery,
+	isSignupStep,
+	mapDomainUrl,
+	transferDomainUrl,
+	forcePrecheck = false,
+}: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+	const [ submittingWhois ] = useState( forcePrecheck );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly &&
+		( ( selectedSite?.plan && ! isPlan( selectedSite.plan ) ) || isSignupStep );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
 	}
 
-	goToMapDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordMappingButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getMapDomainUrl() );
-	};
-
-	getTransferDomainUrl() {
-		const { basePath, transferDomainUrl, selectedSite } = this.props;
-
-		if ( transferDomainUrl ) {
-			return transferDomainUrl;
-		}
-
-		let buildTransferDomainUrl;
-		const basePathForTransfer = basePath?.endsWith( '/use-your-domain' )
-			? basePath.substring( 0, basePath.length - 16 )
-			: basePath;
-
-		buildTransferDomainUrl = `${ basePathForTransfer }/transfer`;
-
-		if ( selectedSite ) {
-			const query = stringify( {
-				initialQuery: this.state.searchQuery.trim(),
-				useStandardBack: true,
-			} );
-			buildTransferDomainUrl += `/${ selectedSite.slug }?${ query }`;
-		}
-
-		return buildTransferDomainUrl;
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
 	}
 
-	goToTransferDomainStep = ( event ) => {
-		event.preventDefault();
-
-		this.props.recordTransferButtonClickInUseYourDomain( this.props.analyticsSection );
-
-		page( this.getTransferDomainUrl() );
-	};
-
-	goBack = () => {
-		this.props.goBack();
-	};
-
-	getTransferFreeText = () => {
-		const { cart, translate, domainsWithPlansOnly, isSignupStep, selectedSite } = this.props;
-		const { searchQuery } = this.state;
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		let domainProductFreeText = null;
-
-		if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
-			domainProductFreeText = translate( 'Free with your plan' );
-		} else if ( domainsWithPlansOnlyButNoPlan ) {
-			domainProductFreeText = translate( 'Included in paid plans' );
-		}
-
-		return domainProductFreeText;
-	};
-
-	getTransferSalePriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-		const domainProductSalePrice = getDomainTransferSalePrice(
-			productSlug,
-			productsList,
-			currencyCode
-		);
-
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
 		if (
-			isEmpty( domainProductSalePrice ) ||
 			isNextDomainFree( cart ) ||
 			isDomainBundledWithPlan( cart, searchQuery ) ||
-			domainsWithPlansOnlyButNoPlan
-		) {
-			return;
-		}
-
-		return translate( 'Sale price is %(cost)s', { args: { cost: domainProductSalePrice } } );
-	};
-
-	getTransferPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			translate,
-			domainsWithPlansOnly,
-			isSignupStep,
-			productsList,
-			selectedSite,
-		} = this.props;
-		const { searchQuery } = this.state;
-		const productSlug = getDomainProductSlug( searchQuery );
-		const domainsWithPlansOnlyButNoPlan =
-			domainsWithPlansOnly && ( ( selectedSite && ! isPlan( selectedSite.plan ) ) || isSignupStep );
-
-		const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
-
-		if (
-			domainProductPrice &&
-			( isNextDomainFree( cart ) ||
-				isDomainBundledWithPlan( cart, searchQuery ) ||
-				domainsWithPlansOnlyButNoPlan ||
-				getDomainTransferSalePrice( productSlug, productsList, currencyCode ) )
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
 		) {
-			return translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
-		}
-
-		if ( domainProductPrice ) {
-			return translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
-		}
-	};
-
-	getMappingPriceText = () => {
-		const {
-			cart,
-			currencyCode,
-			domainsWithPlansOnly,
-			primaryWithPlansOnly,
-			productsList,
-			selectedSite,
-			translate,
-		} = this.props;
-		const { searchQuery } = this.state;
-
-		let mappingProductPrice;
-
-		const price = get( productsList, [ 'domain_map', 'cost' ], null );
-		if ( price ) {
-			mappingProductPrice = formatCurrency( price, currencyCode );
-			mappingProductPrice = translate(
-				'%(cost)s per year plus registration costs at your current provider',
-				{ args: { cost: mappingProductPrice } }
-			);
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
 		}
+	}
 
-		if (
-			isDomainMappingFree( selectedSite ) ||
-			isNextDomainFree( cart ) ||
-			isDomainBundledWithPlan( cart, searchQuery )
-		) {
-			mappingProductPrice = translate(
-				'Free with your plan, but registration costs at your current provider still apply'
-			);
-		} else if ( domainsWithPlansOnly || primaryWithPlansOnly ) {
-			mappingProductPrice = translate(
-				'Included in annual paid plans, but registration costs at your current provider still apply'
-			);
-		}
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
+		mappingPriceText = translate(
+			'%(cost)s per year plus registration costs at your current provider',
+			{ args: { cost: mappingPriceText } }
+		);
+	}
+	if (
+		isDomainMappingFree( selectedSite ?? undefined ) ||
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: https://github.com/Automattic/wp-calypso/pull/101854/commits/d637096357993e001d4c96b85c118040515ba736
created_at: 2025-03-27T17:46:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,313 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( ! phrase ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		domainProductSalePrice !== null &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = productsList?.domain_map?.cost;
+	if ( price && currencyCode ) {
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: nit: the `formatCurrency` result should be assigned to its own variable. Like `cost`.
created_at: 2025-03-28T11:01:51Z
author_association: MEMBER
diff_hunk: @@ -821,11 +822,11 @@ export function hasToUpgradeToPayForADomain(
 	return false;
 }
 
-export function isDomainMappingFree(
-	selectedSite: undefined | { plan: { product_slug: string } }
-): boolean {
+export function isDomainMappingFree( selectedSite: undefined | SiteDetails | null ): boolean {
path: client/lib/cart-values/cart-items.ts
user: login: jsnajdr
body: nit: the sorting of the `|` subtypes looks very random. Usually we put the "real" type first:
```ts
SiteDetails | null | undefined
```
This file seems to use some sort of Yoda convention, not sure why.
created_at: 2025-03-28T11:06:19Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,311 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { get, isEmpty } from 'lodash';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>;
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( isEmpty( phrase ) ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		! isEmpty( domainProductSalePrice ) &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice! },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = get( productsList, [ 'domain_map', 'cost' ], null );
+	if ( price ) {
+		// @ts-expect-error despite the TS error, formatCurrency works with a
+		// `null` currencyCode and uses a fallback currency.
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: jsnajdr
body: One thing that could improve the `formatCurrency` situation is fixing the `getDomainPrice` function: it should return `null` if the `currencyCode` is not valid.

There are just a handful of usages, and the function already returns `null` when there is no valid "price" value, so hopefully this would be an easy refactoring.
created_at: 2025-03-28T11:08:51Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,313 @@
+import { isPlan } from '@automattic/calypso-products';
+import page from '@automattic/calypso-router';
+import { Card, Button, Gridicon } from '@automattic/components';
+import { localizeUrl } from '@automattic/i18n-utils';
+import { useShoppingCart } from '@automattic/shopping-cart';
+import {
+	CALYPSO_HELP_WITH_HELP_CENTER,
+	INCOMING_DOMAIN_TRANSFER,
+	MAP_EXISTING_DOMAIN,
+} from '@automattic/urls';
+import { formatCurrency, useTranslate } from 'i18n-calypso';
+import { stringify } from 'qs';
+import React, { useState } from 'react';
+import migratingHostImage from 'calypso/assets/images/illustrations/migrating-host-diy.svg';
+import themesImage from 'calypso/assets/images/illustrations/themes.svg';
+import QueryProducts from 'calypso/components/data/query-products-list';
+import HeaderCake from 'calypso/components/header-cake';
+import {
+	isDomainBundledWithPlan,
+	isDomainMappingFree,
+	isNextDomainFree,
+} from 'calypso/lib/cart-values/cart-items';
+import {
+	getDomainPrice,
+	getDomainProductSlug,
+	getDomainTransferSalePrice,
+} from 'calypso/lib/domains';
+import useCartKey from 'calypso/my-sites/checkout/use-cart-key';
+import { useSelector, useDispatch } from 'calypso/state';
+import { recordTracksEvent } from 'calypso/state/analytics/actions';
+import { getCurrentUserCurrencyCode } from 'calypso/state/currency-code/selectors';
+import {
+	DOMAINS_WITH_PLANS_ONLY,
+	NON_PRIMARY_DOMAINS_TO_FREE_USERS,
+} from 'calypso/state/current-user/constants';
+import { currentUserHasFlag } from 'calypso/state/current-user/selectors';
+import { getProductsList } from 'calypso/state/products-list/selectors';
+import { getSelectedSite } from 'calypso/state/ui/selectors';
+
+import './style.scss';
+
+const ANALYTICS_SECTION = 'domains';
+
+const PAGE_URL = '/use-your-domain';
+function extractBasePath( basePath: string ) {
+	return basePath.endsWith( PAGE_URL )
+		? basePath.substring( 0, basePath.length - PAGE_URL.length )
+		: basePath;
+}
+
+type OptionContentProps = {
+	image: string;
+	title: string;
+	reasons: React.ReactNode[];
+	onClick: React.MouseEventHandler;
+	buttonText: string;
+	isPrimary: boolean;
+	learnMore: React.ReactNode;
+};
+
+function UseYourDomainStepContent( {
+	image,
+	title,
+	reasons,
+	onClick,
+	buttonText,
+	isPrimary,
+	learnMore,
+}: OptionContentProps ) {
+	return (
+		<Card className="use-your-domain-step__option" compact>
+			<div className="use-your-domain-step__option-inner-wrap">
+				<div className="use-your-domain-step__option-content">
+					<div className="use-your-domain-step__option-illustration">
+						<img src={ image } alt="" />
+					</div>
+					<h3 className="use-your-domain-step__option-title">{ title }</h3>
+					<div className="use-your-domain-step__option-reasons">
+						{ reasons.map( ( phrase, index ) => {
+							if ( ! phrase ) {
+								return;
+							}
+
+							return (
+								<div className="use-your-domain-step__option-reason" key={ index }>
+									<Gridicon icon="checkmark" size={ 18 } />
+									{ phrase }
+								</div>
+							);
+						} ) }
+					</div>
+				</div>
+				<div className="use-your-domain-step__option-action">
+					<Button
+						className="use-your-domain-step__option-button"
+						primary={ isPrimary }
+						onClick={ onClick }
+					>
+						{ buttonText }
+					</Button>
+					<div className="use-your-domain-step__learn-more">{ learnMore }</div>
+				</div>
+			</div>
+		</Card>
+	);
+}
+
+type UseYourDomainStepProps = {
+	basePath: string;
+	goBack?: () => void;
+	initialQuery?: string;
+};
+
+function UseYourDomainStep( { basePath, goBack, initialQuery }: UseYourDomainStepProps ) {
+	const cartKey = useCartKey();
+	const { responseCart: cart } = useShoppingCart( cartKey );
+
+	const translate = useTranslate();
+	const dispatch = useDispatch();
+
+	const [ searchQuery ] = useState( initialQuery || '' );
+
+	const selectedSite = useSelector( getSelectedSite );
+	const productsList = useSelector( getProductsList );
+	const currencyCode = useSelector( getCurrentUserCurrencyCode );
+	const domainsWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, DOMAINS_WITH_PLANS_ONLY )
+	);
+	const primaryWithPlansOnly = useSelector( ( state ) =>
+		currentUserHasFlag( state, NON_PRIMARY_DOMAINS_TO_FREE_USERS )
+	);
+
+	const domainsWithPlansOnlyButNoPlan =
+		domainsWithPlansOnly && selectedSite?.plan && ! isPlan( selectedSite.plan );
+	const productSlug = getDomainProductSlug( searchQuery );
+	const domainProductSalePrice = getDomainTransferSalePrice(
+		productSlug,
+		productsList,
+		currencyCode
+	);
+
+	let domainProductFreeText = null;
+	if ( isNextDomainFree( cart ) || isDomainBundledWithPlan( cart, searchQuery ) ) {
+		domainProductFreeText = translate( 'Free with your plan' );
+	} else if ( domainsWithPlansOnlyButNoPlan ) {
+		domainProductFreeText = translate( 'Included in paid plans' );
+	}
+
+	let transferSalePriceText = null;
+	if (
+		domainProductSalePrice !== null &&
+		! isNextDomainFree( cart ) &&
+		! isDomainBundledWithPlan( cart, searchQuery ) &&
+		domainsWithPlansOnlyButNoPlan
+	) {
+		transferSalePriceText = translate( 'Sale price is %(cost)s', {
+			args: { cost: domainProductSalePrice },
+		} );
+	}
+
+	let transferPriceText = null;
+	const domainProductPrice = getDomainPrice( productSlug, productsList, currencyCode );
+	if ( domainProductPrice ) {
+		if (
+			isNextDomainFree( cart ) ||
+			isDomainBundledWithPlan( cart, searchQuery ) ||
+			domainsWithPlansOnlyButNoPlan ||
+			getDomainTransferSalePrice( productSlug, productsList, currencyCode )
+		) {
+			transferPriceText = translate( 'Renews at %(cost)s', { args: { cost: domainProductPrice } } );
+		} else {
+			transferPriceText = translate( '%(cost)s per year', { args: { cost: domainProductPrice } } );
+		}
+	}
+
+	let mappingPriceText;
+	const price = productsList?.domain_map?.cost;
+	if ( price && currencyCode ) {
+		mappingPriceText = formatCurrency( price, currencyCode );
path: client/components/domains/use-your-domain-step/index.tsx
user: login: ciampo
body: https://github.com/Automattic/wp-calypso/pull/101854/commits/af0065c24222dbbbe75663c76130ff513cf9e238

Totally agree, it's always hard to draw a line when doing these refactors, as there's much more that I'd like to change 😅 
created_at: 2025-03-28T11:48:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -821,11 +822,11 @@ export function hasToUpgradeToPayForADomain(
 	return false;
 }
 
-export function isDomainMappingFree(
-	selectedSite: undefined | { plan: { product_slug: string } }
-): boolean {
+export function isDomainMappingFree( selectedSite: undefined | SiteDetails | null ): boolean {
path: client/lib/cart-values/cart-items.ts
user: login: ciampo
body: Yup https://github.com/Automattic/wp-calypso/pull/101854/commits/8380a7b24da99701e24f29279d5d0cb00d9de28b

created_at: 2025-03-28T11:48:54Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-25T17:53:05Z
body: <details>
<summary>Calypso Live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-138854">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-138854%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-138854">https://calypso.live?image=registry.a8c.com/calypso/app:build-138854</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Jetpack Cloud live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-138854&env=jetpack">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-138854%26env%3Djetpack%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-138854&env=jetpack">https://calypso.live?image=registry.a8c.com/calypso/app:build-138854&env=jetpack</a>
</td>
</tr>
</table>
</details>
<details>
<summary>Automattic for Agencies live <a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-138854&env=a8c-for-agencies">(direct link)</a></summary>
<table>
<tr>
<td>
<img src="https://chart.googleapis.com/chart?chs=150x150&cht=qr&chl=https%3A%2F%2Fcalypso.live%3Fimage%3Dregistry.a8c.com%2Fcalypso%2Fapp%3Abuild-138854%26env%3Da8c-for-agencies%26flags%3Doauth&choe=UTF-8" />
</td>
<td>
<a href="https://calypso.live?image=registry.a8c.com/calypso/app:build-138854&env=a8c-for-agencies">https://calypso.live?image=registry.a8c.com/calypso/app:build-138854&env=a8c-for-agencies</a>
</td>
</tr>
</table>
</details>
<!--calypso-live-watermark:apr@v1-->
author_association: NONE
user: login: matticbot
created_at: 2025-03-25T18:03:04Z
body: <!-- icfy-watermark: c52822 -->
Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

**Sections** (~205 bytes removed 📉 [gzipped])
<details>

```
name     parsed_size           gzip_size
domains      -2787 B  (-0.1%)     -205 B  (-0.0%)
```

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.
</details>

**Legend**
<details>
<summary>What is parsed and gzip size?</summary>

**Parsed Size:** Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
**Gzip Size:** Compressed size of the JS and CSS files. This much data needs to be downloaded over network.
</details>

Generated by performance advisor bot at [iscalypsofastyet.com](http://iscalypsofastyet.com).
author_association: CONTRIBUTOR
user: login: matticbot
created_at: 2025-03-25T18:10:03Z
body: **This PR modifies the release build for the following Calypso Apps:**

_For info about this notification, see here: PCYsg-OT6-p2_

* help-center
* notifications
* wpcom-block-editor

To test WordPress.com changes, run `install-plugin.sh $pluginSlug refactor/use-your-domain-class-to-functional` on your sandbox.
<!--calypso-app-artifacts-watermark:apr@v1-->
author_association: CONTRIBUTOR