Skip to content

Commit f7d75f7

Browse files
committed
fix(github-oauth): improve UX after OAuth flow
GitHubCallbackPage: - Check sessionStorage before attempting exchange (prevents double-execution errors) - Navigate with ?openGitHubImport=true param to auto-open modal - Handle edge case where state already cleared (fast redirect) DashboardHome: - Read openGitHubImport param and auto-open GitHub import modal - Clear param from URL after opening modal GitHubRepoSelector: - Add hasFetchedReposRef to prevent repeated fetch calls - Only fetch repos once per modal open Fixes: double callback errors, manual modal reopen after OAuth, repeated API calls
1 parent d8a010e commit f7d75f7

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

frontend/src/components/GitHubRepoSelector.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect } from 'react';
1+
import { useState, useEffect, useRef } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
33
import { Github, Search, Lock, Star, Loader2, X, Check, AlertCircle, ExternalLink } from 'lucide-react';
44
import { Button } from '@/components/ui/button';
@@ -24,6 +24,7 @@ export function GitHubRepoSelector({
2424
const [selected, setSelected] = useState<Set<number>>(new Set());
2525
const [searchQuery, setSearchQuery] = useState('');
2626
const [connecting, setConnecting] = useState(false);
27+
const hasFetchedReposRef = useRef(false);
2728

2829
const remainingSlots = maxSelectable - currentRepoCount;
2930

@@ -33,11 +34,14 @@ export function GitHubRepoSelector({
3334
setSelected(new Set());
3435
setSearchQuery('');
3536
clearError();
37+
hasFetchedReposRef.current = false;
3638
}
3739
}, [isOpen, checkStatus, clearError]);
3840

3941
useEffect(() => {
40-
if (isOpen && status?.connected) {
42+
// Only fetch once per modal open, after status confirms connected
43+
if (isOpen && status?.connected && !hasFetchedReposRef.current) {
44+
hasFetchedReposRef.current = true;
4145
fetchRepos();
4246
}
4347
}, [isOpen, status?.connected, fetchRepos]);

frontend/src/components/dashboard/DashboardHome.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useState, useEffect } from 'react'
2+
import { useSearchParams } from 'react-router-dom'
23
import { motion, AnimatePresence } from 'framer-motion'
34
import { toast } from 'sonner'
45
import {
@@ -35,6 +36,7 @@ type RepoTab = 'overview' | 'search' | 'dependencies' | 'insights' | 'impact'
3536

3637
export function DashboardHome() {
3738
const { session } = useAuth()
39+
const [searchParams, setSearchParams] = useSearchParams()
3840
const [repos, setRepos] = useState<Repository[]>([])
3941
const [selectedRepo, setSelectedRepo] = useState<string | null>(null)
4042
const [activeTab, setActiveTab] = useState<RepoTab>('overview')
@@ -48,6 +50,16 @@ export function DashboardHome() {
4850
const [indexingRepoName, setIndexingRepoName] = useState<string>('')
4951
const [showIndexingModal, setShowIndexingModal] = useState(false)
5052

53+
// Auto-open GitHub import modal if redirected from OAuth callback
54+
useEffect(() => {
55+
if (searchParams.get('openGitHubImport') === 'true') {
56+
setShowGitHubSelector(true)
57+
// Clear the param from URL without triggering navigation
58+
searchParams.delete('openGitHubImport')
59+
setSearchParams(searchParams, { replace: true })
60+
}
61+
}, [searchParams, setSearchParams])
62+
5163
const fetchRepos = async () => {
5264
if (!session?.access_token) return
5365
try {

frontend/src/pages/GitHubCallbackPage.tsx

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,26 @@ export function GitHubCallbackPage() {
1616
useEffect(() => {
1717
// Prevent double-execution in React StrictMode
1818
if (callbackRanRef.current) return;
19+
20+
const code = searchParams.get('code');
21+
const state = searchParams.get('state');
22+
23+
// Also check if we already processed this code (sessionStorage cleanup happens on success)
24+
const storedState = sessionStorage.getItem('github_oauth_state');
25+
if (!storedState) {
26+
// State already cleared = already processed
27+
setStatus('success');
28+
timeoutRef.current = setTimeout(() => {
29+
navigate('/dashboard?openGitHubImport=true', { replace: true });
30+
}, 500);
31+
return;
32+
}
33+
1934
callbackRanRef.current = true;
2035

2136
let mounted = true;
2237

2338
const handleCallback = async () => {
24-
const code = searchParams.get('code');
25-
const state = searchParams.get('state');
2639
const error = searchParams.get('error');
2740
const errorDescription = searchParams.get('error_description');
2841

@@ -53,7 +66,8 @@ export function GitHubCallbackPage() {
5366
setStatus('success');
5467
timeoutRef.current = setTimeout(() => {
5568
if (mounted) {
56-
navigate('/dashboard', { replace: true });
69+
// Navigate with param to auto-open import modal
70+
navigate('/dashboard?openGitHubImport=true', { replace: true });
5771
}
5872
}, 1500);
5973
} else {

0 commit comments

Comments
 (0)