├── .gitIgnore ├── frontend ├── postcss.config.js ├── tailwind.config.js ├── vite.config.js ├── .gitignore ├── index.html ├── README.md ├── src │ ├── components │ │ ├── chat │ │ │ ├── Message.jsx │ │ │ ├── Chat.jsx │ │ │ ├── MessageInput.jsx │ │ │ └── MessageContainer.jsx │ │ ├── Loading.jsx │ │ ├── SimpleModal.jsx │ │ ├── NotFound.jsx │ │ ├── Modal.jsx │ │ ├── LikeModal.jsx │ │ ├── NavigationBar.jsx │ │ ├── AddPost.jsx │ │ └── PostCard.jsx │ ├── index.css │ ├── pages │ │ ├── Home.jsx │ │ ├── Reels.jsx │ │ ├── Search.jsx │ │ ├── Login.jsx │ │ ├── ChatPage.jsx │ │ ├── Register.jsx │ │ ├── UserAccount.jsx │ │ └── Account.jsx │ ├── main.jsx │ ├── context │ │ ├── ChatContext.jsx │ │ ├── SocketContext.jsx │ │ ├── PostContext.jsx │ │ └── UserContext.jsx │ ├── App.jsx │ └── assets │ │ └── react.svg ├── .eslintrc.cjs ├── package.json └── public │ └── vite.svg ├── backend ├── middlewares │ ├── multer.js │ └── isAuth.js ├── utils │ ├── Trycatch.js │ ├── urlGenrator.js │ └── generateToken.js ├── database │ └── db.js ├── routes │ ├── messageRoutes.js │ ├── authRoutes.js │ ├── userRoutes.js │ └── postRoutes.js ├── models │ ├── ChatModel.js │ ├── Messages.js │ ├── userModel.js │ └── postModel.js ├── socket │ └── socket.js ├── controllers │ ├── messageControllers.js │ ├── authControllers.js │ ├── userControllers.js │ └── postControllers.js └── index.js ├── vercel.json └── package.json /.gitIgnore: -------------------------------------------------------------------------------- 1 | .env 2 | /node_modules -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/middlewares/multer.js: -------------------------------------------------------------------------------- 1 | import multer from "multer"; 2 | 3 | const storage = multer.memoryStorage(); 4 | 5 | const uploadFile = multer({ storage }).single("file"); 6 | 7 | export default uploadFile; 8 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /backend/utils/Trycatch.js: -------------------------------------------------------------------------------- 1 | const TryCatch = (handler) => { 2 | return async (req, res, next) => { 3 | try { 4 | await handler(req, res, next); 5 | } catch (error) { 6 | res.status(500).json({ 7 | message: error.message, 8 | }); 9 | } 10 | }; 11 | }; 12 | 13 | export default TryCatch; 14 | -------------------------------------------------------------------------------- /backend/database/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | export const connectDb = async () => { 4 | try { 5 | await mongoose.connect(process.env.MONGO_URL, { 6 | dbName: "YoutubeMernSocial", 7 | }); 8 | 9 | console.log("Connected To MongoDb"); 10 | } catch (error) { 11 | console.log(error); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /backend/utils/urlGenrator.js: -------------------------------------------------------------------------------- 1 | import DataUriParser from "datauri/parser.js"; 2 | import path from "path"; 3 | 4 | const getDataUrl = (file) => { 5 | const parser = new DataUriParser(); 6 | 7 | const extName = path.extname(file.originalname).toString(); 8 | return parser.format(extName, file.buffer); 9 | }; 10 | 11 | export default getDataUrl; 12 | -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react-swc"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | server: { 8 | proxy: { 9 | "/api": { 10 | target: "http://localhost:7000", 11 | }, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /backend/routes/messageRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { isAuth } from "../middlewares/isAuth.js"; 3 | import { 4 | getAllMessages, 5 | sendMessage, 6 | } from "../controllers/messageControllers.js"; 7 | 8 | const router = express.Router(); 9 | 10 | router.post("/", isAuth, sendMessage); 11 | router.get("/:id", isAuth, getAllMessages); 12 | 13 | export default router; 14 | -------------------------------------------------------------------------------- /backend/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | const generateToken = (id, res) => { 4 | const token = jwt.sign({ id }, process.env.JWT_SEC, { 5 | expiresIn: "15d", 6 | }); 7 | 8 | res.cookie("token", token, { 9 | maxAge: 15 * 24 * 60 * 60 * 1000, 10 | httpOnly: true, 11 | sameSite: "strict", 12 | }); 13 | }; 14 | 15 | export default generateToken; 16 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /backend/models/ChatModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const chatSchema = new mongoose.Schema( 4 | { 5 | users: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], 6 | latestMessage: { 7 | text: String, 8 | sender: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, 9 | }, 10 | }, 11 | { timestamps: true } 12 | ); 13 | 14 | export const Chat = mongoose.model("Chat", chatSchema); 15 | -------------------------------------------------------------------------------- /backend/routes/authRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { 3 | loginUser, 4 | logoutUser, 5 | registerUser, 6 | } from "../controllers/authControllers.js"; 7 | import uploadFile from "../middlewares/multer.js"; 8 | 9 | const router = express.Router(); 10 | 11 | router.post("/register", uploadFile, registerUser); 12 | router.post("/login", loginUser); 13 | router.get("/logout", logoutUser); 14 | 15 | export default router; 16 | -------------------------------------------------------------------------------- /backend/models/Messages.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const messageSchema = new mongoose.Schema( 4 | { 5 | chatId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: "Chat", 8 | }, 9 | sender: { type: mongoose.Schema.Types.ObjectId, ref: "User" }, 10 | text: String, 11 | }, 12 | { 13 | timestamps: true, 14 | } 15 | ); 16 | 17 | export const Messages = mongoose.model("Messages", messageSchema); 18 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /frontend/src/components/chat/Message.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Message = ({ ownMessage, message }) => { 4 | return ( 5 |
6 | 11 | {message} 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default Message; 18 | -------------------------------------------------------------------------------- /frontend/src/components/Loading.jsx: -------------------------------------------------------------------------------- 1 | export const Loading = () => { 2 | return ( 3 |
4 |
5 |
6 | ); 7 | }; 8 | 9 | export const LoadingAnimation = () => { 10 | return ( 11 |
12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .custom-input { 6 | @apply bg-gray-100 rounded-lg px-5 py-2 focus:border border-blue-600 focus:outline-none text-black placeholder:text-gray-600 placeholder:opacity-50 font-semibold md:w-72 lg:w-[340px]; 7 | } 8 | 9 | .auth-btn { 10 | @apply px-24 md:px-[118px] lg:px-[140px] py-2 rounded-md text-white bg-gradient-to-l from-blue-400 to-yellow-400 font-medium m-2 mb-6 hover:from-blue-500 hover:to-yellow-500; 11 | } 12 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "name": "mern-social", 4 | "builds": [ 5 | { 6 | "src": "backend/index.js", 7 | "use": "@vercel/node" 8 | }, 9 | { 10 | "src": "frontend/package.json", 11 | "use": "@vercel/static-build", 12 | "config": { 13 | "distDir": "frontend/dist" 14 | } 15 | } 16 | ], 17 | "routes": [ 18 | { 19 | "src": "/api/(.*)", 20 | "dest": "/backend/index.js" 21 | }, 22 | { 23 | "src": "/(.*)", 24 | "dest": "/frontend/dist/$1" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/components/SimpleModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const SimpleModal = ({ isOpen, onClose, children }) => { 4 | if (!isOpen) return null; 5 | return ( 6 |
7 |
8 |
9 | 12 |
13 |
{children}
14 |
15 |
16 | ); 17 | }; 18 | 19 | export default SimpleModal; 20 | -------------------------------------------------------------------------------- /backend/middlewares/isAuth.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { User } from "../models/userModel.js"; 3 | 4 | export const isAuth = async (req, res, next) => { 5 | try { 6 | const token = req.cookies.token; 7 | 8 | if (!token) return res.status(403).json({ message: "Unauthorized" }); 9 | 10 | const decodedData = jwt.verify(token, process.env.JWT_SEC); 11 | 12 | if (!decodedData) 13 | return res.status(400).json({ 14 | message: "Token Expired", 15 | }); 16 | 17 | req.user = await User.findById(decodedData.id); 18 | 19 | next(); 20 | } catch (error) { 21 | res.status(500).json({ 22 | message: "Please Login", 23 | }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /frontend/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddPost from "../components/AddPost"; 3 | import PostCard from "../components/PostCard"; 4 | import { PostData } from "../context/PostContext"; 5 | import { Loading } from "../components/Loading"; 6 | 7 | const Home = () => { 8 | const { posts, loading } = PostData(); 9 | return ( 10 | <> 11 | {loading ? ( 12 | 13 | ) : ( 14 |
15 | 16 | {posts && posts.length > 0 ? ( 17 | posts.map((e) => ) 18 | ) : ( 19 |

No Post Yet

20 | )} 21 |
22 | )} 23 | 24 | ); 25 | }; 26 | 27 | export default Home; 28 | -------------------------------------------------------------------------------- /backend/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { isAuth } from "../middlewares/isAuth.js"; 3 | import { 4 | followandUnfollowUser, 5 | myProfile, 6 | updatePassword, 7 | updateProfile, 8 | userFollowerandFollowingData, 9 | userProfile, 10 | } from "../controllers/userControllers.js"; 11 | import uploadFile from "../middlewares/multer.js"; 12 | 13 | const router = express.Router(); 14 | 15 | router.get("/me", isAuth, myProfile); 16 | router.get("/:id", isAuth, userProfile); 17 | router.post("/:id", isAuth, updatePassword); 18 | router.put("/:id", isAuth, uploadFile, updateProfile); 19 | router.post("/follow/:id", isAuth, followandUnfollowUser); 20 | router.get("/followdata/:id", isAuth, userFollowerandFollowingData); 21 | 22 | export default router; 23 | -------------------------------------------------------------------------------- /backend/routes/postRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { isAuth } from "../middlewares/isAuth.js"; 3 | import { 4 | commentonPost, 5 | deleteComment, 6 | deletePost, 7 | editCaption, 8 | getAllPosts, 9 | likeUnlikePost, 10 | newPost, 11 | } from "../controllers/postControllers.js"; 12 | import uploadFile from "../middlewares/multer.js"; 13 | 14 | const router = express.Router(); 15 | 16 | router.post("/new", isAuth, uploadFile, newPost); 17 | 18 | router.put("/:id", isAuth, editCaption); 19 | router.delete("/:id", isAuth, deletePost); 20 | 21 | router.get("/all", isAuth, getAllPosts); 22 | 23 | router.post("/like/:id", isAuth, likeUnlikePost); 24 | 25 | router.post("/comment/:id", isAuth, commentonPost); 26 | router.delete("/comment/:id", isAuth, deleteComment); 27 | 28 | export default router; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mern-social", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node backend/index.js", 9 | "dev": "nodemon backend/index.js", 10 | "build": "npm install && cd frontend && npm install && npm run build" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "axios": "^1.7.7", 17 | "bcrypt": "^5.1.1", 18 | "cloudinary": "^2.2.0", 19 | "cookie-parser": "^1.4.6", 20 | "datauri": "^4.1.0", 21 | "dotenv": "^16.4.5", 22 | "express": "^4.19.2", 23 | "jsonwebtoken": "^9.0.2", 24 | "mongoose": "^8.4.1", 25 | "multer": "^1.4.5-lts.1", 26 | "socket.io": "^4.7.5" 27 | }, 28 | "devDependencies": { 29 | "nodemon": "^3.1.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.jsx"; 4 | import "./index.css"; 5 | import { UserContextProvider } from "./context/UserContext.jsx"; 6 | import { PostContextProvider } from "./context/PostContext.jsx"; 7 | import { ChatContextProvider } from "./context/ChatContext.jsx"; 8 | import { SocketContextProvider } from "./context/SocketContext.jsx"; 9 | 10 | ReactDOM.createRoot(document.getElementById("root")).render( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /backend/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | email: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | }, 14 | password: { 15 | type: String, 16 | required: true, 17 | }, 18 | gender: { 19 | type: String, 20 | required: true, 21 | enum: ["male", "female"], 22 | }, 23 | followers: [ 24 | { 25 | type: mongoose.Schema.Types.ObjectId, 26 | ref: "User", 27 | }, 28 | ], 29 | followings: [ 30 | { 31 | type: mongoose.Schema.Types.ObjectId, 32 | ref: "User", 33 | }, 34 | ], 35 | profilePic: { 36 | id: String, 37 | url: String, 38 | }, 39 | }, 40 | { 41 | timestamps: true, 42 | } 43 | ); 44 | 45 | export const User = mongoose.model("User", userSchema); 46 | -------------------------------------------------------------------------------- /frontend/src/components/NotFound.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | 4 | const NotFound = () => { 5 | const navigate = useNavigate(); 6 | return ( 7 |
8 |
9 |
Social Media
10 |
Page not found
11 |
sorry, this page isn't available
12 |
13 |
navigate("/")} 15 | className="bg-gray-600 px-4 py-1 text-white font-medium rounded-lg hover:scale-105 cursor-pointer" 16 | > 17 | Visit Homepage 18 |
19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | export default NotFound; 26 | -------------------------------------------------------------------------------- /frontend/src/context/ChatContext.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { createContext, useContext, useState } from "react"; 3 | import toast from "react-hot-toast"; 4 | 5 | const ChatContext = createContext(); 6 | 7 | export const ChatContextProvider = ({ children }) => { 8 | const [chats, setChats] = useState([]); 9 | const [selectedChat, setSelectedChat] = useState(null); 10 | 11 | async function createChat(id) { 12 | try { 13 | const { data } = await axios.post("/api/messages", { 14 | recieverId: id, 15 | message: "hii", 16 | }); 17 | } catch (error) { 18 | toast.error(error.response.data.message); 19 | console.log(error); 20 | } 21 | } 22 | return ( 23 | 26 | {children} 27 | 28 | ); 29 | }; 30 | 31 | export const ChatData = () => useContext(ChatContext); 32 | -------------------------------------------------------------------------------- /backend/models/postModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const postSchema = new mongoose.Schema({ 4 | caption: String, 5 | 6 | post: { 7 | id: String, 8 | url: String, 9 | }, 10 | 11 | type: { 12 | type: String, 13 | required: true, 14 | }, 15 | 16 | owner: { 17 | type: mongoose.Schema.Types.ObjectId, 18 | ref: "User", 19 | }, 20 | 21 | createdAt: { 22 | type: Date, 23 | default: Date.now, 24 | }, 25 | 26 | likes: [ 27 | { 28 | type: mongoose.Schema.Types.ObjectId, 29 | ref: "User", 30 | }, 31 | ], 32 | 33 | comments: [ 34 | { 35 | user: { 36 | type: mongoose.Schema.Types.ObjectId, 37 | ref: "User", 38 | }, 39 | name: { 40 | type: String, 41 | required: true, 42 | }, 43 | comment: { 44 | type: String, 45 | required: true, 46 | }, 47 | }, 48 | ], 49 | }); 50 | 51 | export const Post = mongoose.model("Post", postSchema); 52 | -------------------------------------------------------------------------------- /backend/socket/socket.js: -------------------------------------------------------------------------------- 1 | import { Server } from "socket.io"; 2 | import http from "http"; 3 | import express from "express"; 4 | 5 | const app = express(); 6 | 7 | const server = http.createServer(app); 8 | 9 | const io = new Server(server, { 10 | cors: { 11 | origin: "*", 12 | methods: ["GET", "POST"], 13 | }, 14 | }); 15 | 16 | export const getReciverSocketId = (reciverId) => { 17 | return userSocketMap[reciverId]; 18 | }; 19 | 20 | const userSocketMap = {}; 21 | 22 | io.on("connection", (socket) => { 23 | console.log("User Connected", socket.id); 24 | 25 | const userId = socket.handshake.query.userId; 26 | 27 | if (userId != "undefined") userSocketMap[userId] = socket.id; 28 | 29 | io.emit("getOnlineUser", Object.keys(userSocketMap)); //[1,2,3,4] 30 | 31 | socket.on("disconnect", () => { 32 | console.log("User disconnected"); 33 | delete userSocketMap[userId]; 34 | io.emit("getOnlineUser", Object.keys(userSocketMap)); 35 | }); 36 | }); 37 | 38 | export { io, server, app }; 39 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "axios": "^1.7.2", 14 | "date-fns": "^3.6.0", 15 | "react": "^18.2.0", 16 | "react-dom": "^18.2.0", 17 | "react-hot-toast": "^2.4.1", 18 | "react-icons": "^5.2.1", 19 | "react-router-dom": "^6.23.1", 20 | "socket.io-client": "^4.7.5" 21 | }, 22 | "devDependencies": { 23 | "@types/react": "^18.2.66", 24 | "@types/react-dom": "^18.2.22", 25 | "@vitejs/plugin-react-swc": "^3.5.0", 26 | "autoprefixer": "^10.4.19", 27 | "eslint": "^8.57.0", 28 | "eslint-plugin-react": "^7.34.1", 29 | "eslint-plugin-react-hooks": "^4.6.0", 30 | "eslint-plugin-react-refresh": "^0.4.6", 31 | "postcss": "^8.4.38", 32 | "tailwindcss": "^3.4.4", 33 | "vite": "^5.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/context/SocketContext.jsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from "react"; 2 | import io from "socket.io-client"; 3 | import { UserData } from "./UserContext"; 4 | 5 | const EndPoint = "https://mern-social-3e3m.onrender.com"; 6 | 7 | const SocketContext = createContext(); 8 | 9 | export const SocketContextProvider = ({ children }) => { 10 | const [socket, setSocket] = useState(null); 11 | const [onlineUsers, setOnlineUsers] = useState([]); 12 | const { user } = UserData(); 13 | useEffect(() => { 14 | const socket = io(EndPoint, { 15 | query: { 16 | userId: user?._id, 17 | }, 18 | }); 19 | 20 | setSocket(socket); 21 | 22 | socket.on("getOnlineUser", (users) => { 23 | setOnlineUsers(users); 24 | }); 25 | 26 | return () => socket && socket.close(); 27 | }, [user?._id]); 28 | return ( 29 | 30 | {children} 31 | 32 | ); 33 | }; 34 | 35 | export const SocketData = () => useContext(SocketContext); 36 | -------------------------------------------------------------------------------- /frontend/src/components/chat/Chat.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { UserData } from "../../context/UserContext"; 3 | import { BsSendCheck } from "react-icons/bs"; 4 | 5 | const Chat = ({ chat, setSelectedChat, isOnline }) => { 6 | const { user: loggedInUser } = UserData(); 7 | let user; 8 | if (chat) user = chat.users[0]; 9 | return ( 10 |
11 | {user && ( 12 |
setSelectedChat(chat)} 15 | > 16 |
17 | {isOnline && ( 18 |
.
19 | )} 20 | 25 | {user.name} 26 |
27 | 28 | 29 | {loggedInUser._id === chat.latestMessage.sender ? ( 30 | 31 | ) : ( 32 | "" 33 | )} 34 | {chat.latestMessage.text.slice(0, 18)}... 35 | 36 |
37 | )} 38 |
39 | ); 40 | }; 41 | 42 | export default Chat; 43 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/Modal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Modal = ({ value, title, setShow }) => { 5 | return ( 6 |
7 |
8 |

{title}

9 |
10 | 16 |
17 |
18 | {value && value.length > 0 ? ( 19 | value.map((e, i) => ( 20 | setShow(false)} 25 | > 26 | {i + 1}{" "} 27 | 32 | {e.name} 33 | 34 | )) 35 | ) : ( 36 |

No {title} yet

37 | )} 38 |
39 |
40 |
41 | ); 42 | }; 43 | 44 | export default Modal; 45 | -------------------------------------------------------------------------------- /frontend/src/components/chat/MessageInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { ChatData } from "../../context/ChatContext"; 3 | import toast from "react-hot-toast"; 4 | import axios from "axios"; 5 | 6 | const MessageInput = ({ setMessages, selectedChat }) => { 7 | const [textMsg, setTextMsg] = useState(""); 8 | const { setChats } = ChatData(); 9 | 10 | const handleMessage = async (e) => { 11 | e.preventDefault(); 12 | try { 13 | const { data } = await axios.post("/api/messages", { 14 | message: textMsg, 15 | recieverId: selectedChat.users[0]._id, 16 | }); 17 | 18 | setMessages((message) => [...message, data]); 19 | setTextMsg(""); 20 | setChats((prev) => { 21 | const updatedChat = prev.map((chat) => { 22 | if (chat._id === selectedChat._id) { 23 | return { 24 | ...chat, 25 | latestMessage: { 26 | text: textMsg, 27 | sender: data.sender, 28 | }, 29 | }; 30 | } 31 | 32 | return chat; 33 | }); 34 | 35 | return updatedChat; 36 | }); 37 | } catch (error) { 38 | console.log(error); 39 | toast.error(error.response.data.message); 40 | } 41 | }; 42 | return ( 43 |
44 |
45 | setTextMsg(e.target.value)} 51 | required 52 | /> 53 | 56 |
57 |
58 | ); 59 | }; 60 | 61 | export default MessageInput; 62 | -------------------------------------------------------------------------------- /backend/controllers/messageControllers.js: -------------------------------------------------------------------------------- 1 | import { Chat } from "../models/ChatModel.js"; 2 | import { Messages } from "../models/Messages.js"; 3 | import { getReciverSocketId, io } from "../socket/socket.js"; 4 | import TryCatch from "../utils/Trycatch.js"; 5 | 6 | export const sendMessage = TryCatch(async (req, res) => { 7 | const { recieverId, message } = req.body; 8 | 9 | const senderId = req.user._id; 10 | 11 | if (!recieverId) 12 | return res.status(400).json({ 13 | message: "Please give reciever id", 14 | }); 15 | 16 | let chat = await Chat.findOne({ 17 | users: { $all: [senderId, recieverId] }, 18 | }); 19 | 20 | if (!chat) { 21 | chat = new Chat({ 22 | users: [senderId, recieverId], 23 | latestMessage: { 24 | text: message, 25 | sender: senderId, 26 | }, 27 | }); 28 | 29 | await chat.save(); 30 | } 31 | 32 | const newMessage = new Messages({ 33 | chatId: chat._id, 34 | sender: senderId, 35 | text: message, 36 | }); 37 | 38 | await newMessage.save(); 39 | 40 | await chat.updateOne({ 41 | latestMessage: { 42 | text: message, 43 | sender: senderId, 44 | }, 45 | }); 46 | 47 | const reciverSocketId = getReciverSocketId(recieverId); 48 | 49 | if (reciverSocketId) { 50 | io.to(reciverSocketId).emit("newMessage", newMessage); 51 | } 52 | 53 | res.status(201).json(newMessage); 54 | }); 55 | 56 | export const getAllMessages = TryCatch(async (req, res) => { 57 | const { id } = req.params; 58 | const userId = req.user._id; 59 | 60 | const chat = await Chat.findOne({ 61 | users: { $all: [userId, id] }, 62 | }); 63 | 64 | if (!chat) 65 | return res.status(404).json({ 66 | message: "No Chat with these users", 67 | }); 68 | 69 | const messages = await Messages.find({ 70 | chatId: chat._id, 71 | }); 72 | 73 | res.json(messages); 74 | }); 75 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 3 | import Home from "./pages/Home"; 4 | import Login from "./pages/Login"; 5 | import Register from "./pages/Register"; 6 | import { UserData } from "./context/UserContext"; 7 | import Account from "./pages/Account"; 8 | import NavigationBar from "./components/NavigationBar"; 9 | import NotFound from "./components/NotFound"; 10 | import Reels from "./pages/Reels"; 11 | import { Loading } from "./components/Loading"; 12 | import UserAccount from "./pages/UserAccount"; 13 | import Search from "./pages/Search"; 14 | import ChatPage from "./pages/ChatPage"; 15 | 16 | const App = () => { 17 | const { loading, isAuth, user } = UserData(); 18 | 19 | return ( 20 | <> 21 | {loading ? ( 22 | 23 | ) : ( 24 | 25 | 26 | : } /> 27 | : } /> 28 | : } 31 | /> 32 | : } 35 | /> 36 | : } /> 37 | : } 40 | /> 41 | } /> 42 | : } 45 | /> 46 | : } /> 47 | : } 50 | /> 51 | 52 | {isAuth && } 53 | 54 | )} 55 | 56 | ); 57 | }; 58 | 59 | export default App; 60 | -------------------------------------------------------------------------------- /backend/controllers/authControllers.js: -------------------------------------------------------------------------------- 1 | import { User } from "../models/userModel.js"; 2 | import TryCatch from "../utils/Trycatch.js"; 3 | import generateToken from "../utils/generateToken.js"; 4 | import getDataUrl from "../utils/urlGenrator.js"; 5 | import bcrypt from "bcrypt"; 6 | import cloudinary from "cloudinary"; 7 | 8 | export const registerUser = TryCatch(async (req, res) => { 9 | const { name, email, password, gender } = req.body; 10 | 11 | const file = req.file; 12 | 13 | if (!name || !email || !password || !gender || !file) { 14 | return res.status(400).json({ 15 | message: "Please give all values", 16 | }); 17 | } 18 | 19 | let user = await User.findOne({ email }); 20 | 21 | if (user) 22 | return res.status(400).json({ 23 | message: "User Already Exist", 24 | }); 25 | 26 | const fileUrl = getDataUrl(file); 27 | 28 | const hashPassword = await bcrypt.hash(password, 10); 29 | 30 | const myCloud = await cloudinary.v2.uploader.upload(fileUrl.content); 31 | 32 | user = await User.create({ 33 | name, 34 | email, 35 | password: hashPassword, 36 | gender, 37 | profilePic: { 38 | id: myCloud.public_id, 39 | url: myCloud.secure_url, 40 | }, 41 | }); 42 | 43 | generateToken(user._id, res); 44 | 45 | res.status(201).json({ 46 | message: "User Registered", 47 | user, 48 | }); 49 | }); 50 | 51 | export const loginUser = TryCatch(async (req, res) => { 52 | const { email, password } = req.body; 53 | 54 | const user = await User.findOne({ email }); 55 | 56 | if (!user) 57 | return res.status(400).json({ 58 | message: "Invalid Credentials", 59 | }); 60 | 61 | const comparePassword = await bcrypt.compare(password, user.password); 62 | 63 | if (!comparePassword) 64 | return res.status(400).json({ 65 | message: "Invalid Credentials", 66 | }); 67 | 68 | generateToken(user._id, res); 69 | 70 | res.json({ 71 | message: "User Logged in", 72 | user, 73 | }); 74 | }); 75 | 76 | export const logoutUser = TryCatch((req, res) => { 77 | res.cookie("token", "", { maxAge: 0 }); 78 | 79 | res.json({ 80 | message: "Logged out successfully", 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /frontend/src/components/LikeModal.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import React, { useEffect, useState } from "react"; 3 | import { LoadingAnimation } from "./Loading"; 4 | 5 | const LikeModal = ({ isOpen, onClose, id }) => { 6 | if (!isOpen) return null; 7 | const [value, setValue] = useState([]); 8 | 9 | const [loading, setLoading] = true; 10 | 11 | async function fetchLikes() { 12 | try { 13 | const { data } = await axios.get("/api/post/" + id); 14 | 15 | setValue(data); 16 | setLoading(false); 17 | } catch (error) { 18 | console.log(error); 19 | setLoading(false); 20 | } 21 | } 22 | 23 | useEffect(() => { 24 | fetchLikes(); 25 | }, [id]); 26 | return ( 27 | <> 28 | {loading ? ( 29 | 30 | ) : ( 31 |
32 |
33 |
34 | 37 |
38 |
39 | {value && value.length > 0 ? ( 40 | value.map((e, i) => ( 41 | setShow(false)} 46 | > 47 | {i + 1}{" "} 48 | 53 | {e.name} 54 | 55 | )) 56 | ) : ( 57 |

No Likes yet

58 | )} 59 |
60 |
61 |
62 | )} 63 | 64 | ); 65 | }; 66 | 67 | export default LikeModal; 68 | -------------------------------------------------------------------------------- /frontend/src/pages/Reels.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import AddPost from "../components/AddPost"; 3 | import { PostData } from "../context/PostContext"; 4 | import PostCard from "../components/PostCard"; 5 | import { FaArrowUp, FaArrowDownLong } from "react-icons/fa6"; 6 | import { Loading } from "../components/Loading"; 7 | 8 | const Reels = () => { 9 | const { reels, loading } = PostData(); 10 | const [index, setIndex] = useState(0); 11 | 12 | const prevReel = () => { 13 | if (index === 0) { 14 | console.log("null"); 15 | return null; 16 | } 17 | setIndex(index - 1); 18 | }; 19 | const nextReel = () => { 20 | if (index === reels.length - 1) { 21 | console.log("null"); 22 | return null; 23 | } 24 | setIndex(index + 1); 25 | }; 26 | return ( 27 | <> 28 | {loading ? ( 29 | 30 | ) : ( 31 |
32 | 33 |
34 | {reels && reels.length > 0 ? ( 35 | 40 | ) : ( 41 |

No reels yet

42 | )} 43 |
44 | {index === 0 ? ( 45 | "" 46 | ) : ( 47 | 53 | )} 54 | {index === reels.length - 1 ? ( 55 | "" 56 | ) : ( 57 | 63 | )} 64 |
65 |
66 |
67 | )} 68 | 69 | ); 70 | }; 71 | 72 | export default Reels; 73 | -------------------------------------------------------------------------------- /frontend/src/pages/Search.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import React, { useState } from "react"; 3 | import { Link } from "react-router-dom"; 4 | import { LoadingAnimation } from "../components/Loading"; 5 | 6 | const Search = () => { 7 | const [users, setUsers] = useState([]); 8 | const [search, setSearch] = useState(""); 9 | const [loading, setLoading] = useState(false); 10 | async function fetchUsers() { 11 | setLoading(true); 12 | try { 13 | const { data } = await axios.get("/api/user/all?search=" + search); 14 | 15 | setUsers(data); 16 | setLoading(false); 17 | } catch (error) { 18 | console.log(error); 19 | setLoading(false); 20 | } 21 | } 22 | return ( 23 |
24 |
25 |
26 | setSearch(e.target.value)} 33 | /> 34 | 40 |
41 | {loading ? ( 42 | 43 | ) : ( 44 | <> 45 | {users && users.length > 0 ? ( 46 | users.map((e) => ( 47 | 52 | {" "} 57 | {e.name} 58 | 59 | )) 60 | ) : ( 61 |

No User please Search

62 | )} 63 | 64 | )} 65 |
66 |
67 | ); 68 | }; 69 | 70 | export default Search; 71 | -------------------------------------------------------------------------------- /frontend/src/components/NavigationBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import { AiOutlineHome, AiFillHome } from "react-icons/ai"; 4 | import { BsCameraReelsFill, BsCameraReels } from "react-icons/bs"; 5 | import { IoSearchCircleOutline, IoSearchCircle } from "react-icons/io5"; 6 | import { 7 | IoChatbubbleEllipses, 8 | IoChatbubbleEllipsesOutline, 9 | } from "react-icons/io5"; 10 | import { RiAccountCircleFill, RiAccountCircleLine } from "react-icons/ri"; 11 | 12 | const NavigationBar = () => { 13 | const [tab, setTab] = useState(window.location.pathname); 14 | return ( 15 |
16 |
17 | setTab("/")} 20 | className="flex flex-col items-center text-2xl" 21 | > 22 | {tab === "/" ? : } 23 | 24 | setTab("/reels")} 27 | className="flex flex-col items-center text-2xl" 28 | > 29 | 30 | {tab === "/reels" ? : } 31 | 32 | 33 | setTab("/search")} 35 | to={"/search"} 36 | className="flex flex-col items-center text-2xl" 37 | > 38 | 39 | {tab === "/search" ? : } 40 | 41 | 42 | setTab("/chat")} 44 | to={"/chat"} 45 | className="flex flex-col items-center text-2xl" 46 | > 47 | 48 | {tab === "/chat" ? ( 49 | 50 | ) : ( 51 | 52 | )} 53 | 54 | 55 | setTab("/account")} 57 | to={"/account"} 58 | className="flex flex-col items-center text-2xl" 59 | > 60 | 61 | {tab === "/account" ? ( 62 | 63 | ) : ( 64 | 65 | )} 66 | 67 | 68 |
69 |
70 | ); 71 | }; 72 | 73 | export default NavigationBar; 74 | -------------------------------------------------------------------------------- /frontend/src/components/AddPost.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { PostData } from "../context/PostContext"; 3 | import { LoadingAnimation } from "./Loading"; 4 | 5 | const AddPost = ({ type }) => { 6 | const [caption, setCaption] = useState(""); 7 | const [file, setFile] = useState(""); 8 | const [filePrev, setFilePrev] = useState(""); 9 | 10 | const { addPost, addLoading } = PostData(); 11 | 12 | const changeFileHandler = (e) => { 13 | const file = e.target.files[0]; 14 | const reader = new FileReader(); 15 | 16 | reader.readAsDataURL(file); 17 | 18 | reader.onloadend = () => { 19 | setFilePrev(reader.result); 20 | setFile(file); 21 | }; 22 | }; 23 | 24 | const submitHandler = (e) => { 25 | e.preventDefault(); 26 | const formdata = new FormData(); 27 | 28 | formdata.append("caption", caption); 29 | formdata.append("file", file); 30 | addPost(formdata, setFile, setCaption, setFilePrev, type); 31 | }; 32 | return ( 33 |
34 |
35 |
39 | setCaption(e.target.value)} 45 | /> 46 | 53 | {filePrev && ( 54 | <> 55 | {type === "post" ? ( 56 | 57 | ) : ( 58 |
75 |
76 | ); 77 | }; 78 | 79 | export default AddPost; 80 | -------------------------------------------------------------------------------- /frontend/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link, useNavigate } from "react-router-dom"; 3 | import { UserData } from "../context/UserContext"; 4 | import { PostData } from "../context/PostContext"; 5 | 6 | const Login = () => { 7 | const [email, setEmail] = useState(""); 8 | const [password, setPassword] = useState(""); 9 | const navigate = useNavigate(); 10 | 11 | const { loginUser, loading } = UserData(); 12 | const { fetchPosts } = PostData(); 13 | 14 | const submitHandler = (e) => { 15 | e.preventDefault(); 16 | loginUser(email, password, navigate, fetchPosts); 17 | }; 18 | return ( 19 | <> 20 | {loading ? ( 21 |

Loading...

22 | ) : ( 23 |
24 |
25 |
26 |
27 |

28 | Login to social media 29 |

30 |
31 | 32 |
33 |
34 | setEmail(e.target.value)} 40 | required 41 | /> 42 | setPassword(e.target.value)} 48 | required 49 | /> 50 |
51 |
52 | 53 |
54 |
55 |
56 | 57 |
58 |
59 |

Don't Have Account?

60 |

Register to Social Media

61 | 65 | Register 66 | 67 |
68 |
69 |
70 |
71 | )} 72 | 73 | ); 74 | }; 75 | 76 | export default Login; 77 | -------------------------------------------------------------------------------- /frontend/src/context/PostContext.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { createContext, useContext, useEffect, useState } from "react"; 3 | import toast from "react-hot-toast"; 4 | 5 | const PostContext = createContext(); 6 | 7 | export const PostContextProvider = ({ children }) => { 8 | const [posts, setPosts] = useState([]); 9 | const [reels, setReels] = useState([]); 10 | const [loading, setLoading] = useState(true); 11 | 12 | async function fetchPosts() { 13 | try { 14 | const { data } = await axios.get("/api/post/all"); 15 | 16 | setPosts(data.posts); 17 | setReels(data.reels); 18 | setLoading(false); 19 | } catch (error) { 20 | console.log(error); 21 | setLoading(false); 22 | } 23 | } 24 | 25 | const [addLoading, setAddLoading] = useState(false); 26 | 27 | async function addPost(formdata, setFile, setFilePrev, setCaption, type) { 28 | setAddLoading(true); 29 | try { 30 | const { data } = await axios.post("/api/post/new?type=" + type, formdata); 31 | 32 | toast.success(data.message); 33 | fetchPosts(); 34 | setFile(""); 35 | setFilePrev(""); 36 | setCaption(""); 37 | setAddLoading(false); 38 | } catch (error) { 39 | toast.error(error.response.data.message); 40 | setAddLoading(false); 41 | } 42 | } 43 | 44 | async function likePost(id) { 45 | try { 46 | const { data } = await axios.post("/api/post/like/" + id); 47 | 48 | toast.success(data.message); 49 | fetchPosts(); 50 | } catch (error) { 51 | toast.error(error.response.data.message); 52 | } 53 | } 54 | 55 | async function addComment(id, comment, setComment, setShow) { 56 | try { 57 | const { data } = await axios.post("/api/post/comment/" + id, { 58 | comment, 59 | }); 60 | toast.success(data.message); 61 | fetchPosts(); 62 | setComment(""); 63 | setShow(false); 64 | } catch (error) { 65 | toast.error(error.response.data.message); 66 | } 67 | } 68 | 69 | async function deletePost(id) { 70 | setLoading(true); 71 | try { 72 | const { data } = await axios.delete("/api/post/" + id); 73 | 74 | toast.success(data.message); 75 | fetchPosts(); 76 | setLoading(false); 77 | } catch (error) { 78 | toast.error(error.response.data.message); 79 | setLoading(false); 80 | } 81 | } 82 | 83 | async function deleteComment(id, commentId) { 84 | try { 85 | const { data } = await axios.delete( 86 | `/api/post/comment/${id}?commentId=${commentId}` 87 | ); 88 | 89 | toast.success(data.message); 90 | fetchPosts(); 91 | } catch (error) { 92 | toast.error(error.response.data.message); 93 | } 94 | } 95 | 96 | useEffect(() => { 97 | fetchPosts(); 98 | }, []); 99 | return ( 100 | 114 | {children} 115 | 116 | ); 117 | }; 118 | 119 | export const PostData = () => useContext(PostContext); 120 | -------------------------------------------------------------------------------- /backend/index.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | import { connectDb } from "./database/db.js"; 4 | import cloudinary from "cloudinary"; 5 | import cookieParser from "cookie-parser"; 6 | import { Chat } from "./models/ChatModel.js"; 7 | import { isAuth } from "./middlewares/isAuth.js"; 8 | import { User } from "./models/userModel.js"; 9 | import { app, server } from "./socket/socket.js"; 10 | import path from "path"; 11 | import axios from 'axios'; 12 | 13 | const url = `https://mern-social-3e3m.onrender.com`; 14 | const interval = 30000; 15 | 16 | function reloadWebsite() { 17 | axios 18 | .get(url) 19 | .then((response) => { 20 | console.log( 21 | `Reloaded at ${new Date().toISOString()}: Status Code ${ 22 | response.status 23 | }` 24 | ); 25 | }) 26 | .catch((error) => { 27 | console.error( 28 | `Error reloading at ${new Date().toISOString()}:`, 29 | error.message 30 | ); 31 | }); 32 | } 33 | 34 | setInterval(reloadWebsite, interval); 35 | 36 | dotenv.config(); 37 | 38 | cloudinary.v2.config({ 39 | cloud_name: process.env.Cloudinary_Cloud_Name, 40 | api_key: process.env.Cloudinary_Api, 41 | api_secret: process.env.Cloudinary_Secret, 42 | }); 43 | 44 | //using middlewares 45 | app.use(express.json()); 46 | app.use(cookieParser()); 47 | 48 | const port = process.env.PORT; 49 | 50 | // to get all chats of user 51 | app.get("/api/messages/chats", isAuth, async (req, res) => { 52 | try { 53 | const chats = await Chat.find({ 54 | users: req.user._id, 55 | }).populate({ 56 | path: "users", 57 | select: "name profilePic", 58 | }); 59 | 60 | chats.forEach((e) => { 61 | e.users = e.users.filter( 62 | (user) => user._id.toString() !== req.user._id.toString() 63 | ); 64 | }); 65 | 66 | res.json(chats); 67 | } catch (error) { 68 | res.status(500).json({ 69 | message: error.message, 70 | }); 71 | } 72 | }); 73 | 74 | // to get all users 75 | app.get("/api/user/all", isAuth, async (req, res) => { 76 | try { 77 | const search = req.query.search || ""; 78 | const users = await User.find({ 79 | name: { 80 | $regex: search, 81 | $options: "i", 82 | }, 83 | _id: { $ne: req.user._id }, 84 | }).select("-password"); 85 | 86 | res.json(users); 87 | } catch (error) { 88 | res.status(500).json({ 89 | message: error.message, 90 | }); 91 | } 92 | }); 93 | 94 | // importing routes 95 | import userRoutes from "./routes/userRoutes.js"; 96 | import authRoutes from "./routes/authRoutes.js"; 97 | import postRoutes from "./routes/postRoutes.js"; 98 | import messageRoutes from "./routes/messageRoutes.js"; 99 | 100 | //using routes 101 | app.use("/api/auth", authRoutes); 102 | app.use("/api/user", userRoutes); 103 | app.use("/api/post", postRoutes); 104 | app.use("/api/messages", messageRoutes); 105 | 106 | const __dirname = path.resolve(); 107 | 108 | app.use(express.static(path.join(__dirname, "/frontend/dist"))); 109 | 110 | app.get("*", (req, res) => { 111 | res.sendFile(path.join(__dirname, "frontend", "dist", "index.html")); 112 | }); 113 | 114 | server.listen(port, () => { 115 | console.log(`Server is running on http://localhost:${port}`); 116 | connectDb(); 117 | }); 118 | -------------------------------------------------------------------------------- /frontend/src/components/chat/MessageContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import { UserData } from "../../context/UserContext"; 3 | import axios from "axios"; 4 | import { LoadingAnimation } from "../Loading"; 5 | import Message from "./Message"; 6 | import MessageInput from "./MessageInput"; 7 | import { SocketData } from "../../context/SocketContext"; 8 | 9 | const MessageContainer = ({ selectedChat, setChats }) => { 10 | const [messages, setMessages] = useState([]); 11 | const { user } = UserData(); 12 | const [loading, setLoading] = useState(false); 13 | const { socket } = SocketData(); 14 | 15 | useEffect(() => { 16 | socket.on("newMessage", (message) => { 17 | if (selectedChat._id === message.chatId) { 18 | setMessages((prev) => [...prev, message]); 19 | } 20 | 21 | setChats((prev) => { 22 | const updatedChat = prev.map((chat) => { 23 | if (chat._id === message.chatId) { 24 | return { 25 | ...chat, 26 | latestMessage: { 27 | text: message.text, 28 | sender: message.sender, 29 | }, 30 | }; 31 | } 32 | return chat; 33 | }); 34 | return updatedChat; 35 | }); 36 | }); 37 | 38 | return () => socket.off("newMessage"); 39 | }, [socket, selectedChat, setChats]); 40 | 41 | async function fetchMessages() { 42 | setLoading(true); 43 | try { 44 | const { data } = await axios.get( 45 | "/api/messages/" + selectedChat.users[0]._id 46 | ); 47 | 48 | setMessages(data); 49 | setLoading(false); 50 | } catch (error) { 51 | console.log(error); 52 | setLoading(false); 53 | } 54 | } 55 | 56 | console.log(messages); 57 | 58 | useEffect(() => { 59 | fetchMessages(); 60 | }, [selectedChat]); 61 | 62 | const messageContainerRef = useRef(null); 63 | 64 | useEffect(() => { 65 | if (messageContainerRef.current) { 66 | messageContainerRef.current.scrollTop = 67 | messageContainerRef.current.scrollHeight; 68 | } 69 | }, [messages]); 70 | return ( 71 |
72 | {selectedChat && ( 73 |
74 |
75 | 80 | {selectedChat.users[0].name} 81 |
82 | {loading ? ( 83 | 84 | ) : ( 85 | <> 86 |
90 | {messages && 91 | messages.map((e) => ( 92 | 96 | ))} 97 |
98 | 99 | 103 | 104 | )} 105 |
106 | )} 107 |
108 | ); 109 | }; 110 | 111 | export default MessageContainer; 112 | -------------------------------------------------------------------------------- /backend/controllers/userControllers.js: -------------------------------------------------------------------------------- 1 | import TryCatch from "../utils/Trycatch.js"; 2 | import { User } from "../models/userModel.js"; 3 | import getDataUrl from "../utils/urlGenrator.js"; 4 | import cloudinary from "cloudinary"; 5 | import bcrypt from "bcrypt"; 6 | 7 | export const myProfile = TryCatch(async (req, res) => { 8 | const user = await User.findById(req.user._id).select("-password"); 9 | 10 | res.json(user); 11 | }); 12 | 13 | export const userProfile = TryCatch(async (req, res) => { 14 | const user = await User.findById(req.params.id).select("-password"); 15 | 16 | if (!user) 17 | return res.status(404).json({ 18 | message: "No User with is id", 19 | }); 20 | 21 | res.json(user); 22 | }); 23 | 24 | export const followandUnfollowUser = TryCatch(async (req, res) => { 25 | const user = await User.findById(req.params.id); 26 | const loggedInUser = await User.findById(req.user._id); 27 | 28 | if (!user) 29 | return res.status(404).json({ 30 | message: "No User with is id", 31 | }); 32 | 33 | if (user._id.toString() === loggedInUser._id.toString()) 34 | return res.status(400).json({ 35 | message: "You can't follow yourself", 36 | }); 37 | 38 | if (user.followers.includes(loggedInUser._id)) { 39 | const indexFollowing = loggedInUser.followings.indexOf(user._id); 40 | const indexFollower = user.followers.indexOf(loggedInUser._id); 41 | 42 | loggedInUser.followings.splice(indexFollowing, 1); 43 | user.followers.splice(indexFollower, 1); 44 | 45 | await loggedInUser.save(); 46 | await user.save(); 47 | 48 | res.json({ 49 | message: "User Unfollowed", 50 | }); 51 | } else { 52 | loggedInUser.followings.push(user._id); 53 | user.followers.push(loggedInUser._id); 54 | 55 | await loggedInUser.save(); 56 | await user.save(); 57 | 58 | res.json({ 59 | message: "User Followed", 60 | }); 61 | } 62 | }); 63 | 64 | export const userFollowerandFollowingData = TryCatch(async (req, res) => { 65 | const user = await User.findById(req.params.id) 66 | .select("-password") 67 | .populate("followers", "-password") 68 | .populate("followings", "-password"); 69 | 70 | const followers = user.followers; 71 | const followings = user.followings; 72 | 73 | res.json({ 74 | followers, 75 | followings, 76 | }); 77 | }); 78 | 79 | export const updateProfile = TryCatch(async (req, res) => { 80 | const user = await User.findById(req.user._id); 81 | 82 | const { name } = req.body; 83 | 84 | if (name) { 85 | user.name = name; 86 | } 87 | 88 | const file = req.file; 89 | if (file) { 90 | const fileUrl = getDataUrl(file); 91 | 92 | await cloudinary.v2.uploader.destroy(user.profilePic.id); 93 | 94 | const myCloud = await cloudinary.v2.uploader.upload(fileUrl.content); 95 | 96 | user.profilePic.id = myCloud.public_id; 97 | user.profilePic.url = myCloud.secure_url; 98 | } 99 | 100 | await user.save(); 101 | 102 | res.json({ 103 | message: "Profile updated", 104 | }); 105 | }); 106 | 107 | export const updatePassword = TryCatch(async (req, res) => { 108 | const user = await User.findById(req.user._id); 109 | 110 | const { oldPassword, newPassword } = req.body; 111 | 112 | const comparePassword = await bcrypt.compare(oldPassword, user.password); 113 | 114 | if (!comparePassword) 115 | return res.status(400).json({ 116 | message: "Wrong old password", 117 | }); 118 | 119 | user.password = await bcrypt.hash(newPassword, 10); 120 | 121 | await user.save(); 122 | 123 | res.json({ 124 | message: "Password Updated", 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /frontend/src/context/UserContext.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { createContext, useContext, useEffect, useState } from "react"; 3 | import toast, { Toaster } from "react-hot-toast"; 4 | 5 | const UserContext = createContext(); 6 | 7 | export const UserContextProvider = ({ children }) => { 8 | const [user, setUser] = useState([]); 9 | const [isAuth, setIsAuth] = useState(false); 10 | const [loading, setLoading] = useState(true); 11 | 12 | async function registerUser(formdata, navigate, fetchPosts) { 13 | setLoading(true); 14 | try { 15 | const { data } = await axios.post("/api/auth/register", formdata); 16 | 17 | toast.success(data.message); 18 | setIsAuth(true); 19 | setUser(data.user); 20 | navigate("/"); 21 | setLoading(false); 22 | fetchPosts(); 23 | } catch (error) { 24 | toast.error(error.response.data.message); 25 | setLoading(false); 26 | } 27 | } 28 | 29 | async function loginUser(email, password, navigate, fetchPosts) { 30 | setLoading(true); 31 | try { 32 | const { data } = await axios.post("/api/auth/login", { 33 | email, 34 | password, 35 | navigate, 36 | }); 37 | 38 | toast.success(data.message); 39 | setIsAuth(true); 40 | setUser(data.user); 41 | navigate("/"); 42 | setLoading(false); 43 | fetchPosts(); 44 | } catch (error) { 45 | toast.error(error.response.data.message); 46 | setLoading(false); 47 | } 48 | } 49 | 50 | async function fetchUser() { 51 | try { 52 | const { data } = await axios.get("/api/user/me"); 53 | 54 | setUser(data); 55 | setIsAuth(true); 56 | setLoading(false); 57 | } catch (error) { 58 | console.log(error); 59 | setIsAuth(false); 60 | setLoading(false); 61 | } 62 | } 63 | 64 | async function logoutUser(navigate) { 65 | try { 66 | const { data } = await axios.get("/api/auth/logout"); 67 | 68 | if (data.message) { 69 | toast.success(data.message); 70 | setUser([]); 71 | setIsAuth(false); 72 | navigate("/login"); 73 | } 74 | } catch (error) { 75 | toast.error(error.response.data.message); 76 | } 77 | } 78 | 79 | async function followUser(id, fetchUser) { 80 | try { 81 | const { data } = await axios.post("/api/user/follow/" + id); 82 | 83 | toast.success(data.message); 84 | fetchUser(); 85 | } catch (error) { 86 | toast.error(error.response.data.message); 87 | } 88 | } 89 | 90 | async function updateProfilePic(id, formdata, setFile) { 91 | try { 92 | const { data } = await axios.put("/api/user/" + id, formdata); 93 | toast.success(data.message); 94 | fetchUser(); 95 | setFile(null); 96 | } catch (error) { 97 | toast.error(error.response.data.message); 98 | } 99 | } 100 | async function updateProfileName(id, name, setShowInput) { 101 | try { 102 | const { data } = await axios.put("/api/user/" + id, { name }); 103 | toast.success(data.message); 104 | fetchUser(); 105 | setShowInput(false); 106 | } catch (error) { 107 | toast.error(error.response.data.message); 108 | } 109 | } 110 | 111 | useEffect(() => { 112 | fetchUser(); 113 | }, []); 114 | return ( 115 | 130 | {children} 131 | 132 | 133 | ); 134 | }; 135 | 136 | export const UserData = () => useContext(UserContext); 137 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/pages/ChatPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { ChatData } from "../context/ChatContext"; 3 | import axios from "axios"; 4 | import { FaSearch } from "react-icons/fa"; 5 | import Chat from "../components/chat/Chat"; 6 | import MessageContainer from "../components/chat/MessageContainer"; 7 | import { SocketData } from "../context/SocketContext"; 8 | 9 | const ChatPage = ({ user }) => { 10 | const { createChat, selectedChat, setSelectedChat, chats, setChats } = 11 | ChatData(); 12 | 13 | const [users, setUsers] = useState([]); 14 | const [query, setQuery] = useState(""); 15 | const [search, setSearch] = useState(false); 16 | 17 | async function fetchAllUsers() { 18 | try { 19 | const { data } = await axios.get("/api/user/all?search=" + query); 20 | 21 | setUsers(data); 22 | } catch (error) { 23 | console.log(error); 24 | } 25 | } 26 | 27 | const getAllChats = async () => { 28 | try { 29 | const { data } = await axios.get("/api/messages/chats"); 30 | setChats(data); 31 | } catch (error) { 32 | console.log(error); 33 | } 34 | }; 35 | 36 | useEffect(() => { 37 | fetchAllUsers(); 38 | }, [query]); 39 | 40 | useEffect(() => { 41 | getAllChats(); 42 | }, []); 43 | 44 | async function createNewChat(id) { 45 | await createChat(id); 46 | setSearch(false); 47 | getAllChats(); 48 | } 49 | 50 | const { onlineUsers, socket } = SocketData(); 51 | return ( 52 |
53 |
54 |
55 |
56 | 62 | {search ? ( 63 | <> 64 | setQuery(e.target.value)} 71 | /> 72 | 73 |
74 | {users && users.length > 0 ? ( 75 | users.map((e) => ( 76 |
createNewChat(e._id)} 79 | className="bg-gray-500 text-white p-2 mt-2 cursor-pointer flex justify-center items-center gap-2" 80 | > 81 | 86 | {e.name} 87 |
88 | )) 89 | ) : ( 90 |

No Users

91 | )} 92 |
93 | 94 | ) : ( 95 |
96 | {chats.map((e) => ( 97 | 103 | ))} 104 |
105 | )} 106 |
107 |
108 | {selectedChat === null ? ( 109 |
110 | Hello 👋 {user.name} select a chat to start conversation 111 |
112 | ) : ( 113 |
114 | 115 |
116 | )} 117 |
118 |
119 | ); 120 | }; 121 | 122 | export default ChatPage; 123 | -------------------------------------------------------------------------------- /backend/controllers/postControllers.js: -------------------------------------------------------------------------------- 1 | import { Post } from "../models/postModel.js"; 2 | import TryCatch from "../utils/Trycatch.js"; 3 | import getDataUrl from "../utils/urlGenrator.js"; 4 | import cloudinary from "cloudinary"; 5 | 6 | export const newPost = TryCatch(async (req, res) => { 7 | const { caption } = req.body; 8 | 9 | const ownerId = req.user._id; 10 | 11 | const file = req.file; 12 | const fileUrl = getDataUrl(file); 13 | 14 | let option; 15 | 16 | const type = req.query.type; 17 | if (type === "reel") { 18 | option = { 19 | resource_type: "video", 20 | }; 21 | } else { 22 | option = {}; 23 | } 24 | 25 | const myCloud = await cloudinary.v2.uploader.upload(fileUrl.content, option); 26 | 27 | const post = await Post.create({ 28 | caption, 29 | post: { 30 | id: myCloud.public_id, 31 | url: myCloud.secure_url, 32 | }, 33 | owner: ownerId, 34 | type, 35 | }); 36 | 37 | res.status(201).json({ 38 | message: "Post created", 39 | post, 40 | }); 41 | }); 42 | 43 | export const deletePost = TryCatch(async (req, res) => { 44 | const post = await Post.findById(req.params.id); 45 | 46 | if (!post) 47 | return res.status(404).json({ 48 | message: "No post with this id", 49 | }); 50 | 51 | if (post.owner.toString() !== req.user._id.toString()) 52 | return res.status(403).json({ 53 | message: "Unauthorized", 54 | }); 55 | 56 | await cloudinary.v2.uploader.destroy(post.post.id); 57 | 58 | await post.deleteOne(); 59 | 60 | res.json({ 61 | message: "Post Deleted", 62 | }); 63 | }); 64 | 65 | export const getAllPosts = TryCatch(async (req, res) => { 66 | const posts = await Post.find({ type: "post" }) 67 | .sort({ createdAt: -1 }) 68 | .populate("owner", "-password") 69 | .populate({ 70 | path: "comments.user", 71 | select: "-password", 72 | }); 73 | 74 | const reels = await Post.find({ type: "reel" }) 75 | .sort({ createdAt: -1 }) 76 | .populate("owner", "-password") 77 | .populate({ 78 | path: "comments.user", 79 | select: "-password", 80 | }); 81 | 82 | res.json({ posts, reels }); 83 | }); 84 | 85 | export const likeUnlikePost = TryCatch(async (req, res) => { 86 | const post = await Post.findById(req.params.id); 87 | 88 | if (!post) 89 | return res.status(404).json({ 90 | message: "No Post with this id", 91 | }); 92 | 93 | if (post.likes.includes(req.user._id)) { 94 | const index = post.likes.indexOf(req.user._id); 95 | 96 | post.likes.splice(index, 1); 97 | 98 | await post.save(); 99 | 100 | res.json({ 101 | message: "Post Unlike", 102 | }); 103 | } else { 104 | post.likes.push(req.user._id); 105 | 106 | await post.save(); 107 | 108 | res.json({ 109 | message: "Post liked", 110 | }); 111 | } 112 | }); 113 | 114 | export const commentonPost = TryCatch(async (req, res) => { 115 | const post = await Post.findById(req.params.id); 116 | 117 | if (!post) 118 | return res.status(404).json({ 119 | message: "No Post with this id", 120 | }); 121 | 122 | post.comments.push({ 123 | user: req.user._id, 124 | name: req.user.name, 125 | comment: req.body.comment, 126 | }); 127 | 128 | await post.save(); 129 | 130 | res.json({ 131 | message: "Comment Added", 132 | }); 133 | }); 134 | 135 | export const deleteComment = TryCatch(async (req, res) => { 136 | const post = await Post.findById(req.params.id); 137 | 138 | if (!post) 139 | return res.status(404).json({ 140 | message: "No Post with this id", 141 | }); 142 | 143 | if (!req.query.commentId) 144 | return res.status(404).json({ 145 | message: "Please give comment id", 146 | }); 147 | 148 | const commentIndex = post.comments.findIndex( 149 | (item) => item._id.toString() === req.query.commentId.toString() 150 | ); 151 | 152 | if (commentIndex === -1) { 153 | return res.status(400).json({ 154 | message: "Comment not found", 155 | }); 156 | } 157 | 158 | const comment = post.comments[commentIndex]; 159 | 160 | if ( 161 | post.owner.toString() === req.user._id.toString() || 162 | comment.user.toString() === req.user._id.toString() 163 | ) { 164 | post.comments.splice(commentIndex, 1); 165 | 166 | await post.save(); 167 | 168 | return res.json({ 169 | message: "Comment deleted", 170 | }); 171 | } else { 172 | return res.status(400).json({ 173 | message: "Yor are not allowed to delete this comment", 174 | }); 175 | } 176 | }); 177 | 178 | export const editCaption = TryCatch(async (req, res) => { 179 | const post = await Post.findById(req.params.id); 180 | 181 | if (!post) 182 | return res.status(404).json({ 183 | message: "No Post with this id", 184 | }); 185 | 186 | if (post.owner.toString() !== req.user._id.toString()) 187 | return res.status(403).json({ 188 | message: "You are not owner of this post", 189 | }); 190 | 191 | post.caption = req.body.caption; 192 | 193 | await post.save(); 194 | 195 | res.json({ 196 | message: "post updated", 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /frontend/src/pages/Register.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link, useNavigate } from "react-router-dom"; 3 | import { UserData } from "../context/UserContext"; 4 | import { PostData } from "../context/PostContext"; 5 | 6 | const Register = () => { 7 | const [name, setName] = useState(""); 8 | const [email, setEmail] = useState(""); 9 | const [password, setPassword] = useState(""); 10 | const [gender, setGender] = useState(""); 11 | const [file, setFile] = useState(""); 12 | const [filePrev, setFilePrev] = useState(""); 13 | 14 | const { registerUser, loading } = UserData(); 15 | 16 | const { fetchPosts } = PostData(); 17 | 18 | const changeFileHandler = (e) => { 19 | const file = e.target.files[0]; 20 | const reader = new FileReader(); 21 | 22 | reader.readAsDataURL(file); 23 | 24 | reader.onloadend = () => { 25 | setFilePrev(reader.result); 26 | setFile(file); 27 | }; 28 | }; 29 | 30 | const navigate = useNavigate(); 31 | 32 | const submitHandler = (e) => { 33 | e.preventDefault(); 34 | const formdata = new FormData(); 35 | 36 | formdata.append("name", name); 37 | formdata.append("email", email); 38 | formdata.append("password", password); 39 | formdata.append("gender", gender); 40 | formdata.append("file", file); 41 | 42 | registerUser(formdata, navigate, fetchPosts); 43 | }; 44 | return ( 45 | <> 46 | {loading ? ( 47 |

Loading....

48 | ) : ( 49 |
50 |
51 |
52 |
53 |

54 | Register to social media 55 |

56 |
57 | 58 |
59 |
60 | {filePrev && ( 61 | 66 | )} 67 | 74 | setName(e.target.value)} 80 | required 81 | /> 82 | setEmail(e.target.value)} 88 | required 89 | /> 90 | setPassword(e.target.value)} 96 | required 97 | /> 98 | 108 |
109 |
110 | 111 |
112 |
113 |
114 | 115 |
116 |
117 |

Have Account?

118 |

Login to Social Media

119 | 123 | Login 124 | 125 |
126 |
127 |
128 |
129 | )} 130 | 131 | ); 132 | }; 133 | 134 | export default Register; 135 | -------------------------------------------------------------------------------- /frontend/src/pages/UserAccount.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useNavigate, useParams } from "react-router-dom"; 3 | import { PostData } from "../context/PostContext"; 4 | import PostCard from "../components/PostCard"; 5 | import { FaArrowDownLong, FaArrowUp } from "react-icons/fa6"; 6 | import axios from "axios"; 7 | import { Loading } from "../components/Loading"; 8 | import { UserData } from "../context/UserContext"; 9 | import Modal from "../components/Modal"; 10 | import { SocketData } from "../context/SocketContext"; 11 | 12 | const UserAccount = ({ user: loggedInUser }) => { 13 | const navigate = useNavigate(); 14 | 15 | const { posts, reels } = PostData(); 16 | 17 | const [user, setUser] = useState([]); 18 | 19 | const params = useParams(); 20 | 21 | const [loading, setLoading] = useState(true); 22 | 23 | async function fetchUser() { 24 | try { 25 | const { data } = await axios.get("/api/user/" + params.id); 26 | 27 | setUser(data); 28 | setLoading(false); 29 | } catch (error) { 30 | console.log(error); 31 | setLoading(false); 32 | } 33 | } 34 | 35 | console.log(user); 36 | 37 | useEffect(() => { 38 | fetchUser(); 39 | }, [params.id]); 40 | 41 | let myPosts; 42 | 43 | if (posts) { 44 | myPosts = posts.filter((post) => post.owner._id === user._id); 45 | } 46 | let myReels; 47 | 48 | if (reels) { 49 | myReels = reels.filter((reel) => reel.owner._id === user._id); 50 | } 51 | 52 | const [type, setType] = useState("post"); 53 | 54 | const [index, setIndex] = useState(0); 55 | 56 | const prevReel = () => { 57 | if (index === 0) { 58 | console.log("null"); 59 | return null; 60 | } 61 | setIndex(index - 1); 62 | }; 63 | const nextReel = () => { 64 | if (index === myReels.length - 1) { 65 | console.log("null"); 66 | return null; 67 | } 68 | setIndex(index + 1); 69 | }; 70 | 71 | const [followed, setFollowed] = useState(false); 72 | 73 | const { followUser } = UserData(); 74 | 75 | const followHandler = () => { 76 | setFollowed(!followed); 77 | followUser(user._id, fetchUser); 78 | }; 79 | 80 | const followers = user.followers; 81 | 82 | useEffect(() => { 83 | if (followers && followers.includes(loggedInUser._id)) setFollowed(true); 84 | }, [user]); 85 | 86 | const [show, setShow] = useState(false); 87 | const [show1, setShow1] = useState(false); 88 | 89 | const [followersData, setFollowersData] = useState([]); 90 | const [followingsData, setFollowingsData] = useState([]); 91 | 92 | async function followData() { 93 | try { 94 | const { data } = await axios.get("/api/user/followdata/" + user._id); 95 | 96 | setFollowersData(data.followers); 97 | setFollowingsData(data.followings); 98 | } catch (error) { 99 | console.log(error); 100 | } 101 | } 102 | 103 | useEffect(() => { 104 | followData(); 105 | }, [user]); 106 | 107 | const { onlineUsers } = SocketData(); 108 | return ( 109 | <> 110 | {loading ? ( 111 | 112 | ) : ( 113 | <> 114 | {user && ( 115 | <> 116 |
117 | {show && ( 118 | 123 | )} 124 | {show1 && ( 125 | 130 | )} 131 |
132 |
133 | 138 |
139 | 140 |
141 |

142 | {user.name}{" "} 143 | {onlineUsers.includes(user._id) && ( 144 |

145 | 146 | Online 147 | 148 |
149 | )} 150 |

151 |

{user.email}

152 |

{user.gender}

153 |

setShow(true)} 156 | > 157 | {user.followers.length} follower 158 |

159 |

setShow1(true)} 162 | > 163 | {user.followings.length} following 164 |

165 | 166 | {user._id === loggedInUser._id ? ( 167 | "" 168 | ) : ( 169 | 177 | )} 178 |
179 |
180 | 181 |
182 | 183 | 184 |
185 | 186 | {type === "post" && ( 187 | <> 188 | {myPosts && myPosts.length > 0 ? ( 189 | myPosts.map((e) => ( 190 | 191 | )) 192 | ) : ( 193 |

No Post Yet

194 | )} 195 | 196 | )} 197 | {type === "reel" && ( 198 | <> 199 | {myReels && myReels.length > 0 ? ( 200 |
201 | 206 |
207 | {index === 0 ? ( 208 | "" 209 | ) : ( 210 | 216 | )} 217 | {index === myReels.length - 1 ? ( 218 | "" 219 | ) : ( 220 | 226 | )} 227 |
228 |
229 | ) : ( 230 |

No Reels Yet

231 | )} 232 | 233 | )} 234 |
235 | 236 | )} 237 | 238 | )} 239 | 240 | ); 241 | }; 242 | 243 | export default UserAccount; 244 | -------------------------------------------------------------------------------- /frontend/src/components/PostCard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { BsChatFill, BsThreeDotsVertical } from "react-icons/bs"; 3 | import { IoHeartOutline, IoHeartSharp } from "react-icons/io5"; 4 | import { UserData } from "../context/UserContext"; 5 | import { PostData } from "../context/PostContext"; 6 | import { format } from "date-fns"; 7 | import { Link } from "react-router-dom"; 8 | import { MdDelete } from "react-icons/md"; 9 | import SimpleModal from "./SimpleModal"; 10 | import { LoadingAnimation } from "./Loading"; 11 | import toast from "react-hot-toast"; 12 | import axios from "axios"; 13 | import LikeModal from "./LikeModal"; 14 | import { SocketData } from "../context/SocketContext"; 15 | 16 | const PostCard = ({ type, value }) => { 17 | const [isLike, setIsLike] = useState(false); 18 | const [show, setShow] = useState(false); 19 | const { user } = UserData(); 20 | const { likePost, addComment, deletePost, loading, fetchPosts } = PostData(); 21 | 22 | const formatDate = format(new Date(value.createdAt), "MMMM do"); 23 | 24 | useEffect(() => { 25 | for (let i = 0; i < value.likes.length; i++) { 26 | if (value.likes[i] === user._id) setIsLike(true); 27 | } 28 | }, [value, user._id]); 29 | 30 | const likeHandler = () => { 31 | setIsLike(!isLike); 32 | 33 | likePost(value._id); 34 | }; 35 | 36 | const [comment, setComment] = useState(""); 37 | 38 | const addCommentHandler = (e) => { 39 | e.preventDefault(); 40 | addComment(value._id, comment, setComment, setShow); 41 | }; 42 | 43 | const [showModal, setShowModal] = useState(false); 44 | 45 | const closeModal = () => { 46 | setShowModal(false); 47 | }; 48 | 49 | const deleteHandler = () => { 50 | deletePost(value._id); 51 | }; 52 | 53 | const [showInput, setShowInput] = useState(false); 54 | const editHandler = () => { 55 | setShowModal(false); 56 | setShowInput(true); 57 | }; 58 | 59 | const [caption, setCaption] = useState(value.caption ? value.caption : ""); 60 | const [captionLoading, setCaptionLoading] = useState(false); 61 | 62 | async function updateCaption() { 63 | setCaptionLoading(true); 64 | try { 65 | const { data } = await axios.put("/api/post/" + value._id, { caption }); 66 | 67 | toast.success(data.message); 68 | fetchPosts(); 69 | setShowInput(false); 70 | setCaptionLoading(false); 71 | } catch (error) { 72 | toast.error(error.response.data.message); 73 | setCaptionLoading(false); 74 | } 75 | } 76 | 77 | const [open, setOpen] = useState(false); 78 | 79 | const oncloseLIke = () => { 80 | setOpen(false); 81 | }; 82 | 83 | const { onlineUsers } = SocketData(); 84 | 85 | return ( 86 |
87 | 88 | 89 |
90 | 96 | 103 |
104 |
105 |
106 |
107 | 111 | 116 | 117 | {onlineUsers.includes(value.owner._id) && ( 118 |
.
119 | )} 120 | 121 |
122 |

{value.owner.name}

123 |
{formatDate}
124 |
125 | 126 | 127 | {value.owner._id === user._id && ( 128 |
129 | 135 |
136 | )} 137 |
138 | 139 |
140 | {showInput ? ( 141 | <> 142 | setCaption(e.target.value)} 149 | required 150 | /> 151 | 158 | 164 | 165 | ) : ( 166 |

{value.caption}

167 | )} 168 |
169 | 170 |
171 | {type === "post" ? ( 172 | 177 | ) : ( 178 |
187 |
188 |
189 | 193 | {isLike ? : } 194 | 195 | 201 |
202 | 209 |
210 | {show && ( 211 |
212 | setComment(e.target.value)} 218 | /> 219 | 222 |
223 | )} 224 | 225 |
226 |

Comments

227 |
228 |
229 |
230 | {value.comments && value.comments.length > 0 ? ( 231 | value.comments.map((e) => ( 232 | 239 | )) 240 | ) : ( 241 |

No Comments

242 | )} 243 |
244 |
245 |
246 |
247 | ); 248 | }; 249 | 250 | export default PostCard; 251 | 252 | export const Comment = ({ value, user, owner, id }) => { 253 | const { deleteComment } = PostData(); 254 | 255 | const deleteCommentHandler = () => { 256 | deleteComment(id, value._id); 257 | }; 258 | return ( 259 |
260 | 261 | 266 | 267 |
268 |

{value.user.name}

269 |

{value.comment}

270 |
271 | 272 | {owner === user._id ? ( 273 | "" 274 | ) : ( 275 | <> 276 | {value.user._id === user._id && ( 277 | 280 | )} 281 | 282 | )} 283 | 284 | {owner === user._id && ( 285 | 288 | )} 289 |
290 | ); 291 | }; 292 | -------------------------------------------------------------------------------- /frontend/src/pages/Account.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { UserData } from "../context/UserContext"; 4 | import { PostData } from "../context/PostContext"; 5 | import PostCard from "../components/PostCard"; 6 | import { FaArrowDownLong, FaArrowUp } from "react-icons/fa6"; 7 | import Modal from "../components/Modal"; 8 | import axios from "axios"; 9 | import { Loading } from "../components/Loading"; 10 | import { CiEdit } from "react-icons/ci"; 11 | import toast from "react-hot-toast"; 12 | 13 | const Account = ({ user }) => { 14 | const navigate = useNavigate(); 15 | 16 | const { logoutUser, updateProfilePic, updateProfileName } = UserData(); 17 | 18 | const { posts, reels, loading } = PostData(); 19 | 20 | let myPosts; 21 | 22 | if (posts) { 23 | myPosts = posts.filter((post) => post.owner._id === user._id); 24 | } 25 | let myReels; 26 | 27 | if (reels) { 28 | myReels = reels.filter((reel) => reel.owner._id === user._id); 29 | } 30 | 31 | const [type, setType] = useState("post"); 32 | 33 | const logoutHandler = () => { 34 | logoutUser(navigate); 35 | }; 36 | 37 | const [index, setIndex] = useState(0); 38 | 39 | const prevReel = () => { 40 | if (index === 0) { 41 | console.log("null"); 42 | return null; 43 | } 44 | setIndex(index - 1); 45 | }; 46 | const nextReel = () => { 47 | if (index === myReels.length - 1) { 48 | console.log("null"); 49 | return null; 50 | } 51 | setIndex(index + 1); 52 | }; 53 | 54 | const [show, setShow] = useState(false); 55 | const [show1, setShow1] = useState(false); 56 | 57 | const [followersData, setFollowersData] = useState([]); 58 | const [followingsData, setFollowingsData] = useState([]); 59 | 60 | async function followData() { 61 | try { 62 | const { data } = await axios.get("/api/user/followdata/" + user._id); 63 | 64 | setFollowersData(data.followers); 65 | setFollowingsData(data.followings); 66 | } catch (error) { 67 | console.log(error); 68 | } 69 | } 70 | 71 | const [file, setFile] = useState(""); 72 | 73 | const changeFileHandler = (e) => { 74 | const file = e.target.files[0]; 75 | setFile(file); 76 | }; 77 | 78 | const changleImageHandler = () => { 79 | const formdata = new FormData(); 80 | 81 | formdata.append("file", file); 82 | 83 | updateProfilePic(user._id, formdata, setFile); 84 | }; 85 | 86 | useEffect(() => { 87 | followData(); 88 | }, [user]); 89 | 90 | const [showInput, setShowInput] = useState(false); 91 | const [name, setName] = useState(user.name ? user.name : ""); 92 | 93 | const UpdateName = () => { 94 | updateProfileName(user._id, name, setShowInput); 95 | }; 96 | 97 | const [showUpdatePass, setShowUpdatePass] = useState(false); 98 | const [oldPassword, setOldPassword] = useState(""); 99 | const [newPassword, setNewPassword] = useState(""); 100 | 101 | async function updatePassword(e) { 102 | e.preventDefault(); 103 | try { 104 | const { data } = await axios.post("/api/user/" + user._id, { 105 | oldPassword, 106 | newPassword, 107 | }); 108 | 109 | toast.success(data.message); 110 | setOldPassword(""); 111 | setNewPassword(""); 112 | setShowUpdatePass(false); 113 | } catch (error) { 114 | toast.error(error.response.data.message); 115 | } 116 | } 117 | return ( 118 | <> 119 | {loading ? ( 120 | 121 | ) : ( 122 | <> 123 | {user && ( 124 | <> 125 | {loading ? ( 126 | 127 | ) : ( 128 |
129 | {show && ( 130 | 135 | )} 136 | {show1 && ( 137 | 142 | )} 143 |
144 |
145 | 150 |
151 | 156 | 162 |
163 |
164 | 165 |
166 | {showInput ? ( 167 | <> 168 |
169 | setName(e.target.value)} 174 | placeholder="Enter Name" 175 | required 176 | /> 177 | 178 | 184 |
185 | 186 | ) : ( 187 |

188 | {user.name}{" "} 189 | 192 |

193 | )} 194 |

{user.email}

195 |

{user.gender}

196 |

setShow(true)} 199 | > 200 | {user.followers.length} follower 201 |

202 |

setShow1(true)} 205 | > 206 | {user.followings.length} following 207 |

208 | 214 |
215 |
216 | 217 | 223 | 224 | {showUpdatePass && ( 225 |
229 | setOldPassword(e.target.value)} 235 | required 236 | /> 237 | setNewPassword(e.target.value)} 243 | required 244 | /> 245 | 251 |
252 | )} 253 | 254 |
255 | 256 | 257 |
258 | 259 | {type === "post" && ( 260 | <> 261 | {myPosts && myPosts.length > 0 ? ( 262 | myPosts.map((e) => ( 263 | 264 | )) 265 | ) : ( 266 |

No Post Yet

267 | )} 268 | 269 | )} 270 | {type === "reel" && ( 271 | <> 272 | {myReels && myReels.length > 0 ? ( 273 |
274 | 279 |
280 | {index === 0 ? ( 281 | "" 282 | ) : ( 283 | 289 | )} 290 | {index === myReels.length - 1 ? ( 291 | "" 292 | ) : ( 293 | 299 | )} 300 |
301 |
302 | ) : ( 303 |

No Reels Yet

304 | )} 305 | 306 | )} 307 |
308 | )} 309 | 310 | )} 311 | 312 | )} 313 | 314 | ); 315 | }; 316 | 317 | export default Account; 318 | --------------------------------------------------------------------------------