├── README.md ├── _config.yml ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── src ├── setupTests.js ├── App.css ├── StateProvider.js ├── reportWebVitals.js ├── index.css ├── reducer.js ├── SidebarChat.css ├── Login.css ├── index.js ├── firebase.js ├── App.js ├── Sidebar.css ├── Login.js ├── SidebarChat.js ├── Chat.css ├── Sidebar.js └── Chat.js ├── .gitignore └── package.json /README.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanBinu007/React-WhatsApp/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanBinu007/React-WhatsApp/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlanBinu007/React-WhatsApp/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .app{ 2 | display: grid; 3 | place-items: center; 4 | background-color: #dadbd3; 5 | height: 100vh; 6 | } 7 | .app_body{ 8 | display: flex; 9 | background-color: #ededed; 10 | height: 90vh; 11 | width: 90vw; 12 | margin-top: -50px; 13 | box-shadow: -1px 4px 20px -6px rgba(0, 0, 0, 0.2); 14 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/StateProvider.js: -------------------------------------------------------------------------------- 1 | import React, {createContext,useContext,useReducer} from 'react'; 2 | 3 | export const StateContext = createContext(); 4 | 5 | export const StateProvider = ({ reducer, initialState, children}) => ( 6 | 7 | {children} 8 | 9 | ); 10 | 11 | export const useStateValue = () => useContext(StateContext); -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | . { 2 | margin: 0; 3 | } 4 | body { 5 | margin: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 7 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 8 | sans-serif; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | code { 14 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 15 | monospace; 16 | } 17 | -------------------------------------------------------------------------------- /src/reducer.js: -------------------------------------------------------------------------------- 1 | export const initialState = { 2 | user:null, 3 | }; 4 | 5 | export const actionTypes = { 6 | SET_USER : "SET_USER", 7 | }; 8 | 9 | const reducer = (state, action) => { 10 | switch(action.type){ 11 | case actionTypes.SET_USER: 12 | return { 13 | ...state, 14 | user: action.user, 15 | }; 16 | 17 | default: 18 | return state; 19 | } 20 | }; 21 | 22 | export default reducer; -------------------------------------------------------------------------------- /src/SidebarChat.css: -------------------------------------------------------------------------------- 1 | .sidebarChat{ 2 | display: flex; 3 | padding: 20px; 4 | cursor: pointer; 5 | border-bottom: 1px solid #f6f6f6; 6 | } 7 | 8 | .sidebarChat:hover{ 9 | background-color: #ebebeb; 10 | } 11 | 12 | .sidebarChat_info > h2{ 13 | font-size: 16px; 14 | margin: 0px; 15 | } 16 | 17 | .sidebarChat_info > p{ 18 | margin: 0px; 19 | } 20 | 21 | .sidebarChat_info{ 22 | margin-left: 15px; 23 | } 24 | 25 | .add-new-chat-title{ 26 | margin: 0px 0px 0px 10px; 27 | } 28 | 29 | a { 30 | text-decoration: none; 31 | color: black; 32 | } -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/Login.css: -------------------------------------------------------------------------------- 1 | .login{ 2 | background-image: linear-gradient( 3 | #0f2027, #2c5364); 4 | height: 100vh; 5 | width: 100vw; 6 | display: grid; 7 | place-items: center; 8 | } 9 | 10 | .login_container{ 11 | padding: 100px; 12 | text-align: center; 13 | background-color: rgb(104, 89, 89); 14 | border-radius: 10px; 15 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px; 16 | } 17 | 18 | .login_container > img{ 19 | object-fit: contain; 20 | height: 100px; 21 | margin-bottom: 40px; 22 | } 23 | 24 | .login_container > button{ 25 | margin-top: 50px; 26 | text-transform: inherit !important; 27 | background-color: #0a8d48 !important; 28 | color: white; 29 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | import reducer,{initialState} from './reducer'; 7 | import {StateProvider} from './StateProvider'; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ); 17 | 18 | // If you want to start measuring performance in your app, pass a function 19 | // to log results (for example: reportWebVitals(console.log)) 20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 21 | reportWebVitals(); 22 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | import firebase from "firebase"; 2 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 3 | //GET Below Settings from Firebase > Project Overview > Settings > General > Your apps > Firebase SDK snippet > Config 4 | const firebaseConfig = { 5 | apiKey: "AIzaSyDZzOMYhLjuSZTbxpnv59DbroQ51PwbG1g", 6 | authDomain: "alanbinu-whatsapp.firebaseapp.com", 7 | projectId: "alanbinu-whatsapp", 8 | storageBucket: "alanbinu-whatsapp.appspot.com", 9 | messagingSenderId: "341745951598", 10 | appId: "1:341745951598:web:be9c8a90f37e7f9bf9977f", 11 | measurementId: "G-LSCGXMSBRC", 12 | }; 13 | 14 | const firebaseApp = firebase.initializeApp(firebaseConfig); 15 | const db = firebaseApp.firestore(); 16 | const auth = firebaseApp.auth(); 17 | const provider = new firebase.auth.GoogleAuthProvider(); 18 | 19 | export { auth, provider }; 20 | export default db; 21 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import React, {useState} from 'react'; 3 | import Sidebar from './Sidebar'; 4 | import Chat from './Chat'; 5 | import Login from './Login'; 6 | import { BrowserRouter as Router,Switch, Route} from 'react-router-dom'; 7 | import {useStateValue} from './StateProvider'; 8 | 9 | function App() { 10 | const [{user}, dispatch] = useStateValue(); 11 | return ( 12 |
13 | {!user ? ( 14 | 15 | ):( 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | )} 30 | 31 |
32 | ); 33 | } 34 | 35 | export default App; 36 | -------------------------------------------------------------------------------- /src/Sidebar.css: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | flex: 0.35; 3 | } 4 | 5 | .sidebar_search{ 6 | display: flex; 7 | align-items: center; 8 | background-color: #f6f6f6; 9 | height: 39px; 10 | padding: 10px; 11 | } 12 | 13 | .sidebar_searchContainer > .MuiSvgIcon-root{ 14 | color: gray; 15 | padding: 10px; 16 | } 17 | 18 | .sidebar_searchContainer > input{ 19 | border:none; 20 | margin-left: 10px; 21 | } 22 | .sidebar_searchContainer{ 23 | display: flex; 24 | align-items: center; 25 | background-color: white; 26 | width: 100%; 27 | height: 35px; 28 | border-radius: 20px; 29 | } 30 | 31 | .sidebar_header{ 32 | display: flex; 33 | justify-content: space-between; 34 | padding: 20px; 35 | border-right: 1px solid lightgray; 36 | } 37 | 38 | .sidebar_headerRight{ 39 | display: flex; 40 | align-items: center; 41 | justify-content: space-between; 42 | min-width: 10vw; 43 | } 44 | 45 | .sidebar_headerRight > .MuiSvgIcon-root { 46 | margin-right: 2vw; 47 | font-size: 20px !important; 48 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "whatsapp-clone", 3 | "version": "0.1.0", 4 | "homepage": ".", 5 | "private": true, 6 | "dependencies": { 7 | "@material-ui/core": "^4.11.0", 8 | "@material-ui/icons": "^4.9.1", 9 | "@testing-library/jest-dom": "^5.11.6", 10 | "@testing-library/react": "^11.1.2", 11 | "@testing-library/user-event": "^12.2.2", 12 | "firebase": "^8.0.2", 13 | "react": "^17.0.1", 14 | "react-dom": "^17.0.1", 15 | "react-router-dom": "^5.2.0", 16 | "react-scripts": "4.0.0", 17 | "web-vitals": "^0.2.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Login.js: -------------------------------------------------------------------------------- 1 | import { Button } from '@material-ui/core'; 2 | import React from 'react'; 3 | import './Login.css'; 4 | import {auth,provider} from './firebase'; 5 | import { actionTypes } from './reducer'; 6 | import { useStateValue } from './StateProvider'; 7 | 8 | function Login() { 9 | const [{},dispatch] = useStateValue(); 10 | const signIn = () => { 11 | auth 12 | .signInWithPopup(provider) 13 | .then((result) => { 14 | dispatch({ 15 | type: actionTypes.SET_USER, 16 | user: result.user, 17 | }) 18 | }) 19 | .catch((error) => alert(error.message)); 20 | } 21 | return ( 22 |
23 |
24 | 25 |
26 |

Sign in to Whatsapp

27 |
28 | 29 |
30 |

Design by Alan Binu 💝

31 |
32 | ); 33 | } 34 | 35 | export default Login 36 | -------------------------------------------------------------------------------- /src/SidebarChat.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {Avatar} from "@material-ui/core"; 3 | import './SidebarChat.css'; 4 | import db from './firebase'; 5 | import {Link} from 'react-router-dom'; 6 | 7 | function SidebarChat({id,name,addNewChat}) { 8 | const [seed, setSeed] = useState(""); 9 | const [messages, setMessages] = useState(""); 10 | 11 | useEffect(() => { 12 | if(id){ 13 | db.collection('rooms').doc(id).collection('messages').orderBy('timestamp','desc').onSnapshot(snapshot => { 14 | setMessages(snapshot.docs.map((doc) => doc.data())) 15 | }) 16 | } 17 | }, [id]); 18 | 19 | useEffect(() => { 20 | setSeed(Math.floor(Math.random() * 5000)); 21 | }, []); 22 | 23 | const createChat = () => { 24 | const roomName = prompt("Please Enter Name for Chat"); 25 | 26 | if(roomName){ 27 | db.collection("rooms").add({ 28 | name: roomName 29 | }) 30 | } 31 | }; 32 | 33 | return !addNewChat ? ( 34 | 35 |
36 | 37 |
38 |

{name}

39 |

{messages[0]?.message}

40 |
41 |
42 | 43 | 44 | ) : ( 45 |
46 |

Add New Chat

47 |
48 | ) 49 | } 50 | 51 | export default SidebarChat 52 | -------------------------------------------------------------------------------- /src/Chat.css: -------------------------------------------------------------------------------- 1 | .chat{ 2 | flex: 0.65; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | .chat-room-name{ 8 | margin: 0px; 9 | } 10 | 11 | .chat-room-last-seen{ 12 | margin: 0; 13 | } 14 | 15 | .chat_header{ 16 | display: flex; 17 | padding: 20px; 18 | align-items: center; 19 | border-bottom: 1px solid lightgray; 20 | } 21 | 22 | .chat_headerInfo{ 23 | flex: 1; 24 | padding-left: 20px; 25 | } 26 | 27 | .chat_headerInfo > h3 { 28 | margin-bottom: 3px; 29 | font-weight: 500; 30 | } 31 | 32 | .chat_headerInfo > p { 33 | color:gray; 34 | } 35 | 36 | .chat_headerRight{ 37 | display: flex; 38 | justify-content: space-between; 39 | min-width: 100px; 40 | } 41 | 42 | .chat_body{ 43 | flex: 1; 44 | background-image: url('https://user-images.githubusercontent.com/15075759/28719144-86dc0f70-73b1-11e7-911d-60d70fcded21.png'); 45 | background-repeat: repeat; 46 | background-position: center; 47 | padding: 30px; 48 | overflow:scroll; 49 | } 50 | 51 | .chat_message{ 52 | position: relative; 53 | font-size: 16px; 54 | border-radius: 10px; 55 | padding: 10px; 56 | background-color: white; 57 | width: fit-content; 58 | } 59 | 60 | .chat_name{ 61 | position: absolute; 62 | top:-15px; 63 | font-weight: 800; 64 | font-size: xx-small; 65 | } 66 | 67 | .chat_timestemp{ 68 | margin-left: 10px; 69 | font-size: xx-small; 70 | } 71 | 72 | .chat_receiver{ 73 | margin-left: auto; 74 | background-color: #dcf8c6; 75 | } 76 | 77 | .chat_footer{ 78 | display: flex; 79 | align-items: center; 80 | justify-content: space-between; 81 | height: 62px; 82 | border: 1px solid lightgray; 83 | } 84 | 85 | .chat_footer > form{ 86 | flex: 1; 87 | display: flex; 88 | } 89 | 90 | .chat_footer > form > input{ 91 | flex: 1; 92 | border-radius: 30px; 93 | padding: 10px; 94 | border: none; 95 | margin: 0 10px 0px 10px; 96 | } 97 | 98 | .chat_footer > form > button{ 99 | display: none; 100 | } -------------------------------------------------------------------------------- /src/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, {useState,useEffect} from 'react'; 2 | import './Sidebar.css'; 3 | import {Avatar, IconButton} from "@material-ui/core"; 4 | import DonutLargeIcon from "@material-ui/icons/DonutLarge"; 5 | import ChatIcon from "@material-ui/icons/Chat"; 6 | import MoreVertIcon from "@material-ui/icons/MoreVert"; 7 | import {SearchOutlined} from "@material-ui/icons"; 8 | import SidebarChat from "./SidebarChat"; 9 | import db from './firebase'; 10 | import { useStateValue } from './StateProvider'; 11 | 12 | function Sidebar(props) { 13 | 14 | const [rooms, setRooms] = useState([]); 15 | const [{user},dispatch] = useStateValue(); 16 | 17 | useEffect(() => { 18 | const unsubscribe = db.collection('rooms').onSnapshot(snapshot => ( 19 | setRooms(snapshot.docs.map(doc => ( 20 | { 21 | id: doc.id, 22 | data: doc.data() 23 | } 24 | ) 25 | 26 | )) 27 | )); 28 | 29 | return () => { 30 | unsubscribe(); 31 | } 32 | },[]); 33 | 34 | return ( 35 |
36 |
37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 |
51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 | 59 | {rooms.map(room=> ( 60 | 61 | ))} 62 |
63 |
64 | ); 65 | } 66 | 67 | export default Sidebar; -------------------------------------------------------------------------------- /src/Chat.js: -------------------------------------------------------------------------------- 1 | import React, {useState,useEffect} from 'react'; 2 | import {Avatar, IconButton} from '@material-ui/core'; 3 | import {AttachFile, MoreVert, SearchOutlined} from '@material-ui/icons'; 4 | import MicIcon from '@material-ui/icons/Mic'; 5 | import InsertEmoticonIcon from '@material-ui/icons/InsertEmoticon'; 6 | import './Chat.css'; 7 | import { useParams } from 'react-router-dom'; 8 | import db from './firebase'; 9 | import firebase from 'firebase'; 10 | import {useStateValue} from "./StateProvider"; 11 | 12 | function Chat() { 13 | const [input, setInput] = useState(""); 14 | const [seed, setSeed] = useState(""); 15 | const {roomId} = useParams(); 16 | const [roomName, setRoomName] = useState(""); 17 | const [messages, setMessages] = useState([]); 18 | const [{user}, dispatch] = useStateValue(); 19 | 20 | useEffect(()=>{ 21 | if(roomId){ 22 | db.collection('rooms').doc(roomId).onSnapshot(snapshot => { 23 | setRoomName(snapshot.data().name); 24 | }); 25 | 26 | db.collection('rooms').doc(roomId).collection("messages").orderBy("timestamp","asc").onSnapshot(snapshot => { 27 | setMessages(snapshot.docs.map(doc => doc.data())) 28 | }); 29 | 30 | } 31 | },[roomId]) 32 | 33 | useEffect(() => { 34 | setSeed(Math.floor(Math.random() * 5000)); 35 | }, [roomId]); 36 | 37 | const sendMessage = (e) => { 38 | e.preventDefault(); 39 | db.collection('rooms').doc(roomId).collection('messages').add({ 40 | message: input, 41 | name: user.displayName, 42 | timestamp: firebase.firestore.FieldValue.serverTimestamp(), 43 | }) 44 | 45 | setInput(""); 46 | } 47 | 48 | return ( 49 |
50 |
51 | 52 |
53 |

{roomName}

54 |

55 | Last seen {" "} 56 | {new Date( 57 | messages[messages.length - 1]?. 58 | timestamp?.toDate() 59 | ).toUTCString()} 60 |

61 |
62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 | {messages.map(message => ( 77 |

78 | {message.name} 79 | {message.message} 80 | {new Date(message.timestamp?.toDate()).toUTCString()} 81 |

82 | ))} 83 |
84 |
85 | 86 |
87 | setInput(e.target.value)} type="text" placeholder="Type a message"/> 88 | 89 |
90 | 91 |
92 | 93 |
94 | ) 95 | } 96 | 97 | export default Chat 98 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 101 | 102 | 103 | --------------------------------------------------------------------------------