diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index 838d68d1..dd9bad2a 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -109,7 +109,8 @@ "code_is_required": "Language code is required", "text_is_required": "Text is required", "invalid_file_type": "Invalid file type. Please select a file in the supported format.", - "select_category": "Select a flow" + "select_category": "Select a flow", + "logout_failed": "Something went wrong during logout" }, "menu": { "terms": "Terms of Use", diff --git a/frontend/public/locales/fr/translation.json b/frontend/public/locales/fr/translation.json index 521b911a..7ce2c0dd 100644 --- a/frontend/public/locales/fr/translation.json +++ b/frontend/public/locales/fr/translation.json @@ -109,7 +109,8 @@ "code_is_required": "Le code est requis", "text_is_required": "Texte requis", "invalid_file_type": "Type de fichier invalide. Veuillez choisir un fichier dans un format pris en charge.", - "select_category": "Sélectionner une catégorie" + "select_category": "Sélectionner une catégorie", + "logout_failed": "Une erreur s'est produite lors de la déconnexion" }, "menu": { "terms": "Conditions d'utilisation", diff --git a/frontend/src/app-components/inputs/PasswordInput.tsx b/frontend/src/app-components/inputs/PasswordInput.tsx index f7bc47ec..35206a5e 100644 --- a/frontend/src/app-components/inputs/PasswordInput.tsx +++ b/frontend/src/app-components/inputs/PasswordInput.tsx @@ -1,5 +1,5 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. @@ -9,34 +9,26 @@ import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import { IconButton, InputAdornment, TextFieldProps } from "@mui/material"; -import React, { forwardRef, useState } from "react"; +import { forwardRef, useState } from "react"; import { Input } from "./Input"; export const PasswordInput = forwardRef( - ({ onChange, InputProps, value, ...rest }, ref) => { - const [password, setPassword] = useState(value as string); + ({ onChange, InputProps, ...rest }, ref) => { const [showPassword, setShowPassword] = useState(false); const handleTogglePasswordVisibility = () => { setShowPassword(!showPassword); }; - const handleChange = (event: React.ChangeEvent) => { - setPassword(event.target.value); - if (onChange) { - onChange(event); - } - }; return ( {showPassword ? ( diff --git a/frontend/src/contexts/auth.context.tsx b/frontend/src/contexts/auth.context.tsx index dfdc1089..a2faf186 100644 --- a/frontend/src/contexts/auth.context.tsx +++ b/frontend/src/contexts/auth.context.tsx @@ -6,7 +6,6 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ - import getConfig from "next/config"; import { useRouter } from "next/router"; import { createContext, ReactNode, useEffect, useState } from "react"; @@ -22,7 +21,6 @@ import { Progress } from "@/app-components/displays/Progress"; import { useLogout } from "@/hooks/entities/auth-hooks"; import { useApiClient } from "@/hooks/useApiClient"; import { CURRENT_USER_KEY, PUBLIC_PATHS } from "@/hooks/useAuth"; -import { useToast } from "@/hooks/useToast"; import { useTranslate } from "@/hooks/useTranslate"; import { RouterType } from "@/services/types"; import { IUser } from "@/types/user.types"; @@ -54,18 +52,16 @@ export const AuthProvider = ({ children }: AuthProviderProps): JSX.Element => { const router = useRouter(); const [search, setSearch] = useState(""); const hasPublicPath = PUBLIC_PATHS.includes(router.pathname); - const { i18n, t } = useTranslate(); - const { toast } = useToast(); + const { i18n } = useTranslate(); const [isReady, setIsReady] = useState(false); const queryClient = useQueryClient(); const updateLanguage = (lang: string) => { i18n.changeLanguage(lang); }; - const { mutateAsync: logoutSession } = useLogout(); + const { mutate: logoutSession } = useLogout(); const logout = async () => { updateLanguage(publicRuntimeConfig.lang.default); - await logoutSession(); - toast.success(t("message.logout_success")); + logoutSession(); }; const authRedirection = async (isAuthenticated: boolean) => { if (isAuthenticated) { diff --git a/frontend/src/hooks/entities/auth-hooks.ts b/frontend/src/hooks/entities/auth-hooks.ts index c1fa01a1..e3a1b07b 100755 --- a/frontend/src/hooks/entities/auth-hooks.ts +++ b/frontend/src/hooks/entities/auth-hooks.ts @@ -22,6 +22,8 @@ import { useSocket } from "@/websocket/socket-hooks"; import { useFind } from "../crud/useFind"; import { useApiClient } from "../useApiClient"; import { CURRENT_USER_KEY, useAuth, useLogoutRedirection } from "../useAuth"; +import { useToast } from "../useToast"; +import { useTranslate } from "../useTranslate"; export const useLogin = ( options?: Omit< @@ -54,6 +56,8 @@ export const useLogout = ( const { apiClient } = useApiClient(); const { socket } = useSocket(); const { logoutRedirection } = useLogoutRedirection(); + const { toast } = useToast(); + const { t } = useTranslate(); return useMutation({ ...options, @@ -65,6 +69,10 @@ export const useLogout = ( onSuccess: async () => { queryClient.removeQueries([CURRENT_USER_KEY]); await logoutRedirection(); + toast.success(t("message.logout_success")); + }, + onError: () => { + toast.error(t("message.logout_failed")); }, }); }; diff --git a/widget/src/components/UserSubscription.tsx b/widget/src/components/UserSubscription.tsx index 4e78e65e..487be242 100644 --- a/widget/src/components/UserSubscription.tsx +++ b/widget/src/components/UserSubscription.tsx @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ + import React, { SyntheticEvent, useCallback, @@ -75,7 +76,7 @@ const UserSubscription: React.FC = () => { }); setMessages(messages); setParticipants([ - ...participants, + participants[0], { id: profile.foreign_id, foreign_id: profile.foreign_id, diff --git a/widget/src/providers/ChatProvider.tsx b/widget/src/providers/ChatProvider.tsx index 3b903ddd..f24795cd 100644 --- a/widget/src/providers/ChatProvider.tsx +++ b/widget/src/providers/ChatProvider.tsx @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ + import React, { createContext, ReactNode, @@ -268,7 +269,7 @@ const ChatProvider: React.FC<{ content_type: QuickReplyType.text, text: qr.title, payload: qr.payload, - }) as ISuggestion, + } as ISuggestion), ), ); } else { @@ -308,11 +309,17 @@ const ChatProvider: React.FC<{ async (firstName?: string, lastName?: string) => { try { setConnectionState(2); + const queryParams: Record = + firstName && lastName + ? { first_name: firstName, last_name: lastName } + : {}; const { body } = await socketCtx.socket.get<{ messages: TMessage[]; profile: ISubscriber; }>( - `/webhook/${config.channel}/?first_name=${firstName}&last_name=${lastName}`, + `/webhook/${config.channel}/?${new URLSearchParams( + queryParams, + ).toString()}`, ); localStorage.setItem("profile", JSON.stringify(body.profile)); @@ -332,7 +339,7 @@ const ChatProvider: React.FC<{ }), ); setParticipants([ - ...participants, + participants[0], { id: body.profile.foreign_id, foreign_id: body.profile.foreign_id, @@ -382,6 +389,24 @@ const ChatProvider: React.FC<{ if (screen === "chat" && connectionState === ConnectionState.connected) { handleSubscription(); } + + // When user loses internet connection, on reconnect + // we will need to subscribe him again (join the io room) + const reSubscribe = () => { + const item = localStorage.getItem("profile"); + + if (item) { + const profile = JSON.parse(item) as ISubscriber; + + handleSubscription(profile.first_name, profile.last_name); + } + }; + + socketCtx.socket.io.on("reconnect", reSubscribe); + + return () => { + socketCtx.socket.io.off("reconnect", reSubscribe); + }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/widget/src/utils/SocketIoClient.ts b/widget/src/utils/SocketIoClient.ts index 538754b6..9a9f55a0 100644 --- a/widget/src/utils/SocketIoClient.ts +++ b/widget/src/utils/SocketIoClient.ts @@ -1,11 +1,12 @@ /* - * Copyright © 2024 Hexastack. All rights reserved. + * Copyright © 2025 Hexastack. All rights reserved. * * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ + import { io, ManagerOptions, Socket, SocketOptions } from "socket.io-client"; import { Config } from "../types/config.types"; @@ -72,6 +73,10 @@ export class SocketIoClient { this.init(handlers); } + get io() { + return this.socket.io; + } + /** * Initializes the socket client and sets up event handlers. * @param handlers Event handlers for connection, disconnection, and connection errors