95 lines · 3.4 KB · TypeScript
RawDownload
import { useState } from "react"; import {  AuthTemplateSlug,  ConnectionName,  IntegrationSlug,  OAuthClientSlug,  type Connection,  type AuthTemplateSlug as AuthTemplateSlugType,} from "@executor-js/sdk/shared";import { OAuthSignInButton, useOAuthPopupFlow } from "@executor-js/react/plugins/oauth-sign-in";import {  ConnectionOwnerDropdown,  useConnectionOwner,} from "@executor-js/react/plugins/connection-owner";import { useOwnerDisplay } from "@executor-js/react/api/owner-display"; import { graphqlConnectionName } from "./defaults"; // v2 OAuth sign-in. `oauth.start` runs a registered OAuth client for one// integration + template, minting an owner-scoped connection (the redirect is// driven through the popup; `state` correlates it). There is no per-source// connection slot or `oauthConnectionId` anymore — the connection is owned by// the chosen owner and identified by (owner, integration, name).const GRAPHQL_OAUTH_CLIENT = "graphql-oauth"; export default function GraphqlSignInButton(props: {  readonly slug: IntegrationSlug;  readonly template: AuthTemplateSlugType;  readonly displayName: string;  readonly existing: readonly Connection[];}) {  const { connectionOwner, setConnectionOwner, connectionOwnerOptions } = useConnectionOwner();  const oauth = useOAuthPopupFlow({    popupName: "graphql-oauth",    startErrorMessage: "Failed to start OAuth",  });  const ownerDisplay = useOwnerDisplay();  const [connectedOwner, setConnectedOwner] = useState<string | null>(null);   const existingForOwner = props.existing.find(    (connection) => connection.owner === connectionOwner && connection.template === props.template,  );  const isConnected = existingForOwner !== undefined || connectedOwner === connectionOwner;   const handleSignIn = (): void => {    setConnectedOwner(null);    void oauth.start({      payload: {        client: OAuthClientSlug.make(GRAPHQL_OAUTH_CLIENT),        // GraphQL manages its own client per owner — the app and connection        // share one owner.        clientOwner: connectionOwner,        owner: connectionOwner,        name: graphqlConnectionName(String(props.slug), connectionOwner),        integration: IntegrationSlug.make(String(props.slug)),        template: AuthTemplateSlug.make(String(props.template)),        identityLabel: `${props.displayName} OAuth`,      },      onSuccess: (payload: { readonly connection: ConnectionName }) => {        // Touch the minted connection name to satisfy the success contract; the        // connection list re-reads via reactivity keys after the flow completes.        void payload.connection;        setConnectedOwner(connectionOwner);      },    });  };   return (    <div className="space-y-2">      {isConnected && (        <p className="text-xs text-emerald-600 dark:text-emerald-400">          Connected in {ownerDisplay.label(connectionOwner)}          {existingForOwner?.identityLabel ? ` as ${existingForOwner.identityLabel}` : ""}.        </p>      )}      <ConnectionOwnerDropdown        value={connectionOwner}        options={connectionOwnerOptions}        onChange={(owner) => {          setConnectionOwner(owner);          setConnectedOwner(null);        }}        label="Connection saved to"        help="Choose who can use the OAuth connection."      />      <OAuthSignInButton        busy={oauth.busy}        error={oauth.error}        isConnected={isConnected}        onSignIn={handleSignIn}      />    </div>  );}