diff --git a/.gitignore b/.gitignore
index fc9158a..34a7f16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,7 +29,6 @@ frontend/node_modules/
# testing
/coverage
-
# next.js
frontend/.next/
/out/
diff --git a/frontend/components/chat/chatContainer.jsx b/frontend/components/chat/chatContainer.jsx
deleted file mode 100644
index 11137d5..0000000
--- a/frontend/components/chat/chatContainer.jsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import React from "react";
-import Image from "next/image";
-import moment from "moment";
-import { useState } from "react";
-import Avatar1 from "../../assets/avatars/avatar_1.svg";
-import Avatar2 from "../../assets/avatars/avatar_2.svg";
-import Avatar3 from "../../assets/avatars/avatar_3.svg";
-import Avatar4 from "../../assets/avatars/avatar_4.svg";
-import Avatar5 from "../../assets/avatars/avatar_5.svg";
-import Avatar6 from "../../assets/avatars/avatar_6.svg";
-import Avatar7 from "../../assets/avatars/avatar_7.svg";
-import Avatar8 from "../../assets/avatars/avatar_8.svg";
-import Avatar9 from "../../assets/avatars/avatar_9.svg";
-import Avatar10 from "../../assets/avatars/avatar_10.svg";
-import Avatar11 from "../../assets/avatars/avatar_11.svg";
-import Avatar12 from "../../assets/avatars/avatar_12.svg";
-import Avatar13 from "../../assets/avatars/avatar_13.svg";
-import Avatar14 from "../../assets/avatars/avatar_14.svg";
-import Avatar15 from "../../assets/avatars/avatar_15.svg";
-import getAvatar from "../../utils/session/getAvatar";
-
-export default function ChatContainer({ messages, messagesEndRef }) {
- const formatTime = (timestamp) => {
- const date = new Date(timestamp * 1000);
- return moment(date).format("hh:mm A");
- };
-
- const AvatarList = [
- Avatar1,
- Avatar2,
- Avatar3,
- Avatar4,
- Avatar5,
- Avatar6,
- Avatar7,
- Avatar8,
- Avatar9,
- Avatar10,
- Avatar11,
- Avatar12,
- Avatar13,
- Avatar14,
- Avatar15,
- ];
- const [Avatar, setAvatar] = useState(Avatar1);
-
- React.useEffect(() => {
- if (messagesEndRef.current) {
- messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
- }
- async function loadAvatar() {
- const AvatarId = await getAvatar();
- const Avatar = AvatarList[AvatarId];
- setAvatar(Avatar);
- }
- loadAvatar();
- }, [messages]);
-
- return (
-
-
- {messages?.map((message, index) => (
-
-
-
-
-
-
-
-
- {message.username}
-
-
-
-
-
- {formatTime(message.timestamp)}
-
-
-
-
-
- ))}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/frontend/components/chat/chatContainer.tsx b/frontend/components/chat/chatContainer.tsx
new file mode 100644
index 0000000..bf76cee
--- /dev/null
+++ b/frontend/components/chat/chatContainer.tsx
@@ -0,0 +1,115 @@
+import React, { useState, useEffect } from "react";
+import Echofy from "../../assets/logo.svg";
+import Image from "next/image";
+import Avatar1 from "../../assets/avatars/avatar_1.svg";
+import Avatar2 from "../../assets/avatars/avatar_2.svg";
+import Avatar3 from "../../assets/avatars/avatar_3.svg";
+import Avatar4 from "../../assets/avatars/avatar_4.svg";``
+import Avatar5 from "../../assets/avatars/avatar_5.svg";
+import Avatar6 from "../../assets/avatars/avatar_6.svg";
+import Avatar7 from "../../assets/avatars/avatar_7.svg";
+import Avatar8 from "../../assets/avatars/avatar_8.svg";
+import Avatar9 from "../../assets/avatars/avatar_9.svg";
+import Avatar10 from "../../assets/avatars/avatar_10.svg";
+import Avatar11 from "../../assets/avatars/avatar_11.svg";
+import Avatar12 from "../../assets/avatars/avatar_12.svg";
+import Avatar13 from "../../assets/avatars/avatar_13.svg";
+import Avatar14 from "../../assets/avatars/avatar_14.svg";
+import Avatar15 from "../../assets/avatars/avatar_15.svg";
+import parseMessageText from "../../utils/chatbot_formatting/parseMessageText";
+import getAvatar from "../../utils/session/getAvatar";
+import { ChatContainerProps, Message } from "../../interface/interface";
+
+export default function ChatContainer({ messages, messagesEndRef }: ChatContainerProps): React.JSX.Element {
+ const [filteredMessage, setFilteredMessage] = useState([]);
+
+ const AvatarList = [
+ Avatar1,
+ Avatar2,
+ Avatar3,
+ Avatar4,
+ Avatar5,
+ Avatar6,
+ Avatar7,
+ Avatar8,
+ Avatar9,
+ Avatar10,
+ Avatar11,
+ Avatar12,
+ Avatar13,
+ Avatar14,
+ Avatar15,
+ ];
+ const [Avatar, setAvatar] = useState(Avatar1.src);
+
+ useEffect(() => {
+ if (messagesEndRef.current) {
+ messagesEndRef.current.scrollIntoView({ behavior: "smooth" });
+ }
+
+ async function loadAvatar() {
+ const AvatarId = await getAvatar();
+ const Avatar = AvatarList[AvatarId];
+ setAvatar(Avatar.src);
+ }
+ loadAvatar();
+ }, [messages]);
+
+ useEffect(() => {
+ const filterMessages = () => {
+ const newMessages = messages.filter((message) => {
+ // message is already an object, no need to parse it
+ return !(message.userID && message.userID.startsWith("chatbot"));
+ });
+ return newMessages;
+ };
+ const newFilteredMessages = filterMessages();
+ setFilteredMessage(newFilteredMessages as Message[]);
+ }, [messages]);
+
+ return (
+
+
+
+ {filteredMessage?.map((message, index) => {
+ return (
+
+
+
+
+
+
+
+
{message.username}
+
+
+
+
+ {message.isSent ? message.text : parseMessageText(message.text)}
+
+
+
+
+
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/frontend/components/chat/chatInputBox.jsx b/frontend/components/chat/chatInputBox.jsx
deleted file mode 100644
index 6835f88..0000000
--- a/frontend/components/chat/chatInputBox.jsx
+++ /dev/null
@@ -1,75 +0,0 @@
-import { useState, useRef } from "react";
-import sendLogo from "../../assets/send.svg";
-import Image from "next/image";
-export default function ChatInputBox({ socketRef }) {
- const [newMessage, setNewMessage] = useState("");
- const [isTimeout, setIsTimeout] = useState(false);
- const messageTimesRef = useRef([]);
-
- function handleInputChange(event) {
- setNewMessage(event.target.value);
- }
-
- function handleSendClick() {
- if (isTimeout) return;
-
- if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) {
- socketRef.current.send(newMessage);
- setNewMessage("");
- messageTimesRef.current.push(Date.now());
- checkForTimeout();
- } else {
- //todo : add an alert in case of websocket is not connected, redirect user to login screen
- }
- }
-
- function handleKeyPress(event) {
- if (event.key === "Enter") {
- handleSendClick();
- }
- }
-
- const checkForTimeout = () => {
- const now = Date.now();
- messageTimesRef.current = messageTimesRef.current.filter(
- (t) => now - t < 3000,
- );
-
- if (messageTimesRef.current.length >= 3) {
- setIsTimeout(true);
- setTimeout(() => {
- setIsTimeout(false);
- messageTimesRef.current = [];
- }, 5000);
- }
- };
-
- return (
- <>
-
- >
- );
-}
\ No newline at end of file
diff --git a/frontend/components/chat/chatInputBox.tsx b/frontend/components/chat/chatInputBox.tsx
new file mode 100644
index 0000000..bb0c3b6
--- /dev/null
+++ b/frontend/components/chat/chatInputBox.tsx
@@ -0,0 +1,87 @@
+import React, { useState, useRef } from "react";
+import sendLogo from "../../assets/send.svg";
+import Image from "next/image";
+import { ChatInputBoxProps } from "../../interface/interface";
+
+
+const ChatInputBox: React.FC = ({ socketRef }) => {
+ const [newMessage, setNewMessage] = useState("");
+ const [isTimeout, setIsTimeout] = useState(false);
+ const messageTimesRef = useRef([]);
+
+ const handleInputChange = (event: React.ChangeEvent) => {
+ setNewMessage(event.target.value);
+ };
+
+ const handleSendClick = () => {
+ if (isTimeout) return;
+
+ const socket = socketRef.current;
+ if (socket && socket.readyState === WebSocket.OPEN) {
+ socket.send(newMessage);
+ setNewMessage("");
+ messageTimesRef.current.push(Date.now());
+ checkForTimeout();
+ } else {
+ // Handle websocket not connected (e.g., alert, redirect)
+ console.error("WebSocket is not connected.");
+ }
+ };
+
+ const handleKeyPress = (event: React.KeyboardEvent) => {
+ if (event.key === "Enter") {
+ handleSendClick();
+ }
+ };
+
+ const checkForTimeout = () => {
+ const now = Date.now();
+ messageTimesRef.current = messageTimesRef.current.filter(
+ (t) => now - t < 3000
+ );
+
+ if (messageTimesRef.current.length >= 3) {
+ setIsTimeout(true);
+ setTimeout(() => {
+ setIsTimeout(false);
+ messageTimesRef.current = [];
+ }, 5000); // Timeout for 5 seconds
+ }
+ };
+
+ return (
+
+ );
+};
+
+export default ChatInputBox;
diff --git a/frontend/components/chat/loginModal.jsx b/frontend/components/chat/loginModal.tsx
similarity index 58%
rename from frontend/components/chat/loginModal.jsx
rename to frontend/components/chat/loginModal.tsx
index a82c539..283faae 100644
--- a/frontend/components/chat/loginModal.jsx
+++ b/frontend/components/chat/loginModal.tsx
@@ -5,59 +5,76 @@ import getSessionUserId from "../../utils/session/getSessionUserId";
import setSessionUser from "../../utils/session/setSessionUser";
import removeSessionUserId from "../../utils/session/removeSessionUserId";
import checkAndPromptSessionChange from "../../utils/alerts/checkAndPromptSessionChange";
-
-const LoginModal = ({ onClose, redirect }) => {
- const popupRef = useRef();
+import { toast } from "react-toastify";
+import {LoginModalProps} from "../../interface/interface"
+const LoginModal: React.FC = ({ onClose, redirect }) => {
+ const popupRef = useRef(null);
const [username, setUsername] = useState("");
const router = useRouter();
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node))
+ {
+ onClose();
+ }
+ };
+
+ document.addEventListener("mousedown", handleClickOutside);
+ return () => document.removeEventListener("mousedown", handleClickOutside);
+
+ }, [onClose]);
+
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus();
+ }
+ }, []);
- function handleUsernameChange(event) {
+ const handleUsernameChange = (event: React.ChangeEvent) => {
setUsername(event.target.value);
- }
+ };
- function handleEnterClick(event) {
+ const handleEnterClick = (event: React.KeyboardEvent) => {
if (event.key === "Enter") {
handleChatWithUsClick();
}
- }
+ };
- useEffect(() => {
- function handleClickOutside(event) {
- if (popupRef.current && !popupRef.current.contains(event.target)) {
- onClose();
- }
+ const handleChatWithUsClick = async () => {
+ if (!username.trim()) {
+ toast.error("Please enter a username.");
+ return;
}
- document.addEventListener("mousedown", handleClickOutside);
- return () => document.removeEventListener("mousedown", handleClickOutside);
- }, [popupRef, onClose]);
-
- async function handleChatWithUsClick() {
const chatType = redirect;
const currentUser = getSessionUser();
const currentUserId = getSessionUserId();
- const query={channel:chatType};
- if (currentUser && currentUserId) {
- if (currentUser === username) {
- router.push({pathname:'/chat',query});
- } else {
- const hasChanged = await checkAndPromptSessionChange(
- currentUser,
- username,
- () => {
- removeSessionUserId();
- setSessionUser(username);
- },
- );
- if (hasChanged) {
- router.push({pathname:'/chat',query});
- }
- }
+ const queryParams = new URLSearchParams({ channel: chatType });
+
+ if (currentUser && currentUserId) {
+ if (currentUser === username) {
+ router.push(`/chat?${queryParams.toString()}`);
} else {
- setSessionUser(username);
- router.push({pathname:'/chat',query});
+ const hasChanged = await checkAndPromptSessionChange({
+ currentUser: currentUser,
+ username: username,
+ onConfirm: () => {
+ removeSessionUserId();
+ setSessionUser(username);
+ },
+ });
+ if (hasChanged) {
+ router.push(`/chat?${queryParams.toString()}`);
+ }
}
- }
+ } else {
+ setSessionUser(username);
+ router.push(`/chat?${queryParams.toString()}`);
+ }
+ };
+ const closeModal = ()=>{onClose()}
return (
{
/>
CHAT WITH US
diff --git a/frontend/components/chatbot/chatbotContainer.jsx b/frontend/components/chatbot/chatbotContainer.tsx
similarity index 96%
rename from frontend/components/chatbot/chatbotContainer.jsx
rename to frontend/components/chatbot/chatbotContainer.tsx
index 48d2403..1911001 100644
--- a/frontend/components/chatbot/chatbotContainer.jsx
+++ b/frontend/components/chatbot/chatbotContainer.tsx
@@ -18,8 +18,9 @@ import Avatar14 from "../../assets/avatars/avatar_14.svg";
import Avatar15 from "../../assets/avatars/avatar_15.svg";
import parseMessageText from "../../utils/chatbot_formatting/parseMessageText";
import getAvatar from "../../utils/session/getAvatar";
+import { ChatContainerProps } from "../../interface/interface";
-export default function ChatContainer({ messages, messagesEndRef }) {
+export default function ChatContainer({ messages, messagesEndRef }:ChatContainerProps): React.JSX.Element {
const [filteredMessage, setFilteredMessage] = useState([]);
const AvatarList = [
@@ -58,7 +59,7 @@ export default function ChatContainer({ messages, messagesEndRef }) {
const filterMessages = () => {
const newMessages = messages.filter((message) => {
try {
- message = JSON.parse(message);
+ // message = JSON.parse(message);
const messageData = JSON.parse(message.text);
return !(
messageData.userID && messageData.userID.startsWith("chatbot")
diff --git a/frontend/components/chatbot/chatbotLoginModal.jsx b/frontend/components/chatbot/chatbotLoginModal.tsx
similarity index 75%
rename from frontend/components/chatbot/chatbotLoginModal.jsx
rename to frontend/components/chatbot/chatbotLoginModal.tsx
index 9dc3ce4..d4cac22 100644
--- a/frontend/components/chatbot/chatbotLoginModal.jsx
+++ b/frontend/components/chatbot/chatbotLoginModal.tsx
@@ -7,47 +7,59 @@ import removeSessionUserId from "../../utils/session/removeSessionUserId";
import checkAndPromptSessionChange from "../../utils/alerts/checkAndPromptSessionChange";
import { TopicDropdown } from "../topicDropdown";
-const ChatBotLoginModal = ({ onClose }) => {
- const popupRef = useRef();
- const [username, setUsername] = useState("");
- const [topic, setTopic] = useState("SELECT A TOPIC");
+interface ChatBotLoginModalProps {
+ onClose: () => void;
+ redirect;
+}
+
+interface DropdownProps {
+ topic: string;
+ setTopic: React.Dispatch
>;
+ login?: boolean;
+}
+
+const ChatBotLoginModal: React.FC = ({ onClose , redirect}) => {
+ const popupRef = useRef(null);
+ const [username, setUsername] = useState("");
+ const [topic, setTopic] = useState("SELECT A TOPIC");
const router = useRouter();
- function handleUsernameChange(event) {
+ const handleUsernameChange = (event: React.ChangeEvent) => {
setUsername(event.target.value);
- }
+ };
- function handleEnterClick(event) {
+ const handleEnterClick = (event: React.KeyboardEvent) => {
if (event.key === "Enter") {
handleChatWithUsClick();
}
- }
+ };
useEffect(() => {
- function handleClickOutside(event) {
- if (popupRef.current && !popupRef.current.contains(event.target)) {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
onClose();
}
- }
+ };
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
- }, [popupRef, onClose]);
+ }, [onClose]);
- async function handleChatWithUsClick() {
+ const handleChatWithUsClick = async () => {
const currentUser = getSessionUser();
const currentUserId = getSessionUserId();
+
if (currentUser && currentUserId) {
if (currentUser === username) {
router.push(`/chat_bot?topic=${encodeURIComponent(topic)}`);
} else {
- const hasChanged = await checkAndPromptSessionChange(
+ const hasChanged = await checkAndPromptSessionChange({
currentUser,
username,
- () => {
+ onConfirm: () => {
removeSessionUserId();
setSessionUser(username);
},
- );
+ });
if (hasChanged) {
router.push(`/chat_bot?topic=${encodeURIComponent(topic)}`);
}
@@ -56,7 +68,8 @@ const ChatBotLoginModal = ({ onClose }) => {
setSessionUser(username);
router.push(`/chat_bot?topic=${encodeURIComponent(topic)}`);
}
- }
+ };
+
return (
{
onKeyDown={handleEnterClick}
/>
-
+
{
);
};
-export default ChatBotLoginModal;
\ No newline at end of file
+
+export default ChatBotLoginModal;
diff --git a/frontend/components/layout.jsx b/frontend/components/layout.tsx
similarity index 94%
rename from frontend/components/layout.jsx
rename to frontend/components/layout.tsx
index cb04d58..f0734ae 100644
--- a/frontend/components/layout.jsx
+++ b/frontend/components/layout.tsx
@@ -3,11 +3,11 @@ import Image from "next/image";
import styles from "./layout.module.css";
import utilStyles from "../styles/utils.module.css";
import Link from "next/link";
-
+import { LayoutProps } from "../interface/interface";
const name = "Choose the Chat Mode";
export const siteTitle = "Jinora";
-export default function Layout({ children, home }) {
+export default function Layout({ children, home }:LayoutProps) {
return (
diff --git a/frontend/components/mail.jsx b/frontend/components/mail.tsx
similarity index 79%
rename from frontend/components/mail.jsx
rename to frontend/components/mail.tsx
index 7eff024..020138a 100644
--- a/frontend/components/mail.jsx
+++ b/frontend/components/mail.tsx
@@ -1,32 +1,36 @@
-import React from "react";
+import React, { useEffect, useRef } from "react";
import { useState } from "react";
import subscribe from "../services/api/subscribeApi";
import getSessionUser from "../utils/session/getSessionUser";
import getSessionUserId from "../utils/session/getSessionUserId";
+import { MailProps } from "../interface/interface";
+
+
export default function Mail({
isOpen,
onClose,
channel,
-}) {
- const [email, setEmail] = useState("");
- const popupRef = React.useRef();
+} : MailProps) {
+ const [email, setEmail] = useState
("");
+ const popupRef = useRef(null);
- const handleSubmit = async (e) => {
+ const handleSubmit = async (e: React.FormEvent) => {
let userId = getSessionUserId();
let username = getSessionUser();
+ let timestamp = Date.now();
e.preventDefault();
try {
- await subscribe(email, username, userId, channel, userId);
+ await subscribe({email, username, userId, channel, timestamp});
onClose();
} catch (error) {
//todo -> enable sentry logger here
}
};
- React.useEffect(() => {
- function handleClickOutside(event) {
- if (popupRef.current && !popupRef.current.contains(event.target)) {
+ useEffect(() => {
+ function handleClickOutside(event:MouseEvent) {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
onClose();
}
}
diff --git a/frontend/components/mdgBox.jsx b/frontend/components/mdgBox.tsx
similarity index 96%
rename from frontend/components/mdgBox.jsx
rename to frontend/components/mdgBox.tsx
index ddec593..6822978 100644
--- a/frontend/components/mdgBox.jsx
+++ b/frontend/components/mdgBox.tsx
@@ -4,8 +4,11 @@ import { useEffect } from "react";
import { useRouter } from "next/router";
import { fetchProjects } from "../services/api/projectsApi";
import { ProjectList } from "./projects/projectList";
+import { BoxProps } from "../interface/interface";
-export default function Box({ channel }) {
+
+
+export default function Box({ channel }: BoxProps) {
const router = useRouter();
const arr = ["public", "private", "chatbot"];
const newArr = arr.filter((item) => item !== channel);
@@ -51,7 +54,7 @@ export default function Box({ channel }) {
}
};
const [topic, setTopic] = useState(" ");
- const handleDivClick = (e) => {
+ const handleDivClick = (e: any) => {
const content = e.target.textContent;
setTopic(content);
router.push({
diff --git a/frontend/components/modal.jsx b/frontend/components/modal.tsx
similarity index 80%
rename from frontend/components/modal.jsx
rename to frontend/components/modal.tsx
index 7a6f5f3..053ca66 100644
--- a/frontend/components/modal.jsx
+++ b/frontend/components/modal.tsx
@@ -1,7 +1,8 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
+import { ModalProps } from "../interface/interface";
-export default function Modal({ isOpen, onClose, children }) {
+export default function Modal({ isOpen, onClose, children }:ModalProps) {
const router = useRouter();
useEffect(() => {
diff --git a/frontend/components/navbar.jsx b/frontend/components/navbar.tsx
similarity index 83%
rename from frontend/components/navbar.jsx
rename to frontend/components/navbar.tsx
index 1197abd..c253177 100644
--- a/frontend/components/navbar.jsx
+++ b/frontend/components/navbar.tsx
@@ -5,13 +5,16 @@ import Image from "next/image";
import jinoraLogo from "../assets/logo.svg";
import slack from ".././assets/slack_blue.svg";
import { TopicDropdown } from "./topicDropdown";
+import { NavbarProps } from "../interface/interface";
-export const Navbar = ({ currentPage, currentTopic }) => {
- const [isMailOpen, setIsMailOpen] = useState(false);
- const [logo, setLogo] = useState(jinoraLogo);
- const [leftText, setLeftText] = useState("");
- const [toShow, setToShow] = useState(false);
- const [topic,setTopic]=useState(currentTopic);
+
+
+export const Navbar = ({ currentPage, currentTopic }: NavbarProps) => {
+ const [isMailOpen, setIsMailOpen] = useState(false);
+ const [logo, setLogo] = useState(jinoraLogo);
+ const [leftText, setLeftText] = useState("");
+ const [toShow, setToShow] = useState(false);
+ const [topic,setTopic]=useState(currentTopic);
function openMail() {
setIsMailOpen(true);
diff --git a/frontend/components/projects/projectCard.jsx b/frontend/components/projects/projectCard.tsx
similarity index 96%
rename from frontend/components/projects/projectCard.jsx
rename to frontend/components/projects/projectCard.tsx
index 29edb40..41169a8 100644
--- a/frontend/components/projects/projectCard.jsx
+++ b/frontend/components/projects/projectCard.tsx
@@ -3,8 +3,11 @@ import ps from "../../assets/Playstore.svg";
import as from "../../assets/Apple_logo_grey.svg";
import Image from "next/image";
import { useRouter } from "next/router";
+import { ProjectCardProps } from "../../interface/interface";
-export const ProjectCard = ({
+
+
+export const ProjectCard : React.FC = ({
name,
shortDesc,
ImageLink,
diff --git a/frontend/components/projects/projectData.jsx b/frontend/components/projects/projectData.tsx
similarity index 100%
rename from frontend/components/projects/projectData.jsx
rename to frontend/components/projects/projectData.tsx
diff --git a/frontend/components/projects/projectList.jsx b/frontend/components/projects/projectList.tsx
similarity index 82%
rename from frontend/components/projects/projectList.jsx
rename to frontend/components/projects/projectList.tsx
index 3a84f23..b2536b5 100644
--- a/frontend/components/projects/projectList.jsx
+++ b/frontend/components/projects/projectList.tsx
@@ -1,6 +1,8 @@
+import { ProjectListProps } from "../../interface/interface";
import { ProjectCard } from "./projectCard";
-export const ProjectList = ({ projects, category, heightDecrease }) => {
+
+export const ProjectList : React.FC = ({ projects, category, heightDecrease }) => {
return (
diff --git a/frontend/components/rightPane.jsx b/frontend/components/rightPane.tsx
similarity index 95%
rename from frontend/components/rightPane.jsx
rename to frontend/components/rightPane.tsx
index faf4abf..58dccef 100644
--- a/frontend/components/rightPane.jsx
+++ b/frontend/components/rightPane.tsx
@@ -7,13 +7,15 @@ import Image from "next/image";
import SettingsPopup from "./settingsPopup";
import React, { useState } from "react";
import { useRouter } from "next/router";
+import { SettingsPopupProps } from "../interface/interface";
export default function RightPane({
+ onClose,
soundEnabled,
setSoundEnabled,
notificationsEnabled,
setNotificationsEnabled,
-}) {
+}:SettingsPopupProps) {
const [showSettings, setShowSettings] = useState(false);
const router = useRouter();
const hoverEffectClasses ="hover:scale-125 hover:cursor-pointer transition-transform duration-300 ease-in-out";
diff --git a/frontend/components/settingsPopup.jsx b/frontend/components/settingsPopup.tsx
similarity index 91%
rename from frontend/components/settingsPopup.jsx
rename to frontend/components/settingsPopup.tsx
index 0f0a242..f9b49aa 100644
--- a/frontend/components/settingsPopup.jsx
+++ b/frontend/components/settingsPopup.tsx
@@ -7,6 +7,8 @@ import {
MdClose,
MdAudiotrack,
} from "react-icons/md";
+import { SettingsPopupProps } from "../interface/interface";
+
const SettingsPopup = ({
onClose,
@@ -14,12 +16,12 @@ const SettingsPopup = ({
setSoundEnabled,
notificationsEnabled,
setNotificationsEnabled,
-}) => {
- const popupRef = useRef();
+}: SettingsPopupProps) => {
+ const popupRef = useRef
(null);
useEffect(() => {
- function handleClickOutside(event) {
- if (popupRef.current && !popupRef.current.contains(event.target)) {
+ function handleClickOutside(event:MouseEvent) {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
onClose();
}
}
diff --git a/frontend/components/topicDropdown.jsx b/frontend/components/topicDropdown.tsx
similarity index 72%
rename from frontend/components/topicDropdown.jsx
rename to frontend/components/topicDropdown.tsx
index e955bfa..37d9d08 100644
--- a/frontend/components/topicDropdown.jsx
+++ b/frontend/components/topicDropdown.tsx
@@ -1,50 +1,52 @@
"use client";
-import React, { useState } from "react";
+import React, { useState, useEffect, useRef } from "react";
import { fetchProjects } from "../services/api/projectsApi";
-import { useEffect, useRef } from "react";
+import { Project,TopicDropdownProps} from "../interface/interface";
-export const TopicDropdown = ({ topic, setTopic,login }) => {
- const popupRef = useRef();
+export const TopicDropdown: React.FC = ({ topic, setTopic, login }) => {
+ const popupRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
- const [projects, setProjects] = useState([]);
- const projectList = projects.filter(
- (project) => project.Category === "Projects",
- );
+ const [projects, setProjects] = useState([]);
+
+ const projectList = projects.filter((project) => project.Category === "Projects");
const eventList = projects.filter((project) => project.Category === "Events");
const toggleDropdown = () => {
setIsOpen(!isOpen);
};
- const handleClick = (e) => {
- const content = e.target.textContent;
+ const handleClick = (event: React.MouseEvent) => {
+ event.preventDefault(); // Prevent default link behavior
+ const content = event.currentTarget.textContent || ""; // Get text content safely
setTopic(content);
- if(!login){
+ setIsOpen(false);
+ if (!login) {
window.location.href = `/chat_bot?topic=${encodeURIComponent(content)}`;
}
};
useEffect(() => {
- async function fetchProjectsData() {
+ const fetchProjectsData = async () => {
const data = await fetchProjects();
- if (data!=null){
- setProjects(data);
- }else{
- setProjects([]);
- }
- }
+ setProjects(data || []); // Handle null response
+ };
+
fetchProjectsData();
- }, []);
+ }, []);
useEffect(() => {
- function handleClickOutside(event) {
- if (popupRef.current && !popupRef.current.contains(event.target)) {
- setIsOpen(false);
+ const handleClickOutside = (event: MouseEvent) => {
+ if (popupRef.current && !popupRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+
}
- }
+ };
document.addEventListener("mousedown", handleClickOutside);
- return () => document.removeEventListener("mousedown", handleClickOutside);
- }, [popupRef, isOpen]);
+ return () => document.removeEventListener("mousedown",
+ handleClickOutside);
+ }, [popupRef]);
+ // No need for isOpen in dependency array
+
return (
{
{`${topic}`}
{
-
+
Projects
{projectList.map((project, index) => (
@@ -94,7 +96,7 @@ export const TopicDropdown = ({ topic, setTopic,login }) => {
-
+
Events
{eventList.map((event, index) => (
diff --git a/frontend/components/topicSelectionModal.jsx b/frontend/components/topicSelectionModal.tsx
similarity index 65%
rename from frontend/components/topicSelectionModal.jsx
rename to frontend/components/topicSelectionModal.tsx
index b210bc5..1921a4c 100644
--- a/frontend/components/topicSelectionModal.jsx
+++ b/frontend/components/topicSelectionModal.tsx
@@ -1,18 +1,21 @@
import React from "react";
+import { TopicSelectionModalProps } from "../interface/interface";
-const TopicSelectionModal = ({ onClose }) => {
+
+const TopicSelectionModal: React.FC = ({ onClose }) => {
return (
Please select a topic from the left pane.
Close
-
+
);
};
-export default TopicSelectionModal;
\ No newline at end of file
+
+export default TopicSelectionModal;
diff --git a/frontend/components/usernameInput.jsx b/frontend/components/usernameInput.jsx
deleted file mode 100644
index 4805abd..0000000
--- a/frontend/components/usernameInput.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-
;
\ No newline at end of file
diff --git a/frontend/components/usernameInput.tsx b/frontend/components/usernameInput.tsx
new file mode 100644
index 0000000..18428e5
--- /dev/null
+++ b/frontend/components/usernameInput.tsx
@@ -0,0 +1,27 @@
+import React, { useState, ChangeEvent } from 'react';
+import { UsernameInputProps } from '../interface/interface';
+
+
+const UsernameInput: React.FC
= ({ value, onChange }) => {
+ const handleInputChange = (event: ChangeEvent) => {
+ onChange(event.target.value);
+ };
+
+ return (
+
+ );
+};
+
+export default UsernameInput;
diff --git a/frontend/context/userContext.js b/frontend/context/userContext.js
deleted file mode 100644
index f1ee3f2..0000000
--- a/frontend/context/userContext.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import React, { createContext, useState } from "react";
-
-export const UserContext = createContext();
-
-export const UserProvider = ({ children }) => {
- const [userName, setUserName] = useState(null);
- return (
-
- {children}
-
- );
-};
diff --git a/frontend/context/userContext.tsx b/frontend/context/userContext.tsx
new file mode 100644
index 0000000..1e16ce0
--- /dev/null
+++ b/frontend/context/userContext.tsx
@@ -0,0 +1,16 @@
+import React, { createContext, useState, ReactNode } from "react";
+import { UserContextType, UserProviderProps } from "../interface/interface";
+
+
+export const UserContext = createContext(undefined);
+
+export const UserProvider: React.FC = ({ children }) => {
+ const [userName, setUserName] = useState(null);
+
+ return (
+
+ {children}
+
+
+ );
+};
\ No newline at end of file
diff --git a/frontend/global.d.ts b/frontend/global.d.ts
new file mode 100644
index 0000000..ce579d2
--- /dev/null
+++ b/frontend/global.d.ts
@@ -0,0 +1,5 @@
+declare module "*.mp3" {
+ const src: string;
+ export default src;
+ }
+
\ No newline at end of file
diff --git a/frontend/hooks/useLeaveChat.jsx b/frontend/hooks/useLeaveChat.jsx
deleted file mode 100644
index 3c85cb1..0000000
--- a/frontend/hooks/useLeaveChat.jsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { useEffect } from "react";
-import getSessionUserId from "../utils/session/getSessionUserId";
-import removeSessionUserId from "../utils/session/removeSessionUserId";
-import { leaveChat } from "../services/api/leaveChatApi";
-
-const useLeaveChat=(router)=>{
- useEffect(() => {
- const leaveChatOnNavigation = () => {
- leaveChat(getSessionUserId());
- removeSessionUserId();
- };
- const handleBeforeUnload = (e) => {
- leaveChat(getSessionUserId());
- };
-
- router.events.on("routeChangeStart", leaveChatOnNavigation);
- window.addEventListener("beforeunload", handleBeforeUnload);
-
- return () => {
- router.events.off("routeChangeStart", leaveChatOnNavigation);
- window.removeEventListener("beforeunload", handleBeforeUnload);
- };
- }, [router]);
-}
-export default useLeaveChat;
\ No newline at end of file
diff --git a/frontend/hooks/useLeaveChat.tsx b/frontend/hooks/useLeaveChat.tsx
new file mode 100644
index 0000000..2b514ea
--- /dev/null
+++ b/frontend/hooks/useLeaveChat.tsx
@@ -0,0 +1,35 @@
+// useLeaveChat.ts
+import { useEffect } from "react";
+import getSessionUserId from "../utils/session/getSessionUserId";
+import removeSessionUserId from "../utils/session/removeSessionUserId";
+import { leaveChat } from "../services/api/leaveChatApi";
+import { NextRouter } from "next/router";
+
+const useLeaveChat = (router: NextRouter) => {
+ useEffect(() => {
+ const leaveChatOnNavigation = () => {
+ const userId = getSessionUserId();
+ if (userId) {
+ leaveChat(userId);
+ removeSessionUserId();
+ }
+ };
+
+ const handleBeforeUnload = (e: BeforeUnloadEvent) => {
+ const userId = getSessionUserId();
+ if (userId) {
+ leaveChat(userId);
+ }
+ };
+
+ router.events.on("routeChangeStart", leaveChatOnNavigation);
+ window.addEventListener("beforeunload", handleBeforeUnload);
+
+ return () => {
+ router.events.off("routeChangeStart", leaveChatOnNavigation);
+ window.removeEventListener("beforeunload", handleBeforeUnload);
+ };
+ }, [router]);
+};
+
+export default useLeaveChat;
diff --git a/frontend/hooks/useLoadSettings.jsx b/frontend/hooks/useLoadSettings.jsx
deleted file mode 100644
index 1eecb9b..0000000
--- a/frontend/hooks/useLoadSettings.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import {useEffect} from 'react';
-
-const useLoadSetting=(setSoundEnabled,setNotificationsEnabled)=>{
- useEffect(() => {
- const savedSoundEnabled = localStorage.getItem("soundEnabled");
- const savedNotificationsEnabled = localStorage.getItem(
- "notificationsEnabled",
- );
-
- if (savedSoundEnabled !== null) {
- setSoundEnabled(JSON.parse(savedSoundEnabled));
- }
- if (savedNotificationsEnabled !== null) {
- setNotificationsEnabled(JSON.parse(savedNotificationsEnabled));
- }
- }, []);
-}
-export default useLoadSetting;
\ No newline at end of file
diff --git a/frontend/hooks/useLoadSettings.tsx b/frontend/hooks/useLoadSettings.tsx
new file mode 100644
index 0000000..990e8f7
--- /dev/null
+++ b/frontend/hooks/useLoadSettings.tsx
@@ -0,0 +1,20 @@
+import { useEffect } from 'react';
+import { UseLoadSettingProps } from '../interface/interface';
+
+const useLoadSetting = ({ setSoundEnabled, setNotificationsEnabled }: UseLoadSettingProps) => {
+ useEffect(() => {
+ const savedSoundEnabled = localStorage.getItem("soundEnabled");
+ const savedNotificationsEnabled = localStorage.getItem("notificationsEnabled");
+
+ if (savedSoundEnabled !== null) {
+ setSoundEnabled(JSON.parse(savedSoundEnabled));
+ }
+
+ if (savedNotificationsEnabled !== null) {
+ setNotificationsEnabled(JSON.parse(savedNotificationsEnabled));
+ }
+ }, [setSoundEnabled, setNotificationsEnabled]);
+};
+
+export default useLoadSetting;
+
diff --git a/frontend/hooks/useSettings.jsx b/frontend/hooks/useSettings.jsx
deleted file mode 100644
index 2f9a7ff..0000000
--- a/frontend/hooks/useSettings.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useEffect } from "react";
-
-const useSettings=(soundEnabled,notificationsEnabled)=>{
- useEffect(() => {
- localStorage.setItem("soundEnabled", JSON.stringify(soundEnabled));
- }, [soundEnabled]);
-
- useEffect(() => {
- localStorage.setItem(
- "notificationsEnabled",
- JSON.stringify(notificationsEnabled),
- );
- }, [notificationsEnabled]);
-}
-export default useSettings;
\ No newline at end of file
diff --git a/frontend/hooks/useSettings.tsx b/frontend/hooks/useSettings.tsx
new file mode 100644
index 0000000..344eb49
--- /dev/null
+++ b/frontend/hooks/useSettings.tsx
@@ -0,0 +1,14 @@
+import { useEffect } from "react";
+import { UseSettingsProps } from "../interface/interface";
+
+const useSettings = ({ soundEnabled, notificationsEnabled }: UseSettingsProps) => {
+ useEffect(() => {
+ localStorage.setItem("soundEnabled", JSON.stringify(soundEnabled));
+ }, [soundEnabled]);
+
+ useEffect(() => {
+ localStorage.setItem("notificationsEnabled", JSON.stringify(notificationsEnabled));
+ }, [notificationsEnabled]);
+};
+
+export default useSettings;
diff --git a/frontend/hooks/useVisibilityChange.jsx b/frontend/hooks/useVisibilityChange.jsx
deleted file mode 100644
index 89d2b7e..0000000
--- a/frontend/hooks/useVisibilityChange.jsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useEffect } from "react";
-
-const useVisibilityChange=(setUnreadCount)=>{
- useEffect(() => {
- const handleVisibilityChange = () => {
- if (!document.hidden) {
- setUnreadCount(0);
- }
- };
- window.addEventListener("visibilitychange", handleVisibilityChange);
- window.addEventListener("focus", handleVisibilityChange);
- return () => {
- window.removeEventListener("visibilitychange", handleVisibilityChange);
- window.removeEventListener("focus", handleVisibilityChange);
- };
- }, []);
-}
-export default useVisibilityChange;
\ No newline at end of file
diff --git a/frontend/hooks/useVisibilityChange.tsx b/frontend/hooks/useVisibilityChange.tsx
new file mode 100644
index 0000000..1cbc47b
--- /dev/null
+++ b/frontend/hooks/useVisibilityChange.tsx
@@ -0,0 +1,24 @@
+import { useEffect } from 'react';
+import { UseVisibilityChangeProps } from '../interface/interface';
+
+
+
+const useVisibilityChange = ({ setUnreadCount }: UseVisibilityChangeProps) => {
+ useEffect(() => {
+ const handleVisibilityChange = () => {
+ if (!document.hidden) {
+ setUnreadCount(0);
+ }
+ };
+
+ window.addEventListener('visibilitychange', handleVisibilityChange);
+ window.addEventListener('focus', handleVisibilityChange);
+
+ return () => {
+ window.removeEventListener('visibilitychange', handleVisibilityChange);
+ window.removeEventListener('focus', handleVisibilityChange);
+ };
+ }, [setUnreadCount]);
+};
+
+export default useVisibilityChange;
diff --git a/frontend/hooks/useWebSocketForChatBot.jsx b/frontend/hooks/useWebSocketForChatBot.tsx
similarity index 89%
rename from frontend/hooks/useWebSocketForChatBot.jsx
rename to frontend/hooks/useWebSocketForChatBot.tsx
index 6349eac..957c19e 100644
--- a/frontend/hooks/useWebSocketForChatBot.jsx
+++ b/frontend/hooks/useWebSocketForChatBot.tsx
@@ -8,8 +8,9 @@ import { handleWebSocketError } from "../utils/websocket/handleWebSocketError";
import processWebSocketMessage from "../utils/websocket/processWebSocketMessage";
import { buildWebSocketURL } from "../services/url-builder/url-builder";
import { initializeWebSocketConnection } from "../services/api/api";
+import { useWebsocketForChatbotProps } from "../interface/interface";
-const useWebsocketForChatbot=(socketRef,setMessages,router)=>{
+const useWebsocketForChatbot=({socketRef,setMessages,router}:useWebsocketForChatbotProps)=>{
useEffect(() => {
const username = getSessionUser();
if (!username || username === "null" || username === "undefined") {
@@ -31,16 +32,18 @@ const useWebsocketForChatbot=(socketRef,setMessages,router)=>{
//todo-> toast connected to server
};
const handleMessage = (event) =>
- processWebSocketMessage(event, setMessages, () => router.push("/"), true);
+ processWebSocketMessage({event, setMessages, navigateToLogin:() => router.push("/"), isChatbot:true});
const handleClose = (event) =>
handleWebSocketClose(event, () => router.push("/"));
const handleError = handleWebSocketError;
const socket = initializeWebSocketConnection(
url,
+ {
handleOpen,
handleMessage,
handleClose,
handleError,
+ }
);
socketRef.current = socket;
diff --git a/frontend/hooks/useWebsocket.jsx b/frontend/hooks/useWebsocket.tsx
similarity index 82%
rename from frontend/hooks/useWebsocket.jsx
rename to frontend/hooks/useWebsocket.tsx
index dcae442..fc852f8 100644
--- a/frontend/hooks/useWebsocket.jsx
+++ b/frontend/hooks/useWebsocket.tsx
@@ -1,4 +1,4 @@
-import { useEffect} from "react";
+import { MutableRefObject, useEffect} from "react";
import getSessionUser from "../utils/session/getSessionUser";
import getSessionUserId from "../utils/session/getSessionUserId";
import handleWebSocketClose from "../utils/websocket/handleWebSocketClose";
@@ -7,8 +7,26 @@ import processWebSocketMessage from "../utils/websocket/processWebSocketMessage"
import { buildWebSocketURL } from "../services/url-builder/url-builder";
import { initializeWebSocketConnection } from "../services/api/api";
import playSound from "../utils/playSound";
+import { Message } from "../interface/interface"
+import { NextRouter } from "next/router";
-const useWebsocket=(soundEnabled,channel,socketRef,setMessages,router,setUnreadCount)=>{
+interface UseWebSocketProps {
+ soundEnabled: boolean,
+ channel: string,
+ socketRef: MutableRefObject,
+ setMessages: React.Dispatch>,
+ router: NextRouter,
+ setUnreadCount: React.Dispatch>
+}
+
+const useWebsocket=({
+ soundEnabled,
+ channel,
+ socketRef,
+ setMessages,
+ router,
+ setUnreadCount,
+}: UseWebSocketProps)=>{
useEffect(() => {
const username = getSessionUser();
if (!username || username === "null" || username === "undefined") {
@@ -20,27 +38,29 @@ const useWebsocket=(soundEnabled,channel,socketRef,setMessages,router,setUnreadC
//todo-> toast connected to server
};
const handleMessage = (event) =>
- processWebSocketMessage(
+ processWebSocketMessage({
event,
setMessages,
- () => router.push("/"),
- false,
+ navigateToLogin:() => router.push("/"),
+ isChatbot:false,}
);
const handleClose = (event) =>
handleWebSocketClose(event, () => router.push("/"));
const handleError = handleWebSocketError;
const socket = initializeWebSocketConnection(
url,
+ {
handleOpen,
handleMessage,
handleClose,
handleError,
+ }
);
socketRef.current = socket;
socket.addEventListener("message", (event) => {
try {
- let data = "";
+ let data:any = "";
if (
event.data != "Message send successful" &&
event.data != "Welcome to MDG Chat!"
@@ -83,6 +103,7 @@ const useWebsocket=(soundEnabled,channel,socketRef,setMessages,router,setUnreadC
username: data.sender,
timestamp: parseFloat(data.timestamp),
avatar: data.url,
+ userID: data.userID,
},
]);
if (soundEnabled) playSound(isSent);
diff --git a/frontend/interface/interface.ts b/frontend/interface/interface.ts
new file mode 100644
index 0000000..a4c1c19
--- /dev/null
+++ b/frontend/interface/interface.ts
@@ -0,0 +1,299 @@
+import React, { Dispatch, MutableRefObject, ReactNode, SetStateAction, useEffect, useState } from "react";
+import { useRouter } from 'next/navigation';
+import alertServerError from "../utils/alerts/alertServerError";
+
+export interface SettingsPopupProps {
+ onClose: () => void;
+ soundEnabled: boolean;
+ setSoundEnabled: (enabled: boolean) => void;
+ notificationsEnabled: boolean;
+ setNotificationsEnabled: (enabled: boolean) => void;
+ }
+
+export interface RightPaneProps extends SettingsPopupProps {}
+
+ interface IconProps {
+ src: string;
+ alt: string;
+ onClick?: () => void;
+ }
+
+export interface LayoutProps {
+ children: React.ReactNode;
+ home: boolean;
+}
+
+export interface TopicDropdownProps {
+ topic: string;
+ setTopic: (topic: string) => void;
+ login: boolean;
+ }
+
+export interface Project {
+ Category: string;
+ Name: string;
+ ShortDesc: string;
+ LongDesc: string;
+ ImageLink: string;
+ AppStoreLink: string;
+ GithubLink: string;
+ PlayStoreLink: string;
+ }
+
+ export interface TopicSelectionModalProps {
+ onClose: () => void;
+ }
+
+ export interface UsernameInputProps {
+ value: string;
+ onChange: (value: string) => void;
+ }
+
+export interface NavbarProps{
+ currentPage: string;
+ currentTopic:string;
+ }
+
+export interface ModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ children: React.ReactNode;
+ }
+
+ export interface BoxProps {
+ channel: "public" | "private" | "chatbot";
+ }
+
+export interface MailProps {
+ isOpen?: boolean;
+ onClose: () => void;
+ channel: "public" | "private" | "chatbot";
+ }
+ export interface ProjectListProps {
+ projects: Project[];
+ category: string;
+ heightDecrease?: boolean;
+ }
+
+ export interface ProjectCardProps {
+ name: string;
+ shortDesc: string;
+ ImageLink: string;
+ Github?: string;
+ PlayStore?: string;
+ AppStore?: string;
+ }
+
+ export interface LoginModalProps {
+ onClose: () => void;
+ redirect: string;
+ }
+
+ export interface ChatBotLoginModalProps {
+ onClose: () => void; // Function to close the modal
+ }
+ export type Topic = "SELECT A TOPIC" | "Option 1" | "Option 2" | string ;
+
+ export interface Message {
+ isSent: boolean;
+ avatar?: string;
+ username: string;
+ text: string;
+ timestamp: number;
+ userID: string;
+ }
+ export interface ChatContainerProps {
+ messages: Message[]; // Assuming messages are serialized JSON strings
+ messagesEndRef: React.RefObject; // Ref to scroll to the bottom
+ }
+
+
+
+
+
+ export interface UserContextType {
+ userName: string | null;
+ setUserName: React.Dispatch>;
+ }
+
+ export interface UserProviderProps {
+ children: React.ReactNode;
+ }
+
+ export interface UseLoadSettingProps {
+ setSoundEnabled: React.Dispatch>;
+ setNotificationsEnabled: React.Dispatch>;
+}
+
+ export interface UseSettingsProps {
+ soundEnabled: boolean;
+ notificationsEnabled: boolean;
+ }
+
+ export interface UseVisibilityChangeProps {
+ setUnreadCount: Dispatch>
+}
+
+export interface UseWebsocketProps {
+ soundEnabled: boolean;
+ channel: string;
+ socketRef: React.MutableRefObject;
+ setMessages: React.Dispatch>;
+ router: any;
+ setUnreadCount: React.Dispatch>;
+}
+
+export interface useWebsocketForChatbotProps {
+ socketRef: MutableRefObject;
+ setMessages: Dispatch>;
+ router: any;
+}
+
+interface UseLoadSettingHook {
+ (setSoundEnabled: (enabled: boolean) => void, setNotificationsEnabled: (enabled: boolean) => void): void;
+}
+
+interface UseSettingsHook {
+ (soundEnabled: boolean, notificationsEnabled: boolean): void;
+}
+
+interface UseWebsocketForChatbotHook {
+ (
+ socketRef: React.MutableRefObject,
+ setMessages: React.Dispatch>,
+ router: typeof useRouter,
+ messagesEndRef: React.MutableRefObject
+ ): void;
+}
+
+interface UseVisibilityChangeHook {
+ (setUnreadCount: React.Dispatch>): void;
+}
+
+interface UseLeaveChatHook {
+ (router: typeof useRouter, socketRef: React.MutableRefObject): void;
+}
+
+interface ChatbotContainerProps {
+ messages: Message[];
+ messagesEndRef: React.MutableRefObject;
+}
+
+export interface ProcessWebSocketMessageProps {
+ event: MessageEvent;
+ setMessages: React.Dispatch>;
+ navigateToLogin: () => void;
+ isChatbot: boolean;
+}
+
+export interface DataFromServer {
+ userID?: string;
+ Message?: string;
+ Delete?: string;
+}
+
+
+
+
+
+export interface WebSocketHandlers {
+ onOpen: (event: Event) => void;
+ onMessage: (event: MessageEvent) => void;
+ onClose: (event: CloseEvent) => void;
+ onError: (event: Event) => void;
+}
+
+
+export type AlertSameUserProps = (reason: string, navigate: NavigateFunction) => void;
+export type AlertBadRequestProps = (reason: string, navigate: NavigateFunction) => void;
+export type AlertServerErrorProps = (reason: string, navigate: NavigateFunction) => void;
+
+export interface WebSocketURLParams {
+ userId: string;
+ username: string;
+ channel: string;
+ topic?: string;
+}
+
+export interface URLBuilderParams {
+ host: string;
+ port: string;
+ protocol: string;
+}
+
+export interface LeaveChatURLParams {
+ userID: string;
+}
+
+export interface ChatContainerProps {
+ messages: Message[]; // An array of message objects
+ messagesEndRef: React.RefObject; // A ref for auto-scrolling to the bottom of the chat
+}
+
+export interface AvatarListProps {
+ AvatarList: string[]; // List of avatar image paths/URLs
+}
+
+export interface ChatInputBoxProps {
+ socketRef: React.MutableRefObject;
+}
+
+
+
+export type CheckAndPromptSessionChange = (
+ currentUser: string,
+ newUsername: string,
+ onSessionChange: () => void
+) => Promise;
+
+export type GetSessionUser = () => string | null;
+export type GetSessionUserId = () => string | null;
+export type SetSessionUser = (username: string) => void;
+export type RemoveSessionUserId = () => void;
+
+export interface LeaveChatResponse {
+ // Define the expected properties of the response here
+ success: boolean;
+ message?: string;
+}
+
+// Define the return type of the fetchProjects function
+export type FetchProjectsResponse = Project[]; // Array of projects
+
+// Define the interface for a project
+export interface Project {
+ id: string; // Adjust the type as per your actual API response
+ name: string; // Example property
+ description?: string; // Optional property
+ // Add more properties as needed based on your project data structure
+}
+
+// Define the interface for the subscribe response
+export interface SubscribeResponse {
+ code: number;
+ message: string;
+}
+
+// Define the parameters for the subscribe function
+export interface SubscribeParams {
+ email: string;
+ username: string;
+ userId: string;
+ channel: string;
+ timestamp: number;
+}
+
+// Define an interface for the environment variables
+export interface BackendEnvironment {
+ NEXT_PUBLIC_BACKEND_HOST: string;
+ NEXT_PUBLIC_BACKEND_PORT: string;
+ NEXT_PUBLIC_BACKEND_ENVIRONMENT: 'development' | 'production';
+}
+
+// Create a type for the optional topic parameter
+
+export type NavigateFunction = (path: string) => void;
+
+export type AlertBannedUser = (reason: string, navigate: NavigateFunction) => void;
+
+export type AlertAbnormalCloseProps = (reason: string, navigate: NavigateFunction) => void;
diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts
new file mode 100644
index 0000000..4f11a03
--- /dev/null
+++ b/frontend/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ba56355..dd8f4d3 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,19 +9,26 @@
"axios": "^1.6.8",
"file-loader": "^6.2.0",
"moment": "^2.29.4",
- "next": "latest",
+ "next": "^14.2.5",
"postcss": "^8.4.31",
"prettier": "^3.2.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.12.0",
+ "react-router-dom": "^6.26.2",
"react-switch": "^7.0.0",
+ "react-toastify": "^10.0.5",
"sharp": "^0.33.1",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"start": "^5.1.0",
"sweetalert2": "^11.10.1",
"tailwindcss": "^3.3.3"
+ },
+ "devDependencies": {
+ "@types/node": "^22.6.1",
+ "@types/react": "^18.3.3",
+ "typescript": "5.5.4"
}
},
"node_modules/@alloc/quick-lru": {
@@ -529,17 +536,19 @@
}
},
"node_modules/@next/env": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.4.tgz",
- "integrity": "sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ=="
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz",
+ "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==",
+ "license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.4.tgz",
- "integrity": "sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz",
+ "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -549,12 +558,13 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.4.tgz",
- "integrity": "sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz",
+ "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -564,12 +574,13 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.4.tgz",
- "integrity": "sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz",
+ "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -579,12 +590,13 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.4.tgz",
- "integrity": "sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz",
+ "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -594,12 +606,13 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.4.tgz",
- "integrity": "sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz",
+ "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -609,12 +622,13 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.4.tgz",
- "integrity": "sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz",
+ "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"linux"
@@ -624,12 +638,13 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.4.tgz",
- "integrity": "sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz",
+ "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==",
"cpu": [
"arm64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -639,12 +654,13 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.4.tgz",
- "integrity": "sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz",
+ "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==",
"cpu": [
"ia32"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -654,12 +670,13 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.4.tgz",
- "integrity": "sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz",
+ "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==",
"cpu": [
"x64"
],
+ "license": "MIT",
"optional": true,
"os": [
"win32"
@@ -700,16 +717,33 @@
"node": ">= 8"
}
},
+ "node_modules/@remix-run/router": {
+ "version": "1.19.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
+ "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/@swc/helpers": {
- "version": "0.5.2",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz",
- "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
+ "license": "Apache-2.0",
"dependencies": {
+ "@swc/counter": "^0.1.3",
"tslib": "^2.4.0"
}
},
@@ -758,11 +792,30 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/node": {
- "version": "20.8.9",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.9.tgz",
- "integrity": "sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==",
+ "version": "22.6.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.6.1.tgz",
+ "integrity": "sha512-V48tCfcKb/e6cVUigLAaJDAILdMP0fUW6BidkPK4GpGjXcfbnoHasCZDwz3N3yVt5we2RHm4XTQCpv0KJz9zqw==",
+ "license": "MIT",
"dependencies": {
- "undici-types": "~5.26.4"
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.12",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
+ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.3",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz",
+ "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.0.2"
}
},
"node_modules/@webassemblyjs/ast": {
@@ -1158,9 +1211,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001543",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz",
- "integrity": "sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA==",
+ "version": "1.0.30001646",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz",
+ "integrity": "sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==",
"funding": [
{
"type": "opencollective",
@@ -1174,7 +1227,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/chokidar": {
"version": "3.5.3",
@@ -1227,6 +1281,15 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
@@ -1319,6 +1382,13 @@
"node": ">=4"
}
},
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1667,7 +1737,8 @@
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
- "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
+ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+ "peer": true
},
"node_modules/graceful-fs": {
"version": "4.2.11",
@@ -1979,37 +2050,39 @@
"peer": true
},
"node_modules/next": {
- "version": "13.5.4",
- "resolved": "https://registry.npmjs.org/next/-/next-13.5.4.tgz",
- "integrity": "sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==",
+ "version": "14.2.5",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.5.tgz",
+ "integrity": "sha512-0f8aRfBVL+mpzfBjYfQuLWh2WyAwtJXCRfkPF4UJ5qd2YwrHczsrSzXU4tRMV0OAxR8ZJZWPFn6uhSC56UTsLA==",
+ "license": "MIT",
"dependencies": {
- "@next/env": "13.5.4",
- "@swc/helpers": "0.5.2",
+ "@next/env": "14.2.5",
+ "@swc/helpers": "0.5.5",
"busboy": "1.6.0",
- "caniuse-lite": "^1.0.30001406",
+ "caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
"postcss": "8.4.31",
- "styled-jsx": "5.1.1",
- "watchpack": "2.4.0"
+ "styled-jsx": "5.1.1"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
- "node": ">=16.14.0"
+ "node": ">=18.17.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "13.5.4",
- "@next/swc-darwin-x64": "13.5.4",
- "@next/swc-linux-arm64-gnu": "13.5.4",
- "@next/swc-linux-arm64-musl": "13.5.4",
- "@next/swc-linux-x64-gnu": "13.5.4",
- "@next/swc-linux-x64-musl": "13.5.4",
- "@next/swc-win32-arm64-msvc": "13.5.4",
- "@next/swc-win32-ia32-msvc": "13.5.4",
- "@next/swc-win32-x64-msvc": "13.5.4"
+ "@next/swc-darwin-arm64": "14.2.5",
+ "@next/swc-darwin-x64": "14.2.5",
+ "@next/swc-linux-arm64-gnu": "14.2.5",
+ "@next/swc-linux-arm64-musl": "14.2.5",
+ "@next/swc-linux-x64-gnu": "14.2.5",
+ "@next/swc-linux-x64-musl": "14.2.5",
+ "@next/swc-win32-arm64-msvc": "14.2.5",
+ "@next/swc-win32-ia32-msvc": "14.2.5",
+ "@next/swc-win32-x64-msvc": "14.2.5"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"sass": "^1.3.0"
@@ -2018,10 +2091,7 @@
"@opentelemetry/api": {
"optional": true
},
- "fibers": {
- "optional": true
- },
- "node-sass": {
+ "@playwright/test": {
"optional": true
},
"sass": {
@@ -2320,6 +2390,38 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "node_modules/react-router": {
+ "version": "6.26.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
+ "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.19.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8"
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "6.26.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
+ "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@remix-run/router": "1.19.2",
+ "react-router": "6.26.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/react-switch": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/react-switch/-/react-switch-7.0.0.tgz",
@@ -2332,6 +2434,19 @@
"react-dom": "^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
+ "node_modules/react-toastify": {
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
+ "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.1.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -2857,10 +2972,25 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
},
+ "node_modules/typescript": {
+ "version": "5.5.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
"node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "license": "MIT"
},
"node_modules/update-browserslist-db": {
"version": "1.0.13",
@@ -2916,6 +3046,7 @@
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
+ "peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
diff --git a/frontend/package.json b/frontend/package.json
index d026958..a68540c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -11,18 +11,25 @@
"axios": "^1.6.8",
"file-loader": "^6.2.0",
"moment": "^2.29.4",
- "next": "latest",
+ "next": "^14.2.5",
"postcss": "^8.4.31",
"prettier": "^3.2.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.12.0",
+ "react-router-dom": "^6.26.2",
"react-switch": "^7.0.0",
+ "react-toastify": "^10.0.5",
"sharp": "^0.33.1",
"socket.io": "^4.7.2",
"socket.io-client": "^4.7.2",
"start": "^5.1.0",
"sweetalert2": "^11.10.1",
"tailwindcss": "^3.3.3"
+ },
+ "devDependencies": {
+ "@types/node": "^22.6.1",
+ "@types/react": "^18.3.3",
+ "typescript": "5.5.4"
}
}
diff --git a/frontend/pages/_app.jsx b/frontend/pages/_app.tsx
similarity index 56%
rename from frontend/pages/_app.jsx
rename to frontend/pages/_app.tsx
index ac0e0ab..ac4f267 100644
--- a/frontend/pages/_app.jsx
+++ b/frontend/pages/_app.tsx
@@ -1,10 +1,14 @@
+import React from 'react';
+import { AppProps } from 'next/app';
import { UserProvider } from "../context/userContext";
import "../styles/globals.css";
-export default function App({ Component, pageProps }) {
+function App({ Component, pageProps }: AppProps) {
return (
);
}
+
+export default App;
diff --git a/frontend/pages/chat.jsx b/frontend/pages/chat.tsx
similarity index 83%
rename from frontend/pages/chat.jsx
rename to frontend/pages/chat.tsx
index 9afbf65..7cb1552 100644
--- a/frontend/pages/chat.jsx
+++ b/frontend/pages/chat.tsx
@@ -10,21 +10,24 @@ import useSettings from "../hooks/useSettings";
import useWebsocket from "../hooks/useWebsocket";
import useLeaveChat from "../hooks/useLeaveChat";
import useVisibilityChange from "../hooks/useVisibilityChange";
+import { Message } from "../interface/interface";
+
+
export default function Home(){
- const [messages, setMessages] = useState([]);
- const [unreadCount, setUnreadCount] = useState(0);
- const [soundEnabled, setSoundEnabled] = useState(true);
- const [notificationsEnabled, setNotificationsEnabled] = useState(true);
- const router = useRouter()
- const {channel}=router.query;
- const socketRef = useRef(null);
- const messagesEndRef = useRef(null);
+ const [messages, setMessages] = useState([]);
+ const [unreadCount, setUnreadCount] = useState(0);
+ const [soundEnabled, setSoundEnabled] = useState(true);
+ const [notificationsEnabled, setNotificationsEnabled] = useState(true);
+ const router = useRouter();
+ const {channel}=router.query as { channel?: string };;
+ const socketRef = useRef(null);
+ const messagesEndRef = useRef(null);
- useLoadSetting(setSoundEnabled,setNotificationsEnabled);
- useSettings(soundEnabled,notificationsEnabled);
- useWebsocket(soundEnabled,channel,socketRef,setMessages,router,setUnreadCount)
+ useLoadSetting({setSoundEnabled,setNotificationsEnabled});
+ useSettings({soundEnabled,notificationsEnabled});
+ useWebsocket({soundEnabled,channel,socketRef,setMessages,router,setUnreadCount})
useLeaveChat(router);
- useVisibilityChange(setUnreadCount);
+ useVisibilityChange({setUnreadCount});
useEffect(() => {}, [messages]);
diff --git a/frontend/pages/chat_bot.jsx b/frontend/pages/chat_bot.tsx
similarity index 87%
rename from frontend/pages/chat_bot.jsx
rename to frontend/pages/chat_bot.tsx
index c88924f..0798206 100644
--- a/frontend/pages/chat_bot.jsx
+++ b/frontend/pages/chat_bot.tsx
@@ -1,4 +1,4 @@
-+"use client";
+"use client";
import ChatInputBox from "../components/chat/chatInputBox";
import Box from "../components/mdgBox";
import { useState, useEffect, useRef } from "react";
@@ -11,25 +11,25 @@ import useWebsocketForChatbot from "../hooks/useWebSocketForChatBot";
import useVisibilityChange from "../hooks/useVisibilityChange";
import useLeaveChat from "../hooks/useLeaveChat";
+
export default function Home() {
const [messages, setMessages] = useState([]);
const [unreadCount, setUnreadCount] = useState(0);
const [soundEnabled, setSoundEnabled] = useState(true);
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
- const [topic, setTopic] = useState("Appetizer");
+ const [topic, setTopic] = useState("Appetizer");
const router = useRouter();
const socketRef = useRef(null);
const messagesEndRef = useRef(null);
- useLoadSetting(setSoundEnabled,setNotificationsEnabled);
- useSettings(soundEnabled,notificationsEnabled);
- useWebsocketForChatbot(socketRef,setMessages,router);
- useVisibilityChange(setUnreadCount);
+ useLoadSetting({setSoundEnabled,setNotificationsEnabled});
+ useSettings({soundEnabled,notificationsEnabled});
+ useWebsocketForChatbot({socketRef,setMessages,router});
+ useVisibilityChange({setUnreadCount});
useLeaveChat(router);
- useEffect(() => {
- setTopic(router.query.topic ?? "Appetizer");
- }, [router.query]);
+ useEffect(() => setTopic(router.query.topic as string ?? "Appetizer"), [router.query]);
+
useEffect(() => {}, [messages]);
diff --git a/frontend/pages/index.jsx b/frontend/pages/index.tsx
similarity index 88%
rename from frontend/pages/index.jsx
rename to frontend/pages/index.tsx
index d09a811..77ea60b 100644
--- a/frontend/pages/index.jsx
+++ b/frontend/pages/index.tsx
@@ -7,11 +7,11 @@ import LoginModal from "../components/chat/loginModal";
import ChatBotLoginModal from "../components/chatbot/chatbotLoginModal";
export default function Home() {
- const [isModalOpen, setIsModalOpen] = useState(false);
- const [isChatBotModalOpen, setISChatBotModalOpen] = useState(false);
- const [redirect, setRedirect] = useState("");
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [isChatBotModalOpen, setISChatBotModalOpen] = useState(false);
+ const [redirect, setRedirect] = useState("");
- const openModal = (redirect) => {
+ const openModal = (redirect:string) => {
setIsModalOpen(true);
setRedirect(redirect);
};
@@ -45,14 +45,14 @@ export default function Home() {
{isModalOpen && (
)}
{isChatBotModalOpen && (
-
+
)}
@@ -76,7 +76,7 @@ export default function Home() {
onClick={goToChatbot}
>
-
+
TALK TO OUR CHATBOT
@@ -85,7 +85,7 @@ export default function Home() {
onClick={goToPrivateChat}
>
-
+
PRIVATE CHAT ON SLACK
@@ -94,7 +94,7 @@ export default function Home() {
onClick={goToPublicChat}
>
-
+
PUBLIC MDG CHAT FORUM
diff --git a/frontend/services/api/api.js b/frontend/services/api/api.js
deleted file mode 100644
index 58a88a8..0000000
--- a/frontend/services/api/api.js
+++ /dev/null
@@ -1,14 +0,0 @@
-export function initializeWebSocketConnection(
- url,
- onOpen,
- onMessage,
- onClose,
- onError,
-) {
- const socket = new WebSocket(url);
- socket.onopen = onOpen;
- socket.onmessage = onMessage;
- socket.onclose = onClose;
- socket.onerror = onError;
- return socket;
-}
\ No newline at end of file
diff --git a/frontend/services/api/api.ts b/frontend/services/api/api.ts
new file mode 100644
index 0000000..516175c
--- /dev/null
+++ b/frontend/services/api/api.ts
@@ -0,0 +1,20 @@
+interface WebSocketHandlers {
+ handleOpen: (event: Event) => void;
+ handleMessage: (event: MessageEvent) => void;
+ handleClose: (event: CloseEvent) => void;
+ handleError: (event: Event) => void;
+}
+
+export function initializeWebSocketConnection(
+ url: string,
+ handlers: WebSocketHandlers
+): WebSocket {
+ const socket = new WebSocket(url);
+
+ socket.onopen = handlers.handleOpen;
+ socket.onmessage = handlers.handleMessage;
+ socket.onclose = handlers.handleClose;
+ socket.onerror = handlers.handleError;
+
+ return socket;
+}
diff --git a/frontend/services/api/leaveChatApi.js b/frontend/services/api/leaveChatApi.js
deleted file mode 100644
index 9452cef..0000000
--- a/frontend/services/api/leaveChatApi.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import axios from "axios";
-import { leaveChatURLbuildr } from "../url-builder/url-builder";
-
-export async function leaveChat(userID) {
- if (userID) {
- try {
- const url = leaveChatURLbuildr(userID);
- const response = await axios.post(url, {});
- } catch (error) {
- // console.error("Error in leaving chat:", error)
- }
- }
-}
\ No newline at end of file
diff --git a/frontend/services/api/leaveChatApi.ts b/frontend/services/api/leaveChatApi.ts
new file mode 100644
index 0000000..2a4a120
--- /dev/null
+++ b/frontend/services/api/leaveChatApi.ts
@@ -0,0 +1,17 @@
+import axios from "axios";
+import { leaveChatURLbuildr } from "../url-builder/url-builder";
+import { LeaveChatResponse } from "../../interface/interface";
+
+export async function leaveChat(userID: string): Promise {
+ if (userID) {
+ try {
+ const url = leaveChatURLbuildr(userID);
+ const response = await axios.post(url, {});
+
+ return response.data; // Assuming the API returns a response with the properties defined in LeaveChatResponse
+ } catch (error) {
+ console.error("Error in leaving chat:", error);
+ return undefined; // Or handle the error as needed
+ }
+ }
+}
diff --git a/frontend/services/api/projectsApi.js b/frontend/services/api/projectsApi.js
deleted file mode 100644
index b7365cd..0000000
--- a/frontend/services/api/projectsApi.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import axios from "axios";
-import { projectURLbuildr } from "../url-builder/url-builder";
-export const fetchProjects = async () => {
- try {
- const url = projectURLbuildr();
- const res = await axios.get(url);
- return res.data;
- } catch (error) {
- //todo -> enable sentry logger here
- }
-};
\ No newline at end of file
diff --git a/frontend/services/api/projectsApi.ts b/frontend/services/api/projectsApi.ts
new file mode 100644
index 0000000..fb8905e
--- /dev/null
+++ b/frontend/services/api/projectsApi.ts
@@ -0,0 +1,17 @@
+import axios from "axios";
+import { projectURLbuildr } from "../url-builder/url-builder";
+
+import { FetchProjectsResponse, Project } from "../../interface/interface";
+
+export const fetchProjects = async (): Promise => {
+ try {
+ const url = projectURLbuildr();
+ const res = await axios.get(url);
+ return res.data;
+ } catch (error) {
+ console.error("Error fetching projects:", error);
+ // todo -> enable sentry logger here
+ return undefined; // Handle error as needed
+ }
+};
+
diff --git a/frontend/services/api/subscribeApi.js b/frontend/services/api/subscribeApi.js
deleted file mode 100644
index 2767e0c..0000000
--- a/frontend/services/api/subscribeApi.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import axios from "axios";
-import { subscribeURLbuildr } from "../url-builder/url-builder";
-
-const subscribe = async (email, username, userId, channel, timestamp) => {
- const url = subscribeURLbuildr();
- try {
- const response = await axios.post(
- url,
- {
- email: email,
- username: username,
- userId: userId,
- channel: channel,
- timestamp: timestamp,
- },
- {
- headers: {
- "Content-Type": "multipart/form-data",
- },
- },
- );
-
- if (response.status === 200) {
- return response.data;
- }
- } catch (error) {
- throw error.response.data;
- }
-};
-export default subscribe;
\ No newline at end of file
diff --git a/frontend/services/api/subscribeApi.ts b/frontend/services/api/subscribeApi.ts
new file mode 100644
index 0000000..e362411
--- /dev/null
+++ b/frontend/services/api/subscribeApi.ts
@@ -0,0 +1,45 @@
+import axios from "axios";
+import { subscribeURLbuildr } from "../url-builder/url-builder";
+
+import { SubscribeResponse ,SubscribeParams } from "../../interface/interface";
+
+
+// Convert the subscribe function to TypeScript
+const subscribe = async ({
+ email,
+ username,
+ userId,
+ channel,
+ timestamp,
+}: SubscribeParams): Promise => {
+ const url = subscribeURLbuildr();
+
+ try {
+ const response = await axios.post(
+ url,
+ {
+ email,
+ username,
+ userId,
+ channel,
+ timestamp,
+ },
+ {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ }
+ );
+
+ if (response.status === 200) {
+ return response.data;
+ } else {
+ throw new Error("Subscription failed");
+ }
+ } catch (error: any) {
+ // Here, we throw a new error with a message from the server if available
+ throw new Error(error.response?.data?.message || "An error occurred during subscription");
+ }
+};
+
+export default subscribe;
diff --git a/frontend/services/url-builder/url-builder.js b/frontend/services/url-builder/url-builder.js
deleted file mode 100644
index 991266f..0000000
--- a/frontend/services/url-builder/url-builder.js
+++ /dev/null
@@ -1,53 +0,0 @@
-export function buildWebSocketURL(userId, username, channel, topic) {
- const host = process.env.NEXT_PUBLIC_BACKEND_HOST;
- const port = process.env.NEXT_PUBLIC_BACKEND_PORT;
- const protocol =
- process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development"
- ? "ws"
- : "wss";
- const baseUrl = `${protocol}://${host}:${port}/chat`;
- const params = new URLSearchParams({
- channel: channel || "public",
- name: username,
- userID: userId || "0",
- topic: topic || null,
- });
- return `${baseUrl}?${params.toString()}`;
-}
-
-export function projectURLbuildr() {
- const host = process.env.NEXT_PUBLIC_BACKEND_HOST;
- const port = process.env.NEXT_PUBLIC_BACKEND_PORT;
- const protocol =
- process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development"
- ? "http"
- : "https";
- const baseUrl = `${protocol}://${host}:${port}/projects`;
- return baseUrl;
-}
-
-export function leaveChatURLbuildr(userID) {
- const host = process.env.NEXT_PUBLIC_BACKEND_HOST;
- const port = process.env.NEXT_PUBLIC_BACKEND_PORT;
- const protocol =
- process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development"
- ? "http"
- : "https";
- const baseUrl = `${protocol}://${host}:${port}/chat/leave`;
- const params = new URLSearchParams({
- userID: userID,
- });
-
- return `${baseUrl}?${params.toString()}`;
-}
-
-export function subscribeURLbuildr() {
- const host = process.env.NEXT_PUBLIC_BACKEND_HOST;
- const port = process.env.NEXT_PUBLIC_BACKEND_PORT;
- const protocol =
- process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development"
- ? "http"
- : "https";
- const baseUrl = `${protocol}://${host}:${port}/subscribe`;
- return baseUrl;
-}
\ No newline at end of file
diff --git a/frontend/services/url-builder/url-builder.ts b/frontend/services/url-builder/url-builder.ts
new file mode 100644
index 0000000..a5940a9
--- /dev/null
+++ b/frontend/services/url-builder/url-builder.ts
@@ -0,0 +1,74 @@
+
+import { BackendEnvironment , Topic } from "../../interface/interface";
+// Function to build WebSocket URL
+export function buildWebSocketURL(
+ userId: string,
+ username: string,
+ channel: string,
+ topic?: Topic
+): string {
+ const env: BackendEnvironment = {
+ NEXT_PUBLIC_BACKEND_HOST: process.env.NEXT_PUBLIC_BACKEND_HOST || 'localhost',
+ NEXT_PUBLIC_BACKEND_PORT: process.env.NEXT_PUBLIC_BACKEND_PORT || '1323',
+ NEXT_PUBLIC_BACKEND_ENVIRONMENT: process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT as 'development' | 'production',
+ };
+
+ const protocol = env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development" ? "ws" : "wss";
+ const baseUrl = `${protocol}://${env.NEXT_PUBLIC_BACKEND_HOST}:${env.NEXT_PUBLIC_BACKEND_PORT}/chat`;
+
+ const params = new URLSearchParams({
+ channel: channel || "public",
+ name: username,
+ userID: userId || "0",
+ topic: topic || '',
+ });
+
+ return `${baseUrl}?${params.toString()}`;
+}
+
+
+// Function to build project URL
+export function projectURLbuildr(): string {
+ const env: BackendEnvironment = {
+ NEXT_PUBLIC_BACKEND_HOST: process.env.NEXT_PUBLIC_BACKEND_HOST || 'localhost',
+ NEXT_PUBLIC_BACKEND_PORT: process.env.NEXT_PUBLIC_BACKEND_PORT || '1323',
+ NEXT_PUBLIC_BACKEND_ENVIRONMENT: process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT as 'development' | 'production',
+ };
+
+ const protocol = env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development" ? "http" : "https";
+ const baseUrl = `${protocol}://${env.NEXT_PUBLIC_BACKEND_HOST}:${env.NEXT_PUBLIC_BACKEND_PORT}/projects`;
+
+ return baseUrl;
+}
+
+// Function to build leave chat URL
+export function leaveChatURLbuildr(userID: string): string {
+ const env: BackendEnvironment = {
+ NEXT_PUBLIC_BACKEND_HOST: process.env.NEXT_PUBLIC_BACKEND_HOST || 'localhost',
+ NEXT_PUBLIC_BACKEND_PORT: process.env.NEXT_PUBLIC_BACKEND_PORT || '1323',
+ NEXT_PUBLIC_BACKEND_ENVIRONMENT: process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT as 'development' | 'production',
+ };
+
+ const protocol = env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development" ? "http" : "https";
+ const baseUrl = `${protocol}://${env.NEXT_PUBLIC_BACKEND_HOST}:${env.NEXT_PUBLIC_BACKEND_PORT}/chat/leave`;
+
+ const params = new URLSearchParams({
+ userID: userID,
+ });
+
+ return `${baseUrl}?${params.toString()}`;
+}
+
+
+// Function to build subscribe URL
+export function subscribeURLbuildr(): string {
+ const env: BackendEnvironment = {
+ NEXT_PUBLIC_BACKEND_HOST: process.env.NEXT_PUBLIC_BACKEND_HOST || 'localhost',
+ NEXT_PUBLIC_BACKEND_PORT: process.env.NEXT_PUBLIC_BACKEND_PORT || '1323',
+ NEXT_PUBLIC_BACKEND_ENVIRONMENT: process.env.NEXT_PUBLIC_BACKEND_ENVIRONMENT as 'development' | 'production',
+ };
+
+ const protocol = env.NEXT_PUBLIC_BACKEND_ENVIRONMENT === "development" ? "http" : "https";
+ const baseUrl = `${protocol}://${env.NEXT_PUBLIC_BACKEND_HOST}:${env.NEXT_PUBLIC_BACKEND_PORT}/subscribe`;
+ return baseUrl;
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000..09d1f3f
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "noEmit": true,
+ "incremental": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve"
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx"
+, "context/userContext.ts" ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/frontend/utils/alerts/alertAbnormalClose.js b/frontend/utils/alerts/alertAbnormalClose.js
deleted file mode 100644
index 84d6ecd..0000000
--- a/frontend/utils/alerts/alertAbnormalClose.js
+++ /dev/null
@@ -1,23 +0,0 @@
-"use client";
-import Swal from "sweetalert2";
-
-export default function alertAbnormalClose(reason, navigateToLogin) {
- try {
- Swal.fire({
- title: "Connection lost",
- text: `Please try again or with a different username ${reason}`,
- icon: "warning",
-
- iconColor: "#3670F5",
- confirmButtonColor: "#3670F5",
- confirmButtonText: "OK",
- didOpen: (popup) => {
- popup.style.borderRadius = "1rem";
- },
- }).then((result) => {
- try {
- if (result.isConfirmed) navigateToLogin();
- } catch (error) {}
- });
- } catch (error) {}
- }
\ No newline at end of file
diff --git a/frontend/utils/alerts/alertAbnormalClose.ts b/frontend/utils/alerts/alertAbnormalClose.ts
new file mode 100644
index 0000000..48f33eb
--- /dev/null
+++ b/frontend/utils/alerts/alertAbnormalClose.ts
@@ -0,0 +1,32 @@
+"use client";
+import Swal from 'sweetalert2';
+import { Dispatch, SetStateAction } from 'react'; // Import types for state setters
+import { AlertAbnormalCloseProps } from '../../interface/interface';
+import { navigateToLogin } from '../websocket/handleWebSocketClose';
+
+const alertAbnormalClose: AlertAbnormalCloseProps = async (reason, navigate) => { // Make the function async
+ try {
+ const result = await Swal.fire({ // Corrected line
+ title: "Connection lost",
+ text: `Please try again or with a different username. Reason: ${reason}`,
+ icon: "warning",
+ iconColor: "#3670F5",
+ confirmButtonColor: "#3670F5",
+ confirmButtonText: "OK",
+ didOpen: (popup: HTMLElement) => {
+ popup.style.borderRadius = "1rem";
+ },
+ });
+
+ if (result.isConfirmed) {
+ navigate("/");
+ }
+ } catch (error) {
+ // More specific error handling here, e.g., log the error
+ console.error("Error in SweetAlert:", error);
+ }
+
+ return null; // This component doesn't render anything directly, it's just a function
+};
+
+export default alertAbnormalClose;
diff --git a/frontend/utils/alerts/alertBadRequest.js b/frontend/utils/alerts/alertBadRequest.ts
similarity index 56%
rename from frontend/utils/alerts/alertBadRequest.js
rename to frontend/utils/alerts/alertBadRequest.ts
index 8f1d643..f003dd5 100644
--- a/frontend/utils/alerts/alertBadRequest.js
+++ b/frontend/utils/alerts/alertBadRequest.ts
@@ -1,7 +1,8 @@
"use client";
import Swal from "sweetalert2";
-
-export default function alertBadRequest(reason, navigateToLogin) {
+import { navigateToLogin } from "../websocket/handleWebSocketClose";
+import { AlertBadRequestProps } from "../../interface/interface";
+const alertBadRequest: AlertBadRequestProps = (reason, navigate) => {
try {
Swal.fire({
title: "Bad request",
@@ -11,13 +12,15 @@ export default function alertBadRequest(reason, navigateToLogin) {
iconColor: "#3670F5",
confirmButtonColor: "#3670F5",
confirmButtonText: "OK",
- didOpen: (popup) => {
+ didOpen: (popup: HTMLElement) => {
popup.style.borderRadius = "1rem";
},
}).then((result) => {
try {
- if (result.isConfirmed) navigateToLogin();
+ if (result.isConfirmed) navigate("/");
} catch (error) {}
});
} catch (error) {}
- }
\ No newline at end of file
+ }
+
+ export default alertBadRequest;
\ No newline at end of file
diff --git a/frontend/utils/alerts/alertBannedUser.js b/frontend/utils/alerts/alertBannedUser.ts
similarity index 59%
rename from frontend/utils/alerts/alertBannedUser.js
rename to frontend/utils/alerts/alertBannedUser.ts
index ac5adb6..27af9a4 100644
--- a/frontend/utils/alerts/alertBannedUser.js
+++ b/frontend/utils/alerts/alertBannedUser.ts
@@ -1,7 +1,8 @@
"use client";
import Swal from "sweetalert2";
-
-export default function alertBannedUser(reason, navigateToLogin) {
+import {NavigateFunction , AlertBannedUser } from "../../interface/interface";
+import { navigateToLogin } from "../websocket/handleWebSocketClose";
+const alertBannedUser: AlertBannedUser = (reason, navigate) => {
try {
Swal.fire({
title: "You have been banned",
@@ -11,7 +12,7 @@ export default function alertBannedUser(reason, navigateToLogin) {
confirmButtonColor: "#3670F5",
iconColor: "#3670F5",
confirmButtonText: "OK",
- didOpen: (popup) => {
+ didOpen: (popup: HTMLElement) => {
popup.style.borderRadius = "1rem";
},
customClass: {
@@ -19,8 +20,10 @@ export default function alertBannedUser(reason, navigateToLogin) {
},
}).then((result) => {
try {
- if (result.isConfirmed) navigateToLogin();
+ if (result.isConfirmed) navigate("/");
} catch (error) {}
});
} catch (error) {}
- }
\ No newline at end of file
+ }
+
+export default alertBannedUser;
\ No newline at end of file
diff --git a/frontend/utils/alerts/alertSameUsername.js b/frontend/utils/alerts/alertSameUsername.ts
similarity index 59%
rename from frontend/utils/alerts/alertSameUsername.js
rename to frontend/utils/alerts/alertSameUsername.ts
index 6f0fc5d..c2decae 100644
--- a/frontend/utils/alerts/alertSameUsername.js
+++ b/frontend/utils/alerts/alertSameUsername.ts
@@ -1,7 +1,8 @@
"use client";
import Swal from "sweetalert2";
-
-export default function alertSameUsername(reason, navigateToLogin) {
+import { navigateToLogin } from "../websocket/handleWebSocketClose";
+import {AlertSameUserProps } from "../../interface/interface";
+const alertSameUsername: AlertSameUserProps = (reason, navigate) => {
try {
Swal.fire({
title: "Username already exists",
@@ -12,13 +13,15 @@ export default function alertSameUsername(reason, navigateToLogin) {
imageAlt: "Username Taken",
confirmButtonColor: "#3670F5",
confirmButtonText: "OK",
- didOpen: (popup) => {
+ didOpen: (popup:HTMLElement) => {
popup.style.borderRadius = "1rem";
},
}).then((result) => {
try {
- if (result.isConfirmed) navigateToLogin();
+ if (result.isConfirmed) navigate("/");
} catch (error) {}
});
} catch (error) {}
- }
\ No newline at end of file
+ }
+
+ export default alertSameUsername;
\ No newline at end of file
diff --git a/frontend/utils/alerts/alertServerError.js b/frontend/utils/alerts/alertServerError.ts
similarity index 56%
rename from frontend/utils/alerts/alertServerError.js
rename to frontend/utils/alerts/alertServerError.ts
index 981350f..338b400 100644
--- a/frontend/utils/alerts/alertServerError.js
+++ b/frontend/utils/alerts/alertServerError.ts
@@ -1,7 +1,8 @@
"use client";
import Swal from "sweetalert2";
-
-export default function alertServerError(reason, navigateToLogin) {
+import { AlertServerErrorProps } from "../../interface/interface";
+import { navigateToLogin } from "../websocket/handleWebSocketClose";
+const alertServerError: AlertServerErrorProps = (reason, navigate) => {
try {
Swal.fire({
title: "Server error",
@@ -11,13 +12,15 @@ export default function alertServerError(reason, navigateToLogin) {
iconColor: "#3670F5",
confirmButtonColor: "#3670F5",
confirmButtonText: "OK",
- didOpen: (popup) => {
+ didOpen: (popup:HTMLElement) => {
popup.style.borderRadius = "1rem";
},
}).then((result) => {
try {
- if (result.isConfirmed) navigateToLogin();
+ if (result.isConfirmed) navigate("/");
} catch (error) {}
});
} catch (error) {}
- }
\ No newline at end of file
+ }
+
+ export default alertServerError;
\ No newline at end of file
diff --git a/frontend/utils/alerts/checkAndPromptSessionChange.js b/frontend/utils/alerts/checkAndPromptSessionChange.js
deleted file mode 100644
index ca356ba..0000000
--- a/frontend/utils/alerts/checkAndPromptSessionChange.js
+++ /dev/null
@@ -1,49 +0,0 @@
-"use client";
-import Swal from "sweetalert2";
-
-export default async function checkAndPromptSessionChange(
- currentUsername,
- inputUsername,
- onConfirm,
- ) {
- if (currentUsername && currentUsername !== inputUsername) {
- try {
- const result = await Swal.fire({
- title: "Change Username?",
- text: `You already have a running session with the username "${currentUsername}". Do you want to change your username?`,
- icon: "question",
- iconColor: "#3670F5",
-
- showCancelButton: true,
- confirmButtonColor: "3670F5",
- cancelButtonColor: "#d33",
- confirmButtonText: "Yes, change it!",
- });
-
- if (result.isConfirmed && inputUsername.length < 20) {
- onConfirm();
- return true;
- } else {
- if (inputUsername.length > 20) {
- Swal.fire({
- title: "Username too long",
- text: `Please choose a username with less than 20 characters`,
- icon: "warning",
-
- iconColor: "#3670F5",
- confirmButtonColor: "3670F5",
- confirmButtonText: "OK",
- didOpen: (popup) => {
- popup.style.borderRadius = "1rem";
- },
- });
- }
- return false;
- }
- } catch (error) {
- console.error("Error with SweetAlert2:", error);
- return false;
- }
- }
- return false;
- }
\ No newline at end of file
diff --git a/frontend/utils/alerts/checkAndPromptSessionChange.ts b/frontend/utils/alerts/checkAndPromptSessionChange.ts
new file mode 100644
index 0000000..05a8a7b
--- /dev/null
+++ b/frontend/utils/alerts/checkAndPromptSessionChange.ts
@@ -0,0 +1,58 @@
+"use client";
+import Swal, { SweetAlertResult } from "sweetalert2";
+
+// Interface for the function parameters
+interface CheckAndPromptSessionChangeProps {
+ currentUser: string | null;
+ username: string | null;
+ onConfirm: () => void;
+}
+
+// Function to check and prompt session change using SweetAlert2
+export default async function checkAndPromptSessionChange({
+ currentUser,
+ username,
+ onConfirm,
+}: CheckAndPromptSessionChangeProps): Promise {
+ // Check if the current username exists and differs from the input username
+ if (currentUser && currentUser !== username) {
+ try {
+ const result: SweetAlertResult = await Swal.fire({
+ title: "Change Username?",
+ text: `You already have a running session with the username "${currentUser}". Do you want to change your username?`,
+ icon: "question",
+ iconColor: "#3670F5",
+ showCancelButton: true,
+ confirmButtonColor: "#3670F5",
+ cancelButtonColor: "#d33",
+ confirmButtonText: "Yes, change it!",
+ });
+
+ // If the user confirms and the new username is less than 20 characters
+ if (result.isConfirmed && username.length < 20) {
+ onConfirm();
+ return true;
+ } else {
+ // If the input username exceeds the length limit, display a warning
+ if (username.length > 20) {
+ Swal.fire({
+ title: "Username too long",
+ text: `Please choose a username with less than 20 characters`,
+ icon: "warning",
+ iconColor: "#3670F5",
+ confirmButtonColor: "#3670F5",
+ confirmButtonText: "OK",
+ didOpen: (popup: HTMLElement) => {
+ popup.style.borderRadius = "1rem";
+ },
+ });
+ }
+ return false;
+ }
+ } catch (error) {
+ console.error("Error with SweetAlert2:", error);
+ return false;
+ }
+ }
+ return false;
+}
diff --git a/frontend/utils/chatbot_formatting/formatChatbotUserText.js b/frontend/utils/chatbot_formatting/formatChatbotUserText.ts
similarity index 50%
rename from frontend/utils/chatbot_formatting/formatChatbotUserText.js
rename to frontend/utils/chatbot_formatting/formatChatbotUserText.ts
index 93fb920..6d6e5de 100644
--- a/frontend/utils/chatbot_formatting/formatChatbotUserText.js
+++ b/frontend/utils/chatbot_formatting/formatChatbotUserText.ts
@@ -1,4 +1,4 @@
-export default function formatChatbotUserText(message) {
+export default function formatChatbotUserText(message: string): string | undefined {
if (message.split(":")[0] === "You") {
return message.split(":")[1];
}
diff --git a/frontend/utils/chatbot_formatting/getIsSentForChatBot.js b/frontend/utils/chatbot_formatting/getIsSentForChatBot.ts
similarity index 59%
rename from frontend/utils/chatbot_formatting/getIsSentForChatBot.js
rename to frontend/utils/chatbot_formatting/getIsSentForChatBot.ts
index 41ec026..e57ddbc 100644
--- a/frontend/utils/chatbot_formatting/getIsSentForChatBot.js
+++ b/frontend/utils/chatbot_formatting/getIsSentForChatBot.ts
@@ -1,4 +1,4 @@
-export default function getIsSentForChatBot(message) {
+export default function getIsSentForChatBot(message: string): boolean {
if (message.split(":")[0] === "You") {
return true;
} else {
diff --git a/frontend/utils/chatbot_formatting/parseMessageText.jsx b/frontend/utils/chatbot_formatting/parseMessageText.tsx
similarity index 73%
rename from frontend/utils/chatbot_formatting/parseMessageText.jsx
rename to frontend/utils/chatbot_formatting/parseMessageText.tsx
index 5b64f54..0235889 100644
--- a/frontend/utils/chatbot_formatting/parseMessageText.jsx
+++ b/frontend/utils/chatbot_formatting/parseMessageText.tsx
@@ -1,17 +1,18 @@
"use client";
+import React from "react";
-export default function parseMessageText(text) {
- const replaceURLs = (message) => {
+export default function parseMessageText(text: string): JSX.Element {
+ const replaceURLs = (message: string): string => {
const urlRegex =
/(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gi;
return message.replace(urlRegex, (url) => `${url} `);
};
- const replaceBoldText = (message) => {
+ const replaceBoldText = (message: string): string => {
return message.replace(/\*\*(.*?)\*\*/g, "$1 ");
};
- const replaceCodeText = (message) => {
+ const replaceCodeText = (message: string): string => {
return message.replace(
/`(.*?)`/g,
'$1
',
diff --git a/frontend/utils/getTimestampFromDate.js b/frontend/utils/getTimestampFromDate.js
deleted file mode 100644
index 67afced..0000000
--- a/frontend/utils/getTimestampFromDate.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default function getTimestampFromDate(dateString) {
- const date = new Date(dateString);
- const timestamp = Math.floor(date.getTime() / 1000);
- return timestamp;
- }
\ No newline at end of file
diff --git a/frontend/utils/getTimestampFromDate.ts b/frontend/utils/getTimestampFromDate.ts
new file mode 100644
index 0000000..e46f260
--- /dev/null
+++ b/frontend/utils/getTimestampFromDate.ts
@@ -0,0 +1,5 @@
+export default function getTimestampFromDate(dateString: string): number {
+ const date = new Date(dateString);
+ const timestamp = Math.floor(date.getTime() / 1000);
+ return timestamp;
+}
diff --git a/frontend/utils/playSound.js b/frontend/utils/playSound.ts
similarity index 69%
rename from frontend/utils/playSound.js
rename to frontend/utils/playSound.ts
index fba1100..08bb7d8 100644
--- a/frontend/utils/playSound.js
+++ b/frontend/utils/playSound.ts
@@ -2,13 +2,14 @@ import notif from "../assets/sounds/notif.mp3";
import notifRecieve from "../assets/sounds/notif-recieve.mp3";
import { useCallback } from "react";
-const playSound = (soundEnabled)=>{
- useCallback(
- (isSent) => {
+const playSound = (soundEnabled: boolean): void => {
+ useCallback(
+ (isSent: boolean) => {
const sound = isSent ? new Audio(notif) : new Audio(notifRecieve);
sound.play();
},
[soundEnabled],
);
}
-export default playSound;
\ No newline at end of file
+
+export default playSound;
diff --git a/frontend/utils/session/getAvatar.js b/frontend/utils/session/getAvatar.js
deleted file mode 100644
index 3590906..0000000
--- a/frontend/utils/session/getAvatar.js
+++ /dev/null
@@ -1,10 +0,0 @@
-"use client";
-
-export default async function getAvatar() {
- let avatarId = sessionStorage.getItem("avatarId");
- if (avatarId == null || isNaN(avatarId) || avatarId < 0 || avatarId > 14) {
- avatarId = Math.floor(Math.random() * 15);
- sessionStorage.setItem("avatarId", avatarId);
- }
- return avatarId;
- }
\ No newline at end of file
diff --git a/frontend/utils/session/getAvatar.ts b/frontend/utils/session/getAvatar.ts
new file mode 100644
index 0000000..74b6e57
--- /dev/null
+++ b/frontend/utils/session/getAvatar.ts
@@ -0,0 +1,15 @@
+"use client";
+
+export default async function getAvatar() {
+
+ let avatarIdString = sessionStorage.getItem("avatarId");
+ let avatarId: number;
+
+ if (avatarIdString === null || isNaN(Number(avatarIdString)) || Number(avatarIdString) < 0 || Number(avatarIdString) > 14) {
+ avatarId = Math.floor(Math.random() * 15);
+ sessionStorage.setItem("avatarId", avatarId.toString());
+ } else {
+ avatarId = Number(avatarIdString);
+ }
+ return avatarId;
+ }
\ No newline at end of file
diff --git a/frontend/utils/session/getSessionUser.js b/frontend/utils/session/getSessionUser.ts
similarity index 100%
rename from frontend/utils/session/getSessionUser.js
rename to frontend/utils/session/getSessionUser.ts
diff --git a/frontend/utils/session/getSessionUserId.js b/frontend/utils/session/getSessionUserId.ts
similarity index 100%
rename from frontend/utils/session/getSessionUserId.js
rename to frontend/utils/session/getSessionUserId.ts
diff --git a/frontend/utils/session/removeSessionUserId.js b/frontend/utils/session/removeSessionUserId.ts
similarity index 52%
rename from frontend/utils/session/removeSessionUserId.js
rename to frontend/utils/session/removeSessionUserId.ts
index 24c9730..517de64 100644
--- a/frontend/utils/session/removeSessionUserId.js
+++ b/frontend/utils/session/removeSessionUserId.ts
@@ -1,5 +1,5 @@
"use client";
-export default function removeSessionUserId() {
+export default function removeSessionUserId():void {
sessionStorage.removeItem("userID");
}
\ No newline at end of file
diff --git a/frontend/utils/session/setSessionUser.js b/frontend/utils/session/setSessionUser.ts
similarity index 100%
rename from frontend/utils/session/setSessionUser.js
rename to frontend/utils/session/setSessionUser.ts
diff --git a/frontend/utils/websocket/handleWebSocketClose.js b/frontend/utils/websocket/handleWebSocketClose.js
deleted file mode 100644
index 2880c73..0000000
--- a/frontend/utils/websocket/handleWebSocketClose.js
+++ /dev/null
@@ -1,31 +0,0 @@
-"use client";
-import {
- isUserBanned,
- isSameUsername,
- isBadRequest,
- isServerError,
- isAbnormalClose,
- } from "./websocketHelpers";
-import alertBannedUser from "../alerts/alertBannedUser";
-import alertSameUsername from "../alerts/alertSameUsername";
-import alertBadRequest from "../alerts/alertBadRequest";
-import alertServerError from "../alerts/alertServerError";
-import alertAbnormalClose from "../alerts/alertAbnormalClose";
-
-export default function handleWebSocketClose(event, navigateToLogin) {
- if (isUserBanned(event.code)) {
- alertBannedUser(event.reason, navigateToLogin);
- }
- if (isSameUsername(event.code)) {
- alertSameUsername(event.reason, navigateToLogin);
- }
- if (isBadRequest(event.code)) {
- alertBadRequest(event.reason, navigateToLogin);
- }
- if (isServerError(event.code)) {
- alertServerError(event.reason, navigateToLogin);
- }
- if (isAbnormalClose(event.code)) {
- alertAbnormalClose(event.reason, navigateToLogin);
- }
- }
\ No newline at end of file
diff --git a/frontend/utils/websocket/handleWebSocketClose.ts b/frontend/utils/websocket/handleWebSocketClose.ts
new file mode 100644
index 0000000..bd85615
--- /dev/null
+++ b/frontend/utils/websocket/handleWebSocketClose.ts
@@ -0,0 +1,49 @@
+"use client";
+import {
+ isUserBanned,
+ isSameUsername,
+ isBadRequest,
+ isServerError,
+ isAbnormalClose,
+} from "./websocketHelpers";
+import alertBannedUser from "../alerts/alertBannedUser";
+import alertSameUsername from "../alerts/alertSameUsername"; // Ensure all alert imports are correctly defined
+import alertBadRequest from "../alerts/alertBadRequest";
+import alertServerError from "../alerts/alertServerError";
+import alertAbnormalClose from "../alerts/alertAbnormalClose";
+import {useNavigate} from "react-router-dom";
+// Define the type for the WebSocket close event
+interface WebSocketCloseEvent {
+ code: number;
+ reason: string;
+}
+
+
+export function navigateToLogin(): void {
+ const navigate = useNavigate(); // Import the useNavigate hook from react-router-dom
+ navigate("/"); // Adjust the path as needed
+}
+
+// Define the type for the navigate function
+type NavigateFunction = (path: string) => void;
+
+export default function handleWebSocketClose(
+ event: WebSocketCloseEvent,
+ navigateToLogin: NavigateFunction
+): void {
+ if (isUserBanned(event.code)) {
+ alertBannedUser(event.reason, navigateToLogin);
+ }
+ if (isSameUsername(event.code)) {
+ alertSameUsername(event.reason, navigateToLogin);
+ }
+ if (isBadRequest(event.code)) {
+ alertBadRequest(event.reason, navigateToLogin);
+ }
+ if (isServerError(event.code)) {
+ alertServerError(event.reason, navigateToLogin);
+ }
+ if (isAbnormalClose(event.code)) {
+ alertAbnormalClose(event.reason, navigateToLogin);
+ }
+}
diff --git a/frontend/utils/websocket/handleWebSocketError.js b/frontend/utils/websocket/handleWebSocketError.ts
similarity index 100%
rename from frontend/utils/websocket/handleWebSocketError.js
rename to frontend/utils/websocket/handleWebSocketError.ts
diff --git a/frontend/utils/websocket/processWebSocketMessage.js b/frontend/utils/websocket/processWebSocketMessage.ts
similarity index 53%
rename from frontend/utils/websocket/processWebSocketMessage.js
rename to frontend/utils/websocket/processWebSocketMessage.ts
index 5438017..5938093 100644
--- a/frontend/utils/websocket/processWebSocketMessage.js
+++ b/frontend/utils/websocket/processWebSocketMessage.ts
@@ -1,14 +1,11 @@
"use client";
+import { DataFromServer, Message, ProcessWebSocketMessageProps } from "../../interface/interface";
import alertBannedUser from "../alerts/alertBannedUser";
export default function processWebSocketMessage(
- event,
- setMessages,
- navigateToLogin,
- isChatbot,
- ) {
+{ event, setMessages, navigateToLogin, isChatbot }: ProcessWebSocketMessageProps) {
if (isChatbot) {
- const userIdRegex = /\buserID\b/;
+ const userIdRegex = /\buserID\b/;
if (userIdRegex.test(event.data)) {
handleUserID(JSON.parse(event.data));
}
@@ -36,25 +33,23 @@ export default function processWebSocketMessage(
console.error("Error parsing or handling the message:", error);
}
- function handleUserID(data) {
+ function handleUserID(data: DataFromServer) {
if (data.userID) {
- sessionStorage.setItem("userID", data.userID);
+ sessionStorage.setItem("userID", data.userID);
}
- }
-
- function handleBannedUser(data, navigateToLogin) {
+ }
+
+ function handleBannedUser(data: DataFromServer, navigateToLogin: () => void) {
if (data.Message && data.Message === "You are banned now") {
- alertBannedUser(data.Message);
- navigateToLogin();
- return true;
+ alertBannedUser(data.Message,navigateToLogin);
+ navigateToLogin();
+ return true;
}
return false;
- }
-
- function handleDeleteMessage(data, setMessages) {
- const deleteTimestamp = parseFloat(data.Delete);
- setMessages((prevMessages) =>
- prevMessages.filter((message) => message.timestamp !== deleteTimestamp),
- );
- }
- }
\ No newline at end of file
+ }
+
+ function handleDeleteMessage(data: DataFromServer, setMessages: React.Dispatch>) {
+ const deleteTimestamp = parseFloat(data.Delete!);
+ setMessages((prevMessages) => prevMessages.filter((message) => message.timestamp !== deleteTimestamp));
+ }
+}
\ No newline at end of file
diff --git a/frontend/utils/websocket/websocketHelpers.js b/frontend/utils/websocket/websocketHelpers.ts
similarity index 100%
rename from frontend/utils/websocket/websocketHelpers.js
rename to frontend/utils/websocket/websocketHelpers.ts
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..10a3adc
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,42 @@
+{
+ "name": "echofy",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e1f4bc9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,5 @@
+{
+ "dependencies": {
+ "react": "^18.3.1"
+ }
+}
diff --git a/semantic.yml b/semantic.yml
new file mode 100644
index 0000000..e69de29