mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 17:31:43 +00:00
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:
parent
d1b71a0a8a
commit
03f82d67b2
@ -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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { createContext } from 'react'
|
||||
|
||||
import { Caller, CodyProApiCaller } from '../client'
|
||||
import { type Caller, CodyProApiCaller } from '../client'
|
||||
|
||||
export interface CodyProApiClient {
|
||||
caller: Caller
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BillingInterval } from './teamSubscriptions'
|
||||
import type { BillingInterval } from './teamSubscriptions'
|
||||
|
||||
export interface CreateCheckoutSessionRequest {
|
||||
interval: BillingInterval
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user