From bd2158ee66ed066af0b79ee42130516026ea391a Mon Sep 17 00:00:00 2001 From: IndieCoderMM Date: Tue, 31 Oct 2023 22:41:46 +0630 Subject: [PATCH] Refactor chat features --- README.md | 28 ++-- index.html | 4 +- src/App.tsx | 17 +-- src/common.types.ts | 32 ----- src/features/Chat/ChatMessageSkeleton.tsx | 14 -- src/features/Chat/ChatWindow.tsx | 132 ------------------ src/features/Chat/Header.tsx | 89 ++++++++++++ .../Chat/{ChatMessage.tsx => Message.tsx} | 13 ++ ...MessagesView.tsx => MessagesContainer.tsx} | 9 +- src/features/Chat/index.ts | 4 + src/lib/constants.ts | 4 +- src/pages/Chat.tsx | 68 ++++++++- src/pages/index.ts | 14 +- 13 files changed, 201 insertions(+), 227 deletions(-) delete mode 100644 src/common.types.ts delete mode 100644 src/features/Chat/ChatMessageSkeleton.tsx delete mode 100644 src/features/Chat/ChatWindow.tsx create mode 100644 src/features/Chat/Header.tsx rename src/features/Chat/{ChatMessage.tsx => Message.tsx} (70%) rename src/features/Chat/{MessagesView.tsx => MessagesContainer.tsx} (81%) create mode 100644 src/features/Chat/index.ts diff --git a/README.md b/README.md index 672e4c8..6015dd4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ - [Convoz](#convoz) - [🚀 Visit Live Website](#-visit-live-website) - [🔥 Features](#-features) - - [🧰 Stack](#-stack) - - [🛠️ Getting Started](#️-getting-started) + - [🛠️ Stack](#️-stack) + - [📙 Getting Started](#-getting-started) - [🗂️ Project Structure](#️-project-structure) - [🤝 Contributing](#-contributing) - [📜 License](#-license) @@ -35,21 +35,21 @@ - **User Authentication**: Secure user authentication with Google Sign-In - **User-Friendly Interface**: Intuitive UI/UX design to enhance user experience -## 🧰 Stack +## 🛠️ Stack This project was built using the following technologies: -- React -- TypeScript -- Firebase -- Redux Toolkit -- Vite -- ESLint -- react-firebase-hooks -- react-router-dom -- Tailwind CSS +- ⚛️ React +- 🔷 TypeScript +- 🔥 Firebase +- 🧰 Redux Toolkit +- ⚡ Vite +- 🔍 ESLint +- 🪝 react-firebase-hooks +- 🚦 react-router-dom +- 🎨 Tailwind CSS -## 🛠️ Getting Started +## 📙 Getting Started To get started with this project, follow these steps: @@ -99,4 +99,4 @@ Please check out our [Contribution Guide](./CONTRIBUTING.md) for more details. ## 📜 License -This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENSE](./LICENSE) for details. diff --git a/index.html b/index.html index 232b8d2..c9007b3 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,9 @@ - Convoz - Awesome Chat Platform for Any Interest + Convoz - Awesome Chat Platform | Open-source Project on GitHub + content="Convoz is an open-source chat platform that allows you to easily create and join online chat rooms. Built with React, Redux, Firebase, and Vite" /> diff --git a/src/App.tsx b/src/App.tsx index 4c10685..40d2ae0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,8 +1,7 @@ import { useEffect } from "react"; import { Toaster } from "react-hot-toast"; -import { Navigate, Route, Routes, useNavigate } from "react-router-dom"; +import { Route, Routes, useNavigate } from "react-router-dom"; -import ChatWindow from "./features/Chat/ChatWindow"; import { clearUser, selectAuthStatus, @@ -13,7 +12,7 @@ import { AuthStatus } from "./lib/constants"; import { useAppDispatch, useAppSelector } from "./lib/store"; import { Channels, - Chat, + ChatPage, Explore, LandingPage, Profile, @@ -21,8 +20,6 @@ import { } from "./pages"; import RootLayout from "./RootLayout"; -const GENERAL_CHANNEL_ID = import.meta.env.VITE_GENERAL_CHANNEL_ID || "general"; - const App = () => { const { user, data, loading } = useAuthUser(); const authStatus = useAppSelector(selectAuthStatus); @@ -52,15 +49,7 @@ const App = () => { errorElement={
Not found
} > } /> - }> - - } - /> - } /> - + } /> } /> } /> } /> diff --git a/src/common.types.ts b/src/common.types.ts deleted file mode 100644 index 1435edc..0000000 --- a/src/common.types.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface UserInterface { - id: string; - name: string; - bio: string; - avatarId: number; - email: string; - createdAt: number; - channels: string[]; - role: "admin" | "user"; -} - -export type ChannelType = "public" | "private" | "static" | "announcement"; - -export interface ChannelInterface { - id: string; - name: string; - description: string; - createdAt: number; - createdBy: string; - members: string[]; - type: ChannelType; - messages: MessageInterface[]; -} - -export interface MessageInterface { - id: string; - channelId: string; - text: string; - createdAt: number; - createdBy: string; - author: UserInterface; -} diff --git a/src/features/Chat/ChatMessageSkeleton.tsx b/src/features/Chat/ChatMessageSkeleton.tsx deleted file mode 100644 index 9a35990..0000000 --- a/src/features/Chat/ChatMessageSkeleton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -const ChatMessageSkeleton = () => { - return ( -
-
-
-
-
-
-
-
- ); -}; - -export default ChatMessageSkeleton; diff --git a/src/features/Chat/ChatWindow.tsx b/src/features/Chat/ChatWindow.tsx deleted file mode 100644 index a597068..0000000 --- a/src/features/Chat/ChatWindow.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import dayjs from "dayjs"; -import LocalizedFormat from "dayjs/plugin/localizedFormat"; -import { useEffect, useState } from "react"; -import { FaEdit, FaPlus } from "react-icons/fa"; -import { useParams } from "react-router-dom"; - -import { avatars } from "../../lib/constants"; -import { usersRef } from "../../lib/firebase"; -import { getDocIfExists, mapDocToUser } from "../../lib/firestore-utils"; -import { useAppSelector } from "../../lib/store"; -import { getChannelById } from "../Channels/channelsSlice"; -import { selectUser } from "../User/userSlice"; -import ChatForm from "./ChatForm"; -import MessagesView from "./MessagesView"; -import Navbar from "./Navbar"; - -import type { User } from "../../schema"; -import Modal from "../../components/Modal"; -import ChannelForm from "../Channels/ChannelForm"; -dayjs.extend(LocalizedFormat); - -const ChatWindow = () => { - const { channelId } = useParams(); - const [creator, setCreator] = useState(null); - const currentUser = useAppSelector(selectUser); - const [openModal, setOpenModal] = useState(false); - - const channel = useAppSelector((state) => - getChannelById(state, channelId ?? ""), - ); - - useEffect(() => { - if (!channel) return; - getDocIfExists(usersRef, channel.createdBy) - .then(({ data }) => { - if (data) { - setCreator(mapDocToUser(data)); - } - }) - .catch((err) => { - console.error(err); - }); - }, [channel]); - - const editable = currentUser?.id === channel?.createdBy; - - if (!channel) { - return ( -
-

Welcome to Convoz Chat!

-

- Please select a channel from the sidebar -

-
- ); - } - - return ( -
-
- -
- # -
-

- Welcome to #{channel.name.toLowerCase()} channel! -

-

{channel?.description}

- {/* Channel Actions ------------------------------------------------------------------------------- */} - {editable ? ( -
- - - -
- ) : null} -
-
- -
-

{creator?.name}

- - 🟢 Admin - -
-
- - {dayjs(channel.createdAt).format("LLLL")} - -
-
- {/* Messages View --------------------------------------------------------------------------------------- */} -
- -
- {/* Chat Form --------------------------------------------------------------------------------------- */} -
- -
- {/* Edit Channel Modal --------------------------------------------------------------------------------- */} - setOpenModal(false)} - title="Edit Channel" - > - setOpenModal(false)} - /> - -
- ); -}; - -export default ChatWindow; diff --git a/src/features/Chat/Header.tsx b/src/features/Chat/Header.tsx new file mode 100644 index 0000000..fd25e1b --- /dev/null +++ b/src/features/Chat/Header.tsx @@ -0,0 +1,89 @@ +import { useState } from "react"; +import type { Channel, User } from "../../schema"; +import Navbar from "./Navbar"; +import { FaEdit, FaPlus } from "react-icons/fa"; +import { avatars } from "../../lib/constants"; +import dayjs from "dayjs"; +import LocalizedFormat from "dayjs/plugin/localizedFormat"; +import Modal from "../../components/Modal"; +import ChannelForm from "../Channels/ChannelForm"; + +dayjs.extend(LocalizedFormat); + +const ChatHeader = ({ + channel, + editable, + creator, +}: { + channel: Channel; + editable: boolean; + creator?: User; +}) => { + const [openModal, setOpenModal] = useState(false); + + return ( +
+ +
+ # +
+

+ Welcome to #{channel.name.toLowerCase()} channel! +

+

{channel?.description}

+ {/* Channel Actions ------------------------------------------------------------------------------- */} + {editable ? ( +
+ + + +
+ ) : null} +
+
+ +
+

{creator?.name}

+ + 🟢 Admin + +
+
+ + {dayjs(channel.createdAt).format("LLLL")} + +
+ + {/* Edit Channel Modal --------------------------------------------------------------------------------- */} + setOpenModal(false)} + title="Edit Channel" + > + setOpenModal(false)} + /> + +
+ ); +}; + +export default ChatHeader; diff --git a/src/features/Chat/ChatMessage.tsx b/src/features/Chat/Message.tsx similarity index 70% rename from src/features/Chat/ChatMessage.tsx rename to src/features/Chat/Message.tsx index de36cec..998642b 100644 --- a/src/features/Chat/ChatMessage.tsx +++ b/src/features/Chat/Message.tsx @@ -36,4 +36,17 @@ const ChatMessage = (props: Message) => { ); }; +export const ChatMessageSkeleton = () => { + return ( +
+
+
+
+
+
+
+
+ ); +}; + export default ChatMessage; diff --git a/src/features/Chat/MessagesView.tsx b/src/features/Chat/MessagesContainer.tsx similarity index 81% rename from src/features/Chat/MessagesView.tsx rename to src/features/Chat/MessagesContainer.tsx index aa19777..4411f68 100644 --- a/src/features/Chat/MessagesView.tsx +++ b/src/features/Chat/MessagesContainer.tsx @@ -1,9 +1,8 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react"; -import { useAppSelector } from '../../lib/store'; -import { getChannelById } from '../Channels/channelsSlice'; -import ChatMessage from './ChatMessage'; -import ChatMessageSkeleton from './ChatMessageSkeleton'; +import { useAppSelector } from "../../lib/store"; +import { getChannelById } from "../Channels/channelsSlice"; +import ChatMessage, { ChatMessageSkeleton } from "./Message"; type Props = { channelId: string; diff --git a/src/features/Chat/index.ts b/src/features/Chat/index.ts new file mode 100644 index 0000000..434eed7 --- /dev/null +++ b/src/features/Chat/index.ts @@ -0,0 +1,4 @@ +export { default as Header } from "./Header"; +export { default as Sidebar } from "./Sidebar"; +export { default as MessagesContainer } from "./MessagesContainer"; +export { default as ChatForm } from "./ChatForm"; diff --git a/src/lib/constants.ts b/src/lib/constants.ts index d5a7a8a..aaac707 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -22,6 +22,8 @@ import { import type { ChannelType } from "../schema"; import { type IconType } from "react-icons"; +const GENERAL_CHANNEL_ID = import.meta.env.VITE_GENERAL_CHANNEL_ID || "general"; + export const NavLinks = [ { href: "/", @@ -29,7 +31,7 @@ export const NavLinks = [ icon: FaRegCompass, }, { - href: "/chat", + href: `/chat/channels/${GENERAL_CHANNEL_ID}`, label: "Chat", icon: HiOutlineChat, }, diff --git a/src/pages/Chat.tsx b/src/pages/Chat.tsx index 04a3d53..ca3c1a3 100644 --- a/src/pages/Chat.tsx +++ b/src/pages/Chat.tsx @@ -1,13 +1,71 @@ -import { Outlet } from "react-router-dom"; -import Sidebar from "../features/Chat/Sidebar"; +import { useEffect, useState } from "react"; + +import { useParams } from "react-router-dom"; + +import { usersRef } from "../lib/firebase"; +import { getDocIfExists, mapDocToUser } from "../lib/firestore-utils"; +import { useAppSelector } from "../lib/store"; +import { getChannelById } from "../features/Channels/channelsSlice"; +import { selectUser } from "../features/User/userSlice"; + +import type { User } from "../schema"; +import { Sidebar, Header, MessagesContainer, ChatForm } from "../features/Chat"; + +const BlankChannel = () => ( +
+

Welcome to Convoz Chat!

+

Please select a channel from the sidebar

+
+); + +const ChatPage = () => { + const { channelId } = useParams(); + + const [creator, setCreator] = useState(null); + const currentUser = useAppSelector(selectUser); + const channel = useAppSelector((state) => + getChannelById(state, channelId ?? ""), + ); + + useEffect(() => { + if (!channel) return; + getDocIfExists(usersRef, channel.createdBy) + .then(({ data }) => { + if (data) { + setCreator(mapDocToUser(data)); + } + }) + .catch((err) => { + console.error(err); + }); + }, [channel]); + + const editable = currentUser?.id === channel?.createdBy; -const Chat = () => { return (
- + {channel ? ( +
+
+ {/* Messages View --------------------------------------------------------------------------------------- */} +
+ +
+ {/* Chat Form --------------------------------------------------------------------------------------- */} +
+ +
+
+ ) : ( + + )}
); }; -export default Chat; +export default ChatPage; diff --git a/src/pages/index.ts b/src/pages/index.ts index c400177..919c1b9 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,8 +1,6 @@ -import Channels from './Channels'; -import Chat from './Chat'; -import Explore from './Explore'; -import LandingPage from './Landing'; -import Profile from './Profile'; -import Settings from './Settings'; - -export { LandingPage, Chat, Explore, Profile, Channels, Settings }; +export { default as Channels } from "./Channels"; +export { default as ChatPage } from "./Chat"; +export { default as Explore } from "./Explore"; +export { default as LandingPage } from "./Landing"; +export { default as Profile } from "./Profile"; +export { default as Settings } from "./Settings";