├── .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 | logo 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 |
123 | {emoji && ( 124 |
125 | 128 | setMessageContent((prev) => prev + e.emoji) 129 | } 130 | /> 131 |
132 | )} 133 | ( 135 | setMessageContent(e.target.value), setEmoji(false) 136 | )} 137 | value={messageContent} 138 | className="w-full outline-none rounded-md pl-3 text-base font-normal font-inter text-white bg-transparent placeholder-[#99AAB5]" 139 | type="text" 140 | placeholder="Text Here" 141 | /> 142 |
143 |
144 | setEmoji(!emoji)} 146 | className="cursor-pointer hover:text-[#7289DA] duration-300 z-10" 147 | /> 148 |
149 | 152 |
153 |
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 | logo 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 |
126 | {emoji && ( 127 |
128 | 131 | setMessageContent((prev) => prev + e.emoji) 132 | } 133 | /> 134 |
135 | )} 136 | ( 138 | setMessageContent(e.target.value), setEmoji(false) 139 | )} 140 | value={messageContent} 141 | className="w-full outline-none rounded-md pl-3 text-base font-normal font-inter text-white bg-transparent placeholder-[#99AAB5]" 142 | type="text" 143 | placeholder="Text Here" 144 | /> 145 |
146 |
147 | setEmoji(!emoji)} 149 | className="cursor-pointer hover:text-[#7289DA] duration-300 z-10" 150 | /> 151 |
152 | 155 |
156 |
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 | profile_photo 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 | profile_photo 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 |
279 | 280 |
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 |
67 |
68 | 69 | 71 | setUserData((prev) => ({ ...prev, email: e.target.value })) 72 | } 73 | type="email" 74 | name="email" 75 | placeholder="Email Address" 76 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]" 77 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`} 78 | /> 79 |
80 | 81 |
82 | 83 |
84 | 86 | setUserData((prev) => ({ ...prev, password: e.target.value })) 87 | } 88 | type={isOpen ? "password" : "text"} 89 | name="password" 90 | placeholder="Password" 91 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]" 92 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`} 93 | /> 94 | {isOpen ? ( 95 | setIsOpen(false)} 98 | /> 99 | ) : ( 100 | setIsOpen(true)} 103 | /> 104 | )} 105 |
106 |
107 | 108 |
109 | 116 | 120 | Forgot Password? 121 | 122 |
123 | 124 | 130 |
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 |
89 |
90 | 91 | 93 | setUserData((prev) => ({ ...prev, username: e.target.value })) 94 | } 95 | type="text" 96 | name="username" 97 | placeholder="Username" 98 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]" 99 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`} 100 | /> 101 |
102 | 103 |
104 | 105 | 107 | setUserData((prev) => ({ ...prev, email: e.target.value })) 108 | } 109 | type="email" 110 | name="email" 111 | placeholder="Email Address" 112 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 border-[#2C2F33] rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`} 113 | /> 114 |
115 | 116 |
117 | 118 |
119 | 121 | setUserData((prev) => ({ 122 | ...prev, 123 | password: e.target.value, 124 | })) 125 | } 126 | type={isOpen ? "password" : "text"} 127 | name="password" 128 | placeholder="Password" 129 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]" rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`} 130 | /> 131 | {isOpen ? ( 132 | setIsOpen(false)} 135 | /> 136 | ) : ( 137 | setIsOpen(true)} 140 | /> 141 | )} 142 |
143 |
144 | 145 | 152 |
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 |
36 |
37 | 38 | setUserEmail(e.target.value)} 40 | type="email" 41 | name="email" 42 | placeholder="Email Address" 43 | className={`w-full pl-10 pr-4 py-4 bg-[#1E2124] border-2 "border-[#2C2F33]" 44 | rounded-lg text-white placeholder-[#99AAB5] focus:outline-none focus:border-[#7289DA] transition-all hover:border-[#7289DA]`} 45 | /> 46 |
47 | 48 | 54 |
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 | logo 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 | logo 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 | logo 37 |

40 | {name} 41 |

42 |
43 |

{time}

44 |
45 | {show && ( 46 |
47 | profile 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 | profile 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 | profile 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 | --------------------------------------------------------------------------------