Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit 611dcfa

Browse files
authored
feat(plg): Add seats (#63227)
1 parent d378d73 commit 611dcfa

13 files changed

+624
-259
lines changed

client/web/BUILD.bazel

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,14 @@ ts_project(
249249
"src/cody/management/api/teamMembers.ts",
250250
"src/cody/management/api/teamSubscriptions.ts",
251251
"src/cody/management/api/types.ts",
252+
"src/cody/management/subscription/BillingAddressPreview.tsx",
253+
"src/cody/management/subscription/PaymentMethodPreview.tsx",
252254
"src/cody/management/subscription/StripeAddressElement.tsx",
253255
"src/cody/management/subscription/StripeCardDetails.tsx",
254256
"src/cody/management/subscription/manage/BillingAddress.tsx",
255257
"src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx",
256258
"src/cody/management/subscription/manage/InvoiceHistory.tsx",
257259
"src/cody/management/subscription/manage/LoadingIconButton.tsx",
258-
"src/cody/management/subscription/manage/NonEditableBillingAddress.tsx",
259260
"src/cody/management/subscription/manage/PaymentDetails.tsx",
260261
"src/cody/management/subscription/manage/SubscriptionDetails.tsx",
261262
"src/cody/management/subscription/manage/utils.ts",

client/web/src/cody/management/api/react-query/subscriptions.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@ import {
88

99
import { Client } from '../client'
1010
import type {
11-
UpdateSubscriptionRequest,
12-
Subscription,
13-
SubscriptionSummary,
1411
CreateTeamRequest,
15-
PreviewResult,
1612
PreviewCreateTeamRequest,
13+
PreviewResult,
14+
PreviewUpdateSubscriptionRequest,
15+
Subscription,
16+
SubscriptionSummary,
17+
UpdateSubscriptionRequest,
1718
GetSubscriptionInvoicesResponse,
1819
} from '../teamSubscriptions'
1920

@@ -85,6 +86,18 @@ export const usePreviewCreateTeam = (): UseMutationResult<PreviewResult | undefi
8586
useMutation({
8687
mutationFn: async requestBody => {
8788
const response = await callCodyProApi(Client.previewCreateTeam(requestBody))
89+
return response.json()
90+
},
91+
})
92+
93+
export const usePreviewUpdateCurrentSubscription = (): UseMutationResult<
94+
PreviewResult | undefined,
95+
Error,
96+
PreviewUpdateSubscriptionRequest
97+
> =>
98+
useMutation({
99+
mutationFn: async requestBody => {
100+
const response = await callCodyProApi(Client.previewUpdateCurrentSubscription(requestBody))
88101
return (await response.json()) as PreviewResult
89102
},
90103
})

client/web/src/cody/management/api/teamSubscriptions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ export interface PaymentMethod {
3131
export interface PreviewResult {
3232
dueNow: UsdCents
3333
newPrice: UsdCents
34-
dueDate: Date
34+
dueDate: string
3535
}
3636

3737
export interface DiscountInfo {
3838
description: string
39-
expiresAt?: Date
39+
expiresAt?: string
4040
}
4141

4242
export interface Invoice {

client/web/src/cody/management/subscription/manage/NonEditableBillingAddress.tsx renamed to client/web/src/cody/management/subscription/BillingAddressPreview.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,29 @@
11
import React from 'react'
22

3-
import { Text } from '@sourcegraph/wildcard'
3+
import { mdiPencilOutline } from '@mdi/js'
44

5-
import type { Subscription } from '../../api/teamSubscriptions'
5+
import { Text, H3, Button, Icon } from '@sourcegraph/wildcard'
6+
7+
import type { Subscription } from '../api/teamSubscriptions'
8+
9+
import styles from './manage/PaymentDetails.module.scss'
10+
11+
export const BillingAddressPreview: React.FC<{
12+
subscription: Subscription
13+
isEditable: boolean
14+
onButtonClick?: () => void
15+
className?: string
16+
}> = ({ subscription: { name, address }, isEditable, onButtonClick = () => undefined, className }) => (
17+
<div className={className}>
18+
<div className="d-flex align-items-center justify-content-between">
19+
<H3>Billing address</H3>
20+
{isEditable && (
21+
<Button variant="link" className={styles.titleButton} onClick={onButtonClick}>
22+
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
23+
</Button>
24+
)}
25+
</div>
626

7-
export const NonEditableBillingAddress: React.FC<{ subscription: Subscription }> = ({
8-
subscription: { name, address },
9-
}) => (
10-
<div>
1127
<div className="mt-3">
1228
<Text size="small" className="mb-1 text-muted font-weight-medium">
1329
Full name
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react'
2+
3+
import { mdiPencilOutline, mdiCreditCardOutline, mdiPlus } from '@mdi/js'
4+
import classNames from 'classnames'
5+
6+
import { H3, Button, Icon, Text } from '@sourcegraph/wildcard'
7+
8+
import type { Subscription } from '../api/teamSubscriptions'
9+
10+
import styles from './manage/PaymentDetails.module.scss'
11+
12+
export const PaymentMethodPreview: React.FC<
13+
Pick<Subscription, 'paymentMethod'> & { isEditable: boolean; onButtonClick?: () => void; className?: string }
14+
> = ({ paymentMethod, isEditable, onButtonClick = () => undefined, className }) =>
15+
paymentMethod ? (
16+
<div className={className}>
17+
<div className="d-flex align-items-center justify-content-between">
18+
<H3>Active credit card</H3>
19+
{isEditable && (
20+
<Button variant="link" className={styles.titleButton} onClick={onButtonClick}>
21+
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
22+
</Button>
23+
)}
24+
</div>
25+
<div className="mt-3 d-flex justify-content-between">
26+
<Text as="span" className={classNames('text-muted', styles.paymentMethodNumber)}>
27+
<Icon aria-hidden={true} svgPath={mdiCreditCardOutline} /> ···· ···· ···· {paymentMethod.last4}
28+
</Text>
29+
<Text as="span" className="text-muted">
30+
Expires {paymentMethod.expMonth}/{paymentMethod.expYear}
31+
</Text>
32+
</div>
33+
</div>
34+
) : (
35+
<div className={classNames('d-flex align-items-center justify-content-between', className)}>
36+
<H3>No payment method is available</H3>
37+
{isEditable && (
38+
<Button variant="link" className={styles.titleButton} onClick={onButtonClick}>
39+
<Icon aria-hidden={true} svgPath={mdiPlus} className="mr-1" /> Add
40+
</Button>
41+
)}
42+
</div>
43+
)

client/web/src/cody/management/subscription/manage/BillingAddress.tsx

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import React, { useMemo, useState, useEffect } from 'react'
22

3-
import { mdiPencilOutline, mdiCheck } from '@mdi/js'
3+
import { mdiCheck } from '@mdi/js'
44
import { useStripe, useElements, AddressElement, Elements } from '@stripe/react-stripe-js'
55
import type { Stripe, StripeElementsOptions } from '@stripe/stripe-js'
66
import classNames from 'classnames'
77

88
import { useTheme, Theme } from '@sourcegraph/shared/src/theme'
9-
import { H3, Button, Icon, Text, Form } from '@sourcegraph/wildcard'
9+
import { H3, Button, Text, Form } from '@sourcegraph/wildcard'
1010

1111
import { useUpdateCurrentSubscription } from '../../api/react-query/subscriptions'
1212
import type { Subscription } from '../../api/teamSubscriptions'
13+
import { BillingAddressPreview } from '../BillingAddressPreview'
1314
import { StripeAddressElement } from '../StripeAddressElement'
1415

1516
import { LoadingIconButton } from './LoadingIconButton'
16-
import { NonEditableBillingAddress } from './NonEditableBillingAddress'
1717

1818
import styles from './PaymentDetails.module.scss'
1919

@@ -63,26 +63,15 @@ export const useBillingAddressStripeElementsOptions = (): StripeElementsOptions
6363
interface BillingAddressProps {
6464
stripe: Stripe | null
6565
subscription: Subscription
66-
title?: string
67-
editable: boolean
6866
}
6967

70-
export const BillingAddress: React.FC<BillingAddressProps> = ({ stripe, subscription, title, editable }) => {
68+
export const BillingAddress: React.FC<BillingAddressProps> = ({ stripe, subscription }) => {
7169
const [isEditMode, setIsEditMode] = useState(false)
7270

7371
const options = useBillingAddressStripeElementsOptions()
7472

7573
return (
7674
<div>
77-
<div className="d-flex align-items-center justify-content-between">
78-
{title ?? <H3>{title}</H3>}
79-
{editable && (
80-
<Button variant="link" className={styles.titleButton} onClick={() => setIsEditMode(true)}>
81-
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
82-
</Button>
83-
)}
84-
</div>
85-
8675
{isEditMode ? (
8776
<Elements stripe={stripe} options={options}>
8877
<BillingAddressForm
@@ -92,7 +81,13 @@ export const BillingAddress: React.FC<BillingAddressProps> = ({ stripe, subscrip
9281
/>
9382
</Elements>
9483
) : (
95-
<NonEditableBillingAddress subscription={subscription} />
84+
<>
85+
<BillingAddressPreview
86+
subscription={subscription}
87+
isEditable={true}
88+
onButtonClick={() => setIsEditMode(true)}
89+
/>
90+
</>
9691
)}
9792
</div>
9893
)
@@ -169,26 +164,29 @@ const BillingAddressForm: React.FC<BillingAddressFormProps> = ({ subscription, o
169164
}
170165

171166
return (
172-
<Form onSubmit={handleSubmit} onReset={onReset} className={styles.billingAddressForm}>
173-
<StripeAddressElement subscription={subscription} onFocus={() => setIsErrorVisible(false)} />
174-
175-
{isErrorVisible && errorMessage ? <Text className="mt-3 text-danger">{errorMessage}</Text> : null}
176-
177-
<div className={classNames('d-flex justify-content-end', styles.billingAddressFormButtonContainer)}>
178-
<Button type="reset" variant="secondary" outline={true}>
179-
Cancel
180-
</Button>
181-
<LoadingIconButton
182-
type="submit"
183-
variant="primary"
184-
className="ml-2"
185-
disabled={isLoading}
186-
isLoading={isLoading}
187-
iconSvgPath={mdiCheck}
188-
>
189-
Save
190-
</LoadingIconButton>
191-
</div>
192-
</Form>
167+
<>
168+
<H3>Billing address</H3>
169+
<Form onSubmit={handleSubmit} onReset={onReset} className={styles.billingAddressForm}>
170+
<StripeAddressElement subscription={subscription} onFocus={() => setIsErrorVisible(false)} />
171+
172+
{isErrorVisible && errorMessage ? <Text className="mt-3 text-danger">{errorMessage}</Text> : null}
173+
174+
<div className={classNames('d-flex justify-content-end', styles.billingAddressFormButtonContainer)}>
175+
<Button type="reset" variant="secondary" outline={true}>
176+
Cancel
177+
</Button>
178+
<LoadingIconButton
179+
type="submit"
180+
variant="primary"
181+
className="ml-2"
182+
disabled={isLoading}
183+
isLoading={isLoading}
184+
iconSvgPath={mdiCheck}
185+
>
186+
Save
187+
</LoadingIconButton>
188+
</div>
189+
</Form>
190+
</>
193191
)
194192
}

client/web/src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ const PageContent: React.FC = () => {
9696
<PageHeader className="mt-4">
9797
<PageHeader.Heading as="h2" styleAs="h1" className="mb-4 d-flex align-items-center">
9898
<PageHeaderIcon name="cody-logo" className="mr-2" />
99-
<Text as="span">Manage Subscription</Text>
99+
<Text as="span">Manage subscription</Text>
100100
</PageHeader.Heading>
101101
</PageHeader>
102102

client/web/src/cody/management/subscription/manage/PaymentDetails.tsx

Lines changed: 12 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import React, { useEffect, useState } from 'react'
22

3-
import { mdiPencilOutline, mdiCreditCardOutline, mdiPlus, mdiCheck } from '@mdi/js'
3+
import { mdiCheck } from '@mdi/js'
44
import { CardNumberElement, Elements, useElements, useStripe } from '@stripe/react-stripe-js'
55
import { loadStripe } from '@stripe/stripe-js'
6-
import classNames from 'classnames'
76

87
import { logger } from '@sourcegraph/common'
9-
import { Button, Form, Grid, H3, Icon, Text } from '@sourcegraph/wildcard'
8+
import { Button, Form, Grid, H3, Text } from '@sourcegraph/wildcard'
109

1110
import { useUpdateCurrentSubscription } from '../../api/react-query/subscriptions'
1211
// Suppressing false positive caused by an ESLint bug. See https://github.com/typescript-eslint/typescript-eslint/issues/4608
1312
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
1413
import type { PaymentMethod, Subscription } from '../../api/types'
14+
import { PaymentMethodPreview } from '../PaymentMethodPreview'
1515
import { StripeCardDetails } from '../StripeCardDetails'
1616

1717
import { BillingAddress } from './BillingAddress'
@@ -37,59 +37,31 @@ export const PaymentDetails: React.FC<{ subscription: Subscription }> = ({ subsc
3737
<PaymentMethod paymentMethod={subscription.paymentMethod} />
3838
</div>
3939
<div className={styles.gridItem}>
40-
<BillingAddress stripe={stripe} subscription={subscription} title="Billing address" editable={true} />
40+
<BillingAddress stripe={stripe} subscription={subscription} />
4141
</div>
4242
</Grid>
4343
)
4444

4545
const PaymentMethod: React.FC<{ paymentMethod: PaymentMethod | undefined }> = ({ paymentMethod }) => {
4646
const [isEditMode, setIsEditMode] = useState(false)
4747

48-
if (!paymentMethod) {
49-
return <PaymentMethodMissing onAddButtonClick={() => setIsEditMode(true)} />
50-
}
51-
52-
if (isEditMode) {
48+
if (isEditMode && paymentMethod) {
5349
return (
5450
<Elements stripe={stripe}>
5551
<PaymentMethodForm onReset={() => setIsEditMode(false)} onSubmit={() => setIsEditMode(false)} />
5652
</Elements>
5753
)
5854
}
5955

60-
return <ActivePaymentMethod paymentMethod={paymentMethod} onEditButtonClick={() => setIsEditMode(true)} />
56+
return (
57+
<PaymentMethodPreview
58+
paymentMethod={paymentMethod}
59+
isEditable={true}
60+
onButtonClick={() => setIsEditMode(true)}
61+
/>
62+
)
6163
}
6264

63-
const PaymentMethodMissing: React.FC<{ onAddButtonClick: () => void }> = ({ onAddButtonClick }) => (
64-
<div className="d-flex align-items-center justify-content-between">
65-
<H3>No payment method is available</H3>
66-
<Button variant="link" className={styles.titleButton} onClick={onAddButtonClick}>
67-
<Icon aria-hidden={true} svgPath={mdiPlus} className="mr-1" /> Add
68-
</Button>
69-
</div>
70-
)
71-
72-
const ActivePaymentMethod: React.FC<
73-
Required<Pick<Subscription, 'paymentMethod'>> & { onEditButtonClick: () => void }
74-
> = props => (
75-
<>
76-
<div className="d-flex align-items-center justify-content-between">
77-
<H3>Active credit card</H3>
78-
<Button variant="link" className={styles.titleButton} onClick={props.onEditButtonClick}>
79-
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
80-
</Button>
81-
</div>
82-
<div className="mt-3 d-flex justify-content-between">
83-
<Text as="span" className={classNames('text-muted', styles.paymentMethodNumber)}>
84-
<Icon aria-hidden={true} svgPath={mdiCreditCardOutline} /> ···· ···· ···· {props.paymentMethod.last4}
85-
</Text>
86-
<Text as="span" className="text-muted">
87-
Expires {props.paymentMethod.expMonth}/{props.paymentMethod.expYear}
88-
</Text>
89-
</div>
90-
</>
91-
)
92-
9365
interface PaymentMethodFormProps {
9466
onReset: () => void
9567
onSubmit: () => void

0 commit comments

Comments
 (0)