-
- {(mode === "solo" || isHost) && (
-
- )}
-
- {question && (
- <>
-
{question.title}
-
-
- Difficulty: {question.difficulty}
+
+
+ {/* LEFT - QUESTION LIST */}
+
+
Questions
+
+ {questions.length === 0 ? (
+
No questions available
+ ) : (
+ questions.map((q) => (
+
handleSelect(q)}
+ className={`p-2 mb-2 rounded cursor-pointer border ${
+ selected?._id === q._id ? "bg-blue-100" : "hover:bg-gray-100"
+ }`}
+ >
+
{q.title}
+
{q.difficulty}
+
+ ))
+ )}
+
+
+ {/* RIGHT - QUESTION DETAILS */}
+
+
+ {!selected ? (
+
+ Select a question from left panel
-
-
- {["description", "examples", "constraints"].map((tab) => (
-
- ))}
-
-
-
- {activeTab === "description" &&
{question.description}
}
-
- {activeTab === "constraints" && (
-
{question.constraints}
- )}
-
- {activeTab === "examples" && (
-
- {question.examples.map((ex, i) => (
+ ) : (
+ <>
+
{selected.title}
+
+
+ Difficulty: {selected.difficulty}
+
+
+ {/* tabs */}
+
+ {["description", "examples", "constraints"].map((tab) => (
+
+ ))}
+
+
+
+ {activeTab === "description" && (
+
{selected.description}
+ )}
+
+ {activeTab === "constraints" && (
+
{selected.constraints}
+ )}
+
+ {activeTab === "examples" && (
+ selected.examples?.map((ex, i) => (
Input: {ex.input}
Output: {ex.output}
Explanation: {ex.explanation}
- ))}
-
- )}
-
-
-
-
-
-
-
- {question.starterCode?.[language]}
-
- >
- )}
+ ))
+ )}
+
+
+ {/* starter code */}
+
+
+
+
+ {selected.starterCode?.[language]}
+
+
+ >
+ )}
+
);
}
\ No newline at end of file
diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx
index 286154e..f749347 100644
--- a/frontend/src/main.jsx
+++ b/frontend/src/main.jsx
@@ -1,10 +1,11 @@
-import React from 'react'
-import ReactDOM from 'react-dom/client'
-import { BrowserRouter } from 'react-router-dom'
-import App from './App'
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+import { BrowserRouter } from "react-router-dom";
+import "./index.css";
-ReactDOM.createRoot(document.getElementById('root')).render(
+ReactDOM.createRoot(document.getElementById("root")).render(
-)
\ No newline at end of file
+);
\ No newline at end of file
diff --git a/frontend/src/pages/AdminPanel.jsx b/frontend/src/pages/AdminPanel.jsx
new file mode 100644
index 0000000..bb00357
--- /dev/null
+++ b/frontend/src/pages/AdminPanel.jsx
@@ -0,0 +1,142 @@
+import { useEffect, useState } from "react";
+import {
+ getAllQuestions,
+ createQuestion,
+ deleteQuestion,
+} from "../services/questionAPI";
+
+export default function AdminPanel() {
+ const [questions, setQuestions] = useState([]);
+
+ const [form, setForm] = useState({
+ title: "",
+ description: "",
+ difficulty: "easy",
+ tags: "",
+ });
+
+ // LOAD QUESTIONS
+ const loadQuestions = async () => {
+ const data = await getAllQuestions();
+ setQuestions(Array.isArray(data) ? data : []);
+ };
+
+ useEffect(() => {
+ loadQuestions();
+ }, []);
+
+ // HANDLE INPUT
+ const handleChange = (e) => {
+ setForm({ ...form, [e.target.name]: e.target.value });
+ };
+
+ // ADD QUESTION
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+ const payload = {
+ ...form,
+ tags: form.tags.split(",").map((t) => t.trim()),
+ examples: [],
+ starterCode: { cpp: "", java: "", python: "" },
+ testCases: [],
+ };
+
+ await createQuestion(payload);
+
+ setForm({
+ title: "",
+ description: "",
+ difficulty: "easy",
+ tags: "",
+ });
+
+ loadQuestions();
+ };
+
+ // DELETE
+ const handleDelete = async (id) => {
+ await deleteQuestion(id);
+ loadQuestions();
+ };
+
+ return (
+
+
+
Admin Panel
+
+ {/* ADD FORM */}
+
+
+ {/* QUESTION LIST */}
+
+
All Questions
+
+ {questions.map((q) => (
+
+
+
{q.title}
+
{q.difficulty}
+
+
+
+
+ ))}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Interview.jsx b/frontend/src/pages/Interview.jsx
index de94673..fbaff4c 100644
--- a/frontend/src/pages/Interview.jsx
+++ b/frontend/src/pages/Interview.jsx
@@ -1,8 +1,10 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { socket } from "../services/socket";
+
import CodeEditor from "../components/InterviewRoom/CodeEditor";
import Chat from "../components/InterviewRoom/Chat";
+import QuestionPanel from "../components/InterviewRoom/QuestionPanel";
export default function Interview() {
const { roomId } = useParams();
@@ -10,67 +12,94 @@ export default function Interview() {
const [mode, setMode] = useState("peer");
const [timeLeft, setTimeLeft] = useState(null);
const [duration, setDuration] = useState(600);
+ const [question, setQuestion] = useState(null);
useEffect(() => {
- socket.on("room-users", ({ mode }) => {
+ const handleRoomUsers = ({ mode }) => {
setMode(mode);
- });
+ };
+ socket.on("room-users", handleRoomUsers);
socket.on("timer-update", setTimeLeft);
-
socket.on("timer-ended", () => alert("Time up"));
return () => {
- socket.off("room-users");
- socket.off("timer-update");
+ socket.off("room-users", handleRoomUsers);
+ socket.off("timer-update", setTimeLeft);
socket.off("timer-ended");
};
}, []);
const format = (t) => {
- if (!t) return "Not Started";
+ if (!t && t !== 0) return "Not Started";
const m = Math.floor(t / 60);
const s = t % 60;
return `${m}:${s < 10 ? "0" : ""}${s}`;
};
return (
-
-
- {/* TIMER */}
- {mode !== "practice" && (
-
-
⏱ {format(timeLeft)}
-
setDuration(e.target.value)}
- />
-
+
+
+ {/* TOP BAR */}
+
+
+
+
+ {mode !== "practice" && (
+
+
+ ⏱ {format(timeLeft)}
+
+
+ setDuration(Number(e.target.value))}
+ className="border px-2 py-1 w-20 rounded"
+ />
+
+
+
+ )}
+
+
+ {/* MAIN SECTION */}
+
+
+ {/* QUESTION PANEL (BOTH USERS CAN CONTROL) */}
+
+
- )}
-
-
-
-
+ {/* CODE EDITOR */}
+
+
+ {/* CHAT */}
{mode === "peer" && (
-
);
diff --git a/frontend/src/pages/SoloInterview.jsx b/frontend/src/pages/SoloInterview.jsx
index 67026f9..912b6ff 100644
--- a/frontend/src/pages/SoloInterview.jsx
+++ b/frontend/src/pages/SoloInterview.jsx
@@ -1,8 +1,10 @@
import { useState } from "react";
import CodeEditor from "../components/InterviewRoom/CodeEditor";
+import QuestionPanel from "../components/InterviewRoom/QuestionPanel";
export default function SoloInterview() {
const [timeLeft, setTimeLeft] = useState(600);
+ const [question, setQuestion] = useState(null);
const startTimer = () => {
const interval = setInterval(() => {
@@ -23,15 +25,38 @@ export default function SoloInterview() {
};
return (
-
-
💻 Practice Mode
+
-
⏱ {format(timeLeft)}
+ {/* TOP BAR */}
+
-
+
💻 Practice Mode
+
+
+
⏱ {format(timeLeft)}
+
+
+
+
+
+ {/* MAIN SECTION */}
+
+
+ {/* QUESTION */}
+
+
+
+
+ {/* CODE EDITOR */}
+
+
+
-
-
);
diff --git a/frontend/src/services/questionAPI.js b/frontend/src/services/questionAPI.js
index a2d18bb..da72a9b 100644
--- a/frontend/src/services/questionAPI.js
+++ b/frontend/src/services/questionAPI.js
@@ -1,11 +1,33 @@
import axios from "axios";
+import API from "./api";
+
export const getQuestionById = async (id) => {
- const res = await axios.get(`/api/questions/${id}`);
+ const res = await API.get(`/questions/${id}`);
return res.data;
};
export const runCode = async (payload) => {
- const res = await axios.post("/api/code/run", payload);
+ const res = await API.post("/code/run", payload);
+ return res.data;
+};
+
+export const getRandomQuestion = async () => {
+ const res = await API.get("/questions/random");
+ return res.data;
+};
+
+export const getAllQuestions = async (filters = {}) => {
+ const res = await API.get("/questions", { params: filters });
+ return Array.isArray(res.data) ? res.data : [];
+};
+
+export const createQuestion = async (data) => {
+ const res = await API.post("/questions", data);
+ return res.data;
+};
+
+export const deleteQuestion = async (id) => {
+ const res = await API.delete(`/questions/${id}`);
return res.data;
};
\ No newline at end of file
diff --git a/frontend/src/services/socket.js b/frontend/src/services/socket.js
index 9152190..086422d 100644
--- a/frontend/src/services/socket.js
+++ b/frontend/src/services/socket.js
@@ -2,20 +2,32 @@ import { io } from "socket.io-client";
const URL = import.meta.env.VITE_BACKEND_URL || "http://localhost:5000";
-// don't connect immediately
export const socket = io(URL, {
autoConnect: false,
reconnection: true,
+ transports: ["websocket"],
});
-//call this AFTER login
+let isConnecting = false;
+
export const connectSocket = () => {
- if (socket.connected) return;
+ if (socket.connected || isConnecting) return;
+
+ isConnecting = true;
+
const token = localStorage.getItem("token");
- socket.auth = {
- token: token,
- };
+ socket.auth = { token };
socket.connect();
+
+ socket.once("connect", () => {
+ console.log("SOCKET CONNECTED:", socket.id);
+ isConnecting = false;
+ });
+
+ socket.once("connect_error", (err) => {
+ console.log("SOCKET ERROR:", err.message);
+ isConnecting = false;
+ });
};
\ No newline at end of file