Unified · Split
| @@ -2,4 +2,4 @@ · 1 unmodified line | ||
| 2 | 2 | "executor": patch |
| 3 | 3 | --- |
| 4 | 4 | |
| 5 | −Show a clear, cancellable sign-in state and prevent cancelled OAuth requests from disrupting a retry. | |
| 5 | +Let users cancel sign-in from the connection form and prevent cancelled OAuth requests from disrupting a retry. | |
| @@ -122,6 +122,11 @@ scenario( · 121 unmodified lines | ||
| 122 | 122 | await step("Sign in with a provider account without naming the connection", async () => { |
| 123 | 123 | await visit(page, `/integrations/${slug}?addAccount=1`); |
| 124 | 124 | await page.getByRole("tab", { name: "OAuth2", exact: true }).click(); |
| 125 | + // Attached previews can contain other compatible apps. Use this | |
| 126 | + // scenario's provider instance for the full callback and token exchange. | |
| 127 | + await page | |
| 128 | + .getByRole("radio", { name: new RegExp(slug.replace("setup-", ""), "i") }) | |
| 129 | + .check(); | |
| 125 | 130 | const opened = page.waitForEvent("popup"); |
| 126 | 131 | await page.getByRole("button", { name: "Connect with OAuth", exact: true }).click(); |
| 127 | 132 | const popup = await opened; |
| @@ -299,6 +304,11 @@ scenario( · 171 unmodified lines | ||
| 299 | 304 | await step("Start provider sign-in without entering a name", async () => { |
| 300 | 305 | await visit(page, `/integrations/${slug}?addAccount=1`); |
| 301 | 306 | await page.getByRole("tab", { name: "OAuth2", exact: true }).click(); |
| 307 | + // Attached previews can contain other compatible apps. Use this | |
| 308 | + // scenario's provider instance for the full callback and token exchange. | |
| 309 | + await page | |
| 310 | + .getByRole("radio", { name: new RegExp(slug.replace("setup-", ""), "i") }) | |
| 311 | + .check(); | |
| 302 | 312 | const opened = page.waitForEvent("popup"); |
| 303 | 313 | await page.getByRole("button", { name: "Connect with OAuth", exact: true }).click(); |
| 304 | 314 | const popup = await opened; |
| @@ -313,6 +323,11 @@ scenario( · 8 unmodified lines | ||
| 313 | 323 | ).toBe(true); |
| 314 | 324 | expect(await cancel.isEnabled()).toBe(true); |
| 315 | 325 | await page.getByText("Continue in the sign-in window", { exact: true }).waitFor(); |
| 326 | + expect( | |
| 327 | + await page.getByRole("tab", { name: "OAuth2", exact: true }).isVisible(), | |
| 328 | + "the authentication form stays visible during sign-in", | |
| 329 | + ).toBe(true); | |
| 330 | + expect(await page.getByRole("textbox", { name: /Display name/ }).isVisible()).toBe(true); | |
| 316 | 331 | expect( |
| 317 | 332 | await page.getByRole("button", { name: "Connecting…", exact: true }).count(), |
| 318 | 333 | "waiting for provider consent must not leave a dead Connecting button", |
| @@ -371,10 +386,17 @@ scenario( · 52 unmodified lines | ||
| 371 | 386 | try { |
| 372 | 387 | await step("Cancel while the authorization response is still in flight", async () => { |
| 373 | 388 | await visit(page, `/integrations/${slug}?addAccount=1`); |
| 389 | + await page | |
| 390 | + .getByRole("radio", { name: new RegExp(slug.replace("setup-", ""), "i") }) | |
| 391 | + .check(); | |
| 374 | 392 | const opened = page.waitForEvent("popup"); |
| 375 | 393 | await page.getByRole("button", { name: "Connect with OAuth", exact: true }).click(); |
| 376 | 394 | const popup = await opened; |
| 377 | 395 | const oldState = await started.promise; |
| 396 | + expect( | |
| 397 | + await page.getByRole("textbox", { name: /Display name/ }).isVisible(), | |
| 398 | + "the connection form stays visible while sign-in is being prepared", | |
| 399 | + ).toBe(true); | |
| 378 | 400 | await page.getByRole("button", { name: "Cancel sign-in", exact: true }).click(); |
| 379 | 401 | await expect.poll(() => popup.isClosed()).toBe(true); |
| 380 | 402 | const cancelled = page.waitForResponse((response) => |
| @@ -100,15 +100,7 @@ import { · 99 unmodified lines | ||
| 100 | 100 | DropdownMenuItem, |
| 101 | 101 | DropdownMenuTrigger, |
| 102 | 102 | } from "./dropdown-menu"; |
| 103 | −import { | |
| 104 | − ChevronDown, | |
| 105 | − EyeIcon, | |
| 106 | − EyeOffIcon, | |
| 107 | − LoaderCircleIcon, | |
| 108 | − PanelsTopLeftIcon, | |
| 109 | − PlusIcon, | |
| 110 | − XIcon, | |
| 111 | −} from "lucide-react"; | |
| 103 | +import { ChevronDown, EyeIcon, EyeOffIcon, PlusIcon, XIcon } from "lucide-react"; | |
| 112 | 104 | import { |
| 113 | 105 | Dialog, |
| 114 | 106 | DialogContent, |
| @@ -2749,49 +2741,7 @@ function AddAccountModalView(props: AddAccountModalProps) { · 2634 unmodified lines | ||
| 2749 | 2741 | : "sm:max-w-xl", |
| 2750 | 2742 | )} |
| 2751 | 2743 | > |
| 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 ? ( | |
| 2744 | + {addingMethod && createCustomMethod ? ( | |
| 2795 | 2745 | <> |
| 2796 | 2746 | <DialogHeader className="border-b border-border/60 px-5 py-4"> |
| 2797 | 2747 | <DialogTitle className="text-base">Add authentication method</DialogTitle> |
| @@ -3442,10 +3392,20 @@ function AddAccountModalView(props: AddAccountModalProps) { · 644 unmodified lines | ||
| 3442 | 3392 | </p> |
| 3443 | 3393 | ) : null} |
| 3444 | 3394 | <DialogFooter> |
| 3395 | + {signInPending ? ( | |
| 3396 | + <p role="status" className="flex-1 self-center text-xs text-muted-foreground"> | |
| 3397 | + {oauthPopup.phase === "authorizing" | |
| 3398 | + ? "Continue in the sign-in window" | |
| 3399 | + : oauthPopup.phase === "saving" | |
| 3400 | + ? "Finishing connection…" | |
| 3401 | + : "Preparing sign-in…"} | |
| 3402 | + </p> | |
| 3403 | + ) : null} | |
| 3445 | 3404 | <Button type="button" variant="ghost" onClick={close} disabled={submitting || ccBusy}> |
| 3446 | 3405 | {isOAuth ? "Close" : "Cancel"} |
| 3447 | 3406 | </Button> |
| 3448 | 3407 | {/* Footer action, in precedence order: |
| 3408 | + - pending sign-in: cancel, or wait for an authorized connection to save; | |
| 3449 | 3409 | - transparent CIMD (no app registration): create/reuse a public |
| 3450 | 3410 | metadata-document client and start OAuth; |
| 3451 | 3411 | - transparent DCR (no picker): a single Connect that runs |
| @@ -3453,7 +3413,17 @@ function AddAccountModalView(props: AddAccountModalProps) { · 1 unmodified line | ||
| 3453 | 3413 | - registering a BYO app: the form owns its own submit, no footer; |
| 3454 | 3414 | - picked BYO OAuth app: Connect with OAuth / Connect (client creds); |
| 3455 | 3415 | - credential/no-auth method: Add connection. */} |
| 3456 | − {cimdActive ? ( | |
| 3416 | + {signInPending ? ( | |
| 3417 | + oauthPopup.phase === "saving" ? ( | |
| 3418 | + <Button type="button" loading> | |
| 3419 | + Finishing… | |
| 3420 | + </Button> | |
| 3421 | + ) : ( | |
| 3422 | + <Button type="button" variant="outline" onClick={cancelSignIn}> | |
| 3423 | + Cancel sign-in | |
| 3424 | + </Button> | |
| 3425 | + ) | |
| 3426 | + ) : cimdActive ? ( | |
| 3457 | 3427 | <Button |
| 3458 | 3428 | type="button" |
| 3459 | 3429 | onClick={() => void handleCimdConnect()} |