├── public ├── _redirects └── index.html ├── src ├── hooks │ ├── index.js │ └── useOutsideClick.jsx ├── main-container │ ├── index.js │ └── MainContainer.jsx ├── utils │ ├── index.js │ └── helpers.js ├── index.css ├── asset │ ├── socialbuzz.png │ ├── index.js │ └── icons │ │ └── metarialIcons.js ├── components │ ├── shared │ │ ├── Mockman.jsx │ │ ├── Navbar.jsx │ │ └── Sidebar.jsx │ ├── authentication │ │ ├── PrivateRoute.jsx │ │ ├── InputFeild.jsx │ │ ├── formValidation.js │ │ ├── LoginForm.jsx │ │ └── SignupForm.jsx │ ├── shimmer-effect │ │ ├── ShimmerUserCard .jsx │ │ ├── ShimmerUserProfile .jsx │ │ └── ShimmerPostList.jsx │ ├── Bookmark │ │ └── Bookmark.jsx │ ├── liked-posts │ │ └── LikedPosts.jsx │ ├── index.js │ ├── FollowButton │ │ └── FollowButton.jsx │ ├── explore │ │ └── Explore.jsx │ ├── home │ │ ├── Home.jsx │ │ ├── PostList.jsx │ │ ├── CommentItem.jsx │ │ ├── Comment.jsx │ │ ├── PostForm.jsx │ │ ├── PostModal.jsx │ │ ├── EditPostModel.jsx │ │ └── Post.jsx │ ├── UserProfile │ │ └── UserProfile.jsx │ ├── Profile │ │ └── Profile.jsx │ ├── UserDetails │ │ └── UserDetails.jsx │ ├── suggetion&search │ │ ├── FollowUserBar.jsx │ │ └── SearchBar.jsx │ └── ProfileModal │ │ └── ProfileModal.jsx ├── store │ └── store.js ├── slices │ ├── themeSlice.js │ ├── index.js │ ├── authSlice.js │ ├── userSlice.js │ └── postsSlice.js ├── services │ ├── index.js │ ├── authServices.js │ ├── commentServices.js │ ├── postsServices.js │ └── userServices.js ├── index.js ├── backend │ ├── utils │ │ └── authUtils.js │ ├── controllers │ │ ├── AuthController.js │ │ ├── PostController.js │ │ ├── CommentsController.js │ │ └── UserController.js │ └── db │ │ ├── users.js │ │ └── posts.js ├── App.css ├── App.js └── server.js ├── jsconfig.json ├── .gitignore ├── tailwind.config.js ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { useOutsideClick } from "./useOutsideClick"; 2 | -------------------------------------------------------------------------------- /src/main-container/index.js: -------------------------------------------------------------------------------- 1 | export { MainContainer } from "./MainContainer"; 2 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export { getSortedPosts, sortOptions } from "./helpers"; 2 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /src/asset/socialbuzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chhakuli123/SocialBuzz/HEAD/src/asset/socialbuzz.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "exclude": ["node_modules", "build"], 6 | "include": ["src"] 7 | } -------------------------------------------------------------------------------- /src/components/shared/Mockman.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Mockman from "mockman-js" 3 | 4 | function MockAPI() { 5 | return ( 6 |
7 | 8 |
9 | ); 10 | } 11 | 12 | export { MockAPI }; -------------------------------------------------------------------------------- /src/store/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { authReducer, postReducer, themeReducer, userReducer } from "slices"; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | auth: authReducer, 7 | user: userReducer, 8 | post: postReducer, 9 | theme: themeReducer, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /src/components/authentication/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import { Outlet, Navigate, useLocation } from "react-router-dom"; 3 | 4 | export const PrivateRoute = () => { 5 | const { token } = useSelector((state) => state.auth); 6 | const location = useLocation(); 7 | 8 | return token ? ( 9 | 10 | ) : ( 11 | 12 | ); 13 | }; -------------------------------------------------------------------------------- /src/slices/themeSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | theme: "light", 5 | }; 6 | 7 | const themeSlice = createSlice({ 8 | name: "theme", 9 | initialState, 10 | reducers: { 11 | toggleTheme: (state) => { 12 | state.theme = state.theme === "light" ? "dark" : "light"; 13 | }, 14 | }, 15 | }); 16 | 17 | export const { toggleTheme } = themeSlice.actions; 18 | export const themeReducer = themeSlice.reducer; 19 | -------------------------------------------------------------------------------- /src/hooks/useOutsideClick.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | export const useOutsideClick = (handler) => { 4 | let domNode = useRef(); 5 | let handleOutsideClick = (e) => { 6 | if (domNode && !domNode?.current?.contains(e.target)) handler(); 7 | }; 8 | useEffect(() => { 9 | window.addEventListener("mousedown", handleOutsideClick); 10 | return () => { 11 | window.removeEventListener("mousedown", handleOutsideClick); 12 | }; 13 | }); 14 | return domNode; 15 | }; 16 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | export { loginService, signUpService } from "./authServices"; 2 | export { 3 | getAllUsers, 4 | editUserData, 5 | getUserByUsername, 6 | getBookmarks, 7 | bookmarkPost, 8 | unBookmarkPost, 9 | followUser, 10 | unFollowUser, 11 | } from "./userServices"; 12 | export { 13 | getAllPostsFromServer, 14 | addPost, 15 | deletePost, 16 | editPost, 17 | likePost, 18 | dislikePost, 19 | getPostsByUserName, 20 | } from "./postsServices"; 21 | export { addComment, editComment, deleteComment } from "./commentServices"; 22 | -------------------------------------------------------------------------------- /src/services/authServices.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const loginService = (userDetails) => 4 | axios.post("/api/auth/login", { 5 | username: userDetails.username, 6 | password: userDetails.password, 7 | }); 8 | 9 | const signUpService = (userDetails) => 10 | axios.post("/api/auth/signup", { 11 | firstName: userDetails.firstName, 12 | lastName: userDetails.lastName, 13 | email: userDetails.email, 14 | username: userDetails.username, 15 | password: userDetails.password, 16 | }); 17 | export { loginService, signUpService }; 18 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: ["./src/**/*.{js,jsx}"], 3 | theme: { 4 | extend: { 5 | colors: { 6 | customBg: "#FAF9F9", 7 | deepBlue: "#253053", 8 | customBlue: "#1d9bf0", 9 | customGreen: "#079E83", 10 | activeGreen: "#DFE2EC", 11 | lightBlue: "#4e5f86", 12 | brightBlue: "#7685ab", 13 | logoNameColor: 14 | "linear-gradient(90deg, rgba(35,56,86,1) 31%, rgba(35,56,86,1) 33%, rgba(37,48,83,1) 36%, rgba(7,158,131,1) 100%)", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { BrowserRouter as Router } from "react-router-dom"; 4 | import { Provider } from "react-redux"; 5 | 6 | import App from "./App"; 7 | import { makeServer } from "./server"; 8 | import { store } from "store/store"; 9 | import "./index.css"; 10 | 11 | // Call make Server 12 | makeServer(); 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 18 | 19 | 20 | 21 | , 22 | document.getElementById("root") 23 | ); 24 | -------------------------------------------------------------------------------- /src/main-container/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Outlet } from "react-router-dom"; 3 | 4 | import { FollowUserBar, Navbar, SearchBar, Sidebar } from "components"; 5 | 6 | const MainContainer = () => { 7 | return ( 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 |
18 |
19 | ); 20 | }; 21 | 22 | export { MainContainer }; 23 | -------------------------------------------------------------------------------- /src/slices/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | loginUser, 3 | signupUser, 4 | authReducer, 5 | logout, 6 | editUserDetails, 7 | } from "./authSlice"; 8 | export { 9 | userReducer, 10 | fetchAllUsers, 11 | fetchUserDetails, 12 | followUnfollowUser, 13 | } from "./userSlice"; 14 | export { 15 | postReducer, 16 | getPosts, 17 | addUserPost, 18 | editUserPost, 19 | deleteUserPost, 20 | addUserComment, 21 | editUserComment, 22 | deleteUserComment, 23 | likeDislikeUserPost, 24 | fetchAllBookmarks, 25 | bookmarkUnbookmarkUserPost, 26 | fetchUserPosts, 27 | } from "./postsSlice"; 28 | export { themeReducer, toggleTheme } from "./themeSlice"; 29 | -------------------------------------------------------------------------------- /src/utils/helpers.js: -------------------------------------------------------------------------------- 1 | export const sortOptions = { 2 | Latest: "Latest Posts", 3 | Oldest: "Oldest Posts", 4 | Trending: "Trending Posts", 5 | }; 6 | 7 | export const getSortedPosts = (posts, sortBy) => { 8 | switch (sortBy.toUpperCase()) { 9 | case "LATEST": 10 | return [...posts].sort( 11 | (a, b) => new Date(b.createdAt) - new Date(a.createdAt) 12 | ); 13 | case "OLDEST": 14 | return [...posts].sort( 15 | (a, b) => new Date(a.createdAt) - new Date(b.createdAt) 16 | ); 17 | case "TRENDING": 18 | return [...posts].sort((a, b) => b.likes.likeCount - a.likes.likeCount); 19 | default: 20 | return [...posts]; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/backend/utils/authUtils.js: -------------------------------------------------------------------------------- 1 | import { Response } from "miragejs"; 2 | import dayjs from "dayjs"; 3 | import jwt_decode from "jwt-decode"; 4 | 5 | export const requiresAuth = function (request) { 6 | const encodedToken = request.requestHeaders.authorization; 7 | const decodedToken = jwt_decode( 8 | encodedToken, 9 | process.env.REACT_APP_JWT_SECRET 10 | ); 11 | if (decodedToken) { 12 | const user = this.db.users.findBy({ username: decodedToken.username }); 13 | return user; 14 | } 15 | return new Response( 16 | 401, 17 | {}, 18 | { errors: ["The token is invalid. Unauthorized access error."] } 19 | ); 20 | }; 21 | 22 | export const formatDate = () => dayjs().format("YYYY-MM-DDTHH:mm:ssZ"); 23 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | SocialBuzz 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /src/asset/index.js: -------------------------------------------------------------------------------- 1 | export { 2 | DarkModeOutlinedIcon, 3 | LightModeOutlinedIcon, 4 | HomeOutlinedIcon, 5 | BookmarkBorderOutlinedIcon, 6 | LogoutIcon, 7 | PostAddIcon, 8 | RocketOutlinedIcon, 9 | FavoriteBorderOutlinedIcon, 10 | AddCircleOutlineOutlinedIcon, 11 | SearchOutlinedIcon, 12 | NewReleasesRoundedIcon, 13 | WhatshotRoundedIcon, 14 | GitHubIcon, 15 | HistoryEduIcon, 16 | ErrorOutlineOutlinedIcon, 17 | RemoveRedEyeOutlinedIcon, 18 | VisibilityOffOutlinedIcon, 19 | AddPhotoAlternateOutlinedIcon, 20 | AddReactionOutlinedIcon, 21 | ThumbUpOffAltIcon, 22 | ThumbUpIcon, 23 | BookmarkBorderIcon, 24 | BookmarkIcon, 25 | MoreVertIcon, 26 | SendIcon, 27 | ClearIcon, 28 | MapsUgcOutlinedIcon, 29 | ThumbDownAltIcon, 30 | EditIcon, 31 | MoreHorizIcon, 32 | PersonOutlineIcon, 33 | TuneIcon, 34 | ElderlyOutlinedIcon, 35 | } from "./icons/metarialIcons"; 36 | -------------------------------------------------------------------------------- /src/services/commentServices.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const getComments = (postId) => axios.get(`/api/comments/${postId}`); 4 | 5 | const addComment = (postId, commentData) => 6 | axios.post( 7 | `/api/comments/add/${postId}`, 8 | { commentData }, 9 | { 10 | headers: { authorization: localStorage.getItem("token") }, 11 | } 12 | ); 13 | 14 | const editComment = (postId, commentId, commentData) => 15 | axios.post( 16 | `/api/comments/edit/${postId}/${commentId}`, 17 | { commentData }, 18 | { 19 | headers: { authorization: localStorage.getItem("token") }, 20 | } 21 | ); 22 | 23 | const deleteComment = (postId, commentId) => 24 | axios.delete(`/api/comments/delete/${postId}/${commentId}`, { 25 | headers: { authorization: localStorage.getItem("token") }, 26 | }); 27 | 28 | export { getComments, addComment, editComment, deleteComment }; 29 | -------------------------------------------------------------------------------- /src/components/shimmer-effect/ShimmerUserCard .jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ShimmerUserCard = () => { 4 | return ( 5 |
6 | {[...Array(4)].map((_, index) => ( 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
+
17 |
Follow
18 |
19 |
20 | ))} 21 |
22 | ); 23 | }; 24 | 25 | export { ShimmerUserCard }; 26 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | -webkit-tap-highlight-color: transparent; 5 | -moz-tap-highlight-color: transparent; 6 | -ms-tap-highlight-color: transparent; 7 | tap-highlight-color: transparent; 8 | } 9 | /* Custom Scrollbar */ 10 | ::-webkit-scrollbar { 11 | background-color: transparent; 12 | width: 0.1rem; 13 | } 14 | 15 | ::-webkit-scrollbar-thumb { 16 | background-color: #253053; 17 | border-radius: 1rem; 18 | } 19 | 20 | /* css for dark theme */ 21 | 22 | .theme-dark .bg-customBg { 23 | background-color: #0f172a; 24 | } 25 | 26 | .theme-dark, 27 | .theme-dark .logo-name, 28 | .theme-dark .text-deepBlue { 29 | color: #edeeee; 30 | } 31 | 32 | .theme-dark .bg-white, 33 | .theme-dark .textarea, 34 | .theme-dark .bg-activeGreen { 35 | background-color: #253044; 36 | } 37 | 38 | .theme-dark .border { 39 | border-color: #525f79; 40 | } 41 | 42 | .theme-dark .btn { 43 | background-color: #1d9bf0; 44 | } 45 | 46 | .theme-dark .input, 47 | .theme-dark .sidebar:hover, 48 | .theme-dark .options:hover { 49 | background-color: #334e68; 50 | } 51 | -------------------------------------------------------------------------------- /src/components/Bookmark/Bookmark.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | 4 | import { PostList } from "components"; 5 | import { fetchAllBookmarks } from "slices"; 6 | 7 | const Bookmark = () => { 8 | const { allPosts, bookmarks } = useSelector((state) => state.post); 9 | const dispatch = useDispatch(); 10 | 11 | useEffect(() => { 12 | dispatch(fetchAllBookmarks()); 13 | }, [dispatch]); 14 | 15 | const bookmarkPost = allPosts?.filter((post) => 16 | bookmarks.find((bookmarkPost) => bookmarkPost._id === post._id) 17 | ); 18 | 19 | return ( 20 |
21 |

Bookmarks

22 | {bookmarkPost.length !== 0 ? ( 23 | 24 | ) : ( 25 |

26 | Bookmark posts to see them here! 27 |

28 | )} 29 |
30 | ); 31 | }; 32 | 33 | export { Bookmark }; 34 | -------------------------------------------------------------------------------- /src/components/liked-posts/LikedPosts.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useSelector } from "react-redux"; 3 | 4 | import { PostList } from "components"; 5 | 6 | const LikedPosts = () => { 7 | const [postsLikedByUser, setPostsLikedByUser] = useState([]); 8 | 9 | const { allPosts } = useSelector((state) => state.post); 10 | const { user } = useSelector((state) => state.auth); 11 | 12 | useEffect(() => { 13 | const likedPosts = allPosts.filter((currPost) => 14 | currPost.likes.likedBy.find( 15 | (currUser) => currUser.username === user?.username 16 | ) 17 | ); 18 | setPostsLikedByUser(likedPosts); 19 | }, [allPosts, user]); 20 | 21 | return ( 22 |
23 |

Liked Posts

24 | {postsLikedByUser.length !== 0 ? ( 25 | 26 | ) : ( 27 |

28 | No Liked Posts Yet! 29 |

30 | )} 31 |
32 | ); 33 | }; 34 | 35 | export { LikedPosts }; 36 | -------------------------------------------------------------------------------- /src/components/shimmer-effect/ShimmerUserProfile .jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ShimmerUserProfile = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | ); 26 | }; 27 | export { ShimmerUserProfile }; 28 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { Navbar } from "./shared/Navbar"; 2 | export { Sidebar } from "./shared/Sidebar"; 3 | export { SearchBar } from "./suggetion&search/SearchBar"; 4 | export { FollowUserBar } from "./suggetion&search/FollowUserBar"; 5 | export { MockAPI } from "./shared/Mockman"; 6 | export { PrivateRoute } from "./authentication/PrivateRoute"; 7 | export { LoginForm } from "./authentication/LoginForm"; 8 | export { SignupForm } from "./authentication/SignupForm"; 9 | export { Home } from "./home/Home"; 10 | export { PostModal } from "./home/PostModal"; 11 | export { PostList } from "./home/PostList"; 12 | export { PostForm } from "./home/PostForm"; 13 | export { Explore } from "./explore/Explore"; 14 | export { LikedPosts } from "./liked-posts/LikedPosts"; 15 | export { Bookmark } from "./Bookmark/Bookmark"; 16 | export { UserDetails } from "./UserDetails/UserDetails"; 17 | export { ProfileModal } from "./ProfileModal/ProfileModal"; 18 | export { UserProfile } from "./UserProfile/UserProfile"; 19 | export { Profile } from "./Profile/Profile"; 20 | export { FollowButton } from "./FollowButton/FollowButton"; 21 | export { ShimmerPostList } from "./shimmer-effect/ShimmerPostList"; 22 | export { ShimmerUserProfile } from "./shimmer-effect/ShimmerUserProfile "; 23 | export { ShimmerUserCard } from "./shimmer-effect/ShimmerUserCard "; 24 | -------------------------------------------------------------------------------- /src/components/FollowButton/FollowButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | 4 | import { followUnfollowUser } from "slices"; 5 | 6 | const FollowButton = ({ userDetails }) => { 7 | const dispatch = useDispatch(); 8 | const { user } = useSelector((state) => state.auth); 9 | 10 | const isFollowing = userDetails.followers.some( 11 | (currentUser) => currentUser.username === user.username 12 | ); 13 | 14 | return isFollowing ? ( 15 | 29 | ) : ( 30 | 44 | ); 45 | }; 46 | 47 | export { FollowButton }; 48 | -------------------------------------------------------------------------------- /src/components/shimmer-effect/ShimmerPostList.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ShimmerPostList = () => { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 | {[...Array(3)].map((_, index) => ( 11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ))} 29 |
30 | ); 31 | }; 32 | 33 | export { ShimmerPostList }; 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "social-buzz", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.11.0", 7 | "@emotion/styled": "^11.11.0", 8 | "@mui/icons-material": "^5.11.16", 9 | "@mui/material": "^5.13.2", 10 | "@reduxjs/toolkit": "^1.9.5", 11 | "axios": "^1.4.0", 12 | "dayjs": "^1.11.7", 13 | "emoji-picker-react": "^4.4.9", 14 | "jwt-decode": "^3.1.2", 15 | "jwt-encode": "^1.0.1", 16 | "miragejs": "^0.1.47", 17 | "mockman-js": "^1.1.5", 18 | "react": "^18.2.0", 19 | "react-dom": "^18.2.0", 20 | "react-hot-toast": "^2.4.1", 21 | "react-redux": "^8.0.5", 22 | "react-router": "^6.11.2", 23 | "react-router-dom": "^6.11.2", 24 | "react-scripts": "5.0.1", 25 | "uuid": "^8.3.2" 26 | }, 27 | "scripts": { 28 | "start": "react-scripts start", 29 | "build": "react-scripts build", 30 | "test": "react-scripts test", 31 | "eject": "react-scripts eject" 32 | }, 33 | "eslintConfig": { 34 | "extends": "react-app" 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "tailwindcss": "^3.3.2" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/explore/Explore.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | 4 | import { getPosts } from "slices"; 5 | import { PostList, ShimmerPostList } from "components"; 6 | 7 | const Explore = () => { 8 | const { allPosts,getAllPostsStatus } = useSelector((state) => state.post); 9 | const { user } = useSelector((state) => state.auth); 10 | const dispatch = useDispatch(); 11 | 12 | useEffect(() => { 13 | dispatch(getPosts()); 14 | }, [dispatch]); 15 | 16 | //posts from users other than the current user and whom the current user is not following. 17 | const explorePosts = allPosts?.filter( 18 | (post) => 19 | post.username !== user?.username && 20 | !user?.following?.find( 21 | (followingUser) => followingUser?.username === post?.username 22 | ) 23 | ); 24 | 25 | return ( 26 |
27 |

Explore

28 | {getAllPostsStatus === "pending" ?( 29 | 30 | ) :explorePosts?.length ? ( 31 | 32 | ) : ( 33 |

34 | No more posts available to explore! 35 |

36 | )} 37 |
38 | ); 39 | }; 40 | 41 | export { Explore }; 42 | -------------------------------------------------------------------------------- /src/components/home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | 4 | import { getPosts } from "slices"; 5 | import { PostList } from "./PostList"; 6 | import { PostForm } from "./PostForm"; 7 | import { ShimmerPostList } from "components"; 8 | 9 | 10 | const Home = () => { 11 | const { allPosts, getAllPostsStatus } = useSelector((state) => state.post); 12 | const { user } = useSelector((state) => state.auth); 13 | const dispatch = useDispatch(); 14 | 15 | useEffect(() => { 16 | dispatch(getPosts()); 17 | }, [dispatch]); 18 | 19 | // posts from the current user or posts from users whom the current user is following. 20 | const homePosts = allPosts?.filter( 21 | (post) => 22 | post?.username === user?.username || 23 | user?.following?.find( 24 | (followingUser) => followingUser?.username === post?.username 25 | ) 26 | ); 27 | 28 | return ( 29 |
30 | 31 | {getAllPostsStatus === "pending" ? ( 32 | 33 | ) : homePosts?.length ? ( 34 | 35 | ) : ( 36 |

37 | Follow users to see their posts here! 38 |

39 | )} 40 |
41 | ); 42 | }; 43 | 44 | export { Home }; 45 | -------------------------------------------------------------------------------- /src/components/UserProfile/UserProfile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | 4 | import { PostList, ShimmerPostList, UserDetails } from "components"; 5 | import { fetchUserPosts } from "slices"; 6 | 7 | const UserProfile = () => { 8 | const { user } = useSelector((state) => state.auth); 9 | const { userPosts } = useSelector((state) => state.post); 10 | const { allPosts, getUserPostsStatus } = useSelector((state) => state.post); 11 | 12 | const dispatch = useDispatch(); 13 | 14 | useEffect(() => { 15 | dispatch(fetchUserPosts(user?.username)); 16 | }, [dispatch, allPosts, user?.username]); 17 | 18 | return ( 19 |
20 |

Profile

21 | {user?.username && ( 22 | <> 23 | 28 | {getUserPostsStatus === "pending" ? ( 29 | 30 | ): userPosts.length > 0 ? ( 31 | 32 | ) : ( 33 |

Add some cool posts!

34 | )} 35 | 36 | )} 37 |
38 | ); 39 | }; 40 | 41 | export { UserProfile }; 42 | -------------------------------------------------------------------------------- /src/services/postsServices.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const getAllPostsFromServer = () => axios.get("/api/posts"); 4 | 5 | const getPostsByUserName = (username) => 6 | axios.get(`/api/posts/user/${username}`); 7 | 8 | const addPost = (postData) => { 9 | return axios.post( 10 | "/api/posts", 11 | { postData }, 12 | { 13 | headers: { authorization: localStorage.getItem("token") }, 14 | } 15 | ); 16 | }; 17 | 18 | const deletePost = async (postId) => { 19 | try { 20 | const response = await axios.delete(`/api/posts/${postId}`, { 21 | headers: { authorization: localStorage.getItem("token") }, 22 | }); 23 | return response.data; 24 | } catch (error) { 25 | throw error; 26 | } 27 | }; 28 | 29 | const editPost = (postData) => 30 | axios.post( 31 | `/api/posts/edit/${postData._id}`, 32 | { postData }, 33 | { 34 | headers: { authorization: localStorage.getItem("token") }, 35 | } 36 | ); 37 | 38 | const likePost = (postId) => 39 | axios.post( 40 | `/api/posts/like/${postId}`, 41 | {}, 42 | { 43 | headers: { authorization: localStorage.getItem("token") }, 44 | } 45 | ); 46 | 47 | const dislikePost = (postId) => 48 | axios.post( 49 | `/api/posts/dislike/${postId}`, 50 | {}, 51 | { 52 | headers: { authorization: localStorage.getItem("token") }, 53 | } 54 | ); 55 | 56 | export { 57 | getAllPostsFromServer, 58 | getPostsByUserName, 59 | addPost, 60 | deletePost, 61 | editPost, 62 | likePost, 63 | dislikePost, 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/authentication/InputFeild.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ErrorOutlineOutlinedIcon, RemoveRedEyeOutlinedIcon, VisibilityOffOutlinedIcon } from "asset"; 3 | 4 | const InputField = ({ 5 | id, 6 | label, 7 | type, 8 | value, 9 | onChange, 10 | onFocus, 11 | error, 12 | toggleHide, 13 | hidePassword 14 | }) => { 15 | return ( 16 |
17 | 20 |
21 | 29 | {toggleHide && ( 30 | 36 | {hidePassword ? ( 37 | 38 | ) : ( 39 | 40 | )} 41 | 42 | )} 43 |
44 | {error && ( 45 |
46 | 47 | {error} 48 |
49 | )} 50 |
51 | ); 52 | }; 53 | 54 | export { InputField }; 55 | -------------------------------------------------------------------------------- /src/services/userServices.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const getAllUsers = () => axios.get("/api/users"); 4 | 5 | const getUserByUsername = (username) => axios.get(`/api/users/${username}`); 6 | 7 | const editUserData = (userData) => 8 | axios.post( 9 | `/api/users/edit`, 10 | { userData }, 11 | { 12 | headers: { authorization: localStorage.getItem("token") }, 13 | } 14 | ); 15 | 16 | const getBookmarks = () => 17 | axios.get(`/api/users/bookmark`, { 18 | headers: { authorization: localStorage.getItem("token") }, 19 | }); 20 | 21 | const bookmarkPost = (postId) => 22 | axios.post( 23 | `/api/users/bookmark/${postId}`, 24 | {}, 25 | { 26 | headers: { authorization: localStorage.getItem("token") }, 27 | } 28 | ); 29 | 30 | const unBookmarkPost = (postId) => 31 | axios.post( 32 | `/api/users/remove-bookmark/${postId}`, 33 | {}, 34 | { 35 | headers: { authorization: localStorage.getItem("token") }, 36 | } 37 | ); 38 | 39 | const followUser = (followUserId) => 40 | axios.post( 41 | `/api/users/follow/${followUserId}`, 42 | {}, 43 | { 44 | headers: { authorization: localStorage.getItem("token") }, 45 | } 46 | ); 47 | 48 | const unFollowUser = (followUserId) => 49 | axios.post( 50 | `/api/users/unfollow/${followUserId}`, 51 | {}, 52 | { 53 | headers: { authorization: localStorage.getItem("token") }, 54 | } 55 | ); 56 | 57 | export { 58 | getAllUsers, 59 | getUserByUsername, 60 | editUserData, 61 | getBookmarks, 62 | bookmarkPost, 63 | unBookmarkPost, 64 | followUser, 65 | unFollowUser, 66 | }; 67 | -------------------------------------------------------------------------------- /src/components/Profile/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useParams } from "react-router-dom"; 4 | 5 | import { 6 | PostList, 7 | ShimmerPostList, 8 | ShimmerUserProfile, 9 | UserDetails, 10 | } from "components"; 11 | import { fetchUserDetails, fetchUserPosts } from "slices"; 12 | 13 | const Profile = () => { 14 | const dispatch = useDispatch(); 15 | const { username } = useParams(); 16 | 17 | const { userPosts } = useSelector((state) => state.post); 18 | const { userDetails, allUsers, userDetailsStatus } = useSelector( 19 | (state) => state.user 20 | ); 21 | const { allPosts } = useSelector((state) => state.post); 22 | 23 | useEffect(() => { 24 | dispatch(fetchUserDetails(username)); 25 | dispatch(fetchUserPosts(username)); 26 | }, [dispatch, allUsers, allPosts, username]); 27 | 28 | return ( 29 |
30 |

Profile

31 | {userDetails?.username ? ( 32 | <> 33 | 34 | {userDetailsStatus === "pending" ? ( 35 | 36 | ) : userPosts.length > 0 ? ( 37 | 38 | ) : ( 39 |

User don't have posts!

40 | )} 41 | 42 | ) : ( 43 | <> 44 | 45 | 46 | )} 47 |
48 | ); 49 | }; 50 | 51 | export { Profile }; 52 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Route, Routes } from "react-router-dom"; 2 | import { Toaster } from "react-hot-toast"; 3 | 4 | import "./App.css"; 5 | import { 6 | Bookmark, 7 | Explore, 8 | Home, 9 | LikedPosts, 10 | LoginForm, 11 | MockAPI, 12 | PrivateRoute, 13 | Profile, 14 | SignupForm, 15 | UserProfile, 16 | } from "components"; 17 | import { MainContainer } from "main-container"; 18 | import { useSelector } from "react-redux"; 19 | 20 | function App() { 21 | const theme = useSelector((state) => state.theme.theme); 22 | 23 | return ( 24 |
25 | 37 | 38 | } /> 39 | } /> 40 | }> 41 | }> 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | } /> 48 | 49 | } /> 50 | 51 | 52 |
53 | ); 54 | } 55 | 56 | export default App; 57 | -------------------------------------------------------------------------------- /src/components/authentication/formValidation.js: -------------------------------------------------------------------------------- 1 | export const validateSignupForm = (userDetails) => { 2 | let isValid = true; 3 | const errors = { 4 | firstName: "", 5 | lastName: "", 6 | email: "", 7 | password: "", 8 | confirmPassword: "", 9 | }; 10 | 11 | const nameRegex = /^[A-Za-z]+$/; 12 | const emailRegex = /^\S+@\S+\.\S+$/; 13 | const passwordRegex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/; 14 | 15 | if (!userDetails.firstName) { 16 | errors.firstName = "Please enter your first name"; 17 | isValid = false; 18 | } else if (!userDetails.firstName.match(nameRegex)) { 19 | errors.firstName = "Invalid first name"; 20 | isValid = false; 21 | } 22 | 23 | if (!userDetails.lastName) { 24 | errors.lastName = "Please enter your last name"; 25 | isValid = false; 26 | } else if (!userDetails.lastName.match(nameRegex)) { 27 | errors.lastName = "Invalid last name"; 28 | isValid = false; 29 | } 30 | 31 | if (!userDetails.email) { 32 | errors.email = "Please enter your email"; 33 | isValid = false; 34 | } else if (!userDetails.email.match(emailRegex)) { 35 | errors.email = "Invalid email"; 36 | isValid = false; 37 | } 38 | 39 | if (!userDetails.username) { 40 | errors.username = "Please enter your user name"; 41 | isValid = false; 42 | } else if (!userDetails.username.match(nameRegex)) { 43 | errors.username = "Invalid user name"; 44 | isValid = false; 45 | } 46 | 47 | if (!userDetails.password) { 48 | errors.password = "Please enter a password"; 49 | isValid = false; 50 | } else if (!userDetails.password.match(passwordRegex)) { 51 | errors.password = 52 | "Password must contain at least 8 characters, one lowercase letter, one uppercase letter, and one number"; 53 | isValid = false; 54 | } 55 | if (!userDetails.confirmPassword) { 56 | errors.confirmPassword = "Please confirm your password"; 57 | isValid = false; 58 | } else if (userDetails.password !== userDetails.confirmPassword) { 59 | errors.confirmPassword = "Passwords do not match"; 60 | isValid = false; 61 | } 62 | 63 | return { isValid, errors }; 64 | }; 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | ## 🌟 SocialBuzz 🌟 5 | 6 | SocialBuzz is a compact social media app designed for connecting and engaging with others. It offers a simple and intuitive platform where people can share their thoughts with their friends and explore different points of view. Users can converse with each other and express their opinions in a fun and interactive way. 7 |
8 | 9 | --- 10 | 11 | ## 💻 How to run the app locally? 12 | 13 | ``` 14 | $ git clone https://github.com/chhakuli123/SocialBuzz.git 15 | $ cd social-buzz 16 | $ npm install 17 | $ npm start 18 | ``` 19 | 20 | --- 21 | 22 | ## 🚀 Deployed Link 23 | 24 | Check out the live demo of SocialBuzz: [SocialBuzz](https://social-buzz-app.netlify.app/) 25 | 26 | --- 27 | 28 | ## 🛠️ Built With 29 | 30 | - ReactJS 31 | - React Router 32 | - Tailwind CSS 33 | - Redux Toolkit 34 | - MockBee 35 | - Git for Version Control 36 | - Netlify for Deployment 37 | 38 | --- 39 | 40 | ## ✨ Features 41 | 42 | - User Authentication Pages 🚪 43 | - User Signup 📝 44 | - User Login 🔐 45 | - Home Page 🏠 46 | - View your own posts and posts from people you follow 📃 47 | - User Search Bar 🔍 48 | - Search for other users 🔎 49 | - Click on a user to go to their profile page 👤 50 | - Follow Menu Bar 👥 51 | - Suggestions of users to follow 👀 52 | - Explore Page 🔍 53 | - View posts from other users 🌍 54 | - Bookmarks Page 🔖 55 | - View bookmarked posts 📑 56 | - Liked Page 👍 57 | - View liked posts ❤️ 58 | - User Profile Page 👤 59 | - Edit your profile details - profile photo, name, bio, and website link ✏️ 60 | - Other Users Profile Page 👥 61 | - View other users' profile page 62 | - Check their posts 63 | - Follow/unfollow them 64 | - Filters for Sorting Posts 🗂️ 65 | - Sort posts by latest, oldest, and trending 66 | - Posts ✉️ 67 | - Create posts with photos and emojis 📷😄 68 | - Update posts 69 | - Delete posts 70 | - Like and bookmark posts 71 | - Click on a user's name or image to go to their profile page 👤 72 | - Comments 💬 73 | - Add comments to posts 74 | - Delete comments 75 | - Update comments 76 | - Alerts 🚨 77 | - Alerts in the app to notify users about success/failure operations 78 | - Theme 🌗 79 | - Toggle between light and dark themes 80 | - Responsive 📱 81 | - All screens are responsive 82 | 83 | --- 84 | ## Demo Video -------------------------------------------------------------------------------- /src/components/shared/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router"; 3 | import { Link } from "react-router-dom"; 4 | import { useDispatch, useSelector } from "react-redux"; 5 | 6 | import { DarkModeOutlinedIcon, GitHubIcon, LightModeOutlinedIcon } from "asset"; 7 | import { toggleTheme } from "slices"; 8 | 9 | const Navbar = () => { 10 | const navigate = useNavigate(); 11 | const theme = useSelector((state) => state.theme.theme); 12 | const dispatch = useDispatch(); 13 | 14 | const handleThemeToggle = () => { 15 | dispatch(toggleTheme()); 16 | }; 17 | 18 | return ( 19 | 62 | ); 63 | }; 64 | 65 | export { Navbar }; 66 | -------------------------------------------------------------------------------- /src/components/UserDetails/UserDetails.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import { FollowButton, ProfileModal } from "components"; 5 | import { EditIcon } from "asset"; 6 | 7 | const UserDetails = ({ user, showEditButton, postLength }) => { 8 | const [profileModal, setProfileModal] = useState(false); 9 | 10 | return ( 11 | <> 12 |
13 | {user.avatarUrl ? ( 14 | User Avatar 19 | ) : ( 20 | avatar 25 | )} 26 | 27 |
28 |
29 |
30 |

31 | {user?.firstName} {user?.lastName} 32 |

33 |

34 | @{user?.username} 35 |

36 |
37 | {showEditButton ? ( 38 |
setProfileModal(true)} 41 | > 42 | Edit 43 |
44 | ) : ( 45 | 46 | )} 47 |
48 | 49 |

{user?.bio}

50 | 51 |
52 | {postLength ?? 0} Posts 53 | {user?.followers?.length ?? 0} Followers 54 | {user?.following?.length ?? 0} Following 55 |
56 | 57 |
58 | 63 | {user.website} 64 | 65 |
66 |
67 |
68 | {profileModal && ( 69 | 74 | )} 75 | 76 | ); 77 | }; 78 | 79 | export { UserDetails }; 80 | -------------------------------------------------------------------------------- /src/backend/controllers/AuthController.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuid } from "uuid"; 2 | import { Response } from "miragejs"; 3 | import { formatDate } from "../utils/authUtils"; 4 | const sign = require("jwt-encode"); 5 | 6 | /** 7 | * All the routes related to Auth are present here. 8 | * These are Publicly accessible routes. 9 | * */ 10 | 11 | /** 12 | * This handler handles user signups. 13 | * send POST Request at /api/auth/signup 14 | * body contains {firstName, lastName, username, password} 15 | * */ 16 | 17 | export const signupHandler = function (schema, request) { 18 | const { username, password, ...rest } = JSON.parse(request.requestBody); 19 | try { 20 | // check if username already exists 21 | const foundUser = schema.users.findBy({ username: username }); 22 | if (foundUser) { 23 | return new Response( 24 | 422, 25 | {}, 26 | { 27 | errors: ["Unprocessable Entity. Username Already Exists."], 28 | } 29 | ); 30 | } 31 | const _id = uuid(); 32 | 33 | const newUser = { 34 | _id, 35 | createdAt: formatDate(), 36 | updatedAt: formatDate(), 37 | username, 38 | password, 39 | ...rest, 40 | followers: [], 41 | following: [], 42 | bookmarks: [], 43 | }; 44 | const createdUser = schema.users.create(newUser); 45 | const encodedToken = sign( 46 | { _id, username }, 47 | process.env.REACT_APP_JWT_SECRET 48 | ); 49 | return new Response(201, {}, { createdUser, encodedToken }); 50 | } catch (error) { 51 | return new Response( 52 | 500, 53 | {}, 54 | { 55 | error, 56 | } 57 | ); 58 | } 59 | }; 60 | 61 | /** 62 | * This handler handles user login. 63 | * send POST Request at /api/auth/login 64 | * body contains {username, password} 65 | * */ 66 | 67 | export const loginHandler = function (schema, request) { 68 | const { username, password } = JSON.parse(request.requestBody); 69 | try { 70 | const foundUser = schema.users.findBy({ username: username }); 71 | if (!foundUser) { 72 | return new Response( 73 | 404, 74 | {}, 75 | { 76 | errors: [ 77 | "The username you entered is not Registered. Not Found error", 78 | ], 79 | } 80 | ); 81 | } 82 | if (password === foundUser.password) { 83 | const encodedToken = sign( 84 | { _id: foundUser._id, username }, 85 | process.env.REACT_APP_JWT_SECRET 86 | ); 87 | return new Response(200, {}, { foundUser, encodedToken }); 88 | } 89 | return new Response( 90 | 401, 91 | {}, 92 | { 93 | errors: [ 94 | "The credentials you entered are invalid. Unauthorized access error.", 95 | ], 96 | } 97 | ); 98 | } catch (error) { 99 | return new Response( 100 | 500, 101 | {}, 102 | { 103 | error, 104 | } 105 | ); 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /src/asset/icons/metarialIcons.js: -------------------------------------------------------------------------------- 1 | import DarkModeOutlinedIcon from "@mui/icons-material/DarkModeOutlined"; 2 | import LightModeOutlinedIcon from "@mui/icons-material/LightModeOutlined"; 3 | import HomeOutlinedIcon from "@mui/icons-material/HomeOutlined"; 4 | import BookmarkBorderOutlinedIcon from "@mui/icons-material/BookmarkBorderOutlined"; 5 | import LogoutIcon from "@mui/icons-material/Logout"; 6 | import PostAddIcon from "@mui/icons-material/PostAdd"; 7 | import RocketOutlinedIcon from "@mui/icons-material/RocketOutlined"; 8 | import FavoriteBorderOutlinedIcon from "@mui/icons-material/FavoriteBorderOutlined"; 9 | import AddCircleOutlineOutlinedIcon from "@mui/icons-material/AddCircleOutlineOutlined"; 10 | import SearchOutlinedIcon from "@mui/icons-material/SearchOutlined"; 11 | import WhatshotRoundedIcon from "@mui/icons-material/WhatshotRounded"; 12 | import NewReleasesRoundedIcon from "@mui/icons-material/NewReleasesRounded"; 13 | import GitHubIcon from "@mui/icons-material/GitHub"; 14 | import HistoryEduIcon from "@mui/icons-material/HistoryEdu"; 15 | import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined"; 16 | import RemoveRedEyeOutlinedIcon from "@mui/icons-material/RemoveRedEyeOutlined"; 17 | import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; 18 | import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; 19 | import AddReactionOutlinedIcon from "@mui/icons-material/AddReactionOutlined"; 20 | import ThumbUpOffAltIcon from "@mui/icons-material/ThumbUpOffAlt"; 21 | import ThumbUpIcon from "@mui/icons-material/ThumbUp"; 22 | import BookmarkBorderIcon from "@mui/icons-material/BookmarkBorder"; 23 | import BookmarkIcon from "@mui/icons-material/Bookmark"; 24 | import MoreVertIcon from "@mui/icons-material/MoreVert"; 25 | import SendIcon from "@mui/icons-material/Send"; 26 | import ClearIcon from "@mui/icons-material/Clear"; 27 | import MapsUgcOutlinedIcon from "@mui/icons-material/MapsUgcOutlined"; 28 | import EditIcon from "@mui/icons-material/Edit"; 29 | import ThumbDownAltIcon from "@mui/icons-material/ThumbDownAlt"; 30 | import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; 31 | import PersonOutlineIcon from "@mui/icons-material/PersonOutline"; 32 | import TuneIcon from "@mui/icons-material/Tune"; 33 | import ElderlyOutlinedIcon from "@mui/icons-material/ElderlyOutlined"; 34 | export { 35 | DarkModeOutlinedIcon, 36 | LightModeOutlinedIcon, 37 | HomeOutlinedIcon, 38 | BookmarkBorderOutlinedIcon, 39 | LogoutIcon, 40 | PostAddIcon, 41 | RocketOutlinedIcon, 42 | FavoriteBorderOutlinedIcon, 43 | AddCircleOutlineOutlinedIcon, 44 | SearchOutlinedIcon, 45 | NewReleasesRoundedIcon, 46 | WhatshotRoundedIcon, 47 | GitHubIcon, 48 | HistoryEduIcon, 49 | ErrorOutlineOutlinedIcon, 50 | RemoveRedEyeOutlinedIcon, 51 | VisibilityOffOutlinedIcon, 52 | AddPhotoAlternateOutlinedIcon, 53 | AddReactionOutlinedIcon, 54 | ThumbUpOffAltIcon, 55 | ThumbUpIcon, 56 | BookmarkBorderIcon, 57 | BookmarkIcon, 58 | MoreVertIcon, 59 | SendIcon, 60 | ClearIcon, 61 | MapsUgcOutlinedIcon, 62 | ThumbDownAltIcon, 63 | EditIcon, 64 | MoreHorizIcon, 65 | PersonOutlineIcon, 66 | TuneIcon, 67 | ElderlyOutlinedIcon, 68 | }; 69 | -------------------------------------------------------------------------------- /src/components/home/PostList.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector } from "react-redux"; 3 | 4 | import { Post } from "./Post"; 5 | import { useOutsideClick } from "hooks"; 6 | import { getSortedPosts, sortOptions } from "utils"; 7 | import { 8 | ElderlyOutlinedIcon, 9 | NewReleasesRoundedIcon, 10 | TuneIcon, 11 | WhatshotRoundedIcon, 12 | } from "asset"; 13 | 14 | const PostList = ({ posts }) => { 15 | const [showSortOptions, setShowSortOptions] = useState(false); 16 | const [sortByOption, setSortByOption] = useState("Latest"); 17 | 18 | const { allUsers } = useSelector((state) => state.user); 19 | const domNode = useOutsideClick(() => setShowSortOptions(false)); 20 | 21 | const sortedPosts = getSortedPosts(posts, sortByOption); 22 | 23 | return ( 24 |
25 |
26 | 27 |

28 | {sortOptions[sortByOption]} 29 |

30 |
31 | setShowSortOptions(!showSortOptions)} 37 | /> 38 |
    43 |
  • setSortByOption("Latest")} 48 | > 49 | 52 | Latest 53 |
  • 54 |
  • setSortByOption("Oldest")} 59 | > 60 | 63 | Oldest 64 |
  • 65 |
  • setSortByOption("Trending")} 70 | > 71 | 74 | Trending 75 |
  • 76 |
77 |
78 |
79 |
80 | 81 | {sortedPosts.map((post) => { 82 | return ; 83 | })} 84 |
85 | ); 86 | }; 87 | export { PostList }; 88 | -------------------------------------------------------------------------------- /src/components/suggetion&search/FollowUserBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useNavigate } from "react-router-dom"; 4 | 5 | import { fetchAllUsers, followUnfollowUser } from "slices"; 6 | import { SearchBar } from "./SearchBar"; 7 | import { ShimmerUserCard } from "components"; 8 | 9 | const FollowUserBar = () => { 10 | const { user } = useSelector((state) => state.auth); 11 | const { allUsers,allUserStatus } = useSelector((state) => state.user); 12 | 13 | const dispatch = useDispatch(); 14 | const navigate = useNavigate(); 15 | 16 | useEffect(() => { 17 | dispatch(fetchAllUsers()); 18 | }, [dispatch]); 19 | 20 | const followData = allUsers 21 | ?.filter((currentUser) => currentUser?._id !== user?._id) 22 | .filter( 23 | (currentUser) => 24 | !user?.following?.find( 25 | (followingUser) => followingUser?._id === currentUser?._id 26 | ) 27 | ) 28 | .slice(0, 4); 29 | 30 | const handleNavigate = (username) => { 31 | user.username === username 32 | ? navigate("/user-profile") 33 | : navigate(`/profile/${username}`); 34 | }; 35 | 36 | return ( 37 |
38 |
39 | 40 |
41 |

Who to follow?

42 | {allUserStatus === "pending" ? ( 43 | 44 | ) : followData.length === 0 ? ( 45 |
No Suggestions
46 | ) : ( 47 | followData.map((user) => ( 48 |
52 |
53 | avatar handleNavigate(user?.username)} 58 | /> 59 |
handleNavigate(user?.username)} 62 | > 63 |

64 | {user?.firstName} {user?.lastName} 65 |

66 |

@{user?.username}

67 |
68 |
69 | 84 |
85 | )) 86 | )} 87 |
88 |
89 |
90 | ); 91 | }; 92 | 93 | 94 | export { FollowUserBar }; 95 | -------------------------------------------------------------------------------- /src/components/home/CommentItem.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | 3 | import { MoreVertIcon } from "asset"; 4 | import { useOutsideClick } from "hooks"; 5 | 6 | const CommentItem = ({ 7 | comment, 8 | user, 9 | allUsers, 10 | handleEditComment, 11 | handleDeleteComment, 12 | }) => { 13 | const [commentOptions, setCommentOptions] = useState(false); 14 | 15 | const commentUser = allUsers.find((u) => u.username === comment.username); 16 | const isCurrentUserComment = comment.username === user?.username; 17 | 18 | const domNode = useOutsideClick(() => { 19 | setCommentOptions(false); 20 | }); 21 | 22 | const toggleOptions = () => { 23 | setCommentOptions((prevShowOptions) => !prevShowOptions); 24 | }; 25 | 26 | return ( 27 |
28 | {/* Comment user avatar */} 29 | {isCurrentUserComment ? ( 30 | user?.avatarUrl ? ( 31 | current user avatar 36 | ) : ( 37 | default avatar 42 | ) 43 | ) : commentUser?.avatarUrl ? ( 44 | comment user avatar 49 | ) : ( 50 | default avatar 55 | )} 56 | 57 | {/* Comment content */} 58 |
59 |
60 | {isCurrentUserComment ? ( 61 | {user?.firstName} {user?.lastName} 62 | ) : commentUser ? ( 63 | {commentUser?.firstName} {commentUser?.lastName} 64 | ) : ( 65 | Unknown User 66 | )} 67 | {isCurrentUserComment && ( 68 |
69 | 73 | {commentOptions && ( 74 |
75 |
handleEditComment(comment)} 78 | > 79 | Edit 80 |
81 |
handleDeleteComment(comment._id)} 84 | > 85 | Delete 86 |
87 |
88 | )} 89 |
90 | )} 91 |
92 |

{comment.text}

93 |
94 |
95 | ); 96 | }; 97 | 98 | export { CommentItem }; 99 | -------------------------------------------------------------------------------- /src/slices/authSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; 2 | import { toast } from "react-hot-toast"; 3 | 4 | import { editUserData, loginService, signUpService } from "services"; 5 | 6 | const initialState = { 7 | token: localStorage.getItem("token") || null, 8 | user: JSON.parse(localStorage.getItem("user")) || null, 9 | error: null, 10 | }; 11 | 12 | export const loginUser = createAsyncThunk( 13 | "auth/loginUser", 14 | async (userDetails, { rejectWithValue }) => { 15 | try { 16 | const { data } = await loginService(userDetails); 17 | return data; 18 | } catch (e) { 19 | return rejectWithValue(e.message); 20 | } 21 | } 22 | ); 23 | 24 | export const signupUser = createAsyncThunk( 25 | "auth/signupUser", 26 | async (userDetails, { rejectWithValue }) => { 27 | try { 28 | const { data } = await signUpService(userDetails); 29 | return data; 30 | } catch (e) { 31 | return rejectWithValue(e.message); 32 | } 33 | } 34 | ); 35 | 36 | export const editUserDetails = createAsyncThunk( 37 | "auth/editUserData", 38 | async (userDetails, { rejectWithValue }) => { 39 | try { 40 | const { data } = await editUserData(userDetails); 41 | return data.user; 42 | } catch (e) { 43 | return rejectWithValue(e.message); 44 | } 45 | } 46 | ); 47 | 48 | const authSlice = createSlice({ 49 | name: "auth", 50 | initialState, 51 | reducers: { 52 | logout: (state) => { 53 | state.token = ""; 54 | state.user = null; 55 | localStorage.removeItem("token"); 56 | localStorage.removeItem("user"); 57 | }, 58 | }, 59 | extraReducers: (builder) => { 60 | builder 61 | .addCase(loginUser.fulfilled, (state, action) => { 62 | state.token = action.payload.encodedToken; 63 | state.user = action.payload.foundUser; 64 | localStorage.setItem("token", action.payload.encodedToken); 65 | localStorage.setItem("user", JSON.stringify(action.payload.foundUser)); 66 | toast.success(`Welcome back! ${state.user.firstName}`, { icon: "👋" }); 67 | }) 68 | .addCase(loginUser.rejected, (state, action) => { 69 | state.error = action.payload; 70 | toast.error(`Some went wrong, Please try again:( ${state.error}`); 71 | }) 72 | .addCase(signupUser.fulfilled, (state, action) => { 73 | state.token = action.payload.encodedToken; 74 | state.user = action.payload.createdUser; 75 | localStorage.setItem("token", action.payload.encodedToken); 76 | localStorage.setItem( 77 | "user", 78 | JSON.stringify(action.payload.createdUser) 79 | ); 80 | toast.success( 81 | `Account Created Successfully, 82 | Welcome ${state.user.firstName} `, 83 | { icon: "👋" } 84 | ); 85 | }) 86 | .addCase(signupUser.rejected, (state, action) => { 87 | state.error = action.payload; 88 | toast.error(`Some went wrong, Please try again:( ${state.error}`); 89 | }) 90 | .addCase(editUserDetails.fulfilled, (state, action) => { 91 | state.user = action.payload; 92 | localStorage.setItem("user", JSON.stringify(state.user)); 93 | }) 94 | .addCase(editUserDetails.rejected, (state, action) => { 95 | state.error = action.payload; 96 | toast.error(`Some went wrong, Please try again, ${state.error}`); 97 | }); 98 | }, 99 | }); 100 | 101 | export const { logout } = authSlice.actions; 102 | export const authReducer = authSlice.reducer; 103 | -------------------------------------------------------------------------------- /src/components/home/Comment.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | 4 | import { addUserComment, deleteUserComment, editUserComment } from "slices"; 5 | import { SendIcon } from "asset"; 6 | import { CommentItem } from "./CommentItem"; 7 | 8 | const Comment = ({ post, allUsers }) => { 9 | const [commentText, setCommentText] = useState(""); 10 | const [editingComment, setEditingComment] = useState(null); 11 | const { user } = useSelector((state) => state.auth); 12 | 13 | const dispatch = useDispatch(); 14 | 15 | const handleAddComment = () => { 16 | dispatch( 17 | addUserComment({ 18 | postId: post._id, 19 | commentData: { 20 | text: commentText, 21 | }, 22 | }) 23 | ); 24 | setCommentText(""); 25 | }; 26 | 27 | const handleEditComment = (comment) => { 28 | setEditingComment(comment); 29 | setCommentText(comment.text); 30 | }; 31 | 32 | const handleUpdateComment = () => { 33 | dispatch( 34 | editUserComment({ 35 | postId: post._id, 36 | commentId: editingComment._id, 37 | commentData: { 38 | text: editingComment.text, 39 | }, 40 | }) 41 | ); 42 | setEditingComment(null); 43 | setCommentText(""); 44 | }; 45 | 46 | const handleDeleteComment = (commentId) => { 47 | dispatch(deleteUserComment({ postId: post._id, commentId })); 48 | }; 49 | 50 | return ( 51 |
52 |
53 | {user?.avatarUrl ? ( 54 | user avatar 59 | ):(avtar)} 60 |
61 | 67 | editingComment 68 | ? setEditingComment({ 69 | ...editingComment, 70 | text: e.target.value, 71 | }) 72 | : setCommentText(e.target.value) 73 | } 74 | /> 75 | 85 |
86 |
87 | 88 |
89 | {post.comments && post.comments.length > 0 ? ( 90 | post.comments.map((comment) => ( 91 | 99 | )) 100 | ) : ( 101 |

Share your comment with us!

102 | )} 103 |
104 |
105 | ); 106 | }; 107 | 108 | export { Comment }; 109 | -------------------------------------------------------------------------------- /src/slices/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; 2 | 3 | import { 4 | followUser, 5 | getAllUsers, 6 | getUserByUsername, 7 | unFollowUser, 8 | } from "services"; 9 | import { editUserDetails } from "./authSlice"; 10 | import { toast } from "react-hot-toast"; 11 | 12 | const initialState = { 13 | allUsers: [], 14 | allUserStatus: "idle", 15 | allUsersError: null, 16 | userDetails: {}, 17 | userDetailsStatus: "idle", 18 | userDetailsError: null, 19 | followUserStatus: "idle", 20 | followUserError: null, 21 | }; 22 | 23 | export const fetchAllUsers = createAsyncThunk( 24 | "user/getAllUsers", 25 | async (_, { rejectWithValue }) => { 26 | try { 27 | const { data } = await getAllUsers(); 28 | return data.users; 29 | } catch (e) { 30 | console.error("Error:", e); 31 | return rejectWithValue(e.message); 32 | } 33 | } 34 | ); 35 | 36 | export const fetchUserDetails = createAsyncThunk( 37 | "post/getUserByUsername", 38 | async (username, { rejectWithValue }) => { 39 | try { 40 | const { data } = await getUserByUsername(username); 41 | return data.user; 42 | } catch (e) { 43 | return rejectWithValue(e.message); 44 | } 45 | } 46 | ); 47 | 48 | export const followUnfollowUser = createAsyncThunk( 49 | "post/followUnfollowUser", 50 | async ({ userId, isFollowing, dispatch }, { rejectWithValue }) => { 51 | try { 52 | const { data } = isFollowing 53 | ? await unFollowUser(userId) 54 | : await followUser(userId); 55 | dispatch(editUserDetails(data.user)); 56 | const toastMessage = isFollowing ? "Unfollowed!" : "Followed!"; 57 | toast.success(toastMessage); 58 | return data; 59 | } catch (e) { 60 | return rejectWithValue(e.message); 61 | } 62 | } 63 | ); 64 | 65 | const userSlice = createSlice({ 66 | name: "user", 67 | initialState, 68 | reducers: {}, 69 | extraReducers: (builder) => { 70 | builder 71 | .addCase(fetchAllUsers.pending, (state, action) => { 72 | state.allUserStatus = "pending"; 73 | }) 74 | .addCase(fetchAllUsers.fulfilled, (state, action) => { 75 | state.allUserStatus = "fulfilled"; 76 | state.allUsers = action.payload; 77 | }) 78 | .addCase(fetchAllUsers.rejected, (state, action) => { 79 | state.allUserStatus = "rejected"; 80 | state.allUsersError = action.payload; 81 | }) 82 | .addCase(fetchUserDetails.pending, (state) => { 83 | state.userDetailsStatus = "pending"; 84 | }) 85 | .addCase(fetchUserDetails.fulfilled, (state, action) => { 86 | state.userDetailsStatus = "fulfilled"; 87 | state.userDetails = action.payload; 88 | }) 89 | .addCase(fetchUserDetails.rejected, (state, action) => { 90 | state.userDetailsStatus = "rejected"; 91 | state.userDetailsError = action.payload; 92 | }) 93 | .addCase(followUnfollowUser.fulfilled, (state, action) => { 94 | const { user, followUser } = action.payload; 95 | state.followUserStatus = "fulfilled"; 96 | state.allUsers = state.allUsers.map((currentUser) => 97 | currentUser.username === user.username 98 | ? { ...user } 99 | : currentUser.username === followUser.username 100 | ? { ...followUser } 101 | : currentUser 102 | ); 103 | }) 104 | .addCase(followUnfollowUser.rejected, (state, action) => { 105 | state.followUserStatus = "rejected"; 106 | state.followUserError = action.payload; 107 | }); 108 | }, 109 | }); 110 | 111 | export const userReducer = userSlice.reducer; 112 | -------------------------------------------------------------------------------- /src/server.js: -------------------------------------------------------------------------------- 1 | import { Server, Model, RestSerializer } from "miragejs"; 2 | import { posts } from "./backend/db/posts"; 3 | import { users } from "./backend/db/users"; 4 | import { 5 | loginHandler, 6 | signupHandler, 7 | } from "./backend/controllers/AuthController"; 8 | import { 9 | createPostHandler, 10 | getAllpostsHandler, 11 | getPostHandler, 12 | deletePostHandler, 13 | editPostHandler, 14 | likePostHandler, 15 | dislikePostHandler, 16 | getAllUserPostsHandler, 17 | } from "./backend/controllers/PostController"; 18 | import { 19 | followUserHandler, 20 | getAllUsersHandler, 21 | getUserHandler, 22 | getBookmarkPostsHandler, 23 | bookmarkPostHandler, 24 | removePostFromBookmarkHandler, 25 | unfollowUserHandler, 26 | editUserHandler, 27 | } from "./backend/controllers/UserController"; 28 | 29 | import { 30 | getPostCommentsHandler, 31 | addPostCommentHandler, 32 | editPostCommentHandler, 33 | deletePostCommentHandler, 34 | upvotePostCommentHandler, 35 | downvotePostCommentHandler, 36 | } from "./backend/controllers/CommentsController"; 37 | 38 | export function makeServer({ environment = "development" } = {}) { 39 | return new Server({ 40 | serializers: { 41 | application: RestSerializer, 42 | }, 43 | environment, 44 | // TODO: Use Relationships to have named relational Data 45 | models: { 46 | post: Model, 47 | user: Model, 48 | }, 49 | 50 | // Runs on the start of the server 51 | seeds(server) { 52 | server.logging = false; 53 | users.forEach((item) => 54 | server.create("user", { 55 | ...item, 56 | followers: [], 57 | following: [], 58 | bookmarks: [], 59 | }) 60 | ); 61 | posts.forEach((item) => server.create("post", { ...item })); 62 | }, 63 | 64 | routes() { 65 | this.namespace = "api"; 66 | // auth routes (public) 67 | this.post("/auth/signup", signupHandler.bind(this)); 68 | this.post("/auth/login", loginHandler.bind(this)); 69 | 70 | // post routes (public) 71 | this.get("/posts", getAllpostsHandler.bind(this)); 72 | this.get("/posts/:postId", getPostHandler.bind(this)); 73 | this.get("/posts/user/:username", getAllUserPostsHandler.bind(this)); 74 | 75 | // post routes (private) 76 | this.post("/posts", createPostHandler.bind(this)); 77 | this.delete("/posts/:postId", deletePostHandler.bind(this)); 78 | this.post("/posts/edit/:postId", editPostHandler.bind(this)); 79 | this.post("/posts/like/:postId", likePostHandler.bind(this)); 80 | this.post("/posts/dislike/:postId", dislikePostHandler.bind(this)); 81 | 82 | // user routes (public) 83 | this.get("/users", getAllUsersHandler.bind(this)); 84 | this.get("/users/:username", getUserHandler.bind(this)); 85 | 86 | //post comments routes (public) 87 | this.get("/comments/:postId", getPostCommentsHandler.bind(this)); 88 | 89 | //post comments routes (private) 90 | this.post("/comments/add/:postId", addPostCommentHandler.bind(this)); 91 | this.post( 92 | "/comments/edit/:postId/:commentId", 93 | editPostCommentHandler.bind(this) 94 | ); 95 | this.delete( 96 | "/comments/delete/:postId/:commentId", 97 | deletePostCommentHandler.bind(this) 98 | ); 99 | this.post( 100 | "/comments/upvote/:postId/:commentId", 101 | upvotePostCommentHandler.bind(this) 102 | ); 103 | this.post( 104 | "/comments/downvote/:postId/:commentId", 105 | downvotePostCommentHandler.bind(this) 106 | ); 107 | 108 | // user routes (private) 109 | this.post("users/edit", editUserHandler.bind(this)); 110 | this.get("/users/bookmark", getBookmarkPostsHandler.bind(this)); 111 | this.post("/users/bookmark/:postId/", bookmarkPostHandler.bind(this)); 112 | this.post( 113 | "/users/remove-bookmark/:postId/", 114 | removePostFromBookmarkHandler.bind(this) 115 | ); 116 | this.post("/users/follow/:followUserId/", followUserHandler.bind(this)); 117 | this.post( 118 | "/users/unfollow/:followUserId/", 119 | unfollowUserHandler.bind(this) 120 | ); 121 | 122 | this.passthrough(); 123 | this.passthrough( 124 | "https://api.cloudinary.com/v1_1/dptfwcnro/image/upload", 125 | ["post"] 126 | ); 127 | }, 128 | }); 129 | } 130 | -------------------------------------------------------------------------------- /src/components/home/PostForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import Picker from "emoji-picker-react"; 4 | 5 | import { 6 | AddPhotoAlternateOutlinedIcon, 7 | AddReactionOutlinedIcon, 8 | ClearIcon, 9 | } from "asset"; 10 | import { addUserPost } from "slices"; 11 | import { useOutsideClick } from "hooks"; 12 | 13 | const PostForm = () => { 14 | const [postContent, setPostContent] = useState(""); 15 | const [selectedImageName, setSelectedImageName] = useState(null); 16 | const [showEmojiPicker, setShowEmojiPicker] = useState(false); 17 | 18 | const dispatch = useDispatch(); 19 | const { user } = useSelector((state) => state.auth); 20 | 21 | const domNode = useOutsideClick(() => setShowEmojiPicker(false)); 22 | 23 | const handleImageSelect = () => { 24 | const input = document.createElement("input"); 25 | input.type = "file"; 26 | input.accept = "image/*"; 27 | input.onchange = (e) => { 28 | const file = e.target.files[0]; 29 | const imageUrl = URL.createObjectURL(file); 30 | setSelectedImageName(imageUrl); 31 | }; 32 | input.click(); 33 | }; 34 | 35 | const handleEmojiClick = (emojiObj) => { 36 | const emoji = emojiObj.emoji; 37 | const updatedContent = postContent + emoji; 38 | setPostContent(updatedContent); 39 | setShowEmojiPicker(false); 40 | }; 41 | 42 | const handlePostClick = () => { 43 | const postData = { 44 | content: postContent, 45 | mediaURL: selectedImageName, 46 | userId: user._id, 47 | }; 48 | dispatch(addUserPost(postData)); 49 | setPostContent(""); 50 | setSelectedImageName(null); 51 | }; 52 | 53 | const isPostDisabled = postContent.trim() === ""; 54 | 55 | return ( 56 |
57 |
58 | {user.avatarUrl ? ( 59 | avatar 64 | ) : ( 65 | avtar 70 | )} 71 | 77 |
78 | 79 | {selectedImageName && ( 80 |
81 | Post 86 | 94 |
95 | )} 96 | 97 |
98 |
99 | 103 |
104 | setShowEmojiPicker(!showEmojiPicker)} 108 | /> 109 | {showEmojiPicker && ( 110 |
111 | 112 |
113 | )} 114 |
115 |
116 | 117 | 126 |
127 |
128 | ); 129 | }; 130 | 131 | export { PostForm }; 132 | -------------------------------------------------------------------------------- /src/backend/db/users.js: -------------------------------------------------------------------------------- 1 | import { formatDate } from "../utils/authUtils"; 2 | /** 3 | * User Database can be added here. 4 | * You can add default users of your wish with different attributes 5 | * */ 6 | 7 | export const users = [ 8 | { 9 | _id: "t7cZfWIp-q", 10 | firstName: "Chhakuli", 11 | lastName: "Zingare", 12 | username: "chhakulizingare", 13 | password: "chhakuli@123", 14 | bio: 15 | "Frontend Developer 💻✨Turning ideas into beautiful and interactive websites✨🚀", 16 | bookmarks: [], 17 | avatarUrl: 18 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685515809/SocialBuzz/photo_pd6e7o.jpg", 19 | website: "https://github.com/chhakuli123", 20 | createdAt: "2022-01-01T10:55:06+05:30", 21 | updatedAt: formatDate(), 22 | }, 23 | { 24 | _id: "t7cZfWIp-q", 25 | firstName: "Alice", 26 | lastName: "Jones", 27 | username: "alicejohnson", 28 | password: "alice123", 29 | bio: 30 | "Software Developer 👩‍💻💡.Passionate about technology and creating innovative software 🌟🚀", 31 | bookmarks: [], 32 | avatarUrl: 33 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685596157/SocialBuzz/images_vrd6b5.jpg", 34 | website: "https://alicejohnson.com/", 35 | createdAt: "2022-05-15T08:30:00+05:30", 36 | updatedAt: formatDate(), 37 | }, 38 | { 39 | _id: "79Gksh9otl", 40 | firstName: "Bob", 41 | lastName: "Smith", 42 | username: "bobsmith", 43 | password: "bob123", 44 | bio: 45 | "Web Designer 🎨🌈.Creating digital masterpieces with colors and imagination🎨✨", 46 | bookmarks: [], 47 | avatarUrl: 48 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595771/SocialBuzz/247-2479526_round-profile-picture-png-transparent-png_ukpjxm.png", 49 | website: "https://bobsmith.com/", 50 | createdAt: "2022-06-20T14:45:00+05:30", 51 | updatedAt: formatDate(), 52 | }, 53 | { 54 | _id: "1T6Be1QpLm", 55 | firstName: "Emma", 56 | lastName: "Davis", 57 | username: "emmadavis", 58 | password: "emma123", 59 | bio: 60 | "Photographer 📷✨Capturing moments that tell stories.Exploring the world through a lens. Join me on this visual journey! 🌍✨", 61 | bookmarks: [], 62 | avatarUrl: 63 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg", 64 | website: "https://emmadavis.com/", 65 | createdAt: "2022-04-10T12:15:00+05:30", 66 | updatedAt: formatDate(), 67 | }, 68 | { 69 | _id: "LCrc9f0Zl0", 70 | firstName: "James", 71 | lastName: "Wilson", 72 | username: "jameswilson", 73 | password: "james123", 74 | bio: "Musician 🎵🎸. Composing music that touches hearts 🎶✨", 75 | bookmarks: [], 76 | avatarUrl: 77 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595800/SocialBuzz/images_wxjv0c.jpg", 78 | website: "https://jameswilson.com/", 79 | createdAt: "2022-07-05T17:20:00+05:30", 80 | updatedAt: formatDate(), 81 | }, 82 | { 83 | _id: "o5gzWjEeX_", 84 | firstName: "Lily", 85 | lastName: "Anderson", 86 | username: "lilyanderson", 87 | password: "lily123", 88 | bio: "Graphic Designer 🎨✏️\nFrom pixels to perfection! ✨🌟", 89 | bookmarks: [], 90 | avatarUrl: 91 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595837/SocialBuzz/images_b9qohl.jpg", 92 | website: "https://lilyanderson.com/", 93 | createdAt: "2022-03-01T09:00:00+05:30", 94 | updatedAt: formatDate(), 95 | }, 96 | { 97 | _id: "M1NR81Bzlz", 98 | firstName: "Oliver", 99 | lastName: "Taylor", 100 | username: "olivertaylor", 101 | password: "oliver123", 102 | bio: 103 | "Travel Blogger ✈️🌍\nExploring the world, one adventure at a time 🗺️✨", 104 | bookmarks: [], 105 | avatarUrl: 106 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595881/SocialBuzz/images_tidrd1.jpg", 107 | website: "https://olivertaylor.com/", 108 | createdAt: "2022-08-10T13:55:00+05:30", 109 | updatedAt: formatDate(), 110 | }, 111 | { 112 | _id: "qq8zWjEeXd", 113 | firstName: "Sophie", 114 | lastName: "Clark", 115 | username: "sophieclark", 116 | password: "sophie123", 117 | bio: 118 | "Fitness Enthusiast 💪🏋️‍♀️🌱\nEmpowering and inspiring others to lead a healthy and balanced lifestyle💚✨", 119 | bookmarks: [], 120 | avatarUrl: 121 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595920/SocialBuzz/images_qynsis.jpg", 122 | website: "https://sophieclark.com/", 123 | createdAt: "2022-09-25T10:10:00+05:30", 124 | updatedAt: formatDate(), 125 | }, 126 | ]; 127 | -------------------------------------------------------------------------------- /src/components/home/PostModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import EmojiPicker from "emoji-picker-react"; 4 | 5 | import { addUserPost } from "slices"; 6 | import { 7 | AddPhotoAlternateOutlinedIcon, 8 | AddReactionOutlinedIcon, 9 | ClearIcon, 10 | } from "asset"; 11 | import { useOutsideClick } from "hooks"; 12 | 13 | const PostModal = ({ onClose }) => { 14 | const [newPostContent, setNewPostContent] = useState(""); 15 | const [newPostImage, setNewPostImage] = useState(null); 16 | const [showEmojiPicker, setShowEmojiPicker] = useState(false); 17 | 18 | const dispatch = useDispatch(); 19 | 20 | const domNode = useOutsideClick(() => setShowEmojiPicker(false)); 21 | 22 | const handleCreatePost = () => { 23 | const newPost = { 24 | content: newPostContent, 25 | mediaURL: newPostImage, 26 | }; 27 | dispatch(addUserPost(newPost)) 28 | .then(() => { 29 | onClose(); 30 | }) 31 | .catch((error) => { 32 | console.error("Error creating post:", error); 33 | }); 34 | }; 35 | 36 | const handleImageChange = (e) => { 37 | const input = document.createElement("input"); 38 | input.type = "file"; 39 | input.accept = "image/*"; 40 | input.onchange = (e) => { 41 | const file = e.target.files[0]; 42 | const imageUrl = URL.createObjectURL(file); 43 | setNewPostImage(imageUrl); 44 | }; 45 | input.click(); 46 | }; 47 | 48 | const handleEmojiClick = (emojiObj) => { 49 | const emoji = emojiObj.emoji; 50 | const updatedContent = newPostContent + emoji; 51 | setNewPostContent(updatedContent); 52 | setShowEmojiPicker(false); 53 | }; 54 | 55 | const isPostDisabled = newPostContent.trim() === ""; 56 | 57 | return ( 58 |
59 |
60 |
61 |

Create Post

62 | 65 |
66 |