├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE.MD ├── README.md ├── final └── src │ ├── App.css │ ├── App.js │ ├── components │ ├── ChatBox.js │ ├── Message.js │ ├── NavBar.js │ ├── SendMessage.js │ └── Welcome.js │ ├── firebase.js │ ├── img │ └── btn_google_signin_dark_pressed_web.png │ └── index.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── setup └── src │ ├── App.css │ ├── App.js │ ├── components │ ├── ChatBox.js │ ├── Message.js │ ├── NavBar.js │ ├── SendMessage.js │ └── Welcome.js │ ├── img │ └── btn_google_signin_dark_pressed_web.png │ └── index.js └── src ├── App.css ├── App.js ├── components ├── ChatBox.js ├── Message.js ├── NavBar.js ├── SendMessage.js └── Welcome.js ├── firebase.js ├── img └── btn_google_signin_dark_pressed_web.png └── index.js /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ["timonwa"] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ["https://www.buymeacoffee.com/timonwa", "https://paystack.com/pay/timonwa"] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .vscode -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Timonwa Akintokun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a Real-time Chat App with ReactJs and Firebase. :writing_hand: 2 | 3 | This is the code example for my article on [Building a Real-time Chat App with ReactJs and Firebase](https://www.freecodecamp.org/news/building-a-real-time-chat-app-with-reactjs-and-firebase/) for [FreeCodeCamp](https://freecodecamp.org/). Here is the [live demo](https://react-chat-timonwa.vercel.app/). 4 | 5 | Visit my blog, [Timonwa's Notes](https://blog.timonwa.com), for awesome technical contents like articles, codesnippets, tech goodies, community projects and more. 6 | 7 | Please give this repo a ⭐ if it was helpful to you. 8 | 9 | ![A screencast demo of the react chat app. I log in in with my Google account, the chat room shows up, then I log out](https://user-images.githubusercontent.com/63044364/211147631-d8b8a732-1572-4801-ba01-99a271b77bc4.gif) 10 | -------------------------------------------------------------------------------- /final/src/App.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, 10 | body { 11 | max-width: 100vw; 12 | font-size: 16px; 13 | font-family: sans-serif; 14 | } 15 | 16 | button, 17 | input { 18 | cursor: pointer; 19 | } 20 | 21 | .App { 22 | width: 100%; 23 | min-height: 100vh; 24 | background-color: #1c2c4c; 25 | color: #4c768d; 26 | padding-top: 60px; 27 | } 28 | 29 | /* navbar */ 30 | .nav-bar { 31 | padding: 10px 30px; 32 | display: flex; 33 | align-items: center; 34 | justify-content: space-between; 35 | background-color: #4c768d; 36 | color: #242443; 37 | height: 60px; 38 | position: fixed; 39 | left: 0; 40 | top: 0; 41 | width: 100%; 42 | z-index: 1; 43 | } 44 | .sign-in { 45 | border: none; 46 | background-color: transparent; 47 | } 48 | .sign-in > img { 49 | height: 30px; 50 | width: auto; 51 | } 52 | .sign-out { 53 | padding: 5px 10px; 54 | border-radius: 5px; 55 | color: #88dded; 56 | border: 1px solid #1c2c4c; 57 | background-color: #1c2c4c; 58 | font-weight: 600; 59 | } 60 | 61 | /* welcome page */ 62 | .welcome { 63 | padding: 30px; 64 | text-align: center; 65 | margin-top: 40px; 66 | color: #7cc5d9; 67 | } 68 | .welcome :is(h2, p, img) { 69 | margin-bottom: 20px; 70 | } 71 | 72 | /* chat component */ 73 | .messages-wrapper { 74 | padding: 30px; 75 | margin-bottom: 60px; 76 | } 77 | .chat-bubble { 78 | border-radius: 20px 20px 20px 0; 79 | padding: 15px; 80 | background-color: #7cc5d9; 81 | color: #1c2c4c; 82 | width: max-content; 83 | max-width: calc(100% - 50px); 84 | box-shadow: -2px 2px 1px 1px #4c768d; 85 | display: flex; 86 | align-items: flex-start; 87 | margin-bottom: 20px; 88 | } 89 | .chat-bubble.right { 90 | margin-left: auto; 91 | border-radius: 20px 20px 0 20px; 92 | background-color: #fff; 93 | box-shadow: -2px 2px 1px 1px #88dded; 94 | } 95 | .chat-bubble__left { 96 | width: 35px; 97 | height: 35px; 98 | border-radius: 50%; 99 | margin-right: 10px; 100 | } 101 | .user-name { 102 | font-weight: bold; 103 | margin-bottom: 5px; 104 | font-size: 0.9rem; 105 | color: #1c2c4c; 106 | } 107 | .user-message { 108 | word-break: break-all; 109 | } 110 | .message-time { 111 | display: block; 112 | text-align: right; 113 | } 114 | 115 | /* send message */ 116 | .send-message { 117 | position: fixed; 118 | bottom: 0px; 119 | width: 100%; 120 | padding: 20px 30px; 121 | background-color: #4c768d; 122 | display: flex; 123 | } 124 | .send-message > input { 125 | height: 40px; 126 | padding: 10px 10px; 127 | border-radius: 5px 0 0 5px; 128 | border: none; 129 | flex-grow: 1; 130 | background-color: white; 131 | color: #1c2c4c; 132 | font-size: 1rem; 133 | } 134 | .send-message > input:placeholder { 135 | color: #ddd; 136 | } 137 | .send-message > :is(input, button):focus { 138 | outline: none; 139 | border-bottom: 1px solid #7cc5d9; 140 | } 141 | .send-message > button { 142 | width: 70px; 143 | height: 40px; 144 | padding: 5px 10px; 145 | border-radius: 0 5px 5px 0; 146 | color: #242443; 147 | border: 1px solid #7cc5d9; 148 | background-color: #7cc5d9; 149 | font-weight: 600; 150 | } 151 | -------------------------------------------------------------------------------- /final/src/App.js: -------------------------------------------------------------------------------- 1 | import { auth } from "./firebase"; 2 | import { useAuthState } from "react-firebase-hooks/auth"; 3 | import "./App.css"; 4 | import NavBar from "./components/NavBar"; 5 | import ChatBox from "./components/ChatBox"; 6 | import Welcome from "./components/Welcome"; 7 | 8 | function App() { 9 | const [user] = useAuthState(auth); 10 | 11 | return ( 12 |
13 | 14 | {!user ? ( 15 | 16 | ) : ( 17 | <> 18 | 19 | 20 | )} 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /final/src/components/ChatBox.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import { 3 | query, 4 | collection, 5 | orderBy, 6 | onSnapshot, 7 | limit, 8 | } from "firebase/firestore"; 9 | import { db } from "../firebase"; 10 | import Message from "./Message"; 11 | import SendMessage from "./SendMessage"; 12 | 13 | const ChatBox = () => { 14 | const [messages, setMessages] = useState([]); 15 | const scroll = useRef(); 16 | 17 | useEffect(() => { 18 | const q = query( 19 | collection(db, "messages"), 20 | orderBy("createdAt", "desc"), 21 | limit(50) 22 | ); 23 | 24 | const unsubscribe = onSnapshot(q, (QuerySnapshot) => { 25 | const fetchedMessages = []; 26 | QuerySnapshot.forEach((doc) => { 27 | fetchedMessages.push({ ...doc.data(), id: doc.id }); 28 | }); 29 | const sortedMessages = fetchedMessages.sort( 30 | (a, b) => a.createdAt - b.createdAt 31 | ); 32 | setMessages(sortedMessages); 33 | }); 34 | return () => unsubscribe; 35 | }, []); 36 | 37 | return ( 38 |
39 |
40 | {messages?.map((message) => ( 41 | 42 | ))} 43 |
44 | {/* when a new message enters the chat, the screen scrolls down to the scroll div */} 45 | 46 | 47 |
48 | ); 49 | }; 50 | 51 | export default ChatBox; 52 | -------------------------------------------------------------------------------- /final/src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { auth } from "../firebase"; 3 | import { useAuthState } from "react-firebase-hooks/auth"; 4 | 5 | const Message = ({ message }) => { 6 | const [user] = useAuthState(auth); 7 | return ( 8 |
10 | user avatar 15 |
16 |

{message.name}

17 |

{message.text}

18 |
19 |
20 | ); 21 | }; 22 | 23 | export default Message; 24 | -------------------------------------------------------------------------------- /final/src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png"; 3 | import { auth } from "../firebase"; 4 | import { useAuthState } from "react-firebase-hooks/auth"; 5 | import { GoogleAuthProvider, signInWithPopup } from "firebase/auth"; 6 | 7 | const NavBar = () => { 8 | const [user] = useAuthState(auth); 9 | 10 | const googleSignIn = () => { 11 | const provider = new GoogleAuthProvider(); 12 | signInWithPopup(auth, provider); 13 | }; 14 | 15 | const signOut = () => { 16 | auth.signOut(); 17 | }; 18 | 19 | return ( 20 | 37 | ); 38 | }; 39 | 40 | export default NavBar; 41 | -------------------------------------------------------------------------------- /final/src/components/SendMessage.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { auth, db } from "../firebase"; 3 | import { addDoc, collection, serverTimestamp } from "firebase/firestore"; 4 | 5 | const SendMessage = ({ scroll }) => { 6 | const [message, setMessage] = useState(""); 7 | 8 | const sendMessage = async (event) => { 9 | event.preventDefault(); 10 | if (message.trim() === "") { 11 | alert("Enter valid message"); 12 | return; 13 | } 14 | const { uid, displayName, photoURL } = auth.currentUser; 15 | await addDoc(collection(db, "messages"), { 16 | text: message, 17 | name: displayName, 18 | avatar: photoURL, 19 | createdAt: serverTimestamp(), 20 | uid, 21 | }); 22 | setMessage(""); 23 | scroll.current.scrollIntoView({ behavior: "smooth" }); 24 | }; 25 | return ( 26 |
sendMessage(event)} className="send-message"> 27 | 30 | setMessage(e.target.value)} 38 | /> 39 | 40 |
41 | ); 42 | }; 43 | 44 | export default SendMessage; 45 | -------------------------------------------------------------------------------- /final/src/components/Welcome.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png"; 3 | import { auth } from "../firebase"; 4 | import { GoogleAuthProvider, signInWithPopup } from "firebase/auth"; 5 | 6 | const Welcome = () => { 7 | const googleSignIn = () => { 8 | const provider = new GoogleAuthProvider(); 9 | signInWithPopup(auth, provider); 10 | }; 11 | 12 | return ( 13 |
14 |

Welcome to React Chat.

15 | ReactJs logo 16 |

Sign in with Google to chat with with your fellow React Developers.

17 | 25 |
26 | ); 27 | }; 28 | 29 | export default Welcome; 30 | -------------------------------------------------------------------------------- /final/src/firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | // TODO: Add SDKs for Firebase products that you want to use 4 | // https://firebase.google.com/docs/web/setup#available-libraries 5 | import { getAuth } from "firebase/auth"; 6 | import { getFirestore } from "firebase/firestore"; 7 | 8 | // Your web app's Firebase configuration 9 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 10 | const firebaseConfig = { 11 | apiKey: process.env.REACT_APP_API_KEY, 12 | authDomain: process.env.REACT_APP_AUTH_DOMAIN, 13 | projectId: process.env.REACT_APP_PROJECT_ID, 14 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET, 15 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, 16 | appId: process.env.REACT_APP_APP_ID, 17 | measurementId: process.env.REACT_APP_MEASUREMENT_ID, 18 | }; 19 | 20 | // Initialize Firebase 21 | const app = initializeApp(firebaseConfig); 22 | 23 | // Initialize Firebase Authentication and get a reference to the service 24 | export const auth = getAuth(app); 25 | export const db = getFirestore(app); 26 | -------------------------------------------------------------------------------- /final/src/img/btn_google_signin_dark_pressed_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timonwa/react-chat/24987e677d2104d2fd248299e71596c4c895a4ef/final/src/img/btn_google_signin_dark_pressed_web.png -------------------------------------------------------------------------------- /final/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById("root")); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-chat", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "firebase": "^9.13.0", 10 | "react": "^18.2.0", 11 | "react-dom": "^18.2.0", 12 | "react-firebase-hooks": "^5.0.3", 13 | "react-scripts": "5.0.1", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timonwa/react-chat/24987e677d2104d2fd248299e71596c4c895a4ef/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React Chat built by Timonwa 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timonwa/react-chat/24987e677d2104d2fd248299e71596c4c895a4ef/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timonwa/react-chat/24987e677d2104d2fd248299e71596c4c895a4ef/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /setup/src/App.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, 10 | body { 11 | max-width: 100vw; 12 | font-size: 16px; 13 | font-family: sans-serif; 14 | } 15 | 16 | button, 17 | input { 18 | cursor: pointer; 19 | } 20 | 21 | .App { 22 | width: 100%; 23 | min-height: 100vh; 24 | background-color: #1c2c4c; 25 | color: #4c768d; 26 | padding-top: 60px; 27 | } 28 | 29 | /* navbar */ 30 | .nav-bar { 31 | padding: 10px 30px; 32 | display: flex; 33 | align-items: center; 34 | justify-content: space-between; 35 | background-color: #4c768d; 36 | color: #242443; 37 | height: 60px; 38 | position: fixed; 39 | left: 0; 40 | top: 0; 41 | width: 100%; 42 | z-index: 1; 43 | } 44 | .sign-in { 45 | border: none; 46 | background-color: transparent; 47 | } 48 | .sign-in > img { 49 | height: 30px; 50 | width: auto; 51 | } 52 | .sign-out { 53 | padding: 5px 10px; 54 | border-radius: 5px; 55 | color: #88dded; 56 | border: 1px solid #1c2c4c; 57 | background-color: #1c2c4c; 58 | font-weight: 600; 59 | } 60 | 61 | /* welcome page */ 62 | .welcome { 63 | padding: 30px; 64 | text-align: center; 65 | margin-top: 40px; 66 | color: #7cc5d9; 67 | } 68 | .welcome :is(h2, p, img) { 69 | margin-bottom: 20px; 70 | } 71 | 72 | /* chat component */ 73 | .messages-wrapper { 74 | padding: 30px; 75 | margin-bottom: 60px; 76 | } 77 | .chat-bubble { 78 | border-radius: 20px 20px 20px 0; 79 | padding: 15px; 80 | background-color: #7cc5d9; 81 | color: #1c2c4c; 82 | width: max-content; 83 | max-width: calc(100% - 50px); 84 | box-shadow: -2px 2px 1px 1px #4c768d; 85 | display: flex; 86 | align-items: flex-start; 87 | margin-bottom: 20px; 88 | } 89 | .chat-bubble.right { 90 | margin-left: auto; 91 | border-radius: 20px 20px 0 20px; 92 | background-color: #fff; 93 | box-shadow: -2px 2px 1px 1px #88dded; 94 | } 95 | .chat-bubble__left { 96 | width: 35px; 97 | height: 35px; 98 | border-radius: 50%; 99 | margin-right: 10px; 100 | } 101 | .user-name { 102 | font-weight: bold; 103 | margin-bottom: 5px; 104 | font-size: 0.9rem; 105 | color: #1c2c4c; 106 | } 107 | .user-message { 108 | word-break: break-all; 109 | } 110 | .message-time { 111 | display: block; 112 | text-align: right; 113 | } 114 | 115 | /* send message */ 116 | .send-message { 117 | position: fixed; 118 | bottom: 0px; 119 | width: 100%; 120 | padding: 20px 30px; 121 | background-color: #4c768d; 122 | display: flex; 123 | } 124 | .send-message > input { 125 | height: 40px; 126 | padding: 10px 10px; 127 | border-radius: 5px 0 0 5px; 128 | border: none; 129 | flex-grow: 1; 130 | background-color: white; 131 | color: #1c2c4c; 132 | font-size: 1rem; 133 | } 134 | .send-message > input:placeholder { 135 | color: #ddd; 136 | } 137 | .send-message > :is(input, button):focus { 138 | outline: none; 139 | border-bottom: 1px solid #7cc5d9; 140 | } 141 | .send-message > button { 142 | width: 70px; 143 | height: 40px; 144 | padding: 5px 10px; 145 | border-radius: 0 5px 5px 0; 146 | color: #242443; 147 | border: 1px solid #7cc5d9; 148 | background-color: #7cc5d9; 149 | font-weight: 600; 150 | } 151 | -------------------------------------------------------------------------------- /setup/src/App.js: -------------------------------------------------------------------------------- 1 | import "./App.css"; 2 | import NavBar from "./components/NavBar"; 3 | import ChatBox from "./components/ChatBox"; 4 | import Welcome from "./components/Welcome"; 5 | import { useState } from "react"; 6 | 7 | function App() { 8 | const [user, setUser] = useState(false); 9 | 10 | return ( 11 |
12 | 13 | {!user ? ( 14 | 15 | ) : ( 16 | <> 17 | 18 | 19 | )} 20 |
21 | ); 22 | } 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /setup/src/components/ChatBox.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Message from "./Message"; 3 | import SendMessage from "./SendMessage"; 4 | 5 | const ChatBox = () => { 6 | return ( 7 |
8 |
9 | 10 |
11 | 12 |
13 | ); 14 | }; 15 | 16 | export default ChatBox; 17 | -------------------------------------------------------------------------------- /setup/src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Message = () => { 4 | return ( 5 |
6 | user avatar 7 |
8 |

Timonwa Akintokun

9 |

10 | We are building a real time chat app with React and Firebase. 11 |

12 |
13 |
14 | ); 15 | }; 16 | 17 | export default Message; 18 | -------------------------------------------------------------------------------- /setup/src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png"; 3 | 4 | const NavBar = () => { 5 | const [user, setUser] = useState(false); 6 | 7 | const googleSignIn = () => { 8 | setUser(true); 9 | }; 10 | 11 | const signOut = () => { 12 | setUser(false); 13 | }; 14 | 15 | return ( 16 | 33 | ); 34 | }; 35 | 36 | export default NavBar; 37 | -------------------------------------------------------------------------------- /setup/src/components/SendMessage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const SendMessage = () => { 4 | return ( 5 |
6 | 9 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default SendMessage; 22 | -------------------------------------------------------------------------------- /setup/src/components/Welcome.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png"; 3 | 4 | const Welcome = () => { 5 | const googleSignIn = () => { 6 | }; 7 | 8 | return ( 9 |
10 |

Welcome to React Chat.

11 | ReactJs logo 12 |

Sign in with Google to chat with with your fellow React Developers.

13 | 21 |
22 | ); 23 | }; 24 | 25 | export default Welcome; 26 | -------------------------------------------------------------------------------- /setup/src/img/btn_google_signin_dark_pressed_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timonwa/react-chat/24987e677d2104d2fd248299e71596c4c895a4ef/setup/src/img/btn_google_signin_dark_pressed_web.png -------------------------------------------------------------------------------- /setup/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById("root")); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html, 10 | body { 11 | max-width: 100vw; 12 | font-size: 16px; 13 | font-family: sans-serif; 14 | } 15 | 16 | button, 17 | input { 18 | cursor: pointer; 19 | } 20 | 21 | .App { 22 | width: 100%; 23 | min-height: 100vh; 24 | background-color: #1c2c4c; 25 | color: #4c768d; 26 | padding-top: 60px; 27 | } 28 | 29 | /* navbar */ 30 | .nav-bar { 31 | padding: 10px 30px; 32 | display: flex; 33 | align-items: center; 34 | justify-content: space-between; 35 | background-color: #4c768d; 36 | color: #242443; 37 | height: 60px; 38 | position: fixed; 39 | left: 0; 40 | top: 0; 41 | width: 100%; 42 | z-index: 1; 43 | } 44 | .sign-in { 45 | border: none; 46 | background-color: transparent; 47 | } 48 | .sign-in > img { 49 | height: 30px; 50 | width: auto; 51 | } 52 | .sign-out { 53 | padding: 5px 10px; 54 | border-radius: 5px; 55 | color: #88dded; 56 | border: 1px solid #1c2c4c; 57 | background-color: #1c2c4c; 58 | font-weight: 600; 59 | } 60 | 61 | /* welcome page */ 62 | .welcome { 63 | padding: 30px; 64 | text-align: center; 65 | margin-top: 40px; 66 | color: #7cc5d9; 67 | } 68 | .welcome :is(h2, p, img) { 69 | margin-bottom: 20px; 70 | } 71 | 72 | /* chat component */ 73 | .messages-wrapper { 74 | padding: 30px; 75 | margin-bottom: 60px; 76 | } 77 | .chat-bubble { 78 | border-radius: 20px 20px 20px 0; 79 | padding: 15px; 80 | background-color: #7cc5d9; 81 | color: #1c2c4c; 82 | width: max-content; 83 | max-width: calc(100% - 50px); 84 | box-shadow: -2px 2px 1px 1px #4c768d; 85 | display: flex; 86 | align-items: flex-start; 87 | margin-bottom: 20px; 88 | } 89 | .chat-bubble.right { 90 | margin-left: auto; 91 | border-radius: 20px 20px 0 20px; 92 | background-color: #fff; 93 | box-shadow: -2px 2px 1px 1px #88dded; 94 | } 95 | .chat-bubble__left { 96 | width: 35px; 97 | height: 35px; 98 | border-radius: 50%; 99 | margin-right: 10px; 100 | } 101 | .user-name { 102 | font-weight: bold; 103 | margin-bottom: 5px; 104 | font-size: 0.9rem; 105 | color: #1c2c4c; 106 | } 107 | .user-message { 108 | word-break: break-all; 109 | } 110 | .message-time { 111 | display: block; 112 | text-align: right; 113 | } 114 | 115 | /* send message */ 116 | .send-message { 117 | position: fixed; 118 | bottom: 0px; 119 | width: 100%; 120 | padding: 20px 30px; 121 | background-color: #4c768d; 122 | display: flex; 123 | } 124 | .send-message > input { 125 | height: 40px; 126 | padding: 10px 10px; 127 | border-radius: 5px 0 0 5px; 128 | border: none; 129 | flex-grow: 1; 130 | background-color: white; 131 | color: #1c2c4c; 132 | font-size: 1rem; 133 | } 134 | .send-message > input:placeholder { 135 | color: #ddd; 136 | } 137 | .send-message > :is(input, button):focus { 138 | outline: none; 139 | border-bottom: 1px solid #7cc5d9; 140 | } 141 | .send-message > button { 142 | width: 70px; 143 | height: 40px; 144 | padding: 5px 10px; 145 | border-radius: 0 5px 5px 0; 146 | color: #242443; 147 | border: 1px solid #7cc5d9; 148 | background-color: #7cc5d9; 149 | font-weight: 600; 150 | } 151 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { auth } from "./firebase"; 2 | import { useAuthState } from "react-firebase-hooks/auth"; 3 | import "./App.css"; 4 | import NavBar from "./components/NavBar"; 5 | import ChatBox from "./components/ChatBox"; 6 | import Welcome from "./components/Welcome"; 7 | 8 | function App() { 9 | const [user] = useAuthState(auth); 10 | 11 | return ( 12 |
13 | 14 | {!user ? ( 15 | 16 | ) : ( 17 | <> 18 | 19 | 20 | )} 21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /src/components/ChatBox.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import { 3 | query, 4 | collection, 5 | orderBy, 6 | onSnapshot, 7 | limit, 8 | } from "firebase/firestore"; 9 | import { db } from "../firebase"; 10 | import Message from "./Message"; 11 | import SendMessage from "./SendMessage"; 12 | 13 | const ChatBox = () => { 14 | const [messages, setMessages] = useState([]); 15 | const scroll = useRef(); 16 | 17 | useEffect(() => { 18 | const q = query( 19 | collection(db, "messages"), 20 | orderBy("createdAt", "desc"), 21 | limit(50) 22 | ); 23 | 24 | const unsubscribe = onSnapshot(q, (QuerySnapshot) => { 25 | const fetchedMessages = []; 26 | QuerySnapshot.forEach((doc) => { 27 | fetchedMessages.push({ ...doc.data(), id: doc.id }); 28 | }); 29 | const sortedMessages = fetchedMessages.sort( 30 | (a, b) => a.createdAt - b.createdAt 31 | ); 32 | setMessages(sortedMessages); 33 | }); 34 | return () => unsubscribe; 35 | }, []); 36 | 37 | return ( 38 |
39 |
40 | {messages?.map((message) => ( 41 | 42 | ))} 43 |
44 | {/* when a new message enters the chat, the screen scrolls down to the scroll div */} 45 | 46 | 47 |
48 | ); 49 | }; 50 | 51 | export default ChatBox; 52 | -------------------------------------------------------------------------------- /src/components/Message.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { auth } from "../firebase"; 3 | import { useAuthState } from "react-firebase-hooks/auth"; 4 | 5 | const Message = ({ message }) => { 6 | const [user] = useAuthState(auth); 7 | return ( 8 |
9 | user avatar 14 |
15 |

{message.name}

16 |

{message.text}

17 |
18 |
19 | ); 20 | }; 21 | 22 | export default Message; 23 | -------------------------------------------------------------------------------- /src/components/NavBar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png"; 3 | import { auth } from "../firebase"; 4 | import { useAuthState } from "react-firebase-hooks/auth"; 5 | import { GoogleAuthProvider, signInWithPopup } from "firebase/auth"; 6 | 7 | const NavBar = () => { 8 | const [user] = useAuthState(auth); 9 | 10 | const googleSignIn = () => { 11 | const provider = new GoogleAuthProvider(); 12 | signInWithPopup(auth, provider); 13 | }; 14 | 15 | const signOut = () => { 16 | auth.signOut(); 17 | }; 18 | 19 | return ( 20 | 37 | ); 38 | }; 39 | 40 | export default NavBar; 41 | -------------------------------------------------------------------------------- /src/components/SendMessage.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { auth, db } from "../firebase"; 3 | import { addDoc, collection, serverTimestamp } from "firebase/firestore"; 4 | 5 | const SendMessage = ({ scroll }) => { 6 | const [message, setMessage] = useState(""); 7 | 8 | const sendMessage = async (event) => { 9 | event.preventDefault(); 10 | if (message.trim() === "") { 11 | alert("Enter valid message"); 12 | return; 13 | } 14 | const { uid, displayName, photoURL } = auth.currentUser; 15 | await addDoc(collection(db, "messages"), { 16 | text: message, 17 | name: displayName, 18 | avatar: photoURL, 19 | createdAt: serverTimestamp(), 20 | uid, 21 | }); 22 | setMessage(""); 23 | scroll.current.scrollIntoView({ behavior: "smooth" }); 24 | }; 25 | return ( 26 |
sendMessage(event)} className="send-message"> 27 | 30 | setMessage(e.target.value)} 38 | /> 39 | 40 |
41 | ); 42 | }; 43 | 44 | export default SendMessage; 45 | -------------------------------------------------------------------------------- /src/components/Welcome.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GoogleSignin from "../img/btn_google_signin_dark_pressed_web.png"; 3 | import { auth } from "../firebase"; 4 | import { GoogleAuthProvider, signInWithPopup } from "firebase/auth"; 5 | 6 | const Welcome = () => { 7 | const googleSignIn = () => { 8 | const provider = new GoogleAuthProvider(); 9 | signInWithPopup(auth, provider); 10 | }; 11 | 12 | return ( 13 |
14 |

Welcome to React Chat.

15 | ReactJs logo 16 |

Sign in with Google to chat with with your fellow React Developers.

17 | 25 |
26 | ); 27 | }; 28 | 29 | export default Welcome; 30 | -------------------------------------------------------------------------------- /src/firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | // TODO: Add SDKs for Firebase products that you want to use 4 | // https://firebase.google.com/docs/web/setup#available-libraries 5 | import { getAuth } from "firebase/auth"; 6 | import { getFirestore } from "firebase/firestore"; 7 | 8 | // Your web app's Firebase configuration 9 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 10 | const firebaseConfig = { 11 | apiKey: process.env.REACT_APP_API_KEY, 12 | authDomain: process.env.REACT_APP_AUTH_DOMAIN, 13 | projectId: process.env.REACT_APP_PROJECT_ID, 14 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET, 15 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, 16 | appId: process.env.REACT_APP_APP_ID, 17 | measurementId: process.env.REACT_APP_MEASUREMENT_ID, 18 | }; 19 | 20 | // Initialize Firebase 21 | const app = initializeApp(firebaseConfig); 22 | 23 | // Initialize Firebase Authentication and get a reference to the service 24 | export const auth = getAuth(app); 25 | export const db = getFirestore(app); 26 | -------------------------------------------------------------------------------- /src/img/btn_google_signin_dark_pressed_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Timonwa/react-chat/24987e677d2104d2fd248299e71596c4c895a4ef/src/img/btn_google_signin_dark_pressed_web.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | const root = ReactDOM.createRoot(document.getElementById("root")); 6 | root.render( 7 | 8 | 9 | 10 | ); 11 | --------------------------------------------------------------------------------