Skip to content

Commit f4cd7a2

Browse files
authored
Merge pull request #223 from DevanshuNEU/feature/oauth-social-login
feat: Add GitHub and Google OAuth Login
2 parents b672728 + 584c48b commit f4cd7a2

8 files changed

Lines changed: 308 additions & 41 deletions

File tree

frontend/src/components/auth/LoginForm.tsx

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export function LoginForm() {
1313
const [password, setPassword] = useState('')
1414
const [error, setError] = useState('')
1515
const [loading, setLoading] = useState(false)
16-
const { signIn } = useAuth()
16+
const [oauthLoading, setOauthLoading] = useState<'github' | 'google' | null>(null)
17+
const { signIn, signInWithGitHub, signInWithGoogle } = useAuth()
1718
const navigate = useNavigate()
1819

1920
const handleSubmit = async (e: React.FormEvent) => {
@@ -31,6 +32,30 @@ export function LoginForm() {
3132
}
3233
}
3334

35+
const handleGitHubSignIn = async () => {
36+
setError('')
37+
setOauthLoading('github')
38+
try {
39+
await signInWithGitHub()
40+
} catch (err: any) {
41+
setError(err.message || 'GitHub sign in failed')
42+
} finally {
43+
setOauthLoading(null)
44+
}
45+
}
46+
47+
const handleGoogleSignIn = async () => {
48+
setError('')
49+
setOauthLoading('google')
50+
try {
51+
await signInWithGoogle()
52+
} catch (err: any) {
53+
setError(err.message || 'Google sign in failed')
54+
} finally {
55+
setOauthLoading(null)
56+
}
57+
}
58+
3459
return (
3560
<div className="min-h-screen bg-background flex flex-col">
3661
<Navbar />
@@ -118,20 +143,69 @@ export function LoginForm() {
118143
<div className="w-full border-t border-border" />
119144
</div>
120145
<div className="relative flex justify-center text-xs">
121-
<span className="px-2 bg-card text-muted-foreground">or</span>
146+
<span className="px-2 bg-card text-muted-foreground">or continue with</span>
122147
</div>
123148
</div>
124149

125-
<Button
126-
type="button"
127-
variant="outline"
128-
className="w-full h-10"
129-
disabled
130-
>
131-
<Github className="w-4 h-4 mr-2" />
132-
Continue with GitHub
133-
<span className="ml-1 text-xs text-muted-foreground">(Soon)</span>
134-
</Button>
150+
<div className="grid grid-cols-2 gap-3">
151+
<Button
152+
type="button"
153+
variant="outline"
154+
className="h-10"
155+
onClick={handleGitHubSignIn}
156+
disabled={loading || oauthLoading !== null}
157+
aria-label={oauthLoading === 'github' ? 'Signing in with GitHub' : undefined}
158+
>
159+
{oauthLoading === 'github' ? (
160+
<>
161+
<Loader2 className="w-4 h-4 animate-spin" />
162+
<span className="sr-only">Signing in with GitHub</span>
163+
</>
164+
) : (
165+
<>
166+
<Github className="w-4 h-4 mr-2" />
167+
GitHub
168+
</>
169+
)}
170+
</Button>
171+
<Button
172+
type="button"
173+
variant="outline"
174+
className="h-10"
175+
onClick={handleGoogleSignIn}
176+
disabled={loading || oauthLoading !== null}
177+
aria-label={oauthLoading === 'google' ? 'Signing in with Google' : undefined}
178+
>
179+
{oauthLoading === 'google' ? (
180+
<>
181+
<Loader2 className="w-4 h-4 animate-spin" />
182+
<span className="sr-only">Signing in with Google</span>
183+
</>
184+
) : (
185+
<>
186+
<svg className="w-4 h-4 mr-2" viewBox="0 0 24 24">
187+
<path
188+
fill="currentColor"
189+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
190+
/>
191+
<path
192+
fill="currentColor"
193+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
194+
/>
195+
<path
196+
fill="currentColor"
197+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
198+
/>
199+
<path
200+
fill="currentColor"
201+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
202+
/>
203+
</svg>
204+
Google
205+
</>
206+
)}
207+
</Button>
208+
</div>
135209
</div>
136210

137211
<p className="text-center text-sm text-muted-foreground mt-6">

frontend/src/components/auth/SignupForm.tsx

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ export function SignupForm() {
1414
const [confirmPassword, setConfirmPassword] = useState('')
1515
const [error, setError] = useState('')
1616
const [loading, setLoading] = useState(false)
17-
const { signUp } = useAuth()
17+
const [oauthLoading, setOauthLoading] = useState<'github' | 'google' | null>(null)
18+
const { signUp, signInWithGitHub, signInWithGoogle } = useAuth()
1819
const navigate = useNavigate()
1920

2021
const handleSubmit = async (e: React.FormEvent) => {
@@ -42,6 +43,30 @@ export function SignupForm() {
4243
}
4344
}
4445

46+
const handleGitHubSignIn = async () => {
47+
setError('')
48+
setOauthLoading('github')
49+
try {
50+
await signInWithGitHub()
51+
} catch (err: any) {
52+
setError(err.message || 'GitHub sign in failed')
53+
} finally {
54+
setOauthLoading(null)
55+
}
56+
}
57+
58+
const handleGoogleSignIn = async () => {
59+
setError('')
60+
setOauthLoading('google')
61+
try {
62+
await signInWithGoogle()
63+
} catch (err: any) {
64+
setError(err.message || 'Google sign in failed')
65+
} finally {
66+
setOauthLoading(null)
67+
}
68+
}
69+
4570
return (
4671
<div className="min-h-screen bg-background flex flex-col">
4772
<Navbar />
@@ -146,20 +171,69 @@ export function SignupForm() {
146171
<div className="w-full border-t border-border" />
147172
</div>
148173
<div className="relative flex justify-center text-xs">
149-
<span className="px-2 bg-card text-muted-foreground">or</span>
174+
<span className="px-2 bg-card text-muted-foreground">or continue with</span>
150175
</div>
151176
</div>
152177

153-
<Button
154-
type="button"
155-
variant="outline"
156-
className="w-full h-10"
157-
disabled
158-
>
159-
<Github className="w-4 h-4 mr-2" />
160-
Continue with GitHub
161-
<span className="ml-1 text-xs text-muted-foreground">(Soon)</span>
162-
</Button>
178+
<div className="grid grid-cols-2 gap-3">
179+
<Button
180+
type="button"
181+
variant="outline"
182+
className="h-10"
183+
onClick={handleGitHubSignIn}
184+
disabled={loading || oauthLoading !== null}
185+
aria-label={oauthLoading === 'github' ? 'Signing in with GitHub' : undefined}
186+
>
187+
{oauthLoading === 'github' ? (
188+
<>
189+
<Loader2 className="w-4 h-4 animate-spin" />
190+
<span className="sr-only">Signing in with GitHub</span>
191+
</>
192+
) : (
193+
<>
194+
<Github className="w-4 h-4 mr-2" />
195+
GitHub
196+
</>
197+
)}
198+
</Button>
199+
<Button
200+
type="button"
201+
variant="outline"
202+
className="h-10"
203+
onClick={handleGoogleSignIn}
204+
disabled={loading || oauthLoading !== null}
205+
aria-label={oauthLoading === 'google' ? 'Signing in with Google' : undefined}
206+
>
207+
{oauthLoading === 'google' ? (
208+
<>
209+
<Loader2 className="w-4 h-4 animate-spin" />
210+
<span className="sr-only">Signing in with Google</span>
211+
</>
212+
) : (
213+
<>
214+
<svg className="w-4 h-4 mr-2" viewBox="0 0 24 24">
215+
<path
216+
fill="currentColor"
217+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
218+
/>
219+
<path
220+
fill="currentColor"
221+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
222+
/>
223+
<path
224+
fill="currentColor"
225+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
226+
/>
227+
<path
228+
fill="currentColor"
229+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
230+
/>
231+
</svg>
232+
Google
233+
</>
234+
)}
235+
</Button>
236+
</div>
163237
</div>
164238

165239
<p className="text-center text-sm text-muted-foreground mt-6">

frontend/src/components/dashboard/DashboardHome.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export function DashboardHome() {
117117
] as const
118118

119119
return (
120-
<div className="pt-14 min-h-screen">
120+
<div className="min-h-screen">
121121
<AnimatePresence mode="wait">
122122
{/* Repository List View */}
123123
{!isRepoView && (

frontend/src/components/dashboard/DashboardLayout.tsx

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState, useEffect } from 'react'
2-
import { Outlet } from 'react-router-dom'
2+
import { Outlet, useLocation } from 'react-router-dom'
33
import { Sidebar } from './Sidebar'
44
import { TopNav } from './TopNav'
55
import { CommandPalette } from './CommandPalette'
@@ -15,7 +15,9 @@ const SIDEBAR_STORAGE_KEY = 'codeintel-sidebar-collapsed'
1515

1616
export function DashboardLayout({ children }: DashboardLayoutProps) {
1717
const { theme } = useTheme()
18+
const location = useLocation()
1819

20+
// Desktop: collapsed state (narrow sidebar)
1921
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
2022
try {
2123
const stored = localStorage.getItem(SIDEBAR_STORAGE_KEY)
@@ -24,8 +26,12 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
2426
return false
2527
}
2628
})
29+
30+
// Mobile: open/closed state (overlay)
31+
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
2732
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false)
2833

34+
// Persist desktop collapsed state
2935
useEffect(() => {
3036
try {
3137
localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(sidebarCollapsed))
@@ -34,6 +40,30 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
3440
}
3541
}, [sidebarCollapsed])
3642

43+
// Close mobile menu on route change
44+
useEffect(() => {
45+
setMobileMenuOpen(false)
46+
}, [location.pathname])
47+
48+
// Close mobile menu on escape key
49+
useEffect(() => {
50+
const handleEscape = (e: KeyboardEvent) => {
51+
if (e.key === 'Escape') setMobileMenuOpen(false)
52+
}
53+
document.addEventListener('keydown', handleEscape)
54+
return () => document.removeEventListener('keydown', handleEscape)
55+
}, [])
56+
57+
// Prevent body scroll when mobile menu is open
58+
useEffect(() => {
59+
if (mobileMenuOpen) {
60+
document.body.style.overflow = 'hidden'
61+
} else {
62+
document.body.style.overflow = ''
63+
}
64+
return () => { document.body.style.overflow = '' }
65+
}, [mobileMenuOpen])
66+
3767
useKeyboardShortcut(SHORTCUTS.COMMAND_PALETTE, () => {
3868
setCommandPaletteOpen(true)
3969
})
@@ -42,26 +72,49 @@ export function DashboardLayout({ children }: DashboardLayoutProps) {
4272
setSidebarCollapsed((prev: boolean) => !prev)
4373
})
4474

75+
const handleToggleSidebar = () => {
76+
// On mobile: toggle overlay menu
77+
// On desktop: toggle collapsed state
78+
if (window.innerWidth < 1024) {
79+
setMobileMenuOpen(!mobileMenuOpen)
80+
} else {
81+
setSidebarCollapsed(!sidebarCollapsed)
82+
}
83+
}
84+
4585
return (
4686
<div className="min-h-screen bg-background">
4787
<TopNav
48-
onToggleSidebar={() => setSidebarCollapsed(!sidebarCollapsed)}
88+
onToggleSidebar={handleToggleSidebar}
4989
sidebarCollapsed={sidebarCollapsed}
5090
onOpenCommandPalette={() => setCommandPaletteOpen(true)}
5191
/>
5292

5393
<div className="flex">
94+
{/* Mobile backdrop */}
95+
{mobileMenuOpen && (
96+
<div
97+
className="fixed inset-0 z-30 bg-black/50 lg:hidden"
98+
onClick={() => setMobileMenuOpen(false)}
99+
aria-hidden="true"
100+
/>
101+
)}
102+
54103
<Sidebar
55104
collapsed={sidebarCollapsed}
56-
onToggle={() => setSidebarCollapsed(!sidebarCollapsed)}
105+
onToggle={handleToggleSidebar}
106+
mobileOpen={mobileMenuOpen}
107+
onMobileClose={() => setMobileMenuOpen(false)}
57108
/>
58109

110+
{/* Main content - no margin on mobile, dynamic margin on desktop */}
59111
<main
60-
className={`flex-1 transition-all duration-300 ${
61-
sidebarCollapsed ? 'ml-16' : 'ml-60'
62-
}`}
112+
className={`
113+
flex-1 transition-all duration-300 pt-[var(--navbar-height)]
114+
ml-0 ${sidebarCollapsed ? 'lg:ml-[var(--sidebar-width-collapsed)]' : 'lg:ml-[var(--sidebar-width)]'}
115+
`}
63116
>
64-
<div className="p-6">
117+
<div className="p-4 md:p-6">
65118
{children || <Outlet />}
66119
</div>
67120
</main>

0 commit comments

Comments
 (0)