You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ChatList.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import React, { useState, useEffect, useContext } from "react";
  2. import { useDispatch, useSelector } from "react-redux";
  3. import { Form, InputGroup } from "react-bootstrap";
  4. import { UserContext } from "../contexts/userContext";
  5. import { FiPlus } from "react-icons/fi";
  6. import { FaSignInAlt } from "react-icons/fa";
  7. import { GiSandsOfTime } from "react-icons/gi";
  8. import { CgEnter } from "react-icons/cg";
  9. import Dialog from "./UI/Dialog";
  10. import {
  11. chatActions,
  12. fetchChatRoomsAsync,
  13. fetchSupportRoomsAsync,
  14. } from "../store/chat-slice";
  15. import { createChatRoomAsync } from "../store/chat-slice";
  16. import { createJoinRequestAsync, requestActions } from "../store/request-slice";
  17. import { fetchRequestsAsync } from "../store/request-slice";
  18. import { HubConnectionBuilder } from "@microsoft/signalr";
  19. import { loadNotifications } from "../store/chat-slice";
  20. import { readNotificationsAsync } from "../store/chat-slice";
  21. // Ovde ce biti dostupne grupe i razgovori
  22. const ChatList = () => {
  23. const [createChat, setCreateChat] = useState(false);
  24. const [chatName, setChatName] = useState("");
  25. const [requestedRooms, setRequestedRooms] = useState([]);
  26. const {
  27. rooms,
  28. status: chatStatus,
  29. error,
  30. notifications,
  31. activeRoom,
  32. } = useSelector((state) => state.chat);
  33. const {
  34. chosenRoom,
  35. status: requestsStatus,
  36. requests,
  37. } = useSelector((state) => state.requests);
  38. const [showModal, setShowModal] = useState(false);
  39. const [loadedNotification, setLoadedNotification] = useState(false);
  40. const { user } = useContext(UserContext);
  41. const [myConnection, setMyConnection] = useState(null);
  42. const [chatMessage, setChatMessage] = useState(null);
  43. const [notificationRoom, setNotificationRoom] = useState(null);
  44. const dispatch = useDispatch();
  45. useEffect(() => {
  46. if (user && !loadedNotification) {
  47. dispatch(loadNotifications(user.id));
  48. setLoadedNotification((oldState) => !oldState);
  49. }
  50. }, [user, dispatch, loadedNotification]);
  51. // Maybe don't work
  52. useEffect(() => {
  53. user !== null && user.roles.includes("Support")
  54. ? dispatch(fetchSupportRoomsAsync(user.id))
  55. : user !== null && dispatch(fetchChatRoomsAsync(user.id));
  56. dispatch(fetchRequestsAsync());
  57. }, [dispatch]);
  58. useEffect(() => {
  59. if (requests && rooms) {
  60. const userRequests = requests
  61. .filter((request) => request.senderId === user.id)
  62. .map((request) => request.roomId);
  63. setRequestedRooms(userRequests);
  64. }
  65. }, [requests, rooms, user]);
  66. const addChatSubmitHandler = (e) => {
  67. e.preventDefault();
  68. alert(`Chat ${chatName} has been created`);
  69. dispatch(createChatRoomAsync({ name: chatName, createdBy: user.id }));
  70. setCreateChat(false);
  71. setChatName("");
  72. };
  73. const showRoomMessagesHandler = (n) => {
  74. dispatch(chatActions.readNotifications(n.id));
  75. dispatch(chatActions.setRoom(n));
  76. };
  77. const joinRoom = async (n) => {
  78. try {
  79. const connection = new HubConnectionBuilder()
  80. .withUrl("http://localhost:5116/chatHub", {
  81. accessTokenFactory: () => user.token,
  82. })
  83. .withAutomaticReconnect()
  84. .build();
  85. connection.on("ReceiveMessage", (data) => {
  86. // When user enter room first time after login, generated Context.ConnectionId will be saved in redux
  87. if (data.connId) {
  88. dispatch(
  89. chatActions.saveContextId({ connId: data.connId, userId: user.id })
  90. );
  91. setChatMessage(data);
  92. }
  93. });
  94. connection.on("ViewTyping", (data) => {
  95. dispatch(chatActions.addTyping(data));
  96. });
  97. connection.on("ReceiveNotifications", (userId, roomId) => {
  98. if (user.id !== userId) {
  99. dispatch(chatActions.addNotification(roomId));
  100. setNotificationRoom(roomId);
  101. }
  102. });
  103. // When user changed room, array with messages from previous room will be deleted from redux
  104. dispatch(chatActions.newMessage({ changedRoom: true }));
  105. connection.onclose((e) => {
  106. // On close connection
  107. });
  108. await connection.start();
  109. await connection.invoke("JoinRoom", {
  110. userId: user.id,
  111. username: user.username,
  112. roomId: n.id,
  113. });
  114. dispatch(chatActions.setRoom(n));
  115. dispatch(chatActions.setConnection(connection));
  116. setMyConnection(connection);
  117. } catch (e) {
  118. console.log(e);
  119. }
  120. };
  121. // Maybe don't work
  122. useEffect(() => {
  123. if (myConnection) {
  124. myConnection.on("ReceiveMessage", (data) => {
  125. // When user enter room first time after login, generated Context.ConnectionId will be saved in redux
  126. if (data.connId) {
  127. dispatch(
  128. chatActions.saveContextId({ connId: data.connId, userId: user.id })
  129. );
  130. }
  131. setChatMessage(data);
  132. });
  133. }
  134. }, [myConnection, dispatch]);
  135. // Maybe don't work
  136. useEffect(() => {
  137. if (chatMessage && activeRoom.id === chatMessage.roomId) {
  138. dispatch(
  139. chatActions.newMessage({
  140. content: chatMessage.message,
  141. createdAtUtc: new Date(),
  142. deletedAtUtc: null,
  143. id: null,
  144. senderId: chatMessage.userId,
  145. updatedAtUtc: null,
  146. username: user.username,
  147. isAccessMessage: chatMessage.isAccessMessage,
  148. })
  149. );
  150. }
  151. }, [chatMessage, dispatch]);
  152. // Maybe don't work
  153. useEffect(() => {
  154. if (notificationRoom && activeRoom) {
  155. if (notificationRoom === activeRoom.id) {
  156. dispatch(chatActions.readNotifications(activeRoom.id));
  157. dispatch(
  158. readNotificationsAsync({ userId: user.id, roomId: activeRoom.id })
  159. );
  160. }
  161. }
  162. setNotificationRoom(null);
  163. }, [notificationRoom, dispatch]);
  164. const openModal = (n) => {
  165. setShowModal(true);
  166. dispatch(requestActions.chooseRoom(n));
  167. };
  168. const dialogHandler = () => {
  169. dispatch(
  170. createJoinRequestAsync({
  171. senderId: user.id,
  172. senderUsername: user.username,
  173. roomId: chosenRoom.id,
  174. roomName: chosenRoom.name,
  175. })
  176. );
  177. };
  178. const notificationCounter = (room) => {
  179. for (let i = 0; i < notifications.length; i++) {
  180. if (notifications[i].roomId === room.id) {
  181. return notifications[i].notificationCount;
  182. }
  183. }
  184. return null;
  185. };
  186. useEffect(() => {
  187. if (requestsStatus === "idle") {
  188. setShowModal(false);
  189. }
  190. }, [requestsStatus]);
  191. const getView = () => {
  192. let acceptedRequests = [];
  193. let pendingRequests = [];
  194. let availableRequests = [];
  195. for (let i = 0; i < rooms.length; i++) {
  196. if (rooms[i].customers.some((x) => x.customerId === user.id)) {
  197. acceptedRequests.push(rooms[i]);
  198. } else {
  199. if (requestedRooms.includes(rooms[i].id)) {
  200. pendingRequests.push(rooms[i]);
  201. } else {
  202. availableRequests.push(rooms[i]);
  203. }
  204. }
  205. }
  206. return user !== null && user.roles.includes("Support") ? (
  207. <div>
  208. {rooms.map((room, index) => (
  209. <div
  210. className="border-bottom roomsBtn d-flex bg-light"
  211. key={index}
  212. onClick={() => joinRoom(room)}
  213. >
  214. <button
  215. className="text-start w-100 py-2 px-3 btn btn-light h-100"
  216. onClick={showRoomMessagesHandler.bind(this, room)}
  217. >
  218. {room.name}
  219. </button>
  220. </div>
  221. ))}
  222. </div>
  223. ) : (
  224. <div>
  225. {acceptedRequests.length > 0 && (
  226. <div>
  227. <h5 className="text-start w-100 ps-3 text-light py-2 pt-3">
  228. Accepted Rooms
  229. </h5>
  230. {acceptedRequests.map((n, index) => (
  231. <div
  232. className="border-bottom roomsBtn d-flex bg-light"
  233. key={index}
  234. onClick={() => joinRoom(n)}
  235. >
  236. {notificationCounter(n) && (
  237. <div className="notification rounded-circle my-auto ms-3">
  238. {notificationCounter(n)}
  239. </div>
  240. )}
  241. <button
  242. className="text-start w-100 py-2 px-3 btn btn-light h-100"
  243. onClick={showRoomMessagesHandler.bind(this, n)}
  244. >
  245. {n.name}
  246. </button>
  247. <button className="btn btn-light">
  248. <CgEnter />
  249. </button>
  250. </div>
  251. ))}
  252. </div>
  253. )}
  254. {pendingRequests.length > 0 && (
  255. <div>
  256. <h5 className="text-start w-100 ps-3 text-light py-2 pt-3">
  257. Pending Requests
  258. </h5>
  259. {pendingRequests.map((n, index) => (
  260. <div
  261. className="border-bottom roomsBtn bg-light d-flex"
  262. key={index}
  263. >
  264. <button className="text-start w-100 py-2 px-3 btn btn-light h-100">
  265. {n.name}
  266. </button>
  267. <button className="btn btn-light">
  268. <GiSandsOfTime />
  269. </button>
  270. </div>
  271. ))}
  272. </div>
  273. )}
  274. {availableRequests.length > 0 && (
  275. <div>
  276. <h5 className="text-start w-100 ps-3 text-light py-2 pt-3">
  277. Available Rooms
  278. </h5>
  279. {availableRequests.map((n, index) => (
  280. <div
  281. onClick={(e) => openModal(n)}
  282. className="border-bottom roomsBtn bg-light d-flex"
  283. key={index}
  284. >
  285. <button className="text-start w-100 py-2 px-3 btn btn-light h-100">
  286. {n.name}
  287. </button>
  288. <button className="btn btn-light">
  289. <FaSignInAlt />
  290. </button>
  291. </div>
  292. ))}
  293. </div>
  294. )}
  295. </div>
  296. );
  297. };
  298. return (
  299. <>
  300. <Dialog
  301. changeModalVisibility={() => setShowModal(false)}
  302. open={showModal}
  303. acceptHandler={dialogHandler}
  304. />
  305. <div className="p-0 h-100-auto-overflow overflow-x">
  306. <div className="pe-4 bg-transparent w-100 mb-3 p-0 border-bottom border-light d-flex justify-content-between align-items-center">
  307. <h4 className="p-0 m-0 py-3 w-100 text-start ps-3 text-light bg-transparent">
  308. Chat Rooms
  309. </h4>
  310. {user?.roles[0] === "Support" && (
  311. <button
  312. className="btn p-0 m-0 btn-light pt-0 mt-0 px-3 pb-2 pt-1"
  313. onClick={(e) => setCreateChat(true)}
  314. >
  315. <FiPlus className="m-0 p-0" />
  316. </button>
  317. )}
  318. </div>
  319. {createChat && (
  320. <div className="w-100 d-flex align-items-center justify-content-center pb-3 border-bottom">
  321. <Form onSubmit={addChatSubmitHandler} className="w-100">
  322. <InputGroup className="w-100 px-2">
  323. <input
  324. type="text"
  325. className="border-1 px-2 w-75"
  326. value={chatName}
  327. onChange={(e) => setChatName(e.target.value)}
  328. />
  329. <input
  330. type="submit"
  331. className="w-25 btn btn-dark"
  332. value={"Add"}
  333. />
  334. </InputGroup>
  335. </Form>
  336. </div>
  337. )}
  338. {/* ovo ce biti zamenjeno konkretnim podacima */}
  339. {(!error && requestedRooms && chatStatus === "pendingFetchRooms") ||
  340. chatStatus === "pendingAddRoom" ? (
  341. <div className="spinner-border mt-3" role="status">
  342. <span className="visually-hidden">Loading...</span>
  343. </div>
  344. ) : (
  345. getView()
  346. )}
  347. {error && <p>REJECTED</p>}
  348. <div className="pt-5"></div>
  349. </div>
  350. </>
  351. );
  352. };
  353. export default ChatList;