├── 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 | 
4 | 
5 | 
6 | 
7 |
8 |
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 |
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 |
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 |
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 | You need to enable JavaScript to run this app.
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 |
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 |
65 |
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 |
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 |
--------------------------------------------------------------------------------