From a9e2142a57ad008036f9c05ab8f02c92de2454f0 Mon Sep 17 00:00:00 2001 From: "marthajerushamarumudi7@gmail.com" <144675544+Jerusha547@users.noreply.github.com> Date: Sun, 24 May 2026 21:24:11 +0530 Subject: [PATCH] feat: dropdown menu added --- client/src/pages/Dashboard.jsx | 840 +++++++++++++++++++-------------- 1 file changed, 493 insertions(+), 347 deletions(-) diff --git a/client/src/pages/Dashboard.jsx b/client/src/pages/Dashboard.jsx index 40638427..464ae361 100644 --- a/client/src/pages/Dashboard.jsx +++ b/client/src/pages/Dashboard.jsx @@ -1,377 +1,523 @@ -import React, { useState, useEffect } from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; -import { v4 as uuidv4 } from 'uuid'; -import api from '../lib/api'; -import { FaPlus, FaSignInAlt, FaSignOutAlt, FaRocket, FaFolder, FaClock, FaTrash, FaCopy, FaCheck } from 'react-icons/fa'; -import { BsLightningChargeFill } from 'react-icons/bs'; -import { motion } from 'framer-motion'; -import CreateBoardModal from '../components/CreateBoardModal'; +import React, { useState, useEffect } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { v4 as uuidv4 } from "uuid"; +import api from "../lib/api"; +import { BsLightningChargeFill } from "react-icons/bs"; +import { motion } from "framer-motion"; +import CreateBoardModal from "../components/CreateBoardModal"; +import { + FaPlus, + FaSignInAlt, + FaSignOutAlt, + FaRocket, + FaFolder, + FaClock, + FaTrash, + FaCopy, + FaCheck, + FaUser, + FaCog, + FaQuestionCircle, + FaMoon, +} from "react-icons/fa"; + +import { AnimatePresence } from "framer-motion"; const Dashboard = () => { - const [roomId, setRoomId] = useState(''); - const [isModalOpen, setIsModalOpen] = useState(false); - const [savedBoards, setSavedBoards] = useState([]); - const [loading, setLoading] = useState(true); - const [copiedBoardId, setCopiedBoardId] = useState(null); - const navigate = useNavigate(); - const location = useLocation(); - const user = JSON.parse(localStorage.getItem('user')); - const isTeacher = user?.role === 'teacher'; - - // Redirect admin to admin panel if they somehow reach this page - useEffect(() => { - if (user?.role === 'admin') { - navigate('/admin', { replace: true }); - } - }, [user, navigate]); - - // Fetch saved boards for both teachers and students - useEffect(() => { - if (user?.id) { - fetchSavedBoards(); - } else { - setLoading(false); - } - }, [user?.id, location.pathname]); - - const fetchSavedBoards = async () => { + const [roomId, setRoomId] = useState(""); + const [isModalOpen, setIsModalOpen] = useState(false); + const [savedBoards, setSavedBoards] = useState([]); + const [loading, setLoading] = useState(true); + const [copiedBoardId, setCopiedBoardId] = useState(null); + const [isProfileOpen, setIsProfileOpen] = useState(false); + const navigate = useNavigate(); + const location = useLocation(); + const user = JSON.parse(localStorage.getItem("user")); + const isTeacher = user?.role === "teacher"; + + // Redirect admin to admin panel if they somehow reach this page + useEffect(() => { + if (user?.role === "admin") { + navigate("/admin", { replace: true }); + } + }, [user, navigate]); + + // Fetch saved boards for both teachers and students + useEffect(() => { + if (user?.id) { + fetchSavedBoards(); + } else { + setLoading(false); + } + }, [user?.id, location.pathname]); + + const fetchSavedBoards = async () => { + try { + let res; + if (isTeacher) { + // Teachers: fetch boards they created + res = await api.get(`/api/boards/user/${user.id}`); + } else { + // Students: fetch their saved boards (independent copies) + res = await api.get(`/api/boards/saved/${user.id}`); + } + setSavedBoards(res.data); + } catch (err) { + console.error("Error fetching boards:", err); + } finally { + setLoading(false); + } + }; + + const generateRoomId = () => { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const randomStr = (len) => + Array.from( + { length: len }, + () => chars[Math.floor(Math.random() * chars.length)], + ).join(""); + return `EDU-${randomStr(4)}-${randomStr(4)}`; + }; + + const handleCreateBoard = async (boardName) => { + try { + const newRoomId = generateRoomId(); + const payload = { + name: boardName, + userId: user.id, + roomId: newRoomId, + }; + const response = await api.post("/api/boards/create", payload); + setIsModalOpen(false); + navigate(`/board/${newRoomId}`); + } catch (err) { + console.error("Error creating board:", err); + console.error("Error response:", err.response?.data); + alert("Failed to create board. Please try again."); + } + }; + + const openBoard = (roomId) => { + navigate(`/board/${roomId}`); + }; + + const handleDeleteBoard = async (roomId, boardName, boardId, e) => { + e.stopPropagation(); + + if (!window.confirm(`Are you sure you want to delete "${boardName}"?`)) { + return; + } + + try { + if (isTeacher) { + // Teachers: delete the actual board try { - let res; - if (isTeacher) { - // Teachers: fetch boards they created - res = await api.get(`/api/boards/user/${user.id}`); - } else { - // Students: fetch their saved boards (independent copies) - res = await api.get(`/api/boards/saved/${user.id}`); - } - setSavedBoards(res.data); + console.log("[DELETE] Teacher deleting board by roomId:", roomId); + await api.delete(`/api/boards/${roomId}`); } catch (err) { - console.error('Error fetching boards:', err); - } finally { - setLoading(false); + // If delete by roomId fails, try by _id (for orphaned boards) + if (err.response?.status === 404) { + console.log( + "[DELETE] Teacher deleting board by _id with force:", + boardId, + ); + await api.delete(`/api/boards/by-id/${boardId}?force=true`); + } else { + throw err; + } } - }; + } else { + // Students: delete their saved copy + await api.delete(`/api/boards/saved/${boardId}`); + } + fetchSavedBoards(); + } catch (err) { + console.error("Error deleting board:", err); + console.error("Error response:", err.response?.data); + alert( + `Failed to delete board: ${err.response?.data?.message || err.message}`, + ); + } + }; + + const joinMeeting = async (e) => { + e.preventDefault(); + const code = roomId.trim().toUpperCase(); + + if (!code) return; + + // Validation - ensure either standard UUID or EDU-XXXX-XXXX format + // (to remain backwards compatible with old boards while strictly checking new ones) + const isEduPattern = /^EDU-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(code); + const isUUIDPattern = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( + code, + ); + + if (!isEduPattern && !isUUIDPattern) { + alert("Invalid room code format. Expected format: EDU-XXXX-XXXX"); + return; + } + + try { + const response = await api.get(`/api/boards/${code}`); + if (response.data) { + navigate(`/board/${code}`); + } else { + alert("Room code is invalid or room no longer exists."); + } + } catch (err) { + alert("Room code is invalid or room no longer exists."); + } + }; + + const handleLogout = () => { + localStorage.removeItem("token"); + localStorage.removeItem("user"); + navigate("/login"); + }; + + const handleCopyLink = (roomId, e) => { + e.stopPropagation(); + const boardUrl = `${window.location.origin}/whiteboard/${roomId}`; + navigator.clipboard + .writeText(boardUrl) + .then(() => { + setCopiedBoardId(roomId); + setTimeout(() => setCopiedBoardId(null), 2000); + }) + .catch((err) => { + console.error("Failed to copy:", err); + }); + }; + + const formatDate = (dateString) => { + const date = new Date(dateString); + const now = new Date(); + const diffTime = Math.abs(now - date); + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays === 0) return "Today"; + if (diffDays === 1) return "Yesterday"; + if (diffDays < 7) return `${diffDays} days ago`; + return date.toLocaleDateString(); + }; + + return ( +
+ {/* Header */} +
+
+

+ {" "} + EduBoard{" "} + + PRO + +

+

+ Collaborative workspace & infinite canvas +

+
+
+ + {/* Profile Button */} + + + {/* Dropdown */} + + {isProfileOpen && ( + - const handleDeleteBoard = async (roomId, boardName, boardId, e) => { - e.stopPropagation(); + {/* User Header */} +
+
+
+ {user?.username?.charAt(0)?.toUpperCase() || 'U'} +
- if (!window.confirm(`Are you sure you want to delete "${boardName}"?`)) { - return; - } +
+

+ {user?.username} +

- try { - if (isTeacher) { - // Teachers: delete the actual board - try { - console.log('[DELETE] Teacher deleting board by roomId:', roomId); - await api.delete(`/api/boards/${roomId}`); - } catch (err) { - // If delete by roomId fails, try by _id (for orphaned boards) - if (err.response?.status === 404) { - console.log('[DELETE] Teacher deleting board by _id with force:', boardId); - await api.delete(`/api/boards/by-id/${boardId}?force=true`); - } else { - throw err; - } - } - } else { - // Students: delete their saved copy - await api.delete(`/api/boards/saved/${boardId}`); - } - fetchSavedBoards(); - } catch (err) { - console.error('Error deleting board:', err); - console.error('Error response:', err.response?.data); - alert(`Failed to delete board: ${err.response?.data?.message || err.message}`); - } - }; +

+ {user?.role || 'Student'} +

+
+
+
- const joinMeeting = async (e) => { - e.preventDefault(); - const code = roomId.trim().toUpperCase(); + {/* Menu Items */} +
- if (!code) return; + - // Validation - ensure either standard UUID or EDU-XXXX-XXXX format - // (to remain backwards compatible with old boards while strictly checking new ones) - const isEduPattern = /^EDU-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(code); - const isUUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(code); + - if (!isEduPattern && !isUUIDPattern) { - alert('Invalid room code format. Expected format: EDU-XXXX-XXXX'); - return; - } + + + + +
- try { - const response = await api.get(`/api/boards/${code}`); - if (response.data) { - navigate(`/board/${code}`); - } else { - alert('Room code is invalid or room no longer exists.'); - } - } catch (err) { - alert('Room code is invalid or room no longer exists.'); - } - }; - - const handleLogout = () => { - localStorage.removeItem('token'); - localStorage.removeItem('user'); - navigate('/login'); - }; - - const handleCopyLink = (roomId, e) => { - e.stopPropagation(); - const boardUrl = `${window.location.origin}/whiteboard/${roomId}`; - navigator.clipboard.writeText(boardUrl).then(() => { - setCopiedBoardId(roomId); - setTimeout(() => setCopiedBoardId(null), 2000); - }).catch(err => { - console.error('Failed to copy:', err); - }); - }; - - const formatDate = (dateString) => { - const date = new Date(dateString); - const now = new Date(); - const diffTime = Math.abs(now - date); - const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); - - if (diffDays === 0) return 'Today'; - if (diffDays === 1) return 'Yesterday'; - if (diffDays < 7) return `${diffDays} days ago`; - return date.toLocaleDateString(); - }; - - return ( -
- - {/* Header */} -
-
-

- EduBoard PRO -

-

Collaborative workspace & infinite canvas

-
-
-
-

{user?.username}

-

{user?.role || 'Student'}

-
-
- - {/* Bento Grid */} - - {/* Main Action: New Board (Large) - Teachers Only */} - {isTeacher && ( - setIsModalOpen(true)} - > -
- -
-
- -
-

Create New
Whiteboard

-

- Start a new session on an infinite high-performance canvas. Optimized for teaching and sketching. -

-
+
+ )} + +
-
- Launch Editor -
- - )} - - {/* Student Message - Students Only */} - {!isTeacher && ( -
-
-
- -
-

Student Access

-
-

- As a student, you can join whiteboards shared by your teachers using the room code below. +

+ + {/* Bento Grid */} + + {/* Main Action: New Board (Large) - Teachers Only */} + {isTeacher && ( + setIsModalOpen(true)} + > +
+ +
+
+ +
+

+ Create New
Whiteboard +

+

+ Start a new session on an infinite high-performance canvas. + Optimized for teaching and sketching. +

+
+ +
+ Launch Editor +
+
+ )} + + {/* Student Message - Students Only */} + {!isTeacher && ( +
+
+
+ +
+

+ Student Access +

+
+

+ As a student, you can join whiteboards shared by your teachers + using the room code below. +

+
+ )} + + {/* Join Session - Students Only */} + {!isTeacher && ( +
+
+

+ Join Session +

+

+ Enter a room code to connect. +

+ +
+ setRoomId(e.target.value)} + className="bg-black/50 border border-white/10 rounded-lg px-3 sm:px-4 py-2.5 sm:py-3 text-white w-full focus:outline-none focus:border-cyan-500/50 transition-colors font-mono text-xs sm:text-sm" + /> + +
+
+ )} + + {/* Saved Boards List - Both Teachers and Students */} +
+
+
+

+ {" "} + Saved Boards +

+

+ {isTeacher + ? "Your recent whiteboards" + : "Boards from classes you attended"} +

+
+
+ + {/* Boards List */} +
+ {loading ? ( +
+
+
+ ) : savedBoards.length === 0 ? ( +
+ +

No saved boards yet

+

+ {isTeacher + ? "Create your first board to get started" + : "Join a class to see boards here"} +

+
+ ) : ( + savedBoards.map((board) => ( + openBoard(board.roomId)} + className="bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl p-4 cursor-pointer transition-all group" + > +
+
+

+ {isTeacher + ? board.name || "Untitled Board" + : board.boardName || "Untitled Board"} +

+ {!isTeacher && board.teacherName && ( +

+ Teacher: {board.teacherName}

+ )}
- )} - - {/* Join Session - Students Only */} - {!isTeacher && ( -
-
-

- Join Session -

-

Enter a room code to connect.

- -
- setRoomId(e.target.value)} - className="bg-black/50 border border-white/10 rounded-lg px-3 sm:px-4 py-2.5 sm:py-3 text-white w-full focus:outline-none focus:border-cyan-500/50 transition-colors font-mono text-xs sm:text-sm" - /> - -
-
- )} - - {/* Saved Boards List - Both Teachers and Students */} -
-
-
-

- Saved Boards -

-

- {isTeacher ? 'Your recent whiteboards' : 'Boards from classes you attended'} -

-
-
- - {/* Boards List */} -
- {loading ? ( -
-
-
- ) : savedBoards.length === 0 ? ( -
- -

No saved boards yet

-

- {isTeacher ? 'Create your first board to get started' : 'Join a class to see boards here'} -

-
- ) : ( - savedBoards.map((board) => ( - openBoard(board.roomId)} - className="bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl p-4 cursor-pointer transition-all group" - > -
-
-

- {isTeacher - ? (board.name || 'Untitled Board') - : (board.boardName || 'Untitled Board')} -

- {!isTeacher && board.teacherName && ( -

- Teacher: {board.teacherName} -

- )} -
-
- {isTeacher && ( - - )} - -
- → -
-
-
-
- )) - )} +
+ {isTeacher && ( + + )} + +
+ → +
-
- - - - - {/* Create Board Modal */} - setIsModalOpen(false)} - onCreateBoard={handleCreateBoard} - /> +
+ + )) + )} +
- ); + + + {/* Create Board Modal */} + setIsModalOpen(false)} + onCreateBoard={handleCreateBoard} + /> +
+ ); }; export default Dashboard;