├── .eslintignore ├── .eslintrc.js ├── .firebaserc ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.js ├── README.md ├── firebase.json ├── index.html ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── scripts └── env-to-config.sh ├── src ├── App.scss ├── App.test.tsx ├── App.tsx ├── assets │ ├── demo.gif │ ├── fbImgLogo.png │ ├── fbNameLogo.png │ └── techUsed.png ├── components │ ├── CreatePost │ │ ├── CreatePost.scss │ │ └── index.tsx │ ├── Feed │ │ ├── Feed.scss │ │ └── index.tsx │ ├── Header │ │ ├── Header.scss │ │ └── index.tsx │ ├── Post │ │ ├── Post.scss │ │ └── index.tsx │ ├── PostAction │ │ ├── PostAction.scss │ │ └── index.tsx │ ├── Routes │ │ ├── PrivateRoute.tsx │ │ └── PublicRoute.tsx │ ├── SideBar │ │ ├── SideBar.scss │ │ ├── SideBarRow.tsx │ │ └── index.tsx │ ├── StoryReel │ │ ├── Story.tsx │ │ ├── StoryReel.scss │ │ └── index.tsx │ ├── Widget │ │ ├── Widget.scss │ │ └── index.tsx │ └── index.tsx ├── context │ └── auth.tsx ├── db │ └── index.tsx ├── favicon.svg ├── index.css ├── index.tsx ├── pages │ ├── Home │ │ ├── Home.scss │ │ └── index.tsx │ ├── Login │ │ ├── Login.scss │ │ └── index.tsx │ └── index.ts ├── react-app-env.d.ts ├── reportWebVitals.ts └── utils │ ├── _mixins.scss │ ├── _variables.scss │ ├── constants.ts │ ├── firebase.ts │ └── icons.ts ├── tsconfig.json ├── vite.config.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | **/dist/ 3 | **/coverage/ 4 | **/build/ 5 | **/.webpack/ 6 | *.md 7 | *.json 8 | *.yml -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaVersion: 2020, 5 | sourceType: 'module', 6 | ecmaFeatures: { 7 | jsx: true 8 | } 9 | }, 10 | settings: { 11 | react: { 12 | version: '17.0.1' 13 | } 14 | }, 15 | env: { 16 | es6: true, 17 | browser: true, 18 | node: true 19 | }, 20 | plugins: ['react', '@typescript-eslint', 'prettier'], 21 | extends: [ 22 | 'eslint:recommended', 23 | 'plugin:@typescript-eslint/recommended', 24 | 'plugin:react/recommended', 25 | 'plugin:react-hooks/recommended' 26 | ], 27 | rules: { 28 | // JS/TS RULES 29 | quotes: ['error', 'single'], 30 | camelcase: 'error', 31 | 'prefer-const': 'error', 32 | 'no-var': 'error', 33 | '@typescript-eslint/no-inferrable-types': 'off', 34 | '@typescript-eslint/semi': ['error', 'always'], 35 | '@typescript-eslint/indent': ['error', 2], 36 | '@typescript-eslint/brace-style': ['error', '1tbs', { allowSingleLine: true }], 37 | 'no-multiple-empty-lines': ['error', { max: 2, maxBOF: 0, maxEOF: 1 }], 38 | 'no-trailing-spaces': 'error', 39 | '@typescript-eslint/type-annotation-spacing': ['error'], 40 | 'object-curly-spacing': ['error', 'always'], 41 | 'key-spacing': ['error', { beforeColon: false }], 42 | 'object-shorthand': ['error', 'always'], 43 | '@typescript-eslint/no-explicit-any': 'off', 44 | '@typescript-eslint/ban-types': 'off', 45 | '@typescript-eslint/no-var-requires': 'off', 46 | // JSX RULES 47 | 'jsx-quotes': ['error', 'prefer-single'], 48 | 'react/jsx-boolean-value': 'error', 49 | 'react/jsx-closing-bracket-location': 'error', 50 | 'react/jsx-equals-spacing': 'error', 51 | 'react/jsx-indent-props': ['error', 2], 52 | 'react/jsx-indent': ['error', 2], 53 | 'react/jsx-max-props-per-line': ['error', { maximum: 4 }], 54 | 'react/jsx-no-bind': ['error', { allowArrowFunctions: true }], 55 | 'react/jsx-no-literals': 'off', 56 | 'react/jsx-tag-spacing': ['error', { beforeSelfClosing: 'always' }] 57 | } 58 | }; -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "facebook-ts-clone" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.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 | 25 | .eslintcache 26 | 27 | config.json 28 | 29 | debug.log 30 | firebase-debug.log 31 | 32 | .firebase 33 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact = true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | **/dist/ 3 | **/coverage/ 4 | **/build/ 5 | **/.webpack/ 6 | *.json 7 | *.md 8 | *.yml -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | printWidth: 120, 4 | tabWidth: 2, 5 | trailingComma: 'none', 6 | jsxBracketSameLine: true, 7 | jsxSingleQuote: true, 8 | arrowParens: 'avoid' 9 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Facebook Clone 2 | A simple, responsive Facebook Clone made using Typescript and React. 3 | 4 | I chose [vitejs](https://vitejs.dev/) as a build tool for this project (Thanks [@karanpratapsingh](https://github.com/karanpratapsingh/) for the recommendation! 🙌). It truly is blazing fast!⚡ 5 | 6 | Currently, I have implemented the feed update feature only, along with a widget. 7 | 8 | Deployed it using vercel [here](https://fb-clone-ayushiee.vercel.app). 9 | 10 | Please leave a ⭐ as motivation if you liked the implementation 😄 11 | 12 | 13 | ## Demo 14 | ![Demo-1](src/assets/demo.gif) 15 |
16 |
17 | 18 | ## Technologies used 19 | ![Tech used](src/assets/techUsed.png) 20 | 21 | 22 | ## Running the project 23 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 24 | 25 | In the project directory, you can run: 26 | 27 | #### `yarn start` 28 | 29 | It runs the app in the development mode.
30 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 31 | 32 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Facebook Clone 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fb-clone", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vite --host", 7 | "build": "yarn bootstrap && tsc && vite build", 8 | "serve": "vite preview --host", 9 | "build:dev": "tsc && vite build", 10 | "bootstrap": "chmod 777 ./scripts/*.sh && ./scripts/env-to-config.sh" 11 | }, 12 | "dependencies": { 13 | "@material-ui/core": "^4.11.4", 14 | "@material-ui/icons": "^4.11.2", 15 | "@testing-library/jest-dom": "^5.11.4", 16 | "@testing-library/react": "^11.1.0", 17 | "@testing-library/user-event": "^12.1.10", 18 | "@types/jest": "^26.0.15", 19 | "@types/node": "^12.0.0", 20 | "@types/react": "^17.0.0", 21 | "@types/react-dom": "^17.0.0", 22 | "cuid": "2.1.8", 23 | "firebase": "^8.6.8", 24 | "react": "^17.0.2", 25 | "react-dom": "^17.0.2", 26 | "react-router": "5.2.0", 27 | "react-router-dom": "5.2.0", 28 | "react-scripts": "4.0.3", 29 | "sass": "^1.35.1", 30 | "typescript": "^4.3.4", 31 | "web-vitals": "^1.0.1" 32 | }, 33 | "devDependencies": { 34 | "@types/react-router": "5.1.15", 35 | "@types/react-router-dom": "5.1.7", 36 | "@typescript-eslint/parser": "^4.28.0", 37 | "@vitejs/plugin-react-refresh": "^1.3.4", 38 | "eslint": "^7.29.0", 39 | "eslint-plugin-prettier": "^3.4.0", 40 | "eslint-plugin-react": "^7.24.0", 41 | "prettier": "^2.3.1", 42 | "vite": "^2.3.8" 43 | }, 44 | "eslintConfig": { 45 | "extends": [ 46 | "react-app", 47 | "react-app/jest" 48 | ] 49 | }, 50 | "browserslist": { 51 | "production": [ 52 | ">0.2%", 53 | "not dead", 54 | "not op_mini all" 55 | ], 56 | "development": [ 57 | "last 1 chrome version", 58 | "last 1 firefox version", 59 | "last 1 safari version" 60 | ] 61 | }, 62 | "volta": { 63 | "node": "12.18.3", 64 | "yarn": "1.22.5" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/public/logo512.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /scripts/env-to-config.sh: -------------------------------------------------------------------------------- 1 | echo $FIREBASE_CONFIG | base64 --decode > ./src/config.json -------------------------------------------------------------------------------- /src/App.scss: -------------------------------------------------------------------------------- 1 | @import './utils/variables'; 2 | 3 | .App { 4 | background-color: $app-background; 5 | height: 100vh; 6 | overflow: scroll; 7 | 8 | .appBody { 9 | display: flex; 10 | flex-direction: row; 11 | // height: 100vh; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Switch } from 'react-router-dom'; 3 | 4 | import { Login, Home } from './pages'; 5 | import { PublicRoute, PrivateRoute } from './components'; 6 | import { AuthProvider } from './context/auth'; 7 | import './App.scss'; 8 | import { ROUTES } from './utils/constants'; 9 | 10 | function App(): React.ReactElement { 11 | return ( 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/src/assets/demo.gif -------------------------------------------------------------------------------- /src/assets/fbImgLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/src/assets/fbImgLogo.png -------------------------------------------------------------------------------- /src/assets/fbNameLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/src/assets/fbNameLogo.png -------------------------------------------------------------------------------- /src/assets/techUsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ayushiee/fb-clone/45130705a516034f242bee34128e48cc453ca5ec/src/assets/techUsed.png -------------------------------------------------------------------------------- /src/components/CreatePost/CreatePost.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .createPost { 5 | background-color: $component-background; 6 | border-radius: 0.4rem; 7 | padding: 0.3rem; 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | width: 100%; 12 | box-shadow: 0px 5px 7px -7px $box-shadow; 13 | margin: 0.8rem; 14 | 15 | @include for-xl-devices { 16 | width: 90%; 17 | } 18 | 19 | @include for-large-devices { 20 | width: 85%; 21 | } 22 | 23 | .top { 24 | display: flex; 25 | flex-direction: row; 26 | align-items: center; 27 | justify-content: space-around; 28 | padding: 0.8rem 0.5rem; 29 | width: 99%; 30 | border-bottom: 1px solid $border-color; 31 | 32 | @include for-small-screens { 33 | overflow: hidden; 34 | } 35 | 36 | .form { 37 | flex: 1; 38 | display: flex; 39 | 40 | @include for-small-screens { 41 | flex-wrap: wrap; 42 | } 43 | 44 | input { 45 | border-radius: 3rem; 46 | padding: 0.7rem 1rem; 47 | border: none; 48 | background-color: $comment-background; 49 | outline: none; 50 | border-radius: 3rem; 51 | overflow: hidden; 52 | margin-left: 0.5rem; 53 | } 54 | 55 | .button { 56 | display: none; 57 | @include for-small-screens { 58 | display: flex; 59 | } 60 | } 61 | 62 | .textInput { 63 | flex: 1; 64 | } 65 | .imgUrlInput { 66 | flex: 0.5; 67 | } 68 | } 69 | } 70 | 71 | .bottom { 72 | display: flex; 73 | flex-direction: row; 74 | justify-content: space-evenly; 75 | padding-top: 0.4rem; 76 | width: 98%; 77 | 78 | @include for-small-screens { 79 | padding: 0.2rem; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/components/CreatePost/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Avatar, IconButton } from '@material-ui/core'; 3 | import firebase from 'firebase'; 4 | 5 | import { firestore } from '../../utils/firebase'; 6 | import { PostAction } from '..'; 7 | import { 8 | InsertEmoticonOutlinedIcon, 9 | PhotoLibraryRoundedIcon, 10 | VideocamRoundedIcon, 11 | SendRoundedIcon 12 | } from '../../utils/icons'; 13 | 14 | import './CreatePost.scss'; 15 | import cuid from 'cuid'; 16 | 17 | interface CreatePostProps { 18 | photoUrl?: string; 19 | username: string | null; 20 | } 21 | 22 | function CreatePost({ photoUrl, username }: CreatePostProps): React.ReactElement { 23 | const [input, setInput] = useState(''); 24 | const [imgUrl, setImgUrl] = useState(''); 25 | 26 | const handleSubmit = async (event: any) => { 27 | event.preventDefault(); 28 | 29 | const id: string = cuid(); 30 | await firestore.collection('posts').doc(id).set({ 31 | profilePic: photoUrl, 32 | username: username, 33 | timestamp: new Date(), 34 | image: imgUrl.trim(), 35 | text: input.trim() 36 | }); 37 | 38 | setInput(''); 39 | setImgUrl(''); 40 | }; 41 | 42 | return ( 43 |
44 |
45 | 46 |
47 | setInput(e.target.value)} 51 | placeholder={`What's on your mind, ${username}?`} 52 | /> 53 | setImgUrl(e.target.value)} 57 | placeholder='Image URL (optional)' 58 | /> 59 | 60 | 61 | 62 |
63 |
64 |
65 | {bottomIcon.map(({ Icon, title, color }, index) => ( 66 | 67 | ))} 68 |
69 |
70 | ); 71 | } 72 | 73 | export default CreatePost; 74 | 75 | const bottomIcon = [ 76 | { 77 | Icon: VideocamRoundedIcon, 78 | title: 'Live Video', 79 | color: 'red' 80 | }, 81 | { 82 | Icon: PhotoLibraryRoundedIcon, 83 | title: 'Photo/Video', 84 | color: 'green' 85 | }, 86 | { 87 | Icon: InsertEmoticonOutlinedIcon, 88 | title: 'Feeling/Activity', 89 | color: 'orange' 90 | } 91 | ]; 92 | -------------------------------------------------------------------------------- /src/components/Feed/Feed.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .feed { 5 | display: flex; 6 | flex-direction: column; 7 | flex: 0.45; 8 | justify-content: flex-start; 9 | align-items: center; 10 | padding: 0.35rem; 11 | height: 100vh; 12 | 13 | @include for-xl-devices { 14 | flex: 0.3; 15 | } 16 | 17 | @include for-large-devices { 18 | flex: 1; 19 | // align-items: center; 20 | // justify-content: center; 21 | } 22 | 23 | @include for-small-screens { 24 | overflow-x: hidden; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Feed/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import firebase from 'firebase'; 3 | import StoryReel from '../StoryReel'; 4 | import CreatePost from '../CreatePost'; 5 | import Post from '../Post'; 6 | import { firestore, DocumentData } from '../../utils/firebase'; 7 | 8 | import './Feed.scss'; 9 | 10 | interface FeedProps { 11 | photoUrl?: string; 12 | username: string | null; 13 | } 14 | 15 | function Feed({ photoUrl, username }: FeedProps): React.ReactElement { 16 | const [posts, setPosts] = useState([]); 17 | 18 | useEffect(() => { 19 | firestore 20 | .collection('posts') 21 | .orderBy('timestamp', 'desc') 22 | .onSnapshot(snapshot => { 23 | setPosts(snapshot.docs.map(doc => ({ id: doc.id, data: doc.data() }))); 24 | }); 25 | }, []); 26 | 27 | return ( 28 |
29 | 30 | 31 | {posts.map((post: DocumentData) => ( 32 | 40 | ))} 41 |
42 | ); 43 | } 44 | 45 | export default Feed; 46 | -------------------------------------------------------------------------------- /src/components/Header/Header.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .header { 5 | background-color: $component-background; 6 | padding: 0.2rem 0.5rem 0; 7 | display: flex; 8 | flex-direction: row; 9 | flex: auto; 10 | justify-content: space-between; 11 | position: sticky; 12 | z-index: 100; 13 | top: 0; 14 | box-shadow: 0px 5px 8px -9px $box-shadow; 15 | 16 | @include for-small-screens { 17 | padding: 0.4rem 0.5rem; 18 | } 19 | 20 | .headerLeft { 21 | display: flex; 22 | flex-direction: row; 23 | align-items: center; 24 | flex: 0.3; 25 | 26 | @include for-xl-devices { 27 | flex: 0.2; 28 | } 29 | 30 | @include for-large-devices { 31 | flex: 0.2; 32 | } 33 | 34 | 35 | .logo { 36 | height: 40px; 37 | 38 | @include for-small-screens { 39 | height: 35px; 40 | } 41 | 42 | } 43 | 44 | .searchInput { 45 | margin: 0 0.4rem; 46 | display: flex; 47 | background-color: $comment-background; 48 | padding: 0.2rem; 49 | border-radius: 3rem; 50 | justify-content: center; 51 | align-items: center; 52 | overflow: hidden; 53 | 54 | @include for-xl-devices { 55 | flex: 0.7; 56 | } 57 | 58 | @include for-large-devices { 59 | flex: 0.4; 60 | } 61 | 62 | @include for-small-screens { 63 | display: none; 64 | } 65 | 66 | .searchIcon { 67 | color: $comment-text; 68 | margin-left: 0.5rem; 69 | @include for-large-devices { 70 | display: none; 71 | } 72 | } 73 | 74 | .inputBar { 75 | background-color: transparent; 76 | border: none; 77 | outline: none; 78 | padding: 0.4rem; 79 | color: $comment-text; 80 | @include for-large-devices { 81 | text-align: center; 82 | } 83 | } 84 | } 85 | } 86 | 87 | .headerCenter { 88 | display: flex; 89 | flex-direction: row; 90 | justify-content: space-evenly; 91 | align-items: center; 92 | flex: 0.55; 93 | @include for-xl-devices { 94 | flex: 0.4; 95 | } 96 | 97 | @include for-large-devices { 98 | display: flex; 99 | flex-direction: row; 100 | justify-content: space-between; 101 | flex: 0.3; 102 | } 103 | 104 | @include for-small-screens { 105 | display: none; 106 | } 107 | 108 | .option { 109 | display: flex; 110 | align-items: center; 111 | padding: 0.6rem 3rem; 112 | border-radius: 0.4rem; 113 | transition: linear 200ms; 114 | 115 | @include for-large-devices { 116 | padding: 0.6rem; 117 | margin-left: 0.2rem; 118 | } 119 | 120 | @include for-xl-devices { 121 | padding: 0.6rem 1rem; 122 | } 123 | 124 | &:hover { 125 | cursor: pointer; 126 | background-color: $button-hover; 127 | transform: translateY(-1.5px); 128 | .icon { 129 | color: $primary; 130 | } 131 | } 132 | 133 | .icon { 134 | color: $icon; 135 | } 136 | } 137 | 138 | .activeOption { 139 | display: flex; 140 | align-items: center; 141 | padding: 0.6rem 3rem; 142 | cursor: pointer; 143 | border-bottom: 4px solid $primary; 144 | @include for-large-devices { 145 | padding: 0.5rem; 146 | } 147 | @include for-xl-devices { 148 | padding: 0.6rem 1rem; 149 | } 150 | 151 | .icon { 152 | color: $primary; 153 | } 154 | } 155 | } 156 | 157 | .headerRight { 158 | display: flex; 159 | flex-direction: row; 160 | align-items: center; 161 | flex: 0.3; 162 | justify-content: flex-end; 163 | 164 | @include for-xl-devices { 165 | flex: 0.5; 166 | } 167 | 168 | button { 169 | background-color: $button-background; 170 | color: $button-icon; 171 | margin: 0 0.3rem; 172 | transition: linear 200ms; 173 | @include for-xl-devices { 174 | padding: 0.6rem; 175 | } 176 | 177 | @include for-small-screens { 178 | padding: 0.5rem; 179 | } 180 | 181 | &:hover { 182 | background-color: $sec-button-hover; 183 | transform: translateY(-2px); 184 | } 185 | } 186 | 187 | .info { 188 | display: flex; 189 | align-items: center; 190 | margin-right: 0.5rem; 191 | 192 | @include for-large-devices { 193 | display: none; 194 | } 195 | 196 | h4 { 197 | margin-left: 0.5rem; 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/components/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Avatar, IconButton } from '@material-ui/core'; 3 | import { useHistory } from 'react-router-dom'; 4 | 5 | import { 6 | SearchIcon, 7 | HomeRoundedIcon, 8 | SubscriptionsRoundedIcon, 9 | SupervisedUserCircleRoundedIcon, 10 | AddRoundedIcon, 11 | NotificationsRoundedIcon, 12 | StorefrontRoundedIcon, 13 | ExitToAppRoundedIcon 14 | } from '../../utils/icons'; 15 | import { useAuth } from '../../context/auth'; 16 | import { ROUTES } from '../../utils/constants'; 17 | import fbImgLogo from '../../assets/fbImgLogo.png'; 18 | import './Header.scss'; 19 | 20 | interface HeaderProps { 21 | photoUrl?: string; 22 | username: string | null; 23 | } 24 | 25 | export default function Header({ photoUrl, username }: HeaderProps): React.ReactElement { 26 | const { logout } = useAuth(); 27 | const history = useHistory(); 28 | 29 | const onLogout = async () => { 30 | try { 31 | await logout(); 32 | history.push(ROUTES.SIGNIN); 33 | } catch (error) { 34 | console.log(error.message); 35 | } 36 | }; 37 | 38 | return ( 39 |
40 |
41 | fb logo 42 |
43 | 44 | 45 |
46 |
47 | 48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 |
64 |
65 | 66 |

{username}

67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 |
79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /src/components/Post/Post.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .post { 5 | background-color: $component-background; 6 | border-radius: 0.5rem; 7 | padding: 0.3rem; 8 | display: flex; 9 | flex-direction: column; 10 | width: 100%; 11 | box-shadow: 0px 5px 7px -7px $box-shadow; 12 | margin: 0.8rem; 13 | 14 | @include for-xl-devices { 15 | width: 90%; 16 | } 17 | 18 | @include for-large-devices { 19 | width: 85%; 20 | } 21 | 22 | .top { 23 | display: flex; 24 | flex-direction: row; 25 | align-items: center; 26 | padding: 0.3rem; 27 | 28 | .info { 29 | margin-left: 0.6rem; 30 | 31 | h4 { 32 | text-transform: capitalize; 33 | margin-bottom: 0; 34 | padding-bottom: 0; 35 | } 36 | 37 | span { 38 | font-size: small; 39 | color: $comment-text; 40 | } 41 | } 42 | } 43 | 44 | .text { 45 | display: flex; 46 | padding: 0.3rem 0; 47 | flex-wrap: wrap; 48 | margin: 0.6rem 0.3rem; 49 | word-break: break-all; 50 | word-wrap: break-word; 51 | } 52 | 53 | .image { 54 | padding: 0.3rem; 55 | img { 56 | width: 100%; 57 | } 58 | } 59 | } 60 | 61 | .bottomAction { 62 | padding: 0.3rem 0.3rem 0; 63 | border-top: 1px solid $border-color; 64 | display: flex; 65 | align-items: center; 66 | justify-content: space-evenly; 67 | 68 | .action { 69 | transition: linear 200ms; 70 | cursor: pointer; 71 | display: flex; 72 | flex-direction: row; 73 | padding: 0.5rem 1rem ; 74 | flex: 1; 75 | align-items: center; 76 | justify-content: center; 77 | 78 | &:hover { 79 | background-color: $button-hover; 80 | transform: translateY(-2px); 81 | border-radius: 0.3rem; 82 | } 83 | 84 | h4 { 85 | color: $comment-text; 86 | margin-left: 0.5rem; 87 | 88 | @include for-small-screens { 89 | display: none; 90 | } 91 | } 92 | 93 | .postAction { 94 | margin: 0 0.5rem; 95 | color: $icon; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/components/Post/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Avatar } from '@material-ui/core'; 3 | 4 | import { ChatBubbleOutlineRoundedIcon, NearMeRoundedIcon, ThumbUpRoundedIcon } from '../../utils/icons'; 5 | 6 | import './Post.scss'; 7 | 8 | interface PostProps { 9 | profilePic: string; 10 | username: string; 11 | text?: string; 12 | timestamp: string; 13 | image?: string; 14 | } 15 | 16 | function Post(props: PostProps): React.ReactElement { 17 | const { profilePic, username, text, timestamp, image } = props; 18 | return ( 19 |
20 |
21 | 22 |
23 |

{username}

24 | {timestamp} 25 |
26 |
27 | {text && ( 28 |
29 |

{text}

30 |
31 | )} 32 | {image && ( 33 |
34 | Post Image 35 |
36 | )} 37 |
38 |
39 | 40 |

Like

41 |
42 |
43 | 44 |

Comment

45 |
46 |
47 | 48 |

Share

49 |
50 |
51 |
52 | ); 53 | } 54 | 55 | const iconList = [ 56 | { 57 | Icon: , 58 | title: 'Like', 59 | color: 'grey' 60 | }, 61 | { 62 | Icon: , 63 | title: 'Comment', 64 | color: 'grey' 65 | }, 66 | { 67 | Icon: , 68 | title: 'Share', 69 | color: 'grey' 70 | } 71 | ]; 72 | 73 | export default Post; 74 | -------------------------------------------------------------------------------- /src/components/PostAction/PostAction.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .actions { 5 | transition: linear 200ms; 6 | cursor: pointer; 7 | display: flex; 8 | flex-direction: row; 9 | padding: 0.3rem 1rem; 10 | margin-right: 0.2rem; 11 | justify-content: space-evenly; 12 | align-items: center; 13 | 14 | @include for-small-screens { 15 | padding: 0.3rem 1rem; 16 | } 17 | 18 | &:hover { 19 | background-color: $button-hover; 20 | transform: translateY(-2px); 21 | border-radius: 0.5rem; 22 | } 23 | 24 | h5 { 25 | color: $comment-text; 26 | margin-left: 0.5rem; 27 | 28 | @include for-small-screens { 29 | display: none; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/PostAction/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './PostAction.scss' 4 | 5 | interface PostActionProps { 6 | Icon: any; 7 | title: string; 8 | color: string; 9 | } 10 | 11 | function PostAction(props: PostActionProps): React.ReactElement { 12 | const { Icon, title, color } = props; 13 | return ( 14 |
15 | 16 |
{title}
17 |
18 | ); 19 | } 20 | 21 | export default PostAction; 22 | -------------------------------------------------------------------------------- /src/components/Routes/PrivateRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Redirect, Route, RouteProps } from 'react-router-dom'; 3 | import { ROUTES } from '../../utils/constants'; 4 | import { useAuth } from '../../context/auth'; 5 | 6 | interface PrivateRouteProps extends RouteProps { 7 | component?: React.ComponentType; 8 | children?: React.ReactNode; 9 | } 10 | 11 | export default function PrivateRoute({ 12 | component: Component, 13 | children, 14 | ...rest 15 | }: PrivateRouteProps): React.ReactElement { 16 | const { isAuthenticated } = useAuth(); 17 | 18 | const render = (props: any) => { 19 | if (!isAuthenticated) { 20 | return ; 21 | } else { 22 | if (Component) { 23 | return ; 24 | } else { 25 | return children; 26 | } 27 | } 28 | }; 29 | 30 | return ; 31 | } 32 | -------------------------------------------------------------------------------- /src/components/Routes/PublicRoute.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect, Route, RouteProps } from 'react-router-dom'; 3 | import { ROUTES } from '../../utils/constants'; 4 | import { useAuth } from '../../context/auth'; 5 | 6 | interface PublicRouteProps extends RouteProps { 7 | restricted?: boolean; 8 | component: React.ComponentType; 9 | children?: React.ReactNode; 10 | } 11 | 12 | export default function PublicRoute({ 13 | component: Component, 14 | restricted = false, 15 | children, 16 | ...rest 17 | }: PublicRouteProps): React.ReactElement { 18 | const { isAuthenticated } = useAuth(); 19 | 20 | const render = (props: any) => { 21 | if (isAuthenticated && restricted) { 22 | return ; 23 | } else { 24 | if (Component) { 25 | return ; 26 | } else { 27 | return children; 28 | } 29 | } 30 | }; 31 | 32 | return ; 33 | } 34 | -------------------------------------------------------------------------------- /src/components/SideBar/SideBar.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .sideBarRow { 5 | display: flex; 6 | margin: 0.2rem 0.4rem; 7 | cursor: pointer; 8 | 9 | .row { 10 | display: flex; 11 | flex-direction: row; 12 | align-items: center; 13 | padding: 0.5rem; 14 | transition: linear 200ms; 15 | 16 | h4 { 17 | margin-left: 0.8rem; 18 | color: $button-icon; 19 | } 20 | 21 | .image { 22 | height: 2.4rem; 23 | width: 2.4rem; 24 | } 25 | 26 | .rowIcon { 27 | color: $primary; 28 | } 29 | 30 | &:hover { 31 | background-color: $button-hover; 32 | transform: translateY(-2px); 33 | border-radius: 0.2rem; 34 | } 35 | } 36 | } 37 | 38 | .sideBar { 39 | padding: 0.2rem; 40 | flex: 0.3; 41 | 42 | @include for-xl-devices { 43 | flex: 0.2; 44 | display: none; 45 | } 46 | 47 | 48 | } -------------------------------------------------------------------------------- /src/components/SideBar/SideBarRow.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from '@material-ui/core'; 2 | import React from 'react'; 3 | 4 | import './SideBar.scss'; 5 | 6 | interface SideBarRowProps { 7 | src?: string | null; 8 | title: string | null; 9 | url?: string; 10 | Icon?: any; 11 | } 12 | 13 | function SideBarRow(props: SideBarRowProps): React.ReactElement { 14 | const { src, title, url, Icon } = props; 15 | return ( 16 |
17 |
18 | {src && } 19 | {url && } 20 | {Icon && } 21 |

{title}

22 |
23 |
24 | ); 25 | } 26 | 27 | export default SideBarRow; 28 | -------------------------------------------------------------------------------- /src/components/SideBar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SideBarRow from './SideBarRow'; 3 | import { 4 | StorefrontRoundedIcon, 5 | LocalHospitalRoundedIcon, 6 | GroupRoundedIcon, 7 | ChatRoundedIcon, 8 | VideoLibraryRoundedIcon, 9 | FlagRoundedIcon 10 | } from '../../utils/icons'; 11 | 12 | import './SideBar.scss'; 13 | 14 | interface SideBarProps { 15 | photoUrl?: string; 16 | username: string | null; 17 | } 18 | 19 | function SideBar({ photoUrl, username }: SideBarProps): React.ReactElement { 20 | return ( 21 |
22 | 23 | {rowIconList.map(({ Icon, title }, index) => ( 24 | 25 | ))} 26 |
27 | ); 28 | } 29 | 30 | const rowIconList = [ 31 | { 32 | Icon: LocalHospitalRoundedIcon, 33 | title: 'COVID-19 Information Center' 34 | }, 35 | { 36 | Icon: FlagRoundedIcon, 37 | title: 'Pages' 38 | }, 39 | { 40 | Icon: GroupRoundedIcon, 41 | title: 'Friends' 42 | }, 43 | { 44 | Icon: ChatRoundedIcon, 45 | title: 'Messenger' 46 | }, 47 | { 48 | Icon: StorefrontRoundedIcon, 49 | title: 'Marketplace' 50 | }, 51 | { 52 | Icon: VideoLibraryRoundedIcon, 53 | title: 'Videos' 54 | } 55 | ]; 56 | 57 | export default SideBar; 58 | -------------------------------------------------------------------------------- /src/components/StoryReel/Story.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar } from '@material-ui/core'; 2 | import React from 'react'; 3 | 4 | import './StoryReel.scss'; 5 | 6 | interface StoryProps { 7 | storyImage: string; 8 | profilePic: string; 9 | username: string; 10 | } 11 | 12 | function Story(props: StoryProps): React.ReactElement { 13 | const { storyImage, profilePic, username } = props; 14 | return ( 15 |
22 | 23 |

{username}

24 |
25 | ); 26 | } 27 | 28 | export default Story; 29 | -------------------------------------------------------------------------------- /src/components/StoryReel/StoryReel.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .story { 5 | padding: 0.5rem; 6 | height: 10rem; 7 | width: 6rem; 8 | position: relative; 9 | background-position: center center; 10 | background-size: cover; 11 | background-repeat: no-repeat; 12 | box-shadow: 0px 5px 17px -7px $box-shadow; 13 | border-radius: 0.5rem; 14 | margin-right: 0.5rem; 15 | transition: linear 200ms ease-in-out; 16 | cursor: pointer; 17 | justify-content: space-between; 18 | align-items: flex-end; 19 | 20 | @include for-small-screens { 21 | height: 8rem; 22 | width: 5rem; 23 | } 24 | 25 | &:hover { 26 | transform: scale(1.04); 27 | } 28 | 29 | .storyAvatar { 30 | border: 4px solid $primary; 31 | margin: 0.1rem; 32 | } 33 | 34 | h4 { 35 | position: absolute; 36 | bottom: 0.6rem; 37 | color: $white; 38 | box-shadow: 6px 5px 17px -17px $black; 39 | 40 | @include for-small-screens { 41 | font-size: smaller; 42 | } 43 | } 44 | } 45 | 46 | .createStory { 47 | background-color: $component-background; 48 | height: 11rem; 49 | width: 7rem; 50 | border-radius: 0.5rem; 51 | margin-right: 0.5rem; 52 | box-shadow: 0px 5px 17px -7px $box-shadow; 53 | display: flex; 54 | flex-direction: column; 55 | overflow: hidden; 56 | transition: linear 200ms ease-in-out; 57 | cursor: pointer; 58 | 59 | @include for-small-screens { 60 | height: 9rem; 61 | width: 6rem; 62 | } 63 | 64 | @include for-mobile-only { 65 | display: none; 66 | } 67 | 68 | 69 | &:hover { 70 | transform: scale(1.03); 71 | } 72 | 73 | img { 74 | min-height: 75%; 75 | object-fit: cover; 76 | z-index: 0; 77 | } 78 | 79 | .lower { 80 | display: flex; 81 | flex-direction: column; 82 | align-items: center; 83 | justify-content: space-between; 84 | align-items: center; 85 | position: relative; 86 | bottom: 1rem; 87 | 88 | .circle { 89 | background-color: $primary; 90 | padding: 0.25rem; 91 | border-radius: 100%; 92 | display: flex; 93 | align-items: center; 94 | margin-top: -0.2rem; 95 | margin-bottom: 0.1rem; 96 | border: 4px solid $component-background; 97 | color: white; 98 | } 99 | } 100 | } 101 | 102 | .storyReel { 103 | display: flex; 104 | flex-direction: row; 105 | margin: 0.6rem; 106 | align-items: center; 107 | 108 | @include for-xl-devices { 109 | width: 90%; 110 | } 111 | 112 | @include for-large-devices { 113 | align-items: center; 114 | justify-content: center; 115 | } 116 | 117 | @include for-small-screens { 118 | font-size: smaller; 119 | } 120 | } 121 | 122 | -------------------------------------------------------------------------------- /src/components/StoryReel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AddRoundedIcon } from '../../utils/icons'; 3 | import Story from './Story'; 4 | 5 | import './StoryReel.scss'; 6 | 7 | interface StoryReelProps { 8 | photoUrl?: string; 9 | } 10 | 11 | function StoryReel({ photoUrl }: StoryReelProps): React.ReactElement { 12 | return ( 13 |
14 |
15 | profile-pic 16 |
17 |
18 | 19 |
20 |
Create Story
21 |
22 |
23 | {storyList.map(({ username, storyImage, profilePic }, index) => ( 24 | 25 | ))} 26 |
27 | ); 28 | } 29 | 30 | export default StoryReel; 31 | 32 | const storyList = [ 33 | { 34 | username: 'John Doe', 35 | storyImage: 36 | 'https://images.unsplash.com/photo-1611698529145-9fabdd4720c4?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80', 37 | profilePic: 38 | 'https://images.unsplash.com/photo-1603415526960-f7e0328c63b1?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80' 39 | }, 40 | { 41 | username: 'Blake Cheek', 42 | storyImage: 43 | 'https://images.unsplash.com/photo-1625339591418-46878dce5f69?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=80', 44 | profilePic: 45 | 'https://images.unsplash.com/photo-1517841905240-472988babdf9?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80' 46 | }, 47 | { 48 | username: 'Bella', 49 | storyImage: 50 | 'https://images.unsplash.com/photo-1625007387168-cb24505afb14?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80', 51 | profilePic: 52 | 'https://images.unsplash.com/photo-1558898479-33c0057a5d12?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80' 53 | }, 54 | { 55 | username: 'Adam Samson', 56 | storyImage: 57 | 'https://images.unsplash.com/flagged/photo-1569231290150-9c6200705c5b?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80', 58 | profilePic: 59 | 'https://images.unsplash.com/photo-1581803118522-7b72a50f7e9f?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=334&q=80' 60 | } 61 | ]; 62 | -------------------------------------------------------------------------------- /src/components/Widget/Widget.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .widget { 5 | display: flex; 6 | flex: 0.3; 7 | justify-content: flex-end; 8 | 9 | @include for-large-devices { 10 | display: none; 11 | } 12 | 13 | @include for-xl-devices { 14 | flex: 0.7; 15 | } 16 | 17 | .widgetIframe { 18 | overflow: hidden; 19 | 20 | &:hover { 21 | overflow-y: scroll; 22 | } 23 | } 24 | } 25 | 26 | .widgetIframe::-webkit-scrollbar { 27 | width: 12px; 28 | } 29 | 30 | .widgetIframe::-webkit-scrollbar-track { 31 | // -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); 32 | border-radius: 10px; 33 | } 34 | 35 | .widgetIframe::-webkit-scrollbar-thumb { 36 | border-radius: 10px; 37 | // -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5); 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Widget/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './Widget.scss'; 4 | 5 | function Widget(): React.ReactElement { 6 | return ( 7 |
8 | 19 |
20 | ); 21 | } 22 | 23 | export default Widget; 24 | -------------------------------------------------------------------------------- /src/components/index.tsx: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | import SideBar from './SideBar'; 3 | import Feed from './Feed'; 4 | import Story from './StoryReel'; 5 | import CreatePost from './CreatePost'; 6 | import PostAction from './PostAction'; 7 | import Widget from './Widget'; 8 | import PrivateRoute from './Routes/PrivateRoute'; 9 | import PublicRoute from './Routes/PublicRoute'; 10 | 11 | export { Header, SideBar, Feed, Story, CreatePost, PostAction, Widget, PrivateRoute, PublicRoute }; 12 | -------------------------------------------------------------------------------- /src/context/auth.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, useContext, useReducer, useState, useEffect } from 'react'; 2 | import { auth, provider } from '../utils/firebase'; 3 | import firebase from 'firebase'; 4 | 5 | interface AuthContextProps { 6 | children?: JSX.Element; 7 | } 8 | 9 | interface AuthContext { 10 | signin: () => Promise; 11 | currentUser: firebase.User | null; 12 | logout: () => Promise; 13 | isAuthenticated: boolean; 14 | } 15 | 16 | const AuthContext = createContext({} as AuthContext); 17 | 18 | export function useAuth(): AuthContext { 19 | return useContext(AuthContext); 20 | } 21 | 22 | export function AuthProvider({ children }: AuthContextProps): JSX.Element { 23 | const [currentUser, setCurrentUser] = useState(() => auth.currentUser); 24 | const [loading, setLoading] = useState(true); 25 | const [isAuthenticated, setIsAuthenticated] = useState(true); 26 | 27 | const signin = async ():Promise => { 28 | return auth.signInWithPopup(provider) 29 | }; 30 | 31 | const logout = async (): Promise => { 32 | return auth.signOut(); 33 | }; 34 | 35 | useEffect(() => { 36 | const unsubscribe = auth.onAuthStateChanged((user: firebase.User | null) => { 37 | setCurrentUser(user); 38 | setIsAuthenticated(!!user); 39 | setLoading(false); 40 | }); 41 | return unsubscribe; 42 | }, []); 43 | 44 | const value: AuthContext = { currentUser, logout, isAuthenticated, signin }; 45 | return {!loading && children}; 46 | } 47 | -------------------------------------------------------------------------------- /src/db/index.tsx: -------------------------------------------------------------------------------- 1 | import cuid from 'cuid'; 2 | import firebase from 'firebase'; 3 | import { firestore } from '../utils/firebase'; 4 | 5 | type Post = { 6 | username: string; 7 | profilePic: string; 8 | timestamp: Date; 9 | imgUrl?: string; 10 | text?: string; 11 | }; 12 | 13 | export const CreatePost = async ({ profilePic, username, imgUrl, text }: Post): Promise => { 14 | const id = cuid(); 15 | 16 | await firestore.collection('posts').doc(id).set({ 17 | profilePic: profilePic, 18 | username: username, 19 | timestamp: new Date(), 20 | image: imgUrl, 21 | text: text?.trim() 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /src/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | } 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 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /src/pages/Home/Home.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | 3 | .homeBody { 4 | display: flex; 5 | flex-direction: row; 6 | } 7 | -------------------------------------------------------------------------------- /src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Header, SideBar, Feed, Widget } from '../../components'; 3 | import { useAuth } from '../../context/auth'; 4 | 5 | import './Home.scss'; 6 | 7 | function Home(): React.ReactElement { 8 | const { currentUser } = useAuth(); 9 | const photo = currentUser?.photoURL ?? undefined; 10 | const user = currentUser?.displayName ?? null; 11 | 12 | return ( 13 | <> 14 |
15 |
16 | 17 | 18 | 19 |
20 | 21 | ); 22 | } 23 | 24 | export default Home; 25 | -------------------------------------------------------------------------------- /src/pages/Login/Login.scss: -------------------------------------------------------------------------------- 1 | @import '../../utils/variables'; 2 | @import '../../utils/mixins'; 3 | 4 | .login { 5 | height: 100%; 6 | background-color: $app-background; 7 | display: flex; 8 | flex-direction: row; 9 | align-items: center; 10 | justify-content: center; 11 | 12 | @include for-small-screens { 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: space-evenly; 16 | } 17 | 18 | .logo { 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | flex: 1; 23 | 24 | @include for-small-screens { 25 | flex: 0.1; 26 | } 27 | 28 | img { 29 | width: 28rem; 30 | 31 | @include for-mobile-only { 32 | width: 19rem; 33 | } 34 | } 35 | 36 | h3 { 37 | margin-top: -2rem; 38 | width: 22rem; 39 | font-weight: 400; 40 | 41 | @include for-mobile-only { 42 | width: 16rem; 43 | text-align: center; 44 | font-size: 1.1rem; 45 | margin-top: -1rem; 46 | } 47 | } 48 | } 49 | 50 | .loginCard { 51 | display: flex; 52 | flex: 0.8; 53 | align-items: flex-start; 54 | flex-direction: column; 55 | 56 | .card { 57 | display: flex; 58 | flex-direction: column; 59 | background-color: $white; 60 | padding: 3rem; 61 | border: 1px solid $border-color; 62 | border-radius: 0.4rem; 63 | 64 | @include for-mobile-only { 65 | padding: 1.8rem; 66 | } 67 | } 68 | 69 | .title { 70 | font-size: 2.1rem; 71 | font-weight: 600; 72 | @include fluid-type(1.8rem, 2.1rem) 73 | } 74 | 75 | .button { 76 | background-color: $primary; 77 | color: $white; 78 | align-self: bottom; 79 | margin-top: 3rem; 80 | padding: 0.4rem 4rem; 81 | transition: linear 200ms; 82 | 83 | &:hover { 84 | background-color: $primary-light; 85 | transform: translateY(-2px); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/pages/Login/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@material-ui/core'; 3 | import { useHistory } from 'react-router'; 4 | 5 | import './Login.scss'; 6 | import { useAuth } from '../../context/auth'; 7 | import { ROUTES } from '../../utils/constants'; 8 | import fbNameLogo from '../../assets/fbNameLogo.png'; 9 | 10 | function Login(): React.ReactElement { 11 | const { signin } = useAuth(); 12 | const history = useHistory(); 13 | 14 | const onSignIn = async () => { 15 | try { 16 | await signin(); 17 | history.push(ROUTES.HOME); 18 | } catch (error) { 19 | console.log(error.message); 20 | } 21 | }; 22 | 23 | return ( 24 |
25 |
26 | name logo 27 |

Facebook clone made using Typescript and React.

28 |
29 |
30 |
31 | Sign In 32 |

Sign in with your Google account

33 | 36 |
37 |
38 |
39 | ); 40 | } 41 | 42 | export default Login; 43 | -------------------------------------------------------------------------------- /src/pages/index.ts: -------------------------------------------------------------------------------- 1 | import Login from './Login'; 2 | import Home from './Home'; 3 | 4 | export { Login, Home }; 5 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/utils/_mixins.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | 3 | //breakpoints 4 | $xs: 400px; 5 | $sm: 576px; 6 | $md: 768px; 7 | $lg: 992px; 8 | $xl: 1025px; 9 | $xxl: 1200px; 10 | 11 | //Font-size 12 | $base-font-size: 16px; 13 | $default-min-font-size: 8px; 14 | $default-max-font-size: 32px; 15 | 16 | @mixin for-xxl-devices { 17 | @media (max-width: $xxl) { 18 | @content; 19 | } 20 | } 21 | 22 | @mixin for-xl-devices { 23 | @media (max-width: $xl) { 24 | @content; 25 | } 26 | } 27 | 28 | @mixin for-large-devices { 29 | @media (max-width: $lg) { 30 | @content; 31 | } 32 | } 33 | 34 | @mixin for-small-screens { 35 | @media (max-width: $md) { 36 | @content; 37 | } 38 | } 39 | 40 | @mixin for-mobile-only { 41 | @media only screen and (max-width: $sm) and (orientation: portrait) { 42 | @content; 43 | } 44 | } 45 | 46 | @mixin fluid-type( 47 | $min-font-size: $default-min-font-size, 48 | $max-font-size: $default-max-font-size, 49 | $lower-range: $xs, 50 | $upper-range: $xxl 51 | ) { 52 | font-size: calc( 53 | #{$min-font-size} + #{( 54 | math.div($max-font-size, ($max-font-size * 0 + 1)) - math.div($min-font-size, ($min-font-size * 0 + 1)) 55 | )} * math.div((100vw - #{$lower-range}), #{( 56 | math.div($upper-range, ($upper-range * 0 + 1)) - math.div($lower-range, ($lower-range * 0 + 1)) 57 | )}) 58 | ); 59 | 60 | @media screen and (max-width: $lower-range) { 61 | font-size: $min-font-size; 62 | } 63 | 64 | @media screen and (min-width: $upper-range) { 65 | font-size: $max-font-size; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/utils/_variables.scss: -------------------------------------------------------------------------------- 1 | //main theme 2 | $primary: #2d88ff; 3 | $primary-light: #5098f7; 4 | // $primary-light: #7995cd; 5 | $secondary: #a6b8de; 6 | $secondary-light: #c8d3e4; 7 | 8 | $white: #fff; 9 | $black: #000; 10 | 11 | //background - light 12 | $app-background: #f1f2f5; 13 | $component-background: #fff; 14 | $comment-background: #f0f2f5; 15 | $comment-text: #65676b; 16 | $icon: #4A4A4A; 17 | $box-shadow: #8D949E; 18 | $button-hover: #EBEDF0; 19 | $button-background: #E4E6EB; 20 | $button-icon: #000; 21 | $sec-button-hover: #DADDE1; 22 | $border-color: #eff2f5; 23 | 24 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export enum ROUTES { 2 | HOME = '/home', 3 | SIGNIN = '/' 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/firebase.ts: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase/app'; 2 | import 'firebase/firestore'; 3 | import 'firebase/auth'; 4 | 5 | import config from '../config.json'; 6 | 7 | const firebaseConfig = config.firebase; 8 | 9 | firebase.initializeApp(firebaseConfig); 10 | 11 | export type Auth = firebase.auth.Auth; 12 | export type Firestore = firebase.firestore.Firestore; 13 | export type GoogleAuthProvider = firebase.auth.GoogleAuthProvider; 14 | export type DocumentData = firebase.firestore.DocumentData; 15 | export type UnsubscribeFn = () => void; 16 | export type DocumentSnapshot = firebase.firestore.DocumentSnapshot; 17 | export type SnapshotFn = (snapshot: DocumentSnapshot) => void; 18 | 19 | export const firestore: Firestore = firebase.firestore(); 20 | export const auth: Auth = firebase.auth(); 21 | export const provider: GoogleAuthProvider = new firebase.auth.GoogleAuthProvider(); 22 | -------------------------------------------------------------------------------- /src/utils/icons.ts: -------------------------------------------------------------------------------- 1 | import SearchIcon from '@material-ui/icons/Search'; 2 | import HomeRoundedIcon from '@material-ui/icons/HomeRounded'; 3 | import FlagRoundedIcon from '@material-ui/icons/FlagRounded'; 4 | import SubscriptionsRoundedIcon from '@material-ui/icons/SubscriptionsRounded'; 5 | import StorefrontRoundedIcon from '@material-ui/icons/StorefrontRounded'; 6 | import SupervisedUserCircleRoundedIcon from '@material-ui/icons/SupervisedUserCircleRounded'; 7 | import AddRoundedIcon from '@material-ui/icons/AddRounded'; 8 | import NotificationsRoundedIcon from '@material-ui/icons/NotificationsRounded'; 9 | import ArrowDropDownRoundedIcon from '@material-ui/icons/ArrowDropDownRounded'; 10 | import LocalHospitalRoundedIcon from '@material-ui/icons/LocalHospitalRounded'; 11 | import GroupRoundedIcon from '@material-ui/icons/GroupRounded'; 12 | import ChatRoundedIcon from '@material-ui/icons/ChatRounded'; 13 | import VideoLibraryRoundedIcon from '@material-ui/icons/VideoLibraryRounded'; 14 | import VideocamRoundedIcon from '@material-ui/icons/VideocamRounded'; 15 | import PhotoLibraryRoundedIcon from '@material-ui/icons/PhotoLibraryRounded'; 16 | import InsertEmoticonOutlinedIcon from '@material-ui/icons/InsertEmoticonOutlined'; 17 | import ThumbUpRoundedIcon from '@material-ui/icons/ThumbUpRounded'; 18 | import ChatBubbleOutlineRoundedIcon from '@material-ui/icons/ChatBubbleOutlineRounded'; 19 | import NearMeRoundedIcon from '@material-ui/icons/NearMeRounded'; 20 | import ExitToAppRoundedIcon from '@material-ui/icons/ExitToAppRounded'; 21 | import SendRoundedIcon from '@material-ui/icons/SendRounded'; 22 | 23 | export { 24 | SearchIcon, 25 | HomeRoundedIcon, 26 | FlagRoundedIcon, 27 | SubscriptionsRoundedIcon, 28 | SupervisedUserCircleRoundedIcon, 29 | AddRoundedIcon, 30 | ArrowDropDownRoundedIcon, 31 | NotificationsRoundedIcon, 32 | StorefrontRoundedIcon, 33 | LocalHospitalRoundedIcon, 34 | GroupRoundedIcon, 35 | ChatRoundedIcon, 36 | VideoLibraryRoundedIcon, 37 | VideocamRoundedIcon, 38 | PhotoLibraryRoundedIcon, 39 | InsertEmoticonOutlinedIcon, 40 | ThumbUpRoundedIcon, 41 | ChatBubbleOutlineRoundedIcon, 42 | NearMeRoundedIcon, 43 | ExitToAppRoundedIcon, 44 | SendRoundedIcon 45 | }; 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import reactRefresh from '@vitejs/plugin-react-refresh'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [reactRefresh()], 7 | build: { 8 | outDir: 'build' 9 | } 10 | }); 11 | --------------------------------------------------------------------------------