├── src ├── App.css ├── App.js ├── reportWebVitals.js ├── components │ ├── SideBarListItem.js │ ├── Trending.css │ ├── FeedPost.js │ ├── Feed.js │ ├── SideBarListItem.css │ ├── Trending.js │ ├── Feed.css │ ├── Sidebar.css │ ├── Post.js │ └── Sidebar.js ├── index.js ├── config │ └── firebase.js └── index.css ├── public ├── robots.txt ├── favicon.ico ├── manifest.json └── index.html ├── .gitignore ├── README.md └── package.json /src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnuberbacher/react_twitter_clone/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import 'bootstrap/dist/css/bootstrap.min.css'; 3 | import SideBar from "./components/Sidebar"; 4 | import Feed from "./components/Feed"; 5 | import Trending from "./components/Trending"; 6 | 7 | function App() { 8 | return ( 9 |
10 | 11 | 12 | 13 |
14 | ); 15 | } 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /.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 | /src/config 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/components/SideBarListItem.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./SideBarListItem.css"; 3 | 4 | function SideBarListItem({ Text, Icon }) { 5 | return ( 6 | 7 |
8 |
9 | 10 |
11 | { Text } 12 |
13 |
14 | ) 15 | } 16 | 17 | export default SideBarListItem; 18 | -------------------------------------------------------------------------------- /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 reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitter Clone - React WebApp 2 | ![](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB) ![](https://img.shields.io/badge/bootstrap-%23563D7C.svg?style=for-the-badge&logo=bootstrap&logoColor=white) ![](https://img.shields.io/badge/firebase-%23039BE5.svg?style=for-the-badge&logo=firebase) 3 | 4 | 5 | 6 | ### Installation 7 | 8 | ``` 9 | git clone https://github.com/johnuberbacher/react_twitter_clone.git 10 | 11 | npm cd react_twitter_clone 12 | 13 | npm start 14 | ``` 15 | 16 | ### Demo 17 | https://twitter-clone-johnuberbacher.herokuapp.com/ 18 | -------------------------------------------------------------------------------- /src/components/Trending.css: -------------------------------------------------------------------------------- 1 | .trending { 2 | transition: 0.2s width, 0.2s padding; 3 | width: auto; 4 | display: inline-block; 5 | height: 100%; 6 | } 7 | .trendingWrapper { 8 | padding: 0.5rem 0.75rem; 9 | transition: 0.2s width, 0.2s padding; 10 | } 11 | @media(min-width: 992px) { 12 | .trending { 13 | display: flex; 14 | flex-direction: revert; 15 | justify-content: flex-start; 16 | flex-grow: 1; 17 | width: 25% 18 | } 19 | .trending .trendingWrapper { 20 | width: 275px; 21 | } 22 | } 23 | 24 | .trending .trending-search { 25 | background: #eff3f4; 26 | padding: 0.66rem 1.5rem; 27 | border: 0; 28 | height: auto; 29 | border-radius: 9999px; 30 | } 31 | -------------------------------------------------------------------------------- /src/config/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase"; 2 | 3 | // Configure Firebase variables within Heroku => Config Vars 4 | const firebaseConfig = { 5 | apiKey: process.env.REACT_APP_FIREBASE_API_KEY, 6 | authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN, 7 | projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, 8 | storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET, 9 | appID: process.env.REACT_APP_FIREBASE_APP_ID, 10 | messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID, 11 | measurementId: process.env.REACT_APP_FIREBASE_MEASUREMENT_ID 12 | }; 13 | 14 | const firebaseApp = firebase.initializeApp(firebaseConfig); 15 | const db = firebaseApp.firestore(); 16 | export default db; 17 | -------------------------------------------------------------------------------- /src/components/FeedPost.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Feed.css"; 3 | 4 | function FeedPost({ 5 | name, 6 | username, 7 | avatar, 8 | postMsg, 9 | upload 10 | }) { 11 | return ( 12 |
13 |
14 | 15 |
16 |
{name}
@JohnUberbacher · 4h
17 |
{postMsg}
18 | 19 |
20 |
21 |
22 | ) 23 | } 24 | 25 | export default FeedPost; 26 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; 4 | } 5 | body { 6 | margin: 0; 7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | } 13 | 14 | code { 15 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 16 | monospace; 17 | } 18 | 19 | .app { 20 | max-width: 1400px; 21 | margin: 0px auto; 22 | } 23 | 24 | .btn { 25 | border-radius: 9999px!important; 26 | font-size: 15px!important; 27 | font-weight: 700!important; 28 | padding: 0.5rem 1.15rem!important; 29 | } 30 | 31 | .shadow { 32 | box-shadow: rgb(0 0 0 / 8%) 0px 8px 28px 33 | } 34 | -------------------------------------------------------------------------------- /src/components/Feed.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import "./Feed.css"; 3 | import FeedPost from "./FeedPost"; 4 | import Post from "./Post"; 5 | import db from '../config/firebase'; 6 | 7 | function Feed() { 8 | const [posts, setPosts] = useState([]); 9 | 10 | useEffect(() => { 11 | db.collection('posts').onSnapshot(snapshot => ( 12 | setPosts(snapshot.docs.map(doc => doc.data())) 13 | )) 14 | }) 15 | 16 | return ( 17 |
18 |
19 |
20 | Home 21 |
22 | 23 |
24 | {posts.map((post) => ( 25 | 26 | ))} 27 |
28 |
29 |
30 | ) 31 | } 32 | 33 | export default Feed; 34 | -------------------------------------------------------------------------------- /src/components/SideBarListItem.css: -------------------------------------------------------------------------------- 1 | .sideBarListItem { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | justify-content: flex-start; 6 | color: rgb(15, 20, 25); 7 | text-decoration: none!important; 8 | font-size: 18px; 9 | font-weight: 700; 10 | } 11 | 12 | .sideBarListItem .sideBarHover { 13 | display: flex; 14 | align-items: center; 15 | justify-content: flex-start; 16 | padding: 0.9rem 0.9rem; 17 | border-radius: 9999px; 18 | transition: 0.15s background, 0.15s color; 19 | } 20 | 21 | .sideBarListItem:hover .sideBarHover { 22 | color: rgb(29 161 242); 23 | background: rgba(29, 161, 242, 0.1); 24 | } 25 | 26 | .sideBarListItem .sidebar-icon { 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | } 31 | 32 | @media(min-width: 992px) { 33 | .sideBarListItem .sidebar-icon { 34 | margin-right: 1rem; 35 | } 36 | } 37 | 38 | .sideBarHover span { 39 | display: none 40 | } 41 | 42 | @media(min-width: 992px) { 43 | .sideBarHover span { 44 | display: inline-block 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/Trending.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Trending.css" 3 | import TwitterIcon from "@material-ui/icons/Twitter"; 4 | import HomeIcon from "@material-ui/icons/Home"; 5 | import SearchIcon from '@material-ui/icons/Search'; 6 | import PersonIcon from '@material-ui/icons/Person'; 7 | import NotificationsIcon from '@material-ui/icons/Notifications'; 8 | import MailOutlineIcon from '@material-ui/icons/MailOutline'; 9 | import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder'; 10 | import DescriptionIcon from '@material-ui/icons/Description'; 11 | import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; 12 | import EditIcon from '@material-ui/icons/Edit'; 13 | 14 | function trending() { 15 | return ( 16 |
17 |
18 | 19 |
20 |
Whats Happening
21 |
22 |
23 |
24 | ) 25 | } 26 | 27 | export default trending; 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react_twitter_clone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.12.1", 7 | "@material-ui/icons": "^4.11.2", 8 | "@testing-library/jest-dom": "^5.14.1", 9 | "@testing-library/react": "^11.2.7", 10 | "@testing-library/user-event": "^12.8.3", 11 | "bootstrap": "^4.6.0", 12 | "firebase": "^8.7.1", 13 | "react": "^17.0.2", 14 | "react-bootstrap": "^1.6.1", 15 | "react-dom": "^17.0.2", 16 | "react-scripts": "4.0.3", 17 | "web-vitals": "^1.1.2" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/Feed.css: -------------------------------------------------------------------------------- 1 | .main { 2 | flex-grow: 1; 3 | width: 50%; 4 | transition: 0.2s width, 0.2s padding; 5 | margin-left: 77px; 6 | } 7 | @media(min-width: 992px) { 8 | .main { 9 | margin-left: 0; 10 | } 11 | } 12 | 13 | .app { 14 | display: flex; 15 | flex-direction: row; 16 | justify-content: flex-start; 17 | align-items: flex-start; 18 | } 19 | 20 | html, body, #root, .app { 21 | height: 100%; 22 | } 23 | .post .form-control { 24 | overflow: hidden; 25 | padding-bottom: 2px; 26 | padding-top: 2px; 27 | position: relative; 28 | white-space: pre-wrap; 29 | font-size: 20px; 30 | border: 0; 31 | border-radius: 0; 32 | box-shadow: 0; 33 | padding-left: 0; 34 | } 35 | .post .form-control:focus { 36 | border: 0; 37 | border-radius: 0; 38 | outline: none; 39 | box-shadow: none; 40 | 41 | } 42 | .post .img { 43 | width: 48px; 44 | height: 48px; 45 | } 46 | .feed-roll { 47 | background: var(--light) 48 | } 49 | .feed-post { 50 | background: white; 51 | transition: 0.3s background; 52 | } 53 | .feed-post:hover { 54 | background: rgba(0, 0, 0, 0.03) 55 | } 56 | .feed-post .img { 57 | width: 48px; 58 | height: 48px; 59 | } 60 | -------------------------------------------------------------------------------- /src/components/Sidebar.css: -------------------------------------------------------------------------------- 1 | .sideBar { 2 | width: auto; 3 | max-width: 77px; 4 | display: inline-block; 5 | position: fixed; 6 | height: 100%; 7 | transition: 0.3s width, 0.3s padding; 8 | } 9 | .sideBarWrapper { 10 | height: 100%; 11 | padding: 0.5rem 0.75rem; 12 | transition: 0.3s width, 0.3s padding; 13 | } 14 | @media(min-width: 992px) { 15 | .sideBar { 16 | display: flex; 17 | flex-direction: revert; 18 | justify-content: flex-end; 19 | flex-grow: 1; 20 | height: auto; 21 | width: 25%; 22 | max-width: 100%; 23 | position: sticky; 24 | } 25 | .sideBar .sideBarWrapper { 26 | width: 275px; 27 | height: auto; 28 | } 29 | } 30 | 31 | .sideBar .sideBar-Index { 32 | border-radius: 9999px; 33 | height: 52px; 34 | width: 52px; 35 | display: flex; 36 | align-items: center; 37 | justify-content: center; 38 | color: rgb(29 161 242); 39 | transition: 0.15s background; 40 | cursor: pointer; 41 | } 42 | 43 | @media(min-width: 992px) { 44 | .sideBar .sideBar-Index { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | .sideBar .sideBar-Index:hover { 50 | background: rgba(29, 161, 242, 0.1); 51 | } 52 | 53 | .sideBar .sideBar-Index svg { 54 | height: 2rem; 55 | width: 2rem; 56 | } 57 | 58 | .sideBar .btn-tweet { 59 | margin-top: 1rem; 60 | border-radius: 9999px; 61 | font-size: 15px; 62 | font-weight: 700; 63 | padding: 0.875rem 0.1rem!important; 64 | } 65 | 66 | .sideBar .btn-tweet svg { 67 | width: 22px; 68 | height: 22px; 69 | } 70 | -------------------------------------------------------------------------------- /src/components/Post.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from "react"; 2 | import "./Feed.css"; 3 | import db from '../config/firebase'; 4 | 5 | function Post() { 6 | const [newPost, setNewPost] = useState(""); 7 | // const [postImage, setNewPost] = useState("");// 8 | const submitPost = (e) => { 9 | e.preventDefault(); 10 | 11 | db.collection("posts").add({ 12 | name: "John Uberbacher", 13 | username: "JohnUberbacher", 14 | postMsg: newPost, 15 | upload: '', 16 | avatar: 17 | "https://pbs.twimg.com/profile_images/1307919711338864640/Jn1eTlz1_normal.jpg", 18 | }); 19 | 20 | setNewPost(""); 21 | // setTweetImage(""); 22 | }; 23 | 24 | return ( 25 |
26 |
27 | 28 |
29 |
30 |
31 | setNewPost(e.target.value)} className="form-control" placeholder="What's Happening?"/> 32 |
33 |
34 | 35 |
36 |
37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | export default Post; 44 | -------------------------------------------------------------------------------- /src/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./Sidebar.css" 3 | import TwitterIcon from "@material-ui/icons/Twitter"; 4 | import HomeIcon from "@material-ui/icons/Home"; 5 | import SideBarListItem from './SideBarListItem'; 6 | import SearchIcon from '@material-ui/icons/Search'; 7 | import PersonIcon from '@material-ui/icons/Person'; 8 | import NotificationsIcon from '@material-ui/icons/Notifications'; 9 | import MailOutlineIcon from '@material-ui/icons/MailOutline'; 10 | import BookmarkBorderIcon from '@material-ui/icons/BookmarkBorder'; 11 | import DescriptionIcon from '@material-ui/icons/Description'; 12 | import MoreHorizIcon from '@material-ui/icons/MoreHoriz'; 13 | import EditIcon from '@material-ui/icons/Edit'; 14 | 15 | function Sidebar() { 16 | return ( 17 |
18 |
19 |
20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Tweet 31 |
32 |
33 | ) 34 | } 35 | 36 | export default Sidebar; 37 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Twitter Clone 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | --------------------------------------------------------------------------------