From 6cd495d6de1be245a07881f183ea78a20036d239 Mon Sep 17 00:00:00 2001 From: baha-a Date: Wed, 15 Nov 2023 03:21:53 +0300 Subject: [PATCH 01/14] Remove unused variables --- src/components/chat/chat.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 8d567796..7116d1fa 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -65,10 +65,8 @@ const Chat = (): JSX.Element => { idleChat, showContactForm, showUnavailableContactForm, - customerSupportId, feedback, messages, - isChatOpen, chatDimensions, } = useChatSelector(); const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector( From 6d833cdfac33caf9e804180c018a949e08beb5c6 Mon Sep 17 00:00:00 2001 From: baha-a Date: Wed, 15 Nov 2023 10:32:35 +0300 Subject: [PATCH 02/14] Add Chat flow component --- src/components/chat-content/chat-content.tsx | 14 ++++-- .../chat-message/chat-flow-message.tsx | 43 +++++++++++++++++++ src/components/chat/chat.tsx | 4 +- src/slices/chat-slice.ts | 6 ++- 4 files changed, 60 insertions(+), 7 deletions(-) create mode 100644 src/components/chat-message/chat-flow-message.tsx diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index 73948028..ea90d957 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -9,10 +9,11 @@ import styles from './chat-content.module.scss'; import WaitingTimeNotification from '../waiting-time-notification/waiting-time-notification'; import { useAppDispatch } from '../../store'; import { getEstimatedWaitingTime, setEstimatedWaitingTimeToZero } from '../../slices/chat-slice'; +import ChatFlowMessage from '../chat-message/chat-flow-message'; const ChatContent = (): JSX.Element => { const OSref = useRef(null); - const { messages,estimatedWaiting,customerSupportId } = useChatSelector(); + const { messages, estimatedWaiting, customerSupportId } = useChatSelector(); const dispatch = useAppDispatch(); useEffect(() => { @@ -42,11 +43,16 @@ const ChatContent = (): JSX.Element => { }} > {/* TODO: Logic is incorrect, commented out until it gets fixed */} - {/* {~~estimatedWaiting.durationInSeconds > 0 && + {/* {~~estimatedWaiting.durationInSeconds > 0 && ~~estimatedWaiting.positionInUnassignedChats > 0 && } */} - {messages.map((message) => ( - message.type === 'flow' ? ( + + ):( + diff --git a/src/components/chat-message/chat-flow-message.tsx b/src/components/chat-message/chat-flow-message.tsx new file mode 100644 index 00000000..dbaa2753 --- /dev/null +++ b/src/components/chat-message/chat-flow-message.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Message } from '../../model/message-model'; +import { AUTHOR_ROLES, CHAT_EVENTS } from '../../constants'; +import AdminMessage from './message-types/admin-message'; +import ClientMessage from './message-types/client-message'; +import EventMessage from './message-types/event-message'; +import AuthenticationMessage from './message-types/authentication-message'; +import PermissionMessage from './message-types/permission-message'; +import RedirectMessage from './message-types/redirect-message'; + +const ChatFlowMessage = (props: { message: Message }): JSX.Element => { + const { t } = useTranslation(); + const { + message, + message: { authorRole, event }, + } = props; + + const endChatMessage = ( + <> +
{t('notifications.chat-ended')}
+
{t('notifications.chat-ended-new-chat')}
+ + ); + if (event === CHAT_EVENTS.MESSAGE_READ) return <> + if (event === CHAT_EVENTS.ASK_PERMISSION_IGNORED) return ; + if (event === CHAT_EVENTS.ASK_PERMISSION_ACCEPTED) return ; + if (event === CHAT_EVENTS.ASK_PERMISSION_REJECTED) return ; + if (event === CHAT_EVENTS.ASK_PERMISSION) return } />; + if (event === CHAT_EVENTS.CONTACT_INFORMATION_REJECTED) return ; + if (event === CHAT_EVENTS.REQUESTED_AUTHENTICATION) return } />; + if (event === CHAT_EVENTS.REDIRECTED) return ; + if (event === CHAT_EVENTS.ANSWERED || event === CHAT_EVENTS.TERMINATED) return ; + if (event === CHAT_EVENTS.CONTACT_INFORMATION) return ; + if (event === CHAT_EVENTS.REQUESTED_CHAT_FORWARD) return } />; + if (event === CHAT_EVENTS.REQUESTED_CHAT_FORWARD_ACCEPTED) return ; + if (event === CHAT_EVENTS.REQUESTED_CHAT_FORWARD_REJECTED) return ; + if (event === CHAT_EVENTS.CONTACT_INFORMATION_FULFILLED || authorRole === AUTHOR_ROLES.END_USER) return ; + if (authorRole === AUTHOR_ROLES.END_USER) return ; + return ; +}; + +export default ChatFlowMessage; diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 7116d1fa..bda98939 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -68,6 +68,7 @@ const Chat = (): JSX.Element => { feedback, messages, chatDimensions, + chatMode, } = useChatSelector(); const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector( (state) => state.widget @@ -289,7 +290,8 @@ const Chat = (): JSX.Element => { {!showWidgetDetails && !showContactForm && !showUnavailableContactForm && - !feedback.isFeedbackConfirmationShown && } + !feedback.isFeedbackConfirmationShown && + chatMode === 'free' && } )} diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index c46be30c..d1607434 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -90,7 +90,8 @@ export interface ChatState { isSubmitted: boolean, isFailed: boolean, } - } + }, + chatMode: 'free' | 'flow', } const initialState: ChatState = { @@ -148,7 +149,8 @@ const initialState: ChatState = { isSubmitted: false, isFailed: false, } - } + }, + chatMode: 'free' }; export const initChat = createAsyncThunk('chat/init', async (message: Message) => { From 8745d6f85ce983972ba99af073fc9ae95783811c Mon Sep 17 00:00:00 2001 From: baha-a Date: Thu, 16 Nov 2023 00:34:33 +0300 Subject: [PATCH 03/14] Refactor display message logic --- src/hooks/use-get-new-messages.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/hooks/use-get-new-messages.ts b/src/hooks/use-get-new-messages.ts index d8ea5c01..dcdcb382 100644 --- a/src/hooks/use-get-new-messages.ts +++ b/src/hooks/use-get-new-messages.ts @@ -17,7 +17,18 @@ const useGetNewMessages = (): void => { if (!chatId || isChatEnded || !lastReadMessageTimestamp) return undefined; const onMessage = (messages: Message[]) => { - const newDisplayableMessages = messages.filter((msg) => msg.event !== CHAT_EVENTS.GREETING && msg.event !== TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED && msg.event !== TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION && msg.event !== TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS && msg.event !== TERMINATE_STATUS.ACCEPTED && msg.event !== TERMINATE_STATUS.HATE_SPEECH && msg.event !== TERMINATE_STATUS.OTHER && msg.event !== TERMINATE_STATUS.RESPONSE_SENT_TO_CLIENT_EMAIL); + const nonDisplayableEvent = [ + CHAT_EVENTS.GREETING.toString(), + TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED.toString(), + TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION.toString(), + TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS.toString(), + TERMINATE_STATUS.ACCEPTED.toString(), + TERMINATE_STATUS.HATE_SPEECH.toString(), + TERMINATE_STATUS.OTHER.toString(), + TERMINATE_STATUS.RESPONSE_SENT_TO_CLIENT_EMAIL.toString(), + ] + + const newDisplayableMessages = messages.filter((msg) => !nonDisplayableEvent.includes(msg.event!)); const stateChangingEventMessages = messages.filter((msg) => isStateChangingEventMessage(msg)); dispatch(addMessagesToDisplay(newDisplayableMessages)); dispatch(handleStateChangingEventMessages(stateChangingEventMessages)); From 4ee7a76b52b2902e3dfd32c716eb6158855b046a Mon Sep 17 00:00:00 2001 From: baha-a Date: Thu, 16 Nov 2023 03:15:49 +0300 Subject: [PATCH 04/14] Handle flow chat mode --- src/components/chat-content/chat-content.tsx | 11 +--- .../chat-message/chat-message.module.scss | 28 +++++++++ .../message-types/admin-message.tsx | 55 ++++++++++------- .../message-types/chat-button-group.tsx | 59 +++++++++++++++++++ src/constants.ts | 3 +- src/slices/chat-slice.ts | 15 +++++ src/test-initial-states.ts | 3 +- 7 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 src/components/chat-message/message-types/chat-button-group.tsx diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index ea90d957..acab58bc 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -9,7 +9,6 @@ import styles from './chat-content.module.scss'; import WaitingTimeNotification from '../waiting-time-notification/waiting-time-notification'; import { useAppDispatch } from '../../store'; import { getEstimatedWaitingTime, setEstimatedWaitingTimeToZero } from '../../slices/chat-slice'; -import ChatFlowMessage from '../chat-message/chat-flow-message'; const ChatContent = (): JSX.Element => { const OSref = useRef(null); @@ -46,17 +45,11 @@ const ChatContent = (): JSX.Element => { {/* {~~estimatedWaiting.durationInSeconds > 0 && ~~estimatedWaiting.positionInUnassignedChats > 0 && } */} - {messages.map((message) => message.type === 'flow' ? ( - - ):( - - ))} + )} diff --git a/src/components/chat-message/chat-message.module.scss b/src/components/chat-message/chat-message.module.scss index 0ad2034c..9776d571 100644 --- a/src/components/chat-message/chat-message.module.scss +++ b/src/components/chat-message/chat-message.module.scss @@ -53,6 +53,34 @@ } } + .action-button { + cursor: pointer; + font-family: $font-chat; + font-size: 1em; + margin: 0.7rem 0.3rem 0.3rem 0.3rem; + padding: 0.3rem 1.5rem; + background-color: $color-primary; + color: white; + border: none; + border-radius: 4px; + box-shadow: 2px 1px 4px grey; + + &:hover { + filter: brightness(0.95); + } + + &:active:not(:disabled) { + filter: brightness(0.75); + box-shadow: none; + } + + &:disabled { + opacity: 0.5; + box-shadow: none; + cursor: default; + } + } + .decline-event-button, .redirect-event-button { cursor: pointer; diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index 04a0a34c..a207f6a4 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -17,6 +17,7 @@ import { updateMessage, } from "../../../slices/chat-slice"; import { useAppDispatch } from "../../../store"; +import ChatButtonGroup from "./chat-button-group"; const leftAnimation = { animate: { opacity: 1, x: 0 }, @@ -24,9 +25,8 @@ const leftAnimation = { transition: { duration: 0.25, delay: 0.25 }, }; -const AdminMessage = (props: { message: Message }): JSX.Element => { +const AdminMessage = ({ message }: { message: Message }): JSX.Element => { const dispatch = useAppDispatch(); - const { message } = props; const setNewFeedbackRating = (newRating: string): void => { const updatedMessage = { @@ -44,25 +44,38 @@ const AdminMessage = (props: { message: Message }): JSX.Element => { transition={leftAnimation.transition} >
- {CLIENT_NAME_ENABLED && ( -
{message.authorRole}
- )} + { + CLIENT_NAME_ENABLED && + message.event !== CHAT_EVENTS.BUTTONS && ( +
{message.authorRole}
+ ) + }
-
- {message.event === CHAT_EVENTS.EMERGENCY_NOTICE ? ( -
!
- ) : ( - Robot icon - )} -
-
- -
+ { + message.event !== CHAT_EVENTS.BUTTONS && ( +
+ {message.event === CHAT_EVENTS.EMERGENCY_NOTICE ? ( +
!
+ ) : ( + Robot icon + )} +
+ ) + } + { + message.event === CHAT_EVENTS.BUTTONS + ? + : ( +
+ +
+ ) + }
{ : styles.row )} > - {![CHAT_EVENTS.GREETING, CHAT_EVENTS.EMERGENCY_NOTICE].includes( + {![CHAT_EVENTS.GREETING, CHAT_EVENTS.EMERGENCY_NOTICE, CHAT_EVENTS.BUTTONS].includes( message.event as CHAT_EVENTS ) && (
diff --git a/src/components/chat-message/message-types/chat-button-group.tsx b/src/components/chat-message/message-types/chat-button-group.tsx new file mode 100644 index 00000000..242bba1b --- /dev/null +++ b/src/components/chat-message/message-types/chat-button-group.tsx @@ -0,0 +1,59 @@ + +import React, { useMemo, useState } from 'react'; +import { Message } from '../../../model/message-model'; +import { AUTHOR_ROLES } from '../../../constants'; +import { useAppDispatch } from '../../../store'; +import { addMessage, initChat, queueMessage, sendNewMessage } from '../../../slices/chat-slice'; +import useChatSelector from '../../../hooks/use-chat-selector'; +import styles from '../chat-message.module.scss'; + +const ChatButtonGroup = ({ content }: { content: string }): JSX.Element => { + const dispatch = useAppDispatch(); + const { chatId, loading } = useChatSelector(); + const [used, setUsed] = useState(false); + + if(!content) + return <>; + + const buttons = useMemo(() => JSON.parse(content), [content]); + + const addNewMessageToState = (userInput: string): void => { + const message: Message = { + chatId, + content: encodeURIComponent(userInput), + authorTimestamp: new Date().toISOString(), + authorRole: AUTHOR_ROLES.END_USER, + }; + + dispatch(addMessage(message)); + + if (!chatId && !loading) { + dispatch(initChat(message)); + } + if (loading) { + dispatch(queueMessage(message)); + } + if (chatId) { + dispatch(sendNewMessage(message)); + } + + setUsed(true); + } + + return ( +
+ {buttons?.map((button: string) => ( + + ))} +
+ ); +} + +export default ChatButtonGroup; diff --git a/src/constants.ts b/src/constants.ts index 96876bb4..8c64e217 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -68,7 +68,8 @@ export enum CHAT_EVENTS { MESSAGE_READ = 'message-read', UNAVAILABLE_ORGANIZATION = 'unavailable_organization', UNAVAILABLE_CSAS = 'unavailable_csas', - UNAVAILABLE_HOLIDAY = 'unavailable_holiday' + UNAVAILABLE_HOLIDAY = 'unavailable_holiday', + BUTTONS = 'buttons', } export enum TERMINATE_STATUS { diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index d1607434..d7e605fd 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -350,6 +350,13 @@ export const chatSlice = createSlice({ state.newMessagesAmount += receivedMessages.length; state.messages.push(...receivedMessages); setToSessionStorage('newMessagesAmount', state.newMessagesAmount); + + if(state.chatMode !== 'flow') { + const containsButtonEvents = receivedMessages.find((msg) => msg.event === CHAT_EVENTS.BUTTONS); + if(containsButtonEvents){ + state.chatMode = 'flow'; + } + } }, handleStateChangingEventMessages: (state, action: PayloadAction) => { action.payload.forEach((msg) => { @@ -416,6 +423,12 @@ export const chatSlice = createSlice({ } }); }, + switchToFlowMode: (state) => { + state.chatMode = 'flow'; + }, + switchToFreeMode: (state) => { + state.chatMode = 'free'; + }, }, extraReducers: (builder) => { builder.addCase(initChat.pending, (state) => { @@ -526,6 +539,8 @@ export const { setChat, addMessagesToDisplay, handleStateChangingEventMessages, + switchToFlowMode, + switchToFreeMode, } = chatSlice.actions; export default chatSlice.reducer; diff --git a/src/test-initial-states.ts b/src/test-initial-states.ts index 358824b2..786b9e8d 100644 --- a/src/test-initial-states.ts +++ b/src/test-initial-states.ts @@ -61,7 +61,8 @@ export const initialChatState: ChatState = { isSubmitted: false, isFailed: false, } - } + }, + chatMode: 'free', }; export const initialAuthState: AuthenticationState = { From e3acf29f9aea306afa99f65c7b55d2cdc8ab1b16 Mon Sep 17 00:00:00 2001 From: baha-a Date: Thu, 16 Nov 2023 03:16:09 +0300 Subject: [PATCH 05/14] Remove unused code --- .../chat-message/chat-flow-message.tsx | 43 ------------------- 1 file changed, 43 deletions(-) delete mode 100644 src/components/chat-message/chat-flow-message.tsx diff --git a/src/components/chat-message/chat-flow-message.tsx b/src/components/chat-message/chat-flow-message.tsx deleted file mode 100644 index dbaa2753..00000000 --- a/src/components/chat-message/chat-flow-message.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Message } from '../../model/message-model'; -import { AUTHOR_ROLES, CHAT_EVENTS } from '../../constants'; -import AdminMessage from './message-types/admin-message'; -import ClientMessage from './message-types/client-message'; -import EventMessage from './message-types/event-message'; -import AuthenticationMessage from './message-types/authentication-message'; -import PermissionMessage from './message-types/permission-message'; -import RedirectMessage from './message-types/redirect-message'; - -const ChatFlowMessage = (props: { message: Message }): JSX.Element => { - const { t } = useTranslation(); - const { - message, - message: { authorRole, event }, - } = props; - - const endChatMessage = ( - <> -
{t('notifications.chat-ended')}
-
{t('notifications.chat-ended-new-chat')}
- - ); - if (event === CHAT_EVENTS.MESSAGE_READ) return <> - if (event === CHAT_EVENTS.ASK_PERMISSION_IGNORED) return ; - if (event === CHAT_EVENTS.ASK_PERMISSION_ACCEPTED) return ; - if (event === CHAT_EVENTS.ASK_PERMISSION_REJECTED) return ; - if (event === CHAT_EVENTS.ASK_PERMISSION) return } />; - if (event === CHAT_EVENTS.CONTACT_INFORMATION_REJECTED) return ; - if (event === CHAT_EVENTS.REQUESTED_AUTHENTICATION) return } />; - if (event === CHAT_EVENTS.REDIRECTED) return ; - if (event === CHAT_EVENTS.ANSWERED || event === CHAT_EVENTS.TERMINATED) return ; - if (event === CHAT_EVENTS.CONTACT_INFORMATION) return ; - if (event === CHAT_EVENTS.REQUESTED_CHAT_FORWARD) return } />; - if (event === CHAT_EVENTS.REQUESTED_CHAT_FORWARD_ACCEPTED) return ; - if (event === CHAT_EVENTS.REQUESTED_CHAT_FORWARD_REJECTED) return ; - if (event === CHAT_EVENTS.CONTACT_INFORMATION_FULFILLED || authorRole === AUTHOR_ROLES.END_USER) return ; - if (authorRole === AUTHOR_ROLES.END_USER) return ; - return ; -}; - -export default ChatFlowMessage; From f7651553a9cd44af90eac0b31910201ea4073499 Mon Sep 17 00:00:00 2001 From: baha-a Date: Thu, 16 Nov 2023 10:37:54 +0300 Subject: [PATCH 06/14] Refactor & cleanups --- src/components/chat/chat.tsx | 3 ++- src/constants.ts | 7 +++++ src/slices/chat-slice.ts | 19 ++++++++----- src/utils/state-management-utils.ts | 42 ++++++++++++++++++----------- 4 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index bda98939..b51f7544 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -11,6 +11,7 @@ import { IDLE_CHAT_INTERVAL, AUTHOR_ROLES, IDLE_CHAT_CHOICES_INTERVAL, + CHAT_MODES, } from "../../constants"; import ChatContent from "../chat-content/chat-content"; import ChatHeader from "../chat-header/chat-header"; @@ -291,7 +292,7 @@ const Chat = (): JSX.Element => { !showContactForm && !showUnavailableContactForm && !feedback.isFeedbackConfirmationShown && - chatMode === 'free' && } + chatMode === CHAT_MODES.FREE && } )} diff --git a/src/constants.ts b/src/constants.ts index 8c64e217..50823c28 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,6 +36,11 @@ export const CHAT_BUBBLE_ANIMATION = 'shockwave'; export const CHAT_INPUT_DEBOUNCE_TIMEOUT = 500; export const CURRENT_COUNTRY = 'EE'; +export enum CHAT_MODES { + FLOW = 'flow', + FREE = 'free', +} + export enum CHAT_STATUS { ENDED = 'ENDED', OPEN = 'OPEN', @@ -70,6 +75,8 @@ export enum CHAT_EVENTS { UNAVAILABLE_CSAS = 'unavailable_csas', UNAVAILABLE_HOLIDAY = 'unavailable_holiday', BUTTONS = 'buttons', + SWITCH_TO_FLOW_MODE = 'switch-to-flow-mode', + SWITCH_TO_FREE_MODE = 'switch-to-free-mode', } export enum TERMINATE_STATUS { diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index d7e605fd..cd283981 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -18,6 +18,7 @@ import { CHAT_BUBBLE_PROACTIVE_SECONDS, CHAT_SHOW_BUBBLE_MESSAGE, TERMINATE_STATUS, + CHAT_MODES, } from '../constants'; import { getFromSessionStorage, setToSessionStorage } from '../utils/session-storage-utils'; import { Chat } from '../model/chat-model'; @@ -91,7 +92,7 @@ export interface ChatState { isFailed: boolean, } }, - chatMode: 'free' | 'flow', + chatMode: CHAT_MODES, } const initialState: ChatState = { @@ -150,7 +151,7 @@ const initialState: ChatState = { isFailed: false, } }, - chatMode: 'free' + chatMode: CHAT_MODES.FREE }; export const initChat = createAsyncThunk('chat/init', async (message: Message) => { @@ -351,10 +352,10 @@ export const chatSlice = createSlice({ state.messages.push(...receivedMessages); setToSessionStorage('newMessagesAmount', state.newMessagesAmount); - if(state.chatMode !== 'flow') { + if(state.chatMode !== CHAT_MODES.FLOW) { const containsButtonEvents = receivedMessages.find((msg) => msg.event === CHAT_EVENTS.BUTTONS); if(containsButtonEvents){ - state.chatMode = 'flow'; + state.chatMode = CHAT_MODES.FLOW; } } }, @@ -419,15 +420,21 @@ export const chatSlice = createSlice({ clearStateVariablesFromSessionStorage(); state.chatStatus = CHAT_STATUS.ENDED; break; + case CHAT_EVENTS.SWITCH_TO_FLOW_MODE: + state.chatMode = CHAT_MODES.FLOW; + break; + case CHAT_EVENTS.SWITCH_TO_FREE_MODE: + state.chatMode = CHAT_MODES.FREE; + break; default: } }); }, switchToFlowMode: (state) => { - state.chatMode = 'flow'; + state.chatMode = CHAT_MODES.FLOW; }, switchToFreeMode: (state) => { - state.chatMode = 'free'; + state.chatMode = CHAT_MODES.FREE; }, }, extraReducers: (builder) => { diff --git a/src/utils/state-management-utils.ts b/src/utils/state-management-utils.ts index e89de652..22e02a3f 100644 --- a/src/utils/state-management-utils.ts +++ b/src/utils/state-management-utils.ts @@ -19,22 +19,32 @@ export const findMatchingMessageFromMessageList = (messageToMatch: Message, mess message.authorRole === messageToMatch.authorRole, ); -export const isStateChangingEventMessage = (msg: Message): boolean => - msg.event === CHAT_EVENTS.GREETING || - msg.event === CHAT_EVENTS.ASK_PERMISSION_IGNORED || - (msg.event === CHAT_EVENTS.CONTACT_INFORMATION && msg.content?.length === 0) || - msg.event === CHAT_EVENTS.ANSWERED || - msg.event === CHAT_EVENTS.TERMINATED || - msg.event === CHAT_EVENTS.UNAVAILABLE_ORGANIZATION || - msg.event === CHAT_EVENTS.UNAVAILABLE_CSAS || - msg.event === CHAT_EVENTS.UNAVAILABLE_HOLIDAY || - msg.event === TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED || - msg.event === TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION || - msg.event === TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS || - msg.event === TERMINATE_STATUS.ACCEPTED || - msg.event === TERMINATE_STATUS.HATE_SPEECH || - msg.event === TERMINATE_STATUS.OTHER || - msg.event === TERMINATE_STATUS.RESPONSE_SENT_TO_CLIENT_EMAIL +export const isStateChangingEventMessage = (msg: Message): boolean => { + if(!msg.event) + return false; + + const whitelist = [ + CHAT_EVENTS.GREETING, + CHAT_EVENTS.ASK_PERMISSION_IGNORED, + CHAT_EVENTS.ANSWERED, + CHAT_EVENTS.TERMINATED, + CHAT_EVENTS.UNAVAILABLE_ORGANIZATION, + CHAT_EVENTS.UNAVAILABLE_CSAS, + CHAT_EVENTS.UNAVAILABLE_HOLIDAY, + CHAT_EVENTS.SWITCH_TO_FLOW_MODE, + CHAT_EVENTS.SWITCH_TO_FREE_MODE, + TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED, + TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION , + TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS , + TERMINATE_STATUS.ACCEPTED, + TERMINATE_STATUS.HATE_SPEECH , + TERMINATE_STATUS.OTHER , + TERMINATE_STATUS.RESPONSE_SENT_TO_CLIENT_EMAIL, + ]; + + const event = msg.event as CHAT_EVENTS | TERMINATE_STATUS; + return whitelist.includes(event) || (event === CHAT_EVENTS.CONTACT_INFORMATION && msg.content?.length === 0); +} export const clearStateVariablesFromSessionStorage = (): void => { setToSessionStorage(SESSION_STORAGE_CHAT_ID_KEY, null); From 040c0c460299b9ed7cce3a8aa1204cc88318b827 Mon Sep 17 00:00:00 2001 From: baha-a Date: Thu, 16 Nov 2023 11:11:24 +0300 Subject: [PATCH 07/14] Refactor & cleanups --- src/test-initial-states.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test-initial-states.ts b/src/test-initial-states.ts index 786b9e8d..3ec0d8fd 100644 --- a/src/test-initial-states.ts +++ b/src/test-initial-states.ts @@ -1,7 +1,7 @@ import { ChatState } from './slices/chat-slice'; import { AuthenticationState } from './slices/authentication-slice'; import { Message } from './model/message-model'; -import { CHAT_STATUS, CHAT_WINDOW_WIDTH, CHAT_WINDOW_HEIGHT, CHAT_BUBBLE_ANIMATION, CHAT_BUBBLE_COLOR, CHAT_BUBBLE_MESSAGE_DELAY_SECONDS, CHAT_BUBBLE_PROACTIVE_SECONDS, CHAT_SHOW_BUBBLE_MESSAGE } from './constants'; +import { CHAT_STATUS, CHAT_WINDOW_WIDTH, CHAT_WINDOW_HEIGHT, CHAT_BUBBLE_ANIMATION, CHAT_BUBBLE_COLOR, CHAT_BUBBLE_MESSAGE_DELAY_SECONDS, CHAT_BUBBLE_PROACTIVE_SECONDS, CHAT_SHOW_BUBBLE_MESSAGE, CHAT_MODES } from './constants'; export const initialChatState: ChatState = { endUserContacts: { @@ -62,7 +62,7 @@ export const initialChatState: ChatState = { isFailed: false, } }, - chatMode: 'free', + chatMode: CHAT_MODES.FREE, }; export const initialAuthState: AuthenticationState = { From 3b2d6fe64d36de30a5182080f19bcd918ad9cf6e Mon Sep 17 00:00:00 2001 From: baha-a Date: Fri, 17 Nov 2023 16:21:10 +0300 Subject: [PATCH 08/14] Remove buttons event and use `message.buttons` instead --- .../chat-message/chat-message.module.scss | 4 ++ .../message-types/admin-message.tsx | 46 ++++++++----------- .../message-types/chat-button-group.tsx | 45 ++++++++++-------- src/constants.ts | 5 +- src/model/message-model.ts | 1 + src/slices/chat-slice.ts | 22 +-------- src/utils/state-management-utils.ts | 2 - 7 files changed, 53 insertions(+), 72 deletions(-) diff --git a/src/components/chat-message/chat-message.module.scss b/src/components/chat-message/chat-message.module.scss index 9776d571..215f0ab0 100644 --- a/src/components/chat-message/chat-message.module.scss +++ b/src/components/chat-message/chat-message.module.scss @@ -81,6 +81,10 @@ } } + .buttonsRow { + margin-left: 2.8em; + } + .decline-event-button, .redirect-event-button { cursor: pointer; diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index a207f6a4..afe6f328 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -45,37 +45,26 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { >
{ - CLIENT_NAME_ENABLED && - message.event !== CHAT_EVENTS.BUTTONS && ( + CLIENT_NAME_ENABLED && (
{message.authorRole}
) }
- { - message.event !== CHAT_EVENTS.BUTTONS && ( -
- {message.event === CHAT_EVENTS.EMERGENCY_NOTICE ? ( -
!
- ) : ( - Robot icon - )} -
- ) - } - { - message.event === CHAT_EVENTS.BUTTONS - ? - : ( -
- -
- ) - } +
+ {message.event === CHAT_EVENTS.EMERGENCY_NOTICE ? ( +
!
+ ) : ( + Robot icon + )} +
+
+ +
{ : styles.row )} > - {![CHAT_EVENTS.GREETING, CHAT_EVENTS.EMERGENCY_NOTICE, CHAT_EVENTS.BUTTONS].includes( + {![CHAT_EVENTS.GREETING, CHAT_EVENTS.EMERGENCY_NOTICE].includes( message.event as CHAT_EVENTS ) && (
@@ -124,6 +113,7 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { )}
+ {message.buttons && }
); diff --git a/src/components/chat-message/message-types/chat-button-group.tsx b/src/components/chat-message/message-types/chat-button-group.tsx index 242bba1b..197b5a56 100644 --- a/src/components/chat-message/message-types/chat-button-group.tsx +++ b/src/components/chat-message/message-types/chat-button-group.tsx @@ -1,26 +1,34 @@ import React, { useMemo, useState } from 'react'; import { Message } from '../../../model/message-model'; -import { AUTHOR_ROLES } from '../../../constants'; +import { AUTHOR_ROLES, CHAT_MODES } from '../../../constants'; import { useAppDispatch } from '../../../store'; import { addMessage, initChat, queueMessage, sendNewMessage } from '../../../slices/chat-slice'; import useChatSelector from '../../../hooks/use-chat-selector'; import styles from '../chat-message.module.scss'; -const ChatButtonGroup = ({ content }: { content: string }): JSX.Element => { - const dispatch = useAppDispatch(); - const { chatId, loading } = useChatSelector(); - const [used, setUsed] = useState(false); +type MessageButton = { + title: string; + payload: string; +} - if(!content) - return <>; +const ChatButtonGroup = ({ message }: { message: Message }): JSX.Element => { + const dispatch = useAppDispatch(); + const { chatId, loading, chatMode, messages } = useChatSelector(); - const buttons = useMemo(() => JSON.parse(content), [content]); + const parsedButtons: MessageButton[] = useMemo(() => { + try { + return JSON.parse(decodeURIComponent(message.buttons!)) as MessageButton[]; + } catch(e) { + console.error(e); + return []; + } + }, [message.buttons]); - const addNewMessageToState = (userInput: string): void => { + const addNewMessageToState = (buttonPayload: string): void => { const message: Message = { chatId, - content: encodeURIComponent(userInput), + content: encodeURIComponent(buttonPayload), authorTimestamp: new Date().toISOString(), authorRole: AUTHOR_ROLES.END_USER, }; @@ -36,20 +44,21 @@ const ChatButtonGroup = ({ content }: { content: string }): JSX.Element => { if (chatId) { dispatch(sendNewMessage(message)); } - - setUsed(true); } + const enabled = messages[messages.length - 1] === message && chatMode === CHAT_MODES.FLOW; + + return ( -
- {buttons?.map((button: string) => ( +
+ {parsedButtons?.map(({ title, payload }) => ( ))}
diff --git a/src/constants.ts b/src/constants.ts index 50823c28..2d55d20d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -73,10 +73,7 @@ export enum CHAT_EVENTS { MESSAGE_READ = 'message-read', UNAVAILABLE_ORGANIZATION = 'unavailable_organization', UNAVAILABLE_CSAS = 'unavailable_csas', - UNAVAILABLE_HOLIDAY = 'unavailable_holiday', - BUTTONS = 'buttons', - SWITCH_TO_FLOW_MODE = 'switch-to-flow-mode', - SWITCH_TO_FREE_MODE = 'switch-to-free-mode', + UNAVAILABLE_HOLIDAY = 'unavailable_holiday' } export enum TERMINATE_STATUS { diff --git a/src/model/message-model.ts b/src/model/message-model.ts index 4ac672c6..89cb5e88 100644 --- a/src/model/message-model.ts +++ b/src/model/message-model.ts @@ -10,6 +10,7 @@ export interface Message { chatId: string | null; id?: string; content?: string; + buttons?: string; file?: Attachment; event?: string; rating?: string; diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index cd283981..6aaec560 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -352,12 +352,8 @@ export const chatSlice = createSlice({ state.messages.push(...receivedMessages); setToSessionStorage('newMessagesAmount', state.newMessagesAmount); - if(state.chatMode !== CHAT_MODES.FLOW) { - const containsButtonEvents = receivedMessages.find((msg) => msg.event === CHAT_EVENTS.BUTTONS); - if(containsButtonEvents){ - state.chatMode = CHAT_MODES.FLOW; - } - } + state.chatMode = receivedMessages[receivedMessages.length - 1]?.buttons + ? CHAT_MODES.FLOW : CHAT_MODES.FREE; }, handleStateChangingEventMessages: (state, action: PayloadAction) => { action.payload.forEach((msg) => { @@ -420,22 +416,10 @@ export const chatSlice = createSlice({ clearStateVariablesFromSessionStorage(); state.chatStatus = CHAT_STATUS.ENDED; break; - case CHAT_EVENTS.SWITCH_TO_FLOW_MODE: - state.chatMode = CHAT_MODES.FLOW; - break; - case CHAT_EVENTS.SWITCH_TO_FREE_MODE: - state.chatMode = CHAT_MODES.FREE; - break; default: } }); }, - switchToFlowMode: (state) => { - state.chatMode = CHAT_MODES.FLOW; - }, - switchToFreeMode: (state) => { - state.chatMode = CHAT_MODES.FREE; - }, }, extraReducers: (builder) => { builder.addCase(initChat.pending, (state) => { @@ -546,8 +530,6 @@ export const { setChat, addMessagesToDisplay, handleStateChangingEventMessages, - switchToFlowMode, - switchToFreeMode, } = chatSlice.actions; export default chatSlice.reducer; diff --git a/src/utils/state-management-utils.ts b/src/utils/state-management-utils.ts index 22e02a3f..0ceb6e94 100644 --- a/src/utils/state-management-utils.ts +++ b/src/utils/state-management-utils.ts @@ -31,8 +31,6 @@ export const isStateChangingEventMessage = (msg: Message): boolean => { CHAT_EVENTS.UNAVAILABLE_ORGANIZATION, CHAT_EVENTS.UNAVAILABLE_CSAS, CHAT_EVENTS.UNAVAILABLE_HOLIDAY, - CHAT_EVENTS.SWITCH_TO_FLOW_MODE, - CHAT_EVENTS.SWITCH_TO_FREE_MODE, TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED, TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION , TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS , From db733bd9acac2bcbc2cfd47bd093a413c5c422a6 Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 21 Nov 2023 10:49:11 +0300 Subject: [PATCH 09/14] Sync with mvp version --- .../chat-message/message-types/admin-message.tsx | 2 +- .../chat-message/message-types/chat-button-group.tsx | 3 ++- src/slices/chat-slice.ts | 11 +++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index afe6f328..e4e42761 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -77,7 +77,7 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { > {![CHAT_EVENTS.GREETING, CHAT_EVENTS.EMERGENCY_NOTICE].includes( message.event as CHAT_EVENTS - ) && ( + ) && !message.buttons && (
- {message.buttons && } + {hasButtons && }
); diff --git a/src/components/chat-message/message-types/chat-button-group.tsx b/src/components/chat-message/message-types/chat-button-group.tsx index 273b7394..942c7579 100644 --- a/src/components/chat-message/message-types/chat-button-group.tsx +++ b/src/components/chat-message/message-types/chat-button-group.tsx @@ -5,24 +5,16 @@ import { AUTHOR_ROLES, CHAT_MODES } from '../../../constants'; import { useAppDispatch } from '../../../store'; import { addMessage, initChat, queueMessage, sendNewMessage } from '../../../slices/chat-slice'; import useChatSelector from '../../../hooks/use-chat-selector'; +import { parseButtons } from '../../../utils/chat-utils'; import styles from '../chat-message.module.scss'; -type MessageButton = { - title: string; - payload: string; -} const ChatButtonGroup = ({ message }: { message: Message }): JSX.Element => { const dispatch = useAppDispatch(); const { chatId, loading, chatMode, messages } = useChatSelector(); - const parsedButtons: MessageButton[] = useMemo(() => { - try { - return JSON.parse(decodeURIComponent(message.buttons!)) as MessageButton[]; - } catch(e) { - console.error(e); - return []; - } + const parsedButtons = useMemo(() => { + return parseButtons(message); }, [message.buttons]); const addNewMessageToState = (buttonPayload: string): void => { @@ -48,7 +40,6 @@ const ChatButtonGroup = ({ message }: { message: Message }): JSX.Element => { const enabled = messages[messages.length - 1] === message && chatMode === CHAT_MODES.FLOW; - return (
{parsedButtons?.map(({ title, payload }) => ( diff --git a/src/model/message-model.ts b/src/model/message-model.ts index 89cb5e88..d0f8f4f8 100644 --- a/src/model/message-model.ts +++ b/src/model/message-model.ts @@ -48,3 +48,8 @@ export enum AttachmentTypes { OGG = 'video/ogg', MOV = 'video/quicktime', } + +export interface MessageButton { + title: string; + payload: string; +} diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index 1b340c11..19de5438 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -29,6 +29,7 @@ import { } from '../utils/state-management-utils'; import { setToLocalStorage } from '../utils/local-storage-utils'; import getHolidays from '../utils/holidays'; +import { getChatModeBasedOnLastMessage } from '../utils/chat-utils'; export interface EstimatedWaiting { positionInUnassignedChats: string; @@ -352,10 +353,7 @@ export const chatSlice = createSlice({ state.messages.push(...receivedMessages); setToSessionStorage('newMessagesAmount', state.newMessagesAmount); - if(receivedMessages && receivedMessages.length > 0){ - state.chatMode = receivedMessages[receivedMessages.length - 1]?.buttons - ? CHAT_MODES.FLOW : CHAT_MODES.FREE; - } + state.chatMode = getChatModeBasedOnLastMessage(receivedMessages); }, handleStateChangingEventMessages: (state, action: PayloadAction) => { action.payload.forEach((msg) => { @@ -443,10 +441,7 @@ export const chatSlice = createSlice({ state.lastReadMessageTimestamp = new Date().toISOString(); state.messages = action.payload; - if(state.messages && state.messages.length > 0){ - state.chatMode = state.messages[state.messages.length - 1]?.buttons - ? CHAT_MODES.FLOW : CHAT_MODES.FREE; - } + state.chatMode = getChatModeBasedOnLastMessage(state.messages); }); builder.addCase(getGreeting.fulfilled, (state, action) => { if (!action.payload.isActive) return; diff --git a/src/utils/chat-utils.ts b/src/utils/chat-utils.ts new file mode 100644 index 00000000..2dd70ad3 --- /dev/null +++ b/src/utils/chat-utils.ts @@ -0,0 +1,23 @@ +import { CHAT_MODES } from "../constants"; +import { Message, MessageButton } from "../model/message-model"; + +export const parseButtons = (message: Message): MessageButton[] => { + try { + return JSON.parse(decodeURIComponent(message?.buttons || '[]')) as MessageButton[]; + } catch(e) { + console.error(e); + return []; + } +} + +export const getChatModeBasedOnLastMessage = (messages: Message[]): CHAT_MODES => { + let lastMsgButtonsCount = 0; + + if(messages && messages.length > 0) { + const lastMsg = messages[messages.length - 1]; + const buttons = parseButtons(lastMsg); + lastMsgButtonsCount = buttons.length; + } + + return lastMsgButtonsCount === 0 ? CHAT_MODES.FREE : CHAT_MODES.FLOW; +} From 9125f35b348dc7350e0a271637b991257cdd8d1b Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 28 Nov 2023 12:42:46 +0300 Subject: [PATCH 11/14] Avoid extra sse connections --- src/hooks/use-get-chat.ts | 30 +++++++++----- src/hooks/use-get-new-messages.ts | 67 ++++++++++++++++++------------- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/hooks/use-get-chat.ts b/src/hooks/use-get-chat.ts index eba1ad4c..8fa41e77 100644 --- a/src/hooks/use-get-chat.ts +++ b/src/hooks/use-get-chat.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useAppDispatch } from '../store'; import sse from '../services/sse-service'; import useChatSelector from './use-chat-selector'; @@ -7,22 +7,30 @@ import { setChat } from '../slices/chat-slice'; import { Chat } from '../model/chat-model'; const useGetChat = (): void => { - const { lastReadMessageTimestamp, isChatEnded, isChatRedirected, chatId } = useChatSelector(); + const { isChatEnded, chatId } = useChatSelector(); const dispatch = useAppDispatch(); + const [sseUrl, setSseUrl] = useState(''); useEffect(() => { - if (!chatId || isChatEnded) return undefined; - - const events = sse( - `${RUUTER_ENDPOINTS.GET_CHAT_BY_ID}?id=${chatId}`, - (data: Chat) => dispatch(setChat(data)) - ); + if (isChatEnded || !chatId){ + setSseUrl(''); + } else if(chatId) { + setSseUrl(`${RUUTER_ENDPOINTS.GET_CHAT_BY_ID}?id=${chatId}`); + } + }, [chatId, isChatEnded]); + useEffect(() => { + let events: EventSource | undefined; + if (sseUrl){ + events = sse( + sseUrl, + (data: Chat) => dispatch(setChat(data)) + ); + } return () => { - events.close(); + events?.close(); }; - - }, [dispatch, lastReadMessageTimestamp, chatId, isChatEnded, isChatRedirected]); + }, [sseUrl]); }; export default useGetChat; diff --git a/src/hooks/use-get-new-messages.ts b/src/hooks/use-get-new-messages.ts index dcdcb382..63ab17b0 100644 --- a/src/hooks/use-get-new-messages.ts +++ b/src/hooks/use-get-new-messages.ts @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useAppDispatch } from '../store'; import sse from '../services/sse-service'; import useChatSelector from './use-chat-selector'; @@ -12,38 +12,51 @@ const useGetNewMessages = (): void => { const { lastReadMessageTimestamp, isChatEnded, chatId } = useChatSelector(); const { jwtCookie } = useAuthenticationSelector(); const dispatch = useAppDispatch(); - + const [sseUrl, setSseUrl] = useState(''); + const [lastReadMessageTimestampValue, setLastReadMessageTimestampValue] = useState(''); + useEffect(() => { - if (!chatId || isChatEnded || !lastReadMessageTimestamp) return undefined; - - const onMessage = (messages: Message[]) => { - const nonDisplayableEvent = [ - CHAT_EVENTS.GREETING.toString(), - TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED.toString(), - TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION.toString(), - TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS.toString(), - TERMINATE_STATUS.ACCEPTED.toString(), - TERMINATE_STATUS.HATE_SPEECH.toString(), - TERMINATE_STATUS.OTHER.toString(), - TERMINATE_STATUS.RESPONSE_SENT_TO_CLIENT_EMAIL.toString(), - ] + if(lastReadMessageTimestamp && !lastReadMessageTimestampValue){ + setLastReadMessageTimestampValue(lastReadMessageTimestamp); + } + }, [lastReadMessageTimestamp]); - const newDisplayableMessages = messages.filter((msg) => !nonDisplayableEvent.includes(msg.event!)); - const stateChangingEventMessages = messages.filter((msg) => isStateChangingEventMessage(msg)); - dispatch(addMessagesToDisplay(newDisplayableMessages)); - dispatch(handleStateChangingEventMessages(stateChangingEventMessages)); - }; + useEffect(() => { + if(isChatEnded || !chatId) { + setSseUrl(''); + } + else if (chatId && lastReadMessageTimestampValue) { + setSseUrl(`${RUUTER_ENDPOINTS.GET_NEW_MESSAGES}?chatId=${chatId}&timeRangeBegin=${lastReadMessageTimestampValue.split('+')[0]}`); + } + }, [isChatEnded, chatId, lastReadMessageTimestampValue]); - const events = sse( - `${RUUTER_ENDPOINTS.GET_NEW_MESSAGES}?chatId=${chatId}&timeRangeBegin=${lastReadMessageTimestamp.split('+')[0]}`, - onMessage - ); + useEffect(() => { + let events: EventSource | undefined; + if (sseUrl) { + const onMessage = (messages: Message[]) => { + const nonDisplayableEvent = [ + CHAT_EVENTS.GREETING.toString(), + TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED.toString(), + TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION.toString(), + TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS.toString(), + TERMINATE_STATUS.ACCEPTED.toString(), + TERMINATE_STATUS.HATE_SPEECH.toString(), + TERMINATE_STATUS.OTHER.toString(), + TERMINATE_STATUS.RESPONSE_SENT_TO_CLIENT_EMAIL.toString(), + ] + + const newDisplayableMessages = messages.filter((msg) => !nonDisplayableEvent.includes(msg.event!)); + const stateChangingEventMessages = messages.filter((msg) => isStateChangingEventMessage(msg)); + dispatch(addMessagesToDisplay(newDisplayableMessages)); + dispatch(handleStateChangingEventMessages(stateChangingEventMessages)); + }; + events = sse(sseUrl, onMessage); + } return () => { - events.close(); + events?.close(); }; - - }, [dispatch, lastReadMessageTimestamp, chatId, isChatEnded, jwtCookie]); + }, [sseUrl]); }; export default useGetNewMessages; From 02dd64d2d699ea8eb9435adc333b15222626a9ba Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 28 Nov 2023 12:43:18 +0300 Subject: [PATCH 12/14] Use shorter audio file --- src/static/ding.mp3 | Bin 65574 -> 26266 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/src/static/ding.mp3 b/src/static/ding.mp3 index 917c9b885d6f22e1d3ebf77a81f36a6365e9314f..af75c65a52ac4e99c8d3e26b688552fc6169259a 100644 GIT binary patch delta 193 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 Date: Tue, 28 Nov 2023 12:43:26 +0300 Subject: [PATCH 13/14] Clean ups --- src/services/sse-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/sse-service.ts b/src/services/sse-service.ts index 82303bbd..fbf05201 100644 --- a/src/services/sse-service.ts +++ b/src/services/sse-service.ts @@ -16,11 +16,11 @@ const sse = (url: string, onMessage: (data: T) => void): EventSource => { }; eventSource.onopen = () => { - console.log("SSE connection Opened"); + console.log('SSE connection opened, url:', url); }; eventSource.onerror = () => { - console.error('SSE error'); + console.error('SSE error, url:', url); }; return eventSource; From 992afa8565ea0573ee6dad9e6995939d4e3fd932 Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 28 Nov 2023 16:21:24 +0300 Subject: [PATCH 14/14] Fix not clickable chat buttons issue --- src/slices/chat-slice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index 19de5438..7476d70a 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -353,7 +353,7 @@ export const chatSlice = createSlice({ state.messages.push(...receivedMessages); setToSessionStorage('newMessagesAmount', state.newMessagesAmount); - state.chatMode = getChatModeBasedOnLastMessage(receivedMessages); + state.chatMode = getChatModeBasedOnLastMessage(state.messages); }, handleStateChangingEventMessages: (state, action: PayloadAction) => { action.payload.forEach((msg) => {