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

feat(plg): Add seats #63227

Merged
merged 27 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion client/web/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,14 @@ ts_project(
"src/cody/management/api/teamMembers.ts",
"src/cody/management/api/teamSubscriptions.ts",
"src/cody/management/api/types.ts",
"src/cody/management/subscription/BillingAddressPreview.tsx",
"src/cody/management/subscription/PaymentMethodPreview.tsx",
"src/cody/management/subscription/StripeAddressElement.tsx",
"src/cody/management/subscription/StripeCardDetails.tsx",
"src/cody/management/subscription/manage/BillingAddress.tsx",
"src/cody/management/subscription/manage/CodySubscriptionManagePage.tsx",
"src/cody/management/subscription/manage/InvoiceHistory.tsx",
"src/cody/management/subscription/manage/LoadingIconButton.tsx",
"src/cody/management/subscription/manage/NonEditableBillingAddress.tsx",
"src/cody/management/subscription/manage/PaymentDetails.tsx",
"src/cody/management/subscription/manage/SubscriptionDetails.tsx",
"src/cody/management/subscription/manage/utils.ts",
Expand Down
21 changes: 17 additions & 4 deletions client/web/src/cody/management/api/react-query/subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import {

import { Client } from '../client'
import type {
UpdateSubscriptionRequest,
Subscription,
SubscriptionSummary,
CreateTeamRequest,
PreviewResult,
PreviewCreateTeamRequest,
PreviewResult,
PreviewUpdateSubscriptionRequest,
Subscription,
SubscriptionSummary,
UpdateSubscriptionRequest,
GetSubscriptionInvoicesResponse,
} from '../teamSubscriptions'

Expand Down Expand Up @@ -85,6 +86,18 @@ export const usePreviewCreateTeam = (): UseMutationResult<PreviewResult | undefi
useMutation({
mutationFn: async requestBody => {
const response = await callCodyProApi(Client.previewCreateTeam(requestBody))
return response.json()
},
})

export const usePreviewUpdateCurrentSubscription = (): UseMutationResult<
PreviewResult | undefined,
Error,
PreviewUpdateSubscriptionRequest
> =>
useMutation({
mutationFn: async requestBody => {
const response = await callCodyProApi(Client.previewUpdateCurrentSubscription(requestBody))
return (await response.json()) as PreviewResult
vdavid marked this conversation as resolved.
Show resolved Hide resolved
},
})
4 changes: 2 additions & 2 deletions client/web/src/cody/management/api/teamSubscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export interface PaymentMethod {
export interface PreviewResult {
dueNow: UsdCents
newPrice: UsdCents
dueDate: Date
dueDate: string
}

export interface DiscountInfo {
description: string
expiresAt?: Date
expiresAt?: string
}

export interface Invoice {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import React from 'react'

import { Text } from '@sourcegraph/wildcard'
import { mdiPencilOutline } from '@mdi/js'

import type { Subscription } from '../../api/teamSubscriptions'
import { Text, H3, Button, Icon } from '@sourcegraph/wildcard'

import type { Subscription } from '../api/teamSubscriptions'

import styles from './manage/PaymentDetails.module.scss'

export const BillingAddressPreview: React.FC<{
subscription: Subscription
isEditable: boolean
onButtonClick?: () => void
className?: string
}> = ({ subscription: { name, address }, isEditable, onButtonClick = () => undefined, className }) => (
<div className={className}>
<div className="d-flex align-items-center justify-content-between">
<H3>Billing address</H3>
{isEditable && (
<Button variant="link" className={styles.titleButton} onClick={onButtonClick}>
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
</Button>
)}
</div>

export const NonEditableBillingAddress: React.FC<{ subscription: Subscription }> = ({
subscription: { name, address },
}) => (
<div>
<div className="mt-3">
<Text size="small" className="mb-1 text-muted font-weight-medium">
Full name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react'

import { mdiPencilOutline, mdiCreditCardOutline, mdiPlus } from '@mdi/js'
import classNames from 'classnames'

import { H3, Button, Icon, Text } from '@sourcegraph/wildcard'

import type { Subscription } from '../api/teamSubscriptions'

import styles from './manage/PaymentDetails.module.scss'

export const PaymentMethodPreview: React.FC<
Pick<Subscription, 'paymentMethod'> & { editButton: boolean; onButtonClick?: () => void; className?: string }
> = ({ paymentMethod, editButton, onButtonClick = () => undefined, className }) =>
paymentMethod ? (
<div className={className}>
<div className="d-flex align-items-center justify-content-between">
<H3>Active credit card</H3>
{editButton && (
vdavid marked this conversation as resolved.
Show resolved Hide resolved
<Button variant="link" className={styles.titleButton} onClick={onButtonClick}>
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
</Button>
)}
</div>
<div className="mt-3 d-flex justify-content-between">
<Text as="span" className={classNames('text-muted', styles.paymentMethodNumber)}>
<Icon aria-hidden={true} svgPath={mdiCreditCardOutline} /> ···· ···· ···· {paymentMethod.last4}
</Text>
<Text as="span" className="text-muted">
Expires {paymentMethod.expMonth}/{paymentMethod.expYear}
</Text>
</div>
</div>
) : (
<div className={classNames('d-flex align-items-center justify-content-between', className)}>
<H3>No payment method is available</H3>
{editButton && (
<Button variant="link" className={styles.titleButton} onClick={onButtonClick}>
<Icon aria-hidden={true} svgPath={mdiPlus} className="mr-1" /> Add
</Button>
)}
</div>
)
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React, { useMemo, useState, useEffect } from 'react'

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

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

import { useUpdateCurrentSubscription } from '../../api/react-query/subscriptions'
import type { Subscription } from '../../api/teamSubscriptions'
import { BillingAddressPreview } from '../BillingAddressPreview'
import { StripeAddressElement } from '../StripeAddressElement'

import { LoadingIconButton } from './LoadingIconButton'
import { NonEditableBillingAddress } from './NonEditableBillingAddress'

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

Expand Down Expand Up @@ -63,26 +63,15 @@ export const useBillingAddressStripeElementsOptions = (): StripeElementsOptions
interface BillingAddressProps {
stripe: Stripe | null
subscription: Subscription
title?: string
editable: boolean
}

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

const options = useBillingAddressStripeElementsOptions()

return (
<div>
<div className="d-flex align-items-center justify-content-between">
{title ?? <H3>{title}</H3>}
{editable && (
<Button variant="link" className={styles.titleButton} onClick={() => setIsEditMode(true)}>
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
</Button>
)}
</div>

{isEditMode ? (
<Elements stripe={stripe} options={options}>
<BillingAddressForm
Expand All @@ -92,7 +81,13 @@ export const BillingAddress: React.FC<BillingAddressProps> = ({ stripe, subscrip
/>
</Elements>
) : (
<NonEditableBillingAddress subscription={subscription} />
<>
<BillingAddressPreview
subscription={subscription}
isEditable={true}
onButtonClick={() => setIsEditMode(true)}
/>
</>
)}
</div>
)
Expand Down Expand Up @@ -169,26 +164,29 @@ const BillingAddressForm: React.FC<BillingAddressFormProps> = ({ subscription, o
}

return (
<Form onSubmit={handleSubmit} onReset={onReset} className={styles.billingAddressForm}>
<StripeAddressElement subscription={subscription} onFocus={() => setIsErrorVisible(false)} />

{isErrorVisible && errorMessage ? <Text className="mt-3 text-danger">{errorMessage}</Text> : null}

<div className={classNames('d-flex justify-content-end', styles.billingAddressFormButtonContainer)}>
<Button type="reset" variant="secondary" outline={true}>
Cancel
</Button>
<LoadingIconButton
type="submit"
variant="primary"
className="ml-2"
disabled={isLoading}
isLoading={isLoading}
iconSvgPath={mdiCheck}
>
Save
</LoadingIconButton>
</div>
</Form>
<>
<H3>Billing address</H3>
<Form onSubmit={handleSubmit} onReset={onReset} className={styles.billingAddressForm}>
<StripeAddressElement subscription={subscription} onFocus={() => setIsErrorVisible(false)} />

{isErrorVisible && errorMessage ? <Text className="mt-3 text-danger">{errorMessage}</Text> : null}

<div className={classNames('d-flex justify-content-end', styles.billingAddressFormButtonContainer)}>
<Button type="reset" variant="secondary" outline={true}>
Cancel
</Button>
<LoadingIconButton
type="submit"
variant="primary"
className="ml-2"
disabled={isLoading}
isLoading={isLoading}
iconSvgPath={mdiCheck}
>
Save
</LoadingIconButton>
</div>
</Form>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const PageContent: React.FC = () => {
<PageHeader className="mt-4">
<PageHeader.Heading as="h2" styleAs="h1" className="mb-4 d-flex align-items-center">
<PageHeaderIcon name="cody-logo" className="mr-2" />
<Text as="span">Manage Subscription</Text>
<Text as="span">Manage subscription</Text>
</PageHeader.Heading>
</PageHeader>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React, { useEffect, useState } from 'react'

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

import { logger } from '@sourcegraph/common'
import { Button, Form, Grid, H3, Icon, Text } from '@sourcegraph/wildcard'
import { Button, Form, Grid, H3, Text } from '@sourcegraph/wildcard'

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

import { BillingAddress } from './BillingAddress'
Expand All @@ -37,59 +37,31 @@ export const PaymentDetails: React.FC<{ subscription: Subscription }> = ({ subsc
<PaymentMethod paymentMethod={subscription.paymentMethod} />
</div>
<div className={styles.gridItem}>
<BillingAddress stripe={stripe} subscription={subscription} title="Billing address" editable={true} />
<BillingAddress stripe={stripe} subscription={subscription} />
</div>
</Grid>
)

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

if (!paymentMethod) {
return <PaymentMethodMissing onAddButtonClick={() => setIsEditMode(true)} />
}

if (isEditMode) {
if (isEditMode && paymentMethod) {
return (
<Elements stripe={stripe}>
<PaymentMethodForm onReset={() => setIsEditMode(false)} onSubmit={() => setIsEditMode(false)} />
</Elements>
)
}

return <ActivePaymentMethod paymentMethod={paymentMethod} onEditButtonClick={() => setIsEditMode(true)} />
return (
<PaymentMethodPreview
paymentMethod={paymentMethod}
editButton={true}
vdavid marked this conversation as resolved.
Show resolved Hide resolved
onButtonClick={() => setIsEditMode(true)}
/>
)
}

const PaymentMethodMissing: React.FC<{ onAddButtonClick: () => void }> = ({ onAddButtonClick }) => (
<div className="d-flex align-items-center justify-content-between">
<H3>No payment method is available</H3>
<Button variant="link" className={styles.titleButton} onClick={onAddButtonClick}>
<Icon aria-hidden={true} svgPath={mdiPlus} className="mr-1" /> Add
</Button>
</div>
)

const ActivePaymentMethod: React.FC<
Required<Pick<Subscription, 'paymentMethod'>> & { onEditButtonClick: () => void }
> = props => (
<>
<div className="d-flex align-items-center justify-content-between">
<H3>Active credit card</H3>
<Button variant="link" className={styles.titleButton} onClick={props.onEditButtonClick}>
<Icon aria-hidden={true} svgPath={mdiPencilOutline} className="mr-1" /> Edit
</Button>
</div>
<div className="mt-3 d-flex justify-content-between">
<Text as="span" className={classNames('text-muted', styles.paymentMethodNumber)}>
<Icon aria-hidden={true} svgPath={mdiCreditCardOutline} /> ···· ···· ···· {props.paymentMethod.last4}
</Text>
<Text as="span" className="text-muted">
Expires {props.paymentMethod.expMonth}/{props.paymentMethod.expYear}
</Text>
</div>
</>
)

interface PaymentMethodFormProps {
onReset: () => void
onSubmit: () => void
Expand Down
Loading
Loading