From 5f26d3964dafe845c095315a9d1a885985a35ad5 Mon Sep 17 00:00:00 2001 From: AndyMik90 Date: Fri, 19 Dec 2025 16:17:02 +0100 Subject: [PATCH] auto-claude: subtask-2-1 - Add device code display state and UI component - Add device code, auth URL, and browser opened state variables to GitHubOAuthFlow - Display prominent device code card during authentication with copy button - Show manual auth URL link when browser fails to open - Update IPC types to include deviceCode, authUrl, browserOpened, and fallbackUrl - Add visual feedback for code copy (checkmark icon when copied) - Instructions adapt based on whether browser opened successfully --- .../project-settings/GitHubOAuthFlow.tsx | 146 ++++++++++++++++-- auto-claude-ui/src/shared/types/ipc.ts | 9 +- 2 files changed, 138 insertions(+), 17 deletions(-) diff --git a/auto-claude-ui/src/renderer/components/project-settings/GitHubOAuthFlow.tsx b/auto-claude-ui/src/renderer/components/project-settings/GitHubOAuthFlow.tsx index c743cd6c..df9e677e 100644 --- a/auto-claude-ui/src/renderer/components/project-settings/GitHubOAuthFlow.tsx +++ b/auto-claude-ui/src/renderer/components/project-settings/GitHubOAuthFlow.tsx @@ -6,7 +6,9 @@ import { AlertCircle, Info, ExternalLink, - Terminal + Terminal, + Copy, + Check } from 'lucide-react'; import { Button } from '../ui/button'; import { Card, CardContent } from '../ui/card'; @@ -40,6 +42,12 @@ export function GitHubOAuthFlow({ onSuccess, onCancel }: GitHubOAuthFlowProps) { const [cliVersion, setCliVersion] = useState(); const [username, setUsername] = useState(); + // Device flow state for displaying code and auth URL + const [deviceCode, setDeviceCode] = useState(null); + const [authUrl, setAuthUrl] = useState(null); + const [browserOpened, setBrowserOpened] = useState(false); + const [codeCopied, setCodeCopied] = useState(false); + // Check gh CLI installation and authentication status on mount // Use a ref to prevent double-execution in React Strict Mode const hasCheckedRef = useRef(false); @@ -138,18 +146,44 @@ export function GitHubOAuthFlow({ onSuccess, onCancel }: GitHubOAuthFlowProps) { setStatus('authenticating'); setError(null); + // Reset device flow state + setDeviceCode(null); + setAuthUrl(null); + setBrowserOpened(false); + setCodeCopied(false); + try { debugLog('Calling startGitHubAuth...'); const result = await window.electronAPI.startGitHubAuth(); debugLog('startGitHubAuth result:', result); + // Capture device flow info if available + if (result.data?.deviceCode) { + debugLog('Device code received:', result.data.deviceCode); + setDeviceCode(result.data.deviceCode); + } + if (result.data?.authUrl) { + debugLog('Auth URL received:', result.data.authUrl); + setAuthUrl(result.data.authUrl); + } + if (result.data?.browserOpened !== undefined) { + debugLog('Browser opened status:', result.data.browserOpened); + setBrowserOpened(result.data.browserOpened); + } + if (result.success && result.data?.success) { debugLog('Auth successful, fetching token...'); // Fetch the token and notify parent await fetchAndNotifyToken(); } else { debugLog('Auth failed:', result.error); - setError(result.error || 'Authentication failed'); + // Include fallback URL info in error message if available + const errorMessage = result.error || 'Authentication failed'; + setError(errorMessage); + // Keep authUrl from response for fallback display + if (result.data?.fallbackUrl) { + setAuthUrl(result.data.fallbackUrl); + } setStatus('error'); } } catch (err) { @@ -169,6 +203,26 @@ export function GitHubOAuthFlow({ onSuccess, onCancel }: GitHubOAuthFlowProps) { checkGitHubStatus(); }; + const handleCopyDeviceCode = async () => { + if (!deviceCode) return; + debugLog('Copying device code to clipboard'); + try { + await navigator.clipboard.writeText(deviceCode); + setCodeCopied(true); + // Reset the copied state after 2 seconds + setTimeout(() => setCodeCopied(false), 2000); + } catch (err) { + debugLog('Failed to copy device code:', err); + } + }; + + const handleOpenAuthUrl = () => { + if (authUrl) { + debugLog('Opening auth URL manually:', authUrl); + window.open(authUrl, '_blank'); + } + }; + debugLog('Rendering with status:', status); return ( @@ -263,21 +317,81 @@ export function GitHubOAuthFlow({ onSuccess, onCancel }: GitHubOAuthFlowProps) { {/* Authenticating */} {status === 'authenticating' && ( - - -
- -
-

- Authenticating... -

-

- Please complete the authentication in your browser. This window will update automatically. -

+
+ + +
+ +
+

+ Authenticating... +

+

+ {browserOpened + ? 'Please complete the authentication in your browser. This window will update automatically.' + : 'Waiting for authentication flow to start...'} +

+
-
- - + + + + {/* Device Code Display */} + {deviceCode && ( + + +
+
+

+ Your one-time code +

+
+ + {deviceCode} + + +
+
+ +
+

+ {browserOpened + ? 'Enter this code in your browser to complete authentication.' + : 'Copy this code, then open the link below to authenticate.'} +

+ {!browserOpened && authUrl && ( + + )} +
+
+
+
+ )} +
)} {/* Success */} diff --git a/auto-claude-ui/src/shared/types/ipc.ts b/auto-claude-ui/src/shared/types/ipc.ts index 050efe6c..26d0a88b 100644 --- a/auto-claude-ui/src/shared/types/ipc.ts +++ b/auto-claude-ui/src/shared/types/ipc.ts @@ -319,7 +319,14 @@ export interface ElectronAPI { // GitHub OAuth operations (gh CLI) checkGitHubCli: () => Promise>; checkGitHubAuth: () => Promise>; - startGitHubAuth: () => Promise>; + startGitHubAuth: () => Promise>; getGitHubToken: () => Promise>; getGitHubUser: () => Promise>; listGitHubUserRepos: () => Promise }>>;