├── .gitignore
├── README.md
├── dbConfig.js
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── public
├── images
│ ├── default.png
│ ├── icon.png
│ └── logo.png
└── vite.svg
├── src
├── App.jsx
├── assets
│ └── react.svg
├── components
│ ├── Chat
│ │ ├── ChatBox.jsx
│ │ └── ChatList.jsx
│ ├── Group
│ │ ├── AddGroupItems.jsx
│ │ ├── Group.jsx
│ │ ├── GroupChatBox.jsx
│ │ ├── GroupItems.jsx
│ │ └── JoinGroupItems.jsx
│ └── Navbar
│ │ ├── Contact.jsx
│ │ ├── Profile.jsx
│ │ ├── Request.jsx
│ │ └── Settings.jsx
├── index.css
├── main.jsx
├── pages
│ ├── Error.jsx
│ ├── Home.jsx
│ ├── Login.jsx
│ ├── Registration.jsx
│ └── Reset.jsx
├── store
│ ├── slices
│ │ ├── authSlice.js
│ │ └── conversationSlice.js
│ └── store.js
└── utils
│ ├── ChatItems.jsx
│ ├── CommonPerson.jsx
│ ├── FriendItem.jsx
│ └── UserList.jsx
├── vercel.json
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 💬 ShimantoChat — Real-Time Chat App
2 |
3 | A real-time chat application built with **React**, **Firebase**, and **React Redux**, enabling users to send and receive messages instantly. The app is fast, responsive, and perfect for one-on-one real-time communication.
4 |
5 | 🚀 **Live Demo**: [shimantochat.vercel.app](https://shimantochat.vercel.app/)
6 |
7 | ---
8 |
9 | ## 🛠️ Tech Stack
10 |
11 | - ⚛️ **React** — Frontend framework
12 | - 🔥 **Firebase** — Realtime database and authentication
13 | - 🗃️ **React Redux** — Global state management
14 | - 🎨 **Tailwind CSS** — Styling
15 |
16 | ---
17 |
18 | ## ✨ Features
19 |
20 | - 🔐 **Firebase Authentication**
21 | - Sign Up / Login / Logout
22 | - 💬 **Real-time Messaging**
23 | - Messages instantly synced across devices
24 | - 📲 **Responsive Design**
25 | - Works seamlessly on desktop and mobile
26 | - 👤 **User Session Handling**
27 | - Persistent login with Redux & Firebase
28 | - 🌓 **(Optional)** Theme support *(Dark/Light)*
29 |
30 | ---
31 | ## 🙋♂️ Author
32 |
33 | ### **Shimanto Sarkar**
34 | 💼 MERN Stack Developer
35 | 📧 shimanto.dev.bd@gmail.com
36 | 🔗 [GitHub](https://github.com/shimanto)
37 | 📘 [Facebook](https://www.facebook.com/iamshimanto18)
38 | 🔗 [LinkedIn](https://www.linkedin.com/in/iam-shimanto/)
39 |
40 |
41 |
--------------------------------------------------------------------------------
/dbConfig.js:
--------------------------------------------------------------------------------
1 | import { initializeApp } from "firebase/app";
2 |
3 | const firebaseConfig = {
4 | apiKey: "AIzaSyA3OY829f7AitP8qiTe-W16u4wt9l5yyAA",
5 | authDomain: "chatweb-bdfa3.firebaseapp.com",
6 | projectId: "chatweb-bdfa3",
7 | storageBucket: "chatweb-bdfa3.firebasestorage.app",
8 | messagingSenderId: "849946275076",
9 | appId: "1:849946275076:web:012cf1307db93f5f53f451",
10 | };
11 |
12 |
13 | export const app = initializeApp(firebaseConfig);
14 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import reactHooks from 'eslint-plugin-react-hooks'
4 | import reactRefresh from 'eslint-plugin-react-refresh'
5 |
6 | export default [
7 | { ignores: ['dist'] },
8 | {
9 | files: ['**/*.{js,jsx}'],
10 | languageOptions: {
11 | ecmaVersion: 2020,
12 | globals: globals.browser,
13 | parserOptions: {
14 | ecmaVersion: 'latest',
15 | ecmaFeatures: { jsx: true },
16 | sourceType: 'module',
17 | },
18 | },
19 | plugins: {
20 | 'react-hooks': reactHooks,
21 | 'react-refresh': reactRefresh,
22 | },
23 | rules: {
24 | ...js.configs.recommended.rules,
25 | ...reactHooks.configs.recommended.rules,
26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
27 | 'react-refresh/only-export-components': [
28 | 'warn',
29 | { allowConstantExport: true },
30 | ],
31 | },
32 | },
33 | ]
34 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ChatApp Firebase
8 |
9 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chat-app-firebase",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint .",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^2.8.2",
14 | "@tailwindcss/vite": "^4.1.10",
15 | "emoji-picker-react": "^4.12.3",
16 | "firebase": "^11.9.1",
17 | "react": "^19.1.0",
18 | "react-dom": "^19.1.0",
19 | "react-icons": "^5.5.0",
20 | "react-redux": "^9.2.0",
21 | "react-router": "^7.6.2",
22 | "react-toastify": "^11.0.5",
23 | "tailwindcss": "^4.1.10"
24 | },
25 | "devDependencies": {
26 | "@eslint/js": "^9.25.0",
27 | "@types/react": "^19.1.2",
28 | "@types/react-dom": "^19.1.2",
29 | "@vitejs/plugin-react": "^4.4.1",
30 | "eslint": "^9.25.0",
31 | "eslint-plugin-react-hooks": "^5.2.0",
32 | "eslint-plugin-react-refresh": "^0.4.19",
33 | "globals": "^16.0.0",
34 | "vite": "^6.3.5"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/public/images/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamShimanto/Chat-App/fa5580d76c50072ba8fec0dcf27f3a3e2f2ef89b/public/images/default.png
--------------------------------------------------------------------------------
/public/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamShimanto/Chat-App/fa5580d76c50072ba8fec0dcf27f3a3e2f2ef89b/public/images/icon.png
--------------------------------------------------------------------------------
/public/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamShimanto/Chat-App/fa5580d76c50072ba8fec0dcf27f3a3e2f2ef89b/public/images/logo.png
--------------------------------------------------------------------------------
/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Route, Routes } from "react-router";
2 | import Login from "./pages/Login";
3 | import Registration from "./pages/Registration";
4 | import Home from "./pages/Home";
5 | import Reset from "./pages/Reset";
6 | import Error from "./pages/Error";
7 |
8 | function App() {
9 | return (
10 | <>
11 |
12 |
13 | } />
14 | } />
15 | } />
16 | } />
17 | } />
18 |
19 |
20 | >
21 | );
22 | }
23 |
24 | export default App;
25 |
--------------------------------------------------------------------------------
/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/Chat/ChatBox.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { IoSend } from "react-icons/io5";
3 | import { GrEmoji } from "react-icons/gr";
4 | import { useSelector } from "react-redux";
5 | import {
6 | getDatabase,
7 | onValue,
8 | push,
9 | ref,
10 | set,
11 | update,
12 | } from "firebase/database";
13 | import EmojiPicker from "emoji-picker-react";
14 |
15 | const ChatBox = () => {
16 | const userInfo = useSelector((state) => state.userData.user);
17 | const activeFriend = useSelector((state) => state.activeFriend.friend);
18 | const db = getDatabase();
19 | const [messageContent, setMessageContent] = useState("");
20 | const [message, setMessage] = useState([]);
21 | const [emoji, setEmoji] = useState(false);
22 | const emojiRef = useRef(null);
23 | const chatboxRef = useRef(null);
24 |
25 | // ============ write message
26 | const handleSendMessage = (e) => {
27 | e.preventDefault();
28 |
29 | if (messageContent) {
30 | set(push(ref(db, "messages/")), {
31 | senderId: userInfo.uid,
32 | recieverId: activeFriend.id,
33 | message: messageContent,
34 | });
35 | }
36 |
37 | update(ref(db, "friendList/" + activeFriend.messageId), {
38 | lastMessage: messageContent,
39 | time: new Date().toLocaleTimeString(),
40 | });
41 |
42 | setMessageContent("");
43 | setEmoji(false);
44 | };
45 | // ============ write message
46 |
47 | // ============ read message
48 | useEffect(() => {
49 | onValue(ref(db, "messages"), (snapshot) => {
50 | let arr = [];
51 | snapshot.forEach((item) => {
52 | if (
53 | (item.val().senderId === userInfo.uid ||
54 | item.val().recieverId === userInfo.uid) &
55 | (item.val().senderId === activeFriend.id ||
56 | item.val().recieverId === activeFriend.id)
57 | ) {
58 | arr.push({ ...item.val(), id: item.key });
59 | }
60 | setMessage(arr);
61 | });
62 | });
63 | }, [activeFriend]);
64 | // ============ read message
65 |
66 | // =================== emoji box click ====================
67 | window.addEventListener("mousedown", (e) => {
68 | if (emojiRef.current && !emojiRef.current.contains(e.target)) {
69 | setEmoji(false);
70 | }
71 | });
72 | // =================== emoji box click ====================
73 |
74 | useEffect(() => {
75 | if (chatboxRef.current) {
76 | chatboxRef.current.scrollTop = chatboxRef.current.scrollHeight;
77 | }
78 | }, [message]);
79 |
80 | return (
81 | <>
82 |
83 |
84 |
85 |

90 |
91 | {activeFriend.name}
92 |
93 |
94 |
95 |
99 |
100 | {message.map((item) =>
101 | item.senderId === userInfo.uid ? (
102 |
106 | {item.message}
107 |
108 | ) : (
109 |
113 | {item.message}
114 |
115 | )
116 | )}
117 |
118 |
119 |
154 |
155 | >
156 | );
157 | };
158 |
159 | export default ChatBox;
160 |
--------------------------------------------------------------------------------
/src/components/Chat/ChatList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { CiSearch } from "react-icons/ci";
3 | import { getDatabase, onValue, ref } from "firebase/database";
4 | import { useSelector } from "react-redux";
5 | import ChatItems from "../../utils/ChatItems";
6 | import UserList from "../../utils/UserList";
7 |
8 | const ChatList = () => {
9 | const db = getDatabase();
10 | const userInfo = useSelector((state) => state.userData.user);
11 | const [data, setData] = useState([]);
12 | const [add, setAdd] = useState(false);
13 | const addFriendRef = useRef(null);
14 | const [friendList, setFriendList] = useState([]);
15 |
16 | // ============= add freind data show
17 | const handleAdd = () => {
18 | const arr = [];
19 | onValue(ref(db, "users/"), (snapshot) => {
20 | snapshot.forEach((item) => {
21 | if (item.key !== userInfo.uid) {
22 | const isFriend = friendList.find(
23 | (friend) =>
24 | (friend.creatorId == userInfo.uid &&
25 | friend.participantId === item.key) ||
26 | (friend.participantId == userInfo.uid &&
27 | friend.creatorId == item.key)
28 | );
29 | if (!isFriend) {
30 | arr.push({ ...item.val(), id: item.key });
31 | }
32 | }
33 | });
34 | setData(arr);
35 | });
36 | setAdd(true);
37 | };
38 | // ============= add freind data show
39 |
40 | // ============= outside click event
41 | window.addEventListener("mousedown", (e) => {
42 | if (addFriendRef.current && !addFriendRef.current.contains(e.target)) {
43 | setAdd(false);
44 | }
45 | });
46 |
47 | // ============= friend list data
48 | useEffect(() => {
49 | onValue(ref(db, "friendList"), (snapshot) => {
50 | let arr = [];
51 | snapshot.forEach((item) => {
52 | arr.push({ ...item.val(), id: item.key });
53 | });
54 | setFriendList(arr);
55 | });
56 | }, []);
57 | // ============= friend list data
58 |
59 | return (
60 | <>
61 |
62 |
63 |
Chats
64 |
70 |
71 |
72 |
73 |
78 | {add && (
79 |
83 |
88 |
89 | {data.map((item) => (
90 |
91 | ))}
92 |
93 |
94 | )}
95 |
96 |
97 | Recent
98 |
99 |
100 | {/* ============= show freind ================= */}
101 |
102 |
103 | {friendList.map(
104 | (item) =>
105 | (item.creatorId == userInfo.uid && (
106 |
119 | )) ||
120 | (item.participantId == userInfo.uid && (
121 |
133 | ))
134 | )}
135 |
136 |
137 | >
138 | );
139 | };
140 |
141 | export default ChatList;
142 |
--------------------------------------------------------------------------------
/src/components/Group/AddGroupItems.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { IoMdArrowRoundBack } from "react-icons/io";
3 | import ChatBox from "../Chat/ChatBox";
4 | import { getDatabase, onValue, push, ref, set } from "firebase/database";
5 |
6 | const AddGroupItems = ({
7 | avater,
8 | name,
9 | id,
10 | styling = "bg-white",
11 | stylingName = "text-black22",
12 | groupData,
13 | }) => {
14 | const reqRef = useRef(null);
15 | const [groupMemberList, setGroupMemberList] = useState([]);
16 | const db = getDatabase();
17 |
18 | // ============== add to group member
19 | const handleAddUser = () => {
20 | set(push(ref(db, "groupMember/" + groupData.id)), {
21 | groupId: groupData.id,
22 | memberId: id,
23 | memberName: name,
24 | creatorId: groupData.creatorId,
25 | });
26 | };
27 |
28 | // ============= group show
29 | useEffect(() => {
30 | onValue(ref(db, "groupMember/" + groupData.id), (snapshot) => {
31 | let arr = [];
32 | snapshot.forEach((item) => {
33 | arr.push(item.val().memberId);
34 | });
35 | setGroupMemberList(arr);
36 | });
37 | }, []);
38 |
39 | // ============ if already in group then hide
40 | const isAlreadyMember = groupMemberList.includes(id);
41 | if (isAlreadyMember) return null;
42 |
43 | return (
44 | <>
45 |
49 |
50 |

51 |
52 |
55 | {name}
56 |
57 |
58 |
59 |
65 |
66 | >
67 | );
68 | };
69 |
70 | export default AddGroupItems;
71 |
--------------------------------------------------------------------------------
/src/components/Group/Group.jsx:
--------------------------------------------------------------------------------
1 | import { getDatabase, onValue, push, ref, set } from "firebase/database";
2 | import React, { useEffect, useRef, useState } from "react";
3 | import { useSelector } from "react-redux";
4 | import { toast, ToastContainer } from "react-toastify";
5 | import GroupItems from "./GroupItems";
6 | import JoinGroupItems from "./JoinGroupItems";
7 |
8 | const Group = () => {
9 | const db = getDatabase();
10 | const [createGroup, setCreateGroup] = useState(false);
11 | const [joinGroup, setJoinGroup] = useState(false);
12 | const createGroupRef = useRef(null);
13 | const joinGroupRef = useRef(null);
14 | const [groupName, setGroupName] = useState("");
15 | const userInfo = useSelector((state) => state.userData.user);
16 | const [groupList, setGroupList] = useState([]);
17 | const [groupMember, setGroupMember] = useState([]);
18 | const [joinGroupMember, setJoinGroupMember] = useState([]);
19 |
20 | // ============ outside click
21 | window.addEventListener("mousedown", (e) => {
22 | if (createGroupRef.current && !createGroupRef.current.contains(e.target)) {
23 | setCreateGroup(false);
24 | }
25 | });
26 | window.addEventListener("mousedown", (e) => {
27 | if (joinGroupRef.current && !joinGroupRef.current.contains(e.target)) {
28 | setJoinGroup(false);
29 | }
30 | });
31 | // ============ outside click
32 |
33 | // =============== create group
34 |
35 | const handleCreateGroup = () => {
36 | if (groupName) {
37 | set(push(ref(db, "groups/")), {
38 | groupName,
39 | creatorName: userInfo.displayName,
40 | creatorId: userInfo.uid,
41 | });
42 | toast.success("Group created successfully!");
43 | setCreateGroup(false);
44 | setGroupName("");
45 | } else {
46 | toast.error("Please enter valid group name");
47 | }
48 | };
49 |
50 | // =============== create group
51 |
52 | // =============== group show
53 |
54 | useEffect(() => {
55 | onValue(ref(db, "groups"), (snapshot) => {
56 | let arr = [];
57 | snapshot.forEach((item) => {
58 | arr.push({ ...item.val(), id: item.key });
59 | });
60 | setGroupList(arr);
61 | });
62 | }, []);
63 |
64 | useEffect(() => {
65 | onValue(ref(db, "groupMember/"), (snapshot) => {
66 | let arr = [];
67 | snapshot.forEach((item) => {
68 | item.forEach((data) => {
69 | if (
70 | data.val().memberId === userInfo.uid ||
71 | data.val().creatorId === userInfo.uid
72 | ) {
73 | arr.push(item.key);
74 | }
75 | });
76 | });
77 | setGroupMember(arr);
78 | });
79 | }, []);
80 |
81 | // =============== group show
82 |
83 | // ============ join group
84 | const handleJoinGroup = () => {
85 | setJoinGroup(true);
86 | onValue(ref(db, "groups/"), (snapshot) => {
87 | let arr = [];
88 | snapshot.forEach((item) => {
89 | if (
90 | !(item.val().creatorId === userInfo.uid) &
91 | !groupMember.includes(item.key)
92 | ) {
93 | arr.push({ ...item.val(), id: item.key });
94 | }
95 | });
96 | setJoinGroupMember(arr);
97 | });
98 | };
99 | // ============ join group
100 |
101 | return (
102 |
103 |
104 |
105 | GroupList
106 |
107 |
108 |
114 |
120 | {createGroup && (
121 |
125 |
126 | setGroupName(e.target.value)}
128 | className=" outline-none w-7/10 bg-transparent text-[#fff] placeholder-[#3680b1] pb-3 border mb-3 tracking-widest text-center pt-2 rounded-lg text-xl"
129 | type="text"
130 | placeholder="Group Name"
131 | />
132 |
133 |
139 |
140 | )}
141 |
142 | {joinGroup && (
143 |
147 |
148 | Join Group
149 |
150 |
151 | {joinGroupMember.map((item) => (
152 |
153 | ))}
154 |
155 |
156 | )}
157 |
158 |
159 | {groupList.map(
160 | (item) =>
161 | (item.creatorId === userInfo.uid ||
162 | groupMember.includes(item.id)) && (
163 |
164 | )
165 | )}
166 |
167 |
168 | );
169 | };
170 |
171 | export default Group;
172 |
--------------------------------------------------------------------------------
/src/components/Group/GroupChatBox.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { IoSend } from "react-icons/io5";
3 | import { GrEmoji } from "react-icons/gr";
4 | import { useSelector } from "react-redux";
5 | import {
6 | getDatabase,
7 | onValue,
8 | push,
9 | ref,
10 | set,
11 | update,
12 | } from "firebase/database";
13 | import EmojiPicker from "emoji-picker-react";
14 |
15 | const GroupChatBox = () => {
16 | const activeGroup = useSelector((state) => state.activeFriend.group);
17 | const userInfo = useSelector((state) => state.userData.user);
18 | const db = getDatabase();
19 | const [messageContent, setMessageContent] = useState("");
20 | const [message, setMessage] = useState([]);
21 | const [emoji, setEmoji] = useState(false);
22 | const emojiRef = useRef(null);
23 | const chatboxRef = useRef(null);
24 |
25 |
26 | // ============ write message
27 | const handleSendMessage = (e) => {
28 | e.preventDefault();
29 |
30 | if (messageContent) {
31 | set(push(ref(db, "groupMessages/")), {
32 | senderId: userInfo.uid,
33 | recieverId: activeGroup.id,
34 | message: messageContent,
35 | senderName: userInfo.displayName,
36 | });
37 | }
38 |
39 | update(ref(db, "friendList/" + activeGroup.messageId), {
40 | lastMessage: messageContent,
41 | time: new Date().toLocaleTimeString(),
42 | });
43 |
44 | setMessageContent("");
45 | setEmoji(false);
46 | };
47 | // ============ write message
48 |
49 | // ============ read message
50 | useEffect(() => {
51 | onValue(ref(db, "groupMessages"), (snapshot) => {
52 | let arr = [];
53 | snapshot.forEach((item) => {
54 | if (
55 | (item.val().senderId === activeGroup.creator ||
56 | item.val().recieverId === activeGroup.id) &
57 | (item.val().senderId === userInfo.uid ||
58 | item.val().recieverId === activeGroup.id)
59 | ) {
60 | arr.push({ ...item.val(), id: item.key });
61 | }
62 | });
63 | setMessage(arr);
64 | });
65 | }, [activeGroup]);
66 | // ============ read message
67 |
68 | // =================== emoji box click ====================
69 | window.addEventListener("mousedown", (e) => {
70 | if (emojiRef.current && !emojiRef.current.contains(e.target)) {
71 | setEmoji(false);
72 | }
73 | });
74 | // =================== emoji box click ====================
75 |
76 | // ================== last message show first
77 | useEffect(() => {
78 | if (chatboxRef.current) {
79 | chatboxRef.current.scrollTop = chatboxRef.current.scrollHeight;
80 | }
81 | }, [message]);
82 |
83 | return (
84 | <>
85 |
86 |
87 |
88 |
89 | {activeGroup.groupName[0]}
90 |
91 |
92 | {activeGroup.groupName}
93 |
94 |
95 |
96 |
100 |
101 | {message.map((item) =>
102 | item.senderId === userInfo.uid ? (
103 |
107 | {item.message}
108 | {item.senderName}
109 |
110 | ) : (
111 |
115 | {item.message}
116 | {item.senderName}
117 |
118 | )
119 | )}
120 |
121 |
122 |
157 |
158 | >
159 | );
160 | };
161 |
162 | export default GroupChatBox;
163 |
--------------------------------------------------------------------------------
/src/components/Group/GroupItems.jsx:
--------------------------------------------------------------------------------
1 | import { getDatabase, onValue, ref } from "firebase/database";
2 | import React, { useEffect, useRef, useState } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import AddGroupItems from "./AddGroupItems";
5 | import { selectGroup } from "../../store/slices/conversationSlice";
6 |
7 | const GroupItems = ({ data }) => {
8 | const db = getDatabase();
9 | const [add, setAdd] = useState(false);
10 | const userInfo = useSelector((state) => state.userData.user);
11 | const [friendList, setFriendList] = useState([]);
12 | const dispatch = useDispatch();
13 | const addFriendRef = useRef(null)
14 |
15 | // =========== outside click
16 | window.addEventListener("mousedown", (e) => {
17 | if (addFriendRef.current && !addFriendRef.current.contains(e.target)) {
18 | setAdd(false);
19 | }
20 | });
21 | // =========== outside click
22 |
23 | // ============== friend list show with filter
24 | useEffect(() => {
25 | onValue(ref(db, "friendList"), (snapshot) => {
26 | let arr = [];
27 | snapshot.forEach((item) => {
28 | if (
29 | item.val().creatorId === userInfo.uid ||
30 | item.val().participantId === userInfo.uid
31 | )
32 | arr.push({ ...item.val(), id: item.key });
33 | });
34 | setFriendList(arr);
35 | });
36 | }, []);
37 |
38 | // ============= data transfer to redux
39 | const handleClick = () => {
40 | dispatch(selectGroup(data));
41 | };
42 |
43 | return (
44 | <>
45 |
46 |
50 |
51 |
52 | {data.groupName[0]}
53 |
54 |
55 |
58 | {data.groupName}
59 |
60 |
61 |
62 |
68 |
69 | {add && (
70 |
74 |
79 |
80 | {friendList.map(
81 | (item) =>
82 | (item.creatorId == userInfo.uid && (
83 |
96 | )) ||
97 | (item.participantId == userInfo.uid && (
98 |
110 | ))
111 | )}
112 |
113 |
114 | )}
115 |
116 | >
117 | );
118 | };
119 |
120 | export default GroupItems;
121 |
--------------------------------------------------------------------------------
/src/components/Group/JoinGroupItems.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { getDatabase, onValue, push, ref, set } from "firebase/database";
3 | import { useSelector } from "react-redux";
4 |
5 | const JoinGroupItems = ({ data }) => {
6 | const db = getDatabase();
7 | const userInfo = useSelector((state) => state.userData.user);
8 | const [groupMemberList, setGroupMemberList] = useState([]);
9 |
10 | // ============== joining group
11 | const handleJoinGroup = () => {
12 | set(push(ref(db, "groupMember/" + data.id)), {
13 | groupId: data.id,
14 | memberId: userInfo.uid,
15 | memberName: userInfo.displayName,
16 | creatorId: data.creatorId,
17 | });
18 | };
19 |
20 | // ================ group member data
21 | useEffect(() => {
22 | onValue(ref(db, "groupMember/" + data.id), (snapshot) => {
23 | let arr = [];
24 | snapshot.forEach((item) => {
25 | arr.push(item.val().memberId);
26 | });
27 | setGroupMemberList(arr);
28 | });
29 | }, []);
30 |
31 | // ============ is already in group then hide
32 | const isAlreadyMember = groupMemberList.includes(userInfo.uid);
33 | if (isAlreadyMember) return null;
34 |
35 | return (
36 | <>
37 |
40 |
41 |
42 | {data.groupName[0]}
43 |
44 |
45 |
48 | {data.groupName}
49 |
50 |
51 |
52 |
58 |
59 | >
60 | );
61 | };
62 |
63 | export default JoinGroupItems;
64 |
--------------------------------------------------------------------------------
/src/components/Navbar/Contact.jsx:
--------------------------------------------------------------------------------
1 | import { getDatabase, onValue, ref } from "firebase/database";
2 | import React, { useEffect, useState } from "react";
3 | import { useSelector } from "react-redux";
4 | import FriendItem from "../../utils/FriendItem";
5 |
6 | const Contact = () => {
7 | const db = getDatabase();
8 | const userInfo = useSelector((state) => state.userData.user);
9 | const [friendList, setFriendList] = useState([]);
10 |
11 | useEffect(() => {
12 | onValue(ref(db, "friendList"), (snapshot) => {
13 | let arr = [];
14 | snapshot.forEach((item) => {
15 | arr.push({ ...item.val(), id: item.key });
16 | });
17 | setFriendList(arr);
18 | });
19 | }, []);
20 |
21 | return (
22 |
23 |
24 | FriendList
25 |
26 |
27 | {/* ================ friend list show =============== */}
28 |
29 | {friendList.map(
30 | (item) =>
31 | (item.creatorId == userInfo.uid && (
32 |
43 | )) ||
44 | (item.participantId == userInfo.uid && (
45 |
55 | ))
56 | )}
57 |
58 |
59 | );
60 | };
61 |
62 | export default Contact;
63 |
--------------------------------------------------------------------------------
/src/components/Navbar/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useState } from "react";
3 | import { GoDotFill } from "react-icons/go";
4 | import { MdOutlinePerson3 } from "react-icons/md";
5 | import { useSelector } from "react-redux";
6 | import { getDatabase, ref, onValue } from "firebase/database";
7 |
8 | const Profile = () => {
9 | const userInfo = useSelector((state) => state.userData.user);
10 | const db = getDatabase();
11 | const [userData, setUserData] = useState([]);
12 | const [time, setTime] = useState(new Date());
13 |
14 | // =========== time
15 | useEffect(() => {
16 | const interval = setInterval(() => {
17 | setTime(new Date());
18 | }, 1000);
19 | return () => clearInterval(interval);
20 | }, []);
21 |
22 | // ========== read data
23 | useEffect(() => {
24 | const starCountRef = ref(db, "users/" + userInfo.uid);
25 | onValue(starCountRef, (snapshot) => {
26 | const data = snapshot.val();
27 | setUserData(data);
28 | });
29 | }, []);
30 |
31 | return (
32 | <>
33 |
34 |
My Profile
35 |
36 |

41 |
42 |
43 |
44 | {userData.username}
45 |
46 |
47 |
48 | Active
49 |
50 |
51 |
52 |
53 | About
54 |
55 |
56 |
Name
57 |
58 | {userData.username}
59 |
60 |
61 |
62 |
63 | Email
64 |
65 |
66 | {userData.email}
67 |
68 |
69 |
70 |
Time
71 |
72 | {time.toLocaleString("en-US", {
73 | hour: "numeric",
74 | minute: "numeric",
75 | second: "numeric",
76 | hour12: true,
77 | })}
78 |
79 |
80 |
81 |
82 | Location
83 |
84 |
85 | {userData.location}
86 |
87 |
88 |
89 | >
90 | );
91 | };
92 |
93 | export default Profile;
94 |
--------------------------------------------------------------------------------
/src/components/Navbar/Request.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import CommonPerson from "../../utils/CommonPerson";
3 | import { getDatabase, onValue, ref } from "firebase/database";
4 | import { useSelector } from "react-redux";
5 |
6 | const Request = () => {
7 | const db = getDatabase();
8 | const userInfo = useSelector((state) => state.userData.user);
9 | const [list, setList] = useState([]);
10 | const [show, setShow] = useState(false);
11 |
12 | // ================= request data show ==============
13 | useEffect(() => {
14 | onValue(ref(db, "requestList/"), (snapshot) => {
15 | let arr = [];
16 | snapshot.forEach((item) => {
17 | arr.push({ ...item.val(), id: item.key });
18 | });
19 | setList(arr);
20 | });
21 | }, []);
22 |
23 | return (
24 |
25 |
26 | {show ? "Sent Request" : "Friend Request"}
27 |
28 |
34 |
35 | {/* ========== friend request ================= */}
36 | {!show && (
37 |
38 | {list.map(
39 | (item) =>
40 | item.participantId == userInfo.uid && (
41 |
50 | )
51 | )}
52 |
53 | )}
54 |
55 | {/* ============== sent request =============== */}
56 | {show && (
57 |
58 | {list.map(
59 | (item) =>
60 | item.creatorId == userInfo.uid && (
61 |
70 | )
71 | )}
72 |
73 | )}
74 |
75 | );
76 | };
77 |
78 | export default Request;
79 |
--------------------------------------------------------------------------------
/src/components/Navbar/Settings.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 | import { useState } from "react";
3 | import { GoDotFill } from "react-icons/go";
4 | import { MdOutlinePerson3 } from "react-icons/md";
5 | import { useDispatch, useSelector } from "react-redux";
6 | import { getDatabase, ref, onValue, update } from "firebase/database";
7 | import { FaEdit } from "react-icons/fa";
8 | import {
9 | getAuth,
10 | reauthenticateWithCredential,
11 | updatePassword,
12 | updateProfile,
13 | } from "firebase/auth";
14 | import { loggedUser } from "../../store/slices/authSlice";
15 | import { toast, ToastContainer } from "react-toastify";
16 | import { EmailAuthProvider } from "firebase/auth/web-extension";
17 |
18 | const Settings = () => {
19 | const userInfo = useSelector((state) => state.userData.user);
20 | const db = getDatabase();
21 | const auth = getAuth();
22 | const updateProfileRef = useRef(null);
23 | const updatePassRef = useRef(null);
24 | const dispatch = useDispatch();
25 | const [userData, setUserData] = useState([]);
26 | const [time, setTime] = useState(new Date());
27 | const [editable, setEditable] = useState(false);
28 | const [editablePass, setEditablePass] = useState(false);
29 | const [usernameUpdate, setUsernameUpdate] = useState(false);
30 | const [updataData, setUpdataData] = useState({
31 | avater: "",
32 | username: "",
33 | });
34 | const [friendList, setFriendList] = useState([]);
35 | const [newPass, setNewPass] = useState("");
36 | const [oldPass, setOldPass] = useState("");
37 |
38 | // ============== friend list data
39 | useEffect(() => {
40 | onValue(ref(db, "friendList"), (snapshot) => {
41 | let arr = [];
42 | snapshot.forEach((item) => {
43 | arr.push({ ...item.val(), id: item.key });
44 | });
45 | setFriendList(arr);
46 | });
47 | }, []);
48 |
49 | // ============== outside click event
50 | window.addEventListener("mousedown", (e) => {
51 | if (
52 | updateProfileRef.current &&
53 | !updateProfileRef.current.contains(e.target)
54 | ) {
55 | setEditable(false);
56 | }
57 | });
58 | window.addEventListener("mousedown", (e) => {
59 | if (updatePassRef.current && !updatePassRef.current.contains(e.target)) {
60 | setEditablePass(false);
61 | }
62 | });
63 |
64 | // ============== time
65 | useEffect(() => {
66 | const interval = setInterval(() => {
67 | setTime(new Date());
68 | }, 1000);
69 | return () => clearInterval(interval);
70 | }, []);
71 |
72 | // ============== database write data
73 | useEffect(() => {
74 | const starCountRef = ref(db, "users/" + userInfo.uid);
75 | onValue(starCountRef, (snapshot) => {
76 | const data = snapshot.val();
77 | setUserData(data);
78 | });
79 | }, []);
80 |
81 | // ================== update profile
82 | const handleUpdate = () => {
83 | updateProfile(auth.currentUser, {
84 | displayName: updataData.username || auth.currentUser.displayName,
85 | photoURL: updataData.avater || auth.currentUser.photoURL,
86 | })
87 | .then(() => {
88 | const updatedUsername =
89 | updataData.username || auth.currentUser.displayName;
90 | const updatedAvater = updataData.avater || auth.currentUser.photoURL;
91 |
92 | update(ref(db, "users/" + userInfo.uid), {
93 | username: updatedUsername,
94 | profile_picture: updatedAvater,
95 | });
96 | dispatch(loggedUser(auth.currentUser));
97 |
98 | friendList.forEach((item) => {
99 | const itemRef = ref(db, "friendList/" + item.id);
100 | if (item.creatorId === userInfo.uid) {
101 | update(itemRef, {
102 | creatorName: updatedUsername,
103 | creatorAvater: updatedAvater,
104 | });
105 | } else if (item.participantId === userInfo.uid) {
106 | update(itemRef, {
107 | participantName: updatedUsername,
108 | participantAvater: updatedAvater,
109 | });
110 | }
111 | });
112 |
113 | setEditable(false);
114 | setUsernameUpdate(false);
115 | })
116 | .catch((error) => {
117 | console.log(error);
118 | });
119 | };
120 |
121 | // ============ change password
122 |
123 | const handleChangePass = () => {
124 | const user = auth.currentUser;
125 |
126 | if (!oldPass || !newPass) {
127 | toast.error("Please fill in both old and new password fields.");
128 | return;
129 | }
130 |
131 | if (oldPass.length < 6) {
132 | toast.error("Old password must be at least 6 characters long.");
133 | return;
134 | }
135 | if (newPass.length < 6) {
136 | toast.error("New password must be at least 6 characters long.");
137 | return;
138 | }
139 |
140 | const credential = EmailAuthProvider.credential(user.email, oldPass);
141 |
142 | reauthenticateWithCredential(user, credential)
143 | .then(() => {
144 | return updatePassword(user, newPass);
145 | })
146 | .then(() => {
147 | toast.success("✅ Password updated successfully!");
148 | setNewPass("");
149 | setOldPass("");
150 | setEditablePass(false);
151 | })
152 | .catch((error) => {
153 | const errorCode = error.code;
154 |
155 | if (errorCode === "auth/invalid-credential") {
156 | toast.error("❌ Old password is incorrect.");
157 | } else if (errorCode === "auth/weak-password") {
158 | toast.error("❌ New password must be at least 6 characters.");
159 | } else if (errorCode === "auth/too-many-requests") {
160 | toast.error("⚠️ Too many failed attempts. Try again later.");
161 | } else if (errorCode === "auth/requires-recent-login") {
162 | toast.error("⚠️ Please log in again to change your password.");
163 | } else {
164 | toast.error("❌ " + error.message);
165 | }
166 | });
167 | };
168 |
169 | // ============ change password
170 |
171 | return (
172 | <>
173 |
174 |
175 |
Settings
176 |
177 |

182 |
183 |
184 |
185 | {userData.username}
186 |
187 |
188 |
189 | Active
190 |
191 |
192 |
193 |
194 | Personal Info
195 |
196 |
197 |
Name
198 |
199 | {userData.username}
200 | {
202 | setEditable(true), setUsernameUpdate(true);
203 | }}
204 | className="text-2xl text-primary cursor-pointer hover:text-brand"
205 | />
206 |
207 |
208 |
209 |
210 | Email
211 |
212 |
213 | {userData.email}
214 |
215 |
216 |
217 |
Time
218 |
219 | {time.toLocaleString("en-US", {
220 | hour: "numeric",
221 | minute: "numeric",
222 | second: "numeric",
223 | hour12: true,
224 | })}
225 |
226 |
227 |
228 |
229 | Location
230 |
231 |
232 | {userData.location}
233 |
234 |
235 |
236 |
Name
237 |
238 | Change Password
239 | {
241 | setEditablePass(true);
242 | }}
243 | className="text-2xl text-primary cursor-pointer hover:text-brand"
244 | />
245 |
246 |
247 | {editable && (
248 |
252 |
253 | {usernameUpdate && (
254 |
256 | setUpdataData((prev) => ({
257 | ...prev,
258 | username: e.target.value,
259 | }))
260 | }
261 | type="text"
262 | placeholder="Edit Your Name"
263 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]"
264 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`}
265 | />
266 | )}
267 |
268 |
274 |
280 |
281 |
282 |
283 | )}
284 |
285 | {editablePass && (
286 |
290 |
291 |
setOldPass(e.target.value)}
293 | value={oldPass}
294 | type="password"
295 | placeholder="Edit Your Old Password"
296 | className={`w-full pl-5.5 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]"
297 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`}
298 | />
299 |
300 |
setNewPass(e.target.value)}
302 | value={newPass}
303 | type="password"
304 | placeholder="Edit Your New Password"
305 | className={`w-full pl-5.5 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]"
306 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`}
307 | />
308 |
309 |
310 |
316 |
322 |
323 |
324 |
325 | )}
326 |
327 | >
328 | );
329 | };
330 |
331 | export default Settings;
332 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import "tailwindcss";
2 |
3 | @theme {
4 | --color-nav_bg: #36404a;
5 | --color-primary: #eff2f7;
6 | --color-secondary: #abb4d2;
7 | --color-icons: #a6b0cf;
8 | --color-brand: #7269ef;
9 | }
10 |
11 | * {
12 | font-family: "Public Sans", sans-serif;
13 | }
14 | *::-webkit-scrollbar {
15 | display: none;
16 | }
17 | body {
18 | background-color: #303841;
19 | }
20 | Link:active {
21 | background: #000;
22 | }
23 |
24 | .add {
25 | box-sizing: border-box;
26 | background: rgba(217, 217, 217, 0.58);
27 | border: 2px solid white;
28 | box-shadow: 12px 17px 51px rgba(0, 0, 0, 0.22);
29 | backdrop-filter: blur(6px);
30 | border-radius: 17px;
31 | transition: all 0.5s;
32 | user-select: none;
33 | font-weight: bolder;
34 | color: black;
35 | }
36 |
37 | .add:hover {
38 | border: 2px solid black;
39 | transform: scale(1.03);
40 | }
41 |
42 | .add:active {
43 | transform: scale(0.95) rotateZ(1.7deg);
44 | }
45 |
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import "./index.css";
4 | import App from "./App.jsx";
5 | import { app } from "../dbConfig.js";
6 | import { Provider } from "react-redux";
7 | import store from "./store/store.js";
8 |
9 | createRoot(document.getElementById("root")).render(
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/src/pages/Error.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSelector } from "react-redux";
3 | import { Navigate } from "react-router";
4 |
5 | const Error = () => {
6 | const userInfo = useSelector((state) => state.userData.user);
7 |
8 | if (userInfo) {
9 | return ;
10 | } else {
11 | return ;
12 | }
13 | };
14 |
15 | export default Error;
16 |
--------------------------------------------------------------------------------
/src/pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from "react";
2 | import { BsChatRight } from "react-icons/bs";
3 | import { FaUserGroup, FaUserPlus } from "react-icons/fa6";
4 | import { GrContactInfo } from "react-icons/gr";
5 | import { IoSettingsOutline } from "react-icons/io5";
6 | import { MdOutlinePerson3 } from "react-icons/md";
7 | import { useDispatch, useSelector } from "react-redux";
8 | import { loggedUser } from "../store/slices/authSlice";
9 | import { Link, Navigate } from "react-router";
10 | import { FiMessageSquare } from "react-icons/fi";
11 | import Group from "../components/Group/Group";
12 | import GroupChatBox from "../components/Group/GroupChatBox";
13 | import ChatList from "../components/Chat/ChatList";
14 | import Profile from "../components/Navbar/Profile";
15 | import Settings from "../components/Navbar/Settings";
16 | import Request from "../components/Navbar/Request";
17 | import Contact from "../components/Navbar/Contact";
18 | import ChatBox from "../components/Chat/ChatBox";
19 |
20 | const Home = () => {
21 | const [editable, setEditable] = useState(false);
22 | const updateProfileRef = useRef(null);
23 | const dispatch = useDispatch();
24 | const userInfo = useSelector((state) => state.userData.user);
25 | const activeFriend = useSelector((state) => state.activeFriend.friend);
26 | const activeGroup = useSelector((state) => state.activeFriend.group);
27 |
28 | // =============== use ref ========
29 | const chatlistref = useRef(null);
30 | const chatbgref = useRef(null);
31 | const profileref = useRef(null);
32 | const requestRef = useRef(null);
33 | const ContactRef = useRef(null);
34 | const groupRef = useRef(null);
35 | const chatBoxRef = useRef(null);
36 | const GroupChatBoxRef = useRef(null);
37 |
38 | // ============ bg ref
39 | const profilebgref = useRef(null);
40 | const settingsref = useRef(null);
41 | const settingsbgref = useRef(null);
42 | const requestbgRef = useRef(null);
43 | const ContactBgRef = useRef(null);
44 | const groupBgRef = useRef(null);
45 | // =============== use ref ========
46 |
47 | // ======= click event ====
48 | const handleProf = () => {
49 | chatlistref.current.style = "display : none;";
50 | profileref.current.style = "display : block;";
51 | settingsref.current.style = "display : none;";
52 | requestRef.current.style = "display : none;";
53 | ContactRef.current.style = "display : none";
54 | groupRef.current.style = "display : none";
55 | profilebgref.current.style = "background-color: #7269EF;";
56 | chatbgref.current.style = "background-color: transparent;";
57 | settingsbgref.current.style = "background-color: transparent;";
58 | requestbgRef.current.style = "background-color: transparent;";
59 | ContactBgRef.current.style = "background-color: transparent;";
60 | groupBgRef.current.style = "background-color: transparent;";
61 | };
62 | const hancleChat = () => {
63 | GroupChatBoxRef.current.style = "display : none";
64 | chatBoxRef.current.style = "display : block";
65 | chatlistref.current.style = "display : block;";
66 | profileref.current.style = "display : none;";
67 | settingsref.current.style = "display : none;";
68 | requestRef.current.style = "display : none;";
69 | ContactRef.current.style = "display : none";
70 | groupRef.current.style = "display : none";
71 | profilebgref.current.style = "background-color: transparent;";
72 | chatbgref.current.style = "background-color: #7269EF;";
73 | settingsbgref.current.style = "background-color: transparent;";
74 | requestbgRef.current.style = "background-color: transparent;";
75 | ContactBgRef.current.style = "background-color: transparent;";
76 | groupBgRef.current.style = "background-color: transparent;";
77 | };
78 | const handleSett = () => {
79 | chatlistref.current.style = "display : none;";
80 | profileref.current.style = "display : none;";
81 | requestRef.current.style = "display : none;";
82 | settingsref.current.style = "display : block;";
83 | ContactRef.current.style = "display : none";
84 | groupRef.current.style = "display : none";
85 | profilebgref.current.style = "background-color: transparent;";
86 | chatbgref.current.style = "background-color: transparent;";
87 | requestbgRef.current.style = "background-color: transparent;";
88 | settingsbgref.current.style = "background-color: #7269EF;";
89 | ContactBgRef.current.style = "background-color: transparent;";
90 | groupBgRef.current.style = "background-color: transparent;";
91 | };
92 | const handleReq = () => {
93 | chatlistref.current.style = "display : none;";
94 | profileref.current.style = "display : none;";
95 | settingsref.current.style = "display : none;";
96 | requestRef.current.style = "display : block";
97 | ContactRef.current.style = "display : none";
98 | groupRef.current.style = "display : none";
99 | profilebgref.current.style = "background-color: transparent;";
100 | chatbgref.current.style = "background-color: transparent;";
101 | settingsbgref.current.style = "background-color: transparent;";
102 | requestbgRef.current.style = "background-color: #7269EF;";
103 | ContactBgRef.current.style = "background-color: transparent;";
104 | groupBgRef.current.style = "background-color: transparent;";
105 | };
106 | const handleContact = () => {
107 | chatlistref.current.style = "display : none;";
108 | profileref.current.style = "display : none;";
109 | settingsref.current.style = "display : none;";
110 | requestRef.current.style = "display : none";
111 | groupRef.current.style = "display : none";
112 | ContactRef.current.style = "display : block";
113 | profilebgref.current.style = "background-color: transparent;";
114 | chatbgref.current.style = "background-color: transparent;";
115 | settingsbgref.current.style = "background-color: transparent;";
116 | requestbgRef.current.style = "background-color: transparent;";
117 | groupBgRef.current.style = "background-color: transparent;";
118 | ContactBgRef.current.style = "background-color: #7269EF;";
119 | };
120 | const handleGroup = () => {
121 | GroupChatBoxRef.current.style = "display : block";
122 | chatBoxRef.current.style = "display : none";
123 | chatlistref.current.style = "display : none;";
124 | profileref.current.style = "display : none;";
125 | settingsref.current.style = "display : none;";
126 | requestRef.current.style = "display : none";
127 | ContactRef.current.style = "display : none";
128 | groupRef.current.style = "display : block";
129 | profilebgref.current.style = "background-color: transparent;";
130 | chatbgref.current.style = "background-color: transparent;";
131 | settingsbgref.current.style = "background-color: transparent;";
132 | requestbgRef.current.style = "background-color: transparent;";
133 | ContactBgRef.current.style = "background-color: transparent;";
134 | groupBgRef.current.style = "background-color: #7269EF;";
135 | };
136 |
137 | // ======= click event ====
138 |
139 | const handleProfile = () => {
140 | if (editable) {
141 | setEditable(false);
142 | } else {
143 | setEditable(true);
144 | }
145 | window.addEventListener("mousedown", (e) => {
146 | if (
147 | updateProfileRef.current &&
148 | !updateProfileRef.current.contains(e.target)
149 | ) {
150 | setEditable(false);
151 | }
152 | });
153 | };
154 |
155 | // -============ user sign out
156 | const handleSignOut = () => {
157 | dispatch(loggedUser(null));
158 | };
159 |
160 | // =========== protection
161 | if (!userInfo) {
162 | return ;
163 | }
164 |
165 | return (
166 |
167 | {/* ================ navbar ==================== */}
168 |
169 |
272 | {/* ================ navbar ==================== */}
273 |
274 | {/* ================ navabr request components ================== */}
275 |
276 |
277 |
278 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 | {/* ================ navabr request components ================== */}
294 |
295 | {/* ============= chat box =============== */}
296 |
297 | {activeFriend ? (
298 |
299 | ) : (
300 |
301 |
302 |
303 |
304 |
305 | Start Conversation
306 |
307 |
308 | )}
309 |
310 |
311 | {/* ============= chat box =============== */}
312 | {/* ============= group chat box =============== */}
313 |
314 | {activeGroup ? (
315 |
316 | ) : (
317 |
318 |
319 |
320 |
321 |
322 | Start Conversation
323 |
324 |
325 | )}
326 |
327 | {/* ============= group chat box =============== */}
328 |
329 | );
330 | };
331 |
332 | export default Home;
333 |
--------------------------------------------------------------------------------
/src/pages/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, Navigate, useNavigate } from "react-router";
3 | import { FaEnvelope, FaLock } from "react-icons/fa";
4 | import { IoIosEye, IoIosEyeOff } from "react-icons/io";
5 | import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
6 | import { toast, ToastContainer } from "react-toastify";
7 | import { useDispatch, useSelector } from "react-redux";
8 | import { loggedUser } from "../store/slices/authSlice";
9 |
10 | const Login = () => {
11 | const [isOpen, setIsOpen] = useState(true);
12 | const auth = getAuth();
13 | const navigate = useNavigate();
14 | const dispatch = useDispatch();
15 | const userInfo = useSelector((state) => state.userData.user);
16 | const [userData, setUserData] = useState({
17 | email: "",
18 | password: "",
19 | });
20 |
21 | // =========== login
22 | const handleSubmit = (e) => {
23 | e.preventDefault();
24 |
25 | signInWithEmailAndPassword(auth, userData.email, userData.password)
26 | .then(() => {
27 | if (auth.currentUser.emailVerified) {
28 | toast.success("Login Successful!");
29 | setTimeout(() => {
30 | navigate("/");
31 | dispatch(loggedUser(auth.currentUser));
32 | }, 2000);
33 | } else {
34 | toast.error("Please Verify Your Email!");
35 | }
36 | })
37 | .catch((error) => {
38 | const errorCode = error.code;
39 | console.log(errorCode);
40 | if (errorCode === "auth/invalid-email") {
41 | toast.error("Enter a valid Email!");
42 | }
43 | if (errorCode === "auth/invalid-credential") {
44 | toast.error("Invalid Credential!");
45 | }
46 | if (errorCode === "auth/missing-password") {
47 | toast.error("Enter Password!");
48 | }
49 | });
50 | };
51 |
52 | // =========== protection
53 | if (userInfo) {
54 | return ;
55 | }
56 |
57 | return (
58 |
59 |
60 |
61 |
62 |
Welcome
63 |
We're so excited to see you again!
64 |
65 |
66 |
131 |
132 |
133 |
134 | Need an account?{" "}
135 |
139 | Register
140 |
141 |
142 |
143 |
144 |
145 | );
146 | };
147 |
148 | export default Login;
149 |
--------------------------------------------------------------------------------
/src/pages/Registration.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, Navigate, useNavigate } from "react-router";
3 | import { FaUser, FaEnvelope, FaLock } from "react-icons/fa";
4 | import { IoIosEye, IoIosEyeOff } from "react-icons/io";
5 | import {
6 | getAuth,
7 | createUserWithEmailAndPassword,
8 | updateProfile,
9 | sendEmailVerification,
10 | } from "firebase/auth";
11 | import { toast, ToastContainer } from "react-toastify";
12 | import { getDatabase, ref, set } from "firebase/database";
13 | import { useSelector } from "react-redux";
14 |
15 | const Registration = () => {
16 | const [isOpen, setIsOpen] = useState(true);
17 | const userInfo = useSelector((state) => state.userData.user);
18 | const navigate = useNavigate();
19 | const auth = getAuth();
20 | const db = getDatabase();
21 | const [userData, setUserData] = useState({
22 | username: "",
23 | email: "",
24 | password: "",
25 | });
26 |
27 | // ============== create user
28 | const handleSubmit = (e) => {
29 | e.preventDefault();
30 |
31 | createUserWithEmailAndPassword(auth, userData.email, userData.password)
32 | .then(() => {
33 | updateProfile(auth.currentUser, {
34 | displayName: userData.username,
35 | photoURL: "images/default.png",
36 | }).then(() => {
37 | sendEmailVerification(auth.currentUser).then(() => {
38 | toast.success("Registration Successful, Please verify Your Email!");
39 | setTimeout(() => {
40 | navigate("/signin");
41 | }, 2000);
42 | set(ref(db, "users/" + auth.currentUser.uid), {
43 | username: auth.currentUser.displayName,
44 | email: auth.currentUser.email,
45 | profile_picture: auth.currentUser.photoURL,
46 | location: "Bangladesh",
47 | });
48 | });
49 | });
50 | })
51 | .catch((error) => {
52 | const errorMessage = error.code;
53 | console.log(errorMessage);
54 | if (errorMessage === "auth/missing-email") {
55 | toast.error("Please Enter Your Email!");
56 | }
57 | if (errorMessage === "auth/invalid-email") {
58 | toast.error("Please Enter a valid Email!");
59 | }
60 | if (errorMessage === "auth/email-already-in-use") {
61 | toast.error("Email is already exist!");
62 | }
63 | if (errorMessage === "auth/missing-password") {
64 | toast.error("Please Enter Your Password!");
65 | }
66 | if (errorMessage === "auth/weak-password") {
67 | toast.error("Password need at least 6 characters!");
68 | }
69 | });
70 | };
71 |
72 | // ============= protection
73 | if (userInfo) {
74 | return ;
75 | }
76 |
77 | return (
78 | <>
79 |
80 |
81 |
82 |
83 |
84 | Create Account
85 |
86 |
Join our community today!
87 |
88 |
153 |
154 |
155 |
156 | Already have an account?{" "}
157 |
161 | Sign In
162 |
163 |
164 |
165 |
166 |
167 | >
168 | );
169 | };
170 |
171 | export default Registration;
172 |
--------------------------------------------------------------------------------
/src/pages/Reset.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { FaEnvelope } from "react-icons/fa";
3 | import { Link } from "react-router";
4 | import { getAuth, sendPasswordResetEmail } from "firebase/auth";
5 | import { toast, ToastContainer } from "react-toastify";
6 |
7 | const Reset = () => {
8 | const auth = getAuth();
9 | const [userEmail, setUserEmail] = useState();
10 |
11 | // =============== reset email
12 | const handleSubmit = (e) => {
13 | e.preventDefault();
14 | sendPasswordResetEmail(auth, userEmail)
15 | .then(() => {
16 | toast.success("Password reset Email sent Successfully!");
17 | })
18 | .catch((error) => {
19 | const errorCode = error.code;
20 | console.log(errorCode);
21 | });
22 | };
23 |
24 | return (
25 | <>
26 |
27 |
28 |
29 |
30 |
31 | Reset Password
32 |
33 |
34 |
35 |
55 |
56 |
57 | Return to Sing In?{" "}
58 |
62 | Sign In
63 |
64 |
65 |
66 |
67 |
68 | >
69 | );
70 | };
71 |
72 | export default Reset;
73 |
--------------------------------------------------------------------------------
/src/store/slices/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const authSlice = createSlice({
4 | name: "auth",
5 | initialState: {
6 | user: JSON.parse(localStorage.getItem("userData")) || null,
7 | },
8 | reducers: {
9 | loggedUser: (state, action) => {
10 | state.user = action.payload;
11 | localStorage.setItem("userData", JSON.stringify(action.payload));
12 | },
13 | },
14 | });
15 |
16 | export const { loggedUser } = authSlice.actions;
17 |
18 | export default authSlice.reducer;
19 |
--------------------------------------------------------------------------------
/src/store/slices/conversationSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | export const conversaionSlice = createSlice({
4 | name: "conversaion",
5 | initialState: {
6 | friend: null,
7 | group: null,
8 | },
9 | reducers: {
10 | selectConversation: (state, action) => {
11 | state.friend = action.payload;
12 | },
13 | selectGroup: (state, action) => {
14 | state.group = action.payload;
15 | },
16 | },
17 | });
18 |
19 | export const { selectConversation, selectGroup } = conversaionSlice.actions;
20 |
21 | export default conversaionSlice.reducer;
22 |
--------------------------------------------------------------------------------
/src/store/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authSlice from "./slices/authSlice";
3 | import conversaionSlice from "./slices/conversationSlice";
4 |
5 | export default configureStore({
6 | reducer: {
7 | userData: authSlice,
8 | activeFriend: conversaionSlice,
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/src/utils/ChatItems.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import { selectConversation } from "../store/slices/conversationSlice";
4 | import ChatBox from "../components/Chat/ChatBox";
5 | import { IoMdArrowRoundBack } from "react-icons/io";
6 |
7 | const ChatItems = ({
8 | avater,
9 | name,
10 | id,
11 | messageId,
12 | lastMessage,
13 | styling = "bg-white",
14 | stylingName = "text-black22",
15 | time,
16 | }) => {
17 | const dispatch = useDispatch();
18 | const [show, setShow] = useState(false);
19 |
20 | // ============== user data ==> redux
21 | const handleClick = () => {
22 | dispatch(selectConversation({ name, avater, id, messageId }));
23 | setShow(true);
24 | };
25 |
26 | return (
27 | <>
28 |
32 |
33 |

34 |
35 |
38 | {name}
39 |
40 |
41 | {lastMessage && lastMessage.length > 12
42 | ? lastMessage.substring(0, 10) + " ..."
43 | : lastMessage}
44 |
45 |
46 |
47 |
48 |
{time}
49 |
50 |
51 | {show && (
52 |
53 |
54 |
60 |
61 | )}
62 |
63 | >
64 | );
65 | };
66 |
67 | export default ChatItems;
68 |
--------------------------------------------------------------------------------
/src/utils/CommonPerson.jsx:
--------------------------------------------------------------------------------
1 | import { getDatabase, push, ref, remove, set } from "firebase/database";
2 | import React from "react";
3 | import { BsSave2 } from "react-icons/bs";
4 | import { MdCancel } from "react-icons/md";
5 |
6 | const CommonPerson = ({
7 | avater,
8 | name,
9 | styling = "bg-white",
10 | stylingName = "text-black22",
11 | // stylingMessage = "[#7A7A7A]",
12 | reqData,
13 | sentData,
14 | }) => {
15 | const db = getDatabase();
16 | console.log();
17 | const handleRemove = (data) => {
18 | remove(ref(db, "requestList/" + data.id));
19 | };
20 |
21 | // ================= add to friendList and remove in requestlist
22 | const handleAdd = (item) => {
23 | set(push(ref(db, "friendList/")), {
24 | creatorName: item.creatorName,
25 | creatorAvater: item.creatorAvater,
26 | creatorId: item.creatorId,
27 | creatorEmail: item.creatorEmail,
28 | participantName: item.participantName,
29 | participantAvater: item.participantAvater,
30 | participantId: item.participantId,
31 | participantEmail: item.participantEmail,
32 | lastMessage: "",
33 | });
34 | setTimeout(() => {
35 | remove(ref(db, "requestList/" + item.id));
36 | }, 1000);
37 | };
38 |
39 | return (
40 | <>
41 |
44 |
45 |

46 |
49 | {name}
50 |
51 |
52 |
53 | {reqData ? (
54 |
55 |
handleAdd(reqData)}
57 | className="accept text-white px-4 py-2 bg-black rounded-lg hover:bg-green-600"
58 | >
59 |
60 |
61 |
handleRemove(reqData)}
63 | className="cencel text-white px-4 py-2 bg-black rounded-lg hover:bg-red-600"
64 | >
65 |
66 |
67 |
68 | ) : (
69 |
70 |
handleRemove(sentData)}
72 | className="cencel text-white px-4 py-2 bg-black rounded-lg hover:bg-red-600"
73 | >
74 |
75 |
76 |
77 | )}
78 |
79 | >
80 | );
81 | };
82 |
83 | export default CommonPerson;
84 |
--------------------------------------------------------------------------------
/src/utils/FriendItem.jsx:
--------------------------------------------------------------------------------
1 | import { getDatabase, ref, remove } from "firebase/database";
2 | import React, { useState } from "react";
3 |
4 | const FriendItem = ({
5 | avater,
6 | name,
7 | email,
8 | id,
9 | styling = "bg-white",
10 | stylingName = "text-black22",
11 | time,
12 | }) => {
13 | const [show, setShow] = useState(false);
14 | const db = getDatabase();
15 |
16 | const handleShow = () => {
17 | if (!show) {
18 | setShow(true);
19 | } else {
20 | setShow(false);
21 | }
22 | };
23 |
24 | // ============= remove friend
25 | const handleRemove = () => {
26 | remove(ref(db, "friendList/" + id));
27 | };
28 |
29 | return (
30 | <>
31 |
35 |
36 |

37 |
40 | {name}
41 |
42 |
43 |
{time}
44 |
45 | {show && (
46 |
47 |

52 |
53 | {name}
54 |
55 |
56 | {email}
57 |
58 |
59 |
65 |
66 |
67 | )}
68 | >
69 | );
70 | };
71 |
72 | export default FriendItem;
73 |
--------------------------------------------------------------------------------
/src/utils/UserList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 | import { getDatabase, onValue, push, ref, set } from "firebase/database";
3 | import { useSelector } from "react-redux";
4 |
5 | const UserList = ({ data }) => {
6 | const [show, setShow] = useState(false);
7 | const db = getDatabase();
8 | const userInfo = useSelector((state) => state.userData.user);
9 | const [requestList, setRequestList] = useState([]);
10 | const reqRef = useRef(null);
11 |
12 | const handleUser = () => {
13 | if (!show) {
14 | setShow(true);
15 | } else {
16 | setShow(false);
17 | }
18 | };
19 |
20 | // ============ friend request sent
21 | const handleAdd = () => {
22 | set(push(ref(db, "requestList/")), {
23 | creatorName: userInfo.displayName,
24 | creatorId: userInfo.uid,
25 | creatorAvater: userInfo.photoURL,
26 | creatorEmail: userInfo.email,
27 | participantName: data.username,
28 | participantId: data.id,
29 | participantAvater: data.profile_picture,
30 | participantEmail: data.email,
31 | });
32 | };
33 |
34 | // ============ request data
35 | useEffect(() => {
36 | onValue(ref(db, "requestList/"), (snapshot) => {
37 | let arr = [];
38 | snapshot.forEach((item) => {
39 | arr.push(item.val().creatorId + item.val().participantId);
40 | });
41 | setRequestList(arr);
42 | });
43 | }, []);
44 |
45 | return (
46 | <>
47 |
51 |
handleUser()} className="profile flex gap-4">
52 |
53 |

58 |
59 | {data.username}
60 |
61 |
62 |
63 | {requestList.includes(data.id + userInfo.uid) ||
64 | requestList.includes(userInfo.uid + data.id) ? (
65 | (reqRef.current.style = "display : none;")
66 | ) : (
67 |
73 | )}
74 |
75 |
76 | {/* ============ user data show ========== */}
77 | {show && (
78 |
79 |

84 |
85 | {data.username}
86 |
87 |
88 | {data.email}
89 |
90 |
91 | {data.location}
92 |
93 |
94 | )}
95 | >
96 | );
97 | };
98 |
99 | export default UserList;
100 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | { "source": "/(.*)", "destination": "/index.html" }
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import tailwindcss from '@tailwindcss/vite'
4 |
5 |
6 | // https://vite.dev/config/
7 | export default defineConfig({
8 | plugins: [react(), tailwindcss()],
9 | });
10 |
--------------------------------------------------------------------------------