Skip to content

Commit 8758c0b

Browse files
feat: added authentication and join room routes
1 parent fdaf2e8 commit 8758c0b

File tree

8 files changed

+471
-326
lines changed

8 files changed

+471
-326
lines changed

frontend/package-lock.json

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

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"@radix-ui/react-tooltip": "^1.2.8",
2020
"@tailwindcss/vite": "^4.1.14",
2121
"@tanstack/react-query": "^5.90.3",
22+
"axios": "^1.12.2",
2223
"class-variance-authority": "^0.7.1",
2324
"clsx": "^2.1.1",
2425
"lucide-react": "^0.545.0",

frontend/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Index from "./pages/Index.js";
77
import SignUp from "./pages/Signup.js";
88
import SignIn from "./pages/Signin.js";
99
import OAuthSuccess from "./pages/OAuthSuccess.js";
10+
import RoomActions from "./pages/RoomActions.js";
1011
import "./index.css"
1112
const queryClient = new QueryClient();
1213

@@ -20,6 +21,7 @@ const App = () => (
2021
<Route path="/" element={<Index />} />
2122
<Route path="/signup" element={<SignUp />} />
2223
<Route path="/signin" element={<SignIn />} />
24+
<Route path="/room-actions" element={<RoomActions />} />
2325
<Route path="/oauth-success" element={<OAuthSuccess />} />
2426
</Routes>
2527
</BrowserRouter>

frontend/src/components/Header.tsx

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
11
import { useState, useEffect } from "react";
22
import { Button } from "../components/ui/button.js";
33
import { useNavigate } from "react-router-dom";
4-
import { Menu, X } from "lucide-react";
4+
import { Menu, X, PlusSquare, LogIn, LogOut } from "lucide-react";
5+
import axios from "axios";
56

67
function Header() {
78
const [isScrolled, setIsScrolled] = useState(false);
89
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
10+
const [user, setUser] = useState<{ _id: string; name: string } | null>(null);
11+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
912
const navigate = useNavigate();
1013

14+
// Scroll effect
1115
useEffect(() => {
12-
const handleScroll = () => {
13-
setIsScrolled(window.scrollY > 20);
14-
};
16+
const handleScroll = () => setIsScrolled(window.scrollY > 20);
1517
window.addEventListener("scroll", handleScroll);
1618
return () => window.removeEventListener("scroll", handleScroll);
1719
}, []);
1820

21+
// Fetch authenticated user
22+
useEffect(() => {
23+
const token = localStorage.getItem("token");
24+
if (!token) return;
25+
26+
const fetchUser = async () => {
27+
try {
28+
const res = await axios.get("http://localhost:3000/api/auth/me", {
29+
headers: { Authorization: `Bearer ${token}` },
30+
});
31+
setUser(res.data.user);
32+
} catch {
33+
setUser(null);
34+
localStorage.removeItem("token"); // remove invalid token
35+
}
36+
};
37+
38+
fetchUser();
39+
}, []);
40+
1941
const scrollToSection = (id: string) => {
2042
const element = document.getElementById(id);
2143
if (element) {
@@ -24,25 +46,36 @@ function Header() {
2446
}
2547
};
2648

49+
// Navigate to a separate page which provides actions (join/create/logout)
50+
const handleJoinNow = () => {
51+
if (!user) {
52+
navigate("/signin");
53+
} else {
54+
navigate("/room-actions");
55+
}
56+
};
57+
58+
const handleLogout = () => {
59+
localStorage.removeItem("token");
60+
setUser(null);
61+
setIsDropdownOpen(false);
62+
navigate("/");
63+
};
64+
2765
return (
2866
<header
2967
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
30-
isScrolled
31-
? "bg-white/80 backdrop-blur-lg shadow-lg"
32-
: "bg-transparent"
68+
isScrolled ? "bg-white/80 backdrop-blur-lg shadow-lg" : "bg-transparent"
3369
}`}
3470
>
3571
<nav className="container mx-auto px-6 py-4">
3672
<div className="flex items-center justify-between">
37-
<a
38-
href="/"
39-
className="text-2xl font-bold text-green-600 bg-clip-text"
40-
>
73+
<a href="/" className="text-2xl font-bold text-green-600 bg-clip-text">
4174
PeerCall
4275
</a>
4376

4477
{/* Desktop Navigation */}
45-
<div className="hidden md:flex items-center gap-8">
78+
<div className="hidden md:flex items-center gap-8 relative">
4679
<button
4780
onClick={() => scrollToSection("features")}
4881
className="text-gray-900 hover:text-green-600 transition-colors font-medium"
@@ -55,13 +88,15 @@ function Header() {
5588
>
5689
Tech Stack
5790
</button>
58-
<Button
59-
size="default"
60-
className="bg-green-600 text-white hover:bg-green-700"
61-
onClick={() => navigate("/signup")}
62-
>
63-
Join Now
64-
</Button>
91+
<div className="relative">
92+
<Button
93+
size="default"
94+
className="bg-green-600 text-white hover:bg-green-700 flex items-center gap-2"
95+
onClick={handleJoinNow}
96+
>
97+
Join Now
98+
</Button>
99+
</div>
65100
</div>
66101

67102
{/* Mobile Menu Button */}
@@ -91,11 +126,12 @@ function Header() {
91126
</button>
92127
<Button
93128
size="default"
94-
className="w-full bg-green-600 text-white hover:bg-green-700"
95-
onClick={() => navigate("/signup")}
129+
className="w-full bg-green-600 text-white hover:bg-green-700 flex items-center justify-center gap-2"
130+
onClick={handleJoinNow}
96131
>
97132
Join Now
98133
</Button>
134+
{/* mobile: action buttons moved to /room-actions page */}
99135
</div>
100136
)}
101137
</nav>

frontend/src/components/Hero.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
11
import { Button } from "./ui/button.js";
22
import { ArrowRight, Play } from "lucide-react";
33
import { useNavigate } from "react-router-dom";
4+
import axios from "axios";
5+
import { useState } from "react";
46

57
const Hero = () => {
68
const navigate = useNavigate();
9+
const [loading, setLoading] = useState(false);
10+
11+
const handleJoinNow = async () => {
12+
const token = localStorage.getItem("token");
13+
if (!token) {
14+
navigate("/signin");
15+
return;
16+
}
17+
18+
setLoading(true);
19+
try {
20+
const res = await axios.get("http://localhost:3000/api/auth/me", {
21+
headers: { Authorization: `Bearer ${token}` },
22+
});
23+
24+
if (res?.data?.user) {
25+
navigate("/room-actions");
26+
} else {
27+
localStorage.removeItem("token");
28+
navigate("/signin");
29+
}
30+
} catch (err) {
31+
localStorage.removeItem("token");
32+
navigate("/signin");
33+
} finally {
34+
setLoading(false);
35+
}
36+
};
737

838
return (
939
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-20 bg-gray-50 text-gray-900">
@@ -27,8 +57,8 @@ const Hero = () => {
2757
</p>
2858

2959
<div className="flex flex-col sm:flex-row gap-4 justify-center lg:justify-start">
30-
<Button onClick={() => navigate("/signup")} className="group bg-green-700 text-white hover:bg-purple-700">
31-
Join Now
60+
<Button onClick={handleJoinNow} disabled={loading} className="group bg-green-700 text-white hover:bg-purple-700">
61+
{loading ? 'Checking...' : 'Join Now'}
3262
<ArrowRight className="ml-2 group-hover:translate-x-1 transition-transform" />
3363
</Button>
3464

frontend/src/pages/RoomActions.tsx

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useEffect, useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { Button } from "../components/ui/button.js";
4+
import { PlusSquare, LogIn, LogOut } from "lucide-react";
5+
import axios from "axios";
6+
7+
export default function RoomActions() {
8+
const [user, setUser] = useState<{ _id: string; name: string } | null>(null);
9+
const [loading, setLoading] = useState(true);
10+
const navigate = useNavigate();
11+
12+
useEffect(() => {
13+
const token = localStorage.getItem("token");
14+
if (!token) {
15+
navigate("/signin");
16+
return;
17+
}
18+
19+
const fetchUser = async () => {
20+
try {
21+
const res = await axios.get("http://localhost:3000/api/auth/me", {
22+
headers: { Authorization: `Bearer ${token}` },
23+
});
24+
setUser(res.data.user);
25+
} catch (err) {
26+
// invalid token or user not found
27+
localStorage.removeItem("token");
28+
navigate("/signin");
29+
} finally {
30+
setLoading(false);
31+
}
32+
};
33+
34+
fetchUser();
35+
}, [navigate]);
36+
37+
const handleCreate = () => navigate("/create-room");
38+
const handleJoin = () => navigate("/join-room");
39+
const handleLogout = () => {
40+
localStorage.removeItem("token");
41+
navigate("/");
42+
};
43+
44+
if (loading) return <div className="p-6">Loading...</div>;
45+
46+
return (
47+
<div className="container mx-auto px-6 py-10">
48+
<div className="max-w-md mx-auto bg-white shadow rounded-lg p-6">
49+
<h2 className="text-xl font-semibold mb-4 ">Room Actions</h2>
50+
<p className="text-sm text-gray-600 mb-6">Hello {user?.name ?? "user"}, choose an action:</p>
51+
52+
<div className="space-y-3">
53+
<Button
54+
className="w-full justify-center bg-green-600 hover:bg-green-700 text-white"
55+
onClick={handleJoin}
56+
>
57+
<LogIn className="mr-2 h-4 w-4" /> Join Room
58+
</Button>
59+
60+
<Button
61+
className="w-full justify-center bg-blue-600 hover:bg-blue-700 text-white"
62+
onClick={handleCreate}
63+
>
64+
<PlusSquare className="mr-2 h-4 w-4" /> Create Room
65+
</Button>
66+
67+
<Button
68+
className="w-full justify-center bg-red-600 hover:bg-red-700 text-white"
69+
onClick={handleLogout}
70+
>
71+
<LogOut className="mr-2 h-4 w-4" /> Logout
72+
</Button>
73+
</div>
74+
</div>
75+
</div>
76+
);
77+
}

frontend/src/pages/Signin.tsx

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,56 @@
11
import { useState } from "react";
22
import { useForm } from "react-hook-form";
3-
import { Link } from "react-router-dom";
4-
import { Button } from "../components/ui/button.js"
5-
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../components/ui/card.js";
6-
import { InputField } from "../components/InputField.js";
7-
import { toast } from "../hooks/use-toast.js";
8-
import { mockLogin, SignInData } from "../lib/api.js";
3+
import { Link, useNavigate } from "react-router-dom";
4+
import axios from "axios";
5+
import { Button } from "../components/ui/button";
6+
import {
7+
Card,
8+
CardContent,
9+
CardDescription,
10+
CardFooter,
11+
CardHeader,
12+
CardTitle,
13+
} from "../components/ui/card";
14+
import { InputField } from "../components/InputField";
15+
import { toast } from "../hooks/use-toast";
916
import { Loader2 } from "lucide-react";
10-
import SocialLogin from "../components/SocialLogin.js";
17+
import SocialLogin from "../components/SocialLogin";
18+
19+
interface SignInData {
20+
email: string;
21+
password: string;
22+
}
1123

1224
const SignIn = () => {
1325
const [isLoading, setIsLoading] = useState(false);
14-
const {
15-
register,
16-
handleSubmit,
17-
formState: { errors },
18-
reset,
19-
} = useForm<SignInData>();
26+
const navigate = useNavigate();
27+
const { register, handleSubmit, formState: { errors }, reset } = useForm<SignInData>();
2028

2129
const onSubmit = async (data: SignInData) => {
2230
setIsLoading(true);
2331
try {
24-
const response = await mockLogin(data);
32+
const response = await axios.post(
33+
"http://localhost:3000/api/auth/signin",
34+
{ email: data.email, password: data.password },
35+
{ headers: { "Content-Type": "application/json" } }
36+
);
37+
2538
toast({
2639
title: "Welcome back!",
27-
description: response.message,
40+
description: response.data.message || "Login successful",
2841
});
42+
43+
// store JWT token in localStorage
44+
if (response.data.token) {
45+
localStorage.setItem("token", response.data.token);
46+
}
47+
2948
reset();
30-
// Navigate home page
49+
navigate("/"); // redirect to home/dashboard
3150
} catch (error: any) {
3251
toast({
3352
title: "Error",
34-
description: error.message || "Login failed. Please try again.",
53+
description: error.response?.data?.message || "Login failed. Please try again.",
3554
variant: "destructive",
3655
});
3756
} finally {
@@ -50,6 +69,7 @@ const SignIn = () => {
5069
Sign in to your account
5170
</CardDescription>
5271
</CardHeader>
72+
5373
<CardContent>
5474
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
5575
<InputField
@@ -98,8 +118,11 @@ const SignIn = () => {
98118
)}
99119
</Button>
100120
</form>
121+
101122
<div className="mt-6">
102-
<div className="text-center text-sm text-gray-500 mb-3">Or continue with</div>
123+
<div className="text-center text-sm text-gray-500 mb-3">
124+
Or continue with
125+
</div>
103126
<SocialLogin />
104127
</div>
105128
</CardContent>

0 commit comments

Comments
 (0)