├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.css
├── App.js
├── components
├── Loading.js
├── Message.js
├── MessageForm.js
├── Navbar.js
├── PrivateRoute.js
├── User.js
└── svg
│ ├── Attachment.js
│ ├── Camera.js
│ └── Delete.js
├── context
└── auth.js
├── firebase.js
├── image1.jpg
├── index.css
├── index.js
└── pages
├── Home.js
├── Login.js
├── Profile.js
└── Register.js
/.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
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Private Chat App With React and Firebase version 9
2 |
3 | This project is built with React and latest Firebase SDK version 9.
4 |
5 | ## Run the app
6 |
7 | - Clone this repo
8 | - Run **npm install**
9 | - Create **.env** file at root level of project and add REACT_APP_API_KEY, REACT_APP_AUTH_DOMAIN, REACT_APP_DATABASE_URL, REACT_APP_PROJECT_ID, REACT_APP_STORAGE_BUCKET, REACT_APP_MESSAGING_SENDER_ID, REACT_APP_APP_ID
10 | - Value of REACT_APP_DATABASE_URL will be https://YOUR-FIREBASE-PROJECT-NAME.firebaseio.com simply replace YOUR-FIREBASE-PROJECT-NAME with your project name
11 | - Run **npm start**
12 |
13 | [YouTube video tutorial](https://youtu.be/fdcruaIiQxc)
14 |
15 | [Text based and video tutorials](https://farhanfarooq.com/tutorials)
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-messenger",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^11.2.7",
8 | "@testing-library/user-event": "^12.8.3",
9 | "firebase": "^9.0.2",
10 | "moment": "^2.29.1",
11 | "react": "^17.0.2",
12 | "react-dom": "^17.0.2",
13 | "react-moment": "^1.1.1",
14 | "react-router-dom": "^5.3.0",
15 | "react-scripts": "4.0.3",
16 | "web-vitals": "^1.1.2"
17 | },
18 | "scripts": {
19 | "start": "react-scripts start",
20 | "build": "react-scripts build",
21 | "test": "react-scripts test",
22 | "eject": "react-scripts eject"
23 | },
24 | "eslintConfig": {
25 | "extends": [
26 | "react-app",
27 | "react-app/jest"
28 | ]
29 | },
30 | "browserslist": {
31 | "production": [
32 | ">0.2%",
33 | "not dead",
34 | "not op_mini all"
35 | ],
36 | "development": [
37 | "last 1 chrome version",
38 | "last 1 firefox version",
39 | "last 1 safari version"
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsmefarhan/react-firebase-chat/6810f607c3822d46cc96d4a276adbef653619f1f/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsmefarhan/react-firebase-chat/6810f607c3822d46cc96d4a276adbef653619f1f/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsmefarhan/react-firebase-chat/6810f607c3822d46cc96d4a276adbef653619f1f/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 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | :root {
6 | --color-1: #242526;
7 | --color-2: white;
8 | --color-3: #0084ff;
9 | --color-4: grey;
10 | --color-5: #eb4034;
11 | --color-6: #333;
12 | }
13 |
14 | /* Navbar */
15 | nav {
16 | display: flex;
17 | align-items: center;
18 | justify-content: space-between;
19 | height: 70px;
20 | padding: 0px 20px;
21 | background-color: var(--color-1);
22 | border-bottom: 1px solid var(--color-6);
23 | }
24 | nav a {
25 | color: var(--color-2);
26 | text-decoration: none;
27 | }
28 | nav div a {
29 | margin-right: 20px;
30 | }
31 |
32 | section {
33 | max-width: 500px;
34 | margin: 0 auto;
35 | margin-top: 100px;
36 | box-shadow: 1px 2px 10px var(--color-4);
37 | padding: 10px 20px;
38 | border-radius: 5px;
39 | }
40 | section h3 {
41 | text-align: center;
42 | font-size: 20px;
43 | color: var(--color-4);
44 | }
45 | /* Register */
46 | section .form {
47 | margin-top: 30px;
48 | padding: 0px 20px;
49 | }
50 | .input_container {
51 | margin-top: 20px;
52 | }
53 | .input_container input {
54 | width: 100%;
55 | padding: 10px;
56 | outline: none;
57 | margin-top: 10px;
58 | border: 1px solid var(--color-6);
59 | border-radius: 5px;
60 | }
61 | .btn_container {
62 | margin: 10px 0px;
63 | text-align: center;
64 | }
65 | .btn {
66 | padding: 10px;
67 | border-radius: 5px;
68 | outline: none;
69 | border: 1px solid var(--color-4);
70 | background: var(--color-1);
71 | color: var(--color-2);
72 | cursor: pointer;
73 | transition: 0.3s ease-in-out all;
74 | font-size: 16px;
75 | }
76 | .btn:hover {
77 | transform: scale(1.05);
78 | }
79 | .error {
80 | text-align: center;
81 | color: var(--color-5);
82 | }
83 |
84 | /* Profile */
85 | .profile_container {
86 | display: flex;
87 | align-items: center;
88 | }
89 | .img_container {
90 | position: relative;
91 | margin-right: 20px;
92 | }
93 | .img_container img {
94 | width: 100px;
95 | height: 100px;
96 | border-radius: 50%;
97 | border: 1px solid var(--color-4);
98 | transition: 0.5s ease-in-out all;
99 | }
100 | .img_container:hover img {
101 | opacity: 0.4;
102 | }
103 | .img_container:hover .overlay {
104 | opacity: 1;
105 | }
106 | .overlay {
107 | transition: 0.5s ease;
108 | opacity: 0;
109 | position: absolute;
110 | top: 50%;
111 | left: 50%;
112 | transform: translate(-50%, -50%);
113 | text-align: center;
114 | }
115 | .text_container {
116 | flex-grow: 1;
117 | }
118 | .text_container h3 {
119 | text-align: left;
120 | }
121 |
122 | .home_container {
123 | position: relative;
124 | display: grid;
125 | grid-template-columns: 1fr 3fr;
126 | overflow: hidden;
127 | height: calc(100vh - 70px);
128 | width: 100vw;
129 | }
130 | .users_container {
131 | margin-top: 10px;
132 | border-right: 2px solid var(--color-6);
133 | overflow-y: auto;
134 | }
135 | .user_wrapper {
136 | margin-bottom: 10px;
137 | padding: 10px;
138 | cursor: pointer;
139 | }
140 | .user_info {
141 | display: flex;
142 | justify-content: space-between;
143 | align-items: center;
144 | }
145 | .user_detail {
146 | display: flex;
147 | align-items: center;
148 | }
149 | .user_detail h4 {
150 | margin-left: 10px;
151 | }
152 | .avatar {
153 | width: 50px;
154 | height: 50px;
155 | border-radius: 50%;
156 | border: 1px solid var(--color-4);
157 | }
158 | .user_status {
159 | width: 10px;
160 | height: 10px;
161 | border-radius: 50%;
162 | }
163 | .online {
164 | background: #34eb52;
165 | }
166 | .offline {
167 | background: var(--color-5);
168 | }
169 | .messages_container {
170 | position: relative;
171 | width: 100%;
172 | }
173 | .messages_user {
174 | padding: 10px;
175 | text-align: center;
176 | border-bottom: 2px solid var(--color-6);
177 | }
178 | .no_conv {
179 | font-size: 20px;
180 | color: var(--color-4);
181 | text-align: center;
182 | }
183 | .message_form {
184 | position: absolute;
185 | bottom: 0;
186 | left: 20%;
187 | width: 100%;
188 | height: 30px;
189 | display: flex;
190 | align-items: center;
191 | }
192 | .message_form input {
193 | width: 40vw;
194 | margin: 0px 10px 10px;
195 | padding: 10px;
196 | border-radius: 5px;
197 | outline: none;
198 | border: none;
199 | }
200 | .message_form .btn {
201 | margin-top: -10px;
202 | }
203 | .messages {
204 | height: calc(100vh - 200px);
205 | overflow-y: auto;
206 | border-bottom: 1px solid var(--color-6);
207 | }
208 | .message_wrapper {
209 | margin-top: 5px;
210 | padding: 0px 5px;
211 | }
212 | .message_wrapper img {
213 | width: 100%;
214 | border-radius: 5px;
215 | }
216 | .message_wrapper p {
217 | padding: 10px;
218 | display: inline-block;
219 | max-width: 50%;
220 | text-align: left;
221 | border-radius: 5px;
222 | }
223 | .message_wrapper small {
224 | display: inline-block;
225 | margin-top: 15px;
226 | opacity: 0.8;
227 | }
228 | .message_wrapper.own {
229 | text-align: right;
230 | }
231 | .me {
232 | background: var(--color-3);
233 | color: white;
234 | }
235 | .friend {
236 | background: var(--color-6);
237 | }
238 | .selected_user {
239 | background: var(--color-6);
240 | }
241 | .truncate {
242 | font-size: 14px;
243 | white-space: nowrap;
244 | width: 90%;
245 | overflow: hidden;
246 | text-overflow: ellipsis;
247 | }
248 | .truncate strong {
249 | margin-right: 10px;
250 | }
251 | .unread {
252 | margin-left: 10px;
253 | background: var(--color-3);
254 | color: white;
255 | padding: 2px 4px;
256 | border-radius: 10px;
257 | }
258 | .sm_screen {
259 | display: none;
260 | }
261 | @media screen and (max-width: 767px) {
262 | .home_container {
263 | grid-template-columns: 2fr 3fr;
264 | }
265 | .message_form {
266 | left: 3%;
267 | right: 0;
268 | bottom: 5px;
269 | }
270 | .message_wrapper p {
271 | max-width: 75%;
272 | }
273 | }
274 |
275 | @media screen and (max-width: 576px) {
276 | .home_container {
277 | grid-template-columns: 1fr 5fr;
278 | }
279 | .user_wrapper {
280 | display: none;
281 | }
282 | .sm_container {
283 | padding: 10px 0px;
284 | text-align: center;
285 | cursor: pointer;
286 | }
287 | .sm_screen {
288 | display: inline-block;
289 | }
290 | .message_form input {
291 | width: 50vw;
292 | margin: 0px 10px;
293 | }
294 | .message_form .btn {
295 | margin: 0px;
296 | }
297 |
298 | .message_wrapper p {
299 | max-width: 100%;
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { BrowserRouter, Switch, Route } from "react-router-dom";
3 | import Home from "./pages/Home";
4 | import Navbar from "./components/Navbar";
5 | import Register from "./pages/Register";
6 | import Login from "./pages/Login";
7 | import Profile from './pages/Profile'
8 | import AuthProvider from "./context/auth";
9 | import PrivateRoute from "./components/PrivateRoute";
10 |
11 | function App() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | export default App;
28 |
--------------------------------------------------------------------------------
/src/components/Loading.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Loading = () => {
4 | return (
5 |
6 |
14 | Loading...
15 |
16 |
17 | );
18 | };
19 |
20 | export default Loading;
21 |
--------------------------------------------------------------------------------
/src/components/Message.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from "react";
2 | import Moment from "react-moment";
3 |
4 | const Message = ({ msg, user1 }) => {
5 | const scrollRef = useRef();
6 |
7 | useEffect(() => {
8 | scrollRef.current?.scrollIntoView({ behavior: "smooth" });
9 | }, [msg]);
10 | return (
11 |
15 |
16 | {msg.media ?
: null}
17 | {msg.text}
18 |
19 |
20 | {msg.createdAt.toDate()}
21 |
22 |
23 |
24 | );
25 | };
26 |
27 | export default Message;
28 |
--------------------------------------------------------------------------------
/src/components/MessageForm.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Attachment from "./svg/Attachment";
3 |
4 | const MessageForm = ({ handleSubmit, text, setText, setImg }) => {
5 | return (
6 |
29 | );
30 | };
31 |
32 | export default MessageForm;
33 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Link } from "react-router-dom";
3 | import { auth, db } from "../firebase";
4 | import { signOut } from "firebase/auth";
5 | import { updateDoc, doc } from "firebase/firestore";
6 | import { AuthContext } from "../context/auth";
7 | import { useHistory } from "react-router-dom";
8 |
9 | const Navbar = () => {
10 | const history = useHistory();
11 | const { user } = useContext(AuthContext);
12 |
13 | const handleSignout = async () => {
14 | await updateDoc(doc(db, "users", auth.currentUser.uid), {
15 | isOnline: false,
16 | });
17 | await signOut(auth);
18 | history.replace("/login");
19 | };
20 | return (
21 |
41 | );
42 | };
43 |
44 | export default Navbar;
45 |
--------------------------------------------------------------------------------
/src/components/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { AuthContext } from "../context/auth";
3 | import { Redirect, Route } from "react-router-dom";
4 |
5 | const PrivateRoute = ({ component: Component, ...rest }) => {
6 | const { user } = useContext(AuthContext);
7 |
8 | return (
9 |
13 | user ? :
14 | }
15 | />
16 | );
17 | };
18 |
19 | export default PrivateRoute;
20 |
--------------------------------------------------------------------------------
/src/components/User.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Img from "../image1.jpg";
3 | import { onSnapshot, doc } from "firebase/firestore";
4 | import { db } from "../firebase";
5 |
6 | const User = ({ user1, user, selectUser, chat }) => {
7 | const user2 = user?.uid;
8 | const [data, setData] = useState("");
9 |
10 | useEffect(() => {
11 | const id = user1 > user2 ? `${user1 + user2}` : `${user2 + user1}`;
12 | let unsub = onSnapshot(doc(db, "lastMsg", id), (doc) => {
13 | setData(doc.data());
14 | });
15 | return () => unsub();
16 | }, []);
17 |
18 | return (
19 | <>
20 | selectUser(user)}
23 | >
24 |
25 |
26 |

27 |
{user.name}
28 | {data?.from !== user1 && data?.unread && (
29 |
New
30 | )}
31 |
32 |
35 |
36 | {data && (
37 |
38 | {data.from === user1 ? "Me:" : null}
39 | {data.text}
40 |
41 | )}
42 |
43 | selectUser(user)}
45 | className={`sm_container ${chat.name === user.name && "selected_user"}`}
46 | >
47 |

52 |
53 | >
54 | );
55 | };
56 |
57 | export default User;
58 |
--------------------------------------------------------------------------------
/src/components/svg/Attachment.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Attachment = () => {
4 | return (
5 |
22 | );
23 | };
24 |
25 | export default Attachment;
26 |
--------------------------------------------------------------------------------
/src/components/svg/Camera.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Camera = () => {
4 | return (
5 |
17 | );
18 | };
19 |
20 | export default Camera;
21 |
--------------------------------------------------------------------------------
/src/components/svg/Delete.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Delete = ({ deleteImage }) => {
4 | return (
5 |
23 | );
24 | };
25 |
26 | export default Delete;
27 |
--------------------------------------------------------------------------------
/src/context/auth.js:
--------------------------------------------------------------------------------
1 | import { createContext, useEffect, useState } from "react";
2 | import { onAuthStateChanged } from "firebase/auth";
3 | import { auth } from "../firebase";
4 | import Loading from "../components/Loading";
5 |
6 | export const AuthContext = createContext();
7 |
8 | const AuthProvider = ({ children }) => {
9 | const [user, setUser] = useState(null);
10 | const [loading, setLoading] = useState(true);
11 |
12 | useEffect(() => {
13 | onAuthStateChanged(auth, (user) => {
14 | setUser(user);
15 | setLoading(false);
16 | });
17 | }, []);
18 | if (loading) {
19 | return ;
20 | }
21 | return (
22 | {children}
23 | );
24 | };
25 |
26 | export default AuthProvider;
27 |
--------------------------------------------------------------------------------
/src/firebase.js:
--------------------------------------------------------------------------------
1 | // Import the functions you need from the SDKs you need
2 | import { initializeApp } from "firebase/app";
3 | import { getAuth } from "firebase/auth";
4 | import { getFirestore } from "firebase/firestore";
5 | import { getStorage } from "firebase/storage";
6 | // TODO: Add SDKs for Firebase products that you want to use
7 | // https://firebase.google.com/docs/web/setup#available-libraries
8 |
9 | // Your web app's Firebase configuration
10 | const firebaseConfig = {
11 | apiKey: process.env.REACT_APP_API_KEY,
12 | authDomain: process.env.REACT_APP_AUTH_DOMAIN,
13 | databaseURL: process.env.REACT_APP_DATABASE_URL,
14 | projectId: process.env.REACT_APP_PROJECT_ID,
15 | storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
16 | messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
17 | appId: process.env.REACT_APP_APP_ID,
18 | };
19 |
20 | // Initialize Firebase
21 | const app = initializeApp(firebaseConfig);
22 | const auth = getAuth(app);
23 | const db = getFirestore(app);
24 | const storage = getStorage(app);
25 |
26 | export { auth, db, storage };
27 |
--------------------------------------------------------------------------------
/src/image1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itsmefarhan/react-firebase-chat/6810f607c3822d46cc96d4a276adbef653619f1f/src/image1.jpg
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | background: #242526;
9 | color: white;
10 | }
11 |
12 | code {
13 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
14 | monospace;
15 | }
16 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { db, auth, storage } from "../firebase";
3 | import {
4 | collection,
5 | query,
6 | where,
7 | onSnapshot,
8 | addDoc,
9 | Timestamp,
10 | orderBy,
11 | setDoc,
12 | doc,
13 | getDoc,
14 | updateDoc,
15 | } from "firebase/firestore";
16 | import { ref, getDownloadURL, uploadBytes } from "firebase/storage";
17 | import User from "../components/User";
18 | import MessageForm from "../components/MessageForm";
19 | import Message from "../components/Message";
20 |
21 | const Home = () => {
22 | const [users, setUsers] = useState([]);
23 | const [chat, setChat] = useState("");
24 | const [text, setText] = useState("");
25 | const [img, setImg] = useState("");
26 | const [msgs, setMsgs] = useState([]);
27 |
28 | const user1 = auth.currentUser.uid;
29 |
30 | useEffect(() => {
31 | const usersRef = collection(db, "users");
32 | // create query object
33 | const q = query(usersRef, where("uid", "not-in", [user1]));
34 | // execute query
35 | const unsub = onSnapshot(q, (querySnapshot) => {
36 | let users = [];
37 | querySnapshot.forEach((doc) => {
38 | users.push(doc.data());
39 | });
40 | setUsers(users);
41 | });
42 | return () => unsub();
43 | }, []);
44 |
45 | const selectUser = async (user) => {
46 | setChat(user);
47 |
48 | const user2 = user.uid;
49 | const id = user1 > user2 ? `${user1 + user2}` : `${user2 + user1}`;
50 |
51 | const msgsRef = collection(db, "messages", id, "chat");
52 | const q = query(msgsRef, orderBy("createdAt", "asc"));
53 |
54 | onSnapshot(q, (querySnapshot) => {
55 | let msgs = [];
56 | querySnapshot.forEach((doc) => {
57 | msgs.push(doc.data());
58 | });
59 | setMsgs(msgs);
60 | });
61 |
62 | // get last message b/w logged in user and selected user
63 | const docSnap = await getDoc(doc(db, "lastMsg", id));
64 | // if last message exists and message is from selected user
65 | if (docSnap.data() && docSnap.data().from !== user1) {
66 | // update last message doc, set unread to false
67 | await updateDoc(doc(db, "lastMsg", id), { unread: false });
68 | }
69 | };
70 |
71 | const handleSubmit = async (e) => {
72 | e.preventDefault();
73 |
74 | const user2 = chat.uid;
75 |
76 | const id = user1 > user2 ? `${user1 + user2}` : `${user2 + user1}`;
77 |
78 | let url;
79 | if (img) {
80 | const imgRef = ref(
81 | storage,
82 | `images/${new Date().getTime()} - ${img.name}`
83 | );
84 | const snap = await uploadBytes(imgRef, img);
85 | const dlUrl = await getDownloadURL(ref(storage, snap.ref.fullPath));
86 | url = dlUrl;
87 | }
88 |
89 | await addDoc(collection(db, "messages", id, "chat"), {
90 | text,
91 | from: user1,
92 | to: user2,
93 | createdAt: Timestamp.fromDate(new Date()),
94 | media: url || "",
95 | });
96 |
97 | await setDoc(doc(db, "lastMsg", id), {
98 | text,
99 | from: user1,
100 | to: user2,
101 | createdAt: Timestamp.fromDate(new Date()),
102 | media: url || "",
103 | unread: true,
104 | });
105 |
106 | setText("");
107 | setImg("");
108 | };
109 | return (
110 |
111 |
112 | {users.map((user) => (
113 |
120 | ))}
121 |
122 |
123 | {chat ? (
124 | <>
125 |
126 |
{chat.name}
127 |
128 |
129 | {msgs.length
130 | ? msgs.map((msg, i) => (
131 |
132 | ))
133 | : null}
134 |
135 |
141 | >
142 | ) : (
143 |
Select a user to start conversation
144 | )}
145 |
146 |
147 | );
148 | };
149 |
150 | export default Home;
151 |
--------------------------------------------------------------------------------
/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { signInWithEmailAndPassword } from "firebase/auth";
3 | import { auth, db } from "../firebase";
4 | import { updateDoc, doc } from "firebase/firestore";
5 | import { useHistory } from "react-router-dom";
6 |
7 | const Login = () => {
8 | const [data, setData] = useState({
9 | email: "",
10 | password: "",
11 | error: null,
12 | loading: false,
13 | });
14 |
15 | const history = useHistory();
16 |
17 | const { email, password, error, loading } = data;
18 |
19 | const handleChange = (e) => {
20 | setData({ ...data, [e.target.name]: e.target.value });
21 | };
22 |
23 | const handleSubmit = async (e) => {
24 | e.preventDefault();
25 | setData({ ...data, error: null, loading: true });
26 | if (!email || !password) {
27 | setData({ ...data, error: "All fields are required" });
28 | }
29 | try {
30 | const result = await signInWithEmailAndPassword(auth, email, password);
31 |
32 | await updateDoc(doc(db, "users", result.user.uid), {
33 | isOnline: true,
34 | });
35 | setData({
36 | email: "",
37 | password: "",
38 | error: null,
39 | loading: false,
40 | });
41 | history.replace("/");
42 | } catch (err) {
43 | setData({ ...data, error: err.message, loading: false });
44 | }
45 | };
46 | return (
47 |
48 | Log into your Account
49 |
75 |
76 | );
77 | };
78 |
79 | export default Login;
80 |
--------------------------------------------------------------------------------
/src/pages/Profile.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import Camera from "../components/svg/Camera";
3 | import Img from "../image1.jpg";
4 | import { storage, db, auth } from "../firebase";
5 | import {
6 | ref,
7 | getDownloadURL,
8 | uploadBytes,
9 | deleteObject,
10 | } from "firebase/storage";
11 | import { getDoc, doc, updateDoc } from "firebase/firestore";
12 | import Delete from "../components/svg/Delete";
13 | import { useHistory } from "react-router-dom";
14 |
15 | const Profile = () => {
16 | const [img, setImg] = useState("");
17 | const [user, setUser] = useState();
18 | const history = useHistory("");
19 |
20 | useEffect(() => {
21 | getDoc(doc(db, "users", auth.currentUser.uid)).then((docSnap) => {
22 | if (docSnap.exists) {
23 | setUser(docSnap.data());
24 | }
25 | });
26 |
27 | if (img) {
28 | const uploadImg = async () => {
29 | const imgRef = ref(
30 | storage,
31 | `avatar/${new Date().getTime()} - ${img.name}`
32 | );
33 | try {
34 | if (user.avatarPath) {
35 | await deleteObject(ref(storage, user.avatarPath));
36 | }
37 | const snap = await uploadBytes(imgRef, img);
38 | const url = await getDownloadURL(ref(storage, snap.ref.fullPath));
39 |
40 | await updateDoc(doc(db, "users", auth.currentUser.uid), {
41 | avatar: url,
42 | avatarPath: snap.ref.fullPath,
43 | });
44 |
45 | setImg("");
46 | } catch (err) {
47 | console.log(err.message);
48 | }
49 | };
50 | uploadImg();
51 | }
52 | }, [img]);
53 |
54 | const deleteImage = async () => {
55 | try {
56 | const confirm = window.confirm("Delete avatar?");
57 | if (confirm) {
58 | await deleteObject(ref(storage, user.avatarPath));
59 |
60 | await updateDoc(doc(db, "users", auth.currentUser.uid), {
61 | avatar: "",
62 | avatarPath: "",
63 | });
64 | history.replace("/");
65 | }
66 | } catch (err) {
67 | console.log(err.message);
68 | }
69 | };
70 | return user ? (
71 |
72 |
73 |
74 |

75 |
76 |
77 |
80 | {user.avatar ? : null}
81 | setImg(e.target.files[0])}
87 | />
88 |
89 |
90 |
91 |
92 |
{user.name}
93 |
{user.email}
94 |
95 |
Joined on: {user.createdAt.toDate().toDateString()}
96 |
97 |
98 |
99 | ) : null;
100 | };
101 |
102 | export default Profile;
103 |
--------------------------------------------------------------------------------
/src/pages/Register.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { createUserWithEmailAndPassword } from "firebase/auth";
3 | import { auth, db } from "../firebase";
4 | import { setDoc, doc, Timestamp } from "firebase/firestore";
5 | import { useHistory } from "react-router-dom";
6 |
7 | const Register = () => {
8 | const [data, setData] = useState({
9 | name: "",
10 | email: "",
11 | password: "",
12 | error: null,
13 | loading: false,
14 | });
15 |
16 | const history = useHistory();
17 |
18 | const { name, email, password, error, loading } = data;
19 |
20 | const handleChange = (e) => {
21 | setData({ ...data, [e.target.name]: e.target.value });
22 | };
23 |
24 | const handleSubmit = async (e) => {
25 | e.preventDefault();
26 | setData({ ...data, error: null, loading: true });
27 | if (!name || !email || !password) {
28 | setData({ ...data, error: "All fields are required" });
29 | }
30 | try {
31 | const result = await createUserWithEmailAndPassword(
32 | auth,
33 | email,
34 | password
35 | );
36 | await setDoc(doc(db, "users", result.user.uid), {
37 | uid: result.user.uid,
38 | name,
39 | email,
40 | createdAt: Timestamp.fromDate(new Date()),
41 | isOnline: true,
42 | });
43 | setData({
44 | name: "",
45 | email: "",
46 | password: "",
47 | error: null,
48 | loading: false,
49 | });
50 | history.replace("/");
51 | } catch (err) {
52 | setData({ ...data, error: err.message, loading: false });
53 | }
54 | };
55 | return (
56 |
57 | Create An Account
58 |
88 |
89 | );
90 | };
91 |
92 | export default Register;
93 |
--------------------------------------------------------------------------------