Skip to content

chore(seer): Adjust copy + styling in checkout #93778

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
chore(seer): Adjust copy + styling in checkout
  • Loading branch information
isabellaenriquez committed Jun 17, 2025
commit fbbba63ba7bed99c9a716835bd5d4f9af77786d5
3 changes: 2 additions & 1 deletion static/gsApp/views/amCheckout/steps/planSelectRow.tsx
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor spacing issues i was asked to fix

Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ const PlanContainer = styled('div')`

const PriceContainer = styled('div')<{hasFeatures?: boolean}>`
display: grid;
gap: ${space(0.5)};
gap: 0px;
grid-template-rows: repeat(2, auto);
justify-items: end;

Expand Down Expand Up @@ -374,4 +374,5 @@ const EventPriceTag = styled(Tag)`
display: flex;
align-items: center;
line-height: normal;
margin-top: ${space(0.5)};
`;
219 changes: 147 additions & 72 deletions static/gsApp/views/amCheckout/steps/productSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,19 @@ import mediumStarDark from 'sentry-images/spot/product-select-star-m-dark.svg';
import smallStarLight from 'sentry-images/spot/product-select-star-s.svg';
import smallStarDark from 'sentry-images/spot/product-select-star-s-dark.svg';

import {Tag} from 'sentry/components/core/badge/tag';
import {Button} from 'sentry/components/core/button';
import PanelItem from 'sentry/components/panels/panelItem';
import {IconAdd, IconCheckmark, IconSeer} from 'sentry/icons';
import {t, tct} from 'sentry/locale';
import ConfigStore from 'sentry/stores/configStore';
import {useLegacyStore} from 'sentry/stores/useLegacyStore';
import {space} from 'sentry/styles/space';
import {DataCategory} from 'sentry/types/core';
import {toTitleCase} from 'sentry/utils/string/toTitleCase';
import type {Color} from 'sentry/utils/theme';

import {getSingularCategoryName} from 'getsentry/utils/dataCategory';
import formatCurrency from 'getsentry/utils/formatCurrency';
import {SelectableProduct, type StepProps} from 'getsentry/views/amCheckout/types';
import * as utils from 'getsentry/views/amCheckout/utils';
Expand Down Expand Up @@ -46,12 +49,27 @@ function ProductSelect({
color: theme.pink400 as Color,
gradientEndColor: theme.pink100 as Color,
buttonBorderColor: theme.pink200 as Color,
description: t('Detect and fix issues faster with our AI debugging agent.'),
features: [
t('Issue scan'),
t('Root cause analysis'),
t('Solution and code changes'),
],
description: (includedBudget: string) =>
tct(
'Detect and fix issues faster with [includedBudget]/mo in credits towards our AI agent',
{
includedBudget,
}
),
categoryInfo: {
[DataCategory.SEER_AUTOFIX]: {
description: t(
'Uses the latest AI models with Sentry data to find root causes & proposes PRs'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Uses the latest AI models with Sentry data to find root causes & proposes PRs'
'Uses the latest AI models with Sentry data to find root causes & propose PRs'

),
maxEventPriceDigits: 2,
},
[DataCategory.SEER_SCANNER]: {
description: t(
'Triages issues as they happen, automatically flagging the most important ones for followup'
),
maxEventPriceDigits: 3,
},
},
},
};
const billingInterval = utils.getShortInterval(activePlan.billingInterval);
Expand All @@ -67,17 +85,18 @@ function ProductSelect({
return null;
}

const cost = formatCurrency(
utils.getReservedPriceForReservedBudgetCategory({
plan: activePlan,
reservedBudgetCategory: productInfo.apiName,
})
);
const priceInCents = utils.getReservedPriceForReservedBudgetCategory({
plan: activePlan,
reservedBudgetCategory: productInfo.apiName,
});
const priceInDollars = utils.formatPrice({
cents: priceInCents,
});

// if no default budget, then the included budget is how much the customer is paying for the product
const includedBudget = productInfo.defaultBudget
? formatCurrency(productInfo.defaultBudget)
: cost;
const formattedMonthlyBudget = formatCurrency(
productInfo.defaultBudget ?? priceInCents
);

return (
<ProductOption
Expand Down Expand Up @@ -118,17 +137,48 @@ function ProductSelect({
})}
</ProductName>
</ProductLabel>
<p>{checkoutInfo.description}</p>
</Column>
<Column>
{checkoutInfo.features.map(feature => (
<Feature key={feature}>
<IconCheckmark color={checkoutInfo.color} />
{feature}
</Feature>
))}
<ProductDescription>
{checkoutInfo.description(formattedMonthlyBudget)}
</ProductDescription>
</Column>
<PriceContainer>
<PriceHeader>{t('Starts At')}</PriceHeader>
<Price>
<Currency>$</Currency>
<Amount>{priceInDollars}</Amount>
<BillingInterval>{`/${billingInterval}`}</BillingInterval>
</Price>
</PriceContainer>
</Row>
<FeatureRow>
{Object.entries(checkoutInfo.categoryInfo).map(([category, info]) => {
const pricingInfo = activePlan.planCategories[category as DataCategory];
const eventPrice = pricingInfo ? pricingInfo[1]?.onDemandPrice : null;
return (
<Feature key={category}>
<IconContainer>
<IconCheckmark color={checkoutInfo.color} />
</IconContainer>
<FeatureTitle key={category}>
<span>
{getSingularCategoryName({
plan: activePlan,
category: category as DataCategory,
hadCustomDynamicSampling: false,
title: true,
})}
</span>

{eventPrice && (
<EventPriceTag>{`${utils.displayUnitPrice({cents: eventPrice, minDigits: 2, maxDigits: info.maxEventPriceDigits})} / ${getSingularCategoryName({plan: activePlan, category: category as DataCategory, hadCustomDynamicSampling: false, capitalize: false})}`}</EventPriceTag>
)}
</FeatureTitle>
<div />
<FeatureDescription>{info.description}</FeatureDescription>
</Feature>
);
})}
</FeatureRow>
<Row>
<StyledButton>
<ButtonContent
Expand All @@ -149,30 +199,12 @@ function ProductSelect({
) : (
<Fragment>
<IconAdd />
{tct(' Add for [cost]/[billingInterval]', {
cost,
billingInterval,
})}
{t('Add to plan')}
</Fragment>
)}
</ButtonContent>
</StyledButton>
</Row>
<Row justifyContent="center">
<Subtitle>
{tct(
'Includes [includedBudget]/mo of credits for [productName] services. Additional usage is drawn from your [budgetTerm] budget.',
{
includedBudget,
budgetTerm:
activePlan.budgetTerm === 'pay-as-you-go'
? 'PAYG'
: activePlan.budgetTerm,
productName: toTitleCase(productInfo.productName),
}
)}
</Subtitle>
</Row>
<IllustrationContainer>
<Star1 src={prefersDarkMode ? largeStarDark : largeStarLight} />
<Star2 src={prefersDarkMode ? mediumStarDark : mediumStarLight} />
Expand Down Expand Up @@ -298,8 +330,7 @@ const Column = styled('div')<{alignItems?: string}>`
const Row = styled('div')<{justifyContent?: string}>`
display: flex;
gap: ${space(4)};
justify-content: ${p => p.justifyContent ?? 'flex-start'};
align-items: center;
justify-content: ${p => p.justifyContent ?? 'space-between'};
`;

const ProductLabel = styled('div')<{productColor: string}>`
Expand All @@ -316,11 +347,54 @@ const ProductName = styled('div')`
font-weight: 600;
`;

const Subtitle = styled('p')`
font-size: ${p => p.theme.fontSizeSmall};
const ProductDescription = styled('p')`
margin: ${space(0.5)} 0 ${space(2)};
font-weight: 600;
text-wrap: balance;
`;

const PriceContainer = styled(Column)`
gap: 0px;
`;

const PriceHeader = styled('div')`
color: ${p => p.theme.subText};
text-align: center;
margin: 0;
font-size: ${p => p.theme.fontSizeSmall};
text-transform: uppercase;
font-weight: bold;
`;

const Price = styled('div')`
display: inline-grid;
grid-template-columns: repeat(3, auto);
color: ${p => p.theme.textColor};
`;

const Currency = styled('span')`
padding-top: ${space(0.5)};
`;

const Amount = styled('span')`
font-size: 24px;
align-self: end;
`;

const BillingInterval = styled('span')`
font-size: ${p => p.theme.fontSizeMedium};
align-self: end;
padding-bottom: ${space(0.25)};
`;

const EventPriceTag = styled(Tag)`
display: flex;
align-items: center;
line-height: normal;
width: fit-content;
font-weight: normal;
`;

const IconContainer = styled('div')`
margin-right: ${space(1)};
`;

const StyledButton = styled(Button)`
Expand All @@ -335,40 +409,41 @@ const ButtonContent = styled('div')<{color: string}>`
color: ${p => p.color};
`;

const FeatureRow = styled('div')`
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: ${space(2)};
`;

const Feature = styled('div')`
display: grid;
grid-template-columns: auto auto;
font-size: ${p => p.theme.fontSizeSmall};
row-gap: ${space(0.5)};
`;

const FeatureTitle = styled('div')`
font-weight: 600;
display: flex;
gap: ${space(1)};
align-items: center;
align-content: center;
svg {
flex-shrink: 0;
flex-wrap: wrap;

> span {
margin-right: ${space(0.5)};
}
font-size: ${p => p.theme.fontSizeSmall};
`;

const FeatureDescription = styled('div')`
text-wrap: balance;
`;

const IllustrationContainer = styled('div')`
display: none;

@media (min-width: ${p => p.theme.breakpoints.small}) {
display: block;
position: absolute;
bottom: 84px;
right: 12px;
height: 175px;
width: 200px;
overflow: hidden;
border-radius: 0 ${p => p.theme.borderRadius} ${p => p.theme.borderRadius} 0;
pointer-events: none;
}

@media (min-width: ${p => p.theme.breakpoints.large}) {
display: none;
}

@media (min-width: ${p => p.theme.breakpoints.xlarge}) {
@media (min-width: ${p => p.theme.breakpoints.xsmall}) {
display: block;
position: absolute;
bottom: 84px;
bottom: 0px;
right: 12px;
height: 175px;
width: 200px;
Expand Down
Loading