├── 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 | 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 | 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 | 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 | 29 | 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 |
8 |
9 | {user?.name} 10 |
11 | 12 |
13 |

{user?.name}

14 |

15 | {user.typing ? "typing..." : "online"} 16 |

17 |
18 |
19 | 29 | 42 |
43 |
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 |
26 |
27 | 28 |
29 |
30 |

Phone Not Connected

31 |

32 | Make sure your phone has an active internet connection.{" "} 33 | 38 | {" "} 39 | Learn more.{" "} 40 | 41 |

42 |
43 |
, 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 | 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 | 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 | {contact.profile_picture} 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 | 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 | 32 | )} 33 | 41 | {showEmojis && ( 42 | <> 43 | 46 | 49 | 50 | )} 51 |
52 | 60 | 61 |
64 | {attachButtons.map((btn) => ( 65 | 72 | ))} 73 |
74 |
75 | setNewMessage(e.target.value)} 80 | onKeyDown={detectEnterPress} 81 | /> 82 | {newMessage ? ( 83 | 86 | ) : ( 87 | 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 | 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 | 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 | 64 |
65 | ) : message.sender ? ( 66 |

67 | {message.content} 68 | 69 | 70 | {formatTime(message.time)} 71 | 72 | 78 |

79 | ) : ( 80 |

81 | {message.content} 82 | 83 | 84 | {formatTime(message.time)} 85 | 98 | 99 | 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 | {user.name} 34 |
35 |

{user.name}

36 |
37 | 38 |
39 |
40 |

Media, Links and Documents

41 | 44 |
45 |
46 | media 47 | media 48 | media 49 |
50 |
51 | 52 |
    53 |
  • 54 |

    55 | 56 | Mute Notifications 57 | 58 |

    59 |
    60 | 61 |
    62 |
  • 63 |
  • 64 |

    65 | 66 | Starred Messages 67 | 68 |

    69 | 72 |
  • 73 |
  • 74 |

    75 | 76 | Disappearing Messages 77 | 78 | 79 | Off 80 | 81 |

    82 | 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 | Group 3 110 |
111 |
112 |

113 | {group.name} 114 |

115 |

116 | {group.members} 117 |

118 |
119 |
120 | ))} 121 |
122 | 123 |
124 | 125 |

Block

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