Skip to content

Commit 6225676

Browse files
authored
Merge pull request #35 from shivam7147/main
Implemented frontend for login and register routes
2 parents 068a69b + d9e4f58 commit 6225676

File tree

14 files changed

+1027
-27
lines changed

14 files changed

+1027
-27
lines changed

client/package-lock.json

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

client/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
"@radix-ui/react-separator": "^1.1.7",
1414
"@radix-ui/react-slider": "^1.3.6",
1515
"@tailwindcss/vite": "^4.1.14",
16+
"axios": "^1.12.2",
1617
"class-variance-authority": "^0.7.1",
1718
"clsx": "^2.1.1",
1819
"jspdf": "^3.0.3",
1920
"lucide-react": "^0.545.0",
2021
"react": "^19.1.1",
2122
"react-dom": "^19.1.1",
23+
"react-router-dom": "^7.9.4",
2224
"socket.io-client": "^4.8.1",
2325
"sonner": "^2.0.7",
2426
"tailwind-merge": "^3.3.1",

client/src/App.jsx

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
1-
import { Canvas } from "./components/Canvas";
1+
import { BrowserRouter as Router, Routes, Route, Navigate } from "react-router-dom";
2+
import {Canvas} from "./components/Canvas";
3+
import Login from "./pages/Login";
4+
import Register from "./pages/Register";
25

36
function App() {
7+
const token = localStorage.getItem("token");
48

5-
return (
6-
<><Canvas/></>
7-
);
9+
return (
10+
<Router>
11+
<Routes>
12+
{/* Redirect to Canvas if already logged in, otherwise to Login */}
13+
<Route path="/" element={<Canvas />} />
14+
15+
{/* Auth Pages */}
16+
<Route path="/login" element={<Login />} />
17+
<Route path="/register" element={<Register />} />
18+
19+
{/* Protected Canvas Route */}
20+
<Route
21+
path="/canvas"
22+
element={<Canvas />}
23+
/>
24+
</Routes>
25+
</Router>
26+
);
827
}
928

1029
export default App;

client/src/api.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import axios from "axios";
2+
3+
const API = axios.create({
4+
baseURL: "http://localhost:3000/api",
5+
});
6+
7+
export const registerUser = (data) => API.post("/auth/signup", data);
8+
export const loginUser = (data) => API.post("/auth/login", data);
9+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export default function AuthLayout({ children, title }) {
2+
return (
3+
<div className="flex items-center justify-center h-screen bg-canvas relative overflow-hidden">
4+
<div className="bg-white/90 border border-gray-300 rounded-2xl shadow-2xl p-8 w-[90%] sm:w-[400px] text-center backdrop-blur-sm">
5+
<h1 className="text-2xl font-bold mb-6 text-gray-800">{title}</h1>
6+
{children}
7+
</div>
8+
</div>
9+
);
10+
}

client/src/components/Canvas.jsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,33 @@ export const Canvas = () => {
2222
const [socket, setSocket] = useState(null);
2323
const [isModalOpen, setIsModalOpen] = useState(false);
2424

25+
const [isLoggedIn, setIsLoggedIn] = useState(
26+
!!localStorage.getItem("token")
27+
);
28+
29+
const handleLogout = async () => {
30+
const token = localStorage.getItem("token");
31+
if (!token) return;
32+
try {
33+
const res = await fetch("http://localhost:3000/api/auth/logout", {
34+
method: "POST",
35+
headers: { "Content-Type": "application/json" },
36+
body: JSON.stringify({ token }),
37+
});
38+
const data = await res.json();
39+
if (res.ok) {
40+
localStorage.removeItem("token");
41+
setIsLoggedIn(false);
42+
toast.success("Logged out successfully!");
43+
} else {
44+
toast.error(data.message);
45+
}
46+
} catch (err) {
47+
console.error(err);
48+
toast.error("Logout failed!");
49+
}
50+
};
51+
2552
useEffect(() => {
2653
const s = io("http://localhost:3000");
2754
setSocket(s);
@@ -181,6 +208,61 @@ export const Canvas = () => {
181208

182209
return (
183210
<div className="relative w-full h-screen overflow-hidden bg-canvas">
211+
212+
{/* 🔹 Login / Logout buttons */}
213+
<div className="fixed top-4 left-4 z-[9999]">
214+
{isLoggedIn ? (
215+
// Logout button if logged in
216+
<button
217+
onClick={handleLogout}
218+
className="bg-red-600 text-white px-4 py-2 rounded-lg shadow hover:bg-red-700"
219+
>
220+
Logout
221+
</button>
222+
) : (
223+
<>
224+
{/* Desktop view: two separate buttons */}
225+
<div className="hidden sm:flex gap-3">
226+
<button
227+
onClick={() => window.location.href = "/login"}
228+
className="bg-blue-600 text-white px-4 py-2 rounded-lg shadow hover:bg-blue-700"
229+
>
230+
Sign In
231+
</button>
232+
<button
233+
onClick={() => window.location.href = "/register"}
234+
className="bg-blue-600 text-white px-4 py-2 rounded-lg shadow hover:bg-blue-700"
235+
>
236+
Sign Up
237+
</button>
238+
</div>
239+
240+
{/* Mobile view: dropdown */}
241+
<div className="sm:hidden relative">
242+
<details className="bg-blue-600 text-white px-4 py-2 rounded-lg shadow cursor-pointer select-none">
243+
<summary className="outline-none list-none">Menu ☰</summary>
244+
<div className="absolute left-0 mt-2 w-32 bg-white text-black rounded-lg shadow-lg border">
245+
<button
246+
onClick={() => window.location.href = "/login"}
247+
className="block w-full text-left px-4 py-2 hover:bg-gray-100"
248+
>
249+
Sign In
250+
</button>
251+
<button
252+
onClick={() => window.location.href = "/register"}
253+
className="block w-full text-left px-4 py-2 hover:bg-gray-100"
254+
>
255+
Sign Up
256+
</button>
257+
</div>
258+
</details>
259+
</div>
260+
</>
261+
)}
262+
</div>
263+
264+
265+
184266
<Toolbar
185267
activeTool={activeTool}
186268
onToolChange={handleToolChange}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export default function InputField({ label, type, value, onChange }) {
2+
return (
3+
<div className="flex flex-col gap-1 w-full">
4+
<label className="text-sm font-medium text-gray-700">{label}</label>
5+
<input
6+
type={type}
7+
value={value}
8+
onChange={onChange}
9+
required
10+
className="border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
11+
/>
12+
</div>
13+
);
14+
}

client/src/pages/Login.jsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
4+
export default function Login() {
5+
const [email, setEmail] = useState("");
6+
const [password, setPassword] = useState("");
7+
const [loading, setLoading] = useState(false);
8+
const navigate = useNavigate();
9+
10+
const handleLogin = async (e) => {
11+
e.preventDefault();
12+
setLoading(true);
13+
try {
14+
const res = await fetch("http://localhost:3000/api/auth/login", {
15+
method: "POST",
16+
headers: { "Content-Type": "application/json" },
17+
body: JSON.stringify({ email, password }),
18+
});
19+
const data = await res.json();
20+
console.log("Response status:", res.status);
21+
console.log("Response data:", data);
22+
23+
if (res.ok) {
24+
localStorage.setItem("token", data.token);
25+
navigate("/canvas"); // redirect after login
26+
} else {
27+
alert(data.message || "Invalid credentials!");
28+
}
29+
} catch (err) {
30+
console.error(err);
31+
alert("Login failed!");
32+
}
33+
setLoading(false);
34+
};
35+
36+
return (
37+
<div className="flex items-center justify-center min-h-screen bg-gradient-to-br from-gray-100 to-gray-300 px-4">
38+
<div className="w-full max-w-md bg-white rounded-2xl shadow-lg p-8 sm:p-10">
39+
<h1 className="text-3xl font-bold text-center text-blue-700 mb-6">
40+
Welcome Back
41+
</h1>
42+
<p className="text-center text-gray-500 mb-8">
43+
Please login to continue to Collab Canvas
44+
</p>
45+
46+
<form onSubmit={handleLogin} className="flex flex-col gap-5">
47+
<input
48+
type="email"
49+
placeholder="Email"
50+
value={email}
51+
onChange={(e) => setEmail(e.target.value)}
52+
className="border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition"
53+
required
54+
/>
55+
<input
56+
type="password"
57+
placeholder="Password"
58+
value={password}
59+
onChange={(e) => setPassword(e.target.value)}
60+
className="border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition"
61+
required
62+
/>
63+
<button
64+
type="submit"
65+
disabled={loading}
66+
className={`bg-blue-600 text-white py-3 rounded-lg shadow hover:bg-blue-700 transition font-semibold ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
67+
>
68+
{loading ? "Logging in..." : "Login"}
69+
</button>
70+
</form>
71+
72+
<div className="mt-6 text-center">
73+
<p className="text-gray-500">
74+
Don’t have an account?{" "}
75+
<span
76+
onClick={() => navigate("/register")}
77+
className="text-blue-600 font-medium cursor-pointer hover:underline"
78+
>
79+
Register
80+
</span>
81+
</p>
82+
</div>
83+
</div>
84+
</div>
85+
86+
);
87+
}

client/src/pages/Register.jsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
4+
export default function Register() {
5+
const [email, setEmail] = useState("");
6+
const [password, setPassword] = useState("");
7+
const [loading, setLoading] = useState(false);
8+
const navigate = useNavigate();
9+
10+
const handleRegister = async (e) => {
11+
e.preventDefault();
12+
setLoading(true);
13+
try {
14+
const res = await fetch("http://localhost:3000/api/auth/signup", {
15+
method: "POST",
16+
headers: { "Content-Type": "application/json" },
17+
body: JSON.stringify({ email, password }),
18+
});
19+
const data = await res.json();
20+
21+
if (res.ok) {
22+
alert("Registration successful! Please login.");
23+
navigate("/login"); // redirect to login after registration
24+
} else {
25+
alert(data.message || "Registration failed!");
26+
}
27+
} catch (err) {
28+
console.error(err);
29+
alert("Registration failed!");
30+
}
31+
setLoading(false);
32+
};
33+
34+
return (
35+
<div className="flex items-center justify-center min-h-screen bg-gradient-to-br from-gray-100 to-gray-300 px-4">
36+
<div className="w-full max-w-md bg-white rounded-2xl shadow-lg p-8 sm:p-10">
37+
<h1 className="text-3xl font-bold text-center text-blue-700 mb-6">
38+
Create Account
39+
</h1>
40+
<p className="text-center text-gray-500 mb-8">
41+
Sign up to start collaborating on Collab Canvas
42+
</p>
43+
44+
<form onSubmit={handleRegister} className="flex flex-col gap-5">
45+
<input
46+
type="email"
47+
placeholder="Email"
48+
value={email}
49+
onChange={(e) => setEmail(e.target.value)}
50+
className="border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition"
51+
required
52+
/>
53+
<input
54+
type="password"
55+
placeholder="Password"
56+
value={password}
57+
onChange={(e) => setPassword(e.target.value)}
58+
className="border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent transition"
59+
required
60+
/>
61+
<button
62+
type="submit"
63+
disabled={loading}
64+
className={`bg-blue-600 text-white py-3 rounded-lg shadow hover:bg-blue-700 transition font-semibold ${loading ? 'opacity-70 cursor-not-allowed' : ''}`}
65+
>
66+
{loading ? "Registering..." : "Register"}
67+
</button>
68+
</form>
69+
70+
<div className="mt-6 text-center">
71+
<p className="text-gray-500">
72+
Already have an account?{" "}
73+
<span
74+
onClick={() => navigate("/login")}
75+
className="text-blue-600 font-medium cursor-pointer hover:underline"
76+
>
77+
Login
78+
</span>
79+
</p>
80+
</div>
81+
</div>
82+
</div>
83+
84+
);
85+
}

server/.env

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1-
PORT=5000
2-
MONGO_URI=your_mongodb_connection_string
3-
CORS_ORIGIN=http://localhost:3000
1+
PORT=3000
2+
MONGO_URI=your_mongodb_connection_string_here
3+
CORS_ORIGIN=http://localhost:5173
4+
JWT_SECRET=your_jwt_secret_here

0 commit comments

Comments
 (0)