diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/front.iml b/.idea/front.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/front.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a3b5ecf --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/Dashboard/Chat/page.tsx b/app/Dashboard/Chat/page.tsx index 56e2f1a..a5102f9 100644 --- a/app/Dashboard/Chat/page.tsx +++ b/app/Dashboard/Chat/page.tsx @@ -1,78 +1,72 @@ "use client"; -import { Image } from "next/image"; import { motion } from "framer-motion"; import { ChatCard } from "@/app/components/Dashboard/Chat/ChatCard"; -import MiddleBuble from "@/app/components/Dashboard/Chat/LeftBubbles/MiddleBuble"; import { AnimatePresence } from "framer-motion"; import { Inter } from "next/font/google"; -import { LuPhone } from "react-icons/lu"; -import { IoVideocamOutline } from "react-icons/io5"; import { IoIosInformationCircleOutline } from "react-icons/io"; import { CiCirclePlus } from "react-icons/ci"; -import { MdOutlineAddPhotoAlternate } from "react-icons/md"; -import { IoCameraOutline } from "react-icons/io5"; -import { PiMicrophoneLight } from "react-icons/pi"; +import { CgAdd } from "react-icons/cg"; import { IoSendOutline } from "react-icons/io5"; import { FormEvent, - FormEventHandler, useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react"; -import axios from "axios"; +import axios, { Axios, AxiosError } from "axios"; import { useGlobalState } from "@/app/components/Sign/GlobalState"; -import { channel, target, user } from "./type"; -import MiddleBubbleRight from "@/app/components/Dashboard/Chat/RightBubbles/MiddleBubbleRight"; +import { channel, participants, user, message } from "./type"; import { set, useForm } from "react-hook-form"; -import { sendError } from "next/dist/server/api-utils"; import JoinChannel from "@/app/components/Dashboard/Chat/JoinChannel"; import { useSwipeable } from "react-swipeable"; - +import toast from "react-hot-toast"; +import { OnlinePreview } from "@/app/components/Dashboard/Chat/onlinePreview"; +import ChannelManagement from "@/app/components/Dashboard/Chat/channelManagement"; +import ChatComponent from "@/app/components/Dashboard/Chat/ChatComponent"; +import Image from "next/image"; +import CreateChannel from "@/app/components/Dashboard/Chat/createChannel"; +import { useSearchParams, useRouter } from "next/navigation"; +import { Search } from "lucide-react"; const inter = Inter({ subsets: ["latin"] }); -export type message = { - id?: number; - channel_id?: number; - sender_id?: number; - sender_picture?: string; - conversation_id?: number; - content: string; - content_type: string; - createdAt: Date; -}; - const Page = () => { + // const chatQuery = + // const url = "http://localhost:8080/chat/1"; + // const router = useRouter(); // for later improvement when i want to add a chat id to the url + const searchParam = useSearchParams(); + const { register } = useForm(); const inputMessage = useRef(null); + const [participants, setParticipants] = useState([]); const [showMessage, setShowMessage] = useState(false); const containerRef = useRef(null); const [online, setOnline] = useState(false); - const { register } = useForm(); const [update, setUpdate] = useState(false); const [chatList, setChatList] = useState([]); - const [targetUser, setTargetUser] = useState(); - const [targetChannel, setTargetChannel] = useState(); + const [targetUser, setTargetUser] = useState(null); + const [targetChannel, setTargetChannel] = useState(null); const globalState = useGlobalState(); const [messages, setMessages] = useState(null); const [modlar, setModlar] = useState(false); + const [createModlar, setCreateModlar] = useState(false); const [channelManagement, setChannelManagement] = useState(false); + // ill be addin a loading screen in the chat card while waiting for everything to update properly, and i will have to use pagination when getting that chat list from the server + //test , remove the push method when creating a message in the server and see if the messages gets pushed automatically in the backed + useEffect(() => { - if (globalState.state.user) { + if (globalState?.state?.user) { + // if (searchParam.get("id")) { + + // } axios - .get(`http://localhost:8080/chat/chatlist/${globalState.state.user.id}`) + .get( + `http://localhost:8080/chat/chatlist/${globalState?.state?.user?.id}` + ) .then((res) => { setChatList(res.data); }) - .catch((error) => { - console.log(error); - }); - if (targetUser) { - axios.get(`http://localhost:8080/user/${targetUser.id}`).then((res) => { - setTargetUser(res.data); - }); - } + .catch((error) => {}); } }, [globalState, update]); /////////////////////////////////////////////////////////// @@ -80,6 +74,7 @@ const Page = () => { const handleEscapeKeyPress = useCallback((e: any) => { if (e.key === "Escape") { setModlar(false); + setCreateModlar(false); } }, []); @@ -107,7 +102,7 @@ const Page = () => { setUpdate(true); }); return () => { - globalState?.state?.socket?.off("ok"); + globalState?.state?.socket?.off("update"); }; }, [globalState?.state?.socket]); @@ -118,39 +113,18 @@ const Page = () => { } }, [messages]); - useEffect(() => { - globalState?.state?.socket?.on("message", (data: any) => {}); - }, [globalState?.state?.socket]); - - // useEffect(() => { - // if (socket.current) { - // socket.current.on("message", (data: any) => { - // if (targetUser) { - // if (data.sender_id === targetUser.id) { - // setUpdate(true); - // } - // } - // if (targetChannel) { - // if (data.channel_id === targetChannel.id) { - // setUpdate(true); - // } - // } - // }); - // } - // return () => { - // if (socket.current) { - // socket.current.off("message"); - // } - // }; - // }, [socket.current]); - useEffect(() => { if (targetUser) { if (update) { - const data = fetchMessagesForUser(targetUser.id); - data.then((res) => { - setMessages(res); - }); + axios + .get(`http://localhost:8080/user/${targetUser.id}`) + .then((res) => { + fetchMessagesForUser(targetUser.id).then((res) => { + setMessages(res); + }); + setTargetUser(res.data); + }) + .catch((error) => {}); } } return () => { @@ -158,18 +132,45 @@ const Page = () => { }; }, [targetUser, update]); + // is updated to use the data i get from the chat list when including the other data in it useEffect(() => { - if (targetChannel) { - if (update) { - const data = fetchMessagesForChannel(targetChannel.id); - data.then((res) => { - setMessages(res); - }); + if (update) { + if (targetChannel) { + if (update) { + console.log("fetching messages for channel"); + const data = fetchMessagesForChannel(targetChannel.id); + data + .then((res) => { + setMessages(res); + const part = fetchChannelParticipants(targetChannel.id); + part.then((res) => { + setParticipants(res); + }); + }) + .catch((error: AxiosError) => {}); + } setUpdate(false); } } }, [targetChannel, update]); + const fetchChannelParticipants = async ( + id: number | undefined + ): Promise => { + const data = await axios + .get( + `http://localhost:8080/channels/participants/${id}?uid=${globalState.state.user.id}` + ) + .then((res) => { + if (res.status === 200) { + return res.data; + } + }) + .catch((error: AxiosError) => {}); + + return data; + }; + const fetchMessagesForUser = async ( id: number | undefined ): Promise => { @@ -178,11 +179,9 @@ const Page = () => { `http://localhost:8080/conversations?uid1=${id}&uid2=${globalState.state.user.id}` ) .then((res) => { - return res.data.messages; + if (res.status === 200) return res.data.messages; }) - .catch((error) => { - console.log(error); - }); + .catch((error: AxiosError) => {}); return data; }; @@ -196,8 +195,8 @@ const Page = () => { .then((res) => { return res.data; }) - .catch((error) => { - console.log(error); + .catch((error: AxiosError) => { + toast.error(`failed to fetch messages for ${targetChannel!.name}`); }); return data; }; @@ -217,15 +216,15 @@ const Page = () => { }; await axios .post(`http://localhost:8080/message`, message) + .then((res) => {}) .catch((error) => { - console.log(error); + toast.error("failed to send message"); }); globalState?.state?.socket?.emit("channelmessage", { - channel: targetChannel.id, - sender: globalState.state.user.id, + roomName: targetChannel.name, + user: globalState?.state?.user, }); - } - if (targetUser) { + } else if (targetUser) { await axios .post(`http://localhost:8080/message`, { message: { @@ -237,18 +236,20 @@ const Page = () => { user2: globalState.state.user.id, user1: targetUser.id, }) + .then((res) => {}) .catch((error) => { - console.log(error); + toast.error("failed to send message"); }); globalState?.state?.socket?.emit("dmmessage", { reciever: targetUser.id, sender: globalState.state.user.id, }); + setUpdate(true); } inputMessage.current!.value = ""; - setUpdate(true); return (e: FormEvent) => {}; }; + const handleClick = () => { setModlar(false); }; @@ -272,13 +273,23 @@ const Page = () => { const handlers = useSwipeable({ onSwipedLeft: () => setShowMessage(true), onSwipedRight: () => setShowMessage(false), - // onSwiped:()=>setExpanded(!expanded), }); - console.log(showMessage); return (
- {modlar && } + {modlar ? ( + + ) : createModlar ? ( + setCreateModlar(false)} + user={globalState.state.user} + socket={globalState.state.socket} + /> + ) : null}
{ {
-

- Join a{" "} - setModlar(true)} - className="text-sky-500 cursor-pointer" - > - Public - {" "} - Group Chat -

+
+

+ Join a{" "} + setModlar(true)} + className="text-sky-500 cursor-pointer" + > + Public + {" "} + Group Chat +

+
+ setCreateModlar(true)}> + create svg + +
+
{ @@ -365,40 +388,36 @@ const Page = () => { })}
- {/** here we display the messages and stuff, gonna do it after properly fetching data */} {targetChannel || targetUser ? (
- {/* this image needs to be filled with the target user */} -

{targetUser ? targetUser.name : targetChannel!.name}

- {/** needs to be fixed */} - {targetUser && - (targetUser.status === "ONLINE" ? ( -

Online

- ) : ( -

Offline

- ))} + {targetUser && ( + + )}
setChannelManagement(!channelManagement)} + onClick={(e) => { + e.preventDefault(); + setChannelManagement(!channelManagement); + }} >
@@ -408,68 +427,30 @@ const Page = () => { className=" p-4 flex-1 overflow-y-scroll no-scrollbar " ref={containerRef} > - {!channelManagement ? ( -
-
-
- {messages && - messages.map((value, key: any) => { - if ( - value.sender_id === globalState.state.user.id - ) { - return ( -
- -
- ); - } else { - return ( - - ); - } - })} -
-
-

- {messages && messages.length > 0 - ? messages[messages.length - 1].createdAt - .toString() - .substring(0, 10) + - " at " + - messages[messages.length - 1].createdAt - .toString() - .substring(11, 16) - : "No messages yet"} -

-
+ {targetUser ? ( + + ) : !channelManagement ? ( + ) : ( -
-
- image -
-
-
+ )}
diff --git a/app/Dashboard/Chat/type.tsx b/app/Dashboard/Chat/type.tsx index 6231969..93f6810 100644 --- a/app/Dashboard/Chat/type.tsx +++ b/app/Dashboard/Chat/type.tsx @@ -1,18 +1,45 @@ +export type banned = { + id: number; + user_id: number; + channel_id: number; + joinedAt: Date; +} + export type message = { id?: number; - message: string; - sender: string; - time: Date; + channel_id?: number; + sender_id?: number; + sender_picture?: string; + conversation_id?: number; + content: string; + content_type: string; + createdAt: Date; +}; +export type participants = { + id: number; + user_id: number; + channel_id: number; + role: string; + mute: boolean; }; export type channel = { + key: string; + state: string; id?: number; + topic: string; name: string; + picture: string; messages: message[]; + participants: participants[]; + ban: banned[]; + createdAt: Date; }; export type conversation = { id?: number; + user_a_id: number; + user_b_id: number; messages: message[]; }; @@ -26,7 +53,7 @@ export type user = { id: number; googleId: string; fortytwoId: number; - username: string; + nickname: string; name: string; password: string; picture: string; @@ -36,4 +63,5 @@ export type user = { createdAt: Date; twoFa: boolean; twoFaSecret: string; + conversations: conversation; }; diff --git a/app/Dashboard/Profile/page.tsx b/app/Dashboard/Profile/page.tsx index 279b166..687a910 100644 --- a/app/Dashboard/Profile/page.tsx +++ b/app/Dashboard/Profile/page.tsx @@ -4,15 +4,20 @@ import { useSearchParams } from 'next/navigation' import UserProfile from '@/app/components/Dashboard/Profile/UserProfile'; import axios from 'axios'; import { useGlobalState } from '@/app/components/Sign/GlobalState'; +import { useRouter } from 'next/navigation'; const Profile = () => { const searchParams = useSearchParams() const [targetUser, setTargetUser] = useState(null) const [is , setIs] = useState(false) const {state} = useGlobalState(); - const socket : any= state.socket; + const router = useRouter(); + const {socket , user}= state; const id = searchParams.get('id') + + if (user && id == user?.id) + router.push('/Dashboard') useEffect(() => { socket?.on('ok', () => { setIs((prev) => !prev); }) @@ -27,13 +32,22 @@ const Profile = () => { } , [id, is]) return ( -
-
-
- {targetUser && } -
-
-
+ <> + { + (user && id != user?.id ) ? +
+
+
+ {targetUser && } +
+
+
+ : +
+
+
+ } + ); }; diff --git a/app/Dashboard/layout.tsx b/app/Dashboard/layout.tsx index 7c397e6..10ba603 100644 --- a/app/Dashboard/layout.tsx +++ b/app/Dashboard/layout.tsx @@ -1,9 +1,19 @@ import type { Metadata } from "next"; import { Inter } from "next/font/google"; import ContentWrapper from "./ContentWrapper"; +import { Toaster } from "react-hot-toast"; +import { + useQuery, + useMutation, + useQueryClient, + QueryClient, + QueryClientProvider, +} from "react-query"; const inter = Inter({ subsets: ["latin"] }); +// const queryClient = new QueryClient(); + export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", @@ -15,12 +25,15 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( -
+
+ {/* */} {children} + -
+ {/* */} +
); } diff --git a/app/components/Dashboard/Chat/ChatCard.tsx b/app/components/Dashboard/Chat/ChatCard.tsx index db83f15..7c87544 100644 --- a/app/components/Dashboard/Chat/ChatCard.tsx +++ b/app/components/Dashboard/Chat/ChatCard.tsx @@ -2,8 +2,9 @@ import React from "react"; import { motion } from "framer-motion"; import { useEffect, useState } from "react"; import axios from "axios"; -import { message } from "@/app/Dashboard/Chat/page"; +import { message } from "@/app/Dashboard/Chat/type"; import { getTime } from "@/app/utils"; +import toast from "react-hot-toast"; export const ChatCard = (props: any) => { const [msg, setMessage] = useState(); @@ -15,7 +16,8 @@ export const ChatCard = (props: any) => { ) .then((res) => { setMessage(res.data.messages); - }); + }) + .catch((err) => {}); } else { axios .get( @@ -23,14 +25,20 @@ export const ChatCard = (props: any) => { ) .then((res) => { setMessage(res.data); - }); + }) + .catch((err) => {}); } }, [props.value.id, props.self.id, props.value.user, props.update]); + if (!msg) { + return
Loading...
; + } + return ( { + e.preventDefault(); if (props.value.user === false) { props.setTargetChannel(props.value); props.setTargetUser(null); @@ -40,24 +48,19 @@ export const ChatCard = (props: any) => { } props.setUpdate(true); props.handleClick(); - e.preventDefault(); }} initial={{ opacity: 0, y: -20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.25 * props.index }} > -
+
User2
- {/*here we check the status of the user if he is online or not */} {props.value.user && (props.value.status === "ONLINE" ? (
@@ -71,8 +74,14 @@ export const ChatCard = (props: any) => {
-

- {msg && msg.length > 0 && msg[msg.length - 1]?.content} +

+ {msg && + msg.length > 0 && + msg[msg.length - 1]?.content.length >= 10 + ? msg[msg.length - 1]?.content.slice(0, 10) + "..." + : msg && msg.length > 0 + ? msg[msg.length - 1]?.content + : null}

@@ -84,7 +93,7 @@ export const ChatCard = (props: any) => { {msg && msg.length > 0 && getTime(msg[msg.length - 1]?.createdAt)}

- Feb 1 + Feb 1{/** this should change to get the date only */}

diff --git a/app/components/Dashboard/Chat/ChatComponent.tsx b/app/components/Dashboard/Chat/ChatComponent.tsx new file mode 100644 index 0000000..868d9bb --- /dev/null +++ b/app/components/Dashboard/Chat/ChatComponent.tsx @@ -0,0 +1,54 @@ +import { message } from '@/app/Dashboard/Chat/type'; +import React from 'react' +import MiddleBubbleRight from './RightBubbles/MiddleBubbleRight'; +import MiddleBuble from './LeftBubbles/MiddleBuble'; + +const ChatComponent = ({ + handlers, + messages, + globalStateUserId, +}: { + handlers: any; + messages: message[]; + globalStateUserId: number; +}) => { + return ( +
+
+
+ {messages && + messages.map((value, key: any) => { + if (value.sender_id === globalStateUserId) { + return ; + } else { + return ( + + ); + } + })} +
+
+

+ {messages && messages.length > 0 + ? messages[messages.length - 1].createdAt + .toString() + .substring(0, 10) + + " at " + + messages[messages.length - 1].createdAt.toString().substring(11, 16) + : "No messages yet"} +

+
+ ); +}; + +export default ChatComponent \ No newline at end of file diff --git a/app/components/Dashboard/Chat/JoinChannel.tsx b/app/components/Dashboard/Chat/JoinChannel.tsx index 4730451..1ec6e55 100644 --- a/app/components/Dashboard/Chat/JoinChannel.tsx +++ b/app/components/Dashboard/Chat/JoinChannel.tsx @@ -1,9 +1,12 @@ -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Inter } from "next/font/google"; import { motion } from "framer-motion"; import JoinChannelBubble from "./JoinChannelBubble"; import { IoIosSearch } from "react-icons/io"; import { AiOutlineClose } from "react-icons/ai"; +import axios from "axios"; +import { user } from "@/app/Dashboard/Chat/type"; +import { useForm } from "react-hook-form"; const inter = Inter({ subsets: ["latin"] }); const modalVariants = { @@ -25,9 +28,44 @@ const modalVariants = { }, }; -const JoinChannel = ({ handleClick }: { handleClick: () => void }) => { +const JoinChannel = ({ + handleClick, + user, + socket, +}: { + handleClick: () => void; + user: user; + socket: any; +}) => { + const [channels, setChannels] = useState([]); + const [filteredChannels, setFilteredChannels] = useState([]); + const search = useRef(null); + const { register } = useForm(); + useEffect(() => { + const fetchChannels = async () => { + const res = await axios + .get("http://localhost:8080/channels") + .then((data) => { + setChannels(data.data); + setFilteredChannels(data.data); + }); + }; + fetchChannels(); + }, []); + + const filterBySearch = (e: React.ChangeEvent, value: string | null) => { + const res = channels.filter((channel) => { + return channel.name + .toLowerCase() + .includes(value?.toLowerCase() as string); + }); + setFilteredChannels(res); + }; + return ( -