Skip to content

Commit 6b2ab1b

Browse files
committed
fix: auth, added useAuth hook for single source of truth
1 parent 49f6d36 commit 6b2ab1b

File tree

13 files changed

+355
-208
lines changed

13 files changed

+355
-208
lines changed

app/api/leaderboard/route.ts

Whitespace-only changes.

app/auth/callback/route.ts

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,55 @@
11
import { NextResponse } from 'next/server'
22
import { createClient } from '@/utils/supabase/server'
33

4+
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
5+
46
export async function GET(request: Request) {
57
const { searchParams, origin } = new URL(request.url)
68
const code = searchParams.get('code')
79
const next = searchParams.get('next') ?? '/dashboard'
810

9-
if (code) {
10-
const supabase = await createClient()
11-
const { error } = await supabase.auth.exchangeCodeForSession(code)
12-
if (!error) {
13-
const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer
14-
const isLocalEnv = process.env.NODE_ENV === 'development'
15-
if (isLocalEnv) {
16-
// we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host
17-
return NextResponse.redirect(`${origin}${next}`)
18-
} else if (forwardedHost) {
19-
return NextResponse.redirect(`https://${forwardedHost}${next}`)
20-
} else {
21-
return NextResponse.redirect(`${origin}${next}`)
22-
}
23-
}
11+
if (!code) {
12+
return NextResponse.redirect(`${origin}/?error=no_code`)
13+
}
14+
15+
const supabase = await createClient()
16+
const { data, error } = await supabase.auth.exchangeCodeForSession(code)
17+
18+
if (error || !data.session) {
19+
console.error('Session exchange error:', error)
20+
return NextResponse.redirect(`${origin}/?error=auth_failed`)
2421
}
2522

26-
// return the user to an error page with instructions
27-
return NextResponse.redirect(`${origin}/auth/auth-code-error`)
23+
// Verify user exists in backend database
24+
try {
25+
const response = await fetch(`${API_URL}/user`, {
26+
method: 'POST',
27+
headers: {
28+
'Authorization': `Bearer ${data.session.access_token}`,
29+
'Content-Type': 'application/json',
30+
},
31+
})
32+
33+
if (!response.ok) {
34+
// User not registered on VTOP - sign them out
35+
await supabase.auth.signOut()
36+
return NextResponse.redirect(`${origin}/?error=not_registered`)
37+
}
38+
39+
// User verified! Redirect to dashboard
40+
const forwardedHost = request.headers.get('x-forwarded-host')
41+
const isLocalEnv = process.env.NODE_ENV === 'development'
42+
43+
if (isLocalEnv) {
44+
return NextResponse.redirect(`${origin}${next}`)
45+
} else if (forwardedHost) {
46+
return NextResponse.redirect(`https://${forwardedHost}${next}`)
47+
} else {
48+
return NextResponse.redirect(`${origin}${next}`)
49+
}
50+
} catch (error) {
51+
console.error('Backend verification error:', error)
52+
await supabase.auth.signOut()
53+
return NextResponse.redirect(`${origin}/?error=verification_failed`)
54+
}
2855
}

app/dashboard/page.tsx

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,39 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useEffect } from "react";
44
import { useRouter } from "next/navigation";
5-
import { supabase } from "@/lib/supabase";
5+
import { useAuth } from "@/hooks/useAuth";
66
import Navbar from "@/components/Navbar";
77
import DashboardSection from "@/components/DashboardSection";
88

99
export default function DashboardPage() {
10-
const [user, setUser] = useState<any>(null);
11-
const [isLoggedIn, setIsLoggedIn] = useState(false);
12-
const [isLoading, setIsLoading] = useState(true);
10+
const { user, isAuthenticated, isLoading } = useAuth();
1311
const router = useRouter();
1412

1513
useEffect(() => {
16-
supabase.auth.getSession().then(({ data }) => {
17-
if (!data.session) {
18-
router.push("/");
19-
} else {
20-
setIsLoggedIn(true);
21-
setUser(data.session.user);
22-
}
23-
setIsLoading(false);
24-
});
25-
26-
const { data: listener } = supabase.auth.onAuthStateChange(
27-
(_event, session) => {
28-
if (!session) {
29-
router.push("/");
30-
} else {
31-
setIsLoggedIn(true);
32-
setUser(session.user);
33-
}
34-
}
35-
);
36-
37-
return () => listener.subscription.unsubscribe();
38-
}, [router]);
14+
if (!isLoading && !isAuthenticated) {
15+
router.push("/");
16+
}
17+
}, [isLoading, isAuthenticated, router]);
3918

4019
if (isLoading) {
41-
return <div className="min-h-screen bg-black flex items-center justify-center text-white">Loading...</div>;
20+
return (
21+
<div className="min-h-screen bg-black flex items-center justify-center text-white">
22+
Loading...
23+
</div>
24+
);
4225
}
4326

44-
if (!isLoggedIn) {
27+
if (!isAuthenticated) {
4528
return null;
4629
}
4730

4831
return (
4932
<>
50-
<Navbar isLoggedIn={true} />
33+
<Navbar />
5134
<main className="h-screen w-full overflow-hidden relative">
5235
<DashboardSection user={user} />
5336
</main>
54-
5537
</>
5638
);
5739
}

app/layout.tsx

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Metadata } from "next";
22
import "./globals.css";
33
import { sora } from "@/lib/fonts";
4+
import { AuthProvider } from "@/hooks/useAuth";
45

56
export const metadata: Metadata = {
67
title: "ModelArena | IEEECS-VIT",
@@ -17,31 +18,9 @@ export default function RootLayout({
1718
}) {
1819
return (
1920
<html lang="en">
20-
<body className={sora.className}>{children}</body>
21+
<body className={sora.className}>
22+
<AuthProvider>{children}</AuthProvider>
23+
</body>
2124
</html>
2225
);
2326
}
24-
25-
// import { supabase } from "@/lib/supabase";
26-
// import Navbar from "@/components/Navbar";
27-
28-
// export default async function RootLayout({
29-
// children,
30-
// }: {
31-
// children: React.ReactNode;
32-
// }) {
33-
// const {
34-
// data: { session },
35-
// } = await supabase.auth.getSession();
36-
37-
// return (
38-
// <html lang="en">
39-
// <body>
40-
// <Navbar isLoggedIn={!!session} />
41-
// {children}
42-
// </body>
43-
// </html>
44-
// );
45-
// }
46-
47-

app/leaderboard/final/page.tsx

Lines changed: 0 additions & 25 deletions
This file was deleted.

app/leaderboard/live/page.tsx

Lines changed: 0 additions & 7 deletions
This file was deleted.

app/login/page.tsx

Lines changed: 0 additions & 17 deletions
This file was deleted.

app/page.tsx

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useEffect } from "react";
44
import dynamic from "next/dynamic";
5-
import { supabase } from "../lib/supabase";
5+
import { useAuth } from "@/hooks/useAuth";
66

77
import Navbar from "../components/Navbar";
88
import Preloader from "../components/Preloader";
@@ -26,59 +26,34 @@ const Hero3D = dynamic(() => import("../components/Hero3D"), {
2626
});
2727

2828
export default function LandingPage() {
29-
const [isLoggedIn, setIsLoggedIn] = useState(false);
29+
const { isAuthenticated } = useAuth();
3030

3131
usePremiumScrollEffects();
3232

33+
// Check for auth errors from callback
3334
useEffect(() => {
34-
supabase.auth.getSession().then(({ data }) => {
35-
setIsLoggedIn(!!data.session);
36-
});
37-
38-
const { data: listener } = supabase.auth.onAuthStateChange(
39-
async (event, session) => {
40-
setIsLoggedIn(!!session);
41-
42-
// // Call /api/user on sign in to check if user is registered on VTOP
43-
// if (event === "SIGNED_IN" && session) {
44-
// try {
45-
// const response = await fetch("/api/user", {
46-
// method: "POST",
47-
// headers: {
48-
// "Authorization": `Bearer ${session.access_token}`,
49-
// "Content-Type": "application/json",
50-
// },
51-
// });
52-
53-
// if (!response.ok) {
54-
// let errorMessage = "Authentication failed";
55-
// try {
56-
// const errorData = await response.json();
57-
// errorMessage = errorData.error || errorMessage;
58-
// } catch {
59-
// // Response wasn't JSON, use status text
60-
// errorMessage = response.statusText || errorMessage;
61-
// }
62-
// // Sign out the user and show backend error message
63-
// await supabase.auth.signOut();
64-
// alert(errorMessage);
65-
// }
66-
// } catch (error) {
67-
// console.error("Failed to check user registration:", error);
68-
// // Don't crash the app, just log the error
69-
// }
70-
// }
35+
const params = new URLSearchParams(window.location.search);
36+
const errorParam = params.get("error");
37+
38+
if (errorParam) {
39+
let message = "Login failed. Please try again.";
40+
if (errorParam === "not_registered") {
41+
message = "Your email is not registered on VTOP. Please register on VTOP first.";
42+
} else if (errorParam === "verification_failed") {
43+
message = "Could not verify your account. Please try again.";
7144
}
72-
);
7345

74-
return () => {
75-
listener.subscription.unsubscribe();
76-
};
46+
// Show toast or alert
47+
alert(message);
48+
49+
// Clear the error from URL
50+
window.history.replaceState({}, document.title, "/");
51+
}
7752
}, []);
7853

7954
return (
8055
<>
81-
<Navbar isLoggedIn={isLoggedIn} />
56+
<Navbar />
8257
<Preloader />
8358

8459
<main className="scroll-smooth">
@@ -137,15 +112,15 @@ export default function LandingPage() {
137112
<ScaleReveal from={0.8} delay={0.7}>
138113
<button
139114
onClick={() => {
140-
if (isLoggedIn) {
115+
if (isAuthenticated) {
141116
window.location.href = "/dashboard";
142117
} else {
143118
window.open("https://vtop.vit.ac.in", "_blank");
144119
}
145120
}}
146121
className="mt-8 bg-[#CCFF00] px-6 py-2.5 text-sm font-bold tracking-widest text-black uppercase transition-all duration-300 shadow-[4px_4px_0px_0px_rgba(255,255,255,0.9)] hover:shadow-none hover:translate-x-[4px] hover:translate-y-[4px] hover:scale-105"
147122
>
148-
{isLoggedIn ? "DASHBOARD" : "REGISTER NOW"}
123+
{isAuthenticated ? "DASHBOARD" : "REGISTER NOW"}
149124
</button>
150125
</ScaleReveal>
151126
</div>
@@ -172,7 +147,7 @@ export default function LandingPage() {
172147
<AboutSection />
173148
</ClipReveal>
174149
</section>
175-
150+
176151
{/* ========== TIMELINE SECTION ========== */}
177152
<section id="timeline">
178153
<ClipReveal direction="bottom">

app/submit/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export default function SubmitPage() {
8585

8686
return (
8787
<>
88-
<Navbar isLoggedIn={true} />
88+
<Navbar />
8989
<main className="min-h-screen w-full bg-black pt-24 px-6 md:px-12 font-mono">
9090
<div className="max-w-4xl mx-auto">
9191
{/* HEADER */}

app/team/page.tsx

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)