├── 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 |
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 |
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 |
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 |
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 |
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 |
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 |

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 |
--------------------------------------------------------------------------------
/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 |

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 |

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 |

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 |
16 |
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 |
}`})
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 |
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 |
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 |

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 |
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 |
13 | ) : (
14 |
{renderAction(item._id)}
15 | )}
16 |

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 |
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 |
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 |
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 |
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 |
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 |
})
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 |
--------------------------------------------------------------------------------