-
- {liveClass.teacher?.name}
-
-
- {isTeacher && cameraOn && (
-
- π· on
-
- )}
- {isTeacher && micOn && (
-
- π€ on
-
- )}
- {isTeacher && screenSharing && (
-
- π₯οΈ sharing
-
- )}
- {isTeacher && isRecording && (
-
- βΊ rec
-
- )}
+ {/* β Q&A ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */}
+ {panelTab === "qa" && (
+ <>
+
+ {questions.length === 0 && (
+
-
-
-
-
- {/* Raised hands */}
- {handCount > 0 && (
-
-
- β Raised Hands ({handCount})
-
-
- {Object.entries(raisedHands).map(([uid, name]) => (
-
-
-
-
- {name}
-
+ )}
+ {questions.map((q) => (
+
+
+
+ {q.student?.name?.[0]?.toUpperCase() ?? "?"}
+
+
+
+
+ {q.student?.name}
+
+ {q.isAnswered && (
+
+ Answered
+
+ )}
+
+
+ {q.question}
+
- {isTeacher && (
+
+ {isTeacher && !q.isAnswered && (
+
+
+
+ )}
+
+ ))}
+
+ {!isTeacher && (
+
+ )}
+ >
+ )}
+
+ {/* β People βββββββββββββββββββββββββββββββββββββββββββββββββββββ */}
+ {panelTab === "people" && (
+
+ {/* Host */}
+
+
+ Host
+
+
+
+ {liveClass.teacher?.name?.[0]?.toUpperCase() ?? "T"}
+
+
+
+ {liveClass.teacher?.name}
+
+
+ {isTeacher && cameraOn && (
+
+ π· cam
+
+ )}
+ {isTeacher && micOn && (
+
+ π€ mic
+
+ )}
+ {isTeacher && screenSharing && (
+
+ π₯οΈ screen
+
+ )}
+ {isTeacher && isRecording && (
+
+ βΊ rec
+
)}
- ))}
+
- )}
- {/* Participant count */}
-
-
- Students Β· {participantCount}
-
-
- {participantCount}{" "}
- {participantCount === 1 ? "person has" : "people have"} joined
- this session.
-
- {liveClass.recordingUrl && (
-
- βΆ Class Recording
-
+ {/* Raised hands */}
+ {handCount > 0 && (
+
+
+ β Raised Hands ({handCount})
+
+
+ {Object.entries(raisedHands).map(([uid, name]) => (
+
+
+
+ {name?.[0]?.toUpperCase()}
+
+
+ {name}
+
+
+ {isTeacher && (
+
+ )}
+
+ ))}
+
+
)}
+
+ {/* Students */}
+
+
+ Students Β· {participantCount}
+
+ {isTeacher && studentTilesList.length > 0 ? (
+
+ {studentTilesList.map(([sid, info]) => (
+
+
+ {info.userName?.[0]?.toUpperCase() ?? "?"}
+
+
+ {info.userName}
+
+ {info.camOn && (
+
+ π·
+
+ )}
+ {raisedHands[info.userId] && (
+
β
+ )}
+
+ ))}
+
+ ) : (
+
+ {participantCount}{" "}
+ {participantCount === 1 ? "person has" : "people have"}{" "}
+ joined this session.
+
+ )}
+ {liveClass.recordingUrl && (
+
+ βΆ Class Recording
+
+ )}
+
-
- )}
-
+ )}
+
+ )}
);
diff --git a/docs/app/page.tsx b/docs/app/page.tsx
index 3f36f7c..1500c93 100644
--- a/docs/app/page.tsx
+++ b/docs/app/page.tsx
@@ -3,7 +3,7 @@ import Image from "next/image";
export default function Home() {
return (
-
+
-
-
- To get started, edit the page.tsx file.
-
-
- Looking for a starting point or more instructions? Head over to{" "}
-
- Templates
- {" "}
- or the{" "}
-
- Learning
- {" "}
- center.
-
-
-
+ SmartClass Documentation
+ Welcome to the SmartClass docs site.
+
+ Quick Start
+
+ - Install dependencies:
npm install
+ - Run locally:
npm run dev
+ - Open:
http://localhost:3000
+
+
+ Scripts
+
+ npm run dev β Start development server
+ npm run build β Build production output
+ npm start β Run production server
+ npm run lint β Run lint checks
+
);
diff --git a/server/app.js b/server/app.js
index 1780321..94bf72d 100644
--- a/server/app.js
+++ b/server/app.js
@@ -67,7 +67,7 @@ export function buildApp() {
socket.to(`liveclass:${liveClassId}`).emit("broadcaster-ready", { liveClassId });
});
- socket.on("viewer", ({ liveClassId }) => {
+ socket.on("viewer", ({ liveClassId, userId, userName }) => {
if (!liveClassId) return;
socket.join(`liveclass:${liveClassId}`);
const broadcasterSocketId = broadcasters.get(liveClassId);
@@ -75,10 +75,26 @@ export function buildApp() {
io.to(broadcasterSocketId).emit("new-viewer", {
viewerSocketId: socket.id,
liveClassId,
+ userId,
+ userName,
});
}
});
+ // ββ Student camera presence βββββββββββββββββββββββββββββββββββββββββββββ
+ socket.on("student-cam-on", ({ liveClassId, userId, userName }) => {
+ if (!liveClassId) return;
+ socket
+ .to(`liveclass:${liveClassId}`)
+ .emit("student-cam-on", { userId, userName, socketId: socket.id });
+ });
+ socket.on("student-cam-off", ({ liveClassId, userId }) => {
+ if (!liveClassId) return;
+ socket
+ .to(`liveclass:${liveClassId}`)
+ .emit("student-cam-off", { userId, socketId: socket.id });
+ });
+
socket.on("offer", ({ to, offer, liveClassId }) => {
io.to(to).emit("offer", { from: socket.id, offer, liveClassId });
});