diff --git a/src/sentry/organizations/absolute_url.py b/src/sentry/organizations/absolute_url.py index a4e2c62a7154ba..854c613b321452 100644 --- a/src/sentry/organizations/absolute_url.py +++ b/src/sentry/organizations/absolute_url.py @@ -27,6 +27,7 @@ re.compile(r"^\/?(?!settings)[^/]+\/([^/]+)\/getting-started\/(.*)"), r"/getting-started/\1/\2", ), + (re.compile(r"^\/?checkout\/[^/]+\/?.*"), r"/checkout/"), ] diff --git a/static/app/utils/url/normalizeUrl.spec.tsx b/static/app/utils/url/normalizeUrl.spec.tsx index 0122580ef2335d..4618b1e9ed8edb 100644 --- a/static/app/utils/url/normalizeUrl.spec.tsx +++ b/static/app/utils/url/normalizeUrl.spec.tsx @@ -79,6 +79,9 @@ describe('normalizeUrl', () => { ['/onboarding/acme/', '/onboarding/'], ['/onboarding/acme/project/', '/onboarding/project/'], + ['/checkout/', '/checkout/'], + ['/checkout/acme/', '/checkout/'], + ['/organizations/new/', '/organizations/new/'], ['/organizations/albertos-organizations/issues/', '/issues/'], [ diff --git a/static/app/utils/url/normalizeUrl.tsx b/static/app/utils/url/normalizeUrl.tsx index 8429cab1329af4..137eed67ba7a35 100644 --- a/static/app/utils/url/normalizeUrl.tsx +++ b/static/app/utils/url/normalizeUrl.tsx @@ -22,6 +22,7 @@ const NORMALIZE_PATTERNS: Array<[pattern: RegExp, replacement: string]> = [ // Handles /org-slug/project-slug/getting-started/platform/ -> /getting-started/project-slug/platform/ [/^\/?(?!settings)[^/]+\/([^/]+)\/getting-started\/(.*)/, '/getting-started/$1/$2'], [/^\/?accept-terms\/[^/]*\/?$/, '/accept-terms/'], + [/^\/?checkout\/[^/]+\/?.*$/, '/checkout/'], ]; type NormalizeUrlOptions = { diff --git a/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx b/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx index 7cf5c9e2de11ee..fd41b3415571bf 100644 --- a/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx +++ b/static/app/views/performance/newTraceDetails/traceTypeWarnings/errorsOnlyWarnings.tsx @@ -174,7 +174,7 @@ function PerformanceQuotaExceededWarning(props: ErrorOnlyWarningsProps) { props.tree.shape ); browserHistory.push({ - pathname: `/settings/billing/checkout/?referrer=trace-view`, + pathname: `/checkout/?referrer=trace-view`, query: { skipBundles: true, }, diff --git a/static/gsApp/components/addEventsCTA.tsx b/static/gsApp/components/addEventsCTA.tsx index a08acfd2d4d207..c9ec499a6076b2 100644 --- a/static/gsApp/components/addEventsCTA.tsx +++ b/static/gsApp/components/addEventsCTA.tsx @@ -108,7 +108,7 @@ function AddEventsCTA(props: Props) { }, 0); }; - const checkoutUrl = `/settings/${organization.slug}/billing/checkout/?referrer=${referrer}`; + const checkoutUrl = `/checkout/?referrer=${referrer}`; const subscriptionUrl = `/settings/${organization.slug}/billing/overview/`; switch (action) { diff --git a/static/gsApp/components/ai/AiSetupDataConsent.tsx b/static/gsApp/components/ai/AiSetupDataConsent.tsx index 8a08595ea65307..3a68c34ffff525 100644 --- a/static/gsApp/components/ai/AiSetupDataConsent.tsx +++ b/static/gsApp/components/ai/AiSetupDataConsent.tsx @@ -84,7 +84,7 @@ function AiSetupDataConsent({groupId}: AiSetupDataConsentProps) { const autofixAcknowledgeMutation = useSeerAcknowledgeMutation(); function handlePurchaseSeer() { - navigate(`/settings/billing/checkout/?referrer=ai_setup_data_consent`); + navigate(`/checkout/?referrer=ai_setup_data_consent`); } function handleAddBudget() { @@ -93,7 +93,7 @@ function AiSetupDataConsent({groupId}: AiSetupDataConsentProps) { } if (isPerCategoryOnDemand) { // Seer does not support per category on demand budgets, so we need to redirect to the checkout page to prompt the user to switch - navigate(`/settings/billing/checkout/?referrer=ai_setup_data_consent#step3`); + navigate('/checkout/?referrer=ai_setup_data_consent#step3'); return; } openOnDemandBudgetEditModal({ diff --git a/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx b/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx index 6d1226c69bb392..af2a6da0739391 100644 --- a/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx +++ b/static/gsApp/components/crons/cronsBannerUpgradeCTA.tsx @@ -21,7 +21,7 @@ export function CronsBannerUpgradeCTA({hasBillingAccess}: UpgradeCTAProps) { if (hasBillingAccess) { return ( diff --git a/static/gsApp/components/gsBanner.tsx b/static/gsApp/components/gsBanner.tsx index 488e667247563b..634b4f8a35f845 100644 --- a/static/gsApp/components/gsBanner.tsx +++ b/static/gsApp/components/gsBanner.tsx @@ -1014,7 +1014,7 @@ class GSBanner extends Component { // if there are deactivated members, than anyone who doesn't have org:billing will be // prevented from accessing this view anyways cause they will be deactivated if (isOverMemberLimit && !deactivatedMemberDismissed && this.hasBillingPerms) { - const checkoutUrl = `/settings/${organization.slug}/billing/checkout/?referrer=deactivated_member_header`; + const checkoutUrl = '/checkout/?referrer=deactivated_member_header'; const wrappedNumber = {membersDeactivatedFromLimit}; // only disabling members if the plan allows exactly one member return ( diff --git a/static/gsApp/components/partnerPlanEndingBanner.tsx b/static/gsApp/components/partnerPlanEndingBanner.tsx index 945e81276a1f6e..307d7a13a45813 100644 --- a/static/gsApp/components/partnerPlanEndingBanner.tsx +++ b/static/gsApp/components/partnerPlanEndingBanner.tsx @@ -81,7 +81,7 @@ function PartnerPlanEndingBanner({ analyticsEventName="Partner Plan Ending Banner: Manage Subscription" size="md" onClick={() => handleAnalytics()} - to={`/settings/${organization.slug}/billing/checkout/?referrer=partner_plan_ending_banner`} + to="/checkout/?referrer=partner_plan_ending_banner" > {t('Upgrade to %s', planToUpgradeTo)} diff --git a/static/gsApp/components/partnerPlanEndingModal.tsx b/static/gsApp/components/partnerPlanEndingModal.tsx index 5ad0148b66ea43..d18a759a40d841 100644 --- a/static/gsApp/components/partnerPlanEndingModal.tsx +++ b/static/gsApp/components/partnerPlanEndingModal.tsx @@ -157,7 +157,7 @@ function PartnerPlanEndingModal({organization, subscription, closeModal}: Props) {hasBillingAccess ? ( diff --git a/static/gsApp/components/performance/quotaExceededAlert.spec.tsx b/static/gsApp/components/performance/quotaExceededAlert.spec.tsx index d484d8dbebe246..e17b6941613df6 100644 --- a/static/gsApp/components/performance/quotaExceededAlert.spec.tsx +++ b/static/gsApp/components/performance/quotaExceededAlert.spec.tsx @@ -121,10 +121,7 @@ describe('Renders QuotaExceededAlert correctly for spans', () => { expect( screen.getByRole('link', {name: /increase your on-demand budget/}) - ).toHaveAttribute( - 'href', - '/settings/billing/checkout/?referrer=trace-view&skipBundles=true' - ); + ).toHaveAttribute('href', '/checkout/?referrer=trace-view&skipBundles=true'); }); it('renders alert when quota is exceeded for logs', async () => { @@ -176,9 +173,6 @@ describe('Renders QuotaExceededAlert correctly for spans', () => { expect( screen.getByRole('link', {name: /increase your on-demand budget/}) - ).toHaveAttribute( - 'href', - '/settings/billing/checkout/?referrer=trace-view&skipBundles=true' - ); + ).toHaveAttribute('href', '/checkout/?referrer=trace-view&skipBundles=true'); }); }); diff --git a/static/gsApp/components/performance/quotaExceededAlert.tsx b/static/gsApp/components/performance/quotaExceededAlert.tsx index f8b47da50133ac..3870c20566a0c5 100644 --- a/static/gsApp/components/performance/quotaExceededAlert.tsx +++ b/static/gsApp/components/performance/quotaExceededAlert.tsx @@ -79,7 +79,7 @@ function useQuotaExceededAlertMessage( const billingPageLink = ( { browserHistory.push( - normalizeUrl(`/settings/${organization.slug}/billing/checkout/`) + normalizeUrl('/checkout/?referrer=product-trial-alert') ); }} > @@ -212,9 +212,7 @@ function ProductTrialAlert(props: ProductTrialAlertProps) { {t('Manage Subscription')} diff --git a/static/gsApp/components/replayOnboardingCTA.tsx b/static/gsApp/components/replayOnboardingCTA.tsx index 4da04d3ef2cb01..cbaa382505c838 100644 --- a/static/gsApp/components/replayOnboardingCTA.tsx +++ b/static/gsApp/components/replayOnboardingCTA.tsx @@ -95,7 +95,7 @@ function ReplayOnboardingCTAUpsell({ if (hasBillingAccess) { // Redirect the user to the subscriptions page, where they will find important information. // If they wish to update their plan, we ask them to contact our sales/support team. - redirectToManage(organization); + redirectToManage(); } return; } diff --git a/static/gsApp/components/upgradeNowModal/actionButtons.tsx b/static/gsApp/components/upgradeNowModal/actionButtons.tsx index 2ab4047b4750b0..8129a37581ba0f 100644 --- a/static/gsApp/components/upgradeNowModal/actionButtons.tsx +++ b/static/gsApp/components/upgradeNowModal/actionButtons.tsx @@ -80,7 +80,7 @@ function ActionButtons({ }); } catch (err) { Sentry.captureException(err); - redirectToManage(organization); + redirectToManage(); } }, [ api, @@ -114,7 +114,7 @@ function ActionButtons({ }); }, onError: () => { - redirectToManage(organization); + redirectToManage(); }, }); }, [api, organization, subscription, surface, onComplete]); @@ -142,7 +142,7 @@ function ActionButtons({ {t('Update Now')} {t('Manage Subscription')} diff --git a/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx b/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx index 0787c94139478b..6df7532db2374d 100644 --- a/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx +++ b/static/gsApp/components/upgradeNowModal/modalSamePrice.tsx @@ -75,7 +75,7 @@ function UpgradeNowModal({ }); } catch (err) { Sentry.captureException(err); - redirectToManage(organization); + redirectToManage(); addErrorMessage( t( 'Oops! Unable to update Subscription automatically. Click through to update manually.' diff --git a/static/gsApp/components/upgradeNowModal/utils.tsx b/static/gsApp/components/upgradeNowModal/utils.tsx index 6d370d333614e1..90cea27a10d981 100644 --- a/static/gsApp/components/upgradeNowModal/utils.tsx +++ b/static/gsApp/components/upgradeNowModal/utils.tsx @@ -1,11 +1,10 @@ -import type {Organization} from 'sentry/types/organization'; import {browserHistory} from 'sentry/utils/browserHistory'; import normalizeUrl from 'sentry/utils/url/normalizeUrl'; -export function redirectToManage(organization: Organization) { +export function redirectToManage() { browserHistory.replace( normalizeUrl({ - pathname: `/settings/${organization.slug}/billing/checkout/`, + pathname: '/checkout/', query: { referrer: 'replay_onboard-error-redirect', }, diff --git a/static/gsApp/components/upgradeOrTrialButton.tsx b/static/gsApp/components/upgradeOrTrialButton.tsx index 6b47e8a7ff2f75..d9c17c703f9e31 100644 --- a/static/gsApp/components/upgradeOrTrialButton.tsx +++ b/static/gsApp/components/upgradeOrTrialButton.tsx @@ -153,7 +153,7 @@ function UpgradeOrTrialButton({ if (hasAccess) { // send self-serve directly to checkout const baseUrl = subscription.canSelfServe - ? `/settings/${slug}/billing/checkout/` + ? '/checkout/' : `/settings/${slug}/billing/overview/`; return ( diff --git a/static/gsApp/components/upsellModal/footer.spec.tsx b/static/gsApp/components/upsellModal/footer.spec.tsx index 8d673de636d71a..c8a51243c774f7 100644 --- a/static/gsApp/components/upsellModal/footer.spec.tsx +++ b/static/gsApp/components/upsellModal/footer.spec.tsx @@ -9,7 +9,7 @@ describe('Business Landing Footer', () => { const organization = OrganizationFixture({access: ['org:billing']}); const subscription = SubscriptionFixture({organization}); - const checkoutPage = `/settings/${organization.slug}/billing/checkout/?referrer=upgrade-business-landing.unknown`; + const checkoutPage = '/checkout/?referrer=upgrade-business-landing.unknown'; beforeEach(() => { MockApiClient.clearMockResponses(); diff --git a/static/gsApp/components/upsellProvider.spec.tsx b/static/gsApp/components/upsellProvider.spec.tsx index f269a7cbc4543c..23e02f07f12486 100644 --- a/static/gsApp/components/upsellProvider.spec.tsx +++ b/static/gsApp/components/upsellProvider.spec.tsx @@ -120,7 +120,7 @@ describe('UpsellProvider', () => { expect(router.location).toEqual( expect.objectContaining({ - pathname: `/settings/${org.slug}/billing/checkout/`, + pathname: '/checkout/', query: { referrer: 'upsell-test-abc', }, diff --git a/static/gsApp/components/upsellProvider.tsx b/static/gsApp/components/upsellProvider.tsx index 0aa522ec7d990f..db722937e72ad2 100644 --- a/static/gsApp/components/upsellProvider.tsx +++ b/static/gsApp/components/upsellProvider.tsx @@ -213,7 +213,7 @@ function UpsellProvider({ } else { // for self-serve can send them to checkout const baseUrl = subscription.canSelfServe - ? `/settings/${organization.slug}/billing/checkout/` + ? '/checkout/' : `/settings/${organization.slug}/billing/overview/`; browserHistory.push(`${normalizeUrl(baseUrl)}?referrer=upsell-${source}`); } diff --git a/static/gsApp/hooks/dashboardsLimit.tsx b/static/gsApp/hooks/dashboardsLimit.tsx index 18e0facaecd1ee..6afae13a3c2a3c 100644 --- a/static/gsApp/hooks/dashboardsLimit.tsx +++ b/static/gsApp/hooks/dashboardsLimit.tsx @@ -54,9 +54,7 @@ export function useDashboardsLimit(): UseDashboardsLimitResult { ? tct( 'You have reached the maximum number of Dashboards available on your plan. To add more, [link:upgrade your plan]', { - link: ( - - ), + link: , } ) : null; diff --git a/static/gsApp/hooks/rootRoutes.tsx b/static/gsApp/hooks/rootRoutes.tsx index 9e13c9b649f6ce..ab19c28ff7f737 100644 --- a/static/gsApp/hooks/rootRoutes.tsx +++ b/static/gsApp/hooks/rootRoutes.tsx @@ -1,21 +1,37 @@ import {makeLazyloadComponent as make} from 'sentry/makeLazyloadComponent'; import type {SentryRouteObject} from 'sentry/router/types'; import errorHandler from 'sentry/utils/errorHandler'; +import withDomainRedirect from 'sentry/utils/withDomainRedirect'; +import withDomainRequired from 'sentry/utils/withDomainRequired'; import OrganizationSubscriptionContext from 'getsentry/components/organizationSubscriptionContext'; const rootRoutes = (): SentryRouteObject => ({ children: [ { - // TODO(checkout v3): rename this to /checkout/ when the legacy checkout route is removed - path: '/checkout-v3/', + path: '/checkout/', component: errorHandler(OrganizationSubscriptionContext), - deprecatedRouteProps: true, customerDomainOnlyRoute: true, + deprecatedRouteProps: true, + children: [ + { + index: true, + component: withDomainRequired( + make(() => import('getsentry/views/decideCheckout')) + ), + }, + ], + }, + { + path: '/checkout/:orgId/', + component: errorHandler(OrganizationSubscriptionContext), + deprecatedRouteProps: true, children: [ { index: true, - component: make(() => import('getsentry/views/decideCheckout')), + component: withDomainRedirect( + make(() => import('getsentry/views/decideCheckout')) + ), }, ], }, diff --git a/static/gsApp/hooks/settingsRoutes.tsx b/static/gsApp/hooks/settingsRoutes.tsx index 77f8871a2fb077..56bda6722a0471 100644 --- a/static/gsApp/hooks/settingsRoutes.tsx +++ b/static/gsApp/hooks/settingsRoutes.tsx @@ -23,18 +23,10 @@ const settingsRoutes = (): SentryRouteObject => ({ redirectTo: 'overview/', }, { - // TODO(checkout v3): This should be removed when checkout v3 is GA'd + // NOTE: This route is retained for legacy linking path: 'checkout/', name: 'Change', - component: errorHandler(SubscriptionContext), - deprecatedRouteProps: true, - children: [ - { - index: true, - component: make(() => import('../views/decideCheckout')), - deprecatedRouteProps: true, - }, - ], + redirectTo: '/checkout/', }, { path: 'cancel/', diff --git a/static/gsApp/hooks/targetedOnboardingHeader.tsx b/static/gsApp/hooks/targetedOnboardingHeader.tsx index 708fb148e7a1a6..71eb9b79d9d0b8 100644 --- a/static/gsApp/hooks/targetedOnboardingHeader.tsx +++ b/static/gsApp/hooks/targetedOnboardingHeader.tsx @@ -55,9 +55,7 @@ function TargetedOnboardingHeader({source, subscription}: Props) { {cta} } diff --git a/static/gsApp/views/amCheckout/checkoutSuccess.tsx b/static/gsApp/views/amCheckout/checkoutSuccess.tsx index a3ae4d71211d73..5b536af054c71c 100644 --- a/static/gsApp/views/amCheckout/checkoutSuccess.tsx +++ b/static/gsApp/views/amCheckout/checkoutSuccess.tsx @@ -606,7 +606,7 @@ function CheckoutSuccess({ {t('Edit plan')} diff --git a/static/gsApp/views/amCheckout/index.tsx b/static/gsApp/views/amCheckout/index.tsx index 2992a18449d49a..ef7a2cfd1f43ed 100644 --- a/static/gsApp/views/amCheckout/index.tsx +++ b/static/gsApp/views/amCheckout/index.tsx @@ -6,7 +6,6 @@ import {loadStripe} from '@stripe/stripe-js'; import type {Location} from 'history'; import isEqual from 'lodash/isEqual'; import moment from 'moment-timezone'; -import * as qs from 'query-string'; import type {Client} from 'sentry/api'; import {Alert} from 'sentry/components/core/alert'; @@ -134,21 +133,6 @@ class AMCheckout extends Component { ) { props.onToggleLegacy(props.subscription.planTier); } - const query = props.location?.query; - const queryString = - query && Object.keys(query).length > 0 ? `?${qs.stringify(query)}` : ''; - - // TODO(checkout v3): remove these checks once checkout v3 is GA'd and we've remove the legacy checkout route - if (props.location?.pathname.includes('checkout-v3') && !props.isNewCheckout) { - props.navigate( - `/settings/${props.organization.slug}/billing/checkout/${queryString}`, - { - replace: true, - } - ); - } else if (!props.location?.pathname.includes('checkout-v3') && props.isNewCheckout) { - props.navigate(`/checkout-v3/${queryString}`, {replace: true}); - } let step = 1; if (props.location?.hash) { const stepMatch = /^#step(\d)$/.exec(props.location.hash); diff --git a/static/gsApp/views/spendAllocations/index.tsx b/static/gsApp/views/spendAllocations/index.tsx index 4b07b6bdfb4e2d..50d1ff5eda6de6 100644 --- a/static/gsApp/views/spendAllocations/index.tsx +++ b/static/gsApp/views/spendAllocations/index.tsx @@ -371,7 +371,7 @@ export function SpendAllocationsRoot({organization, subscription}: Props) { aria-label={t('Manage Subscription')} size="sm" style={{marginRight: space(1)}} - to={`/settings/${organization.slug}/billing/checkout/?referrer=spend_allocations`} + to="/checkout/?referrer=spend_allocations" > {t('Manage Subscription')} diff --git a/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx b/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx index 12c2ec45539851..185e9c2eb086fc 100644 --- a/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx +++ b/static/gsApp/views/subscriptionPage/promotions/performanceReservedTransactionsPromo.tsx @@ -87,7 +87,7 @@ function openPerformanceReservedTransactionsDiscountModal({ onAccept: () => { navigate( normalizeUrl({ - pathname: `/settings/${organization.slug}/billing/checkout/`, + pathname: '/checkout/', query: { skipBundles: true, }, diff --git a/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx b/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx index 3fecb67e9b2d39..9386c06ef4df27 100644 --- a/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx +++ b/static/gsApp/views/subscriptionPage/subscriptionHeader.tsx @@ -146,7 +146,7 @@ function SubscriptionHeader(props: Props) { {subscription.canSelfServe && hasBillingPerms && ( {t('Manage Subscription')} @@ -210,7 +210,7 @@ function SubscriptionHeader(props: Props) { {subscription.canSelfServe && hasBillingPerms && ( diff --git a/static/gsApp/views/subscriptionPage/usageTotals.tsx b/static/gsApp/views/subscriptionPage/usageTotals.tsx index 7e94c15e75e040..ae9a8c0b676006 100644 --- a/static/gsApp/views/subscriptionPage/usageTotals.tsx +++ b/static/gsApp/views/subscriptionPage/usageTotals.tsx @@ -1081,7 +1081,7 @@ export function CombinedUsageTotals({ } > {tct('Enable [productName]', {