Skip to content

Commit 6397d17

Browse files
committed
Merge branch 'main' into feat/socket
2 parents b775cf1 + c58e801 commit 6397d17

File tree

6 files changed

+180
-44
lines changed

6 files changed

+180
-44
lines changed

client/src/components/Canvas.jsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const Canvas = () => {
1111
const [activeColor, setActiveColor] = useState("#000000");
1212
const [strokeWidth, setStrokeWidth] = useState(3);
1313
const [isDrawing, setIsDrawing] = useState(false);
14+
const [isCanvasFocused, setIsCanvasFocused] = useState(false); // 👈 new state
15+
1416
const [roomId, setRoomId] = useState("");
1517
const [joined, setJoined] = useState(false);
1618
const [socket, setSocket] = useState(null);
@@ -58,6 +60,38 @@ export const Canvas = () => {
5860
return () => window.removeEventListener("resize", handleResize);
5961
}, []);
6062

63+
// 🎹 Keyboard shortcut handling
64+
useEffect(() => {
65+
const handleKeyDown = (e) => {
66+
if (!isCanvasFocused) return; // only when canvas focused
67+
68+
if (e.key === "p" || e.key === "P" || e.key === "1") {
69+
handleToolChange("pen");
70+
} else if (e.key === "e" || e.key === "E" || e.key === "2") {
71+
handleToolChange("eraser");
72+
}
73+
};
74+
75+
window.addEventListener("keydown", handleKeyDown);
76+
return () => window.removeEventListener("keydown", handleKeyDown);
77+
}, [isCanvasFocused]);
78+
79+
// 🎹 Keyboard shortcut handling
80+
useEffect(() => {
81+
const handleKeyDown = (e) => {
82+
if (!isCanvasFocused) return; // only when canvas focused
83+
84+
if (e.key === "p" || e.key === "P" || e.key === "1") {
85+
handleToolChange("pen");
86+
} else if (e.key === "e" || e.key === "E" || e.key === "2") {
87+
handleToolChange("eraser");
88+
}
89+
};
90+
91+
window.addEventListener("keydown", handleKeyDown);
92+
return () => window.removeEventListener("keydown", handleKeyDown);
93+
}, [isCanvasFocused]);
94+
6195
// Drawing functions
6296
const startDrawing = (e) => {
6397
if (!joined) {
@@ -143,11 +177,14 @@ export const Canvas = () => {
143177

144178
<canvas
145179
ref={canvasRef}
180+
tabIndex={0} // 👈 allows focus
181+
onFocus={() => setIsCanvasFocused(true)} // 👈 activate shortcuts
182+
onBlur={() => setIsCanvasFocused(false)} // 👈 deactivate shortcuts
146183
onMouseDown={startDrawing}
147184
onMouseMove={draw}
148185
onMouseUp={stopDrawing}
149186
onMouseLeave={stopDrawing}
150-
className="cursor-crosshair"
187+
className="cursor-crosshair focus:outline-2 focus:outline-primary"
151188
/>
152189

153190
{!joined && (

client/src/components/Toolbar.jsx

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,62 @@
1-
import { MousePointer2, Pen, Eraser, Square, Circle, Minus, Trash2 } from "lucide-react";
1+
import {
2+
MousePointer2,
3+
Pen,
4+
Eraser,
5+
Square,
6+
Circle,
7+
Minus,
8+
Trash2,
9+
} from "lucide-react";
210
import { cn } from "../lib/utils";
311
import { Button } from "./ui/Button";
412
import { Separator } from "./ui/Separator";
513

614
export const Toolbar = ({ activeTool, onToolChange, onClear }) => {
7-
const tools = [
8-
{ type: "select", icon: MousePointer2 },
9-
{ type: "pen", icon: Pen },
10-
{ type: "eraser", icon: Eraser },
11-
{ type: "rectangle", icon: Square },
12-
{ type: "circle", icon: Circle },
13-
{ type: "line", icon: Minus },
14-
];
15+
const tools = [
16+
{ type: "select", icon: MousePointer2 },
17+
{ type: "pen", icon: Pen },
18+
{ type: "eraser", icon: Eraser },
19+
{ type: "rectangle", icon: Square },
20+
{ type: "circle", icon: Circle },
21+
{ type: "line", icon: Minus },
22+
];
1523

16-
return (
17-
<div className="fixed top-6 left-1/2 -translate-x-1/2 z-50">
18-
<div className="bg-toolbar border border-toolbar-border rounded-2xl shadow-xl backdrop-blur-sm p-2 flex items-center gap-2">
19-
{tools.map((tool) => {
20-
const Icon = tool.icon;
21-
return (
22-
<Button
23-
key={tool.type}
24-
variant="ghost"
25-
size="icon"
26-
onClick={() => onToolChange(tool.type)}
27-
className={cn(
28-
"h-10 w-10 transition-all duration-200 hover:bg-secondary",
29-
activeTool === tool.type && "bg-primary text-primary-foreground hover:bg-primary/90"
30-
)}
31-
aria-label={tool.type}
32-
>
33-
<Icon className="h-5 w-5" />
34-
</Button>
35-
);
36-
})}
24+
return (
25+
<div className="fixed top-6 left-1/2 -translate-x-1/2 z-50">
26+
<div className="bg-toolbar border border-toolbar-border rounded-2xl shadow-xl backdrop-blur-sm p-2 flex items-center gap-2">
27+
{tools.map((tool) => {
28+
const Icon = tool.icon;
29+
return (
30+
<Button
31+
key={tool.type}
32+
variant="ghost"
33+
size="icon"
34+
onClick={() => onToolChange(tool.type)}
35+
className={cn(
36+
"h-10 w-10 transition-all duration-200 hover:bg-secondary",
37+
activeTool === tool.type
38+
? "bg-primary text-primary-foreground ring-2 ring-offset-2 ring-primary"
39+
: ""
40+
)}
41+
aria-label={tool.type}
42+
>
43+
<Icon className="h-5 w-5" />
44+
</Button>
45+
);
46+
})}
3747

38-
<Separator orientation="vertical" className="h-8 mx-1" />
48+
<Separator orientation="vertical" className="h-8 mx-1" />
3949

40-
<Button
41-
variant="ghost"
42-
size="icon"
43-
onClick={onClear}
44-
className="h-10 w-10 transition-all duration-200 hover:bg-destructive/10 hover:text-destructive"
45-
aria-label="Clear canvas"
46-
>
47-
<Trash2 className="h-5 w-5" />
48-
</Button>
49-
</div>
50-
</div>
51-
);
50+
<Button
51+
variant="ghost"
52+
size="icon"
53+
onClick={onClear}
54+
className="h-10 w-10 transition-all duration-200 hover:bg-destructive/10 hover:text-destructive"
55+
aria-label="Clear canvas"
56+
>
57+
<Trash2 className="h-5 w-5" />
58+
</Button>
59+
</div>
60+
</div>
61+
);
5262
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const bcrypt = require("bcrypt");
2+
const jwt = require("jsonwebtoken");
3+
4+
const users = [];
5+
const tokenBlacklist = [];
6+
7+
const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret";
8+
const JWT_EXPIRES_IN = "1h";
9+
10+
exports.registerUser = async (req, res) => {
11+
const { email, password } = req.body;
12+
if (users.find(user => user.email === email)) {
13+
return res.status(400).json({ message: "User already exists" });
14+
}
15+
const hash = await bcrypt.hash(password, 10);
16+
users.push({ email, password: hash });
17+
res.json({ message: "User registered" });
18+
}
19+
exports.loginUser = async (req, res) => {
20+
const { email, password } = req.body;
21+
const user = users.find((u) => u.email === email);
22+
if (!user) return res.status(400).json({ message: "Invalid credentials" });
23+
24+
const match = await bcrypt.compare(password, user.password);
25+
if (!match) return res.status(400).json({ message: "Invalid credentials" });
26+
27+
const token = jwt.sign({ email }, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
28+
res.json({ token });
29+
}
30+
exports.logoutUser = (req, res) => {
31+
const { token } = req.body;
32+
tokenBlacklist.push(token);
33+
res.json({ message: "Logged out" });
34+
}
35+
36+
exports.getUsers =() => users;
37+
38+
exports.getBlacklist =() => tokenBlacklist;

server/index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,24 @@ const { Server } = require("socket.io");
44
const cors = require("cors");
55

66
const app = express();
7+
const PORT = process.env.PORT || 3000;
8+
9+
// Middleware
710
app.use(express.json());
811
app.use(cors());
912

13+
// Routes
14+
const authRoutes = require("./routes/authRoutes");
15+
app.use("/api/auth", authRoutes);
16+
17+
// Root
18+
app.get("/", (req, res) => {
19+
res.send("Collab Canvas server is running!");
20+
});
21+
22+
app.listen(PORT, () => {
23+
console.log(`Server listening on port ${PORT}`);
24+
1025
const PORT = process.env.PORT || 3000;
1126

1227
// Basic root route
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const jwt = require("jsonwebtoken");
2+
const { getBlacklist } = require("../controllers/authController");
3+
4+
const JWT_SECRET = process.env.JWT_SECRET || "your_jwt_secret";
5+
6+
exports.authMiddleware = (req, res, next) => {
7+
const auth = req.headers.authorization;
8+
if (!auth) return res.status(401).json({ message: "No token" });
9+
10+
const token = auth.split(" ")[1];
11+
if (tokenBlacklist.includes(token)) {
12+
return res.status(403).json({ message: "Logged out" });
13+
}
14+
15+
try {
16+
const payload = jwt.verify(token, JWT_SECRET);
17+
req.user = payload;
18+
next();
19+
} catch {
20+
res.status(403).json({ message: "Invalid token" });
21+
}
22+
}
23+

server/routes/authRoutes.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const express = require("express");
2+
const router = express.Router();
3+
const { registerUser, loginUser, logoutUser } = require("../controllers/authController");
4+
const { authMiddleware } = require("../middleware/authMiddleware");
5+
6+
router.post("/signup", registerUser);
7+
router.post("/login", loginUser);
8+
router.post("/logout", logoutUser);
9+
router.get("/me", authMiddleware, (req, res) => {
10+
res.json({ user: req.user });
11+
});
12+
13+
module.exports = router;

0 commit comments

Comments
 (0)