Skip to content

Commit afa7465

Browse files
Merge pull request #16 from ft4bhi/main
middle ware and profile
2 parents dbc2848 + cfda25f commit afa7465

10 files changed

Lines changed: 343 additions & 73 deletions

File tree

public/images/FingerprintImg.png

222 KB
Loading
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// pages/api/set-auth-cookie/route.ts
2+
import type { NextApiRequest, NextApiResponse } from 'next';
3+
import { getAuth } from 'firebase-admin/auth';
4+
import { initializeApp, getApps, cert } from 'firebase-admin/app';
5+
import { serialize } from 'cookie';
6+
7+
const serviceAccount = {
8+
// Replace these with your service account values
9+
projectId: process.env.FIREBASE_PROJECT_ID,
10+
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
11+
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
12+
};
13+
14+
if (!getApps().length) {
15+
initializeApp({
16+
credential: cert(serviceAccount as any),
17+
});
18+
}
19+
20+
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
21+
if (req.method !== 'POST') return res.status(405).end();
22+
23+
const { idToken } = req.body;
24+
if (!idToken) return res.status(400).json({ error: 'ID token required' });
25+
26+
try {
27+
const decodedToken = await getAuth().verifyIdToken(idToken);
28+
29+
// Set secure HttpOnly cookie
30+
const cookie = serialize('authToken', idToken, {
31+
httpOnly: true,
32+
secure: process.env.NODE_ENV === 'production',
33+
maxAge: 60 * 60 * 24, // 1 day
34+
path: '/',
35+
sameSite: 'lax',
36+
});
37+
38+
res.setHeader('Set-Cookie', cookie);
39+
res.status(200).json({ success: true });
40+
} catch (err) {
41+
console.error(err);
42+
res.status(401).json({ error: 'Invalid token' });
43+
}
44+
}

src/app/auth/page.tsx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
"use client";
3+
import { useState } from "react";
4+
import { signInWithGoogle } from "@/lib/firebase/auth";
5+
import { useRouter } from "next/navigation";
6+
import Image from "next/image";
7+
import Link from "next/link";
8+
9+
export default function AuthPage() {
10+
const [error, setError] = useState<string | null>(null);
11+
const [loading, setLoading] = useState(false);
12+
const [agreed, setAgreed] = useState(false);
13+
const router = useRouter();
14+
15+
const handleLoginWithGoogle = async () => {
16+
if (!agreed) {
17+
setError("Please agree to the terms and services.");
18+
return;
19+
}
20+
21+
setLoading(true);
22+
setError(null);
23+
try {
24+
const result = await signInWithGoogle();
25+
if (result?.isAdmin) {
26+
router.push("/admin");
27+
} else {
28+
router.push("/");
29+
}
30+
} catch (err) {
31+
console.error("Failed to log in with Google:", err);
32+
setError(err instanceof Error ? err.message : "Failed to log in with Google");
33+
} finally {
34+
setLoading(false);
35+
}
36+
};
37+
38+
return (
39+
<div className="flex flex-col items-center justify-center min-h-screen bg-white dark:bg-black px-6 py-8 relative">
40+
{/* Back Arrow Button */}
41+
<Link
42+
href="/"
43+
className="absolute top-6 left-6 p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
44+
aria-label="Go back"
45+
>
46+
<svg
47+
xmlns="http://www.w3.org/2000/svg"
48+
className="w-6 h-6"
49+
viewBox="0 0 24 24"
50+
fill="none"
51+
stroke="currentColor"
52+
strokeWidth="2"
53+
strokeLinecap="round"
54+
strokeLinejoin="round"
55+
>
56+
<line x1="19" y1="12" x2="5" y2="12"></line>
57+
<polyline points="12 19 5 12 12 5"></polyline>
58+
</svg>
59+
</Link>
60+
61+
<div className="w-full max-w-md text-center space-y-6">
62+
{/* Title */}
63+
<h1 className="text-2xl md:text-3xl font-semibold text-black dark:text-white">
64+
Create your account
65+
</h1>
66+
67+
{/* Illustration */}
68+
<div className="flex justify-center">
69+
<Image
70+
src={"/images/FingerprintImg.png"}
71+
alt="Fingerprint illustration"
72+
width={240}
73+
height={240}
74+
className="rounded-xl w-[180px] h-[180px] md:w-[220px] md:h-[220px]"
75+
priority
76+
/>
77+
</div>
78+
79+
{/* Google Login Button */}
80+
<button
81+
onClick={handleLoginWithGoogle}
82+
disabled={loading}
83+
aria-label="Continue with Google"
84+
type="button"
85+
className="flex items-center justify-center w-full py-3 px-6 rounded-full shadow-md
86+
bg-white border border-gray-300 hover:bg-gray-100
87+
transition-all disabled:opacity-60 disabled:cursor-not-allowed"
88+
>
89+
{loading ? (
90+
<span className="text-base font-medium text-gray-600">Logging in...</span>
91+
) : (
92+
<>
93+
<Image
94+
src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg"
95+
alt="Google logo"
96+
width={24}
97+
height={24}
98+
className="w-5 h-5 md:w-6 md:h-6"
99+
/>
100+
<span className="ml-3 text-base font-medium text-gray-700">
101+
Continue with Google
102+
</span>
103+
</>
104+
)}
105+
</button>
106+
107+
{/* Terms and Services */}
108+
<div className="flex items-center justify-center space-x-3 text-sm md:text-base">
109+
<input
110+
type="checkbox"
111+
id="terms"
112+
checked={agreed}
113+
onChange={() => setAgreed(!agreed)}
114+
className="form-checkbox text-blue-600 w-4 h-4 md:w-5 md:h-5"
115+
/>
116+
<label htmlFor="terms" className="text-gray-600 dark:text-gray-400">
117+
I agree to my <span className="font-semibold underline">terms and services</span>
118+
</label>
119+
</div>
120+
121+
{/* Error Message */}
122+
{error && (
123+
<p className="text-red-500 text-sm md:text-base">
124+
{error}
125+
</p>
126+
)}
127+
</div>
128+
</div>
129+
);
130+
}

src/app/login/page.tsx

Lines changed: 0 additions & 66 deletions
This file was deleted.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { ArrowLeft, ChevronDown } from "lucide-react";
5+
import { useState } from "react";
6+
7+
export default function EditProfile() {
8+
const router = useRouter();
9+
const [sem, setSem] = useState("");
10+
const [dept, setDept] = useState("");
11+
const [course, setCourse] = useState("");
12+
13+
return (
14+
<div className="min-h-screen bg-white flex flex-col items-center p-4 md:p-8">
15+
<div className="w-full max-w-md">
16+
<button onClick={() => router.back()} className="mb-4">
17+
<ArrowLeft className="text-gray-700" />
18+
</button>
19+
20+
<h1 className="text-2xl font-semibold text-center mb-1">Profile</h1>
21+
<p className="text-center text-gray-500 mb-6">Edit Profile</p>
22+
23+
<div className="flex flex-col items-center mb-6">
24+
<div className="w-24 h-24 bg-gray-300 rounded-full flex items-center justify-center text-sm text-gray-600 mb-2">
25+
Upload Image
26+
</div>
27+
</div>
28+
29+
<div className="space-y-4">
30+
<input
31+
type="text"
32+
placeholder="Enter Your Sem"
33+
value={sem}
34+
onChange={(e) => setSem(e.target.value)}
35+
className="w-full px-4 py-2 border rounded-md outline-none focus:ring-2 ring-gray-300"
36+
/>
37+
<input
38+
type="text"
39+
placeholder="Enter Your Department"
40+
value={dept}
41+
onChange={(e) => setDept(e.target.value)}
42+
className="w-full px-4 py-2 border rounded-md outline-none focus:ring-2 ring-gray-300"
43+
/>
44+
<div className="relative">
45+
<select
46+
value={course}
47+
onChange={(e) => setCourse(e.target.value)}
48+
className="w-full appearance-none px-4 py-2 border rounded-md outline-none focus:ring-2 ring-gray-300"
49+
>
50+
<option value="">Select Your Course</option>
51+
<option value="btech">B.Tech</option>
52+
<option value="mtech">M.Tech</option>
53+
<option value="mba">MBA</option>
54+
</select>
55+
<ChevronDown className="absolute right-3 top-3 text-gray-500 pointer-events-none" />
56+
</div>
57+
<button className="w-full py-2 bg-gray-700 text-white rounded-md hover:bg-gray-800 transition">
58+
Confirm
59+
</button>
60+
</div>
61+
</div>
62+
</div>
63+
);
64+
}

src/app/profile/page.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client";
2+
3+
import { useRouter } from "next/navigation";
4+
import { ArrowLeft } from "lucide-react";
5+
6+
export default function Profile() {
7+
const router = useRouter();
8+
9+
return (
10+
<div className="min-h-screen bg-white flex flex-col items-center p-4 md:p-8">
11+
<div className="w-full max-w-md">
12+
<button onClick={() => router.back()} className="mb-4">
13+
<ArrowLeft className="text-gray-700" />
14+
</button>
15+
16+
<h1 className="text-2xl font-semibold text-center mb-1">Profile</h1>
17+
<div className="w-24 h-1 bg-gray-300 mx-auto rounded-full mb-6"></div>
18+
19+
<div className="flex flex-col items-center">
20+
<div className="w-24 h-24 bg-gray-300 rounded-full mb-4" />
21+
22+
<h2 className="text-lg font-medium">Name</h2>
23+
<p className="text-sm text-gray-500">Sem - Dept</p>
24+
25+
<button
26+
onClick={() => router.push("/profile/editProfile")}
27+
className="mt-6 px-6 py-2 border border-gray-600 rounded-md hover:bg-gray-100 transition"
28+
>
29+
Edit Profile
30+
</button>
31+
</div>
32+
</div>
33+
</div>
34+
);
35+
}

src/components/home/ResponsiveDashboard.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
"use client";
2-
import TopNavBar from '../topNavbar';
2+
import { useEffect, useState } from "react";
3+
import TopNavBar from "../topNavbar";
34
import { QuickActions } from "./QuickActions";
45
import { TimeTableBlock } from "./TimeTable";
6+
import { onAuthStateChanged } from "@/lib/firebase/auth"; // Update with correct path
7+
import { User } from "firebase/auth";
58

69
const ResponsiveDashboard = () => {
10+
const [userName, setUserName] = useState<string>("");
11+
const [userPhotoURL, setUserPhotoURL] = useState<string | null>(null);
12+
13+
useEffect(() => {
14+
const unsubscribe = onAuthStateChanged((authUser: User | null) => {
15+
if (authUser) {
16+
setUserName(authUser.displayName || "Student");
17+
setUserPhotoURL(authUser.photoURL || null);
18+
} else {
19+
setUserName("Guest");
20+
setUserPhotoURL(null);
21+
}
22+
});
23+
24+
return () => unsubscribe();
25+
}, []);
26+
27+
728
return (
829
<div className="min-h-screen">
9-
<TopNavBar userName="John Doe" semester={4} department="CSE" />
30+
<TopNavBar userName={userName} userPhotoURL={userPhotoURL} />
1031
<div className="mx-auto max-w-[var(--max-screen-size)] pt-4">
1132
<main className="px-3 pb-24">
1233
<div className="md:flex lg:h-[calc(100vh-12rem)] md:gap-6 lg:gap-8">

0 commit comments

Comments
 (0)