├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── .firebaserc ├── img-project.png ├── src ├── app │ └── store.js ├── components │ ├── InputOption │ │ ├── index.js │ │ └── InputOption.css │ ├── HeaderOption │ │ ├── HeaderOption.css │ │ └── index.js │ ├── Post │ │ ├── Post.css │ │ └── index.js │ ├── Feed │ │ ├── Feed.css │ │ └── index.js │ ├── Login │ │ ├── Login.css │ │ └── index.js │ ├── Widgets │ │ ├── Widgets.css │ │ └── index.js │ ├── Header │ │ ├── Header.css │ │ └── index.js │ └── Sidebar │ │ ├── index.js │ │ └── Sidebar.css ├── App.css ├── index.css ├── features │ └── userSlice.js ├── firebase.js ├── index.js ├── App.js └── serviceWorker.js ├── firebase.json ├── .gitignore ├── .firebase └── hosting.YnVpbGQ.cache ├── package.json └── README.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "linkedin-clone-6a323" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /img-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabomoreira/linkedin-clone-react/HEAD/img-project.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabomoreira/linkedin-clone-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabomoreira/linkedin-clone-react/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabomoreira/linkedin-clone-react/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/app/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import userReducer from "../features/userSlice"; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | user: userReducer, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/InputOption/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./InputOption.css"; 3 | 4 | const InputOption = ({ Icon, title, color }) => { 5 | return ( 6 |
7 | 8 |

{title}

9 |
10 | ); 11 | }; 12 | 13 | export default InputOption; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | display: flex; 3 | justify-content: flex-start; 4 | flex-direction: column; 5 | background-color: #f3f2ef; 6 | min-height: 100vh; 7 | } 8 | 9 | .app__body { 10 | padding: 0 15px; 11 | display: flex; 12 | margin-top: 35px; 13 | max-width: 1200px; 14 | margin-right: auto; 15 | margin-left: auto; 16 | } 17 | 18 | @media screen and (max-width: 450px) { 19 | .app__body { 20 | flex: 1; 21 | margin-right: 0; 22 | margin-left: 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | margin: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 10 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 11 | sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | code { 17 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 18 | monospace; 19 | } 20 | -------------------------------------------------------------------------------- /src/features/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | export const userSlice = createSlice({ 4 | name: "user", 5 | initialState: { 6 | user: null, 7 | }, 8 | reducers: { 9 | login: (state, action) => { 10 | state.user = action.payload; 11 | }, 12 | logout: (state) => { 13 | state.user = null; 14 | }, 15 | }, 16 | }); 17 | 18 | export const { login, logout } = userSlice.actions; 19 | 20 | export const selectUser = (state) => state.user.user; 21 | 22 | export default userSlice.reducer; 23 | -------------------------------------------------------------------------------- /src/components/InputOption/InputOption.css: -------------------------------------------------------------------------------- 1 | .inputOption { 2 | display: flex; 3 | align-items: center; 4 | margin-top: 10px; 5 | color: gray; 6 | padding: 10px; 7 | cursor: pointer; 8 | } 9 | 10 | .inputOption:hover { 11 | border-radius: 10px; 12 | background-color: whitesmoke; 13 | } 14 | 15 | .inputOption > h4 { 16 | margin-left: 5px; 17 | } 18 | 19 | @media screen and (max-width: 450px) { 20 | .inputOption { 21 | flex-direction: column; 22 | } 23 | 24 | .inputOption > h4 { 25 | font-size: 12px; 26 | } 27 | .Icon { 28 | font-size: 11px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase/compat/app"; 2 | import "firebase/compat/auth"; 3 | import "firebase/compat/firestore"; 4 | 5 | const firebaseConfig = { 6 | apiKey: "AIzaSyCSJ2CH_hZC4C0wRbxZDd3HqGM50WHHnKE", 7 | authDomain: "linkedin-clone-6a323.firebaseapp.com", 8 | projectId: "linkedin-clone-6a323", 9 | storageBucket: "linkedin-clone-6a323.appspot.com", 10 | messagingSenderId: "902941075676", 11 | appId: "1:902941075676:web:ccced21ce533956a1eec19", 12 | }; 13 | 14 | const firebaseApp = firebase.initializeApp(firebaseConfig); 15 | 16 | // Use these for db & auth 17 | const db = firebaseApp.firestore(); 18 | const auth = firebase.auth(); 19 | 20 | export { auth, db }; 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import { store } from './app/store'; 6 | import { Provider } from 'react-redux'; 7 | import * as serviceWorker from './serviceWorker'; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | // If you want your app to work offline and load faster, you can change 19 | // unregister() to register() below. Note this comes with some pitfalls. 20 | // Learn more about service workers: https://bit.ly/CRA-PWA 21 | serviceWorker.unregister(); 22 | -------------------------------------------------------------------------------- /src/components/HeaderOption/HeaderOption.css: -------------------------------------------------------------------------------- 1 | .headerOption, 2 | .headerOption__screen { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | margin-left: 20px; 7 | color: gray; 8 | cursor: pointer; 9 | } 10 | 11 | .headerOption:hover { 12 | color: black; 13 | } 14 | 15 | .headerOption__title { 16 | font-size: 12px; 17 | font-weight: 400; 18 | } 19 | 20 | .headerOption__icon { 21 | object-fit: contain; 22 | height: 25px !important; 23 | width: 25px !important; 24 | } 25 | 26 | @media screen and (max-width: 450px) { 27 | .headerOption__screen { 28 | display: none; 29 | } 30 | } 31 | 32 | @media screen and (max-width: 710px) { 33 | .headerOption__screen { 34 | display: none; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Post/Post.css: -------------------------------------------------------------------------------- 1 | .post { 2 | background: white; 3 | padding: 15px; 4 | border-radius: 10px; 5 | margin-bottom: 10px; 6 | } 7 | 8 | .post__header { 9 | display: flex; 10 | align-items: center; 11 | margin-bottom: 10px; 12 | } 13 | 14 | .post__info { 15 | margin-left: 10px; 16 | } 17 | .post__info > h2 { 18 | font-size: 15px; 19 | } 20 | 21 | .post__info > p { 22 | font-size: 12px; 23 | color: gray; 24 | } 25 | 26 | .post__body { 27 | overflow-wrap: break-word; 28 | } 29 | 30 | .post__buttons { 31 | display: flex; 32 | justify-content: space-evenly; 33 | } 34 | 35 | @media screen and (max-width: 450px) { 36 | .post__info { 37 | margin-left: 10px; 38 | } 39 | .post__info > h2 { 40 | font-size: 12px; 41 | } 42 | 43 | .post__info > p { 44 | font-size: 8px; 45 | color: gray; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/HeaderOption/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./HeaderOption.css"; 3 | import { Avatar } from "@mui/material"; 4 | import { useSelector } from "react-redux"; 5 | import { selectUser } from "../../features/userSlice"; 6 | 7 | const HeaderOptions = ({ Icon, title, avatar, onClick, screen }) => { 8 | const user = useSelector(selectUser); 9 | return ( 10 |
14 | {Icon && } 15 | 16 | {avatar && ( 17 | 18 | {user?.email[0]} 19 | 20 | )} 21 |

{title}

22 |
23 | ); 24 | }; 25 | 26 | export default HeaderOptions; 27 | -------------------------------------------------------------------------------- /src/components/Feed/Feed.css: -------------------------------------------------------------------------------- 1 | .feed { 2 | flex: 0.6; 3 | margin: 0 20px; 4 | } 5 | 6 | .feed__inputContainer { 7 | background-color: white; 8 | padding: 10px; 9 | padding-bottom: 20px; 10 | border-radius: 10px; 11 | margin-bottom: 20px; 12 | } 13 | 14 | .feed__input { 15 | display: flex; 16 | color: gray; 17 | border-radius: 10px; 18 | border: 1px solid lightgray; 19 | padding: 10px; 20 | padding-left: 15px; 21 | align-items: center; 22 | } 23 | 24 | .feed__input > form { 25 | display: flex; 26 | width: 100%; 27 | } 28 | 29 | .feed__input > form > input { 30 | flex: 1; 31 | border: none; 32 | outline: none; 33 | margin-left: 10px; 34 | font-weight: 600; 35 | } 36 | 37 | .feed__input > form > button { 38 | display: none; 39 | } 40 | 41 | .feed_inputOptions { 42 | display: flex; 43 | justify-content: space-evenly; 44 | } 45 | 46 | @media screen and (max-width: 450px) { 47 | .feed { 48 | flex: 1; 49 | min-width: 100%; 50 | } 51 | } 52 | 53 | @media screen and (max-width: 768px) { 54 | .feed { 55 | flex: 0.6; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/components/Login/Login.css: -------------------------------------------------------------------------------- 1 | .login { 2 | display: grid; 3 | place-items: center; 4 | margin: 0 auto; 5 | padding: 100px 0; 6 | height: 100vh; 7 | } 8 | 9 | .login > img { 10 | object-fit: contain; 11 | height: 70px; 12 | margin: 20px; 13 | } 14 | 15 | .login > form { 16 | display: flex; 17 | align-items: center; 18 | flex-direction: column; 19 | } 20 | 21 | .login > form > input { 22 | width: 350px; 23 | height: 50px; 24 | font-size: 20px; 25 | padding-left: 10px; 26 | margin-bottom: 10px; 27 | border-radius: 5px; 28 | } 29 | 30 | .login > form > button { 31 | width: 365px; 32 | height: 50px; 33 | font-size: large; 34 | color: #fff; 35 | background-color: #0074b1; 36 | border-radius: 5px; 37 | cursor: pointer; 38 | } 39 | 40 | .login__register { 41 | color: #0177b7; 42 | cursor: pointer; 43 | } 44 | 45 | .login > p { 46 | margin-top: 20px; 47 | } 48 | 49 | @media screen and (max-width: 450px) { 50 | .login { 51 | padding: 70px 0; 52 | } 53 | .login > img { 54 | height: 50px; 55 | } 56 | .login > form > input { 57 | width: 280px; 58 | height: 40px; 59 | font-size: 16px; 60 | } 61 | .login > form > button { 62 | width: 285px; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1643721571310,1ffca3ba56b2dc31c0f7d331ec309300e8d68d53d70b9cc0de36453c8bbf5a91 2 | favicon.ico,1642272163548,87aecafb41d21ea93b7af81398237b980c43ee880676c78630ee2d1e9a6d711f 3 | index.html,1643721571309,7b51186962a3b2d21f97213517c51cd6fe6b1b8f313b73f3552874140b713373 4 | logo192.png,1642272179351,ebf380e341383bbf65d6a762603f630a9f7610c97f32bd297bc097635bc37ee3 5 | logo512.png,1642272180246,c55d034d9cf7abfac7600919cbf4f4ce2f3ea3478df91d01177e8ff136f58918 6 | manifest.json,1642272176178,aff3449bdc238776f5d6d967f19ec491b36aed5fb7f23ccff6500736fd58494a 7 | robots.txt,1642272181559,5fcd5c559ded0c02b3ed7840ca3ee77e95b798730af98d9f18bc627ac898071e 8 | static/css/main.94f1859a.css,1643721571372,a213d8f5ca4b7d26ca95083ae6c1dae4af11a59f7cf5513b0224c52e3f7660a8 9 | static/css/main.94f1859a.css.map,1643721571371,b1d6dd7419a7ff48a588eea7f3ea391d9694d99a0398efe931bf6fa9f77abe52 10 | static/js/main.1d5923f1.js,1643721571373,e127ae9376eb722a126171e6bdfa960df2b9cd8b78ed2d36f204ce025dfffc51 11 | static/js/main.1d5923f1.js.LICENSE.txt,1643721571372,bcb02c7baec6ab778b2f12a0a1c9907033f2f9ff0fbbc7fe52e4aadce01fdc8f 12 | static/js/main.1d5923f1.js.map,1643721571378,b95357b00714553b7cb784c2a1af0f2cda79a2c41b9f4394d9fa65df47a46983 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linkedin-clone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/react": "^11.7.1", 7 | "@emotion/styled": "^11.6.0", 8 | "@mui/icons-material": "^5.2.5", 9 | "@mui/material": "^5.2.8", 10 | "@reduxjs/toolkit": "^1.7.1", 11 | "@testing-library/jest-dom": "^4.2.4", 12 | "@testing-library/react": "^9.5.0", 13 | "@testing-library/user-event": "^7.2.1", 14 | "firebase": "^9.6.3", 15 | "react": "^17.0.2", 16 | "react-dom": "^17.0.2", 17 | "react-flip-move": "^3.0.4", 18 | "react-redux": "^7.2.6", 19 | "react-scripts": "5.0.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": "react-app" 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | }, 42 | "devDependencies": { 43 | "mini-css-extract-plugin": "2.4.5" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/Widgets/Widgets.css: -------------------------------------------------------------------------------- 1 | .widgets { 2 | position: sticky; 3 | top: 80px; 4 | flex: 0.2; 5 | background-color: white; 6 | border-radius: 10px; 7 | border: 1px solid lightgray; 8 | height: fit-content; 9 | padding-bottom: 10px; 10 | } 11 | 12 | .widgets__header { 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | padding: 10px; 17 | } 18 | 19 | .widgets__header > h2 { 20 | font-size: 16px; 21 | font-weight: 400; 22 | } 23 | 24 | .widgets__article { 25 | display: flex; 26 | padding: 10px; 27 | cursor: pointer; 28 | } 29 | 30 | .widgets__article:hover { 31 | background-color: whitesmoke; 32 | } 33 | 34 | .widgets__articleLeft { 35 | color: #0177b7; 36 | margin-right: 5px; 37 | } 38 | 39 | .widgets__articleLeft > .MuiSvgIcon-root { 40 | font-size: 15px; 41 | } 42 | 43 | .widgets__articleRight { 44 | flex: 1; 45 | } 46 | 47 | .widgets__articleRight > h4 { 48 | font-size: 14px; 49 | } 50 | 51 | .widgets__articleRight > p { 52 | font-size: 12px; 53 | color: gray; 54 | } 55 | 56 | @media screen and (max-width: 450px) { 57 | .widgets { 58 | display: none; 59 | } 60 | } 61 | 62 | @media screen and (max-width: 768px) { 63 | .widgets { 64 | display: none; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | position: sticky; 3 | top: 0; 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-evenly; 7 | border-bottom: 0.1px solid lightgray; 8 | z-index: 999; 9 | padding: 10px 0; 10 | width: 100%; 11 | background-color: white; 12 | } 13 | 14 | .header__left { 15 | display: flex; 16 | align-items: center; 17 | } 18 | 19 | .header__left > img { 20 | object-fit: contain; 21 | height: 40px; 22 | margin-right: 10px; 23 | } 24 | 25 | .header__search { 26 | display: flex; 27 | align-items: center; 28 | padding: 10px; 29 | border-radius: 5px; 30 | height: 22px; 31 | color: gray; 32 | background: #eef3f8; 33 | } 34 | 35 | .header__search > input { 36 | outline: none; 37 | border: none; 38 | background: none; 39 | } 40 | 41 | .header__right { 42 | display: flex; 43 | align-items: center; 44 | } 45 | 46 | @media screen and (max-width: 450px) { 47 | .header { 48 | justify-content: space-between; 49 | padding: 10px 20px; 50 | } 51 | .header__search { 52 | display: none; 53 | } 54 | .header__left > img { 55 | object-fit: contain; 56 | height: 30px; 57 | margin-right: 10px; 58 | } 59 | } 60 | 61 | @media screen and (max-width: 768px) { 62 | .header { 63 | padding: 10px 20px; 64 | justify-content: space-between; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LinkedIn Clone 2 | 3 | ![GitHub repo size](https://img.shields.io/github/repo-size/gabomoreira/linkedin-clone-react?style=for-the-badge) 4 | ![GitHub language count](https://img.shields.io/github/languages/count/gabomoreira/linkedin-clone-react?style=for-the-badge) 5 | ![GitHub forks](https://img.shields.io/github/forks/gabomoreira/linkedin-clone-react?style=for-the-badge) 6 | ![Bitbucket open issues](https://img.shields.io/bitbucket/issues/gabomoreira/linkedin-clone-react?style=for-the-badge) 7 | 8 | LinkedIn Clone 9 | 10 | > Aplicação para o envio de postagens. 11 | 12 | ## ☕ Usando LinkedIn Clone 13 | 14 | Para usar LinkedIn Clone, clique no link abaixo: 15 | 16 | [![Link]](https://linkedin-clone-6a323.web.app/) 17 | 18 | ## 💻 Tecnologias 19 | 20 | As tecnologias utilizadas para construir o LinkedIn Clone foram: 21 | 22 | - React 23 | - JavaScript 24 | - Redux 25 | - Material UI 26 | - Firebase 27 | 28 | ## 🤝 Colaborador 29 | 30 | Agradecemos à seguinte pessoa que contribuíu para este projeto: 31 | 32 | 33 | 34 | 42 | 43 |
35 | 36 | Foto do Gabriel Moreira no GitHub
37 | 38 | Gabriel Moreira 39 | 40 |
41 |
44 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import "./App.css"; 3 | import Feed from "./components/Feed"; 4 | import Header from "./components/Header"; 5 | import Login from "./components/Login"; 6 | import Sideabar from "./components/Sidebar"; 7 | import { login, logout, selectUser } from "./features/userSlice"; 8 | import { useSelector, useDispatch } from "react-redux"; 9 | import { auth } from "./firebase"; 10 | import Widgets from "./components/Widgets"; 11 | 12 | function App() { 13 | const user = useSelector(selectUser); 14 | const dispatch = useDispatch(); 15 | 16 | useEffect(() => { 17 | auth.onAuthStateChanged((userAuth) => { 18 | if (userAuth) { 19 | //user is logged in 20 | dispatch( 21 | login({ 22 | email: userAuth.email, 23 | uid: userAuth.uid, 24 | displayName: userAuth.displayName, 25 | photoUrl: userAuth.photoURL, 26 | }) 27 | ); 28 | } else { 29 | dispatch(logout({})); 30 | } 31 | }); 32 | }, []); 33 | 34 | return ( 35 |
36 | {!user ? ( 37 | 38 | ) : ( 39 | <> 40 |
41 |
42 | 43 | 44 | 45 |
46 | 47 | )} 48 |
49 | ); 50 | } 51 | 52 | export default App; 53 | -------------------------------------------------------------------------------- /src/components/Widgets/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Widgets.css"; 3 | import InfoIcon from "@mui/icons-material/Info"; 4 | import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; 5 | 6 | const Widgets = () => { 7 | const newArticle = (heading, subtitle) => ( 8 |
9 |
10 | 11 |
12 |
13 |

{heading}

14 |

{subtitle}

15 |
16 |
17 | ); 18 | 19 | return ( 20 |
21 |
22 |

LinkedIn News

23 | 24 |
25 | 26 | {newArticle("Javascript gave me a job", "Top news - 23099 readers")} 27 | {newArticle( 28 | "0 richest in the world doubled their fortunes in the pandemicob", 29 | "Top news - 8027 readers" 30 | )} 31 | {newArticle( 32 | "Twitter launches function to report fake news in Brazil after pressure", 33 | "Top news - 19498 readers" 34 | )} 35 | {newArticle( 36 | "App That Downloaded Movies From Netflix And Disney+ Is Banned From GitHub", 37 | "Top news - 6908 readers" 38 | )} 39 | {newArticle( 40 | "Phil Spencer Says Sony's Game Pass Is 'Inevitable'", 41 | "Top news - 2095 readers" 42 | )} 43 |
44 | ); 45 | }; 46 | 47 | export default Widgets; 48 | -------------------------------------------------------------------------------- /src/components/Post/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState, forwardRef } from "react"; 2 | import { Avatar } from "@mui/material"; 3 | import InputOption from "../InputOption"; 4 | import "./Post.css"; 5 | import ThumbUpAltOutlinedIcon from "@mui/icons-material/ThumbUpAltOutlined"; 6 | import ChatOutlinedIcon from "@mui/icons-material/ChatOutlined"; 7 | import ShareOutlinedIcon from "@mui/icons-material/ShareOutlined"; 8 | import SendOutlinedIcon from "@mui/icons-material/SendOutlined"; 9 | import { useSelector } from "react-redux"; 10 | import { selectUser } from "../../features/userSlice"; 11 | 12 | const Post = forwardRef(({ name, description, message, photoUrl }, ref) => { 13 | const user = useSelector(selectUser); 14 | return ( 15 |
16 |
17 | {user.email[0]} 18 |
19 |

{name}

20 |

{description}

21 |
22 |
23 | 24 |
25 |

{message}

26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 |
34 |
35 | ); 36 | }); 37 | 38 | export default Post; 39 | -------------------------------------------------------------------------------- /src/components/Sidebar/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Sidebar.css"; 3 | import { Avatar } from "@mui/material"; 4 | import { selectUser } from "../../features/userSlice"; 5 | import { useSelector } from "react-redux"; 6 | 7 | const Sidebar = () => { 8 | const user = useSelector(selectUser); 9 | 10 | const recentItem = (topic) => { 11 | return ( 12 |
13 | # 14 |

{topic}

15 |
16 | ); 17 | }; 18 | 19 | return ( 20 |
21 |
22 | 26 | 27 | {user.email[0]} 28 | 29 |

{user.displayName}

30 |

{user.email}

31 |
32 | 33 |
34 |
35 |

Who viewed you:

36 |

2,435

37 |
38 |
39 |

Views on post:

40 |

2,435

41 |
42 |
43 | 44 |
45 |

Recent

46 | {recentItem("reactjs")} 47 | {recentItem("programming")} 48 | {recentItem("softareengineering")} 49 | {recentItem("design")} 50 | {recentItem("developer")} 51 |
52 |
53 | ); 54 | }; 55 | 56 | export default Sidebar; 57 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | LinkedIn 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Header.css"; 3 | import HeaderOption from "../HeaderOption"; 4 | import SearchIcon from "@mui/icons-material/Search"; 5 | import HomeIcon from "@mui/icons-material/Home"; 6 | import SupervisorAccountIcon from "@mui/icons-material/SupervisorAccount"; 7 | import BusinessCenterIcon from "@mui/icons-material/BusinessCenter"; 8 | import ChatIcon from "@mui/icons-material/Chat"; 9 | import NotificationsIcon from "@mui/icons-material/Notifications"; 10 | import { useDispatch, useSelector } from "react-redux"; 11 | import { logout, selectUser } from "../../features/userSlice"; 12 | import { auth } from "../../firebase"; 13 | 14 | const Header = () => { 15 | const dispatch = useDispatch(); 16 | const logoutOfApp = () => { 17 | dispatch(logout()); 18 | auth.signOut(); 19 | }; 20 | 21 | return ( 22 |
23 |
24 | Linkedin 28 | 29 |
30 | 31 | 32 |
33 |
34 | 35 |
36 | 37 | 42 | 43 | 44 | 49 | 50 |
51 |
52 | ); 53 | }; 54 | 55 | export default Header; 56 | -------------------------------------------------------------------------------- /src/components/Sidebar/Sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | position: sticky; 3 | top: 80px; 4 | flex: 0.2; 5 | border-radius: 10px; 6 | text-align: center; 7 | height: fit-content; 8 | } 9 | 10 | .sidebar__avatar { 11 | margin-bottom: 10px; 12 | } 13 | 14 | .sidebar__top > h2 { 15 | font-size: 18px; 16 | } 17 | 18 | .sidebar__top > h4 { 19 | font-size: 12px; 20 | color: gray; 21 | } 22 | 23 | .sidebar__top { 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | border: 1px solid lightgray; 28 | border-top-left-radius: 10px; 29 | border-top-right-radius: 10px; 30 | border-bottom: 0; 31 | background-color: white; 32 | padding-bottom: 10px; 33 | overflow: hidden; 34 | } 35 | 36 | .sidebar__top > img { 37 | margin-bottom: -20px; 38 | width: 100%; 39 | height: 60px; 40 | object-fit: cover; 41 | } 42 | 43 | .sidebar__stats { 44 | padding: 10px; 45 | margin-bottom: 10px; 46 | border: 1px solid lightgray; 47 | background-color: white; 48 | border-bottom-left-radius: 10px; 49 | border-bottom-right-radius: 10px; 50 | } 51 | 52 | .sidebar__stat { 53 | display: flex; 54 | justify-content: space-between; 55 | margin-top: 10px; 56 | } 57 | 58 | .sidebar__stat > p { 59 | font-size: 13px; 60 | font-weight: 600; 61 | color: gray; 62 | } 63 | 64 | .sidebar__statNumber { 65 | font-weight: bold; 66 | color: #0a66c2 !important; 67 | } 68 | 69 | .sidebar__bottom { 70 | text-align: left; 71 | padding: 10px; 72 | border: 1px solid lightgray; 73 | background-color: white; 74 | border-radius: 10px; 75 | margin-top: 10px; 76 | } 77 | 78 | .sidebar__bottom > p { 79 | font-size: 13px; 80 | padding-bottom: 10px; 81 | } 82 | 83 | .sidebar__recentItem { 84 | display: flex; 85 | font-size: 13px; 86 | color: gray; 87 | font-weight: bolder; 88 | margin-bottom: 5px; 89 | padding: 5px; 90 | cursor: pointer; 91 | } 92 | 93 | .sidebar__recentItem:hover { 94 | color: black; 95 | background-color: #f3f2ef; 96 | border-radius: 10px; 97 | } 98 | 99 | .sidebar__hash { 100 | margin-right: 10px; 101 | margin-left: 5px; 102 | } 103 | 104 | @media screen and (max-width: 450px) { 105 | .sidebar { 106 | display: none; 107 | } 108 | } 109 | 110 | @media screen and (max-width: 768px) { 111 | .sidebar { 112 | flex: 0.4; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/components/Login/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./Login.css"; 3 | import { auth } from "../../firebase"; 4 | import { useDispatch } from "react-redux"; 5 | import { login } from "../../features/userSlice"; 6 | 7 | const Login = () => { 8 | const [name, setName] = useState(""); 9 | const [profilePic, setProfilePic] = useState(""); 10 | const [email, setEmail] = useState(""); 11 | const [password, setPassword] = useState(""); 12 | const dispatch = useDispatch(); 13 | 14 | const loginToApp = (e) => { 15 | e.preventDefault(); 16 | 17 | auth 18 | .signInWithEmailAndPassword(email, password) 19 | .then((userAuth) => { 20 | dispatch( 21 | login({ 22 | email: userAuth.user.email, 23 | uid: userAuth.user.uid, 24 | displayName: userAuth.user.displayName, 25 | photoUrl: userAuth.user.photoURL, 26 | }) 27 | ); 28 | }) 29 | .catch((error) => alert(error)); 30 | }; 31 | 32 | const register = () => { 33 | if (!name) { 34 | return alert("Please enter a full name"); 35 | } 36 | 37 | auth 38 | .createUserWithEmailAndPassword(email, password) 39 | .then((userAuth) => { 40 | userAuth.user 41 | .updateProfile({ 42 | displayName: name, 43 | photoURL: profilePic, 44 | }) 45 | .then(() => { 46 | dispatch( 47 | login({ 48 | email: userAuth.user.email, 49 | uid: userAuth.user.uid, 50 | displayName: name, 51 | photoURL: profilePic, 52 | }) 53 | ); 54 | }); 55 | }) 56 | .catch((error) => alert(error.message)); 57 | }; 58 | 59 | return ( 60 |
61 | Linkedin 65 |
66 | setName(e.target.value)} 71 | /> 72 | setProfilePic(e.target.value)} 77 | /> 78 | setEmail(e.target.value)} 83 | /> 84 | setPassword(e.target.value)} 89 | /> 90 | 93 |
94 |

95 | Not a member?{" "} 96 | 97 | Register Now 98 | 99 |

100 |
101 | ); 102 | }; 103 | 104 | export default Login; 105 | -------------------------------------------------------------------------------- /src/components/Feed/index.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "./Feed.css"; 3 | import InputOption from "../InputOption"; 4 | import CreateIcon from "@mui/icons-material/Create"; 5 | import ImageIcon from "@mui/icons-material/Image"; 6 | import SubscriptionsIcon from "@mui/icons-material/Subscriptions"; 7 | import EventNoteIcon from "@mui/icons-material/EventNote"; 8 | import CalendarViewDayIcon from "@mui/icons-material/CalendarViewDay"; 9 | import Post from "../Post"; 10 | import { db } from "../../firebase.js"; 11 | import firebase from "firebase/compat/app"; 12 | import { useSelector } from "react-redux"; 13 | import { selectUser } from "../../features/userSlice"; 14 | import FlipMove from "react-flip-move"; 15 | 16 | const Feed = () => { 17 | const user = useSelector(selectUser); 18 | const [posts, setPosts] = useState([]); 19 | const [input, setInput] = useState(""); 20 | 21 | useEffect(() => { 22 | db.collection("posts") 23 | .orderBy("timestamp", "desc") 24 | .onSnapshot((snapshot) => 25 | setPosts( 26 | snapshot.docs.map((doc) => ({ 27 | id: doc.id, 28 | data: doc.data(), 29 | })) 30 | ) 31 | ); 32 | }, []); 33 | 34 | const sendPost = (e) => { 35 | e.preventDefault(); 36 | 37 | db.collection("posts").add({ 38 | name: user.displayName, 39 | description: user.email, 40 | message: input, 41 | photoUrl: user.photoUrl || "", 42 | timestamp: firebase.firestore.FieldValue.serverTimestamp(), 43 | }); 44 | 45 | setInput(""); 46 | }; 47 | 48 | return ( 49 |
50 |
51 |
52 | 53 |
54 | setInput(e.target.value)} 59 | /> 60 | 63 |
64 |
65 | 66 |
67 | 68 | 69 | 70 | 75 |
76 |
77 | 78 | 79 | {posts.map(({ id, data: { name, description, message, photoUrl } }) => ( 80 | 87 | ))} 88 | 89 | 90 | {/* */} 96 |
97 | ); 98 | }; 99 | 100 | export default Feed; 101 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then((registration) => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch((error) => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then((response) => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then((registration) => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then((registration) => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | --------------------------------------------------------------------------------