From 3ff1183e61cee567fac33eda447d3016cbca04f0 Mon Sep 17 00:00:00 2001 From: baha-a Date: Thu, 13 Jun 2024 18:30:50 +0200 Subject: [PATCH 01/95] Fetch user info on refresh --- src/hooks/use-authentication.ts | 13 ++++++++++--- src/services/authentication-service.ts | 5 +++++ src/utils/auth-utils.ts | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/hooks/use-authentication.ts b/src/hooks/use-authentication.ts index bbe3359d..63ef0e68 100644 --- a/src/hooks/use-authentication.ts +++ b/src/hooks/use-authentication.ts @@ -1,12 +1,20 @@ import { useEffect } from 'react'; import { useAppDispatch } from '../store'; -import { loginWithTaraJwt, setIsAuthenticated } from '../slices/authentication-slice'; +import { getUserinfo, loginWithTaraJwt, setIsAuthenticated } from '../slices/authentication-slice'; import useAuthenticationSelector from './use-authentication-selector'; import { redirectIfComeBackFromTim } from '../utils/auth-utils'; +import authenticationService from '../services/authentication-service'; const useAuthentication = (): void => { const dispatch = useAppDispatch(); const { isAuthenticated, fetchingUserInfo, loggedInWithTaraJwt } = useAuthenticationSelector(); + + useEffect(() => { + const cookieFound = authenticationService.hasCookie(); + if (cookieFound) { + dispatch(getUserinfo()); + } + }, []); useEffect(() => { redirectIfComeBackFromTim(() => { @@ -14,8 +22,7 @@ const useAuthentication = (): void => { }); if (!isAuthenticated || loggedInWithTaraJwt) { - // To be done: TO be uncommented later when end user authentication is ready - // dispatch(getUserinfo()); + dispatch(getUserinfo()); } }, [isAuthenticated, loggedInWithTaraJwt]); diff --git a/src/services/authentication-service.ts b/src/services/authentication-service.ts index 88438c06..a26fe33c 100644 --- a/src/services/authentication-service.ts +++ b/src/services/authentication-service.ts @@ -15,6 +15,11 @@ class AuthenticationService { const response = await http.get(RUUTER_ENDPOINTS.CUSTOM_JWT_EXTEND); return { jwtCookie: `${response}` }; } + + hasCookie(): boolean { + const name = 'clientCustomJwtCookie'; + return document.cookie.split(';').some((c) => c.trim().startsWith(`${name}=`)); + } } export default new AuthenticationService(); diff --git a/src/utils/auth-utils.ts b/src/utils/auth-utils.ts index 4607e4d8..c68f84a8 100644 --- a/src/utils/auth-utils.ts +++ b/src/utils/auth-utils.ts @@ -2,7 +2,8 @@ import { LOCAL_STORAGE_TARA_LOGIN_REDIRECT } from "../constants"; import widgetService from "../services/widget-service"; export function redirectToTim() { - saveCurrentBrowserPath(); window.location.assign(window._env_.TIM_AUTHENTICATION_URL); + saveCurrentBrowserPath(); + window.location.assign(window._env_.TIM_AUTHENTICATION_URL); } export function redirectIfComeBackFromTim(callback: any) { From d69ef4d613c4622848db10ee2c0fa66535f2eb89 Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Mon, 17 Jun 2024 00:40:01 +0300 Subject: [PATCH 02/95] Added release notes --- RELEASE_NOTES.md | 34 ++++------------------------------ release.env | 6 +++--- 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9c24dca7..bb8ef5c7 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,33 +1,7 @@ -## v1.0.0-alpha.1 (2024-05-15) - -> Description - -### Upgrade Steps - -- [ACTION REQUIRED] -- +## [v2.0.0](https://github.com/buerokratt/Chat-Widget/compare/main...dev) ### Breaking Changes -- -- - -### New Features - -- -- - -### Bug Fixes - -- -- - -### Performance Improvements - -- -- - -### Other Changes - -- -- +- Added Software Versioning [#561](https://github.com/buerokratt/Buerokratt-Chatbot/issues/561) +- Removed bugs and vulnerabilites [#5](https://github.com/buerokratt/Test-Driven-Development/issues/5) +- Serve static content on ngnix [#612](https://github.com/buerokratt/Buerokratt-Chatbot/issues/612) diff --git a/release.env b/release.env index 3b79496f..59c9b56d 100644 --- a/release.env +++ b/release.env @@ -1,18 +1,18 @@ BRANCH=test -MAJOR=1 +MAJOR=2 MINOR=0 PATCH=0 TYPE=alpha TYPE_VERSION=1 BRANCH=stage -MAJOR=1 +MAJOR=2 MINOR=0 PATCH=0 TYPE=rc TYPE_VERSION=1 BRANCH=main -MAJOR=1 +MAJOR=2 MINOR=0 PATCH=0 From 3b9e79d77f84bac9570b14117536bb6eb860ed68 Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:57:39 +0300 Subject: [PATCH 03/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index da9cb2b2..1ccc42e9 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=5 +FIX=6 From 17ff1aed87e82f0556da353eb160a9e585bbf7fe Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 04:02:48 +0300 Subject: [PATCH 04/95] Added loading message --- src/components/chat-content/chat-content.tsx | 2 ++ .../chat-message/loading-animation.scss | 36 +++++++++++++++++++ .../message-types/loading-message.tsx | 34 ++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 src/components/chat-message/loading-animation.scss create mode 100644 src/components/chat-message/message-types/loading-message.tsx diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index 33842b53..e8c38930 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -7,6 +7,7 @@ import styles from './chat-content.module.scss'; import WaitingTimeNotification from '../waiting-time-notification/waiting-time-notification'; import 'overlayscrollbars/css/OverlayScrollbars.css'; import './os-custom-theme.scss'; +import LoadingMessage from '../chat-message/message-types/loading-message'; const ChatContent = (): JSX.Element => { const OSref = useRef(null); @@ -45,6 +46,7 @@ const ChatContent = (): JSX.Element => { /> ); })} + diff --git a/src/components/chat-message/loading-animation.scss b/src/components/chat-message/loading-animation.scss new file mode 100644 index 00000000..073e464c --- /dev/null +++ b/src/components/chat-message/loading-animation.scss @@ -0,0 +1,36 @@ +body { + display: flex; + align-items: center; + justify-content: center; +} + +.bouncing-loader { + display: flex; + justify-content: center; + margin: 0.65em 0em 0.3em 0.2em; +} + +.bouncing-loader > div { + width: 8px; + height: 8px; + margin: 2px 4px; + border-radius: 50%; + background-color: #ffffff; + opacity: 1; + animation: bouncing-loader 0.6s infinite alternate; +} + +@keyframes bouncing-loader { + to { + opacity: 0.1; + transform: translateY(-8px); + } +} + +.bouncing-loader > div:nth-child(2) { + animation-delay: 0.2s; +} + +.bouncing-loader > div:nth-child(3) { + animation-delay: 0.4s; +} diff --git a/src/components/chat-message/message-types/loading-message.tsx b/src/components/chat-message/message-types/loading-message.tsx new file mode 100644 index 00000000..7ec61971 --- /dev/null +++ b/src/components/chat-message/message-types/loading-message.tsx @@ -0,0 +1,34 @@ +import { motion } from "framer-motion"; +import classNames from "classnames"; +import styles from "../chat-message.module.scss"; +import RobotIcon from "../../../static/icons/buerokratt.svg"; +import "../loading-animation.scss"; + +const leftAnimation = { + animate: { opacity: 1, x: 0 }, + initial: { opacity: 0, x: -20 }, + transition: { duration: 0.25, delay: 0.25 }, +}; + +const LoadingMessage = (): JSX.Element => { + return ( + +
+
+
+ Robot icon +
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default LoadingMessage; From eb935386b8231eb5572eeb99a4fcd554b5ecb543 Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 05:46:03 +0300 Subject: [PATCH 05/95] Added Response Error Message --- src/components/chat-content/chat-content.tsx | 4 +- src/components/chat/chat.tsx | 3 + .../response-error-notification.module.scss | 59 +++++++++++++++++++ .../response-error-notification.tsx | 38 ++++++++++++ src/slices/chat-slice.ts | 51 ++++++++++++++-- src/test-initial-states.ts | 29 +++++---- src/translations/en/common.json | 6 +- src/translations/et/common.json | 6 +- 8 files changed, 173 insertions(+), 23 deletions(-) create mode 100644 src/components/response-error-notification/response-error-notification.module.scss create mode 100644 src/components/response-error-notification/response-error-notification.tsx diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index e8c38930..c3800901 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -11,7 +11,7 @@ import LoadingMessage from '../chat-message/message-types/loading-message'; const ChatContent = (): JSX.Element => { const OSref = useRef(null); - const { messages } = useChatSelector(); + const { messages, showLoadingMessage } = useChatSelector(); useEffect(() => { if (OSref.current) { @@ -46,7 +46,7 @@ const ChatContent = (): JSX.Element => { /> ); })} - + {showLoadingMessage && } diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index f94d59f6..23c8ed55 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -42,6 +42,7 @@ import UnavailableEndUserContacts from "../unavailable-end-user-contacts/unavail import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; import useQueueCounter from "../../hooks/use-queue-counter"; import useWindowDimensions from "../../hooks/useWindowDimensions"; +import ResponseErrorNotification from "../response-error-notification/response-error-notification"; const RESIZABLE_HANDLES = { topLeft: true, @@ -73,6 +74,7 @@ const Chat = (): JSX.Element => { messages, chatDimensions, chatMode, + showResponseError, } = useChatSelector(); const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector((state) => state.widget); @@ -216,6 +218,7 @@ const Chat = (): JSX.Element => { {!showWidgetDetails && showUnavailableContactForm && } {!showWidgetDetails && !showContactForm && !showUnavailableContactForm && } {idleChat.isIdle && } + {showResponseError && } {showFeedbackResult ? ( ) : ( diff --git a/src/components/response-error-notification/response-error-notification.module.scss b/src/components/response-error-notification/response-error-notification.module.scss new file mode 100644 index 00000000..80429cac --- /dev/null +++ b/src/components/response-error-notification/response-error-notification.module.scss @@ -0,0 +1,59 @@ +@use '/src/scss'; + +.container { + background: rgba(234, 2, 2, 0.8); + border-radius: 8px; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2px; +} + +.content { + background-color: #fff; + box-shadow: 0 2px 2px rgba(167, 169, 171, 0.2); + display: flex; + flex-flow: column nowrap; + left: 50%; + min-width: 70%; + padding: 1.5rem; + position: absolute; + top: 50%; + transform: translate3d(-50%, -50%, 0); + width: 80%; + height: 80%; + justify-content: center; + align-items: center; + gap: 10px; +} + +.title { + font-family: scss.$font-chat; + font-style: normal; + font-weight: 400; + font-size: scss.rem(20px); + line-height: 30px; + text-align: center; + color: scss.$color-mustakivi; + margin: 0; +} + +.actions { + align-items: center; + flex-flow: row nowrap; + justify-content: center; + gap: 10px; + column-gap: 10px; + row-gap: 10px; + + button { + margin-top: 15px; + width: 100%; + } +} diff --git a/src/components/response-error-notification/response-error-notification.tsx b/src/components/response-error-notification/response-error-notification.tsx new file mode 100644 index 00000000..d76d1e81 --- /dev/null +++ b/src/components/response-error-notification/response-error-notification.tsx @@ -0,0 +1,38 @@ +import { useTranslation } from "react-i18next"; +import Button from "../button/button"; +import styles from "./response-error-notification.module.scss"; +import { useAppDispatch } from "../../store"; +import { setShowErrorMessage, resetState } from "../../slices/chat-slice"; +import useChatSelector from "../../hooks/use-chat-selector"; + +const ResponseErrorNotification = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { responseErrorMessage } = useChatSelector(); + + console.log("responseErrorMessage", responseErrorMessage); + return ( +
+ +

+ {t(responseErrorMessage.length > 0 ? responseErrorMessage : "widget.error.technicalProblems")} +

+
+ + +
+
+
+ ); +}; + +export default ResponseErrorNotification; diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index d7a035d1..3853317b 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -90,6 +90,9 @@ export interface ChatState { chatMode: CHAT_MODES; nameVisibility: boolean; titleVisibility: boolean; + showLoadingMessage: boolean; + showResponseError: boolean; + responseErrorMessage: string; } const initialEstimatedTime = { @@ -153,6 +156,9 @@ const initialState: ChatState = { chatMode: CHAT_MODES.FREE, nameVisibility: false, titleVisibility: false, + showLoadingMessage: false, + showResponseError: false, + responseErrorMessage: "", }; export const initChat = createAsyncThunk("chat/init", async (message: Message) => { @@ -402,6 +408,15 @@ export const chatSlice = createSlice({ resetNewMessagesAmount: (state) => { state.newMessagesAmount = 0; }, + setResponseErrorMessage: (state, action: PayloadAction) => { + state.responseErrorMessage = action.payload; + }, + setShowErrorMessage: (state, action: PayloadAction) => { + state.showResponseError = action.payload; + if (!action.payload) { + state.responseErrorMessage = ""; + } + }, updateMessage: (state, action: PayloadAction) => { state.messages = state.messages.map((message) => (message.id === action.payload.id ? action.payload : message)); }, @@ -489,21 +504,44 @@ export const chatSlice = createSlice({ }); }, removeEstimatedWaitingMessage: (state) => { - const estimatedMsgIndex = state.messages.findIndex(msg => msg.id === "estimatedWaiting"); - if(estimatedMsgIndex === -1) - return; - state.messages[estimatedMsgIndex].content = 'hidden'; + const estimatedMsgIndex = state.messages.findIndex((msg) => msg.id === "estimatedWaiting"); + if (estimatedMsgIndex === -1) return; + state.messages[estimatedMsgIndex].content = "hidden"; }, }, extraReducers: (builder) => { builder.addCase(initChat.pending, (state) => { state.lastReadMessageTimestamp = new Date().toISOString(); state.loading = true; + state.showLoadingMessage = true; }); builder.addCase(initChat.fulfilled, (state, action) => { state.chatId = action.payload.id; state.loading = false; state.chatStatus = CHAT_STATUS.OPEN; + state.showLoadingMessage = false; + }); + builder.addCase(initChat.rejected, (state) => { + state.showLoadingMessage = false; + state.showResponseError = true; + state.responseErrorMessage = "widget.error.botError"; + }); + builder.addCase(sendNewMessage.pending, (state) => { + if (state.customerSupportId === "chatbot") { + state.showLoadingMessage = true; + } + }); + builder.addCase(sendNewMessage.fulfilled, (state) => { + state.showLoadingMessage = false; + }); + builder.addCase(sendNewMessage.rejected, (state) => { + state.showLoadingMessage = false; + state.showResponseError = true; + if (state.customerSupportId === "chatbot") { + state.responseErrorMessage = "widget.error.botError"; + } else { + state.responseErrorMessage = "widget.error.technicalProblems"; + } }); builder.addCase(getChat.fulfilled, (state, action) => { if (!action.payload) return; @@ -563,8 +601,7 @@ export const chatSlice = createSlice({ state.estimatedWaiting = action.payload; const estimatedMsg = state.messages.find((msg) => msg.id === "estimatedWaiting"); - if(estimatedMsg) - return; + if (estimatedMsg) return; state.messages.push({ id: "estimatedWaiting", @@ -621,6 +658,8 @@ export const { resetNewMessagesAmount, setPhoneNumber, setIsFeedbackConfirmationShown, + setShowErrorMessage, + setResponseErrorMessage, setEmailAdress, setContactFormComment, setShowContactForm, diff --git a/src/test-initial-states.ts b/src/test-initial-states.ts index c3f5fe19..3e24b860 100644 --- a/src/test-initial-states.ts +++ b/src/test-initial-states.ts @@ -5,36 +5,36 @@ import { CHAT_STATUS, CHAT_WINDOW_WIDTH, CHAT_WINDOW_HEIGHT, CHAT_MODES } from ' export const initialChatState: ChatState = { endUserContacts: { - idCode: '', - mailAddress: '', - phoneNr: '', - comment: '', + idCode: "", + mailAddress: "", + phoneNr: "", + comment: "", }, - errorMessage: '', + errorMessage: "", chatId: null, chatDimensions: { width: CHAT_WINDOW_WIDTH, - height: CHAT_WINDOW_HEIGHT + height: CHAT_WINDOW_HEIGHT, }, - customerSupportId: '1', + customerSupportId: "1", isChatOpen: false, showContactForm: false, showUnavailableContactForm: false, - contactContentMessage: '', + contactContentMessage: "", isChatRedirected: false, messages: [], messageQueue: [], eventMessagesToHandle: [], chatStatus: null, lastReadMessageTimestamp: null, - contactMsgId: '', + contactMsgId: "", estimatedWaiting: { - positionInUnassignedChats: '', - durationInSeconds: '', + positionInUnassignedChats: "", + durationInSeconds: "", }, idleChat: { isIdle: false, - lastActive: '', + lastActive: "", }, loading: false, newMessagesAmount: 0, @@ -60,11 +60,14 @@ export const initialChatState: ChatState = { isLoading: false, isSubmitted: false, isFailed: false, - } + }, }, chatMode: CHAT_MODES.FREE, nameVisibility: false, titleVisibility: false, + showLoadingMessage: false, + showResponseError: false, + responseErrorMessage: "", }; export const initialAuthState: AuthenticationState = { diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 0e5048ae..6bb54c46 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -83,5 +83,9 @@ "widget.form.success": "Thank you, you will be contacted as soon as possible.", "widget.form.error": "An error occurred!", "widget.time.minutes": "minutes", - "widget.time.minute": "minute" + "widget.time.minute": "minute", + "widget.action.restartChat": "Start a new chat", + "widget.action.continueChat": "Continue current chat", + "widget.error.botError": "Sorry, the bot stopped working due to technical issues", + "widget.error.technicalProblems": "Sorry, there were technical problems" } diff --git a/src/translations/et/common.json b/src/translations/et/common.json index d9a90ee1..a7efca1d 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -83,5 +83,9 @@ "widget.form.success": "Aitäh pöördumast, teiega võetakse esimesel võimalusel ühendust", "widget.form.error": "Saatmisel esines viga", "widget.time.minutes": "minutit", - "widget.time.minute": "minut" + "widget.time.minute": "minut", + "widget.action.restartChat": "Alustage uut vestlust", + "widget.action.continueChat": "Jätka praegust vestlust", + "widget.error.botError": "Vabandame, lakkas bot tehniliste probleemide tõttu töötamast", + "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid" } From cf5b193aa34ccf5be6f43cabddaf086fc6e8751e Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 05:47:27 +0300 Subject: [PATCH 06/95] Removed Console.log --- .../response-error-notification/response-error-notification.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/response-error-notification/response-error-notification.tsx b/src/components/response-error-notification/response-error-notification.tsx index d76d1e81..c9db8170 100644 --- a/src/components/response-error-notification/response-error-notification.tsx +++ b/src/components/response-error-notification/response-error-notification.tsx @@ -10,7 +10,6 @@ const ResponseErrorNotification = () => { const dispatch = useAppDispatch(); const { responseErrorMessage } = useChatSelector(); - console.log("responseErrorMessage", responseErrorMessage); return (
From 80f805ddbf16d619ff53aa47c9803ea30b68c5c2 Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:23:20 +0300 Subject: [PATCH 07/95] Modified et bot translation --- src/translations/et/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/et/common.json b/src/translations/et/common.json index a7efca1d..5ed3046f 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -86,6 +86,6 @@ "widget.time.minute": "minut", "widget.action.restartChat": "Alustage uut vestlust", "widget.action.continueChat": "Jätka praegust vestlust", - "widget.error.botError": "Vabandame, lakkas bot tehniliste probleemide tõttu töötamast", + "widget.error.botError": "Vabandame, vestlus katkes tehniliste probleemide tõttu.", "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid" } From d0f12585968f25c2bb1f96979cc05eaa017796ce Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:27:40 +0300 Subject: [PATCH 08/95] Understanding if bot is broken or thinking (#230) * Added loading message * Added Response Error Message * Removed Console.log * Modified et bot translation --- src/components/chat-content/chat-content.tsx | 4 +- .../chat-message/loading-animation.scss | 36 +++++++++++ .../message-types/loading-message.tsx | 34 +++++++++++ src/components/chat/chat.tsx | 3 + .../response-error-notification.module.scss | 59 +++++++++++++++++++ .../response-error-notification.tsx | 37 ++++++++++++ src/slices/chat-slice.ts | 51 ++++++++++++++-- src/test-initial-states.ts | 29 +++++---- src/translations/en/common.json | 6 +- src/translations/et/common.json | 6 +- 10 files changed, 243 insertions(+), 22 deletions(-) create mode 100644 src/components/chat-message/loading-animation.scss create mode 100644 src/components/chat-message/message-types/loading-message.tsx create mode 100644 src/components/response-error-notification/response-error-notification.module.scss create mode 100644 src/components/response-error-notification/response-error-notification.tsx diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index 33842b53..c3800901 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -7,10 +7,11 @@ import styles from './chat-content.module.scss'; import WaitingTimeNotification from '../waiting-time-notification/waiting-time-notification'; import 'overlayscrollbars/css/OverlayScrollbars.css'; import './os-custom-theme.scss'; +import LoadingMessage from '../chat-message/message-types/loading-message'; const ChatContent = (): JSX.Element => { const OSref = useRef(null); - const { messages } = useChatSelector(); + const { messages, showLoadingMessage } = useChatSelector(); useEffect(() => { if (OSref.current) { @@ -45,6 +46,7 @@ const ChatContent = (): JSX.Element => { /> ); })} + {showLoadingMessage && }
diff --git a/src/components/chat-message/loading-animation.scss b/src/components/chat-message/loading-animation.scss new file mode 100644 index 00000000..073e464c --- /dev/null +++ b/src/components/chat-message/loading-animation.scss @@ -0,0 +1,36 @@ +body { + display: flex; + align-items: center; + justify-content: center; +} + +.bouncing-loader { + display: flex; + justify-content: center; + margin: 0.65em 0em 0.3em 0.2em; +} + +.bouncing-loader > div { + width: 8px; + height: 8px; + margin: 2px 4px; + border-radius: 50%; + background-color: #ffffff; + opacity: 1; + animation: bouncing-loader 0.6s infinite alternate; +} + +@keyframes bouncing-loader { + to { + opacity: 0.1; + transform: translateY(-8px); + } +} + +.bouncing-loader > div:nth-child(2) { + animation-delay: 0.2s; +} + +.bouncing-loader > div:nth-child(3) { + animation-delay: 0.4s; +} diff --git a/src/components/chat-message/message-types/loading-message.tsx b/src/components/chat-message/message-types/loading-message.tsx new file mode 100644 index 00000000..7ec61971 --- /dev/null +++ b/src/components/chat-message/message-types/loading-message.tsx @@ -0,0 +1,34 @@ +import { motion } from "framer-motion"; +import classNames from "classnames"; +import styles from "../chat-message.module.scss"; +import RobotIcon from "../../../static/icons/buerokratt.svg"; +import "../loading-animation.scss"; + +const leftAnimation = { + animate: { opacity: 1, x: 0 }, + initial: { opacity: 0, x: -20 }, + transition: { duration: 0.25, delay: 0.25 }, +}; + +const LoadingMessage = (): JSX.Element => { + return ( + +
+
+
+ Robot icon +
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default LoadingMessage; diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index f94d59f6..23c8ed55 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -42,6 +42,7 @@ import UnavailableEndUserContacts from "../unavailable-end-user-contacts/unavail import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; import useQueueCounter from "../../hooks/use-queue-counter"; import useWindowDimensions from "../../hooks/useWindowDimensions"; +import ResponseErrorNotification from "../response-error-notification/response-error-notification"; const RESIZABLE_HANDLES = { topLeft: true, @@ -73,6 +74,7 @@ const Chat = (): JSX.Element => { messages, chatDimensions, chatMode, + showResponseError, } = useChatSelector(); const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector((state) => state.widget); @@ -216,6 +218,7 @@ const Chat = (): JSX.Element => { {!showWidgetDetails && showUnavailableContactForm && } {!showWidgetDetails && !showContactForm && !showUnavailableContactForm && } {idleChat.isIdle && } + {showResponseError && } {showFeedbackResult ? ( ) : ( diff --git a/src/components/response-error-notification/response-error-notification.module.scss b/src/components/response-error-notification/response-error-notification.module.scss new file mode 100644 index 00000000..80429cac --- /dev/null +++ b/src/components/response-error-notification/response-error-notification.module.scss @@ -0,0 +1,59 @@ +@use '/src/scss'; + +.container { + background: rgba(234, 2, 2, 0.8); + border-radius: 8px; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 2px; +} + +.content { + background-color: #fff; + box-shadow: 0 2px 2px rgba(167, 169, 171, 0.2); + display: flex; + flex-flow: column nowrap; + left: 50%; + min-width: 70%; + padding: 1.5rem; + position: absolute; + top: 50%; + transform: translate3d(-50%, -50%, 0); + width: 80%; + height: 80%; + justify-content: center; + align-items: center; + gap: 10px; +} + +.title { + font-family: scss.$font-chat; + font-style: normal; + font-weight: 400; + font-size: scss.rem(20px); + line-height: 30px; + text-align: center; + color: scss.$color-mustakivi; + margin: 0; +} + +.actions { + align-items: center; + flex-flow: row nowrap; + justify-content: center; + gap: 10px; + column-gap: 10px; + row-gap: 10px; + + button { + margin-top: 15px; + width: 100%; + } +} diff --git a/src/components/response-error-notification/response-error-notification.tsx b/src/components/response-error-notification/response-error-notification.tsx new file mode 100644 index 00000000..c9db8170 --- /dev/null +++ b/src/components/response-error-notification/response-error-notification.tsx @@ -0,0 +1,37 @@ +import { useTranslation } from "react-i18next"; +import Button from "../button/button"; +import styles from "./response-error-notification.module.scss"; +import { useAppDispatch } from "../../store"; +import { setShowErrorMessage, resetState } from "../../slices/chat-slice"; +import useChatSelector from "../../hooks/use-chat-selector"; + +const ResponseErrorNotification = () => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const { responseErrorMessage } = useChatSelector(); + + return ( +
+ +

+ {t(responseErrorMessage.length > 0 ? responseErrorMessage : "widget.error.technicalProblems")} +

+
+ + +
+
+
+ ); +}; + +export default ResponseErrorNotification; diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index d7a035d1..3853317b 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -90,6 +90,9 @@ export interface ChatState { chatMode: CHAT_MODES; nameVisibility: boolean; titleVisibility: boolean; + showLoadingMessage: boolean; + showResponseError: boolean; + responseErrorMessage: string; } const initialEstimatedTime = { @@ -153,6 +156,9 @@ const initialState: ChatState = { chatMode: CHAT_MODES.FREE, nameVisibility: false, titleVisibility: false, + showLoadingMessage: false, + showResponseError: false, + responseErrorMessage: "", }; export const initChat = createAsyncThunk("chat/init", async (message: Message) => { @@ -402,6 +408,15 @@ export const chatSlice = createSlice({ resetNewMessagesAmount: (state) => { state.newMessagesAmount = 0; }, + setResponseErrorMessage: (state, action: PayloadAction) => { + state.responseErrorMessage = action.payload; + }, + setShowErrorMessage: (state, action: PayloadAction) => { + state.showResponseError = action.payload; + if (!action.payload) { + state.responseErrorMessage = ""; + } + }, updateMessage: (state, action: PayloadAction) => { state.messages = state.messages.map((message) => (message.id === action.payload.id ? action.payload : message)); }, @@ -489,21 +504,44 @@ export const chatSlice = createSlice({ }); }, removeEstimatedWaitingMessage: (state) => { - const estimatedMsgIndex = state.messages.findIndex(msg => msg.id === "estimatedWaiting"); - if(estimatedMsgIndex === -1) - return; - state.messages[estimatedMsgIndex].content = 'hidden'; + const estimatedMsgIndex = state.messages.findIndex((msg) => msg.id === "estimatedWaiting"); + if (estimatedMsgIndex === -1) return; + state.messages[estimatedMsgIndex].content = "hidden"; }, }, extraReducers: (builder) => { builder.addCase(initChat.pending, (state) => { state.lastReadMessageTimestamp = new Date().toISOString(); state.loading = true; + state.showLoadingMessage = true; }); builder.addCase(initChat.fulfilled, (state, action) => { state.chatId = action.payload.id; state.loading = false; state.chatStatus = CHAT_STATUS.OPEN; + state.showLoadingMessage = false; + }); + builder.addCase(initChat.rejected, (state) => { + state.showLoadingMessage = false; + state.showResponseError = true; + state.responseErrorMessage = "widget.error.botError"; + }); + builder.addCase(sendNewMessage.pending, (state) => { + if (state.customerSupportId === "chatbot") { + state.showLoadingMessage = true; + } + }); + builder.addCase(sendNewMessage.fulfilled, (state) => { + state.showLoadingMessage = false; + }); + builder.addCase(sendNewMessage.rejected, (state) => { + state.showLoadingMessage = false; + state.showResponseError = true; + if (state.customerSupportId === "chatbot") { + state.responseErrorMessage = "widget.error.botError"; + } else { + state.responseErrorMessage = "widget.error.technicalProblems"; + } }); builder.addCase(getChat.fulfilled, (state, action) => { if (!action.payload) return; @@ -563,8 +601,7 @@ export const chatSlice = createSlice({ state.estimatedWaiting = action.payload; const estimatedMsg = state.messages.find((msg) => msg.id === "estimatedWaiting"); - if(estimatedMsg) - return; + if (estimatedMsg) return; state.messages.push({ id: "estimatedWaiting", @@ -621,6 +658,8 @@ export const { resetNewMessagesAmount, setPhoneNumber, setIsFeedbackConfirmationShown, + setShowErrorMessage, + setResponseErrorMessage, setEmailAdress, setContactFormComment, setShowContactForm, diff --git a/src/test-initial-states.ts b/src/test-initial-states.ts index c3f5fe19..3e24b860 100644 --- a/src/test-initial-states.ts +++ b/src/test-initial-states.ts @@ -5,36 +5,36 @@ import { CHAT_STATUS, CHAT_WINDOW_WIDTH, CHAT_WINDOW_HEIGHT, CHAT_MODES } from ' export const initialChatState: ChatState = { endUserContacts: { - idCode: '', - mailAddress: '', - phoneNr: '', - comment: '', + idCode: "", + mailAddress: "", + phoneNr: "", + comment: "", }, - errorMessage: '', + errorMessage: "", chatId: null, chatDimensions: { width: CHAT_WINDOW_WIDTH, - height: CHAT_WINDOW_HEIGHT + height: CHAT_WINDOW_HEIGHT, }, - customerSupportId: '1', + customerSupportId: "1", isChatOpen: false, showContactForm: false, showUnavailableContactForm: false, - contactContentMessage: '', + contactContentMessage: "", isChatRedirected: false, messages: [], messageQueue: [], eventMessagesToHandle: [], chatStatus: null, lastReadMessageTimestamp: null, - contactMsgId: '', + contactMsgId: "", estimatedWaiting: { - positionInUnassignedChats: '', - durationInSeconds: '', + positionInUnassignedChats: "", + durationInSeconds: "", }, idleChat: { isIdle: false, - lastActive: '', + lastActive: "", }, loading: false, newMessagesAmount: 0, @@ -60,11 +60,14 @@ export const initialChatState: ChatState = { isLoading: false, isSubmitted: false, isFailed: false, - } + }, }, chatMode: CHAT_MODES.FREE, nameVisibility: false, titleVisibility: false, + showLoadingMessage: false, + showResponseError: false, + responseErrorMessage: "", }; export const initialAuthState: AuthenticationState = { diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 0e5048ae..6bb54c46 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -83,5 +83,9 @@ "widget.form.success": "Thank you, you will be contacted as soon as possible.", "widget.form.error": "An error occurred!", "widget.time.minutes": "minutes", - "widget.time.minute": "minute" + "widget.time.minute": "minute", + "widget.action.restartChat": "Start a new chat", + "widget.action.continueChat": "Continue current chat", + "widget.error.botError": "Sorry, the bot stopped working due to technical issues", + "widget.error.technicalProblems": "Sorry, there were technical problems" } diff --git a/src/translations/et/common.json b/src/translations/et/common.json index d9a90ee1..5ed3046f 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -83,5 +83,9 @@ "widget.form.success": "Aitäh pöördumast, teiega võetakse esimesel võimalusel ühendust", "widget.form.error": "Saatmisel esines viga", "widget.time.minutes": "minutit", - "widget.time.minute": "minut" + "widget.time.minute": "minut", + "widget.action.restartChat": "Alustage uut vestlust", + "widget.action.continueChat": "Jätka praegust vestlust", + "widget.error.botError": "Vabandame, vestlus katkes tehniliste probleemide tõttu.", + "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid" } From 07fe68016ff6e3c381341bc81e01b0c6266b0d2e Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:30:16 +0300 Subject: [PATCH 09/95] Handled bot error --- src/slices/chat-slice.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index 3853317b..00631b0a 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -521,10 +521,14 @@ export const chatSlice = createSlice({ state.chatStatus = CHAT_STATUS.OPEN; state.showLoadingMessage = false; }); - builder.addCase(initChat.rejected, (state) => { + builder.addCase(initChat.rejected, (state, action) => { state.showLoadingMessage = false; state.showResponseError = true; - state.responseErrorMessage = "widget.error.botError"; + if (action.error.message?.includes("code 420")) { + state.responseErrorMessage = "widget.error.botError"; + } else { + state.responseErrorMessage = "widget.error.technicalProblems"; + } }); builder.addCase(sendNewMessage.pending, (state) => { if (state.customerSupportId === "chatbot") { @@ -534,10 +538,10 @@ export const chatSlice = createSlice({ builder.addCase(sendNewMessage.fulfilled, (state) => { state.showLoadingMessage = false; }); - builder.addCase(sendNewMessage.rejected, (state) => { + builder.addCase(sendNewMessage.rejected, (state, action) => { state.showLoadingMessage = false; state.showResponseError = true; - if (state.customerSupportId === "chatbot") { + if (action.error.message?.includes("code 420")) { state.responseErrorMessage = "widget.error.botError"; } else { state.responseErrorMessage = "widget.error.technicalProblems"; From 2e6a3034b7996a0271cbfb663218fe9ba98df6c9 Mon Sep 17 00:00:00 2001 From: ahmedyasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:32:55 +0300 Subject: [PATCH 10/95] removed white spaces --- 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 87363f48..00631b0a 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -544,7 +544,7 @@ export const chatSlice = createSlice({ if (action.error.message?.includes("code 420")) { state.responseErrorMessage = "widget.error.botError"; } else { - state.responseErrorMessage = "widget.error.technicalProblems"; + state.responseErrorMessage = "widget.error.technicalProblems"; } }); builder.addCase(getChat.fulfilled, (state, action) => { From 2469032e8c5985993ea56f4f97587d713ac0053a Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 25 Jun 2024 04:33:04 +0200 Subject: [PATCH 11/95] Handle message with options --- .../chat-message/chat-message.module.scss | 4 ++ .../message-types/admin-message.tsx | 11 +++- .../message-types/chat-option-group.tsx | 58 +++++++++++++++++++ src/model/message-model.ts | 1 + src/utils/chat-utils.ts | 10 ++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/components/chat-message/message-types/chat-option-group.tsx diff --git a/src/components/chat-message/chat-message.module.scss b/src/components/chat-message/chat-message.module.scss index 215f0ab0..cbdd2c0e 100644 --- a/src/components/chat-message/chat-message.module.scss +++ b/src/components/chat-message/chat-message.module.scss @@ -81,6 +81,10 @@ } } + .action-option { + background-color: $color-secondary; + } + .buttonsRow { margin-left: 2.8em; } diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index 07ef0616..1519c222 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -18,7 +18,8 @@ import { } from "../../../slices/chat-slice"; import { useAppDispatch } from "../../../store"; import ChatButtonGroup from "./chat-button-group"; -import { parseButtons } from "../../../utils/chat-utils"; +import ChatOptionGroup from "./chat-option-group"; +import { parseButtons, parseOptions } from "../../../utils/chat-utils"; import useChatSelector from "../../../hooks/use-chat-selector"; const leftAnimation = { @@ -44,6 +45,10 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { return parseButtons(message).length > 0; }, [message.buttons]); + const hasOptions = useMemo(() => { + return parseOptions(message).length > 0; + }, [message.options]); + const csaName = useMemo(() => ( (message.authorFirstName ?? "") + " " + @@ -95,7 +100,8 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { {![CHAT_EVENTS.GREETING, CHAT_EVENTS.EMERGENCY_NOTICE].includes( message.event as CHAT_EVENTS ) && - !hasButtons && isHiddenFeatureEnabled && ( + !hasButtons && !hasOptions && + isHiddenFeatureEnabled && (
{hasButtons && } + {hasOptions && } ); diff --git a/src/components/chat-message/message-types/chat-option-group.tsx b/src/components/chat-message/message-types/chat-option-group.tsx new file mode 100644 index 00000000..d91b0855 --- /dev/null +++ b/src/components/chat-message/message-types/chat-option-group.tsx @@ -0,0 +1,58 @@ +import React, { useMemo } from "react"; +import { Message } from "../../../model/message-model"; +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 { parseOptions } from "../../../utils/chat-utils"; +import styles from "../chat-message.module.scss"; + +const ChatOptionGroup = ({ message }: { message: Message }): JSX.Element => { + const dispatch = useAppDispatch(); + const { chatId, loading, chatMode, messages } = useChatSelector(); + + const parsedOptions = useMemo(() => { + return parseOptions(message); + }, [message.options]); + + const addNewMessageToState = (selectedOption: string): void => { + const message: Message = { + chatId: chatId ?? "", + content: selectedOption, + 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)); + } + }; + + const enabled = messages[messages.length - 1] === message && chatMode === CHAT_MODES.FLOW; + + return ( +
+ {parsedOptions?.map(option => ( + + ))} +
+ ); +}; + +export default ChatOptionGroup; diff --git a/src/model/message-model.ts b/src/model/message-model.ts index 34525c5c..16d45b94 100644 --- a/src/model/message-model.ts +++ b/src/model/message-model.ts @@ -11,6 +11,7 @@ export interface Message { id?: string; content?: string; buttons?: string; + options?: string; file?: Attachment; event?: string; rating?: string; diff --git a/src/utils/chat-utils.ts b/src/utils/chat-utils.ts index 6eef0721..e5137049 100644 --- a/src/utils/chat-utils.ts +++ b/src/utils/chat-utils.ts @@ -12,6 +12,16 @@ export const parseButtons = (message: Message): MessageButton[] => { return []; } } +export const parseOptions = (message: Message): string[] => { + try { + if(!message?.options || message.options === '') + return []; + return JSON.parse(decodeURIComponent(message.options)) as string[]; + } catch(e) { + console.error(e); + return []; + } +} export const getChatModeBasedOnLastMessage = (messages: Message[]): CHAT_MODES => { let lastMsgButtonsCount = 0; From 19799c1c2966099679ddc7acb1b0eda029577e62 Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 25 Jun 2024 05:01:21 +0200 Subject: [PATCH 12/95] Disable options after use --- src/components/chat-message/message-types/admin-message.tsx | 3 +++ .../chat-message/message-types/chat-option-group.tsx | 2 +- src/translations/en/common.json | 1 + src/translations/et/common.json | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index 1519c222..98d528ad 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -21,6 +21,7 @@ import ChatButtonGroup from "./chat-button-group"; import ChatOptionGroup from "./chat-option-group"; import { parseButtons, parseOptions } from "../../../utils/chat-utils"; import useChatSelector from "../../../hooks/use-chat-selector"; +import { useTranslation } from "react-i18next"; const leftAnimation = { animate: { opacity: 1, x: 0 }, @@ -29,6 +30,7 @@ const leftAnimation = { }; const AdminMessage = ({ message }: { message: Message }): JSX.Element => { + const { t } = useTranslation(); const dispatch = useAppDispatch(); const { nameVisibility, titleVisibility } = useChatSelector(); @@ -86,6 +88,7 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { }`} > + {hasOptions && !message.content && t('widget.action.select')}
{ } }; - const enabled = messages[messages.length - 1] === message && chatMode === CHAT_MODES.FLOW; + const enabled = messages[messages.length - 1]?.id === message?.id; return (
diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 0e5048ae..d0117d50 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -76,6 +76,7 @@ "widget.action.download-chat": "Download conversation", "widget.action.forward-chat": "Forward to an email", "widget.action.forward-email": "Your email address", + "widget.action.select": "Choose one of the following options", "widget.form.phone": "Your phone number", "widget.form.email": "Your email address", "widget.form.message": "Type your question here", diff --git a/src/translations/et/common.json b/src/translations/et/common.json index d9a90ee1..28fb2ec5 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -76,6 +76,7 @@ "widget.action.download-chat": "Lae vestlus alla", "widget.action.forward-chat": "Edasta vestlus e-posti", "widget.action.forward-email": "Teie e-posti aadress", + "widget.action.select": "Valige üks järgmistest valikutest", "widget.form.phone": "Teie telefon", "widget.form.email": "Teie e-posti aadress", "widget.form.message": "Täpsustage siin oma küsimust", From e7f0988917570453201d3a87982adbcde0e512f8 Mon Sep 17 00:00:00 2001 From: baha-a Date: Tue, 25 Jun 2024 06:11:32 +0200 Subject: [PATCH 13/95] Add empty line --- src/utils/chat-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/chat-utils.ts b/src/utils/chat-utils.ts index e5137049..30af3cd5 100644 --- a/src/utils/chat-utils.ts +++ b/src/utils/chat-utils.ts @@ -12,6 +12,7 @@ export const parseButtons = (message: Message): MessageButton[] => { return []; } } + export const parseOptions = (message: Message): string[] => { try { if(!message?.options || message.options === '') From 43a57aa0a71cbe032d90df5c3a441133ef329b25 Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:27:44 +0300 Subject: [PATCH 14/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 1ccc42e9..6d67354a 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=6 +FIX=7 From 9b8a51fb60aaef32db8e16f37a871bcafe38ce96 Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Thu, 27 Jun 2024 10:36:35 +0300 Subject: [PATCH 15/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 6d67354a..5f01017a 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=7 +FIX=8 From 7929b332b150831bcb113e8531be1e20a8d78eed Mon Sep 17 00:00:00 2001 From: "juri b." Date: Tue, 6 Aug 2024 10:11:14 +0300 Subject: [PATCH 16/95] Remove 'ask for contact details' from widget when CSA is online (#802) (#299) - removed queue length notification --- src/components/chat/chat.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 23c8ed55..5960cf54 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -40,7 +40,6 @@ import getIdleTime from "../../utils/getIdleTime"; import { Message } from "../../model/message-model"; import UnavailableEndUserContacts from "../unavailable-end-user-contacts/unavailable-end-user-contacts"; import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; -import useQueueCounter from "../../hooks/use-queue-counter"; import useWindowDimensions from "../../hooks/useWindowDimensions"; import ResponseErrorNotification from "../response-error-notification/response-error-notification"; @@ -79,8 +78,6 @@ const Chat = (): JSX.Element => { const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector((state) => state.widget); - const queueCount = useQueueCounter(); - useEffect(() => { if (feedback.isFeedbackRatingGiven && feedback.isFeedbackMessageGiven && !feedback.isFeedbackConfirmationShown) { setShowFeedbackResult(true); @@ -209,9 +206,6 @@ const Chat = (): JSX.Element => { detailHandler={() => setShowWidgetDetails(!showWidgetDetails)} /> {messageQueue.length >= 5 && } - {queueCount > 0 && ( - - )} {burokrattOnlineStatus !== true && } {showWidgetDetails && } {!showWidgetDetails && showContactForm && } From 5079293a5a712b2acd339927007927e1bd8f833c Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Tue, 6 Aug 2024 18:40:16 +0300 Subject: [PATCH 17/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 5f01017a..1c38706e 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=8 +FIX=9 From 000cec5a6af8d6dab00e90df921a936b689953eb Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Tue, 6 Aug 2024 20:01:05 +0300 Subject: [PATCH 18/95] Update ci-build-image.yml --- .github/workflows/ci-build-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build-image.yml b/.github/workflows/ci-build-image.yml index 05a3e6aa..a5333c7e 100644 --- a/.github/workflows/ci-build-image.yml +++ b/.github/workflows/ci-build-image.yml @@ -33,7 +33,7 @@ jobs: echo "$GITHUB_ENV" - name: Docker Build run: | - docker image build -f Dockerfile --tag $DOCKER_TAG_CUSTOM . + docker image build -f Dockerfile --tag $DOCKER_TAG_CUSTOM --no-cache . - name: Log in to GitHub container registry run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin From 9a7d3a06c11882be05122942f7e255959d7c0f36 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Tue, 6 Aug 2024 20:01:17 +0300 Subject: [PATCH 19/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 1c38706e..8c14d8f2 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=9 +FIX=10 From 190bb737d14d97352c3ee7a91832c0557e51b222 Mon Sep 17 00:00:00 2001 From: "juri b." Date: Wed, 7 Aug 2024 10:06:06 +0300 Subject: [PATCH 20/95] Notify end user that chat has been taken over by CSA (#246) (#297) * Notify end user that chat has been taken over by CSA (#246) - added value to CHAT_EVENTS - added notification translation - added event handling * Notify end user that chat has been taken over by CSA (#246) - improved translation wording --- src/components/chat-message/chat-message.tsx | 2 ++ src/constants.ts | 1 + src/translations/en/common.json | 1 + src/translations/et/common.json | 1 + 4 files changed, 5 insertions(+) diff --git a/src/components/chat-message/chat-message.tsx b/src/components/chat-message/chat-message.tsx index 9530f641..ce50d4ed 100644 --- a/src/components/chat-message/chat-message.tsx +++ b/src/components/chat-message/chat-message.tsx @@ -44,6 +44,8 @@ const ChatMessage = (props: { message: Message }): JSX.Element => { return } />; case CHAT_EVENTS.REDIRECTED: return ; + case CHAT_EVENTS.TAKEN_OVER: + return ; case CHAT_EVENTS.ANSWERED: case CHAT_EVENTS.TERMINATED: return ; diff --git a/src/constants.ts b/src/constants.ts index 2f26cdda..eb427c02 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -65,6 +65,7 @@ export enum CHAT_EVENTS { REQUESTED_CHAT_FORWARD_ACCEPTED = 'requested-chat-forward-accepted', REQUESTED_CHAT_FORWARD_REJECTED = 'requested-chat-forward-rejected', REDIRECTED = 'redirected', + TAKEN_OVER = 'taken-over', CONTACT_INFORMATION = 'contact-information', CONTACT_INFORMATION_FULFILLED = 'contact-information-fulfilled', CONTACT_INFORMATION_REJECTED = 'contact-information-rejected', diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 54883cb5..0763dea2 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -18,6 +18,7 @@ "notifications.idle-chat-notification": "Do you want to continue the conversation?", "notifications.offline": "Service currently not available!", "notifications.ask-contact-information": "Would you like to leave your contact information, so customer support can get back to you?", + "notifications.human-took-over": "A human took the conversation over", "keypad.input.placeholder": "Enter your message...", "keypad.input.feedbackPlaceholder": "Enter your feedback...", "keypad.button.label": "Send", diff --git a/src/translations/et/common.json b/src/translations/et/common.json index 0cd4dc89..6ea53423 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -21,6 +21,7 @@ "notifications.offline": "Teenus pole hetkel saadaval!", "notifications.idle-chat-notification": "Kas soovite vestlust jätkata?", "notifications.ask-contact-information": "Kas soovite jätta enda kontaktandmed, et klienditeenindaja saaks ise ühendust võtta?", + "notifications.human-took-over": "Inimene võttis vestluse üle", "keypad.input.placeholder": "Kirjutage oma sõnum...", "keypad.input.feedbackPlaceholder": "Sisestage oma tagasiside...", "keypad.button.label": "Saada", From 5f8ae267a778b82004611089a9809a0c66481c57 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Mon, 12 Aug 2024 08:23:04 +0300 Subject: [PATCH 21/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 8c14d8f2..bbb269c4 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=10 +FIX=11 From d99f1f8b65818766e180b1cac92c73209c323f75 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Wed, 14 Aug 2024 10:21:33 +0000 Subject: [PATCH 22/95] fix: Dockerfile to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-ALPINE318-CURL-7569012 - https://snyk.io/vuln/SNYK-ALPINE318-CURL-7569012 - https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-6913411 - https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249265 - https://snyk.io/vuln/SNYK-ALPINE318-BUSYBOX-7249419 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f6204f9f..9e7ad975 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ARG node_version=node:lts -ARG nginx_version=nginx:1.25.4-alpine +ARG nginx_version=nginx:1.26.0-alpine FROM $node_version AS image From 99eb3ee1b42434a09003716f1e8df4ef2cf79468 Mon Sep 17 00:00:00 2001 From: SanderSepp Date: Wed, 21 Aug 2024 10:12:44 +0300 Subject: [PATCH 23/95] bugfix(#830): Send silent message on unavailable end user contact information submission (#301) - Fixes issue where bot sends unnecessary message after chat is finalized meaning that contact details are submitted with comment and chat window is automatically closed on user side. Co-authored-by: Sander Sepp --- .../unavailable-end-user-contacts.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx b/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx index c7328746..51b194a4 100644 --- a/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx +++ b/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx @@ -14,7 +14,7 @@ import { sendMessagePreview, endChat, setContactFormComment, - sendNewMessage, + sendNewSilentMessage, } from "../../slices/chat-slice"; import { Message } from "../../model/message-model"; import StyledButton from "../styled-components/styled-button"; @@ -81,7 +81,7 @@ const UnavailableEndUserContacts = (): JSX.Element => { contactMsgId, t ); - dispatch(sendNewMessage(commentMsg)); + dispatch(sendNewSilentMessage(commentMsg)); } dispatch( From 388947a1d07e0873f0e3d7fe9510652d0f028028 Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:32:23 +0300 Subject: [PATCH 24/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index bbb269c4..e4b443b9 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=11 +FIX=12 From 9338b35549428b6ce91159016eb8f012f458c34d Mon Sep 17 00:00:00 2001 From: Joonas Roosalu <104758060+joonasroosalung@users.noreply.github.com> Date: Wed, 28 Aug 2024 12:49:23 +0300 Subject: [PATCH 25/95] feat: send message-read event when window becomes active and last message is not event and is backoffice-user message #11 (#304) --- src/components/chat/chat.tsx | 18 ++++++++---------- src/hooks/useTabActive.tsx | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/hooks/useTabActive.tsx diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 5960cf54..727ba9ea 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -42,6 +42,7 @@ import UnavailableEndUserContacts from "../unavailable-end-user-contacts/unavail import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; import useWindowDimensions from "../../hooks/useWindowDimensions"; import ResponseErrorNotification from "../response-error-notification/response-error-notification"; +import useTabActive from '../../hooks/useTabActive'; const RESIZABLE_HANDLES = { topLeft: true, @@ -56,7 +57,6 @@ const RESIZABLE_HANDLES = { const Chat = (): JSX.Element => { const dispatch = useAppDispatch(); - const [submittingMessageRead, setSubmittingMessageRead] = useState(false); const [showWidgetDetails, setShowWidgetDetails] = useState(false); const [showFeedbackResult, setShowFeedbackResult] = useState(false); const { t } = useTranslation(); @@ -76,6 +76,8 @@ const Chat = (): JSX.Element => { showResponseError, } = useChatSelector(); + const isTabActive = useTabActive(); + const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector((state) => state.widget); useEffect(() => { @@ -164,26 +166,22 @@ const Chat = (): JSX.Element => { useReloadChatEndEffect(); - useLayoutEffect(() => { + useEffect(() => { if ( - !submittingMessageRead && + isTabActive && messages.length > 0 && + !messages[messages.length - 1].event && messages[messages.length - 1].authorRole === AUTHOR_ROLES.BACKOFFICE_USER ) { - setSubmittingMessageRead(true); const message: Message = { chatId, - content: CHAT_EVENTS.MESSAGE_READ, authorRole: AUTHOR_ROLES.END_USER, authorTimestamp: new Date().toISOString(), event: CHAT_EVENTS.MESSAGE_READ, - preview: "", }; - dispatch(sendNewMessage(message)).then((_) => { - setSubmittingMessageRead(false); - }); + dispatch(sendNewMessage(message)); } - }, [dispatch, messages]); + }, [isTabActive]); return (
diff --git a/src/hooks/useTabActive.tsx b/src/hooks/useTabActive.tsx new file mode 100644 index 00000000..cfd77fe3 --- /dev/null +++ b/src/hooks/useTabActive.tsx @@ -0,0 +1,20 @@ +import { useCallback, useEffect, useState } from 'react'; + +const useTabActive = () => { + const [isTabActive, setIsTabActive] = useState(true); + + const handleVisibilityChange = useCallback(() => { + setIsTabActive(document.visibilityState === 'visible'); + }, []); + + useEffect(() => { + document.addEventListener('visibilitychange', handleVisibilityChange); + return () => { + document.removeEventListener('visibilitychange', handleVisibilityChange); + }; + }, []); + + return isTabActive; +}; + +export default useTabActive; From d6ce3fced7cd2716a179ee6055fd56d9e9eeac34 Mon Sep 17 00:00:00 2001 From: danyil Date: Fri, 11 Oct 2024 09:30:12 +0300 Subject: [PATCH 26/95] Updated chat style for small screens. --- src/components/chat/chat.module.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/chat/chat.module.scss b/src/components/chat/chat.module.scss index eef02950..08860e1e 100644 --- a/src/components/chat/chat.module.scss +++ b/src/components/chat/chat.module.scss @@ -35,7 +35,10 @@ @media screen and (max-width: 480px) { .chat { - height: auto; + position: fixed; + top: 0; + right: 0; + height: 100vh; width: 100%; } } From 044286d7f146cef829b930de6e3e5b1b4a9b974b Mon Sep 17 00:00:00 2001 From: danyil Date: Fri, 11 Oct 2024 16:55:16 +0300 Subject: [PATCH 27/95] Issue related to Chrome window viewport calculation. --- src/components/chat/chat.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chat/chat.module.scss b/src/components/chat/chat.module.scss index 08860e1e..051ebdcb 100644 --- a/src/components/chat/chat.module.scss +++ b/src/components/chat/chat.module.scss @@ -38,7 +38,7 @@ position: fixed; top: 0; right: 0; - height: 100vh; + height: 100%; width: 100%; } } From 208b5bf043766402e4d1608c60b1b4ae4dc44fd3 Mon Sep 17 00:00:00 2001 From: jaX10bt <132996313+jaX10bt@users.noreply.github.com> Date: Wed, 16 Oct 2024 15:37:11 +0300 Subject: [PATCH 28/95] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 19 +++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 16 ++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..6ffe93c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,19 @@ +--- +name: Bug report +about: For reporting bugs +title: "[BUG]" +labels: bug +assignees: '' + +--- + +**Bug description:** + + +**Expected behavior:** + + +**Screenshots:** + + +**Additional information:** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..178c8783 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,16 @@ +--- +name: Feature request +about: New feature or enhancement +title: '' +labels: '' +assignees: '' + +--- + +**AS A** *role or user type* +**I WANT** *the feature or functionality desired* +**SO THAT** *desired outcome or goal* + +### Acceptance Criteria + +- [ ] ... From a441baf0815d1703080355a8fcc928b5a99d0cc0 Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Wed, 23 Oct 2024 11:30:49 +0300 Subject: [PATCH 29/95] Merging V2.0.1 to dev (#321) * Fixed Chat tab focus & messages if the user is already focused (#311) * Refactor message-to-bot internal endpoint (#308) * Show selected button title instead of payload in chat * Handle empty message content * Update .env * Update .env * Update ci-build-image.yml * Update .env * Added bold text detection * Added bold text detection (#312) * Update .env * Added Markdown Support * Handled Bold Text Detection in scss * Update .env * Updated chat style for small screens. * Update .env * Fix parsing percentages in chat (#318) * Update .env --------- Co-authored-by: Bahaa Alsharif Co-authored-by: jaX10bt <132996313+jaX10bt@users.noreply.github.com> Co-authored-by: Varmo <101868197+varmoh@users.noreply.github.com> Co-authored-by: danyil Co-authored-by: Mihhail Kohhantsuk <32133759+Minwasko@users.noreply.github.com> --- .env | 2 +- .github/workflows/ci-build-image.yml | 2 +- package.json | 3 ++- src/components/chat-content/chat-content.tsx | 3 ++- .../chat-keypad/chat-keypad.module.scss | 3 +++ src/components/chat-keypad/chat-keypad.tsx | 4 +-- .../chat-message/chat-message.module.scss | 1 - src/components/chat-message/chat-message.tsx | 10 ++++++- .../message-types/Markdownify.tsx | 15 +++++++++++ .../message-types/admin-message.tsx | 10 ++++--- .../message-types/client-message.tsx | 10 +++---- .../message-types/linkifier.test.tsx | 2 +- .../chat-message/message-types/linkifier.tsx | 27 ------------------- src/components/chat/chat.module.scss | 9 ++++++- src/components/chat/chat.tsx | 13 ++++++++- src/scss/_mixins.scss | 5 +++- src/scss/_typography.scss | 3 +++ src/translations/en/common.json | 5 ++-- src/translations/et/common.json | 5 ++-- src/utils/chat-utils.ts | 4 +-- 20 files changed, 82 insertions(+), 54 deletions(-) create mode 100644 src/components/chat-message/message-types/Markdownify.tsx delete mode 100644 src/components/chat-message/message-types/linkifier.tsx diff --git a/.env b/.env index e4b443b9..adbf39b2 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=12 +FIX=17 diff --git a/.github/workflows/ci-build-image.yml b/.github/workflows/ci-build-image.yml index a5333c7e..c7ca2f03 100644 --- a/.github/workflows/ci-build-image.yml +++ b/.github/workflows/ci-build-image.yml @@ -3,7 +3,7 @@ name: Build and publish Chat-Widget on: push: branches: - - dev + - v2.0.1 paths: - '.env' diff --git a/package.json b/package.json index 3d6f59b0..d2e8fcde 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@babel/runtime": "^7.12.13", "@reduxjs/toolkit": "^1.6.2", "@types/styled-components": "^5.1.19", + "@xmldom/xmldom": "^0.7.5", "axios": "^1.6.8", "classnames": "^2.3.1", "cross-env": "^7.0.3", @@ -17,8 +18,8 @@ "linkify-react": "^3.0.3", "linkifyjs": "^3.0.3", "luxon": "^2.5.2", + "markdown-to-jsx": "^7.5.0", "msw": "^1.3.3", - "@xmldom/xmldom": "^0.7.5", "overlayscrollbars": "^1.13.1", "overlayscrollbars-react": "^0.2.3", "primeicons": "^4.1.0", diff --git a/src/components/chat-content/chat-content.tsx b/src/components/chat-content/chat-content.tsx index c3800901..47deeccb 100644 --- a/src/components/chat-content/chat-content.tsx +++ b/src/components/chat-content/chat-content.tsx @@ -33,7 +33,7 @@ const ChatContent = (): JSX.Element => { scrollbars: { visibility: 'auto', autoHide: 'leave' }, }} > - {messages.map((message) => { + {messages.map((message, index) => { if(message.id === "estimatedWaiting" && message.content === "hidden") return <>; if(message.id === "estimatedWaiting") @@ -43,6 +43,7 @@ const ChatContent = (): JSX.Element => { 0 ? messages[index - 1] : undefined} /> ); })} diff --git a/src/components/chat-keypad/chat-keypad.module.scss b/src/components/chat-keypad/chat-keypad.module.scss index 320275ac..511e2407 100644 --- a/src/components/chat-keypad/chat-keypad.module.scss +++ b/src/components/chat-keypad/chat-keypad.module.scss @@ -25,6 +25,9 @@ border-bottom: 1px solid $color-primary; border-radius: 0; padding-bottom: 5px; + height: 1.5em; + resize: none; + overflow: hidden; color: inherit; font: inherit; &::placeholder { diff --git a/src/components/chat-keypad/chat-keypad.tsx b/src/components/chat-keypad/chat-keypad.tsx index 00fcc3b8..59e6d57e 100644 --- a/src/components/chat-keypad/chat-keypad.tsx +++ b/src/components/chat-keypad/chat-keypad.tsx @@ -109,7 +109,7 @@ const ChatKeyPad = (): JSX.Element => { if (!isInputValid()) return; const message: Message = { chatId: chatId ?? "", - content: encodeURIComponent(userInput), + content: userInput, file: userInputFile, authorTimestamp: new Date().toISOString(), authorRole: AUTHOR_ROLES.END_USER, @@ -168,7 +168,7 @@ const ChatKeyPad = (): JSX.Element => {
{errorMessage}
- { +const ChatMessage = (props: { message: Message, previousMessage?: Message }): JSX.Element => { const { t } = useTranslation(); const { message, message: { authorRole, event }, + previousMessage, } = props; const endChatMessage = useMemo( @@ -26,6 +28,12 @@ const ChatMessage = (props: { message: Message }): JSX.Element => { [] ); + if(authorRole === AUTHOR_ROLES.END_USER && previousMessage && previousMessage.authorRole !== AUTHOR_ROLES.END_USER && previousMessage?.buttons) { + const selectedButton = parseButtons(previousMessage).find(x => x.payload === message.content); + if(selectedButton) + return + } + switch (event) { case CHAT_EVENTS.MESSAGE_READ: case CHAT_EVENTS.USER_AUTHENTICATED: diff --git a/src/components/chat-message/message-types/Markdownify.tsx b/src/components/chat-message/message-types/Markdownify.tsx new file mode 100644 index 00000000..f2fb7a3c --- /dev/null +++ b/src/components/chat-message/message-types/Markdownify.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import Markdown from "markdown-to-jsx"; + +interface MarkdownifyProps { + message: string | undefined; +} +const Markdownify: React.FC = ({ message }) => ( +
+ + { message ?? '' } + +
+); + +export default Markdownify; diff --git a/src/components/chat-message/message-types/admin-message.tsx b/src/components/chat-message/message-types/admin-message.tsx index 98d528ad..1e3bf24c 100644 --- a/src/components/chat-message/message-types/admin-message.tsx +++ b/src/components/chat-message/message-types/admin-message.tsx @@ -10,7 +10,6 @@ import { RATING_TYPES, isHiddenFeatureEnabled, } from "../../../constants"; -import Linkifier from "./linkifier"; import Thumbs from "../../../static/icons/thumbs.svg"; import { sendMessageWithRating, @@ -22,6 +21,7 @@ import ChatOptionGroup from "./chat-option-group"; import { parseButtons, parseOptions } from "../../../utils/chat-utils"; import useChatSelector from "../../../hooks/use-chat-selector"; import { useTranslation } from "react-i18next"; +import Markdownify from "./Markdownify"; const leftAnimation = { animate: { opacity: 1, x: 0 }, @@ -87,8 +87,12 @@ const AdminMessage = ({ message }: { message: Message }): JSX.Element => { styles.emergency_content }`} > - - {hasOptions && !message.content && t('widget.action.select')} + + {!message.content && ( + hasOptions || hasButtons + ? t('widget.action.select') + : {t('widget.error.empty')} + )}
{ - const { - message: { content }, - } = props; +const ClientMessage = (props: { message?: Message, content?: string }): JSX.Element => { + const content = props.message?.content || props.content; if (props.message?.file) { return ( @@ -53,7 +51,7 @@ const ClientMessage = (props: { message: Message }): JSX.Element => { Person icon
- +
diff --git a/src/components/chat-message/message-types/linkifier.test.tsx b/src/components/chat-message/message-types/linkifier.test.tsx index 38bd1139..49ce6a44 100644 --- a/src/components/chat-message/message-types/linkifier.test.tsx +++ b/src/components/chat-message/message-types/linkifier.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import Linkifier from './linkifier'; +import Linkifier from './Markdownify'; describe('Linkifier', () => { it('should render message link between strings', () => { diff --git a/src/components/chat-message/message-types/linkifier.tsx b/src/components/chat-message/message-types/linkifier.tsx deleted file mode 100644 index fec1f573..00000000 --- a/src/components/chat-message/message-types/linkifier.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import Linkify from 'linkify-react'; - -interface LinkifierProps { - message: string | undefined; -} - -const URL_REGEX = /^(https?:\/\/|www\.)[a-zA-ZõäöüÕÄÖÜ0-9.-]+[\\/]?$/; - -const Linkifier: React.FC = ({ message }) => ( -
- URL_REGEX.test(value), - email: false, - }, - }} - > - {message || ''} - -
-); - -export default Linkifier; diff --git a/src/components/chat/chat.module.scss b/src/components/chat/chat.module.scss index eef02950..22dcccfe 100644 --- a/src/components/chat/chat.module.scss +++ b/src/components/chat/chat.module.scss @@ -27,6 +27,10 @@ border-radius: 8px; font-size: 14px; line-height: 1.5; + + b, strong { + font-family: $font-chat-bold; + } } .authenticated { @@ -35,7 +39,10 @@ @media screen and (max-width: 480px) { .chat { - height: auto; + position: fixed; + top: 0; + right: 0; + height: 100vh; width: 100%; } } diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 727ba9ea..80960a46 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -43,6 +43,7 @@ import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; import useWindowDimensions from "../../hooks/useWindowDimensions"; import ResponseErrorNotification from "../response-error-notification/response-error-notification"; import useTabActive from '../../hooks/useTabActive'; +import { use } from "i18next"; const RESIZABLE_HANDLES = { topLeft: true, @@ -78,6 +79,8 @@ const Chat = (): JSX.Element => { const isTabActive = useTabActive(); + const [isFocused, setIsFocused] = useState(true); + const { burokrattOnlineStatus, showConfirmationModal } = useAppSelector((state) => state.widget); useEffect(() => { @@ -169,6 +172,7 @@ const Chat = (): JSX.Element => { useEffect(() => { if ( isTabActive && + isFocused && messages.length > 0 && !messages[messages.length - 1].event && messages[messages.length - 1].authorRole === AUTHOR_ROLES.BACKOFFICE_USER @@ -181,7 +185,14 @@ const Chat = (): JSX.Element => { }; dispatch(sendNewMessage(message)); } - }, [isTabActive]); + }, [isTabActive, isFocused, messages]); + + window.onfocus = function () { + setIsFocused(true); + }; + window.onblur = function () { + setIsFocused(false); + }; return (
diff --git a/src/scss/_mixins.scss b/src/scss/_mixins.scss index c348043c..7661cd03 100644 --- a/src/scss/_mixins.scss +++ b/src/scss/_mixins.scss @@ -12,6 +12,7 @@ @mixin text-m-bold { @include text-m; + font-family: typography.$font-chat-bold; font-weight: 700; } @@ -25,6 +26,7 @@ @mixin text-s-bold { @include text-s; + font-family: typography.$font-chat-bold; font-weight: 700; } @@ -39,10 +41,11 @@ @mixin text-xs-bold { @include text-xs; + font-family: typography.$font-chat-bold; font-weight: 700; } @mixin button-s { @include text-xs; text-transform: uppercase; -} \ No newline at end of file +} diff --git a/src/scss/_typography.scss b/src/scss/_typography.scss index 812df59e..2f2682db 100644 --- a/src/scss/_typography.scss +++ b/src/scss/_typography.scss @@ -22,4 +22,7 @@ $font-title: 'Aino Headline', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Ro $font-chat: 'Aino Regular', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; +$font-chat-bold: 'Aino Bold', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', + 'Helvetica Neue', sans-serif; + $font-size: 16px; diff --git a/src/translations/en/common.json b/src/translations/en/common.json index 0763dea2..ff0fe356 100644 --- a/src/translations/en/common.json +++ b/src/translations/en/common.json @@ -77,7 +77,7 @@ "widget.action.download-chat": "Download conversation", "widget.action.forward-chat": "Forward to an email", "widget.action.forward-email": "Your email address", - "widget.action.select": "Choose one of the following options", + "widget.action.select": "Choose one of the following options:", "widget.form.phone": "Your phone number", "widget.form.email": "Your email address", "widget.form.message": "Type your question here", @@ -89,5 +89,6 @@ "widget.action.restartChat": "Start a new chat", "widget.action.continueChat": "Continue current chat", "widget.error.botError": "Sorry, the bot stopped working due to technical issues", - "widget.error.technicalProblems": "Sorry, there were technical problems" + "widget.error.technicalProblems": "Sorry, there were technical problems", + "widget.error.empty": "Empty message" } diff --git a/src/translations/et/common.json b/src/translations/et/common.json index 6ea53423..4fa431ea 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -77,7 +77,7 @@ "widget.action.download-chat": "Lae vestlus alla", "widget.action.forward-chat": "Edasta vestlus e-posti", "widget.action.forward-email": "Teie e-posti aadress", - "widget.action.select": "Valige üks järgmistest valikutest", + "widget.action.select": "Valige üks järgmistest valikutest:", "widget.form.phone": "Teie telefon", "widget.form.email": "Teie e-posti aadress", "widget.form.message": "Täpsustage siin oma küsimust", @@ -89,5 +89,6 @@ "widget.action.restartChat": "Alustage uut vestlust", "widget.action.continueChat": "Jätka praegust vestlust", "widget.error.botError": "Vabandame, vestlus katkes tehniliste probleemide tõttu.", - "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid" + "widget.error.technicalProblems": "Vabandame, tekkisid tehnilised probleemid", + "widget.error.empty": "Tühi sõnum" } diff --git a/src/utils/chat-utils.ts b/src/utils/chat-utils.ts index 30af3cd5..387b546d 100644 --- a/src/utils/chat-utils.ts +++ b/src/utils/chat-utils.ts @@ -6,7 +6,7 @@ export const parseButtons = (message: Message): MessageButton[] => { try { if(!message?.buttons || message.buttons === '') return []; - return JSON.parse(decodeURIComponent(message.buttons)) as MessageButton[]; + return JSON.parse(message.buttons) as MessageButton[]; } catch(e) { console.error(e); return []; @@ -17,7 +17,7 @@ export const parseOptions = (message: Message): string[] => { try { if(!message?.options || message.options === '') return []; - return JSON.parse(decodeURIComponent(message.options)) as string[]; + return JSON.parse(message.options) as string[]; } catch(e) { console.error(e); return []; From 7117b6584f1a6738160238affa73b01d2b0650a8 Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Wed, 23 Oct 2024 14:06:19 +0300 Subject: [PATCH 30/95] Added New Changes to working times (#322) --- .../chat-feedback/chat-feedback.tsx | 14 +- .../confirmation-modal-nps.tsx | 2 +- .../unavailable-end-user-contacts.tsx | 128 ++++++++---------- src/constants.ts | 1 + src/slices/chat-slice.ts | 11 +- src/test-initial-states.ts | 1 + src/utils/state-management-utils.ts | 1 + 7 files changed, 76 insertions(+), 82 deletions(-) diff --git a/src/components/chat-feedback/chat-feedback.tsx b/src/components/chat-feedback/chat-feedback.tsx index 102f4eff..1de21b06 100644 --- a/src/components/chat-feedback/chat-feedback.tsx +++ b/src/components/chat-feedback/chat-feedback.tsx @@ -3,11 +3,7 @@ import styled from "styled-components"; import { useTranslation } from "react-i18next"; import StyledButton from "../styled-components/styled-button"; import { useAppDispatch } from "../../store"; -import { - downloadChat, - sendChatNpmRating, - setFeedbackRatingGiven, -} from "../../slices/chat-slice"; +import { downloadChat, sendChatNpmRating, setFeedbackRatingGiven } from "../../slices/chat-slice"; import { StyledButtonType } from "../../constants"; import useChatSelector from "../../hooks/use-chat-selector"; import { Download, DownloadElement } from "../../hooks/use-download-file"; @@ -26,7 +22,7 @@ const ChatFeedback = (): JSX.Element => { dispatch(sendChatNpmRating({ NpmRating: parseInt(feedbackRating ?? "1", 10) })); dispatch(setFeedbackRatingGiven(true)); }; - + const handleDownload = async () => { const response = await dispatch(downloadChat(false)); if (response.meta.requestStatus === "rejected") { @@ -43,10 +39,8 @@ const ChatFeedback = (): JSX.Element => { organization: window._env_.ORGANIZATION_NAME, })}

- {feedback.showFeedbackWarning && ( -

{t("feedback.warningText")}

- )} -
+ {feedback.showFeedbackWarning &&

{t("feedback.warningText")}

} +
{Array.from(Array(11).keys()).map((val: number) => ( { organization: window._env_.ORGANIZATION_NAME, })}

-
+
{Array.from(Array(11).keys()).map((val: number) => ( { - const { endUserContacts, chatId, contactMsgId, contactContentMessage } = - useChatSelector(); + const { endUserContacts, chatId, contactMsgId, contactContentMessage, askForContactsIfNoCsa } = useChatSelector(); const dispatch = useAppDispatch(); const { t } = useTranslation(); const { validateInput, invalidMessage } = useMessageValidator(); @@ -57,30 +53,18 @@ const UnavailableEndUserContacts = (): JSX.Element => { const submitForm = async (e: React.MouseEvent) => { e.preventDefault(); if (validateInput(endUserContacts)) { - await WidgetService.sendContactInfo( - chatId ?? "", - endUserContacts.mailAddress, - endUserContacts.phoneNr - ).catch(console.error); - - const newMsg = getContactFormFulfilledNewMessage( - endUserContacts, - chatId, - contactMsgId, - t + await WidgetService.sendContactInfo(chatId ?? "", endUserContacts.mailAddress, endUserContacts.phoneNr).catch( + console.error ); + + const newMsg = getContactFormFulfilledNewMessage(endUserContacts, chatId, contactMsgId, t); dispatch(sendMessageWithNewEvent(newMsg)); dispatch(setShowUnavailableContactForm(false)); newMsg.content = ""; dispatch(sendMessagePreview(newMsg)); if (endUserContacts.comment) { - const commentMsg = getContactCommentNewMessage( - endUserContacts.comment, - chatId, - contactMsgId, - t - ); + const commentMsg = getContactCommentNewMessage(endUserContacts.comment, chatId, contactMsgId, t); dispatch(sendNewSilentMessage(commentMsg)); } @@ -99,56 +83,60 @@ const UnavailableEndUserContacts = (): JSX.Element => {

{contactContentMessage}

- {invalidMessage && ( -

{invalidMessage}

- )} -
-
-
{t("widget.contacts.contact.mail.label")}
- dispatch(setEmailAdress(e.target.value))} - /> + {invalidMessage &&

{invalidMessage}

} + {askForContactsIfNoCsa && ( +
+
+
{t("widget.contacts.contact.mail.label")}
+ dispatch(setEmailAdress(e.target.value))} + /> +
+
+
{t("widget.contacts.contact.phone.label")}
+ dispatch(setPhoneNumber(e.target.value))} + /> +
+
+
{t("widget.contacts.contact.comment.label")}
+ dispatch(setContactFormComment(e.target.value))} + /> +
-
-
{t("widget.contacts.contact.phone.label")}
- dispatch(setPhoneNumber(e.target.value))} - /> + )} + {askForContactsIfNoCsa && ( +
+ + {t("widget.contacts.contact.skip.label")} + + + {t("widget.contacts.contact.submit.label")} +
-
-
{t("widget.contacts.contact.comment.label")}
- - dispatch(setContactFormComment(e.target.value)) - } - /> + )} + {!askForContactsIfNoCsa && ( +
+ + {t("header.button.close.label")} +
-
-
- - {t("widget.contacts.contact.skip.label")} - - - {t("widget.contacts.contact.submit.label")} - -
+ )}
diff --git a/src/constants.ts b/src/constants.ts index eb427c02..41b7f6f6 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -74,6 +74,7 @@ export enum CHAT_EVENTS { MESSAGE_READ = 'message-read', UNAVAILABLE_ORGANIZATION = 'unavailable_organization', UNAVAILABLE_CSAS = 'unavailable_csas', + UNAVAILABLE_CSAS_ASK_CONTACTS = 'unavailable_csas_ask_contacts', UNAVAILABLE_HOLIDAY = 'unavailable_holiday' } diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index 00631b0a..86815d7c 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -58,6 +58,7 @@ export interface ChatState { loading: boolean; showContactForm: boolean; showUnavailableContactForm: boolean; + askForContactsIfNoCsa: boolean; contactMsgId: string; contactContentMessage: string; isChatRedirected: boolean; @@ -114,6 +115,7 @@ const initialState: ChatState = { errorMessage: "", showContactForm: false, showUnavailableContactForm: false, + askForContactsIfNoCsa: true, contactContentMessage: "", isChatRedirected: false, estimatedWaiting: initialEstimatedTime, @@ -481,12 +483,19 @@ export const chatSlice = createSlice({ state.contactMsgId = msg.id ?? ""; break; case CHAT_EVENTS.UNAVAILABLE_HOLIDAY: - case CHAT_EVENTS.UNAVAILABLE_CSAS: + case CHAT_EVENTS.UNAVAILABLE_CSAS_ASK_CONTACTS: case CHAT_EVENTS.UNAVAILABLE_ORGANIZATION: + state.askForContactsIfNoCsa = true; state.showUnavailableContactForm = true; state.contactMsgId = msg.id ?? ""; state.contactContentMessage = msg.content ?? ""; break; + case CHAT_EVENTS.UNAVAILABLE_CSAS: + state.askForContactsIfNoCsa = false; + state.showUnavailableContactForm = true; + state.contactMsgId = msg.id ?? ""; + state.contactContentMessage = msg.content ?? ""; + break; case CHAT_EVENTS.ANSWERED: case CHAT_EVENTS.TERMINATED: case TERMINATE_STATUS.ACCEPTED: diff --git a/src/test-initial-states.ts b/src/test-initial-states.ts index 3e24b860..f573c6c9 100644 --- a/src/test-initial-states.ts +++ b/src/test-initial-states.ts @@ -20,6 +20,7 @@ export const initialChatState: ChatState = { isChatOpen: false, showContactForm: false, showUnavailableContactForm: false, + askForContactsIfNoCsa: true, contactContentMessage: "", isChatRedirected: false, messages: [], diff --git a/src/utils/state-management-utils.ts b/src/utils/state-management-utils.ts index 63f96d82..8eb25d42 100644 --- a/src/utils/state-management-utils.ts +++ b/src/utils/state-management-utils.ts @@ -25,6 +25,7 @@ const stateChangingEventMessages = [ CHAT_EVENTS.TERMINATED, CHAT_EVENTS.UNAVAILABLE_ORGANIZATION, CHAT_EVENTS.UNAVAILABLE_CSAS, + CHAT_EVENTS.UNAVAILABLE_CSAS_ASK_CONTACTS, CHAT_EVENTS.UNAVAILABLE_HOLIDAY, TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED, TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION , From 4dbe3dd5ead3fd7b6bb0ff5d71c0cfdf06caa576 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:01:04 +0200 Subject: [PATCH 31/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index adbf39b2..7a0c060c 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=17 +FIX=18 From 0fe21acd377f18d5b600e30218c513fd758e8184 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:53:16 +0200 Subject: [PATCH 32/95] Update ci-build-image.yml --- .github/workflows/ci-build-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-build-image.yml b/.github/workflows/ci-build-image.yml index c7ca2f03..e27094f8 100644 --- a/.github/workflows/ci-build-image.yml +++ b/.github/workflows/ci-build-image.yml @@ -3,7 +3,7 @@ name: Build and publish Chat-Widget on: push: branches: - - v2.0.1 + - v2.0.2 paths: - '.env' From 2835ed6221ddb1b0c23a10a31db360f851f30c93 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:53:25 +0200 Subject: [PATCH 33/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 7a0c060c..95c378f6 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=18 +FIX=18 From 7d01416ab37b9b8c32bd434b4b15cd6cd5e802b1 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Tue, 29 Oct 2024 13:45:36 +0300 Subject: [PATCH 34/95] Added I Frame Support --- README.md | 32 ++++++++++++++++++++++++++++ public/env-config.js | 3 ++- public/iframe-index.html | 34 ++++++++++++++++++++++++++++++ public/widget-index.html | 6 +++--- src/App.tsx | 33 +++++++++-------------------- src/components/chat/chat.tsx | 6 +++++- src/components/profile/profile.tsx | 4 ++++ src/setupTests.ts | 28 ++++++++++++------------ 8 files changed, 105 insertions(+), 41 deletions(-) create mode 100644 public/iframe-index.html diff --git a/README.md b/README.md index 4c623dde..21a1caec 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,38 @@ Snippet can be embedded to any site using the following html: ``` +## Iframe Support + +If you want to use the widget inside an Iframe use the following snippet or reference iframe-index.html + +``` + + + + +``` + ## Configurable variables - `RUUTER_API_URL`: Location of newer back end for fetching data diff --git a/public/env-config.js b/public/env-config.js index 8896cb82..4dddacf1 100644 --- a/public/env-config.js +++ b/public/env-config.js @@ -11,5 +11,6 @@ window._env_ = { END: 24, DAYS: [1, 2, 3, 4, 5, 6, 7], }, - ENABLE_HIDDEN_FEATURES: 'TRUE', + ENABLE_HIDDEN_FEATURES: "TRUE", + IFRAME_TARGET_OIRGIN: "*", }; diff --git a/public/iframe-index.html b/public/iframe-index.html new file mode 100644 index 00000000..aa1c1e52 --- /dev/null +++ b/public/iframe-index.html @@ -0,0 +1,34 @@ + + + + + + + Bürokratt + + + + + + diff --git a/public/widget-index.html b/public/widget-index.html index 8a349397..2cc6def6 100644 --- a/public/widget-index.html +++ b/public/widget-index.html @@ -4,15 +4,15 @@ - Byrokratt + Bürokratt
diff --git a/src/App.tsx b/src/App.tsx index 52317136..cdb899cc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -50,6 +50,7 @@ declare global { DAYS: number[]; }; ENABLE_HIDDEN_FEATURES: string; + IFRAME_TARGET_OIRGIN: string; }; } } @@ -61,31 +62,20 @@ const App: FC = () => { const [displayWidget, setDisplayWidget] = useState( !!getFromLocalStorage(SESSION_STORAGE_CHAT_ID_KEY) || isOfficeHours() ); - const [onlineCheckInterval, setOnlineCheckInterval] = useState( - ONLINE_CHECK_INTERVAL - ); + const [onlineCheckInterval, setOnlineCheckInterval] = useState(ONLINE_CHECK_INTERVAL); const { burokrattOnlineStatus } = useAppSelector((state) => state.widget); const { chatStatus } = useAppSelector((state) => state.chat); useLayoutEffect(() => { - if (burokrattOnlineStatus === null) - dispatch(burokrattOnlineStatusRequest()); - else if (burokrattOnlineStatus === false) - setOnlineCheckInterval(ONLINE_CHECK_INTERVAL); - else if (chatStatus === CHAT_STATUS.OPEN) - setOnlineCheckInterval(ONLINE_CHECK_INTERVAL_ACTIVE_CHAT); + if (burokrattOnlineStatus === null) dispatch(burokrattOnlineStatusRequest()); + else if (burokrattOnlineStatus === false) setOnlineCheckInterval(ONLINE_CHECK_INTERVAL); + else if (chatStatus === CHAT_STATUS.OPEN) setOnlineCheckInterval(ONLINE_CHECK_INTERVAL_ACTIVE_CHAT); }, [chatStatus, burokrattOnlineStatus]); - useInterval( - () => dispatch(burokrattOnlineStatusRequest()), - onlineCheckInterval - ); + useInterval(() => dispatch(burokrattOnlineStatusRequest()), onlineCheckInterval); useInterval( - () => - setDisplayWidget( - !!getFromLocalStorage(SESSION_STORAGE_CHAT_ID_KEY) || isOfficeHours() - ), + () => setDisplayWidget(!!getFromLocalStorage(SESSION_STORAGE_CHAT_ID_KEY) || isOfficeHours()), OFFICE_HOURS_INTERVAL_TIMEOUT ); @@ -99,7 +89,7 @@ const App: FC = () => { useEffect(() => { const storageHandler = () => { const storedData = getFromLocalStorage(SESSION_STORAGE_CHAT_ID_KEY); - const previousChatId = getFromLocalStorage('previousChatId'); + const previousChatId = getFromLocalStorage("previousChatId"); if (storedData === null && previousChatId === null) { setChatId(""); dispatch(setChatId("")); @@ -142,9 +132,7 @@ const App: FC = () => { }, [messages]); useEffect(() => { - const sessionStorageChatId = getFromLocalStorage( - SESSION_STORAGE_CHAT_ID_KEY - ); + const sessionStorageChatId = getFromLocalStorage(SESSION_STORAGE_CHAT_ID_KEY); if (sessionStorageChatId) { dispatch(setChatId(sessionStorageChatId)); dispatch(setIsChatOpen(true)); @@ -163,8 +151,7 @@ const App: FC = () => { useNameAndTitleVisibility(); if (burokrattOnlineStatus !== true) return <>; - if (displayWidget && widgetConfig.isLoaded) - return isChatOpen ? : ; + if (displayWidget && widgetConfig.isLoaded) return isChatOpen ? : ; return <>; }; diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 80960a46..2841a59c 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -42,7 +42,7 @@ import UnavailableEndUserContacts from "../unavailable-end-user-contacts/unavail import useReloadChatEndEffect from "../../hooks/use-reload-chat-end-effect"; import useWindowDimensions from "../../hooks/useWindowDimensions"; import ResponseErrorNotification from "../response-error-notification/response-error-notification"; -import useTabActive from '../../hooks/useTabActive'; +import useTabActive from "../../hooks/useTabActive"; import { use } from "i18next"; const RESIZABLE_HANDLES = { @@ -93,6 +93,10 @@ const Chat = (): JSX.Element => { } }, [dispatch, feedback.isFeedbackConfirmationShown, feedback.isFeedbackMessageGiven, feedback.isFeedbackRatingGiven]); + useEffect(() => { + window.parent.postMessage({ isOpened: true }, window._env_.IFRAME_TARGET_OIRGIN); + }, []); + useEffect(() => { if ( !chatId && diff --git a/src/components/profile/profile.tsx b/src/components/profile/profile.tsx index 2299d241..e104926f 100644 --- a/src/components/profile/profile.tsx +++ b/src/components/profile/profile.tsx @@ -33,6 +33,10 @@ export const Profile = (): JSX.Element => { setTimeout(() => setDelayFinished(true), widgetConfig.bubbleMessageSeconds * 1000); }, []); + useEffect(() => { + window.parent.postMessage({ isOpened: false }, window._env_.IFRAME_TARGET_OIRGIN); + }, []); + useReloadChatEndEffect(); const getActiveProfileClass = () => { diff --git a/src/setupTests.ts b/src/setupTests.ts index a687743b..da8a1ab6 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -1,31 +1,33 @@ -import '@testing-library/jest-dom'; -import { Settings } from 'luxon'; -import { server } from './mocks/server'; -import './i18n'; +import "@testing-library/jest-dom"; +import { Settings } from "luxon"; +import { server } from "./mocks/server"; +import "./i18n"; const { getComputedStyle } = window; window.getComputedStyle = (elt) => getComputedStyle(elt); window._env_ = { - RUUTER_API_URL: 'http://localhost:8086', - NOTIFICATION_NODE_URL: 'http://localhost:4040', - ENVIRONMENT: 'development', - TIM_AUTHENTICATION_URL: 'http://localhost:8085/oauth2/authorization/tara?callback_url=http://localhost:3000/auth/callback', - TERMS_AND_CONDITIONS_LINK: 'https://www.kratid.ee/kasutustingimused', - ORGANIZATION_NAME: 'TEST', + RUUTER_API_URL: "http://localhost:8086", + NOTIFICATION_NODE_URL: "http://localhost:4040", + ENVIRONMENT: "development", + TIM_AUTHENTICATION_URL: + "http://localhost:8085/oauth2/authorization/tara?callback_url=http://localhost:3000/auth/callback", + TERMS_AND_CONDITIONS_LINK: "https://www.kratid.ee/kasutustingimused", + ORGANIZATION_NAME: "TEST", OFFICE_HOURS: { ENABLED: false, - TIMEZONE: 'Europe/Tallinn', + TIMEZONE: "Europe/Tallinn", BEGIN: 9, END: 17, DAYS: [1, 2, 3, 4, 5], }, - ENABLE_HIDDEN_FEATURES: 'TRUE', + ENABLE_HIDDEN_FEATURES: "TRUE", + IFRAME_TARGET_OIRGIN: "*", }; beforeAll(() => { Settings.now = () => new Date(2021, 12, 18, 12).valueOf(); - jest.mock('react-i18next', () => ({ + jest.mock("react-i18next", () => ({ useTranslation: () => ({ t: jest.fn(), }), From acd3fac18fdfc1e434e5e4ee767841974137c2f8 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:26:24 +0300 Subject: [PATCH 35/95] Moved Iframe post Logic to app.tsx --- src/App.tsx | 4 ++++ src/components/chat/chat.tsx | 4 ---- src/components/profile/profile.tsx | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index cdb899cc..54c6475a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -79,6 +79,10 @@ const App: FC = () => { OFFICE_HOURS_INTERVAL_TIMEOUT ); + useEffect(() => { + window.parent.postMessage({ isOpened: isChatOpen }, window._env_.IFRAME_TARGET_OIRGIN); + }, [isChatOpen]); + useGetWidgetConfig(); useGetEmergencyNotice(); useAuthentication(); diff --git a/src/components/chat/chat.tsx b/src/components/chat/chat.tsx index 2841a59c..30ec4843 100644 --- a/src/components/chat/chat.tsx +++ b/src/components/chat/chat.tsx @@ -93,10 +93,6 @@ const Chat = (): JSX.Element => { } }, [dispatch, feedback.isFeedbackConfirmationShown, feedback.isFeedbackMessageGiven, feedback.isFeedbackRatingGiven]); - useEffect(() => { - window.parent.postMessage({ isOpened: true }, window._env_.IFRAME_TARGET_OIRGIN); - }, []); - useEffect(() => { if ( !chatId && diff --git a/src/components/profile/profile.tsx b/src/components/profile/profile.tsx index e104926f..2299d241 100644 --- a/src/components/profile/profile.tsx +++ b/src/components/profile/profile.tsx @@ -33,10 +33,6 @@ export const Profile = (): JSX.Element => { setTimeout(() => setDelayFinished(true), widgetConfig.bubbleMessageSeconds * 1000); }, []); - useEffect(() => { - window.parent.postMessage({ isOpened: false }, window._env_.IFRAME_TARGET_OIRGIN); - }, []); - useReloadChatEndEffect(); const getActiveProfileClass = () => { From fe8e14507b1b4409327dabeada63d958118e2497 Mon Sep 17 00:00:00 2001 From: Igor Krupenja Date: Wed, 30 Oct 2024 11:57:13 +0200 Subject: [PATCH 36/95] Fix emergency notice position (#326) * Add message to top * Clean up --- src/hooks/use-get-emergency-notice.ts | 9 +- src/slices/chat-slice.ts | 349 +++++++++++++++++--------- 2 files changed, 230 insertions(+), 128 deletions(-) diff --git a/src/hooks/use-get-emergency-notice.ts b/src/hooks/use-get-emergency-notice.ts index 2673f488..ca5f35e8 100644 --- a/src/hooks/use-get-emergency-notice.ts +++ b/src/hooks/use-get-emergency-notice.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; import { CHAT_EVENTS } from "../constants"; -import { getEmergencyNotice, addMessage } from "../slices/chat-slice"; +import { getEmergencyNotice, addMessageToTop } from "../slices/chat-slice"; import { useAppDispatch } from "../store"; import useChatSelector from "./use-chat-selector"; @@ -16,9 +16,12 @@ const useGetEmergencyNotice = (): void => { if (!messages.map((m) => m.event).includes(CHAT_EVENTS.EMERGENCY_NOTICE)) { if (!emergencyNotice.isVisible) return; - if (new Date(emergencyNotice.start) <= new Date() && new Date(emergencyNotice.end) >= new Date()) { + if ( + new Date(emergencyNotice.start) <= new Date() && + new Date(emergencyNotice.end) >= new Date() + ) { dispatch( - addMessage({ + addMessageToTop({ chatId, event: CHAT_EVENTS.EMERGENCY_NOTICE, content: emergencyNotice.text, diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index 86815d7c..f3894857 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -18,10 +18,19 @@ import { findMatchingMessageFromMessageList, getInitialChatDimensions, } from "../utils/state-management-utils"; -import { getFromLocalStorage, setToLocalStorage } from "../utils/local-storage-utils"; +import { + getFromLocalStorage, + setToLocalStorage, +} from "../utils/local-storage-utils"; import getHolidays from "../utils/holidays"; -import { filterDuplicatMessages, getChatModeBasedOnLastMessage } from "../utils/chat-utils"; -import { isChatAboutToBeTerminated, wasPageReloaded } from "../utils/browser-utils"; +import { + filterDuplicatMessages, + getChatModeBasedOnLastMessage, +} from "../utils/chat-utils"; +import { + isChatAboutToBeTerminated, + wasPageReloaded, +} from "../utils/browser-utils"; export interface EstimatedWaiting { positionInUnassignedChats: string; @@ -163,45 +172,60 @@ const initialState: ChatState = { responseErrorMessage: "", }; -export const initChat = createAsyncThunk("chat/init", async (message: Message) => { - const { holidays, holidayNames } = getHolidays(); - return ChatService.init( - message, - { - endUserUrl: window.location.href.toString(), - endUserOs: navigator.userAgent.toString(), - }, - holidays, - holidayNames - ); -}); +export const initChat = createAsyncThunk( + "chat/init", + async (message: Message) => { + const { holidays, holidayNames } = getHolidays(); + return ChatService.init( + message, + { + endUserUrl: window.location.href.toString(), + endUserOs: navigator.userAgent.toString(), + }, + holidays, + holidayNames + ); + } +); -export const getChat = createAsyncThunk("chat/getChat", async (_args, thunkApi) => { - const { - chat: { chatId }, - } = thunkApi.getState() as { chat: ChatState }; - if (chatId) return ChatService.getChatById(); - return null; -}); +export const getChat = createAsyncThunk( + "chat/getChat", + async (_args, thunkApi) => { + const { + chat: { chatId }, + } = thunkApi.getState() as { chat: ChatState }; + if (chatId) return ChatService.getChatById(); + return null; + } +); -export const getChatMessages = createAsyncThunk("chat/getChatMessages", async (args, thunkApi) => { - const { - chat: { chatId }, - } = thunkApi.getState() as { chat: ChatState }; - return chatId ? ChatService.getMessages() : null; -}); +export const getChatMessages = createAsyncThunk( + "chat/getChatMessages", + async (args, thunkApi) => { + const { + chat: { chatId }, + } = thunkApi.getState() as { chat: ChatState }; + return chatId ? ChatService.getMessages() : null; + } +); -export const getNewMessages = createAsyncThunk("chat/getNewMessages", async (args: { timeRangeBegin: string }, _) => { - return ChatService.getNewMessages(args.timeRangeBegin); -}); +export const getNewMessages = createAsyncThunk( + "chat/getNewMessages", + async (args: { timeRangeBegin: string }, _) => { + return ChatService.getNewMessages(args.timeRangeBegin); + } +); -export const sendChatNpmRating = createAsyncThunk("chat/sendChatNpmRating", (args: { NpmRating: number }, thunkApi) => { - const { - chat: { chatId }, - } = (thunkApi.getState() as { chat: ChatState }) || ""; - if (chatId === null) return; - ChatService.sendNpmRating({ chatId, npmRating: args.NpmRating }); -}); +export const sendChatNpmRating = createAsyncThunk( + "chat/sendChatNpmRating", + (args: { NpmRating: number }, thunkApi) => { + const { + chat: { chatId }, + } = (thunkApi.getState() as { chat: ChatState }) || ""; + if (chatId === null) return; + ChatService.sendNpmRating({ chatId, npmRating: args.NpmRating }); + } +); export const sendFeedbackMessage = createAsyncThunk( "chat/sendFeedbackMessage", @@ -216,13 +240,18 @@ export const sendFeedbackMessage = createAsyncThunk( export const endChat = createAsyncThunk( "chat/endChat", - async (args: { event: CHAT_EVENTS | null; isUpperCase: boolean }, thunkApi) => { + async ( + args: { event: CHAT_EVENTS | null; isUpperCase: boolean }, + thunkApi + ) => { const { chat: { chatStatus, chatId }, } = thunkApi.getState() as { chat: ChatState }; thunkApi.dispatch(resetState()); - const endEvent = args.isUpperCase ? args.event?.toUpperCase() : args.event ?? ""; + const endEvent = args.isUpperCase + ? args.event?.toUpperCase() + : args.event ?? ""; let chatServiceStatus = null; if (endEvent === CHAT_EVENTS.UNAVAILABLE_CONTACT_INFORMATION_FULFILLED) { chatServiceStatus = "IDLE"; @@ -242,18 +271,21 @@ export const endChat = createAsyncThunk( } ); -export const addChatToTerminationQueue = createAsyncThunk("chat/addChatToTerminationQueue", async (args, thunkApi) => { - const { chat } = thunkApi.getState() as { chat: ChatState }; +export const addChatToTerminationQueue = createAsyncThunk( + "chat/addChatToTerminationQueue", + async (args, thunkApi) => { + const { chat } = thunkApi.getState() as { chat: ChatState }; - sessionStorage.setItem("terminationTime", Date.now().toString()); - localStorage.setItem("previousChatId", chat.chatId ?? ""); + sessionStorage.setItem("terminationTime", Date.now().toString()); + localStorage.setItem("previousChatId", chat.chatId ?? ""); - thunkApi.dispatch(resetState()); + thunkApi.dispatch(resetState()); - if (chat.chatId) { - return ChatService.addChatToTerminationQueue(chat.chatId); + if (chat.chatId) { + return ChatService.addChatToTerminationQueue(chat.chatId); + } } -}); +); export const removeChatFromTerminationQueue = createAsyncThunk( "chat/removeChatFromTerminationQueue", @@ -273,93 +305,128 @@ export const removeChatFromTerminationQueue = createAsyncThunk( } ); -export const resetChatState = createAsyncThunk("", async (args: { event: CHAT_EVENTS | null }, thunkApi) => { - const { - chat: { chatStatus, chatId }, - } = thunkApi.getState() as { chat: ChatState }; - thunkApi.dispatch(resetState()); +export const resetChatState = createAsyncThunk( + "", + async (args: { event: CHAT_EVENTS | null }, thunkApi) => { + const { + chat: { chatStatus, chatId }, + } = thunkApi.getState() as { chat: ChatState }; + thunkApi.dispatch(resetState()); - const resetEvent = args.event?.toUpperCase(); - let chatServiceStatus = null; - if (resetEvent === CHAT_EVENTS.UNAVAILABLE_CONTACT_INFORMATION_FULFILLED) { - chatServiceStatus = "IDLE"; - } + const resetEvent = args.event?.toUpperCase(); + let chatServiceStatus = null; + if (resetEvent === CHAT_EVENTS.UNAVAILABLE_CONTACT_INFORMATION_FULFILLED) { + chatServiceStatus = "IDLE"; + } - return chatStatus === CHAT_STATUS.ENDED - ? null - : ChatService.endChat( - { - chatId, - authorTimestamp: new Date().toISOString(), - authorRole: AUTHOR_ROLES.END_USER, - event: resetEvent, - }, - chatServiceStatus ?? "ENDED" - ); -}); + return chatStatus === CHAT_STATUS.ENDED + ? null + : ChatService.endChat( + { + chatId, + authorTimestamp: new Date().toISOString(), + authorRole: AUTHOR_ROLES.END_USER, + event: resetEvent, + }, + chatServiceStatus ?? "ENDED" + ); + } +); -export const sendMessageWithRating = createAsyncThunk("chat/sendMessageWithRating", async (message: Message) => - ChatService.sendMessageWithRating(message) +export const sendMessageWithRating = createAsyncThunk( + "chat/sendMessageWithRating", + async (message: Message) => ChatService.sendMessageWithRating(message) ); -export const sendMessageWithNewEvent = createAsyncThunk("chat/sendMessageWithNewEvent", (message: Message) => - ChatService.sendMessageWithNewEvent(message) +export const sendMessageWithNewEvent = createAsyncThunk( + "chat/sendMessageWithNewEvent", + (message: Message) => ChatService.sendMessageWithNewEvent(message) ); -export const sendUserContacts = createAsyncThunk("chat/sendUserContacts", (args: UserContacts) => { - ChatService.sendUserContacts(args); -}); +export const sendUserContacts = createAsyncThunk( + "chat/sendUserContacts", + (args: UserContacts) => { + ChatService.sendUserContacts(args); + } +); -export const getGreeting = createAsyncThunk("chat/getGreeting", async () => ChatService.getGreeting()); +export const getGreeting = createAsyncThunk("chat/getGreeting", async () => + ChatService.getGreeting() +); -export const getEmergencyNotice = createAsyncThunk("chat/getEmergencyNotice", async () => - ChatService.getEmergencyNotice() +export const getEmergencyNotice = createAsyncThunk( + "chat/getEmergencyNotice", + async () => ChatService.getEmergencyNotice() ); -export const sendNewMessage = createAsyncThunk("chat/sendNewMessage", (message: Message) => { - const { holidays, holidayNames } = getHolidays(); - return ChatService.sendNewMessage(message, holidays, holidayNames); -}); +export const sendNewMessage = createAsyncThunk( + "chat/sendNewMessage", + (message: Message) => { + const { holidays, holidayNames } = getHolidays(); + return ChatService.sendNewMessage(message, holidays, holidayNames); + } +); -export const sendNewSilentMessage = createAsyncThunk("chat/sendNewSilentMessage", (message: Message) => { - const { holidays, holidayNames } = getHolidays(); - return ChatService.sendNewSilentMessage(message, holidays, holidayNames); -}); +export const sendNewSilentMessage = createAsyncThunk( + "chat/sendNewSilentMessage", + (message: Message) => { + const { holidays, holidayNames } = getHolidays(); + return ChatService.sendNewSilentMessage(message, holidays, holidayNames); + } +); -export const sendMessagePreview = createAsyncThunk("chat/postMessagePreview", (message: Message) => - ChatService.sendMessagePreview(message) +export const sendMessagePreview = createAsyncThunk( + "chat/postMessagePreview", + (message: Message) => ChatService.sendMessagePreview(message) ); -export const getEstimatedWaitingTime = createAsyncThunk("chat/getEstimatedWaitingTime", async (_args, thunkApi) => { - const { - chat: { chatId }, - } = (thunkApi.getState() as { chat: ChatState }) || ""; +export const getEstimatedWaitingTime = createAsyncThunk( + "chat/getEstimatedWaitingTime", + async (_args, thunkApi) => { + const { + chat: { chatId }, + } = (thunkApi.getState() as { chat: ChatState }) || ""; - return chatId ? ChatService.getEstimatedWaitingTime(chatId) : initialEstimatedTime; -}); + return chatId + ? ChatService.getEstimatedWaitingTime(chatId) + : initialEstimatedTime; + } +); -export const removeChatForwardingValue = createAsyncThunk("chat/removeChatForwardingValue", async () => - ChatService.removeChatForwardingValue() +export const removeChatForwardingValue = createAsyncThunk( + "chat/removeChatForwardingValue", + async () => ChatService.removeChatForwardingValue() ); -export const generateForwardingRequest = createAsyncThunk("chat/generateForwardingRequest", async () => - ChatService.generateForwardingRequest() +export const generateForwardingRequest = createAsyncThunk( + "chat/generateForwardingRequest", + async () => ChatService.generateForwardingRequest() ); -export const sendAttachment = createAsyncThunk("chat/sendAttachment", async (attachment: Attachment) => - ChatService.sendAttachment(attachment) +export const sendAttachment = createAsyncThunk( + "chat/sendAttachment", + async (attachment: Attachment) => ChatService.sendAttachment(attachment) ); -export const downloadChat = createAsyncThunk("chat/downloadChat", async (isForwardToEmail: boolean, thunkApi) => { - const { - chat: { chatId, endUserContacts }, - } = thunkApi.getState() as { chat: ChatState }; - const isForwardToEmailAddress = isForwardToEmail ? endUserContacts.mailAddress : null; - return chatId ? ChatService.generateDownloadChatRequest(chatId, isForwardToEmailAddress) : null; -}); -export const getNameVisibility = createAsyncThunk("chat/getNameVisibility", async () => - ChatService.getNameVisibility() +export const downloadChat = createAsyncThunk( + "chat/downloadChat", + async (isForwardToEmail: boolean, thunkApi) => { + const { + chat: { chatId, endUserContacts }, + } = thunkApi.getState() as { chat: ChatState }; + const isForwardToEmailAddress = isForwardToEmail + ? endUserContacts.mailAddress + : null; + return chatId + ? ChatService.generateDownloadChatRequest(chatId, isForwardToEmailAddress) + : null; + } +); +export const getNameVisibility = createAsyncThunk( + "chat/getNameVisibility", + async () => ChatService.getNameVisibility() ); -export const getTitleVisibility = createAsyncThunk("chat/getTitleVisibility", async () => - ChatService.getTitleVisibility() +export const getTitleVisibility = createAsyncThunk( + "chat/getTitleVisibility", + async () => ChatService.getTitleVisibility() ); export const chatSlice = createSlice({ @@ -374,14 +441,23 @@ export const chatSlice = createSlice({ state.chatId = action.payload; }, addMessage: (state, action: PayloadAction) => { - state.messages = filterDuplicatMessages([...state.messages, action.payload]); + state.messages = filterDuplicatMessages([ + ...state.messages, + action.payload, + ]); + }, + addMessageToTop: (state, action: PayloadAction) => { + state.messages = [action.payload, ...state.messages]; }, setIsChatOpen: (state, action: PayloadAction) => { state.chatId = getFromLocalStorage(SESSION_STORAGE_CHAT_ID_KEY); state.isChatOpen = action.payload; state.newMessagesAmount = 0; }, - setChatDimensions: (state, action: PayloadAction<{ width: number; height: number }>) => { + setChatDimensions: ( + state, + action: PayloadAction<{ width: number; height: number }> + ) => { state.chatDimensions = action.payload; setToLocalStorage(LOCAL_STORAGE_CHAT_DIMENSIONS_KEY, action.payload); }, @@ -420,7 +496,9 @@ export const chatSlice = createSlice({ } }, updateMessage: (state, action: PayloadAction) => { - state.messages = state.messages.map((message) => (message.id === action.payload.id ? action.payload : message)); + state.messages = state.messages.map((message) => + message.id === action.payload.id ? action.payload : message + ); }, setIsFeedbackConfirmationShown: (state, action: PayloadAction) => { state.feedback.isFeedbackConfirmationShown = action.payload; @@ -455,28 +533,44 @@ export const chatSlice = createSlice({ if (!receivedMessages.length) return; const newMessagesList = state.messages.map((existingMessage) => { - const matchingMessage = findMatchingMessageFromMessageList(existingMessage, receivedMessages); + const matchingMessage = findMatchingMessageFromMessageList( + existingMessage, + receivedMessages + ); if (!matchingMessage) return existingMessage; - receivedMessages = receivedMessages.filter((rMsg) => rMsg.id !== matchingMessage.id); + receivedMessages = receivedMessages.filter( + (rMsg) => rMsg.id !== matchingMessage.id + ); return { ...existingMessage, ...matchingMessage }; }); - if (newMessagesList.length + receivedMessages.length === state.messages.length) { + if ( + newMessagesList.length + receivedMessages.length === + state.messages.length + ) { return; } state.lastReadMessageTimestamp = new Date().toISOString(); state.newMessagesAmount += receivedMessages.length; - state.messages = filterDuplicatMessages([...newMessagesList, ...receivedMessages]); + state.messages = filterDuplicatMessages([ + ...newMessagesList, + ...receivedMessages, + ]); setToLocalStorage("newMessagesAmount", state.newMessagesAmount); state.chatMode = getChatModeBasedOnLastMessage(state.messages); }, - handleStateChangingEventMessages: (state, action: PayloadAction) => { + handleStateChangingEventMessages: ( + state, + action: PayloadAction + ) => { action.payload.forEach((msg) => { switch (msg.event) { case CHAT_EVENTS.ASK_PERMISSION_IGNORED: - state.messages = state.messages.map((message) => (message.id === msg.id ? msg : message)); + state.messages = state.messages.map((message) => + message.id === msg.id ? msg : message + ); break; case CHAT_EVENTS.CONTACT_INFORMATION: state.showContactForm = true; @@ -494,7 +588,7 @@ export const chatSlice = createSlice({ state.askForContactsIfNoCsa = false; state.showUnavailableContactForm = true; state.contactMsgId = msg.id ?? ""; - state.contactContentMessage = msg.content ?? ""; + state.contactContentMessage = msg.content ?? ""; break; case CHAT_EVENTS.ANSWERED: case CHAT_EVENTS.TERMINATED: @@ -513,7 +607,9 @@ export const chatSlice = createSlice({ }); }, removeEstimatedWaitingMessage: (state) => { - const estimatedMsgIndex = state.messages.findIndex((msg) => msg.id === "estimatedWaiting"); + const estimatedMsgIndex = state.messages.findIndex( + (msg) => msg.id === "estimatedWaiting" + ); if (estimatedMsgIndex === -1) return; state.messages[estimatedMsgIndex].content = "hidden"; }, @@ -613,7 +709,9 @@ export const chatSlice = createSlice({ builder.addCase(getEstimatedWaitingTime.fulfilled, (state, action) => { state.estimatedWaiting = action.payload; - const estimatedMsg = state.messages.find((msg) => msg.id === "estimatedWaiting"); + const estimatedMsg = state.messages.find( + (msg) => msg.id === "estimatedWaiting" + ); if (estimatedMsg) return; state.messages.push({ @@ -658,6 +756,7 @@ export const chatSlice = createSlice({ export const { addMessage, + addMessageToTop, setChatId, setIsChatOpen, setChatDimensions, From ad2fbda2f18295d1e8bb0b61d82f8b04f3ba8420 Mon Sep 17 00:00:00 2001 From: danyil Date: Wed, 30 Oct 2024 14:52:19 +0200 Subject: [PATCH 37/95] Chat widget css update to set the height of window dynamically. --- src/components/chat/chat.module.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/chat/chat.module.scss b/src/components/chat/chat.module.scss index 051ebdcb..0ba148db 100644 --- a/src/components/chat/chat.module.scss +++ b/src/components/chat/chat.module.scss @@ -27,6 +27,10 @@ border-radius: 8px; font-size: 14px; line-height: 1.5; + + b, strong { + font-family: $font-chat-bold; + } } .authenticated { @@ -38,7 +42,7 @@ position: fixed; top: 0; right: 0; - height: 100%; + height: 100dvh; width: 100%; } } From 720ff0aa1c35c322e1e67c019fcf4fced9b39f09 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Wed, 30 Oct 2024 14:58:45 +0200 Subject: [PATCH 38/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 95c378f6..2da2a6de 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=18 +FIX=19 From a1661f2c15559c7491caf1cfddf15be2992dd632 Mon Sep 17 00:00:00 2001 From: Varmo <101868197+varmoh@users.noreply.github.com> Date: Thu, 7 Nov 2024 12:00:08 +0200 Subject: [PATCH 39/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 2da2a6de..db34a986 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=19 +FIX=20 From b4bf47fb8588215945f3a667e79688d44de50a38 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:41:03 +0200 Subject: [PATCH 40/95] Fixed Loading animation body --- src/components/chat-message/loading-animation.scss | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/chat-message/loading-animation.scss b/src/components/chat-message/loading-animation.scss index 073e464c..c9aad49b 100644 --- a/src/components/chat-message/loading-animation.scss +++ b/src/components/chat-message/loading-animation.scss @@ -1,9 +1,3 @@ -body { - display: flex; - align-items: center; - justify-content: center; -} - .bouncing-loader { display: flex; justify-content: center; From 4d195335540607af2394ec9ca6f7d034581584fb Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:43:50 +0200 Subject: [PATCH 41/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index db34a986..51e374bd 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=20 +FIX=21 From 68daa86fd21d91fc815c155a2613d2b8d04c0606 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:15:25 +0200 Subject: [PATCH 42/95] Fixed Opening Links in new tab --- src/components/chat-message/message-types/Markdownify.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/chat-message/message-types/Markdownify.tsx b/src/components/chat-message/message-types/Markdownify.tsx index f2fb7a3c..69a2ae8a 100644 --- a/src/components/chat-message/message-types/Markdownify.tsx +++ b/src/components/chat-message/message-types/Markdownify.tsx @@ -6,8 +6,8 @@ interface MarkdownifyProps { } const Markdownify: React.FC = ({ message }) => (
- - { message ?? '' } + + {message ?? ""}
); From 487f935c35ae63296995f8a53a5ea9fd5c6b9e56 Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:17:57 +0200 Subject: [PATCH 43/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 51e374bd..5048a11e 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=21 +FIX=22 From 723e23f9b74c83b79e01280947ed410a424d5d72 Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:28:56 +0200 Subject: [PATCH 44/95] Added Outside Working Hours Changes (#328) --- .../unavailable-end-user-contacts.tsx | 8 ++++---- src/constants.ts | 4 +++- src/slices/chat-slice.ts | 14 ++++++++------ src/test-initial-states.ts | 2 +- src/utils/state-management-utils.ts | 2 ++ 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx b/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx index a3e57fa9..cf672f62 100644 --- a/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx +++ b/src/components/unavailable-end-user-contacts/unavailable-end-user-contacts.tsx @@ -23,7 +23,7 @@ import useMessageValidator from "../../hooks/use-message-validator"; import { getContactCommentNewMessage, getContactFormFulfilledNewMessage } from "../../utils/chat-utils"; const UnavailableEndUserContacts = (): JSX.Element => { - const { endUserContacts, chatId, contactMsgId, contactContentMessage, askForContactsIfNoCsa } = useChatSelector(); + const { endUserContacts, chatId, contactMsgId, contactContentMessage, askForContacts } = useChatSelector(); const dispatch = useAppDispatch(); const { t } = useTranslation(); const { validateInput, invalidMessage } = useMessageValidator(); @@ -84,7 +84,7 @@ const UnavailableEndUserContacts = (): JSX.Element => {

{contactContentMessage}

{invalidMessage &&

{invalidMessage}

} - {askForContactsIfNoCsa && ( + {askForContacts && (
{t("widget.contacts.contact.mail.label")}
@@ -120,7 +120,7 @@ const UnavailableEndUserContacts = (): JSX.Element => {
)} - {askForContactsIfNoCsa && ( + {askForContacts && (
{t("widget.contacts.contact.skip.label")} @@ -130,7 +130,7 @@ const UnavailableEndUserContacts = (): JSX.Element => {
)} - {!askForContactsIfNoCsa && ( + {!askForContacts && (
{t("header.button.close.label")} diff --git a/src/constants.ts b/src/constants.ts index 41b7f6f6..88c048f2 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -73,9 +73,11 @@ export enum CHAT_EVENTS { CONTACT_INFORMATION_SKIPPED = 'contact-information-skipped', MESSAGE_READ = 'message-read', UNAVAILABLE_ORGANIZATION = 'unavailable_organization', + UNAVAILABLE_ORGANIZATION_ASK_CONTACTS = "unavailable_organization_ask_contacts", UNAVAILABLE_CSAS = 'unavailable_csas', UNAVAILABLE_CSAS_ASK_CONTACTS = 'unavailable_csas_ask_contacts', - UNAVAILABLE_HOLIDAY = 'unavailable_holiday' + UNAVAILABLE_HOLIDAY = 'unavailable_holiday', + UNAVAILABLE_HOLIDAY_ASK_CONTACTS = "unavailable_holiday_ask_contacts", } export enum TERMINATE_STATUS { diff --git a/src/slices/chat-slice.ts b/src/slices/chat-slice.ts index f3894857..9429d349 100644 --- a/src/slices/chat-slice.ts +++ b/src/slices/chat-slice.ts @@ -67,7 +67,7 @@ export interface ChatState { loading: boolean; showContactForm: boolean; showUnavailableContactForm: boolean; - askForContactsIfNoCsa: boolean; + askForContacts: boolean; contactMsgId: string; contactContentMessage: string; isChatRedirected: boolean; @@ -124,7 +124,7 @@ const initialState: ChatState = { errorMessage: "", showContactForm: false, showUnavailableContactForm: false, - askForContactsIfNoCsa: true, + askForContacts: true, contactContentMessage: "", isChatRedirected: false, estimatedWaiting: initialEstimatedTime, @@ -576,16 +576,18 @@ export const chatSlice = createSlice({ state.showContactForm = true; state.contactMsgId = msg.id ?? ""; break; - case CHAT_EVENTS.UNAVAILABLE_HOLIDAY: + case CHAT_EVENTS.UNAVAILABLE_HOLIDAY_ASK_CONTACTS: case CHAT_EVENTS.UNAVAILABLE_CSAS_ASK_CONTACTS: - case CHAT_EVENTS.UNAVAILABLE_ORGANIZATION: - state.askForContactsIfNoCsa = true; + case CHAT_EVENTS.UNAVAILABLE_ORGANIZATION_ASK_CONTACTS: + state.askForContacts = true; state.showUnavailableContactForm = true; state.contactMsgId = msg.id ?? ""; state.contactContentMessage = msg.content ?? ""; break; + case CHAT_EVENTS.UNAVAILABLE_HOLIDAY: case CHAT_EVENTS.UNAVAILABLE_CSAS: - state.askForContactsIfNoCsa = false; + case CHAT_EVENTS.UNAVAILABLE_ORGANIZATION: + state.askForContacts = false; state.showUnavailableContactForm = true; state.contactMsgId = msg.id ?? ""; state.contactContentMessage = msg.content ?? ""; diff --git a/src/test-initial-states.ts b/src/test-initial-states.ts index f573c6c9..c99c1e2d 100644 --- a/src/test-initial-states.ts +++ b/src/test-initial-states.ts @@ -20,7 +20,7 @@ export const initialChatState: ChatState = { isChatOpen: false, showContactForm: false, showUnavailableContactForm: false, - askForContactsIfNoCsa: true, + askForContacts: true, contactContentMessage: "", isChatRedirected: false, messages: [], diff --git a/src/utils/state-management-utils.ts b/src/utils/state-management-utils.ts index 8eb25d42..90c46efa 100644 --- a/src/utils/state-management-utils.ts +++ b/src/utils/state-management-utils.ts @@ -24,9 +24,11 @@ const stateChangingEventMessages = [ CHAT_EVENTS.ANSWERED, CHAT_EVENTS.TERMINATED, CHAT_EVENTS.UNAVAILABLE_ORGANIZATION, + CHAT_EVENTS.UNAVAILABLE_ORGANIZATION_ASK_CONTACTS, CHAT_EVENTS.UNAVAILABLE_CSAS, CHAT_EVENTS.UNAVAILABLE_CSAS_ASK_CONTACTS, CHAT_EVENTS.UNAVAILABLE_HOLIDAY, + CHAT_EVENTS.UNAVAILABLE_HOLIDAY_ASK_CONTACTS, TERMINATE_STATUS.CLIENT_LEFT_WITH_ACCEPTED, TERMINATE_STATUS.CLIENT_LEFT_WITH_NO_RESOLUTION , TERMINATE_STATUS.CLIENT_LEFT_FOR_UNKNOWN_REASONS , From 5f7240b3f162a148dd8f1fd371a602bde2c2e384 Mon Sep 17 00:00:00 2001 From: Vassili Moskaljov <112167412+ExiRain@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:29:36 +0200 Subject: [PATCH 45/95] Removed auth button with related imports. (#331) --- src/components/chat-header/widget-details.tsx | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/components/chat-header/widget-details.tsx b/src/components/chat-header/widget-details.tsx index 30a70715..9f3e2938 100644 --- a/src/components/chat-header/widget-details.tsx +++ b/src/components/chat-header/widget-details.tsx @@ -6,28 +6,14 @@ import StyledButton from '../styled-components/styled-button'; import { StyledButtonType, TERMS_AND_CONDITIONS_LINK } from '../../constants'; import EU_SF_logo_src from '../../static/icons/sf_logo_horizontal.jpg'; import NEXT_GEN_FLAGS from '../../static/icons/NextGen_Rahastanud_EL_NextGeneration.jpg'; -import useChatSelector from '../../hooks/use-chat-selector'; -import useAuthenticationSelector from '../../hooks/use-authentication-selector'; -import { redirectToTim } from '../../utils/auth-utils'; const WidgetDetails = (): JSX.Element => { const { t } = useTranslation(); - const { chatId } = useChatSelector(); - const { isAuthenticated } = useAuthenticationSelector(); return (
- {chatId && !isAuthenticated && ( - - {t('authenticate.with-tara')} - - )} window.open(TERMS_AND_CONDITIONS_LINK, '_blank')} @@ -50,11 +36,6 @@ const WidgetDetails = (): JSX.Element => { ); }; -const AuthenticateWithTaraStyles = styled(StyledButton)` - display: flex; - align-items: center; -`; - const TermsAndConditionsStyles = styled(StyledButton)` display: flex; align-items: center; From d1ed616d88fd3940812205e9bd3e7495bc581197 Mon Sep 17 00:00:00 2001 From: Vassili Moskaljov <112167412+ExiRain@users.noreply.github.com> Date: Mon, 18 Nov 2024 12:30:06 +0200 Subject: [PATCH 46/95] Updated notification text (#335) --- src/translations/et/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/et/common.json b/src/translations/et/common.json index 4fa431ea..6f33100b 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -21,7 +21,7 @@ "notifications.offline": "Teenus pole hetkel saadaval!", "notifications.idle-chat-notification": "Kas soovite vestlust jätkata?", "notifications.ask-contact-information": "Kas soovite jätta enda kontaktandmed, et klienditeenindaja saaks ise ühendust võtta?", - "notifications.human-took-over": "Inimene võttis vestluse üle", + "notifications.human-took-over": "Klienditeenindaja võttis vestluse üle", "keypad.input.placeholder": "Kirjutage oma sõnum...", "keypad.input.feedbackPlaceholder": "Sisestage oma tagasiside...", "keypad.button.label": "Saada", From 1b1f44c460d0178862c61af229ea7da3df906f0f Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:12:13 +0200 Subject: [PATCH 47/95] Fixed Opening links in new tab from within the package --- src/components/chat-message/message-types/Markdownify.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chat-message/message-types/Markdownify.tsx b/src/components/chat-message/message-types/Markdownify.tsx index 69a2ae8a..47770ed8 100644 --- a/src/components/chat-message/message-types/Markdownify.tsx +++ b/src/components/chat-message/message-types/Markdownify.tsx @@ -6,7 +6,7 @@ interface MarkdownifyProps { } const Markdownify: React.FC = ({ message }) => (
- + {message ?? ""}
From 2e278deb856373b4e35407965efbd49d0d1db8ac Mon Sep 17 00:00:00 2001 From: KlviG <78801020+KlviG@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:14:32 +0200 Subject: [PATCH 48/95] Update .env --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 5048a11e..77cfda96 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ RELEASE=PRE-ALPHA-test VERSION=1 BUILD=1 -FIX=22 +FIX=23 From 050d834d6559413eb4ae4409ba92d3783c885b1a Mon Sep 17 00:00:00 2001 From: jaX10bt <132996313+jaX10bt@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:46:58 +0200 Subject: [PATCH 49/95] Update common.json --- src/translations/et/common.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/et/common.json b/src/translations/et/common.json index 6f33100b..984a7502 100644 --- a/src/translations/et/common.json +++ b/src/translations/et/common.json @@ -74,7 +74,7 @@ "widget.action.no-dont-close": "Ei soovi sulgeda", "widget.action.skip": "Jäta vahele", "widget.action.confirm": "Kinnita", - "widget.action.download-chat": "Lae vestlus alla", + "widget.action.download-chat": "Laadi vestlus alla", "widget.action.forward-chat": "Edasta vestlus e-posti", "widget.action.forward-email": "Teie e-posti aadress", "widget.action.select": "Valige üks järgmistest valikutest:", From 3327c6b2ea559e34340721a0eaabbda45635b024 Mon Sep 17 00:00:00 2001 From: Ahmed yasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:57:56 +0200 Subject: [PATCH 50/95] Added Chat end handling on error if the chat is open (#339) --- .../chat-message/message-types/Markdownify.tsx | 2 +- .../response-error-notification.tsx | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/components/chat-message/message-types/Markdownify.tsx b/src/components/chat-message/message-types/Markdownify.tsx index 47770ed8..812822a2 100644 --- a/src/components/chat-message/message-types/Markdownify.tsx +++ b/src/components/chat-message/message-types/Markdownify.tsx @@ -6,7 +6,7 @@ interface MarkdownifyProps { } const Markdownify: React.FC = ({ message }) => (
- + {message ?? ""}
diff --git a/src/components/response-error-notification/response-error-notification.tsx b/src/components/response-error-notification/response-error-notification.tsx index c9db8170..73da5c45 100644 --- a/src/components/response-error-notification/response-error-notification.tsx +++ b/src/components/response-error-notification/response-error-notification.tsx @@ -2,13 +2,14 @@ import { useTranslation } from "react-i18next"; import Button from "../button/button"; import styles from "./response-error-notification.module.scss"; import { useAppDispatch } from "../../store"; -import { setShowErrorMessage, resetState } from "../../slices/chat-slice"; +import { setShowErrorMessage, resetState, endChat } from "../../slices/chat-slice"; import useChatSelector from "../../hooks/use-chat-selector"; +import { CHAT_EVENTS } from "../../constants"; const ResponseErrorNotification = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); - const { responseErrorMessage } = useChatSelector(); + const { chatId, responseErrorMessage } = useChatSelector(); return (
@@ -20,6 +21,14 @@ const ResponseErrorNotification = () => {