├── client ├── src │ ├── Components │ │ ├── FollowerItem │ │ │ └── index.js │ │ ├── ImageUploader │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── PageLikeBtn │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── UnBlockBtn │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── PrivateGroupContainer │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── TimeLine │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── SettingsDropDown │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── InlineLoader │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ImageUploaderModal │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── SaveButton │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── DeleteBtn │ │ │ ├── index.styles.scss │ │ │ └── index.js │ │ ├── Container │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── Loader.js │ │ ├── Menu │ │ │ └── MenuItem.js │ │ ├── notify.js │ │ ├── FormError │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── MenuDropDown │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── Toast │ │ │ ├── index.styles.scss │ │ │ └── index.js │ │ ├── Like │ │ │ ├── index.scss │ │ │ └── index.js │ │ ├── SettingDropDownItem │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── GroupLeaveBtn │ │ │ └── index.js │ │ ├── ImageUploadField │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── CancelBtn │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── BlockButton │ │ │ ├── index.styles.scss │ │ │ └── index.js │ │ ├── EditPostModal │ │ │ └── index.style.scss │ │ ├── List │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── FollowBtn │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ErrorComponent │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── RemoveBtn │ │ │ └── index.js │ │ ├── TextArea │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── BlockItem │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── NotiIcon │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── BlockContainer │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── SignUp │ │ │ └── index.style.scss │ │ ├── Posts │ │ │ ├── posts.style.scss │ │ │ └── index.js │ │ ├── ErrorBoundary │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── FormField │ │ │ ├── RadioButton.js │ │ │ ├── index.js │ │ │ ├── radio.scss │ │ │ └── index.style.scss │ │ ├── modal │ │ │ ├── ScrollModal.component.js │ │ │ └── modal.component.js │ │ ├── SecondaryNav │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── FollowerList │ │ │ └── index.js │ │ ├── FollowingList │ │ │ └── index.js │ │ ├── ConfirmPasswordModal │ │ │ ├── index.styles.scss │ │ │ └── index.js │ │ ├── PGList │ │ │ ├── PGItem.js │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── CustomLink │ │ │ └── index.js │ │ ├── About │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── Login │ │ │ ├── login.Style.scss │ │ │ └── index.js │ │ ├── PeopleItem │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ProfileAbout │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── CommentDeleteBtn │ │ │ └── index.js │ │ ├── JoinBtn │ │ │ └── index.js │ │ ├── Notification │ │ │ └── index.js │ │ ├── GroupsGetter │ │ │ └── index.js │ │ ├── Header │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── CoverPhoto │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── TimelineNav │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ProfilePhoto │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── CommentItem │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── MembersModal │ │ │ └── index.js │ │ ├── GroupMemberList │ │ │ └── index.js │ │ ├── RequestsModal │ │ │ └── index.js │ │ ├── CommentList │ │ │ └── index.style.scss │ │ ├── NotificationList │ │ │ └── index.scss │ │ └── Post │ │ │ └── post.style.scss │ ├── Pages │ │ ├── GroupsPage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── BlockMessagePage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ProfileEditPage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── HomePage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ProfilePage │ │ │ └── index.style.scss │ │ ├── PageSettingsPage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── BlockedUserPage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── NotFoundPage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── ExpiredLinkPage │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── GroupPage │ │ │ └── index.style.scss │ │ ├── RegisterPage │ │ │ ├── index.js │ │ │ └── index.style.scss │ │ ├── FbPage │ │ │ └── index.style.scss │ │ ├── PeoplePage │ │ │ ├── index.styles.scss │ │ │ └── index.js │ │ ├── CreateGroupPage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ ├── CreateFbPagePage │ │ │ ├── index.style.scss │ │ │ └── index.js │ │ └── PagesMainPage │ │ │ └── index.js │ ├── Constants │ │ └── index.js │ ├── sounds │ │ └── noti.mp3 │ ├── history.js │ ├── sass │ │ ├── components │ │ │ ├── _link.scss │ │ │ ├── _modal.scss │ │ │ ├── _logo.scss │ │ │ └── _button.scss │ │ ├── _animations.scss │ │ ├── _utils.scss │ │ ├── index.scss │ │ ├── _variables.scss │ │ ├── _base.scss │ │ ├── _layout.scss │ │ └── _mixins.scss │ ├── Reducers │ │ ├── setError.js │ │ ├── Image.js │ │ ├── checkUser.js │ │ ├── people.js │ │ ├── pagination.js │ │ ├── auth.js │ │ ├── blockedUser.js │ │ ├── notification.js │ │ ├── index.js │ │ ├── pages.js │ │ ├── groupReducer.js │ │ ├── profileReducer.js │ │ └── Posts.js │ ├── utils │ │ ├── tokenUtils.js │ │ └── transform.js │ ├── Images │ │ ├── logo.svg │ │ ├── page.svg │ │ ├── bell.svg │ │ ├── home.svg │ │ ├── remove.svg │ │ ├── people.svg │ │ ├── activeHome.svg │ │ └── group.svg │ ├── index.js │ ├── Api │ │ └── index.js │ └── Actions │ │ ├── peopleActions.js │ │ ├── index.js │ │ ├── commentActions.js │ │ ├── notification.js │ │ └── blockActions.js ├── public │ ├── fb.ico │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── robots.txt │ ├── manifest.json │ └── index.html ├── webpack.config.js ├── .gitignore └── package.json ├── fb-clone-backend ├── constants │ └── index.js ├── config │ ├── production.json │ ├── custom-environment-variables.json │ ├── default.json │ └── development.json ├── public │ ├── free.jpg │ └── free1.jpg ├── .gitignore ├── startup │ ├── prod.js │ ├── notification.js │ ├── db.js │ ├── logger.js │ └── routes.js ├── helpers │ └── extractPosts.js ├── Middleware │ ├── errorMiddlware.js │ ├── asyncMiddleware.js │ ├── auth.js │ ├── pageAdmin.js │ ├── groupAdmin.js │ └── confirmPassword.js ├── models │ ├── AdminSchema.js │ ├── Likes.js │ ├── Followers.js │ ├── Following.js │ ├── profile_dp.js │ ├── ActiveUser.js │ ├── Comment.js │ ├── NotificationModel.js │ ├── PageModel.js │ ├── profile.js │ ├── user.js │ ├── PostModel.js │ └── GroupModel.js ├── index.js ├── package.json ├── routes │ ├── unBlockRoutes.js │ ├── peopleRoutes.js │ ├── notification.js │ ├── home.js │ └── user.js └── notification │ └── Realtime.js └── readme.md /client/src/Components/FollowerItem/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Pages/GroupsPage/index.style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Components/ImageUploader/index.style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Components/PageLikeBtn/index.style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Components/UnBlockBtn/index.style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Pages/BlockMessagePage/index.style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Components/PrivateGroupContainer/index.style.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/public/fb.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/client/public/fb.ico -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/client/public/logo192.png -------------------------------------------------------------------------------- /client/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/client/public/logo512.png -------------------------------------------------------------------------------- /client/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /fb-clone-backend/constants/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Notification: "NOTIFICATION", 3 | }; 4 | -------------------------------------------------------------------------------- /client/src/Constants/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | NotificationType: "RealTime/NOTIFICATION", 3 | }; 4 | -------------------------------------------------------------------------------- /client/src/sounds/noti.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/client/src/sounds/noti.mp3 -------------------------------------------------------------------------------- /client/src/Components/TimeLine/index.style.scss: -------------------------------------------------------------------------------- 1 | .timeline-section { 2 | float: right; 3 | width: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/history.js: -------------------------------------------------------------------------------- 1 | import {createBrowserHistory} from "history"; 2 | 3 | export default createBrowserHistory(); -------------------------------------------------------------------------------- /fb-clone-backend/config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongoUri": "", 3 | "secretKey": "", 4 | "clientUrl": "" 5 | } 6 | -------------------------------------------------------------------------------- /fb-clone-backend/public/free.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/fb-clone-backend/public/free.jpg -------------------------------------------------------------------------------- /fb-clone-backend/public/free1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Asad8746/FbCLone/HEAD/fb-clone-backend/public/free1.jpg -------------------------------------------------------------------------------- /client/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: /.svg$/, 3 | use: ["@svgr/webpack", "url-loader"], 4 | }; 5 | -------------------------------------------------------------------------------- /fb-clone-backend/.gitignore: -------------------------------------------------------------------------------- 1 | #dependencies 2 | /node_modules/ 3 | /tests/ 4 | /config/test.json 5 | /config/development.json 6 | *.log -------------------------------------------------------------------------------- /client/src/Components/SettingsDropDown/index.style.scss: -------------------------------------------------------------------------------- 1 | .setting-dropdown { 2 | background-color: white; 3 | width: 30rem; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/sass/components/_link.scss: -------------------------------------------------------------------------------- 1 | .page-link { 2 | display: block; 3 | font-size: 1.4rem; 4 | padding: 1.5rem 0; 5 | color: $primary-color; 6 | } 7 | -------------------------------------------------------------------------------- /fb-clone-backend/config/custom-environment-variables.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongoUri": "mongoUri", 3 | "secretKey": "secretKey", 4 | "clientUrl": "clientUrl" 5 | } 6 | -------------------------------------------------------------------------------- /fb-clone-backend/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fb-Clone", 3 | "mongoUri": "", 4 | "secretKey": "", 5 | "clientUrl": "http://localhost:3000" 6 | } 7 | -------------------------------------------------------------------------------- /fb-clone-backend/config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "mongoUri": "mongodb://localhost/Fbclone", 3 | "secretKey": "1234", 4 | "clientUrl": "http://localhost:3000" 5 | } 6 | -------------------------------------------------------------------------------- /client/src/sass/_animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes goToDark { 2 | 0% { 3 | background-color: rgba(0, 0, 0, 0.3); 4 | } 5 | 100% { 6 | background-color: rgba(0, 0, 0, 0.9); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/src/Components/InlineLoader/index.style.scss: -------------------------------------------------------------------------------- 1 | .loader__container { 2 | background-color: white; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | padding: 2rem; 7 | } 8 | -------------------------------------------------------------------------------- /client/src/Pages/ProfileEditPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .profile-edit { 3 | &__heading { 4 | font-size: 1.8rem; 5 | color: $font-color; 6 | margin-left: 1rem; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /fb-clone-backend/startup/prod.js: -------------------------------------------------------------------------------- 1 | const helmet = require("helmet"); 2 | const compression = require("compression"); 3 | 4 | module.exports = (app) => { 5 | app.use(helmet()); 6 | app.use(compression()); 7 | }; 8 | -------------------------------------------------------------------------------- /client/src/Components/ImageUploaderModal/index.style.scss: -------------------------------------------------------------------------------- 1 | .upload-placeholder { 2 | &__container { 3 | padding: 2rem 3rem; 4 | } 5 | &__img { 6 | height: 50rem; 7 | width: 100%; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fb-clone-backend/helpers/extractPosts.js: -------------------------------------------------------------------------------- 1 | module.exports = (user_profiles) => { 2 | let posts = user_profiles.reduce((arr,user_profile)=> { 3 | return [...arr,...user_profile.posts]; 4 | },[]) 5 | return posts; 6 | } -------------------------------------------------------------------------------- /client/src/Reducers/setError.js: -------------------------------------------------------------------------------- 1 | export default (state="",action) => { 2 | switch(action.type) { 3 | case "SET_ERROR_MESSAGE": 4 | return action.payload; 5 | default : 6 | return state; 7 | } 8 | } -------------------------------------------------------------------------------- /fb-clone-backend/Middleware/errorMiddlware.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | 3 | module.exports = (ex, req, res, next) => { 4 | winston.error(ex.message, ex); 5 | return res.status(500).send("Oops Something goes wrong"); 6 | }; 7 | -------------------------------------------------------------------------------- /client/src/Components/SaveButton/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .save-btn { 3 | background-color: $primary-color; 4 | border: none; 5 | .icon { 6 | color: $white-color; 7 | font-size: 1.1rem; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/src/Components/DeleteBtn/index.styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .delete-btn { 3 | background-color: tomato; 4 | border: 1px solid tomato; 5 | .icon { 6 | color: $white-color; 7 | font-size: 1.1rem; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fb-clone-backend/Middleware/asyncMiddleware.js: -------------------------------------------------------------------------------- 1 | module.exports = function (handler) { 2 | return async (req, res, next) => { 3 | try { 4 | await handler(req, res); 5 | } catch (ex) { 6 | next(ex); 7 | } 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /fb-clone-backend/models/AdminSchema.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | module.exports = new mongoose.Schema({ 4 | _id: { 5 | type: mongoose.Types.ObjectId, 6 | required: true, 7 | ref: "Profile", 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /client/src/sass/components/_modal.scss: -------------------------------------------------------------------------------- 1 | .modal { 2 | &__bg { 3 | background-color: rgba($black-color, 0.8); 4 | position: fixed; 5 | top: 0; 6 | right: 0; 7 | left: 0; 8 | bottom: 0; 9 | z-index: 99999; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /client/src/Components/Container/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .layout-container { 3 | .hidden__container { 4 | height: 5.6rem; 5 | width: 100%; 6 | } 7 | @include respond(phone) { 8 | margin-bottom: 5.6rem; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client/src/Components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Loader = () => { 4 | return ( 5 |
6 |
Loading
7 |
8 | ); 9 | }; 10 | 11 | export default Loader; 12 | -------------------------------------------------------------------------------- /client/src/Components/Menu/MenuItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const MenuItem = ({ icon, onClick }) => { 4 | return ( 5 |
6 | {icon} 7 |
8 | ); 9 | }; 10 | 11 | export default MenuItem; 12 | -------------------------------------------------------------------------------- /client/src/Reducers/Image.js: -------------------------------------------------------------------------------- 1 | import { imageTypes } from "./constants"; 2 | 3 | export default (state = null, action) => { 4 | switch (action.type) { 5 | case imageTypes.setImage: 6 | return action.payload; 7 | default: 8 | return state; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /fb-clone-backend/models/Likes.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const likeSchema = new mongoose.Schema({ 4 | _id: { 5 | type: mongoose.Types.ObjectId, 6 | ref: "Profile", 7 | requried: true, 8 | }, 9 | }); 10 | 11 | module.exports = likeSchema; 12 | -------------------------------------------------------------------------------- /client/src/Reducers/checkUser.js: -------------------------------------------------------------------------------- 1 | import { checkTypes } from "./constants"; 2 | 3 | export default (state = false, action) => { 4 | switch (action.type) { 5 | case checkTypes.checkUser: 6 | return action.payload; 7 | default: 8 | return state; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /client/src/Pages/HomePage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .home { 3 | width: 60%; 4 | margin: 0 auto; 5 | margin-top: 1rem; 6 | @include respond(tab-port) { 7 | width: 100%; 8 | } 9 | @include respond(phone) { 10 | margin-bottom: 5.6rem; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/src/Pages/ProfilePage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | 3 | .profile { 4 | width: 70%; 5 | margin: 0 auto; 6 | // margin-top: 5.6rem; 7 | color: rgb(28, 30, 33); 8 | position: relative; 9 | @include respond(tab-port) { 10 | width: 100%; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /fb-clone-backend/startup/notification.js: -------------------------------------------------------------------------------- 1 | const Notification = require("../notification/Realtime"); 2 | 3 | module.exports = (io, app) => { 4 | const notification = new Notification(io); 5 | app.use((req, res, next) => { 6 | req.notification = notification; 7 | next(); 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /client/src/Components/notify.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { toast } from "react-toastify"; 3 | import Toast from "./Toast"; 4 | 5 | const notify = (lastNoti) => { 6 | toast(, { 7 | hideProgressBar: true, 8 | style: {}, 9 | }); 10 | }; 11 | export default notify; 12 | -------------------------------------------------------------------------------- /client/src/Pages/HomePage/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Posts from "../../Components/Posts"; 3 | import "./index.style.scss"; 4 | 5 | const Home = () => { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | }; 12 | 13 | export default Home; 14 | -------------------------------------------------------------------------------- /client/src/Components/FormError/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | 3 | .form__error { 4 | margin-left: 1.5rem; 5 | padding-top: 1rem; 6 | font-size: 1.2rem; 7 | color: $error-color; 8 | position: absolute; 9 | bottom: 0; 10 | text-transform: capitalize; 11 | letter-spacing: 1px; 12 | } 13 | -------------------------------------------------------------------------------- /client/src/sass/_utils.scss: -------------------------------------------------------------------------------- 1 | .u-center-text { 2 | width: 100%; 3 | display: block; 4 | text-align: center; 5 | } 6 | 7 | .u-margin-top-medium { 8 | margin-top: 4rem; 9 | @include respond(tab-port) { 10 | margin-top: 2rem; 11 | } 12 | } 13 | .u-margin-bottom-medium { 14 | margin-bottom: 4rem; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/Components/InlineLoader/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | const InlineLoader = () => { 4 | return ( 5 |
6 |
7 |
8 | ); 9 | }; 10 | 11 | export default InlineLoader; 12 | -------------------------------------------------------------------------------- /fb-clone-backend/models/Followers.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | module.exports = new mongoose.Schema({ 4 | _id: { 5 | type: mongoose.Types.ObjectId, 6 | required: true, 7 | ref: "Profile", 8 | }, 9 | follower_name: { 10 | type: String, 11 | required: true, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /fb-clone-backend/models/Following.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | module.exports = new mongoose.Schema({ 4 | _id: { 5 | type: mongoose.Types.ObjectId, 6 | required: true, 7 | ref: "Profile", 8 | }, 9 | followed_name: { 10 | type: String, 11 | required: true, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /fb-clone-backend/models/profile_dp.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const Profile_Dp = new mongoose.Schema({ 4 | name: String, 5 | data: Buffer, 6 | contentType: String, 7 | uploaded_Date: { 8 | type: Date, 9 | default: Date.now(), 10 | }, 11 | }); 12 | 13 | module.exports = Profile_Dp; 14 | -------------------------------------------------------------------------------- /client/src/sass/index.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "./mixins.scss"; 3 | @import "./utils"; 4 | @import "./animations"; 5 | 6 | @import "./base"; 7 | @import "./layout"; 8 | 9 | @import "./components/button"; 10 | @import "./components/link"; 11 | @import "./components/logo"; 12 | @import "./components/modal"; 13 | -------------------------------------------------------------------------------- /client/src/Components/MenuDropDown/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | const DropDownMenu = ({ children }) => { 4 | return ( 5 |
e.stopPropagation()}> 6 | {children} 7 |
8 | ); 9 | }; 10 | 11 | export default DropDownMenu; 12 | -------------------------------------------------------------------------------- /client/src/Components/FormError/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | const FormError = ({ meta }) => { 4 | const { touched, error } = meta; 5 | if (touched && error) { 6 | return
{error}
; 7 | } 8 | return <>; 9 | }; 10 | 11 | export default FormError; 12 | -------------------------------------------------------------------------------- /client/src/Components/SaveButton/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | const SaveButton = () => { 4 | return ( 5 | 8 | ); 9 | }; 10 | 11 | export default SaveButton; 12 | -------------------------------------------------------------------------------- /client/src/Components/Container/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | const Container = ({ children }) => { 4 | return ( 5 |
6 |
7 | {children} 8 |
9 | ); 10 | }; 11 | 12 | export default Container; 13 | -------------------------------------------------------------------------------- /client/src/sass/components/_logo.scss: -------------------------------------------------------------------------------- 1 | .logo { 2 | font-size: 2.5rem; 3 | text-transform: uppercase; 4 | margin-bottom: 2.5rem; 5 | text-align: center; 6 | color: $logo-color; 7 | background: linear-gradient(to right bottom, $logo-color, $logo-color-light); 8 | -webkit-background-clip: text; 9 | background-clip: text; 10 | } 11 | -------------------------------------------------------------------------------- /client/src/utils/tokenUtils.js: -------------------------------------------------------------------------------- 1 | export const getToken = () => { 2 | return localStorage.getItem("token"); 3 | }; 4 | 5 | export const setToken = (headers) => { 6 | const token = headers["x-auth-token"]; 7 | localStorage.setItem("token", token); 8 | }; 9 | 10 | export const removeToken = () => { 11 | localStorage.removeItem("token"); 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/Components/Toast/index.styles.scss: -------------------------------------------------------------------------------- 1 | .notification--container { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: center; 5 | #noti_avatar { 6 | margin-right: 10px; 7 | } 8 | .notification { 9 | font-size: 12px; 10 | display: flex; 11 | align-items: center; 12 | letter-spacing: 1px; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/Components/Like/index.scss: -------------------------------------------------------------------------------- 1 | .like { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | cursor: pointer; 6 | font-family: "Open Sans"; 7 | font-weight: 600; 8 | font-size: 14px; 9 | color: "#a4a4a9"; 10 | font-weight: 500; 11 | .like__icon { 12 | color: tomato; 13 | font-size: 17px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/Components/SettingDropDownItem/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import "./index.style.scss"; 4 | const DropDownItem = ({ label, link }) => { 5 | return ( 6 | 7 | {label} 8 | 9 | ); 10 | }; 11 | 12 | export default DropDownItem; 13 | -------------------------------------------------------------------------------- /fb-clone-backend/models/ActiveUser.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const active_user_schema = new mongoose.Schema({ 4 | socket_id: { 5 | type: String, 6 | required: true, 7 | }, 8 | user_id: { 9 | type: mongoose.Types.ObjectId, 10 | required: true, 11 | }, 12 | }); 13 | 14 | module.exports = mongoose.model("ActiveUsers", active_user_schema); 15 | -------------------------------------------------------------------------------- /fb-clone-backend/Middleware/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("config"); 3 | module.exports = (req, res, next) => { 4 | const token = req.headers["x-auth-token"]; 5 | try { 6 | let user = jwt.verify(token, config.get("secretKey")); 7 | req.user = user; 8 | next(); 9 | } catch (ex) { 10 | res.status(400).send("Invalid Token"); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /client/src/Components/GroupLeaveBtn/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { leaveGroup } from "../../Actions"; 3 | 4 | const GroupLeaveBtn = ({ id }) => { 5 | return ( 6 | 13 | ); 14 | }; 15 | 16 | export default GroupLeaveBtn; 17 | -------------------------------------------------------------------------------- /client/src/Components/ImageUploadField/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .image-field { 3 | width: 90%; 4 | margin: 0 auto; 5 | &__label { 6 | color: $font-color; 7 | text-align: center; 8 | font-weight: 600; 9 | text-transform: capitalize; 10 | letter-spacing: 2px; 11 | display: block; 12 | font-size: 1.3rem; 13 | margin-bottom: 2rem; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /client/src/Components/CancelBtn/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .cancel-btn { 3 | background-color: $white-color-1; 4 | border: 1px solid $primary-color; 5 | border-radius: 100px; 6 | position: relative; 7 | cursor: pointer; 8 | transition: all 0.2s ease; 9 | text-transform: uppercase; 10 | .cancel-icon { 11 | color: $primary-color; 12 | font-size: 1.1rem; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/src/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #3b5998; 2 | $error-color: rgb(248, 121, 71); 3 | $valid-color: #11998e; 4 | $font-color: rgb(64, 74, 94); 5 | $font-color-2: #999; 6 | $white-color: #fff; 7 | $white-color-1: #f7f7f7; 8 | $grey-color-border: #cccc; 9 | $grey-color-2: rgb(177, 177, 177); 10 | $black-color: #000; 11 | $main-bg-color: #f7f7f7; 12 | $logo-color: #1877f2; 13 | $logo-color-light: #0f4c9a; 14 | -------------------------------------------------------------------------------- /client/src/Components/BlockButton/index.styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .block-btn { 3 | border: none; 4 | padding: 1rem 2rem; 5 | transition: all 0.5s ease; 6 | background-color: tomato; 7 | border-radius: 0.5rem; 8 | cursor: pointer; 9 | color: white; 10 | font-size: 1.1rem; 11 | font-weight: 600; 12 | letter-spacing: 0.2rem; 13 | &:hover { 14 | border-radius: 5rem; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/Pages/PageSettingsPage/index.style.scss: -------------------------------------------------------------------------------- 1 | .pg__actions { 2 | display: flex; 3 | flex-direction: row; 4 | justify-content: space-evenly; 5 | padding: 20px; 6 | } 7 | 8 | .animateBtn:hover { 9 | transform: translateY(-10px); 10 | box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); 11 | } 12 | .animateBtn:active { 13 | transform: translateY(-3px); 14 | box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); 15 | } 16 | -------------------------------------------------------------------------------- /client/.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.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /client/src/Components/DeleteBtn/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.styles.scss"; 3 | const DeleteBtn = ({ onDeleteBtnClick }) => { 4 | return ( 5 | 12 | ); 13 | }; 14 | 15 | export default DeleteBtn; 16 | -------------------------------------------------------------------------------- /client/src/Components/EditPostModal/index.style.scss: -------------------------------------------------------------------------------- 1 | .edit-post { 2 | &__label { 3 | text-align: center; 4 | color: #555; 5 | margin: 1rem 0; 6 | font-weight: 500; 7 | font-size: 1.3rem; 8 | text-transform: uppercase; 9 | letter-spacing: 2px; 10 | display: block; 11 | } 12 | &__textarea-container { 13 | margin: 0 auto; 14 | margin-bottom: 1rem; 15 | width: 95%; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/Components/List/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .list { 3 | &__container { 4 | background-color: $white-color; 5 | padding: 2rem; 6 | } 7 | &__grid-container { 8 | display: grid; 9 | gap: 1rem; 10 | background-color: $white-color; 11 | grid-template-columns: 1fr 1fr; 12 | @include respond(phone) { 13 | grid-template-columns: 1fr; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/Components/FollowBtn/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .follow__button { 3 | border: none; 4 | padding: 1rem 2rem; 5 | transition: all 0.5s ease; 6 | background-color: $logo-color; 7 | border-radius: 0.5rem; 8 | cursor: pointer; 9 | color: white; 10 | font-size: 1.1rem; 11 | font-weight: 600; 12 | letter-spacing: 0.2rem; 13 | &:hover { 14 | border-radius: 5rem; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/src/Components/UnBlockBtn/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { unBlockUser } from "../../Actions"; 3 | import { connect } from "react-redux"; 4 | 5 | const UnBlockBtn = ({ id, unBlockUser }) => { 6 | return ( 7 | 10 | ); 11 | }; 12 | 13 | export default connect(null, { unBlockUser })(UnBlockBtn); 14 | -------------------------------------------------------------------------------- /client/src/Components/ErrorComponent/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .error__message { 3 | width: 90%; 4 | margin: 0 auto; 5 | padding: 0; 6 | line-height: 0; 7 | color: $error-color; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | 12 | p { 13 | display: inline-block; 14 | } 15 | #error__message-icon { 16 | margin-left: 1rem; 17 | cursor: pointer; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/src/Components/SettingDropDownItem/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .setting-dropdown { 3 | &__item { 4 | cursor: pointer; 5 | width: 100%; 6 | display: block; 7 | padding: 2rem; 8 | display: flex; 9 | font-size: 1.2rem; 10 | color: $font-color; 11 | font-weight: 500; 12 | &:hover { 13 | background-color: $white-color-1; 14 | color: $font-color; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/src/sass/_base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 62.5%; 3 | @include respond(large-screen) { 4 | font-size: 75%; 5 | } 6 | @include respond(tab-land) { 7 | font-size: 56.25%; 8 | } 9 | @include respond(tab-port) { 10 | font-size: 50%; 11 | } 12 | @include respond(tab-port) { 13 | font-size: 43.75%; 14 | } 15 | } 16 | body { 17 | background-color: $main-bg-color; 18 | font-family: "Lato", sans-serif; 19 | } 20 | -------------------------------------------------------------------------------- /client/src/Components/MenuDropDown/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .dropdown-menu { 3 | position: absolute; 4 | right: 0; 5 | top: 5.7rem; 6 | transform: translateX(-2rem); 7 | box-shadow: 0 1rem 4rem rgba($black-color, 0.2); 8 | z-index: 600; 9 | } 10 | .dropdown-modal { 11 | height: 100vh; 12 | width: 100vw; 13 | position: fixed; 14 | top: 0; 15 | left: 0; 16 | right: 0; 17 | bottom: 0; 18 | z-index: 500; 19 | } 20 | -------------------------------------------------------------------------------- /client/src/Components/RemoveBtn/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const GroupModalBtn = ({ title, onClick }) => { 5 | return ( 6 | 9 | ); 10 | }; 11 | GroupModalBtn.propTypes = { 12 | title: PropTypes.string.isRequired, 13 | onClick: PropTypes.func.isRequired, 14 | }; 15 | 16 | export default GroupModalBtn; 17 | -------------------------------------------------------------------------------- /client/src/Components/CancelBtn/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import history from "../../history"; 3 | 4 | import "./index.style.scss"; 5 | 6 | const CancelBtn = () => { 7 | return ( 8 | 15 | ); 16 | }; 17 | 18 | export default CancelBtn; 19 | -------------------------------------------------------------------------------- /client/src/Components/TextArea/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .post__textarea { 3 | font-size: 1.2rem; 4 | border: none; 5 | width: 100%; 6 | // text-align: start; 7 | border-bottom: 3px solid $grey-color-border; 8 | resize: none; 9 | overflow: hidden; 10 | padding: 5px; 11 | &::placeholder { 12 | font-size: 1.2rem; 13 | } 14 | &:focus { 15 | outline: none; 16 | border-bottom: 3px solid $primary-color; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/src/Pages/BlockedUserPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .blocked { 3 | &__container { 4 | width: 40%; 5 | background-color: $white-color; 6 | margin: 0 auto; 7 | margin-top: 1rem; 8 | padding: 1rem 2rem; 9 | box-shadow: 0 1rem 4rem rgba($black-color, 0.1); 10 | @include respond(phone) { 11 | width: 100%; 12 | } 13 | } 14 | &__empty { 15 | text-align: center; 16 | padding: 1rem 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/src/Pages/NotFoundPage/index.style.scss: -------------------------------------------------------------------------------- 1 | .nf-container { 2 | position: absolute; 3 | top: 40%; 4 | left: 50%; 5 | transform: translate(-50%, -50%); 6 | text-align: center; 7 | .nf--heading { 8 | text-transform: uppercase; 9 | font-size: 60px; 10 | color: tomato; 11 | font-weight: 100; 12 | letter-spacing: 10px; 13 | } 14 | #nf-btn { 15 | background-color: tomato; 16 | color: white; 17 | font-size: 15px; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fb-clone-backend/startup/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const config = require("config"); 3 | const winston = require("winston"); 4 | 5 | module.exports = function () { 6 | mongoose 7 | .connect(config.get("mongoUri"), { useNewUrlParser: true }) 8 | .then(() => { 9 | console.log(`Connected to Db`); 10 | winston.info("Connected to Database"); 11 | }) 12 | .catch((err) => { 13 | console.log(err.message); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /client/src/Components/PrivateGroupContainer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | const GroupInfoPage = ({ adminName }) => { 5 | return ( 6 |
7 |

Private Group

8 |

Group managed By {adminName}

9 |
10 | ); 11 | }; 12 | GroupInfoPage.propTypes = { 13 | adminName: PropTypes.string.isRequired, 14 | }; 15 | 16 | export default GroupInfoPage; 17 | -------------------------------------------------------------------------------- /fb-clone-backend/Middleware/pageAdmin.js: -------------------------------------------------------------------------------- 1 | const PageModel = require("../models/PageModel"); 2 | 3 | module.exports = async (req, res, next) => { 4 | try { 5 | const page = await PageModel.findById({ _id: req.params.page_id }).select( 6 | "page_admin_id" 7 | ); 8 | if (page.page_admin_id == req.user.profile) { 9 | req.userPage = page; 10 | return next(); 11 | } 12 | return res.sendStatus(401); 13 | } catch (err) { 14 | res.sendStatus(400); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /client/src/Pages/ExpiredLinkPage/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import "./index.style.scss"; 4 | const ExpiredLink = () => { 5 | return ( 6 |
7 |

8 | The Link you are trying to access is expired 9 |

10 | 11 | ← Go Home 12 | 13 |
14 | ); 15 | }; 16 | 17 | export default ExpiredLink; 18 | -------------------------------------------------------------------------------- /client/src/Components/BlockItem/index.style.scss: -------------------------------------------------------------------------------- 1 | .block__item { 2 | display: flex; 3 | justify-content: space-between; 4 | padding: 1rem 1.5rem; 5 | padding-bottom: 0; 6 | &__content { 7 | display: flex; 8 | align-items: center; 9 | .avatar { 10 | height: 3rem; 11 | width: 3rem; 12 | border-radius: 50%; 13 | } 14 | p { 15 | margin-left: 1.3rem; 16 | font-size: 1.4rem; 17 | font-weight: 500; 18 | letter-spacing: 1px; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/Components/NotiIcon/index.style.scss: -------------------------------------------------------------------------------- 1 | #noti__bell-icon { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | } 6 | .count-info { 7 | font-size: 1rem; 8 | height: 1.8rem; 9 | width: 1.8rem; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | border-radius: 100px; 14 | background-color: tomato; 15 | position: absolute; 16 | right: 0; 17 | top: 0; 18 | transform: translateX(40%); 19 | color: white; 20 | font-weight: 700; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/Components/BlockContainer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import "./index.style.scss"; 5 | const BlockMessage = ({ message }) => { 6 | return ( 7 |
8 |

{message}

9 |
10 | ); 11 | }; 12 | BlockMessage.defaultProps = { 13 | message: "", 14 | }; 15 | 16 | BlockMessage.propTypes = { 17 | message: PropTypes.string.isRequired, 18 | }; 19 | 20 | export default BlockMessage; 21 | -------------------------------------------------------------------------------- /client/src/Components/SignUp/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | 3 | .signup { 4 | &__btn { 5 | background-color: $primary-color; 6 | letter-spacing: 2px; 7 | border-radius: 5px; 8 | position: relative; 9 | cursor: pointer; 10 | z-index: 2; 11 | width: 15rem; 12 | text-transform: uppercase; 13 | &::after { 14 | background-color: $primary-color; 15 | } 16 | &:hover::after { 17 | transform: scaleX(1.4) scaleY(1.6); 18 | opacity: 0; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/Components/Posts/posts.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .posts { 3 | .end-message { 4 | text-align: center; 5 | padding: 1.5rem 0; 6 | color: $font-color; 7 | letter-spacing: 0.2rem; 8 | font-size: 1.6rem; 9 | } 10 | &__empty-container { 11 | // height: 3rem; 12 | padding: 2.5rem; 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | background-color: $white-color; 17 | color: $font-color; 18 | font-size: 1.6rem; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/src/Pages/NotFoundPage/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | import history from "../../history"; 4 | 5 | const NotFound = () => { 6 | const onBackBtnClick = () => { 7 | history.push("/"); 8 | }; 9 | return ( 10 |
11 |

404 Not Found

12 | 15 |
16 | ); 17 | }; 18 | 19 | export default NotFound; 20 | -------------------------------------------------------------------------------- /client/src/Reducers/people.js: -------------------------------------------------------------------------------- 1 | import { peopleTypes } from "./constants"; 2 | 3 | const INITIAL_STATE = { 4 | list: [], 5 | loading: true, 6 | }; 7 | export default (state = INITIAL_STATE, action) => { 8 | switch (action.type) { 9 | case peopleTypes.SET_PEOPLE: 10 | return { ...state, list: action.payload }; 11 | case peopleTypes.reset: 12 | return { ...INITIAL_STATE }; 13 | case peopleTypes.setLoading: 14 | return { ...state, loading: action.payload }; 15 | default: 16 | return state; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /client/src/Components/BlockContainer/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .block { 3 | &__container { 4 | background-color: white; 5 | position: absolute; 6 | top: 50%; 7 | left: 50%; 8 | transform: translate(-50%, -50%); 9 | width: 40%; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | padding: 4rem; 14 | } 15 | &__message { 16 | color: $error-color; 17 | font-size: 2rem; 18 | text-transform: capitalize; 19 | letter-spacing: 1px; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/src/Components/Toast/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { url } from "../../Api"; 3 | import "./index.styles.scss"; 4 | const ToastMessage = ({ data }) => { 5 | return ( 6 |
7 | 13 |

{data.notification}

14 |
15 | ); 16 | }; 17 | export default ToastMessage; 18 | -------------------------------------------------------------------------------- /fb-clone-backend/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const winston = require("winston"); 3 | const socketIO = require("socket.io"); 4 | 5 | let app = express(); 6 | 7 | const PORT = process.env.PORT || 5000; 8 | const server = app.listen(PORT, () => { 9 | winston.info(`Listening to ${PORT}...`); 10 | }); 11 | 12 | const io = socketIO(server); 13 | require("./startup/notification")(io, app); 14 | require("./startup/logger")(); 15 | require("./startup/routes")(app); 16 | require("./startup/db")(); 17 | require("./startup/prod")(app); 18 | -------------------------------------------------------------------------------- /client/src/Pages/GroupPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .group { 3 | width: 70%; 4 | margin: 0 auto; 5 | position: relative; 6 | @include respond(tab-land) { 7 | width: 100%; 8 | } 9 | &__timeline-container { 10 | display: flex; 11 | align-items: flex-start; 12 | @include respond(phone) { 13 | display: block; 14 | } 15 | } 16 | &__timeline { 17 | width: 70%; 18 | @include respond(phone) { 19 | width: 100%; 20 | } 21 | } 22 | .page-image { 23 | width: 100%; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/Pages/RegisterPage/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Signup from "../../Components/SignUp/"; 3 | import "react-toastify/dist/ReactToastify.css"; 4 | import "./index.style.scss"; 5 | 6 | const RegisterPage = () => { 7 | return ( 8 |
9 |
10 |
11 |

FaceBook Clone

12 | 13 |
14 |
15 |
16 | ); 17 | }; 18 | 19 | export default RegisterPage; 20 | -------------------------------------------------------------------------------- /client/src/sass/_layout.scss: -------------------------------------------------------------------------------- 1 | .pg { 2 | &__container { 3 | background-color: $white-color; 4 | width: 50%; 5 | margin: 0 auto; 6 | margin-top: 1rem; 7 | padding: 2rem 0; 8 | } 9 | &__actions { 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: space-evenly; 13 | padding: 20px; 14 | } 15 | &__btn { 16 | height: 4rem; 17 | width: 4rem; 18 | border-radius: 50%; 19 | display: flex; 20 | justify-content: center; 21 | align-items: center; 22 | cursor: pointer; 23 | transition: all 0.2s ease; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/Images/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fb-clone-backend/models/Comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const commentSchema = new mongoose.Schema({ 4 | author: { 5 | type: mongoose.Types.ObjectId, 6 | ref: "Profile", 7 | requried: true, 8 | }, 9 | comment: { 10 | type: String, 11 | required: true, 12 | minlength: 5, 13 | }, 14 | post_id: { 15 | type: mongoose.Types.ObjectId, 16 | required: true, 17 | ref: "Post", 18 | }, 19 | date: { 20 | type: Date, 21 | default: Date.now(), 22 | }, 23 | }); 24 | 25 | module.exports = mongoose.model("Comment", commentSchema); 26 | -------------------------------------------------------------------------------- /client/src/Components/ErrorBoundary/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .error-boundary { 3 | height: 100vh; 4 | margin: 0 auto; 5 | display: flex; 6 | justify-content: center; 7 | flex-direction: column; 8 | align-items: center; 9 | .error-svg { 10 | height: 50rem; 11 | width: 50rem; 12 | @include respond(phone) { 13 | height: 25rem; 14 | width: 25rem; 15 | } 16 | } 17 | p { 18 | color: $font-color; 19 | font-size: 4rem; 20 | letter-spacing: 3px; 21 | @include respond(phone) { 22 | font-size: 2rem; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /client/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "./App"; 4 | import { Provider } from "react-redux"; 5 | import { createStore, applyMiddleware, compose } from "redux"; 6 | import reducers from "./Reducers"; 7 | import thunk from "redux-thunk"; 8 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 9 | const store = createStore(reducers, composeEnhancers(applyMiddleware(thunk))); 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.querySelector("#root") 16 | ); 17 | -------------------------------------------------------------------------------- /fb-clone-backend/startup/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const { label } = require("joi"); 3 | module.exports = function () { 4 | winston.add(new winston.transports.File({ filename: "logger.log" })); 5 | winston.exceptions.handle( 6 | new winston.transports.File({ filename: "unCaughtExceptions.log" }), 7 | new winston.transports.Console({ 8 | format: winston.format.combine( 9 | winston.format.colorize(), 10 | winston.format.prettyPrint() 11 | ), 12 | }) 13 | ), 14 | process.on("unhandledRejection", (ex) => { 15 | throw ex; 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /client/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /client/src/Pages/FbPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .page { 3 | width: 70%; 4 | margin: 0 auto; 5 | position: relative; 6 | @include respond(tab-port) { 7 | width: 100%; 8 | } 9 | &__timeline-container { 10 | display: flex; 11 | align-items: flex-start; 12 | width: 100%; 13 | padding: 0; 14 | margin: 0; 15 | @include respond(phone) { 16 | display: block; 17 | } 18 | } 19 | .timeline__section { 20 | width: 70%; 21 | @include respond(phone) { 22 | width: 100%; 23 | } 24 | } 25 | .page-image { 26 | width: 100%; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/src/Pages/ExpiredLinkPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .expired { 3 | background-color: $white-color; 4 | // width: 40%; 5 | @include centerContainer; 6 | text-align: center; 7 | padding: 4rem; 8 | &__message { 9 | color: $error-color; 10 | font-size: 2rem; 11 | text-transform: capitalize; 12 | margin-bottom: 3rem; 13 | } 14 | &__btn { 15 | color: $primary-color; 16 | text-transform: capitalize; 17 | font-size: 1.7rem; 18 | text-align: center; 19 | 20 | padding-bottom: 5px; 21 | 22 | border-bottom: 1px solid $primary-color; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/src/Components/FormField/RadioButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./radio.scss"; 3 | const RadioBtn = ({ type, input, label, ...rest }) => { 4 | return ( 5 |
6 | 15 | 19 |
20 | ); 21 | }; 22 | 23 | export default RadioBtn; 24 | -------------------------------------------------------------------------------- /client/src/Api/index.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { getToken } from "../utils/tokenUtils"; 3 | import runTimeEnv from "@mars/heroku-js-runtime-env"; 4 | const env = runTimeEnv(); 5 | export const url = 6 | process.env.NODE_ENV === "production" 7 | ? env.REACT_APP_URL 8 | : "http://localhost:5000"; 9 | 10 | const Api = axios.create({ 11 | baseURL: url, 12 | }); 13 | Api.interceptors.request.use((config) => { 14 | const token = getToken(); 15 | const customHeaders = token ? { "x-auth-token": token } : {}; 16 | config.headers = { ...config.headers, ...customHeaders }; 17 | return config; 18 | }); 19 | 20 | export default Api; 21 | -------------------------------------------------------------------------------- /client/src/Components/modal/ScrollModal.component.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const ScrollModal = ({ header, content, onDismiss }) => { 4 | return ( 5 |
{ 8 | onDismiss(); 9 | }} 10 | > 11 |
{ 14 | e.stopPropagation(); 15 | }} 16 | > 17 |
{header}
18 |
{content}
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default ScrollModal; 25 | -------------------------------------------------------------------------------- /client/src/Images/page.svg: -------------------------------------------------------------------------------- 1 | 2 | Page 3 | -------------------------------------------------------------------------------- /client/src/Components/ImageUploader/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { setImage } from "../../Actions"; 4 | 5 | const ImageUploaderComponent = ({ setImage, LabelForImage }) => { 6 | const onChange = (e) => { 7 | setImage(e.target.files[0]); 8 | }; 9 | return ( 10 | <> 11 | 12 | 19 | 20 | ); 21 | }; 22 | 23 | export default connect(null, { setImage })(ImageUploaderComponent); 24 | -------------------------------------------------------------------------------- /client/src/Components/SecondaryNav/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | const SecondaryNav = ({ itemList, rightMenu }) => { 4 | const renderItem = () => { 5 | return itemList.map((item, index) => { 6 | return ( 7 |
13 | {item.title} 14 |
15 | ); 16 | }); 17 | }; 18 | return ( 19 |
20 | {renderItem()} 21 | {rightMenu ? rightMenu : null} 22 |
23 | ); 24 | }; 25 | 26 | export default SecondaryNav; 27 | -------------------------------------------------------------------------------- /client/src/Components/NotiIcon/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import "./index.style.scss"; 4 | import PropTypes from "prop-types"; 5 | const NotiIcon = ({ count }) => { 6 | return ( 7 | 8 | {count !== 0 &&
{count}
} 9 |
10 | ); 11 | }; 12 | 13 | NotiIcon.defaultProps = { 14 | count: 0, 15 | }; 16 | NotiIcon.propTypes = { 17 | count: PropTypes.number.isRequired, 18 | }; 19 | 20 | const mapStateToProps = (state) => { 21 | return { 22 | count: state.notification.count, 23 | }; 24 | }; 25 | export default connect(mapStateToProps)(NotiIcon); 26 | -------------------------------------------------------------------------------- /client/src/Reducers/pagination.js: -------------------------------------------------------------------------------- 1 | import { paginationTypes } from "./constants"; 2 | const INITIAL_STATE = { 3 | pageNumber: 1, 4 | hasMore: true, 5 | pageLimit: 10, 6 | }; 7 | 8 | export default (state = INITIAL_STATE, action) => { 9 | switch (action.type) { 10 | case paginationTypes.setPageNumber: 11 | return { ...state, pageNumber: action.payload }; 12 | case paginationTypes.incPageNumber: 13 | return { ...state, pageNumber: state.pageNumber + 1 }; 14 | case paginationTypes.hasMore: 15 | return { ...state, hasMore: action.payload }; 16 | case paginationTypes.reset: 17 | return { ...INITIAL_STATE }; 18 | default: 19 | return state; 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/Components/ImageUploadField/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { setImage } from "../../Actions"; 3 | import { connect } from "react-redux"; 4 | import "./index.style.scss"; 5 | const ImageUploadField = ({ label, setImage }) => { 6 | return ( 7 |
8 | 11 | { 16 | setImage(e.target.files[0]); 17 | }} 18 | /> 19 |
20 | ); 21 | }; 22 | 23 | export default connect(null, { setImage })(ImageUploadField); 24 | -------------------------------------------------------------------------------- /client/src/Components/FollowerList/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | 4 | import List from "../List"; 5 | import { getFollowers } from "../../Actions"; 6 | 7 | const FollowersComponent = ({ profileId, followers, getFollowers }) => { 8 | useEffect(() => { 9 | getFollowers(profileId); 10 | }, [profileId, getFollowers]); 11 | 12 | return ; 13 | }; 14 | 15 | const mapStateToProps = (state) => { 16 | return { 17 | profileId: state.profileReducer.profile._id, 18 | followers: state.profileReducer.followers, 19 | }; 20 | }; 21 | 22 | export default connect(mapStateToProps, { 23 | getFollowers, 24 | })(FollowersComponent); 25 | -------------------------------------------------------------------------------- /client/src/Components/FollowingList/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import List from "../List"; 3 | import { getFollowing } from "../../Actions"; 4 | 5 | import { connect } from "react-redux"; 6 | 7 | const FollowingComponent = ({ profileId, following, getFollowing }) => { 8 | useEffect(() => { 9 | getFollowing(profileId); 10 | }, [profileId, getFollowing]); 11 | 12 | return ; 13 | }; 14 | 15 | const mapStateToProps = (state) => { 16 | return { 17 | profileId: state.profileReducer.profile._id, 18 | following: state.profileReducer.following, 19 | }; 20 | }; 21 | 22 | export default connect(mapStateToProps, { 23 | getFollowing, 24 | })(FollowingComponent); 25 | -------------------------------------------------------------------------------- /fb-clone-backend/Middleware/groupAdmin.js: -------------------------------------------------------------------------------- 1 | const { GroupModel } = require("../models/GroupModel"); 2 | const ProfileModel = require("../models/profile"); 3 | 4 | module.exports = async (req, res, next) => { 5 | try { 6 | const { group_id } = req.params; 7 | const { profile: profile_id } = req.user; 8 | const group = await GroupModel.findById({ _id: group_id }) 9 | .select("-cover") 10 | .populate("group_admin_id", "_id f_name l_name"); 11 | if (group.group_admin_id._id == profile_id) { 12 | req.group = group; 13 | return next(); 14 | } 15 | return res.sendStatus(400); 16 | } catch (err) { 17 | console.log("Got error"); 18 | res.status(400).send(err.message); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /client/src/Components/ErrorComponent/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { eraseError } from "../../Actions"; 4 | import "./index.style.scss"; 5 | const ErrorComponent = ({ error, eraseError }) => { 6 | return !error ? null : ( 7 |
8 |

{error}

9 | { 13 | eraseError(); 14 | }} 15 | > 16 |
17 | ); 18 | }; 19 | 20 | const mapStateToProps = (state) => { 21 | return { error: state.error }; 22 | }; 23 | 24 | export default connect(mapStateToProps, { eraseError })(ErrorComponent); 25 | -------------------------------------------------------------------------------- /client/src/Components/ConfirmPasswordModal/index.styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .confirm-password { 3 | &__form { 4 | width: 80%; 5 | margin: 2rem auto; 6 | label { 7 | font-size: 1.2rem; 8 | margin-bottom: 0.8rem; 9 | width: 100%; 10 | text-align: center; 11 | text-transform: capitalize; 12 | display: inline-block; 13 | } 14 | input { 15 | border: none; 16 | padding: 1rem; 17 | border: 1px solid $grey-color-border; 18 | display: block; 19 | width: 100%; 20 | border-radius: 5px; 21 | &:focus { 22 | border: 1px solid $primary-color; 23 | outline: 1px solid $primary-color; 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /client/src/Reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { AuthTypes } from "./constants"; 2 | const INITIALSTATE = { 3 | isAuthenticated: false, 4 | isLoading: true, 5 | id: "", 6 | f_name: "", 7 | l_name: "", 8 | }; 9 | export default (state = INITIALSTATE, action) => { 10 | switch (action.type) { 11 | case AuthTypes.SET_AUTH: 12 | return { ...state, isAuthenticated: action.payload }; 13 | case AuthTypes.SET_USER: 14 | const { id, f_name, l_name } = action.payload; 15 | return { ...state, id, f_name, l_name }; 16 | case AuthTypes.reset: 17 | return { ...INITIALSTATE }; 18 | case AuthTypes.setLoading: 19 | return { ...state, isLoading: action.payload }; 20 | default: 21 | return state; 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /client/src/Components/ErrorBoundary/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import "./index.style.scss"; 3 | import { ReactComponent as ServerDownSvg } from "../../Images/server-down.svg"; 4 | class ErrorBoundary extends Component { 5 | state = { 6 | hasError: false, 7 | }; 8 | static getDerivedStateFromError(error) { 9 | console.log(error); 10 | return { hasError: true }; 11 | } 12 | render() { 13 | if (this.state.hasError) { 14 | return ( 15 |
16 | 17 |

Oops! Server is Down

18 |
19 | ); 20 | } 21 | return this.props.children; 22 | } 23 | } 24 | 25 | export default ErrorBoundary; 26 | -------------------------------------------------------------------------------- /client/src/Images/bell.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fb-clone-backend/Middleware/confirmPassword.js: -------------------------------------------------------------------------------- 1 | const { UserModel } = require("../models/user"); 2 | const bcrypt = require("bcrypt"); 3 | module.exports = async (req, res, next) => { 4 | try { 5 | const { confirmPassword, password } = req.body; 6 | const { id } = req.user; 7 | const user = await UserModel.findById({ _id: id }); 8 | 9 | const passwordCheck = await bcrypt.compare(confirmPassword, user.password); 10 | if (passwordCheck) { 11 | if (password) { 12 | user.password = await bcrypt.hash(password, await bcrypt.genSalt(10)); 13 | await user.save(); 14 | } 15 | return next(); 16 | } 17 | return res.status(400).send("Wrong Password"); 18 | } catch (err) { 19 | res.send(err.message); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /client/src/Components/FormField/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import FormError from "../FormError"; 3 | import "./index.style.scss"; 4 | const FormField = ({ 5 | label, 6 | type, 7 | name, 8 | placeholder, 9 | required, 10 | input, 11 | meta, 12 | }) => { 13 | return ( 14 |
15 | 23 | 24 | 25 |
26 | ); 27 | }; 28 | 29 | FormField.defaultProps = { 30 | required: false, 31 | }; 32 | export default FormField; 33 | -------------------------------------------------------------------------------- /client/src/sass/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin clearfix { 2 | &::after { 3 | clear: both; 4 | display: table; 5 | content: ""; 6 | } 7 | } 8 | 9 | @mixin centerContainer { 10 | position: absolute; 11 | top: 50%; 12 | left: 50%; 13 | transform: translate(-50%, -50%); 14 | } 15 | @mixin respond($breakpoint) { 16 | @if $breakpoint == phone { 17 | @media (max-width: 37.5em) { 18 | @content; 19 | } 20 | } 21 | @if $breakpoint == tab-port { 22 | @media (max-width: 56.25em) { 23 | @content; 24 | } 25 | } 26 | @if $breakpoint == tab-land { 27 | @media (max-width: 75em) { 28 | @content; 29 | } 30 | } 31 | @if $breakpoint == large-screen { 32 | @media (min-width: 112.5em) { 33 | @content; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/src/Components/PGList/PGItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import history from "../../history"; 3 | import { url } from "../../Api"; 4 | const PageItem = ({ item, source }) => { 5 | return ( 6 |
history.push(`/${source}/${item._id}`)} 9 | > 10 | {`${item.name} 15 |
16 |
17 |

{item.name}

18 |

{item.description}

19 |
20 |
21 | ); 22 | }; 23 | 24 | export default PageItem; 25 | -------------------------------------------------------------------------------- /client/src/Pages/RegisterPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .register { 3 | width: 100%; 4 | height: 100vh; 5 | // @include respond(tab-port) { 6 | // width: 80%; 7 | // } 8 | // @include respond(phone) { 9 | // width: 100%; 10 | // } 11 | &__bg { 12 | background-color: rgba($black-color, 0.6); 13 | height: 100%; 14 | width: 100%; 15 | } 16 | &__container { 17 | width: 60%; 18 | background-color: $white-color; 19 | padding: 2rem; 20 | box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4); 21 | position: absolute; 22 | top: 50%; 23 | left: 50%; 24 | transform: translate(-50%, -50%); 25 | border-radius: 5px; 26 | @include respond(phone) { 27 | width: 100%; 28 | height: 100%; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /client/src/Images/home.svg: -------------------------------------------------------------------------------- 1 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /fb-clone-backend/models/NotificationModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const schema = new mongoose.Schema({ 4 | profile_id: { 5 | type: mongoose.Types.ObjectId, 6 | required: true, 7 | ref: "Profile", 8 | }, 9 | noti_from_id: { 10 | type: mongoose.Types.ObjectId, 11 | required: true, 12 | }, 13 | notification: { 14 | type: String, 15 | required: true, 16 | minlength: 1, 17 | maxlength: 255, 18 | }, 19 | link: { 20 | type: String, 21 | required: true, 22 | minlength: 1, 23 | maxlength: 255, 24 | }, 25 | seen: { 26 | type: Boolean, 27 | default: false, 28 | }, 29 | date: { 30 | type: Date, 31 | default: Date.now(), 32 | }, 33 | }); 34 | 35 | module.exports = mongoose.model("Notification", schema); 36 | -------------------------------------------------------------------------------- /client/src/Components/SettingsDropDown/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | 4 | import { logoutUser } from "../../Actions"; 5 | 6 | import DropDownItem from "../SettingDropDownItem"; 7 | import "./index.style.scss"; 8 | const SettingDropDown = ({ logoutUser }) => { 9 | return ( 10 |
11 | 12 | 13 |
{ 16 | logoutUser(); 17 | }} 18 | > 19 | Logout 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default connect(null, { logoutUser })(SettingDropDown); 26 | -------------------------------------------------------------------------------- /client/src/Components/modal/modal.component.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | const Modal = ({ header, body, onDismiss, actions }) => { 5 | return ReactDOM.createPortal( 6 |
{ 9 | onDismiss(); 10 | }} 11 | > 12 |
{ 15 | e.stopPropagation(); 16 | }} 17 | > 18 |
{header}
19 | {!body ? null :
{body}
} 20 |
{actions}
21 |
22 |
, 23 | document.querySelector("#modal") 24 | ); 25 | }; 26 | 27 | export default Modal; 28 | -------------------------------------------------------------------------------- /client/src/Components/CustomLink/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, useRouteMatch } from "react-router-dom"; 3 | 4 | const CustomLink = ({ to, render }) => { 5 | const match = useRouteMatch({ 6 | path: to, 7 | exact: true, 8 | }); 9 | const active = match 10 | ? { 11 | borderBottomWidth: 3, 12 | borderBottomColor: "#1877f2", 13 | borderBottomStyle: "solid", 14 | } 15 | : {}; 16 | return ( 17 | 28 | {render(match)} 29 | 30 | ); 31 | }; 32 | 33 | export default CustomLink; 34 | -------------------------------------------------------------------------------- /client/src/utils/transform.js: -------------------------------------------------------------------------------- 1 | export const transformGroup = (group, profile_id) => { 2 | const { 3 | _id: id, 4 | group_privacy: privacy, 5 | created_on, 6 | name, 7 | description, 8 | members, 9 | requests, 10 | } = group; 11 | const { f_name, l_name } = group.group_admin_id; 12 | return { 13 | privacy, 14 | created_on, 15 | id, 16 | name, 17 | description, 18 | members, 19 | requests, 20 | admin_name: `${f_name} ${l_name}`, 21 | isPrivate: privacy === "private", 22 | }; 23 | }; 24 | 25 | export const transformComments = (comments) => { 26 | let list = {}; 27 | console.log("Comments Length", comments.length); 28 | for (let comment of comments) { 29 | console.log("Comment", comment); 30 | return (list[comment._id] = comment); 31 | } 32 | return list; 33 | }; 34 | -------------------------------------------------------------------------------- /client/src/Components/About/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.style.scss"; 3 | import PropTypes from "prop-types"; 4 | 5 | const About = ({ title, description, actions, information }) => { 6 | return ( 7 |
8 |

{title}

9 |

{description}

10 |
{information}
11 |
{actions}
12 |
13 | ); 14 | }; 15 | 16 | About.defaultProps = { 17 | title: "", 18 | description: "", 19 | information: <>, 20 | actions: <>, 21 | }; 22 | 23 | About.propTypes = { 24 | title: PropTypes.string, 25 | description: PropTypes.string, 26 | information: PropTypes.element, 27 | actions: PropTypes.element, 28 | }; 29 | 30 | export default About; 31 | -------------------------------------------------------------------------------- /client/src/Components/Login/login.Style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .login { 3 | width: 60%; 4 | padding: 4rem 0; 5 | border-radius: 5px; 6 | box-shadow: 0 1rem 4rem rgba($black-color, 0.1); 7 | background-color: $white-color; 8 | position: absolute; 9 | top: 40%; 10 | left: 50%; 11 | transform: translate(-50%, -50%); 12 | @include respond(phone) { 13 | width: 100%; 14 | } 15 | // z-index: -2; 16 | &__btn { 17 | cursor: pointer; 18 | margin-top: 10px; 19 | background-color: $primary-color; 20 | color: $white-color; 21 | position: relative; 22 | border-radius: 5px; 23 | width: 15rem; 24 | z-index: 2; 25 | &::after { 26 | background-color: $primary-color; 27 | } 28 | &:hover::after { 29 | transform: scaleX(1.4) scaleY(1.6); 30 | opacity: 0; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/Components/PeopleItem/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .people { 3 | &__item { 4 | height: 100%; 5 | width: 100%; 6 | cursor: pointer; 7 | border: 2px solid rgba($grey-color-border, 0.2); 8 | border-radius: 1rem; 9 | display: flex; 10 | &__left { 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | padding: 1.5rem; 15 | } 16 | &__right { 17 | flex: 3; 18 | padding: 2rem; 19 | display: flex; 20 | align-items: center; 21 | justify-content: space-between; 22 | } 23 | &__avatar { 24 | height: 5rem; 25 | width: 5rem; 26 | border: 1px solid rgba($grey-color-border, 0.3); 27 | border-radius: 0.5rem; 28 | } 29 | &__name { 30 | font-size: 1.4rem; 31 | color: $font-color; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /client/src/Components/ProfileAbout/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .profile-about { 3 | background-color: white; 4 | padding: 2rem; 5 | width: 100%; 6 | display: flex; 7 | flex-direction: column; 8 | align-items: center; 9 | &__heading { 10 | font-size: 2rem; 11 | color: $font-color; 12 | font-weight: 400; 13 | text-align: center; 14 | padding: 1rem 0; 15 | } 16 | .profile__information { 17 | margin: auto; 18 | font-size: 1.6rem; 19 | color: $font-color; 20 | text-transform: uppercase; 21 | letter-spacing: 1px; 22 | display: inline-block; 23 | &-item { 24 | // display: flex; 25 | // justify-content: center; 26 | // align-items: center; 27 | 28 | padding: 1rem 0; 29 | p { 30 | display: inline-block; 31 | margin-left: 3rem; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fb-clone-backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fb-clone-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "dev": "nodemon index.js", 9 | "test": "jest --watchAll" 10 | }, 11 | "engines": { 12 | "node": "12.16.3" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "bcrypt": "^4.0.1", 19 | "compression": "^1.7.4", 20 | "config": "^3.3.1", 21 | "cors": "^2.8.5", 22 | "express": "^4.17.1", 23 | "express-fileupload": "^1.1.6", 24 | "helmet": "^4.1.1", 25 | "joi": "^14.3.1", 26 | "jsonwebtoken": "^8.5.1", 27 | "mongoose": "^5.9.3", 28 | "socket.io": "^2.3.0", 29 | "winston": "^3.3.3", 30 | "winston-mongodb": "^5.0.3" 31 | }, 32 | "devDependencies": { 33 | "jest": "^26.4.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /client/src/Components/BlockButton/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { blockUser } from "../../Actions"; 5 | import "./index.styles.scss"; 6 | const BlockButton = ({ toBeBlockedId, blockUser }) => { 7 | return ( 8 | 16 | ); 17 | }; 18 | BlockButton.defaultProps = { 19 | toBeBlockedId: "", 20 | blockUser: () => {}, 21 | }; 22 | 23 | BlockButton.propTypes = { 24 | toBeBlockedId: PropTypes.string.isRequired, 25 | blockUser: PropTypes.func, 26 | }; 27 | 28 | const mapStateToProps = (state) => { 29 | return { toBeBlockedId: state.profileReducer.profile._id }; 30 | }; 31 | export default connect(mapStateToProps, { blockUser })(BlockButton); 32 | -------------------------------------------------------------------------------- /client/src/Pages/BlockMessagePage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import { setReducer } from "../../Actions"; 4 | import { Redirect } from "react-router-dom"; 5 | import BlockMessage from "../../Components/BlockContainer"; 6 | import { blockTypes } from "../../Reducers/constants"; 7 | 8 | const BlockMessagePage = ({ message, setReducer }) => { 9 | useEffect(() => { 10 | return () => { 11 | setReducer({ 12 | type: blockTypes.reset, 13 | }); 14 | }; 15 | }, [setReducer]); 16 | if (!message) return ; 17 | if (message) { 18 | return ; 19 | } 20 | 21 | return <>; 22 | }; 23 | const mapStateToProps = (state) => { 24 | return { message: state.blocked.message }; 25 | }; 26 | export default connect(mapStateToProps, { setReducer })(BlockMessagePage); 27 | -------------------------------------------------------------------------------- /client/src/Reducers/blockedUser.js: -------------------------------------------------------------------------------- 1 | import { blockTypes } from "./constants"; 2 | const INITIAL_STATE = { 3 | blockedStatus: false, 4 | blockLoader: true, 5 | message: null, 6 | blockedUsers: [], 7 | }; 8 | export default (state = INITIAL_STATE, action) => { 9 | switch (action.type) { 10 | case blockTypes.setBlockedUsers: 11 | return { ...state, blockedUsers: action.payload }; 12 | case blockTypes.setLoading: 13 | return { ...state, blockLoader: action.payload }; 14 | case blockTypes.setBlockStatus: 15 | return { ...state, blockedStatus: action.payload }; 16 | case blockTypes.setMessage: 17 | return { ...state, message: action.payload }; 18 | case blockTypes.reset: 19 | return { ...INITIAL_STATE }; 20 | case blockTypes.resetBlockState: 21 | return { ...state, blockedStatus: false, blockLoader: true }; 22 | default: 23 | return state; 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /client/src/Components/SecondaryNav/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .secondary-navbar { 3 | width: 100%; 4 | padding: 0; 5 | background-color: $white-color; 6 | @include clearfix; 7 | 8 | .right-menu { 9 | float: right; 10 | @include respond(phone) { 11 | float: none; 12 | } 13 | } 14 | // padding-left: 5px; 15 | #active { 16 | border-bottom: 3px solid $primary-color; 17 | } 18 | .item { 19 | display: inline-block; 20 | font-size: 1.4rem; 21 | padding: 2rem 0; 22 | margin: 0px 1rem; 23 | letter-spacing: 0.3rem; 24 | text-transform: uppercase; 25 | cursor: pointer; 26 | font-weight: 500; 27 | border-bottom: 3px solid transparent; 28 | 29 | @include respond(phone) { 30 | display: block; 31 | text-align: center; 32 | } 33 | &:hover { 34 | border-bottom: 3px solid rgba($grey-color-2, 0.4); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/src/Components/CommentDeleteBtn/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | const CommentDeleteBtn = ({ auth_id, comment, onDeleteClick }) => { 5 | return ( 6 | <> 7 | {comment.author._id === auth_id ? ( 8 |
{ 10 | onDeleteClick(comment._id); 11 | }} 12 | className="comment-item__delete-btn" 13 | style={{ cursor: "pointer" }} 14 | > 15 | Delete 16 |
17 | ) : null} 18 | 19 | ); 20 | }; 21 | 22 | CommentDeleteBtn.propTypes = { 23 | auth_id: PropTypes.string.isRequired, 24 | comment: PropTypes.object, 25 | onDeleteClick: PropTypes.func, 26 | }; 27 | const mapStateToProps = (state) => { 28 | return { auth_id: state.Authentication.id }; 29 | }; 30 | 31 | export default connect(mapStateToProps)(CommentDeleteBtn); 32 | -------------------------------------------------------------------------------- /client/src/Images/remove.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Reducers/notification.js: -------------------------------------------------------------------------------- 1 | const INITIAL_STATE = { 2 | count: 0, 3 | list: [], 4 | total: 0, 5 | loading: true, 6 | }; 7 | 8 | const reducer = (state = INITIAL_STATE, action) => { 9 | switch (action.type) { 10 | case "INC_COUNT": 11 | return { ...state, count: state.count + 1 }; 12 | case "RESET_NOTI": 13 | return { ...state, list: [] }; 14 | case "SET_COUNT": 15 | return { ...state, count: action.payload }; 16 | case "RESET_COUNT": 17 | return { ...state, count: 0 }; 18 | case "GET_NOTIFICATIONS": 19 | let { notifications, total } = action.payload; 20 | return { ...state, list: notifications, total, loading: false }; 21 | case "GET_MORE_NOTIFICATIONS": 22 | return { 23 | ...state, 24 | list: [...state.list, ...action.payload.notifications], 25 | }; 26 | default: 27 | return state; 28 | } 29 | }; 30 | 31 | export default reducer; 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Facebook Clone 2 | 3 | ## Description 4 | 5 | A Social Media App with some Features Built using MERN 6 | 7 | ### Backend 8 | 9 | Backend Rest Api is powered by 10 | 11 | - Expressjs for restful Api 12 | - MongodDb used as Database 13 | - Mongoose used as ORM 14 | - joi for validation 15 | - For RealTime Notification (Socket.io) 16 | 17 | ### Frontend 18 | 19 | Frontend SPA is build using 20 | 21 | - React (SPA) 22 | - Redux (State Managment) 23 | - Reat-Router-Dom (Routing) 24 | 25 | ## Functionalities 26 | 27 | 1. User can Login / Register to Fb Clone (Authentication) 28 | 2. User will receive a Notification whenever someone (like,comment) on your Posts , try to join a group etc. 29 | 3. User can create/delete/update posts 30 | 4. User can create/delete/update pages 31 | 5. User can create/delete/update Groups and can add other members to that group. 32 | 6. User can comment or like a post 33 | 7. User can follow/unFollow/Block other Users. 34 | -------------------------------------------------------------------------------- /client/src/Components/List/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Loader from "../Loader"; 3 | import PeopleItem from "../PeopleItem"; 4 | import "./index.style.scss"; 5 | 6 | const List = ({ list }) => { 7 | if (!list) { 8 | return ( 9 |
10 | 11 |
12 | ); 13 | } 14 | 15 | return ( 16 |
17 | {list.length === 0 ? ( 18 |
No One yet
19 | ) : ( 20 |
21 | {list.map((item) => ( 22 | 29 | ))} 30 |
31 | )} 32 |
33 | ); 34 | }; 35 | 36 | export default List; 37 | -------------------------------------------------------------------------------- /client/src/Components/TextArea/index.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from "react"; 2 | import "./index.style.scss"; 3 | const TextArea = ({ value, placeholder, id, setValue, error }) => { 4 | const [textAreaHeight, setTextHeight] = useState(""); 5 | const textAreaRef = useRef(); 6 | useEffect(() => { 7 | setTextHeight(textAreaRef.current.scrollHeight); 8 | if (value.length === 0) { 9 | setTextHeight("auto"); 10 | } 11 | }, [value]); 12 | const onChange = (e) => { 13 | if (textAreaHeight !== "auto") { 14 | setTextHeight("auto"); 15 | } 16 | setValue(e.target.value); 17 | }; 18 | return ( 19 | 28 | ); 29 | }; 30 | 31 | export default TextArea; 32 | -------------------------------------------------------------------------------- /client/src/Reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from "redux"; 2 | import authReducer from "./auth"; 3 | import profileReducer from "./profileReducer"; 4 | import People from "./people"; 5 | import Posts from "./Posts"; 6 | import checkUser from "./checkUser"; 7 | import image from "./Image"; 8 | import errorReducer from "./setError"; 9 | import blockedUserReducer from "./blockedUser"; 10 | import pagesReducer from "./pages"; 11 | import groupReducer from "./groupReducer"; 12 | import { reducer as FormReducer } from "redux-form"; 13 | import notificationReducer from "./notification"; 14 | import pagination from "./pagination"; 15 | 16 | export default combineReducers({ 17 | Authentication: authReducer, 18 | form: FormReducer, 19 | profileReducer, 20 | People, 21 | Posts, 22 | isUser: checkUser, 23 | image, 24 | error: errorReducer, 25 | pages: pagesReducer, 26 | group: groupReducer, 27 | blocked: blockedUserReducer, 28 | notification: notificationReducer, 29 | pagination, 30 | }); 31 | -------------------------------------------------------------------------------- /client/src/Components/JoinBtn/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { requestGroup, checkIsRequested, cancelRequest } from "../../Actions"; 3 | import Loader from "../Loader"; 4 | 5 | const JoinBtn = ({ id }) => { 6 | const [isRequested, setIsRequested] = useState(null); 7 | useEffect(() => { 8 | let mounted = true; 9 | if (mounted) { 10 | checkIsRequested(id, setIsRequested); 11 | } 12 | return () => { 13 | mounted = false; 14 | }; 15 | }, [id]); 16 | if (isRequested === null) return ; 17 | return ( 18 | <> 19 | 30 | 31 | ); 32 | }; 33 | 34 | export default JoinBtn; 35 | -------------------------------------------------------------------------------- /client/src/Components/Notification/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import NotificationList from "../NotificationList/"; 4 | import "react-toastify/dist/ReactToastify.css"; 5 | import { 6 | updateUnSeenNoti, 7 | setNotiCount, 8 | getNotiList, 9 | getMoreNotiList, 10 | } from "../../Actions"; 11 | const Notification = ({ notiCount, setNotiCount, getNotiList }) => { 12 | useEffect(() => { 13 | if (notiCount > 0) { 14 | updateUnSeenNoti((status) => { 15 | if (status) { 16 | setNotiCount(0); 17 | } 18 | }); 19 | } 20 | }, []); 21 | return ( 22 |
23 | 24 |
25 | ); 26 | }; 27 | 28 | const mapStateToProps = (state) => { 29 | return { id: state.Authentication.id, notiCount: state.notification.count }; 30 | }; 31 | export default connect(mapStateToProps, { 32 | setNotiCount, 33 | getNotiList, 34 | getMoreNotiList, 35 | })(Notification); 36 | -------------------------------------------------------------------------------- /client/src/Actions/peopleActions.js: -------------------------------------------------------------------------------- 1 | import Api from "../Api"; 2 | import { peopleTypes } from "../Reducers/constants"; 3 | export const getPeople = () => { 4 | return async (dispatch) => { 5 | try { 6 | const response = await Api.get("/people"); 7 | if (response.status === 200) { 8 | dispatch({ type: peopleTypes.SET_PEOPLE, payload: response.data }); 9 | dispatch({ type: peopleTypes.setLoading, payload: false }); 10 | } 11 | } catch (err) { 12 | console.log(err.message); 13 | } 14 | }; 15 | }; 16 | 17 | export const getNewsFeed = (pageNumber, pageSize) => { 18 | return async (dispatch) => { 19 | try { 20 | const response = await Api.get( 21 | `/home?pageSize=${pageSize}&pageNumber=${pageNumber}` 22 | ); 23 | return dispatch({ type: "GET_POSTS", payload: response.data }); 24 | } catch (err) { 25 | console.log(err.message); 26 | } 27 | }; 28 | }; 29 | 30 | // export const peopleActions = { 31 | // getNewsFeed, 32 | // getPeople, 33 | // }; 34 | -------------------------------------------------------------------------------- /client/src/Images/people.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Components/GroupsGetter/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import PGList from "../PGList"; 3 | import { connect } from "react-redux"; 4 | import { getAllGroups, setReducer } from "../../Actions"; 5 | 6 | const GroupsGetter = ({ type, groups, loading, getAllGroups, setReducer }) => { 7 | useEffect(() => { 8 | // getAllGroups(type); 9 | return () => { 10 | setReducer({ 11 | type: "RESET_GROUPS", 12 | payload: [], 13 | }); 14 | }; 15 | }, [type]); 16 | 17 | const getResource = (pageNumber) => { 18 | getAllGroups(type, pageNumber); 19 | }; 20 | return ( 21 | 27 | ); 28 | }; 29 | 30 | GroupsGetter.defaultProps = { 31 | type: "", 32 | }; 33 | const mapStateToProps = (state) => { 34 | return { groups: state.group.groups, loading: state.group.loading }; 35 | }; 36 | 37 | export default connect(mapStateToProps, { getAllGroups, setReducer })( 38 | GroupsGetter 39 | ); 40 | -------------------------------------------------------------------------------- /client/src/Reducers/pages.js: -------------------------------------------------------------------------------- 1 | import { pageTypes } from "./constants"; 2 | 3 | const INITIAL_STATE = { 4 | pagesList: [], 5 | page: null, 6 | loading: true, 7 | }; 8 | 9 | export default (state = INITIAL_STATE, action) => { 10 | switch (action.type) { 11 | case pageTypes.getPages: 12 | return { ...state, pagesList: action.payload, loading: false }; 13 | case pageTypes.getMorePages: 14 | return { ...state, pagesList: [...state.pagesList, ...action.payload] }; 15 | case pageTypes.getPage: 16 | return { ...state, page: action.payload }; 17 | case pageTypes.modifyPage: 18 | return { ...state, page: { ...state.page, ...action.payload } }; 19 | case pageTypes.updatePageLikes: 20 | return { ...state, page: { ...state.page, ...action.payload } }; 21 | case pageTypes.reset: 22 | return { ...INITIAL_STATE }; 23 | case pageTypes.resetPages: 24 | return { ...state, pagesList: [] }; 25 | case pageTypes.setLoading: 26 | return { ...state, loading: action.payload }; 27 | default: 28 | return state; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /fb-clone-backend/models/PageModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const LikeSchema = require("./Likes"); 3 | const AdminSchema = require("./AdminSchema"); 4 | 5 | const pageSchema = new mongoose.Schema({ 6 | name: { 7 | type: String, 8 | required: true, 9 | minlength: 3, 10 | maxlength: 255, 11 | }, 12 | cover: { 13 | data: Buffer, 14 | contentType: String, 15 | }, 16 | description: { 17 | type: String, 18 | required: true, 19 | minlength: 3, 20 | maxlength: 255, 21 | }, 22 | created_on: { 23 | type: Date, 24 | default: Date.now(), 25 | }, 26 | likes: { 27 | type: [LikeSchema], 28 | default: [], 29 | }, 30 | page_admin_id: { 31 | type: mongoose.Types.ObjectId, 32 | required: true, 33 | ref: "Profile", 34 | }, 35 | posts: { 36 | type: [ 37 | { 38 | _id: { 39 | type: mongoose.Types.ObjectId, 40 | required: true, 41 | }, 42 | }, 43 | ], 44 | default: [], 45 | }, 46 | }); 47 | 48 | module.exports = mongoose.model("Page", pageSchema); 49 | -------------------------------------------------------------------------------- /fb-clone-backend/routes/unBlockRoutes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const mongoose = require("mongoose"); 3 | const auth = require("../Middleware/auth"); 4 | const asyncMiddleware = require("../Middleware/asyncMiddleware"); 5 | 6 | const ProfileModel = mongoose.model("Profile"); 7 | const router = express.Router(); 8 | 9 | router.put( 10 | "/:_id", 11 | auth, 12 | asyncMiddleware(async (req, res) => { 13 | const { _id } = req.params; 14 | const { profile: profile_id } = req.user; 15 | const profile = await ProfileModel.findOneAndUpdate( 16 | { 17 | _id: profile_id, 18 | blocked_users: _id, 19 | }, 20 | { 21 | $pull: { blocked_users: _id }, 22 | }, 23 | { new: true } 24 | ).select({ blocked_users: 1 }); 25 | const toBeUnBlocked = await ProfileModel.findOneAndUpdate( 26 | { _id, blocked_by: profile_id }, 27 | { 28 | $pull: { blocked_by: profile_id }, 29 | }, 30 | { new: true } 31 | ); 32 | res.status(200).send(profile.blocked_users); 33 | }) 34 | ); 35 | 36 | module.exports = router; 37 | -------------------------------------------------------------------------------- /client/src/Images/activeHome.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Pages/PeoplePage/index.styles.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .people { 3 | &__container { 4 | background-color: $white-color; 5 | width: 70%; 6 | margin: 0 auto; 7 | margin-top: 1rem; 8 | padding: 2rem 1rem; 9 | @include respond(tab-land) { 10 | width: 100%; 11 | padding: 2rem; 12 | } 13 | } 14 | &__empty-container { 15 | padding: 2rem; 16 | display: flex; 17 | justify-content: center; 18 | align-items: center; 19 | text-transform: capitalize; 20 | font-size: 1.2rem; 21 | } 22 | .loader__container { 23 | padding: 2rem; 24 | 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | width: 100%; 29 | margin: auto; 30 | } 31 | 32 | &__list-container { 33 | display: grid; 34 | gap: 1rem; 35 | grid-template-columns: 1fr 1fr; 36 | @include respond(tab-land) { 37 | grid-template-columns: 1fr; 38 | } 39 | } 40 | &__header { 41 | padding: 1.5rem 0; 42 | font-size: 1.6rem; 43 | color: $font-color; 44 | letter-spacing: 0.1rem; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/src/Components/BlockItem/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { url } from "../../Api"; 3 | import PropTypes from "prop-types"; 4 | 5 | import UnBlockBtn from "../UnBlockBtn"; 6 | import "./index.style.scss"; 7 | const BlockUserItem = ({ user }) => { 8 | const { _id, f_name, l_name } = user; 9 | return ( 10 |
11 |
12 | {`${f_name} 17 |

18 | {f_name} {l_name} 19 |

20 |
21 | 22 |
23 | ); 24 | }; 25 | 26 | BlockUserItem.defaultProps = { 27 | user: { 28 | _id: "", 29 | f_name: "", 30 | l_name: "", 31 | }, 32 | }; 33 | BlockUserItem.propTypes = { 34 | user: PropTypes.exact({ 35 | _id: PropTypes.string.isRequired, 36 | f_name: PropTypes.string.isRequired, 37 | l_name: PropTypes.string.isRequired, 38 | }), 39 | }; 40 | 41 | export default BlockUserItem; 42 | -------------------------------------------------------------------------------- /client/src/sass/components/_button.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | border: none; 3 | outline: none; 4 | padding: 1rem 1.5rem; 5 | font-size: 1.5rem; 6 | color: $white-color; 7 | &::after { 8 | content: ""; 9 | width: 100%; 10 | height: 100%; 11 | position: absolute; 12 | left: 0; 13 | top: 0; 14 | transition: all 0.5s ease; 15 | z-index: -1; 16 | border-radius: 5px; 17 | } 18 | } 19 | 20 | .circle-btn { 21 | color: $primary-color; 22 | background-color: #f7f7f7; 23 | padding: 2rem; 24 | border: 1px solid $primary-color; 25 | border-radius: 10rem; 26 | position: relative; 27 | cursor: pointer; 28 | transition: all 0.2s ease; 29 | text-transform: uppercase; 30 | flex: 0 0; 31 | #btnIcon { 32 | color: $primary-color; 33 | font-size: 1.7rem; 34 | position: absolute; 35 | top: 50%; 36 | left: 50%; 37 | transform: translate(-50%, -50%); 38 | display: flex; 39 | justify-content: center; 40 | align-items: center; 41 | } 42 | } 43 | 44 | .animateBtn { 45 | &:hover { 46 | transform: translateY(-3px); 47 | } 48 | &:active { 49 | transform: translateY(-1px); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /client/src/Components/Header/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .header-section { 3 | position: relative; 4 | transition: all 2s; 5 | .profile-pic_container { 6 | position: absolute; 7 | bottom: 0; 8 | left: 50%; 9 | transform: translateX(-50%); 10 | z-index: 10; 11 | @include respond(tab-land) { 12 | bottom: 5rem; 13 | } 14 | &::after { 15 | content: ""; 16 | display: table; 17 | clear: both; 18 | } 19 | .profile_name { 20 | font-size: 2.2rem; 21 | text-transform: uppercase; 22 | text-align: center; 23 | margin: 0; 24 | padding: 10px 0; 25 | color: white; 26 | font-weight: 500; 27 | @include respond(phone) { 28 | font-size: 1.5rem; 29 | } 30 | // width: 101%; 31 | // text-overflow: ellipsis; 32 | // white-space: nowrap; 33 | // overflow: hidden; 34 | } 35 | } 36 | } 37 | 38 | .action-box { 39 | position: absolute; 40 | top: 29px; 41 | left: 20px; 42 | color: #c0bcbc; 43 | cursor: pointer; 44 | #action-icon { 45 | color: #c0bcbc; 46 | margin-right: 5px; 47 | font-size: 20px; 48 | transition: font-size 2s ease-out; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/src/Components/About/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .sidebar { 3 | width: 30%; 4 | background-color: $white-color; 5 | border: 1px solid rgba(0, 0, 0, 0.1); 6 | margin: 0; 7 | padding: 2rem; 8 | text-align: center; 9 | @include respond(phone) { 10 | width: 100%; 11 | z-index: 999; 12 | } 13 | .sidebar__title { 14 | display: block; 15 | text-transform: uppercase; 16 | font-size: 1.7rem; 17 | font-weight: 400; 18 | text-align: center; 19 | } 20 | .sidebar__description { 21 | font-size: 1.6rem; 22 | margin-top: 2rem; 23 | @include respond(phone) { 24 | font-size: 1.6rem; 25 | margin-top: 1rem; 26 | } 27 | } 28 | .side__actions { 29 | display: block; 30 | margin-top: 3rem; 31 | width: 100%; 32 | display: flex; 33 | justify-content: space-evenly; 34 | align-items: flex-start; 35 | @include respond(phone) { 36 | margin-top: 0; 37 | } 38 | } 39 | .sidebar__info { 40 | font-size: 1.3rem; 41 | text-transform: uppercase; 42 | letter-spacing: 1px; 43 | @include respond(phone) { 44 | display: flex; 45 | margin: 0 auto; 46 | width: 80%; 47 | justify-content: space-around; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/src/Reducers/groupReducer.js: -------------------------------------------------------------------------------- 1 | import { groupTypes } from "./constants"; 2 | const INITIALSTATE = { 3 | groups: [], 4 | loading: true, 5 | group: null, 6 | isMember: false, 7 | isPrivate: false, 8 | requests: null, 9 | members: null, 10 | isAdmin: false, 11 | }; 12 | 13 | export default (state = INITIALSTATE, action) => { 14 | switch (action.type) { 15 | case groupTypes.getGroups: 16 | return { ...state, groups: action.payload, loading: false }; 17 | case groupTypes.getMoreGroups: 18 | return { ...state, groups: [...state.groups, ...action.payload] }; 19 | case groupTypes.resetGroups: 20 | return { ...INITIALSTATE }; 21 | case groupTypes.getGroup: 22 | return { ...state, group: action.payload }; 23 | case groupTypes.checkMember: 24 | return { ...state, isMember: action.payload }; 25 | case groupTypes.updateRequest: 26 | return { ...state, requests: action.payload }; 27 | case groupTypes.updateMembers: 28 | return { ...state, members: action.payload }; 29 | case groupTypes.setAdmin: 30 | return { ...state, isAdmin: action.payload }; 31 | case groupTypes.setLoading: 32 | return { ...state, loading: action.payload }; 33 | default: 34 | return state; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /client/src/Pages/CreateGroupPage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .create-group { 3 | &__container { 4 | background-color: $white-color; 5 | width: 50%; 6 | margin: 0 auto; 7 | margin-top: 1rem; 8 | padding: 2rem 0; 9 | @include respond(tab-port) { 10 | width: 100%; 11 | // margin-top: 5.6rem; 12 | position: relative; 13 | } 14 | } 15 | &__button { 16 | cursor: pointer; 17 | border: none; 18 | background-color: $primary-color; 19 | padding: 1rem 3rem; 20 | color: white; 21 | text-transform: uppercase; 22 | font-weight: 500; 23 | letter-spacing: 0.3rem; 24 | font-size: 1rem; 25 | position: relative; 26 | overflow: hidden; 27 | border: 1px solid $primary-color; 28 | 29 | &::after { 30 | content: ""; 31 | height: 100%; 32 | width: 100%; 33 | position: absolute; 34 | background: white; 35 | transition: all 0.5s ease; 36 | left: 0; 37 | bottom: 0; 38 | transform: translateX(-100%); 39 | } 40 | &:hover::after { 41 | transform: translateX(0) scale(1.1); 42 | z-index: -1; 43 | } 44 | &:hover { 45 | color: $primary-color; 46 | border: 1px solid $primary-color; 47 | z-index: 1; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/src/Actions/index.js: -------------------------------------------------------------------------------- 1 | import Api from "../Api"; 2 | import { imageTypes } from "../Reducers/constants"; 3 | 4 | export * from "./authActions"; 5 | export * from "./fbPagesActions"; 6 | export * from "./profileActions"; 7 | export * from "./fbPagesActions"; 8 | export * from "./peopleActions"; 9 | export * from "./fbpostsActions"; 10 | export * from "./blockActions"; 11 | export * from "./commentActions"; 12 | export * from "./groupActions"; 13 | export * from "./notification"; 14 | export const whatToShow = (value) => { 15 | return (dispatch) => { 16 | dispatch({ type: "what_To_show", payload: value }); 17 | }; 18 | }; 19 | export const setReducer = (configObj) => { 20 | return configObj; 21 | }; 22 | export const eraseError = () => { 23 | return { type: "SET_ERROR_MESSAGE", payload: "" }; 24 | }; 25 | export const setError = (errMessage) => { 26 | return { type: "SET_ERROR_MESSAGE", payload: errMessage }; 27 | }; 28 | 29 | export const setImage = (file) => { 30 | return { type: imageTypes.setImage, payload: file }; 31 | }; 32 | 33 | export const updateUnSeenNoti = async (cb = () => {}) => { 34 | try { 35 | const response = await Api.put("/notification/seen"); 36 | if (response.status === 200) { 37 | cb(true); 38 | } 39 | } catch (err) { 40 | cb(false); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /client/src/Components/CoverPhoto/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import history from "../../history"; 5 | import "./index.style.scss"; 6 | 7 | const CoverPhoto = ({ 8 | isAuthUser, 9 | renderActions, 10 | alt, 11 | urlToImage, 12 | uploadUrl, 13 | }) => { 14 | return ( 15 |
16 |
17 | {`${alt} 18 | {isAuthUser && ( 19 | { 22 | history.push("/upload", { uploadUrl }); 23 | }} 24 | > 25 | 26 | 27 | )} 28 | {!isAuthUser &&
{renderActions()}
} 29 |
30 | ); 31 | }; 32 | CoverPhoto.defaultProps = { 33 | renderActions: () => null, 34 | }; 35 | 36 | CoverPhoto.propTypes = { 37 | isAuthUser: PropTypes.bool.isRequired, 38 | renderActions: PropTypes.func, 39 | alt: PropTypes.string, 40 | urlToImage: PropTypes.string.isRequired, 41 | uploadUrl: PropTypes.string.isRequired, 42 | }; 43 | 44 | export default connect()(CoverPhoto); 45 | -------------------------------------------------------------------------------- /client/src/Components/PeopleItem/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { connect } from "react-redux"; 4 | 5 | import FollowBtn from "../FollowBtn"; 6 | import history from "../../history"; 7 | import { url } from "../../Api"; 8 | 9 | import "./index.style.scss"; 10 | 11 | const PeopleItem = ({ id, name, profile_id }) => { 12 | return ( 13 |
history.push(`/profile/${id}`)} 16 | key={id} 17 | > 18 |
19 | {`${name} 24 |
25 |
26 |

{name}

27 | {id !== profile_id && } 28 |
29 |
30 | ); 31 | }; 32 | 33 | PeopleItem.propTypes = { 34 | id: PropTypes.string.isRequired, 35 | name: PropTypes.string.isRequired, 36 | profile_id: PropTypes.string.isRequired, 37 | }; 38 | 39 | const mapStateToProps = (state) => { 40 | return { profile_id: state.Authentication.id }; 41 | }; 42 | export default connect(mapStateToProps)(PeopleItem); 43 | -------------------------------------------------------------------------------- /client/src/Components/ProfileAbout/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | 5 | import "./index.style.scss"; 6 | const About = ({ name, about, gender }) => { 7 | return ( 8 |
9 |

About {name}

10 |
11 | {/* {renderInfo()} */} 12 |
13 | 14 |

{about}

15 |
16 |
17 | 18 |

{gender}

19 |
20 |
21 |
22 | ); 23 | }; 24 | 25 | About.defaultProps = { 26 | renderInfo: () => {}, 27 | }; 28 | About.propTypes = { 29 | name: PropTypes.string.isRequired, 30 | about: PropTypes.string.isRequired, 31 | gender: PropTypes.string.isRequired, 32 | }; 33 | const mapStateToProps = (state) => { 34 | const { f_name, l_name, about, gender } = state.profileReducer.profile; 35 | return { 36 | name: `${f_name} ${l_name}`, 37 | about, 38 | gender, 39 | }; 40 | }; 41 | export default connect(mapStateToProps)(About); 42 | -------------------------------------------------------------------------------- /client/src/Components/TimelineNav/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .timeline { 3 | width: 100%; 4 | &__nav { 5 | list-style: none; 6 | display: flex; 7 | margin: 0; 8 | padding: 0; 9 | height: 5rem; 10 | // justify-content: center; 11 | border-bottom: 5px solid $white-color-1; 12 | 13 | .active { 14 | color: #3b5998; 15 | border-bottom: 3px solid $primary-color; 16 | } 17 | &-item { 18 | display: flex; 19 | background-color: rgb(255, 255, 255); 20 | font-weight: 700; 21 | text-transform: uppercase; 22 | color: #948b8b; 23 | flex: 1; 24 | font-size: 1.3rem; 25 | justify-content: center; 26 | align-items: center; 27 | border-bottom: 3px solid $white-color; 28 | transition: all 0.1s ease-out; 29 | cursor: pointer; 30 | &:hover { 31 | border-bottom: 3px solid $primary-color; 32 | } 33 | } 34 | } 35 | .follow-btn { 36 | border: none; 37 | margin-top: 30px; 38 | padding: 10px 30px; 39 | transition: all 0.2s; 40 | background-color: rgb(219, 94, 94); 41 | border-radius: 5px; 42 | color: white; 43 | cursor: pointer; 44 | &:hover { 45 | background-color: rgb(219, 94, 94); 46 | border-radius: 50px; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /client/src/Pages/CreateFbPagePage/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .page { 3 | &__container { 4 | background-color: $white-color; 5 | width: 50%; 6 | margin: 0 auto; 7 | margin-top: 1rem; 8 | padding: 2rem 0; 9 | @include respond(tab-port) { 10 | width: 100%; 11 | } 12 | } 13 | &__btn { 14 | z-index: 10; 15 | cursor: pointer; 16 | background-color: $white-color; 17 | position: relative; 18 | border-radius: 0.5rem; 19 | letter-spacing: 2px; 20 | font-weight: 500; 21 | text-transform: capitalize; 22 | padding: 1rem 3rem; 23 | font-size: 1.3rem; 24 | color: $primary-color; 25 | overflow: hidden; 26 | cursor: pointer; 27 | border: 3px solid $primary-color; 28 | // border: none; 29 | &::after { 30 | content: ""; 31 | height: 100%; 32 | width: 100%; 33 | position: absolute; 34 | bottom: 0; 35 | left: 0; 36 | // right: 0; 37 | z-index: -1; 38 | transform: translateY(110%); 39 | background-color: $primary-color; 40 | transition: all 0.5s ease; 41 | border: 3px solid $primary-color; 42 | border-radius: 50%; 43 | } 44 | &:hover { 45 | color: white; 46 | } 47 | &:hover::after { 48 | transform: translateY(0) scale(2); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /fb-clone-backend/routes/peopleRoutes.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const router = require("express").Router(); 3 | const asyncMiddleware = require("../Middleware/asyncMiddleware"); 4 | const auth = require("../Middleware/auth"); 5 | const ProfileModel = mongoose.model("Profile"); 6 | 7 | router.get( 8 | "/", 9 | auth, 10 | asyncMiddleware(async (req, res) => { 11 | const pageNumber = parseInt(req.query.pageNumber); 12 | const pageSize = parseInt(req.query.pageSize); 13 | let { following, blocked_by, blocked_users } = await ProfileModel.findById({ 14 | _id: req.user.profile, 15 | }).select({ following: 1, blocked_users: 1, blocked_by: 1 }); 16 | const profiles = await ProfileModel.find({ 17 | $and: [ 18 | { 19 | _id: { $ne: req.user.profile }, 20 | }, 21 | { 22 | _id: { 23 | $nin: following, 24 | }, 25 | }, 26 | { 27 | _id: { 28 | $nin: blocked_by, 29 | }, 30 | }, 31 | { 32 | _id: { 33 | $nin: blocked_users, 34 | }, 35 | }, 36 | ], 37 | }) 38 | .select({ _id: 1, f_name: 1, l_name: 1 }) 39 | .skip((pageNumber - 1) * pageSize) 40 | .limit(pageSize); 41 | return res.send(profiles); 42 | }) 43 | ); 44 | module.exports = router; 45 | -------------------------------------------------------------------------------- /client/src/Components/FollowBtn/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | import { followProfile, unfollowProfile, checkFollower } from "../../Actions"; 4 | 5 | import "./index.style.scss"; 6 | const FollowBtn = ({ id, check }) => { 7 | const [isFollower, setIsFollower] = useState(false); 8 | const [loading, setLoading] = useState(check ? true : false); 9 | useEffect(() => { 10 | if (check) { 11 | checkFollower(id, (status) => { 12 | setLoading(false); 13 | if (status) { 14 | setIsFollower(true); 15 | } 16 | }); 17 | } 18 | }, []); 19 | return ( 20 | 37 | ); 38 | }; 39 | FollowBtn.defaultProps = { 40 | check: true, 41 | }; 42 | FollowBtn.propTypes = { 43 | id: PropTypes.string.isRequired, 44 | check: PropTypes.bool.isRequired, 45 | }; 46 | 47 | export default FollowBtn; 48 | -------------------------------------------------------------------------------- /client/src/Components/ProfilePhoto/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | 5 | import { url } from "../../Api"; 6 | import history from "../../history"; 7 | 8 | import "./index.style.scss"; 9 | 10 | const ProfilePhoto = ({ id, isUser }) => { 11 | return ( 12 |
13 | cool img 18 | {isUser && ( 19 | 31 | )} 32 |
33 | ); 34 | }; 35 | ProfilePhoto.propTypes = { 36 | id: PropTypes.string.isRequired, 37 | isUser: PropTypes.bool.isRequired, 38 | }; 39 | 40 | const mapStateToProps = (state) => { 41 | return { 42 | id: state.profileReducer.profile._id, 43 | isUser: state.isUser, 44 | }; 45 | }; 46 | export default connect(mapStateToProps)(ProfilePhoto); 47 | -------------------------------------------------------------------------------- /client/src/Images/group.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/Actions/commentActions.js: -------------------------------------------------------------------------------- 1 | import Api from "../Api"; 2 | import { commentTypes, postTypes } from "../Reducers/constants"; 3 | export const deleteComment = (id, comment_id, cb) => { 4 | return async (dispatch) => { 5 | try { 6 | const response = await Api.delete(`/posts/${id}/comment/${comment_id}`); 7 | 8 | cb(commentTypes.deleteComment, response.data.id); 9 | dispatch({ type: postTypes.decPostComments, payload: id }); 10 | } catch (err) { 11 | console.log(err.response.data); 12 | } 13 | }; 14 | }; 15 | 16 | export const createComment = (id, comment, actionCreater) => { 17 | return async (dispatch) => { 18 | try { 19 | let response = await Api.put(`/posts/comment/${id}`, { comment }); 20 | if (response.status === 200) { 21 | actionCreater(commentTypes.createComment, response.data); 22 | dispatch({ type: postTypes.incPostComments, payload: id }); 23 | } 24 | } catch (err) { 25 | console.log(err.message); 26 | } 27 | }; 28 | }; 29 | 30 | export const getComments = async (id, pageNumber, setHasMore, cb) => { 31 | try { 32 | const response = await Api.get( 33 | `/posts/comment/${id}?pageNumber=${pageNumber}` 34 | ); 35 | if (response.data.length === 0) { 36 | setHasMore(false); 37 | } 38 | cb(commentTypes.getComments, response.data); 39 | } catch (err) { 40 | console.log(err.message); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /client/src/Reducers/profileReducer.js: -------------------------------------------------------------------------------- 1 | import { profileTypes } from "./constants"; 2 | 3 | const INITIAL_STATE = { 4 | profile: {}, 5 | followers: null, 6 | following: null, 7 | error: "", 8 | loading: true, 9 | }; 10 | 11 | export default (state = INITIAL_STATE, action) => { 12 | switch (action.type) { 13 | case profileTypes.getProfile: 14 | return { ...state, profile: action.payload }; 15 | case profileTypes.setLoading: 16 | return { ...state, loading: action.payload }; 17 | case profileTypes.reset: 18 | return { ...INITIAL_STATE }; 19 | case profileTypes.error: 20 | return { ...state, error: action.payload }; 21 | case profileTypes.getFollowers: 22 | return { ...state, followers: action.payload }; 23 | case profileTypes.getFollowing: 24 | return { ...state, following: action.payload }; 25 | case profileTypes.createPost: 26 | return { ...state, ...action.payload }; 27 | case profileTypes.followUser: 28 | return { 29 | ...state, 30 | profile: { 31 | ...state.profile, 32 | followers: state.profile.followers + 1, 33 | }, 34 | }; 35 | case profileTypes.unfollow: 36 | return { 37 | ...state, 38 | profile: { 39 | ...state.profile, 40 | following: state.profile.following - 1, 41 | }, 42 | }; 43 | default: 44 | return state; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /client/src/Components/CommentItem/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .comment-item { 3 | width: 100%; 4 | display: flex; 5 | padding: 1rem; 6 | &__delete-btn { 7 | font-size: 1.3rem; 8 | } 9 | &__avatar { 10 | margin-top: 1rem; 11 | height: 3.5rem; 12 | flex: 0 0 3.5rem; 13 | border-radius: 50%; 14 | overflow: hidden; 15 | 16 | img { 17 | height: 100%; 18 | width: 100%; 19 | } 20 | } 21 | &__content { 22 | display: flex; 23 | margin-left: 0.5rem; 24 | padding: 0.8rem 1.2rem; 25 | background-color: $white-color-1; 26 | flex-direction: column; 27 | border-radius: 1.5rem; 28 | flex: 1; 29 | overflow: hidden; 30 | .author { 31 | color: $font-color; 32 | font-weight: 600; 33 | text-decoration: none; 34 | margin-bottom: 0.5rem; 35 | } 36 | } 37 | &__text { 38 | display: -webkit-box; 39 | -webkit-line-clamp: 3; 40 | -webkit-box-orient: vertical; 41 | overflow: hidden; 42 | width: 100%; 43 | text-overflow: ellipsis; 44 | word-break: break-all; 45 | // text-align: justify; 46 | // text-transform: ; 47 | } 48 | &__actions { 49 | margin-left: 1.5rem; 50 | display: flex; 51 | justify-content: flex-start; 52 | // justify-content: center; 53 | // align-items: center; 54 | flex: 0 0; 55 | } 56 | &__date { 57 | margin-left: 0.8rem; 58 | font-size: 1.3rem; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/src/Components/FormField/radio.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .radio-btn { 3 | padding: 0 2rem; 4 | width: 20%; 5 | display: inline-block; 6 | position: relative; 7 | @media (max-width: 1300px) { 8 | width: 100%; 9 | margin-bottom: 1.5rem; 10 | } 11 | 12 | &__input { 13 | display: none; 14 | } 15 | &__input:checked ~ &__label &__custom::after { 16 | opacity: 1; 17 | } 18 | &__label { 19 | text-transform: uppercase; 20 | margin-left: 3rem; 21 | font-size: 1.4rem; 22 | letter-spacing: 1px; 23 | font-weight: 500; 24 | cursor: pointer; 25 | } 26 | &__custom { 27 | height: 2.5rem; 28 | width: 2.5rem; 29 | border: 0.3rem solid $primary-color; 30 | border-radius: 50%; 31 | display: inline-block; 32 | position: absolute; 33 | left: 1rem; 34 | top: 0; 35 | transform: translateY(-0.5rem); 36 | &::after { 37 | content: ""; 38 | position: absolute; 39 | top: 50%; 40 | left: 50%; 41 | transform: translate(-50%, -50%); 42 | height: 1rem; 43 | width: 1rem; 44 | border-radius: 50%; 45 | background-color: $primary-color; 46 | opacity: 0; 47 | transition: all 0.2s ease; 48 | } 49 | } 50 | } 51 | .radio__label { 52 | font-size: 1.4rem; 53 | margin-bottom: 2rem; 54 | margin-left: 1.5rem; 55 | font-weight: 600; 56 | display: block; 57 | @include respond(tab-port) { 58 | margin-bottom: 1.5rem; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /client/src/Pages/BlockedUserPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import { getAllBlockedUsers, setReducer } from "../../Actions"; 4 | import BlockUserItem from "../../Components/BlockItem"; 5 | import { blockTypes } from "../../Reducers/constants"; 6 | import "./index.style.scss"; 7 | 8 | const BlockedUsers = ({ 9 | blockedUsers, 10 | blockLoader, 11 | getAllBlockedUsers, 12 | setReducer, 13 | }) => { 14 | useEffect(() => { 15 | getAllBlockedUsers(); 16 | return () => { 17 | setReducer({ type: blockTypes.reset }); 18 | }; 19 | }, [setReducer]); 20 | return ( 21 |
22 |

BLOCKED USERS

23 | {blockLoader ? ( 24 |
25 |
26 |
27 | ) : blockedUsers.length === 0 ? ( 28 |
Found No Blocked Users
29 | ) : ( 30 | blockedUsers.map((item) => { 31 | return ; 32 | }) 33 | )} 34 |
35 | ); 36 | }; 37 | 38 | const mapStateToProps = (state) => { 39 | return { 40 | blockedUsers: state.blocked.blockedUsers, 41 | blockLoader: state.blocked.blockLoader, 42 | }; 43 | }; 44 | export default connect(mapStateToProps, { getAllBlockedUsers, setReducer })( 45 | BlockedUsers 46 | ); 47 | -------------------------------------------------------------------------------- /fb-clone-backend/routes/notification.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const auth = require("../Middleware/auth"); 3 | const asyncMiddleware = require("../Middleware/asyncMiddleware"); 4 | const NotificationModel = require("../models/NotificationModel"); 5 | 6 | router.get( 7 | "/", 8 | auth, 9 | asyncMiddleware(async (req, res) => { 10 | const { profile: profile_id } = req.user; 11 | let { pageNumber } = req.query; 12 | pageNumber = parseInt(pageNumber); 13 | const notiCount = await NotificationModel.find({ profile_id }).count(); 14 | const notifications = await NotificationModel.find({ profile_id }) 15 | .limit(10) 16 | .skip((pageNumber - 1) * 10) 17 | .sort({ date: -1 }); 18 | res.status(200).send({ total: notiCount, notifications }); 19 | }) 20 | ); 21 | 22 | router.get( 23 | "/unseen", 24 | auth, 25 | asyncMiddleware(async (req, res) => { 26 | const { profile: profile_id } = req.user; 27 | const notificationsCount = await NotificationModel.find({ 28 | profile_id, 29 | seen: false, 30 | }).count(); 31 | res.status(200).send({ unSeenNotiCount: notificationsCount }); 32 | }) 33 | ); 34 | router.put("/seen", auth, async (req, res) => { 35 | const { profile: profile_id } = req.user; 36 | const notifications = await NotificationModel.updateMany( 37 | { 38 | profile_id, 39 | }, 40 | { 41 | seen: true, 42 | } 43 | ); 44 | res.status(200).send(notifications); 45 | }); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /client/src/Components/PageLikeBtn/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import PropTypes from "prop-types"; 4 | import { checkIsLiked, likePage, disLikePage } from "../../Actions"; 5 | const PageLikeButton = ({ likePage, page_id, disLikePage }) => { 6 | const [isLiked, setIsLiked] = useState(null); 7 | const [loading, setLoading] = useState(true); 8 | const cb = (likeStatus) => { 9 | setLoading(false); 10 | if (likeStatus !== null && likeStatus !== isLiked) { 11 | setIsLiked(likeStatus); 12 | } 13 | }; 14 | useEffect(() => { 15 | checkIsLiked(page_id, cb); 16 | }, [page_id]); 17 | 18 | const onClick = () => { 19 | if (loading) return; 20 | if (!isLiked) { 21 | setIsLiked(true); 22 | likePage(page_id, cb); 23 | return; 24 | } else { 25 | setIsLiked(false); 26 | disLikePage(page_id, cb); 27 | } 28 | }; 29 | 30 | return ( 31 | 38 | ); 39 | }; 40 | PageLikeButton.propTypes = { 41 | likePage: PropTypes.func.isRequired, 42 | page_id: PropTypes.string.isRequired, 43 | disLikePage: PropTypes.func.isRequired, 44 | }; 45 | 46 | export default connect(null, { likePage, disLikePage })(PageLikeButton); 47 | -------------------------------------------------------------------------------- /client/src/Components/ProfilePhoto/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .profile-pic { 3 | height: 20rem; 4 | width: 20rem; 5 | border-radius: 50%; 6 | position: relative; 7 | margin: 0 auto; 8 | overflow: hidden; 9 | 10 | @include respond(phone) { 11 | height: 12rem; 12 | width: 12rem; 13 | } 14 | &__image { 15 | width: 100%; 16 | height: 100%; 17 | } 18 | &__label { 19 | font-size: 1rem; 20 | } 21 | &__upload-btn { 22 | position: absolute; 23 | bottom: 0px; 24 | z-index: 1; 25 | width: 100%; 26 | height: 50%; 27 | background-color: rgba(0, 0, 0, 0.9); 28 | border: none; 29 | color: white; 30 | display: none; 31 | text-transform: uppercase; 32 | letter-spacing: 4px; 33 | font-size: 1.4rem; 34 | overflow: hidden; 35 | @include respond(phone) { 36 | display: block; 37 | letter-spacing: 1px; 38 | font-size: 1.2rem; 39 | background-color: rgba(0, 0, 0, 0.7); 40 | } 41 | #icon { 42 | color: #fff; 43 | margin-bottom: 1rem; 44 | font-size: 1.2rem; 45 | } 46 | } 47 | &:hover &__upload-btn { 48 | display: block; 49 | animation: goToDark 0.8s ease; 50 | } 51 | &:hover &__icon-container { 52 | display: block; 53 | animation: moveFromBottom 0.5s ease 0.2s both; 54 | } 55 | } 56 | 57 | @keyframes moveFromBottom { 58 | 0% { 59 | opacity: 0; 60 | transform: translateY(100%); 61 | } 62 | 100% { 63 | opacity: 1; 64 | transform: translateY(0); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /client/src/Components/TimeLine/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { connect } from "react-redux"; 3 | 4 | import Posts from "../Posts"; 5 | import CreatePostForm from "../CreatePostForm"; 6 | import TimelineNavComponent from "../TimelineNav"; 7 | import FollowerComponent from "../FollowerList"; 8 | import FollowingComponent from "../FollowingList"; 9 | import ProfileAbout from "../ProfileAbout"; 10 | import "./index.style.scss"; 11 | 12 | const TimeLine = ({ isUser, urlToPost, profileId }) => { 13 | const [display, setDisplay] = useState(1); // 1 represents Posts 2 respresents Followers and 3 represents Following 4 represents About 14 | const renderContent = () => { 15 | switch (display) { 16 | case 1: 17 | return ( 18 | <> 19 | {isUser ? : null} 20 | 21 | 22 | ); 23 | case 2: 24 | return ; 25 | case 3: 26 | return ; 27 | case 4: 28 | return ; 29 | default: 30 | return; 31 | } 32 | }; 33 | return ( 34 | <> 35 | 36 | {renderContent()} 37 | 38 | ); 39 | }; 40 | const mapStateToProps = (state) => { 41 | return { profileId: state.profileReducer.profile._id, isUser: state.isUser }; 42 | }; 43 | export default connect(mapStateToProps)(TimeLine); 44 | -------------------------------------------------------------------------------- /client/src/Components/CoverPhoto/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .cover { 3 | height: 40rem; 4 | width: 100%; 5 | position: relative; 6 | overflow: hidden; 7 | &__image { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | @include respond(phone) { 12 | height: 30rem; 13 | } 14 | &__overlay { 15 | background: linear-gradient( 16 | to bottom, 17 | transparent, 18 | transparent, 19 | rgba(0, 0, 0, 0.5) 20 | ); 21 | z-index: 1; 22 | width: 100%; 23 | height: 100%; 24 | position: absolute; 25 | } 26 | .cover__upload { 27 | position: absolute; 28 | top: 2.9rem; 29 | left: 2.9rem; 30 | height: 4rem; 31 | width: 4rem; 32 | display: flex; 33 | justify-content: center; 34 | align-items: center; 35 | background-color: rgba(0, 0, 0, 0.9); 36 | cursor: pointer; 37 | z-index: 2; 38 | // @include respond(phone) { 39 | // height: 3rem; 40 | // width: 3rem; 41 | // } 42 | .action-icon { 43 | color: $white-color; 44 | font-size: 1.5rem; 45 | 46 | // transform: translateY(-40%); 47 | } 48 | } 49 | &__actions { 50 | position: absolute; 51 | right: 0; 52 | bottom: 0; 53 | width: 20%; 54 | z-index: 999; 55 | height: 5rem; 56 | margin: 0 2rem; 57 | display: flex; 58 | justify-content: space-between; 59 | align-items: center; 60 | @include respond(tab-land) { 61 | width: 100%; 62 | justify-content: center; 63 | margin: 0; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /fb-clone-backend/startup/routes.js: -------------------------------------------------------------------------------- 1 | //Dependencies 2 | const cors = require("cors"); 3 | const express = require("express"); 4 | const fileupload = require("express-fileupload"); 5 | const config = require("config"); 6 | //Routes 7 | const userRouter = require("../routes/user"); 8 | const profileRouter = require("../routes/profile"); 9 | const PostsRouter = require("../routes/posts"); 10 | const homeRouter = require("../routes/home"); 11 | const blockRouter = require("../routes/blockuser"); 12 | const unBlockRouter = require("../routes/unBlockRoutes"); 13 | const pageRouter = require("../routes/pageRoutes"); 14 | const groupRouter = require("../routes/groupRoutes"); 15 | const notificationRouter = require("../routes/notification"); 16 | const peopleRouter = require("../routes/peopleRoutes"); 17 | //Middleware 18 | const errorMiddleware = require("../Middleware/errorMiddlware"); 19 | 20 | module.exports = function (app) { 21 | app.use( 22 | cors({ exposedHeaders: "x-auth-token", origin: config.get("clientUrl") }) 23 | ); 24 | app.use(express.static("public")); 25 | app.use(fileupload()); 26 | app.use(express.json()); 27 | app.use("/home", homeRouter); 28 | app.use("/user", userRouter); 29 | app.use("/profile", profileRouter); 30 | app.use("/posts", PostsRouter); 31 | app.use("/block", blockRouter); 32 | app.use("/unblock", unBlockRouter); 33 | app.use("/pages", pageRouter); 34 | app.use("/groups", groupRouter); 35 | app.use("/people", peopleRouter); 36 | app.use("/notification", notificationRouter); 37 | 38 | app.use(errorMiddleware); 39 | }; 40 | -------------------------------------------------------------------------------- /client/src/Components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import CoverPhoto from "../CoverPhoto"; 4 | import FollowBtn from "../FollowBtn"; 5 | import BlockBtn from "../BlockButton"; 6 | import ProfilePhoto from "../ProfilePhoto"; 7 | import { url } from "../../Api"; 8 | import "./index.style.scss"; 9 | 10 | const Header = ({ name, id, isUser }) => { 11 | const renderActions = () => { 12 | if (isUser) return null; 13 | return ( 14 | <> 15 |
16 | 17 |
18 | 19 | 20 | ); 21 | }; 22 | return ( 23 |
24 | 31 |
32 |
36 | 37 |

{name}

38 |
39 |
40 |
41 | ); 42 | }; 43 | 44 | const mapStateToProps = (state) => { 45 | return { 46 | name: `${state.profileReducer.profile.f_name} ${state.profileReducer.profile.l_name}`, 47 | id: state.profileReducer.profile._id, 48 | isUser: state.isUser, 49 | }; 50 | }; 51 | export default connect(mapStateToProps)(Header); 52 | -------------------------------------------------------------------------------- /client/src/Components/CommentItem/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | import ReactEmoji from "react-emoji"; 4 | import CommentDeleteBtn from "../CommentDeleteBtn"; 5 | import PropTypes from "prop-types"; 6 | import moment from "moment"; 7 | import { url } from "../../Api"; 8 | import "./index.style.scss"; 9 | 10 | const CommentItem = ({ comment, onDeleteClick }) => { 11 | const { _id, f_name, l_name } = comment.author; 12 | const date = new Date(comment.date); 13 | return ( 14 |
15 |
16 | {`${f_name} 20 |
21 |
22 |
23 | {`${f_name} ${l_name}`} 27 |
28 | {ReactEmoji.emojify(comment.comment)} 29 |
30 |
31 |
32 | {" "} 33 | {moment(date).fromNow()} 34 |
35 |
36 |
37 | ); 38 | }; 39 | CommentItem.propTypes = { 40 | comment: PropTypes.object, 41 | onDeleteClick: PropTypes.func, 42 | }; 43 | 44 | export default CommentItem; 45 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@mars/heroku-js-runtime-env": "^3.0.2", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.5.0", 9 | "@testing-library/user-event": "^7.2.1", 10 | "axios": "^0.19.2", 11 | "lodash": "^4.17.20", 12 | "moment": "^2.29.1", 13 | "node-sass": "^4.14.1", 14 | "react": "^16.13.1", 15 | "react-dom": "^16.13.1", 16 | "react-emoji": "^0.5.0", 17 | "react-flip-move": "^3.0.4", 18 | "react-infinite-scroll-component": "^5.1.0", 19 | "react-redux": "^7.2.1", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "3.4.0", 22 | "react-toastify": "^6.0.9", 23 | "redux": "^4.0.5", 24 | "redux-form": "^8.3.6", 25 | "redux-thunk": "^2.3.0", 26 | "socket.io-client": "^2.3.1", 27 | "styled-components": "^5.2.1" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test", 33 | "eject": "react-scripts eject" 34 | }, 35 | "engines": { 36 | "node": "12.16.3", 37 | "npm": "7.9.0" 38 | }, 39 | "eslintConfig": { 40 | "extends": "react-app" 41 | }, 42 | "browserslist": { 43 | "production": [ 44 | ">0.2%", 45 | "not dead", 46 | "not op_mini all" 47 | ], 48 | "development": [ 49 | "last 1 chrome version", 50 | "last 1 firefox version", 51 | "last 1 safari version" 52 | ] 53 | }, 54 | "devDependencies": { 55 | "@svgr/webpack": "^4.3.3" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /fb-clone-backend/models/profile.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const FollowerSchema = require("./Followers"); 3 | const FollowingSchema = require("./Following"); 4 | const Profile_Dp_Schema = require("./profile_dp"); 5 | 6 | let ProfileSchema = new mongoose.Schema({ 7 | f_name: { 8 | type: String, 9 | required: true, 10 | minlength: 3, 11 | maxlength: 255, 12 | }, 13 | l_name: { 14 | type: String, 15 | required: true, 16 | minlength: 3, 17 | maxlength: 255, 18 | }, 19 | dob: { 20 | type: Date, 21 | }, 22 | gender: { 23 | type: String, 24 | required: true, 25 | }, 26 | user_id: { 27 | type: mongoose.SchemaTypes.ObjectId, 28 | required: true, 29 | }, 30 | profile_Dps: { 31 | type: [Profile_Dp_Schema], 32 | default: [], 33 | }, 34 | cover_pic: { 35 | data: Buffer, 36 | contentType: String, 37 | default: {}, 38 | }, 39 | following: { 40 | type: [FollowingSchema], 41 | default: [], 42 | }, 43 | followers: { 44 | type: [FollowerSchema], 45 | default: [], 46 | }, 47 | blocked_users: { 48 | type: [ 49 | { 50 | type: mongoose.Types.ObjectId, 51 | ref: "Profile", 52 | required: true, 53 | }, 54 | ], 55 | default: [], 56 | }, 57 | blocked_by: { 58 | type: [ 59 | { 60 | type: mongoose.Types.ObjectId, 61 | ref: "Profile", 62 | required: true, 63 | }, 64 | ], 65 | default: [], 66 | }, 67 | about: { 68 | type: String, 69 | default: "", 70 | }, 71 | }); 72 | 73 | let ProfileModel = mongoose.model("Profile", ProfileSchema); 74 | 75 | module.exports = ProfileModel; 76 | -------------------------------------------------------------------------------- /client/src/Components/MembersModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import { Redirect } from "react-router-dom"; 4 | import { removeMember, getMembers, setReducer } from "../../Actions"; 5 | 6 | import ScrollModal from "../modal/ScrollModal.component"; 7 | import RemoveBtn from "../RemoveBtn"; 8 | import Loader from "../Loader"; 9 | import GroupMemberList from "../GroupMemberList"; 10 | 11 | const RequestModal = ({ auth_id, members, history, getMembers, match }) => { 12 | const { id } = match.params; 13 | useEffect(() => { 14 | getMembers(id); 15 | }, [id, getMembers]); 16 | const renderAction = (id) => { 17 | if (members.group_admin_id === auth_id) { 18 | return ( 19 | removeMember(members._id, id)} 22 | /> 23 | ); 24 | } 25 | }; 26 | const onDissmiss = () => { 27 | return history.goBack(); 28 | }; 29 | if (!members) return ; 30 | if (members.group_admin_id !== auth_id) return ; 31 | return ( 32 | 40 | } 41 | onDismiss={onDissmiss} 42 | /> 43 | ); 44 | }; 45 | 46 | const mapStatetoProps = (state) => { 47 | return { 48 | members: state.group.members, 49 | auth_id: state.Authentication.id, 50 | }; 51 | }; 52 | export default connect(mapStatetoProps, { 53 | setReducer, 54 | getMembers, 55 | })(RequestModal); 56 | -------------------------------------------------------------------------------- /client/src/Pages/PeoplePage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import { getPeople, setReducer } from "../../Actions"; 4 | import PeopleItem from "../../Components/PeopleItem"; 5 | import { peopleTypes } from "../../Reducers/constants"; 6 | import "./index.styles.scss"; 7 | 8 | const FindPeople = ({ peopleList, loading, getPeople, setReducer }) => { 9 | useEffect(() => { 10 | getPeople(); 11 | return () => { 12 | setReducer({ type: peopleTypes.reset }); 13 | }; 14 | }, [getPeople, setReducer]); 15 | 16 | const renderPeopleList = () => { 17 | return peopleList.map((people) => { 18 | return ( 19 | 24 | ); 25 | }); 26 | }; 27 | return ( 28 |
29 |
30 |

Who to Follow

31 |
32 | {loading ? ( 33 |
34 |
35 |
36 | ) : peopleList.length === 0 ? ( 37 |
Found no Person
38 | ) : ( 39 |
{renderPeopleList()}
40 | )} 41 |
42 | ); 43 | }; 44 | 45 | const mapStateToProps = (state) => { 46 | return { 47 | peopleList: state.People.list, 48 | loading: state.People.loading, 49 | }; 50 | }; 51 | export default connect(mapStateToProps, { getPeople, setReducer })(FindPeople); 52 | -------------------------------------------------------------------------------- /fb-clone-backend/notification/Realtime.js: -------------------------------------------------------------------------------- 1 | const ActiveUserModel = require("../models/ActiveUser"); 2 | const NotificationModel = require("../models/NotificationModel"); 3 | const config = require("config"); 4 | class Notification { 5 | notification; 6 | constructor(io) { 7 | this.notification = io.of("/notification"); 8 | this.notification.on("connection", async (socket) => { 9 | const { id } = socket.handshake.query; 10 | let activeUser = await ActiveUserModel.findOne({ user_id: id }); 11 | if (activeUser) { 12 | activeUser.socket_id = socket.id; 13 | await activeUser.save(); 14 | } else { 15 | activeUser = activeUser = new ActiveUserModel({ 16 | socket_id: socket.id, 17 | user_id: id, 18 | }); 19 | await activeUser.save(); 20 | } 21 | socket.on("disconnect", async () => { 22 | await ActiveUserModel.findOneAndDelete({ socket_id: socket.id }); 23 | console.log("User disconnected from Notification"); 24 | }); 25 | }); 26 | } 27 | send = async ({ profile_id, notification, link, noti_from_id }) => { 28 | const newNotification = new NotificationModel({ 29 | notification, 30 | link: `${config.get("clientUrl")}/${link}`, 31 | profile_id, 32 | noti_from_id, 33 | }); 34 | await newNotification.save(); 35 | ActiveUserModel.findOne({ user_id: profile_id }) 36 | .then((user) => { 37 | if (user) 38 | return this.notification 39 | .to(user.socket_id) 40 | .emit("notification", newNotification); 41 | }) 42 | .catch((err) => console.log(err.message)); 43 | }; 44 | } 45 | 46 | module.exports = Notification; 47 | -------------------------------------------------------------------------------- /client/src/Actions/notification.js: -------------------------------------------------------------------------------- 1 | import Api from "../Api"; 2 | export const incNotiCount = () => { 3 | return { type: "INC_COUNT" }; 4 | }; 5 | export const setNotiCount = (value) => { 6 | return { type: "SET_COUNT", payload: value }; 7 | }; 8 | 9 | export const getUnseenNotiCount = () => { 10 | return async (dispatch) => { 11 | try { 12 | const response = await Api.get("/notification/unseen"); 13 | if (response.status === 200) { 14 | dispatch({ type: "SET_COUNT", payload: response.data.unSeenNotiCount }); 15 | } 16 | } catch (err) { 17 | if (err && err.response) { 18 | console.log(err.response.data); 19 | } 20 | } 21 | }; 22 | }; 23 | 24 | export const getNotiList = (pageNumber, setHasMore) => { 25 | return async (dispatch, getState) => { 26 | try { 27 | const response = await Api.get(`/notification?pageNumber=${pageNumber}`); 28 | const notifications = getState().notification.list; 29 | if (notifications.length > 0) { 30 | return dispatch({ 31 | type: "GET_MORE_NOTIFICATIONS", 32 | payload: response.data, 33 | }); 34 | } 35 | dispatch({ type: "GET_NOTIFICATIONS", payload: response.data }); 36 | } catch (err) { 37 | console.log(err.response.data); 38 | } 39 | }; 40 | }; 41 | 42 | export const getMoreNotiList = (pageNumber, cb = () => {}) => { 43 | return async (dispatch) => { 44 | try { 45 | const response = await Api.get(`/notification?pageNumber=${pageNumber}`); 46 | console.log(response.data); 47 | dispatch({ type: "GET_MORE_NOTIFICATIONS", payload: response.data }); 48 | cb(false); 49 | } catch (err) { 50 | console.log(err); 51 | } 52 | }; 53 | }; 54 | -------------------------------------------------------------------------------- /client/src/Components/TimelineNav/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import "./index.style.scss"; 4 | 5 | const TimeLineNavigation = ({ following, followers, display, setDisplay }) => { 6 | // label can be 1:Posts,2:Followers,3:Following,4:About 7 | const selectActiveElement = (label) => { 8 | return display === label ? "active" : ""; 9 | }; 10 | const onClick = (value) => { 11 | if (value === display) return; 12 | setDisplay(value); 13 | }; 14 | 15 | return ( 16 |
17 |
    18 |
  • onClick(1)} 20 | className={`${selectActiveElement(1)} timeline__nav-item`} 21 | > 22 | TimeLine 23 |
  • 24 |
  • onClick(2)} 26 | className={`${selectActiveElement(2)} timeline__nav-item`} 27 | > 28 | {followers} {followers in [1, 0] ? "Follower" : "Followers"} 29 |
  • 30 |
  • onClick(3)} 32 | className={`${selectActiveElement(3)} timeline__nav-item`} 33 | > 34 | {following} {following in [1, 0] ? "Following" : "Followings"} 35 |
  • 36 |
  • onClick(4)} 38 | className={`${selectActiveElement(4)} timeline__nav-item`} 39 | > 40 | About 41 |
  • 42 |
43 |
44 | ); 45 | }; 46 | 47 | const mapStateToProps = (state) => { 48 | return { 49 | followers: state.profileReducer.profile.followers, 50 | following: state.profileReducer.profile.following, 51 | }; 52 | }; 53 | 54 | export default connect(mapStateToProps)(TimeLineNavigation); 55 | -------------------------------------------------------------------------------- /client/src/Components/FormField/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .form-field { 3 | border: none; 4 | width: 90%; 5 | margin: 0 auto; 6 | position: relative; 7 | &:not(:last-child) { 8 | margin-bottom: 1.5rem; 9 | @include respond(tab-port) { 10 | margin-bottom: 0.5rem; 11 | } 12 | } 13 | &__label { 14 | display: inline-block; 15 | text-transform: capitalize; 16 | font-size: 1rem; 17 | margin-left: 1rem; 18 | transition: all 0.5s ease; 19 | font-weight: 600; 20 | letter-spacing: 1px; 21 | z-index: 2; 22 | opacity: 1; 23 | visibility: visible; 24 | } 25 | &__input { 26 | display: block; 27 | width: 100%; 28 | font-size: 1.6rem; 29 | padding: 1.5rem 1rem; 30 | border: 2px solid #f7f7f7; 31 | border-radius: 1rem; 32 | margin: 0; 33 | 34 | &:-webkit-autofill { 35 | &, 36 | &:active, 37 | &:focus, 38 | &:hover { 39 | background-color: transparent; 40 | border: 2px solid #f7f7f7 !important; 41 | outline: none; 42 | box-shadow: none; 43 | } 44 | } 45 | &:invalid { 46 | border: 2px solid #f7f7f7; 47 | box-shadow: none; 48 | outline: none; 49 | } 50 | &:focus { 51 | border: none; 52 | outline: none; 53 | border: 2px solid $primary-color; 54 | &:invalid { 55 | border: 2px solid $primary-color; 56 | } 57 | } 58 | &::placeholder { 59 | color: $font-color-2; 60 | font-weight: 500; 61 | text-transform: capitalize; 62 | font-size: 1.1rem; 63 | } 64 | } 65 | &__input:placeholder-shown + &__label { 66 | visibility: hidden; 67 | opacity: 0; 68 | transform: translateY(-4rem); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /client/src/Components/Like/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { likePost, disLikePost, checkLike } from "../../Actions"; 3 | import { connect } from "react-redux"; 4 | import "./index.scss"; 5 | const likeContainer = {}; 6 | const Like = ({ id, likes }) => { 7 | const [likeCount, setLikeCount] = useState(likes); 8 | const [postisLiked, setPostisLiked] = useState(false); 9 | const cb = (likeStatus, updateLikeCount) => { 10 | if (likeCount !== updateLikeCount) { 11 | setLikeCount(updateLikeCount); 12 | } 13 | if (likeStatus !== postisLiked) { 14 | setPostisLiked(likeStatus); 15 | } 16 | }; 17 | const onClick = () => { 18 | if (postisLiked) { 19 | setLikeCount((prev) => prev - 1); 20 | setPostisLiked(false); 21 | return disLikePost(id, cb); 22 | } 23 | setLikeCount((prev) => prev + 1); 24 | setPostisLiked(true); 25 | return likePost(id, cb); 26 | }; 27 | useEffect(() => { 28 | checkLike(id, setPostisLiked); 29 | }, []); 30 | 31 | return ( 32 | <> 33 |
34 | {postisLiked ? ( 35 | 36 | ) : ( 37 | 42 | )} 43 |

44 | {likeCount} {likeCount === 0 || likeCount === 1 ? "Like" : "Likes"} 45 |

46 |
47 | 48 | ); 49 | }; 50 | 51 | const mapStateToProps = (state) => { 52 | return { profile_id: state.Authentication.id }; 53 | }; 54 | 55 | export default connect(mapStateToProps)(Like); 56 | -------------------------------------------------------------------------------- /client/src/Components/GroupMemberList/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { url } from "../../Api"; 4 | 5 | const GroupMemberList = ({ data, empty_msg, group_admin_id, renderAction }) => { 6 | const renderItem = (item) => { 7 | return ( 8 |
9 | {group_admin_id === item._id ? ( 10 |
11 |
Admin
12 |
13 | ) : ( 14 |
{renderAction(item._id)}
15 | )} 16 | {`${item.name} 21 |
{item.name}
22 |
23 | ); 24 | }; 25 | return data.length === 0 ? ( 26 |
34 | {empty_msg} 35 |
36 | ) : ( 37 |
38 | {data.map((item) => { 39 | return renderItem(item); 40 | })} 41 |
42 | ); 43 | }; 44 | 45 | const mapStateToProps = (state) => { 46 | let group_admin_id; 47 | if (state.group.members) { 48 | group_admin_id = state.group.members.group_admin_id; 49 | } else if (state.requests) { 50 | group_admin_id = state.group.requests.group_admin_id; 51 | } 52 | return { 53 | authId: state.Authentication.id, 54 | group_admin_id, 55 | }; 56 | }; 57 | 58 | export default connect(mapStateToProps)(GroupMemberList); 59 | -------------------------------------------------------------------------------- /fb-clone-backend/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const joi = require("joi"); 3 | const jwt = require("jsonwebtoken"); 4 | const config = require("config"); 5 | 6 | const userSchema = new mongoose.Schema({ 7 | email: { 8 | type: String, 9 | required: true, 10 | minlength: 5, 11 | maxlength: 255, 12 | unique: true, 13 | }, 14 | password: { 15 | type: String, 16 | required: true, 17 | minlength: 3, 18 | }, 19 | profile_id: { 20 | type: mongoose.SchemaTypes.ObjectId, 21 | required: true, 22 | }, 23 | }); 24 | 25 | userSchema.methods.genToken = function () { 26 | const token = jwt.sign( 27 | { id: this._id, profile: this.profile_id }, 28 | config.get("secretKey"), 29 | { 30 | expiresIn: "2 days", 31 | } 32 | ); 33 | return token; 34 | }; 35 | 36 | const validateRegisterUser = (body) => { 37 | let schema = { 38 | f_name: joi.string().required().min(3).max(255), 39 | l_name: joi.string().required().min(3).max(255), 40 | email: joi.string().required().min(5).max(255).email(), 41 | about: joi.string().required().min(3).max(255), 42 | password: joi.string().required().min(3).max(255), 43 | gender: joi.string().required(), 44 | }; 45 | return joi.validate(body, schema); 46 | }; 47 | 48 | const validateAuthUser = (body) => { 49 | let schema = { 50 | email: joi.string().required().min(5).max(255).email(), 51 | password: joi.string().required().min(3).max(255), 52 | }; 53 | return joi.validate(body, schema); 54 | }; 55 | const UserModel = mongoose.model("User", userSchema); 56 | 57 | UserModel.methods; 58 | 59 | module.exports.validateRegisterUser = validateRegisterUser; 60 | module.exports.UserModel = UserModel; 61 | module.exports.validateAuthUser = validateAuthUser; 62 | -------------------------------------------------------------------------------- /client/src/Components/RequestsModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { connect } from "react-redux"; 3 | import { Redirect } from "react-router-dom"; 4 | import PropTypes from "prop-types"; 5 | 6 | import { addMember, getRequests, setReducer } from "../../Actions"; 7 | 8 | import ScrollModal from "../modal/ScrollModal.component"; 9 | import Loader from "../Loader"; 10 | import GroupMemberList from "../GroupMemberList"; 11 | 12 | const RequestModal = ({ getRequests, auth_id, history, requests, match }) => { 13 | const { id } = match.params; 14 | useEffect(() => { 15 | getRequests(id); 16 | }, [id, getRequests]); 17 | const renderAction = (id) => { 18 | return ( 19 | 25 | ); 26 | }; 27 | 28 | const onDissmiss = () => { 29 | return history.goBack(); 30 | }; 31 | if (!requests) return ; 32 | if (requests.group_admin_id !== auth_id) return ; 33 | return ( 34 | 42 | } 43 | onDismiss={onDissmiss} 44 | /> 45 | ); 46 | }; 47 | RequestModal.propTypes = { 48 | getRequests: PropTypes.func.isRequired, 49 | auth_id: PropTypes.string.isRequired, 50 | }; 51 | 52 | const mapStatetoProps = (state) => { 53 | return { 54 | requests: state.group.requests, 55 | auth_id: state.Authentication.id, 56 | }; 57 | }; 58 | export default connect(mapStatetoProps, { 59 | getRequests, 60 | setReducer, 61 | })(RequestModal); 62 | -------------------------------------------------------------------------------- /client/src/Components/CommentList/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .comments { 3 | width: 100%; 4 | position: relative; 5 | padding: 1rem 0 0; 6 | 7 | &__list { 8 | .loader__container { 9 | padding: 2.5rem; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | } 14 | } 15 | .more-btn { 16 | width: 90%; 17 | margin: 0 auto; 18 | margin-bottom: 1rem; 19 | cursor: pointer; 20 | p { 21 | display: inline-block; 22 | padding: 0.5rem; 23 | text-transform: capitalize; 24 | font-size: 1.2rem; 25 | &:hover { 26 | background-color: rgba($grey-color-border, 0.1); 27 | } 28 | } 29 | } 30 | &__form { 31 | display: flex; 32 | flex-direction: row; 33 | width: 100%; 34 | align-items: center; 35 | margin: 0 auto; 36 | &-avatar { 37 | margin-top: 1rem; 38 | height: 3.5rem; 39 | flex: 0 0 3.5rem; 40 | margin-right: 1rem; 41 | border-radius: 50%; 42 | overflow: hidden; 43 | img { 44 | height: 100%; 45 | width: 100%; 46 | } 47 | } 48 | } 49 | #comments__input { 50 | width: 100%; 51 | border-bottom: 2px solid $grey-color-border; 52 | &:focus { 53 | outline: none; 54 | border: none; 55 | border-bottom: 2px solid $primary-color; 56 | } 57 | } 58 | &__btn { 59 | margin-left: 5px; 60 | border-radius: 50%; 61 | border: none; 62 | padding: 0.8rem; 63 | background-color: $primary-color; 64 | // padding: 1rem; 65 | display: flex; 66 | justify-content: center; 67 | align-items: center; 68 | cursor: pointer; 69 | #comments-btn-icon { 70 | color: white; 71 | font-size: 1.4rem; 72 | padding: none; 73 | display: flex; 74 | justify-content: center; 75 | align-items: center; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /client/src/Components/PGList/index.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index.scss"; 2 | .empty-container { 3 | width: 60%; 4 | margin: 0 auto; 5 | background-color: $white-color; 6 | padding: 2rem; 7 | margin-top: 1rem; 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | font-size: 1.6rem; 12 | @include respond(phone) { 13 | width: 100%; 14 | } 15 | } 16 | .pg { 17 | &__list { 18 | background-color: $white-color; 19 | margin: 0 auto; 20 | margin-top: 1rem; 21 | padding: 1.5rem; 22 | margin-bottom: 5.6rem; 23 | width: 60%; 24 | text-align: center; 25 | @include respond(phone) { 26 | width: 100%; 27 | padding: 0.5rem; 28 | } 29 | } 30 | &__item { 31 | width: 100%; 32 | height: 40rem; 33 | cursor: pointer; 34 | border-radius: 5px; 35 | position: relative; 36 | padding: 0; 37 | .overlay-container { 38 | z-index: 1; 39 | background-color: rgba(0, 0, 0, 0.5); 40 | position: absolute; 41 | left: 0; 42 | right: 0; 43 | top: 0; 44 | bottom: 0; 45 | } 46 | @include respond(phone) { 47 | height: 25rem; 48 | } 49 | &:not(:last-child) { 50 | margin-bottom: 2rem; 51 | } 52 | } 53 | 54 | &__item-image { 55 | height: 100%; 56 | width: 100%; 57 | } 58 | &__info { 59 | position: absolute; 60 | left: 0; 61 | right: 0; 62 | top: 0; 63 | bottom: 0; 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | flex-direction: column; 68 | z-index: 2; 69 | } 70 | &__item-title { 71 | font-size: 1.9rem; 72 | text-transform: uppercase; 73 | padding: 1rem 2rem; 74 | margin-bottom: 1rem; 75 | background-color: $white-color; 76 | letter-spacing: 0.3rem; 77 | } 78 | &__item-description { 79 | font-size: 1.4rem; 80 | color: $white-color; 81 | letter-spacing: 0.3rem; 82 | font-weight: 700; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /fb-clone-backend/models/PostModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const joi = require("joi"); 3 | const likeSchema = require("./Likes"); 4 | 5 | const postSchema = new mongoose.Schema({ 6 | description: { 7 | type: String, 8 | required: true, 9 | minlength: 5, 10 | }, 11 | belongsTo: { 12 | type: String, 13 | enum: ["page", "group", "profile"], 14 | default: "", 15 | }, 16 | // pageId: { 17 | // type: mongoose.Types.ObjectId, 18 | // required: function () { 19 | // return this.belongsTo === "page"; 20 | // }, 21 | // ref: "Page", 22 | // }, 23 | groupId: { 24 | type: mongoose.Types.ObjectId, 25 | required: function () { 26 | return this.belongsTo === "group"; 27 | }, 28 | ref: "Group", 29 | }, 30 | author_id: { 31 | type: mongoose.SchemaTypes.ObjectId, 32 | required: function () { 33 | return this.belongsTo === "" || this.belongsTo === "group"; 34 | }, 35 | ref: function () { 36 | if (this.belongsTo === "page") { 37 | return "Page"; 38 | } else { 39 | return "Profile"; 40 | } 41 | }, 42 | }, 43 | author_name: { 44 | type: String, 45 | required: true, 46 | minlength: 3, 47 | maxlength: 255, 48 | }, 49 | image: { 50 | data: Buffer, 51 | contentType: String, 52 | }, 53 | hasImage: { 54 | type: Boolean, 55 | default: false, 56 | }, 57 | likes: { 58 | type: [likeSchema], 59 | default: [], 60 | }, 61 | comments: { 62 | type: [ 63 | { 64 | type: mongoose.Types.ObjectId, 65 | ref: "Comment", 66 | }, 67 | ], 68 | default: [], 69 | }, 70 | date: { 71 | type: Date, 72 | default: Date.now(), 73 | }, 74 | }); 75 | 76 | let PostModel = mongoose.model("Post", postSchema); 77 | 78 | const validatePost = (body) => { 79 | let Schema = { 80 | description: joi.string().min(3).required(), 81 | }; 82 | return joi.validate(body, Schema); 83 | }; 84 | module.exports = { 85 | validatePost, 86 | PostModel, 87 | }; 88 | -------------------------------------------------------------------------------- /fb-clone-backend/routes/home.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const ProfileModel = require("../models/profile"); 3 | const { PostModel } = require("../models/PostModel"); 4 | const auth = require("../Middleware/auth"); 5 | const asyncMiddleware = require("../Middleware/asyncMiddleware"); 6 | const router = express.Router(); 7 | 8 | router.get( 9 | "/", 10 | auth, 11 | asyncMiddleware(async (req, res) => { 12 | let { profile: user_profile_id } = req.user; 13 | const { pageSize, pageNumber } = req.query; 14 | const { following, blocked_users, blocked_by } = 15 | await ProfileModel.findById({ 16 | _id: user_profile_id, 17 | }).select({ following: 1, blocked_users: 1, blocked_by: 1, pages: 1 }); 18 | let total = await PostModel.find({ 19 | belongsTo: "profile", 20 | $or: [ 21 | { author_id: user_profile_id }, 22 | { profile_id: { $in: following } }, 23 | { 24 | $and: [ 25 | { author_id: { $nin: blocked_by } }, 26 | { author_id: { $nin: blocked_users } }, 27 | ], 28 | }, 29 | ], 30 | }).count(); 31 | let posts = await PostModel.find({ 32 | belongsTo: "profile", 33 | $or: [ 34 | { author_id: user_profile_id }, 35 | { profile_id: { $in: following } }, 36 | { 37 | $and: [ 38 | { author_id: { $nin: blocked_by } }, 39 | { author_id: { $nin: blocked_users } }, 40 | ], 41 | }, 42 | ], 43 | }) 44 | .select("-image") 45 | .populate("author_id", "_id f_name l_name page_admin_id") 46 | .populate("groupId", "_id name group_admin_id") 47 | .sort("-date") 48 | .skip(parseInt((pageNumber - 1) * 10)) 49 | .limit(10); 50 | posts = posts.map((item) => { 51 | return { 52 | ...item._doc, 53 | comments: item.comments.length, 54 | likes: item.likes.length, 55 | }; 56 | }); 57 | 58 | return res.send({ total, posts }); 59 | }) 60 | ); 61 | 62 | module.exports = router; 63 | -------------------------------------------------------------------------------- /client/src/Pages/CreateFbPagePage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { reduxForm, Field } from "redux-form"; 3 | import { connect } from "react-redux"; 4 | 5 | import { setImage, createPage } from "../../Actions"; 6 | 7 | import FormField from "../../Components/FormField"; 8 | import ImageUploadField from "../../Components/ImageUploadField"; 9 | import "./index.style.scss"; 10 | 11 | const CreatePageForm = ({ 12 | setImage, 13 | image, 14 | createPage, 15 | handleSubmit, 16 | ...rest 17 | }) => { 18 | const onSubmit = (formValues) => { 19 | createPage(formValues); 20 | }; 21 | useEffect(() => { 22 | return () => { 23 | setImage(null); 24 | }; 25 | }, []); 26 | return ( 27 |
28 |
29 | 36 | 43 |
44 | 45 |
46 |
47 | 50 |
51 | 52 |
53 | ); 54 | }; 55 | const mapStateToProps = (state) => { 56 | return { image: state.image }; 57 | }; 58 | const validation = (formValues) => { 59 | let errors = {}; 60 | if (!formValues.name) { 61 | errors.name = "Page Name is required"; 62 | } 63 | if (!formValues.description) { 64 | errors.description = "Description is required"; 65 | } 66 | return errors; 67 | }; 68 | 69 | const wrappedComponent = reduxForm({ form: "Page", validate: validation })( 70 | CreatePageForm 71 | ); 72 | 73 | export default connect(mapStateToProps, { setImage, createPage })( 74 | wrappedComponent 75 | ); 76 | -------------------------------------------------------------------------------- /client/src/Components/NotificationList/index.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .notification { 3 | position: relative; 4 | z-index: 1000; 5 | &____avatar-container { 6 | display: flex; 7 | margin-right: 3rem; 8 | } 9 | &__loader { 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | padding: 2rem 0; 14 | width: 100%; 15 | } 16 | .last-item { 17 | margin-top: 1.5rem; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | &__showbtn { 23 | border: none; 24 | background-color: transparent; 25 | border-bottom: 2px solid #3f74e7; 26 | text-transform: capitalize; 27 | color: $font-color; 28 | padding: 0.5rem 1rem; 29 | cursor: pointer; 30 | } 31 | &__list { 32 | z-index: 9999; 33 | background-color: $white-color; 34 | width: 40rem; 35 | max-height: 70vh; 36 | overflow-y: auto; 37 | transition: all 2s ease; 38 | scrollbar-width: none; 39 | &__header__container { 40 | background-color: #3f74e7; 41 | text-align: center; 42 | } 43 | &__header { 44 | color: white; 45 | letter-spacing: 2px; 46 | font-size: 1.6rem; 47 | } 48 | ul { 49 | padding: 0 1.5rem; 50 | } 51 | &::-webkit-scrollbar { 52 | display: none; 53 | } 54 | .notification__item { 55 | display: flex; 56 | align-items: center; 57 | padding: 15px; 58 | &:not(:first-child) { 59 | border-top: 1px solid rgba(97, 90, 90, 0.1); 60 | } 61 | .notification__avatar { 62 | height: 4.5rem; 63 | width: 4.5rem; 64 | border-radius: 10rem; 65 | } 66 | .right { 67 | flex: 1; 68 | margin-left: 1rem; 69 | .notification__item__header { 70 | margin: 0; 71 | margin-bottom: 1rem; 72 | font-size: 1.5rem; 73 | } 74 | } 75 | } 76 | .notification__emptyContainer { 77 | text-align: center; 78 | width: 100%; 79 | padding: 1rem; 80 | } 81 | h2 { 82 | font-size: 2rem; 83 | width: 100%; 84 | padding: 1rem; 85 | } 86 | } 87 | &__item__date { 88 | font-size: 1.3rem; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /client/src/Reducers/Posts.js: -------------------------------------------------------------------------------- 1 | import { postTypes } from "./constants"; 2 | import _ from "lodash"; 3 | 4 | const tranformPosts = (initialPosts) => { 5 | const posts = {}; 6 | initialPosts.forEach((item) => { 7 | return (posts[item._id] = item); 8 | }); 9 | return posts; 10 | }; 11 | const INITIAL_STATE = { 12 | data: {}, 13 | loading: true, 14 | post: {}, 15 | }; 16 | export default (state = INITIAL_STATE, action) => { 17 | switch (action.type) { 18 | case postTypes.reset: 19 | return { 20 | ...INITIAL_STATE, 21 | }; 22 | case postTypes.setPost: 23 | return { 24 | ...state, 25 | post: action.payload, 26 | }; 27 | case postTypes.setLoading: 28 | return { 29 | ...state, 30 | loading: action.payload, 31 | }; 32 | case postTypes.setPosts: 33 | return { 34 | ...state, 35 | data: { ...tranformPosts(action.payload.posts) }, 36 | }; 37 | case postTypes.setMorePosts: 38 | const newPosts = tranformPosts(action.payload.posts); 39 | return { 40 | ...state, 41 | data: { ...state.data, ...newPosts }, 42 | }; 43 | case postTypes.createPost: 44 | const { _id: id } = action.payload; 45 | return { ...state, data: { [id]: action.payload, ...state.data } }; 46 | case postTypes.updatePost: 47 | const { _id } = action.payload; 48 | return { ...state, data: { [_id]: action.payload } }; 49 | case postTypes.incPostComments: 50 | return { 51 | ...state, 52 | data: { 53 | ...state.data, 54 | [action.payload]: { 55 | ...state.data[action.payload], 56 | comments: state.data[action.payload].comments + 1, 57 | }, 58 | }, 59 | }; 60 | case postTypes.decPostComments: 61 | return { 62 | ...state, 63 | data: { 64 | ...state.data, 65 | [action.payload]: { 66 | ...state.data[action.payload], 67 | comments: state.data[action.payload].comments - 1, 68 | }, 69 | }, 70 | }; 71 | case postTypes.deletePost: 72 | return { ...state, data: _.omit(state.data, [action.payload]) }; 73 | default: 74 | return state; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /client/src/Pages/PagesMainPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import SecondaryNav from "../../Components/SecondaryNav/"; 4 | import PGList from "../../Components/PGList"; 5 | 6 | import { getPages, resetPages } from "../../Actions"; 7 | import { connect } from "react-redux"; 8 | 9 | const Pages = ({ pages, loading, getPages, resetPages }) => { 10 | const [activeItem, setActiveItem] = useState("all"); 11 | const renderRightMenu = () => { 12 | return ( 13 |
14 | 15 | Create New Page 16 | 17 |
18 | ); 19 | }; 20 | 21 | const getResource = (pageNumber = 1) => { 22 | getPages(activeItem, pageNumber); 23 | }; 24 | const reset = () => { 25 | resetPages(); 26 | }; 27 | return ( 28 |
29 | { 35 | setActiveItem("all"); 36 | }, 37 | }, 38 | { 39 | title: "Liked Page", 40 | styleId: activeItem === "liked" ? "active" : "", 41 | onItemClick: () => { 42 | setActiveItem("liked"); 43 | }, 44 | }, 45 | { 46 | title: "Pages Managed by you", 47 | styleId: activeItem === "managed" ? "active" : "", 48 | onItemClick: () => { 49 | setActiveItem("managed"); 50 | }, 51 | }, 52 | ]} 53 | rightMenu={renderRightMenu()} 54 | /> 55 | 65 |
66 | ); 67 | }; 68 | 69 | const mapStateToProps = (state) => { 70 | return { 71 | pages: state.pages.pagesList, 72 | loading: state.pages.loading, 73 | }; 74 | }; 75 | export default connect(mapStateToProps, { getPages, resetPages })(Pages); 76 | -------------------------------------------------------------------------------- /fb-clone-backend/models/GroupModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const LikeSchema = require("./Likes"); 3 | const joi = require("joi"); 4 | 5 | const groupSchema = new mongoose.Schema({ 6 | name: { 7 | type: String, 8 | required: true, 9 | minlength: 3, 10 | maxlength: 255, 11 | }, 12 | cover: { 13 | data: Buffer, 14 | contentType: String, 15 | }, 16 | description: { 17 | type: String, 18 | required: true, 19 | minlength: 3, 20 | maxlength: 255, 21 | }, 22 | group_privacy: { 23 | type: String, 24 | enum: ["public", "private"], 25 | default: "public", 26 | }, 27 | created_on: { 28 | type: Date, 29 | default: Date.now(), 30 | }, 31 | group_admin_id: { 32 | type: mongoose.Types.ObjectId, 33 | required: true, 34 | ref: "Profile", 35 | }, 36 | members: { 37 | type: [ 38 | { 39 | _id: { 40 | type: mongoose.Types.ObjectId, 41 | required: true, 42 | ref: "Profile", 43 | }, 44 | name: { 45 | type: String, 46 | required: true, 47 | }, 48 | isAdmin: { 49 | type: Boolean, 50 | default: false, 51 | }, 52 | }, 53 | ], 54 | default: [], 55 | }, 56 | requests: { 57 | type: [ 58 | { 59 | _id: { 60 | type: mongoose.Types.ObjectId, 61 | required: true, 62 | ref: "Profile", 63 | }, 64 | name: { 65 | type: String, 66 | required: true, 67 | minlength: 3, 68 | }, 69 | }, 70 | ], 71 | default: [], 72 | }, 73 | posts: { 74 | type: [ 75 | { 76 | _id: { 77 | type: mongoose.Types.ObjectId, 78 | required: true, 79 | }, 80 | }, 81 | ], 82 | default: [], 83 | }, 84 | }); 85 | 86 | const GroupModel = mongoose.model("Group", groupSchema); 87 | 88 | const validateGroup = (body) => { 89 | const schema = { 90 | name: joi.string().required().min(2).max(255), 91 | description: joi.string().required().min(2).max(255), 92 | group_privacy: joi.string().required().valid("public", "private"), 93 | }; 94 | return joi.validate(body, schema); 95 | }; 96 | 97 | module.exports = { GroupModel, validateGroup }; 98 | -------------------------------------------------------------------------------- /client/src/Components/Post/post.style.scss: -------------------------------------------------------------------------------- 1 | @import "../../sass/index"; 2 | .post { 3 | background-color: $white-color; 4 | width: 100%; 5 | padding: 2.5rem; 6 | margin-bottom: 1rem; 7 | #comment-icon { 8 | color: $font-color; 9 | } 10 | .post__header { 11 | display: flex; 12 | justify-content: space-between; 13 | .post__header__left { 14 | display: flex; 15 | flex-direction: row; 16 | .post__userAvatar { 17 | height: 4.5rem; 18 | width: 4.5rem; 19 | border-radius: 10rem; 20 | } 21 | .post__InfoContainer { 22 | display: flex; 23 | flex-direction: column; 24 | margin-left: 2rem; 25 | h3 { 26 | color: $font-color; 27 | margin: 0; 28 | cursor: pointer; 29 | font-size: 1.8rem; 30 | } 31 | p { 32 | color: $grey-color-2; 33 | font-size: 1.3rem; 34 | } 35 | } 36 | } 37 | .post__header__right { 38 | display: flex; 39 | #post__icon { 40 | font-size: 1.5rem; 41 | margin: 0; 42 | padding: 0; 43 | cursor: pointer; 44 | color: $grey-color-2; 45 | &:hover { 46 | color: $font-color; 47 | } 48 | // #post__icon:last-child { 49 | // margin-left: 5px; 50 | // } 51 | } 52 | } 53 | } 54 | .post__description { 55 | color: $font-color; 56 | font-weight: 400; 57 | font-size: 1.4rem; 58 | margin: 1.5rem 0; 59 | margin-left: 1rem; 60 | } 61 | .post__image { 62 | height: 40rem; 63 | width: 100%; 64 | border-radius: 0.5rem; 65 | } 66 | .post__actionsContainer { 67 | width: 100%; 68 | margin-top: 20px; 69 | border-top: 2px solid rgba(167, 165, 165, 0.2); 70 | border-bottom: 2px solid rgba(167, 165, 165, 0.2); 71 | // background-color: red; 72 | padding: 10px; 73 | display: flex; 74 | .post__likeContainer { 75 | flex: 1; 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | } 80 | .post__commentContainer { 81 | flex: 1; 82 | display: flex; 83 | justify-content: center; 84 | align-items: center; 85 | font-size: 14; 86 | color: $font-color; 87 | font-weight: 600; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /fb-clone-backend/routes/user.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | UserModel, 4 | validateAuthUser, 5 | validateRegisterUser, 6 | } = require("../models/user"); 7 | 8 | const asyncMiddleware = require("../Middleware/asyncMiddleware"); 9 | const bcrypt = require("bcrypt"); 10 | const ProfileModel = require("../models/profile"); 11 | const router = express.Router(); 12 | 13 | router.post( 14 | "/register", 15 | asyncMiddleware(async (req, res) => { 16 | let { error } = validateRegisterUser(req.body); 17 | if (error) return res.status(400).send(error.details[0].message); 18 | const { f_name, l_name, gender, about } = req.body; 19 | let user = await UserModel.findOne({ email: req.body.email }); 20 | if (user) return res.status(400).send("User Already Registered"); 21 | user = new UserModel({ 22 | email: req.body.email, 23 | password: await bcrypt.hash(req.body.password, await bcrypt.genSalt(10)), 24 | }); 25 | let userProfile = new ProfileModel({ 26 | f_name, 27 | l_name, 28 | gender, 29 | about, 30 | }); 31 | user.profile_id = userProfile._id; 32 | userProfile.user_id = user._id; 33 | await user.save(); 34 | await userProfile.save(); 35 | return res.header("x-auth-token", user.genToken()).status(200).send({ 36 | id: userProfile._id, 37 | f_name: userProfile.f_name, 38 | l_name: userProfile.l_name, 39 | }); 40 | }) 41 | ); 42 | 43 | router.post( 44 | "/auth", 45 | asyncMiddleware(async (req, res) => { 46 | const { error } = validateAuthUser(req.body); 47 | if (error) return res.status(400).send(error.details[0].message); 48 | const user = await UserModel.findOne({ email: req.body.email }); 49 | if (!user) return res.status(400).send("Incorrect Email or Password"); 50 | const profile = await ProfileModel.findById({ 51 | _id: user.profile_id, 52 | }).select("_id f_name l_name"); 53 | let passwordIsCorrect = await bcrypt.compare( 54 | req.body.password, 55 | user.password 56 | ); 57 | if (!passwordIsCorrect) 58 | return res.status(401).send("Invalid Email or Password"); 59 | return res.header("x-auth-token", user.genToken()).status(200).send({ 60 | id: profile._id, 61 | f_name: profile.f_name, 62 | l_name: profile.l_name, 63 | }); 64 | }) 65 | ); 66 | 67 | module.exports = router; 68 | -------------------------------------------------------------------------------- /client/src/Components/PGList/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import ScrollView from "react-infinite-scroll-component"; 3 | import { connect } from "react-redux"; 4 | import PropTypes from "prop-types"; 5 | 6 | import PGItem from "./PGItem"; 7 | 8 | import { setReducer } from "../../Actions"; 9 | import { paginationTypes } from "../../Reducers/constants"; 10 | 11 | import "./index.style.scss"; 12 | const List = ({ 13 | pageNumber, 14 | hasMore, 15 | type, 16 | data, 17 | source, 18 | loading, 19 | endMessage, 20 | emptyMessage, 21 | getResource, 22 | setReducer, 23 | reset, 24 | }) => { 25 | useEffect(() => { 26 | return () => { 27 | setReducer({ type: paginationTypes.reset }); 28 | reset(); 29 | }; 30 | }, [type]); 31 | useEffect(() => { 32 | getResource(pageNumber); 33 | }, [pageNumber, type]); 34 | 35 | if (loading) { 36 | return
; 37 | } 38 | 39 | if (data.length === 0) { 40 | return
{emptyMessage}
; 41 | } 42 | const next = () => { 43 | setReducer({ type: paginationTypes.incPageNumber }); 44 | }; 45 | return ( 46 | } 51 | className="pg__list" 52 | endMessage={
{endMessage}
} 53 | scrollThreshold="200px" 54 | > 55 | {data.map((item) => { 56 | return ; 57 | })} 58 |
59 | ); 60 | }; 61 | List.defaultProps = { 62 | reset: () => {}, 63 | getResource: () => {}, 64 | }; 65 | 66 | List.propTypes = { 67 | pageNumber: PropTypes.number.isRequired, 68 | hasMore: PropTypes.bool.isRequired, 69 | type: PropTypes.string.isRequired, 70 | data: PropTypes.array.isRequired, 71 | source: PropTypes.string.isRequired, 72 | loading: PropTypes.bool.isRequired, 73 | endMessage: PropTypes.string.isRequired, 74 | emptyMessage: PropTypes.string.isRequired, 75 | getResource: PropTypes.func.isRequired, 76 | setReducer: PropTypes.func.isRequired, 77 | reset: PropTypes.func, 78 | }; 79 | const mapStateToProps = (state) => { 80 | const { pageNumber, hasMore } = state.pagination; 81 | return { 82 | pageNumber, 83 | hasMore, 84 | }; 85 | }; 86 | export default connect(mapStateToProps, { setReducer })(List); 87 | -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 22 | 23 | 32 | Facebook Clone 33 | 37 | 38 | 42 | 43 | 44 | 45 |
46 | 47 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /client/src/Actions/blockActions.js: -------------------------------------------------------------------------------- 1 | import Api from "../Api"; 2 | import history from "../history"; 3 | import { blockTypes } from "../Reducers/constants"; 4 | export const getAllBlockedUsers = () => { 5 | return async (dispatch) => { 6 | try { 7 | const response = await Api.get("/block/users"); 8 | dispatch({ 9 | type: blockTypes.setBlockedUsers, 10 | payload: response.data, 11 | }); 12 | 13 | dispatch({ 14 | type: blockTypes.setLoading, 15 | payload: false, 16 | }); 17 | } catch (err) { 18 | console.log(err.response.data); 19 | dispatch({ 20 | type: blockTypes.setLoading, 21 | payload: false, 22 | }); 23 | } 24 | }; 25 | }; 26 | export const checkBlocked = (id) => { 27 | return async (dispatch) => { 28 | try { 29 | const response = await Api.get(`/block/check/${id}`); 30 | if (response.status === 200) { 31 | const { isBlocked, message } = response.data; 32 | dispatch({ 33 | type: blockTypes.setBlockStatus, 34 | payload: isBlocked, 35 | }); 36 | 37 | dispatch({ type: blockTypes.setMessage, payload: message }); 38 | dispatch({ 39 | type: blockTypes.setLoading, 40 | payload: false, 41 | }); 42 | } 43 | } catch (err) { 44 | console.log(err.response.data); 45 | dispatch({ 46 | type: blockTypes.setLoading, 47 | payload: false, 48 | }); 49 | } 50 | }; 51 | }; 52 | 53 | export const blockUser = (id) => { 54 | return async (dispatch) => { 55 | try { 56 | const response = await Api.put(`/block/${id}`); 57 | if (response.status === 200) { 58 | history.push("/"); 59 | return dispatch({ 60 | type: blockTypes.setBlockStatus, 61 | payload: false, 62 | }); 63 | } 64 | } catch (err) { 65 | console.log(err.response.data); 66 | } 67 | }; 68 | }; 69 | 70 | export const unBlockUser = (id) => { 71 | return async (dispatch) => { 72 | try { 73 | const response = await Api.put(`/unblock/${id}`); 74 | if (response.status === 200) { 75 | return dispatch({ 76 | type: blockTypes.setBlockedUsers, 77 | payload: response.data, 78 | }); 79 | } 80 | } catch (err) { 81 | console.log(err.response.data); 82 | } 83 | }; 84 | }; 85 | 86 | // export const blockActions = { 87 | // checkBlocked, 88 | // blockUser, 89 | // getAllBlockedUsers, 90 | // unBlockUser, 91 | // }; 92 | -------------------------------------------------------------------------------- /client/src/Components/Posts/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import "./posts.style.scss"; 5 | import Post from "../Post"; 6 | import InlineLoader from "../InlineLoader"; 7 | import { connect } from "react-redux"; 8 | import InfiniteScroll from "react-infinite-scroll-component"; 9 | import { setReducer, getPosts } from "../../Actions"; 10 | import { postTypes } from "../../Reducers/constants"; 11 | 12 | let renderPost = (posts) => { 13 | return Object.keys(posts).map((key) => { 14 | return ; 15 | }); 16 | }; 17 | const Posts = ({ id, getHome, type, posts, loading, getPosts, setReducer }) => { 18 | const [pageNumber, setPageNumber] = useState(1); 19 | const [hasMore, setHasMore] = useState(true); 20 | useEffect(() => { 21 | return () => { 22 | setReducer({ type: postTypes.reset }); 23 | }; 24 | }, []); 25 | useEffect(() => { 26 | getPosts({ id, getHome, type, pageNumber, setHasMore }); 27 | }, [pageNumber]); 28 | const fetchMorePosts = () => { 29 | setPageNumber((prevPage) => prevPage + 1); 30 | }; 31 | 32 | return ( 33 |
34 | {loading ? ( 35 | 36 | ) : Object.keys(posts).length === 0 ? ( 37 |
Found no Posts
38 | ) : ( 39 |
45 | } 46 | hasMore={hasMore} 47 | endMessage={

No More Posts

} 48 | scrollThreshold="200px" 49 | > 50 | {renderPost(posts)} 51 | 52 | )} 53 | 54 | ); 55 | }; 56 | 57 | Posts.defaultProps = { 58 | id: "", 59 | type: null, 60 | getHome: false, 61 | }; 62 | Posts.propTypes = { 63 | id: PropTypes.string.isRequired, 64 | getHome: PropTypes.bool.isRequired, 65 | posts: PropTypes.object.isRequired, 66 | type: PropTypes.string.isRequired, 67 | loading: PropTypes.bool.isRequired, 68 | getPosts: PropTypes.func.isRequired, 69 | setReducer: PropTypes.func.isRequired, 70 | }; 71 | 72 | const mapStateToProps = (state) => { 73 | return { posts: state.Posts.data, loading: state.Posts.loading }; 74 | }; 75 | export default connect(mapStateToProps, { setReducer, getPosts })(Posts); 76 | -------------------------------------------------------------------------------- /client/src/Pages/GroupsPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import SecondaryNav from "../../Components/SecondaryNav"; 4 | import PGList from "../../Components/PGList"; 5 | import { connect } from "react-redux"; 6 | import { groupTypes } from "../../Reducers/constants"; 7 | import { getAllGroups, setReducer } from "../../Actions"; 8 | const GroupMainPage = ({ groups, loading, getAllGroups, setReducer }) => { 9 | const [activeItem, setActiveItem] = useState("all"); 10 | useEffect(() => { 11 | return () => { 12 | setReducer({ type: groupTypes.resetGroups }); 13 | }; 14 | }, [setReducer]); 15 | const renderRightMenu = () => { 16 | return ( 17 |
18 | 19 | Create New Group 20 | 21 |
22 | ); 23 | }; 24 | const getResource = (pageNumber) => { 25 | getAllGroups(activeItem, pageNumber); 26 | }; 27 | const reset = () => { 28 | setReducer({ type: groupTypes.resetGroups }); 29 | }; 30 | 31 | return ( 32 |
33 | { 39 | setActiveItem("all"); 40 | }, 41 | }, 42 | { 43 | title: "Groups you're in", 44 | styleId: activeItem === "joined" ? "active" : "", 45 | onItemClick: () => { 46 | setActiveItem("joined"); 47 | }, 48 | }, 49 | { 50 | title: "Groups Managed by you", 51 | styleId: activeItem === "managed" ? "active" : "", 52 | onItemClick: () => { 53 | setActiveItem("managed"); 54 | }, 55 | }, 56 | ]} 57 | rightMenu={renderRightMenu()} 58 | /> 59 | 60 | 70 |
71 | ); 72 | }; 73 | const mapStateToProps = (state) => { 74 | return { groups: state.group.groups, loading: state.group.loading }; 75 | }; 76 | 77 | export default connect(mapStateToProps, { getAllGroups, setReducer })( 78 | GroupMainPage 79 | ); 80 | -------------------------------------------------------------------------------- /client/src/Components/Login/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Field, reduxForm } from "redux-form"; 3 | import { connect } from "react-redux"; 4 | import { Link } from "react-router-dom"; 5 | 6 | import { LoginUser, eraseError } from "../../Actions"; 7 | 8 | import ErrorComponent from "../ErrorComponent"; 9 | import FormField from "../FormField"; 10 | 11 | import "./login.Style.scss"; 12 | 13 | class LoginComponent extends React.Component { 14 | state = { 15 | loading: false, 16 | }; 17 | componentWillUnmount() { 18 | this.props.eraseError(); 19 | } 20 | 21 | onSubmit = (formValues) => { 22 | if (!this.state.loading) { 23 | this.setState({ loading: true }); 24 | } 25 | this.props.LoginUser(formValues, () => { 26 | if (this.state.loading) { 27 | this.setState({ loading: false }); 28 | } 29 | }); 30 | }; 31 | render() { 32 | return ( 33 |
34 |

FaceBook Clone

35 |
36 |
37 | 45 | 53 | 54 |
55 | 62 | 63 | New here? Signup 64 | 65 |
66 | 67 |
68 |
69 | ); 70 | } 71 | } 72 | 73 | const validate = ({ email, password }) => { 74 | let errors = {}; 75 | if (!email) { 76 | errors.email = "Please Enter Your Email"; 77 | } 78 | if (!password) { 79 | errors.password = "Please Enter Your Password"; 80 | } 81 | return errors; 82 | }; 83 | 84 | const wrappedForm = reduxForm({ form: "LoginForm", validate })(LoginComponent); 85 | export default connect(null, { LoginUser, eraseError })(wrappedForm); 86 | -------------------------------------------------------------------------------- /client/src/Pages/CreateGroupPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { reduxForm, Field } from "redux-form"; 3 | import { connect } from "react-redux"; 4 | import { setImage, createGroup, setReducer } from "../../Actions"; 5 | import FormField from "../../Components/FormField"; 6 | import ImageUploadField from "../../Components/ImageUploadField"; 7 | import RadioBtn from "../../Components/FormField/RadioButton"; 8 | 9 | import "./index.style.scss"; 10 | 11 | const CreatePageForm = ({ 12 | setImage, 13 | image, 14 | createGroup, 15 | handleSubmit, 16 | ...rest 17 | }) => { 18 | const onSubmit = (formValues) => { 19 | createGroup(formValues); 20 | }; 21 | useEffect(() => { 22 | return () => { 23 | setImage(null); 24 | }; 25 | }, []); 26 | return ( 27 |
28 |
29 | 36 | 43 |
44 | 45 | 52 | 59 |
60 | 61 |
62 | 65 |
66 | 67 |
68 | ); 69 | }; 70 | const mapStateToProps = (state) => { 71 | return { image: state.image, initialValues: { privacy: "public" } }; 72 | }; 73 | const validation = (formValues) => { 74 | let errors = {}; 75 | if (!formValues.name) { 76 | errors.name = "Group Name is required"; 77 | } 78 | if (!formValues.description) { 79 | errors.description = "Group is required"; 80 | } 81 | return errors; 82 | }; 83 | 84 | const wrappedComponent = reduxForm({ form: "Group", validate: validation })( 85 | CreatePageForm 86 | ); 87 | 88 | export default connect(mapStateToProps, { setImage, createGroup, setReducer })( 89 | wrappedComponent 90 | ); 91 | -------------------------------------------------------------------------------- /client/src/Pages/PageSettingsPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { getPage, deletePage, updatePage } from "../../Actions"; 3 | import { connect } from "react-redux"; 4 | import { reduxForm, Field } from "redux-form"; 5 | import DeleteBtn from "../../Components/DeleteBtn"; 6 | import "./index.style.scss"; 7 | import { Redirect } from "react-router-dom"; 8 | import CancelBtn from "../../Components/CancelBtn"; 9 | import SaveBtn from "../../Components/SaveButton"; 10 | 11 | import FormField from "../../Components/FormField"; 12 | 13 | /* A function for validation which 14 | will check that page_name and page_description 15 | is required. 16 | */ 17 | const validate = (formValues) => { 18 | let errors = {}; 19 | if (!formValues.page_name) { 20 | errors.page_name = "Page Name is required"; 21 | } 22 | if (!formValues.page_description) { 23 | errors.page_description = "Page Description is required"; 24 | } 25 | return errors; 26 | }; 27 | 28 | // Main Component 29 | const PageSettings = ({ 30 | getPage, 31 | match, 32 | history, 33 | page, 34 | auth_id, 35 | handleSubmit, 36 | deletePage, 37 | }) => { 38 | const { id } = match.params; 39 | useEffect(() => { 40 | getPage(id); 41 | }, [id, getPage]); 42 | const onSubmit = ({ name, description }) => { 43 | if (name === page.name && description === page.description) { 44 | return history.push(`/pages/${page._id}`); 45 | } 46 | updatePage(page._id, { name, description }); 47 | }; 48 | if (!page) { 49 | return
; 50 | } 51 | if (page.page_admin_id !== auth_id) return ; 52 | return ( 53 |
54 |
55 | 63 | 71 |
72 | 73 | deletePage(page._id)} /> 74 | 75 |
76 | 77 |
78 | ); 79 | }; 80 | 81 | const mapStateToprops = (state) => { 82 | const { page } = state.pages; 83 | return { initialValues: page, page, auth_id: state.Authentication.id }; 84 | }; 85 | 86 | const wrappedForm = reduxForm({ form: "Page Setting", validate })(PageSettings); 87 | 88 | export default connect(mapStateToprops, { getPage, deletePage })(wrappedForm); 89 | -------------------------------------------------------------------------------- /client/src/Pages/ProfileEditPage/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { reduxForm, Field } from "redux-form"; 3 | import { connect } from "react-redux"; 4 | 5 | import { getProfile, setReducer } from "../../Actions"; 6 | import history from "../../history"; 7 | import { profileTypes } from "../../Reducers/constants"; 8 | import DeleteBtn from "../../Components/DeleteBtn"; 9 | import CancelBtn from "../../Components/CancelBtn"; 10 | import FormField from "../../Components/FormField"; 11 | import Loader from "../../Components/Loader"; 12 | import SaveBtn from "../../Components/SaveButton"; 13 | import "./index.style.scss"; 14 | 15 | const ProfileEditPage = ({ getProfile, setReducer, profile, handleSubmit }) => { 16 | useEffect(() => { 17 | getProfile(); 18 | return () => { 19 | setReducer({ type: profileTypes.reset }); 20 | }; 21 | }, []); 22 | if (Object.keys(profile).length === 0) { 23 | return ; 24 | } 25 | 26 | const onSubmit = (formValues) => { 27 | history.push("/confirm", { formValues, operation: "update" }); 28 | }; 29 | const onDeleteClick = () => { 30 | history.push("/confirm", { operation: "delete" }); 31 | }; 32 | return ( 33 |
34 |
35 |

36 | Profile Settings 37 |

38 |
39 |
40 | 46 | 52 | 59 | 66 |
67 | history.goBack()} /> 68 | 69 | 70 |
71 | 72 |
73 | ); 74 | }; 75 | 76 | const mapStateToProps = (state) => { 77 | const profile = state.profileReducer.profile; 78 | return { initialValues: profile, profile }; 79 | }; 80 | 81 | const formWrapped = reduxForm({ 82 | form: "ProfileEditPage", 83 | enableReinitialize: true, 84 | })(ProfileEditPage); 85 | 86 | export default connect(mapStateToProps, { getProfile, setReducer })( 87 | formWrapped 88 | ); 89 | -------------------------------------------------------------------------------- /client/src/Components/ImageUploaderModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import Modal from "../modal/modal.component"; 3 | import ImageUploaderComponent from "../ImageUploader"; 4 | import history from "../../history"; 5 | import { connect } from "react-redux"; 6 | import { uploadPhoto, setError, eraseError, setImage } from "../../Actions"; 7 | import ErrorComponent from "../ErrorComponent"; 8 | import "./index.style.scss"; 9 | 10 | const ImageUploaderModal = ({ 11 | image, 12 | uploadPhoto, 13 | location, 14 | eraseError, 15 | setError, 16 | setImage, 17 | }) => { 18 | const [loading, setLoading] = useState(false); 19 | const { uploadUrl } = location.state; 20 | useEffect(() => { 21 | return () => { 22 | if (image) { 23 | setImage(null); 24 | } 25 | eraseError(); 26 | }; 27 | }); 28 | 29 | // Event Listener for Save Button 30 | const onSaveClick = (e) => { 31 | if (!image) { 32 | setError("Please Upload a Valid Image"); 33 | return e.preventDefault(); 34 | } 35 | if (!loading) { 36 | setLoading(true); 37 | } 38 | return uploadPhoto(image, uploadUrl, () => { 39 | if (loading) { 40 | setLoading(false); 41 | } 42 | }); 43 | }; 44 | 45 | const renderLabel = () => { 46 | return ( 47 | <> 48 |
Upload
49 | 50 | ); 51 | }; 52 | const renderActions = () => { 53 | return ( 54 | <> 55 | 56 | 57 |
58 | {loading ? ( 59 |
60 | ) : ( 61 |

Save

62 | )} 63 |
64 | 65 | ); 66 | }; 67 | const renderBody = () => { 68 | return ( 69 | image && ( 70 |
71 | Placeholder 76 |
77 | ) 78 | ); 79 | }; 80 | return ( 81 |
82 | { 85 | history.goBack(); 86 | }} 87 | body={renderBody()} 88 | actions={renderActions()} 89 | /> 90 |
91 | ); 92 | }; 93 | 94 | const mapStateToProps = (state) => { 95 | return { image: state.image, progress: state.uploadProgress }; 96 | }; 97 | 98 | export default connect(mapStateToProps, { 99 | uploadPhoto, 100 | setError, 101 | eraseError, 102 | setImage, 103 | })(ImageUploaderModal); 104 | -------------------------------------------------------------------------------- /client/src/Components/ConfirmPasswordModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import PropTypes from "prop-types"; 3 | import Modal from "../modal/modal.component"; 4 | import { updateProfile, setReducer, deleteProfile } from "../../Actions"; 5 | import { connect } from "react-redux"; 6 | import "./index.styles.scss"; 7 | 8 | import { profileTypes } from "../../Reducers/constants"; 9 | const ConfirmPassword = ({ 10 | history, 11 | location, 12 | updateProfile, 13 | setReducer, 14 | deleteProfile, 15 | }) => { 16 | const [confirmPassword, setPassword] = useState(""); 17 | const [error, setError] = useState(""); 18 | useEffect(() => { 19 | return () => { 20 | setReducer({ type: profileTypes.reset }); 21 | }; 22 | }); 23 | const renderForm = () => { 24 | return ( 25 |
26 | 27 | 28 | { 32 | if (error) { 33 | setError(""); 34 | } 35 | setPassword(e.target.value); 36 | }} 37 | /> 38 | {error ?
{error}
: null} 39 |
40 | ); 41 | }; 42 | const onConfirmClick = () => { 43 | const { operation } = location.state; 44 | if (!confirmPassword.length) return setError("Password is required"); 45 | if (error) return setError(""); 46 | if (operation === "update") { 47 | const { formValues } = location.state; 48 | updateProfile({ ...formValues, confirmPassword }); 49 | } 50 | if (operation === "delete") { 51 | console.log("from modal", confirmPassword); 52 | deleteProfile(confirmPassword); 53 | } 54 | }; 55 | 56 | const renderActions = () => { 57 | return ( 58 | <> 59 | 66 | 73 | 74 | ); 75 | }; 76 | 77 | return ( 78 | history.goBack()} 83 | /> 84 | ); 85 | }; 86 | 87 | ConfirmPassword.propTypes = { 88 | history: PropTypes.object.isRequired, 89 | location: PropTypes.object.isRequired, 90 | updateProfile: PropTypes.func.isRequired, 91 | setReducer: PropTypes.func.isRequired, 92 | deleteProfile: PropTypes.func.isRequired, 93 | }; 94 | export default connect(null, { updateProfile, setReducer, deleteProfile })( 95 | ConfirmPassword 96 | ); 97 | --------------------------------------------------------------------------------