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