February 17, 2021

Building a Whatsapp Web clone with React , Express and Firebase - Part 2

In this second part , we are going to continue where we left off on building our Whatsapp web clone . At this point we can login to our app and see a message Welcome to whatsapp Web .

Let’s continue by building the homepage once a user has login . We will have two main components , the sidebar , which will host our contacts and a chat component for conversation .

Go on inside pages , create a new page called HomePage.js as below: -

import React from "react";
import NoChatSelected from "../../components/NoChatSelected/NoChatSelected";
import Sidebar from "../../components/sidebar/Sidebar";
import "./Home.css";

const HomePage = () => {
  return (
    <div style={{ display: "flex" }}>
      <Sidebar />
      <NoChatSelected />
    </div>
  );
};

export default HomePage;

A SideBar component will host our contacts as well as top profile info while NoChatSelected will display iitial screen before a contact has been selected .

Now go to LoginPage and in condition to check if user is logged in , remove h1 and import and add our newly created HomePage as below:-

  {isLoggedIn && <HomePage />}

Now you should see an error

./src/pages/HomePage/HomePage.js
Module not found: Can't resolve '../../components/NoChatSelected/NoChatSelected' in '/Users/robert/projects/clones/whatsapp-web-clone/client/src/pages/HomePage'

Let’s create NoChatSelected component . Create a new folder called components , inside it , create a folder called NoChatSelected and inside , two files NoChatSelected.js and NoChatSelected.css as below : -

You can download connection.jpg file here

import React from "react";
import connectionImg from "../../assets/connection.jpg";
import "./NoChatSelected.css";

const NoChatSelected = () => {
  return (
    <div className="NoChatSelected">
      <img src={connectionImg} alt="Connection" />
      <div className="NoChatSelected__description">
        <h1>Keep your phone connected</h1>
        <p>
          WhatsApp connects to your phone to sync messages. To reduce data
          usage, connect your phone to Wi-Fi.
        </p>
      </div>
    </div>
  );
};

export default NoChatSelected;
.NoChatSelected {
  display: flex;
  flex-direction: column;
  flex: 0.7;
  background: rgb(248, 249, 250);
  height: 100vh;
  padding-top: 100px;
}

.NoChatSelected img {
  width: 250px;
  height: 250px;
  margin: 0 auto;
}
.NoChatSelected h1 {
  margin-top: 28px;
  font-size: 32px;
  color: rgb(82, 82, 82);
}
.NoChatSelected p {
  margin-top: 18px;
  font-size: 14px;
  line-height: 20px;
  color: rgba(0, 0, 0, 0.45);
}

.NoChatSelected__description {
  text-align: center;
  width: 80%;
  max-width: 460px;
  margin: 0 auto;
}

Now you should see the error below:-

./src/pages/HomePage/HomePage.js
Module not found: Can't resolve '../../components/sidebar/Sidebar' in '/Users/robert/projects/clones/whatsapp-web-clone/client/src/pages/HomePage'

Same way , let’s go and create our Sidebar component , inside components folder ,crate a new folder called Sidebar and inside it , Sidebar.js file and Sidebar.css file as below:-

import React, { useState } from "react";
import Menu from "@material-ui/core/Menu";
import MenuItem from "@material-ui/core/MenuItem";
import ChatIcon from "@material-ui/icons/Chat";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import DonutLargeIcon from "@material-ui/icons/DonutLarge";
import MoreVertIcon from "@material-ui/icons/MoreVert";
import { IconButton } from "@material-ui/core";
import { SearchOutlined } from "@material-ui/icons";
import "./Sidebar.css";

const Sidebar = () => {
  const [anchorEl, setAnchorEl] = useState(null);
  const handleClick = (event) => {
    setAnchorEl(event.currentTarget);
  };

  const handleClose = () => {
    setAnchorEl(null);
  };

  return (
    <div className="sidebar">
      <div className="sidebar__header">
        <div className="sidebar__header_left">
          <IconButton>
            <AccountCircleIcon />
          </IconButton>
          Username Here
        </div>
        <div className="sidebar__header_right">
          <IconButton>
            <DonutLargeIcon />
          </IconButton>
          <IconButton>
            <ChatIcon />
          </IconButton>
          <IconButton onClick={handleClick}>
            <MoreVertIcon />
          </IconButton>

          <Menu
            id="simple-menu"
            anchorEl={anchorEl}
            keepMounted
            open={Boolean(anchorEl)}
            onClose={handleClose}
          >
            <MenuItem onClick={handleClose}>New Group</MenuItem>
            <MenuItem onClick={handleClose}>Create a room</MenuItem>
            <MenuItem onClick={handleClose}>Profile</MenuItem>
            <MenuItem onClick={handleClose}>Archived</MenuItem>
            <MenuItem onClick={handleClose}>Starred</MenuItem>
            <MenuItem onClick={handleClose}>Settings</MenuItem>
            <MenuItem onClick={handleClose}>Logout</MenuItem>
          </Menu>
        </div>
      </div>

      <div className="sidebar__search">
        <div className="sidebar__searchContainer">
          <IconButton>
            <SearchOutlined />
          </IconButton>
          <input type="text" placeholder="Search or start new chat" />
        </div>
      </div>

      <div className="sidebar__chats">
        <p>Chat User</p>
      </div>
    </div>
  );
};
export default Sidebar;
.sidebar {
  display: flex;
  flex-direction: column;
  flex: 0.3;
  border-right: 1px solid lightgray;
}

.sidebar__header {
  background: rgb(237, 237, 237);
  display: flex;
  justify-content: space-between;
  padding: 10px 16px;
  min-width: 10vw;
}

.sidebar__header_right {
  margin-left: auto;
}
.sidebar__header_right > .MuiSvgIcon-root {
  margin-right: 2vw;
  font-size: 22px !important;
}
.sidebar__search {
  display: flex;
  align-items: center;
  background: rgb(246, 246, 246);
  height: 49px;
  padding: 10px;
}
.sidebar__searchContainer {
  display: flex;
  align-items: center;
  width: 100%;
  height: 35px;
  border-radius: 18px;
  background: white;
}

.sidebar__searchContainer button {
  margin-left: 10px;
  width: 24px;
  height: 24px;
}
.sidebar__searchContainer input {
  display: flex;
  align-items: center;
  border: none;
  height: 35px;
  outline-width: 0;
  right: 14px;
  left: 12px;
  padding-right: 32px;
  padding-left: 5px;
}
.sidebar__chats {
  display: flex;
  border-right: 1px solid rgba(0, 0, 0, 0.03);
  height: 100%;
  flex-direction: column;
}

If you notice, we have use Material-Ui library , go on and install it .

npm install @material-ui/core

npm install @material-ui/icons

Now , if everything goes well , you should have a screen as below : -

Homepage Whatsapp Web 01

Now let’s add a contact component to replace chat user text , the component will have our avatar , user mobile and last message . Go to components page and create a new folder called Contact and inside it two files Contact.js and Contact.css as below:-

import React from "react";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import "./Contact.css";

const Contact = ({ user }) => {
  return (
    <div className="contact">
      <div className="contact__avatar">
        <AccountCircleIcon />
      </div>
      <div className="contact__text">
        <h2>User Mobile</h2>
        <p>Last message here</p>
      </div>
    </div>
  );
};

export default Contact;
.contact {
  position: relative;
  display: flex;
  flex-direction: row;
  height: 72px;
  pointer-events: all;
  border-bottom: 1px solid rgba(74, 74, 74, 0.2);
  padding-top: 10px;
  width: 100%;
  cursor: pointer;
}
.contact:hover {
  background-color: rgb(245, 245, 245);
}

.contact__avatar {
  border-radius: 50%;
  width: 50px;
  height: 50px;
  padding: 0 15px 0 13px;
}
.contact__avatar .MuiSvgIcon-root {
  font-size: 48px;
  color: #e6e6e6;
}
.contact__avatar img {
  border-radius: 50%;
}
.contact__text {
  flex-basis: auto;
  flex-direction: column;
  flex-grow: 1;
  justify-content: center;
  min-width: 0;
  padding-top: 5px;
  padding-right: 10px;
}
.contact__text h2 {
  font-size: 16px;
  margin: 0;
}
.contact__text p {
  margin-top: 3px;
  font-size: 14px;
  flex-grow: 1;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: rgba(0, 0, 0, 0.6);
}

Now , let’s go to Sidebar.js file and replace Chat User paragraph with our contact component .

<div className="sidebar__chats">
        <Contact />
 </div>

Now , we should have a page like below : - Homepage Whatsapp Web 02

Now let’s add our last component before starting managing state as well as building the backend . When a user click on any contact , we should navigate to chat Component . Let’s go on and create our chat component , inside components folder , create a folder called Chat and inside it , Chat.js and Chat.css as below : -

import React from "react";
import SentimentSatisfiedIcon from "@material-ui/icons/SentimentSatisfied";
import { IconButton } from "@material-ui/core";
import AttachFileIcon from "@material-ui/icons/AttachFile";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import KeyboardVoiceIcon from "@material-ui/icons/KeyboardVoice";
import SearchIcon from "@material-ui/icons/Search";
import MoreVertIcon from "@material-ui/icons/MoreVert";
import "./Chat.css";

const Chat = () => {
  return (
    <div className="chat">
      <div className="chat__header">
        <div className="chat__headerLeft">
          <AccountCircleIcon />

          <h3>Sender Mobile</h3>
        </div>
        <div className="chat__headerRight">
          <IconButton>
            <SearchIcon />
          </IconButton>
          <IconButton>
            <MoreVertIcon />
          </IconButton>
        </div>
      </div>
      <div className="chat__body">
        <div className="chat__message chat__messageIn">
          <div className="chat__messageCotainer">
            <span className="chat__messageContent">
              Hello , how are you doing?
            </span>
            <span class="chat__messageTime">18:22</span>
          </div>
        </div>

        <div className="chat__message chat__messageOut ">
          <div className="chat__messageCotainer">
            <span className="chat__messageContent">I am doing great :-)</span>
            <span class="chat__messageTime">18:22</span>
          </div>
        </div>
      </div>

      <div className="chat_footer">
        <SentimentSatisfiedIcon />
        <AttachFileIcon />
        <input type="text" placeholder="Type a message" />

        <KeyboardVoiceIcon />
      </div>
    </div>
  );
};

export default Chat;

You can download chat-background.jpg here

.chat {
  display: flex;
  flex-direction: column;
  flex: 0.7;
  background: rgb(248, 249, 250);
}
.chat__header {
  display: flex;
  align-items: center;
  background: rgb(237, 237, 237);
  padding: 10px 16px;
}
.chat__headerLeft {
  display: flex;
  flex: 1;
  align-items: center;
}

.chat__headerLeft h3 {
  overflow: hidden;
  color: black;
  font-weight: 400;
  font-size: 16px;
  line-height: 21px;
  white-space: nowrap;
  text-overflow: ellipsis;
  margin-left: 3px;
}

.chat__headerRight {
  display: flex;
  margin-left: 0 auto;
  justify-content: flex-end;
}
.chat__body {
  background: url("../../assets/images/chat-background.jpg");
  height: 100%;
  min-height: calc(100vh - 130px);
  overflow: scroll;
}
.chat__message {
  padding-right: 6%;
  padding-left: 6%;
  margin-bottom: 12px;
  display: flex;
  flex-direction: column;
  font-size: 14.2px;
  line-height: 19px;
  color: rgb(48, 48, 48);
  box-shadow: 0 1px 0.5px rgba(var(--shadow-rgb), 0.13);
  white-space: pre-wrap;
  margin: 10px;
}
.chat__messageCotainer {
  background-color: white;
  padding: 6px 7px 8px 9px;
  border-radius: 7.5px;
}
.chat__messageContent {
  display: inline-block;
}

.chat__messageTime {
  float: right;
  margin: 10px 0 -5px 4px;
  height: 15px;
  color: rgba(0, 0, 0, 0.45);
  font-size: 11px;
}

.chat__messageIn {
  align-items: flex-start;
}
.chat__messageOut {
  align-items: flex-end;
}
.chat__messageOut .chat__messageCotainer {
  background-color: rgb(220, 248, 198);
}

.chat_footer {
  display: flex;
  align-items: center;
  position: relative;
  z-index: 1;
  flex: none;
  order: 3;
  box-sizing: border-box;
  width: 100%;
  min-height: 62px;
  border-left: 1px solid rgba(0, 0, 0, 0.8);
}

.chat_footer input {
  margin: 5px 10px;
  padding: 9px 12px 11px;
  background-color: white;
  border: 1px solid #ccc;
  border-radius: 21px;
  width: 100%;
}

Now , back to our contact component , we want when someone click’s the contact to display our chat component . For now we will use useState , go ahead inside Homepage.js file ,change to below : -

import React, { useState } from "react";
import Chat from "../../components/Chat/Chat";
import NoChatSelected from "../../components/NoChatSelected/NoChatSelected";
import Sidebar from "../../components/Sidebar/Sidebar";

const HomePage = () => {
  const [canShowChat, setCanShowChat] = useState(false);
  return (
    <div style={{ display: "flex" }}>
      <Sidebar setCanShowChat={setCanShowChat} />
      {canShowChat ? <Chat /> : <NoChatSelected />}
    </div>
  );
};

export default HomePage;

In the above code , we will set canShowChat to false and pass a function to display the chat down in our component tree up to Contact.js file while inside Contact.js file we will have something like below:-

import React from "react";
import AccountCircleIcon from "@material-ui/icons/AccountCircle";
import "./Contact.css";

const Contact = ({ setCanShowChat }) => {
  return (
    <div className="contact" onClick={() => setCanShowChat(true)}>
      <div className="contact__avatar">
        <AccountCircleIcon />
      </div>
      <div className="contact__text">
        <h2>User Mobile</h2>
        <p>Last message here</p>
      </div>
    </div>
  );
};

export default Contact;

Now , when you click the contact you should see a screen like below: -

Homepage Whatsapp Web 03

At this point , we are ready to start sending our chats to backend , remove prop drilling by using state management with React Context API as well as having real time chat experience with Pusher.io. We will do all that in the final part of this tutorial series .

see you in the next post , stay positive and keep coding :-)

Buy Me A Coffee

Robert Rutenge
Front-end Developer | ReactJS enthusiast | Problem Solver . Find me on twitter and Github