├── .firebase └── hosting.ZGlzdA.cache ├── .firebaserc ├── .gitignore ├── Readme.md ├── firebase.json ├── index.html ├── package-lock.json ├── package.json ├── public ├── 404.html └── vite.svg ├── src ├── Pages │ ├── Connections.jsx │ ├── Home.jsx │ ├── Login.jsx │ ├── Profile.jsx │ └── Register.jsx ├── Routes │ └── index.jsx ├── Sass │ ├── ConnectionsComponent.scss │ ├── HomeComponent.scss │ └── LoginComponent.scss ├── api │ ├── AuthAPI.jsx │ ├── FirestoreAPI.jsx │ └── ImageUpload.jsx ├── assets │ ├── linkedinLogo.png │ ├── react.svg │ └── user.png ├── components │ ├── ConnectionsComponent.jsx │ ├── HomeComponent.jsx │ ├── LoginComponent.jsx │ ├── ProfileComponent.jsx │ ├── RegisterComponent.jsx │ └── common │ │ ├── Button │ │ ├── index.jsx │ │ └── index.scss │ │ ├── ConnectedUsers │ │ └── index.jsx │ │ ├── FileUploadModal │ │ ├── index.jsx │ │ └── index.scss │ │ ├── LikeButton │ │ ├── index.jsx │ │ └── index.scss │ │ ├── Loader │ │ ├── index.jsx │ │ └── index.scss │ │ ├── Modal │ │ ├── index.jsx │ │ └── index.scss │ │ ├── PostUpdate │ │ ├── index.jsx │ │ └── index.scss │ │ ├── PostsCard │ │ ├── index.jsx │ │ └── index.scss │ │ ├── ProfileCard │ │ ├── index.jsx │ │ └── index.scss │ │ ├── ProfileEdit │ │ ├── index.jsx │ │ └── index.scss │ │ ├── ProfilePopup │ │ ├── index.jsx │ │ └── index.scss │ │ ├── SearchUsers │ │ ├── index.jsx │ │ └── index.scss │ │ └── Topbar │ │ ├── index.jsx │ │ └── index.scss ├── firebaseConfig.js ├── helpers │ ├── getUniqueId.jsx │ ├── useMoment.jsx │ └── useNavigate.jsx ├── index.scss ├── layouts │ ├── ConnectionLayout.jsx │ ├── HomeLayout.jsx │ └── ProfileLayout.jsx └── main.jsx └── vite.config.js /.firebase/hosting.ZGlzdA.cache: -------------------------------------------------------------------------------- 1 | 404.html,1677956389330,daa499dd96d8229e73235345702ba32f0793f0c8e5c0d30e40e37a5872be57aa 2 | vite.svg,1675669638367,59ec4b6085a0cb1bf712a5e48dd5f35b08e34830d49c2026c18241be04e05d5a 3 | index.html,1677959444529,6cc23cb4c31261f47e3da8ad7c836f3c4225f527ee46ba89167c073010c3e605 4 | assets/index-738cf444.css,1677959444529,caae3544d9ac5d202a3867858e84eb4b66a8311ec436c27d675633a8f50d4d20 5 | assets/linkedinLogo-412a7a29.png,1677959444529,be9cdcfd85ba97a250fdf3772952753ddef2508aa5c9071636e2fb4bd2becd90 6 | assets/index-28c6a808.js,1677959444530,3194428b72113e1064cd1759e4ffdc201ab5b0ba5b3d3d5146c0686a85c814ce 7 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "linkedin-clone-a077e" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.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 | firebaseConfig.js* 10 | 11 | node_modules 12 | dist 13 | 14 | dist-ssr 15 | *.local 16 | 17 | # Editor directories and files 18 | .vscode/* 19 | !.vscode/extensions.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | Link: https://linkedin-clone-a077e.web.app 4 | 5 | 1. Authentication 6 | 2. Start a Post 7 | 3. Update a Post 8 | 4. Delete a Post 9 | 5. Add Connections 10 | 6. Like and Comment on a Post 11 | 7. Update Profile 12 | 8. See Other Profiles 13 | 9. Add a Profile Picture 14 | 10. Add Post Images 15 | 11. Search For Other Users 16 | 17 | If you want to learn how to build this, follow my tutorial here: 18 | 19 | Linkedin Clone - React and Firebase Full Tutorial. 20 | 21 | Check the playlist for the videos: https://lnkd.in/d6JvCm-t -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "dist", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Linkedin Clone! 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linkedin-clone", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "antd": "^5.2.1", 13 | "firebase": "^9.17.1", 14 | "firebase-tools": "^11.24.0", 15 | "localforage": "^1.10.0", 16 | "match-sorter": "^6.3.1", 17 | "moment": "^2.29.4", 18 | "quill-emoji": "^0.2.0", 19 | "react": "^18.2.0", 20 | "react-dom": "^18.2.0", 21 | "react-google-button": "^0.7.2", 22 | "react-icons": "^4.7.1", 23 | "react-quill": "^2.0.0", 24 | "react-router-dom": "^6.8.1", 25 | "react-toastify": "^9.1.1", 26 | "react-uuid": "^2.0.0", 27 | "sass": "^1.58.2", 28 | "sort-by": "^1.2.0" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "^18.0.27", 32 | "@types/react-dom": "^18.0.10", 33 | "@vitejs/plugin-react": "^3.1.0", 34 | "vite": "^4.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Page Not Found 7 | 8 | 23 | 24 | 25 |
26 |

404

27 |

Page Not Found

28 |

The specified file was not found on this website. Please check the URL for mistakes and try again.

29 |

Why am I seeing this?

30 |

This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html file in your project's configured public directory.

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Pages/Connections.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import ConnectionsComponent from "../components/ConnectionsComponent"; 3 | import { onAuthStateChanged } from "firebase/auth"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { auth } from "../firebaseConfig"; 6 | import Loader from "../components/common/Loader"; 7 | 8 | export default function Connections({ currentUser }) { 9 | const [loading, setLoading] = useState(true); 10 | let navigate = useNavigate(); 11 | useEffect(() => { 12 | onAuthStateChanged(auth, (res) => { 13 | if (!res?.accessToken) { 14 | navigate("/"); 15 | } else { 16 | setLoading(false); 17 | } 18 | }); 19 | }, []); 20 | return loading ? ( 21 | 22 | ) : ( 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /src/Pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import HomeComponent from "../components/HomeComponent"; 3 | import { onAuthStateChanged } from "firebase/auth"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { auth } from "../firebaseConfig"; 6 | import Loader from "../components/common/Loader"; 7 | 8 | export default function Home({ currentUser }) { 9 | const [loading, setLoading] = useState(true); 10 | let navigate = useNavigate(); 11 | useEffect(() => { 12 | onAuthStateChanged(auth, (res) => { 13 | if (!res?.accessToken) { 14 | navigate("/"); 15 | } else { 16 | setLoading(false); 17 | } 18 | }); 19 | }, []); 20 | return loading ? : ; 21 | } 22 | -------------------------------------------------------------------------------- /src/Pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import LoginComponent from "../components/LoginComponent"; 3 | import { onAuthStateChanged } from "firebase/auth"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { auth } from "../firebaseConfig"; 6 | import Loader from "../components/common/Loader"; 7 | 8 | export default function Login() { 9 | const [loading, setLoading] = useState(true); 10 | let navigate = useNavigate(); 11 | useEffect(() => { 12 | onAuthStateChanged(auth, (res) => { 13 | if (res?.accessToken) { 14 | navigate("/home"); 15 | } else { 16 | setLoading(false); 17 | } 18 | }); 19 | }, []); 20 | return loading ? : ; 21 | } 22 | -------------------------------------------------------------------------------- /src/Pages/Profile.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import ProfileComponent from "../components/ProfileComponent"; 3 | import { onAuthStateChanged } from "firebase/auth"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { auth } from "../firebaseConfig"; 6 | import Loader from "../components/common/Loader"; 7 | 8 | export default function Profile({ currentUser }) { 9 | const [loading, setLoading] = useState(true); 10 | let navigate = useNavigate(); 11 | useEffect(() => { 12 | onAuthStateChanged(auth, (res) => { 13 | if (!res?.accessToken) { 14 | navigate("/"); 15 | } else { 16 | setLoading(false); 17 | } 18 | }); 19 | }, []); 20 | return loading ? : ; 21 | } 22 | -------------------------------------------------------------------------------- /src/Pages/Register.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import RegisterComponent from "../components/RegisterComponent"; 3 | 4 | export default function Register() { 5 | return ; 6 | } 7 | -------------------------------------------------------------------------------- /src/Routes/index.jsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter } from "react-router-dom"; 2 | import Login from "../Pages/Login"; 3 | import Register from "../Pages/Register"; 4 | import HomeLayout from "../layouts/HomeLayout"; 5 | import ProfileLayout from "../layouts/ProfileLayout"; 6 | import ConnectionLayout from "../layouts/ConnectionLayout"; 7 | 8 | export const router = createBrowserRouter([ 9 | { 10 | path: "/", 11 | element: , 12 | }, 13 | { 14 | path: "/register", 15 | element: , 16 | }, 17 | { 18 | path: "/home", 19 | element: , 20 | }, 21 | { 22 | path: "/profile", 23 | element: , 24 | }, 25 | { 26 | path: "/connections", 27 | element: , 28 | }, 29 | ]); 30 | -------------------------------------------------------------------------------- /src/Sass/ConnectionsComponent.scss: -------------------------------------------------------------------------------- 1 | .connections-main { 2 | display: grid; 3 | grid-template-columns: auto auto; 4 | gap: 10px; 5 | justify-content: center; 6 | align-items: center; 7 | padding: 10px; 8 | text-align: center; 9 | margin: 30px; 10 | border: 1px solid #bbbbbb; 11 | background-color: white; 12 | border-radius: 10px; 13 | .grid-child { 14 | border: 1px solid #bbbbbb; 15 | width: 250px; 16 | height: 330px; 17 | margin: 10px; 18 | padding: 10px; 19 | display: flex; 20 | 21 | align-items: center; 22 | flex-direction: column; 23 | border-radius: 10px; 24 | position: relative; 25 | cursor: pointer; 26 | 27 | img { 28 | width: 150px; 29 | height: 150px; 30 | border-radius: 50%; 31 | object-fit: cover; 32 | margin-top: 20px; 33 | } 34 | 35 | .name { 36 | font-family: system-ui; 37 | font-size: 16px; 38 | font-weight: 600; 39 | } 40 | 41 | .headline { 42 | margin-top: -15px; 43 | font-family: system-ui; 44 | font-size: 15px; 45 | font-weight: 400; 46 | } 47 | 48 | button { 49 | width: 90%; 50 | height: 40px; 51 | position: absolute; 52 | 53 | bottom: 10px; 54 | cursor: pointer; 55 | background-color: white; 56 | color: #004284; 57 | border: 1px solid #004284; 58 | font-size: 16px; 59 | font-family: system-ui; 60 | border-radius: 30px; 61 | font-weight: 600; 62 | display: flex; 63 | justify-content: center; 64 | align-items: center; 65 | gap: 10px; 66 | } 67 | 68 | button:hover { 69 | border: 2px solid #004284; 70 | background-color: #bbdefb; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Sass/HomeComponent.scss: -------------------------------------------------------------------------------- 1 | .home-component { 2 | } 3 | -------------------------------------------------------------------------------- /src/Sass/LoginComponent.scss: -------------------------------------------------------------------------------- 1 | $linkedinBlue1: #0072b1; 2 | $linkedinBlue2: #0073b1; 3 | $linkedinBlue3: #004c75; 4 | 5 | body { 6 | margin: 0; 7 | padding: 0; 8 | } 9 | 10 | .login-wrapper { 11 | background-color: white !important; 12 | height: 100vh; 13 | .login-wrapper-inner { 14 | display: flex; 15 | flex-direction: column; 16 | justify-content: center; 17 | align-items: center; 18 | } 19 | .heading { 20 | font-family: system-ui; 21 | font-weight: 500; 22 | color: rgba(0, 0, 0, 0.9); 23 | } 24 | 25 | .sub-heading { 26 | margin-top: -20px; 27 | font-family: system-ui; 28 | } 29 | } 30 | 31 | .login-btn { 32 | width: 300px; 33 | height: 50px; 34 | cursor: pointer; 35 | background-color: $linkedinBlue2; 36 | border-radius: 30px; 37 | outline: none; 38 | border: none; 39 | font-family: system-ui; 40 | font-weight: 600; 41 | color: #ffffff; 42 | font-size: 18px; 43 | margin-top: 20px; 44 | } 45 | 46 | .login-btn:hover { 47 | background-color: $linkedinBlue3; 48 | } 49 | 50 | .auth-inputs { 51 | display: flex; 52 | flex-direction: column; 53 | gap: 10px; 54 | width: 300px; 55 | } 56 | 57 | .linkedinLogo { 58 | width: 80px; 59 | } 60 | 61 | .hr-text { 62 | line-height: 1em; 63 | position: relative; 64 | outline: 0; 65 | border: 0; 66 | color: black; 67 | text-align: center; 68 | height: 1.5em; 69 | opacity: 0.5; 70 | &:before { 71 | content: ""; 72 | // use the linear-gradient for the fading effect 73 | // use a solid background color for a solid bar 74 | background: linear-gradient(to right, transparent, #818078, transparent); 75 | position: absolute; 76 | left: 0; 77 | top: 50%; 78 | width: 100%; 79 | height: 1px; 80 | } 81 | &:after { 82 | content: attr(data-content); 83 | position: relative; 84 | display: inline-block; 85 | color: black; 86 | 87 | padding: 0 0.5em; 88 | line-height: 1.5em; 89 | // this is really the only tricky part, you need to specify the background color of the container element... 90 | color: #818078; 91 | background-color: #fcfcfa; 92 | } 93 | } 94 | 95 | .google-btn-container { 96 | display: flex; 97 | flex-direction: column; 98 | justify-content: center; 99 | align-items: center; 100 | 101 | .google-btn { 102 | width: 300px !important; 103 | } 104 | 105 | .go-to-signup { 106 | font-size: 18px; 107 | } 108 | 109 | .join-now { 110 | color: $linkedinBlue1; 111 | font-family: system-ui; 112 | cursor: pointer; 113 | font-size: 18px; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/api/AuthAPI.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | signInWithEmailAndPassword, 3 | createUserWithEmailAndPassword, 4 | GoogleAuthProvider, 5 | signInWithPopup, 6 | signOut, 7 | } from "firebase/auth"; 8 | import { auth } from "../firebaseConfig"; 9 | 10 | export const LoginAPI = (email, password) => { 11 | try { 12 | let response = signInWithEmailAndPassword(auth, email, password); 13 | return response; 14 | } catch (err) { 15 | return err; 16 | } 17 | }; 18 | 19 | export const RegisterAPI = (email, password) => { 20 | try { 21 | let response = createUserWithEmailAndPassword(auth, email, password); 22 | return response; 23 | } catch (err) { 24 | return err; 25 | } 26 | }; 27 | 28 | export const GoogleSignInAPI = () => { 29 | try { 30 | let googleProvider = new GoogleAuthProvider(); 31 | let res = signInWithPopup(auth, googleProvider); 32 | return res; 33 | } catch (err) { 34 | return err; 35 | } 36 | }; 37 | 38 | export const onLogout = () => { 39 | try { 40 | signOut(auth); 41 | } catch (err) { 42 | return err; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/api/FirestoreAPI.jsx: -------------------------------------------------------------------------------- 1 | import { firestore } from "../firebaseConfig"; 2 | import { 3 | addDoc, 4 | collection, 5 | onSnapshot, 6 | doc, 7 | updateDoc, 8 | query, 9 | where, 10 | setDoc, 11 | deleteDoc, 12 | orderBy, 13 | serverTimestamp, 14 | } from "firebase/firestore"; 15 | import { toast } from "react-toastify"; 16 | 17 | let postsRef = collection(firestore, "posts"); 18 | let userRef = collection(firestore, "users"); 19 | let likeRef = collection(firestore, "likes"); 20 | let commentsRef = collection(firestore, "comments"); 21 | let connectionRef = collection(firestore, "connections"); 22 | 23 | export const postStatus = (object) => { 24 | addDoc(postsRef, object) 25 | .then(() => { 26 | toast.success("Post has been added successfully"); 27 | }) 28 | .catch((err) => { 29 | console.log(err); 30 | }); 31 | }; 32 | 33 | export const getStatus = (setAllStatus) => { 34 | const q = query(postsRef, orderBy("timeStamp")); 35 | onSnapshot(q, (response) => { 36 | setAllStatus( 37 | response.docs.map((docs) => { 38 | return { ...docs.data(), id: docs.id }; 39 | }) 40 | ); 41 | }); 42 | }; 43 | 44 | export const getAllUsers = (setAllUsers) => { 45 | onSnapshot(userRef, (response) => { 46 | setAllUsers( 47 | response.docs.map((docs) => { 48 | return { ...docs.data(), id: docs.id }; 49 | }) 50 | ); 51 | }); 52 | }; 53 | 54 | export const getSingleStatus = (setAllStatus, id) => { 55 | const singlePostQuery = query(postsRef, where("userID", "==", id)); 56 | onSnapshot(singlePostQuery, (response) => { 57 | setAllStatus( 58 | response.docs.map((docs) => { 59 | return { ...docs.data(), id: docs.id }; 60 | }) 61 | ); 62 | }); 63 | }; 64 | 65 | export const getSingleUser = (setCurrentUser, email) => { 66 | const singleUserQuery = query(userRef, where("email", "==", email)); 67 | onSnapshot(singleUserQuery, (response) => { 68 | setCurrentUser( 69 | response.docs.map((docs) => { 70 | return { ...docs.data(), id: docs.id }; 71 | })[0] 72 | ); 73 | }); 74 | }; 75 | 76 | export const postUserData = (object) => { 77 | addDoc(userRef, object) 78 | .then(() => {}) 79 | .catch((err) => { 80 | console.log(err); 81 | }); 82 | }; 83 | 84 | export const getCurrentUser = (setCurrentUser) => { 85 | onSnapshot(userRef, (response) => { 86 | setCurrentUser( 87 | response.docs 88 | .map((docs) => { 89 | return { ...docs.data(), id: docs.id }; 90 | }) 91 | .filter((item) => { 92 | return item.email === localStorage.getItem("userEmail"); 93 | })[0] 94 | ); 95 | }); 96 | }; 97 | 98 | export const editProfile = (userID, payload) => { 99 | let userToEdit = doc(userRef, userID); 100 | 101 | updateDoc(userToEdit, payload) 102 | .then(() => { 103 | toast.success("Profile has been updated successfully"); 104 | }) 105 | .catch((err) => { 106 | console.log(err); 107 | }); 108 | }; 109 | 110 | export const likePost = (userId, postId, liked) => { 111 | try { 112 | let docToLike = doc(likeRef, `${userId}_${postId}`); 113 | if (liked) { 114 | deleteDoc(docToLike); 115 | } else { 116 | setDoc(docToLike, { userId, postId }); 117 | } 118 | } catch (err) { 119 | console.log(err); 120 | } 121 | }; 122 | 123 | export const getLikesByUser = (userId, postId, setLiked, setLikesCount) => { 124 | try { 125 | let likeQuery = query(likeRef, where("postId", "==", postId)); 126 | 127 | onSnapshot(likeQuery, (response) => { 128 | let likes = response.docs.map((doc) => doc.data()); 129 | let likesCount = likes?.length; 130 | 131 | const isLiked = likes.some((like) => like.userId === userId); 132 | 133 | setLikesCount(likesCount); 134 | setLiked(isLiked); 135 | }); 136 | } catch (err) { 137 | console.log(err); 138 | } 139 | }; 140 | 141 | export const postComment = (postId, comment, timeStamp, name) => { 142 | try { 143 | addDoc(commentsRef, { 144 | postId, 145 | comment, 146 | timeStamp, 147 | name, 148 | }); 149 | } catch (err) { 150 | console.log(err); 151 | } 152 | }; 153 | 154 | export const getComments = (postId, setComments) => { 155 | try { 156 | let singlePostQuery = query(commentsRef, where("postId", "==", postId)); 157 | 158 | onSnapshot(singlePostQuery, (response) => { 159 | const comments = response.docs.map((doc) => { 160 | return { 161 | id: doc.id, 162 | ...doc.data(), 163 | }; 164 | }); 165 | 166 | setComments(comments); 167 | }); 168 | } catch (err) { 169 | console.log(err); 170 | } 171 | }; 172 | 173 | export const updatePost = (id, status, postImage) => { 174 | let docToUpdate = doc(postsRef, id); 175 | try { 176 | updateDoc(docToUpdate, { status, postImage }); 177 | toast.success("Post has been updated!"); 178 | } catch (err) { 179 | console.log(err); 180 | } 181 | }; 182 | 183 | export const deletePost = (id) => { 184 | let docToDelete = doc(postsRef, id); 185 | try { 186 | deleteDoc(docToDelete); 187 | toast.success("Post has been Deleted!"); 188 | } catch (err) { 189 | console.log(err); 190 | } 191 | }; 192 | 193 | export const addConnection = (userId, targetId) => { 194 | try { 195 | let connectionToAdd = doc(connectionRef, `${userId}_${targetId}`); 196 | 197 | setDoc(connectionToAdd, { userId, targetId }); 198 | 199 | toast.success("Connection Added!"); 200 | } catch (err) { 201 | console.log(err); 202 | } 203 | }; 204 | 205 | export const getConnections = (userId, targetId, setIsConnected) => { 206 | try { 207 | let connectionsQuery = query( 208 | connectionRef, 209 | where("targetId", "==", targetId) 210 | ); 211 | 212 | onSnapshot(connectionsQuery, (response) => { 213 | let connections = response.docs.map((doc) => doc.data()); 214 | 215 | const isConnected = connections.some( 216 | (connection) => connection.userId === userId 217 | ); 218 | 219 | setIsConnected(isConnected); 220 | }); 221 | } catch (err) { 222 | console.log(err); 223 | } 224 | }; 225 | -------------------------------------------------------------------------------- /src/api/ImageUpload.jsx: -------------------------------------------------------------------------------- 1 | import { storage } from "../firebaseConfig"; 2 | import { ref, getDownloadURL, uploadBytesResumable } from "firebase/storage"; 3 | import { editProfile } from "./FirestoreAPI"; 4 | 5 | export const uploadImage = ( 6 | file, 7 | id, 8 | setModalOpen, 9 | setProgress, 10 | setCurrentImage 11 | ) => { 12 | const profilePicsRef = ref(storage, `profileImages/${file.name}`); 13 | const uploadTask = uploadBytesResumable(profilePicsRef, file); 14 | 15 | uploadTask.on( 16 | "state_changed", 17 | (snapshot) => { 18 | const progress = Math.round( 19 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100 20 | ); 21 | 22 | setProgress(progress); 23 | }, 24 | (error) => { 25 | console.error(err); 26 | }, 27 | () => { 28 | getDownloadURL(uploadTask.snapshot.ref).then((response) => { 29 | editProfile(id, { imageLink: response }); 30 | setModalOpen(false); 31 | setCurrentImage({}); 32 | setProgress(0); 33 | }); 34 | } 35 | ); 36 | }; 37 | 38 | export const uploadPostImage = (file, setPostImage, setProgress) => { 39 | const postPicsRef = ref(storage, `postImages/${file.name}`); 40 | const uploadTask = uploadBytesResumable(postPicsRef, file); 41 | 42 | uploadTask.on( 43 | "state_changed", 44 | (snapshot) => { 45 | const progress = Math.round( 46 | (snapshot.bytesTransferred / snapshot.totalBytes) * 100 47 | ); 48 | 49 | setProgress(progress); 50 | }, 51 | (error) => { 52 | console.error(err); 53 | }, 54 | () => { 55 | getDownloadURL(uploadTask.snapshot.ref).then((response) => { 56 | setPostImage(response); 57 | }); 58 | } 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /src/assets/linkedinLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishant-666/Linkedin-clone-using-React-and-Firebase-9/bd687ceea3813d6bae07e8332210d09db9c534ee/src/assets/linkedinLogo.png -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nishant-666/Linkedin-clone-using-React-and-Firebase-9/bd687ceea3813d6bae07e8332210d09db9c534ee/src/assets/user.png -------------------------------------------------------------------------------- /src/components/ConnectionsComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { getAllUsers, addConnection } from "../api/FirestoreAPI"; 3 | import ConnectedUsers from "./common/ConnectedUsers"; 4 | import "../Sass/ConnectionsComponent.scss"; 5 | 6 | export default function ConnectionsComponent({ currentUser }) { 7 | const [users, setUsers] = useState([]); 8 | const getCurrentUser = (id) => { 9 | addConnection(currentUser.id, id); 10 | }; 11 | useEffect(() => { 12 | getAllUsers(setUsers); 13 | }, []); 14 | 15 | return users.length > 1 ? ( 16 |
17 | {users.map((user) => { 18 | return user.id === currentUser.id ? ( 19 | <> 20 | ) : ( 21 | 26 | ); 27 | })} 28 |
29 | ) : ( 30 |
No Connections to Add!
31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/components/HomeComponent.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "../Sass/HomeComponent.scss"; 3 | import PostStatus from "./common/PostUpdate"; 4 | 5 | export default function HomeComponent({ currentUser }) { 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/LoginComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { LoginAPI } from "../api/AuthAPI"; 3 | import LinkedinLogo from "../assets/linkedinLogo.png"; 4 | import { useNavigate } from "react-router-dom"; 5 | import "../Sass/LoginComponent.scss"; 6 | import { toast } from "react-toastify"; 7 | 8 | export default function LoginComponent() { 9 | let navigate = useNavigate(); 10 | const [credentails, setCredentials] = useState({}); 11 | const login = async () => { 12 | try { 13 | let res = await LoginAPI(credentails.email, credentails.password); 14 | toast.success("Signed In to Linkedin!"); 15 | localStorage.setItem("userEmail", res.user.email); 16 | navigate("/home"); 17 | } catch (err) { 18 | console.log(err); 19 | toast.error("Please Check your Credentials"); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 | 26 | 27 |
28 |

Sign in

29 |

Stay updated on your professional world

30 | 31 |
32 | 34 | setCredentials({ ...credentails, email: event.target.value }) 35 | } 36 | type="email" 37 | className="common-input" 38 | placeholder="Email or Phone" 39 | /> 40 | 42 | setCredentials({ ...credentails, password: event.target.value }) 43 | } 44 | type="password" 45 | className="common-input" 46 | placeholder="Password" 47 | /> 48 |
49 | 52 |
53 |
54 |
55 |

56 | New to LinkedIn?{" "} 57 | navigate("/register")}> 58 | Join now 59 | 60 |

61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/components/ProfileComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import ProfileCard from "./common/ProfileCard"; 3 | import ProfileEdit from "./common/ProfileEdit"; 4 | 5 | export default function ProfileComponent({ currentUser }) { 6 | const [isEdit, setisEdit] = useState(false); 7 | 8 | const onEdit = () => { 9 | setisEdit(!isEdit); 10 | }; 11 | return ( 12 |
13 | {isEdit ? ( 14 | 15 | ) : ( 16 | 17 | )} 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /src/components/RegisterComponent.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { RegisterAPI } from "../api/AuthAPI"; 3 | import { postUserData } from "../api/FirestoreAPI"; 4 | import LinkedinLogo from "../assets/linkedinLogo.png"; 5 | import { useNavigate } from "react-router-dom"; 6 | import { getUniqueID } from "../helpers/getUniqueId"; 7 | import "../Sass/LoginComponent.scss"; 8 | import { toast } from "react-toastify"; 9 | 10 | export default function RegisterComponent() { 11 | let navigate = useNavigate(); 12 | const [credentails, setCredentials] = useState({}); 13 | const register = async () => { 14 | try { 15 | let res = await RegisterAPI(credentails.email, credentails.password); 16 | toast.success("Account Created!"); 17 | postUserData({ 18 | userID: getUniqueID(), 19 | name: credentails.name, 20 | email: credentails.email, 21 | imageLink: 22 | "https://images.unsplash.com/photo-1633332755192-727a05c4013d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=880&q=80", 23 | }); 24 | navigate("/home"); 25 | localStorage.setItem("userEmail", res.user.email); 26 | } catch (err) { 27 | console.log(err); 28 | toast.error("Cannot Create your Account"); 29 | } 30 | }; 31 | 32 | return ( 33 |
34 | 35 | 36 |
37 |

Make the most of your professional life

38 | 39 |
40 | 42 | setCredentials({ ...credentails, name: event.target.value }) 43 | } 44 | type="text" 45 | className="common-input" 46 | placeholder="Your Name" 47 | /> 48 | 50 | setCredentials({ ...credentails, email: event.target.value }) 51 | } 52 | type="email" 53 | className="common-input" 54 | placeholder="Email or phone number" 55 | /> 56 | 58 | setCredentials({ ...credentails, password: event.target.value }) 59 | } 60 | type="password" 61 | className="common-input" 62 | placeholder="Password (6 or more characters)" 63 | /> 64 |
65 | 68 |
69 |
70 |
71 |

72 | Already on LinkedIn?{" "} 73 | navigate("/")}> 74 | Sign in 75 | 76 |

77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/common/Button/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.scss"; 3 | 4 | export default function Button({ title, onClick }) { 5 | return ( 6 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/common/Button/index.scss: -------------------------------------------------------------------------------- 1 | .common-btn { 2 | width: 200px; 3 | height: 30px; 4 | cursor: pointer; 5 | background-color: whitesmoke; 6 | border: 2px solid #003365; 7 | color: #014488; 8 | border-radius: 30px; 9 | font-family: system-ui; 10 | font-weight: 600; 11 | font-size: 14px; 12 | margin: 7px 0; 13 | } 14 | 15 | .common-btn:hover { 16 | background-color: rgb(230, 230, 230); 17 | border: 2px solid #014488; 18 | color: #014488; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/common/ConnectedUsers/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { AiOutlineUsergroupAdd } from "react-icons/ai"; 3 | import { getConnections } from "../../../api/FirestoreAPI"; 4 | 5 | export default function ConnectedUsers({ user, getCurrentUser, currentUser }) { 6 | const [isConnected, setIsConnected] = useState(false); 7 | useEffect(() => { 8 | getConnections(currentUser.id, user.id, setIsConnected); 9 | }, [currentUser.id, user.id]); 10 | return isConnected ? ( 11 | <> 12 | ) : ( 13 |
14 | 15 |

{user.name}

16 |

{user.headline}

17 | 18 | 22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/components/common/FileUploadModal/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button, Modal, Progress } from "antd"; 3 | import "./index.scss"; 4 | 5 | export default function FileUploadModal({ 6 | modalOpen, 7 | setModalOpen, 8 | getImage, 9 | uploadImage, 10 | currentImage, 11 | progress, 12 | }) { 13 | return ( 14 |
15 | setModalOpen(false)} 20 | onCancel={() => setModalOpen(false)} 21 | footer={[ 22 | , 30 | ]} 31 | > 32 |
33 |

{currentImage.name}

34 | 37 | {progress === 0 ? ( 38 | <> 39 | ) : ( 40 |
41 | 42 |
43 | )} 44 | 45 |
46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /src/components/common/FileUploadModal/index.scss: -------------------------------------------------------------------------------- 1 | .image-upload-main { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: auto; 6 | flex-direction: column; 7 | label { 8 | border: 1px solid #212121; 9 | padding: 10px; 10 | cursor: pointer; 11 | font-family: system-ui; 12 | } 13 | 14 | .progress-bar { 15 | padding: 20px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/common/LikeButton/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState } from "react"; 2 | import { 3 | likePost, 4 | getLikesByUser, 5 | postComment, 6 | getComments, 7 | } from "../../../api/FirestoreAPI"; 8 | import { getCurrentTimeStamp } from "../../../helpers/useMoment"; 9 | import "./index.scss"; 10 | import { AiOutlineHeart, AiFillHeart, AiOutlineComment } from "react-icons/ai"; 11 | import { BsFillHandThumbsUpFill, BsHandThumbsUp } from "react-icons/bs"; 12 | 13 | export default function LikeButton({ userId, postId, currentUser }) { 14 | const [likesCount, setLikesCount] = useState(0); 15 | const [showCommentBox, setShowCommentBox] = useState(false); 16 | const [liked, setLiked] = useState(false); 17 | const [comment, setComment] = useState(""); 18 | const [comments, setComments] = useState([]); 19 | const handleLike = () => { 20 | likePost(userId, postId, liked); 21 | }; 22 | const getComment = (event) => { 23 | setComment(event.target.value); 24 | }; 25 | 26 | const addComment = () => { 27 | postComment(postId, comment, getCurrentTimeStamp("LLL"), currentUser?.name); 28 | setComment(""); 29 | }; 30 | useMemo(() => { 31 | getLikesByUser(userId, postId, setLiked, setLikesCount); 32 | getComments(postId, setComments); 33 | }, [userId, postId]); 34 | return ( 35 |
36 |

{likesCount} People Like this Post

37 |
38 |
39 |
40 |
41 |
42 | {liked ? ( 43 | 44 | ) : ( 45 | 46 | )} 47 | 48 |

Like

49 |
50 |
setShowCommentBox(!showCommentBox)} 53 | > 54 | { 55 | 59 | } 60 | 61 |

Comments

62 |
63 |
64 | {showCommentBox ? ( 65 | <> 66 | 73 | 76 | 77 | {comments.length > 0 ? ( 78 | comments.map((comment) => { 79 | return ( 80 |
81 |

{comment.name}

82 |

{comment.comment}

83 | 84 |

{comment.timeStamp}

85 | {/* 86 |

87 | */} 88 |
89 | ); 90 | }) 91 | ) : ( 92 | <> 93 | )} 94 | 95 | ) : ( 96 | <> 97 | )} 98 |
99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /src/components/common/LikeButton/index.scss: -------------------------------------------------------------------------------- 1 | .like-container { 2 | display: flex; 3 | justify-content: center; 4 | 5 | flex-direction: column; 6 | gap: 5px; 7 | padding: 7px 10px; 8 | cursor: pointer; 9 | margin-bottom: -20px; 10 | 11 | .like-comment { 12 | display: grid; 13 | grid-template-columns: auto auto auto auto; 14 | margin-top: 5px; 15 | } 16 | 17 | .comment-input { 18 | height: 40px; 19 | background-color: whitesmoke; 20 | padding-left: 10px; 21 | border-radius: 30px; 22 | border: 1px solid #737373; 23 | font-family: system-ui; 24 | color: #4b4b4b; 25 | font-size: 16px; 26 | } 27 | 28 | .comment-input:focus { 29 | outline: 1px solid #4b4b4b; 30 | color: #4b4b4b; 31 | } 32 | .add-comment-btn { 33 | width: 150px; 34 | height: 35px; 35 | background-color: #0a66c2; 36 | color: white; 37 | outline: none; 38 | border: none; 39 | border-radius: 30px; 40 | cursor: pointer; 41 | margin-top: 15px; 42 | margin-bottom: 15px; 43 | } 44 | .all-comments { 45 | display: flex; 46 | justify-content: center; 47 | flex-direction: column; 48 | background-color: rgb(227, 227, 227); 49 | border-radius: 10px; 50 | position: relative; 51 | margin: 10px; 52 | .name { 53 | color: #212121; 54 | text-decoration: none; 55 | margin-left: 10px; 56 | margin-top: 10px; 57 | } 58 | 59 | .comment { 60 | margin-left: 10px; 61 | margin-top: 0px; 62 | } 63 | 64 | .timestamp { 65 | position: absolute; 66 | right: 10px; 67 | top: 10px; 68 | } 69 | } 70 | .hr-line { 71 | margin-top: -17px; 72 | hr { 73 | border-top: 1px solid rgb(181, 181, 181); 74 | } 75 | } 76 | p { 77 | font-family: Inter, sans-serif; 78 | font-weight: 400; 79 | color: rgba(58, 58, 58, 0.835); 80 | font-size: 14px; 81 | margin-left: 5px; 82 | } 83 | 84 | .likes-comment-inner { 85 | display: flex; 86 | align-items: center; 87 | margin-top: -20px; 88 | .blue { 89 | color: #0a66c2; 90 | } 91 | 92 | .black { 93 | color: rgba(58, 58, 58, 0.835); 94 | } 95 | p { 96 | font-family: Inter, sans-serif; 97 | font-weight: 600; 98 | color: rgba(58, 58, 58, 0.835); 99 | font-size: 16px; 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/components/common/Loader/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Space, Spin } from "antd"; 3 | import "./index.scss"; 4 | 5 | export default function Loader() { 6 | return ( 7 |
8 |

Loading..Please Wait..

9 | 10 | 11 | 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /src/components/common/Loader/index.scss: -------------------------------------------------------------------------------- 1 | .loader { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: 100vh; 6 | flex-direction: column; 7 | gap: 10px; 8 | 9 | p { 10 | font-family: system-ui; 11 | font-weight: 500; 12 | color: rgba(0, 0, 0, 0.9); 13 | font-size: 20px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/common/Modal/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Button, Modal, Progress } from "antd"; 3 | import { AiOutlinePicture } from "react-icons/ai"; 4 | import ReactQuill from "react-quill"; 5 | import "./index.scss"; 6 | 7 | const ModalComponent = ({ 8 | modalOpen, 9 | setModalOpen, 10 | sendStatus, 11 | setStatus, 12 | status, 13 | isEdit, 14 | updateStatus, 15 | uploadPostImage, 16 | setPostImage, 17 | postImage, 18 | currentPost, 19 | setCurrentPost, 20 | }) => { 21 | const [progress, setProgress] = useState(0); 22 | return ( 23 | <> 24 | { 29 | setStatus(""); 30 | setModalOpen(false); 31 | setPostImage(""); 32 | setCurrentPost({}); 33 | }} 34 | onCancel={() => { 35 | setStatus(""); 36 | setModalOpen(false); 37 | setPostImage(""); 38 | setCurrentPost({}); 39 | }} 40 | footer={[ 41 | , 49 | ]} 50 | > 51 |
52 | 59 | {progress === 0 || progress === 100 ? ( 60 | <> 61 | ) : ( 62 |
63 | 64 |
65 | )} 66 | {postImage?.length > 0 || currentPost?.postImage?.length ? ( 67 | postImage 72 | ) : ( 73 | <> 74 | )} 75 |
76 | 79 | 84 | uploadPostImage(event.target.files[0], setPostImage, setProgress) 85 | } 86 | /> 87 |
88 | 89 | ); 90 | }; 91 | 92 | export default ModalComponent; 93 | -------------------------------------------------------------------------------- /src/components/common/Modal/index.scss: -------------------------------------------------------------------------------- 1 | .modal-input { 2 | border: none !important; 3 | background-color: white; 4 | outline: none; 5 | color: black; 6 | font-size: 16px; 7 | font-family: system-ui; 8 | width: 100%; 9 | resize: none; 10 | } 11 | 12 | .ql-container.ql-snow, 13 | .ql-toolbar.ql-snow { 14 | border: none !important; 15 | } 16 | 17 | .ql-container.ql-snow { 18 | font-size: 20px; 19 | font-family: system-ui; 20 | } 21 | 22 | .picture-icon { 23 | color: #0073b1; 24 | cursor: pointer; 25 | position: absolute; 26 | bottom: 20px; 27 | } 28 | 29 | .preview-image { 30 | width: 100%; 31 | margin-top: 20px; 32 | } 33 | 34 | .posts-body { 35 | display: flex; 36 | justify-content: center; 37 | flex-direction: column; 38 | align-items: center; 39 | } 40 | -------------------------------------------------------------------------------- /src/components/common/PostUpdate/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo } from "react"; 2 | import { postStatus, getStatus, updatePost } from "../../../api/FirestoreAPI"; 3 | import { getCurrentTimeStamp } from "../../../helpers/useMoment"; 4 | import ModalComponent from "../Modal"; 5 | import { uploadPostImage } from "../../../api/ImageUpload"; 6 | import { getUniqueID } from "../../../helpers/getUniqueId"; 7 | import PostsCard from "../PostsCard"; 8 | import "./index.scss"; 9 | 10 | export default function PostStatus({ currentUser }) { 11 | const [modalOpen, setModalOpen] = useState(false); 12 | const [status, setStatus] = useState(""); 13 | const [allStatuses, setAllStatus] = useState([]); 14 | const [currentPost, setCurrentPost] = useState({}); 15 | const [isEdit, setIsEdit] = useState(false); 16 | const [postImage, setPostImage] = useState(""); 17 | 18 | const sendStatus = async () => { 19 | let object = { 20 | status: status, 21 | timeStamp: getCurrentTimeStamp("LLL"), 22 | userEmail: currentUser.email, 23 | userName: currentUser.name, 24 | postID: getUniqueID(), 25 | userID: currentUser.id, 26 | postImage: postImage, 27 | }; 28 | await postStatus(object); 29 | await setModalOpen(false); 30 | setIsEdit(false); 31 | await setStatus(""); 32 | }; 33 | 34 | const getEditData = (posts) => { 35 | setModalOpen(true); 36 | setStatus(posts?.status); 37 | setCurrentPost(posts); 38 | setIsEdit(true); 39 | }; 40 | 41 | const updateStatus = () => { 42 | updatePost(currentPost.id, status, postImage); 43 | setModalOpen(false); 44 | }; 45 | 46 | useMemo(() => { 47 | getStatus(setAllStatus); 48 | }, []); 49 | 50 | return ( 51 |
52 |
53 | imageLink 54 |

{currentUser?.name}

55 |

{currentUser?.headline}

56 |
57 |
58 | imageLink 63 | 72 |
73 | 74 | 88 | 89 |
90 | {allStatuses.map((posts) => { 91 | return ( 92 |
93 | 94 |
95 | ); 96 | })} 97 |
98 |
99 | ); 100 | } 101 | -------------------------------------------------------------------------------- /src/components/common/PostUpdate/index.scss: -------------------------------------------------------------------------------- 1 | .post-status-main { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | flex-direction: column; 6 | position: relative; 7 | .user-details { 8 | width: 550px; 9 | height: auto; 10 | background-color: whitesmoke; 11 | margin-top: 100px; 12 | border: 1px solid #b7b7b7; 13 | border-radius: 7px; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | img { 18 | width: 100px; 19 | height: 100px; 20 | object-fit: cover; 21 | border-radius: 50%; 22 | margin-top: -60px; 23 | border: 1px solid #b7b7b7; 24 | padding: 1px; 25 | } 26 | 27 | .name { 28 | font-weight: 600; 29 | font-family: system-ui; 30 | } 31 | 32 | .headline { 33 | font-family: system-ui; 34 | margin-top: -15px; 35 | } 36 | } 37 | .post-status { 38 | width: 550px; 39 | height: 120px; 40 | background-color: whitesmoke; 41 | margin-top: 30px; 42 | border: 1px solid #b7b7b7; 43 | border-radius: 7px; 44 | display: flex; 45 | justify-content: space-around; 46 | align-items: center; 47 | 48 | .post-image { 49 | width: 60px; 50 | height: 60px; 51 | object-fit: cover; 52 | border-radius: 50%; 53 | } 54 | .open-post-modal { 55 | width: 80%; 56 | height: 50px; 57 | text-align: left; 58 | color: rgba(84, 84, 84, 0.89); 59 | background-color: whitesmoke; 60 | outline: none; 61 | border: 1px solid #b7b7b7; 62 | border-radius: 30px; 63 | margin-left: -30px; 64 | cursor: pointer; 65 | padding: 15px; 66 | font-weight: 600; 67 | font-size: 14px; 68 | font-family: system-ui, sans-serif; 69 | } 70 | 71 | .open-post-modal:hover { 72 | background-color: #dcdbdb; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/common/PostsCard/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo, useState, useEffect } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { Button, Modal } from "antd"; 4 | import { BsPencil, BsTrash } from "react-icons/bs"; 5 | import { 6 | getCurrentUser, 7 | getAllUsers, 8 | deletePost, 9 | getConnections, 10 | } from "../../../api/FirestoreAPI"; 11 | import LikeButton from "../LikeButton"; 12 | import "./index.scss"; 13 | 14 | export default function PostsCard({ posts, id, getEditData }) { 15 | let navigate = useNavigate(); 16 | const [currentUser, setCurrentUser] = useState({}); 17 | const [allUsers, setAllUsers] = useState([]); 18 | const [imageModal, setImageModal] = useState(false); 19 | const [isConnected, setIsConnected] = useState(false); 20 | useMemo(() => { 21 | getCurrentUser(setCurrentUser); 22 | getAllUsers(setAllUsers); 23 | }, []); 24 | 25 | useEffect(() => { 26 | getConnections(currentUser.id, posts.userID, setIsConnected); 27 | }, [currentUser.id, posts.userID]); 28 | 29 | return isConnected || currentUser.id === posts.userID ? ( 30 |
31 |
32 | {currentUser.id === posts.userID ? ( 33 |
34 | getEditData(posts)} 38 | /> 39 | deletePost(posts.id)} 43 | /> 44 |
45 | ) : ( 46 | <> 47 | )} 48 | 49 | profile-image item.id === posts.userID) 55 | .map((item) => item.imageLink)[0] 56 | } 57 | /> 58 |
59 |

62 | navigate("/profile", { 63 | state: { id: posts?.userID, email: posts.userEmail }, 64 | }) 65 | } 66 | > 67 | {allUsers.filter((user) => user.id === posts.userID)[0]?.name} 68 |

69 |

70 | {allUsers.filter((user) => user.id === posts.userID)[0]?.headline} 71 |

72 |

{posts.timeStamp}

73 |
74 |
75 | {posts.postImage ? ( 76 | setImageModal(true)} 78 | src={posts.postImage} 79 | className="post-image" 80 | alt="post-image" 81 | /> 82 | ) : ( 83 | <> 84 | )} 85 |

89 | 90 | 95 | 96 | setImageModal(false)} 100 | onCancel={() => setImageModal(false)} 101 | footer={[]} 102 | > 103 | setImageModal(true)} 105 | src={posts.postImage} 106 | className="post-image modal" 107 | alt="post-image" 108 | /> 109 | 110 |
111 | ) : ( 112 | <> 113 | ); 114 | } 115 | -------------------------------------------------------------------------------- /src/components/common/PostsCard/index.scss: -------------------------------------------------------------------------------- 1 | .posts-card { 2 | width: 550px; 3 | min-height: auto; 4 | background-color: whitesmoke; 5 | margin-top: 30px; 6 | border: 1px solid #b7b7b7; 7 | border-radius: 7px; 8 | display: flex; 9 | flex-direction: column; 10 | padding-bottom: 20px; 11 | .post-image-wrapper { 12 | display: flex; 13 | padding: 15px; 14 | gap: 10px; 15 | position: relative; 16 | } 17 | .profile-image { 18 | width: 70px; 19 | height: 70px; 20 | border-radius: 50%; 21 | object-fit: cover; 22 | } 23 | 24 | .name { 25 | font-family: system-ui, sans-serif; 26 | font-size: 18px; 27 | color: #212121; 28 | font-weight: 600; 29 | margin: 0px 0 0 0px; 30 | cursor: pointer; 31 | } 32 | .headline { 33 | font-family: system-ui, sans-serif; 34 | font-size: 15px; 35 | color: rgba(0, 0, 0, 0.6); 36 | font-weight: 500; 37 | margin: 0px 0 0 0px; 38 | } 39 | .timestamp { 40 | font-family: system-ui, sans-serif; 41 | font-size: 12px; 42 | color: rgba(0, 0, 0, 0.6); 43 | font-weight: 400; 44 | margin: 0px 0 0 0px; 45 | } 46 | 47 | .status { 48 | text-align: left; 49 | margin: 10px 0 0 10px; 50 | font-family: Inter, sans-serif; 51 | font-size: 18px; 52 | font-weight: 400; 53 | color: rgba(0, 0, 0, 0.9); 54 | } 55 | } 56 | 57 | .action-container { 58 | position: absolute; 59 | right: 20px; 60 | cursor: pointer; 61 | display: flex; 62 | top: 10px; 63 | .action-icon { 64 | color: #474747; 65 | padding: 10px; 66 | } 67 | 68 | .action-icon:hover { 69 | color: #000000; 70 | background-color: #b7b7b7; 71 | padding: 10px; 72 | border-radius: 50%; 73 | } 74 | } 75 | 76 | .post-image { 77 | cursor: pointer; 78 | width: 100%; 79 | } 80 | 81 | .modal { 82 | margin-top: 30px; 83 | } 84 | -------------------------------------------------------------------------------- /src/components/common/ProfileCard/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useMemo } from "react"; 2 | import { getSingleStatus, getSingleUser } from "../../../api/FirestoreAPI"; 3 | import PostsCard from "../PostsCard"; 4 | import { HiOutlinePencil } from "react-icons/hi"; 5 | import { useLocation } from "react-router-dom"; 6 | import FileUploadModal from "../FileUploadModal"; 7 | import { uploadImage as uploadImageAPI } from "../../../api/ImageUpload"; 8 | import "./index.scss"; 9 | 10 | export default function ProfileCard({ onEdit, currentUser }) { 11 | let location = useLocation(); 12 | const [allStatuses, setAllStatus] = useState([]); 13 | const [currentProfile, setCurrentProfile] = useState({}); 14 | const [currentImage, setCurrentImage] = useState({}); 15 | const [progress, setProgress] = useState(0); 16 | const [modalOpen, setModalOpen] = useState(false); 17 | const getImage = (event) => { 18 | setCurrentImage(event.target.files[0]); 19 | }; 20 | console.log(currentProfile); 21 | const uploadImage = () => { 22 | uploadImageAPI( 23 | currentImage, 24 | currentUser.id, 25 | setModalOpen, 26 | setProgress, 27 | setCurrentImage 28 | ); 29 | }; 30 | 31 | useMemo(() => { 32 | if (location?.state?.id) { 33 | getSingleStatus(setAllStatus, location?.state?.id); 34 | } 35 | 36 | if (location?.state?.email) { 37 | getSingleUser(setCurrentProfile, location?.state?.email); 38 | } 39 | }, []); 40 | 41 | return ( 42 | <> 43 | 51 |
52 | {currentUser.id === location?.state?.id ? ( 53 |
54 | 55 |
56 | ) : ( 57 | <> 58 | )} 59 |
60 |
61 | setModalOpen(true)} 64 | src={ 65 | Object.values(currentProfile).length === 0 66 | ? currentUser.imageLink 67 | : currentProfile?.imageLink 68 | } 69 | alt="profile-image" 70 | /> 71 |

72 | {Object.values(currentProfile).length === 0 73 | ? currentUser.name 74 | : currentProfile?.name} 75 |

76 |

77 | {Object.values(currentProfile).length === 0 78 | ? currentUser.headline 79 | : currentProfile?.headline} 80 |

81 | {(currentUser.city || currentUser.country) && 82 | (currentProfile?.city || currentProfile?.country) ? ( 83 |

84 | {Object.values(currentProfile).length === 0 85 | ? `${currentUser.city}, ${currentUser.country} ` 86 | : `${currentProfile?.city}, ${currentUser.country}`} 87 |

88 | ) : ( 89 | <> 90 | )} 91 | {currentUser.website || currentProfile?.website ? ( 92 | 101 | {Object.values(currentProfile).length === 0 102 | ? `${currentUser.website}` 103 | : currentProfile?.website} 104 | 105 | ) : ( 106 | <> 107 | )} 108 |
109 | 110 |
111 |

112 | {Object.values(currentProfile).length === 0 113 | ? currentUser.college 114 | : currentProfile?.college} 115 |

116 |

117 | {Object.values(currentProfile).length === 0 118 | ? currentUser.company 119 | : currentProfile?.company} 120 |

121 |
122 |
123 |

124 | {Object.values(currentProfile).length === 0 125 | ? currentUser.aboutMe 126 | : currentProfile?.aboutMe} 127 |

128 | 129 | {currentUser.skills || currentProfile?.skills ? ( 130 |

131 | Skills:  132 | {Object.values(currentProfile).length === 0 133 | ? currentUser.skills 134 | : currentProfile?.skills} 135 |

136 | ) : ( 137 | <> 138 | )} 139 |
140 | 141 |
142 | {allStatuses?.map((posts) => { 143 | return ( 144 |
145 | 146 |
147 | ); 148 | })} 149 |
150 | 151 | ); 152 | } 153 | -------------------------------------------------------------------------------- /src/components/common/ProfileCard/index.scss: -------------------------------------------------------------------------------- 1 | .profile-card { 2 | width: auto; 3 | height: auto; 4 | background-color: whitesmoke; 5 | margin: 30px; 6 | border-radius: 5px; 7 | padding: 20px; 8 | position: relative; 9 | .profile-info { 10 | display: flex; 11 | justify-content: space-between; 12 | .profile-image { 13 | width: 200px; 14 | height: 200px; 15 | object-fit: contain; 16 | border-radius: 50%; 17 | border: 2px solid #cacaca; 18 | padding: 5px; 19 | cursor: pointer; 20 | } 21 | .right-info { 22 | margin-top: 25px; 23 | margin-right: 20px; 24 | .college, 25 | .company { 26 | font-family: system-ui; 27 | color: rgba(0, 0, 0, 0.9); 28 | font-size: 14px; 29 | font-weight: 600; 30 | line-height: 30px; 31 | } 32 | 33 | .company { 34 | margin-top: -15px; 35 | } 36 | } 37 | } 38 | 39 | .website { 40 | color: #004c75 !important; 41 | -webkit-link: #004c75; 42 | } 43 | .skill-label { 44 | font-weight: 600; 45 | } 46 | .edit-btn { 47 | position: absolute; 48 | right: 20px; 49 | 50 | .edit-icon { 51 | cursor: pointer; 52 | padding: 10px; 53 | } 54 | 55 | .edit-icon:hover { 56 | background-color: rgb(197, 197, 197); 57 | padding: 10px; 58 | border-radius: 50%; 59 | } 60 | } 61 | .userName { 62 | font-family: system-ui; 63 | color: rgba(0, 0, 0, 0.9); 64 | font-weight: 600; 65 | font-size: 24px; 66 | margin-top: 10px; 67 | } 68 | 69 | .heading { 70 | margin-top: -25px; 71 | font-family: system-ui; 72 | font-size: 16px; 73 | font-weight: 400; 74 | color: rgba(0, 0, 0, 0.9); 75 | width: 320px; 76 | line-height: 20px; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/components/common/ProfileEdit/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { AiOutlineClose } from "react-icons/ai"; 3 | import { editProfile } from "../../../api/FirestoreAPI"; 4 | import "./index.scss"; 5 | 6 | export default function ProfileEdit({ onEdit, currentUser }) { 7 | const [editInputs, setEditInputs] = useState(currentUser); 8 | const getInput = (event) => { 9 | let { name, value } = event.target; 10 | let input = { [name]: value }; 11 | setEditInputs({ ...editInputs, ...input }); 12 | }; 13 | 14 | const updateProfileData = async () => { 15 | await editProfile(currentUser?.id, editInputs); 16 | await onEdit(); 17 | }; 18 | 19 | return ( 20 |
21 |
22 | 23 |
24 | 25 |
26 | 27 | 34 | 35 | 42 | 43 | 50 | 51 | 58 | 59 | 66 | 67 | 74 | 75 | 82 | 83 | 90 | 91 |