WISE
WISE
ON
WISE: SOCIAL MEDIA WEB APP
(FOR THE COURSE OF T. Y. B.Sc. COMPUTER SCIENCE)
GUIDED BY
MS. PRATIKSHA HARWALKAR
Atharva Gholap
Contents
1. Title ..................................................................................................................................... 1
2. Introduction ........................................................................................................................ 2
2.1 Objective of the project: .............................................................................................. 2
2.2 Description of the current system: .............................................................................. 2
2.3 Limitation of the current system: ................................................................................ 2
2.4 Description of the proposed system: ........................................................................... 2
2.5 Advantages of the proposed system: ........................................................................... 2
3. Requirement Specification ................................................................................................. 4
3.1 Software Requirements: .............................................................................................. 4
3.2 Hardware Requirements: ............................................................................................. 4
3.3 Data Requirements: ..................................................................................................... 4
3.4 Fact Finding Questions: .............................................................................................. 4
4. System Design Details ........................................................................................................ 5
4.1 Event Table: ................................................................................................................. 5
4.2 Class Diagram ............................................................................................................. 6
4.3 Use Case Diagram ....................................................................................................... 7
4.4 Sequence Diagram....................................................................................................... 8
4.5 Activity Diagram ......................................................................................................... 9
4.6 State Diagram ............................................................................................................ 10
4.7 Package Diagram....................................................................................................... 10
4.8 Component Diagram ................................................................................................. 11
4.9 Development Diagram .............................................................................................. 11
4.10 Database Design (MongoDB) ................................................................................... 12
4.10.1 Database: test ..................................................................................................... 12
4.10.2 Collection: posts................................................................................................. 12
4.10.3 Collection: users................................................................................................. 13
5. System Implementation .................................................................................................... 14
6. Results .............................................................................................................................. 68
6.1 Validation and Naming Conventions......................................................................... 68
6.2 Screenshots ................................................................................................................ 68
6.2.1 Login Page ......................................................................................................... 68
6.2.2 Account Registration Page ................................................................................. 69
6.2.3 Home Page ......................................................................................................... 70
6.2.4 Profile Page ........................................................................................................ 70
6.2.5 NightMode Activated ......................................................................................... 71
6.2.6 Post..................................................................................................................... 71
6.2.7 Logout ................................................................................................................ 72
7. Future Enhancement & Conclusion .................................................................................. 73
7.1 Future Enhancement .................................................................................................. 73
7.2 Conclusions ............................................................................................................... 73
8. References ........................................................................................................................ 73
9. Annexures ............................................................................................................................ 74
9.1 Figures List ................................................................................................................ 74
9.2 Table List ................................................................................................................... 74
Social Media Web Application
1. Title
2. Introduction
• It will also allow users to post any type of data from photos to any type of
attachments.
3. Requirement Specification
Filling up If successful,
Account details to user will Login
1 Registeration Register User create an navigate to Page.jsx
Or Account login interface
Creation or else error
Filling up If successful,
Account Login respective user will Login
2 Login User email and navigate to Page.jsx
password their profile
interface or else
error
Providing If successful,
a caption content will be
3 Post Content Post User and a file posted and HomePage/
to be visible on the index.jsx
posted interface else
not
Click on On click it will
Like the like the post
4 Like Post icon User icon to and if it was Home
Or Unlike Post ( ) like or previously page.jsx
dislike a liked, it will
post dislike the post
Person Click on Friend List of
Friend Plus the + both the Home
5 Or icon User to Friend accounts will page.jsx
Unfriend ( +) or be updated
Unfriend
Light mode Sun/ Theme of entire
Or Moon User Click on webapp will Home
6 Dark mode icon / change to page.jsx
switch light/dark
mode
Click on The user will Login
Log Out Log Out the logout from the Page.jsx
7 From the User Logout current account
account button
Event Table 1
Figure 1
Figure 2
Figure 3
Figure 4
Figure 5
Figure 6
Figure 7
Figure 8
Figure 9
Figure 10
Figure 11
5. System Implementation
Client
FlexBetween.jsx
import { Box } from "@mui/material";
import { styled } from "@mui/system";
Friend.jsx
import { PersonAddOutlined, PersonRemoveOutlined } from "@mui/icons-material";
import { Box, IconButton, Typography, useTheme } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { setFriends } from "state";
import FlexBetween from "./FlexBetween";
import UserImage from "./UserImage";
import { useState } from "react";
setTimeout(() => {
setIsAdded(false);
}, 2000);
};
return (
<FlexBetween>
<FlexBetween gap="1rem">
<UserImage image={userPicturePath} size="55px" />
<Box
onClick={() => {
navigate(`/profile/${friendId}`);
navigate(0);
}}
>
<Typography
color={main}
variant="h5"
fontWeight="500"
sx={{
"&:hover": {
color: palette.primary.light,
cursor: "pointer",
},
}}
>
{name}
</Typography>
<Typography color={medium} fontSize="0.75rem">
{subtitle}
</Typography>
</Box>
</FlexBetween>
<IconButton
onClick={() => patchFriend()}
sx={{ backgroundColor: primaryLight, p: "0.6rem" }}
>
{isFriend ? (
<PersonRemoveOutlined sx={{ color: primaryDark }} />
):(
<PersonAddOutlined sx={{ color: primaryDark }} />
)}
</IconButton>
{isAdded && (
<Box
position="fixed"
bottom="20px"
left="43%"
transform="translateX(-50%)"
backgroundColor="rgba(0, 0, 0, 0.8)"
color="white"
borderRadius="4px"
padding="20px"
zIndex="100"
textAlign="center"
transition="opacity 0.5s"
fontSize="20px"
>
{isFriend ? "Added to Friend list" : "Removed from Friend list"}
</Box>
)}
</FlexBetween>
);
};
export default Friend;
UserImage.jsx
import { Box } from "@mui/material";
const UserImage=({ image,size="60px"}) => {
return(
<Box width={size} height={size}>
<img
style={{objectFit:"cover",borderRadius:"50%"}}
width={size}
height={size}
alt="user"
src={`http://localhost:3001/assets/${image}`}>
</img>
</Box>
)
}
export default UserImage;
WidgetWrapper.jsx
import styled from "@emotion/styled";
import { Box } from "@mui/material";
homepage → index.jsx
import NavBar from "scenes/navbar";
import { Box, useMediaQuery } from "@mui/material";
import { useSelector } from "react-redux";
import UserWidget from "scenes/widgets/UserWidget";
import MyPostWidget from "scenes/widgets/MyPostWidget";
import PostsWidget from "scenes/widgets/PostsWidget";
import AdvertWidget from "scenes/widgets/AdverWidget";
import FriendListWidget from "scenes/widgets/FriendListWidget";
return (
<Box>
<NavBar />
<Box
width="100%"
padding="2rem"
loginPage→Form.jsx
import { useState } from "react";
import {
Box,
Button,
TextField,
useMediaQuery,
Typography,
useTheme,
Snackbar,
SnackbarContent
} from "@mui/material";
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import { Formik } from "formik";
import * as yup from "yup";
import { useNavigate } from "react-router-dom";
const initialValuesRegister = {
firstName: "",
lastName: "",
email: "",
password: "",
location: "",
occupation: "",
picture: "",
};
const initialValuesLogin = {
email: "",
password: "",
};
if (savedUser) {
setRegistrationSuccess(true);
setRegisterMessage("Tea is Ready: Successfully Registered");
onSubmitProps.resetForm();
setPageType("login");
}
};
);
navigate("/home");
onSubmitProps.resetForm();
}
};
return (
<Formik
onSubmit={handleFormSubmit}
initialValues={isLogin ? initialValuesLogin : initialValuesRegister}
validationSchema={isLogin ? loginSchema : registerSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
setFieldValue,
resetForm,
}) => (
<form onSubmit={handleSubmit}>
<Box
display="grid"
gap="30px"
gridTemplateColumns="repeat(4, minmax(0, 1fr))"
sx={{
"& > div": { gridColumn: isNonMobile ? undefined : "span 4" },
}}
>
{isRegister && (
<>
<TextField
label="First Name"
onBlur={handleBlur}
onChange={handleChange}
value={values.firstName}
name="firstName"
error={
Boolean(touched.firstName) && Boolean(errors.firstName)
}
helperText={touched.firstName && errors.firstName}
sx={{ gridColumn: "span 2" }}
/>
<TextField
label="Last Name"
onBlur={handleBlur}
onChange={handleChange}
value={values.lastName}
name="lastName"
error={Boolean(touched.lastName) && Boolean(errors.lastName)}
helperText={touched.lastName && errors.lastName}
sx={{ gridColumn: "span 2" }}
/>
<TextField
label="Location"
onBlur={handleBlur}
onChange={handleChange}
value={values.location}
name="location"
error={Boolean(touched.location) && Boolean(errors.location)}
helperText={touched.location && errors.location}
sx={{ gridColumn: "span 4" }}
/>
<TextField
label="Occupation"
onBlur={handleBlur}
onChange={handleChange}
value={values.occupation}
name="occupation"
error={
Boolean(touched.occupation) && Boolean(errors.occupation)
}
helperText={touched.occupation && errors.occupation}
sx={{ gridColumn: "span 4" }}
/>
<Box
gridColumn="span 4"
border={`1px solid ${palette.neutral.medium}`}
borderRadius="5px"
p="1rem"
>
<Dropzone
acceptedFiles=".jpg,.jpeg,.png"
multiple={false}
onDrop={(acceptedFiles) =>
setFieldValue("picture", acceptedFiles[0])
}
>
{({ getRootProps, getInputProps }) => (
<Box
{...getRootProps()}
border={`2px dashed ${palette.primary.main}`}
p="1rem"
sx={{ "&:hover": { cursor: "pointer" } }}
>
<input {...getInputProps()} />
{!values.picture ? (
<p>Add Picture Here</p>
):(
<FlexBetween>
<Typography>{values.picture.name}</Typography>
<EditOutlinedIcon />
</FlexBetween>
)}
</Box>
)}
</Dropzone>
</Box>
</>
)}
<TextField
label="Email"
onBlur={handleBlur}
onChange={handleChange}
value={values.email}
name="email"
error={Boolean(touched.email) && Boolean(errors.email)}
helperText={touched.email && errors.email}
sx={{ gridColumn: "span 4" }}
/>
<TextField
label="Password"
type="password"
onBlur={handleBlur}
onChange={handleChange}
value={values.password}
name="password"
error={Boolean(touched.password) && Boolean(errors.password)}
helperText={touched.password && errors.password}
sx={{ gridColumn: "span 4" }}
/>
</Box>
<Snackbar
open={isRegistrationSuccess}
onClose={() => setRegistrationSuccess(false)}
autoHideDuration={2000}
>
<SnackbarContent
message={
<Box
display="flex"
alignItems="center"
justifyContent="center"
p="1rem"
border={`0.5px solid ${theme.palette.primary.main}`}
borderRadius="5px"
bgcolor={theme.palette.background.alt}
color={theme.palette.primary.main}
>
{registerMessage}
</Box>
}
/>
</Snackbar>
</form>
)}
</Formik>
);
};
export default Form;
loginPage →index.jsx
import { Box, Typography, useTheme, useMediaQuery } from "@mui/material";
import Form from "./Form";
backgroundColor={theme.palette.background.alt}
p="1rem 6%"
texAlign="center">
<Typography
fontWeight="bold"
fontSize="clamp(1rem,2rem,2.25rem)"
color="primary"
>
WISE
</Typography>
</Box>
<Box
width={isNonMobileScreens ? "50%" : "93%"}
p="2rem"
m="2rem auto"
borderRadius="1.5rem"
backgroundColor={theme.palette.background.alt}
>
<Typography
fontWeight="500"
variant="h5"
sx={{mb: "1.5rem"}}>
Spill the Tea and use us for free
</Typography>
<Form></Form>
</Box>
</Box>
</center>
);
};
export default LoginPage;
navbar → index.jsx
import { useState } from "react";
import {
Box,
IconButton,
InputBase,
Typography,
Select,
MenuItem,
FormControl,
useTheme,
useMediaQuery,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Button
} from "@mui/material";
import {
Search,
Message,
DarkMode,
LightMode,
Notifications,
Help,
Menu,
Close
} from "@mui/icons-material";
return (
<div
style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
zIndex: 100,
backgroundColor: alt,
}}
>
<FlexBetween padding="1rem 6%" backgroundColor={alt}>
<FlexBetween gap="1.7rem">
<Typography
fontWeight="bold"
fontSize="clamp(1rem,2rem,2.25rem)"
color="primary"
onClick={() => navigate("/home")}
sx={{
"&:hover": {
color: primaryLight,
cursor: "pointer",
},
}}
>
WISE
</Typography>
{isNonMobileScreens && (
<FlexBetween
backgroundColor={neutralLight}
borderRadius="9px"
gap='3rem'
padding="0.1rem 1.5rem">
<InputBase placeholder="Search..." />
<IconButton>
<Search />
</IconButton>
</FlexBetween>
)}
</FlexBetween>
{// DESKTOP NAV
}
{isNonMobileScreens ?
(<FlexBetween gap="2rem">
<IconButton onClick={() => dispatch(setMode())}>
{theme.palette.mode === "dark" ? (
<DarkMode sx={{ fontSize: "25px" }} />
):(
<LightMode sx={{ color: dark, fontSize: "25px" }} />
)}
</IconButton>
<Message sx={{ fontSize: "25px" }} />
<Notifications sx={{ fontSize: "25px" }} />
<Help sx={{ fontSize: "25px" }} />
<FormControl variant="standard" value={fullName}>
<Select
value={fullName}
sx={{
backgroundColor: neutralLight,
width: "175px",
borderRadius: "0.25rem",
p: "0.25rem 1rem",
"& .MuiSvgIcon-root": {
pr: "0.25rem",
width: "3rem"
},
"& .MuiSelect-select:focus": {
backgroundColor: neutralLight
}
}}
input={<InputBase />}
>
<MenuItem value={fullName}>
<Typography>{fullName}</Typography>
</MenuItem>
<MenuItem onClick={handleLogout}>Log Out</MenuItem>
</Select>
</FormControl>
</FlexBetween>
)
:
(
<IconButton onClick={() => setIsMobileMenuToggled(!isMobileMenuToggled)}
>
<Menu />
</IconButton>)}
gap="3rem"
>
<IconButton
onClick={() => dispatch(setMode())}
sx={{ fontSize: "25px" }}
>
{theme.palette.mode === "dark" ? (
<DarkMode sx={{ fontSize: "25px" }} />
):(
<LightMode sx={{ color: dark, fontSize: "25px" }} />
)}
</IconButton>
<Message sx={{ fontSize: "25px" }} />
<Notifications sx={{ fontSize: "25px" }} />
<Help sx={{ fontSize: "25px" }} />
<FormControl variant="standard" value={fullName}>
<Select
value={fullName}
sx={{
backgroundColor: neutralLight,
width: "150px",
borderRadius: "0.25rem",
p: "0.25rem 1rem",
"& .MuiSvgIcon-root": {
pr: "0.25rem",
width: "3rem",
},
"& .MuiSelect-select:focus": {
backgroundColor: neutralLight,
},
}}
input={<InputBase />}
>
<MenuItem value={fullName}>
<Typography>{fullName}</Typography>
</MenuItem>
<MenuItem onClick={handleLogout}>Log Out</MenuItem>
</Select>
</FormControl>
</FlexBetween>
</Box>
)}
{/* Logout Confirmation Dialog */}
<Dialog
open={openLogoutDialog}
onClose={handleCloseLogoutDialog}
maxWidth="sm" /* Adjust maxWidth for width */
fullWidth /* Set fullWidth to use full available width */
>
<DialogTitle>Confirm Logout</DialogTitle>
<DialogContent>
<DialogContentText sx={{ fontSize: "20px" /* Adjust font size */ }}>
Are you sure you want to log out?
</DialogContentText>
</DialogContent>
<DialogActions sx={{ fontSize: "20px" /* Adjust font size */ }}>
<Button onClick={handleCloseLogoutDialog} color="primary">
Cancel
</Button>
<Button onClick={handleConfirmLogout} color="primary">
Confirm
</Button>
</DialogActions>
</Dialog>
{/* Logout Cancelling Message */}
{showTeaMessage && (
<Box
position="fixed"
bottom="20px"
left="38%"
transform="translateX(-50%)"
backgroundColor="rgba(0, 0, 0, 0.8)"
color="white"
borderRadius="4px"
padding="20px"
zIndex="100"
textAlign="center"
transition="opacity 0.5s"
fontSize="20px"
>
Please continue spilling the tea ! :)
</Box>
)}
</FlexBetween>
</div>
);
};
export default NavBar;
profilePage → index.jsx
useEffect(() => {
getUser();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Box>
<Navbar />
<Box
width="100%"
padding="2rem 6%"
display={isNonMobileScreens ? "flex" : "block"}
gap="2rem"
justifyContent="center"
>
<Box flexBasis={isNonMobileScreens ? "26%" : undefined}>
<UserWidget userId={userId} picturePath={user.picturePath} />
</Box>
<Box
flexBasis={isNonMobileScreens ? "42%" : undefined}
mt={isNonMobileScreens ? undefined : "2rem"}
>
<Box m="5rem 0" />
<PostsWidget userId={userId} isProfile />
</Box>
<Box flexBasis={isNonMobileScreens ? "26%" : undefined}>
<FriendListWidget userId={userId} />
</Box>
</Box>
</Box>
);
};
export default ProfilePage;
widgets
AdverWidget.jsx
import { Typography, useTheme } from "@mui/material";
import FlexBetween from "components/FlexBetween";
import WidgetWrapper from "components/WidgetWrapper";
return (
<WidgetWrapper>
<FlexBetween>
<Typography color={dark} variant="h5" fontWeight="500">
Sponsored
</Typography>
<Typography color={medium}>Create Ad</Typography>
</FlexBetween>
<img
width="100%"
height="auto"
alt="advert"
src="http://localhost:3001/assets/info4.jpeg"
style={{ borderRadius: "0.75rem", margin: "0.75rem 0" }}
/>
<FlexBetween>
<Typography color={main}>MikaCosmetics</Typography>
<Typography color={medium}>mikacosmetics.com</Typography>
</FlexBetween>
<Typography color={medium} m="0.5rem 0">
Your pathway to stunning and immaculate beauty and made sure your skin
is exfoliating skin and shining like light.
</Typography>
</WidgetWrapper>
);
};
export default AdvertWidget;
FriendListWidget.jsx
import { Box, Typography, useTheme } from "@mui/material";
import Friend from "components/Friend";
import WidgetWrapper from "components/WidgetWrapper";
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setFriends } from "state";
useEffect(() => {
getFriends();
return (
<div
style={{
position: "sticky",
top: "7rem",
zIndex: 100,
}}
>
<WidgetWrapper
style={{
maxHeight: "30rem", // Adjust as needed
overflowY: "auto",
}}
>
<Typography
color={palette.neutral.dark}
variant="h5"
fontWeight="500"
sx={{ mb: "1.5rem" }}
>
Friend List
</Typography>
<Box display="flex" flexDirection="column" gap="1.5rem">
{friends.map((friend) => (
<Friend
key={friend._id}
friendId={friend._id}
name={`${friend.firstName} ${friend.lastName}`}
subtitle={friend.occupation}
userPicturePath={friend.picturePath}
/>
))}
</Box>
</WidgetWrapper>
</div>
);
};
export default FriendListWidget;
MyPostWidget.jsx
import {
EditOutlined,
DeleteOutlined,
AttachFileOutlined,
GifBoxOutlined,
ImageOutlined,
MicOutlined,
MoreHorizOutlined,
} from "@mui/icons-material";
import {
Box,
Divider,
Typography,
InputBase,
useTheme,
Button,
IconButton,
useMediaQuery,
} from "@mui/material";
import FlexBetween from "components/FlexBetween";
import Dropzone from "react-dropzone";
import UserImage from "components/UserImage";
import WidgetWrapper from "components/WidgetWrapper";
import { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setPosts } from "state";
if (image) {
formData.append("picture", image);
formData.append("picturePath", image.name);
}
return (
<WidgetWrapper>
<FlexBetween gap="1.5rem">
<UserImage image={picturePath} />
<InputBase
placeholder="What's on your mind..."
onChange={(e) => setPost(e.target.value)}
value={post}
sx={{
width: "100%",
backgroundColor: palette.neutral.light,
borderRadius: "2rem",
padding: "1rem 2rem",
}}
/>
</FlexBetween>
{isImage && (
<Box
border={`1px solid ${medium}`}
borderRadius="5px"
mt="1rem"
p="1rem"
>
<Dropzone
acceptedFiles=".jpg,.jpeg,.png"
multiple={false}
onDrop={(acceptedFiles) => setImage(acceptedFiles[0])}
>
{({ getRootProps, getInputProps }) => (
<FlexBetween>
<Box
{...getRootProps()}
border={`2px dashed ${palette.primary.main}`}
p="1rem"
width="100%"
sx={{ "&:hover": { cursor: "pointer" } }}
>
<input {...getInputProps()} />
{!image ? (
<p>Add Image Here</p>
):(
<FlexBetween>
<Typography>{image.name}</Typography>
<EditOutlined />
</FlexBetween>
)}
</Box>
{image && (
<IconButton
onClick={() => setImage(null)}
sx={{ width: "15%" }}
>
<DeleteOutlined />
</IconButton>
)}
</FlexBetween>
)}
</Dropzone>
</Box>
)}
<FlexBetween>
<FlexBetween gap="0.25rem" onClick={() => setIsImage(!isImage)}>
<ImageOutlined sx={{ color: mediumMain }} />
<Typography
color={mediumMain}
{isNonMobileScreens ? (
<>
<FlexBetween gap="0.25rem">
<GifBoxOutlined sx={{ color: mediumMain }} />
<Typography color={mediumMain}>Clip</Typography>
</FlexBetween>
<FlexBetween gap="0.25rem">
<AttachFileOutlined sx={{ color: mediumMain }} />
<Typography color={mediumMain}>Attachment</Typography>
</FlexBetween>
<FlexBetween gap="0.25rem">
<MicOutlined sx={{ color: mediumMain }} />
<Typography color={mediumMain}>Audio</Typography>
</FlexBetween>
</>
):(
<FlexBetween gap="0.25rem">
<MoreHorizOutlined sx={{ color: mediumMain }} />
</FlexBetween>
)}
<Button
disabled={!post}
onClick={handlePost}
sx={{
color: palette.background.alt,
backgroundColor: palette.primary.main,
borderRadius: "3rem",
}}
>
POST
</Button>
</FlexBetween>
isImagePosted && (
<Box
position="fixed"
bottom="20px"
left="43%"
transform="translateX(-50%)"
backgroundColor="rgba(0, 0, 0, 0.8)"
color="white"
borderRadius="4px"
padding="20px"
zIndex="100"
textAlign="center"
transition="opacity 0.5s"
fontSize="20px"
>
Post Uploaded !
</Box>
)
}
</WidgetWrapper>
);
};
export default MyPostWidget;
PostsWidget.jsx
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setPosts } from "state";
import PostWidget from "./PostWidget";
useEffect(() => {
if (isProfile) {
getUserPosts();
} else {
getPosts();
}
}, [posts]); // eslint-disable-line react-hooks/exhaustive-deps
const postsArray = Array.from(posts);
return (
<>
{postsArray.map(
({
_id,
userId,
firstName,
lastName,
description,
location,
picturePath,
userPicturePath,
likes,
comments,
}) => (
<PostWidget
key={_id}
postId={_id}
postUserId={userId}
name={`${firstName} ${lastName}`}
description={description}
location={location}
picturePath={picturePath}
userPicturePath={userPicturePath}
likes={likes}
comments={comments}
/>
)
)}
</>
);
};
export default PostsWidget;
PostWidget.jsx
import React, { useState } from "react";
import {
IconButton,
Button,
Input,
Typography,
useTheme,
Box,
Divider,
} from "@mui/material";
import {
ChatBubbleOutlineOutlined,
FavoriteBorderOutlined,
FavoriteOutlined,
ShareOutlined,
} from "@mui/icons-material";
import WidgetWrapper from "components/WidgetWrapper";
import Friend from "components/Friend";
import FlexBetween from "components/FlexBetween";
import { useDispatch, useSelector } from "react-redux";
import { setPost } from "state";
const PostWidget = ({
postId,
postUserId,
name,
description,
location,
picturePath,
userPicturePath,
likes,
comments,
}) => {
const [isComments, setIsComments] = useState(false);
const [newComment, setNewComment] = useState("");
try {
const requestBody = {
id: postId,
userId: loggedInUserId,
comment: newComment,
};
setNewComment("");
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
}
);
if (response.ok) {
const updatedPost = await response.json();
dispatch(setPost({ post: updatedPost }));
} else {
console.error("Error posting comment");
}
} catch (error) {
console.error("An error occurred:", error);
}
};
return (
<WidgetWrapper m="2rem 0">
<Friend
friendId={postUserId}
name={name}
subtitle={location}
userPicturePath={userPicturePath}
/>
<Typography color={main} sx={{ mt: "1rem" }}>
{description}
</Typography>
{picturePath && (
<img
width="100%"
height="auto"
alt="post"
style={{ borderRadius: "0.75rem", marginTop: "0.75rem" }}
src={`http://localhost:3001/assets/${picturePath}`}
/>
)}
<FlexBetween mt="0.25rem">
<FlexBetween gap="1rem">
<FlexBetween gap="0.3rem">
<IconButton onClick={patchLike}>
{isLiked ? (
<FavoriteOutlined sx={{ color: primary }} />
):(
<FavoriteBorderOutlined />
)}
</IconButton>
<Typography>{likeCount}</Typography>
</FlexBetween>
<FlexBetween gap="0.3rem">
<IconButton onClick={() => setIsComments(!isComments)}>
<ChatBubbleOutlineOutlined />
</IconButton>
<Typography>{comments.length}</Typography>
</FlexBetween>
</FlexBetween>
<IconButton>
<ShareOutlined />
</IconButton>
</FlexBetween>
{isComments && (
<Box mt="0.5rem">
{comments.map((comment, i) => (
<Box key={`${name}-${i}`}>
<Divider />
<Typography sx={{ color: main, m: "0.5rem 0", pl: "1rem" }}>
{comment.comment}
</Typography>
</Box>
))}
<Divider />
<Input
placeholder="Write a comment..."
value={newComment}
onChange={(e) => setNewComment(e.target.value)}
sx={{ marginBottom: "1rem", marginTop:"0.5rem" }}
/>
<Button
onClick={handlePostComment}
sx={{
left:"2rem",
padding:"0.1rem",
color: palette.background.alt,
backgroundColor: palette.primary.main,
borderRadius: "3rem",
}}>
Post
</Button>
</Box>
)}
</WidgetWrapper>
);
};
export default PostWidget;
UserWidget.jsx
import {
ManageAccountsOutlined,
EditOutlined,
LocationOnOutlined,
WorkOutlineOutlined,
} from "@mui/icons-material";
import { Box, Typography, Divider, useTheme } from "@mui/material";
import UserImage from "components/UserImage";
import FlexBetween from "components/FlexBetween";
import WidgetWrapper from "components/WidgetWrapper";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
};
useEffect(() => {
getUser();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
if (!user) {
return null;
}
const {
firstName,
lastName,
location,
occupation,
viewedProfile,
impressions,
friends,
} = user;
return (
<div
style={{
position: "sticky",
top: "7rem",
zIndex: 100,
}}
>
<WidgetWrapper>
{/* FIRST ROW */}
<FlexBetween
gap="0.5rem"
pb="1.1rem"
onClick={() => navigate(`/profile/${userId}`)}
>
<FlexBetween gap="1rem">
<UserImage image={picturePath} />
<Box>
<Typography
variant="h4"
color={dark}
fontWeight="500"
sx={{
"&:hover": {
color: palette.primary.light,
cursor: "pointer",
},
}}
>
{firstName} {lastName}
</Typography>
<Typography color={medium}>{friends.length} friends</Typography>
</Box>
</FlexBetween>
<ManageAccountsOutlined />
</FlexBetween>
<Divider />
<Divider />
<Divider />
<FlexBetween gap="1rem">
<FlexBetween gap="1rem">
<img src="../assets/linkedin.png" alt="linkedin" />
<Box>
<Typography color={main} fontWeight="500">
Linkedin
</Typography>
<Typography color={medium}>Network Platform</Typography>
</Box>
</FlexBetween>
<EditOutlined sx={{ color: main }} />
</FlexBetween>
</Box>
</WidgetWrapper>
</div>
);
};
export default UserWidget;
state → index.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
mode: "light",
user: null,
token: null,
posts: [],
};
App.js
import {BrowserRouter as Router,Routes,Route, Navigate} from "react-router-dom";
import HomePage from "./scenes/homePage";
import LoginPage from "./scenes/loginPage";
import ProfilePage from "./scenes/profilePage";
import { useMemo } from "react";
import { useSelector } from "react-redux/es/hooks/useSelector";
import { CssBaseline,ThemeProvider} from "@mui/material";
import { createTheme } from "@mui/material/styles";
import { themeSettings } from "./theme";
function App() {
const mode = useSelector((state) => state.mode);
const theme = useMemo(() => createTheme(themeSettings(mode)),[mode]);
const isAuth = Boolean(useSelector((state) => state.token))
return (
<div className="app">
<Router>
<ThemeProvider theme={theme}>
<CssBaseline />
<Routes>
<Route path="/" element={<LoginPage />} />
<Route
path="/home"
element={isAuth ? <HomePage /> : <Navigate to="/" />}
/>
<Route
path="/profile/:userId"
element={isAuth ? <ProfilePage /> : <Navigate to="/" />}
/>
</Routes>
</ThemeProvider>
</Router>
</div>
);
}
export default App;
index.css
@import
url('https://fonts.googleapis.com/css2?family=Ysabeau+SC:wght@200;500;700&display=sw
ap');
html,
body,
#root,
.app{
height: 100%;
width: 100%;
font-family: "Ysabeau SC", sans-serif;
}
Index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import authReducer from "./state";
import App from './App';
import { configureStore} from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import {
persistStore,persistReducer,FLUSH,REHYDRATE,PAUSE,PERSIST,PURGE,REGISTER
} from "redux-persist";
import storage from 'redux-persist/lib/storage';
import { PersistGate } from 'redux-persist/integration/react';
</PersistGate>
</Provider>
</React.StrictMode>
);
Theme.js
// color design tokens export
export const colorTokens = {
grey: {
0: "#FFFFFF",
10: "#F6F6F6",
50: "#F0F0F0",
100: "#E0E0E0",
200: "#C2C2C2",
300: "#A3A3A3",
400: "#858585",
500: "#666666",
600: "#4D4D4D",
700: "#333333",
800: "#1A1A1A",
900: "#0A0A0A",
1000: "#000000",
},
primary: {
50: "#E6FBFF",
100: "#CCF7FE",
200: "#99EEFD",
300: "#66E6FC",
400: "#33DDFB",
500: "#00D5FA",
600: "#00A0BC",
700: "#006B7D",
800: "#00353F",
900: "#001519",
},
};
},
h2: {
fontFamily: ["Ysabeau SC", "sans-serif"].join(","),
fontSize: 32,
},
h3: {
fontFamily: ["Ysabeau SC", "sans-serif"].join(","),
fontSize: 24,
},
h4: {
fontFamily: ["Ysabeau SC", "sans-serif"].join(","),
fontSize: 20,
},
h5: {
fontFamily: ["Ysabeau SC", "sans-serif"].join(","),
fontSize: 16,
},
h6: {
fontFamily: ["Ysabeau SC", "sans-serif"].join(","),
fontSize: 14,
},
},
};
};
Server
Controllers → auth.js
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import User from "../models/User.js";
// Logging in
export const login = async(req,res) => {
try{
const { email,password} = req.body;
const user = await User.findOne({email:email});
if(!user) return (
//toast("User does not exist"),
res.status(400).json({msg: "USer does not exist. "}));
Controllers → posts.js
import Post from "../models/Post.js";
import User from "../models/User.js";
/* CREATE */
export const createPost = async (req, res) => {
try {
const { userId, description, picturePath } = req.body;
const user = await User.findById(userId);
const newPost = new Post({
userId,
firstName: user.firstName,
lastName: user.lastName,
location: user.location,
description,
userPicturePath: user.picturePath,
picturePath,
likes: {},
comments: [],
});
await newPost.save();
res.status(201).json(post);
} catch (err) {
res.status(409).json({ message: err.message });
}
};
/* READ */
export const getFeedPosts = async (req, res) => {
try {
const post = await Post.find();
res.status(200).json(post);
} catch (err) {
res.status(404).json({ message: err.message });
}
};
/* UPDATE */
export const likePost = async (req, res) => {
try {
const { id } = req.params;
const { userId } = req.body;
const post = await Post.findById(id);
const isLiked = post.likes.get(userId);
if (isLiked) {
post.likes.delete(userId);
} else {
post.likes.set(userId, true);
}
);
res.status(200).json(updatedPost);
} catch (err) {
res.status(404).json({ message: err.message });
}
};
/* CREATE COMMENT */
export const createComment = async (req, res) => {
try {
const { id, userId, comment } = req.body;
const post = await Post.findById(id);
if (!post) {
return res.status(404).json({ message: "Post not found" });
}
const newComment = {
userId,
comment,
};
post.comments.push(newComment);
await post.save();
console.log("id:",id);
console.log("userId:", userId);
console.log("comment:", comment);
res.status(201).json(updatedPost);
return commentObject
} catch (err) {
res.status(500).json({ message: err.message });
}
};
Controllers → users.js
import User from "../models/User.js";
/* READ */
export const getUser = async (req, res) => {
try {
const { id } = req.params;
const user = await User.findById(id);
res.status(200).json(user);
} catch (err) {
res.status(404).json({ message: err.message });
}
};
/* UPDATE */
export const addRemoveFriend = async (req, res) => {
try {
const { id, friendId } = req.params;
const user = await User.findById(id);
const friend = await User.findById(friendId);
if (user.friends.includes(friendId)) {
user.friends = user.friends.filter((id) => id !== friendId);
friend.friends = friend.friends.filter((id) => id !== id);
} else {
user.friends.push(friendId);
friend.friends.push(id);
}
await user.save();
await friend.save();
res.status(200).json(formattedFriends);
} catch (err) {
res.status(404).json({ message: err.message });
}
};
Middleware → auth.js
import jwt from "jsonwebtoken";
if (!token) {
return res.status(403).send("Access Denied");
}
if (token.startsWith("Bearer ")) {
token = token.slice(7, token.length).trimLeft();
}
Models → User.js
import mongoose from "mongoose";
},
lastName: {
type: String,
required: true,
min: 2,
max: 50,
},
email: {
type: String,
required: true,
max: 50,
unique: true,
},
password: {
type: String,
required: true,
min: 5,
},
picturePath: {
type: String,
default: "",
},
friends: {
type: Array,
default: [],
},
location: String,
occupation: String,
viewedProfile: Number,
impressions: Number,
},
{ timestamps: true }
);
routes → auth.js
import express from "express";
import {login} from "../controllers/auth.js";
router.post('/login',login);
routes → posts.js
import express from "express";
import { getFeedPosts, getUserPosts, likePost, createComment } from "../controllers/posts.js";
import { verifyToken } from "../middleware/auth.js";
// READ
router.get("/", verifyToken, getFeedPosts);
router.get("/:userId/posts", verifyToken, getUserPosts);
// UPDATE
router.patch("/:id/like", verifyToken, likePost);
router.post("/:id/comments", verifyToken, createComment);
export default router;
routes → users.js
import express from "express";
import {getUser,getUserFriends,addRemoveFriend} from "../controllers/users.js";
import {verifyToken} from "../middleware/auth.js";
// READ operations
router.get("/:id",verifyToken,getUser);
router.get("/:id/friends",verifyToken,getUserFriends);
// UPDATE operations
router.patch("/:id/:friendId",verifyToken,addRemoveFriend);
.env
MONGO_URL =
"mongodb+srv://atharvagholap24:[email protected]/?retryWrites=tr
ue&w=majority"
PORT=3001
JWT_SECRET = "idontunderstandbutiloveyou"
Index.js
/* CONFIGURATIONS */
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
dotenv.config();
const app = express();
app.use(express.json());
app.use(helmet());
app.use(helmet.crossOriginResourcePolicy({ policy: "cross-origin" }));
app.use(morgan("common"));
app.use(bodyParser.json({ limit: "30mb", extended: true }));
app.use(bodyParser.urlencoded({ limit: "30mb", extended: true }));
app.use(cors());
app.use("/assets", express.static(path.join(__dirname, "public/assets")));
/* FILE STORAGE */
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "public/assets");
},
filename: function (req, file, cb) {
cb(null, file.originalname);
},
});
const upload = multer({ storage });
/* ROUTES */
app.use("/auth", authRoutes);
app.use("/users", userRoutes);
app.use("/posts", postRoutes);
/* MONGOOSE SETUP */
const PORT = process.env.PORT || 6001;
mongoose
.connect(process.env.MONGO_URL, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => {
app.listen(PORT, () => console.log(`Server Port: ${PORT}`));
//User.insertMany(users);
//Post.insertMany(posts);
})
.catch((error) => console.log(`${error} did not connect`));
6. Results
3 email Required, email Email of the user cannot be empty and its
format should be correct
4 password Required Password of the account cannot be empty
6.2 Screenshots
6.2.1 Login Page
Figure 12
Figure 13
Figure 15
Figure 14
Figure 16
6.2.6 Post
Figure 17
6.2.7 Logout
• Adding Feature to Post variety of contents like Clips, Audios and any other
files
• Dynamic Advertisement of Sponsors
• Active Feature to Create our own Advertisement
• Searching different users from the search bar
• Active incorporating of post impact and number of visitors
• Receive notifications when running is background
• More detailed comments with extra feature to delete our own comment, edit
it and like others comments
• Able to share a post by generating its url
• Active Feature to edit out profile details
7.2 Conclusions
Wise aims to provide a seamless and user-friendly platform for people from all
around the world to connect, interact, and share content. The core objective of this
application is to foster emotional and professional connections among users while
ensuring simplicity and safety. Unlike other existing social media platforms, this
web app focuses on providing a straightforward and easy-to-use interface,
eliminating unnecessary complexities.
8. References
www.stackoverflow.com
www.react.dev
www.mongodb.com/docs
www.expressjs.com
www.mongodb.com/languages/mern-stack-tutorial
www.react-redux.com
9. Annexures