Repository navigation
feat(desktop): install GitHub CLI from onboarding terminal and add gh dialog end states #6117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f31c1ba
a99b464
1c41a40
124a3b8
e3890f8
ba1b138
c37538c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,38 +1,264 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from "@superset/ui/button"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||
| Dialog, | ||||||||||||||||||||||||||||||||||||||||||||
| DialogContent, | ||||||||||||||||||||||||||||||||||||||||||||
| DialogDescription, | ||||||||||||||||||||||||||||||||||||||||||||
| DialogHeader, | ||||||||||||||||||||||||||||||||||||||||||||
| DialogTitle, | ||||||||||||||||||||||||||||||||||||||||||||
| } from "@superset/ui/dialog"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { Spinner } from "@superset/ui/spinner"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { cn } from "@superset/ui/utils"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { useEffect, useRef, useState } from "react"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||
| LuArrowUpRight, | ||||||||||||||||||||||||||||||||||||||||||||
| LuCheck, | ||||||||||||||||||||||||||||||||||||||||||||
| LuCopy, | ||||||||||||||||||||||||||||||||||||||||||||
| LuTriangleAlert, | ||||||||||||||||||||||||||||||||||||||||||||
| } from "react-icons/lu"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { electronTrpcClient } from "renderer/lib/trpc-client"; | ||||||||||||||||||||||||||||||||||||||||||||
| import stripAnsi from "strip-ansi"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { GhAuthTerminal } from "./GhAuthTerminal"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const GH_AUTH_COMMAND = | ||||||||||||||||||||||||||||||||||||||||||||
| "gh auth login --hostname github.com --git-protocol https --web"; | ||||||||||||||||||||||||||||||||||||||||||||
| const GH_INSTALL_COMMAND = `brew install gh && ${GH_AUTH_COMMAND}`; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const ONE_TIME_CODE_PATTERN = /one-time code: ([A-Z0-9]{4}-[A-Z0-9]{4})/; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export type GhAuthDialogMode = "auth" | "install"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| type Phase = "running" | "checking" | "success" | "failed"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| interface GhAuthDialogProps { | ||||||||||||||||||||||||||||||||||||||||||||
| open: boolean; | ||||||||||||||||||||||||||||||||||||||||||||
| mode: GhAuthDialogMode; | ||||||||||||||||||||||||||||||||||||||||||||
| onOpenChange: (open: boolean) => void; | ||||||||||||||||||||||||||||||||||||||||||||
| /** Fired when the gh process exits so the caller can re-check auth status. */ | ||||||||||||||||||||||||||||||||||||||||||||
| onExit: () => void; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export function GhAuthDialog({ | ||||||||||||||||||||||||||||||||||||||||||||
| open, | ||||||||||||||||||||||||||||||||||||||||||||
| mode, | ||||||||||||||||||||||||||||||||||||||||||||
| onOpenChange, | ||||||||||||||||||||||||||||||||||||||||||||
| onExit, | ||||||||||||||||||||||||||||||||||||||||||||
| }: GhAuthDialogProps) { | ||||||||||||||||||||||||||||||||||||||||||||
| const [phase, setPhase] = useState<Phase>("running"); | ||||||||||||||||||||||||||||||||||||||||||||
| const [attempt, setAttempt] = useState(0); | ||||||||||||||||||||||||||||||||||||||||||||
| const [oneTimeCode, setOneTimeCode] = useState<string | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||
| const [copied, setCopied] = useState(false); | ||||||||||||||||||||||||||||||||||||||||||||
| const outputBufferRef = useRef(""); | ||||||||||||||||||||||||||||||||||||||||||||
| const closeTimerRef = useRef<number | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||
| const terminalBoxRef = useRef<HTMLDivElement>(null); | ||||||||||||||||||||||||||||||||||||||||||||
| const ptyWriteRef = useRef<((data: string) => void) | null>(null); | ||||||||||||||||||||||||||||||||||||||||||||
| const onExitRef = useRef(onExit); | ||||||||||||||||||||||||||||||||||||||||||||
| onExitRef.current = onExit; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (!open) return; | ||||||||||||||||||||||||||||||||||||||||||||
| setPhase("running"); | ||||||||||||||||||||||||||||||||||||||||||||
| setAttempt(0); | ||||||||||||||||||||||||||||||||||||||||||||
| setOneTimeCode(null); | ||||||||||||||||||||||||||||||||||||||||||||
| setCopied(false); | ||||||||||||||||||||||||||||||||||||||||||||
| outputBufferRef.current = ""; | ||||||||||||||||||||||||||||||||||||||||||||
| return () => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (closeTimerRef.current !== null) { | ||||||||||||||||||||||||||||||||||||||||||||
| window.clearTimeout(closeTimerRef.current); | ||||||||||||||||||||||||||||||||||||||||||||
| closeTimerRef.current = null; | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
| }, [open]); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| // Auto-copy so the happy path needs no clicks; the Copy button remains as | ||||||||||||||||||||||||||||||||||||||||||||
| // a fallback for when the clipboard write is rejected (e.g. window unfocused). | ||||||||||||||||||||||||||||||||||||||||||||
| useEffect(() => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (!oneTimeCode) return; | ||||||||||||||||||||||||||||||||||||||||||||
| navigator.clipboard.writeText(oneTimeCode).then( | ||||||||||||||||||||||||||||||||||||||||||||
| () => setCopied(true), | ||||||||||||||||||||||||||||||||||||||||||||
| () => setCopied(false), | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| }, [oneTimeCode]); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const handleOutput = (data: string) => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (oneTimeCode) return; | ||||||||||||||||||||||||||||||||||||||||||||
| outputBufferRef.current = (outputBufferRef.current + data).slice(-4096); | ||||||||||||||||||||||||||||||||||||||||||||
| const match = stripAnsi(outputBufferRef.current).match( | ||||||||||||||||||||||||||||||||||||||||||||
| ONE_TIME_CODE_PATTERN, | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| if (match?.[1]) { | ||||||||||||||||||||||||||||||||||||||||||||
| setOneTimeCode(match[1]); | ||||||||||||||||||||||||||||||||||||||||||||
| // gh only waits for Enter so the user can copy the code first — | ||||||||||||||||||||||||||||||||||||||||||||
| // we've captured it, so advance straight to opening the browser. | ||||||||||||||||||||||||||||||||||||||||||||
| ptyWriteRef.current?.("\r"); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const handleTerminalExit = () => { | ||||||||||||||||||||||||||||||||||||||||||||
| setPhase("checking"); | ||||||||||||||||||||||||||||||||||||||||||||
| onExitRef.current(); | ||||||||||||||||||||||||||||||||||||||||||||
| void electronTrpcClient.system.detectGhCli | ||||||||||||||||||||||||||||||||||||||||||||
| .query() | ||||||||||||||||||||||||||||||||||||||||||||
| .then((status) => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (status.installed && status.authenticated) { | ||||||||||||||||||||||||||||||||||||||||||||
| setPhase("success"); | ||||||||||||||||||||||||||||||||||||||||||||
| closeTimerRef.current = window.setTimeout(() => { | ||||||||||||||||||||||||||||||||||||||||||||
| onOpenChange(false); | ||||||||||||||||||||||||||||||||||||||||||||
| }, 1500); | ||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||
| setPhase("failed"); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||
| .catch(() => setPhase("failed")); | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const handleRetry = () => { | ||||||||||||||||||||||||||||||||||||||||||||
| setPhase("running"); | ||||||||||||||||||||||||||||||||||||||||||||
| setOneTimeCode(null); | ||||||||||||||||||||||||||||||||||||||||||||
| setCopied(false); | ||||||||||||||||||||||||||||||||||||||||||||
| outputBufferRef.current = ""; | ||||||||||||||||||||||||||||||||||||||||||||
| setAttempt((prev) => prev + 1); | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const focusTerminal = () => { | ||||||||||||||||||||||||||||||||||||||||||||
| terminalBoxRef.current | ||||||||||||||||||||||||||||||||||||||||||||
| ?.querySelector<HTMLTextAreaElement>(".xterm-helper-textarea") | ||||||||||||||||||||||||||||||||||||||||||||
| ?.focus(); | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const handleCopyCode = () => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (!oneTimeCode) return; | ||||||||||||||||||||||||||||||||||||||||||||
| navigator.clipboard.writeText(oneTimeCode).then( | ||||||||||||||||||||||||||||||||||||||||||||
| () => setCopied(true), | ||||||||||||||||||||||||||||||||||||||||||||
| () => {}, | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
| // Return focus to the terminal so Enter goes to gh, not the button. | ||||||||||||||||||||||||||||||||||||||||||||
| focusTerminal(); | ||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const processActive = phase === "running" || phase === "checking"; | ||||||||||||||||||||||||||||||||||||||||||||
| const isInstall = mode === "install"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||
| <Dialog open={open} onOpenChange={onOpenChange}> | ||||||||||||||||||||||||||||||||||||||||||||
| <DialogContent className="max-w-[752px] gap-4"> | ||||||||||||||||||||||||||||||||||||||||||||
| <Dialog open={open} modal onOpenChange={onOpenChange}> | ||||||||||||||||||||||||||||||||||||||||||||
| <DialogContent | ||||||||||||||||||||||||||||||||||||||||||||
| className="gap-3 sm:max-w-[752px]" | ||||||||||||||||||||||||||||||||||||||||||||
| aria-describedby={undefined} | ||||||||||||||||||||||||||||||||||||||||||||
| onInteractOutside={(event) => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (processActive) event.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||
| onFocusOutside={(event) => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (processActive) event.preventDefault(); | ||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+142
to
+150
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
path="apps/desktop/src/renderer/routes/_authenticated/onboarding/components/GhAuthDialog/GhAuthDialog.tsx"
if [ ! -f "$path" ]; then
echo "Missing file: $path"
git ls-files | rg 'GhAuthDialog\.tsx|GhAuthDialog' || true
exit 0
fi
echo "== file outline =="
ast-grep outline "$path" --view expanded || true
echo "== relevant source =="
wc -l "$path"
sed -n '1,180p' "$path" | cat -n
echo "== all dialog dismissal handler references in file =="
rg -n "onEscapeKeyDown|onInteractOutside|onFocusOutside|processActive|setPhase|detectGhCli|GhAuthTerminal|handleTerminalExit" "$path"Repository: superset-sh/superset Length of output: 8389 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== inspect Dialog implementation =="
git ls-files | rg '`@superset/ui/dialog`|dialog' | sed -n '1,80p' || true
if [ -f "apps/desktop/src/lib/ui/index.ts" ]; then
echo "== ui index excerpt =="
sed -n '1,220p' apps/desktop/src/lib/ui/index.ts | cat -n
fi
if [ -d "apps/desktop/src/lib/ui" ]; then
echo "== ui dialog files =="
git ls-files "apps/desktop/src/lib/ui" | rg -i 'dialog|ui|components' || true
fi
echo "== package imports maybe linked via source =="
rg -n 'DialogContent|useDialog|AlertDialog|Radix.*Dialog|`@radix-ui/react-dialog`' apps/desktop/src || true
echo "== inspect local package metadata if present =="
for f in package.json bun.lockb bun.lock pnpm-lock.yaml yarn.lock; do
[ -f "$f" ] && echo "-- $f --" && (rg -n '"`@radix-ui/react-dialog`"|`@radix-ui/react-dialog`|DialogContent' "$f" || true)
doneRepository: superset-sh/superset Length of output: 50377 🌐 Web query:
💡 Result: The default behavior of the Radix UI Dialog Content component is to close the dialog and return focus to the trigger element when the Escape key is pressed [1][2][3]. To customize or prevent this behavior, you can use the onEscapeKeyDown prop on the Dialog.Content component [1][2]. By calling event.preventDefault within the handler, you can stop the dialog from closing [4][5][6]. Example of preventing the Escape key from closing the dialog: <Dialog.Content onEscapeKeyDown={(event) => { event.preventDefault; }} > {/* Dialog content */} </Dialog.Content> You can also use this prop to execute custom logic before or instead of the default close action, such as showing a confirmation prompt or checking conditions before allowing the dialog to close [5][6]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== local dialog component =="
sed -n '1,220p' packages/ui/src/components/ui/dialog.tsx | cat -n
echo "== local DialogContent exports/usages =="
rg -n "export const DialogContent|DialogContent|DialogContentContent|onEscapeKeyDown|onInteractOutside|onFocusOutside" packages/ui/src/components/ui/dialog.tsx packages/ui/src/components/ui/alert-dialog.tsx || true
echo "== GhAuthTerminal source =="
file="apps/desktop/src/renderer/routes/_authenticated/onboarding/components/GhAuthDialog/GhAuthTerminal.tsx"
if [ -f "$file" ]; then
wc -l "$file"
ast-grep outline "$file" --view expanded || true
sed -n '1,240p' "$file" | cat -n
fiRepository: superset-sh/superset Length of output: 10050 Guard Escape dismissal while a shell process is active.
🔒 Proposed fix to guard the Escape key onFocusOutside={(event) => {
if (processActive) event.preventDefault();
}}
+ onEscapeKeyDown={(event) => {
+ if (processActive) event.preventDefault();
+ }}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||
| <DialogHeader> | ||||||||||||||||||||||||||||||||||||||||||||
| <DialogTitle>Sign in to GitHub CLI</DialogTitle> | ||||||||||||||||||||||||||||||||||||||||||||
| <DialogDescription> | ||||||||||||||||||||||||||||||||||||||||||||
| Follow the prompts below. Press Enter to open your browser, | ||||||||||||||||||||||||||||||||||||||||||||
| authorize the device code, and this window will update once you're | ||||||||||||||||||||||||||||||||||||||||||||
| signed in. | ||||||||||||||||||||||||||||||||||||||||||||
| </DialogDescription> | ||||||||||||||||||||||||||||||||||||||||||||
| <DialogTitle> | ||||||||||||||||||||||||||||||||||||||||||||
| {isInstall ? "Install GitHub CLI" : "Sign in to GitHub CLI"} | ||||||||||||||||||||||||||||||||||||||||||||
| </DialogTitle> | ||||||||||||||||||||||||||||||||||||||||||||
| </DialogHeader> | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="h-[240px] w-full overflow-hidden rounded-md bg-[#151110] p-2"> | ||||||||||||||||||||||||||||||||||||||||||||
| {open && <GhAuthTerminal onExit={onExit} />} | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| {phase === "success" ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex h-[296px] w-full flex-col items-center justify-center gap-2.5 rounded-lg border bg-[#151110]"> | ||||||||||||||||||||||||||||||||||||||||||||
| <LuCheck className="size-5 text-emerald-500" strokeWidth={2} /> | ||||||||||||||||||||||||||||||||||||||||||||
| <p className="text-sm font-medium text-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||
| {isInstall | ||||||||||||||||||||||||||||||||||||||||||||
| ? "GitHub CLI installed and signed in" | ||||||||||||||||||||||||||||||||||||||||||||
| : "Signed in to GitHub"} | ||||||||||||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||
| {phase === "failed" ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex min-h-11 items-center gap-2.5 rounded-md border bg-muted/40 px-3.5 py-1.5 text-sm text-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||
| <LuTriangleAlert className="size-3.5 shrink-0 text-destructive" /> | ||||||||||||||||||||||||||||||||||||||||||||
| <span className="select-text cursor-text"> | ||||||||||||||||||||||||||||||||||||||||||||
| {isInstall | ||||||||||||||||||||||||||||||||||||||||||||
| ? "Installation didn't complete" | ||||||||||||||||||||||||||||||||||||||||||||
| : "Sign-in didn't complete"} | ||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||
| <span className="ml-auto flex shrink-0 items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||
| {isInstall && ( | ||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||||||||||||||
| variant="ghost" | ||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => | ||||||||||||||||||||||||||||||||||||||||||||
| window.open( | ||||||||||||||||||||||||||||||||||||||||||||
| "https://cli.github.com/", | ||||||||||||||||||||||||||||||||||||||||||||
| "_blank", | ||||||||||||||||||||||||||||||||||||||||||||
| "noopener,noreferrer", | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||
| Install manually | ||||||||||||||||||||||||||||||||||||||||||||
| <LuArrowUpRight className="size-3" /> | ||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleRetry} | ||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||
| Retry | ||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : phase === "checking" ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex min-h-11 items-center gap-2.5 rounded-md bg-muted/40 px-3.5 text-sm text-muted-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||
| <Spinner className="size-3.5 shrink-0" /> | ||||||||||||||||||||||||||||||||||||||||||||
| Checking sign-in status… | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : oneTimeCode ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex min-h-11 items-center gap-3 rounded-md border bg-muted/40 px-3.5 py-1.5"> | ||||||||||||||||||||||||||||||||||||||||||||
| <span className="select-text cursor-text font-mono text-lg font-semibold tracking-[0.18em] text-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||
| {oneTimeCode} | ||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-xs text-muted-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||
| Paste this code on GitHub — your browser is opening | ||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||
| {copied ? ( | ||||||||||||||||||||||||||||||||||||||||||||
| <span className="ml-auto flex shrink-0 items-center gap-1.5 text-xs font-medium text-emerald-500"> | ||||||||||||||||||||||||||||||||||||||||||||
| <LuCheck className="size-3.5" strokeWidth={2.5} /> | ||||||||||||||||||||||||||||||||||||||||||||
| Copied | ||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||||||||||||||||||
| className="ml-auto" | ||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleCopyCode} | ||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||
| <LuCopy className="size-3.5" /> | ||||||||||||||||||||||||||||||||||||||||||||
| Copy | ||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex min-h-11 items-center gap-2.5 rounded-md bg-muted/40 px-3.5 text-sm text-muted-foreground"> | ||||||||||||||||||||||||||||||||||||||||||||
| <Spinner className="size-3.5 shrink-0" /> | ||||||||||||||||||||||||||||||||||||||||||||
| Follow the prompts below | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||||||||||||
| ref={terminalBoxRef} | ||||||||||||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||||||||||||
| "h-[240px] w-full overflow-hidden rounded-lg border bg-[#151110] p-3 transition-opacity duration-300", | ||||||||||||||||||||||||||||||||||||||||||||
| phase === "failed" && "opacity-60", | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||
| {open && ( | ||||||||||||||||||||||||||||||||||||||||||||
| <GhAuthTerminal | ||||||||||||||||||||||||||||||||||||||||||||
| key={attempt} | ||||||||||||||||||||||||||||||||||||||||||||
| command={isInstall ? GH_INSTALL_COMMAND : GH_AUTH_COMMAND} | ||||||||||||||||||||||||||||||||||||||||||||
| onExit={handleTerminalExit} | ||||||||||||||||||||||||||||||||||||||||||||
| onOutput={handleOutput} | ||||||||||||||||||||||||||||||||||||||||||||
| onWriterReady={(write) => { | ||||||||||||||||||||||||||||||||||||||||||||
| ptyWriteRef.current = write; | ||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| </DialogContent> | ||||||||||||||||||||||||||||||||||||||||||||
| </Dialog> | ||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Reopening during a previous status check can have the old check mark the new auth attempt successful and auto-close it. Guard this completion with a per-open/per-attempt token (or cancel it) before updating phase or scheduling the close.
Prompt for AI agents