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

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

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