Commit da4148ab authored by Balasuriya D.A.M.'s avatar Balasuriya D.A.M.

Create Group Chat Modal Part 1

parent a70b4c26
......@@ -5,7 +5,7 @@ const generateToken = (id) => {
//add jwt secret from .env
return jwt.sign({ id }, process.env.JWT_SECRET, {
//can change the count of days as i want (secret days)
expiresIn: "30d",
expiresIn: "200d",
});
};
......
......@@ -173,3 +173,7 @@ const removeFromGroup = asyncHandler(async (req, res) => {
});
module.exports = { accessChat, fetchChats, createGroupChat, renameGroup, addToGroup, removeFromGroup };
......@@ -25,7 +25,9 @@ const registerUser = asyncHandler(async (req, res) => {
password,
pic,
});
//after register..also want send JWT token
//after register..also want send JWT token
if (user) {
res.status(201).json({
_id: user._id,
......@@ -63,11 +65,13 @@ const authUser = asyncHandler(async (req, res) => {
}
});
// /api/user?search = minosh - goint to creat search query
//This is how to access the query (search query)
const allUsers = asyncHandler(async (req, res) => {
const keyword = req.query.search
? {
//use $or operation
$or: [
//references from MongoDB pages.can get more information from that about $regex
......@@ -87,3 +91,4 @@ const allUsers = asyncHandler(async (req, res) => {
});
module.exports = { registerUser, authUser,allUsers };
......@@ -107,5 +107,4 @@ const chats = [
},
];
module.exports = { chats };
\ No newline at end of file
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true,unique:true },
......@@ -8,6 +9,7 @@ const userSchema = mongoose.Schema({
pic: {
type: String,
//required: true, if it is required i can use this reuired as true
default:
"https://icon-library.com/images/anonymous-avatar-icon/anonymous-avatar-icon-25.jpg",
},
......@@ -31,6 +33,7 @@ userSchema.pre("save", async function (next) {
this.password = await bcrypt.hash(this.password, salt); //add Hash and bcrypt
});
const User = mongoose.model("User", userSchema);
module.exports = User;
......@@ -3,18 +3,16 @@ const { accessChat, fetchChats, createGroupChat, renameGroup, addToGroup, remove
const { protect } = require("../middleware/authMiddleware");
//create router object
const router = express.Router();
//only login user can access this route.
router.route("/").post(protect, accessChat); //API route for one on one chat
router.route("/").post(protect, accessChat);
router.route("/").get(protect, fetchChats);
router.route("/group").post(protect, createGroupChat); //create group
router.route("/rename").put(protect, renameGroup); //update group
router.route("/groupadd").put(protect, addToGroup); //add someone to group
router.route("/groupremove").put(protect, removeFromGroup); //remove from group
module.exports = router;
......@@ -3,12 +3,10 @@ const { registerUser, authUser, allUsers } = require("../controllers/userControl
const { protect } = require("../middleware/authMiddleware");
const router = express.Router();
router.route("/").post(registerUser).get(protect,allUsers); //User searching API end point
router.post("/login", authUser);
router.post("/login", authUser);
module.exports = router;
const express = require("express");
const dotenv = require("dotenv");
const { chats } = require("./data/data");
......@@ -6,22 +5,22 @@ const connectDB = require("./config/db");
const colors = require("colors");
const userRoutes = require("./routes/userRoutes");
const chatRoutes = require("./routes/chatRoutes");
const { notFound,errorHandler} = require("./middleware/errorMiddleware");
dotenv.config();
dotenv.config();
connectDB();
const app = express();
app.use(express.json()); //to accept JSON data
app.get("/", (req, res) => {
res.send("API is Running Successfully");
res.send("API is Running Sucessfully");
});
//create API end points
app.use("/api/user", userRoutes);
app.use("/api/chat", chatRoutes);
......@@ -29,6 +28,8 @@ app.use("/api/chat", chatRoutes);
app.use(notFound);
app.use(errorHandler);
const PORT = process.env.PORT || 5000;
app.listen(5000, console.log(`Server Started on PORT ${PORT}`.yellow.bold));
......@@ -4,21 +4,19 @@
"private": true,
"proxy": "http://127.0.0.1:5000",
"dependencies": {
"@chakra-ui/button": "^1.5.10",
"@chakra-ui/icons": "^1.1.7",
"@chakra-ui/layout": "^1.8.0",
"@chakra-ui/react": "^1.8.8",
"@chakra-ui/icons": "^2.0.0",
"@chakra-ui/react": "^2.0.0",
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@testing-library/jest-dom": "^5.16.3",
"@testing-library/react": "^12.1.4",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.2.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^0.26.1",
"framer-motion": "^6.2.10",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^5.3.0",
"react-scripts": "5.0.0",
"axios": "^0.27.2",
"framer-motion": "^4.1.17",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-router-dom": "^5.3.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
......
......@@ -15,11 +15,14 @@
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet"
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
integrity="sha512-iBBXm8fW90+nuLcSKlbmrPcLa0OT92xO1BIsZ+ywDWZCvqsWgccV3gFoRBv0z+8dLJgyAHIhR35VZc2oM/gI1w=="
crossorigin="anonymous"
referrerpolicy="no-referrer" />
referrerpolicy="no-referrer"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
......
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@300&display=swap');
.App{
.App {
min-height: 100vh;
display: flex;
background-image: url("./background.jpg");
background-size: cover;
background-position: center;
color:black;
color: black;
}
\ No newline at end of file
import "./App.css";
import { Route } from "react-router-dom";
import Homepage from "./Pages/Homepage";
import ChatPage from "./Pages/ChatPage";
function App() {
return (
<div className="App">
<Route path="/" component={Homepage} exact />
<Route path="/chats" component={ChatPage} />
<Route path="/" component={Homepage } exact />
<Route path="/chats" component={ChatPage } />
</div>
);
......
......@@ -22,8 +22,7 @@ const ChatProvider = ({ children }) => {
}, [history]);
return (
<ChatContext.Provider value={{ user, setUser, selectedChat, setSelectedChat, chats, setChats }}
>
<ChatContext.Provider value={{ user, setUser, selectedChat, setSelectedChat, chats, setChats }}>
{children}
</ChatContext.Provider>
);
......
//import React from "react";
import { Box } from "@chakra-ui/react";
import { ChatState } from "../Context/ChatProvider";
import SideDrawer from "../components/miscellaneous/SideDrawer";
import MyChats from "../components/MyChats";
import ChatBox from "../components/ChatBox";
const ChatPage = () => {
const { user } = ChatState();
return (
<div style={{ width: "100%" }}>
{user && <SideDrawer/>}
{user && <SideDrawer />}
<Box
d="flex"
display="flex"
justifyContent="space-between"
w="100%"
h="91.5vh"
p="10px"
width="100%"
height="91.5vh"
padding="10px"
>
{user && <MyChats/>}
{user && <ChatBox/>}
{user && <MyChats />}
{user && <ChatBox />}
</Box>
</div>
);
};
......
......@@ -14,6 +14,8 @@ import Login from "../components/Authentication/Login";
import Signup from "../components/Authentication/Signup";
import { useHistory } from "react-router-dom";
const Homepage = () => {
const history = useHistory();
......@@ -21,12 +23,13 @@ const Homepage = () => {
useEffect(() => {
const user = JSON.parse(localStorage.getItem("userInfo"));
//check if user is loged in push back to the chat page
//check if user is loged in
if (user) history.push("/chats");
}, [history]);
return (
//use container from chakra ui and remove div
<Container maxW="xl" centerContent>
......@@ -66,7 +69,9 @@ const Homepage = () => {
</TabPanels>
</Tabs>
</Box>
</Container>
);
};
......
......@@ -9,15 +9,19 @@ import { useToast } from "@chakra-ui/react";
import axios from "axios";
import { useHistory } from "react-router-dom";
const Login = () => {
const [show, setShow] = useState(false);
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [loading, setLoading] = useState(false);
const [show, setShow] = useState(false);
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [loading, setLoading] = useState(false);
const toast = useToast();
const history = useHistory();
const toast = useToast();
const history = useHistory();
const handleClick = () => setShow(!show);
const submitHandler =async() => {
......@@ -77,6 +81,8 @@ const Login = () => {
}
};
return (
//Use vstack to allign verticaly
<VStack spacing="5px" color="black">
......@@ -86,6 +92,7 @@ const Login = () => {
<FormLabel>Email</FormLabel>
<Input
value={email}
placeholder="Enter Your Email"
onChange={(e)=>setEmail(e.target.value)}
......@@ -120,6 +127,7 @@ const Login = () => {
style={{ marginTop: 15 }}
onClick={submitHandler}
isLoading={loading}
>
Login
......@@ -137,6 +145,7 @@ const Login = () => {
</Button>
</VStack>
);
}
......
......@@ -9,9 +9,11 @@ import { useToast } from "@chakra-ui/react";
import axios from "axios";
import { useHistory } from "react-router-dom";
const Signup = () => {
const [show, setShow] = useState(false);
const [show, setShow] = useState(false);
const [name, setName] = useState();
const [email, setEmail] = useState();
const [confirmpassword, setConfirmpassword] = useState();
......@@ -21,6 +23,7 @@ const Signup = () => {
const toast = useToast();
const history = useHistory();
const handleClick = () => setShow(!show);
const postDetails = (pics) => {
......@@ -133,8 +136,12 @@ const Signup = () => {
});
setLoading(false);
}
};
return (
//Use vstack to allign verticaly
<VStack spacing="5px" color="black">
......@@ -220,6 +227,7 @@ const Signup = () => {
</VStack>
);
}
......
import { Stack } from "@chakra-ui/layout";
import { Skeleton } from "@chakra-ui/react";
import { Stack, Skeleton } from "@chakra-ui/react";
import React from "react";
const ChatLoading = () => {
return (
<Stack>
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height="45px" />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
<Skeleton height='40px' />
</Stack>
);
};
export default ChatLoading;
\ No newline at end of file
export default ChatLoading
\ No newline at end of file
import { useToast } from "@chakra-ui/react";
import { AddIcon } from "@chakra-ui/icons";
import { Box, Button, Stack, useToast, Text } from "@chakra-ui/react";
import axios from "axios";
import React, { useEffect, useState } from "react";
import { ChatState } from "../Context/ChatProvider";
import { Box, Stack, Text } from "@chakra-ui/layout";
import { Button } from "@chakra-ui/button";
import { AddIcon } from "@chakra-ui/icons";
import ChatLoading from "./ChatLoading";
import { getSender } from "../config/ChatLogics";
import GroupChatModal from "./miscellaneous/GroupChatModal";
const MyChats = () => {
const [loggedUser, setLoggedUser] = useState();
const { selectedChat, setSelectedChat, user, chats, setChats } = ChatState();
const { user, selectedChat, setSelectedChat, chats, setChats } = ChatState();
const toast = useToast();
......@@ -22,11 +20,13 @@ const MyChats = () => {
const config = {
headers: {
Authorization: `Bearer ${user.token}`,
},
};
const { data } = await axios.get("/api/chat", config);
console.log(data);
setChats(data); //setting all of the chats
setChats(data);
} catch (error) {
toast({
title: "Error Occured!",
......@@ -35,7 +35,6 @@ const MyChats = () => {
duration: 5000,
isClosable: true,
position: "bottom-left",
});
}
};
......@@ -43,11 +42,11 @@ const MyChats = () => {
useEffect(() => {
setLoggedUser(JSON.parse(localStorage.getItem("userInfo")));
fetchChats();
}, []);
},[])
return (
<Box
d={{ base: selectedChat ? "none" : "flex", md: "flex" }}
display={{ base: selectedChat ? "none" : "flex", md: "flex" }}
flexDir="column"
alignItems="center"
p={3}
......@@ -61,23 +60,25 @@ const MyChats = () => {
px={3}
fontSize={{ base: "28px", md: "30px" }}
fontFamily="Work sans"
d="flex"
display="flex"
w="100%"
justifyContent="space-between"
alignItems="center"
>
My Chats
<GroupChatModal>
<Button
d="flex"
display="flex"
fontSize={{ base: "17px", md: "10px", lg: "17px" }}
rightIcon={<AddIcon/>}
>
New Organization Chat
</Button>
</GroupChatModal>
</Box>
<Box
d="flex"
display="flex"
flexDir="column"
p={3}
bg="#F8F8F8"
......@@ -100,20 +101,18 @@ const MyChats = () => {
key={chat._id}
>
<Text>
{!chat.isGroupChat
? getSender(loggedUser, chat.users)
: chat.chatName}
{!chat.isGoupChat ? getSender (loggedUser, chat.users):chat.chatName}
</Text>
</Box>
))}
</Stack>
): (
<ChatLoading/>
)}
</Box>
</Box>
</Box>
);
};
......
import { Box, Text } from "@chakra-ui/layout";
import { Avatar } from "@chakra-ui/react";
import { Avatar, Box, Text } from "@chakra-ui/react";
import React from "react";
import { ChatState } from "../../Context/ChatProvider";
const UserListItem = ({user, handleFunction }) => {
const UserListItem = ({ user, handleFunction }) => {
......@@ -14,11 +12,10 @@ const UserListItem = ({user, handleFunction }) => {
cursor="pointer"
bg="#E8E8E8"
_hover={{
background: "#38BAC",
background: "#38B2AC",
color:"white",
}}
w="100%"
d="flex"
alignItems="center"
color="black"
px={3}
......@@ -32,7 +29,6 @@ const UserListItem = ({user, handleFunction }) => {
cursor="pointer"
name={user.name}
src={user.pic}
/>
<Box>
<Text>{user.name}</Text>
......@@ -47,4 +43,4 @@ const UserListItem = ({user, handleFunction }) => {
);
};
export default UserListItem;
\ No newline at end of file
export default UserListItem
\ No newline at end of file
import React, { useState } from "react";
import { useDisclosure, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Button, useToast, FormControl, Input } from "@chakra-ui/react";
import { ChatState } from "../../Context/ChatProvider";
import axios from "axios";
import UserListItem from "../UserAvatar/UserListItem";
const GroupChatModal = ({ children }) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const [groupChatName, setGroupChatName] = useState();
const [selectedUsers, setSelectedUsers] = useState([]);
const [search, setSearch] = useState("");
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const toast = useToast();
const { user, chats, setChats } = ChatState();
const handleSearch = async(query) => {
setSearch(query);
if(!query){
return;
}
try {
setLoading(true);
const config = {
headers: {
Authorization:`Bearer ${user.token}`,
},
};
const { data } = await axios.get(`/api/user?search=${search}`, config);
console.log(data);
setLoading(false);
setSearchResult(data);
} catch (error) {
toast({
title: "Error Occured!",
description: "Failed to Load the Search Result",
status: "error",
duration: 5000,
isClosable: true,
position:"bottom-left",
});
}
};
const handleSubmit = () => { };
const handleGroup = () => { };
return (
<>
<span onClick={onOpen}>{children}</span>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader
fontSize="35px"
fontFamily="Work sans"
display="flex"
justifyContent="center"
>
Create Organization Chat
</ModalHeader>
<ModalCloseButton />
<ModalBody display="flex" flexDir="column" alignItems="center">
<FormControl>
<Input
placeholder="Organization Name"
marginBottom={3}
onChange={(e) => setGroupChatName(e.target.value)}
/>
</FormControl>
<FormControl>
<Input
placeholder="Add Users eg:Doctors,Mining Engineers,Emergency Units"
marginBottom={1}
onChange={(e) => handleSearch(e.target.value)}
/>
</FormControl>
{/* selected users */}
{loading ? (
<div>loading</div>
) : (
searchResult
?.slice(0, 4)
.map((user) => (
<UserListItem
key={user._id}
user={user}
handleFunction={() => handleGroup(user)}
/>
))
)}
</ModalBody>
<ModalFooter>
<Button colorScheme="blue" onClick={handleSubmit}>
Create Organization
</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default GroupChatModal;
\ No newline at end of file
import { ViewIcon } from "@chakra-ui/icons";
import { useDisclosure } from "@chakra-ui/react";
import React from "react";
import { IconButton } from "@chakra-ui/button";
import { Modal } from "@chakra-ui/react";
import { ModalOverlay } from "@chakra-ui/react";
import { ModalContent } from "@chakra-ui/react";
import { ModalHeader } from "@chakra-ui/react";
import { ModalCloseButton } from "@chakra-ui/react";
import { ModalBody } from "@chakra-ui/react";
import { Button } from "@chakra-ui/button";
import { ModalFooter } from "@chakra-ui/react";
import { Image, Text } from "@chakra-ui/react";
import { ViewIcon } from "@chakra-ui/icons";
import { useDisclosure,IconButton,Modal,ModalOverlay,ModalContent,ModalHeader,ModalCloseButton,ModalBody,Button,ModalFooter,Image, Text } from "@chakra-ui/react";
......
import {
Tooltip,
Menu,
MenuButton,
MenuList,
Avatar,
MenuItem,
MenuDivider,
Drawer,
useDisclosure,
DrawerOverlay,
DrawerContent,
DrawerHeader,
DrawerBody,
Input,
useToast,
Spinner,
} from "@chakra-ui/react";
//import React from "react";
import { Tooltip, Menu, MenuButton, MenuList, Avatar, MenuItem, MenuDivider, Box, Text, Button, Drawer, useDisclosure, DrawerOverlay, DrawerContent, DrawerHeader, DrawerBody, Input, useToast, Spinner } from "@chakra-ui/react";
import React, { useState } from "react";
import { BellIcon, ChevronDownIcon } from "@chakra-ui/icons";
import { Box, Text } from "@chakra-ui/layout";
import { Button } from "@chakra-ui/button";
import { ChatState } from "../../Context/ChatProvider";
import ProfileModal from "./ProfileModal";
import { useHistory } from "react-router-dom";
import axios from "axios";
import ChatLoading from "../ChatLoading";
import axios from "axios";
import UserListItem from "../UserAvatar/UserListItem";
const SideDrawer = () => {
const [search, setSearch] = useState("");
const [searchResult, setSearchResult] = useState([]);
const [loading, setLoading] = useState(false);
const [loadingChat, setLoadingChat] = useState();
const { user, setSelectedChat,chats,setChats } = ChatState();
const { user, setSelectedChat, chats, setChats } = ChatState();
const history = useHistory();
const { isOpen, onOpen, onClose } = useDisclosure();
......@@ -46,7 +30,7 @@ const SideDrawer = () => {
const toast = useToast();
const handleSearch = async () => {
const handleSearch = async() => {
if (!search) {
toast({
title: "Please Enter something in search",
......@@ -59,7 +43,7 @@ const SideDrawer = () => {
}
try {
setLoading(true)
setLoading(true);
const config = {
headers: {
......@@ -83,19 +67,20 @@ const SideDrawer = () => {
}
};
const accessChat = async (userId) => {
const accessChat = async(userId) => {
try {
setLoadingChat(true);
const config = {
headers: {
"Content-type": "application/json",
"Content-type":"application/json",
Authorization: `Bearer ${user.token}`,
},
};
const { data } = await axios.post("/api/chat", { userId }, config);
if (!chats.find((c) => c._id === data._id)) setChats([data, ...chats]); //normal find function in java scripts
//normal find function in java script
if (!chats.find((c) => c._id === data._id)) setChats([data, ...chats]);
setSelectedChat(data);
setLoadingChat(false);
......@@ -105,30 +90,26 @@ const SideDrawer = () => {
title: "Error fetching the chat",
description: error.message,
status: "error",
duration: 5000,
duration: "5000",
isClosable: true,
position: "bottom-left",
});
}
};
return (
<>
<Box
d="flex"
display="flex"
justifyContent="space-between"
alignItems="center"
bg="white"
w="100%"
p="5px 10px 5px 10px"
bgColor="white"
width="100%"
padding="5px 10px 5px 10px"
borderWidth="5px"
>
<Tooltip
label="Search User to chat"
hasArrow
placement="bottom-end"
>
<Tooltip label="Search User to chat" hasArrow placement="bottom-end">
<Button variant="ghost" onClick={onOpen}>
<i class="fas fa-search"></i>
<Text d={{base:"none",md:"flex"}} px="4">
......@@ -175,18 +156,16 @@ const SideDrawer = () => {
<DrawerContent>
<DrawerHeader borderBottomWidth="1px">Search User</DrawerHeader>
<DrawerBody>
<Box d="flex" pb={2}>
<Box display="flex" paddingBottom={2}>
<Input
placeholder="Search by name or email"
mr={2}
margin={2}
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<Button
onClick={handleSearch}
>
Go
</Button>
>Go</Button>
</Box>
{loading ? <ChatLoading /> :
(
......@@ -199,12 +178,13 @@ const SideDrawer = () => {
))
)
}
{loadingChat && <Spinner ml="auto" d="flex" />}
{loadingChat && <Spinner ml="auto" display="flex" />}
</DrawerBody>
</DrawerContent>
</Drawer>
</>
);
};
......
export const getSender = (loggedUser, users) => {
//if it is not a organization chat nelow logic will helpfull
return users[0]._id === loggedUser._id ? users[1].name : users[0].name;
};
\ No newline at end of file
......@@ -7,15 +7,19 @@ import { BrowserRouter } from "react-router-dom";
import ChatProvider from "./Context/ChatProvider";
ReactDOM.render(
<ChatProvider>
<BrowserRouter>
<ChakraProvider>
<App />
</ChakraProvider>
</BrowserRouter>
</ChatProvider>,
document.getElementById("root")
//rendering everything on root tag
);
{
"name": "tele-medicine",
"name": "miner-doc",
"version": "1.0.0",
"description": "",
"main": "server.js",
......@@ -9,8 +9,13 @@
"author": "Minosh Balasuriya",
"license": "ISC",
"dependencies": {
"dotenv": "^16.0.0",
"express": "^4.17.3",
"nodemon": "^2.0.15"
"bcryptjs": "^2.4.3",
"colors": "^1.4.0",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-async-handler": "^1.2.0",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.3.3",
"nodemon": "^2.0.16"
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment