├── .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 |
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 |
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 |
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 |
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 |
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 |
185 | )}
186 |
187 |
188 |
189 |
193 | {isLike ? : }
194 |
195 |
201 |
202 |
209 |
210 | {show && (
211 |
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 |
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 |
--------------------------------------------------------------------------------