@@ -100,7 +100,15 @@ import {
100100 DropdownMenuItem ,
101101 DropdownMenuTrigger ,
102102} from "./dropdown-menu" ;
103- import { ChevronDown , EyeIcon , EyeOffIcon , PlusIcon , XIcon } from "lucide-react" ;
103+ import {
104+ ChevronDown ,
105+ EyeIcon ,
106+ EyeOffIcon ,
107+ LoaderCircleIcon ,
108+ PanelsTopLeftIcon ,
109+ PlusIcon ,
110+ XIcon ,
111+ } from "lucide-react" ;
104112import {
105113 Dialog ,
106114 DialogContent ,
@@ -741,6 +749,7 @@ type CimdCreateClientArgs = {
741749} ;
742750
743751type RunCimdConnectDeps = {
752+ readonly isActive : ( ) => boolean ;
744753 readonly createClient : ( args : CimdCreateClientArgs ) => Promise < OAuthClientSlug | null > ;
745754 readonly start : ( args : CimdStartArgs ) => void ;
746755 /** Claim the sign-in window before any await. See `useOAuthPopupFlow.reserve`. */
@@ -767,6 +776,7 @@ type RunCimdConnectInput = {
767776
768777type CimdOutcome =
769778 | { readonly kind : "started" ; readonly client : OAuthClientSlug ; readonly reused : boolean }
779+ | { readonly kind : "aborted" }
770780 | { readonly kind : "popup-blocked" }
771781 | { readonly kind : "failed" ; readonly reason : "missing-endpoints" | "create-failed" } ;
772782
@@ -830,6 +840,10 @@ export async function runCimdConnect(
830840 if ( reservation . kind === "blocked" ) return { kind : "popup-blocked" } ;
831841
832842 const resolved = await resolveCimdClient ( deps , input ) ;
843+ if ( ! deps . isActive ( ) ) {
844+ deps . release ( ) ;
845+ return { kind : "aborted" } ;
846+ }
833847 if ( resolved . kind === "failed" ) {
834848 deps . release ( ) ;
835849 return resolved ;
@@ -1866,7 +1880,14 @@ function AddAccountModalView(props: AddAccountModalProps) {
18661880 const oauthBusy = ccBusy || oauthPopup . busy ;
18671881 const cimdConnecting = cimdBusy || oauthPopup . busy ;
18681882 const dcrConnecting = dcrBusy || oauthPopup . busy ;
1869- const automaticOAuthConnecting = cimdConnecting || dcrConnecting ;
1883+ const signInPending = cimdBusy || dcrBusy || oauthPopup . busy ;
1884+ const automaticAttemptRef = useRef ( 0 ) ;
1885+ const cancelSignIn = ( ) => {
1886+ automaticAttemptRef . current += 1 ;
1887+ oauthPopup . cancel ( ) ;
1888+ setCimdBusy ( false ) ;
1889+ setDcrBusy ( false ) ;
1890+ } ;
18701891
18711892 // "Connection saved to" for a PICKED BYO OAuth app. Cloud: a Workspace (`org`)
18721893 // app can mint Personal or Workspace connections; a Personal (`user`) app can
@@ -2359,11 +2380,16 @@ function AddAccountModalView(props: AddAccountModalProps) {
23592380 const cimdOwner = owner ;
23602381 const connectionName = previewConnectionName ( label , cimdOwner ) ;
23612382 const identityLabel = typedIdentityLabel ( label ) ;
2383+ const attempt = ++ automaticAttemptRef . current ;
2384+ const isActive = ( ) => viewMountedRef . current && attempt === automaticAttemptRef . current ;
23622385 setCimdBusy ( true ) ;
23632386 const outcome = await runCimdConnect (
23642387 {
23652388 reserve : oauthPopup . reserve ,
2366- release : oauthPopup . releaseReservation ,
2389+ release : ( ) => {
2390+ if ( attempt === automaticAttemptRef . current ) oauthPopup . releaseReservation ( ) ;
2391+ } ,
2392+ isActive,
23672393 createClient : createCimdClient ,
23682394 start : ( args : CimdStartArgs ) : void => {
23692395 void oauthPopup . start ( {
@@ -2398,6 +2424,7 @@ function AddAccountModalView(props: AddAccountModalProps) {
23982424 existingClients : clientSummaries ,
23992425 } ,
24002426 ) ;
2427+ if ( ! isActive ( ) ) return ;
24012428 setCimdBusy ( false ) ;
24022429 trackEvent ( "connection_oauth_started" , {
24032430 integration_slug : String ( integration ) ,
@@ -2442,16 +2469,18 @@ function AddAccountModalView(props: AddAccountModalProps) {
24422469 setDcrFailed ( true ) ;
24432470 return ;
24442471 }
2472+ const attempt = ++ automaticAttemptRef . current ;
2473+ const isActive = ( ) => viewMountedRef . current && attempt === automaticAttemptRef . current ;
24452474 setDcrBusy ( true ) ;
24462475 const outcome = await runAutomaticOAuthConnect (
24472476 {
24482477 reserve : oauthPopup . reserve ,
2449- release : oauthPopup . releaseReservation ,
2450- // Closing the modal genuinely unmounts this view (see
2451- // `AddAccountModal`), so "still mounted" is exactly "still open".
2452- // The sequence checks it between round trips: a close mid-flight
2453- // must not register a client or launch the popup afterwards .
2454- isActive : ( ) => viewMountedRef . current ,
2478+ release : ( ) => {
2479+ if ( attempt === automaticAttemptRef . current ) oauthPopup . releaseReservation ( ) ;
2480+ } ,
2481+ // A closed modal or cancelled attempt cannot register a client or
2482+ // launch sign-in after the next round trip. A retry owns a new attempt .
2483+ isActive,
24552484 probe : async ( url : string ) : Promise < OAuthProbeResult | null > => {
24562485 const exit = await doProbe ( { payload : { url } , reactivityKeys : [ ] } ) ;
24572486 if ( Exit . isFailure ( exit ) ) return null ;
@@ -2560,7 +2589,7 @@ function AddAccountModalView(props: AddAccountModalProps) {
25602589 // The modal closed mid-flight: this view is unmounted, so no state may
25612590 // be written at all — not the fallback below, and not even the busy
25622591 // flag, which belongs to the surface that is gone.
2563- if ( outcome . kind === "aborted" ) return ;
2592+ if ( ! isActive ( ) || outcome . kind === "aborted" ) return ;
25642593 setDcrBusy ( false ) ;
25652594 // `connection_oauth_started` measures the connect funnel; a reconnect
25662595 // reports through `connection_reconnected` on the popup callbacks above,
@@ -2720,7 +2749,49 @@ function AddAccountModalView(props: AddAccountModalProps) {
27202749 : "sm:max-w-xl" ,
27212750 ) }
27222751 >
2723- { addingMethod && createCustomMethod ? (
2752+ { signInPending ? (
2753+ < >
2754+ < DialogHeader >
2755+ < DialogTitle > Add connection · { integrationName } </ DialogTitle >
2756+ </ DialogHeader >
2757+ < div role = "status" className = "flex flex-col items-center px-4 py-8 text-center" >
2758+ < div className = "mb-5 flex size-12 items-center justify-center rounded-xl border border-border/70 bg-muted/30 text-foreground shadow-sm" >
2759+ { oauthPopup . phase === "authorizing" ? (
2760+ < PanelsTopLeftIcon aria-hidden = "true" className = "size-5" />
2761+ ) : (
2762+ < LoaderCircleIcon
2763+ aria-hidden = "true"
2764+ className = "size-5 animate-spin motion-reduce:animate-none"
2765+ />
2766+ ) }
2767+ </ div >
2768+ < p className = "text-sm font-medium text-foreground" >
2769+ { oauthPopup . phase === "authorizing"
2770+ ? "Continue in the sign-in window"
2771+ : oauthPopup . phase === "saving"
2772+ ? "Finishing connection"
2773+ : "Preparing sign-in" }
2774+ </ p >
2775+ < DialogDescription className = "mt-2 max-w-xs text-sm leading-relaxed" >
2776+ { oauthPopup . phase === "authorizing"
2777+ ? "If the window closed or sign-in stalled, cancel and try again."
2778+ : oauthPopup . phase === "saving"
2779+ ? "Sign-in complete. Updating your connections."
2780+ : "The sign-in window will be ready shortly. You can cancel at any time." }
2781+ </ DialogDescription >
2782+ </ div >
2783+ < DialogFooter className = "border-t border-border/60 pt-4 sm:justify-between" >
2784+ < Button type = "button" variant = "ghost" onClick = { close } >
2785+ Close
2786+ </ Button >
2787+ { oauthPopup . phase !== "saving" ? (
2788+ < Button type = "button" variant = "outline" onClick = { cancelSignIn } >
2789+ Cancel sign-in
2790+ </ Button >
2791+ ) : null }
2792+ </ DialogFooter >
2793+ </ >
2794+ ) : addingMethod && createCustomMethod ? (
27242795 < >
27252796 < DialogHeader className = "border-b border-border/60 px-5 py-4" >
27262797 < DialogTitle className = "text-base" > Add authentication method</ DialogTitle >
@@ -3371,12 +3442,7 @@ function AddAccountModalView(props: AddAccountModalProps) {
33713442 </ p >
33723443 ) : null }
33733444 < DialogFooter >
3374- < Button
3375- type = "button"
3376- variant = "ghost"
3377- onClick = { close }
3378- disabled = { submitting || oauthBusy || automaticOAuthConnecting }
3379- >
3445+ < Button type = "button" variant = "ghost" onClick = { close } disabled = { submitting || ccBusy } >
33803446 { isOAuth ? "Close" : "Cancel" }
33813447 </ Button >
33823448 { /* Footer action, in precedence order:
0 commit comments