diff --git a/backend/models/userModel.js b/backend/models/userModel.js index 7d8f97b..c304d81 100644 --- a/backend/models/userModel.js +++ b/backend/models/userModel.js @@ -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; \ No newline at end of file +const User = mongoose.model("User", userSchema); +module.exports = User; diff --git a/backend/server.js b/backend/server.js index f02bb26..11968d9 100644 --- a/backend/server.js +++ b/backend/server.js @@ -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('',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("", 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); + }); + }); }); diff --git a/frontend/src/components/ChatComponents/ChatTitle.js b/frontend/src/components/ChatComponents/ChatTitle.js index 112a8e9..a05637a 100644 --- a/frontend/src/components/ChatComponents/ChatTitle.js +++ b/frontend/src/components/ChatComponents/ChatTitle.js @@ -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 <>; @@ -43,6 +49,8 @@ export default function ChatTitle({ openChatModel }) { user = getSender(data.users); } + const currentStatus = userStatus || user; + return (
@@ -50,10 +58,26 @@ export default function ChatTitle({ openChatModel }) { 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 + } >
-
{user.name}
+
+
+ {user.name} +
+ {!isTyping && + (currentStatus.isOnline ? ( +
+ ) : ( +
+ ))} +
{isTyping && (
{data.isGroupChat ? "Someone" : user.name} is typing... diff --git a/frontend/src/components/RootComponents/UserCard.js b/frontend/src/components/RootComponents/UserCard.js index 98015b5..759b8e3 100644 --- a/frontend/src/components/RootComponents/UserCard.js +++ b/frontend/src/components/RootComponents/UserCard.js @@ -1,50 +1,59 @@ -import React,{useState} from 'react' -import { Avatar } from '@mui/material' -import { useNavigate } from 'react-router-dom' -import { useSelector } from 'react-redux' -import { useEffect} from 'react' -import {NullifyActiveChat} from '../../services/Actions/Chat/action' -import { useDispatch } from 'react-redux' -import { FlustAllChats } from '../../services/Actions/Chat/action' +import React, { useState } from "react"; +import { Avatar } from "@mui/material"; +import { useNavigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useEffect } from "react"; +import { NullifyActiveChat } from "../../services/Actions/Chat/action"; +import { useDispatch } from "react-redux"; +import { socket } from "../../socket/socket"; +import { FlustAllChats } from "../../services/Actions/Chat/action"; export default function UserCard() { - const dispatch=useDispatch(); - const dataredux=useSelector((state)=>state.user.userInfo) + const dispatch = useDispatch(); + const dataredux = useSelector((state) => state.user.userInfo); - const Obj=JSON.parse(localStorage.getItem('info')) - const [Name,setName]=useState(Obj.name); - const [Pic,setPic]=useState(Obj.pic); - - useEffect(()=>{ - if(dataredux===null) - return; + const Obj = JSON.parse(localStorage.getItem("info")); + const [Name, setName] = useState(Obj.name); + const [Pic, setPic] = useState(Obj.pic); + useEffect(() => { + if (dataredux === null) return; setName(dataredux.name); setPic(dataredux.pic); - },[dataredux]) - + }, [dataredux]); - const navigate=useNavigate(); + const navigate = useNavigate(); - const logoutHandler=()=>{ - localStorage.removeItem('jwt'); - navigate('/',{replace:true}); + const logoutHandler = () => { + socket.emit("logout"); + localStorage.removeItem("jwt"); + navigate("/", { replace: true }); dispatch(NullifyActiveChat()); dispatch(FlustAllChats()); - } - - let image=Pic; - if(Pic.startsWith('user')) - image=`${process.env.REACT_APP_API_URL}/${Pic}` + }; + let image = Pic; + if (Pic.startsWith("user")) image = `${process.env.REACT_APP_API_URL}/${Pic}`; return ( -
- -
-
{Name}
-
Logout
+
+ +
+
+ {Name} +
+
+ Logout
+
- ) + ); }