I’m creating a public website where users input their information, which is then stored in MongoDB. Users can click a “find” button to see a list of similar users. They can choose to connect with another user, which triggers an invitation. If accepted, a real-time chat box should appear for them to communicate. (Html,css,js, nodejs, mongodb)
This is index.html ( from <body ):
<form id="teamFormationForm">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required><br>
<label for="studentID">Student ID:</label>
<input type="text" id="studentID" name="studentID" pattern="[sS][0-9]{7}"><br>
<label for="mobile">Mobile Phone Number:</label>
<input type="text" id="mobile" name="mobile" required><br>
<label for="universityEmail">University Email:</label>
<input type="email" id="universityEmail" name="universityEmail"><br>
<label for="interests">Interests:</label>
<textarea id="interests" name="interests" rows="4" cols="50" required></textarea><br>
<label for="overallGPA">Current Overall GPA:</label>
<input type="number" id="overallGPA" name="overallGPA" min="0" max="4" step="0.1" required>
<div id="courses">
<!-- Initial course box, hidden by default -->
<div class="course" style="display: none;">
<label for="courseTitle">Course:</label>
<input type="text" class="courseTitle" name="courseTitle[]">
<label for="desiredGPA">Desired GPA:</label>
<select class="desiredGPA" name="desiredGPA[]">
<option value="HD">HD (80-100)</option>
<option value="DI">DI (70-79)</option>
<option value="CR">CR (60-69)</option>
<option value="PA">PA (50-59)</option>
</select>
<button type="button" id="addCourse">Add Course</button><br>
<button type="submit">Submit</button>
<button id="findTeammateBtn">Find Teammate Now</button>
<ul id="resultList"></ul> <!-- Add the resultList here -->
</form>
<script src="/socket.io/socket.io.js"></script>
<script src="script.js"></script>
<script>
// Function to handle connecting with a teammate
function connectWithTeammate(studentID) {
// Emit the 'connectWithTeammate' event to the server with the student ID
socket.emit('connectWithTeammate', { studentID });
}
// Add event listener to the result list for connecting with teammates
document.getElementById('resultList').addEventListener('click', function(event) {
if (event.target.classList.contains('connectButton')) {
var studentID = event.target.dataset.studentId;
connectWithTeammate(studentID);
}
});
</script>
This is script.js (client-side):
document.addEventListener("DOMContentLoaded", function() {
const addCourseBtn = document.getElementById("addCourse");
const coursesContainer = document.getElementById("courses");
addCourseBtn.addEventListener("click", function() {
addCourse();
});
function addCourse() {
const courseDiv = document.createElement("div");
courseDiv.classList.add("course");
const courseFields = `
<label for="courseTitle">Course:</label>
<input type="text" class="courseTitle" name="courseTitle[]" required>
<label for="desiredGPA">Desired GPA:</label>
<select class="desiredGPA" name="desiredGPA[]">
</select>
<label for="notes">Notes:</label>
<textarea class="notes" name="notes[]" rows="2" cols="30"></textarea>
<button type="button" class="removeCourse">Remove Course</button>
`;
courseDiv.innerHTML = courseFields;
coursesContainer.appendChild(courseDiv);
const courseTitleInput = courseDiv.querySelector(".courseTitle");
const dropdownMenu = document.createElement("div");
dropdownMenu.classList.add("dropdown-menu");
courseTitleInput.parentNode.insertBefore(dropdownMenu, courseTitleInput.nextSibling);
const removeCourseBtn = courseDiv.querySelector(".removeCourse");
removeCourseBtn.addEventListener("click", function() {
coursesContainer.removeChild(courseDiv);
});
fetch("courses.json")
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then(data => {
const courses = Object.values(data);
courseTitleInput.addEventListener("input", function() {
const userInput = courseTitleInput.value.toLowerCase();
const filteredCourses = courses.filter(course => {
return course &&
course["COURSE CODE"] &&
course["COURSE TITLE"] &&
(course["COURSE CODE"].toLowerCase().includes(userInput) ||
course["COURSE TITLE"].toLowerCase().includes(userInput));
});
renderDropdown(filteredCourses, dropdownMenu, courseTitleInput);
});
courseDiv.style.display = "block";
})
.catch(error => console.error("Error fetching data:", error));
}
function renderDropdown(courses, dropdownMenu, courseTitleInput) {
dropdownMenu.innerHTML = "";
if (courses.length > 0) {
dropdownMenu.style.display = "block";
courses.forEach(course => {
const option = document.createElement("div");
option.classList.add("dropdown-item");
option.textContent = `${course["COURSE CODE"]} - ${course["COURSE TITLE"]} (${course["CAMPUS"]} - ${course["SEMESTER"]})`;
option.addEventListener("click", function() {
if (courseTitleInput) {
courseTitleInput.value = `${course["COURSE CODE"]} - ${course["COURSE TITLE"]} (${course["CAMPUS"]} - ${course["SEMESTER"]})`;
}
if (dropdownMenu) {
dropdownMenu.style.display = "none";
}
});
dropdownMenu.appendChild(option);
});
} else {
dropdownMenu.style.display = "none";
}
}
document.addEventListener("click", function(event) {
if (!event.target.matches(".courseTitle") && !event.target.matches(".dropdown-item")) {
const dropdownMenus = document.querySelectorAll(".dropdown-menu");
dropdownMenus.forEach(dropdownMenu => {
if (dropdownMenu) {
dropdownMenu.style.display = "none";
}
});
}
});
const form = document.getElementById("teamFormationForm");
form.addEventListener("submit", function(event) {
event.preventDefault();
const formData = new FormData(form);
const studentData = {
name: formData.get('name'),
studentID: formData.get('studentID'),
mobile: formData.get('mobile'),
universityEmail: formData.get('universityEmail'),
interests: formData.get('interests'),
overallGPA: parseFloat(formData.get('overallGPA')),
courseTitle: Array.from(formData.getAll('courseTitle')),
courses: []
};
onst courseDivs = document.querySelectorAll('.course');
courseDivs.forEach(courseDiv => {
const courseTitleInput = courseDiv.querySelector('.courseTitle');
const courseTitle = courseTitleInput ? courseTitleInput.value : '';
const desiredGPAInput = courseDiv.querySelector('.desiredGPA');
const desiredGPA = desiredGPAInput ? desiredGPAInput.value : '';
const notesInput = courseDiv.querySelector('.notes');
const notes = notesInput ? notesInput.value : '';
const campusInput = courseDiv.querySelector('.campus');
const campus = campusInput ? campusInput.value : '';
const semesterInput = courseDiv.querySelector('.semester');
const semester = semesterInput ? semesterInput.value : '';
const modifiedCourseTitle = `${courseTitle} (${campus} - ${semester})`;
studentData.courses.push({ courseTitle: modifiedCourseTitle, desiredGPA, notes });
});
fetch("/submit", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(studentData)
})
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.text();
})
.then(data => {
console.log("Form submitted successfully:", data);
})
.catch(error => {
console.error("Error submitting form:", error);
});
});
const findTeammateBtn = document.getElementById("findTeammateBtn");
findTeammateBtn.addEventListener("click", async function(event) {
event.preventDefault();
const courses = Array.from(document.querySelectorAll('.course')).map(courseDiv => {
const courseTitleInput = courseDiv.querySelector('.courseTitle');
const courseTitle = courseTitleInput ? courseTitleInput.value : '';
const desiredGPAInput = courseDiv.querySelector('.desiredGPA');
const desiredGPA = desiredGPAInput ? desiredGPAInput.value : '';
return {
courseTitle,
desiredGPA,
};
});
const formData = new FormData(document.getElementById("teamFormationForm"));
const studentData = {
name: formData.get('name'),
studentID: formData.get('studentID'),
mobile: formData.get('mobile'),
universityEmail: formData.get('universityEmail'),
interests: formData.get('interests'),
overallGPA: parseFloat(formData.get('overallGPA')),
courseTitle: Array.from(formData.getAll('courseTitle')),
courses: []
};
try {
const response = await fetch("/findTeammate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(studentData)
});
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
if (data.message === "No student with similar data found") {
alert(data.message);
} else {
displayMatchingStudents(data);
}
} catch (error) {
console.error("Error finding matching students:", error);
alert("Error finding matching students. Please try again later.");
}
});
function displayMatchingStudents(students) {
const container = document.createElement("div");
container.classList.add("container");
const overlay = document.createElement("div");
overlay.classList.add("overlay");
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
closeButton.classList.add("close-button");
closeButton.addEventListener("click", function() {
document.body.removeChild(overlay);
});
const resultList = document.createElement("ul");
resultList.classList.add("result-list");
if (students.length === 0) {
const listItem = document.createElement("li");
listItem.textContent = "No student with similar data found";
resultList.appendChild(listItem);
} else {
students.forEach(student => {
const listItem = document.createElement("li");
listItem.innerHTML = `
<p><strong>Name:</strong> ${student.name}</p>
<p><strong>Student ID:</strong> ${student.studentID}</p>
<p><strong>University Email:</strong> ${student.universityEmail}</p>
<p><strong>GPA:</strong> ${student.overallGPA}</p>
<p><strong>Courses:</strong></p>
<ul>
${student.courses.map(course => `<li>${course.courseTitle} - Desired GPA: ${course.desiredGPA}</li>`).join("")}
</ul>
`;
const connectButton = document.createElement("button");
connectButton.textContent = "Connect";
connectButton.classList.add("connectButton");
connectButton.dataset.studentId = student.studentID;
connectButton.addEventListener("click", function() {
connectWithTeammate(student.studentID);
});
listItem.appendChild(connectButton);
resultList.appendChild(listItem);
});
}
overlay.appendChild(container);
container.appendChild(closeButton);
container.appendChild(resultList);
document.body.appendChild(overlay);
}
});
let socket;
let currentStudentID;
document.addEventListener("DOMContentLoaded", function() {
socket = io('https://rmitteammatching-84cf87192b0c.herokuapp.com/', { path: '/socket.io' });
// Register the current student's socket with their ID
// This should be called after the student submits their data and the server returns the studentID
function registerStudentSocket(studentID) {
currentStudentID = studentID;
socket.emit('register', { studentID: currentStudentID });
}
// Toggle the display of the invitation box
function toggleInvitationBox(show, fromStudentID = '') {
const invitationBox = document.getElementById("invitation-box");
if (show) {
document.getElementById("invitation-from").textContent = `Student ${fromStudentID} wants to chat with you.`;
document.getElementById("invitation-from").setAttribute("data-from-student-id", fromStudentID);
invitationBox.style.display = 'block';
} else {
invitationBox.style.display = 'none';
}
}
// Function to send a chat invitation
function sendInvitation(toStudentID) {
socket.emit('sendInvitation', { fromStudentID: currentStudentID, toStudentID });
}
// Listen for chat invitations
socket.on('receiveInvitation', ({ fromStudentID }) => {
// Show the invitation box with options to accept or decline
toggleInvitationBox(true, fromStudentID);
});
// Handle the acceptance of a chat invitation
document.getElementById("accept-invitation").addEventListener('click', function() {
const fromStudentID = document.getElementById("invitation-from").getAttribute("data-from-student-id");
socket.emit('respondToInvitation', { fromStudentID, toStudentID: currentStudentID, accepted: true });
toggleInvitationBox(false);
});
// Function to prepare the chat interface
function prepareChatInterface(room) {
let chatBox = document.getElementById(`chat-box-${room}`);
if (!chatBox) {
chatBox = document.createElement("div");
chatBox.id = `chat-box-${room}`;
chatBox.setAttribute('data-room', room);
chatBox.classList.add("chat-box");
const messagesContainer = document.createElement("div");
messagesContainer.classList.add("messages-container");
const inputField = document.createElement("input");
inputField.classList.add("message-input");
inputField.setAttribute("placeholder", "Type your message...");
const sendButton = document.createElement("button");
sendButton.textContent = "Send";
sendButton.classList.add("send-button");
sendButton.addEventListener('click', function() {
sendMessage(room, inputField.value);
inputField.value = ''; // Clear the input field after sending
});
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
closeButton.classList.add("close-chat");
closeButton.addEventListener('click', function() {
chatBox.style.display = "none";
});
chatBox.appendChild(messagesContainer);
chatBox.appendChild(inputField);
chatBox.appendChild(sendButton);
chatBox.appendChild(closeButton);
document.body.appendChild(chatBox);
}
}
// Function to send messages
function sendMessage(room, message) {
if (message.trim() !== '') {
socket.emit('sendMessage', { room, message, studentID: currentStudentID });
}
}
// Listen for accepted invitations and create the chat interface
socket.on('invitationAccepted', ({ room }) => {
prepareChatInterface(room);
});
// Listen for incoming messages
socket.on('receiveMessage', ({ room, studentID, message }) => {
const chatBox = document.getElementById(`chat-box-${room}`);
if (chatBox) {
const messagesContainer = chatBox.querySelector(".messages-container");
const msgDiv = document.createElement("div");
msgDiv.textContent = `Student ${studentID}: ${message}`;
msgDiv.className = studentID === currentStudentID ? 'message-own' : 'message-partner';
messagesContainer.appendChild(msgDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
});
});
And this is server.js:
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const socketIo = require('socket.io');
const app = express();
const PORT = process.env.PORT || 3001;
// MongoDB connection
mongoose.connect('mongodb+srv://ss:[email protected]/RmitTeamFinding', {
useNewUrlParser: true,
useUnifiedTopology: true
});
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.once('open', () => {
console.log('Connected to MongoDB');
});
const studentSchema = new mongoose.Schema({
name: String,
studentID: String,
mobile: String,
universityEmail: String,
interests: String,
overallGPA: Number,
courses: [{
courseTitle: String,
desiredGPA: String,
notes: String
}]
});
const Student = mongoose.model('Student', studentSchema);
// Middleware to parse JSON bodies
app.use(express.json());
// Serve static files from the "public" directory
app.use(express.static(path.join(__dirname, 'public')));
app.post('/submit', async (req, res) => {
try {
const formData = req.body;
const validCourses = formData.courses.filter(course => {
return course.courseTitle.trim() !== '' && course.courseTitle !== ' ( - )';
});
if (validCourses.length === 0) {
return res.status(400).send('No valid courses provided');
}
const existingStudent = await Student.findOne({ studentID: formData.studentID });
if (existingStudent) {
validCourses.forEach(newCourse => {
const courseExists = existingStudent.courses.some(existingCourse => {
return existingCourse.courseTitle === newCourse.courseTitle;
});
if (!courseExists) {
existingStudent.courses.push(newCourse);
}
});
await existingStudent.save();
return res.status(200).send('Student data updated successfully with no duplicate courses!');
} else {
// If student does not exist, create a new student with the courses
const newStudent = new Student({
name: formData.name,
studentID: formData.studentID,
mobile: formData.mobile,
universityEmail: formData.universityEmail,
interests: formData.interests,
overallGPA: formData.overallGPA,
courses: validCourses
});
await newStudent.save();
return res.status(201).send('New student data saved successfully!');
}
} catch (err) {
console.error(err);
return res.status(500).send('Internal server error');
}
});
// Route to find teammates
app.post('/findTeammate', async (req, res) => {
try {
const { name, interests, overallGPA, courses } = req.body;
console.log("Received search criteria:", { name, interests, overallGPA, courses });
// Construct query criteria
const queryCriteria = {
...(interests ? { interests } : {}),
...(name ? { name } : {}),
overallGPA: { $gte: overallGPA - 0.2, $lte: overallGPA + 0.2 },
};
console.log("Constructed query criteria:", queryCriteria);
const matchingStudents = await Student.find(queryCriteria).sort({ overallGPA: -1 });
console.log("matchingStudents ", matchingStudents);
if (matchingStudents.length === 0) {
return res.status(200).json({ message: "No student with similar data found" });
}
// Search Course
let matchNewStudents = [];
for (let index = 0; index < matchingStudents.length; index++) {
const element = matchingStudents[index];
if (element.courses.length) {
const newArrCourse = [...new Set(element.courses)];
const queryCriteriaCourse = {
// Find students with all matching courses
courses: {
$all: newArrCourse.map((course) => ({
$elemMatch: {
courseTitle: new RegExp(course.courseTitle.replace(/[-/\^$*+?.()|[]{}]/g, "\$&"), "i"),
desiredGPA: course.desiredGPA,
},
})),
},
};
const matchingStudentsCourse = await Student.find(queryCriteriaCourse).sort({ overallGPA: -1 });
if (matchingStudentsCourse && matchingStudentsCourse.length) {
matchNewStudents = matchNewStudents.concat(
matchingStudentsCourse.filter(
(student) =>
!matchingStudents.filter((old) => old._id.toString() === student._id.toString()).length &&
!matchNewStudents.filter((old) => old._id.toString() === student._id.toString()).length
)
);
}
}
}
// Return the list of matching students
res.status(200).json([...matchingStudents, ...matchNewStudents]);
} catch (error) {
console.error("Error finding matching students:", error);
res.status(500).json({ message: "Internal server error" });
}
});
// New route to handle connecting with a teammate
app.post('/connect/:studentID', async (req, res) => {
try {
const studentID = req.params.studentID;
const currentStudentID = req.body.studentID;
// Find the current student who wants to connect
const currentUser = await Student.findOne({ studentID: currentStudentID });
if (!currentUser) {
return res.status(404).json({ message: 'Student not found' });
}
// Find a teammate for the current student
const teammate = await Student.findOne({ studentID });
if (!teammate) {
return res.status(404).json({ message: 'Teammate not found' });
}
await establishConnection(currentUser, teammate);
// Send a response indicating successful connection
res.status(200).json({ message: 'Connected with teammate successfully', studentID, teammate });
} catch (error) {
console.error('Error connecting with teammate:', error);
res.status(500).json({ message: 'Internal server error' });
}
});
// Function to find a teammate for the current student
async function findTeammate(currentUser) {
try {
// Define criteria for finding a teammate
const teammateCriteria = {
overallGPA: { $gte: currentUser.overallGPA - 0.2, $lte: currentUser.overallGPA + 0.2 },
studentID: { $ne: currentUser.studentID }
};
const potentialTeammates = await Student.find(teammateCriteria).limit(1).lean();
if (potentialTeammates.length > 0) {
return potentialTeammates[0];
} else {
return null;
}
} catch (error) {
console.error('Error finding teammate:', error);
return null;
}
}
// Function to establish a connection between two students
async function establishConnection(currentUser, teammate) {
try {
// Update the connection status for both side
await Promise.all([
Student.updateOne({ _id: currentUser._id }, { $set: { connectedWith: teammate._id } }),
Student.updateOne({ _id: teammate._id }, { $set: { connectedWith: currentUser._id } })
]);
return true;
} catch (error) {
console.error('Error establishing connection:', error);
return false;
}
}
function connectWithTeammate(teammateStudentID) {
fetch(`/connect/${teammateStudentID}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ studentID: currentStudentID })
})
.then(response => {
if (!response.ok) {
throw new Error('Failed to connect with teammate');
}
return response.json();
})
.then(data => {
console.log('Connected with teammate:', data);
// Handle successful connection, e.g., display a notification
})
.catch(error => {
console.error('Error connecting with teammate:', error);
// Handle error
});
}
// Initialize Socket.IO
const server = require('http').createServer(app);
const io = socketIo(server, {
path: '/socket.io',
cors: {
origin: 'https://rmitteammatching-84cf87192b0c.herokuapp.com',
methods: ['GET', 'POST'],
credentials: true
}
});
// Create a separate namespace for the chat functionality
const chatNsp = io.of('/chat');
// Store chat-related data
const chatRooms = new Map();
const pendingInvitations = new Map();
const studentToSocketMap = new Map();
chatNsp.on('connection', (socket) => {
console.log('A user connected to the chat namespace');
socket.on('register', ({ studentID }) => {
socket.join(studentID);
studentToSocketMap.set(studentID, socket.id);
console.log(`Student ${studentID} joined the chat namespace`);
});
socket.on('sendInvitation', ({ fromStudentID, toStudentID }) => {
const invitationKey = `${fromStudentID}-${toStudentID}`;
pendingInvitations.set(invitationKey, { fromStudentID, toStudentID });
const toSocketId = studentToSocketMap.get(toStudentID);
if (toSocketId) {
chatNsp.to(toSocketId).emit('receiveInvitation', { fromStudentID });
}
});
// Notify sender response
const fromSocketId = studentToSocketMap.get(fromStudentID);
if (fromSocketId) {
if (accepted) {
chatNsp.to(fromSocketId).emit('invitationAccepted', { fromStudentID });
} else {
chatNsp.to(fromSocketId).emit('invitationDeclined', { fromStudentID });
}
}
});
socket.on('sendMessage', ({ room, message, studentID }) => {
chatNsp.to(room).emit('receiveMessage', { studentID, message });
});
socket.on('disconnect', () => {
console.log('User disconnected from the chat namespace');
const studentID = Array.from(studentToSocketMap.entries()).find(([_, socketId]) => socketId === socket.id)?.[0];
if (studentID) {
studentToSocketMap.delete(studentID);
console.log(`Student ${studentID} disconnected`);
}
});
});
server.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});