├── .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 | 
15 |
16 |
17 |
18 | ## Technologies used
19 | 
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 |
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 |
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 |

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 |
31 | )}
32 | {image && (
33 |
34 |

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 |

16 |
17 |
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 |
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 |

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 |
--------------------------------------------------------------------------------