Skip to content

Commit 2797cdc

Browse files
committed
feat(auth):implmented auth using firebase checking in root layout also added a nav bar as well
1 parent 63b95f7 commit 2797cdc

File tree

9 files changed

+297
-37
lines changed

9 files changed

+297
-37
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"react": "19.1.0",
4141
"react-datepicker": "^8.7.0",
4242
"react-dom": "19.1.0",
43+
"react-icons": "^5.5.0",
4344
"react-testing-library": "^8.0.1",
4445
"react-time-picker": "^8.0.2"
4546
},

public/sw.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
File renamed without changes.

src/app/layout.tsx

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { createMetadata } from "@/lib/metadata";
1+
// app/layout.tsx
2+
"use client"; // Make this layout a client component for auth checks
3+
4+
import { ReactNode, useEffect, useState } from "react";
5+
import { useRouter } from "next/navigation";
26
import { Geist, Geist_Mono } from "next/font/google";
37
import "./globals.css";
4-
import Script from "next/script";
5-
import { jsonLd } from "@/lib/metadata";
6-
8+
import Navbar from "@/components/navbar";
9+
import { onAuthStateChanged } from "@/lib/firebase/auth";
10+
import { firestore } from "@/lib/firebase/config"; // adjust import
11+
import {doc, getDoc} from "firebase/firestore";
712
const geistSans = Geist({
813
variable: "--font-geist-sans",
914
subsets: ["latin"],
@@ -14,38 +19,60 @@ const geistMono = Geist_Mono({
1419
subsets: ["latin"],
1520
});
1621

22+
interface RootLayoutProps {
23+
children: ReactNode;
24+
}
1725

18-
export const metadata = createMetadata({
19-
siteUrl: "https://example.com",
20-
title: "MyApp",
21-
description: "A modern web app",
22-
appName: "MyApp",
23-
category: "Productivity",
24-
keywords: ["app", "nextjs", "productivity"],
25-
twitterHandle: "@myapp",
26-
});
26+
export default function RootLayout({ children }: RootLayoutProps) {
27+
const [loading, setLoading] = useState(true);
28+
const [isAuthenticated, setIsAuthenticated] = useState(false);
29+
const router = useRouter();
30+
31+
useEffect(() => {
32+
const unsubscribe = onAuthStateChanged(async (authUser) => {
33+
if (!authUser?.email) {
34+
router.push("/login");
35+
setLoading(false);
36+
return;
37+
}
38+
39+
try {
40+
const userDocRef = doc(firestore, "adminemail", authUser.email);
41+
const userDoc = await getDoc(userDocRef);
42+
const role = userDoc.exists() ? userDoc.data()?.role : null;
43+
44+
if (role === "admin" || role === "superadmin") {
45+
setIsAuthenticated(true);
46+
} else {
47+
router.push("/login");
48+
}
49+
} catch (err) {
50+
console.error("Auth check failed:", err);
51+
router.push("/login");
52+
} finally {
53+
setLoading(false);
54+
}
55+
});
56+
57+
return () => unsubscribe();
58+
}, [router]);
59+
60+
if (loading) {
61+
return (
62+
<div className="flex items-center justify-center min-h-screen">
63+
<div className="text-center">
64+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto"></div>
65+
<p className="mt-4 text-gray-600">Checking authentication...</p>
66+
</div>
67+
</div>
68+
);
69+
}
2770

28-
<Script
29-
id="json-ld"
30-
type="application/ld+json"
31-
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd({
32-
siteUrl: "https://example.com",
33-
title: "MyApp",
34-
description: "Best productivity app",
35-
})) }}
36-
/>
37-
38-
39-
export default function RootLayout({
40-
children,
41-
}: Readonly<{
42-
children: React.ReactNode;
43-
}>) {
4471
return (
4572
<html lang="en">
4673
<body
47-
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
48-
>
74+
className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
75+
<Navbar />
4976
{children}
5077
</body>
5178
</html>

src/app/login/page.tsx

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
"use client";
2+
import { useState } from "react";
3+
import { signInWithGoogle } from "@/lib/firebase/auth";
4+
import { useRouter } from "next/navigation";
5+
6+
export default function AuthPage() {
7+
const [error, setError] = useState<string | null>(null);
8+
const [loading, setLoading] = useState(false);
9+
const router = useRouter();
10+
11+
const handleLoginWithGoogle = async () => {
12+
setLoading(true);
13+
setError(null);
14+
try {
15+
const result = await signInWithGoogle();
16+
const user = result?.user;
17+
18+
if (user) {
19+
const idToken = await user.getIdToken();
20+
document.cookie = `__session=${idToken}; path=/; `;
21+
console.log("User logged in:", user);
22+
console.log("token stored in cookie:", idToken);
23+
}
24+
if (result?.isAdmin) {
25+
router.push("/");
26+
}
27+
} catch (err) {
28+
console.error("Failed to log in with Google:", err);
29+
setError("Failed to log in. Please try again.");
30+
} finally {
31+
setLoading(false);
32+
}
33+
};
34+
35+
return (
36+
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-blue-100 flex items-center justify-center p-4 relative overflow-hidden">
37+
{/* Subtle background decorations */}
38+
<div className="absolute top-20 left-10 w-32 h-32 bg-blue-200 rounded-full opacity-10 blur-3xl"></div>
39+
<div className="absolute bottom-20 right-10 w-40 h-40 bg-blue-300 rounded-full opacity-15 blur-3xl"></div>
40+
<div className="absolute top-1/2 left-1/4 w-24 h-24 bg-blue-400 rounded-full opacity-10 blur-2xl"></div>
41+
42+
<div className="relative z-10 w-full max-w-md">
43+
{/* Main card */}
44+
<div className="bg-white/80 backdrop-blur-sm rounded-3xl shadow-xl border border-blue-100 p-8 transform hover:scale-105 transition-all duration-300">
45+
{/* Header */}
46+
<div className="text-center mb-8">
47+
<div className="w-16 h-16 bg-gradient-to-br from-blue-500 to-blue-700 rounded-2xl flex items-center justify-center mx-auto mb-4 transform rotate-12">
48+
<svg
49+
className="w-8 h-8 text-white"
50+
fill="none"
51+
stroke="currentColor"
52+
viewBox="0 0 24 24"
53+
>
54+
<path
55+
strokeLinecap="round"
56+
strokeLinejoin="round"
57+
strokeWidth={2}
58+
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
59+
/>
60+
</svg>
61+
</div>
62+
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
63+
Welcome
64+
</h2>
65+
<p className="text-blue-600 mt-2 font-medium">
66+
Sign in to continue your journey
67+
</p>
68+
</div>
69+
70+
{/* Google Login Button */}
71+
<button
72+
onClick={handleLoginWithGoogle}
73+
disabled={loading}
74+
aria-label="Login with Google"
75+
type="button"
76+
className="group relative w-full p-2 bg-white border-2 border-blue-200 rounded-2xl shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-300 focus:outline-none focus:ring-4 focus:ring-blue-300 focus:ring-opacity-50 disabled:opacity-50 disabled:cursor-not-allowed disabled:transform-none"
77+
>
78+
<div className="flex items-center justify-center space-x-3">
79+
{loading ? (
80+
<>
81+
<div className="w-6 h-6 border-2 border-blue-300 border-t-blue-600 rounded-full animate-spin"></div>
82+
<span className="text-lg font-semibold text-blue-700">
83+
Signing you in...
84+
</span>
85+
</>
86+
) : (
87+
<>
88+
<svg
89+
xmlns="http://www.w3.org/2000/svg"
90+
x="0px"
91+
y="0px"
92+
width="30"
93+
height="30"
94+
viewBox="0 0 48 48"
95+
>
96+
<path
97+
fill="#fbc02d"
98+
d="M43.611,20.083H42V20H24v8h11.303c-1.649,4.657-6.08,8-11.303,8c-6.627,0-12-5.373-12-12 s5.373-12,12-12c3.059,0,5.842,1.154,7.961,3.039l5.657-5.657C34.046,6.053,29.268,4,24,4C12.955,4,4,12.955,4,24s8.955,20,20,20 s20-8.955,20-20C44,22.659,43.862,21.35,43.611,20.083z"
99+
></path>
100+
<path
101+
fill="#e53935"
102+
d="M6.306,14.691l6.571,4.819C14.655,15.108,18.961,12,24,12c3.059,0,5.842,1.154,7.961,3.039 l5.657-5.657C34.046,6.053,29.268,4,24,4C16.318,4,9.656,8.337,6.306,14.691z"
103+
></path>
104+
<path
105+
fill="#4caf50"
106+
d="M24,44c5.166,0,9.86-1.977,13.409-5.192l-6.19-5.238C29.211,35.091,26.715,36,24,36 c-5.202,0-9.619-3.317-11.283-7.946l-6.522,5.025C9.505,39.556,16.227,44,24,44z"
107+
></path>
108+
<path
109+
fill="#1565c0"
110+
d="M43.611,20.083L43.595,20L42,20H24v8h11.303c-0.792,2.237-2.231,4.166-4.087,5.571 c0.001-0.001,0.002-0.001,0.003-0.002l6.19,5.238C36.971,39.205,44,34,44,24C44,22.659,43.862,21.35,43.611,20.083z"
111+
></path>
112+
</svg>
113+
114+
<span className="text-lg font-semibold text-gray-700 group-hover:text-blue-700 transition-colors">
115+
Continue with Google
116+
</span>
117+
</>
118+
)}
119+
</div>
120+
121+
{/* Hover effect overlay */}
122+
<div className="absolute inset-0 bg-gradient-to-r from-blue-50 to-blue-100 rounded-2xl opacity-0 group-hover:opacity-100 transition-opacity duration-300 -z-10"></div>
123+
</button>
124+
125+
{/* Error message */}
126+
{error && (
127+
<div className="mt-6 p-4 bg-red-50 border border-red-200 rounded-xl">
128+
<p className="text-red-600 text-sm text-center font-medium">
129+
{error}
130+
</p>
131+
</div>
132+
)}
133+
</div>
134+
135+
{/* Footer text */}
136+
<p className="text-center text-blue-500 text-sm mt-6 font-medium">
137+
Secure authentication powered by Google
138+
</p>
139+
<p className="text-center text-orange-500 text-sm mt-6 font-medium">
140+
Sign in with pkd skp mail
141+
</p>
142+
</div>
143+
</div>
144+
);
145+
}

src/components/navbar.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import Link from "next/link";
5+
import { onAuthStateChanged, signOutWithGoogle } from "@/lib/firebase/auth";
6+
import type { User } from "firebase/auth";
7+
import { useRouter } from "next/navigation";
8+
9+
const Navbar = () => {
10+
const [user, setUser] = useState<User | null>(null);
11+
const router = useRouter();
12+
13+
useEffect(() => {
14+
const unsubscribe = onAuthStateChanged((currentUser) => {
15+
setUser(currentUser);
16+
});
17+
return () => unsubscribe();
18+
}, []);
19+
20+
const handleLogout = async () => {
21+
try {
22+
await signOutWithGoogle();
23+
setUser(null);
24+
router.push("/login"); // redirect to login after logout
25+
} catch (error) {
26+
console.error("Logout failed:", error);
27+
}
28+
};
29+
30+
return (
31+
<nav className="bg-white border-b border-gray-200 shadow-sm sticky top-0 z-50">
32+
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
33+
{/* Logo */}
34+
<Link href="/">
35+
<h1 className="text-2xl font-bold text-gray-900 tracking-tight hover:opacity-90 transition-opacity">
36+
Admin Dashboard
37+
</h1>
38+
</Link>
39+
40+
{/* Right Section */}
41+
<div className="flex items-center space-x-4">
42+
{user ? (
43+
<>
44+
<Link href="/profile" className="flex items-center space-x-2 group">
45+
<div className="w-10 h-10 bg-blue-600 text-white rounded-full flex items-center justify-center text-lg font-bold">
46+
{(user.displayName || user.email || "U")[0].toUpperCase()}
47+
</div>
48+
<span className="hidden sm:inline text-sm font-medium text-gray-700 group-hover:text-blue-600 transition">
49+
{user.displayName || user.email || "User"}
50+
</span>
51+
</Link>
52+
<button
53+
onClick={handleLogout}
54+
className="px-4 py-2 rounded-full bg-red-600 text-white font-medium text-sm hover:bg-red-700 transition shadow-md hover:shadow-lg"
55+
>
56+
Logout
57+
</button>
58+
</>
59+
) : (
60+
<Link href="/login">
61+
<button className="px-5 py-2 rounded-full bg-black text-white font-medium text-sm hover:bg-gray-800 transition shadow-md hover:shadow-lg">
62+
Login
63+
</button>
64+
</Link>
65+
)}
66+
</div>
67+
</div>
68+
</nav>
69+
);
70+
};
71+
72+
export default Navbar;

src/lib/firebase/auth.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
// Import User and other necessary types/functions from Firebase Auth
2-
import { signInWithPopup, GoogleAuthProvider, User, UserCredential , getAuth } from 'firebase/auth';
2+
import { signInWithPopup, GoogleAuthProvider, User as FirebaseUser, UserCredential, getAuth, signOut as firebaseSignOut } from 'firebase/auth';
33
import { doc, getDoc } from 'firebase/firestore';
44
import { auth, firestore } from './config'; // Ensure you are correctly importing from your Firebase config
55

6+
// Re-export the User type
7+
export type User = FirebaseUser;
8+
69
// Function to track authentication state changes
710
export function onAuthStateChanged(callback: (authUser: User | null) => void) {
8-
return auth.onAuthStateChanged(callback);
11+
return auth.onAuthStateChanged((user) => {
12+
callback(user as User | null);
13+
});
914
}
1015

1116
// Function for Google sign-in and role check
@@ -25,7 +30,7 @@ export async function signInWithGoogle(): Promise<{ user:User; isAdmin: boolean
2530
// Restrict login to only emails from "gecskp.ac.in"
2631
// Restrict login to only emails from "gecskp.ac.in", except for a specific admin email
2732
const allowedEmailPattern = /^[a-zA-Z0-9]+@gecskp\.ac\.in$/;
28-
const adminOverrideEmail = "codecompass2024@gmail.com";
33+
const adminOverrideEmail = "shadilrayyan2@gmail.com";
2934

3035
if (user.email !== adminOverrideEmail && !allowedEmailPattern.test(user.email)) {
3136
throw new Error('Only GEC SKP emails are allowed');
@@ -49,7 +54,7 @@ if (user.email !== adminOverrideEmail && !allowedEmailPattern.test(user.email))
4954

5055
export async function signOutWithGoogle(): Promise<void> {
5156
try {
52-
await auth.signOut();
57+
await firebaseSignOut(auth);
5358
} catch (error) {
5459
console.error('Error signing out with Google:', error);
5560
throw error;

0 commit comments

Comments
 (0)