From 507643d45184d6f47dcc541579435b3a39ece673 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 29 Aug 2025 15:08:34 -0400 Subject: [PATCH 01/11] feat(checkout v3): Full screen --- .../organizationSubscriptionContext.tsx | 19 ++++++++++++++++ static/gsApp/hooks/rootRoutes.tsx | 22 ++++++++++++++++++- static/gsApp/hooks/settingsRoutes.tsx | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 static/gsApp/components/organizationSubscriptionContext.tsx diff --git a/static/gsApp/components/organizationSubscriptionContext.tsx b/static/gsApp/components/organizationSubscriptionContext.tsx new file mode 100644 index 00000000000000..69776e808ded0d --- /dev/null +++ b/static/gsApp/components/organizationSubscriptionContext.tsx @@ -0,0 +1,19 @@ +import type React from 'react'; + +import OrganizationContainer from 'sentry/views/organizationContainer'; + +import SubscriptionContext from 'getsentry/components/subscriptionContext'; + +type Props = { + children: React.JSX.Element; +}; + +function OrganizationSubscriptionContext({children}: Props) { + return ( + + {children} + + ); +} + +export default OrganizationSubscriptionContext; diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx index ecae9c4800904b..e91ccf994ff296 100644 --- a/static/gsApp/hooks/rootRoutes.tsx +++ b/static/gsApp/hooks/rootRoutes.tsx @@ -1,5 +1,25 @@ import type {SentryRouteObject} from 'sentry/components/route'; +import {makeLazyloadComponent as make} from 'sentry/makeLazyloadComponent'; +import errorHandler from 'sentry/utils/errorHandler'; -const rootRoutes = (): SentryRouteObject => ({}); +import OrganizationSubscriptionContext from 'getsentry/components/organizationSubscriptionContext'; + +const rootRoutes = (): SentryRouteObject => ({ + children: [ + { + path: '/checkout/', + component: errorHandler(OrganizationSubscriptionContext), + deprecatedRouteProps: true, + customerDomainOnlyRoute: true, + children: [ + { + index: true, + component: make(() => import('../views/decideCheckout')), + deprecatedRouteProps: true, + }, + ], + }, + ], +}); export default rootRoutes; diff --git a/static/gsApp/hooks/settingsRoutes.tsx b/static/gsApp/hooks/settingsRoutes.tsx index 69228fc3946ade..9b7723b9e468a8 100644 --- a/static/gsApp/hooks/settingsRoutes.tsx +++ b/static/gsApp/hooks/settingsRoutes.tsx @@ -24,7 +24,7 @@ const settingsRoutes = (): SentryRouteObject => ({ }, { path: 'checkout/', - name: 'Change', + name: 'Change', // TODO(checkout v3): Should this be renamed? component: errorHandler(SubscriptionContext), deprecatedRouteProps: true, children: [ From 1d331ba4f27b1901cee3545acba121e9cb5642d9 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 29 Aug 2025 15:55:29 -0400 Subject: [PATCH 02/11] route redirects + styling --- static/gsApp/hooks/rootRoutes.tsx | 2 +- static/gsApp/hooks/settingsRoutes.tsx | 3 +- static/gsApp/views/amCheckout/index.tsx | 75 ++++++++++++++++++++++++- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx index e91ccf994ff296..638760de42dfc8 100644 --- a/static/gsApp/hooks/rootRoutes.tsx +++ b/static/gsApp/hooks/rootRoutes.tsx @@ -7,7 +7,7 @@ import OrganizationSubscriptionContext from 'getsentry/components/organizationSu const rootRoutes = (): SentryRouteObject => ({ children: [ { - path: '/checkout/', + path: '/checkout-v3/', // TODO(checkout v3): change this to the correct path (/settings/billing/checkout/) component: errorHandler(OrganizationSubscriptionContext), deprecatedRouteProps: true, customerDomainOnlyRoute: true, diff --git a/static/gsApp/hooks/settingsRoutes.tsx b/static/gsApp/hooks/settingsRoutes.tsx index 9b7723b9e468a8..38726a4c006cb6 100644 --- a/static/gsApp/hooks/settingsRoutes.tsx +++ b/static/gsApp/hooks/settingsRoutes.tsx @@ -23,8 +23,9 @@ const settingsRoutes = (): SentryRouteObject => ({ redirectTo: 'overview/', }, { + // TODO(checkout v3): This should be removed path: 'checkout/', - name: 'Change', // TODO(checkout v3): Should this be renamed? + name: 'Change', component: errorHandler(SubscriptionContext), deprecatedRouteProps: true, children: [ diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 84550a6314c087..34087c1f467118 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -8,17 +8,21 @@ import moment from 'moment-timezone'; import type {Client} from 'sentry/api'; import {Alert} from 'sentry/components/core/alert'; +import {Button} from 'sentry/components/core/button'; import {LinkButton} from 'sentry/components/core/button/linkButton'; import {ExternalLink} from 'sentry/components/core/link'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; +import LogoSentry from 'sentry/components/logoSentry'; import Panel from 'sentry/components/panels/panel'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import TextOverflow from 'sentry/components/textOverflow'; +import {IconArrow} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import type {DataCategory} from 'sentry/types/core'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import type {Organization} from 'sentry/types/organization'; +import {browserHistory} from 'sentry/utils/browserHistory'; import type {QueryClient} from 'sentry/utils/queryClient'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import withApi from 'sentry/utils/withApi'; @@ -120,6 +124,12 @@ class AMCheckout extends Component { ) { props.onToggleLegacy(props.subscription.planTier); } + // TODO(checkout v3): remove this once all customers are on the new checkout flow + if (props.location?.pathname.includes('checkout-v3') && !props.isNewCheckout) { + browserHistory.push(`/settings/${props.organization.slug}/billing/checkout/`); + } else if (!props.location?.pathname.includes('checkout-v3') && props.isNewCheckout) { + browserHistory.push(`/checkout-v3/`); + } let step = 1; if (props.location?.hash) { const stepMatch = /^#step(\d)$/.exec(props.location.hash); @@ -769,12 +779,21 @@ class AMCheckout extends Component { const isOnSponsoredPartnerPlan = (subscription.partner?.isActive && subscription.isSponsored) || false; + const ParentComponent = isNewCheckout ? FullScreenContainer : Fragment; + return ( - + + {isNewCheckout && ( + + + + + + )} {isOnSponsoredPartnerPlan && ( @@ -798,9 +817,20 @@ class AMCheckout extends Component { data-test-id="change-subscription" /> )} - + {isNewCheckout && ( + { + browserHistory.push(`/settings/${organization.slug}/billing/`); + }} + > + + {t('Back')} + + )} {this.renderPartnerAlert()}
{this.renderSteps()}
@@ -856,11 +886,43 @@ class AMCheckout extends Component { )}
-
+ ); } } +const FullScreenHeader = styled('header')` + background: ${p => p.theme.background}; + width: 100%; + border-bottom: 1px solid ${p => p.theme.border}; + display: flex; + justify-content: center; +`; + +const HeaderContent = styled('div')` + max-width: 1360px; + width: 100%; + display: flex; + justify-content: flex-start; + padding: ${p => p.theme.space['2xl']}; +`; + +const FullScreenContainer = styled('div')` + display: flex; + flex-direction: column; + align-items: center; + background: ${p => p.theme.background}; +`; + +const BackButton = styled(Button)` + align-self: flex-start; + padding: 0; + + & span { + margin-left: ${p => p.theme.space.sm}; + } +`; + const CheckoutContainer = styled('div')<{isNewCheckout: boolean}>` display: grid; gap: ${p => p.theme.space['2xl']}; @@ -870,6 +932,13 @@ const CheckoutContainer = styled('div')<{isNewCheckout: boolean}>` p.isNewCheckout ? p.theme.breakpoints.md : p.theme.breakpoints.lg}) { grid-template-columns: auto; } + + ${p => + p.isNewCheckout && + css` + max-width: 1360px; + padding: ${p.theme.space['2xl']}; + `} `; const SidePanel = styled('div')` From 22d4d307092d5979c403a37cde6f315ace830e48 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 29 Aug 2025 16:07:53 -0400 Subject: [PATCH 03/11] clean up --- static/gsApp/hooks/rootRoutes.tsx | 4 +++- static/gsApp/hooks/settingsRoutes.tsx | 2 +- static/gsApp/views/amCheckout/index.tsx | 21 ++++++++++----------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx index 638760de42dfc8..af407d2e36ab17 100644 --- a/static/gsApp/hooks/rootRoutes.tsx +++ b/static/gsApp/hooks/rootRoutes.tsx @@ -7,7 +7,9 @@ import OrganizationSubscriptionContext from 'getsentry/components/organizationSu const rootRoutes = (): SentryRouteObject => ({ children: [ { - path: '/checkout-v3/', // TODO(checkout v3): change this to the correct path (/settings/billing/checkout/) + // TODO(checkout v3): change this to the correct path (/settings/billing/checkout/) + // when GA'd + path: '/checkout-v3/', component: errorHandler(OrganizationSubscriptionContext), deprecatedRouteProps: true, customerDomainOnlyRoute: true, diff --git a/static/gsApp/hooks/settingsRoutes.tsx b/static/gsApp/hooks/settingsRoutes.tsx index 38726a4c006cb6..ce63fcec0eddeb 100644 --- a/static/gsApp/hooks/settingsRoutes.tsx +++ b/static/gsApp/hooks/settingsRoutes.tsx @@ -23,7 +23,7 @@ const settingsRoutes = (): SentryRouteObject => ({ redirectTo: 'overview/', }, { - // TODO(checkout v3): This should be removed + // TODO(checkout v3): This should be removed when checkout v3 is GA'd path: 'checkout/', name: 'Change', component: errorHandler(SubscriptionContext), diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 34087c1f467118..11722719463848 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -124,7 +124,7 @@ class AMCheckout extends Component { ) { props.onToggleLegacy(props.subscription.planTier); } - // TODO(checkout v3): remove this once all customers are on the new checkout flow + // TODO(checkout v3): remove these checks once checkout v3 is GA'd if (props.location?.pathname.includes('checkout-v3') && !props.isNewCheckout) { browserHistory.push(`/settings/${props.organization.slug}/billing/checkout/`); } else if (!props.location?.pathname.includes('checkout-v3') && props.isNewCheckout) { @@ -891,8 +891,14 @@ class AMCheckout extends Component { } } -const FullScreenHeader = styled('header')` +const FullScreenContainer = styled('div')` + display: flex; + flex-direction: column; + align-items: center; background: ${p => p.theme.background}; +`; + +const FullScreenHeader = styled('header')` width: 100%; border-bottom: 1px solid ${p => p.theme.border}; display: flex; @@ -900,18 +906,11 @@ const FullScreenHeader = styled('header')` `; const HeaderContent = styled('div')` - max-width: 1360px; width: 100%; display: flex; justify-content: flex-start; padding: ${p => p.theme.space['2xl']}; -`; - -const FullScreenContainer = styled('div')` - display: flex; - flex-direction: column; - align-items: center; - background: ${p => p.theme.background}; + max-width: ${p => p.theme.breakpoints.xl}; `; const BackButton = styled(Button)` @@ -936,7 +935,7 @@ const CheckoutContainer = styled('div')<{isNewCheckout: boolean}>` ${p => p.isNewCheckout && css` - max-width: 1360px; + max-width: ${p.theme.breakpoints.xl}; padding: ${p.theme.space['2xl']}; `} `; From 8cb37704c527cc5b610036c381523eb2098408a0 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 11:46:02 -0400 Subject: [PATCH 04/11] pr feedback --- static/gsApp/hooks/rootRoutes.tsx | 2 +- static/gsApp/views/amCheckout/index.tsx | 65 ++++++++----------------- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx index af407d2e36ab17..2e1db78b915311 100644 --- a/static/gsApp/hooks/rootRoutes.tsx +++ b/static/gsApp/hooks/rootRoutes.tsx @@ -16,7 +16,7 @@ const rootRoutes = (): SentryRouteObject => ({ children: [ { index: true, - component: make(() => import('../views/decideCheckout')), + component: make(() => import('getsentry/views/decideCheckout')), deprecatedRouteProps: true, }, ], diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 4a916197c35934..8181a69d10245b 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -11,6 +11,7 @@ import type {Client} from 'sentry/api'; import {Alert} from 'sentry/components/core/alert'; import {Button} from 'sentry/components/core/button'; import {LinkButton} from 'sentry/components/core/button/linkButton'; +import {Flex, Grid} from 'sentry/components/core/layout'; import {ExternalLink} from 'sentry/components/core/link'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; @@ -790,11 +791,11 @@ class AMCheckout extends Component { orgSlug={organization.slug} /> {isNewCheckout && ( - - + + - - + + )} {isOnSponsoredPartnerPlan && ( @@ -819,7 +820,15 @@ class AMCheckout extends Component { data-test-id="change-subscription" /> )} - +
{isNewCheckout && ( { browserHistory.push(`/settings/${organization.slug}/billing/`); }} > - - {t('Back')} + + + {t('Back')} + )} {this.renderPartnerAlert()} @@ -892,12 +903,13 @@ class AMCheckout extends Component { )} - + ); } } +// TODO(checkout v3): remove this and use Flex when checkout v3 is GA'd const FullScreenContainer = styled('div')` display: flex; flex-direction: column; @@ -905,46 +917,9 @@ const FullScreenContainer = styled('div')` background: ${p => p.theme.background}; `; -const FullScreenHeader = styled('header')` - width: 100%; - border-bottom: 1px solid ${p => p.theme.border}; - display: flex; - justify-content: center; -`; - -const HeaderContent = styled('div')` - width: 100%; - display: flex; - justify-content: flex-start; - padding: ${p => p.theme.space['2xl']}; - max-width: ${p => p.theme.breakpoints.xl}; -`; - const BackButton = styled(Button)` align-self: flex-start; padding: 0; - - & span { - margin-left: ${p => p.theme.space.sm}; - } -`; - -const CheckoutContainer = styled('div')<{isNewCheckout: boolean}>` - display: grid; - gap: ${p => p.theme.space['2xl']}; - grid-template-columns: 3fr 2fr; - - @media (max-width: ${p => - p.isNewCheckout ? p.theme.breakpoints.md : p.theme.breakpoints.lg}) { - grid-template-columns: auto; - } - - ${p => - p.isNewCheckout && - css` - max-width: ${p.theme.breakpoints.xl}; - padding: ${p.theme.space['2xl']}; - `} `; const SidePanel = styled('div')` From da01c2c8343f3b31196470528c299c882935e505 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 12:25:14 -0400 Subject: [PATCH 05/11] fix padding + breakpoints --- static/gsApp/views/amCheckout/index.tsx | 33 ++++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 8181a69d10245b..51fe1f64e00200 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -11,7 +11,7 @@ import type {Client} from 'sentry/api'; import {Alert} from 'sentry/components/core/alert'; import {Button} from 'sentry/components/core/button'; import {LinkButton} from 'sentry/components/core/button/linkButton'; -import {Flex, Grid} from 'sentry/components/core/layout'; +import {Container, Flex, Grid} from 'sentry/components/core/layout'; import {ExternalLink} from 'sentry/components/core/link'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; @@ -823,26 +823,29 @@ class AMCheckout extends Component {
{isNewCheckout && ( - { - browserHistory.push(`/settings/${organization.slug}/billing/`); - }} - > - - - {t('Back')} - - + + { + browserHistory.push(`/settings/${organization.slug}/billing/`); + }} + > + + + {t('Back')} + + + )} {this.renderPartnerAlert()} Date: Fri, 5 Sep 2025 13:12:05 -0400 Subject: [PATCH 06/11] use hooks --- static/gsApp/views/amCheckout/index.tsx | 16 +++++++++++----- static/gsApp/views/decideCheckout.tsx | 12 +++++++----- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 51fe1f64e00200..c67d45bc95fa2b 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; import {loadStripe} from '@stripe/stripe-js'; import type {QueryObserverResult} from '@tanstack/react-query'; +import type {Location} from 'history'; import isEqual from 'lodash/isEqual'; import moment from 'moment-timezone'; @@ -23,11 +24,11 @@ import {IconArrow} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import ConfigStore from 'sentry/stores/configStore'; import type {DataCategory} from 'sentry/types/core'; -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import type {Organization} from 'sentry/types/organization'; import {browserHistory} from 'sentry/utils/browserHistory'; import type {QueryClient} from 'sentry/utils/queryClient'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; +import type {ReactRouter3Navigate} from 'sentry/utils/useNavigate'; import withApi from 'sentry/utils/withApi'; import withOrganization from 'sentry/utils/withOrganization'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; @@ -98,6 +99,8 @@ type Props = { checkoutTier: PlanTier; isError: boolean; isLoading: boolean; + location: Location; + navigate: ReactRouter3Navigate; onToggleLegacy: (tier: string) => void; organization: Organization; queryClient: QueryClient; @@ -105,7 +108,7 @@ type Props = { isNewCheckout?: boolean; promotionData?: PromotionData; refetch?: () => Promise>; -} & RouteComponentProps, unknown>; +}; type State = { billingConfig: BillingConfig | null; @@ -216,7 +219,10 @@ class AMCheckout extends Component { get referrer(): string | undefined { const {location} = this.props; - return location?.query?.referrer; + if (Array.isArray(location?.query?.referrer)) { + return location?.query?.referrer[0]; + } + return location?.query?.referrer ?? undefined; } /** @@ -224,8 +230,8 @@ class AMCheckout extends Component { * changes to their plan and cannot use the self-serve checkout flow */ handleRedirect() { - const {organization, router} = this.props; - return router.push(normalizeUrl(`/settings/${organization.slug}/billing/overview/`)); + const {organization, navigate} = this.props; + return navigate(normalizeUrl(`/settings/${organization.slug}/billing/overview/`)); } async fetchBillingConfig() { diff --git a/static/gsApp/views/decideCheckout.tsx b/static/gsApp/views/decideCheckout.tsx index 7060bab565503e..00446b814dd12a 100644 --- a/static/gsApp/views/decideCheckout.tsx +++ b/static/gsApp/views/decideCheckout.tsx @@ -1,7 +1,8 @@ import {useState} from 'react'; import ErrorBoundary from 'sentry/components/errorBoundary'; -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; +import {useLocation} from 'sentry/utils/useLocation'; +import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; import {PlanTier} from 'getsentry/types'; @@ -9,17 +10,18 @@ import {hasPartnerMigrationFeature} from 'getsentry/utils/billing'; import AMCheckout from 'getsentry/views/amCheckout'; import {hasCheckoutV3} from 'getsentry/views/amCheckout/utils'; -interface Props extends RouteComponentProps, unknown> {} - -function DecideCheckout(props: Props) { +function DecideCheckout() { + const navigate = useNavigate(); + const location = useLocation(); const organization = useOrganization(); const [tier, setTier] = useState(null); const checkoutProps = { - ...props, organization, onToggleLegacy: setTier, isNewCheckout: hasCheckoutV3(organization), + location, + navigate, }; const hasAm3Feature = organization.features?.includes('am3-billing'); From 8de6ca35d83c99c0ca96ce8fffd3233cae61f157 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 13:13:36 -0400 Subject: [PATCH 07/11] this too --- static/gsApp/hooks/rootRoutes.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx index 2e1db78b915311..d51d1db1175ff2 100644 --- a/static/gsApp/hooks/rootRoutes.tsx +++ b/static/gsApp/hooks/rootRoutes.tsx @@ -17,7 +17,6 @@ const rootRoutes = (): SentryRouteObject => ({ { index: true, component: make(() => import('getsentry/views/decideCheckout')), - deprecatedRouteProps: true, }, ], }, From 003b8edfa6922f086df538dd093cd4de66081966 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 13:15:05 -0400 Subject: [PATCH 08/11] use navigate instead of browserHistory --- static/gsApp/views/amCheckout/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index c67d45bc95fa2b..16faf7d51d9e1c 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -25,7 +25,6 @@ import {t, tct} from 'sentry/locale'; import ConfigStore from 'sentry/stores/configStore'; import type {DataCategory} from 'sentry/types/core'; import type {Organization} from 'sentry/types/organization'; -import {browserHistory} from 'sentry/utils/browserHistory'; import type {QueryClient} from 'sentry/utils/queryClient'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; import type {ReactRouter3Navigate} from 'sentry/utils/useNavigate'; @@ -132,9 +131,9 @@ class AMCheckout extends Component { } // TODO(checkout v3): remove these checks once checkout v3 is GA'd if (props.location?.pathname.includes('checkout-v3') && !props.isNewCheckout) { - browserHistory.push(`/settings/${props.organization.slug}/billing/checkout/`); + props.navigate(`/settings/${props.organization.slug}/billing/checkout/`); } else if (!props.location?.pathname.includes('checkout-v3') && props.isNewCheckout) { - browserHistory.push(`/checkout-v3/`); + props.navigate(`/checkout-v3/`); } let step = 1; if (props.location?.hash) { @@ -744,6 +743,7 @@ class AMCheckout extends Component { promotionData, checkoutTier, isNewCheckout, + navigate, } = this.props; const {loading, error, formData, billingConfig} = this.state; @@ -843,7 +843,7 @@ class AMCheckout extends Component { borderless aria-label={t('Back to Subscription Overview')} onClick={() => { - browserHistory.push(`/settings/${organization.slug}/billing/`); + navigate(`/settings/${organization.slug}/billing/`); }} > From 0f9a09bbd2a25528a8560db34022b34957f41fe2 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 13:37:11 -0400 Subject: [PATCH 09/11] fix tests --- static/gsApp/views/amCheckout/cart.spec.tsx | 1 + .../amCheckout/checkoutOverview.spec.tsx | 5 +- .../amCheckout/checkoutOverviewV2.spec.tsx | 3 +- static/gsApp/views/amCheckout/index.spec.tsx | 83 +++++++++---------- .../steps/addBillingDetails.spec.tsx | 3 +- .../amCheckout/steps/addDataVolume.spec.tsx | 14 ++-- .../steps/addPaymentMethod.spec.tsx | 3 +- .../steps/checkoutV3/buildYourPlan.spec.tsx | 1 + .../amCheckout/steps/contractSelect.spec.tsx | 3 +- .../amCheckout/steps/onDemandBudgets.spec.tsx | 3 +- .../amCheckout/steps/onDemandSpend.spec.tsx | 9 +- .../amCheckout/steps/planSelect.spec.tsx | 39 +++++---- .../amCheckout/steps/productSelect.spec.tsx | 17 ++-- .../steps/reviewAndConfirm.spec.tsx | 3 +- .../amCheckout/steps/setPayAsYouGo.spec.tsx | 17 ++-- .../amCheckout/steps/setSpendCap.spec.tsx | 2 + 16 files changed, 97 insertions(+), 109 deletions(-) diff --git a/static/gsApp/views/amCheckout/cart.spec.tsx b/static/gsApp/views/amCheckout/cart.spec.tsx index c05776e5838e0f..c236b09fa941ed 100644 --- a/static/gsApp/views/amCheckout/cart.spec.tsx +++ b/static/gsApp/views/amCheckout/cart.spec.tsx @@ -23,6 +23,7 @@ describe('Cart', () => { const billingConfig = BillingConfigFixture(PlanTier.AM3); const props = { ...routerProps, + navigate: jest.fn(), isNewCheckout: true, }; const businessPlan = PlanDetailsLookupFixture('am3_business')!; diff --git a/static/gsApp/views/amCheckout/checkoutOverview.spec.tsx b/static/gsApp/views/amCheckout/checkoutOverview.spec.tsx index c980315f3f067b..42306f0e12515f 100644 --- a/static/gsApp/views/amCheckout/checkoutOverview.spec.tsx +++ b/static/gsApp/views/amCheckout/checkoutOverview.spec.tsx @@ -16,7 +16,6 @@ describe('CheckoutOverview', () => { const api = new MockApiClient(); const {organization, routerProps} = initializeOrg(); const subscription = SubscriptionFixture({organization, plan: 'am1_f'}); - const params = {}; const billingConfig = BillingConfigFixture(PlanTier.AM2); const teamPlanAnnual = PlanDetailsLookupFixture('am1_team_auf')!; @@ -52,7 +51,7 @@ describe('CheckoutOverview', () => { api={api} checkoutTier={PlanTier.AM2} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); @@ -86,7 +85,7 @@ describe('CheckoutOverview', () => { checkoutTier={PlanTier.AM1} location={LocationFixture({hash: '#step3'})} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); diff --git a/static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx b/static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx index 93e7a146568a98..492b0875c9591f 100644 --- a/static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx +++ b/static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx @@ -14,7 +14,6 @@ describe('CheckoutOverviewV2', () => { const api = new MockApiClient(); const {organization, routerProps} = initializeOrg(); const subscription = SubscriptionFixture({organization, plan: 'am3_f'}); - const params = {}; const billingConfig = BillingConfigFixture(PlanTier.AM3); const teamPlanAnnual = PlanDetailsLookupFixture('am3_team_auf')!; @@ -50,7 +49,7 @@ describe('CheckoutOverviewV2', () => { api={api} checkoutTier={PlanTier.AM3} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); diff --git a/static/gsApp/views/amCheckout/index.spec.tsx b/static/gsApp/views/amCheckout/index.spec.tsx index 2eb3b4644e7361..aedfbf4809fda4 100644 --- a/static/gsApp/views/amCheckout/index.spec.tsx +++ b/static/gsApp/views/amCheckout/index.spec.tsx @@ -29,7 +29,6 @@ describe('AM1 Checkout', () => { const api = new MockApiClient(); const organization = OrganizationFixture({features: []}); const subscription = SubscriptionFixture({organization}); - const params = {}; beforeEach(() => { SubscriptionStore.set(organization.slug, subscription); @@ -62,7 +61,7 @@ describe('AM1 Checkout', () => { , @@ -92,7 +91,7 @@ describe('AM1 Checkout', () => { , @@ -123,7 +122,7 @@ describe('AM1 Checkout', () => { render( { render( { render( { const {container} = render( { const {container} = render( { render( { render( { render( { render( { render( { render( { render( { render( { const api = new MockApiClient(); const organization = OrganizationFixture(); const subscription = SubscriptionFixture({organization}); - const params = {}; beforeEach(() => { SubscriptionStore.set(organization.slug, subscription); @@ -675,7 +673,7 @@ describe('AM2 Checkout', () => { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { const organization = OrganizationFixture({ features: ['ondemand-budgets', 'am3-billing'], }); - const params = {}; beforeEach(() => { MockApiClient.clearMockResponses(); @@ -1391,7 +1388,7 @@ describe('AM3 Checkout', () => { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { }); const subscription = SubscriptionFixture({organization}); - const params = {}; const billingDetails = BillingDetailsFixture({addressType: null}); const stepNumber = 6; @@ -101,7 +100,7 @@ describe('Billing Details Step', () => { api={api} checkoutTier={PlanTier.AM2} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); diff --git a/static/gsApp/views/amCheckout/steps/addDataVolume.spec.tsx b/static/gsApp/views/amCheckout/steps/addDataVolume.spec.tsx index 8c7d236b98e00b..d8f5a7da360916 100644 --- a/static/gsApp/views/amCheckout/steps/addDataVolume.spec.tsx +++ b/static/gsApp/views/amCheckout/steps/addDataVolume.spec.tsx @@ -58,7 +58,6 @@ describe('AddDataVolume for legacy plans', () => { const api = new MockApiClient(); const {organization, routerProps} = initializeOrg(); const subscription = SubscriptionFixture({organization}); - const params = {}; const billingConfig = BillingConfigFixture(PlanTier.AM2); const bizPlan = PlanDetailsLookupFixture('am1_business')!; @@ -114,7 +113,7 @@ describe('AddDataVolume for legacy plans', () => { api={api} checkoutTier={PlanTier.AM2} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); @@ -133,7 +132,7 @@ describe('AddDataVolume for legacy plans', () => { api={api} checkoutTier={PlanTier.AM2} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); @@ -293,7 +292,7 @@ describe('AddDataVolume for legacy plans', () => { api={api} checkoutTier={PlanTier.AM2} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); const panel = await screen.findByTestId('step-add-data-volume'); @@ -363,7 +362,6 @@ describe('AddDataVolume for modern plans', () => { const api = new MockApiClient(); const {organization, routerProps} = initializeOrg(); const subscription = SubscriptionFixture({organization}); - const params = {}; const billingConfig = BillingConfigFixture(PlanTier.AM3); const bizPlan = PlanDetailsLookupFixture('am3_business')!; @@ -418,7 +416,7 @@ describe('AddDataVolume for modern plans', () => { api={api} checkoutTier={PlanTier.AM3} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); @@ -437,7 +435,7 @@ describe('AddDataVolume for modern plans', () => { api={api} checkoutTier={PlanTier.AM3} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); @@ -596,7 +594,7 @@ describe('AddDataVolume for modern plans', () => { api={api} checkoutTier={PlanTier.AM3} onToggleLegacy={jest.fn()} - params={params} + navigate={jest.fn()} /> ); const panel = await screen.findByTestId('step-add-data-volume'); diff --git a/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx b/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx index a1319b77ea8400..1b1ea187e53293 100644 --- a/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx +++ b/static/gsApp/views/amCheckout/steps/addPaymentMethod.spec.tsx @@ -22,7 +22,6 @@ describe('AddPaymentMethod', () => { const api = new MockApiClient(); const organization = OrganizationFixture(); const subscription = SubscriptionFixture({organization}); - const params = {}; let setupIntent!: jest.Mock; const stepNumber = 5; @@ -87,7 +86,7 @@ describe('AddPaymentMethod', () => { render( { checkoutTier={PlanTier.AM3} isNewCheckout={isNewCheckout} location={location} + navigate={jest.fn()} />, {organization} ); diff --git a/static/gsApp/views/amCheckout/steps/contractSelect.spec.tsx b/static/gsApp/views/amCheckout/steps/contractSelect.spec.tsx index 7f5909453f8615..ad6df4553cf6fe 100644 --- a/static/gsApp/views/amCheckout/steps/contractSelect.spec.tsx +++ b/static/gsApp/views/amCheckout/steps/contractSelect.spec.tsx @@ -19,7 +19,6 @@ describe('ContractSelect', () => { contractPeriodStart: '2025-07-16', contractPeriodEnd: '2025-08-15', }); - const params = {}; const warningText = /You are currently on an annual contract/; @@ -27,7 +26,7 @@ describe('ContractSelect', () => { return render( { const createWrapper = ({subscription}: {subscription: SubscriptionType}) => { SubscriptionStore.set(organization.slug, subscription); - const params = {}; return render( { const api = new MockApiClient(); const organization = OrganizationFixture(); const subscription = SubscriptionFixture({organization}); - const params = {}; const stepBody = /On-Demand spend allows you to pay for additional data/; @@ -74,7 +73,7 @@ describe('OnDemandSpend', () => { render( { render( { render( { render( { const api = new MockApiClient(); const organization = OrganizationFixture(); const subscription = SubscriptionFixture({organization}); - const params = {}; beforeEach(() => { SubscriptionStore.set(organization.slug, subscription); @@ -67,7 +66,7 @@ describe('PlanSelect', () => { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { render( { const api = new MockApiClient(); const organization = OrganizationFixture(); const subscription = SubscriptionFixture({organization}); - const params = {}; beforeEach(() => { MockApiClient.clearMockResponses(); @@ -66,7 +65,7 @@ describe('ProductSelect', () => { render( { render( { render( { render( { render( { render( { render( { render( ReviewAndConfirm', () => { const api = new MockApiClient(); const organization = OrganizationFixture(); const subscription = SubscriptionFixture({organization}); - const params = {}; const bizPlan = PlanDetailsLookupFixture('am1_business')!; const billingConfig = BillingConfigFixture(PlanTier.AM2); @@ -141,7 +140,7 @@ describe('AmCheckout > ReviewAndConfirm', () => { render( { const organization = OrganizationFixture({ features: ['ondemand-budgets', 'am3-billing'], }); - const params = {}; const stepBody = /Pay-as-you-go applies across all Sentry products, on a first-come, first-served basis./; @@ -60,7 +59,7 @@ describe('SetPayAsYouGo', () => { render( { render( { render( { render( { render( { render( { render( { render( { checkoutTier={PlanTier.AM3} onToggleLegacy={jest.fn()} isNewCheckout + navigate={jest.fn()} /> ); @@ -102,6 +103,7 @@ describe('SetSpendCap', () => { checkoutTier={PlanTier.AM2} onToggleLegacy={jest.fn()} isNewCheckout + navigate={jest.fn()} /> ); From 642a300cfbc7755d074ebb3ccb7b7577087f77d2 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 15:16:16 -0400 Subject: [PATCH 10/11] rm FullScreenContainer --- static/gsApp/views/amCheckout/index.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 16faf7d51d9e1c..a2b1813f3d6e83 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -1,4 +1,4 @@ -import {Component, Fragment} from 'react'; +import {Component} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; @@ -788,7 +788,17 @@ class AMCheckout extends Component { const isOnSponsoredPartnerPlan = (subscription.partner?.isActive && subscription.isSponsored) || false; - const ParentComponent = isNewCheckout ? FullScreenContainer : Fragment; + // TODO(checkout v3): remove this once checkout v3 is GA'd + function ParentComponent({children}: {children: React.ReactNode}) { + if (isNewCheckout) { + return ( + + {children} + + ); + } + return children; + } return ( @@ -918,14 +928,6 @@ class AMCheckout extends Component { } } -// TODO(checkout v3): remove this and use Flex when checkout v3 is GA'd -const FullScreenContainer = styled('div')` - display: flex; - flex-direction: column; - align-items: center; - background: ${p => p.theme.background}; -`; - const BackButton = styled(Button)` align-self: flex-start; padding: 0; From 579ee0102e0643727a02719ab22222bdc52ac94b Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 5 Sep 2025 15:32:42 -0400 Subject: [PATCH 11/11] fix --- static/gsApp/views/amCheckout/index.tsx | 273 ++++++++++++------------ 1 file changed, 139 insertions(+), 134 deletions(-) diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index a2b1813f3d6e83..8e5c91a26e50a5 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -1,4 +1,4 @@ -import {Component} from 'react'; +import {Component, Fragment} from 'react'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; @@ -735,6 +735,19 @@ class AMCheckout extends Component { ); } + // TODO(checkout v3): remove this once checkout v3 is GA'd + renderParentComponent({children}: {children: React.ReactNode}) { + const {isNewCheckout} = this.props; + if (isNewCheckout) { + return ( + + {children} + + ); + } + return children; + } + render() { const { subscription, @@ -788,143 +801,135 @@ class AMCheckout extends Component { const isOnSponsoredPartnerPlan = (subscription.partner?.isActive && subscription.isSponsored) || false; - // TODO(checkout v3): remove this once checkout v3 is GA'd - function ParentComponent({children}: {children: React.ReactNode}) { - if (isNewCheckout) { - return ( - - {children} - - ); - } - return children; - } - - return ( - - - {isNewCheckout && ( - - - + return this.renderParentComponent({ + children: ( + + + {isNewCheckout && ( + + + + - - )} - {isOnSponsoredPartnerPlan && ( - - - {t( - 'Your promotional plan with %s ends on %s.', - subscription.partner?.partnership.displayName, - moment(subscription.contractPeriodEnd).format('ll') + )} + {isOnSponsoredPartnerPlan && ( + + + {t( + 'Your promotional plan with %s ends on %s.', + subscription.partner?.partnership.displayName, + moment(subscription.contractPeriodEnd).format('ll') + )} + + + )} + {promotionDisclaimerText && ( + + {promotionDisclaimerText} + + )} + {!isNewCheckout && ( + + )} + +
+ {isNewCheckout && ( + + { + navigate(`/settings/${organization.slug}/billing/`); + }} + > + + + {t('Back')} + + + )} - - - )} - {promotionDisclaimerText && ( - - {promotionDisclaimerText} - - )} - {!isNewCheckout && ( - - )} - -
- {isNewCheckout && ( - - { - navigate(`/settings/${organization.slug}/billing/`); - }} - > - - - {t('Back')} - - - - )} - {this.renderPartnerAlert()} - - {this.renderSteps()} - -
- - - {isNewCheckout ? ( - - ) : checkoutTier === PlanTier.AM3 ? ( - - ) : ( - + {this.renderPartnerAlert()} + + {this.renderSteps()} + +
+ + + {isNewCheckout ? ( + + ) : checkoutTier === PlanTier.AM3 ? ( + + ) : ( + + )} + + {t('Have a question?')} + + {tct('[help:Find an Answer] or [contact:Ask Support]', { + help: ( + + ), + contact: ( + + ), + })} + + + + {discountInfo?.disclaimerText} + + {subscription.canCancel && ( + + + {subscription.cancelAtPeriodEnd + ? t('Pending Cancellation') + : t('Cancel Subscription')} + + )} - - {t('Have a question?')} - - {tct('[help:Find an Answer] or [contact:Ask Support]', { - help: ( - - ), - contact: , - })} - - - - {discountInfo?.disclaimerText} - - {subscription.canCancel && ( - - - {subscription.cancelAtPeriodEnd - ? t('Pending Cancellation') - : t('Cancel Subscription')} - - - )} - {showAnnualTerms && ( - - {tct( - `Annual subscriptions require a one-year non-cancellable commitment. + {showAnnualTerms && ( + + {tct( + `Annual subscriptions require a one-year non-cancellable commitment. By using Sentry you agree to our [terms: Terms of Service].`, - {terms: } - )} - - )} - -
-
- ); + {terms: } + )} + + )} + + + + ), + }); } }