Skip to content

Commit edaddb1

Browse files
fix(checkout v3): UI tweaks (#99554)
Making some broad stroke changes to the new checkout UI - making everything more "chonky" - slightly changed app structure and wrapping - adjustments to color, typography, spacing --------- Co-authored-by: isabellaenriquez <[email protected]>
1 parent e23a773 commit edaddb1

14 files changed

+771
-646
lines changed

static/gsApp/views/amCheckout/billingCycleSelectCard.tsx

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@ import styled from '@emotion/styled';
33
import moment from 'moment-timezone';
44

55
import {Tag} from 'sentry/components/core/badge/tag';
6-
import {Flex} from 'sentry/components/core/layout';
7-
import {Radio} from 'sentry/components/core/radio';
6+
import {Container, Flex} from 'sentry/components/core/layout';
7+
import {Heading, Text} from 'sentry/components/core/text';
88
import {t, tct} from 'sentry/locale';
99

1010
import {ANNUAL} from 'getsentry/constants';
1111
import type {Plan, Subscription} from 'getsentry/types';
12+
import CheckoutOption from 'getsentry/views/amCheckout/checkoutOption';
1213
import type {CheckoutFormData} from 'getsentry/views/amCheckout/types';
1314

1415
type BillingCycleSelectCardProps = {
@@ -73,77 +74,56 @@ function BillingCycleSelectCard({
7374
: t('Cancel anytime');
7475

7576
return (
76-
<BillingCycleOption
77-
data-test-id={`billing-cycle-option-${plan.contractInterval}`}
77+
<CheckoutOption
78+
dataTestId={`billing-cycle-option-${plan.contractInterval}`}
7879
isSelected={isSelected}
7980
onClick={onCycleSelect}
81+
ariaLabel={t('%s billing cycle', intervalName)}
82+
ariaRole="radio"
8083
>
81-
<div>
82-
<Flex align="center" gap="sm">
83-
<BillingInterval>{intervalName}</BillingInterval>
84-
{isAnnual && <Tag type="success">{t('save 10%')}</Tag>}
84+
<Flex align="start" justify="between" gap="md" padding="xl">
85+
<Container paddingTop="2xs">
86+
<RadioMarker isSelected={isSelected} />
87+
</Container>
88+
<Flex direction="column" gap="sm" width="100%">
89+
<Flex align="center" gap="sm">
90+
<Heading as="h3" variant="primary">
91+
{intervalName}
92+
</Heading>
93+
{isAnnual && <Tag type="promotion">{t('save 10%')}</Tag>}
94+
</Flex>
95+
<Flex align="center" gap="md">
96+
{formattedPriceBeforeDiscount && (
97+
<Text
98+
variant={'muted'}
99+
strikethrough
100+
size="2xl"
101+
>{`$${formattedPriceBeforeDiscount}`}</Text>
102+
)}
103+
<Text
104+
size="2xl"
105+
bold
106+
variant="primary"
107+
>{`$${formattedPriceAfterDiscount}`}</Text>
108+
</Flex>
109+
<Flex direction="column" gap="xs" paddingTop="xs">
110+
<Text variant="muted">{cycleInfo}</Text>
111+
<Text variant="muted">{additionalInfo}</Text>
112+
</Flex>
85113
</Flex>
86-
<Flex align="center" gap="sm">
87-
{formattedPriceBeforeDiscount && (
88-
<PriceBeforeDiscount>{`$${formattedPriceBeforeDiscount}`}</PriceBeforeDiscount>
89-
)}
90-
<Price>{`$${formattedPriceAfterDiscount}`}</Price>
91-
</Flex>
92-
<Description>{cycleInfo}</Description>
93-
<Description>{additionalInfo}</Description>
94-
</div>
95-
<StyledRadio
96-
readOnly
97-
id={plan.contractInterval}
98-
name="billing-cycle"
99-
aria-label={`${intervalName} billing cycle`}
100-
value={plan.contractInterval}
101-
checked={isSelected}
102-
onClick={onCycleSelect}
103-
onKeyDown={e => {
104-
if (e.key === 'Enter') {
105-
onCycleSelect();
106-
}
107-
}}
108-
/>
109-
</BillingCycleOption>
114+
</Flex>
115+
</CheckoutOption>
110116
);
111117
}
112118

113119
export default BillingCycleSelectCard;
114120

115-
const BillingCycleOption = styled('div')<{isSelected: boolean}>`
116-
background: ${p => (p.isSelected ? `${p.theme.active}05` : p.theme.background)};
117-
color: ${p => (p.isSelected ? p.theme.activeText : p.theme.textColor)};
118-
border-radius: ${p => p.theme.borderRadius};
119-
border: 1px solid ${p => (p.isSelected ? p.theme.active : p.theme.border)};
120-
cursor: pointer;
121-
122-
display: grid;
123-
padding: ${p => p.theme.space.xl};
124-
grid-template-columns: 1fr max-content;
125-
`;
126-
127-
const BillingInterval = styled('div')`
128-
font-size: ${p => p.theme.fontSize.lg};
129-
font-weight: ${p => p.theme.fontWeight.bold};
130-
`;
131-
132-
const Price = styled('div')`
133-
font-size: ${p => p.theme.fontSize.xl};
134-
font-weight: ${p => p.theme.fontWeight.bold};
135-
`;
136-
137-
const PriceBeforeDiscount = styled(Price)`
138-
text-decoration: line-through;
139-
color: ${p => p.theme.subText};
140-
`;
141-
142-
const Description = styled('div')`
143-
font-size: ${p => p.theme.fontSize.sm};
144-
color: ${p => p.theme.subText};
145-
`;
146-
147-
const StyledRadio = styled(Radio)`
121+
const RadioMarker = styled('div')<{isSelected?: boolean}>`
122+
width: ${p => p.theme.space.xl};
123+
height: ${p => p.theme.space.xl};
124+
border-radius: ${p => p.theme.space['3xl']};
148125
background: ${p => p.theme.background};
126+
border-color: ${p => (p.isSelected ? p.theme.tokens.border.accent : p.theme.border)};
127+
border-width: ${p => (p.isSelected ? '4px' : '1px')};
128+
border-style: solid;
149129
`;

static/gsApp/views/amCheckout/cart.tsx

Lines changed: 66 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Alert} from 'sentry/components/core/alert';
66
import {Tag} from 'sentry/components/core/badge/tag';
77
import {Button} from 'sentry/components/core/button';
88
import {Flex} from 'sentry/components/core/layout';
9+
import {Heading, Text} from 'sentry/components/core/text';
910
import Panel from 'sentry/components/panels/panel';
1011
import Placeholder from 'sentry/components/placeholder';
1112
import {IconChevron, IconLightning, IconLock} from 'sentry/icons';
@@ -133,11 +134,11 @@ function ItemsSummary({activePlan, formData}: ItemsSummaryProps) {
133134
<IconContainer>{getPlanIcon(activePlan)}</IconContainer>
134135
<Flex direction="column" gap="xs">
135136
<ItemFlex>
136-
<strong>{tct('[name] Plan', {name: activePlan.name})}</strong>
137-
<div>
137+
<Text bold>{tct('[name] Plan', {name: activePlan.name})}</Text>
138+
<Text>
138139
{utils.displayPrice({cents: activePlan.totalPrice})}
139140
{`/${shortInterval}`}
140-
</div>
141+
</Text>
141142
</ItemFlex>
142143
{activePlan.categories
143144
.filter(
@@ -166,7 +167,7 @@ function ItemsSummary({activePlan, formData}: ItemsSummaryProps) {
166167
return (
167168
<ItemFlex key={category}>
168169
<div>
169-
{isPaygOnly ? '' : `${formattedReserved} `}
170+
<Text>{isPaygOnly ? '' : `${formattedReserved} `}</Text>
170171
{reserved === 1 && category !== DataCategory.ATTACHMENTS
171172
? getSingularCategoryName({
172173
plan: activePlan,
@@ -682,29 +683,34 @@ function Cart({
682683
onToggle={setChangesIsOpen}
683684
organization={organization}
684685
/>
685-
<PlanSummaryHeader isOpen={summaryIsOpen} shouldShadow={changesIsOpen}>
686-
<Title>{t('Plan Summary')}</Title>
687-
<Flex gap="xs" align="center">
688-
<OrgSlug>{organization.slug.toUpperCase()}</OrgSlug>
689-
<Button
690-
aria-label={`${summaryIsOpen ? 'Hide' : 'Show'} plan summary`}
691-
onClick={() => setSummaryIsOpen(!summaryIsOpen)}
692-
borderless
693-
icon={<IconChevron direction={summaryIsOpen ? 'up' : 'down'} />}
694-
/>
695-
</Flex>
696-
</PlanSummaryHeader>
697-
{summaryIsOpen && (
698-
<div data-test-id="plan-summary">
699-
<ItemsSummary activePlan={activePlan} formData={formData} />
700-
<SubtotalSummary
701-
activePlan={activePlan}
702-
formData={formData}
703-
previewDataLoading={previewState.isLoading}
704-
renewalDate={previewState.renewalDate}
705-
/>
706-
</div>
707-
)}
686+
<PlanSummary>
687+
<PlanSummaryHeader isOpen={summaryIsOpen}>
688+
<Heading as="h2" textWrap="nowrap">
689+
{t('Plan Summary')}
690+
</Heading>
691+
<Flex gap="xs" align="center">
692+
<OrgSlug>{organization.slug.toUpperCase()}</OrgSlug>
693+
<Button
694+
aria-label={summaryIsOpen ? t('Hide plan summary') : t('Show plan summary')}
695+
onClick={() => setSummaryIsOpen(!summaryIsOpen)}
696+
borderless
697+
size="xs"
698+
icon={<IconChevron direction={summaryIsOpen ? 'up' : 'down'} />}
699+
/>
700+
</Flex>
701+
</PlanSummaryHeader>
702+
{summaryIsOpen && (
703+
<PlanSummaryContents data-test-id="plan-summary">
704+
<ItemsSummary activePlan={activePlan} formData={formData} />
705+
<SubtotalSummary
706+
activePlan={activePlan}
707+
formData={formData}
708+
previewDataLoading={previewState.isLoading}
709+
renewalDate={previewState.renewalDate}
710+
/>
711+
</PlanSummaryContents>
712+
)}
713+
</PlanSummary>
708714
<TotalSummary
709715
activePlan={activePlan}
710716
billedTotal={previewState.billedTotal}
@@ -729,39 +735,48 @@ export default Cart;
729735
const CartContainer = styled(Panel)`
730736
display: flex;
731737
flex-direction: column;
738+
border-bottom: ${p => (p.theme.isChonk ? '3px' : '1px')} solid ${p => p.theme.border};
739+
padding-bottom: ${p => p.theme.space.xl};
740+
741+
> *:first-child {
742+
border-bottom: 1px solid ${p => p.theme.border};
743+
}
732744
`;
733745

734746
const SummarySection = styled('div')`
735747
display: flex;
736748
flex-direction: column;
737-
padding: 0 ${p => p.theme.space.xl} ${p => p.theme.space['2xl']};
749+
padding: 0 ${p => p.theme.space.xl};
738750
739751
& > *:not(:last-child) {
740752
margin-bottom: ${p => p.theme.space.xl};
741753
}
742754
743-
border-bottom: 1px solid ${p => p.theme.border};
744-
745755
&:not(:first-child) {
746756
padding-top: ${p => p.theme.space['2xl']};
747757
}
748758
`;
749759

750-
const Title = styled('h1')`
751-
font-size: ${p => p.theme.fontSize.xl};
752-
font-weight: ${p => p.theme.fontWeight.bold};
753-
margin: 0;
754-
text-wrap: nowrap;
760+
const PlanSummary = styled('div')`
761+
&:not(:first-child) {
762+
border-top: 1px solid ${p => p.theme.border};
763+
}
755764
`;
756765

757-
const PlanSummaryHeader = styled('div')<{isOpen: boolean; shouldShadow: boolean}>`
766+
const PlanSummaryHeader = styled('div')<{isOpen: boolean}>`
758767
display: flex;
759768
justify-content: space-between;
760769
align-items: center;
761-
padding: ${p => p.theme.space.xl};
762-
border-bottom: ${p => (p.isOpen ? 'none' : `1px solid ${p.theme.border}`)};
763-
box-shadow: ${p => (p.shouldShadow ? '0 -5px 5px #00000010' : 'none')};
764770
gap: ${p => p.theme.space.sm};
771+
padding: ${p => p.theme.space.lg} ${p => p.theme.space.xl};
772+
`;
773+
774+
const PlanSummaryContents = styled('div')`
775+
> *:last-child {
776+
margin-top: ${p => p.theme.space.lg};
777+
padding: ${p => p.theme.space.xl} ${p => p.theme.space.xl};
778+
border-top: 1px solid ${p => p.theme.border};
779+
}
765780
`;
766781

767782
const OrgSlug = styled('div')`
@@ -788,11 +803,21 @@ const ItemFlex = styled('div')`
788803
justify-content: space-between;
789804
align-items: center;
790805
gap: ${p => p.theme.space['3xl']};
806+
line-height: 100%;
807+
808+
> * {
809+
margin-bottom: ${p => p.theme.space.xs};
810+
}
811+
812+
&:not(:first-child) {
813+
color: ${p => p.theme.subText};
814+
}
791815
`;
792816

793817
const IconContainer = styled('div')`
794818
display: flex;
795819
align-items: center;
820+
margin-top: 1px;
796821
`;
797822

798823
const RenewalDate = styled('div')`
@@ -806,12 +831,12 @@ const DueToday = styled('div')`
806831
`;
807832

808833
const DueTodayPrice = styled('div')`
809-
font-size: ${p => p.theme.fontSize.lg};
834+
font-size: ${p => p.theme.fontSize.md};
810835
`;
811836

812837
const DueTodayAmount = styled('span')`
813838
font-weight: ${p => p.theme.fontWeight.bold};
814-
font-size: ${p => p.theme.fontSize.xl};
839+
font-size: ${p => p.theme.fontSize['2xl']};
815840
`;
816841

817842
const DueTodayAmountBeforeDiscount = styled(DueTodayAmount)`
@@ -837,7 +862,6 @@ const ButtonContainer = styled('div')`
837862
`;
838863

839864
const Subtext = styled('div')`
840-
margin-top: ${p => p.theme.space['2xl']};
841865
font-size: ${p => p.theme.fontSize.sm};
842866
color: ${p => p.theme.subText};
843867
text-align: center;

0 commit comments

Comments
 (0)