SSC: Handle team=1 input on checkout form (#62906)

* Add team feature to checkout form
* Slightly unrelated typo fixes, warning fixes, variable rename, rearrange
This commit is contained in:
David Veszelovszki 2024-05-28 13:02:02 +02:00 committed by GitHub
parent d1b71a0a8a
commit 03f82d67b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 22 additions and 18 deletions

View File

@ -45,7 +45,7 @@ export module Client {
export interface Call<Resp> {
method: 'GET' | 'POST' | 'PATCH' | 'DELETE'
urlSuffix: string
requestBody?: any
requestBody?: unknown
// Unused. This will never be set, it is only to
// pass along the expected response type.
@ -64,7 +64,7 @@ export interface Caller {
// the current Sourcegraph instance's SSC proxy API endpoint.
export class CodyProApiCaller implements Caller {
// e.g. "https://sourcegraph.com"
private origin: string
private readonly origin: string
constructor() {
this.origin = window.location.origin

View File

@ -1,6 +1,6 @@
import { createContext } from 'react'
import { Caller, CodyProApiCaller } from '../client'
import { type Caller, CodyProApiCaller } from '../client'
export interface CodyProApiClient {
caller: Caller

View File

@ -1,6 +1,6 @@
import { useEffect, useState, useContext } from 'react'
import { Call } from '../client'
import type { Call } from '../client'
import { CodyProApiClientContext } from '../components/CodyProApiClient'
export interface ReactFriendlyApiResponse<T> {
@ -15,7 +15,7 @@ export interface ReactFriendlyApiResponse<T> {
// state.
//
// IMPORTANT: In order to avoid the same API request being made multiple times,
// you MUST ensure that the provided call is the same between repains of the
// you MUST ensure that the provided call is the same between repaints of the
// calling React component. i.e. you pretty much always need to create it via
// `useMemo()`.
export function useApiCaller<Resp>(call: Call<Resp>): ReactFriendlyApiResponse<Resp> {
@ -34,7 +34,7 @@ export function useApiCaller<Resp>(call: Call<Resp>): ReactFriendlyApiResponse<R
// https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect
let ignore = false
;(async () => {
async function callApi(): Promise<void> {
try {
const callerResponse = await caller.call(call)
@ -76,7 +76,9 @@ export function useApiCaller<Resp>(call: Call<Resp>): ReactFriendlyApiResponse<R
setResponse(undefined)
setLoading(false)
}
})()
}
void callApi()
return () => {
ignore = true

View File

@ -1,4 +1,4 @@
import { BillingInterval } from './teamSubscriptions'
import type { BillingInterval } from './teamSubscriptions'
export interface CreateCheckoutSessionRequest {
interval: BillingInterval

View File

@ -8,7 +8,7 @@ import { H3, LoadingSpinner, Text } from '@sourcegraph/wildcard'
import { Client } from '../../api/client'
import { useApiCaller } from '../../api/hooks/useApiClient'
import { CreateCheckoutSessionRequest } from '../../api/types'
import type { CreateCheckoutSessionRequest } from '../../api/types'
/**
* CodyProCheckoutForm is essentially an iframe that the Stripe Elements library will
@ -18,16 +18,18 @@ export const CodyProCheckoutForm: React.FunctionComponent<{
stripePromise: Promise<Stripe | null>
customerEmail: string | undefined
}> = ({ stripePromise, customerEmail }) => {
const [urlSearchParams] = useSearchParams()
const creatingTeam = urlSearchParams.get('team') === '1'
// Optionally support the "showCouponCodeAtCheckout" URL query parameter, which, if present,
// will display a "promotional code" element in the Stripe Checkout UI.
const [urlSearchParams] = useSearchParams()
const showPromoCodeField = urlSearchParams.get('showCouponCodeAtCheckout') !== null
// Make the API call to create the Stripe Checkout session.
const call = useMemo(() => {
const req: CreateCheckoutSessionRequest = {
const requestBody: CreateCheckoutSessionRequest = {
interval: 'monthly',
seats: 1,
// If creating a team, we set seatCount=0, which means the user can adjust the seat count.
seats: creatingTeam ? 0 : 1,
customerEmail,
showPromoCodeField,
@ -42,8 +44,8 @@ export const CodyProCheckoutForm: React.FunctionComponent<{
// some prompt, to give the backends an opportunity to sync.
returnUrl: `${origin}/cody/manage?session_id={CHECKOUT_SESSION_ID}`,
}
return Client.createStripeCheckoutSession(req)
}, [customerEmail, showPromoCodeField])
return Client.createStripeCheckoutSession(requestBody)
}, [creatingTeam, customerEmail, showPromoCodeField])
const { loading, error, data } = useApiCaller(call)
// Show a spinner while we wait for the Checkout session to be created.
@ -63,7 +65,7 @@ export const CodyProCheckoutForm: React.FunctionComponent<{
return (
<div>
{data && data.clientSecret && (
{data?.clientSecret && (
<EmbeddedCheckoutProvider stripe={stripePromise} options={{ clientSecret: data.clientSecret }}>
<EmbeddedCheckout />
</EmbeddedCheckoutProvider>

View File

@ -27,7 +27,7 @@ import (
"github.com/sourcegraph/sourcegraph/schema"
)
// SSCAPIProxy is an HTTP handler that essentially proxies API requests from the
// APIProxyHandler is an HTTP handler that essentially proxies API requests from the
// current Sourcegraph instance to the SSC backend, but exchanging the credentials
// of the calling Sourcegraph user with an access token for their SAMS identity.
//
@ -81,7 +81,7 @@ func GetSAMSOAuthContext() (*oauthutil.OAuthContext, error) {
return nil, errors.New("no SAMS configuration found")
}
// getUserIDFromRequest extracts the Sourcegraph User ID from the incomming request,
// getUserIDFromRequest extracts the Sourcegraph User ID from the incoming request,
// or returns an error suitable for sending to the end user.
func (p *APIProxyHandler) getUserIDFromContext(ctx context.Context) (int32, error) {
callingActor := actor.FromContext(ctx)
@ -96,7 +96,7 @@ func (p *APIProxyHandler) getUserIDFromContext(ctx context.Context) (int32, erro
return callingActor.UID, nil
}
// buildProxyRequest converts the incomming HTTP request into what will be sent to the SSC backend.
// buildProxyRequest converts the incoming HTTP request into what will be sent to the SSC backend.
func (p *APIProxyHandler) buildProxyRequest(sourceReq *http.Request, token string) (*http.Request, error) {
// For simplicity, read the full request body before sending the proxy request.
var bodyReader io.Reader