From 90c4c6218a42f1b2a47fe6174616eedd31b2b667 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 14 Nov 2025 12:17:25 -0500 Subject: [PATCH 1/4] ref(checkout): Cleanup file structure --- .../components/upsellModal/featureList.tsx | 2 +- .../checkoutV3/addBillingInfo.spec.tsx | 0 .../{steps => }/checkoutV3/addBillingInfo.tsx | 2 +- .../checkoutV3/buildYourPlan.spec.tsx | 0 .../{steps => }/checkoutV3/buildYourPlan.tsx | 6 +- .../chooseYourBillingCycle.spec.tsx | 0 .../checkoutV3/chooseYourBillingCycle.tsx | 4 +- .../billingCycleSelectCard.tsx | 2 +- .../amCheckout/{ => components}/cart.spec.tsx | 2 +- .../amCheckout/{ => components}/cart.tsx | 2 +- .../{ => components}/cartDiff.spec.tsx | 2 +- .../amCheckout/{ => components}/cartDiff.tsx | 0 .../{ => components}/checkoutOption.tsx | 0 .../checkoutOverview.spec.tsx | 2 +- .../{ => components}/checkoutOverview.tsx | 0 .../checkoutOverviewV2.spec.tsx | 2 +- .../{ => components}/checkoutOverviewV2.tsx | 0 .../{ => components}/checkoutSuccess.spec.tsx | 0 .../{ => components}/checkoutSuccess.tsx | 0 .../legacyPlanToggle.spec.tsx | 2 +- .../{ => components}/legacyPlanToggle.tsx | 0 .../{ => components}/moreFeaturesLink.tsx | 0 .../{ => components}/planFeatures.spec.tsx | 2 +- .../{ => components}/planFeatures.tsx | 0 .../{steps => components}/planSelectCard.tsx | 4 +- .../{steps => components}/planSelectRow.tsx | 2 +- .../{steps => components}/stepHeader.spec.tsx | 2 +- .../{steps => components}/stepHeader.tsx | 2 +- .../{steps => components}/unitTypeItem.tsx | 0 .../{steps => components}/volumeSliders.tsx | 0 static/gsApp/views/amCheckout/index.tsx | 14 +- .../amCheckout/steps/addBillingDetails.tsx | 2 +- .../amCheckout/steps/addBillingInfo.spec.tsx | 158 +++++++++++++ .../views/amCheckout/steps/addBillingInfo.tsx | 78 +++++++ .../views/amCheckout/steps/addDataVolume.tsx | 4 +- .../amCheckout/steps/addPaymentMethod.tsx | 2 +- .../amCheckout/steps/buildYourPlan.spec.tsx | 146 ++++++++++++ .../views/amCheckout/steps/buildYourPlan.tsx | 220 ++++++++++++++++++ .../steps/chooseYourBillingCycle.spec.tsx | 198 ++++++++++++++++ .../steps/chooseYourBillingCycle.tsx | 87 +++++++ .../views/amCheckout/steps/contractSelect.tsx | 4 +- .../amCheckout/steps/onDemandBudgets.tsx | 2 +- .../views/amCheckout/steps/onDemandSpend.tsx | 2 +- .../views/amCheckout/steps/planSelect.tsx | 4 +- .../views/amCheckout/steps/productSelect.tsx | 2 +- .../reserveAdditionalVolume.spec.tsx | 0 .../{ => steps}/reserveAdditionalVolume.tsx | 2 +- .../amCheckout/steps/reviewAndConfirm.tsx | 2 +- .../views/amCheckout/steps/setPayAsYouGo.tsx | 2 +- .../views/amCheckout/steps/setSpendLimit.tsx | 2 +- .../views/spendLimits/spendLimitSettings.tsx | 4 +- 51 files changed, 931 insertions(+), 44 deletions(-) rename static/gsApp/views/amCheckout/{steps => }/checkoutV3/addBillingInfo.spec.tsx (100%) rename static/gsApp/views/amCheckout/{steps => }/checkoutV3/addBillingInfo.tsx (97%) rename static/gsApp/views/amCheckout/{steps => }/checkoutV3/buildYourPlan.spec.tsx (100%) rename static/gsApp/views/amCheckout/{steps => }/checkoutV3/buildYourPlan.tsx (96%) rename static/gsApp/views/amCheckout/{steps => }/checkoutV3/chooseYourBillingCycle.spec.tsx (100%) rename static/gsApp/views/amCheckout/{steps => }/checkoutV3/chooseYourBillingCycle.tsx (93%) rename static/gsApp/views/amCheckout/{ => components}/billingCycleSelectCard.tsx (98%) rename static/gsApp/views/amCheckout/{ => components}/cart.spec.tsx (99%) rename static/gsApp/views/amCheckout/{ => components}/cart.tsx (99%) rename static/gsApp/views/amCheckout/{ => components}/cartDiff.spec.tsx (99%) rename static/gsApp/views/amCheckout/{ => components}/cartDiff.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/checkoutOption.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/checkoutOverview.spec.tsx (98%) rename static/gsApp/views/amCheckout/{ => components}/checkoutOverview.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/checkoutOverviewV2.spec.tsx (98%) rename static/gsApp/views/amCheckout/{ => components}/checkoutOverviewV2.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/checkoutSuccess.spec.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/checkoutSuccess.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/legacyPlanToggle.spec.tsx (98%) rename static/gsApp/views/amCheckout/{ => components}/legacyPlanToggle.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/moreFeaturesLink.tsx (100%) rename static/gsApp/views/amCheckout/{ => components}/planFeatures.spec.tsx (94%) rename static/gsApp/views/amCheckout/{ => components}/planFeatures.tsx (100%) rename static/gsApp/views/amCheckout/{steps => components}/planSelectCard.tsx (96%) rename static/gsApp/views/amCheckout/{steps => components}/planSelectRow.tsx (99%) rename static/gsApp/views/amCheckout/{steps => components}/stepHeader.spec.tsx (97%) rename static/gsApp/views/amCheckout/{steps => components}/stepHeader.tsx (97%) rename static/gsApp/views/amCheckout/{steps => components}/unitTypeItem.tsx (100%) rename static/gsApp/views/amCheckout/{steps => components}/volumeSliders.tsx (100%) create mode 100644 static/gsApp/views/amCheckout/steps/addBillingInfo.spec.tsx create mode 100644 static/gsApp/views/amCheckout/steps/addBillingInfo.tsx create mode 100644 static/gsApp/views/amCheckout/steps/buildYourPlan.spec.tsx create mode 100644 static/gsApp/views/amCheckout/steps/buildYourPlan.tsx create mode 100644 static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.spec.tsx create mode 100644 static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.tsx rename static/gsApp/views/amCheckout/{ => steps}/reserveAdditionalVolume.spec.tsx (100%) rename static/gsApp/views/amCheckout/{ => steps}/reserveAdditionalVolume.tsx (98%) diff --git a/static/gsApp/components/upsellModal/featureList.tsx b/static/gsApp/components/upsellModal/featureList.tsx index b99d160c8e86df..567604512adc94 100644 --- a/static/gsApp/components/upsellModal/featureList.tsx +++ b/static/gsApp/components/upsellModal/featureList.tsx @@ -10,7 +10,7 @@ import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import testableTransition from 'sentry/utils/testableTransition'; -import MoreFeaturesLink from 'getsentry/views/amCheckout/moreFeaturesLink'; +import MoreFeaturesLink from 'getsentry/views/amCheckout/components/moreFeaturesLink'; import type {Feature} from './types'; diff --git a/static/gsApp/views/amCheckout/steps/checkoutV3/addBillingInfo.spec.tsx b/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.spec.tsx similarity index 100% rename from static/gsApp/views/amCheckout/steps/checkoutV3/addBillingInfo.spec.tsx rename to static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.spec.tsx diff --git a/static/gsApp/views/amCheckout/steps/checkoutV3/addBillingInfo.tsx b/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx similarity index 97% rename from static/gsApp/views/amCheckout/steps/checkoutV3/addBillingInfo.tsx rename to static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx index 864cc9776aa9d9..7e6666a9cc5fd8 100644 --- a/static/gsApp/views/amCheckout/steps/checkoutV3/addBillingInfo.tsx +++ b/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx @@ -10,7 +10,7 @@ import BillingDetailsPanel from 'getsentry/components/billingDetails/panel'; import CreditCardPanel from 'getsentry/components/creditCardEdit/panel'; import {useBillingDetails} from 'getsentry/hooks/useBillingDetails'; import {FTCConsentLocation} from 'getsentry/types'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {CheckoutV3StepProps} from 'getsentry/views/amCheckout/types'; import {hasBillingInfo} from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/steps/checkoutV3/buildYourPlan.spec.tsx b/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.spec.tsx similarity index 100% rename from static/gsApp/views/amCheckout/steps/checkoutV3/buildYourPlan.spec.tsx rename to static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.spec.tsx diff --git a/static/gsApp/views/amCheckout/steps/checkoutV3/buildYourPlan.tsx b/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx similarity index 96% rename from static/gsApp/views/amCheckout/steps/checkoutV3/buildYourPlan.tsx rename to static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx index 6188b10e1a60ae..f92f4a12939ae2 100644 --- a/static/gsApp/views/amCheckout/steps/checkoutV3/buildYourPlan.tsx +++ b/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx @@ -18,10 +18,10 @@ import { isNewPayingCustomer, isTrialPlan, } from 'getsentry/utils/billing'; -import PlanFeatures from 'getsentry/views/amCheckout/planFeatures'; -import PlanSelectCard from 'getsentry/views/amCheckout/steps/planSelectCard'; +import PlanFeatures from 'getsentry/views/amCheckout/components/planFeatures'; +import PlanSelectCard from 'getsentry/views/amCheckout/components/planSelectCard'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import ProductSelect from 'getsentry/views/amCheckout/steps/productSelect'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; import type { CheckoutFormData, CheckoutV3StepProps, diff --git a/static/gsApp/views/amCheckout/steps/checkoutV3/chooseYourBillingCycle.spec.tsx b/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.spec.tsx similarity index 100% rename from static/gsApp/views/amCheckout/steps/checkoutV3/chooseYourBillingCycle.spec.tsx rename to static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.spec.tsx diff --git a/static/gsApp/views/amCheckout/steps/checkoutV3/chooseYourBillingCycle.tsx b/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx similarity index 93% rename from static/gsApp/views/amCheckout/steps/checkoutV3/chooseYourBillingCycle.tsx rename to static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx index ce2089bee47a18..94d77ba897fc0a 100644 --- a/static/gsApp/views/amCheckout/steps/checkoutV3/chooseYourBillingCycle.tsx +++ b/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx @@ -4,8 +4,8 @@ import {Flex, Grid} from 'sentry/components/core/layout'; import {t} from 'sentry/locale'; import {ANNUAL} from 'getsentry/constants'; -import BillingCycleSelectCard from 'getsentry/views/amCheckout/billingCycleSelectCard'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import BillingCycleSelectCard from 'getsentry/views/amCheckout/components/billingCycleSelectCard'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {CheckoutV3StepProps} from 'getsentry/views/amCheckout/types'; import * as utils from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/billingCycleSelectCard.tsx b/static/gsApp/views/amCheckout/components/billingCycleSelectCard.tsx similarity index 98% rename from static/gsApp/views/amCheckout/billingCycleSelectCard.tsx rename to static/gsApp/views/amCheckout/components/billingCycleSelectCard.tsx index af66768bc3ad50..3230dd678b3e5a 100644 --- a/static/gsApp/views/amCheckout/billingCycleSelectCard.tsx +++ b/static/gsApp/views/amCheckout/components/billingCycleSelectCard.tsx @@ -10,7 +10,7 @@ import {t, tct} from 'sentry/locale'; import {ANNUAL} from 'getsentry/constants'; import type {Plan, Subscription} from 'getsentry/types'; import {displayBudgetName, isDeveloperPlan} from 'getsentry/utils/billing'; -import CheckoutOption from 'getsentry/views/amCheckout/checkoutOption'; +import CheckoutOption from 'getsentry/views/amCheckout/components/checkoutOption'; import type {CheckoutFormData} from 'getsentry/views/amCheckout/types'; type BillingCycleSelectCardProps = { diff --git a/static/gsApp/views/amCheckout/cart.spec.tsx b/static/gsApp/views/amCheckout/components/cart.spec.tsx similarity index 99% rename from static/gsApp/views/amCheckout/cart.spec.tsx rename to static/gsApp/views/amCheckout/components/cart.spec.tsx index 968a8aa11aaa6b..0dde16b67e4f7b 100644 --- a/static/gsApp/views/amCheckout/cart.spec.tsx +++ b/static/gsApp/views/amCheckout/components/cart.spec.tsx @@ -25,7 +25,7 @@ import { PlanTier, } from 'getsentry/types'; import AMCheckout from 'getsentry/views/amCheckout/'; -import Cart from 'getsentry/views/amCheckout/cart'; +import Cart from 'getsentry/views/amCheckout/components/cart'; import {type CheckoutFormData} from 'getsentry/views/amCheckout/types'; // Jun 06 2022 - with milliseconds diff --git a/static/gsApp/views/amCheckout/cart.tsx b/static/gsApp/views/amCheckout/components/cart.tsx similarity index 99% rename from static/gsApp/views/amCheckout/cart.tsx rename to static/gsApp/views/amCheckout/components/cart.tsx index 78770ee2c0a806..3eca49a925deff 100644 --- a/static/gsApp/views/amCheckout/cart.tsx +++ b/static/gsApp/views/amCheckout/components/cart.tsx @@ -43,7 +43,7 @@ import { } from 'getsentry/utils/billing'; import {getPlanCategoryName, getSingularCategoryName} from 'getsentry/utils/dataCategory'; import type {State as CheckoutState} from 'getsentry/views/amCheckout/'; -import CartDiff from 'getsentry/views/amCheckout/cartDiff'; +import CartDiff from 'getsentry/views/amCheckout/components/cartDiff'; import type {CheckoutFormData} from 'getsentry/views/amCheckout/types'; import * as utils from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/cartDiff.spec.tsx b/static/gsApp/views/amCheckout/components/cartDiff.spec.tsx similarity index 99% rename from static/gsApp/views/amCheckout/cartDiff.spec.tsx rename to static/gsApp/views/amCheckout/components/cartDiff.spec.tsx index 5bd1e23b6f9957..d22a5e8e5d5949 100644 --- a/static/gsApp/views/amCheckout/cartDiff.spec.tsx +++ b/static/gsApp/views/amCheckout/components/cartDiff.spec.tsx @@ -10,7 +10,7 @@ import { type Plan, type Subscription, } from 'getsentry/types'; -import CartDiff from 'getsentry/views/amCheckout/cartDiff'; +import CartDiff from 'getsentry/views/amCheckout/components/cartDiff'; import {type CheckoutFormData} from 'getsentry/views/amCheckout/types'; describe('CartDiff', () => { diff --git a/static/gsApp/views/amCheckout/cartDiff.tsx b/static/gsApp/views/amCheckout/components/cartDiff.tsx similarity index 100% rename from static/gsApp/views/amCheckout/cartDiff.tsx rename to static/gsApp/views/amCheckout/components/cartDiff.tsx diff --git a/static/gsApp/views/amCheckout/checkoutOption.tsx b/static/gsApp/views/amCheckout/components/checkoutOption.tsx similarity index 100% rename from static/gsApp/views/amCheckout/checkoutOption.tsx rename to static/gsApp/views/amCheckout/components/checkoutOption.tsx diff --git a/static/gsApp/views/amCheckout/checkoutOverview.spec.tsx b/static/gsApp/views/amCheckout/components/checkoutOverview.spec.tsx similarity index 98% rename from static/gsApp/views/amCheckout/checkoutOverview.spec.tsx rename to static/gsApp/views/amCheckout/components/checkoutOverview.spec.tsx index ff0dcf76e8808b..ea4bd7352f5f20 100644 --- a/static/gsApp/views/amCheckout/checkoutOverview.spec.tsx +++ b/static/gsApp/views/amCheckout/components/checkoutOverview.spec.tsx @@ -9,7 +9,7 @@ import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestin import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import {AddOnCategory, OnDemandBudgetMode, PlanTier} from 'getsentry/types'; import AMCheckout from 'getsentry/views/amCheckout/'; -import CheckoutOverview from 'getsentry/views/amCheckout/checkoutOverview'; +import CheckoutOverview from 'getsentry/views/amCheckout/components/checkoutOverview'; import {type CheckoutFormData} from 'getsentry/views/amCheckout/types'; describe('CheckoutOverview', () => { diff --git a/static/gsApp/views/amCheckout/checkoutOverview.tsx b/static/gsApp/views/amCheckout/components/checkoutOverview.tsx similarity index 100% rename from static/gsApp/views/amCheckout/checkoutOverview.tsx rename to static/gsApp/views/amCheckout/components/checkoutOverview.tsx diff --git a/static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx b/static/gsApp/views/amCheckout/components/checkoutOverviewV2.spec.tsx similarity index 98% rename from static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx rename to static/gsApp/views/amCheckout/components/checkoutOverviewV2.spec.tsx index 652c49f00698d9..07477c58e0607e 100644 --- a/static/gsApp/views/amCheckout/checkoutOverviewV2.spec.tsx +++ b/static/gsApp/views/amCheckout/components/checkoutOverviewV2.spec.tsx @@ -7,7 +7,7 @@ import {render, screen} from 'sentry-test/reactTestingLibrary'; import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import {AddOnCategory, PlanTier} from 'getsentry/types'; import AMCheckout from 'getsentry/views/amCheckout/'; -import CheckoutOverviewV2 from 'getsentry/views/amCheckout/checkoutOverviewV2'; +import CheckoutOverviewV2 from 'getsentry/views/amCheckout/components/checkoutOverviewV2'; import {type CheckoutFormData} from 'getsentry/views/amCheckout/types'; describe('CheckoutOverviewV2', () => { diff --git a/static/gsApp/views/amCheckout/checkoutOverviewV2.tsx b/static/gsApp/views/amCheckout/components/checkoutOverviewV2.tsx similarity index 100% rename from static/gsApp/views/amCheckout/checkoutOverviewV2.tsx rename to static/gsApp/views/amCheckout/components/checkoutOverviewV2.tsx diff --git a/static/gsApp/views/amCheckout/checkoutSuccess.spec.tsx b/static/gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx similarity index 100% rename from static/gsApp/views/amCheckout/checkoutSuccess.spec.tsx rename to static/gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx diff --git a/static/gsApp/views/amCheckout/checkoutSuccess.tsx b/static/gsApp/views/amCheckout/components/checkoutSuccess.tsx similarity index 100% rename from static/gsApp/views/amCheckout/checkoutSuccess.tsx rename to static/gsApp/views/amCheckout/components/checkoutSuccess.tsx diff --git a/static/gsApp/views/amCheckout/legacyPlanToggle.spec.tsx b/static/gsApp/views/amCheckout/components/legacyPlanToggle.spec.tsx similarity index 98% rename from static/gsApp/views/amCheckout/legacyPlanToggle.spec.tsx rename to static/gsApp/views/amCheckout/components/legacyPlanToggle.spec.tsx index 88b04ed82e569b..234aac534ca581 100644 --- a/static/gsApp/views/amCheckout/legacyPlanToggle.spec.tsx +++ b/static/gsApp/views/amCheckout/components/legacyPlanToggle.spec.tsx @@ -6,7 +6,7 @@ import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestin import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import {CohortId} from 'getsentry/types'; -import LegacyPlanToggle from 'getsentry/views/amCheckout/legacyPlanToggle'; +import LegacyPlanToggle from 'getsentry/views/amCheckout/components/legacyPlanToggle'; describe('LegacyPlanToggle', () => { const organization = OrganizationFixture(); diff --git a/static/gsApp/views/amCheckout/legacyPlanToggle.tsx b/static/gsApp/views/amCheckout/components/legacyPlanToggle.tsx similarity index 100% rename from static/gsApp/views/amCheckout/legacyPlanToggle.tsx rename to static/gsApp/views/amCheckout/components/legacyPlanToggle.tsx diff --git a/static/gsApp/views/amCheckout/moreFeaturesLink.tsx b/static/gsApp/views/amCheckout/components/moreFeaturesLink.tsx similarity index 100% rename from static/gsApp/views/amCheckout/moreFeaturesLink.tsx rename to static/gsApp/views/amCheckout/components/moreFeaturesLink.tsx diff --git a/static/gsApp/views/amCheckout/planFeatures.spec.tsx b/static/gsApp/views/amCheckout/components/planFeatures.spec.tsx similarity index 94% rename from static/gsApp/views/amCheckout/planFeatures.spec.tsx rename to static/gsApp/views/amCheckout/components/planFeatures.spec.tsx index 7c7f71405aac52..f813f23d45bd75 100644 --- a/static/gsApp/views/amCheckout/planFeatures.spec.tsx +++ b/static/gsApp/views/amCheckout/components/planFeatures.spec.tsx @@ -1,7 +1,7 @@ import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup'; import {render, screen} from 'sentry-test/reactTestingLibrary'; -import PlanFeatures from 'getsentry/views/amCheckout/planFeatures'; +import PlanFeatures from 'getsentry/views/amCheckout/components/planFeatures'; describe('PlanFeatures', () => { const freePlan = PlanDetailsLookupFixture('am3_f')!; diff --git a/static/gsApp/views/amCheckout/planFeatures.tsx b/static/gsApp/views/amCheckout/components/planFeatures.tsx similarity index 100% rename from static/gsApp/views/amCheckout/planFeatures.tsx rename to static/gsApp/views/amCheckout/components/planFeatures.tsx diff --git a/static/gsApp/views/amCheckout/steps/planSelectCard.tsx b/static/gsApp/views/amCheckout/components/planSelectCard.tsx similarity index 96% rename from static/gsApp/views/amCheckout/steps/planSelectCard.tsx rename to static/gsApp/views/amCheckout/components/planSelectCard.tsx index 2ddb54d5c0cc22..01d024717938bc 100644 --- a/static/gsApp/views/amCheckout/steps/planSelectCard.tsx +++ b/static/gsApp/views/amCheckout/components/planSelectCard.tsx @@ -6,8 +6,8 @@ import {Heading, Text} from 'sentry/components/core/text'; import {PAYG_BUSINESS_DEFAULT, PAYG_TEAM_DEFAULT} from 'getsentry/constants'; import {OnDemandBudgetMode} from 'getsentry/types'; import {isBizPlanFamily} from 'getsentry/utils/billing'; -import CheckoutOption from 'getsentry/views/amCheckout/checkoutOption'; -import type {PlanSelectRowProps} from 'getsentry/views/amCheckout/steps/planSelectRow'; +import CheckoutOption from 'getsentry/views/amCheckout/components/checkoutOption'; +import type {PlanSelectRowProps} from 'getsentry/views/amCheckout/components/planSelectRow'; import type {CheckoutFormData} from 'getsentry/views/amCheckout/types'; import {getShortInterval} from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/steps/planSelectRow.tsx b/static/gsApp/views/amCheckout/components/planSelectRow.tsx similarity index 99% rename from static/gsApp/views/amCheckout/steps/planSelectRow.tsx rename to static/gsApp/views/amCheckout/components/planSelectRow.tsx index 03e2d1b882dcbb..b55fbd45d1c60e 100644 --- a/static/gsApp/views/amCheckout/steps/planSelectRow.tsx +++ b/static/gsApp/views/amCheckout/components/planSelectRow.tsx @@ -13,7 +13,7 @@ import TextBlock from 'sentry/views/settings/components/text/textBlock'; import {PAYG_BUSINESS_DEFAULT, PAYG_TEAM_DEFAULT} from 'getsentry/constants'; import {OnDemandBudgetMode, type Plan, type Promotion} from 'getsentry/types'; import {isBizPlanFamily} from 'getsentry/utils/billing'; -import MoreFeaturesLink from 'getsentry/views/amCheckout/moreFeaturesLink'; +import MoreFeaturesLink from 'getsentry/views/amCheckout/components/moreFeaturesLink'; import type {CheckoutFormData, PlanContent} from 'getsentry/views/amCheckout/types'; import { displayUnitPrice, diff --git a/static/gsApp/views/amCheckout/steps/stepHeader.spec.tsx b/static/gsApp/views/amCheckout/components/stepHeader.spec.tsx similarity index 97% rename from static/gsApp/views/amCheckout/steps/stepHeader.spec.tsx rename to static/gsApp/views/amCheckout/components/stepHeader.spec.tsx index 4bb28cb18d2c36..564b939de3f3c5 100644 --- a/static/gsApp/views/amCheckout/steps/stepHeader.spec.tsx +++ b/static/gsApp/views/amCheckout/components/stepHeader.spec.tsx @@ -1,6 +1,6 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; describe('StepHeader', () => { let onEdit: any; diff --git a/static/gsApp/views/amCheckout/steps/stepHeader.tsx b/static/gsApp/views/amCheckout/components/stepHeader.tsx similarity index 97% rename from static/gsApp/views/amCheckout/steps/stepHeader.tsx rename to static/gsApp/views/amCheckout/components/stepHeader.tsx index fc00b981a932a3..469eb926b11122 100644 --- a/static/gsApp/views/amCheckout/steps/stepHeader.tsx +++ b/static/gsApp/views/amCheckout/components/stepHeader.tsx @@ -10,7 +10,7 @@ import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import type {PlanTier} from 'getsentry/types'; -import LegacyPlanToggle from 'getsentry/views/amCheckout/legacyPlanToggle'; +import LegacyPlanToggle from 'getsentry/views/amCheckout/components/legacyPlanToggle'; import {getToggleTier} from 'getsentry/views/amCheckout/utils'; // TODO(checkout v3): Remove isActive/isCompleted/onEdit/canSkip diff --git a/static/gsApp/views/amCheckout/steps/unitTypeItem.tsx b/static/gsApp/views/amCheckout/components/unitTypeItem.tsx similarity index 100% rename from static/gsApp/views/amCheckout/steps/unitTypeItem.tsx rename to static/gsApp/views/amCheckout/components/unitTypeItem.tsx diff --git a/static/gsApp/views/amCheckout/steps/volumeSliders.tsx b/static/gsApp/views/amCheckout/components/volumeSliders.tsx similarity index 100% rename from static/gsApp/views/amCheckout/steps/volumeSliders.tsx rename to static/gsApp/views/amCheckout/components/volumeSliders.tsx diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 2992a18449d49a..4600bc73b59263 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -69,16 +69,16 @@ import {getCompletedOrActivePromotion} from 'getsentry/utils/promotions'; import {showSubscriptionDiscount} from 'getsentry/utils/promotionUtils'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; import withPromotions from 'getsentry/utils/withPromotions'; -import Cart from 'getsentry/views/amCheckout/cart'; -import CheckoutOverview from 'getsentry/views/amCheckout/checkoutOverview'; -import CheckoutOverviewV2 from 'getsentry/views/amCheckout/checkoutOverviewV2'; -import CheckoutSuccess from 'getsentry/views/amCheckout/checkoutSuccess'; +import Cart from 'getsentry/views/amCheckout/components/cart'; +import CheckoutOverview from 'getsentry/views/amCheckout/components/checkoutOverview'; +import CheckoutOverviewV2 from 'getsentry/views/amCheckout/components/checkoutOverviewV2'; +import CheckoutSuccess from 'getsentry/views/amCheckout/components/checkoutSuccess'; import AddBillingDetails from 'getsentry/views/amCheckout/steps/addBillingDetails'; +import AddBillingInformation from 'getsentry/views/amCheckout/steps/addBillingInfo'; import AddDataVolume from 'getsentry/views/amCheckout/steps/addDataVolume'; import AddPaymentMethod from 'getsentry/views/amCheckout/steps/addPaymentMethod'; -import AddBillingInformation from 'getsentry/views/amCheckout/steps/checkoutV3/addBillingInfo'; -import BuildYourPlan from 'getsentry/views/amCheckout/steps/checkoutV3/buildYourPlan'; -import ChooseYourBillingCycle from 'getsentry/views/amCheckout/steps/checkoutV3/chooseYourBillingCycle'; +import BuildYourPlan from 'getsentry/views/amCheckout/steps/buildYourPlan'; +import ChooseYourBillingCycle from 'getsentry/views/amCheckout/steps/chooseYourBillingCycle'; import ContractSelect from 'getsentry/views/amCheckout/steps/contractSelect'; import OnDemandBudgetsStep from 'getsentry/views/amCheckout/steps/onDemandBudgets'; import OnDemandSpend from 'getsentry/views/amCheckout/steps/onDemandSpend'; diff --git a/static/gsApp/views/amCheckout/steps/addBillingDetails.tsx b/static/gsApp/views/amCheckout/steps/addBillingDetails.tsx index 3f2bc76b05d181..60cc98a9ead9f9 100644 --- a/static/gsApp/views/amCheckout/steps/addBillingDetails.tsx +++ b/static/gsApp/views/amCheckout/steps/addBillingDetails.tsx @@ -21,7 +21,7 @@ import type {BillingDetails} from 'getsentry/types'; import {AddressType} from 'getsentry/types'; import {getCountryByCode} from 'getsentry/utils/ISO3166codes'; import {getRegionChoiceName, getTaxFieldInfo} from 'getsentry/utils/salesTax'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; type State = { diff --git a/static/gsApp/views/amCheckout/steps/addBillingInfo.spec.tsx b/static/gsApp/views/amCheckout/steps/addBillingInfo.spec.tsx new file mode 100644 index 00000000000000..d3fb9a77e186bc --- /dev/null +++ b/static/gsApp/views/amCheckout/steps/addBillingInfo.spec.tsx @@ -0,0 +1,158 @@ +import {OrganizationFixture} from 'sentry-fixture/organization'; +import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; + +import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; +import {BillingDetailsFixture} from 'getsentry-test/fixtures/billingDetails'; +import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; +import {render, screen} from 'sentry-test/reactTestingLibrary'; + +import SubscriptionStore from 'getsentry/stores/subscriptionStore'; +import {PlanTier} from 'getsentry/types'; +import AMCheckout from 'getsentry/views/amCheckout/'; + +describe('AddBillingInformation', () => { + const api = new MockApiClient(); + const organization = OrganizationFixture(); + const subscription = SubscriptionFixture({organization, plan: 'am3_f'}); + + beforeEach(() => { + organization.access = ['org:billing']; + api.clear(); + MockApiClient.clearMockResponses(); + SubscriptionStore.set(organization.slug, subscription); + + MockApiClient.addMockResponse({ + url: `/subscriptions/${organization.slug}/`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-config/`, + method: 'GET', + body: BillingConfigFixture(PlanTier.AM3), + }); + MockApiClient.addMockResponse({ + method: 'POST', + url: '/_experiment/log_exposure/', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/promotions/trigger-check/`, + method: 'POST', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/plan-migrations/?applied=0`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/subscription/preview/`, + method: 'GET', + body: { + invoiceItems: [], + }, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-details/`, + method: 'GET', + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/payments/setup/`, + method: 'POST', + body: { + id: '123', + clientSecret: 'seti_abc123', + status: 'require_payment_method', + lastError: null, + }, + }); + }); + + it('renders heading with complete existing billing info', async () => { + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-details/`, + method: 'GET', + body: BillingDetailsFixture(), + }); + render( + , + {organization} + ); + + expect(await screen.findByText('Edit billing information')).toBeInTheDocument(); + expect(screen.getByText('Business address')).toBeInTheDocument(); + expect(screen.getByText('Payment method')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Confirm'})).toBeEnabled(); + expect( + screen.getByRole('button', {name: 'Edit business address'}) + ).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Edit payment method'})).toBeInTheDocument(); + expect(screen.queryByRole('button', {name: 'Save Changes'})).not.toBeInTheDocument(); + }); + + it('renders heading with partial billing info', async () => { + // subscription has payment source + render( + , + {organization} + ); + + expect(await screen.findByText('Edit billing information')).toBeInTheDocument(); + expect(screen.getByText('Business address')).toBeInTheDocument(); + expect(screen.getByText('Payment method')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Confirm'})).toBeDisabled(); + expect( + screen.queryByRole('button', {name: 'Edit business address'}) + ).not.toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Edit payment method'})).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Save Changes'})).toBeInTheDocument(); + }); + + it('renders heading and auto opens with no existing billing info', async () => { + const newSubscription = SubscriptionFixture({ + organization, + plan: 'am3_f', + paymentSource: null, + }); + SubscriptionStore.set(organization.slug, newSubscription); + + render( + , + {organization} + ); + + expect(await screen.findByText('Add billing information')).toBeInTheDocument(); + expect(screen.getByText('Business address')).toBeInTheDocument(); + expect(screen.getByText('Payment method')).toBeInTheDocument(); + expect(screen.getByRole('button', {name: 'Confirm'})).toBeDisabled(); + expect( + screen.queryByRole('button', {name: 'Edit business address'}) + ).not.toBeInTheDocument(); + expect( + screen.queryByRole('button', {name: 'Edit payment method'}) + ).not.toBeInTheDocument(); + expect(screen.getAllByRole('button', {name: 'Save Changes'})).toHaveLength(2); + }); +}); diff --git a/static/gsApp/views/amCheckout/steps/addBillingInfo.tsx b/static/gsApp/views/amCheckout/steps/addBillingInfo.tsx new file mode 100644 index 00000000000000..7e6666a9cc5fd8 --- /dev/null +++ b/static/gsApp/views/amCheckout/steps/addBillingInfo.tsx @@ -0,0 +1,78 @@ +import {Fragment, useState} from 'react'; + +import {Alert} from 'sentry/components/core/alert'; +import {Flex} from 'sentry/components/core/layout'; +import LoadingIndicator from 'sentry/components/loadingIndicator'; +import {t} from 'sentry/locale'; +import {useLocation} from 'sentry/utils/useLocation'; + +import BillingDetailsPanel from 'getsentry/components/billingDetails/panel'; +import CreditCardPanel from 'getsentry/components/creditCardEdit/panel'; +import {useBillingDetails} from 'getsentry/hooks/useBillingDetails'; +import {FTCConsentLocation} from 'getsentry/types'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; +import type {CheckoutV3StepProps} from 'getsentry/views/amCheckout/types'; +import {hasBillingInfo} from 'getsentry/views/amCheckout/utils'; + +function AddBillingInformation({ + subscription, + onEdit, + stepNumber, + organization, + activePlan, +}: CheckoutV3StepProps) { + const [isOpen, setIsOpen] = useState(true); + const location = useLocation(); + const { + data: billingDetails, + isLoading: billingDetailsLoading, + error: billingDetailsError, + } = useBillingDetails(); + const showEditBillingInfo = hasBillingInfo(billingDetails, subscription, false); + + return ( + + {billingDetailsError && {billingDetailsError.message}} + + {isOpen && + (billingDetailsLoading ? ( + + ) : ( + + + + + ))} + + ); +} + +export default AddBillingInformation; diff --git a/static/gsApp/views/amCheckout/steps/addDataVolume.tsx b/static/gsApp/views/amCheckout/steps/addDataVolume.tsx index 5307952036d98d..d1ad0834fb3c43 100644 --- a/static/gsApp/views/amCheckout/steps/addDataVolume.tsx +++ b/static/gsApp/views/amCheckout/steps/addDataVolume.tsx @@ -13,8 +13,8 @@ import TextBlock from 'sentry/views/settings/components/text/textBlock'; import {PlanTier} from 'getsentry/types'; import {displayBudgetName, isAmPlan} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; -import VolumeSliders from 'getsentry/views/amCheckout/steps/volumeSliders'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; +import VolumeSliders from 'getsentry/views/amCheckout/components/volumeSliders'; import type {StepProps} from 'getsentry/views/amCheckout/types'; function AddDataVolume({ diff --git a/static/gsApp/views/amCheckout/steps/addPaymentMethod.tsx b/static/gsApp/views/amCheckout/steps/addPaymentMethod.tsx index f4a943efb96fd0..9c074eb0918d8f 100644 --- a/static/gsApp/views/amCheckout/steps/addPaymentMethod.tsx +++ b/static/gsApp/views/amCheckout/steps/addPaymentMethod.tsx @@ -16,7 +16,7 @@ import TextBlock from 'sentry/views/settings/components/text/textBlock'; import CreditCardSetup from 'getsentry/components/creditCardEdit/setup'; import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import {FTCConsentLocation} from 'getsentry/types'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; type Props = StepProps; diff --git a/static/gsApp/views/amCheckout/steps/buildYourPlan.spec.tsx b/static/gsApp/views/amCheckout/steps/buildYourPlan.spec.tsx new file mode 100644 index 00000000000000..ca74ce08b206ca --- /dev/null +++ b/static/gsApp/views/amCheckout/steps/buildYourPlan.spec.tsx @@ -0,0 +1,146 @@ +import {LocationFixture} from 'sentry-fixture/locationFixture'; +import {OrganizationFixture} from 'sentry-fixture/organization'; +import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; + +import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; +import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; +import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary'; + +import SubscriptionStore from 'getsentry/stores/subscriptionStore'; +import {PlanTier} from 'getsentry/types'; +import AMCheckout from 'getsentry/views/amCheckout/'; + +describe('BuildYourPlan', () => { + const api = new MockApiClient(); + const organization = OrganizationFixture(); + const subscription = SubscriptionFixture({organization, plan: 'am3_f'}); + + beforeEach(() => { + api.clear(); + MockApiClient.clearMockResponses(); + SubscriptionStore.set(organization.slug, subscription); + + MockApiClient.addMockResponse({ + url: `/subscriptions/${organization.slug}/`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-config/`, + method: 'GET', + body: BillingConfigFixture(PlanTier.AM3), + }); + MockApiClient.addMockResponse({ + method: 'POST', + url: '/_experiment/log_exposure/', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/promotions/trigger-check/`, + method: 'POST', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/plan-migrations/?applied=0`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/subscription/preview/`, + method: 'GET', + body: { + invoiceItems: [], + }, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-details/`, + method: 'GET', + }); + }); + + function assertAllSubsteps(isNewCheckout: boolean) { + const substepTitles = ['Detect and fix issues faster with our AI agent']; + + if (isNewCheckout) { + substepTitles.forEach(title => { + expect(screen.getByText(title)).toBeInTheDocument(); + }); + } else { + substepTitles.forEach(title => { + expect(screen.queryByText(title)).not.toBeInTheDocument(); + }); + } + } + + function renderCheckout(isNewCheckout: boolean, referrer?: string) { + let location = LocationFixture(); + if (referrer) { + location = LocationFixture({ + query: { + referrer, + }, + }); + } + render( + , + {organization} + ); + } + + it('renders for checkout v3', async () => { + renderCheckout(true); + + expect(await screen.findByText('Select a plan')).toBeInTheDocument(); + expect(screen.queryByTestId('body-choose-your-plan')).not.toBeInTheDocument(); + assertAllSubsteps(true); + }); + + it('does not render for old checkout', async () => { + renderCheckout(false); + + expect(await screen.findByTestId('body-choose-your-plan')).toBeInTheDocument(); + expect(screen.queryByText('Select a plan')).not.toBeInTheDocument(); + assertAllSubsteps(false); + }); + + describe('PlanSubstep', () => { + it('annotates the current plan', async () => { + const bizOrg = OrganizationFixture(); + const businessSubscription = SubscriptionFixture({ + organization: bizOrg, + plan: 'am3_business', + }); + SubscriptionStore.set(bizOrg.slug, businessSubscription); + + renderCheckout(true); + + const businessPlan = await screen.findByTestId('plan-option-am3_business'); + expect(businessPlan).toBeInTheDocument(); + expect(within(businessPlan).getByText('Current')).toBeInTheDocument(); + const teamPlan = screen.getByTestId('plan-option-am3_team'); + expect(within(teamPlan).queryByText('Current')).not.toBeInTheDocument(); + }); + + it('can select plan', async () => { + renderCheckout(true); + + const teamPlan = await screen.findByRole('radio', {name: 'Team'}); + const businessPlan = screen.getByRole('radio', {name: 'Business'}); + + expect(teamPlan).not.toBeChecked(); + expect(businessPlan).toBeChecked(); + + await userEvent.click(teamPlan); + expect(teamPlan).toBeChecked(); + expect(businessPlan).not.toBeChecked(); + }); + }); +}); diff --git a/static/gsApp/views/amCheckout/steps/buildYourPlan.tsx b/static/gsApp/views/amCheckout/steps/buildYourPlan.tsx new file mode 100644 index 00000000000000..f92f4a12939ae2 --- /dev/null +++ b/static/gsApp/views/amCheckout/steps/buildYourPlan.tsx @@ -0,0 +1,220 @@ +import {Fragment, useMemo, useState} from 'react'; +import styled from '@emotion/styled'; +import moment from 'moment-timezone'; + +import {Tag} from 'sentry/components/core/badge/tag'; +import {Flex, Stack} from 'sentry/components/core/layout'; +import {Heading} from 'sentry/components/core/text'; +import {IconSeer} from 'sentry/icons'; +import {t, tct} from 'sentry/locale'; +import type {Organization} from 'sentry/types/organization'; +import getDaysSinceDate from 'sentry/utils/getDaysSinceDate'; + +import type {BillingConfig, Plan, PlanTier, Subscription} from 'getsentry/types'; +import { + getPlanIcon, + isBizPlanFamily, + isDeveloperPlan, + isNewPayingCustomer, + isTrialPlan, +} from 'getsentry/utils/billing'; +import PlanFeatures from 'getsentry/views/amCheckout/components/planFeatures'; +import PlanSelectCard from 'getsentry/views/amCheckout/components/planSelectCard'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; +import ProductSelect from 'getsentry/views/amCheckout/steps/productSelect'; +import type { + CheckoutFormData, + CheckoutV3StepProps, +} from 'getsentry/views/amCheckout/types'; +import * as utils from 'getsentry/views/amCheckout/utils'; + +interface BaseSubstepProps { + activePlan: Plan; + formData: CheckoutFormData; + onUpdate: (data: Partial) => void; +} + +interface PlanSubstepProps extends BaseSubstepProps { + billingConfig: BillingConfig; + organization: Organization; + subscription: Subscription; + checkoutTier?: PlanTier; +} + +interface AdditionalProductsSubstepProps extends BaseSubstepProps {} + +function PlanSubstep({ + billingConfig, + activePlan, + formData, + subscription, + organization, + onUpdate, +}: PlanSubstepProps) { + const planOptions = useMemo(() => { + // TODO(isabella): Remove this once Developer is surfaced + const plans = billingConfig.planList.filter( + ({contractInterval, id}) => + contractInterval === activePlan.contractInterval && id !== billingConfig.freePlan + ); + + if (plans.length === 0) { + throw new Error('Cannot get plan options'); + } + + // sort by price ascending + return plans.sort((a, b) => a.basePrice - b.basePrice); + }, [billingConfig, activePlan.contractInterval]); + + const getBadge = (plan: Plan): React.ReactNode | undefined => { + if ( + plan.id === subscription.plan || + // TODO(checkout v3): Test this once Developer is surfaced + (isTrialPlan(subscription.plan) && isDeveloperPlan(plan)) + ) { + // TODO(checkout v3): Replace with custom badge + const copy = t('Current'); + return {copy}; + } + + if ( + isBizPlanFamily(plan) && + subscription.lastTrialEnd && + !isBizPlanFamily(subscription.planDetails) + ) { + const lastTrialEnd = moment(subscription.lastTrialEnd).utc().fromNow(); + const trialExpired: boolean = getDaysSinceDate(subscription.lastTrialEnd) > 0; + return ( + + {subscription.isTrial && !trialExpired + ? tct('Trial expires [lastTrialEnd]', {lastTrialEnd}) + : t('You trialed this plan')} + + ); + } + return undefined; + }; + + return ( + + + {planOptions.map(plan => { + const isSelected = plan.id === formData.plan; + const shouldShowDefaultPayAsYouGo = isNewPayingCustomer( + subscription, + organization + ); + const basePrice = utils.formatPrice({cents: plan.basePrice}); // TODO(isabella): confirm discountInfo is no longer used + + const planContent = utils.getContentForPlan(plan, true); + + const planIcon = getPlanIcon(plan); + const badge = getBadge(plan); + + return ( + + ); + })} + + + + ); +} + +function AdditionalProductsSubstep({ + activePlan, + formData, + onUpdate, +}: AdditionalProductsSubstepProps) { + return ( + + + + + + {/* TODO(isabella): The heading and icon should be pushed to the child component (ProductSelect) */} + {t('Detect and fix issues faster with our AI agent')} + + + + + + ); +} + +function BuildYourPlan({ + activePlan, + billingConfig, + organization, + subscription, + formData, + onEdit, + onUpdate, + stepNumber, + checkoutTier, +}: CheckoutV3StepProps) { + const [isOpen, setIsOpen] = useState(true); + + return ( + + + {isOpen && ( + + + + + )} + + ); +} + +export default BuildYourPlan; + +const OptionGrid = styled('div')<{columns: number}>` + display: grid; + grid-template-columns: 1fr; + gap: ${p => p.theme.space.lg}; + + @media (min-width: ${p => p.theme.breakpoints.md}) { + grid-template-columns: repeat(${p => p.columns}, 1fr); + row-gap: ${p => p.theme.space.xl}; + } +`; diff --git a/static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.spec.tsx b/static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.spec.tsx new file mode 100644 index 00000000000000..8bd0bade25aa57 --- /dev/null +++ b/static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.spec.tsx @@ -0,0 +1,198 @@ +import {LocationFixture} from 'sentry-fixture/locationFixture'; +import {OrganizationFixture} from 'sentry-fixture/organization'; +import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; + +import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; +import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup'; +import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; +import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary'; +import {resetMockDate, setMockDate} from 'sentry-test/utils'; + +import SubscriptionStore from 'getsentry/stores/subscriptionStore'; +import {PlanTier} from 'getsentry/types'; +import AMCheckout from 'getsentry/views/amCheckout/'; + +describe('ChooseYourBillingCycle', () => { + const api = new MockApiClient(); + const organization = OrganizationFixture(); + const subscription = SubscriptionFixture({ + organization, + plan: 'am3_f', + contractPeriodEnd: '2025-08-28', + }); + + beforeEach(() => { + api.clear(); + setMockDate(new Date('2025-08-13')); + MockApiClient.clearMockResponses(); + SubscriptionStore.set(organization.slug, subscription); + + MockApiClient.addMockResponse({ + url: `/subscriptions/${organization.slug}/`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-config/`, + method: 'GET', + body: BillingConfigFixture(PlanTier.AM3), + }); + MockApiClient.addMockResponse({ + method: 'POST', + url: '/_experiment/log_exposure/', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/promotions/trigger-check/`, + method: 'POST', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/plan-migrations/?applied=0`, + method: 'GET', + body: {}, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/subscription/preview/`, + method: 'GET', + body: { + invoiceItems: [], + }, + }); + MockApiClient.addMockResponse({ + url: `/customers/${organization.slug}/billing-details/`, + method: 'GET', + }); + }); + + afterEach(() => { + resetMockDate(); + organization.features = []; + }); + + async function assertCycleText({ + monthlyInfo, + yearlyInfo, + }: { + monthlyInfo: string | RegExp; + yearlyInfo: string | RegExp; + }) { + expect( + await screen.findByText('Pay monthly or yearly, your choice') + ).toBeInTheDocument(); + + const monthlyOption = screen.getByTestId('billing-cycle-option-monthly'); + expect(within(monthlyOption).getByText('Monthly')).toBeInTheDocument(); + expect(within(monthlyOption).queryByText(/save 10%/)).not.toBeInTheDocument(); + expect(within(monthlyOption).getByText(monthlyInfo)).toBeInTheDocument(); + expect(within(monthlyOption).getByText(/Cancel anytime/)).toBeInTheDocument(); + + const yearlyOption = screen.getByTestId('billing-cycle-option-annual'); + expect(within(yearlyOption).getByText('Yearly')).toBeInTheDocument(); + expect(within(yearlyOption).getByText(/save 10%/)).toBeInTheDocument(); + expect(within(yearlyOption).getByText(yearlyInfo)).toBeInTheDocument(); + expect( + within(yearlyOption).getByText(/PAYG usage billed monthly, discount does not apply/) + ).toBeInTheDocument(); + } + + function renderCheckout(isNewCheckout: boolean, referrer?: string) { + let location = LocationFixture(); + if (referrer) { + location = LocationFixture({ + query: { + referrer, + }, + }); + } + render( + , + {organization} + ); + } + + it('renders for upgrade from developer', async () => { + renderCheckout(true); + await assertCycleText({ + // org is on monthly cycle, but is on developer plan, so upgrade should apply immediately and create a new period + monthlyInfo: /Billed on the 13th of each month/, + yearlyInfo: /Billed annually/, + }); + }); + + it('renders for coterm upgrade', async () => { + const monthlySub = SubscriptionFixture({ + organization, + plan: 'am3_business', + contractPeriodEnd: '2025-08-28', + }); + SubscriptionStore.set(organization.slug, monthlySub); + renderCheckout(true); + await assertCycleText({ + // org is on monthly cycle and is paid plan, so upgrade should apply immediately to the current period + monthlyInfo: /Billed on the 29th of each month/, + yearlyInfo: /Billed annually/, + }); + }); + + it('renders for monthly downgrade', async () => { + const annualSub = SubscriptionFixture({ + planDetails: PlanDetailsLookupFixture('am2_business_auf'), + contractPeriodStart: '2025-07-16', + contractPeriodEnd: '2026-07-15', + organization, + }); + SubscriptionStore.set(organization.slug, annualSub); + renderCheckout(true); + await assertCycleText({ + monthlyInfo: /Billed on the 16th of each month/, + yearlyInfo: /Billed annually/, + }); + }); + + it('renders for migrating partner customers', async () => { + const partnerSub = SubscriptionFixture({ + contractInterval: 'annual', + sponsoredType: 'FOO', + partner: { + isActive: true, + externalId: 'foo', + partnership: { + id: 'foo', + displayName: 'FOO', + supportNote: '', + }, + name: '', + }, + organization, + }); + SubscriptionStore.set(organization.slug, partnerSub); + renderCheckout(true); + await assertCycleText({ + monthlyInfo: /Billed monthly starting on your selected start date on submission/, + yearlyInfo: /Billed annually from your selected start date on submission/, + }); + }); + + it('can select billing cycle', async () => { + renderCheckout(true); + + const monthly = await screen.findByRole('radio', {name: 'Monthly billing cycle'}); + const annual = screen.getByRole('radio', {name: 'Yearly billing cycle'}); + + expect(monthly).toBeChecked(); + expect(annual).not.toBeChecked(); + + await userEvent.click(annual); + expect(monthly).not.toBeChecked(); + expect(annual).toBeChecked(); + }); +}); diff --git a/static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.tsx b/static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.tsx new file mode 100644 index 00000000000000..94d77ba897fc0a --- /dev/null +++ b/static/gsApp/views/amCheckout/steps/chooseYourBillingCycle.tsx @@ -0,0 +1,87 @@ +import {useMemo, useState} from 'react'; + +import {Flex, Grid} from 'sentry/components/core/layout'; +import {t} from 'sentry/locale'; + +import {ANNUAL} from 'getsentry/constants'; +import BillingCycleSelectCard from 'getsentry/views/amCheckout/components/billingCycleSelectCard'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; +import type {CheckoutV3StepProps} from 'getsentry/views/amCheckout/types'; +import * as utils from 'getsentry/views/amCheckout/utils'; + +function ChooseYourBillingCycle({ + formData, + onUpdate, + subscription, + billingConfig, + onEdit, + stepNumber, +}: CheckoutV3StepProps) { + const [isOpen, setIsOpen] = useState(true); + const intervalOptions = useMemo(() => { + const basePlan = formData.plan.replace('_auf', ''); + const plans = billingConfig.planList.filter(({id}) => id.indexOf(basePlan) === 0); + + if (plans.length === 0) { + throw new Error('Cannot get billing interval options'); + } + + return plans; + }, [billingConfig, formData.plan]); + + let previousPlanPrice = 0; + return ( + + + {isOpen && ( + + {intervalOptions.map(plan => { + const isSelected = plan.id === formData.plan; + const isAnnual = plan.contractInterval === ANNUAL; + const priceAfterDiscount = utils.getReservedPriceCents({ + plan, + reserved: formData.reserved, + addOns: formData.addOns, + }); + const formattedPriceAfterDiscount = utils.formatPrice({ + cents: priceAfterDiscount, + }); + + const priceBeforeDiscount = isAnnual ? previousPlanPrice * 12 : 0; + const formattedPriceBeforeDiscount = previousPlanPrice + ? utils.formatPrice({cents: priceBeforeDiscount}) + : ''; + previousPlanPrice = priceAfterDiscount; + + return ( + + ); + })} + + )} + + ); +} + +export default ChooseYourBillingCycle; diff --git a/static/gsApp/views/amCheckout/steps/contractSelect.tsx b/static/gsApp/views/amCheckout/steps/contractSelect.tsx index 865c9795c51569..42a42745c8eb07 100644 --- a/static/gsApp/views/amCheckout/steps/contractSelect.tsx +++ b/static/gsApp/views/amCheckout/steps/contractSelect.tsx @@ -11,8 +11,8 @@ import {space} from 'sentry/styles/space'; import {ANNUAL, MONTHLY} from 'getsentry/constants'; import {InvoiceItemType, type Plan} from 'getsentry/types'; -import PlanSelectRow from 'getsentry/views/amCheckout/steps/planSelectRow'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import PlanSelectRow from 'getsentry/views/amCheckout/components/planSelectRow'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import {formatPrice, getReservedPriceCents} from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/steps/onDemandBudgets.tsx b/static/gsApp/views/amCheckout/steps/onDemandBudgets.tsx index 1829d920729514..c4c437b2335258 100644 --- a/static/gsApp/views/amCheckout/steps/onDemandBudgets.tsx +++ b/static/gsApp/views/amCheckout/steps/onDemandBudgets.tsx @@ -11,7 +11,7 @@ import {space} from 'sentry/styles/space'; import type {OnDemandBudgets} from 'getsentry/types'; import {OnDemandBudgetMode} from 'getsentry/types'; import {displayBudgetName, isDeveloperPlan} from 'getsentry/utils/billing'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import {getReservedPriceCents} from 'getsentry/views/amCheckout/utils'; import OnDemandBudgetEdit from 'getsentry/views/onDemandBudgets/onDemandBudgetEdit'; diff --git a/static/gsApp/views/amCheckout/steps/onDemandSpend.tsx b/static/gsApp/views/amCheckout/steps/onDemandSpend.tsx index b8365a0e89324e..706c8b98872991 100644 --- a/static/gsApp/views/amCheckout/steps/onDemandSpend.tsx +++ b/static/gsApp/views/amCheckout/steps/onDemandSpend.tsx @@ -13,7 +13,7 @@ import TextBlock from 'sentry/views/settings/components/text/textBlock'; import {displayBudgetName} from 'getsentry/utils/billing'; import {listDisplayNames} from 'getsentry/utils/dataCategory'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; type Props = StepProps; diff --git a/static/gsApp/views/amCheckout/steps/planSelect.tsx b/static/gsApp/views/amCheckout/steps/planSelect.tsx index e252af195dfd81..5befc643683624 100644 --- a/static/gsApp/views/amCheckout/steps/planSelect.tsx +++ b/static/gsApp/views/amCheckout/steps/planSelect.tsx @@ -27,9 +27,9 @@ import { } from 'getsentry/utils/promotionUtils'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; import usePromotionTriggerCheck from 'getsentry/utils/usePromotionTriggerCheck'; -import PlanSelectRow from 'getsentry/views/amCheckout/steps/planSelectRow'; +import PlanSelectRow from 'getsentry/views/amCheckout/components/planSelectRow'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import ProductSelect from 'getsentry/views/amCheckout/steps/productSelect'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; import type {PlanContent, StepProps} from 'getsentry/views/amCheckout/types'; import { formatPrice, diff --git a/static/gsApp/views/amCheckout/steps/productSelect.tsx b/static/gsApp/views/amCheckout/steps/productSelect.tsx index b0a2cf49371bd9..be93765b6ece7e 100644 --- a/static/gsApp/views/amCheckout/steps/productSelect.tsx +++ b/static/gsApp/views/amCheckout/steps/productSelect.tsx @@ -31,7 +31,7 @@ import { getSingularCategoryName, } from 'getsentry/utils/dataCategory'; import formatCurrency from 'getsentry/utils/formatCurrency'; -import CheckoutOption from 'getsentry/views/amCheckout/checkoutOption'; +import CheckoutOption from 'getsentry/views/amCheckout/components/checkoutOption'; import {type StepProps} from 'getsentry/views/amCheckout/types'; import * as utils from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx b/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.spec.tsx similarity index 100% rename from static/gsApp/views/amCheckout/reserveAdditionalVolume.spec.tsx rename to static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.spec.tsx diff --git a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx b/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.tsx similarity index 98% rename from static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx rename to static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.tsx index 58d66e2253099f..5051a1aaf3df6b 100644 --- a/static/gsApp/views/amCheckout/reserveAdditionalVolume.tsx +++ b/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.tsx @@ -11,7 +11,7 @@ import type {DataCategory} from 'sentry/types/core'; import {PlanTier} from 'getsentry/types'; import {isAmPlan, isDeveloperPlan} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; -import VolumeSliders from 'getsentry/views/amCheckout/steps/volumeSliders'; +import VolumeSliders from 'getsentry/views/amCheckout/components/volumeSliders'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import {formatPrice, getBucket, getShortInterval} from 'getsentry/views/amCheckout/utils'; diff --git a/static/gsApp/views/amCheckout/steps/reviewAndConfirm.tsx b/static/gsApp/views/amCheckout/steps/reviewAndConfirm.tsx index 730d21af730fde..ff24e219f723e4 100644 --- a/static/gsApp/views/amCheckout/steps/reviewAndConfirm.tsx +++ b/static/gsApp/views/amCheckout/steps/reviewAndConfirm.tsx @@ -19,7 +19,7 @@ import {useStripeInstance} from 'getsentry/hooks/useStripeInstance'; import type {PreviewData, Subscription} from 'getsentry/types'; import {InvoiceItemType} from 'getsentry/types'; import {hasPartnerMigrationFeature} from 'getsentry/utils/billing'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepPropsWithApi} from 'getsentry/views/amCheckout/types'; import type {IntentDetails} from 'getsentry/views/amCheckout/utils'; import { diff --git a/static/gsApp/views/amCheckout/steps/setPayAsYouGo.tsx b/static/gsApp/views/amCheckout/steps/setPayAsYouGo.tsx index 092064c50d72ef..07fdfcc1c454d4 100644 --- a/static/gsApp/views/amCheckout/steps/setPayAsYouGo.tsx +++ b/static/gsApp/views/amCheckout/steps/setPayAsYouGo.tsx @@ -21,7 +21,7 @@ import {OnDemandBudgetMode, type OnDemandBudgets} from 'getsentry/types'; import {isBizPlanFamily} from 'getsentry/utils/billing'; import {getPlanCategoryName} from 'getsentry/utils/dataCategory'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import {getTotalBudget} from 'getsentry/views/onDemandBudgets/utils'; diff --git a/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx b/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx index 4b94fca11af7b9..fe43af79e9b79f 100644 --- a/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx +++ b/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx @@ -6,8 +6,8 @@ import {t} from 'sentry/locale'; import type {OnDemandBudgets} from 'getsentry/types'; import {displayBudgetName} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; +import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; import ReserveAdditionalVolume from 'getsentry/views/amCheckout/reserveAdditionalVolume'; -import StepHeader from 'getsentry/views/amCheckout/steps/stepHeader'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import { getTotalBudget, diff --git a/static/gsApp/views/spendLimits/spendLimitSettings.tsx b/static/gsApp/views/spendLimits/spendLimitSettings.tsx index 8dcea206a087dd..27dded64966328 100644 --- a/static/gsApp/views/spendLimits/spendLimitSettings.tsx +++ b/static/gsApp/views/spendLimits/spendLimitSettings.tsx @@ -35,9 +35,9 @@ import { getPlanCategoryName, getSingularCategoryName, } from 'getsentry/utils/dataCategory'; -import CheckoutOption from 'getsentry/views/amCheckout/checkoutOption'; +import CheckoutOption from 'getsentry/views/amCheckout/components/checkoutOption'; +import {renderPerformanceHovercard} from 'getsentry/views/amCheckout/components/volumeSliders'; import {getProductCheckoutDescription} from 'getsentry/views/amCheckout/steps/productSelect'; -import {renderPerformanceHovercard} from 'getsentry/views/amCheckout/steps/volumeSliders'; import { displayPrice, displayPriceWithCents, From 2cbef1bd6423ced4243c217fe993f865ed2203a1 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 14 Nov 2025 12:19:28 -0500 Subject: [PATCH 2/4] delete this --- .../checkoutV3/addBillingInfo.spec.tsx | 158 ------------- .../amCheckout/checkoutV3/addBillingInfo.tsx | 78 ------- .../checkoutV3/buildYourPlan.spec.tsx | 146 ------------ .../amCheckout/checkoutV3/buildYourPlan.tsx | 220 ------------------ .../chooseYourBillingCycle.spec.tsx | 198 ---------------- .../checkoutV3/chooseYourBillingCycle.tsx | 87 ------- 6 files changed, 887 deletions(-) delete mode 100644 static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.spec.tsx delete mode 100644 static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx delete mode 100644 static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.spec.tsx delete mode 100644 static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx delete mode 100644 static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.spec.tsx delete mode 100644 static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx diff --git a/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.spec.tsx b/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.spec.tsx deleted file mode 100644 index d3fb9a77e186bc..00000000000000 --- a/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.spec.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import {OrganizationFixture} from 'sentry-fixture/organization'; -import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; - -import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; -import {BillingDetailsFixture} from 'getsentry-test/fixtures/billingDetails'; -import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; -import {render, screen} from 'sentry-test/reactTestingLibrary'; - -import SubscriptionStore from 'getsentry/stores/subscriptionStore'; -import {PlanTier} from 'getsentry/types'; -import AMCheckout from 'getsentry/views/amCheckout/'; - -describe('AddBillingInformation', () => { - const api = new MockApiClient(); - const organization = OrganizationFixture(); - const subscription = SubscriptionFixture({organization, plan: 'am3_f'}); - - beforeEach(() => { - organization.access = ['org:billing']; - api.clear(); - MockApiClient.clearMockResponses(); - SubscriptionStore.set(organization.slug, subscription); - - MockApiClient.addMockResponse({ - url: `/subscriptions/${organization.slug}/`, - method: 'GET', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-config/`, - method: 'GET', - body: BillingConfigFixture(PlanTier.AM3), - }); - MockApiClient.addMockResponse({ - method: 'POST', - url: '/_experiment/log_exposure/', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/promotions/trigger-check/`, - method: 'POST', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/plan-migrations/?applied=0`, - method: 'GET', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/subscription/preview/`, - method: 'GET', - body: { - invoiceItems: [], - }, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-details/`, - method: 'GET', - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/payments/setup/`, - method: 'POST', - body: { - id: '123', - clientSecret: 'seti_abc123', - status: 'require_payment_method', - lastError: null, - }, - }); - }); - - it('renders heading with complete existing billing info', async () => { - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-details/`, - method: 'GET', - body: BillingDetailsFixture(), - }); - render( - , - {organization} - ); - - expect(await screen.findByText('Edit billing information')).toBeInTheDocument(); - expect(screen.getByText('Business address')).toBeInTheDocument(); - expect(screen.getByText('Payment method')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Confirm'})).toBeEnabled(); - expect( - screen.getByRole('button', {name: 'Edit business address'}) - ).toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Edit payment method'})).toBeInTheDocument(); - expect(screen.queryByRole('button', {name: 'Save Changes'})).not.toBeInTheDocument(); - }); - - it('renders heading with partial billing info', async () => { - // subscription has payment source - render( - , - {organization} - ); - - expect(await screen.findByText('Edit billing information')).toBeInTheDocument(); - expect(screen.getByText('Business address')).toBeInTheDocument(); - expect(screen.getByText('Payment method')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Confirm'})).toBeDisabled(); - expect( - screen.queryByRole('button', {name: 'Edit business address'}) - ).not.toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Edit payment method'})).toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Save Changes'})).toBeInTheDocument(); - }); - - it('renders heading and auto opens with no existing billing info', async () => { - const newSubscription = SubscriptionFixture({ - organization, - plan: 'am3_f', - paymentSource: null, - }); - SubscriptionStore.set(organization.slug, newSubscription); - - render( - , - {organization} - ); - - expect(await screen.findByText('Add billing information')).toBeInTheDocument(); - expect(screen.getByText('Business address')).toBeInTheDocument(); - expect(screen.getByText('Payment method')).toBeInTheDocument(); - expect(screen.getByRole('button', {name: 'Confirm'})).toBeDisabled(); - expect( - screen.queryByRole('button', {name: 'Edit business address'}) - ).not.toBeInTheDocument(); - expect( - screen.queryByRole('button', {name: 'Edit payment method'}) - ).not.toBeInTheDocument(); - expect(screen.getAllByRole('button', {name: 'Save Changes'})).toHaveLength(2); - }); -}); diff --git a/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx b/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx deleted file mode 100644 index 7e6666a9cc5fd8..00000000000000 --- a/static/gsApp/views/amCheckout/checkoutV3/addBillingInfo.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import {Fragment, useState} from 'react'; - -import {Alert} from 'sentry/components/core/alert'; -import {Flex} from 'sentry/components/core/layout'; -import LoadingIndicator from 'sentry/components/loadingIndicator'; -import {t} from 'sentry/locale'; -import {useLocation} from 'sentry/utils/useLocation'; - -import BillingDetailsPanel from 'getsentry/components/billingDetails/panel'; -import CreditCardPanel from 'getsentry/components/creditCardEdit/panel'; -import {useBillingDetails} from 'getsentry/hooks/useBillingDetails'; -import {FTCConsentLocation} from 'getsentry/types'; -import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; -import type {CheckoutV3StepProps} from 'getsentry/views/amCheckout/types'; -import {hasBillingInfo} from 'getsentry/views/amCheckout/utils'; - -function AddBillingInformation({ - subscription, - onEdit, - stepNumber, - organization, - activePlan, -}: CheckoutV3StepProps) { - const [isOpen, setIsOpen] = useState(true); - const location = useLocation(); - const { - data: billingDetails, - isLoading: billingDetailsLoading, - error: billingDetailsError, - } = useBillingDetails(); - const showEditBillingInfo = hasBillingInfo(billingDetails, subscription, false); - - return ( - - {billingDetailsError && {billingDetailsError.message}} - - {isOpen && - (billingDetailsLoading ? ( - - ) : ( - - - - - ))} - - ); -} - -export default AddBillingInformation; diff --git a/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.spec.tsx b/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.spec.tsx deleted file mode 100644 index ca74ce08b206ca..00000000000000 --- a/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.spec.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import {LocationFixture} from 'sentry-fixture/locationFixture'; -import {OrganizationFixture} from 'sentry-fixture/organization'; -import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; - -import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; -import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; -import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary'; - -import SubscriptionStore from 'getsentry/stores/subscriptionStore'; -import {PlanTier} from 'getsentry/types'; -import AMCheckout from 'getsentry/views/amCheckout/'; - -describe('BuildYourPlan', () => { - const api = new MockApiClient(); - const organization = OrganizationFixture(); - const subscription = SubscriptionFixture({organization, plan: 'am3_f'}); - - beforeEach(() => { - api.clear(); - MockApiClient.clearMockResponses(); - SubscriptionStore.set(organization.slug, subscription); - - MockApiClient.addMockResponse({ - url: `/subscriptions/${organization.slug}/`, - method: 'GET', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-config/`, - method: 'GET', - body: BillingConfigFixture(PlanTier.AM3), - }); - MockApiClient.addMockResponse({ - method: 'POST', - url: '/_experiment/log_exposure/', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/promotions/trigger-check/`, - method: 'POST', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/plan-migrations/?applied=0`, - method: 'GET', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/subscription/preview/`, - method: 'GET', - body: { - invoiceItems: [], - }, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-details/`, - method: 'GET', - }); - }); - - function assertAllSubsteps(isNewCheckout: boolean) { - const substepTitles = ['Detect and fix issues faster with our AI agent']; - - if (isNewCheckout) { - substepTitles.forEach(title => { - expect(screen.getByText(title)).toBeInTheDocument(); - }); - } else { - substepTitles.forEach(title => { - expect(screen.queryByText(title)).not.toBeInTheDocument(); - }); - } - } - - function renderCheckout(isNewCheckout: boolean, referrer?: string) { - let location = LocationFixture(); - if (referrer) { - location = LocationFixture({ - query: { - referrer, - }, - }); - } - render( - , - {organization} - ); - } - - it('renders for checkout v3', async () => { - renderCheckout(true); - - expect(await screen.findByText('Select a plan')).toBeInTheDocument(); - expect(screen.queryByTestId('body-choose-your-plan')).not.toBeInTheDocument(); - assertAllSubsteps(true); - }); - - it('does not render for old checkout', async () => { - renderCheckout(false); - - expect(await screen.findByTestId('body-choose-your-plan')).toBeInTheDocument(); - expect(screen.queryByText('Select a plan')).not.toBeInTheDocument(); - assertAllSubsteps(false); - }); - - describe('PlanSubstep', () => { - it('annotates the current plan', async () => { - const bizOrg = OrganizationFixture(); - const businessSubscription = SubscriptionFixture({ - organization: bizOrg, - plan: 'am3_business', - }); - SubscriptionStore.set(bizOrg.slug, businessSubscription); - - renderCheckout(true); - - const businessPlan = await screen.findByTestId('plan-option-am3_business'); - expect(businessPlan).toBeInTheDocument(); - expect(within(businessPlan).getByText('Current')).toBeInTheDocument(); - const teamPlan = screen.getByTestId('plan-option-am3_team'); - expect(within(teamPlan).queryByText('Current')).not.toBeInTheDocument(); - }); - - it('can select plan', async () => { - renderCheckout(true); - - const teamPlan = await screen.findByRole('radio', {name: 'Team'}); - const businessPlan = screen.getByRole('radio', {name: 'Business'}); - - expect(teamPlan).not.toBeChecked(); - expect(businessPlan).toBeChecked(); - - await userEvent.click(teamPlan); - expect(teamPlan).toBeChecked(); - expect(businessPlan).not.toBeChecked(); - }); - }); -}); diff --git a/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx b/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx deleted file mode 100644 index f92f4a12939ae2..00000000000000 --- a/static/gsApp/views/amCheckout/checkoutV3/buildYourPlan.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import {Fragment, useMemo, useState} from 'react'; -import styled from '@emotion/styled'; -import moment from 'moment-timezone'; - -import {Tag} from 'sentry/components/core/badge/tag'; -import {Flex, Stack} from 'sentry/components/core/layout'; -import {Heading} from 'sentry/components/core/text'; -import {IconSeer} from 'sentry/icons'; -import {t, tct} from 'sentry/locale'; -import type {Organization} from 'sentry/types/organization'; -import getDaysSinceDate from 'sentry/utils/getDaysSinceDate'; - -import type {BillingConfig, Plan, PlanTier, Subscription} from 'getsentry/types'; -import { - getPlanIcon, - isBizPlanFamily, - isDeveloperPlan, - isNewPayingCustomer, - isTrialPlan, -} from 'getsentry/utils/billing'; -import PlanFeatures from 'getsentry/views/amCheckout/components/planFeatures'; -import PlanSelectCard from 'getsentry/views/amCheckout/components/planSelectCard'; -import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; -import ProductSelect from 'getsentry/views/amCheckout/steps/productSelect'; -import type { - CheckoutFormData, - CheckoutV3StepProps, -} from 'getsentry/views/amCheckout/types'; -import * as utils from 'getsentry/views/amCheckout/utils'; - -interface BaseSubstepProps { - activePlan: Plan; - formData: CheckoutFormData; - onUpdate: (data: Partial) => void; -} - -interface PlanSubstepProps extends BaseSubstepProps { - billingConfig: BillingConfig; - organization: Organization; - subscription: Subscription; - checkoutTier?: PlanTier; -} - -interface AdditionalProductsSubstepProps extends BaseSubstepProps {} - -function PlanSubstep({ - billingConfig, - activePlan, - formData, - subscription, - organization, - onUpdate, -}: PlanSubstepProps) { - const planOptions = useMemo(() => { - // TODO(isabella): Remove this once Developer is surfaced - const plans = billingConfig.planList.filter( - ({contractInterval, id}) => - contractInterval === activePlan.contractInterval && id !== billingConfig.freePlan - ); - - if (plans.length === 0) { - throw new Error('Cannot get plan options'); - } - - // sort by price ascending - return plans.sort((a, b) => a.basePrice - b.basePrice); - }, [billingConfig, activePlan.contractInterval]); - - const getBadge = (plan: Plan): React.ReactNode | undefined => { - if ( - plan.id === subscription.plan || - // TODO(checkout v3): Test this once Developer is surfaced - (isTrialPlan(subscription.plan) && isDeveloperPlan(plan)) - ) { - // TODO(checkout v3): Replace with custom badge - const copy = t('Current'); - return {copy}; - } - - if ( - isBizPlanFamily(plan) && - subscription.lastTrialEnd && - !isBizPlanFamily(subscription.planDetails) - ) { - const lastTrialEnd = moment(subscription.lastTrialEnd).utc().fromNow(); - const trialExpired: boolean = getDaysSinceDate(subscription.lastTrialEnd) > 0; - return ( - - {subscription.isTrial && !trialExpired - ? tct('Trial expires [lastTrialEnd]', {lastTrialEnd}) - : t('You trialed this plan')} - - ); - } - return undefined; - }; - - return ( - - - {planOptions.map(plan => { - const isSelected = plan.id === formData.plan; - const shouldShowDefaultPayAsYouGo = isNewPayingCustomer( - subscription, - organization - ); - const basePrice = utils.formatPrice({cents: plan.basePrice}); // TODO(isabella): confirm discountInfo is no longer used - - const planContent = utils.getContentForPlan(plan, true); - - const planIcon = getPlanIcon(plan); - const badge = getBadge(plan); - - return ( - - ); - })} - - - - ); -} - -function AdditionalProductsSubstep({ - activePlan, - formData, - onUpdate, -}: AdditionalProductsSubstepProps) { - return ( - - - - - - {/* TODO(isabella): The heading and icon should be pushed to the child component (ProductSelect) */} - {t('Detect and fix issues faster with our AI agent')} - - - - - - ); -} - -function BuildYourPlan({ - activePlan, - billingConfig, - organization, - subscription, - formData, - onEdit, - onUpdate, - stepNumber, - checkoutTier, -}: CheckoutV3StepProps) { - const [isOpen, setIsOpen] = useState(true); - - return ( - - - {isOpen && ( - - - - - )} - - ); -} - -export default BuildYourPlan; - -const OptionGrid = styled('div')<{columns: number}>` - display: grid; - grid-template-columns: 1fr; - gap: ${p => p.theme.space.lg}; - - @media (min-width: ${p => p.theme.breakpoints.md}) { - grid-template-columns: repeat(${p => p.columns}, 1fr); - row-gap: ${p => p.theme.space.xl}; - } -`; diff --git a/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.spec.tsx b/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.spec.tsx deleted file mode 100644 index 8bd0bade25aa57..00000000000000 --- a/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.spec.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import {LocationFixture} from 'sentry-fixture/locationFixture'; -import {OrganizationFixture} from 'sentry-fixture/organization'; -import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture'; - -import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig'; -import {PlanDetailsLookupFixture} from 'getsentry-test/fixtures/planDetailsLookup'; -import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription'; -import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary'; -import {resetMockDate, setMockDate} from 'sentry-test/utils'; - -import SubscriptionStore from 'getsentry/stores/subscriptionStore'; -import {PlanTier} from 'getsentry/types'; -import AMCheckout from 'getsentry/views/amCheckout/'; - -describe('ChooseYourBillingCycle', () => { - const api = new MockApiClient(); - const organization = OrganizationFixture(); - const subscription = SubscriptionFixture({ - organization, - plan: 'am3_f', - contractPeriodEnd: '2025-08-28', - }); - - beforeEach(() => { - api.clear(); - setMockDate(new Date('2025-08-13')); - MockApiClient.clearMockResponses(); - SubscriptionStore.set(organization.slug, subscription); - - MockApiClient.addMockResponse({ - url: `/subscriptions/${organization.slug}/`, - method: 'GET', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-config/`, - method: 'GET', - body: BillingConfigFixture(PlanTier.AM3), - }); - MockApiClient.addMockResponse({ - method: 'POST', - url: '/_experiment/log_exposure/', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/promotions/trigger-check/`, - method: 'POST', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/plan-migrations/?applied=0`, - method: 'GET', - body: {}, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/subscription/preview/`, - method: 'GET', - body: { - invoiceItems: [], - }, - }); - MockApiClient.addMockResponse({ - url: `/customers/${organization.slug}/billing-details/`, - method: 'GET', - }); - }); - - afterEach(() => { - resetMockDate(); - organization.features = []; - }); - - async function assertCycleText({ - monthlyInfo, - yearlyInfo, - }: { - monthlyInfo: string | RegExp; - yearlyInfo: string | RegExp; - }) { - expect( - await screen.findByText('Pay monthly or yearly, your choice') - ).toBeInTheDocument(); - - const monthlyOption = screen.getByTestId('billing-cycle-option-monthly'); - expect(within(monthlyOption).getByText('Monthly')).toBeInTheDocument(); - expect(within(monthlyOption).queryByText(/save 10%/)).not.toBeInTheDocument(); - expect(within(monthlyOption).getByText(monthlyInfo)).toBeInTheDocument(); - expect(within(monthlyOption).getByText(/Cancel anytime/)).toBeInTheDocument(); - - const yearlyOption = screen.getByTestId('billing-cycle-option-annual'); - expect(within(yearlyOption).getByText('Yearly')).toBeInTheDocument(); - expect(within(yearlyOption).getByText(/save 10%/)).toBeInTheDocument(); - expect(within(yearlyOption).getByText(yearlyInfo)).toBeInTheDocument(); - expect( - within(yearlyOption).getByText(/PAYG usage billed monthly, discount does not apply/) - ).toBeInTheDocument(); - } - - function renderCheckout(isNewCheckout: boolean, referrer?: string) { - let location = LocationFixture(); - if (referrer) { - location = LocationFixture({ - query: { - referrer, - }, - }); - } - render( - , - {organization} - ); - } - - it('renders for upgrade from developer', async () => { - renderCheckout(true); - await assertCycleText({ - // org is on monthly cycle, but is on developer plan, so upgrade should apply immediately and create a new period - monthlyInfo: /Billed on the 13th of each month/, - yearlyInfo: /Billed annually/, - }); - }); - - it('renders for coterm upgrade', async () => { - const monthlySub = SubscriptionFixture({ - organization, - plan: 'am3_business', - contractPeriodEnd: '2025-08-28', - }); - SubscriptionStore.set(organization.slug, monthlySub); - renderCheckout(true); - await assertCycleText({ - // org is on monthly cycle and is paid plan, so upgrade should apply immediately to the current period - monthlyInfo: /Billed on the 29th of each month/, - yearlyInfo: /Billed annually/, - }); - }); - - it('renders for monthly downgrade', async () => { - const annualSub = SubscriptionFixture({ - planDetails: PlanDetailsLookupFixture('am2_business_auf'), - contractPeriodStart: '2025-07-16', - contractPeriodEnd: '2026-07-15', - organization, - }); - SubscriptionStore.set(organization.slug, annualSub); - renderCheckout(true); - await assertCycleText({ - monthlyInfo: /Billed on the 16th of each month/, - yearlyInfo: /Billed annually/, - }); - }); - - it('renders for migrating partner customers', async () => { - const partnerSub = SubscriptionFixture({ - contractInterval: 'annual', - sponsoredType: 'FOO', - partner: { - isActive: true, - externalId: 'foo', - partnership: { - id: 'foo', - displayName: 'FOO', - supportNote: '', - }, - name: '', - }, - organization, - }); - SubscriptionStore.set(organization.slug, partnerSub); - renderCheckout(true); - await assertCycleText({ - monthlyInfo: /Billed monthly starting on your selected start date on submission/, - yearlyInfo: /Billed annually from your selected start date on submission/, - }); - }); - - it('can select billing cycle', async () => { - renderCheckout(true); - - const monthly = await screen.findByRole('radio', {name: 'Monthly billing cycle'}); - const annual = screen.getByRole('radio', {name: 'Yearly billing cycle'}); - - expect(monthly).toBeChecked(); - expect(annual).not.toBeChecked(); - - await userEvent.click(annual); - expect(monthly).not.toBeChecked(); - expect(annual).toBeChecked(); - }); -}); diff --git a/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx b/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx deleted file mode 100644 index 94d77ba897fc0a..00000000000000 --- a/static/gsApp/views/amCheckout/checkoutV3/chooseYourBillingCycle.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import {useMemo, useState} from 'react'; - -import {Flex, Grid} from 'sentry/components/core/layout'; -import {t} from 'sentry/locale'; - -import {ANNUAL} from 'getsentry/constants'; -import BillingCycleSelectCard from 'getsentry/views/amCheckout/components/billingCycleSelectCard'; -import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; -import type {CheckoutV3StepProps} from 'getsentry/views/amCheckout/types'; -import * as utils from 'getsentry/views/amCheckout/utils'; - -function ChooseYourBillingCycle({ - formData, - onUpdate, - subscription, - billingConfig, - onEdit, - stepNumber, -}: CheckoutV3StepProps) { - const [isOpen, setIsOpen] = useState(true); - const intervalOptions = useMemo(() => { - const basePlan = formData.plan.replace('_auf', ''); - const plans = billingConfig.planList.filter(({id}) => id.indexOf(basePlan) === 0); - - if (plans.length === 0) { - throw new Error('Cannot get billing interval options'); - } - - return plans; - }, [billingConfig, formData.plan]); - - let previousPlanPrice = 0; - return ( - - - {isOpen && ( - - {intervalOptions.map(plan => { - const isSelected = plan.id === formData.plan; - const isAnnual = plan.contractInterval === ANNUAL; - const priceAfterDiscount = utils.getReservedPriceCents({ - plan, - reserved: formData.reserved, - addOns: formData.addOns, - }); - const formattedPriceAfterDiscount = utils.formatPrice({ - cents: priceAfterDiscount, - }); - - const priceBeforeDiscount = isAnnual ? previousPlanPrice * 12 : 0; - const formattedPriceBeforeDiscount = previousPlanPrice - ? utils.formatPrice({cents: priceBeforeDiscount}) - : ''; - previousPlanPrice = priceAfterDiscount; - - return ( - - ); - })} - - )} - - ); -} - -export default ChooseYourBillingCycle; From 268d3ca5f44ddd5906fbfc812a37a2ee457f897a Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 14 Nov 2025 12:21:23 -0500 Subject: [PATCH 3/4] fix import --- static/gsApp/views/amCheckout/components/volumeSliders.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/gsApp/views/amCheckout/components/volumeSliders.tsx b/static/gsApp/views/amCheckout/components/volumeSliders.tsx index 1409e7b14daeb5..f53fbe4538bd3e 100644 --- a/static/gsApp/views/amCheckout/components/volumeSliders.tsx +++ b/static/gsApp/views/amCheckout/components/volumeSliders.tsx @@ -22,7 +22,7 @@ import { isByteCategory, } from 'getsentry/utils/dataCategory'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; -import UnitTypeItem from 'getsentry/views/amCheckout/steps/unitTypeItem'; +import UnitTypeItem from 'getsentry/views/amCheckout/components/unitTypeItem'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import * as utils from 'getsentry/views/amCheckout/utils'; From 496c40aed891f6dc209446d913e5b89a78ff0280 Mon Sep 17 00:00:00 2001 From: isabellaenriquez Date: Fri, 14 Nov 2025 12:24:49 -0500 Subject: [PATCH 4/4] fix import --- .../gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx | 2 +- .../views/amCheckout/steps/reserveAdditionalVolume.spec.tsx | 2 +- static/gsApp/views/amCheckout/steps/setSpendLimit.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/static/gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx b/static/gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx index 621848c449186d..3a9f276334191b 100644 --- a/static/gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx +++ b/static/gsApp/views/amCheckout/components/checkoutSuccess.spec.tsx @@ -7,7 +7,7 @@ import {resetMockDate, setMockDate} from 'sentry-test/utils'; import {PreviewDataFixture} from 'getsentry/__fixtures__/previewData'; import {InvoiceItemType} from 'getsentry/types'; -import CheckoutSuccess from 'getsentry/views/amCheckout/checkoutSuccess'; +import CheckoutSuccess from 'getsentry/views/amCheckout/components/checkoutSuccess'; describe('CheckoutSuccess', () => { const bizPlan = PlanDetailsLookupFixture('am3_business')!; diff --git a/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.spec.tsx b/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.spec.tsx index 0263d7fc98f8ec..24fbfafde4797c 100644 --- a/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.spec.tsx +++ b/static/gsApp/views/amCheckout/steps/reserveAdditionalVolume.spec.tsx @@ -9,7 +9,7 @@ import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {MONTHLY} from 'getsentry/constants'; import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import {PlanTier, type Subscription} from 'getsentry/types'; -import ReserveAdditionalVolume from 'getsentry/views/amCheckout/reserveAdditionalVolume'; +import ReserveAdditionalVolume from 'getsentry/views/amCheckout/steps/reserveAdditionalVolume'; type SliderInfo = { billingInterval: string; diff --git a/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx b/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx index fe43af79e9b79f..84b5d710e9626e 100644 --- a/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx +++ b/static/gsApp/views/amCheckout/steps/setSpendLimit.tsx @@ -7,7 +7,7 @@ import type {OnDemandBudgets} from 'getsentry/types'; import {displayBudgetName} from 'getsentry/utils/billing'; import trackGetsentryAnalytics from 'getsentry/utils/trackGetsentryAnalytics'; import StepHeader from 'getsentry/views/amCheckout/components/stepHeader'; -import ReserveAdditionalVolume from 'getsentry/views/amCheckout/reserveAdditionalVolume'; +import ReserveAdditionalVolume from 'getsentry/views/amCheckout/steps/reserveAdditionalVolume'; import type {StepProps} from 'getsentry/views/amCheckout/types'; import { getTotalBudget,