├── 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 | You need to enable JavaScript to run this app.
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 |
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 |
15 |
16 |
17 |
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 |
18 | dispatch(
19 | followUnfollowUser({
20 | userId: userDetails._id,
21 | isFollowing: isFollowing,
22 | dispatch,
23 | })
24 | )
25 | }
26 | >
27 | Following
28 |
29 | ) : (
30 |
33 | dispatch(
34 | followUnfollowUser({
35 | userId: userDetails._id,
36 | isFollowing: isFollowing,
37 | dispatch,
38 | })
39 | )
40 | }
41 | >
42 | + Follow
43 |
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 |
10 | {[...Array(3)].map((_, index) => (
11 |
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 |
18 | {label}
19 |
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 |
20 |
21 |
22 |
navigate("/")}
24 | className="flex items-center cursor-pointer"
25 | >
26 | {theme === "light" ? (
27 |
32 | ) : (
33 |
38 | )}
39 |
40 | SocialBuzz
41 |
42 |
43 |
44 |
45 | {theme === "dark" ? (
46 |
47 | ) : (
48 |
49 | )}
50 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
19 | ) : (
20 |
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 |
handleNavigate(user?.username)}
58 | />
59 |
handleNavigate(user?.username)}
62 | >
63 |
64 | {user?.firstName} {user?.lastName}
65 |
66 |
@{user?.username}
67 |
68 |
69 |
72 | dispatch(
73 | followUnfollowUser({
74 | userId: user?._id,
75 | isFollowing: false,
76 | dispatch,
77 | })
78 | )
79 | }
80 | >
81 | +
82 | Follow
83 |
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 |
36 | ) : (
37 |
42 | )
43 | ) : commentUser?.avatarUrl ? (
44 |
49 | ) : (
50 |
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 |
59 | ):(
)}
60 |
61 |
67 | editingComment
68 | ? setEditingComment({
69 | ...editingComment,
70 | text: e.target.value,
71 | })
72 | : setCommentText(e.target.value)
73 | }
74 | />
75 |
79 | {editingComment ? (
80 | Update
81 | ) : (
82 |
83 | )}
84 |
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 |
64 | ) : (
65 |
70 | )}
71 |
77 |
78 |
79 | {selectedImageName && (
80 |
81 |
86 |
{
89 | setSelectedImageName(null);
90 | }}
91 | >
92 |
93 |
94 |
95 | )}
96 |
97 |
98 |
99 |
103 |
104 |
setShowEmojiPicker(!showEmojiPicker)}
108 | />
109 | {showEmojiPicker && (
110 |
113 | )}
114 |
115 |
116 |
117 |
124 | Post
125 |
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 |
63 |
64 |
65 |
66 |
125 |
126 | );
127 | };
128 |
129 | export { PostModal };
130 |
--------------------------------------------------------------------------------
/src/components/suggetion&search/SearchBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 |
4 | import { SearchOutlinedIcon } from "asset";
5 | import { fetchAllUsers } from "slices";
6 | import { useNavigate } from "react-router";
7 |
8 | const SearchBar = () => {
9 | const [searchTerm, setSearchTerm] = useState("");
10 | const [filteredUsers, setFilteredUsers] = useState([]);
11 | const { allUsers } = useSelector((state) => state.user);
12 | const { user } = useSelector((state) => state.auth);
13 |
14 | const dispatch = useDispatch();
15 | const navigate = useNavigate();
16 |
17 | useEffect(() => {
18 | dispatch(fetchAllUsers());
19 | }, [dispatch]);
20 |
21 | const handleInputChange = (event) => {
22 | const value = event.target.value;
23 | setSearchTerm(value);
24 | const filteredResults = allUsers.filter(
25 | (user) =>
26 | user.firstName.toLowerCase().includes(value.toLowerCase()) ||
27 | user.lastName.toLowerCase().includes(value.toLowerCase()) ||
28 | user.username.toLowerCase().includes(value.toLowerCase())
29 | );
30 | setFilteredUsers(filteredResults);
31 | };
32 |
33 | const clearSearch = () => {
34 | setSearchTerm("");
35 | setFilteredUsers([]);
36 | };
37 |
38 | const handleNavigate = (username) => {
39 | user.username === username
40 | ? navigate("/user-profile")
41 | : navigate(`/profile/${username}`);
42 | };
43 | return (
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 |
58 | {filteredUsers.length > 0 && searchTerm.length > 0 ? (
59 |
60 | {filteredUsers.map((user) => (
61 |
{
65 | handleNavigate(user?.username);
66 | }}
67 | >
68 | {user.avatarUrl ? (
69 |
74 | ) : (
75 |
80 | )}
81 |
82 |
83 | {user?.firstName} {user?.lastName}
84 |
85 |
@{user?.username}
86 |
87 |
88 | ))}
89 |
90 |
94 | Clear Search
95 |
96 |
97 |
98 | ) : (
99 | searchTerm.length > 0 && (
100 |
101 |
102 |
103 | 🕵️♀️ User not found
104 |
105 |
106 |
107 |
111 | Clear Search
112 |
113 |
114 |
115 | )
116 | )}
117 |
118 |
119 | );
120 | };
121 |
122 | export { SearchBar };
123 |
--------------------------------------------------------------------------------
/src/components/home/EditPostModel.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import EmojiPicker from "emoji-picker-react";
4 |
5 | import {
6 | AddPhotoAlternateOutlinedIcon,
7 | AddReactionOutlinedIcon,
8 | ClearIcon,
9 | } from "asset";
10 | import { editUserPost } from "slices";
11 | import { useOutsideClick } from "hooks";
12 |
13 | const EditPostModal = ({ post, onClose }) => {
14 | const [updatedPostContent, setUpdatedPostContent] = useState(post.content);
15 | const [updatedPostImage, setUpdatedPostImage] = useState(post.mediaURL);
16 | const [showEmojiPicker, setShowEmojiPicker] = useState(false);
17 |
18 | const dispatch = useDispatch();
19 |
20 | const domNode = useOutsideClick(() => setShowEmojiPicker(false));
21 |
22 | const handleEditPost = () => {
23 | const updatedPost = {
24 | ...post,
25 | content: updatedPostContent,
26 | mediaURL: updatedPostImage,
27 | };
28 | dispatch(editUserPost(updatedPost))
29 | .then(() => {
30 | onClose();
31 | })
32 | .catch((error) => {
33 | console.error("Error updating post:", error);
34 | });
35 | };
36 |
37 | const handleContentChange = (e) => {
38 | setUpdatedPostContent(e.target.value);
39 | };
40 |
41 | const handleImageChange = (e) => {
42 | const input = document.createElement("input");
43 | input.type = "file";
44 | input.accept = "image/*";
45 | input.onchange = (e) => {
46 | const file = e.target.files[0];
47 | const imageUrl = URL.createObjectURL(file);
48 | setUpdatedPostImage(imageUrl);
49 | };
50 | input.click();
51 | };
52 |
53 | const handleEmojiClick = (emojiObj) => {
54 | const emoji = emojiObj.emoji;
55 | const updatedContent = updatedPostContent + emoji;
56 | setUpdatedPostContent(updatedContent);
57 | setShowEmojiPicker(false);
58 | };
59 |
60 | const handleDeleteImage = () => {
61 | setUpdatedPostImage(null);
62 | };
63 |
64 | return (
65 |
66 |
67 |
68 |
Edit Post
69 |
70 |
71 |
72 |
73 |
79 | {updatedPostImage && (
80 |
81 |
86 |
90 |
91 |
92 |
93 | )}
94 |
95 |
99 |
100 |
setShowEmojiPicker(!showEmojiPicker)}
104 | />
105 | {showEmojiPicker && (
106 | e.stopPropagation()}
109 | >
110 |
115 |
116 | )}
117 |
118 |
119 |
120 |
121 |
125 | Update
126 |
127 |
128 |
129 |
130 | );
131 | };
132 |
133 | export { EditPostModal };
134 |
--------------------------------------------------------------------------------
/src/components/authentication/LoginForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import { toast } from "react-hot-toast";
5 |
6 | import { InputField } from "./InputFeild";
7 | import { loginUser } from "slices";
8 |
9 | const LoginForm = () => {
10 | const [userLoginDetails, setUserLoginDetails] = useState({
11 | username: "",
12 | password: "",
13 | hidePassword: true,
14 | });
15 | const dispatch = useDispatch();
16 | const navigate = useNavigate();
17 | const theme = useSelector((state) => state.theme.theme);
18 |
19 | const loginFormSubmitHandler = async (event) => {
20 | event.preventDefault();
21 |
22 | if (userLoginDetails.username && userLoginDetails.password !== "") {
23 | const response = await dispatch(loginUser(userLoginDetails));
24 | if (response?.payload.encodedToken) {
25 | navigate("/");
26 | }
27 | } else {
28 | toast.error("Please enter valid details");
29 | }
30 | };
31 |
32 | const handlePasswordToggle = () => {
33 | setUserLoginDetails((prevState) => ({
34 | ...prevState,
35 | hidePassword: !prevState.hidePassword,
36 | }));
37 | };
38 |
39 | const loginAsGuestHandler = async () => {
40 | const guestUserDetails = {
41 | username: "chhakulizingare",
42 | password: "chhakuli@123",
43 | };
44 | setUserLoginDetails(guestUserDetails);
45 |
46 | const response = await dispatch(loginUser(guestUserDetails));
47 | if (response?.payload.encodedToken) {
48 | navigate("/");
49 | } else {
50 | toast.error("Please enter valid details");
51 | }
52 | };
53 |
54 | return (
55 |
56 |
57 |
62 |
63 |
64 |
65 |
66 | {theme === "light" ? (
67 |
72 | ) : (
73 |
78 | )}
79 |
80 | SocialBuzz
81 |
82 |
83 |
Login
84 |
135 |
136 |
137 | );
138 | };
139 |
140 | export { LoginForm };
141 |
--------------------------------------------------------------------------------
/src/components/ProfileModal/ProfileModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { useDispatch } from "react-redux";
3 |
4 | import { AddPhotoAlternateOutlinedIcon, ClearIcon } from "asset";
5 | import { editUserDetails } from "slices";
6 | import { toast } from "react-hot-toast";
7 |
8 | const ProfileModal = ({ setProfileModal, user }) => {
9 | const dispatch = useDispatch();
10 |
11 | const [formData, setFormData] = useState({
12 | firstName: "",
13 | lastName: "",
14 | bio: "",
15 | website: "",
16 | avatarUrl: "",
17 | selectedImage: "",
18 | });
19 |
20 | useEffect(() => {
21 | const initializeFormData = () => {
22 | if (user) {
23 | setFormData({
24 | firstName: user.firstName,
25 | lastName: user.lastName,
26 | bio: user.bio,
27 | website: user.website,
28 | avatarUrl: user.avatarUrl,
29 | selectedImage: "",
30 | });
31 | }
32 | };
33 |
34 | initializeFormData();
35 | }, [user, setFormData]);
36 |
37 |
38 | const handleInputChange = (e) => {
39 | setFormData({ ...formData, [e.target.name]: e.target.value });
40 | };
41 |
42 | const handleImageChange = (e) => {
43 | const file = e.target.files[0];
44 | setFormData({
45 | ...formData,
46 | selectedImage: URL.createObjectURL(file),
47 | });
48 | };
49 |
50 | const handleSave = () => {
51 | const updatedUserData = {
52 | ...formData,
53 | _id: user?._id,
54 | username: user?.username,
55 | avatarUrl: formData.selectedImage || user?.avatarUrl,
56 | };
57 | dispatch(editUserDetails(updatedUserData));
58 | toast.success("Details Edited Successfully");
59 | setProfileModal(false);
60 | };
61 |
62 | const handleClose = () => {
63 | setFormData({
64 | firstName: "",
65 | lastName: "",
66 | bio: "",
67 | website: "",
68 | avatarUrl: "",
69 | selectedImage: "",
70 | });
71 | setProfileModal(false);
72 | };
73 |
74 | return (
75 |
76 |
77 |
78 |
Edit Profile
79 |
83 |
84 |
85 |
86 |
87 |
88 | {formData.selectedImage ? (
89 |
94 | ) : (
95 |
96 |
101 |
102 | )}
103 |
104 |
108 |
109 |
116 |
117 |
118 |
119 |
120 |
148 |
149 |
150 |
151 | Bio
152 |
153 |
160 |
161 |
162 |
163 |
164 | Website
165 |
166 |
174 |
175 |
176 |
177 |
181 | Update
182 |
183 |
184 |
185 |
186 | );
187 | };
188 |
189 | export { ProfileModal };
190 |
--------------------------------------------------------------------------------
/src/components/home/Post.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 |
4 | import {
5 | MoreVertIcon,
6 | ThumbUpOffAltIcon,
7 | ThumbUpIcon,
8 | BookmarkBorderIcon,
9 | MapsUgcOutlinedIcon,
10 | BookmarkIcon,
11 | } from "asset";
12 | import {
13 | bookmarkUnbookmarkUserPost,
14 | deleteUserPost,
15 | likeDislikeUserPost,
16 | } from "slices";
17 | import { useOutsideClick } from "hooks";
18 | import { EditPostModal } from "./EditPostModel";
19 | import { Comment } from "./Comment";
20 | import { useNavigate } from "react-router";
21 |
22 | const Post = ({ post, allUsers }) => {
23 | const [showOptions, setShowOptions] = useState(false);
24 | const [showCommentSection, setShowCommentSection] = useState(false);
25 | const [showEditModal, setShowEditModal] = useState(false);
26 |
27 | const { user } = useSelector((state) => state.auth);
28 | const { bookmarks } = useSelector((state) => state.post);
29 | const dispatch = useDispatch();
30 | const navigate = useNavigate();
31 |
32 | const domNode = useOutsideClick(() => {
33 | setShowOptions(false);
34 | });
35 |
36 | const handleEditIconClick = () => {
37 | setShowOptions(false);
38 | setShowEditModal(true);
39 | };
40 | const { _id } = post;
41 | const postUser = allUsers.find((user) => user.username === post.username);
42 | const isOwnedByUser = post.username === user.username;
43 |
44 | const isLiked = post?.likes?.likedBy?.some(
45 | (likeUser) => likeUser?._id === user?._id
46 | );
47 | const isBookmarked = bookmarks?.some((post) => post._id === _id);
48 |
49 | const handleNavigate = (username) => {
50 | if (isOwnedByUser) {
51 | navigate("/user-profile");
52 | } else {
53 | navigate(`/profile/${username}`);
54 | }
55 | };
56 |
57 | return (
58 |
62 |
{
65 | e.stopPropagation();
66 | handleNavigate(isOwnedByUser ? user.username : postUser?.username);
67 | }}
68 | >
69 | {/* User avatar */}
70 | {isOwnedByUser && user.avatarUrl ? (
71 |
76 | ) : postUser?.avatarUrl ? (
77 |
82 | ) : (
83 |
88 | )}
89 |
90 | {/* User details */}
91 |
92 | {isOwnedByUser ? (
93 |
94 | {user.firstName} {user.lastName}
95 |
96 | ) : (
97 | postUser && (
98 |
99 | {postUser.firstName} {postUser.lastName}
100 |
101 | )
102 | )}
103 |
104 | {isOwnedByUser ? user.username : post.username} -{" "}
105 | {new Date(post.createdAt).toLocaleDateString("en-US", {
106 | day: "numeric",
107 | month: "short",
108 | year: "numeric",
109 | })}
110 |
111 |
112 |
113 | {/* More options */}
114 |
115 |
{
118 | e.stopPropagation();
119 | setShowOptions(!showOptions);
120 | }} />
121 | {showOptions && (
122 |
123 |
{
126 | e.stopPropagation();
127 | handleEditIconClick();
128 | }}
129 | >
130 | Edit
131 |
132 |
{
134 | e.stopPropagation();
135 | dispatch(deleteUserPost(post && post?._id));
136 | }}
137 | className="options cursor-pointer py-1 px-4 rounded-lg hover:bg-activeGreen"
138 | >
139 | Delete
140 |
141 |
142 | )}
143 |
144 |
145 |
146 |
{post.content}
147 |
148 | {/* Post Image */}
149 | {post.mediaURL && (
150 |
155 | )}
156 |
157 | {/* Like and Bookmark */}
158 |
159 |
{
162 | dispatch(
163 | likeDislikeUserPost({ postId: post._id, isLiked: isLiked })
164 | );
165 | }}
166 | >
167 | {isLiked ? (
168 |
169 | ) : (
170 |
171 | )}
172 | {post.likes?.likeCount}
173 |
174 |
175 |
setShowCommentSection(!showCommentSection)}
178 | />
179 | {post?.comments?.length}
180 |
181 |
184 | dispatch(bookmarkUnbookmarkUserPost({ postId: _id, isBookmarked }))
185 | }
186 | >
187 | {isBookmarked ? (
188 |
189 | ) : (
190 |
191 | )}
192 |
193 |
194 |
195 | {/* Comments */}
196 | {showCommentSection && (
197 |
203 | )}
204 |
205 | {/* Edit post modal */}
206 | {showEditModal && (
207 |
setShowEditModal(false)} />
208 | )}
209 |
210 | );
211 | };
212 |
213 | export { Post };
214 |
--------------------------------------------------------------------------------
/src/backend/controllers/PostController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 | import { v4 as uuid } from "uuid";
3 | import { formatDate, requiresAuth } from "../utils/authUtils";
4 |
5 | /**
6 | * All the routes related to post are present here.
7 | * */
8 |
9 | /**
10 | * This handler handles gets all posts in the db.
11 | * send GET Request at /api/posts
12 | * */
13 |
14 | export const getAllpostsHandler = function () {
15 | return new Response(200, {}, { posts: this.db.posts });
16 | };
17 |
18 | /**
19 | * This handler gets post by postId in the db.
20 | * send GET Request at /api/posts/:postId
21 | * */
22 |
23 | export const getPostHandler = function (schema, request) {
24 | const postId = request.params.postId;
25 | try {
26 | const post = schema.posts.findBy({ _id: postId }).attrs;
27 | return new Response(200, {}, { post });
28 | } catch (error) {
29 | return new Response(
30 | 500,
31 | {},
32 | {
33 | error,
34 | }
35 | );
36 | }
37 | };
38 |
39 | /**
40 | * This handler gets posts of a user in the db.
41 | * send GET Request at /api/posts/user/:username
42 | * */
43 |
44 | export const getAllUserPostsHandler = function (schema, request) {
45 | const { username } = request.params;
46 | try {
47 | const posts = schema.posts.where({ username })?.models;
48 | return new Response(200, {}, { posts });
49 | } catch (error) {
50 | return new Response(
51 | 500,
52 | {},
53 | {
54 | error,
55 | }
56 | );
57 | }
58 | };
59 |
60 | /**
61 | * This handler handles creating a post in the db.
62 | * send POST Request at /api/user/posts/
63 | * body contains {content}
64 | * */
65 |
66 | export const createPostHandler = function (schema, request) {
67 | const user = requiresAuth.call(this, request);
68 | try {
69 | if (!user) {
70 | return new Response(
71 | 404,
72 | {},
73 | {
74 | errors: [
75 | "The username you entered is not Registered. Not Found error",
76 | ],
77 | }
78 | );
79 | }
80 | const { postData } = JSON.parse(request.requestBody);
81 | const post = {
82 | _id: uuid(),
83 | ...postData,
84 | comments: [],
85 | likes: {
86 | likeCount: 0,
87 | likedBy: [],
88 | dislikedBy: [],
89 | },
90 | username: user.username,
91 | createdAt: formatDate(),
92 | updatedAt: formatDate(),
93 | };
94 | this.db.posts.insert(post);
95 | return new Response(201, {}, { posts: this.db.posts });
96 | } catch (error) {
97 | return new Response(
98 | 500,
99 | {},
100 | {
101 | error,
102 | }
103 | );
104 | }
105 | };
106 |
107 | /**
108 | * This handler handles updating a post in the db.
109 | * send POST Request at /api/posts/edit/:postId
110 | * body contains { postData }
111 | * */
112 | export const editPostHandler = function (schema, request) {
113 | const user = requiresAuth.call(this, request);
114 | try {
115 | if (!user) {
116 | return new Response(
117 | 404,
118 | {},
119 | {
120 | errors: [
121 | "The username you entered is not Registered. Not Found error",
122 | ],
123 | }
124 | );
125 | }
126 | const postId = request.params.postId;
127 | const { postData } = JSON.parse(request.requestBody);
128 | let post = schema.posts.findBy({ _id: postId }).attrs;
129 | if (post.username !== user.username) {
130 | return new Response(
131 | 400,
132 | {},
133 | {
134 | errors: ["Cannot edit a Post doesn't belong to the logged in User."],
135 | }
136 | );
137 | }
138 | post = { ...post, ...postData };
139 | this.db.posts.update({ _id: postId }, post);
140 | return new Response(201, {}, { posts: this.db.posts });
141 | } catch (error) {
142 | return new Response(
143 | 500,
144 | {},
145 | {
146 | error,
147 | }
148 | );
149 | }
150 | };
151 |
152 | /**
153 | * This handler handles liking a post in the db.
154 | * send POST Request at /api/posts/like/:postId
155 | * */
156 |
157 | export const likePostHandler = function (schema, request) {
158 | const user = requiresAuth.call(this, request);
159 | try {
160 | if (!user) {
161 | return new Response(
162 | 404,
163 | {},
164 | {
165 | errors: [
166 | "The username you entered is not Registered. Not Found error",
167 | ],
168 | }
169 | );
170 | }
171 | const postId = request.params.postId;
172 | const post = schema.posts.findBy({ _id: postId }).attrs;
173 | if (post.likes.likedBy.some((currUser) => currUser._id === user._id)) {
174 | return new Response(
175 | 400,
176 | {},
177 | { errors: ["Cannot like a post that is already liked. "] }
178 | );
179 | }
180 | post.likes.dislikedBy = post.likes.dislikedBy.filter(
181 | (currUser) => currUser._id !== user._id
182 | );
183 | post.likes.likeCount += 1;
184 | post.likes.likedBy.push(JSON.parse(JSON.stringify(user)));
185 | this.db.posts.update({ _id: postId }, { ...post, updatedAt: formatDate() });
186 | return new Response(201, {}, { posts: this.db.posts });
187 | } catch (error) {
188 | return new Response(
189 | 500,
190 | {},
191 | {
192 | error,
193 | }
194 | );
195 | }
196 | };
197 |
198 | /**
199 | * This handler handles disliking a post in the db.
200 | * send POST Request at /api/posts/dislike/:postId
201 | * */
202 |
203 | export const dislikePostHandler = function (schema, request) {
204 | const user = requiresAuth.call(this, request);
205 | try {
206 | if (!user) {
207 | return new Response(
208 | 404,
209 | {},
210 | {
211 | errors: [
212 | "The username you entered is not Registered. Not Found error",
213 | ],
214 | }
215 | );
216 | }
217 | const postId = request.params.postId;
218 | let post = schema.posts.findBy({ _id: postId }).attrs;
219 | if (post.likes.likeCount === 0) {
220 | return new Response(
221 | 400,
222 | {},
223 | { errors: ["Cannot decrement like less than 0."] }
224 | );
225 | }
226 | if (post.likes.dislikedBy.some((currUser) => currUser._id === user._id)) {
227 | return new Response(
228 | 400,
229 | {},
230 | { errors: ["Cannot dislike a post that is already disliked. "] }
231 | );
232 | }
233 | post.likes.likeCount -= 1;
234 | const updatedLikedBy = post.likes.likedBy.filter(
235 | (currUser) => currUser._id !== user._id
236 | );
237 | post.likes.dislikedBy.push(JSON.parse(JSON.stringify(user)));
238 | post = { ...post, likes: { ...post.likes, likedBy: updatedLikedBy } };
239 | this.db.posts.update({ _id: postId }, { ...post, updatedAt: formatDate() });
240 | return new Response(201, {}, { posts: this.db.posts });
241 | } catch (error) {
242 | return new Response(
243 | 500,
244 | {},
245 | {
246 | error,
247 | }
248 | );
249 | }
250 | };
251 |
252 | /**
253 | * This handler handles deleting a post in the db.
254 | * send DELETE Request at /api/user/posts/:postId
255 | * */
256 | export const deletePostHandler = function (schema, request) {
257 | const user = requiresAuth.call(this, request);
258 | try {
259 | if (!user) {
260 | return new Response(
261 | 404,
262 | {},
263 | {
264 | errors: [
265 | "The username you entered is not Registered. Not Found error",
266 | ],
267 | }
268 | );
269 | }
270 | const postId = request.params.postId;
271 | let post = schema.posts.findBy({ _id: postId }).attrs;
272 | if (post.username !== user.username) {
273 | return new Response(
274 | 400,
275 | {},
276 | {
277 | errors: [
278 | "Cannot delete a Post doesn't belong to the logged in User.",
279 | ],
280 | }
281 | );
282 | }
283 | this.db.posts.remove({ _id: postId });
284 | return new Response(201, {}, { posts: this.db.posts });
285 | } catch (error) {
286 | return new Response(
287 | 500,
288 | {},
289 | {
290 | error,
291 | }
292 | );
293 | }
294 | };
295 |
--------------------------------------------------------------------------------
/src/backend/controllers/CommentsController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 | import { formatDate, requiresAuth } from "../utils/authUtils";
3 | import { v4 as uuid } from "uuid";
4 |
5 | /**
6 | * All the routes related to post comments are present here.
7 | * */
8 |
9 | /**
10 | * This handler handles getting all comments for a particular post in the db.
11 | * send GET Request at /api/comments/:postId
12 | * */
13 |
14 | export const getPostCommentsHandler = function (schema, request) {
15 | const postId = request.params.postId;
16 | try {
17 | const post = schema.posts.findBy({ _id: postId }).attrs;
18 | return new Response(200, {}, { comments: post.comments });
19 | } catch (error) {
20 | return new Response(
21 | 500,
22 | {},
23 | {
24 | error,
25 | }
26 | );
27 | }
28 | };
29 |
30 | /**
31 | * This handler handles adding a comment to a particular post in the db.
32 | * send POST Request at /api/comments/add/:postId
33 | * */
34 |
35 | export const addPostCommentHandler = function (schema, request) {
36 | const user = requiresAuth.call(this, request);
37 | try {
38 | if (!user) {
39 | return new Response(
40 | 404,
41 | {},
42 | {
43 | errors: [
44 | "The username you entered is not Registered. Not Found error",
45 | ],
46 | }
47 | );
48 | }
49 | const { postId } = request.params;
50 | const { commentData } = JSON.parse(request.requestBody);
51 |
52 | const comment = {
53 | _id: uuid(),
54 | ...commentData,
55 | username: user.username,
56 | fullName: user.fullName,
57 | profileAvatar: user.profileAvatar,
58 | votes: { upvotedBy: [], downvotedBy: [] },
59 | createdAt: formatDate(),
60 | updatedAt: formatDate(),
61 | };
62 | const post = schema.posts.findBy({ _id: postId }).attrs;
63 | post.comments.push(comment);
64 |
65 | this.db.posts.update({ _id: postId }, post);
66 | return new Response(201, {}, { posts: this.db.posts });
67 | } catch (error) {
68 | return new Response(
69 | 500,
70 | {},
71 | {
72 | error,
73 | }
74 | );
75 | }
76 | };
77 |
78 | /**
79 | * This handler handles editing a comment to a particular post in the db.
80 | * send POST Request at /api/comments/edit/:postId/:commentId
81 | * */
82 |
83 | export const editPostCommentHandler = function (schema, request) {
84 | const user = requiresAuth.call(this, request);
85 | try {
86 | if (!user) {
87 | return new Response(
88 | 404,
89 | {},
90 | {
91 | errors: [
92 | "The username you entered is not Registered. Not Found error",
93 | ],
94 | }
95 | );
96 | }
97 | const { postId, commentId } = request.params;
98 | const { commentData } = JSON.parse(request.requestBody);
99 | const post = schema.posts.findBy({ _id: postId }).attrs;
100 | const commentIndex = post.comments.findIndex(
101 | (comment) => comment._id === commentId
102 | );
103 | if (post.comments[commentIndex].username !== user.username) {
104 | return new Response(
105 | 400,
106 | {},
107 | { errors: ["Cannot edit a comment doesn't belong to the User."] }
108 | );
109 | }
110 | post.comments[commentIndex] = {
111 | ...post.comments[commentIndex],
112 | ...commentData,
113 | updatedAt: formatDate(),
114 | };
115 | this.db.posts.update({ _id: postId }, post);
116 | return new Response(201, {}, { posts: this.db.posts });
117 | } catch (error) {
118 | return new Response(
119 | 500,
120 | {},
121 | {
122 | error,
123 | }
124 | );
125 | }
126 | };
127 |
128 | /**
129 | * This handler handles deleting a comment to a particular post in the db.
130 | * send DELETE Request at /api/comments/delete/:postId/:commentId
131 | * */
132 |
133 | export const deletePostCommentHandler = function (schema, request) {
134 | const user = requiresAuth.call(this, request);
135 | try {
136 | if (!user) {
137 | return new Response(
138 | 404,
139 | {},
140 | {
141 | errors: [
142 | "The username you entered is not Registered. Not Found error",
143 | ],
144 | }
145 | );
146 | }
147 | const { postId, commentId } = request.params;
148 | const post = schema.posts.findBy({ _id: postId }).attrs;
149 | const commentIndex = post.comments.findIndex(
150 | (comment) => comment._id === commentId
151 | );
152 | if (
153 | post.comments[commentIndex].username !== user.username &&
154 | post.username !== user.username
155 | ) {
156 | return new Response(
157 | 400,
158 | {},
159 | { errors: ["Cannot delete a comment doesn't belong to the User."] }
160 | );
161 | }
162 | post.comments = post.comments.filter(
163 | (comment) => comment._id !== commentId
164 | );
165 | this.db.posts.update({ _id: postId }, post);
166 | return new Response(201, {}, { posts: this.db.posts });
167 | } catch (error) {
168 | return new Response(
169 | 500,
170 | {},
171 | {
172 | error,
173 | }
174 | );
175 | }
176 | };
177 |
178 | /**
179 | * This handler handles upvoting a comment of a post in the db.
180 | * send POST Request at /api/comments/upvote/:postId/:commentId
181 | * */
182 |
183 | export const upvotePostCommentHandler = function (schema, request) {
184 | const user = requiresAuth.call(this, request);
185 | try {
186 | if (!user) {
187 | return new Response(
188 | 404,
189 | {},
190 | {
191 | errors: [
192 | "The username you entered is not Registered. Not Found error",
193 | ],
194 | }
195 | );
196 | }
197 | const { postId, commentId } = request.params;
198 | const post = schema.posts.findBy({ _id: postId }).attrs;
199 | const commentIndex = post.comments.findIndex(
200 | (comment) => comment._id === commentId
201 | );
202 |
203 | if (
204 | post.comments[commentIndex].votes.upvotedBy.some(
205 | (currUser) => currUser._id === user._id
206 | )
207 | ) {
208 | return new Response(
209 | 400,
210 | {},
211 | { errors: ["Cannot upvote a post that is already upvoted. "] }
212 | );
213 | }
214 | post.comments[commentIndex].votes.downvotedBy = post.comments[
215 | commentIndex
216 | ].votes.downvotedBy.filter((currUser) => currUser._id !== user._id);
217 | post.comments[commentIndex].votes.upvotedBy.push(user);
218 | this.db.posts.update({ _id: postId }, { ...post, updatedAt: formatDate() });
219 | return new Response(201, {}, { comments: post.comments });
220 | } catch (error) {
221 | return new Response(
222 | 500,
223 | {},
224 | {
225 | error,
226 | }
227 | );
228 | }
229 | };
230 |
231 | /**
232 | * This handler handles downvoting a comment of a post in the db.
233 | * send POST Request at /api/comments/downvote/:postId/:commentId
234 | * */
235 |
236 | export const downvotePostCommentHandler = function (schema, request) {
237 | const user = requiresAuth.call(this, request);
238 | try {
239 | if (!user) {
240 | return new Response(
241 | 404,
242 | {},
243 | {
244 | errors: [
245 | "The username you entered is not Registered. Not Found error",
246 | ],
247 | }
248 | );
249 | }
250 | const { postId, commentId } = request.params;
251 | const post = schema.posts.findBy({ _id: postId }).attrs;
252 | const commentIndex = post.comments.findIndex(
253 | (comment) => comment._id === commentId
254 | );
255 |
256 | if (
257 | post.comments[commentIndex].votes.downvotedBy.some(
258 | (currUser) => currUser._id === user._id
259 | )
260 | ) {
261 | return new Response(
262 | 400,
263 | {},
264 | { errors: ["Cannot downvote a post that is already downvoted. "] }
265 | );
266 | }
267 | post.comments[commentIndex].votes.upvotedBy = post.comments[
268 | commentIndex
269 | ].votes.upvotedBy.filter((currUser) => currUser._id !== user._id);
270 | post.comments[commentIndex].votes.downvotedBy.push(user);
271 | this.db.posts.update({ _id: postId }, { ...post, updatedAt: formatDate() });
272 | return new Response(201, {}, { comments: post.comments });
273 | } catch (error) {
274 | return new Response(
275 | 500,
276 | {},
277 | {
278 | error,
279 | }
280 | );
281 | }
282 | };
283 |
--------------------------------------------------------------------------------
/src/components/authentication/SignupForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import { useDispatch, useSelector } from "react-redux";
4 |
5 | import { InputField } from "./InputFeild";
6 | import { validateSignupForm } from "./formValidation";
7 | import { signupUser } from "slices";
8 |
9 |
10 | const SignupForm = () => {
11 | const [userDetails, setUserDetails] = useState({
12 | email: "",
13 | password: "",
14 | firstName: "",
15 | lastName: "",
16 | username: "",
17 | passwordMatch: true,
18 | hide: { password: true, confirmPassword: true },
19 | errors: {
20 | firstName: "",
21 | lastName: "",
22 | email: "",
23 | password: "",
24 | confirmPassword: "",
25 | },
26 | });
27 |
28 | const theme = useSelector((state) => state.theme.theme);
29 | const dispatch = useDispatch();
30 | const navigate = useNavigate();
31 |
32 | const signupFormSubmitHandler = async (event) => {
33 | event.preventDefault();
34 | const { isValid, errors } = validateSignupForm(userDetails);
35 |
36 | if (isValid) {
37 | const response = await dispatch(signupUser(userDetails));
38 | if (response?.payload.encodedToken) {
39 | navigate("/");
40 | }
41 | } else {
42 | setUserDetails((prevState) => ({
43 | ...prevState,
44 | errors,
45 | }));
46 | }
47 | };
48 |
49 | const handleInputFocus = (field) => {
50 | setUserDetails((prevState) => ({
51 | ...prevState,
52 | errors: {
53 | ...prevState.errors,
54 | [field]: "",
55 | },
56 | }));
57 | };
58 |
59 | return (
60 |
61 |
62 |
67 |
68 |
69 |
70 | {theme === "light" ? (
71 |
76 | ) : (
77 |
82 | )}
83 |
84 | SocialBuzz
85 |
86 |
87 |
229 |
230 |
231 | );
232 | };
233 |
234 | export { SignupForm };
235 |
--------------------------------------------------------------------------------
/src/components/shared/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { useNavigate } from "react-router";
4 | import { NavLink } from "react-router-dom";
5 | import { toast } from "react-hot-toast";
6 |
7 | import {
8 | AddCircleOutlineOutlinedIcon,
9 | BookmarkBorderOutlinedIcon,
10 | FavoriteBorderOutlinedIcon,
11 | HistoryEduIcon,
12 | HomeOutlinedIcon,
13 | LogoutIcon,
14 | MoreHorizIcon,
15 | PersonOutlineIcon,
16 | RocketOutlinedIcon,
17 | } from "asset";
18 | import { logout } from "slices";
19 | import { PostModal } from "components";
20 | import { useOutsideClick } from "hooks";
21 |
22 | const Sidebar = () => {
23 | const [isModalOpen, setIsModalOpen] = useState(false);
24 | const [showOptions, setShowOptions] = useState(false);
25 |
26 | const { user } = useSelector((state) => state.auth);
27 |
28 | const navigate = useNavigate();
29 | const dispatch = useDispatch();
30 |
31 | const domNode = useOutsideClick(() => setShowOptions(false));
32 |
33 | const isActiveClass = ({ isActive }) =>
34 | `sidebar flex items-center text-lg mt-1 py-1.5 rounded-3xl px-4 hover:bg-activeGreen w-full cursor-pointer ${
35 | isActive && "bg-activeGreen font-semibold text-deepBlue"
36 | }`;
37 |
38 | return (
39 | <>
40 |
41 |
42 |
43 |
44 |
45 |
46 | Home
47 |
48 |
49 |
50 |
51 |
52 | Explore
53 |
54 |
55 |
56 |
57 |
61 | Bookmarks
62 |
63 |
64 |
65 |
66 |
70 | Liked Posts
71 |
72 |
73 |
74 |
79 |
80 | Profile
81 |
82 |
83 |
84 | setIsModalOpen(true)}
86 | className="btn bg-deepBlue w-full flex justify-center text-lg items-center py-2 px-5 text-white rounded-full"
87 | >
88 |
92 | New Post
93 |
94 |
95 |
96 |
100 |
setShowOptions(!showOptions)}
103 | >
104 |
105 |
106 | {user.avatarUrl ? (
107 |
112 | ) : (
113 |
118 | )}
119 |
120 |
121 |
122 |
123 | {user?.firstName} {user?.lastName}
124 |
125 |
@{user?.username}
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | {showOptions && (
134 |
135 |
136 |
137 | navigate("/user-profile")}
140 | >
141 |
142 | Go to Profile
143 |
144 | {
147 | dispatch(logout());
148 | toast.success("Logged Out!");
149 | }}
150 | >
151 |
152 | Log Out @{user?.username}
153 |
154 |
155 |
156 |
157 |
158 | )}
159 |
160 |
161 |
162 |
163 |
164 | {/* -------------------- MenuBar For Mobile -------------------------- */}
165 |
166 |
167 |
navigate("/")}
169 | className="flex flex-col items-center"
170 | >
171 |
172 |
173 |
navigate("/explore")}
175 | className="flex flex-col items-center"
176 | >
177 |
178 |
179 |
navigate("/bookmarks")}
181 | className="flex flex-col items-center"
182 | >
183 |
184 |
185 |
navigate("/liked-posts")}
187 | className="flex flex-col items-center"
188 | >
189 |
190 |
191 |
navigate("/user-profile")}
193 | className="flex flex-col items-center"
194 | >
195 |
196 |
197 |
{
199 | dispatch(logout());
200 | toast.success("Logged Out!");
201 | }}
202 | className="flex flex-col items-center"
203 | >
204 |
205 |
206 |
207 |
208 | {/* Position the HistoryEduIcon above the right corner */}
209 |
210 |
211 | setIsModalOpen(true)}
213 | className="text-white text-3xl"
214 | >
215 |
216 |
217 |
218 |
219 | {isModalOpen && setIsModalOpen(false)} />}
220 | >
221 | );
222 | };
223 |
224 | export { Sidebar };
225 |
--------------------------------------------------------------------------------
/src/backend/controllers/UserController.js:
--------------------------------------------------------------------------------
1 | import { Response } from "miragejs";
2 | import { formatDate, requiresAuth } from "../utils/authUtils";
3 |
4 | /**
5 | * All the routes related to user are present here.
6 | * */
7 |
8 | /**
9 | * This handler handles gets all users in the db.
10 | * send GET Request at /api/users
11 | * */
12 |
13 | export const getAllUsersHandler = function () {
14 | return new Response(200, {}, { users: this.db.users });
15 | };
16 |
17 | /**
18 | * This handler handles get a user from userId in the db.
19 | * send GET Request at /api/users/:userId
20 | * */
21 |
22 | export const getUserHandler = function (schema, request) {
23 | const username = request.params.username;
24 | try {
25 | const user = schema.users.findBy({ username: username }).attrs;
26 | return new Response(200, {}, { user });
27 | } catch (error) {
28 | return new Response(
29 | 500,
30 | {},
31 | {
32 | error,
33 | }
34 | );
35 | }
36 | };
37 |
38 | /**
39 | * This handler handles updating user details.
40 | * send POST Request at /api/users/edit
41 | * body contains { userData }
42 | * */
43 |
44 | export const editUserHandler = function (schema, request) {
45 | let user = requiresAuth.call(this, request);
46 | try {
47 | if (!user) {
48 | console.log("User not found");
49 | return new Response(
50 | 404,
51 | {},
52 | {
53 | errors: [
54 | "The username you entered is not registered. Not Found error",
55 | ],
56 | }
57 | );
58 | }
59 |
60 | const { userData } = JSON.parse(request.requestBody);
61 |
62 | const { ...updatedUserData } = userData;
63 |
64 | user = { ...user.attrs, ...updatedUserData, updatedAt: formatDate() };
65 |
66 | this.db.users.update(user.id, user);
67 |
68 | const response = new Response(201, {}, { user });
69 | return response;
70 | } catch (error) {
71 | return new Response(500, {}, { error });
72 | }
73 | };
74 |
75 | /**
76 | * This handler gets all the user bookmarks from the db.
77 | * send GET Request at /api/users/bookmark/
78 | * */
79 |
80 | export const getBookmarkPostsHandler = function (schema, request) {
81 | const user = requiresAuth.call(this, request);
82 | try {
83 | if (!user) {
84 | return new Response(
85 | 404,
86 | {},
87 | {
88 | errors: [
89 | "The username you entered is not Registered. Not Found error",
90 | ],
91 | }
92 | );
93 | }
94 | return new Response(200, {}, { bookmarks: user.bookmarks });
95 | } catch (error) {
96 | return new Response(
97 | 500,
98 | {},
99 | {
100 | error,
101 | }
102 | );
103 | }
104 | };
105 | /**
106 | * This handler handles adding a post to user's bookmarks in the db.
107 | * send POST Request at /api/users/bookmark/:postId/
108 | * */
109 |
110 | export const bookmarkPostHandler = function (schema, request) {
111 | const { postId } = request.params;
112 | const post = schema.posts.findBy({ _id: postId }).attrs;
113 | const user = requiresAuth.call(this, request);
114 | try {
115 | if (!user) {
116 | return new Response(
117 | 404,
118 | {},
119 | {
120 | errors: [
121 | "The username you entered is not Registered. Not Found error",
122 | ],
123 | }
124 | );
125 | }
126 | const isBookmarked = user.bookmarks.some(
127 | (currPost) => currPost._id === postId
128 | );
129 | if (isBookmarked) {
130 | return new Response(
131 | 400,
132 | {},
133 | { errors: ["This Post is already bookmarked"] }
134 | );
135 | }
136 | user.bookmarks.push(post);
137 |
138 | this.db.users.update({ id: user.id }, { ...user, updatedAt: formatDate() });
139 |
140 | return new Response(200, {}, { bookmarks: user.bookmarks });
141 | } catch (error) {
142 | return new Response(
143 | 500,
144 | {},
145 | {
146 | error,
147 | }
148 | );
149 | }
150 | };
151 |
152 | /**
153 | * This handler handles adding a post to user's bookmarks in the db.
154 | * send POST Request at /api/users/remove-bookmark/:postId/
155 | * */
156 |
157 | export const removePostFromBookmarkHandler = function (schema, request) {
158 | const { postId } = request.params;
159 | let user = requiresAuth.call(this, request);
160 | try {
161 | if (!user) {
162 | return new Response(
163 | 404,
164 | {},
165 | {
166 | errors: [
167 | "The username you entered is not Registered. Not Found error",
168 | ],
169 | }
170 | );
171 | }
172 | const isBookmarked = user.bookmarks.some(
173 | (currPost) => currPost._id === postId
174 | );
175 | if (!isBookmarked) {
176 | return new Response(400, {}, { errors: ["Post not bookmarked yet"] });
177 | }
178 | const filteredBookmarks = user.bookmarks.filter(
179 | (currPost) => currPost._id !== postId
180 | );
181 | user = { ...user, bookmarks: filteredBookmarks };
182 |
183 | this.db.users.update({ id: user.id }, { ...user, updatedAt: formatDate() });
184 |
185 | return new Response(200, {}, { bookmarks: user.bookmarks });
186 | } catch (error) {
187 | return new Response(
188 | 500,
189 | {},
190 | {
191 | error,
192 | }
193 | );
194 | }
195 | };
196 |
197 | /**
198 | * This handler handles follow action.
199 | * send POST Request at /api/users/follow/:followUserId/
200 | * */
201 |
202 | export const followUserHandler = function (schema, request) {
203 | const user = requiresAuth.call(this, request);
204 | const { followUserId } = request.params;
205 | const followUser = schema.users.findBy({ _id: followUserId }).attrs;
206 | try {
207 | if (!user) {
208 | return new Response(
209 | 404,
210 | {},
211 | {
212 | errors: [
213 | "The username you entered is not Registered. Not Found error",
214 | ],
215 | }
216 | );
217 | }
218 |
219 | if (user._id === followUser._id) {
220 | return new Response(
221 | 404,
222 | {},
223 | {
224 | errors: ["You cannot follow yourself"],
225 | }
226 | );
227 | }
228 |
229 | const isFollowing = user.following.some(
230 | (currUser) => currUser._id === followUser._id
231 | );
232 |
233 | if (isFollowing) {
234 | return new Response(400, {}, { errors: ["User Already following"] });
235 | }
236 |
237 | const updatedUser = {
238 | ...user,
239 | following: [...user.following, { ...followUser }],
240 | };
241 | const updatedFollowUser = {
242 | ...followUser,
243 | followers: [...followUser.followers, { ...user }],
244 | };
245 |
246 | this.db.users.update(
247 | { id: user.id },
248 | { ...updatedUser, updatedAt: formatDate() }
249 | );
250 | this.db.users.update(
251 | { id: followUser.id },
252 | { ...updatedFollowUser, updatedAt: formatDate() }
253 | );
254 |
255 | return new Response(
256 | 200,
257 | {},
258 | { user: updatedUser, followUser: updatedFollowUser }
259 | );
260 | } catch (error) {
261 | return new Response(
262 | 500,
263 | {},
264 | {
265 | error,
266 | }
267 | );
268 | }
269 | };
270 |
271 | /**
272 | * This handler handles unfollow action.
273 | * send POST Request at /api/users/unfollow/:followUserId/
274 | * */
275 |
276 | export const unfollowUserHandler = function (schema, request) {
277 | const user = requiresAuth.call(this, request);
278 | const { followUserId } = request.params;
279 | const followUser = this.db.users.findBy({ _id: followUserId });
280 | try {
281 | if (!user) {
282 | return new Response(
283 | 404,
284 | {},
285 | {
286 | errors: [
287 | "The username you entered is not Registered. Not Found error",
288 | ],
289 | }
290 | );
291 | }
292 | const isFollowing = user.following.some(
293 | (currUser) => currUser._id === followUser._id
294 | );
295 |
296 | if (!isFollowing) {
297 | return new Response(400, {}, { errors: ["User already not following"] });
298 | }
299 |
300 | const updatedUser = {
301 | ...user,
302 | following: user.following.filter(
303 | (currUser) => currUser._id !== followUser._id
304 | ),
305 | };
306 | const updatedFollowUser = {
307 | ...followUser,
308 | followers: followUser.followers.filter(
309 | (currUser) => currUser._id !== user._id
310 | ),
311 | };
312 |
313 | this.db.users.update(
314 | { id: user.id },
315 | { ...updatedUser, updatedAt: formatDate() }
316 | );
317 | this.db.users.update(
318 | { id: followUser.id },
319 | { ...updatedFollowUser, updatedAt: formatDate() }
320 | );
321 |
322 | return new Response(
323 | 200,
324 | {},
325 | { user: updatedUser, followUser: updatedFollowUser }
326 | );
327 | } catch (error) {
328 | return new Response(
329 | 500,
330 | {},
331 | {
332 | error,
333 | }
334 | );
335 | }
336 | };
337 |
--------------------------------------------------------------------------------
/src/slices/postsSlice.js:
--------------------------------------------------------------------------------
1 | import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
2 | import { toast } from "react-hot-toast";
3 |
4 | import {
5 | addComment,
6 | addPost,
7 | deleteComment,
8 | deletePost,
9 | editComment,
10 | editPost,
11 | getAllPostsFromServer,
12 | likePost,
13 | dislikePost,
14 | bookmarkPost,
15 | unBookmarkPost,
16 | getBookmarks,
17 | getPostsByUserName,
18 | } from "services";
19 |
20 | const initialState = {
21 | allPosts: [],
22 | userPosts: [],
23 | bookmarks: [],
24 | getAllPostsStatus: "idle",
25 | getUserPostsStatus: "idle",
26 | postAddStatus: "idle",
27 | postDeleteStatus: "idle",
28 | postEditStatus: "idle",
29 | commentsStatus: "idle",
30 | commentsError: null,
31 | bookmarkStatus: "idle",
32 | };
33 |
34 | export const getPosts = createAsyncThunk(
35 | "post/getAllPosts",
36 | async (_, { rejectWithValue }) => {
37 | try {
38 | const { data } = await getAllPostsFromServer();
39 | return data.posts;
40 | } catch (error) {
41 | console.error(error.message);
42 | return rejectWithValue(error.message);
43 | }
44 | }
45 | );
46 |
47 | export const fetchUserPosts = createAsyncThunk(
48 | "post/getPostsByUserName",
49 | async (username, { rejectWithValue }) => {
50 | try {
51 | const { data } = await getPostsByUserName(username);
52 | return data.posts;
53 | } catch (e) {
54 | return rejectWithValue(e.message);
55 | }
56 | }
57 | );
58 |
59 | export const addUserPost = createAsyncThunk(
60 | "post/addPost",
61 | async (postData, { rejectWithValue }) => {
62 | try {
63 | const { data } = await addPost(postData);
64 | return data.posts;
65 | } catch (error) {
66 | return rejectWithValue(error.message);
67 | }
68 | }
69 | );
70 |
71 | export const deleteUserPost = createAsyncThunk(
72 | "post/deletePost",
73 | async (postId, { rejectWithValue }) => {
74 | try {
75 | const response = await deletePost(postId);
76 | if (response.posts) {
77 | return response.posts;
78 | } else {
79 | throw new Error("Invalid response data");
80 | }
81 | } catch (e) {
82 | return rejectWithValue(e.message);
83 | }
84 | }
85 | );
86 |
87 | export const editUserPost = createAsyncThunk(
88 | "post/editPost",
89 | async (postData, { rejectWithValue }) => {
90 | try {
91 | const { data } = await editPost(postData);
92 | return data.posts;
93 | } catch (e) {
94 | return rejectWithValue(e.message);
95 | }
96 | }
97 | );
98 |
99 | export const addUserComment = createAsyncThunk(
100 | "comments/addComment",
101 | async ({ postId, commentData }, { rejectWithValue }) => {
102 | try {
103 | const { data } = await addComment(postId, commentData);
104 | return data.posts;
105 | } catch (e) {
106 | return rejectWithValue(e.message);
107 | }
108 | }
109 | );
110 |
111 | export const editUserComment = createAsyncThunk(
112 | "comments/editComment",
113 | async ({ postId, commentId, commentData }, { rejectWithValue }) => {
114 | try {
115 | const { data } = await editComment(postId, commentId, commentData);
116 | return data.posts;
117 | } catch (e) {
118 | return rejectWithValue(e.message);
119 | }
120 | }
121 | );
122 |
123 | export const deleteUserComment = createAsyncThunk(
124 | "comments/deleteComment",
125 | async ({ postId, commentId }, { rejectWithValue }) => {
126 | try {
127 | const { data } = await deleteComment(postId, commentId);
128 | return data.posts;
129 | } catch (e) {
130 | return rejectWithValue(e.message);
131 | }
132 | }
133 | );
134 |
135 | export const likeDislikeUserPost = createAsyncThunk(
136 | "post/likeDislikeUserPost",
137 | async ({ postId, isLiked }, { rejectWithValue }) => {
138 | try {
139 | const { data } = isLiked
140 | ? await dislikePost(postId)
141 | : await likePost(postId);
142 | const toastMessage = isLiked ? "Post Disliked!" : "Post Liked!";
143 | toast.success(toastMessage);
144 | return data.posts;
145 | } catch (e) {
146 | return rejectWithValue(e.message);
147 | }
148 | }
149 | );
150 |
151 | export const fetchAllBookmarks = createAsyncThunk(
152 | "post/getBookmarks",
153 | async (_, { rejectWithValue }) => {
154 | try {
155 | const { data } = await getBookmarks();
156 | return data.bookmarks;
157 | } catch (e) {
158 | return rejectWithValue(e.message);
159 | }
160 | }
161 | );
162 |
163 | export const bookmarkUnbookmarkUserPost = createAsyncThunk(
164 | "post/bookmarkUnbookmarkUserPost",
165 | async ({ postId, isBookmarked }, { rejectWithValue }) => {
166 | try {
167 | const { data } = isBookmarked
168 | ? await unBookmarkPost(postId)
169 | : await bookmarkPost(postId);
170 | const toastMessage = isBookmarked
171 | ? "Post UnBookmarked!"
172 | : "Post Bookmarked!";
173 | toast.success(toastMessage);
174 | return data.bookmarks;
175 | } catch (e) {
176 | return rejectWithValue(e.message);
177 | }
178 | }
179 | );
180 |
181 | const postSlice = createSlice({
182 | name: "post",
183 | initialState,
184 | reducers: {},
185 | extraReducers: (builder) => {
186 | builder
187 | .addCase(getPosts.pending, (state) => {
188 | state.getAllPostsStatus = "pending";
189 | })
190 | .addCase(getPosts.fulfilled, (state, action) => {
191 | state.getAllPostsStatus = "fulfilled";
192 | state.allPosts = action.payload;
193 | })
194 | .addCase(getPosts.rejected, (state) => {
195 | state.getAllPostsStatus = "rejected";
196 | })
197 | .addCase(fetchUserPosts.pending, (state, action) => {
198 | state.getUserPostsStatus = "pending";
199 | })
200 | .addCase(fetchUserPosts.fulfilled, (state, action) => {
201 | state.getUserPostsStatus = "fulfilled";
202 | state.userPosts = action.payload;
203 | })
204 | .addCase(fetchUserPosts.rejected, (state) => {
205 | state.getUserPostsStatus = "rejected";
206 | })
207 | .addCase(addUserPost.fulfilled, (state, action) => {
208 | state.postAddStatus = "fulfilled";
209 | state.allPosts = action.payload;
210 | toast.success("Post Created Successfully!");
211 | })
212 | .addCase(addUserPost.rejected, (state) => {
213 | state.postAddStatus = "rejected";
214 | })
215 | .addCase(deleteUserPost.fulfilled, (state, action) => {
216 | state.postDeleteStatus = "fulfilled";
217 | state.allPosts = action.payload;
218 | toast.success("Post Deleted Successfully!");
219 | })
220 | .addCase(deleteUserPost.rejected, (state) => {
221 | state.postDeleteStatus = "rejected";
222 | })
223 | .addCase(editUserPost.fulfilled, (state, action) => {
224 | state.postEditStatus = "fulfilled";
225 | state.allPosts = action.payload;
226 | toast.success("Post Edited Successfully!");
227 | })
228 | .addCase(editUserPost.rejected, (state) => {
229 | state.postEditStatus = "rejected";
230 | })
231 | .addCase(addUserComment.fulfilled, (state, action) => {
232 | state.commentsStatus = "fulfilled";
233 | state.allPosts = action.payload;
234 | toast.success("Comment Added Successfully!");
235 | })
236 | .addCase(addUserComment.rejected, (state, action) => {
237 | state.commentsStatus = "rejected";
238 | state.commentsError = action.payload;
239 | toast.error(
240 | `Some went wrong, Please try again, ${state.commentsError}`
241 | );
242 | })
243 | .addCase(editUserComment.fulfilled, (state, action) => {
244 | state.commentsStatus = "fulfilled";
245 | state.allPosts = action.payload;
246 | toast.success("Comment Edited Successfully!");
247 | })
248 | .addCase(editUserComment.rejected, (state, action) => {
249 | state.commentsStatus = "rejected";
250 | state.commentsError = action.payload;
251 | toast.error(
252 | `Some went wrong, Please try again, ${state.commentsError}`
253 | );
254 | })
255 | .addCase(deleteUserComment.fulfilled, (state, action) => {
256 | state.commentsStatus = "fulfilled";
257 | state.allPosts = action.payload;
258 | toast.success("Comment Deleted Successfully!");
259 | })
260 | .addCase(deleteUserComment.rejected, (state, action) => {
261 | state.commentsStatus = "rejected";
262 | state.commentsError = action.payload;
263 | toast.error(
264 | `Some went wrong, Please try again, ${state.commentsError}`
265 | );
266 | })
267 | .addCase(likeDislikeUserPost.fulfilled, (state, action) => {
268 | state.getAllPostsStatus = "fulfilled";
269 | state.allPosts = action.payload;
270 | })
271 | .addCase(likeDislikeUserPost.rejected, (state) => {
272 | state.getAllPostsStatus = "rejected";
273 | })
274 | .addCase(fetchAllBookmarks.fulfilled, (state, action) => {
275 | state.bookmarkStatus = "fulfilled";
276 | state.bookmarks = action.payload;
277 | })
278 | .addCase(fetchAllBookmarks.rejected, (state) => {
279 | state.bookmarkStatus = "rejected";
280 | })
281 | .addCase(bookmarkUnbookmarkUserPost.fulfilled, (state, action) => {
282 | state.bookmarkStatus = "fulfilled";
283 | state.bookmarks = action.payload;
284 | })
285 | .addCase(bookmarkUnbookmarkUserPost.rejected, (state) => {
286 | state.bookmarkStatus = "rejected";
287 | });
288 | },
289 | });
290 |
291 | export const postReducer = postSlice.reducer;
292 |
--------------------------------------------------------------------------------
/src/backend/db/posts.js:
--------------------------------------------------------------------------------
1 | import { formatDate } from "../utils/authUtils";
2 |
3 | /**
4 | * Posts can be added here.
5 | * You can add default posts of your wish with different attributes
6 | * */
7 |
8 | export const posts = [
9 | {
10 | _id: "ab3d795b-59c6-4e36-b95d-56fa7e3c3bfb",
11 | content: "Nice Weather Today😃!!",
12 | mediaURL:
13 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685682649/SocialBuzz/photo-1570478050568-4a5b7a82159f_vy947n.avif",
14 | likes: {
15 | likeCount: 5,
16 | likedBy: [],
17 | dislikedBy: [],
18 | },
19 | comments: [
20 | {
21 | _id: "e4135a8d-bdc3-4ccf-976b-6b9d4a9e74ed",
22 | username: "bobsmith",
23 | avatarUrl:
24 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595771/SocialBuzz/247-2479526_round-profile-picture-png-transparent-png_ukpjxm.png",
25 | text: "I agree!! Feels like we should go for an outing😃. What say?",
26 | votes: {
27 | upvotedBy: [],
28 | downvotedBy: [],
29 | },
30 | },
31 | ],
32 | username: "chhakulizingare",
33 | createdAt: new Date("11/02/2022"),
34 | updatedAt: formatDate(),
35 | },
36 | {
37 | _id: "c47f5d9e-1f67-4c9a-a2ab-132a7c92c554",
38 | content:
39 | "Just finished working on a new frontend project. Excited to launch🚀 it soon!",
40 | mediaURL:
41 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685682799/SocialBuzz/photo-1544256718-3bcf237f3974_qmlyoo.avif",
42 | likes: {
43 | likeCount: 10,
44 | likedBy: [],
45 | dislikedBy: [],
46 | },
47 | comments: [
48 | {
49 | _id: "fa8d92a3-23bb-4cda-af50-8f15430806e9",
50 | username: "bobsmith",
51 | avatarUrl:
52 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595771/SocialBuzz/247-2479526_round-profile-picture-png-transparent-png_ukpjxm.png",
53 | text: "Great work!🚀 Can't wait to see the final product.",
54 | votes: {
55 | upvotedBy: [],
56 | downvotedBy: [],
57 | },
58 | },
59 | ],
60 | username: "alicejohnson",
61 | createdAt: new Date("07/04/2021"),
62 | updatedAt: formatDate(),
63 | },
64 |
65 | {
66 | _id: "4ff5dcfa-1a45-45bc-9d9a-3286c10dc8e3",
67 | content:
68 | "Life is a beautiful journey filled with ups and downs, twists and turns, and countless moments that shape who we are. It's a canvas upon which we paint our dreams, hopes, and aspirations. Along the way, we encounter challenges that test our resilience and setbacks that teach us valuable lessons. It's in these moments that we discover our true strength and capacity for growth. 💫",
69 | mediaURL: "",
70 | likes: {
71 | likeCount: 20,
72 | likedBy: [],
73 | dislikedBy: [],
74 | },
75 | comments: [
76 | {
77 | _id: "d4c40a4d-7800-4ad4-8d1e-8f1375e9ad43",
78 | username: "sophieclark",
79 | avatarUrl:
80 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595920/SocialBuzz/images_qynsis.jpg",
81 | text:
82 | "Such profound and inspiring words! Thank you for this beautiful thread!",
83 | votes: {
84 | upvotedBy: [],
85 | downvotedBy: [],
86 | },
87 | },
88 | {
89 | _id: "d267bc91-4e9a-4da9-ba7b-3a89fb3a6b4e",
90 | username: "jameswilson",
91 | avatarUrl:
92 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595800/SocialBuzz/images_wxjv0c.jpg",
93 | text: "I couldn't agree more!",
94 | votes: {
95 | upvotedBy: [],
96 | downvotedBy: [],
97 | },
98 | },
99 | {
100 | _id: "b0c82814-9f8a-434e-81d3-72a4eb9c4db5",
101 | username: "emmadavis",
102 | avatarUrl:
103 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg",
104 | text:
105 | "This thread is a gentle reminder to cherish the journey, embrace growth, and live with purpose.",
106 | votes: {
107 | upvotedBy: [],
108 | downvotedBy: [],
109 | },
110 | },
111 | ],
112 | username: "chhakulizingare",
113 | createdAt: new Date("12/05/2021"),
114 | updatedAt: formatDate(),
115 | },
116 | {
117 | _id: "97e6b4d7-2a94-4a7d-bff0-458d846c833e",
118 | content: "Spread kindness like confetti! 🎉✨",
119 | mediaURL:
120 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685938199/SocialBuzz/photo-1581998392741-67879e0ef04a_mcubiw.avif",
121 | likes: {
122 | likeCount: 8,
123 | likedBy: [],
124 | dislikedBy: [],
125 | },
126 | comments: [
127 | {
128 | _id: "a2517e3e-4b6e-4b4b-9d4d-1e0b159a465e",
129 | username: "bobsmith",
130 | avatarUrl:
131 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595771/SocialBuzz/247-2479526_round-profile-picture-png-transparent-png_ukpjxm.png",
132 | text:
133 | "Absolutely! Small acts of kindness can make a big difference✨ in someone's day. Let's make the world a better place!",
134 | votes: {
135 | upvotedBy: [],
136 | downvotedBy: [],
137 | },
138 | },
139 | {
140 | _id: "c37e0491-df4e-4cfd-9b50-8c0e69c012c5",
141 | username: "jameswilson",
142 | avatarUrl:
143 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595800/SocialBuzz/images_wxjv0c.jpg",
144 | text:
145 | "Kindness is contagious!✨ Let's create a ripple effect of positivity and compassion.",
146 | votes: {
147 | upvotedBy: [],
148 | downvotedBy: [],
149 | },
150 | },
151 | ],
152 | username: "chhakulizingare",
153 | createdAt: new Date("08/03/2022"),
154 | updatedAt: formatDate(),
155 | },
156 | {
157 | _id: "a7e9dd2d-48a4-4f6c-86de-6fc02a67c8a5",
158 | content:
159 | "Exploring new web design trends. Can't wait to implement them in my next project! 😃🚀",
160 | mediaURL: "",
161 | likes: {
162 | likeCount: 0,
163 | likedBy: [],
164 | dislikedBy: [],
165 | },
166 | comments: [
167 | {
168 | _id: "625f0ab2-ec68-4c68-8a75-82e67431d112",
169 | username: "emmadavis",
170 | avatarUrl:
171 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg",
172 | website: "https://emmadavis.com/",
173 | text:
174 | "I love experimenting with design too. Let's exchange ideas sometime! 💡💭",
175 | votes: {
176 | upvotedBy: [],
177 | downvotedBy: [],
178 | },
179 | },
180 | ],
181 | username: "bobsmith",
182 | createdAt: new Date("06/05/2021"),
183 | updatedAt: formatDate(),
184 | },
185 | {
186 | _id: "7423f85c-8d4b-46cc-96bc-efa574f7d8b2",
187 | content:
188 | "Embrace the challenges. They are stepping stones to your success.💪Life is a series of twists and turns, filled with both triumphs and trials. Challenges may seem daunting and overwhelming at times, but they are not meant to break you. Instead, they are opportunities for growth, learning, and transformation",
189 | mediaURL: "",
190 | likes: {
191 | likeCount: 12,
192 | likedBy: [],
193 | dislikedBy: [],
194 | },
195 | comments: [
196 | {
197 | _id: "b2e1f19e-58d7-4151-b486-8efb78c39b33",
198 | username: "sophieclark",
199 | avatarUrl:
200 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595920/SocialBuzz/images_qynsis.jpg",
201 | text:
202 | "Absolutely! Every challenge is an opportunity for growth and learning. 💯",
203 | votes: {
204 | upvotedBy: [],
205 | downvotedBy: [],
206 | },
207 | },
208 | {
209 | _id: "a3c5d2e1-76b9-4a8e-9e2f-843dc1dc048d",
210 | username: "lilyanderson",
211 | avatarUrl:
212 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595837/SocialBuzz/images_b9qohl.jpg",
213 | text:
214 | "I love the positive mindset! Challenges make us stronger and more resilient.💪💥 Keep pushing forward!",
215 | votes: {
216 | upvotedBy: [],
217 | downvotedBy: [],
218 | },
219 | },
220 | ],
221 | username: "chhakulizingare",
222 | createdAt: new Date("09/07/2021"),
223 | updatedAt: formatDate(),
224 | },
225 | {
226 | _id: "e4580e2b-52a5-462f-971f-38001a1cbed4",
227 | content:
228 | "Captured some breathtaking landscapes today. Photography is my passion! 📸🌄",
229 | mediaURL:
230 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685682890/SocialBuzz/photo-1563791877359-4a03fb576945_xekyeo.avif",
231 | likes: {
232 | likeCount: 0,
233 | likedBy: [],
234 | dislikedBy: [],
235 | },
236 | comments: [
237 | {
238 | _id: "bc062695-9e82-4d7f-86b4-ee84de68322c",
239 | username: "jameswilson",
240 | avatarUrl:
241 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595800/SocialBuzz/images_wxjv0c.jpg",
242 | website: "https://jameswilson.com/",
243 | text: "Amazing shot! The colors are so vibrant. 🌈👏",
244 | votes: {
245 | upvotedBy: [],
246 | downvotedBy: [],
247 | },
248 | },
249 | ],
250 | username: "emmadavis",
251 | createdAt: new Date("07/06/2022"),
252 | updatedAt: formatDate(),
253 | },
254 | {
255 | _id: "d7c8e1b5-8f91-4d10-a68e-1683c8755cc3",
256 | content: "Adventures await! 🌄✨",
257 | mediaURL:
258 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685937473/SocialBuzz/istockphoto-1133850671-170667a_g9e3kl.webp",
259 | likes: {
260 | likeCount: 7,
261 | likedBy: [],
262 | dislikedBy: [],
263 | },
264 | comments: [
265 | {
266 | _id: "e685c9e9-30e7-4d41-91c1-48f6e12d9f85",
267 | username: "jameswilson",
268 | avatarUrl:
269 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595800/SocialBuzz/images_wxjv0c.jpg",
270 | text: "This photo makes me want to pack my bags and go exploring✨!",
271 | votes: {
272 | upvotedBy: [],
273 | downvotedBy: [],
274 | },
275 | },
276 | {
277 | _id: "f34059b3-11d5-4a1d-936f-9e23dabf67f0",
278 | username: "emmadavis",
279 | avatarUrl:
280 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg",
281 | text: "Where is this place? It looks incredible🌈👏!",
282 | votes: {
283 | upvotedBy: [],
284 | downvotedBy: [],
285 | },
286 | },
287 | ],
288 | username: "chhakulizingare",
289 | createdAt: new Date("10/02/2021"),
290 | updatedAt: formatDate(),
291 | },
292 | {
293 | _id: "87c2c12d-61d5-438d-a10e-58f6a15af792",
294 | content:
295 | "Working on a new music composition. Can't wait to share it with you all!",
296 | mediaURL:
297 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685682994/SocialBuzz/istockphoto-1452556411-170667a_azhaoa.webp",
298 | likes: {
299 | likeCount: 0,
300 | likedBy: [],
301 | dislikedBy: [],
302 | },
303 | comments: [
304 | {
305 | _id: "3a9c01f2-c142-4a70-87f3-8bca0d7e48fe",
306 | username: "chhakulizingare",
307 | avatarUrl:
308 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685515809/SocialBuzz/photo_pd6e7o.jpg",
309 | text: "I'm a big fan of your music! Looking forward to it. 🎶👏",
310 | votes: {
311 | upvotedBy: [],
312 | downvotedBy: [],
313 | },
314 | },
315 | ],
316 | username: "jameswilson",
317 | createdAt: new Date("03/15/2021"),
318 | updatedAt: formatDate(),
319 | },
320 |
321 | {
322 | _id: "e3b02e95-5180-4c83-b450-0f8f8c9e3884",
323 | content:
324 | "Designing a new logo for a client. Loving the creative process! 💡✍️",
325 | mediaURL: "",
326 | likes: {
327 | likeCount: 0,
328 | likedBy: [],
329 | dislikedBy: [],
330 | },
331 | comments: [
332 | {
333 | _id: "59da5de9-0e68-4cc1-b9de-70e94784e1d7",
334 | username: "sophieclark",
335 | avatarUrl:
336 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595920/SocialBuzz/images_qynsis.jpg",
337 | website: "https://sophieclark.com/",
338 | text:
339 | "Your designs are always top-notch! Can't wait to see the final result. 👌🎨",
340 | votes: {
341 | upvotedBy: [],
342 | downvotedBy: [],
343 | },
344 | },
345 | ],
346 | username: "lilyanderson",
347 | createdAt: new Date("07/25/2022"),
348 | updatedAt: formatDate(),
349 | },
350 | {
351 | _id: "329bd206-d0a4-48a5-93f7-0d419b5db764",
352 | content:
353 | "Just returned from an amazing trip. Can't wait to share my travel experiences!",
354 | mediaURL:
355 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685683091/SocialBuzz/istockphoto-1266475132-170667a_ewrjwl.webp",
356 | likes: {
357 | likeCount: 0,
358 | likedBy: [],
359 | dislikedBy: [],
360 | },
361 | comments: [
362 | {
363 | _id: "f24a01e9-8e74-4c95-bd50-6e94a856e105",
364 | username: "alicejohnson",
365 | avatarUrl:
366 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685596157/SocialBuzz/images_vrd6b5.jpg",
367 | website: "https://alicejohnson.com/",
368 | text:
369 | "I'm always inspired by your travel stories. Looking forward to it! ✈️🌍",
370 | votes: {
371 | upvotedBy: [],
372 | downvotedBy: [],
373 | },
374 | },
375 | ],
376 | username: "olivertaylor",
377 | createdAt: new Date("07/06/2022"),
378 | updatedAt: formatDate(),
379 | },
380 |
381 | {
382 | _id: "1df2124f-1187-4963-8044-aaab4d8c5d17",
383 | content:
384 | "Just completed a challenging fitness routine. Feeling accomplished! 💪🔥",
385 | mediaURL:
386 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685683210/SocialBuzz/photo-1522898467493-49726bf28798_z48fvm.avif",
387 | likes: {
388 | likeCount: 0,
389 | likedBy: [],
390 | dislikedBy: [],
391 | },
392 | comments: [
393 | {
394 | _id: "1e141d4e-02a1-44f6-9ab9-d2bb63b523f1",
395 | username: "emmadavis",
396 | avatarUrl:
397 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg",
398 | website: "https://emmadavis.com/",
399 | text: "You're such an inspiration! Keep up the great work. 👏💯",
400 | votes: {
401 | upvotedBy: [],
402 | downvotedBy: [],
403 | },
404 | },
405 | ],
406 | username: "sophieclark",
407 | createdAt: new Date("2/22/2022"),
408 | updatedAt: formatDate(),
409 | },
410 | {
411 | _id: "a3b2c1d4-1e2f-3g4h-5i6j-7k8l9m0n1o2",
412 | content:
413 | "Just finished reading an amazing book. Highly recommend it to everyone!",
414 | mediaURL: "",
415 | likes: {
416 | likeCount: 0,
417 | likedBy: [],
418 | dislikedBy: [],
419 | },
420 | comments: [
421 | {
422 | _id: "b2c3d4e5-2f3g-4h5i-6j7k-8l9m0n1o2p3",
423 | username: "bobsmith",
424 | avatarUrl:
425 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595771/SocialBuzz/247-2479526_round-profile-picture-png-transparent-png_ukpjxm.png",
426 | text: "I love reading! What's the title of the book? 📚",
427 | votes: {
428 | upvotedBy: [],
429 | downvotedBy: [],
430 | },
431 | },
432 | {
433 | _id: "c3d4e5f6-4h5i-6j7k-8l9m0n1o2p3q4",
434 | username: "sophieclark",
435 | avatarUrl:
436 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595920/SocialBuzz/images_qynsis.jpg",
437 | website: "https://sophieclark.com/",
438 | text:
439 | "I'm always looking for book recommendations. Thanks for sharing! 🙏",
440 | votes: {
441 | upvotedBy: [],
442 | downvotedBy: [],
443 | },
444 | },
445 | ],
446 | username: "alicejohnson",
447 | createdAt: new Date("07/03/2022"),
448 | updatedAt: formatDate(),
449 | },
450 | {
451 | _id: "d4e5f6g7-5i6j-7k8l-9m0n-1o2p3q4r5s6",
452 | content: "Just completed a challenging workout. Feeling energized! 💪💥",
453 | mediaURL: "",
454 | likes: {
455 | likeCount: 0,
456 | likedBy: [],
457 | dislikedBy: [],
458 | },
459 | comments: [
460 | {
461 | _id: "e5f6g7h8-6j7k-8l9m-0n1o-2p3q4r5s6t7",
462 | username: "lilyanderson",
463 | avatarUrl:
464 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595837/SocialBuzz/images_b9qohl.jpg",
465 | website: "https://lilyanderson.com/",
466 | text: "Great job! Working out is so important for our well-being. 👍",
467 | votes: {
468 | upvotedBy: [],
469 | downvotedBy: [],
470 | },
471 | },
472 | {
473 | _id: "f6g7h8i9-8l9m-0n1o-2p3q-4r5s6t7u8v9",
474 | username: "emmadavis",
475 | avatarUrl:
476 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg",
477 | website: "https://emmadavis.com/",
478 | text:
479 | "I need some motivation to get back into a fitness routine. Any tips? 🏋️♀️💪",
480 | votes: {
481 | upvotedBy: [],
482 | downvotedBy: [],
483 | },
484 | },
485 | ],
486 | username: "sophieclark",
487 | createdAt: new Date("09/02/2021"),
488 | updatedAt: formatDate(),
489 | },
490 | {
491 | _id: "g7h8i9j0-9k0l-1m2n-3o4p-5q6r7s8t9u0",
492 | content: "Just cooked a delicious homemade meal. Food is love! 🍳🥗",
493 | mediaURL:
494 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685683277/SocialBuzz/photo-1579619168313-d2e074a7ee02_tdz0ft.avif",
495 | likes: {
496 | likeCount: 0,
497 | likedBy: [],
498 | dislikedBy: [],
499 | },
500 | comments: [],
501 | username: "lilyanderson",
502 | createdAt: new Date("07/28/2021"),
503 | updatedAt: formatDate(),
504 | },
505 | {
506 | _id: "h8i9j0k1-0l1m-2n3o-4p5q-6r7s8t9u0v1",
507 | content:
508 | "Exploring a new city today. So many hidden gems to discover! ✈️🗺️",
509 | mediaURL: "",
510 | likes: {
511 | likeCount: 0,
512 | likedBy: [],
513 | dislikedBy: [],
514 | },
515 | comments: [
516 | {
517 | _id: "i9j0k1l2-1m2n-3o4p-5q6r-7s8t9u0v1w2",
518 | username: "alicejohnson",
519 | avatarUrl:
520 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685596157/SocialBuzz/images_vrd6b5.jpg",
521 | website: "https://alicejohnson.com/",
522 | text:
523 | "I love exploring new places! Any recommendations for must-visit spots? 🌇",
524 | votes: {
525 | upvotedBy: [],
526 | downvotedBy: [],
527 | },
528 | },
529 | {
530 | _id: "j0k1l2m3-2n3o-4p5q-6r7s-8t9u0v1w2x3",
531 | username: "emmadavis",
532 | avatarUrl:
533 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595740/SocialBuzz/images_zrbwa3.jpg",
534 | website: "https://emmadavis.com/",
535 | text:
536 | "Enjoy your adventure! Traveling is the best way to broaden our horizons. 🌍✈️",
537 | votes: {
538 | upvotedBy: [],
539 | downvotedBy: [],
540 | },
541 | },
542 | ],
543 | username: "olivertaylor",
544 | createdAt: new Date("07/05/2021"),
545 | updatedAt: formatDate(),
546 | },
547 | {
548 | _id: "k1l2m3n4-3o4p-5q6r-7s8t-9u0v1w2x3y4",
549 | content:
550 | "Just attended an inspiring conference. Learned so much from the speakers! 🎓📚",
551 | mediaURL:
552 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685683340/SocialBuzz/istockphoto-823840662-170667a_zho9lt.webp",
553 | likes: {
554 | likeCount: 0,
555 | likedBy: [],
556 | dislikedBy: [],
557 | },
558 | comments: [
559 | {
560 | _id: "l2m3n4o5-4p5q-6r7s-8t9u-0v1w2x3y4z5",
561 | username: "bobsmith",
562 | avatarUrl:
563 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595771/SocialBuzz/247-2479526_round-profile-picture-png-transparent-png_ukpjxm.png",
564 | text:
565 | "Conferences are a great way to gain new insights. Which conference was it? 🎙️",
566 | votes: {
567 | upvotedBy: [],
568 | downvotedBy: [],
569 | },
570 | },
571 | {
572 | _id: "m3n4o5p6-5q6r-7s8t-9u0v-1w2x3y4z5a6",
573 | username: "jameswilson",
574 | avatarUrl:
575 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685595800/SocialBuzz/images_wxjv0c.jpg",
576 | website: "https://jameswilson.com/",
577 | text:
578 | "I'm always on the lookout for professional development opportunities. Any recommendations? 📊👨💼",
579 | votes: {
580 | upvotedBy: [],
581 | downvotedBy: [],
582 | },
583 | },
584 | ],
585 | username: "emmadavis",
586 | createdAt: new Date("07/02/2023"),
587 | updatedAt: formatDate(),
588 | },
589 | {
590 | _id: "n4o5p6q7-6r7s-8t9u-0v1w-2x3y4z5a6b7",
591 | content: "Just adopted a new pet. Meet my adorable furry friend! 🐾🐱",
592 | mediaURL:
593 | "https://res.cloudinary.com/dptfwcnro/image/upload/v1685683392/SocialBuzz/istockphoto-1276788283-170667a_kyajsw.webp",
594 | likes: {
595 | likeCount: 0,
596 | likedBy: [],
597 | dislikedBy: [],
598 | },
599 | comments: [],
600 | username: "lilyanderson",
601 | createdAt: new Date("06/15/2023"),
602 | updatedAt: formatDate(),
603 | },
604 | ];
605 |
--------------------------------------------------------------------------------