├── public
├── _redirects
├── logo.png
├── robots.txt
├── apple-touch-icon.png
├── manifest.json
└── index.html
├── jsconfig.json
├── src
├── assets
│ ├── images
│ │ ├── women.jpeg
│ │ ├── women.png
│ │ ├── bg-chat-dark.png
│ │ ├── bg-chat-light.png
│ │ ├── emoji-sprite.png
│ │ ├── placeholder.jpeg
│ │ ├── intro-connection-dark.jpg
│ │ ├── intro-connection-light.jpg
│ │ ├── profile-picture-boy-1.jpeg
│ │ ├── profile-picture-boy-2.jpeg
│ │ ├── profile-picture-boy-3.jpeg
│ │ ├── profile-picture-girl-1.jpeg
│ │ ├── profile-picture-girl-2.jpeg
│ │ ├── profile-picture-girl-3.jpeg
│ │ └── profile-picture-girl-4.jpeg
│ ├── css
│ │ ├── index.css
│ │ ├── common.darktheme.css
│ │ ├── overrides.css
│ │ └── common.css
│ └── icons
│ │ └── index.jsx
├── utils
│ ├── formatTime.js
│ └── getRandomSentence.js
├── setupTests.js
├── App.test.js
├── components
│ ├── Icon
│ │ └── index.jsx
│ ├── OptionsButton
│ │ ├── styles
│ │ │ ├── darktheme.css
│ │ │ └── main.css
│ │ └── index.jsx
│ ├── Checkbox
│ │ ├── index.jsx
│ │ └── styles.css
│ ├── Loader
│ │ ├── index.jsx
│ │ └── styles
│ │ │ ├── darktheme.css
│ │ │ └── main.css
│ └── Sidebar
│ │ ├── Alert.jsx
│ │ ├── styles
│ │ ├── darktheme.css
│ │ └── main.css
│ │ ├── index.jsx
│ │ └── Contact.jsx
├── App.darktheme.css
├── reportWebVitals.js
├── index.css
├── pages
│ ├── Home
│ │ ├── styles
│ │ │ ├── darktheme.css
│ │ │ └── main.css
│ │ └── index.jsx
│ └── Chat
│ │ ├── components
│ │ ├── Search.jsx
│ │ ├── ChatSidebar.jsx
│ │ ├── Header.jsx
│ │ ├── ChatInput.jsx
│ │ ├── EmojiTray.jsx
│ │ ├── Convo.jsx
│ │ └── Profile.jsx
│ │ ├── index.jsx
│ │ └── styles
│ │ ├── darktheme.css
│ │ └── main.css
├── context
│ ├── socketContext.js
│ └── usersContext.js
├── index.js
├── App.css
├── App.js
└── data
│ └── contacts.js
├── .gitignore
├── README.md
└── package.json
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/public/logo.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "src"
4 | },
5 | "include": ["src", "test.js"]
6 | }
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/assets/images/women.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/women.jpeg
--------------------------------------------------------------------------------
/src/assets/images/women.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/women.png
--------------------------------------------------------------------------------
/src/assets/css/index.css:
--------------------------------------------------------------------------------
1 | @import url(./common.css);
2 | @import url(./overrides.css);
3 | @import url(./common.darktheme.css);
--------------------------------------------------------------------------------
/src/assets/images/bg-chat-dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/bg-chat-dark.png
--------------------------------------------------------------------------------
/src/assets/images/bg-chat-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/bg-chat-light.png
--------------------------------------------------------------------------------
/src/assets/images/emoji-sprite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/emoji-sprite.png
--------------------------------------------------------------------------------
/src/assets/images/placeholder.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/placeholder.jpeg
--------------------------------------------------------------------------------
/src/assets/images/intro-connection-dark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/intro-connection-dark.jpg
--------------------------------------------------------------------------------
/src/assets/images/intro-connection-light.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/intro-connection-light.jpg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-boy-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-boy-1.jpeg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-boy-2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-boy-2.jpeg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-boy-3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-boy-3.jpeg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-girl-1.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-girl-1.jpeg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-girl-2.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-girl-2.jpeg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-girl-3.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-girl-3.jpeg
--------------------------------------------------------------------------------
/src/assets/images/profile-picture-girl-4.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KarenOk/whatsapp-web-clone/HEAD/src/assets/images/profile-picture-girl-4.jpeg
--------------------------------------------------------------------------------
/src/utils/formatTime.js:
--------------------------------------------------------------------------------
1 | const formatTime = (timeString) => {
2 | let splitTimeString = timeString.split(":");
3 | return `${splitTimeString[0]}:${splitTimeString[1]}`;
4 | };
5 |
6 | export default formatTime;
7 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Whatsapp Clone",
3 | "name": "Whatsapp Web Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
9 |
--------------------------------------------------------------------------------
/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.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/components/Icon/index.jsx:
--------------------------------------------------------------------------------
1 | import Icons from "assets/icons";
2 |
3 | const allIcons = Icons;
4 |
5 | const Icon = ({ id, ...props }) => {
6 | const selectedIcon = allIcons[id];
7 | return selectedIcon ? selectedIcon(props) : null;
8 | };
9 |
10 | export default Icon;
11 |
--------------------------------------------------------------------------------
/src/components/OptionsButton/styles/darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .options-btn--pressed {
2 | background: #20272b;
3 | }
4 |
5 | .dark-theme .options-btn__options {
6 | background-color: rgb(42, 47, 50);
7 | }
8 |
9 | .dark-theme .options-btn__option {
10 | color: rgba(241, 241, 242, 0.92);
11 | }
12 |
13 | .dark-theme .options-btn__option:hover {
14 | background: #20272b;
15 | }
--------------------------------------------------------------------------------
/src/App.darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .app {
2 | background: #090F13;
3 | }
4 |
5 | .dark-theme .app::before {
6 | display: none;
7 | }
8 |
9 | .dark-theme .app__mobile-message {
10 | color: rgba(241, 241, 242, 0.92);
11 | }
12 |
13 | @media screen and (max-width: 500px) {
14 | .dark-theme .app::before {
15 | display: block;
16 | background: rgb(5, 97, 98);
17 | }
18 | }
--------------------------------------------------------------------------------
/.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/assets/css/common.darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .focus-visible {
2 | outline: 2px solid rgba(70, 108, 119, 0.3);
3 | }
4 |
5 | .dark-theme .header {
6 | background: rgb(42, 47, 50)
7 | }
8 |
9 | .dark-theme .search-wrapper {
10 | background: rgb(19, 28, 33);
11 | }
12 |
13 | .dark-theme .search {
14 | background: rgb(50, 55, 57);
15 | }
16 |
17 | .dark-theme .search__back-btn {
18 | color: inherit;
19 | }
--------------------------------------------------------------------------------
/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 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
4 | -webkit-font-smoothing: antialiased;
5 | -moz-osx-font-smoothing: grayscale;
6 | color: #4a4a4a;
7 | overflow: hidden;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
12 | }
--------------------------------------------------------------------------------
/src/pages/Home/styles/darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .home {
2 | background: rgb(38, 45, 49);
3 | border-bottom-color: #056162;
4 | }
5 |
6 | .dark-theme .home__title {
7 | color: rgba(241, 241, 242, 0.88);
8 | }
9 |
10 | .dark-theme .home__text {
11 | color: rgb(166, 168, 170);
12 | }
13 |
14 | .dark-theme .home__text:first-of-type {
15 | border-bottom-color: rgba(241, 241, 242, 0.11);
16 | }
17 |
18 | .dark-theme .home__link {
19 | color: rgb(0, 175, 156);
20 | }
--------------------------------------------------------------------------------
/src/components/Checkbox/index.jsx:
--------------------------------------------------------------------------------
1 | import Icon from "components/Icon";
2 | import React from "react";
3 | import "./styles.css";
4 |
5 | const Checkbox = ({ inputProps }) => {
6 | return (
7 |
8 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export default Checkbox;
22 |
--------------------------------------------------------------------------------
/src/context/socketContext.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext } from "react";
2 | import io from "socket.io-client";
3 |
4 | const SOCKET_URL = window.location.origin.includes("localhost")
5 | ? "http://localhost:5000"
6 | : "https://whatsapp-web-clone-backend.herokuapp.com/";
7 |
8 | const socket = io.connect(SOCKET_URL);
9 |
10 | const SocketContext = createContext();
11 |
12 | const useSocketContext = () => useContext(SocketContext);
13 |
14 | const SocketProvider = ({ children }) => {
15 | return (
16 | {children}
17 | );
18 | };
19 |
20 | export { useSocketContext, SocketProvider };
21 |
--------------------------------------------------------------------------------
/src/pages/Chat/components/Search.jsx:
--------------------------------------------------------------------------------
1 | import Icon from "components/Icon";
2 | import React from "react";
3 |
4 | const Search = () => {
5 | return (
6 | <>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Search for messages with Karen Okonkwo.
18 |
19 | >
20 | );
21 | };
22 |
23 | export default Search;
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Whatsapp Web Clone
2 |
3 | Clone of the current Whatsapp Web UI. Check out its [backend service here](https://github.com/KarenOk/whatsapp-web-clone-backend) which implements WebSocket communication.
4 |
5 | Built with React and Socket.IO .
6 |
7 | ## Run this project locally
8 |
9 | 1. Download and Install Node JS from https://nodejs.org/en/download/
10 | 2. In the root project directory, type `npm install` to install the project's dependencies.
11 | 3. Once installation is complete, type `npm start` to start the project in your local browser.
12 |
13 | _N.B: You will need to run the backend service to simulate getting responses when you send in a message._
14 |
--------------------------------------------------------------------------------
/src/pages/Chat/components/ChatSidebar.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "components/Icon";
3 |
4 | const ChatSidebar = ({ active, closeSidebar, heading, children }) => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 | {heading}
12 |
13 | {children}
14 |
15 | );
16 | };
17 |
18 | export default ChatSidebar;
19 |
--------------------------------------------------------------------------------
/src/components/Loader/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./styles/main.css";
3 | import Icon from "../Icon";
4 |
5 | const Loader = ({ done }) => {
6 | return (
7 |
8 |
9 |
10 |
11 |
14 |
Whatsapp
15 |
16 |
17 | End-to-end encrypted. Built by Karen Okonkwo.
18 |
19 |
20 | );
21 | };
22 |
23 | export default Loader;
24 |
--------------------------------------------------------------------------------
/src/components/Checkbox/styles.css:
--------------------------------------------------------------------------------
1 | .checkbox__input {
2 | position: fixed;
3 | opacity: 0;
4 | width: 0;
5 | }
6 |
7 | .checkbox__label {
8 | display: inline-flex;
9 | justify-content: center;
10 | align-items: center;
11 | border: 2px solid rgb(145, 145, 145);
12 | width: 20px;
13 | height: 20px;
14 | border-radius: 2px;
15 | color: white;
16 | cursor: pointer;
17 | transition: all 0.3s ease;
18 | }
19 |
20 | .checkbox__input:checked~.checkbox__label {
21 | background-color: #009688;
22 | border: 2px solid #009688;
23 | }
24 |
25 | .checkbox__icon {
26 | width: 92%;
27 | height: 100%;
28 | color: transparent;
29 | }
30 |
31 | .checkbox__input:checked~.checkbox__label .checkbox__icon {
32 | color: white;
33 | }
--------------------------------------------------------------------------------
/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 |
7 | import "./assets/css/index.css";
8 | import { UsersProvider } from "context/usersContext";
9 | import { SocketProvider } from "context/socketContext";
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 |
17 |
18 | ,
19 | document.getElementById("root")
20 | );
21 |
22 | // If you want to start measuring performance in your app, pass a function
23 | // to log results (for example: reportWebVitals(console.log))
24 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
25 | reportWebVitals();
26 |
--------------------------------------------------------------------------------
/src/components/Loader/styles/darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .loader {
2 | background: rgb(18.6, 28.22, 33.4);
3 | }
4 |
5 | .dark-theme .loader__logo-wrapper::after {
6 | background: linear-gradient( 90deg, rgba(18.6, 28.22, 33.4, .5) 0, rgba(18.6, 28.22, 33.4, .5) 33.33%, rgba(18.6, 28.22, 33.4, 0) 44.1%, rgba(18.6, 28.22, 33.4, 0) 55.8%, rgba(18.6, 28.22, 33.4, .5) 66.66%, rgba(18.6, 28.22, 33.4, .5));
7 | }
8 |
9 | .dark-theme .loader__logo {
10 | fill: rgb(108, 113, 117);
11 | }
12 |
13 | .dark-theme .loader__progress {
14 | background: rgb(42, 47, 50);
15 | }
16 |
17 | .dark-theme .loader__progress::after {
18 | background: rgb(12, 137, 124);
19 | }
20 |
21 | .dark-theme .loader__title {
22 | color: rgba(241, 241, 242, 0.88);
23 | }
24 |
25 | .dark-theme .loader__desc {
26 | color: rgba(241, 241, 242, 0.45)
27 | }
--------------------------------------------------------------------------------
/src/assets/css/overrides.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | padding: 0;
4 | margin: 0;
5 | font-family: inherit;
6 | font-weight: 400
7 | }
8 |
9 | a, a:active, a:hover {
10 | font-size: inherit;
11 | font-family: inherit;
12 | font-weight: inherit;
13 | text-decoration: none;
14 | color: inherit;
15 | }
16 |
17 | button {
18 | cursor: pointer;
19 | }
20 |
21 | button, input {
22 | border: none;
23 | background-color: transparent;
24 | }
25 |
26 | ul {
27 | list-style: none;
28 | }
29 |
30 | ::-webkit-scrollbar {
31 | width: 6px!important;
32 | height: 6px!important;
33 | }
34 |
35 | ::-webkit-scrollbar-thumb {
36 | background-color: rgba(0, 0, 0, .2);
37 | }
38 |
39 | ::-webkit-scrollbar-track {
40 | background: rgba(255, 255, 255, 0.1);
41 | }
42 |
43 | .dark-theme ::-webkit-scrollbar-thumb {
44 | background-color: rgba(255, 255, 255, 0.16);
45 | }
46 |
47 | .dark-theme ::-webkit-scrollbar-track {
48 | background-color: initial;
49 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "whatsapp-web-clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.11.4",
7 | "@testing-library/react": "^11.1.0",
8 | "@testing-library/user-event": "^12.1.10",
9 | "focus-visible": "^5.2.0",
10 | "react": "^17.0.2",
11 | "react-dom": "^17.0.2",
12 | "react-router-dom": "^5.2.0",
13 | "react-scripts": "4.0.3",
14 | "socket.io-client": "^4.2.0",
15 | "web-vitals": "^1.0.1"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/components/OptionsButton/styles/main.css:
--------------------------------------------------------------------------------
1 | @import url(darktheme.css);
2 | .options-btn {
3 | padding: 5px;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | }
8 |
9 | .options-btn--pressed {
10 | border-radius: 50%;
11 | background: #D5D5D5;
12 | }
13 |
14 | .options-btn__options {
15 | z-index: 1000;
16 | top: 40px;
17 | right: 0;
18 | position: absolute;
19 | width: 200px;
20 | padding: 10px 0;
21 | background-color: white;
22 | border-radius: 3px;
23 | box-shadow: 0 2px 5px 0 #00000042, 0 2px 10px 0 rgba(0, 0, 0, .16);
24 | transition: all 0.2s ease-in;
25 | opacity: 0;
26 | transform: scale(0);
27 | }
28 |
29 | .options-btn__options--right {
30 | right: unset;
31 | left: -210px;
32 | }
33 |
34 | .options-btn__options--active {
35 | opacity: 1;
36 | transform: scale(1);
37 | }
38 |
39 | .options-btn__option {
40 | padding: 15px 20px;
41 | color: #000000;
42 | font-size: 0.9rem;
43 | cursor: pointer;
44 | }
45 |
46 | .options-btn__option:hover {
47 | background: #f5f5f5;
48 | }
--------------------------------------------------------------------------------
/src/components/OptionsButton/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import Icon from "components/Icon";
3 | import "./styles/main.css";
4 |
5 | const OptionsBtn = ({
6 | className,
7 | iconId,
8 | iconClassName,
9 | ariaLabel,
10 | options = [],
11 | position = "left",
12 | showPressed = true,
13 | ...props
14 | }) => {
15 | const [showOptions, setShowOptions] = useState(false);
16 |
17 | return (
18 |
19 |
setShowOptions(!showOptions)}
25 | {...props}
26 | >
27 |
28 |
29 |
34 | {options.map((option, index) => (
35 |
36 | {option}
37 |
38 | ))}
39 |
40 |
41 | );
42 | };
43 |
44 | export default OptionsBtn;
45 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url(./App.darktheme.css);
2 | .app {
3 | width: 100%;
4 | min-height: 100vh;
5 | background: #dddbd1;
6 | position: relative;
7 | }
8 |
9 | .app::before {
10 | width: 100%;
11 | height: 120px;
12 | top: 0;
13 | left: 0;
14 | background: rgb(0, 150, 136);
15 | position: absolute;
16 | content: "";
17 | z-index: 1;
18 | }
19 |
20 | .app__mobile-message {
21 | display: none;
22 | }
23 |
24 | .app-content {
25 | width: 100%;
26 | height: 100vh;
27 | max-width: 1450px;
28 | margin: 0 auto;
29 | box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .06), 0 2px 5px 0 rgba(0, 0, 0, .2);
30 | position: relative;
31 | z-index: 100;
32 | display: flex;
33 | overflow: hidden;
34 | }
35 |
36 | @media screen and (max-width: 500px) {
37 | .app__mobile-message {
38 | padding-top: 200px;
39 | text-align: center;
40 | font-size: 1.2rem;
41 | display: block;
42 | }
43 | .app-content {
44 | display: none;
45 | }
46 | }
47 |
48 | @media screen and (min-width: 1450px) {
49 | .app {
50 | padding: 20px;
51 | }
52 | .app-content {
53 | height: calc(100vh - 40px);
54 | }
55 | }
--------------------------------------------------------------------------------
/src/pages/Home/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./styles/main.css";
3 | import Icon from "components/Icon";
4 | import introImgLight from "assets/images/intro-connection-light.jpg";
5 | import introImgDark from "assets/images/intro-connection-dark.jpg";
6 |
7 | const Home = () => {
8 | const darkTheme = document.body.classList.contains("dark-theme");
9 |
10 | return (
11 |
12 |
13 |
18 |
19 |
20 |
Keep your phone connected
21 |
22 | WhatsApp connects to your phone to sync messages. To reduce data usage,
23 | connect your phone to Wi-Fi.
24 |
25 |
26 |
27 |
28 | WhatsApp is available for Mac.{" "}
29 |
34 | {" "}
35 | Get it here
36 |
37 | .
38 |
39 |
40 |
41 | );
42 | };
43 |
44 | export default Home;
45 |
--------------------------------------------------------------------------------
/src/pages/Home/styles/main.css:
--------------------------------------------------------------------------------
1 | @import url(./darktheme.css);
2 | .home {
3 | background: #f8f9fa;
4 | padding: 20px;
5 | height: 100%;
6 | flex: 60%;
7 | display: flex;
8 | flex-direction: column;
9 | justify-content: center;
10 | align-items: center;
11 | text-align: center;
12 | border-bottom: 6px solid rgb(6, 215, 85);
13 | }
14 |
15 | .home__img-wrapper {
16 | width: 250px;
17 | height: 250px;
18 | margin-bottom: 20px;
19 | }
20 |
21 | .home__img {
22 | width: 100%;
23 | height: 100%;
24 | border-radius: 50%;
25 | }
26 |
27 | .home__title {
28 | color: #525252;
29 | font-size: 2rem;
30 | font-weight: 300;
31 | margin-bottom: 10px;
32 | }
33 |
34 | .home__text {
35 | color: rgba(0, 0, 0, 0.45);
36 | font-size: 0.85rem;
37 | font-weight: 500;
38 | max-width: 500px;
39 | line-height: 24px;
40 | display: flex;
41 | align-items: center;
42 | }
43 |
44 | .home__text:first-of-type {
45 | padding-bottom: 30px;
46 | border-bottom: 1px solid rgba(74, 74, 74, 0.08);
47 | }
48 |
49 | .home__text:last-of-type {
50 | padding-top: 30px;
51 | }
52 |
53 | .home__icon {
54 | display: inline-block;
55 | margin-right: 5px;
56 | }
57 |
58 | .home__link {
59 | color: rgb(7, 188, 76);
60 | }
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import "./App.css";
3 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
4 | import Loader from "./components/Loader";
5 | import Home from "./pages/Home";
6 | import Sidebar from "components/Sidebar";
7 | import Chat from "pages/Chat";
8 |
9 | const userPrefersDark =
10 | window.matchMedia &&
11 | window.matchMedia("(prefers-color-scheme: dark)").matches;
12 |
13 | function App() {
14 | const [appLoaded, setAppLoaded] = useState(false);
15 | const [startLoadProgress, setStartLoadProgress] = useState(false);
16 |
17 | useEffect(() => {
18 | if (userPrefersDark) document.body.classList.add("dark-theme");
19 | stopLoad();
20 | }, []);
21 |
22 | const stopLoad = () => {
23 | setStartLoadProgress(true);
24 | setTimeout(() => setAppLoaded(true), 3000);
25 | };
26 |
27 | if (!appLoaded) return ;
28 |
29 | return (
30 |
31 |
Only available on desktop 😊.
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/src/pages/Chat/components/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "components/Icon";
3 | import OptionsBtn from "components/OptionsButton";
4 |
5 | const Header = ({ user, openProfileSidebar, openSearchSidebar }) => {
6 | return (
7 |
44 | );
45 | };
46 |
47 | export default Header;
48 |
--------------------------------------------------------------------------------
/src/components/Sidebar/Alert.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "components/Icon";
3 |
4 | const alerts = [
5 |
6 |
7 |
8 |
9 |
10 |
Get notified of new messages
11 |
Turn on your notifications
12 |
13 |
,
14 |
15 |
16 |
17 |
18 |
19 |
Phone battery low
20 |
21 | Charge your phone to keep using Whatsapp.
22 |
23 |
24 |
,
25 | ,
44 | ];
45 | const randomAlert = alerts.sort(() => 0.5 - Math.random())[0];
46 |
47 | const Alert = () => {
48 | return <>{randomAlert}>;
49 | };
50 |
51 | export default Alert;
52 |
--------------------------------------------------------------------------------
/src/components/Sidebar/styles/darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .sidebar {
2 | border-right-color: #3C4247;
3 | }
4 |
5 | .dark-theme .sidebar__alert--warning {
6 | background: #d7a53b;
7 | }
8 |
9 | .dark-theme .sidebar__alert--info {
10 | background: #085373;
11 | }
12 |
13 | .dark-theme .sidebar__alert--danger {
14 | background: #a7494c;
15 | }
16 |
17 | .dark-theme .sidebar__alert-icon {
18 | color: rgba(241, 241, 242, 0.8)
19 | }
20 |
21 | .dark-theme .sidebar__alert-text:first-of-type {
22 | color: rgba(241, 241, 242, 0.96)
23 | }
24 |
25 | .dark-theme .sidebar__alert-text:last-of-type {
26 | color: rgba(241, 241, 242, 0.75);
27 | }
28 |
29 | .dark-theme .sidebar__alert--warning .sidebar__alert-text:first-of-type {
30 | color: rgba(9, 14, 17, 0.96);
31 | }
32 |
33 | .dark-theme .sidebar__alert--warning .sidebar__alert-text:last-of-type {
34 | color: rgba(9, 14, 17, 0.75);
35 | }
36 |
37 | .dark-theme .sidebar__contacts {
38 | border-top-color: rgb(36, 45, 50);
39 | background: rgb(19, 28, 33);
40 | }
41 |
42 | .dark-theme .sidebar-contact {
43 | border-bottom-color: #3C4247;
44 | }
45 |
46 | .dark-theme .sidebar-contact:hover {
47 | background: #323739;
48 | }
49 |
50 | .dark-theme .sidebar-contact__name {
51 | color: rgba(241, 241, 242, 0.92);
52 | }
53 |
54 | .dark-theme .sidebar-contact__time {
55 | color: rgba(241, 241, 242, 0.63)
56 | }
57 |
58 | .dark-theme .sidebar-contact__message-wrapper {
59 | color: rgba(241, 241, 242, 0.63);
60 | }
61 |
62 | .dark-theme .sidebar-contact__message--unread {
63 | color: rgb(212, 213, 215)
64 | }
65 |
66 | .dark-theme .sidebar-contact__icons>* {
67 | color: rgba(241, 241, 242, 0.63);
68 | }
69 |
70 | .dark-theme .sidebar-contact__unread {
71 | background-color: rgb(0, 175, 156);
72 | color: rgb(19, 28, 33);
73 | }
--------------------------------------------------------------------------------
/src/components/Loader/styles/main.css:
--------------------------------------------------------------------------------
1 | @import url(darktheme.css);
2 | .loader {
3 | background: rgb(240, 240, 240);
4 | min-height: 100vh;
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | align-items: center;
9 | }
10 |
11 | .loader__logo-wrapper {
12 | position: relative;
13 | }
14 |
15 | .loader__logo-wrapper::after {
16 | content: "";
17 | position: absolute;
18 | left: -100px;
19 | bottom: 0;
20 | top: 0;
21 | width: 100px;
22 | background: linear-gradient( 90deg, rgba(240, 240, 240, .5) 0, rgba(240, 240, 240, .5) 33.33%, rgba(240, 240, 240, 0) 44.1%, rgba(240, 240, 240, 0) 55.8%, rgba(240, 240, 240, .5) 66.66%, rgba(240, 240, 240, .5));
23 | animation: glisten 2s ease-in 0.8s infinite;
24 | }
25 |
26 | .loader__logo {
27 | z-index: 3;
28 | fill: rgb(191, 191, 191);
29 | width: 60px;
30 | height: 60px;
31 | }
32 |
33 | .loader__progress {
34 | width: 200px;
35 | max-width: 400px;
36 | height: 4px;
37 | width: 90%;
38 | margin: 20px auto 30px;
39 | background: rgb(230, 230, 230);
40 | position: relative;
41 | }
42 |
43 | .loader__progress::after {
44 | content: "";
45 | position: absolute;
46 | height: 100%;
47 | background: rgb(0, 216.5, 187.0288);
48 | width: 0%;
49 | transition: width 2s linear;
50 | }
51 |
52 | .loader__progress--done::after {
53 | width: 100%;
54 | }
55 |
56 | .loader__title {
57 | color: #525252;
58 | font-size: 1.1rem;
59 | font-weight: 500;
60 | margin-bottom: 10px;
61 | }
62 |
63 | .loader__desc {
64 | color: rgba(0, 0, 0, 0.25);
65 | font-size: 0.85rem;
66 | }
67 |
68 | .loader__icon {
69 | margin-right: 5px;
70 | }
71 |
72 | @keyframes glisten {
73 | from {
74 | transform: translateX(0);
75 | }
76 | to {
77 | transform: translateX(150px);
78 | }
79 | }
--------------------------------------------------------------------------------
/src/components/Sidebar/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./styles/main.css";
3 | import avatar from "assets/images/profile-picture-girl-1.jpeg";
4 | import Icon from "components/Icon";
5 | import Alert from "./Alert";
6 | import Contact from "./Contact";
7 | import OptionsBtn from "components/OptionsButton";
8 | import { useUsersContext } from "context/usersContext";
9 |
10 | const Sidebar = () => {
11 | const { users: contacts } = useUsersContext();
12 | return (
13 |
14 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | {contacts.map((contact, index) => (
57 |
58 | ))}
59 |
60 |
61 | );
62 | };
63 |
64 | export default Sidebar;
65 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
19 |
20 |
29 | (20) Whatsapp Clone
30 |
31 |
32 |
33 | You need to enable JavaScript to run this app.
34 |
35 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/src/context/usersContext.js:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react";
2 | import contacts from "data/contacts";
3 | import { useSocketContext } from "./socketContext";
4 |
5 | const UsersContext = createContext();
6 |
7 | const useUsersContext = () => useContext(UsersContext);
8 |
9 | const UsersProvider = ({ children }) => {
10 | const socket = useSocketContext();
11 |
12 | const [users, setUsers] = useState(contacts);
13 |
14 | const _updateUserProp = (userId, prop, value) => {
15 | setUsers((users) => {
16 | const usersCopy = [...users];
17 | let userIndex = users.findIndex((user) => user.id === userId);
18 | const userObject = usersCopy[userIndex];
19 | usersCopy[userIndex] = { ...userObject, [prop]: value };
20 | return usersCopy;
21 | });
22 | };
23 |
24 | const setUserAsTyping = (data) => {
25 | const { userId } = data;
26 | _updateUserProp(userId, "typing", true);
27 | };
28 |
29 | const setUserAsNotTyping = (data) => {
30 | const { userId } = data;
31 | _updateUserProp(userId, "typing", false);
32 | };
33 |
34 | const fetchMessageResponse = (data) => {
35 | setUsers((users) => {
36 | const { userId, response } = data;
37 |
38 | let userIndex = users.findIndex((user) => user.id === userId);
39 | const usersCopy = JSON.parse(JSON.stringify(users));
40 | const newMsgObject = {
41 | content: response,
42 | sender: userId,
43 | time: new Date().toLocaleTimeString(),
44 | status: null,
45 | };
46 |
47 | usersCopy[userIndex].messages.TODAY.push(newMsgObject);
48 |
49 | return usersCopy;
50 | });
51 | };
52 |
53 | useEffect(() => {
54 | socket.on("fetch_response", fetchMessageResponse);
55 | socket.on("start_typing", setUserAsTyping);
56 | socket.on("stop_typing", setUserAsNotTyping);
57 | }, [socket]);
58 |
59 | const setUserAsUnread = (userId) => {
60 | _updateUserProp(userId, "unread", 0);
61 | };
62 |
63 | const addNewMessage = (userId, message) => {
64 | let userIndex = users.findIndex((user) => user.id === userId);
65 | const usersCopy = [...users];
66 | const newMsgObject = {
67 | content: message,
68 | sender: null,
69 | time: new Date().toLocaleTimeString(),
70 | status: "delivered",
71 | };
72 |
73 | usersCopy[userIndex].messages.TODAY.push(newMsgObject);
74 | setUsers(usersCopy);
75 |
76 | socket.emit("fetch_response", { userId });
77 | };
78 |
79 | return (
80 |
81 | {children}
82 |
83 | );
84 | };
85 |
86 | export { useUsersContext, UsersProvider };
87 |
--------------------------------------------------------------------------------
/src/assets/css/common.css:
--------------------------------------------------------------------------------
1 | .underline {
2 | text-decoration: underline;
3 | }
4 |
5 | .cursor-pointer {
6 | cursor: pointer;
7 | }
8 |
9 | .pos-rel {
10 | position: relative;
11 | }
12 |
13 | .sb {
14 | display: flex;
15 | align-items: center;
16 | justify-content: space-between;
17 | }
18 |
19 | .flex-1 {
20 | flex: 1
21 | }
22 |
23 | .js-focus-visible :focus:not(.focus-visible) {
24 | outline: none;
25 | }
26 |
27 | .focus-visible {
28 | outline-color: rgba(129, 202, 231, 0.3);
29 | }
30 |
31 | .header {
32 | background: rgb(237, 237, 237);
33 | display: flex;
34 | justify-content: space-between;
35 | align-items: center;
36 | height: 60px;
37 | padding: 10px;
38 | /* Fix for height bug with chat sidebar */
39 | min-height: 60px;
40 | }
41 |
42 | .avatar {
43 | border-radius: 50%;
44 | height: 100%;
45 | width: 100%;
46 | object-fit: cover;
47 | }
48 |
49 | .emoji {
50 | background: url(../../assets/images/emoji-sprite.png) transparent;
51 | width: 40px;
52 | height: 40px;
53 | background-size: 400px;
54 | background-repeat: no-repeat;
55 | width: 50px;
56 | height: 50px;
57 | background-size: 500px;
58 | }
59 |
60 | /* Begin search input */
61 |
62 | .search-wrapper {
63 | padding: 7px 10px;
64 | height: 50px;
65 | background: #F6F6F6;
66 | position: relative;
67 | }
68 |
69 | .search-wrapper:focus-within {
70 | background: white;
71 | }
72 |
73 | .search {
74 | background: white;
75 | color: rgb(74, 74, 74);
76 | padding-left: 60px;
77 | border-radius: 18px;
78 | width: 100%;
79 | height: 100%;
80 | }
81 |
82 | .search::placeholder {
83 | color: rgb(153, 153, 153);
84 | }
85 |
86 | .search-icons {
87 | color: #919191;
88 | position: absolute;
89 | left: 20px;
90 | top: 50%;
91 | transform: translateY(-50%);
92 | width: 24px;
93 | height: 24px;
94 | overflow: hidden;
95 | }
96 |
97 | .search-icon, .search__back-btn {
98 | position: absolute;
99 | width: 100%;
100 | height: 100%;
101 | transition: all 0.8s ease;
102 | }
103 |
104 | .search-icon {
105 | opacity: 1;
106 | transition-delay: 0.3s;
107 | }
108 |
109 | .search__back-btn {
110 | opacity: 0;
111 | transition-delay: 0.3s;
112 | color: rgb(51, 183, 246);
113 | }
114 |
115 | .search-wrapper:focus-within .search-icon {
116 | opacity: 0;
117 | transition-delay: 0s;
118 | }
119 |
120 | .search-wrapper:focus-within .search__back-btn {
121 | transform: rotate(360deg);
122 | opacity: 1;
123 | transition-delay: 0s;
124 | }
125 |
126 | /* End search input */
--------------------------------------------------------------------------------
/src/components/Sidebar/Contact.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "components/Icon";
3 | import { Link } from "react-router-dom";
4 | import formatTime from "utils/formatTime";
5 | import { useUsersContext } from "context/usersContext";
6 |
7 | const Contact = ({ contact }) => {
8 | const { setUserAsUnread } = useUsersContext();
9 | const getLastMessage = () => {
10 | const messageDates = Object.keys(contact.messages);
11 | const recentMessageDate = messageDates[messageDates.length - 1];
12 | const messages = [...contact.messages[recentMessageDate]];
13 | const lastMessage = messages.pop();
14 | return lastMessage;
15 | };
16 |
17 | const lastMessage = getLastMessage(contact);
18 |
19 | return (
20 | setUserAsUnread(contact.id)}
24 | >
25 |
26 |
31 |
32 |
33 |
34 |
{contact.name}
35 |
36 | {formatTime(lastMessage.time)}
37 |
38 |
39 |
40 |
41 | {lastMessage.status && (
42 |
53 | )}
54 |
59 | {contact.typing ? typing... : lastMessage?.content}
60 |
61 |
62 |
63 | {contact.pinned && (
64 |
65 | )}
66 | {!!contact.unread && (
67 | {contact.unread}
68 | )}
69 |
70 |
74 |
75 |
76 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default Contact;
83 |
--------------------------------------------------------------------------------
/src/pages/Chat/components/ChatInput.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "components/Icon";
3 |
4 | const attachButtons = [
5 | { icon: "attachRooms", label: "Choose room" },
6 | { icon: "attachContacts", label: "Choose contact" },
7 | { icon: "attachDocument", label: "Choose document" },
8 | { icon: "attachCamera", label: "Use camera" },
9 | { icon: "attachImage", label: "Choose image" },
10 | ];
11 |
12 | const ChatInput = ({
13 | showAttach,
14 | setShowAttach,
15 | showEmojis,
16 | setShowEmojis,
17 | newMessage,
18 | setNewMessage,
19 | submitNewMessage,
20 | }) => {
21 | const detectEnterPress = (e) => {
22 | if (e.key === "Enter" || e.keyCode === 13) {
23 | submitNewMessage();
24 | }
25 | };
26 | return (
27 |
28 | {showEmojis && (
29 |
setShowEmojis(false)}>
30 |
31 |
32 | )}
33 |
setShowEmojis(true)}>
34 |
40 |
41 | {showEmojis && (
42 | <>
43 |
44 |
45 |
46 |
47 |
48 |
49 | >
50 | )}
51 |
52 |
setShowAttach(!showAttach)}>
53 |
59 |
60 |
61 |
64 | {attachButtons.map((btn) => (
65 |
70 |
71 |
72 | ))}
73 |
74 |
75 |
setNewMessage(e.target.value)}
80 | onKeyDown={detectEnterPress}
81 | />
82 | {newMessage ? (
83 |
84 |
85 |
86 | ) : (
87 |
88 |
89 |
90 | )}
91 |
92 | );
93 | };
94 |
95 | export default ChatInput;
96 |
--------------------------------------------------------------------------------
/src/pages/Chat/components/EmojiTray.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Icon from "components/Icon";
3 |
4 | const emojiTabs = [
5 | { icon: "recent", label: "Recent emojis", active: true },
6 | { icon: "emojiPeople", label: "People emojis", active: false },
7 | { icon: "emojiNature", label: "Nature emojis", active: false },
8 | { icon: "emojiFood", label: "Food emojis", active: false },
9 | { icon: "emojiActivity", label: "Activity emojis", active: false },
10 | { icon: "emojiTravel", label: "Travel emojis", active: false },
11 | { icon: "emojiObjects", label: "Object emojis", active: false },
12 | { icon: "emojiSymbols", label: "Symbol emojis", active: false },
13 | { icon: "emojiFlags", label: "Flag emojis", active: false },
14 | ];
15 |
16 | const EmojiTray = ({ showEmojis, newMessage, setNewMessage }) => {
17 | const addEmoji = (emoji) => {
18 | setNewMessage(newMessage + emoji);
19 | };
20 |
21 | return (
22 |
27 |
28 | {emojiTabs.map((tab) => (
29 |
33 |
34 |
35 |
36 |
37 | ))}
38 |
39 |
40 |
41 |
Smileys {"&"} People
42 |
43 | {new Array(6).fill(null).map((_, rowIndex) =>
44 | new Array(11).fill(null).map((_, colIndex) => (
45 |
addEmoji("emoji")}
49 | key={`${rowIndex}-${colIndex}`}
50 | className="emoji emojis__emoji"
51 | style={{
52 | backgroundPositionX: -3 - 44.2 * colIndex,
53 | backgroundPositionY: -6 - 52 * rowIndex,
54 | }}
55 | >
56 | ))
57 | )}
58 |
59 |
Animals {"&"} Nature
60 |
61 | {new Array(6).fill(null).map((_, rowIndex) =>
62 | new Array(11).fill(null).map((_, colIndex) => (
63 |
addEmoji("emoji")}
67 | key={`${rowIndex}-${colIndex}`}
68 | className="emoji emojis__emoji"
69 | style={{
70 | backgroundPositionX: -3 - 44.2 * colIndex,
71 | backgroundPositionY: -6 - 52 * rowIndex,
72 | }}
73 | >
74 | ))
75 | )}
76 |
77 |
78 |
79 | );
80 | };
81 |
82 | export default EmojiTray;
83 |
--------------------------------------------------------------------------------
/src/pages/Chat/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import "./styles/main.css";
3 | import EmojiTray from "./components/EmojiTray";
4 | import ChatInput from "./components/ChatInput";
5 | import Header from "./components/Header";
6 | import ChatSidebar from "./components/ChatSidebar";
7 | import Icon from "components/Icon";
8 | import Search from "./components/Search";
9 | import Profile from "./components/Profile";
10 | import Convo from "./components/Convo";
11 | import { useUsersContext } from "context/usersContext";
12 |
13 | const Chat = ({ match, history }) => {
14 | const { users, setUserAsUnread, addNewMessage } = useUsersContext();
15 |
16 | const userId = match.params.id;
17 | let user = users.filter((user) => user.id === Number(userId))[0];
18 |
19 | const lastMsgRef = useRef(null);
20 | const [showAttach, setShowAttach] = useState(false);
21 | const [showEmojis, setShowEmojis] = useState(false);
22 | const [showProfileSidebar, setShowProfileSidebar] = useState(false);
23 | const [showSearchSidebar, setShowSearchSidebar] = useState(false);
24 | const [newMessage, setNewMessage] = useState("");
25 |
26 | useEffect(() => {
27 | if (!user) history.push("/");
28 | else {
29 | scrollToLastMsg();
30 | setUserAsUnread(user.id);
31 | }
32 | }, []);
33 |
34 | useEffect(() => {
35 | user && scrollToLastMsg();
36 | }, [users]);
37 |
38 | const openSidebar = (cb) => {
39 | // close any open sidebar first
40 | setShowProfileSidebar(false);
41 | setShowSearchSidebar(false);
42 |
43 | // call callback fn
44 | cb(true);
45 | };
46 |
47 | const scrollToLastMsg = () => {
48 | lastMsgRef.current.scrollIntoView();
49 | };
50 |
51 | const submitNewMessage = () => {
52 | addNewMessage(user.id, newMessage);
53 | setNewMessage("");
54 | scrollToLastMsg();
55 | };
56 |
57 | return (
58 |
59 |
60 |
61 |
62 |
openSidebar(setShowProfileSidebar)}
65 | openSearchSidebar={() => openSidebar(setShowSearchSidebar)}
66 | />
67 |
68 |
69 |
70 |
71 |
76 |
77 |
78 |
83 |
92 |
93 |
94 |
setShowSearchSidebar(false)}
98 | >
99 |
100 |
101 |
102 |
setShowProfileSidebar(false)}
106 | >
107 |
108 |
109 |
110 | );
111 | };
112 |
113 | export default Chat;
114 |
--------------------------------------------------------------------------------
/src/pages/Chat/components/Convo.jsx:
--------------------------------------------------------------------------------
1 | import Icon from "components/Icon";
2 | import React from "react";
3 | import media from "assets/images/women.jpeg";
4 | import formatTime from "utils/formatTime";
5 |
6 | const Convo = ({ lastMsgRef, messages: allMessages }) => {
7 | const dates = Object.keys(allMessages);
8 |
9 | return dates.map((date, dateIndex) => {
10 | const messages = allMessages[date];
11 | return (
12 |
13 |
14 | {date}
15 |
16 | {dateIndex === 0 && (
17 |
18 |
19 | Messages are end-to-end encrypted. No one outside of this chat, not
20 | even WhatsApp, can read or listen to them. Click to learn more.
21 |
22 | )}
23 |
24 | {messages.map((message, msgIndex) => {
25 | const assignRef = () =>
26 | dateIndex === dates.length - 1 && msgIndex === messages.length - 1
27 | ? lastMsgRef
28 | : undefined;
29 | return (
30 | <>
31 | {message.image ? (
32 |
38 |
39 |
40 | {formatTime(message.time)}
41 | {!message.sender && (
42 |
55 | )}
56 |
57 |
58 |
62 |
63 |
64 |
65 | ) : message.sender ? (
66 |
67 | {message.content}
68 |
69 |
70 | {formatTime(message.time)}
71 |
72 |
76 |
77 |
78 |
79 | ) : (
80 |
81 | {message.content}
82 |
83 |
84 | {formatTime(message.time)}
85 |
98 |
99 |
103 |
104 |
105 |
106 | )}
107 | >
108 | );
109 | })}
110 |
111 |
112 | );
113 | });
114 | };
115 |
116 | export default Convo;
117 |
--------------------------------------------------------------------------------
/src/utils/getRandomSentence.js:
--------------------------------------------------------------------------------
1 | const sentences = [
2 | "Ooooh. That seems interesting. Tell me more!",
3 | "Joyce enjoyed eating pancakes with ketchup.",
4 | "It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
5 | "At that moment he wasn't listening to music, he was living an experience.",
6 | "The clock within this blog and the clock on my laptop are 1 hour different from each other.",
7 | "They ran around the corner to find that they had traveled back in time.",
8 | "Please put on these earmuffs because I can't you hear.",
9 | "All she wanted was the answer, but she had no idea how much she would hate it. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
10 | "He enjoys practicing his ballet in the bathroom.",
11 | "Can we go to the park .",
12 | "Where is the orange cat? Said the big black dog.",
13 | "We can make the bird fly away if we jump on something.",
14 | "We can go down to the store with the dog. It is not too far away.",
15 | "My big yellow cat ate the little black bird.",
16 | "I like to read my book at school.",
17 | "We are going to swim at the park. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
18 | "They improved dramatically once the lead singer left. ",
19 | "I hear that Nancy is very pretty. ",
20 | "Sometimes you have to just give up and win by cheating.",
21 | "Blue sounded too cold at the time and yet it seemed to work for gin.",
22 | "The green tea and avocado smoothie turned out exactly as would be expected.",
23 | "In that instant, everything changed. ",
24 | "I currently have 4 windows open up… and I don’t know why.",
25 | "Gary didn't understand why Doug went upstairs to get one dollar bills when he invited him to go cow tipping.",
26 | "The shark-infested South Pine channel was the only way in or out.",
27 | "When he asked her favorite number, she answered without hesitation that it was diamonds.",
28 | "She insisted that cleaning out your closet was the key to good driving.",
29 | "He invested some skill points in Charisma and Strength. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
30 | "Mary realized if her calculator had a history, it would be more embarrassing than her computer browser history.",
31 | "Cats are good pets, for they are clean and are not noisy.",
32 | "It's much more difficult to play tennis with a bowling ball than it is to bowl with a tennis ball.",
33 | "There are over 500 starfish in the bathroom drawer.",
34 | "The murder hornet was disappointed by the preconceived ideas people had of him.",
35 | "It's not often you find a soggy banana on the street.",
36 | "The Japanese yen for commerce is still well-known.",
37 | "Shakespeare was a famous 17th-century diesel mechanic. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
38 | "He decided to live his life by the big beats manifesto.",
39 | "The bees decided to have a mutiny against their queen.",
40 | "His confidence would have bee admirable if it wasn't for his stupidity. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
41 | "He strives to keep the best lawn in the neighborhood.",
42 | "Carol drank the blood as if she were a vampire.",
43 | "Dan ate the clouds like cotton candy.",
44 | "He went on a whiskey diet and immediately lost three days.",
45 | "That is an appealing treasure map that I can't read. It's not possible to convince a monkey to give you a banana by promising it infinite bananas when they die.",
46 | "Henry couldn't decide if he was an auto mechanic or a priest.",
47 | "The small white buoys marked the location of hundreds of crab pots.",
48 | "Don't step on the broken glass.",
49 | "Her scream silenced the rowdy teenagers.",
50 | ];
51 |
52 | const getRandomSentence = () => {
53 | const randomIndex = Math.floor(Math.random() * sentences.length);
54 | const sentence = sentences[randomIndex];
55 | return sentence;
56 | };
57 |
58 | export default getRandomSentence;
59 |
--------------------------------------------------------------------------------
/src/components/Sidebar/styles/main.css:
--------------------------------------------------------------------------------
1 | @import url(./darktheme.css);
2 | .sidebar {
3 | min-width: 300px;
4 | flex: 40%;
5 | border-right: 1px solid #DADADA;
6 | display: flex;
7 | flex-direction: column;
8 | }
9 |
10 | /* Sidebar Header */
11 |
12 | .sidebar__avatar-wrapper {
13 | width: 40px;
14 | height: 40px;
15 | }
16 |
17 | .sidebar__actions {
18 | margin-right: 20px;
19 | }
20 |
21 | .sidebar__actions>* {
22 | display: inline-block;
23 | margin-left: 25px;
24 | cursor: pointer;
25 | }
26 |
27 | .sidebar__action-icon {
28 | color: rgb(145, 145, 145);
29 | }
30 |
31 | /* End Sidebar Header */
32 |
33 | /* Sidebar Alert */
34 |
35 | .sidebar__alert {
36 | min-height: 85px;
37 | padding: 20px;
38 | display: flex;
39 | align-items: center;
40 | }
41 |
42 | .sidebar__alert--warning {
43 | background: #FED859;
44 | }
45 |
46 | .sidebar__alert--info {
47 | background: #9DE1FE;
48 | }
49 |
50 | .sidebar__alert--danger {
51 | background: #F3645B;
52 | }
53 |
54 | .sidebar__alert-icon-wrapper {
55 | margin-right: 10px;
56 | }
57 |
58 | .sidebar__alert-icon {
59 | color: white;
60 | }
61 |
62 | .sidebar__alert-texts {
63 | flex: 1;
64 | }
65 |
66 | .sidebar__alert-text:first-of-type {
67 | font-size: 1rem;
68 | margin-bottom: 5px;
69 | color: #343738;
70 | }
71 |
72 | .sidebar__alert-text:last-of-type {
73 | font-size: 0.85rem;
74 | color: #414A4E;
75 | line-height: 17px;
76 | }
77 |
78 | .sidebar__alert--danger .sidebar__alert-text:first-of-type, .sidebar__alert--danger .sidebar__alert-text:last-of-type {
79 | color: white;
80 | }
81 |
82 | /* End Sidebar Alert */
83 |
84 | /* Sidebar Search */
85 |
86 | .sidebar__search-wrapper {
87 | padding: 7px 10px;
88 | height: 50px;
89 | background: #F6F6F6;
90 | position: relative;
91 | }
92 |
93 | /* End Sidebar Search */
94 |
95 | /* Sidebar Contact List */
96 |
97 | .sidebar__contacts {
98 | flex: 1;
99 | overflow-y: scroll;
100 | background: #F5F5F5;
101 | border-top: 1px solid #DADADA;
102 | }
103 |
104 | .sidebar-contact {
105 | height: 72px;
106 | padding: 10px 20px;
107 | display: flex;
108 | align-items: center;
109 | border-bottom: 1px solid #EBEBEB;
110 | cursor: pointer;
111 | }
112 |
113 | .sidebar-contact:hover {
114 | background-color: #EBEBEB;
115 | }
116 |
117 | .sidebar-contact__avatar-wrapper {
118 | width: 50px;
119 | height: 50px;
120 | margin-right: 10px;
121 | }
122 |
123 | .sidebar-contact__content {
124 | overflow: hidden;
125 | flex: 1;
126 | }
127 |
128 | .sidebar-contact__top-content, .sidebar-contact__bottom-content, .sidebar-contact__message-wrapper {
129 | display: flex;
130 | align-items: center;
131 | justify-content: space-between;
132 | }
133 |
134 | .sidebar-contact__name, .sidebar-contact__message {
135 | flex: 1;
136 | overflow: hidden;
137 | white-space: nowrap;
138 | text-overflow: ellipsis;
139 | }
140 |
141 | .sidebar-contact__top-content {
142 | margin-bottom: 2px;
143 | }
144 |
145 | .sidebar-contact__name {
146 | color: #000000;
147 | font-size: 1rem;
148 | font-weight: 500;
149 | }
150 |
151 | .sidebar-contact__time {
152 | font-size: 0.7rem;
153 | color: rgba(0, 0, 0, 0.45);
154 | }
155 |
156 | .sidebar-contact__message-wrapper {
157 | color: #00000099;
158 | font-size: 0.85rem;
159 | margin-right: 3px;
160 | overflow: hidden;
161 | }
162 |
163 | .sidebar-contact__message-icon {
164 | color: #B3B3B3;
165 | margin-right: 3px;
166 | }
167 |
168 | .sidebar-contact__message-icon--blue {
169 | color: #0DA9E5;
170 | }
171 |
172 | .sidebar-contact__message--unread {
173 | color: #000000;
174 | font-weight: 500;
175 | }
176 |
177 | .sidebar-contact__icons, .sidebar-contact:not(:focus) .sidebar-contact__icons {
178 | display: flex;
179 | justify-content: center;
180 | align-items: center;
181 | transform: translateX(24px);
182 | transition: transform 0.5s ease;
183 | }
184 |
185 | .sidebar-contact:hover .sidebar-contact__icons {
186 | transform: translateX(0);
187 | }
188 |
189 | .sidebar-contact__icons>* {
190 | margin-left: 8px;
191 | color: #B3B3B3;
192 | }
193 |
194 | .sidebar-contact__unread {
195 | display: inline-block;
196 | color: white;
197 | background-color: rgb(6, 215, 85);
198 | border-radius: 18px;
199 | min-width: 18px;
200 | height: 18px;
201 | padding: 0 3px;
202 | line-height: 18px;
203 | vertical-align: middle;
204 | text-align: center;
205 | font-size: 0.75rem;
206 | font-weight: 500;
207 | }
208 |
209 | /* End Sidebar Contact List */
210 |
211 | @media screen and (min-width: 1000px) and (max-width: 1300px) {
212 | .sidebar {
213 | flex: 35%;
214 | }
215 | .sidebar~div {
216 | flex: 65%;
217 | }
218 | }
219 |
220 | @media screen and (min-width: 1301px) {
221 | .sidebar {
222 | flex: 30%;
223 | }
224 | .sidebar~div {
225 | flex: 70%;
226 | }
227 | }
--------------------------------------------------------------------------------
/src/pages/Chat/components/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import groupAvatar from "assets/images/women.png";
3 | import media from "assets/images/placeholder.jpeg";
4 | import Checkbox from "components/Checkbox";
5 | import Icon from "components/Icon";
6 |
7 | const groups = [
8 | {
9 | name: "Group 1",
10 | avatar: groupAvatar,
11 | members:
12 | "Michelle Obama, Sandra Bullock, Kerry Washington, Beyonce Knowles, Kamala Harris, You",
13 | },
14 | {
15 | name: "Group 2",
16 | avatar: groupAvatar,
17 | members:
18 | "Michelle Obama, Sandra Bullock, Kerry Washington, Beyonce Knowles, Kamala Harris, You",
19 | },
20 | {
21 | name: "Group 3",
22 | avatar: groupAvatar,
23 | members:
24 | "Michelle Obama, Sandra Bullock, Kerry Washington, Beyonce Knowles, Kamala Harris, You",
25 | },
26 | ];
27 |
28 | const Profile = ({ user }) => {
29 | return (
30 |
31 |
32 |
33 |
34 |
35 |
{user.name}
36 |
37 |
38 |
39 |
40 |
Media, Links and Documents
41 |
42 |
43 |
44 |
45 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | Mute Notifications
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Starred Messages
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Disappearing Messages
77 |
78 |
79 | Off
80 |
81 |
82 |
83 | {" "}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
About and phone number
91 |
92 |
93 |
94 | Out here saving the world, one block of code at a time.
95 |
96 | +23423456789
97 |
98 |
99 |
100 |
101 |
102 |
103 | Groups in common 3
104 |
105 |
106 | {groups.map((group) => (
107 |
108 |
109 |
110 |
111 |
112 |
113 | {group.name}
114 |
115 |
116 | {group.members}
117 |
118 |
119 |
120 | ))}
121 |
122 |
123 |
127 |
128 |
129 |
130 |
Report contact
131 |
132 |
133 |
134 |
135 |
Delete chat
136 |
137 |
138 | );
139 | };
140 |
141 | export default Profile;
142 |
--------------------------------------------------------------------------------
/src/pages/Chat/styles/darktheme.css:
--------------------------------------------------------------------------------
1 | .dark-theme .chat__body {
2 | border-right-color: #3C4247;
3 | }
4 |
5 | .dark-theme .chat__bg {
6 | background: url(../../../assets/images/bg-chat-dark.png) rgb(13, 20, 24);
7 | }
8 |
9 | /* Chat Header Component */
10 |
11 | .dark-theme .chat__contact-name {
12 | color: rgba(241, 241, 242, 0.92);
13 | }
14 |
15 | .dark-theme .chat__contact-desc {
16 | color: rgba(241, 241, 242, 0.63);
17 | }
18 |
19 | /* End Chat Header Component */
20 |
21 | /* Chat Content */
22 |
23 | .dark-theme .chat__content {
24 | background: rgb(13, 20, 24);
25 | }
26 |
27 | .dark-theme .chat__action-icon {
28 | color: rgb(177, 179, 181)
29 | }
30 |
31 | .dark-theme .chat__date {
32 | background: rgb(30, 42, 48);
33 | color: rgba(241, 241, 242, 0.92);
34 | }
35 |
36 | .dark-theme .chat__encryption-msg {
37 | background: rgb(32, 39, 43);
38 | color: rgb(250, 217, 100);
39 | }
40 |
41 | .dark-theme .chat__encryption-icon {
42 | color: rgb(250, 217, 100)
43 | }
44 |
45 | .dark-theme .chat__msg {
46 | color: rgba(241, 241, 242, 0.95)
47 | }
48 |
49 | .dark-theme .chat__msg--sent {
50 | background: rgb(5, 97, 98);
51 | }
52 |
53 | .dark-theme .chat__msg--rxd {
54 | background: rgb(38, 45, 49);
55 | }
56 |
57 | .dark-theme .chat__msg-group>*:nth-child(1):not(.chat__msg--sent)::before, .dark-theme .chat__msg--sent+.chat__msg--rxd::before {
58 | border-top-color: rgb(38, 45, 49);
59 | border-right-color: rgb(38, 45, 49);
60 | }
61 |
62 | .dark-theme .chat__msg-group>*:nth-child(1):not(.chat__msg--rxd)::before, .dark-theme .chat__msg--rxd+.chat__msg--sent::before {
63 | border-top-color: rgb(5, 97, 98);
64 | border-left-color: rgb(5, 97, 98);
65 | }
66 |
67 | .dark-theme .chat__msg-footer {
68 | color: rgba(241, 241, 242, 0.63)
69 | }
70 |
71 | .dark-theme .chat__msg--rxd .chat__msg-options {
72 | background: rgb(38, 45, 49);
73 | }
74 |
75 | .dark-theme .chat__msg--sent .chat__msg-options {
76 | background: rgb(5, 97, 98);
77 | }
78 |
79 | .dark-theme .chat__img-wrapper .chat__msg-options {
80 | background: transparent;
81 | }
82 |
83 | /* End Chat Content */
84 |
85 | .dark-theme .chat__footer {
86 | background: rgb(30, 36, 40);
87 | }
88 |
89 | .dark-theme .chat__scroll-btn {
90 | background: rgb(38, 45, 49);
91 | color: rgb(121, 124, 126);
92 | }
93 |
94 | /* Emoji Tray */
95 |
96 | .dark-theme .emojis__tab--active::after {
97 | background: rgb(0, 175, 156);
98 | }
99 |
100 | .dark-theme .emojis__tab-icon {
101 | color: rgba(241, 241, 242, 0.32);
102 | }
103 |
104 | .dark-theme .emojis__tab--active .emojis__tab-icon {
105 | color: rgba(241, 241, 242, 0.63);
106 | }
107 |
108 | .dark-theme .emojis__search {
109 | background: rgb(38, 45, 49);
110 | color: rgb(212, 213, 215);
111 | }
112 |
113 | .dark-theme .emojis__search::placeholder {
114 | color: #7D8184;
115 | }
116 |
117 | .dark-theme .emojis__label {
118 | color: rgba(241, 241, 242, 0.45);
119 | }
120 |
121 | /* End Emoji Tray */
122 |
123 | /* Chat Footer Toolbar */
124 |
125 | .dark-theme .chat__input-icon {
126 | color: rgb(130, 134, 137);
127 | }
128 |
129 | .dark-theme .chat__input-icon--highlight {
130 | color: rgb(0, 150, 136);
131 | }
132 |
133 | .dark-theme .chat__input {
134 | background: rgb(51, 56, 59);
135 | color: rgb(241, 241, 242);
136 | }
137 |
138 | /* End Chat Footer Toolbar */
139 |
140 | /* Chat Sidebar */
141 |
142 | .dark-theme .chat-sidebar__header-icon {
143 | color: rgb(130, 134, 137);
144 | }
145 |
146 | .dark-theme .chat-sidebar__heading {
147 | color: rgba(241, 241, 242, 0.92);
148 | }
149 |
150 | .dark-theme .chat-sidebar__search-results {
151 | background: rgb(19, 28, 33);
152 | color: rgba(241, 241, 242, 0.45);
153 | }
154 |
155 | .dark-theme .profile {
156 | background: #0D151A;
157 | }
158 |
159 | .dark-theme .profile__section {
160 | background: rgb(19, 28, 33);
161 | box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 3px 0px;
162 | }
163 |
164 | .dark-theme .profile__name {
165 | color: rgba(241, 241, 242, 0.92);
166 | }
167 |
168 | .dark-theme .profile__heading {
169 | color: rgb(0, 150, 136);
170 | }
171 |
172 | .dark-theme .profile__heading-icon {
173 | color: rgb(130, 134, 137);
174 | }
175 |
176 | .dark-theme .profile__action:not(:last-of-type), .dark-theme .profile__about-item:not(:last-of-type), .dark-theme .profile__group:not(:last-of-type) {
177 | border-bottom: 1px solid #3C4247;
178 | }
179 |
180 | .dark-theme .profile__action-text--top {
181 | color: rgba(241, 241, 242, 0.92);
182 | }
183 |
184 | .dark-theme .profile__action-text--bottom {
185 | color: rgba(241, 241, 242, 0.45);
186 | }
187 |
188 | .dark-theme .profile__about-item {
189 | color: rgba(241, 241, 242, 0.92);
190 | }
191 |
192 | .dark-theme .profile__group:hover {
193 | background-color: #3C4247;
194 | }
195 |
196 | .dark-theme .profile__group-text--top {
197 | color: rgba(241, 241, 242, 0.92);
198 | }
199 |
200 | .dark-theme .profile__group-text--bottom {
201 | color: rgba(241, 241, 242, 0.45);
202 | }
203 |
204 | .dark-theme .profile__section--danger {
205 | color: rgb(239, 105, 122);
206 | }
--------------------------------------------------------------------------------
/src/pages/Chat/styles/main.css:
--------------------------------------------------------------------------------
1 | @import url(./darktheme.css);
2 | .chat {
3 | display: flex;
4 | position: relative;
5 | }
6 |
7 | .chat__body {
8 | min-width: 300px;
9 | flex: 40%;
10 | border-right: 1px solid #DADADA;
11 | display: flex;
12 | flex-direction: column;
13 | position: relative;
14 | z-index: 1;
15 | }
16 |
17 | .chat__bg {
18 | position: absolute;
19 | top: 0;
20 | right: 0;
21 | left: 0;
22 | bottom: 0;
23 | opacity: 0.05;
24 | z-index: 1;
25 | background: url(../../../assets/images/bg-chat-light.png) #E4DCD4;
26 | }
27 |
28 | .chat__header, .chat__footer, .chat__date-wrapper, .chat__msg-group, .chat__encryption-msg {
29 | z-index: 10;
30 | }
31 |
32 | .chat__header {
33 | /* Needed for the options btn to stay on top */
34 | z-index: 20;
35 | }
36 |
37 | /* Chat Header Component */
38 |
39 | .chat__avatar-wrapper {
40 | width: 40px;
41 | height: 40px;
42 | margin-right: 10px;
43 | cursor: pointer;
44 | }
45 |
46 | .chat__contact-wrapper {
47 | flex: 1;
48 | cursor: pointer;
49 | }
50 |
51 | .chat__contact-name, .chat__contact-desc {
52 | overflow: hidden;
53 | white-space: nowrap;
54 | text-overflow: ellipsis;
55 | }
56 |
57 | .chat__contact-name {
58 | color: #000000;
59 | font-size: 1rem;
60 | margin-bottom: 2px;
61 | }
62 |
63 | .chat__contact-desc {
64 | color: #00000099;
65 | font-size: 0.75rem;
66 | }
67 |
68 | .chat__actions {
69 | margin-right: 20px;
70 | display: flex;
71 | align-items: center;
72 | }
73 |
74 | .chat__action {
75 | margin-left: 25px;
76 | cursor: pointer;
77 | }
78 |
79 | .chat__action:not(.options-btn) {
80 | display: inline-block;
81 | }
82 |
83 | .chat__action-icon {
84 | color: rgb(145, 145, 145);
85 | }
86 |
87 | .chat__action-icon--search {
88 | width: 30px;
89 | height: 30px;
90 | }
91 |
92 | /* End Chat Header Component */
93 |
94 | /* Chat Content */
95 |
96 | .chat__content {
97 | flex: 1;
98 | position: relative;
99 | background: #E4DCD4;
100 | overflow-y: scroll;
101 | overflow-x: hidden;
102 | padding: 20px 5% 2pc;
103 | }
104 |
105 | .chat__date-wrapper {
106 | text-align: center;
107 | margin: 10px 0 14px;
108 | position: relative;
109 | }
110 |
111 | .chat__date {
112 | background: #E1F2FA;
113 | display: inline-block;
114 | color: #000000;
115 | font-size: 0.75rem;
116 | padding: 7px 10px;
117 | border-radius: 5px;
118 | }
119 |
120 | .chat__encryption-msg {
121 | background: #FDF4C5;
122 | color: #000000;
123 | font-size: 0.77rem;
124 | text-align: center;
125 | padding: 5px 10px;
126 | position: relative;
127 | margin-bottom: 8px;
128 | border-radius: 5px;
129 | line-height: 20px;
130 | }
131 |
132 | .chat__encryption-icon {
133 | color: #8C866C;
134 | margin-right: 5px;
135 | margin-bottom: -1px;
136 | }
137 |
138 | .chat__msg-group {
139 | display: flex;
140 | flex-direction: column;
141 | margin-bottom: 12px;
142 | position: relative;
143 | }
144 |
145 | .chat__msg {
146 | padding: 6px 7px 8px 9px;
147 | margin-bottom: 12px;
148 | font-size: 0.85rem;
149 | color: #000000;
150 | width: fit-content;
151 | max-width: 95%;
152 | line-height: 20px;
153 | border-radius: 5px;
154 | position: relative;
155 | white-space: pre-line;
156 | display: flex
157 | }
158 |
159 | .chat__msg.chat__img-wrapper {
160 | padding: 4px;
161 | width: 95%;
162 | }
163 |
164 | .chat__msg--sent {
165 | background: #DBF8C6;
166 | align-self: flex-end;
167 | }
168 |
169 | .chat__msg--rxd {
170 | background: white;
171 | align-self: flex-start;
172 | }
173 |
174 | .chat__msg-group>*:nth-child(1):not(.chat__msg--sent)::before, .chat__msg--sent+.chat__msg--rxd::before {
175 | content: "";
176 | position: absolute;
177 | width: 0;
178 | height: 0;
179 | top: 0;
180 | left: -8px;
181 | border-top: 6px solid white;
182 | border-right: 6px solid white;
183 | border-bottom: 6px solid transparent;
184 | border-left: 6px solid transparent;
185 | }
186 |
187 | .chat__msg-group>*:nth-child(1):not(.chat__msg--rxd)::before, .chat__msg--rxd+.chat__msg--sent::before {
188 | right: -8px;
189 | content: "";
190 | position: absolute;
191 | width: 0;
192 | height: 0;
193 | top: 0;
194 | border-top: 6px solid #DBF8C6;
195 | border-right: 6px solid transparent;
196 | border-bottom: 6px solid transparent;
197 | border-left: 6px solid #DBF8C6;
198 | }
199 |
200 | .chat__img {
201 | width: 100%;
202 | height: 100%;
203 | object-fit: cover;
204 | }
205 |
206 | .chat__msg-filler {
207 | width: 65px;
208 | display: inline-block;
209 | height: 3px;
210 | background: transparent;
211 | }
212 |
213 | .chat__msg-footer {
214 | position: absolute;
215 | display: flex;
216 | align-items: center;
217 | right: 7px;
218 | bottom: 3px;
219 | color: rgba(0, 0, 0, 0.45);
220 | font-size: 0.7rem;
221 | font-weight: 500;
222 | }
223 |
224 | .chat__msg-status-icon {
225 | color: #B3B3B3;
226 | margin-left: 3px;
227 | }
228 |
229 | .chat__msg-status-icon--blue {
230 | color: #0DA9E5;
231 | }
232 |
233 | .chat__img-wrapper .chat__msg-footer, .chat__img-wrapper .chat__msg-options-icon, .chat__img-wrapper .chat__msg-status-icon {
234 | color: white;
235 | }
236 |
237 | .chat__msg-options {
238 | opacity: 0;
239 | position: absolute;
240 | right: 5px;
241 | top: 3px;
242 | pointer-events: none;
243 | transition: all 0.2s;
244 | }
245 |
246 | .chat__msg--rxd .chat__msg-options {
247 | background: white;
248 | }
249 |
250 | .chat__msg--sent .chat__msg-options {
251 | background: #DBF8C6;
252 | }
253 |
254 | .chat__img-wrapper .chat__msg-options {
255 | background: transparent;
256 | }
257 |
258 | .chat__msg:hover .chat__msg-options {
259 | opacity: 1;
260 | pointer-events: unset;
261 | }
262 |
263 | .chat__msg-options-icon {
264 | color: rgb(145, 145, 145);
265 | width: 20px;
266 | height: 20px;
267 | }
268 |
269 | /* End Chat Content */
270 |
271 | .chat__footer {
272 | background: rgb(240, 240, 240);
273 | position: relative;
274 | }
275 |
276 | .chat__scroll-btn {
277 | position: absolute;
278 | right: 15px;
279 | bottom: 80px;
280 | width: 42px;
281 | height: 42px;
282 | z-index: -1;
283 | border-radius: 50%;
284 | color: rgb(145, 145, 145);
285 | display: flex;
286 | justify-content: center;
287 | align-items: center;
288 | background: #FFFFFF;
289 | box-shadow: 0 1px 1px 0 rgba(0, 0, 0, .06), 0 2px 5px 0 rgba(0, 0, 0, .2)
290 | }
291 |
292 | /* Emoji Tray */
293 |
294 | .emojis__wrapper {
295 | width: 100%;
296 | overflow: hidden;
297 | display: flex;
298 | flex-direction: column;
299 | height: 0;
300 | min-height: 0;
301 | transition: all 0.4s ease;
302 | background: inherit;
303 | }
304 |
305 | .emojis__wrapper--active {
306 | height: 40vh;
307 | min-height: 350px;
308 | transition: all 0.4s ease;
309 | }
310 |
311 | .emojis__tabs {
312 | display: flex;
313 | align-items: center;
314 | height: 50px;
315 | }
316 |
317 | .emojis__tab {
318 | flex: 1;
319 | padding: 10px 5px 10px;
320 | text-align: center;
321 | position: relative;
322 | }
323 |
324 | .emojis__tab--active::after {
325 | content: "";
326 | position: absolute;
327 | height: 4px;
328 | width: 100%;
329 | bottom: 0;
330 | left: 0;
331 | background: rgb(0, 150, 136);
332 | }
333 |
334 | .emojis__tab-icon {
335 | color: rgba(0, 0, 0, 0.32);
336 | }
337 |
338 | .emojis__tab--active .emojis__tab-icon {
339 | color: rgba(0, 0, 0, 0.6);
340 | }
341 |
342 | .emojis__content {
343 | overflow-y: scroll;
344 | padding: 5px 20px;
345 | flex: 1;
346 | }
347 |
348 | .emojis__search {
349 | height: 40px;
350 | background: #E6E6E6;
351 | width: 100%;
352 | border-radius: 5px;
353 | padding: 5px 10px;
354 | color: rgb(74, 74, 74);
355 | font-size: 0.9rem;
356 | }
357 |
358 | .emojis__search::placeholder {
359 | color: #989898;
360 | }
361 |
362 | .emojis__label {
363 | margin-top: 15px;
364 | margin-bottom: 5px;
365 | font-weight: 500;
366 | color: rgba(0, 0, 0, 0.45);
367 | font-size: 0.85rem;
368 | }
369 |
370 | .emojis__grid {
371 | display: flex;
372 | flex-wrap: wrap;
373 | margin-bottom: 25px;
374 | }
375 |
376 | .emojis__emoji {
377 | margin-right: 3px;
378 | margin-top: 3px;
379 | cursor: pointer;
380 | }
381 |
382 | /* End Emoji Tray */
383 |
384 | /* Chat Footer Toolbar */
385 |
386 | .chat__input-wrapper {
387 | padding: 10px;
388 | height: 60px;
389 | position: relative;
390 | display: flex;
391 | align-items: center;
392 | }
393 |
394 | .chat__input-icon {
395 | color: #919191;
396 | margin-left: 8px;
397 | margin-right: 8px;
398 | width: 28px;
399 | height: 28px;
400 | padding: 3px;
401 | border-radius: 50%;
402 | }
403 |
404 | .chat__input-icon--highlight {
405 | color: teal;
406 | }
407 |
408 | .chat__attach {
409 | display: flex;
410 | flex-direction: column;
411 | position: absolute;
412 | bottom: 50px;
413 | }
414 |
415 | .chat__attach-btn {
416 | transform: scale(0);
417 | opacity: 0;
418 | transition: all 0.5s ease;
419 | }
420 |
421 | .chat__attach-btn:nth-of-type(1) {
422 | transition-delay: 0.5s;
423 | }
424 |
425 | .chat__attach-btn:nth-of-type(2) {
426 | transition-delay: 0.4s;
427 | }
428 |
429 | .chat__attach-btn:nth-of-type(3) {
430 | transition-delay: 0.3s;
431 | }
432 |
433 | .chat__attach-btn:nth-of-type(4) {
434 | transition-delay: 0.2s;
435 | }
436 |
437 | .chat__attach-btn:nth-of-type(5) {
438 | transition-delay: 0.1s;
439 | }
440 |
441 | .chat__attach--active .chat__attach-btn {
442 | transform: scale(1);
443 | opacity: 1;
444 | }
445 |
446 | .chat__attach-btn {
447 | margin-bottom: 10px;
448 | }
449 |
450 | .chat__input-icon--pressed {
451 | background: rgba(0, 0, 0, 0.1);
452 | }
453 |
454 | .chat__input {
455 | background: white;
456 | color: rgb(74, 74, 74);
457 | padding: 20px 10px;
458 | border-radius: 22px;
459 | flex: 1;
460 | height: 100%;
461 | }
462 |
463 | .chat__input::placeholder {
464 | color: rgb(153, 153, 153);
465 | font-size: 0.9rem;
466 | }
467 |
468 | /* End Chat Footer Toolbar */
469 |
470 | /* Chat Sidebar */
471 |
472 | .chat-sidebar {
473 | width: 0;
474 | min-width: 0;
475 | display: flex;
476 | flex-direction: column;
477 | transition: all 0.1s ease;
478 | overflow-x: hidden;
479 | overflow-y: auto;
480 | }
481 |
482 | .chat-sidebar--active {
483 | flex: 30%;
484 | }
485 |
486 | .chat-sidebar__header-icon {
487 | margin-right: 20px;
488 | color: rgb(145, 145, 145);
489 | }
490 |
491 | .chat-sidebar__heading {
492 | flex: 1;
493 | color: #000000;
494 | font-size: 1rem;
495 | margin-bottom: 2px;
496 | }
497 |
498 | .chat-sidebar__content {
499 | flex: 1;
500 | }
501 |
502 | .chat-sidebar__search-results {
503 | background: white;
504 | height: 100%;
505 | padding-top: 5pc;
506 | color: #00000099;
507 | text-align: center;
508 | font-size: 0.85rem;
509 | }
510 |
511 | .profile {
512 | background: rgb(237, 237, 237);
513 | padding-bottom: 2pc;
514 | }
515 |
516 | .profile__section {
517 | background: white;
518 | margin-bottom: 10px;
519 | box-shadow: rgba(0, 0, 0, 0.08) 0px 1px 3px 0px;
520 | padding: 10px 20px;
521 | }
522 |
523 | .profile__section--personal {
524 | display: flex;
525 | justify-content: center;
526 | flex-direction: column;
527 | align-items: center;
528 | padding: 30px 20px;
529 | }
530 |
531 | .profile__avatar-wrapper {
532 | width: 200px;
533 | width: 200px;
534 | margin-bottom: 20px;
535 | display: flex;
536 | justify-content: center;
537 | align-items: center;
538 | }
539 |
540 | .profile__name {
541 | flex: 1;
542 | color: #000000;
543 | font-size: 1.2rem;
544 | align-self: flex-start;
545 | }
546 |
547 | .profile__heading-wrapper {
548 | margin-top: 5px;
549 | margin-bottom: 10px;
550 | }
551 |
552 | .profile__heading {
553 | color: rgb(0, 150, 136);
554 | font-size: 0.85rem;
555 | flex: 1;
556 | }
557 |
558 | .profile__heading-icon {
559 | color: rgb(145, 145, 145);
560 | }
561 |
562 | .profile__media-wrapper {
563 | display: flex;
564 | align-items: center;
565 | justify-content: space-between;
566 | }
567 |
568 | .profile__media {
569 | width: 32%;
570 | }
571 |
572 | .profile__action, .profile__about-item {
573 | display: flex;
574 | align-items: center;
575 | justify-content: space-between;
576 | padding: 15px 0;
577 | margin-bottom: 5px;
578 | cursor: pointer;
579 | }
580 |
581 | .profile__action:not(:last-of-type), .profile__about-item:not(:last-of-type), .profile__group:not(:last-of-type) {
582 | border-bottom: 1px solid #EBEBEB;
583 | }
584 |
585 | .profile__action-left {
586 | flex: 1;
587 | }
588 |
589 | .profile__action-text {
590 | display: block;
591 | }
592 |
593 | .profile__action-text--top, .profile__about-item {
594 | font-weight: 500;
595 | margin-bottom: 5px;
596 | }
597 |
598 | .profile__action-text--bottom {
599 | font-size: 0.85rem;
600 | color: rgba(0, 0, 0, 0.45);
601 | }
602 |
603 | .profile__section--groups {
604 | padding-left: 0;
605 | padding-right: 0;
606 | }
607 |
608 | .profile__group, .profile__group-heading {
609 | padding-left: 20px;
610 | padding-right: 20px;
611 | }
612 |
613 | .profile__group {
614 | display: flex;
615 | align-items: center;
616 | padding-top: 10px;
617 | padding-bottom: 10px;
618 | cursor: pointer;
619 | }
620 |
621 | .profile__group:hover {
622 | background-color: #EBEBEB;
623 | }
624 |
625 | .profile__group-content {
626 | flex: 1;
627 | overflow: hidden;
628 | }
629 |
630 | .profile__group-avatar-wrapper {
631 | width: 50px;
632 | height: 50px;
633 | margin-right: 10px;
634 | }
635 |
636 | .profile__group-text {
637 | flex: 1;
638 | overflow: hidden;
639 | white-space: nowrap;
640 | text-overflow: ellipsis;
641 | }
642 |
643 | .profile__group-text--top {
644 | color: #000000;
645 | font-size: 1rem;
646 | font-weight: 500;
647 | margin-bottom: 5px;
648 | }
649 |
650 | .profile__group-text--bottom {
651 | color: #00000099;
652 | font-size: 0.85rem;
653 | overflow: hidden;
654 | }
655 |
656 | .profile__section--danger {
657 | color: rgb(223, 51, 51);
658 | display: flex;
659 | align-items: center;
660 | padding-top: 20px;
661 | padding-bottom: 20px;
662 | }
663 |
664 | .profile__danger-icon {
665 | margin-right: 20px;
666 | }
667 |
668 | .profile__danger-text {
669 | flex: 1;
670 | }
671 |
672 | /* End Chat Sidebar */
673 |
674 | @media screen and (min-width: 1301px) {
675 | .chat__msg {
676 | max-width: 65%;
677 | }
678 | }
679 |
680 | @media screen and (min-width: 1000px) and (max-width: 1300px) {
681 | .chat__msg {
682 | max-width: 75%;
683 | }
684 | }
685 |
686 | @media screen and (min-width: 900px) and (max-width: 1000px) {
687 | .chat__msg {
688 | max-width: 85%;
689 | }
690 | }
691 |
692 | @media screen and (max-width: 1000px) {
693 | .chat-sidebar {
694 | transition: transform 0.1s ease;
695 | transform: translateX(120vw);
696 | position: absolute;
697 | left: 0;
698 | width: 100%;
699 | height: 100%;
700 | z-index: 10;
701 | }
702 | .chat-sidebar--active {
703 | transform: translateX(0);
704 | transition: transform 0.1s ease;
705 | }
706 | }
707 |
708 | @media screen and (min-width: 750px) {
709 | .chat__msg.chat__img-wrapper {
710 | width: 40%;
711 | min-width: 300px;
712 | max-width: 400px;
713 | }
714 | }
--------------------------------------------------------------------------------
/src/data/contacts.js:
--------------------------------------------------------------------------------
1 | import ppGirl1 from "assets/images/profile-picture-girl-1.jpeg";
2 | import ppGirl2 from "assets/images/profile-picture-girl-2.jpeg";
3 | import ppGirl3 from "assets/images/profile-picture-girl-3.jpeg";
4 | import ppGirl4 from "assets/images/profile-picture-girl-4.jpeg";
5 | import ppBoy1 from "assets/images/profile-picture-boy-1.jpeg";
6 | import ppBoy2 from "assets/images/profile-picture-boy-2.jpeg";
7 | import ppBoy3 from "assets/images/profile-picture-boy-3.jpeg";
8 | import getRandomSentence from "utils/getRandomSentence";
9 |
10 | const users = [
11 | {
12 | id: 1,
13 | profile_picture: ppGirl3,
14 | name: "Love of my life ❤️💜",
15 | phone_number: "+2348123456789",
16 | whatsapp_name: "Beyonce",
17 | unread: 3,
18 | messages: {
19 | "04/06/2021": [
20 | {
21 | content: getRandomSentence(),
22 | sender: 1,
23 | time: "08:11:26",
24 | status: null,
25 | },
26 | {
27 | content: getRandomSentence(),
28 | sender: null,
29 | time: "08:15:45",
30 | status: "read",
31 | },
32 | {
33 | content: getRandomSentence(),
34 | sender: 1,
35 | time: "09:11:26",
36 | status: null,
37 | },
38 | {
39 | content: getRandomSentence(),
40 | sender: null,
41 | time: "09:15:45",
42 | status: "read",
43 | },
44 | ],
45 |
46 | YESTERDAY: [
47 | {
48 | content: getRandomSentence(),
49 | sender: 1,
50 | time: "08:11:26",
51 | status: null,
52 | },
53 | {
54 | content: getRandomSentence(),
55 | sender: null,
56 | time: "08:15:45",
57 | status: "read",
58 | },
59 | {
60 | content: getRandomSentence(),
61 | sender: 1,
62 | time: "09:11:26",
63 | status: null,
64 | },
65 | {
66 | content: getRandomSentence(),
67 | sender: null,
68 | time: "09:15:45",
69 | status: "read",
70 | },
71 | ],
72 |
73 | TODAY: [
74 | {
75 | content: getRandomSentence(),
76 | sender: null,
77 | time: "08:10:26",
78 | status: null,
79 | },
80 | {
81 | content: getRandomSentence(),
82 | sender: 1,
83 | time: "08:11:26",
84 | status: null,
85 | },
86 | {
87 | image: true,
88 | sender: 1,
89 | time: "09:12:26",
90 | status: null,
91 | },
92 | {
93 | content: getRandomSentence(),
94 | sender: null,
95 | time: "08:12:45",
96 | status: "read",
97 | },
98 | {
99 | image: true,
100 | sender: null,
101 | time: "09:13:26",
102 | status: null,
103 | },
104 | {
105 | content: getRandomSentence(),
106 | sender: 1,
107 | time: "09:20:26",
108 | status: null,
109 | },
110 | {
111 | content: getRandomSentence(),
112 | sender: 1,
113 | time: "09:21:26",
114 | status: null,
115 | },
116 | ],
117 | },
118 | group: false,
119 | pinned: true,
120 | typing: false,
121 | },
122 | {
123 | id: 2,
124 | profile_picture: ppGirl2,
125 | name: "Karen Okonkwo",
126 | phone_number: "+2348123456789",
127 | whatsapp_name: "Karen O.",
128 | unread: 0,
129 | messages: {
130 | "04/06/2021": [
131 | {
132 | content: getRandomSentence(),
133 | sender: 2,
134 | time: "08:11:26",
135 | status: null,
136 | },
137 | {
138 | content: getRandomSentence(),
139 | sender: null,
140 | time: "08:15:45",
141 | status: "read",
142 | },
143 | {
144 | content: getRandomSentence(),
145 | sender: 2,
146 | time: "09:11:26",
147 | status: null,
148 | },
149 | {
150 | content: getRandomSentence(),
151 | sender: null,
152 | time: "09:15:45",
153 | status: "read",
154 | },
155 | ],
156 |
157 | YESTERDAY: [
158 | {
159 | content: getRandomSentence(),
160 | sender: 2,
161 | time: "08:11:26",
162 | status: null,
163 | },
164 | {
165 | content: getRandomSentence(),
166 | sender: null,
167 | time: "08:15:45",
168 | status: "read",
169 | },
170 | {
171 | content: getRandomSentence(),
172 | sender: 2,
173 | time: "09:11:26",
174 | status: null,
175 | },
176 | {
177 | content: getRandomSentence(),
178 | sender: null,
179 | time: "09:15:45",
180 | status: "read",
181 | },
182 | ],
183 |
184 | TODAY: [
185 | {
186 | content: getRandomSentence(),
187 | sender: 2,
188 | time: "08:11:26",
189 | status: null,
190 | },
191 | {
192 | content: getRandomSentence(),
193 | sender: null,
194 | time: "08:15:45",
195 | status: "read",
196 | },
197 | {
198 | content: getRandomSentence(),
199 | sender: 2,
200 | time: "09:11:26",
201 | status: null,
202 | },
203 | ],
204 | },
205 | group: false,
206 | pinned: false,
207 | typing: false,
208 | },
209 | {
210 | id: 3,
211 | profile_picture: ppGirl1,
212 | name: "Titilayo Bello",
213 | phone_number: "+2348123456789",
214 | whatsapp_name: "titi123",
215 | unread: 0,
216 | messages: {
217 | "04/06/2021": [
218 | {
219 | content: getRandomSentence(),
220 | sender: 3,
221 | time: "08:11:26",
222 | status: null,
223 | },
224 | {
225 | content: getRandomSentence(),
226 | sender: null,
227 | time: "08:15:45",
228 | status: "read",
229 | },
230 | {
231 | content: getRandomSentence(),
232 | sender: 3,
233 | time: "09:11:26",
234 | status: null,
235 | },
236 | {
237 | content: getRandomSentence(),
238 | sender: null,
239 | time: "09:15:45",
240 | status: "sent",
241 | },
242 | ],
243 |
244 | YESTERDAY: [
245 | {
246 | content: getRandomSentence(),
247 | sender: 3,
248 | time: "08:11:26",
249 | status: null,
250 | },
251 | {
252 | content: getRandomSentence(),
253 | sender: null,
254 | time: "08:15:45",
255 | status: "read",
256 | },
257 | {
258 | content: getRandomSentence(),
259 | sender: 3,
260 | time: "09:11:26",
261 | status: null,
262 | },
263 | {
264 | content: getRandomSentence(),
265 | sender: null,
266 | time: "09:15:45",
267 | status: "read",
268 | },
269 | ],
270 |
271 | TODAY: [
272 | {
273 | content: getRandomSentence(),
274 | sender: 3,
275 | time: "08:11:26",
276 | status: null,
277 | },
278 | {
279 | content: getRandomSentence(),
280 | sender: null,
281 | time: "08:15:45",
282 | status: "read",
283 | },
284 | {
285 | content: getRandomSentence(),
286 | sender: 3,
287 | time: "09:11:26",
288 | status: null,
289 | },
290 | {
291 | image: true,
292 | sender: 3,
293 | time: "09:12:26",
294 | status: null,
295 | },
296 | {
297 | image: true,
298 | sender: null,
299 | time: "09:13:26",
300 | status: null,
301 | },
302 | {
303 | content: getRandomSentence(),
304 | sender: null,
305 | time: "09:15:45",
306 | status: "sent",
307 | },
308 | ],
309 | },
310 | group: false,
311 | pinned: false,
312 | typing: false,
313 | },
314 | {
315 | id: 4,
316 | profile_picture: ppBoy2,
317 | name: "David Schwimmer",
318 | phone_number: "+2348123456789",
319 | whatsapp_name: "David",
320 | unread: 1,
321 | messages: {
322 | "04/06/2021": [
323 | {
324 | content: getRandomSentence(),
325 | sender: 4,
326 | time: "08:11:26",
327 | status: null,
328 | },
329 | {
330 | content: getRandomSentence(),
331 | sender: null,
332 | time: "08:15:45",
333 | status: "read",
334 | },
335 | {
336 | content: getRandomSentence(),
337 | sender: 4,
338 | time: "09:11:26",
339 | status: null,
340 | },
341 | {
342 | content: getRandomSentence(),
343 | sender: null,
344 | time: "09:15:45",
345 | status: "read",
346 | },
347 | ],
348 |
349 | YESTERDAY: [
350 | {
351 | content: getRandomSentence(),
352 | sender: 4,
353 | time: "08:11:26",
354 | status: null,
355 | },
356 | {
357 | content: getRandomSentence(),
358 | sender: null,
359 | time: "08:15:45",
360 | status: "read",
361 | },
362 | {
363 | content: getRandomSentence(),
364 | sender: 4,
365 | time: "09:11:26",
366 | status: null,
367 | },
368 | {
369 | content: getRandomSentence(),
370 | sender: null,
371 | time: "09:15:45",
372 | status: "read",
373 | },
374 | ],
375 |
376 | TODAY: [
377 | {
378 | content: getRandomSentence(),
379 | sender: 4,
380 | time: "08:11:26",
381 | status: null,
382 | },
383 | {
384 | content: getRandomSentence(),
385 | sender: null,
386 | time: "08:15:45",
387 | status: "read",
388 | },
389 | {
390 | content: getRandomSentence(),
391 | sender: 4,
392 | time: "09:11:26",
393 | status: null,
394 | },
395 | {
396 | content: getRandomSentence(),
397 | sender: null,
398 | time: "09:15:45",
399 | status: "read",
400 | },
401 | {
402 | content: getRandomSentence(),
403 | sender: 4,
404 | time: "09:11:26",
405 | status: null,
406 | },
407 | {
408 | content: getRandomSentence(),
409 | sender: 4,
410 | time: "09:11:26",
411 | status: null,
412 | },
413 | ],
414 | },
415 | group: false,
416 | pinned: false,
417 | typing: false,
418 | },
419 |
420 | {
421 | id: 5,
422 | profile_picture: ppBoy1,
423 | name: "Daniel Oladeji",
424 | phone_number: "+2348123456789",
425 | whatsapp_name: "Beyonce",
426 | unread: 0,
427 | messages: {
428 | "04/06/2021": [
429 | {
430 | content: getRandomSentence(),
431 | sender: 5,
432 | time: "08:11:26",
433 | status: null,
434 | },
435 | {
436 | content: getRandomSentence(),
437 | sender: null,
438 | time: "08:15:45",
439 | status: "read",
440 | },
441 | {
442 | content: getRandomSentence(),
443 | sender: 5,
444 | time: "09:11:26",
445 | status: null,
446 | },
447 | {
448 | content: getRandomSentence(),
449 | sender: null,
450 | time: "09:15:45",
451 | status: "read",
452 | },
453 | ],
454 |
455 | YESTERDAY: [
456 | {
457 | content: getRandomSentence(),
458 | sender: 5,
459 | time: "08:11:26",
460 | status: null,
461 | },
462 | {
463 | content: getRandomSentence(),
464 | sender: null,
465 | time: "08:15:45",
466 | status: "read",
467 | },
468 | {
469 | content: getRandomSentence(),
470 | sender: 5,
471 | time: "09:11:26",
472 | status: null,
473 | },
474 | {
475 | content: getRandomSentence(),
476 | sender: null,
477 | time: "09:15:45",
478 | status: "read",
479 | },
480 | ],
481 |
482 | TODAY: [
483 | {
484 | content: getRandomSentence(),
485 | sender: 5,
486 | time: "08:11:26",
487 | status: null,
488 | },
489 | {
490 | content: getRandomSentence(),
491 | sender: null,
492 | time: "08:15:45",
493 | status: "read",
494 | },
495 | {
496 | content: getRandomSentence(),
497 | sender: 5,
498 | time: "09:11:26",
499 | status: null,
500 | },
501 | {
502 | content: getRandomSentence(),
503 | sender: null,
504 | time: "09:15:45",
505 | status: "read",
506 | },
507 | ],
508 | },
509 | group: false,
510 | pinned: false,
511 | typing: false,
512 | },
513 | {
514 | id: 6,
515 | profile_picture: ppBoy3,
516 | name: "Chris Breno",
517 | phone_number: "+2348123456789",
518 | whatsapp_name: "Chris",
519 | unread: 3,
520 | messages: {
521 | "04/06/2021": [
522 | {
523 | content: getRandomSentence(),
524 | sender: 6,
525 | time: "08:11:26",
526 | status: null,
527 | },
528 | {
529 | content: getRandomSentence(),
530 | sender: null,
531 | time: "08:15:45",
532 | status: "read",
533 | },
534 | {
535 | content: getRandomSentence(),
536 | sender: 6,
537 | time: "09:11:26",
538 | status: null,
539 | },
540 | {
541 | content: getRandomSentence(),
542 | sender: null,
543 | time: "09:15:45",
544 | status: "read",
545 | },
546 | ],
547 |
548 | YESTERDAY: [
549 | {
550 | content: getRandomSentence(),
551 | sender: 6,
552 | time: "08:11:26",
553 | status: null,
554 | },
555 | {
556 | content: getRandomSentence(),
557 | sender: null,
558 | time: "08:15:45",
559 | status: "read",
560 | },
561 | {
562 | content: getRandomSentence(),
563 | sender: 6,
564 | time: "09:11:26",
565 | status: null,
566 | },
567 | {
568 | content: getRandomSentence(),
569 | sender: null,
570 | time: "09:15:45",
571 | status: "read",
572 | },
573 | ],
574 |
575 | TODAY: [
576 | {
577 | content: getRandomSentence(),
578 | sender: 6,
579 | time: "08:11:26",
580 | status: null,
581 | },
582 | {
583 | content: getRandomSentence(),
584 | sender: null,
585 | time: "08:15:45",
586 | status: "read",
587 | },
588 | {
589 | content: getRandomSentence(),
590 | sender: 6,
591 | time: "09:11:26",
592 | status: null,
593 | },
594 | {
595 | content: getRandomSentence(),
596 | sender: null,
597 | time: "09:15:45",
598 | status: "read",
599 | },
600 | {
601 | content: getRandomSentence(),
602 | sender: 6,
603 | time: "09:11:26",
604 | status: null,
605 | },
606 | {
607 | content: getRandomSentence(),
608 | sender: 6,
609 | time: "09:11:26",
610 | status: null,
611 | },
612 | ],
613 | },
614 | group: false,
615 | pinned: false,
616 | typing: false,
617 | },
618 |
619 | {
620 | id: 7,
621 | profile_picture: ppGirl3,
622 | name: "Karen Okonkwo",
623 | phone_number: "+2348123456789",
624 | whatsapp_name: "Karen",
625 | unread: 0,
626 | messages: {
627 | "04/06/2021": [
628 | {
629 | content: getRandomSentence(),
630 | sender: 8,
631 | time: "08:11:26",
632 | status: null,
633 | },
634 | {
635 | content: getRandomSentence(),
636 | sender: null,
637 | time: "08:15:45",
638 | status: "read",
639 | },
640 | {
641 | content: getRandomSentence(),
642 | sender: 7,
643 | time: "09:11:26",
644 | status: null,
645 | },
646 | {
647 | content: getRandomSentence(),
648 | sender: null,
649 | time: "09:15:45",
650 | status: "read",
651 | },
652 | ],
653 |
654 | YESTERDAY: [
655 | {
656 | content: getRandomSentence(),
657 | sender: 7,
658 | time: "08:11:26",
659 | status: null,
660 | },
661 | {
662 | content: getRandomSentence(),
663 | sender: null,
664 | time: "08:15:45",
665 | status: "read",
666 | },
667 | {
668 | content: getRandomSentence(),
669 | sender: 7,
670 | time: "09:11:26",
671 | status: null,
672 | },
673 | {
674 | content: getRandomSentence(),
675 | sender: null,
676 | time: "09:15:45",
677 | status: "read",
678 | },
679 | ],
680 |
681 | TODAY: [
682 | {
683 | content: getRandomSentence(),
684 | sender: 7,
685 | time: "08:11:26",
686 | status: null,
687 | },
688 | {
689 | content: getRandomSentence(),
690 | sender: null,
691 | time: "08:15:45",
692 | status: "read",
693 | },
694 | {
695 | content: getRandomSentence(),
696 | sender: 7,
697 | time: "09:11:26",
698 | status: null,
699 | },
700 | {
701 | content: getRandomSentence(),
702 | sender: null,
703 | time: "09:15:45",
704 | status: "read",
705 | },
706 | ],
707 | },
708 | group: false,
709 | pinned: false,
710 | typing: false,
711 | },
712 |
713 | {
714 | id: 8,
715 | profile_picture: ppGirl4,
716 | name: "Beyoncé Knowles",
717 | phone_number: "+2348123456789",
718 | whatsapp_name: "Beyonce",
719 | unread: 0,
720 | messages: {
721 | "04/06/2021": [
722 | {
723 | content: getRandomSentence(),
724 | sender: 8,
725 | time: "08:11:26",
726 | status: null,
727 | },
728 | {
729 | content: getRandomSentence(),
730 | sender: null,
731 | time: "08:15:45",
732 | status: "read",
733 | },
734 | {
735 | content: getRandomSentence(),
736 | sender: 8,
737 | time: "09:11:26",
738 | status: null,
739 | },
740 | {
741 | content: getRandomSentence(),
742 | sender: null,
743 | time: "09:15:45",
744 | status: "read",
745 | },
746 | ],
747 |
748 | YESTERDAY: [
749 | {
750 | content: getRandomSentence(),
751 | sender: 8,
752 | time: "08:11:26",
753 | status: null,
754 | },
755 | {
756 | content: getRandomSentence(),
757 | sender: null,
758 | time: "08:15:45",
759 | status: "read",
760 | },
761 | {
762 | content: getRandomSentence(),
763 | sender: 8,
764 | time: "09:11:26",
765 | status: null,
766 | },
767 | {
768 | content: getRandomSentence(),
769 | sender: null,
770 | time: "09:15:45",
771 | status: "read",
772 | },
773 | ],
774 |
775 | TODAY: [
776 | {
777 | content: getRandomSentence(),
778 | sender: 8,
779 | time: "08:11:26",
780 | status: null,
781 | },
782 | {
783 | content: getRandomSentence(),
784 | sender: null,
785 | time: "08:15:45",
786 | status: "read",
787 | },
788 | {
789 | content: getRandomSentence(),
790 | sender: 8,
791 | time: "09:11:26",
792 | status: null,
793 | },
794 | {
795 | content: getRandomSentence(),
796 | sender: null,
797 | time: "09:15:45",
798 | status: "read",
799 | },
800 | ],
801 | },
802 | group: false,
803 | pinned: false,
804 | typing: false,
805 | },
806 | ];
807 |
808 | export default users;
809 |
--------------------------------------------------------------------------------
/src/assets/icons/index.jsx:
--------------------------------------------------------------------------------
1 | const icons = {
2 | attach: (props) => (
3 |
10 |
14 |
15 | ),
16 | attachCamera: (props) => (
17 |
22 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
45 |
46 |
47 |
48 |
52 |
53 |
54 |
55 | ),
56 | attachContacts: (props) => (
57 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
76 |
80 |
81 |
82 |
86 |
87 |
88 | ),
89 | attachDocument: (props) => (
90 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
109 |
113 |
114 |
115 |
119 |
120 |
121 | ),
122 | attachImage: (props) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
142 |
146 |
147 |
148 |
149 |
153 |
154 |
155 | ),
156 | attachRooms: (props) => (
157 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
177 |
181 |
182 |
183 |
184 |
190 |
191 |
192 | ),
193 | back: (props) => (
194 |
201 |
205 |
206 | ),
207 | block: (props) => (
208 |
215 |
219 |
220 | ),
221 | cancel: (props) => (
222 |
229 |
233 |
234 | ),
235 | chat: (props) => (
236 |
243 |
247 |
248 | ),
249 | check: (props) => (
250 |
257 |
261 |
262 | ),
263 | delete: (props) => (
264 |
271 |
275 |
276 | ),
277 | doubleTick: (props) => (
278 |
285 |
289 |
290 | ),
291 | downArrow: (props) => (
292 |
299 |
303 |
304 | ),
305 | emojiActivity: (props) => (
306 |
313 |
317 |
321 |
322 | ),
323 | emojiFlags: (props) => (
324 |
331 |
335 |
336 | ),
337 | emojiFood: (props) => (
338 |
345 |
349 |
353 |
354 | ),
355 | emojiNature: (props) => (
356 |
363 |
367 |
368 | ),
369 | emojiObjects: (props) => (
370 |
377 |
381 |
385 |
386 | ),
387 | emojiPeople: (props) => (
388 |
395 |
399 |
403 |
404 | ),
405 | emojiSymbols: (props) => (
406 |
413 |
417 |
421 |
422 | ),
423 | emojiTravel: (props) => (
424 |
431 |
435 |
436 | ),
437 | gif: (props) => (
438 |
445 |
449 |
450 | ),
451 | laptop: (props) => (
452 |
459 |
463 |
464 | ),
465 | lock: (props) => (
466 |
474 |
478 |
479 | ),
480 | menu: (props) => (
481 |
488 |
492 |
493 | ),
494 | microphone: (props) => (
495 |
502 |
506 |
507 | ),
508 | notification: (props) => (
509 |
516 |
520 |
521 | ),
522 | noWifi: (props) => (
523 |
530 |
534 |
535 | ),
536 | pinned: (props) => (
537 |
544 |
548 |
549 | ),
550 | recent: (props) => (
551 |
558 |
562 |
563 | ),
564 | rightArrow: (props) => (
565 |
571 |
577 |
581 |
582 |
583 | ),
584 | search: (props) => (
585 |
592 |
596 |
597 | ),
598 | send: (props) => (
599 |
606 |
610 |
611 | ),
612 | singleTick: (props) => (
613 |
620 |
624 |
625 | ),
626 | smiley: (props) => (
627 |
634 |
638 |
639 | ),
640 | status: (props) => (
641 |
649 |
653 |
654 |
655 | ),
656 | sticker: (props) => (
657 |
664 |
668 |
669 | ),
670 | thumbsDown: (props) => (
671 |
678 |
683 |
684 | ),
685 | whatsapp: (props) => (
686 |
694 |
695 |
696 | ),
697 | };
698 |
699 | // name: (props) => (),
700 |
701 | export default icons;
702 |
--------------------------------------------------------------------------------