| if (notification == null) | if (notification == null) | ||||
| { | { | ||||
| return false; | |||||
| return true; | |||||
| } | } | ||||
| user.Notifications.Remove(notification); | user.Notifications.Remove(notification); |
| using Diligent.WebAPI.Host.Mediator.Notifications.Queries; | |||||
| using Diligent.WebAPI.Host.DTOs.Notification; | |||||
| using Diligent.WebAPI.Host.Mediator.Notifications.Commands; | |||||
| using Diligent.WebAPI.Host.Mediator.Notifications.Queries; | |||||
| using MediatR; | using MediatR; | ||||
| using Microsoft.AspNetCore.Mvc; | using Microsoft.AspNetCore.Mvc; | ||||
| [HttpGet("{id}")] | [HttpGet("{id}")] | ||||
| public async Task<IActionResult> GetNotifications([FromRoute] string id) => | public async Task<IActionResult> GetNotifications([FromRoute] string id) => | ||||
| Ok(await _mediator.Send(new GetNotificationsQuery(id))); | Ok(await _mediator.Send(new GetNotificationsQuery(id))); | ||||
| [HttpPost] | |||||
| public async Task<IActionResult> ReadNotifications([FromBody] NotificationDeleteDTO userConnection) => | |||||
| Ok(await _mediator.Send(new DeleteNotificationCommand(userConnection))); | |||||
| } | } | ||||
| } | } |
| _mediator = mediator; | _mediator = mediator; | ||||
| } | } | ||||
| //public override Task OnDisconnectedAsync(Exception? exception) | |||||
| //{ | |||||
| // if (_connections.TryGetValue(Context.ConnectionId, out UserConnection room)) | |||||
| // { | |||||
| // _connections.Remove(Context.ConnectionId); | |||||
| // Clients.Group(room.RoomId).ReceiveMessage(new ChatMessage { User = room.UserId, Message = $"{room.UserId} has left room" }); | |||||
| // } | |||||
| // return base.OnDisconnectedAsync(exception); | |||||
| //} | |||||
| [HubMethodName("SendMessageToGroup")] | [HubMethodName("SendMessageToGroup")] | ||||
| [AuthorizationHubFilter(Roles = "Customer,Support")] | [AuthorizationHubFilter(Roles = "Customer,Support")] | ||||
| public async Task SendMessageToGroup(ChatMessage message) | public async Task SendMessageToGroup(ChatMessage message) | ||||
| // All other users will receive notification | // All other users will receive notification | ||||
| if (_connections.TryGetValue(message.ConnId, out UserConnection room)) | if (_connections.TryGetValue(message.ConnId, out UserConnection room)) | ||||
| { | { | ||||
| await Clients.Group(room.RoomId).ReceiveMessage(new ChatMessage { User = room.UserId, Message = message.Message }); | |||||
| await Clients.OthersInGroup(room.RoomId).ReceiveNotifications(room.UserId, room.RoomId); | |||||
| await Clients.Group(room.RoomId).ReceiveMessage(new ChatMessage { UserId = room.UserId, User = room.UserId, Message = message.Message, RoomId = room.RoomId }); | |||||
| await _mediator.Send(new AddMessageCommand(room.RoomId, new Message { Content = message.Message, SenderId = message.UserId, Username = room.Username })); | await _mediator.Send(new AddMessageCommand(room.RoomId, new Message { Content = message.Message, SenderId = message.UserId, Username = room.Username })); | ||||
| // Find other users in room and save notification in database | // Find other users in room and save notification in database | ||||
| await _mediator.Send(new AddNotificationCommand(new NotificationSaveDTO { RoomId = room.RoomId, ReceiverId = conn.Value.UserId })); | await _mediator.Send(new AddNotificationCommand(new NotificationSaveDTO { RoomId = room.RoomId, ReceiverId = conn.Value.UserId })); | ||||
| } | } | ||||
| } | } | ||||
| await Clients.OthersInGroup(room.RoomId).ReceiveNotifications(room.UserId, room.RoomId); | |||||
| } | } | ||||
| } | } | ||||
| { | { | ||||
| await Groups.AddToGroupAsync(Context.ConnectionId, userConnection.RoomId); | await Groups.AddToGroupAsync(Context.ConnectionId, userConnection.RoomId); | ||||
| _connections[Context.ConnectionId] = userConnection; | _connections[Context.ConnectionId] = userConnection; | ||||
| await Clients.Group(userConnection.RoomId).ReceiveMessage(new ChatMessage { User = userConnection.UserId, Message = $"{userConnection.Username} has joined room", ConnId = Context.ConnectionId }); | |||||
| await Clients.Group(userConnection.RoomId).ReceiveMessage(new ChatMessage { UserId = userConnection.UserId, User = userConnection.UserId, RoomId = userConnection.RoomId, Message = $"{userConnection.Username} has joined room", ConnId = Context.ConnectionId }); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| { | { | ||||
| if (_connections.TryGetValue(connection.ConnId, out UserConnection room)) | if (_connections.TryGetValue(connection.ConnId, out UserConnection room)) | ||||
| { | { | ||||
| // Find user connection in connections dictionary and delete it | |||||
| _connections.Remove(Context.ConnectionId); | _connections.Remove(Context.ConnectionId); | ||||
| await Clients.OthersInGroup(room.RoomId).ReceiveMessage(new ChatMessage { User = room.UserId, Message = $"{room.Username} has left room" }); | |||||
| await Clients.OthersInGroup(room.RoomId).ReceiveMessage(new ChatMessage { UserId = room.UserId, User = room.UserId, Message = $"{room.Username} has left room", RoomId = room.RoomId }); | |||||
| await _mediator.Send(new RemoveUserFromGroupCommand(room.RoomId, room.UserId)); | await _mediator.Send(new RemoveUserFromGroupCommand(room.RoomId, room.UserId)); | ||||
| } | } | ||||
| } | } | ||||
| public async Task SeeWhoIsTyping(TypingInvokeMessage message) | public async Task SeeWhoIsTyping(TypingInvokeMessage message) | ||||
| { | { | ||||
| // Find user's room and send message to everyone | // Find user's room and send message to everyone | ||||
| //if (_connections.TryGetValue(Context.ConnectionId, out UserConnection room)) | |||||
| if (_connections.TryGetValue(message.ConnId, out UserConnection room)) | if (_connections.TryGetValue(message.ConnId, out UserConnection room)) | ||||
| { | { | ||||
| // send the typing info to all in group except the caller | // send the typing info to all in group except the caller | ||||
| // viewtyping(username + "is typing") // message, roomId | |||||
| //await Clients.GroupExcept(message.RoomId, Context.ConnectionId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message }); | |||||
| await Clients.GroupExcept(message.RoomId, message.ConnId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message }); | await Clients.GroupExcept(message.RoomId, message.ConnId).ViewTyping(new TypingMessage { RoomId = room.RoomId, Message = message.Message }); | ||||
| } | } | ||||
| } | } |
| // Context.ConnectionId generated by SignalR | // Context.ConnectionId generated by SignalR | ||||
| public string ConnId { get; set; } | public string ConnId { get; set; } | ||||
| public string RoomId { get; set; } | |||||
| } | } | ||||
| } | } |
| { | { | ||||
| IDs.Remove(s.ConnId); | IDs.Remove(s.ConnId); | ||||
| var msg = new StatusMessage { Id = s.Id, M = "unsubscription" }; | var msg = new StatusMessage { Id = s.Id, M = "unsubscription" }; | ||||
| //breakpoint here ! | |||||
| await Clients.Others.SendAsync("Notify", msg); | await Clients.Others.SendAsync("Notify", msg); | ||||
| } | } | ||||
| //public override async Task OnDisconnectedAsync(Exception exception, string id) | |||||
| //{ | |||||
| // IDs.Remove(id); | |||||
| // var msg = new StatusMessage { Id = id, M = "unsubscription" }; | |||||
| // await Clients.Others.SendAsync("Notify", msg); | |||||
| // await base(); | |||||
| //} | |||||
| } | } | ||||
| } | } |
| import { GiSandsOfTime } from "react-icons/gi"; | import { GiSandsOfTime } from "react-icons/gi"; | ||||
| import { CgEnter } from "react-icons/cg"; | import { CgEnter } from "react-icons/cg"; | ||||
| import Dialog from "./UI/Dialog"; | import Dialog from "./UI/Dialog"; | ||||
| import { chatActions, fetchChatRoomsAsync,fetchSupportRoomsAsync } from "../store/chat-slice"; | |||||
| import { | |||||
| chatActions, | |||||
| fetchChatRoomsAsync, | |||||
| fetchSupportRoomsAsync, | |||||
| } from "../store/chat-slice"; | |||||
| import { createChatRoomAsync } from "../store/chat-slice"; | import { createChatRoomAsync } from "../store/chat-slice"; | ||||
| import { createJoinRequestAsync, requestActions } from "../store/request-slice"; | import { createJoinRequestAsync, requestActions } from "../store/request-slice"; | ||||
| import { fetchRequestsAsync } from "../store/request-slice"; | import { fetchRequestsAsync } from "../store/request-slice"; | ||||
| import { HubConnectionBuilder } from "@microsoft/signalr"; | import { HubConnectionBuilder } from "@microsoft/signalr"; | ||||
| import { loadNotifications } from "../store/chat-slice"; | import { loadNotifications } from "../store/chat-slice"; | ||||
| import { readNotificationsAsync } from "../store/chat-slice"; | |||||
| // Ovde ce biti dostupne grupe i razgovori | // Ovde ce biti dostupne grupe i razgovori | ||||
| const ChatList = () => { | const ChatList = () => { | ||||
| const [createChat, setCreateChat] = useState(false); | const [createChat, setCreateChat] = useState(false); | ||||
| // naziv chata koji ce biti dodan | |||||
| const [chatName, setChatName] = useState(""); | const [chatName, setChatName] = useState(""); | ||||
| // grupe kojima je korisnik poslao zahtev za ulazak | |||||
| const [requestedRooms, setRequestedRooms] = useState([]); | const [requestedRooms, setRequestedRooms] = useState([]); | ||||
| // chats from redux | |||||
| const { rooms, status, error, notifications } = useSelector( | |||||
| const { rooms, status: chatStatus, error, notifications, activeRoom } = useSelector( | |||||
| (state) => state.chat | (state) => state.chat | ||||
| ); | ); | ||||
| const { | const { | ||||
| status: requestsStatus, | status: requestsStatus, | ||||
| requests, | requests, | ||||
| } = useSelector((state) => state.requests); | } = useSelector((state) => state.requests); | ||||
| // show modal | |||||
| const [showModal, setShowModal] = useState(false); | const [showModal, setShowModal] = useState(false); | ||||
| const [loadedNotification, setLoadedNotification] = useState(false); | const [loadedNotification, setLoadedNotification] = useState(false); | ||||
| const { user } = useContext(UserContext); | const { user } = useContext(UserContext); | ||||
| // const [connection, setConnection] = useState(); | |||||
| // const [messages, setMessages] = useState([]); | |||||
| const [myConnection, setMyConnection] = useState(null); | |||||
| const [chatMessage, setChatMessage] = useState(null); | |||||
| const [notificationRoom, setNotificationRoom] = useState(null); | |||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| } | } | ||||
| }, [user, dispatch, loadedNotification]); | }, [user, dispatch, loadedNotification]); | ||||
| // Maybe don't work | |||||
| useEffect(() => { | useEffect(() => { | ||||
| (user !== null && user.roles.includes('Support')) ? dispatch(fetchSupportRoomsAsync(user.id)): | |||||
| (user !== null && dispatch(fetchChatRoomsAsync(user.id))) | |||||
| user !== null && user.roles.includes("Support") | |||||
| ? dispatch(fetchSupportRoomsAsync(user.id)) | |||||
| : user !== null && dispatch(fetchChatRoomsAsync(user.id)); | |||||
| dispatch(fetchRequestsAsync()); | dispatch(fetchRequestsAsync()); | ||||
| }, [dispatch]); | |||||
| }, [dispatch, user]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (requests && rooms) { | if (requests && rooms) { | ||||
| const joinRoom = async (n) => { | const joinRoom = async (n) => { | ||||
| try { | try { | ||||
| const connection = new HubConnectionBuilder() | const connection = new HubConnectionBuilder() | ||||
| .withUrl("http://localhost:5116/chatHub",{accessTokenFactory:() => user.token}) | |||||
| .withUrl("http://localhost:5116/chatHub", { | |||||
| accessTokenFactory: () => user.token, | |||||
| }) | |||||
| .withAutomaticReconnect() | .withAutomaticReconnect() | ||||
| .build(); | .build(); | ||||
| dispatch( | dispatch( | ||||
| chatActions.saveContextId({ connId: data.connId, userId: user.id }) | chatActions.saveContextId({ connId: data.connId, userId: user.id }) | ||||
| ); | ); | ||||
| setChatMessage(data); | |||||
| } | } | ||||
| // Every new received message will be stored in redux | |||||
| dispatch( | |||||
| chatActions.newMessage({ | |||||
| content: data.message, | |||||
| createdAtUtc: new Date(), | |||||
| deletedAtUtc: null, | |||||
| id: null, | |||||
| senderId: user.id, | |||||
| updatedAtUtc: null, | |||||
| username: user.username, | |||||
| }) | |||||
| ); | |||||
| }); | }); | ||||
| connection.on("ViewTyping", (data) =>{ | |||||
| dispatch(chatActions.addTyping(data)); | |||||
| }) | |||||
| connection.on("ViewTyping", (data) => { | |||||
| dispatch(chatActions.addTyping(data)); | |||||
| }); | |||||
| connection.on("ReceiveNotifications", (userId, roomId) => { | connection.on("ReceiveNotifications", (userId, roomId) => { | ||||
| if (user.id !== userId) dispatch(chatActions.addNotification(roomId)); | |||||
| if (user.id !== userId) { | |||||
| dispatch(chatActions.addNotification(roomId)); | |||||
| setNotificationRoom(roomId); | |||||
| } | |||||
| }); | }); | ||||
| // When user changed room, array with messages from previous room will be deleted from redux | // When user changed room, array with messages from previous room will be deleted from redux | ||||
| dispatch(chatActions.newMessage({ changedRoom: true })); | dispatch(chatActions.newMessage({ changedRoom: true })); | ||||
| connection.onclose((e) => { | connection.onclose((e) => { | ||||
| // setConnection(); | |||||
| // setMessages([]); | |||||
| // On close connection | |||||
| }); | }); | ||||
| await connection.start(); | await connection.start(); | ||||
| }); | }); | ||||
| dispatch(chatActions.setRoom(n)); | dispatch(chatActions.setRoom(n)); | ||||
| dispatch(chatActions.setConnection(connection)); | dispatch(chatActions.setConnection(connection)); | ||||
| // setConnection({ connection }); | |||||
| setMyConnection(connection); | |||||
| } catch (e) { | } catch (e) { | ||||
| console.log(e); | console.log(e); | ||||
| } | } | ||||
| }; | }; | ||||
| // Maybe don't work | |||||
| useEffect(() => { | |||||
| if (myConnection) { | |||||
| myConnection.on("ReceiveMessage", (data) => { | |||||
| // When user enter room first time after login, generated Context.ConnectionId will be saved in redux | |||||
| if (data.connId) { | |||||
| dispatch( | |||||
| chatActions.saveContextId({ connId: data.connId, userId: user.id }) | |||||
| ); | |||||
| } | |||||
| setChatMessage(data); | |||||
| }); | |||||
| } | |||||
| }, [myConnection, dispatch, user]); | |||||
| // Maybe don't work | |||||
| useEffect(() => { | |||||
| if (chatMessage && activeRoom.id === chatMessage.roomId) { | |||||
| dispatch( | |||||
| chatActions.newMessage({ | |||||
| content: chatMessage.message, | |||||
| createdAtUtc: new Date(), | |||||
| deletedAtUtc: null, | |||||
| id: null, | |||||
| senderId: user.id, | |||||
| updatedAtUtc: null, | |||||
| username: user.username, | |||||
| }) | |||||
| ); | |||||
| } | |||||
| }, [chatMessage, dispatch, user, activeRoom]); | |||||
| // Maybe don't work | |||||
| useEffect(() => { | |||||
| if (notificationRoom && activeRoom) { | |||||
| if (notificationRoom === activeRoom.id) { | |||||
| dispatch(chatActions.readNotifications(activeRoom.id)); | |||||
| dispatch( | |||||
| readNotificationsAsync({ userId: user.id, roomId: activeRoom.id }) | |||||
| ); | |||||
| } | |||||
| } | |||||
| setNotificationRoom(null); | |||||
| }, [notificationRoom, activeRoom, dispatch, user]); | |||||
| const openModal = (n) => { | const openModal = (n) => { | ||||
| setShowModal(true); | setShowModal(true); | ||||
| dispatch(requestActions.chooseRoom(n)); | dispatch(requestActions.chooseRoom(n)); | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return ( | |||||
| user !== null && user.roles.includes('Support') ? | |||||
| return user !== null && user.roles.includes("Support") ? ( | |||||
| <div> | <div> | ||||
| {rooms.map((room, index) => ( | {rooms.map((room, index) => ( | ||||
| <div | <div | ||||
| className="border-bottom d-flex" | className="border-bottom d-flex" | ||||
| key={index} | key={index} | ||||
| onClick={() => joinRoom(room)} | onClick={() => joinRoom(room)} | ||||
| > | |||||
| <button | |||||
| className="text-start w-100 py-3 px-3 btn btn-light h-100" | |||||
| onClick={showRoomMessagesHandler.bind(this, room)} | |||||
| > | > | ||||
| <button | |||||
| className="text-start w-100 py-3 px-3 btn btn-light h-100" | |||||
| onClick={showRoomMessagesHandler.bind(this, room)} | |||||
| > | |||||
| {room.name} | |||||
| </button> | |||||
| {room.name} | |||||
| </button> | |||||
| </div> | </div> | ||||
| ))} | ))} | ||||
| </div>: | |||||
| </div> | |||||
| ) : ( | |||||
| <div> | <div> | ||||
| {acceptedRequests.length > 0 && ( | {acceptedRequests.length > 0 && ( | ||||
| <div> | <div> | ||||
| )} | )} | ||||
| {/* ovo ce biti zamenjeno konkretnim podacima */} | {/* ovo ce biti zamenjeno konkretnim podacima */} | ||||
| {(!error && requestedRooms && status === "pendingFetchRooms") || | |||||
| status === "pendingAddRoom" ? ( | |||||
| {(!error && requestedRooms && chatStatus === "pendingFetchRooms") || | |||||
| chatStatus === "pendingAddRoom" ? ( | |||||
| <p>Loading</p> | <p>Loading</p> | ||||
| ) : ( | ) : ( | ||||
| getView() | getView() |
| import { UserContext } from "../contexts/userContext"; | import { UserContext } from "../contexts/userContext"; | ||||
| import { useSelector, useDispatch } from "react-redux"; | import { useSelector, useDispatch } from "react-redux"; | ||||
| import { chatActions } from "../store/chat-slice"; | import { chatActions } from "../store/chat-slice"; | ||||
| import { BsCircleFill } from 'react-icons/bs' | |||||
| import { BsCircleFill } from "react-icons/bs"; | |||||
| import TypingBar from "./TypingBar"; | import TypingBar from "./TypingBar"; | ||||
| const ChatWindow = ({ room }) => { | const ChatWindow = ({ room }) => { | ||||
| const messagesEndRef = useRef(null); | const messagesEndRef = useRef(null); | ||||
| const scrollToBottom = () => { | |||||
| messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); | |||||
| }; | |||||
| const [message, setMessage] = useState(""); | const [message, setMessage] = useState(""); | ||||
| const { user } = useContext(UserContext); | const { user } = useContext(UserContext); | ||||
| const connection = useSelector((state) => state.chat.connection); | const connection = useSelector((state) => state.chat.connection); | ||||
| const { typings } = useSelector((s) => s.chat) | |||||
| const connections = useSelector((state) => state.chat.connections); | const connections = useSelector((state) => state.chat.connections); | ||||
| const activeRoom = useSelector((state) => state.chat.activeRoom); | const activeRoom = useSelector((state) => state.chat.activeRoom); | ||||
| const messages = useSelector((state) => state.chat.messages); | const messages = useSelector((state) => state.chat.messages); | ||||
| dispatch(chatActions.setMessages(room.messages)); | dispatch(chatActions.setMessages(room.messages)); | ||||
| }, [dispatch, room.messages]); | }, [dispatch, room.messages]); | ||||
| useEffect(() => { | |||||
| // scrollToBottom(); | |||||
| }, []); | |||||
| const leftRoomHandler = async () => { | const leftRoomHandler = async () => { | ||||
| const userToFetch = connections.filter( | const userToFetch = connections.filter( | ||||
| (conn) => conn.userId === user.id && conn.roomId === activeRoom.id | (conn) => conn.userId === user.id && conn.roomId === activeRoom.id | ||||
| ); | ); | ||||
| console.log(userToFetch[0].connId, user.id, activeRoom.id); | |||||
| await connection.invoke("LeaveRoom", { | await connection.invoke("LeaveRoom", { | ||||
| connId: userToFetch[0].connId, | connId: userToFetch[0].connId, | ||||
| }); | }); | ||||
| const status = useSelector((s) => s.status); | const status = useSelector((s) => s.status); | ||||
| const { activeUsers } = status; | const { activeUsers } = status; | ||||
| const messageOnChangeHandler = async (e) =>{ | |||||
| setMessage(e.target.value) | |||||
| const messageOnChangeHandler = async (e) => { | |||||
| setMessage(e.target.value); | |||||
| const userToFetch = connections.filter( | const userToFetch = connections.filter( | ||||
| (conn) => conn.userId === user.id && conn.roomId === activeRoom.id | (conn) => conn.userId === user.id && conn.roomId === activeRoom.id | ||||
| ); | ); | ||||
| // console.log(userToFetch[0].connId) | |||||
| // console.log(room.id) | |||||
| await connection.send("SeeWhoIsTyping", { | await connection.send("SeeWhoIsTyping", { | ||||
| message: `${user.username} is typing...`, | message: `${user.username} is typing...`, | ||||
| connId: userToFetch[0].connId, | connId: userToFetch[0].connId, | ||||
| roomId: activeRoom.id | |||||
| roomId: activeRoom.id, | |||||
| }); | }); | ||||
| } | |||||
| }; | |||||
| return ( | return ( | ||||
| <div className="px-3 bg-light-transparent rounded h-100 d-flex flex-column"> | <div className="px-3 bg-light-transparent rounded h-100 d-flex flex-column"> | ||||
| <div className="messages p-3 border d-flex flex-column-reverse"> | <div className="messages p-3 border d-flex flex-column-reverse"> | ||||
| {/* mapirane poruke */} | {/* mapirane poruke */} | ||||
| {messages | {messages | ||||
| .map((n, index) => ( | .map((n, index) => ( | ||||
| <div | <div | ||||
| <div ref={messagesEndRef} /> | <div ref={messagesEndRef} /> | ||||
| </div> | </div> | ||||
| <TypingBar id={activeRoom.id}/> | |||||
| <TypingBar id={activeRoom.id} /> | |||||
| <Form style={{ height: "80px" }} className="d-flex align-items-center"> | |||||
| <Form | |||||
| style={{ height: "80px" }} | |||||
| className="d-flex align-items-center" | |||||
| > | |||||
| <InputGroup className="mb-3"> | <InputGroup className="mb-3"> | ||||
| <FormControl | <FormControl | ||||
| placeholder="Enter your messagge..." | placeholder="Enter your messagge..." |
| import { FiCheckCircle, FiXCircle } from "react-icons/fi"; | import { FiCheckCircle, FiXCircle } from "react-icons/fi"; | ||||
| import styles from "./CustomerRequest.module.css"; | import styles from "./CustomerRequest.module.css"; | ||||
| import requestService from "../../services/requestService"; | import requestService from "../../services/requestService"; | ||||
| // import { HubConnectionBuilder } from "@microsoft/signalr"; | |||||
| export default function CustomerRequest({ customer, requestHandled }) { | export default function CustomerRequest({ customer, requestHandled }) { | ||||
| const onAcceptRequestHandler = () => { | const onAcceptRequestHandler = () => { | ||||
| }) | }) | ||||
| .then((res) => { | .then((res) => { | ||||
| requestHandled(res); | requestHandled(res); | ||||
| // Joining in room | |||||
| // joinRoom({ userId: customer.senderId, roomId: customer.roomId }); | |||||
| }) | }) | ||||
| .catch((e) => console.log(e)); | .catch((e) => console.log(e)); | ||||
| }; | }; | ||||
| // const joinRoom = async (joiningCredentials) => { | |||||
| // try { | |||||
| // const connection = new HubConnectionBuilder() | |||||
| // .withUrl("http://localhost:5116/chatHub") | |||||
| // .withAutomaticReconnect() | |||||
| // .build(); | |||||
| // connection.on("ReceiveMessage", (data) => { | |||||
| // console.log("CUSTOMER REQUEST", data); | |||||
| // }); | |||||
| // await connection.start(); | |||||
| // await connection.invoke("JoinRoom", joiningCredentials); | |||||
| // } catch (e) { | |||||
| // console.log(e); | |||||
| // } | |||||
| // }; | |||||
| const onRejectRequestHandler = () => { | const onRejectRequestHandler = () => { | ||||
| requestService | requestService | ||||
| .rejectCustomerRequest({ | .rejectCustomerRequest({ |
| import Activity from "./Activity"; | import Activity from "./Activity"; | ||||
| import ChatList from "./ChatList"; | import ChatList from "./ChatList"; | ||||
| import Requests from "./Requests/Requests"; | import Requests from "./Requests/Requests"; | ||||
| import { useContext, useEffect, useState } from "react"; | |||||
| import { useContext, useEffect } from "react"; | |||||
| import { HubConnectionBuilder } from "@microsoft/signalr"; | import { HubConnectionBuilder } from "@microsoft/signalr"; | ||||
| import { UserContext } from "../contexts/userContext"; | import { UserContext } from "../contexts/userContext"; | ||||
| import { useDispatch, useSelector } from "react-redux"; | |||||
| import { useDispatch } from "react-redux"; | |||||
| import { statusActions } from "../store/status-slice"; | import { statusActions } from "../store/status-slice"; | ||||
| const MiddleContainer = ({ showTerm }) => { | const MiddleContainer = ({ showTerm }) => { | ||||
| // const [conn, setConn] = useState(''); | |||||
| // window.addEventListener('beforeunload', ()=>{ | |||||
| // }) | |||||
| const { user } = useContext(UserContext); | const { user } = useContext(UserContext); | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| .build(); | .build(); | ||||
| dispatch(statusActions.setStatusConn(connection)); | dispatch(statusActions.setStatusConn(connection)); | ||||
| // setConn(connection) | |||||
| // connection.onclose(() => { | |||||
| // connection.send("Unsubscribe", user.id); | |||||
| // }); | |||||
| connection.on("Notify", (data) => { | connection.on("Notify", (data) => { | ||||
| if (data.m === "subscription") { | if (data.m === "subscription") { | ||||
| connection.start().then(fulfilled, rejected); | connection.start().then(fulfilled, rejected); | ||||
| } | } | ||||
| // Maybe don't work | |||||
| useEffect(() => { | useEffect(() => { | ||||
| connect(); | connect(); | ||||
| }, []); | }, []); |
| navigate("/main"); | navigate("/main"); | ||||
| }) | }) | ||||
| .catch((err) => { | .catch((err) => { | ||||
| // console.log(err) | |||||
| setError(err.response.data.message.split(".")); | setError(err.response.data.message.split(".")); | ||||
| }); | }); | ||||
| }; | }; |
| const logOutHandler = () => { | const logOutHandler = () => { | ||||
| if (window.confirm("Are you sure?")) { | if (window.confirm("Are you sure?")) { | ||||
| // ovde ce ici logika za logovanje | |||||
| // uklanjanje iz state-a i slicno | |||||
| dispatch(chatActions.deleteActiveRoom()); | dispatch(chatActions.deleteActiveRoom()); | ||||
| logOut(); | logOut(); | ||||
| } | } | ||||
| <Link | <Link | ||||
| to={'/main'} | to={'/main'} | ||||
| className="btn btn-white w-100 button-block button-block-flex-column" | className="btn btn-white w-100 button-block button-block-flex-column" | ||||
| // onClick={() => onClickHandler("notifications")} | |||||
| > | > | ||||
| <FiBell className="icon-fs" /> | <FiBell className="icon-fs" /> | ||||
| <h5 className="small text-muted text-center fw-light">feed</h5> | <h5 className="small text-muted text-center fw-light">feed</h5> | ||||
| <Link | <Link | ||||
| to={'/chats'} | to={'/chats'} | ||||
| className="btn btn-white button-block mt-4 w-100 button-block-flex-column" | className="btn btn-white button-block mt-4 w-100 button-block-flex-column" | ||||
| // onClick={() => onClickHandler("chats")} | |||||
| > | > | ||||
| <FiMessageSquare className="icon-fs" /> | <FiMessageSquare className="icon-fs" /> | ||||
| <h5 className="small text-muted text-center fw-light">chat</h5> | <h5 className="small text-muted text-center fw-light">chat</h5> | ||||
| <Link | <Link | ||||
| to={'/requests'} | to={'/requests'} | ||||
| className="btn btn-white button-block mt-4 w-100 button-block-flex-column" | className="btn btn-white button-block mt-4 w-100 button-block-flex-column" | ||||
| // onClick={() => onClickHandler("requests")} | |||||
| > | > | ||||
| <FaArrowAltCircleRight className="icon-fs" /> | <FaArrowAltCircleRight className="icon-fs" /> | ||||
| <h5 className="small text-muted text-center fw-light">Requests</h5> | <h5 className="small text-muted text-center fw-light">Requests</h5> |
| return () => clearTimeout(timeout) | return () => clearTimeout(timeout) | ||||
| }, 2500) | }, 2500) | ||||
| }, [rerender]); | |||||
| }, [rerender, dispatch]); | |||||
| return ( | return ( | ||||
| <div className='text-dark'> | <div className='text-dark'> |
| import React, { useState } from "react"; | import React, { useState } from "react"; | ||||
| // import { useSelector, useDispatch } from "react-redux"; | |||||
| import Accordion from "@mui/material/Accordion"; | import Accordion from "@mui/material/Accordion"; | ||||
| import AccordionSummary from "@mui/material/AccordionSummary"; | import AccordionSummary from "@mui/material/AccordionSummary"; |
| window.addEventListener("beforeunload", () => { | window.addEventListener("beforeunload", () => { | ||||
| // that means the user probably shut the browser down without loging out | // that means the user probably shut the browser down without loging out | ||||
| if (localStorage.getItem("activeOnes")) { | if (localStorage.getItem("activeOnes")) { | ||||
| // status.connection.stop(); | |||||
| disconnect(); | disconnect(); | ||||
| } | } | ||||
| }); | }); | ||||
| connId: status.connection.connectionId, | connId: status.connection.connectionId, | ||||
| }) | }) | ||||
| .then(() => console.log("bye")); | .then(() => console.log("bye")); | ||||
| // connection.stop(); | |||||
| }; | }; | ||||
| const rejected = () => { | const rejected = () => { |
| const methods = { | const methods = { | ||||
| loadNotifications: (userId) => | loadNotifications: (userId) => | ||||
| axios.get(`/Notification/${userId}`).then(responseBody), | axios.get(`/Notification/${userId}`).then(responseBody), | ||||
| readNotifications: (payload) => | |||||
| axios.post("/Notification", payload).then(responseBody), | |||||
| }; | }; | ||||
| export default methods; | |||||
| export default methods; |
| rooms: [], | rooms: [], | ||||
| activeRoom: null, | activeRoom: null, | ||||
| error: null, | error: null, | ||||
| // Hub connection | |||||
| connection: null, | connection: null, | ||||
| // List of room messages | |||||
| messages: [], | messages: [], | ||||
| // All active user connections to rooms | |||||
| connections: [], | connections: [], | ||||
| // Notifications | |||||
| notifications: [], | notifications: [], | ||||
| typings: [] | |||||
| typings: [], | |||||
| }; | }; | ||||
| export const fetchChatRoomsAsync = createAsyncThunk( | export const fetchChatRoomsAsync = createAsyncThunk( | ||||
| export const fetchSupportRoomsAsync = createAsyncThunk( | export const fetchSupportRoomsAsync = createAsyncThunk( | ||||
| "chat/fetchSupportRooms", | "chat/fetchSupportRooms", | ||||
| async (payload,thunkAPI) => { | |||||
| try{ | |||||
| async (payload, thunkAPI) => { | |||||
| try { | |||||
| return await chatService.getSupportRooms(payload); | return await chatService.getSupportRooms(payload); | ||||
| }catch(error){ | |||||
| return thunkAPI.rejectWithValue({error}) | |||||
| } catch (error) { | |||||
| return thunkAPI.rejectWithValue({ error }); | |||||
| } | } | ||||
| } | } | ||||
| ) | |||||
| ); | |||||
| export const createChatRoomAsync = createAsyncThunk( | export const createChatRoomAsync = createAsyncThunk( | ||||
| "chat/createChatRoomAsync", | "chat/createChatRoomAsync", | ||||
| } | } | ||||
| ); | ); | ||||
| export const readNotificationsAsync = createAsyncThunk( | |||||
| "chat/deleteNotificationsAsync", | |||||
| async (payload, thunkAPI) => { | |||||
| try { | |||||
| return await notificationService.readNotifications(payload); | |||||
| } catch (error) { | |||||
| return thunkAPI.rejectWithValue({ error }); | |||||
| } | |||||
| } | |||||
| ); | |||||
| const chatSlice = createSlice({ | const chatSlice = createSlice({ | ||||
| name: "chat", | name: "chat", | ||||
| initialState, | initialState, | ||||
| ); | ); | ||||
| }, | }, | ||||
| addTyping: (state, action) => { | addTyping: (state, action) => { | ||||
| if(!state.typings.some(n => n.message === action.payload.message && n.roomId === action.payload.roomId)) | |||||
| if ( | |||||
| !state.typings.some( | |||||
| (n) => | |||||
| n.message === action.payload.message && | |||||
| n.roomId === action.payload.roomId | |||||
| ) | |||||
| ) | |||||
| state.typings = [...state.typings, action.payload]; | state.typings = [...state.typings, action.payload]; | ||||
| else console.log('ima vec') | |||||
| else console.log("ima vec"); | |||||
| }, | }, | ||||
| removeTyping: (state, action) => { | removeTyping: (state, action) => { | ||||
| if(state.typings.length >= 1){ | |||||
| let f = state.typings[0] | |||||
| state.typings = state.typings.filter(n => n.message !== f.message); | |||||
| if (state.typings.length >= 1) { | |||||
| let f = state.typings[0]; | |||||
| state.typings = state.typings.filter((n) => n.message !== f.message); | |||||
| } | } | ||||
| } | |||||
| }, | |||||
| }, | }, | ||||
| extraReducers: (builder) => { | extraReducers: (builder) => { | ||||
| // Fetch chat rooms | // Fetch chat rooms | ||||
| state.status = "idle"; | state.status = "idle"; | ||||
| state.error = action.payload; | state.error = action.payload; | ||||
| }); | }); | ||||
| // Read notifications | |||||
| builder.addCase(readNotificationsAsync.pending, (state) => { | |||||
| state.status = "pendingReadingNotifications"; | |||||
| state.error = null; | |||||
| }); | |||||
| builder.addCase(readNotificationsAsync.fulfilled, (state) => { | |||||
| state.status = "idle"; | |||||
| state.error = null; | |||||
| }); | |||||
| builder.addCase(readNotificationsAsync.rejected, (state, action) => { | |||||
| state.status = "idle"; | |||||
| state.error = action.payload; | |||||
| }); | |||||
| }, | }, | ||||
| }); | }); | ||||
| activeUsers: localStorage.getItem("activeOnes") | activeUsers: localStorage.getItem("activeOnes") | ||||
| ? JSON.parse(localStorage.getItem("activeOnes")) | ? JSON.parse(localStorage.getItem("activeOnes")) | ||||
| : [], | : [], | ||||
| //maybe needed later | |||||
| connection: '' | connection: '' | ||||
| }; | }; | ||||