├── api ├── .gitignore ├── utils │ ├── catchAsync.js │ ├── allowedOrigins.js │ ├── corsOptions.js │ ├── getRelationship.js │ ├── cloudinary.js │ ├── socket.js │ └── notification.js ├── validator │ ├── IsEmpty.js │ ├── PostValidation.js │ ├── SigninValidation.js │ └── SignupValidation.js ├── models │ ├── reaction.js │ ├── friend.js │ ├── comment.js │ ├── notification.js │ └── post.js ├── views │ └── 404.html ├── public │ └── css │ │ └── style.css ├── middlewares │ ├── postsLimiter.js │ ├── multerMiddleware.js │ ├── checkAuth.js │ └── sharpMiddleware.js ├── config │ └── db.js ├── routes │ ├── notifications.js │ ├── friends.js │ ├── users.js │ └── posts.js ├── package.json ├── controllers │ ├── notification.js │ └── auth.js └── server.js ├── client ├── netlify.toml ├── src │ ├── assets │ │ ├── icons │ │ │ ├── Close.png │ │ │ ├── icons11.png │ │ │ ├── icons15.png │ │ │ ├── icons16.png │ │ │ ├── icons17.png │ │ │ ├── icons18.png │ │ │ ├── icons2.png │ │ │ ├── icons25.png │ │ │ ├── icons32.png │ │ │ ├── icons33.png │ │ │ ├── icons40.png │ │ │ ├── icons41.png │ │ │ ├── icons5.png │ │ │ ├── icons6.png │ │ │ ├── icons7.png │ │ │ └── publicpack.png │ │ ├── sound │ │ │ └── notification.wav │ │ └── svg │ │ │ ├── dots.jsx │ │ │ ├── user.svg │ │ │ ├── arrowDow1.jsx │ │ │ ├── Signin.svg │ │ │ ├── Error.svg │ │ │ ├── watch.jsx │ │ │ ├── trash.svg │ │ │ ├── homeActive.jsx │ │ │ ├── friendsActive.jsx │ │ │ ├── notifications.jsx │ │ │ ├── ReturnIcon.jsx │ │ │ ├── newRoom.jsx │ │ │ ├── index.jsx │ │ │ ├── openEye.svg │ │ │ ├── uploadFile.svg │ │ │ ├── home.jsx │ │ │ ├── closeEye.svg │ │ │ ├── friends.jsx │ │ │ ├── liveVideo.jsx │ │ │ ├── feeling.jsx │ │ │ ├── photo.jsx │ │ │ ├── like.svg │ │ │ ├── market.jsx │ │ │ ├── search.jsx │ │ │ ├── ZIWIBook.svg │ │ │ └── 404Error.svg │ ├── layouts │ │ ├── Footer │ │ │ ├── index.css │ │ │ └── index.jsx │ │ ├── index.jsx │ │ ├── DeleteConfirm │ │ │ ├── index.css │ │ │ └── index.jsx │ │ ├── Modal │ │ │ ├── index.jsx │ │ │ ├── ModalManager.jsx │ │ │ └── index.css │ │ └── Header │ │ │ ├── NotificationMenu │ │ │ └── Notification.module.css │ │ │ ├── HeaderMenu │ │ │ ├── DisplayAccessibility.jsx │ │ │ ├── HeaderMenu.jsx │ │ │ └── HeaderMenu.module.css │ │ │ └── Header.module.css │ ├── pages │ │ ├── 404 │ │ │ ├── 404.module.css │ │ │ └── index.jsx │ │ ├── index.jsx │ │ ├── Post │ │ │ ├── style.module.css │ │ │ └── index.jsx │ │ ├── Home │ │ │ ├── home.module.css │ │ │ └── index.jsx │ │ ├── login │ │ │ └── index.jsx │ │ ├── Profile │ │ │ └── profile.module.css │ │ └── friends │ │ │ └── style.module.css │ ├── components │ │ ├── UI │ │ │ ├── Card │ │ │ │ ├── Card.module.css │ │ │ │ └── index.jsx │ │ │ ├── Loading │ │ │ │ ├── index.jsx │ │ │ │ └── loading.module.css │ │ │ ├── FormLoader │ │ │ │ ├── index.css │ │ │ │ └── index.jsx │ │ │ ├── skeleton │ │ │ │ ├── notificationSkeleton.jsx │ │ │ │ ├── skeleton.module.css │ │ │ │ └── postSkeleton.jsx │ │ │ ├── Notification │ │ │ │ ├── Notification.module.css │ │ │ │ └── index.jsx │ │ │ └── Popper │ │ │ │ └── index.jsx │ │ ├── Posts │ │ │ ├── Post │ │ │ │ ├── Likes │ │ │ │ │ ├── react.module.css │ │ │ │ │ └── index.jsx │ │ │ │ ├── PostList.jsx │ │ │ │ ├── Comments │ │ │ │ │ ├── index.jsx │ │ │ │ │ ├── CommentForm.jsx │ │ │ │ │ └── comment.module.css │ │ │ │ ├── PostHead │ │ │ │ │ ├── postHead.module.css │ │ │ │ │ └── index.jsx │ │ │ │ └── post.module.css │ │ │ ├── CreatPost │ │ │ │ ├── index.jsx │ │ │ │ └── postbody.module.css │ │ │ └── AddEditPost │ │ │ │ └── Post.module.css │ │ ├── input │ │ │ ├── CustomInput │ │ │ │ ├── index.css │ │ │ │ └── index.jsx │ │ │ └── AuthInput │ │ │ │ ├── style.module.css │ │ │ │ └── index.jsx │ │ ├── Profile │ │ │ ├── ProfileMenu │ │ │ │ ├── profileMenu.module.css │ │ │ │ └── index.jsx │ │ │ ├── ProfileInfo.jsx │ │ │ ├── ProfileCover │ │ │ │ ├── OldCovers │ │ │ │ │ ├── OldCovers.module.css │ │ │ │ │ └── OldCovers.jsx │ │ │ │ └── cover.module.css │ │ │ ├── Friendship │ │ │ │ └── Friendship.module.css │ │ │ ├── Photos.jsx │ │ │ ├── Friends.jsx │ │ │ ├── index.module.css │ │ │ └── ProfilePhoto │ │ │ │ └── ProfilePhoto.module.css │ │ ├── Search │ │ │ ├── index.css │ │ │ ├── index.jsx │ │ │ └── SearchMenu │ │ │ │ └── SearchMenu.module.css │ │ ├── CustomButton │ │ │ └── index.jsx │ │ ├── index.jsx │ │ ├── LoginForm │ │ │ └── index.css │ │ ├── friendCard │ │ │ └── index.jsx │ │ └── RegisterForm │ │ │ ├── GenderSelector.jsx │ │ │ ├── DateSelector.jsx │ │ │ └── register.module.css │ ├── utils │ │ ├── Portal.jsx │ │ ├── YupValidation.jsx │ │ └── getCroppedImg.jsx │ ├── app │ │ ├── api │ │ │ └── apiSlice.jsx │ │ ├── features │ │ │ ├── search │ │ │ │ └── searchApi.jsx │ │ │ ├── modal │ │ │ │ └── modalSlice.jsx │ │ │ ├── auth │ │ │ │ ├── prefetch.jsx │ │ │ │ └── authApi.jsx │ │ │ ├── socket │ │ │ │ └── socketSlice.jsx │ │ │ ├── notification │ │ │ │ └── notificationApi.jsx │ │ │ ├── user │ │ │ │ ├── photosApi.jsx │ │ │ │ ├── userProfileApi.jsx │ │ │ │ └── userSlice.jsx │ │ │ ├── reaction │ │ │ │ └── reactionApi.jsx │ │ │ └── post │ │ │ │ └── postApi.jsx │ │ └── store.jsx │ ├── styles │ │ └── dark.css │ ├── routes │ │ ├── ForceRedirect.jsx │ │ ├── PrivateRoute.jsx │ │ └── Router.jsx │ ├── main.jsx │ ├── hooks │ │ ├── useOnClickOutside.jsx │ │ └── useHover.jsx │ ├── App.jsx │ └── index.css ├── vite.config.js ├── .gitignore ├── index.html └── package.json ├── screenshots ├── ziwibook7.png ├── ziwibook11.png ├── ziwibook12.png └── ziwibook13.png └── LICENSE /api/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /client/netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 -------------------------------------------------------------------------------- /screenshots/ziwibook7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/screenshots/ziwibook7.png -------------------------------------------------------------------------------- /screenshots/ziwibook11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/screenshots/ziwibook11.png -------------------------------------------------------------------------------- /screenshots/ziwibook12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/screenshots/ziwibook12.png -------------------------------------------------------------------------------- /screenshots/ziwibook13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/screenshots/ziwibook13.png -------------------------------------------------------------------------------- /client/src/assets/icons/Close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/Close.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons11.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons15.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons16.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons17.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons18.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons2.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons25.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons32.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons33.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons40.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons41.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons5.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons6.png -------------------------------------------------------------------------------- /client/src/assets/icons/icons7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/icons7.png -------------------------------------------------------------------------------- /client/src/assets/icons/publicpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/icons/publicpack.png -------------------------------------------------------------------------------- /client/src/assets/sound/notification.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohamedzhioua/ZiwiBook/HEAD/client/src/assets/sound/notification.wav -------------------------------------------------------------------------------- /api/utils/catchAsync.js: -------------------------------------------------------------------------------- 1 | module.exports = (fn) => { 2 | return (req, res, next) => { 3 | fn(req, res, next).catch(next); 4 | }; 5 | }; 6 | -------------------------------------------------------------------------------- /api/utils/allowedOrigins.js: -------------------------------------------------------------------------------- 1 | const allowedOrigins = ["http://127.0.0.1:5173", "http://localhost:3000" ,"https://ziwibook.netlify.app" ]; 2 | 3 | module.exports = allowedOrigins -------------------------------------------------------------------------------- /client/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /client/src/layouts/Footer/index.css: -------------------------------------------------------------------------------- 1 | footer { 2 | position: fixed; 3 | background-color: var(--bg-third); 4 | bottom: 0; 5 | left: 0; 6 | right: 0; 7 | padding: 8px; 8 | text-align: center; 9 | } 10 | -------------------------------------------------------------------------------- /client/src/pages/index.jsx: -------------------------------------------------------------------------------- 1 | export {default as NotFound} from './404' 2 | export {default as Home} from './Home' 3 | export {default as Profile} from './Profile' 4 | export {default as Login} from './login' 5 | export {default as PostPage} from './Post' 6 | -------------------------------------------------------------------------------- /api/validator/IsEmpty.js: -------------------------------------------------------------------------------- 1 | 2 | const isEmpty = (value) => 3 | value === null || value === undefined 4 | || typeof(value) === "object" && Object.keys(value).length === 0 5 | || typeof(value) === "string" && value.trim().length === 0 6 | 7 | module.exports = isEmpty ; -------------------------------------------------------------------------------- /client/src/components/UI/Card/Card.module.css: -------------------------------------------------------------------------------- 1 | .card_container { 2 | background: var(--bg-primary); 3 | border-radius: 10px; 4 | box-shadow: 0 2px 4px var(--shadow-1); 5 | padding: 10px; 6 | /* width: 100%; */ 7 | margin: 10px 0; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /client/src/layouts/Footer/index.jsx: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | const Footer = () => { 4 | const year = new Date().getFullYear(); 5 | 6 | return ; 7 | }; 8 | 9 | export default Footer; 10 | -------------------------------------------------------------------------------- /client/src/layouts/index.jsx: -------------------------------------------------------------------------------- 1 | export {default as Modal } from './Modal' 2 | export {default as Header } from './Header' 3 | export {default as DeleteConfirm } from './DeleteConfirm' 4 | export {default as Footer } from './Footer' 5 | export {default as ModalManager } from './Modal/ModalManager' -------------------------------------------------------------------------------- /client/src/utils/Portal.jsx: -------------------------------------------------------------------------------- 1 | import { createPortal } from "react-dom"; 2 | 3 | function Portal({ children, id }) { 4 | const portalDom = id ? document.getElementById(id) : document.body; 5 | 6 | return createPortal(children, portalDom); 7 | } 8 | 9 | export default Portal; 10 | -------------------------------------------------------------------------------- /client/src/components/UI/Loading/index.jsx: -------------------------------------------------------------------------------- 1 | import style from "./loading.module.css" 2 | export const Loading = () => { 3 | return ( 4 |
5 |
6 |
7 | ); 8 | }; 9 | export default Loading 10 | -------------------------------------------------------------------------------- /client/src/components/UI/Card/index.jsx: -------------------------------------------------------------------------------- 1 | import styles from "./Card.module.css"; 2 | 3 | function Card({ className, innerRef, children }) { 4 | return ( 5 |
6 | {children} 7 |
8 | ); 9 | } 10 | 11 | export default Card; 12 | -------------------------------------------------------------------------------- /client/src/pages/Post/style.module.css: -------------------------------------------------------------------------------- 1 | .post { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | width: 100%; 7 | height: 100%; 8 | background: var(--bg-forth); 9 | padding: 70px 6px; 10 | } 11 | .container { 12 | width: 100%; 13 | max-width: 590px; 14 | } 15 | -------------------------------------------------------------------------------- /client/src/layouts/DeleteConfirm/index.css: -------------------------------------------------------------------------------- 1 | .title { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | padding-top: 10px; 6 | } 7 | .body { 8 | margin-top: 10px; 9 | } 10 | 11 | .button-box { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | padding: 2px 15px 10px; 16 | } 17 | -------------------------------------------------------------------------------- /client/src/components/UI/FormLoader/index.css: -------------------------------------------------------------------------------- 1 | .Loader-wrap { 2 | position: relative; 3 | } 4 | .loader { 5 | position: absolute; 6 | left: 50%; 7 | top: 50%; 8 | -webkit-transform: translate(-50%, -50%); 9 | transform: translate(-50%, -50%); 10 | z-index: 10; 11 | } 12 | .loader-content { 13 | filter: blur(2px); 14 | } 15 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env -------------------------------------------------------------------------------- /client/src/components/Posts/Post/Likes/react.module.css: -------------------------------------------------------------------------------- 1 | .reaction { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | gap: 5px; 6 | cursor: pointer; 7 | width: 30%; 8 | height: 100%; 9 | border-radius: 10px; 10 | } 11 | .react_span { 12 | font-size: 14px; 13 | font-weight: 600; 14 | color: var(--color-third); 15 | } -------------------------------------------------------------------------------- /api/utils/corsOptions.js: -------------------------------------------------------------------------------- 1 | const allowedOrigins = require("./allowedOrigins"); 2 | 3 | const corsOptions = { 4 | origin: function (origin, callback) { 5 | if (allowedOrigins.indexOf(origin) !== -1 || !origin) { 6 | callback(null, true); 7 | } else { 8 | callback(new Error("Not allowed by CORS")); 9 | } 10 | }, 11 | credentials: true, 12 | }; 13 | module.exports = corsOptions; 14 | -------------------------------------------------------------------------------- /client/src/assets/svg/dots.jsx: -------------------------------------------------------------------------------- 1 | function Dots({ color }) { 2 | return ( 3 | 4 | 9 | 10 | ); 11 | } 12 | 13 | export default Dots; 14 | -------------------------------------------------------------------------------- /client/src/components/Posts/Post/PostList.jsx: -------------------------------------------------------------------------------- 1 | import Post from "./Post"; 2 | import React from "react"; 3 | const PostList = ({ posts, isVisitor }) => { 4 | return ( 5 | <> 6 | {posts?.map((post) => ( 7 | 8 | 9 | 10 | ))} 11 | 12 | ); 13 | } 14 | 15 | 16 | export default PostList; 17 | -------------------------------------------------------------------------------- /client/src/app/api/apiSlice.jsx: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; 2 | 3 | export const apiSlice = createApi({ 4 | reducerPath: 'api', // optional 5 | baseQuery: fetchBaseQuery({ 6 | baseUrl: import.meta.env.VITE_API_BASE_URL , 7 | credentials: "include", 8 | }), 9 | tagTypes: ["Post", "Comment", "Reaction", "Photo","Userprofile", "Notif"], 10 | endpoints: (builder) => ({}), 11 | }); 12 | -------------------------------------------------------------------------------- /api/validator/PostValidation.js: -------------------------------------------------------------------------------- 1 | const validator = require("validator"); 2 | const isEmpty = require("./IsEmpty"); 3 | 4 | module.exports = function PostValidation(data) { 5 | let errors = {}; 6 | data.text = !isEmpty(data.text) ? data.text : ""; 7 | 8 | if (validator.isEmpty(data.text)) { 9 | errors.text = "Required post content"; 10 | } 11 | 12 | return { 13 | errors, 14 | isValid: isEmpty(errors), 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /client/src/components/Posts/Post/Comments/index.jsx: -------------------------------------------------------------------------------- 1 | import Comment from "./Comment"; 2 | import Styles from "./comment.module.css"; 3 | 4 | const Comments = ({ rootComments }) => { 5 | 6 | return rootComments?.map((rootComment) => ( 7 |
8 | 9 |
10 | )) 11 | } 12 | 13 | 14 | 15 | export default Comments; 16 | -------------------------------------------------------------------------------- /client/src/components/input/CustomInput/index.css: -------------------------------------------------------------------------------- 1 | label { 2 | font-weight: 600; 3 | font-size: 14px; 4 | } 5 | 6 | .Icon { 7 | position: absolute; 8 | right: 9px; 9 | top: 22px; 10 | font-size: 17px; 11 | cursor: "pointer"; 12 | } 13 | 14 | .commentInput { 15 | outline: none; 16 | border: none; 17 | background: transparent; 18 | width: 100%; 19 | height: 100%; 20 | font-size: 14px; 21 | padding-left: 5px; 22 | } 23 | 24 | -------------------------------------------------------------------------------- /client/src/pages/Home/home.module.css: -------------------------------------------------------------------------------- 1 | .home_container { 2 | min-height: 100vh; 3 | background-color: var(--bg-forth); 4 | 5 | } 6 | 7 | .home_middle { 8 | margin: 0 auto; 9 | max-width: 590px; 10 | margin-top: 60px; 11 | /* left: 50%; 12 | position: absolute; 13 | transform: translateX(-50%); */ 14 | 15 | } 16 | .home_posts { 17 | margin-top: 10px; 18 | display: flex; 19 | flex-direction: column; 20 | margin-bottom: 30px; 21 | } 22 | -------------------------------------------------------------------------------- /client/src/app/features/search/searchApi.jsx: -------------------------------------------------------------------------------- 1 | import { apiSlice } from "../../api/apiSlice"; 2 | 3 | export const SearchApiSlice = apiSlice.injectEndpoints({ 4 | endpoints: (builder) => ({ 5 | Search: builder.mutation({ 6 | query: (credentials) => ({ 7 | url: "/api/users/search", 8 | method: "POST", 9 | body: credentials, 10 | }), 11 | }), 12 | }), 13 | }); 14 | export const { useSearchMutation } = SearchApiSlice; 15 | -------------------------------------------------------------------------------- /client/src/assets/svg/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/models/reaction.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const ReactionSchema = new Schema( 5 | { 6 | post: { 7 | type: Schema.Types.ObjectId, 8 | ref: "post", 9 | required: true, 10 | }, 11 | owner: { 12 | type: Schema.Types.ObjectId, 13 | ref: 'user', 14 | }, 15 | }, 16 | { timestamps: true } 17 | ); 18 | 19 | module.exports = mongoose.model("reaction", ReactionSchema); 20 | -------------------------------------------------------------------------------- /api/views/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 404 Not Found 9 | 10 | 11 | 12 | 13 |

Sorry!

14 |

The resource you have requested does not exist.

15 | 16 | 17 | -------------------------------------------------------------------------------- /api/public/css/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap'); 2 | 3 | * { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | html { 10 | font-family: 'Share Tech Mono', monospace; 11 | font-size: 2.5rem; 12 | } 13 | 14 | body { 15 | min-height: 100vh; 16 | background-color: #000; 17 | color: whitesmoke; 18 | display: grid; 19 | place-content: center; 20 | padding: 1rem; 21 | } -------------------------------------------------------------------------------- /api/middlewares/postsLimiter.js: -------------------------------------------------------------------------------- 1 | const rateLimit = require('express-rate-limit'); 2 | 3 | // limiter for posts 4 | const limiter = rateLimit({ 5 | windowMs: 60 * 60 * 1000 * 24, 6 | max: 15, 7 | 8 | handler: (request, response, next, options) => 9 | response.status(options.statusCode).json({ 10 | message: 11 | 'You can only post 15 posts per day and you have reached the limit. You can post again tomorrow, have fun ', 12 | }), 13 | }); 14 | module.exports = limiter; 15 | -------------------------------------------------------------------------------- /client/src/assets/svg/arrowDow1.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function ArrowDown1({ color }) { 4 | return ( 5 | 6 | 11 | 12 | ); 13 | } 14 | 15 | export default ArrowDown1; 16 | -------------------------------------------------------------------------------- /client/src/pages/404/404.module.css: -------------------------------------------------------------------------------- 1 | .error_container{ 2 | display: flex; 3 | flex-direction: column; 4 | position:absolute; 5 | left: 50%; 6 | top: 50%; 7 | transform: translate(-50%, -50%); 8 | 9 | } 10 | .error_body{ 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | } 15 | 16 | .link{ 17 | color: var(--orange-color) ; 18 | text-decoration: none; 19 | } 20 | .link:hover{ 21 | color: var(--orange-color) ; 22 | text-decoration: underline; 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /api/config/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | require("dotenv").config(); 3 | 4 | const DB = process.env.DATABASE.replace( 5 | '', 6 | process.env.DATABASE_PASSWORD 7 | ); 8 | 9 | mongoose.set('strictQuery', false); 10 | mongoose 11 | .connect(DB, { 12 | useUnifiedTopology: true, 13 | useNewUrlParser: true, 14 | }) 15 | .then(() => console.log("mongoose connected")) 16 | .catch((err) => console.log(err)); 17 | 18 | const db = mongoose.connection; 19 | 20 | module.exports = db; -------------------------------------------------------------------------------- /client/src/styles/dark.css: -------------------------------------------------------------------------------- 1 | .dark { 2 | --bg-primary: #242526; 3 | --bg-secondary: #18191a; 4 | --bg-third: #3a3b3c; 5 | --bg-forth: #3a3b3c; 6 | --bg-fifth: #888888; 7 | --color-secondary: #e4e6eb; 8 | --color-third: #b0b3b8; 9 | --divider: #4e4e4e; 10 | --light-blue-color: rgba(45, 136, 255, 0.1); 11 | } 12 | .dark .blur { 13 | background: rgba(1, 1, 1, 0.53); 14 | } 15 | .dark .small_circle i { 16 | filter: invert(100%); 17 | } 18 | 19 | .dark .create_icon { 20 | filter: invert(100%); 21 | } 22 | -------------------------------------------------------------------------------- /client/src/assets/svg/Signin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/routes/ForceRedirect.jsx: -------------------------------------------------------------------------------- 1 | import { Navigate, Outlet } from "react-router-dom"; 2 | import { useSelector } from "react-redux"; 3 | import { Loading } from "../components"; 4 | import React from "react"; 5 | 6 | const ForceRedirect = () => { 7 | const { token } = useSelector((state) => state.user); 8 | 9 | return token ? ( 10 | 11 | ) : ( 12 | }> 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default ForceRedirect; 19 | -------------------------------------------------------------------------------- /client/src/pages/login/index.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { LoginForm, RegisterForm } from "../../components"; 3 | 4 | function Login() { 5 | const [showRegister, setShowRegister] = useState(false); 6 | 7 | return ( 8 |
9 | 10 | {showRegister && ( 11 | 15 | )} 16 |
17 | ); 18 | } 19 | 20 | export default Login; 21 | -------------------------------------------------------------------------------- /client/src/assets/svg/Error.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import "./styles/dark.css"; 5 | import App from "./App"; 6 | import { store } from "./app/store"; 7 | import { Provider } from "react-redux"; 8 | import "react-toastify/dist/ReactToastify.css"; 9 | import "react-loading-skeleton/dist/skeleton.css"; 10 | 11 | 12 | ReactDOM.createRoot(document.getElementById('root')).render( 13 | 14 | 15 | 16 | 17 | , 18 | ) 19 | -------------------------------------------------------------------------------- /client/src/app/store.jsx: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import { apiSlice } from "./api/apiSlice"; 3 | import userReducer from "./features/user/userSlice"; 4 | import modalReducer from "./features/modal/modalSlice"; 5 | import { useFetchCommentsQuery } from "./features/comment/commentApi"; 6 | export const store = configureStore({ 7 | reducer: { 8 | user: userReducer, 9 | modal: modalReducer, 10 | [apiSlice.reducerPath]:apiSlice.reducer, 11 | }, 12 | middleware:getDefaultMiddleware=> 13 | getDefaultMiddleware().concat(apiSlice.middleware) 14 | }); 15 | -------------------------------------------------------------------------------- /api/models/friend.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const FriendSchema = new Schema( 5 | { 6 | sender: { type: Schema.Types.ObjectId, ref: "user", required: true }, 7 | recipient: { 8 | type: Schema.Types.ObjectId, 9 | 10 | ref: "user", 11 | required: true, 12 | }, 13 | requestStatus: { 14 | type: String, 15 | enum: ["pending", "accepted", "cancelled"], 16 | required: true, 17 | }, 18 | }, 19 | { timestamps: true } 20 | ); 21 | 22 | module.exports = mongoose.model("friend", FriendSchema); 23 | -------------------------------------------------------------------------------- /client/src/assets/svg/watch.jsx: -------------------------------------------------------------------------------- 1 | function Watch({ color }) { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | 9 | export default Watch; 10 | -------------------------------------------------------------------------------- /client/src/assets/svg/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api/routes/notifications.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const checkAuth = require("../middlewares/checkAuth"); 4 | const NotifController = require("../controllers/notification"); 5 | 6 | // PROTECT ALL ROUTES AFTER THIS MIDDLEWARE 7 | router.use(checkAuth); 8 | 9 | // GET request to fetch all Notif. 10 | router.get("/notifies", NotifController.getNotifcations); 11 | 12 | // PATCH request to change the notif seen to true . 13 | router.patch("/isNotifSeen/:id", NotifController.isNotifSeen); 14 | 15 | // DELETE a specific Notif. 16 | router.delete("/delete/:id", NotifController.deleteNotif); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /client/src/assets/svg/homeActive.jsx: -------------------------------------------------------------------------------- 1 | function HomeActive({ className }) { 2 | return ( 3 | 10 | 11 | 12 | ); 13 | } 14 | 15 | export default HomeActive; 16 | -------------------------------------------------------------------------------- /client/src/components/Profile/ProfileMenu/profileMenu.module.css: -------------------------------------------------------------------------------- 1 | /* profile menu */ 2 | 3 | .profile_menu_container { 4 | display: flex; 5 | justify-content: space-between; 6 | align-items: center; 7 | } 8 | .profile_menu { 9 | display: flex; 10 | align-items: center; 11 | } 12 | .profile_menu a { 13 | padding: 10px; 14 | font-family: "Roboto", sans-serif; 15 | font-size: 300; 16 | } 17 | .profile_menu .active { 18 | color: var(--color-primary); 19 | border-bottom: 2px solid var(--color-primary); 20 | margin-bottom: -3px; 21 | } 22 | .link { 23 | text-decoration: none; 24 | } 25 | 26 | :global(.dark) .profile_menu_container svg { 27 | fill: var(--bg-primary); 28 | } 29 | -------------------------------------------------------------------------------- /client/src/components/UI/Loading/loading.module.css: -------------------------------------------------------------------------------- 1 | .loadingSpinnerContainer { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | bottom: 0; 6 | left: 0; 7 | background-color: var(--shadow-5); 8 | z-index: 5000; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | } 13 | .loadingSpinner { 14 | width: 64px; 15 | height: 64px; 16 | border: 8px solid; 17 | border-color: var(--color-secondary) transparent var(--color-third) transparent; 18 | border-radius: 50%; 19 | animation: spin 1.2s linear infinite; 20 | } 21 | 22 | @keyframes spin { 23 | 0% { 24 | transform: rotate(0deg); 25 | } 26 | 100% { 27 | transform: rotate(360deg); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/src/components/Search/index.css: -------------------------------------------------------------------------------- 1 | .searchInputs { 2 | display: flex; 3 | align-items: center; 4 | gap: 6px; 5 | background:var(--bg-secondary); 6 | padding: 10px 20px 10px 13px; 7 | border-radius: 50px; 8 | cursor: text; 9 | min-width: 233px; 10 | } 11 | 12 | .search-input { 13 | outline: none; 14 | border: none; 15 | background: transparent; 16 | font-size: 15px; 17 | font-family: inherit; 18 | padding-left: 2px; 19 | } 20 | 21 | @media (max-width: 1100px) { 22 | .searchInputs { 23 | width: 40px; 24 | height: 40px; 25 | padding: 0; 26 | justify-content: center; 27 | min-width: auto; 28 | } 29 | 30 | .search-input { 31 | display: none; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /client/src/pages/404/index.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import classes from "./404.module.css"; 3 | import ErrorSVG from "../../assets/svg/404Error.svg"; 4 | // 404 page 5 | const NotFound = () => { 6 | return ( 7 |
8 | error_svg 9 |
10 |

11 | Looks like this page does not exist or Something Went Wrong. 12 |

13 |

14 | Go Back to the 15 | 16 | Home Page 17 | 18 |

19 |
20 |
21 | ); 22 | }; 23 | 24 | export default NotFound; 25 | -------------------------------------------------------------------------------- /client/src/components/CustomButton/index.jsx: -------------------------------------------------------------------------------- 1 | import "./index.css"; 2 | 3 | const CustomButton = ({ 4 | onSubmit, 5 | type, 6 | className, 7 | value, 8 | disabled, 9 | onClick, 10 | Icon, 11 | children, 12 | 13 | }) => { 14 | return !Icon ? ( 15 | 23 | ) : ( 24 | 33 | ); 34 | }; 35 | 36 | export default CustomButton; 37 | -------------------------------------------------------------------------------- /api/models/comment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const CommentSchema = new Schema( 5 | { 6 | post: { 7 | type: Schema.Types.ObjectId, 8 | ref: "post", 9 | required: true, 10 | }, 11 | parentId : { 12 | type: String, 13 | default: null 14 | }, 15 | owner: { 16 | type: Schema.Types.ObjectId, 17 | ref: "user", 18 | required: true, 19 | }, 20 | text: { 21 | type: String, 22 | required: true, 23 | }, 24 | likes: { 25 | type: [String], 26 | default: [], 27 | }, 28 | }, 29 | { timestamps: true } 30 | ); 31 | 32 | 33 | module.exports = mongoose.model("comment", CommentSchema); 34 | -------------------------------------------------------------------------------- /client/src/assets/svg/friendsActive.jsx: -------------------------------------------------------------------------------- 1 | function FriendsActive() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | 9 | export default FriendsActive; 10 | -------------------------------------------------------------------------------- /client/src/assets/svg/notifications.jsx: -------------------------------------------------------------------------------- 1 | function Notifications() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | 9 | export default Notifications; 10 | -------------------------------------------------------------------------------- /client/src/components/input/AuthInput/style.module.css: -------------------------------------------------------------------------------- 1 | .authInput_container { 2 | width: 100%; 3 | position: relative; 4 | } 5 | .authInput_container input { 6 | background: var(--bg-secondary); 7 | border: 1px solid var(--bg-third); 8 | border-radius: 10px; 9 | color: var(--color-secondary); 10 | font-size: 17px; 11 | height: 40px; 12 | margin-bottom: 10px; 13 | outline: none; 14 | padding-left: 10px; 15 | width: 100%; 16 | } 17 | .ERROR { 18 | border-color: var(--color-error) !important; 19 | } 20 | .ER { 21 | position: absolute; 22 | right: 40px; 23 | top: 15px; 24 | } 25 | 26 | .showHidePassword { 27 | position: absolute; 28 | right: 9px; 29 | top: 8px; 30 | font-size: 17px; 31 | cursor: "pointer"; 32 | } 33 | -------------------------------------------------------------------------------- /client/src/hooks/useOnClickOutside.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | 3 | function useOnClickOutside(ref, state, handler) { 4 | useEffect(() => { 5 | const listener = (event) => { 6 | if ( 7 | !ref.current || 8 | ref.current.contains(event.target) || 9 | state === false 10 | ) { 11 | return; 12 | } 13 | handler(event); 14 | }; 15 | document.addEventListener("mousedown", listener); 16 | document.addEventListener("touchstart", listener); 17 | return () => { 18 | document.removeEventListener("mousedown", listener); 19 | document.removeEventListener("touchstart", listener); 20 | }; 21 | }, [ref, handler, state]); 22 | } 23 | export default useOnClickOutside; 24 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | ZIWIBook 15 | 16 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /client/src/assets/svg/ReturnIcon.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function ReturnIcon({ color }) { 4 | return ( 5 | 6 | 7 | 11 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default ReturnIcon; 21 | -------------------------------------------------------------------------------- /client/src/assets/svg/newRoom.jsx: -------------------------------------------------------------------------------- 1 | function NewRoom({ color }) { 2 | return ( 3 | 4 | 9 | 10 | ); 11 | } 12 | 13 | export default NewRoom; 14 | -------------------------------------------------------------------------------- /client/src/app/features/modal/modalSlice.jsx: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | isOpen: false, 5 | componentName: null, 6 | childrenProps: {}, 7 | }; 8 | 9 | export const modalSlice = createSlice({ 10 | name: "modal", 11 | initialState, 12 | reducers: { 13 | openModal: (state, action) => { 14 | state.isOpen = true; 15 | state.componentName = action.payload.name; 16 | state.childrenProps = action.payload.childrenProps; 17 | }, 18 | closeModal: (state, action) => { 19 | state.isOpen = false; 20 | state.componentName = null; 21 | state.childrenProps = {}; 22 | }, 23 | }, 24 | }); 25 | 26 | export const { openModal, closeModal } = modalSlice.actions; 27 | export default modalSlice.reducer; 28 | -------------------------------------------------------------------------------- /client/src/assets/svg/index.jsx: -------------------------------------------------------------------------------- 1 | export {default as HomeActive } from './homeActive' 2 | export {default as Market } from './market' 3 | export {default as Notifications } from './notifications' 4 | export {default as Home } from './home' 5 | export {default as Watch } from './watch' 6 | export {default as Friends } from './friends' 7 | export {default as FriendsActive } from './friendsActive' 8 | export {default as Dots } from './dots' 9 | export {default as Feeling } from './feeling' 10 | export {default as LiveVideo } from './liveVideo' 11 | export {default as Photo } from './photo' 12 | export {default as SearchIcon } from './search' 13 | export {default as ReturnIcon } from './ReturnIcon' 14 | export {default as NewRoom } from './newRoom' 15 | export {default as ArrowDown1} from './arrowDow1' 16 | 17 | -------------------------------------------------------------------------------- /client/src/components/Profile/ProfileMenu/index.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import classe from "./profileMenu.module.css"; 3 | 4 | function ProfileMenu() { 5 | return ( 6 |
7 |
8 | 9 | Posts 10 | 11 | 12 | About 13 | 14 | 15 | Friends 16 | 17 | 18 | Photos 19 | 20 |
21 |
22 | ); 23 | } 24 | 25 | export default ProfileMenu; 26 | -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "nodemon server.js" 8 | }, 9 | "author": "Zhioua Mohamed", 10 | "license": "ISC", 11 | "dependencies": { 12 | "bcryptjs": "^2.4.3", 13 | "cloudinary": "^1.32.0", 14 | "cookie-parser": "^1.4.6", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.0.3", 17 | "express": "^4.18.2", 18 | "express-rate-limit": "^6.7.0", 19 | "jsonwebtoken": "^8.5.1", 20 | "mongoose": "^6.7.3", 21 | "multer": "^1.4.5-lts.1", 22 | "nodemon": "^2.0.20", 23 | "sharp": "^0.31.3", 24 | "socket.io": "^4.6.1", 25 | "unique-username-generator": "^1.1.3", 26 | "validator": "^13.7.0", 27 | "xss-clean": "^0.1.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/src/assets/svg/openEye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/app/features/auth/prefetch.jsx: -------------------------------------------------------------------------------- 1 | import { store } from "../../store" 2 | import { useEffect } from 'react'; 3 | import { Outlet } from 'react-router-dom'; 4 | import { extendedApiSlice } from "../post/postApi"; 5 | import { reactionApiSlice } from "../reaction/reactionApi"; 6 | import { CommentApiSlice } from "../comment/commentApi"; 7 | 8 | 9 | const Prefetch = () => { 10 | 11 | useEffect(() => { 12 | store.dispatch(extendedApiSlice.util.prefetch('fetchPosts', 'fetchPosts', { force: true })); 13 | store.dispatch(reactionApiSlice.util.prefetch('fetchReactions', 'fetchReactions', { force: true })); 14 | store.dispatch(CommentApiSlice.util.prefetch('fetchComments', 'fetchComments', { force: true })); 15 | }, []) 16 | 17 | return 18 | } 19 | export default Prefetch 20 | -------------------------------------------------------------------------------- /api/validator/SigninValidation.js: -------------------------------------------------------------------------------- 1 | const validator = require("validator"); 2 | const isEmpty = require("./IsEmpty"); 3 | 4 | module.exports = function SigninValidation(data) { 5 | let errors = {}; 6 | 7 | // Convert empty fields to an empty string so we can use validator 8 | data.email = !isEmpty(data.email) ? data.email : ""; 9 | data.password = !isEmpty(data.password) ? data.password : ""; 10 | 11 | // Email checks 12 | if (validator.isEmpty(data.email)) { 13 | errors.email = "Email field is required"; 14 | } else if (!validator.isEmail(data.email)) { 15 | errors.email = "Format Email required"; 16 | } 17 | 18 | // Password checks 19 | if (validator.isEmpty(data.password)) { 20 | errors.password = "Password field is required"; 21 | } 22 | return { 23 | errors, 24 | isValid: isEmpty(errors), 25 | }; 26 | }; -------------------------------------------------------------------------------- /client/src/layouts/Modal/index.jsx: -------------------------------------------------------------------------------- 1 | import Close from "../../assets/icons/Close.png"; 2 | import Portal from "../../utils/Portal"; 3 | import "./index.css"; 4 | 5 | const Modal = ({ isOpen, children, closeModalHandler }) => { 6 | return ( 7 | 8 |
11 |
14 |
15 | X 21 |
22 |
{children}
23 |
24 |
25 | ); 26 | }; 27 | 28 | export default Modal; 29 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | import Router from "./routes/Router"; 2 | import { BrowserRouter } from "react-router-dom"; 3 | import { Footer, ModalManager } from "./layouts/index"; 4 | import "./index.css"; 5 | import { ToastContainer } from "react-toastify"; 6 | import Portal from "./utils/Portal"; 7 | import { useSelector } from "react-redux"; 8 | import { useEffect } from "react"; 9 | function App() { 10 | const theme = useSelector((state) => state.user.theme); 11 | useEffect(() => { 12 | if (theme === "dark") { 13 | document.body.classList.add("dark"); 14 | } else { 15 | document.body.classList.remove("dark"); 16 | } 17 | }, [theme]); 18 | return ( 19 | 20 | 21 | 22 |