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 ( +
+ Collaborative workspace & infinite canvas +
++ {user?.role || 'Student'} +
+Collaborative workspace & infinite canvas
-{user?.username}
-{user?.role || 'Student'}
-- Start a new session on an infinite high-performance canvas. Optimized for teaching and sketching. -
-
- As a student, you can join whiteboards shared by your teachers using the room code below.
+
+
+ {/* Bento Grid */}
+
+ Start a new session on an infinite high-performance canvas.
+ Optimized for teaching and sketching.
+
+ As a student, you can join whiteboards shared by your teachers
+ using the room code below.
+
+ Enter a room code to connect.
+
+ {isTeacher
+ ? "Your recent whiteboards"
+ : "Boards from classes you attended"}
+ No saved boards yet
+ {isTeacher
+ ? "Create your first board to get started"
+ : "Join a class to see boards here"}
+
+ Teacher: {board.teacherName}
Enter a room code to connect.
- {isTeacher ? 'Your recent whiteboards' : 'Boards from classes you attended'}
- No saved boards yet
- {isTeacher ? 'Create your first board to get started' : 'Join a class to see boards here'}
-
- Teacher: {board.teacherName}
-
+ Create New
+
Whiteboard
+
+ Student Access
+
+
+
+
+
+
+ {isTeacher
+ ? board.name || "Untitled Board"
+ : board.boardName || "Untitled Board"}
+
+ {!isTeacher && board.teacherName && (
+
-
-
-
-
- {isTeacher
- ? (board.name || 'Untitled Board')
- : (board.boardName || 'Untitled Board')}
-
- {!isTeacher && board.teacherName && (
-