Skip to content

Commit b47637e

Browse files
committed
feat: add GitHub and Google OAuth login
- Add signInWithGitHub and signInWithGoogle methods to AuthContext - Update LoginForm with working OAuth buttons (GitHub + Google) - Update SignupForm with same OAuth functionality - Add proper loading states for OAuth flows - Disable buttons during any auth operation to prevent double-clicks - OAuth redirects to /dashboard after successful authentication Supabase handles the OAuth flow automatically via onAuthStateChange listener.
1 parent b672728 commit b47637e

3 files changed

Lines changed: 176 additions & 24 deletions

File tree

frontend/src/components/auth/LoginForm.tsx

Lines changed: 76 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,28 @@ 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+
setOauthLoading(null)
43+
}
44+
}
45+
46+
const handleGoogleSignIn = async () => {
47+
setError('')
48+
setOauthLoading('google')
49+
try {
50+
await signInWithGoogle()
51+
} catch (err: any) {
52+
setError(err.message || 'Google sign in failed')
53+
setOauthLoading(null)
54+
}
55+
}
56+
3457
return (
3558
<div className="min-h-screen bg-background flex flex-col">
3659
<Navbar />
@@ -118,20 +141,61 @@ export function LoginForm() {
118141
<div className="w-full border-t border-border" />
119142
</div>
120143
<div className="relative flex justify-center text-xs">
121-
<span className="px-2 bg-card text-muted-foreground">or</span>
144+
<span className="px-2 bg-card text-muted-foreground">or continue with</span>
122145
</div>
123146
</div>
124147

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>
148+
<div className="grid grid-cols-2 gap-3">
149+
<Button
150+
type="button"
151+
variant="outline"
152+
className="h-10"
153+
onClick={handleGitHubSignIn}
154+
disabled={loading || oauthLoading !== null}
155+
>
156+
{oauthLoading === 'github' ? (
157+
<Loader2 className="w-4 h-4 animate-spin" />
158+
) : (
159+
<>
160+
<Github className="w-4 h-4 mr-2" />
161+
GitHub
162+
</>
163+
)}
164+
</Button>
165+
<Button
166+
type="button"
167+
variant="outline"
168+
className="h-10"
169+
onClick={handleGoogleSignIn}
170+
disabled={loading || oauthLoading !== null}
171+
>
172+
{oauthLoading === 'google' ? (
173+
<Loader2 className="w-4 h-4 animate-spin" />
174+
) : (
175+
<>
176+
<svg className="w-4 h-4 mr-2" viewBox="0 0 24 24">
177+
<path
178+
fill="currentColor"
179+
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"
180+
/>
181+
<path
182+
fill="currentColor"
183+
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"
184+
/>
185+
<path
186+
fill="currentColor"
187+
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"
188+
/>
189+
<path
190+
fill="currentColor"
191+
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"
192+
/>
193+
</svg>
194+
Google
195+
</>
196+
)}
197+
</Button>
198+
</div>
135199
</div>
136200

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

frontend/src/components/auth/SignupForm.tsx

Lines changed: 76 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,28 @@ 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+
setOauthLoading(null)
54+
}
55+
}
56+
57+
const handleGoogleSignIn = async () => {
58+
setError('')
59+
setOauthLoading('google')
60+
try {
61+
await signInWithGoogle()
62+
} catch (err: any) {
63+
setError(err.message || 'Google sign in failed')
64+
setOauthLoading(null)
65+
}
66+
}
67+
4568
return (
4669
<div className="min-h-screen bg-background flex flex-col">
4770
<Navbar />
@@ -146,20 +169,61 @@ export function SignupForm() {
146169
<div className="w-full border-t border-border" />
147170
</div>
148171
<div className="relative flex justify-center text-xs">
149-
<span className="px-2 bg-card text-muted-foreground">or</span>
172+
<span className="px-2 bg-card text-muted-foreground">or continue with</span>
150173
</div>
151174
</div>
152175

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>
176+
<div className="grid grid-cols-2 gap-3">
177+
<Button
178+
type="button"
179+
variant="outline"
180+
className="h-10"
181+
onClick={handleGitHubSignIn}
182+
disabled={loading || oauthLoading !== null}
183+
>
184+
{oauthLoading === 'github' ? (
185+
<Loader2 className="w-4 h-4 animate-spin" />
186+
) : (
187+
<>
188+
<Github className="w-4 h-4 mr-2" />
189+
GitHub
190+
</>
191+
)}
192+
</Button>
193+
<Button
194+
type="button"
195+
variant="outline"
196+
className="h-10"
197+
onClick={handleGoogleSignIn}
198+
disabled={loading || oauthLoading !== null}
199+
>
200+
{oauthLoading === 'google' ? (
201+
<Loader2 className="w-4 h-4 animate-spin" />
202+
) : (
203+
<>
204+
<svg className="w-4 h-4 mr-2" viewBox="0 0 24 24">
205+
<path
206+
fill="currentColor"
207+
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"
208+
/>
209+
<path
210+
fill="currentColor"
211+
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"
212+
/>
213+
<path
214+
fill="currentColor"
215+
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"
216+
/>
217+
<path
218+
fill="currentColor"
219+
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"
220+
/>
221+
</svg>
222+
Google
223+
</>
224+
)}
225+
</Button>
226+
</div>
163227
</div>
164228

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

frontend/src/contexts/AuthContext.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ interface AuthContextType {
88
loading: boolean;
99
signUp: (email: string, password: string) => Promise<void>;
1010
signIn: (email: string, password: string) => Promise<void>;
11+
signInWithGitHub: () => Promise<void>;
12+
signInWithGoogle: () => Promise<void>;
1113
signOut: () => Promise<void>;
1214
}
1315

@@ -64,12 +66,34 @@ export function AuthProvider({ children }: { children: ReactNode }) {
6466
if (error) throw error;
6567
};
6668

69+
const signInWithGitHub = async () => {
70+
const { error } = await supabase.auth.signInWithOAuth({
71+
provider: 'github',
72+
options: {
73+
redirectTo: `${window.location.origin}/dashboard`,
74+
},
75+
});
76+
if (error) throw error;
77+
};
78+
79+
const signInWithGoogle = async () => {
80+
const { error } = await supabase.auth.signInWithOAuth({
81+
provider: 'google',
82+
options: {
83+
redirectTo: `${window.location.origin}/dashboard`,
84+
},
85+
});
86+
if (error) throw error;
87+
};
88+
6789
const value = {
6890
user,
6991
session,
7092
loading,
7193
signUp,
7294
signIn,
95+
signInWithGitHub,
96+
signInWithGoogle,
7397
signOut,
7498
};
7599

0 commit comments

Comments
 (0)