Skip to content

Added Online Presence Indicator to User Profiles #240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 61 additions & 42 deletions backend/models/userModel.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,69 @@
const mongoose=require('mongoose');
const bcrypt=require('bcryptjs');
const axios=require('axios')
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const axios = require("axios");

const userSchema=new mongoose.Schema({
name:{type:String,required:true},
email:{type:String,required:true,unique:true},
password:{type:String,required:true},
pic: {
type:String,
required: true,
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
gender:{
type:String,
}
})
const userSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
pic: {
type: String,
required: true,
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
isAdmin: {
type: Boolean,
required: true,
default: false,
},
isOnline: {
type: Boolean,
default: false,
},
gender: {
type: String,
},
});

userSchema.pre('save',async function(next) {
this.password=await bcrypt.hash(this.password,12);
this.passwordConfirm=undefined
userSchema.pre("save", async function (next) {
this.password = await bcrypt.hash(this.password, 12);
this.passwordConfirm = undefined;

let value=await axios.get(`https://api.genderize.io?name=${this.name.split(' ')[0]}`)
this.gender=value.data.gender;
let value = await axios.get(
`https://api.genderize.io?name=${this.name.split(" ")[0]}`
);
this.gender = value.data.gender;

const menImgList=['https://i.ibb.co/d2qRfFh/9434619.jpg','https://i.ibb.co/GF5K0Zx/9439678.jpg','https://i.ibb.co/LQvJbv1/27470334-7309681.jpg'];
const womenImgList=['https://i.ibb.co/4fSJHhf/27470349-7309670.jpg','https://i.ibb.co/KrCfzc3/27470336-7294793.jpg','https://i.ibb.co/WHQPSRX/7309700.jpg']
const menImgList = [
"https://i.ibb.co/d2qRfFh/9434619.jpg",
"https://i.ibb.co/GF5K0Zx/9439678.jpg",
"https://i.ibb.co/LQvJbv1/27470334-7309681.jpg",
];
const womenImgList = [
"https://i.ibb.co/4fSJHhf/27470349-7309670.jpg",
"https://i.ibb.co/KrCfzc3/27470336-7294793.jpg",
"https://i.ibb.co/WHQPSRX/7309700.jpg",
];

let randomNumber = Math.floor((Math.random() * 3));
if(this.pic==='https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg')
{
this.pic=this.gender==='male'?menImgList[randomNumber]:womenImgList[randomNumber]
}
let randomNumber = Math.floor(Math.random() * 3);
if (
this.pic ===
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg"
) {
this.pic =
this.gender === "male"
? menImgList[randomNumber]
: womenImgList[randomNumber];
}
});

userSchema.methods.correctPassword = async function (
passwordDB,
typedPassword
) {
return await bcrypt.compare(typedPassword, passwordDB);
};

userSchema.methods.correctPassword=async function(passwordDB,typedPassword)
{
return await bcrypt.compare(typedPassword,passwordDB);
}


const User=mongoose.model('User',userSchema);
module.exports=User;
const User = mongoose.model("User", userSchema);
module.exports = User;
149 changes: 83 additions & 66 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,89 +1,106 @@
const express = require('express');
const express = require("express");
// const app = express();
const http = require('http');
const mongoose=require('mongoose');
const dotenv=require('dotenv');
const app=require('./app');
dotenv.config({path:'./config.env'});
const DBAuth=process.env.DB.replace('<password>',process.env.DBpassword);

mongoose.connect(DBAuth,{
useUnifiedTopology: true
}).then((con)=>{
const http = require("http");
const User = require("./models/userModel");
const mongoose = require("mongoose");

const dotenv = require("dotenv");
const app = require("./app");
dotenv.config({ path: "./config.env" });
const DBAuth = process.env.DB.replace("<password>", process.env.DBpassword);

mongoose
.connect(DBAuth, {
useUnifiedTopology: true,
})
.then((con) => {
console.log("DB connection successful");
})

process.on('unhandledRejection',err=>{
console.log(err.name,err.message);
console.log("Unhandled rejection");
})

const port=process.env.PORT;

const server=app.listen(port,()=>{
console.log(`SERVER RUNNING IN PORTNO:${port}`);
})














});

process.on("unhandledRejection", (err) => {
console.log(err.name, err.message);
console.log("Unhandled rejection");
});

const port = process.env.PORT;

const server = app.listen(port, () => {
console.log(`SERVER RUNNING IN PORTNO:${port}`);
});

const io = require('socket.io')(server, {
const io = require("socket.io")(server, {
cors: {
origin: ['https://chat-box-samarthkadam.vercel.app','http://localhost:3000'],
}
origin: [
"https://chat-box-samarthkadam.vercel.app",
"http://localhost:3000",
],
},
});

io.on('connection', (socket) => {
io.on("connection", (socket) => {
socket.on("setup", async (userData) => {
socket.userId = userData._id;
await User.findByIdAndUpdate(socket.userId, {
isOnline: true,
});
socket.join(userData._id);
socket.emit("connected");
io.emit("status update", {
userId: socket.userId,
isOnline: true,
});
});

socket.on("disconnect", async () => {
if (socket.userId) {
await User.findByIdAndUpdate(socket.userId, {
isOnline: false,
});
io.emit("status update", {
userId: socket.userId,
isOnline: false,
});
}
});


socket.on("setup", (userData) => {
console.log("a user connected");
socket.join(userData._id);
socket.emit("connected");
socket.on("logout", async () => {
if (socket.userId) {
await User.findByIdAndUpdate(socket.userId, {
isOnline: false,
});
io.emit("status update", {
userId: socket.userId,
isOnline: false,
});
socket.userId = null;
socket.emit("server processed logout..!");
}
});

socket.on("join chat", (room) => {
socket.join(room);
console.log("User Joined Room: " + room);
});

socket.on("typing",(room)=>{
socket.to(room).emit("typing",room)
})
socket.on("stop typing",(room)=>socket.to(room).emit("stop typing",room));


socket.on("removechatbar-send",(chatId)=>{

console.log('remove chat bar for this id',chatId);
socket.to(chatId).emit("removechatbar-recieve",chatId)
})

socket.on("new message",(newMessageRecieved)=>{
var chat=newMessageRecieved.chat;
socket.on("typing", (room) => {
socket.to(room).emit("typing", room);
});
socket.on("stop typing", (room) => socket.to(room).emit("stop typing", room));

if(!chat.users)return chat.users("users not defined");
socket.on("removechatbar-send", (chatId) => {
console.log("remove chat bar for this id", chatId);
socket.to(chatId).emit("removechatbar-recieve", chatId);
});

chat.users.forEach((user)=>{
if(user._id===newMessageRecieved.sender._id)return;
socket.on("new message", (newMessageRecieved) => {
var chat = newMessageRecieved.chat;

socket.in(user._id).emit('message recieved',newMessageRecieved);
})
})
if (!chat.users) return chat.users("users not defined");

chat.users.forEach((user) => {
if (user._id === newMessageRecieved.sender._id) return;

socket.in(user._id).emit("message recieved", newMessageRecieved);
});
});
});
56 changes: 40 additions & 16 deletions frontend/src/components/ChatComponents/ChatTitle.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,39 @@ import MoreHorizOutlinedIcon from "@mui/icons-material/MoreHorizOutlined";
import { useSelector } from "react-redux";
import { getSender } from "../../helper/Reusable";
import { socket } from "../../socket/socket";
import groupLogo from '../../assets/images/group.png'
import groupLogo from "../../assets/images/group.png";

export default function ChatTitle({ openChatModel }) {
const data = useSelector((state) => state.chat.activeChat);
const [isTyping, setIsTyping] = useState(false);
const [userStatus, setUserStatus] = useState(null);

useEffect(() => {
if (data === null) return;

const setTypeHandler = (room) => {
setIsTyping(data._id === room);
};

const setTypeHandler=(room)=>{
setIsTyping(data._id===room)
}

const unsetTypeHandler=(room)=>{
const unsetTypeHandler = (room) => {
setIsTyping(false);
}

};

socket.on("typing", setTypeHandler);
socket.on("stop typing",unsetTypeHandler);
const statusUpdateHandler = (statusUpdate) => {
if (data && !data.isGroupChat && statusUpdate.userId === user._id) {
setUserStatus(statusUpdate);
}
};

return ()=>{
socket.off("typing",setTypeHandler);
socket.off("stop typing",unsetTypeHandler);
}
socket.on("typing", setTypeHandler);
socket.on("stop typing", unsetTypeHandler);
socket.on("status update", statusUpdateHandler);

return () => {
socket.off("typing", setTypeHandler);
socket.off("stop typing", unsetTypeHandler);
socket.off("status update", statusUpdateHandler);
};
}, [data]);

if (data === null) return <></>;
Expand All @@ -43,17 +49,35 @@ export default function ChatTitle({ openChatModel }) {
user = getSender(data.users);
}

const currentStatus = userStatus || user;

return (
<div className="flex flex-row px-[5%] box-border justify-between w-[100%]">
<div className="flex flex-row">
<Avatar
referrerPolicy="no-referrer"
alt="Group-pic"
sx={{ width: 48, height: 48 }}
src={isGroupChat?groupLogo:(user.pic.startsWith('user')?`${process.env.REACT_APP_API_URL}/${user.pic}`:user.pic)}
src={
isGroupChat
? groupLogo
: user.pic.startsWith("user")
? `${process.env.REACT_APP_API_URL}/${user.pic}`
: user.pic
}
></Avatar>
<div className="flex flex-col ml-3">
<div className="text-xl font-Roboto font-semibold">{user.name}</div>
<div className="flex justify-between items-center">
<div className="text-xs sm:text-sm md:text-base lg:text-lg font-Roboto font-semibold">
{user.name}
</div>
{!isTyping &&
(currentStatus.isOnline ? (
<div className="flex w-2 h-2 bg-green-600 rounded-full ml-3 my-2"></div>
) : (
<div></div>
))}
</div>
{isTyping && (
<div className="text-xs font-normal text-[#30C730]">
{data.isGroupChat ? "Someone" : user.name} is typing...
Expand Down
Loading