From 27b166c11c6b066a940efdbba3733e90554fd651 Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Sun, 1 Oct 2023 10:44:28 +0330 Subject: [PATCH 01/14] CFE-5 initialize socket provider --- example.env.local | 1 + package.json | 1 + src/lib/app/contexts/socket.jsx | 14 ++++++++++++++ src/lib/app/hooks/useSocket.jsx | 10 ++++++++++ src/pages/_app.jsx | 31 +++++++++++++++++-------------- 5 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 src/lib/app/contexts/socket.jsx create mode 100644 src/lib/app/hooks/useSocket.jsx diff --git a/example.env.local b/example.env.local index 0be16f0..59d15b4 100644 --- a/example.env.local +++ b/example.env.local @@ -7,6 +7,7 @@ NEXT_PUBLIC_PRIMARY_MAIN = "#1b4332" NEXT_PUBLIC_SECONDARY_MAIN = "#800e13" NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir" +NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crm.witel.ir" NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir" NODE_ENV = "development" \ No newline at end of file diff --git a/package.json b/package.json index 6e19bf6..5d6417a 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "react-dom": "^18.2.0", "react-toastify": "^9.1.3", "sass": "^1.62.0", + "socket.io-client": "^4.7.2", "stylis": "^4.1.3", "stylis-plugin-rtl": "^2.1.1", "swr": "^2.1.5", diff --git a/src/lib/app/contexts/socket.jsx b/src/lib/app/contexts/socket.jsx new file mode 100644 index 0000000..c21f27e --- /dev/null +++ b/src/lib/app/contexts/socket.jsx @@ -0,0 +1,14 @@ +import {createContext, useMemo} from "react"; +import {io} from "socket.io-client"; + +export const SocketContext = createContext() + +export const SocketProvider = ({children}) => { + const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, { + autoConnect: false, + auth: { + token: "" + } + }), []) + return {children} +} \ No newline at end of file diff --git a/src/lib/app/hooks/useSocket.jsx b/src/lib/app/hooks/useSocket.jsx new file mode 100644 index 0000000..47d3290 --- /dev/null +++ b/src/lib/app/hooks/useSocket.jsx @@ -0,0 +1,10 @@ +import {useContext} from "react"; +import {SocketContext} from "@/lib/app/contexts/socket"; + +const useSocket = () => { + const {socket} = useContext(SocketContext) + + return {socket} +} + +export default useSocket \ No newline at end of file diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index e76fbcb..fe249a6 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -7,24 +7,27 @@ import {UserProvider} from "@/lib/app/contexts/user"; import "moment/locale/fa"; import {NextIntlProvider} from "next-intl"; import TitlePage from "@/core/components/TitlePage"; +import {SocketProvider} from "@/lib/app/contexts/socket"; const App = ({Component, pageProps}) => { return (<> - - - - - {pageProps.messages ? : ''} - - - - - - - - - + + + + + + {pageProps.messages ? : ''} + + + + + + + + + + ) }; From 3992502b94c04b5e57811e9bec4b7ac9ba80981d Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Mon, 2 Oct 2023 10:09:05 +0330 Subject: [PATCH 02/14] CFE-5 config socket provider --- example.env.local | 2 +- src/lib/app/contexts/socket.jsx | 1 + src/lib/app/hooks/useSocket.jsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/example.env.local b/example.env.local index 59d15b4..922eec0 100644 --- a/example.env.local +++ b/example.env.local @@ -7,7 +7,7 @@ NEXT_PUBLIC_PRIMARY_MAIN = "#1b4332" NEXT_PUBLIC_SECONDARY_MAIN = "#800e13" NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir" -NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crm.witel.ir" +NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crmws.witel.ir" NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir" NODE_ENV = "development" \ No newline at end of file diff --git a/src/lib/app/contexts/socket.jsx b/src/lib/app/contexts/socket.jsx index c21f27e..7da4f43 100644 --- a/src/lib/app/contexts/socket.jsx +++ b/src/lib/app/contexts/socket.jsx @@ -10,5 +10,6 @@ export const SocketProvider = ({children}) => { token: "" } }), []) + return {children} } \ No newline at end of file diff --git a/src/lib/app/hooks/useSocket.jsx b/src/lib/app/hooks/useSocket.jsx index 47d3290..3b9855d 100644 --- a/src/lib/app/hooks/useSocket.jsx +++ b/src/lib/app/hooks/useSocket.jsx @@ -4,7 +4,7 @@ import {SocketContext} from "@/lib/app/contexts/socket"; const useSocket = () => { const {socket} = useContext(SocketContext) - return {socket} + return socket } export default useSocket \ No newline at end of file From 27dd632541e4afa7aded5d23d69cd7b98e222b39 Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Mon, 2 Oct 2023 10:09:42 +0330 Subject: [PATCH 03/14] CFE-5 set token socket --- src/lib/app/contexts/user.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/app/contexts/user.jsx b/src/lib/app/contexts/user.jsx index 10611b8..0d7adeb 100644 --- a/src/lib/app/contexts/user.jsx +++ b/src/lib/app/contexts/user.jsx @@ -2,6 +2,7 @@ import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; import axios from "axios"; import {createContext, useCallback, useEffect, useReducer} from "react"; import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler"; +import useSocket from "@/lib/app/hooks/useSocket"; const initialUser = { isAuth: false, @@ -39,6 +40,7 @@ const reducer = (state, action) => { export const UserContext = createContext(); export const UserProvider = ({children}) => { + const socket = useSocket() const [state, dispatch] = useReducer(reducer, initialUser); const clearUser = useCallback(() => { @@ -75,7 +77,7 @@ export const UserProvider = ({children}) => { headers: {authorization: `Bearer ${state.token}`}, }) .then(({data}) => { - if (typeof callback === "function") callback(data); + if (typeof callback === "function") callback(data.data); }) .catch(error => { if (error.response) { @@ -100,12 +102,16 @@ export const UserProvider = ({children}) => { clearUser(); changeAuthState(false); changeLanguageState(false); + + socket.auth.token = "" return; } getUser((data) => { changeUser(data); changeAuthState(true); changeLanguageState(true); + + socket.auth.token = data.telephone_id }); }, [state.token]); From 49f8269c065b4cbd6ae4f7c674b886f606165c45 Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Mon, 2 Oct 2023 10:23:56 +0330 Subject: [PATCH 04/14] CFE-18 fixed any bug in errorHandler and notifications component --- src/core/components/notifications/ErrorNotification.jsx | 2 +- src/core/components/notifications/PendingNotification.jsx | 2 +- src/core/components/notifications/SuccessNotification.jsx | 2 +- src/core/components/notifications/UploadFileNotification.jsx | 2 +- src/core/components/notifications/WarningNotification.jsx | 2 +- src/core/utils/errorHandler.js | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/components/notifications/ErrorNotification.jsx b/src/core/components/notifications/ErrorNotification.jsx index 4ff1ab0..05e2fb9 100644 --- a/src/core/components/notifications/ErrorNotification.jsx +++ b/src/core/components/notifications/ErrorNotification.jsx @@ -36,4 +36,4 @@ const ErrorNotification = (t, status, message) => { ); }; -export default ErrorNotification; +export default ErrorNotification; \ No newline at end of file diff --git a/src/core/components/notifications/PendingNotification.jsx b/src/core/components/notifications/PendingNotification.jsx index 846b847..6183f6b 100644 --- a/src/core/components/notifications/PendingNotification.jsx +++ b/src/core/components/notifications/PendingNotification.jsx @@ -9,4 +9,4 @@ const PendingNotification = (t) => { }); }; -export default PendingNotification; +export default PendingNotification; \ No newline at end of file diff --git a/src/core/components/notifications/SuccessNotification.jsx b/src/core/components/notifications/SuccessNotification.jsx index 998deaa..3507cef 100644 --- a/src/core/components/notifications/SuccessNotification.jsx +++ b/src/core/components/notifications/SuccessNotification.jsx @@ -39,4 +39,4 @@ const SuccessNotification = (t, status) => { ); }; -export default SuccessNotification; +export default SuccessNotification; \ No newline at end of file diff --git a/src/core/components/notifications/UploadFileNotification.jsx b/src/core/components/notifications/UploadFileNotification.jsx index 6556944..89ebbb2 100644 --- a/src/core/components/notifications/UploadFileNotification.jsx +++ b/src/core/components/notifications/UploadFileNotification.jsx @@ -35,4 +35,4 @@ const UploadFileNotification = (t) => { ); }; -export default UploadFileNotification; +export default UploadFileNotification; \ No newline at end of file diff --git a/src/core/components/notifications/WarningNotification.jsx b/src/core/components/notifications/WarningNotification.jsx index 8493180..19d6a2f 100644 --- a/src/core/components/notifications/WarningNotification.jsx +++ b/src/core/components/notifications/WarningNotification.jsx @@ -37,4 +37,4 @@ const WarningNotification = (t, status) => { ); }; -export default WarningNotification; +export default WarningNotification; \ No newline at end of file diff --git a/src/core/utils/errorHandler.js b/src/core/utils/errorHandler.js index 9bd18f1..8dbd364 100644 --- a/src/core/utils/errorHandler.js +++ b/src/core/utils/errorHandler.js @@ -48,7 +48,7 @@ const isServerError = status => status >= 500 && status <= 599; const isClientError = status => status >= 400 && status <= 499; const errorLogic = (response, t, notification) => { - if (notification) ErrorNotification(t, response.status) + if (notification) ErrorNotification(t, response.status, response.data.message) } const errorValidation = (response, t, notification) => { if (notification) { From 7a94678fd46c93c4730f09a4fe5186a558a92f2f Mon Sep 17 00:00:00 2001 From: Yasiu1376 Date: Mon, 2 Oct 2023 15:13:12 +0330 Subject: [PATCH 05/14] CFE-12 implement changepasswordform implement changepasswordform test --- public/locales/fa/app.json | 4 +- .../__test__/index.test.js | 322 ++++++++++++++++++ .../change-password-form/index.js | 137 ++++++++ .../dashboard/change-password/index.jsx | 137 +------- 4 files changed, 465 insertions(+), 135 deletions(-) create mode 100644 src/components/dashboard/change-password/change-password-form/__test__/index.test.js create mode 100644 src/components/dashboard/change-password/change-password-form/index.js diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index dd72e71..885b641 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -79,7 +79,9 @@ "label_current_password": "رمز عبور فعلی", "label_new_password": "رمز عبور جدید", "label_confirm_password": "تکرار رمز عبور جدید", - "error_message_required": "اجباری !", + "error_message_current_password_required": "وارد کردن رمز عبور فعلی الزامیست!", + "error_message_new_password_required": "وارد کردن رمز عبور جدید الزامیست!", + "error_message_confirm_password_required": "وارد کردن تکرار رمز عبور جدید الزامیست!", "error_message_password_length": "رمز عبور باید حداقل 8 کاراکتر باشد.", "error_message_password_match": "پسورد ها یکسان هستند", "error_message_password_not_match": "پسورد و تکرار رمز عبور باید یکسان باشد" diff --git a/src/components/dashboard/change-password/change-password-form/__test__/index.test.js b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js new file mode 100644 index 0000000..4a63240 --- /dev/null +++ b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js @@ -0,0 +1,322 @@ +import { render, screen, waitFor, fireEvent , act } from '@testing-library/react'; +import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; + +describe('Change Password Form Component From Change Password Page', () => { + describe('Rendering', () => { + it('Should see change password heading',() => { + render(); + const changePasswordElement = screen.getByRole('heading', { level: 4 }); + expect(changePasswordElement).toHaveTextContent('تغییر رمز عبور'); + }); + it('Should see the label for current password field', () => { + render(); + const currentPasswordLabel = screen.getByLabelText('رمز عبور فعلی'); + expect(currentPasswordLabel).toBeInTheDocument(); + }); + it('Should see the label for new password field', () => { + render(); + const newPasswordLabel = screen.getByLabelText('رمز عبور جدید'); + expect(newPasswordLabel).toBeInTheDocument(); + }); + it('Should see the label for confirm password field', () => { + render(); + const confirmPasswordLabel = screen.getByLabelText('تکرار رمز عبور جدید'); + expect(confirmPasswordLabel).toBeInTheDocument(); + }); + it('Should see the submit button with the submit label and should be disable', () => { + render(); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + expect(submitButton).toBeInTheDocument(); + expect(submitButton).toBeDisabled(); + }); + it('Should have empty input fields and not see any validation error for current password, new password, and confirm password', () => { + render(); + const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + + expect(currentPasswordInput).toHaveValue(''); + expect(newPasswordInput).toHaveValue(''); + expect(confirmPasswordInput).toHaveValue(''); + + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).not.toBeInTheDocument(); + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + + }); + }); + describe("Behavior", ()=>{ + it('Should fill the current password field', async () => { + render(); + + const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); + + // Simulate typing something in the current password input + fireEvent.change(currentPasswordInput, { target: { value: 'witel@fani0' } }); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).not.toBeInTheDocument(); + expect(currentPasswordInput).toHaveValue('witel@fani0'); + }); + }); + it('Should fill the new password field', async () => { + render(); + + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + + // Simulate typing something in the new password input + fireEvent.change(newPasswordInput, { target: { value: 'witel@fani1' } }); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + expect(newPasswordInput).toHaveValue('witel@fani1'); + }); + }); + it('Should fill the confirm new password field', async () => { + render(); + + const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + + // Simulate typing something in the confirmation new password input + fireEvent.change(confirmPasswordInput, { target: { value: 'witel@fani1' } }); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).not.toBeInTheDocument(); + expect(confirmPasswordInput).toHaveValue('witel@fani1'); + }); + }); + }); + describe("Validation", ()=>{ + it('Should see error while current password is empty on submit click or on blur event and not see any error while its not empty', async () => { + render(); + + // Simulate blur event on the current password input + const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Clear the input field + fireEvent.change(currentPasswordInput, { target: { value: '' } }); + + fireEvent.blur(currentPasswordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument() + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument(); + }); + }); + it('Should see error while new password is empty on submit click or on blur event and not see any error while its not empty', async () => { + render(); + + // Simulate blur event on the new password input + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Clear the input field + fireEvent.change(newPasswordInput, { target: { value: '' } }); + + fireEvent.blur(newPasswordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument() + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument(); + }); + }); + it('Should see error while confirm new password is empty on submit click or on blur event and not see any error while its not empty', async () => { + render(); + + // Simulate blur event on the new password input + const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Clear the input field + fireEvent.change(confirmPasswordInput, { target: { value: '' } }); + + fireEvent.blur(confirmPasswordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument() + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument(); + }); + }); + it('Should see error when new password is less than 8 characters', async () => { + render(); + + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a short new password + fireEvent.change(newPasswordInput, { target: { value: 'test' } }); + fireEvent.blur(newPasswordInput); + + await waitFor(() => { + expect(screen.getByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.getByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); + }); + }); + it('Should not see error when new password are 8 or more characters', async () => { + render(); + + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a valid new password + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(newPasswordInput); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).not.toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).not.toBeInTheDocument(); + }); + }); + it('Should see error when new password and confirm password do not match on blur event and submit click', async () => { + render(); + + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a valid new password + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(newPasswordInput); + + // Simulate entering a different value in the confirm password field + fireEvent.change(confirmPasswordInput, { target: { value: 'MismatchedPassword' } }); + fireEvent.blur(confirmPasswordInput); + + await waitFor(() => { + expect(screen.getByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.getByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); + }); + }); + it('Should not see error when new password and confirm password match on blur event and submit click', async () => { + render(); + + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering a valid new password + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(newPasswordInput); + + // Simulate entering the same value in the confirmation password field + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.blur(confirmPasswordInput); + + await waitFor(() => { + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).not.toBeInTheDocument(); + }); + + // Simulate clicking the submit button + fireEvent.click(submitButton); + + await waitFor(() => { + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).not.toBeInTheDocument(); + }); + }); + }); + describe("Form Submission", () => { + it('Should enable the submit button when the inputs are valid', async () => { + render(); + + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + + // Simulate entering valid values in the form fields + const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + + fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + + // Check if the submit button is enabled + await waitFor(() => { + expect(submitButton).not.toBeDisabled(); + }); + }); + // it('Should submit the form and reset it on success', async () => { + // server.use([rest.get(SET_USER_PASSWORD, (req, res, ctx) => { + // return res(ctx.status(200),) + // })]) + // render(); + // + // // Mock the requestServer function to simulate a successful API response + // const originalRequestServer = global.requestServer; + // global.requestServer = jest.fn().mockResolvedValue({}); + // + // const submitButton = screen.getByRole('button', { name: 'ثبت' }); + // + // // Simulate entering valid values in the form fields + // const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); + // const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + // const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + // + // fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); + // fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + // fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + // + // // Check if the submit button is enabled + // await waitFor(() => { + // expect(submitButton).not.toBeDisabled(); + // }); + // + // // Simulate form submission + // fireEvent.click(submitButton); + // + // // Wait for success message + // await waitFor(() => { + // // const successMessage = screen.getByText("Password changed successfully"); + // // expect(successMessage).toBeInTheDocument(); + // // Check if the form is reset + // + // }); + // + // expect(currentPasswordInput).toHaveValue(''); + // expect(newPasswordInput).toHaveValue(''); + // expect(confirmPasswordInput).toHaveValue(''); + // + // + // + // // Restore the original requestServer function + // global.requestServer = originalRequestServer; + // }); + }); +}) \ No newline at end of file diff --git a/src/components/dashboard/change-password/change-password-form/index.js b/src/components/dashboard/change-password/change-password-form/index.js new file mode 100644 index 0000000..9b7704a --- /dev/null +++ b/src/components/dashboard/change-password/change-password-form/index.js @@ -0,0 +1,137 @@ +import {Formik, Form} from "formik"; +import * as Yup from "yup"; +import {useTranslations} from "next-intl"; +import {Button, Paper, Stack, Typography} from "@mui/material"; +import PasswordField from "@/core/components/PasswordField"; +import StyledForm from "@/core/components/StyledForm"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; + +const ChangePasswordForm = ({onSubmit}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}) + + const handleSubmit = (values, {setSubmitting, resetForm}) => { + requestServer(SET_USER_PASSWORD, 'post', { + data: { + current_password: values.current_password, + new_password: values.new_password, + new_password_confirmation: values.new_password_confirmation, + }, + }).then((response) => { + resetForm(); + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }; + const initialValues = { + current_password: "", + new_password: "", + new_password_confirmation: "", + }; + const validationSchema = Yup.object().shape({ + current_password: Yup.string().required( + t("ChangePassword.error_message_current_password_required") + ), + new_password: Yup.string() + .min(8, t("ChangePassword.error_message_password_length")) + .required(t("ChangePassword.error_message_new_password_required")), + new_password_confirmation: Yup.string() + .required(t("ChangePassword.error_message_confirm_password_required")) + .test( + t("ChangePassword.error_message_password_match"), + t("ChangePassword.error_message_password_not_match"), // Use the correct error message here + function (value) { + return this.parent.new_password === value; + } + ), + }); + + return ( + + {(props) => ( + { + e.preventDefault(); + props.handleSubmit(); + }} + > + + + + {t("ChangePassword.typography_change_password")} + + + + + + + + + + + + + + + )} + + ); +}; + +export default ChangePasswordForm; diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx index ae74de6..b616274 100644 --- a/src/components/dashboard/change-password/index.jsx +++ b/src/components/dashboard/change-password/index.jsx @@ -1,146 +1,15 @@ import CenterLayout from "@/layouts/CenterLayout"; import DashboardLayouts from "@/layouts/dashboardLayouts"; -import {Button, Container, Paper, Stack, Typography,} from "@mui/material"; -import {Formik} from "formik"; -import * as Yup from "yup"; -import {useTranslations} from "next-intl"; -import PasswordField from "@/core/components/PasswordField"; -import StyledForm from "@/core/components/StyledForm"; -import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; -import SvgChangePassword from "@/core/components/svgs/SvgChangePassword"; -import useRequest from "@/lib/app/hooks/useRequest"; +import {Container} from "@mui/material"; +import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; const DashboardChangePasswordComponent = () => { - const t = useTranslations(); - const requestServer = useRequest({auth: true}) - - const handleSubmit = (values, {setSubmitting, resetForm}) => { - requestServer(SET_USER_PASSWORD, 'post', { - data: { - current_password: values.current_password, - new_password: values.new_password, - new_password_confirmation: values.new_password_confirmation, - }, - }).then((response) => { - resetForm(); - }).catch(() => { - }).finally(() => { - setSubmitting(false); - }); - }; - const initialValues = { - current_password: "", - new_password: "", - new_password_confirmation: "", - }; - const validationSchema = Yup.object().shape({ - current_password: Yup.string().required( - t("ChangePassword.error_message_required") - ), - new_password: Yup.string() - .min(8, t("ChangePassword.error_message_password_length")) - .required(t("ChangePassword.error_message_required")), - new_password_confirmation: Yup.string() - .required(t("ChangePassword.error_message_required")) - .test( - t("ChangePassword.error_message_password_match"), - t("ChangePassword.error_message_password_not_match"), - function (value) { - return this.parent.new_password === value; - } - ), - }); return ( - - {(props) => ( - { - e.preventDefault(); - props.handleSubmit(); - }} - > - - - - - {t("ChangePassword.typography_change_password")} - - - - - - - - - - - - - - - )} - + From 7e2c18ca6c0f49416a47968d615f960454ea614a Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Mon, 2 Oct 2023 16:09:05 +0330 Subject: [PATCH 06/14] CFE-6 Fixed any bugs --- .gitignore | 1 + mocks/handler.js | 4 +++- src/components/login-expert/LoginExpertComponent.jsx | 6 +++--- src/core/data/apiRoutes.js | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 3dfb407..a69bd26 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ cypress/videos cypress/screenshots .next .env.local +.env.test .env .idea package-lock.json \ No newline at end of file diff --git a/mocks/handler.js b/mocks/handler.js index 49e0f99..3a8f75d 100644 --- a/mocks/handler.js +++ b/mocks/handler.js @@ -3,6 +3,8 @@ import {rest} from "msw"; export const handler = [rest.get(GET_USER_ROUTE, (req, res, ctx) => { return res(ctx.json({ - id: 10 + data: { + id: 10 + } })) })] \ No newline at end of file diff --git a/src/components/login-expert/LoginExpertComponent.jsx b/src/components/login-expert/LoginExpertComponent.jsx index 9be301b..d2a2d13 100644 --- a/src/components/login-expert/LoginExpertComponent.jsx +++ b/src/components/login-expert/LoginExpertComponent.jsx @@ -10,7 +10,7 @@ import useRequest from "@/lib/app/hooks/useRequest"; import useUser from "@/lib/app/hooks/useUser"; import {useTranslations} from "next-intl"; -const LoginExpertComponent = () =>{ +const LoginExpertComponent = () => { const t = useTranslations(); const requestServer = useRequest() const {setToken} = useUser(); // pass token to set token @@ -24,7 +24,7 @@ const LoginExpertComponent = () =>{ notification: {show: false} } }).then((response) => { - setToken(response.data.token) + setToken(response.data.data.token) }).catch(() => { props.setSubmitting(false) }) @@ -38,7 +38,7 @@ const LoginExpertComponent = () =>{ password: Yup.string().required(t("LoginPage.password_error_message_required")), }); - return( + return ( Date: Mon, 2 Oct 2023 16:13:04 +0330 Subject: [PATCH 07/14] CFE-6 Fixed env file --- .gitignore | 3 +-- jest.setup.js | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index a69bd26..662e667 100644 --- a/.gitignore +++ b/.gitignore @@ -27,8 +27,7 @@ node_modules cypress/videos cypress/screenshots .next -.env.local -.env.test +.env* .env .idea package-lock.json \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js index 7d55f67..fadd766 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -2,8 +2,6 @@ import '@testing-library/jest-dom' import {server} from "./mocks/server"; import mockRouter from 'next-router-mock' -require('dotenv').config({path: './.env.local'}); - jest.mock('next/router', () => jest.requireActual('next-router-mock')) beforeAll(() => { From edc89c9e86246eaffc75e9e4787a6f526c9270f0 Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Mon, 2 Oct 2023 16:14:10 +0330 Subject: [PATCH 08/14] CFE-6 init call widget and change directory dashboard component --- .../dashboard/change-password/index.jsx | 2 +- .../dashboard/edit-profile/index.jsx | 2 +- src/components/dashboard/first/index.jsx | 2 +- .../Dashboard/Breadcrumbs}/BreadcrumbItem.jsx | 0 .../layouts/Dashboard/Breadcrumbs}/index.jsx | 0 .../Dashboard/CallWidget/CallWidgetButton.jsx | 13 ++++++++++ .../CallWidget/CallWidgetDialog/index.jsx | 24 +++++++++++++++++++ .../layouts/Dashboard/CallWidget/index.jsx | 15 ++++++++++++ .../layouts/Dashboard/Header}/ProfileData.jsx | 0 .../layouts/Dashboard/Header}/ProfileMenu.jsx | 0 .../Dashboard/Header}/ProfileOptionLogOut.jsx | 0 .../Dashboard/Header}/ProfileOptions.jsx | 0 .../layouts/Dashboard/Header}/index.jsx | 0 .../Dashboard/Sidebar}/SidebarDrawer.jsx | 2 +- .../Dashboard/Sidebar}/SidebarList.jsx | 1 - .../Dashboard/Sidebar}/SidebarListItem.jsx | 0 .../Dashboard/Sidebar}/SidebarListSubItem.jsx | 0 .../layouts/Dashboard/Sidebar}/index.jsx | 0 .../layouts/Dashboard}/index.jsx | 18 +++++++------- src/core/components/DialogTransition.jsx | 6 +++++ src/core/components/StyledFab.jsx | 11 +++++++++ src/layouts/DashboardLayout.jsx | 14 +++++++++++ 22 files changed, 96 insertions(+), 14 deletions(-) rename src/{layouts/dashboardLayouts/breadcrumbs => components/layouts/Dashboard/Breadcrumbs}/BreadcrumbItem.jsx (100%) rename src/{layouts/dashboardLayouts/breadcrumbs => components/layouts/Dashboard/Breadcrumbs}/index.jsx (100%) create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/index.jsx rename src/{layouts/dashboardLayouts/header => components/layouts/Dashboard/Header}/ProfileData.jsx (100%) rename src/{layouts/dashboardLayouts/header => components/layouts/Dashboard/Header}/ProfileMenu.jsx (100%) rename src/{layouts/dashboardLayouts/header => components/layouts/Dashboard/Header}/ProfileOptionLogOut.jsx (100%) rename src/{layouts/dashboardLayouts/header => components/layouts/Dashboard/Header}/ProfileOptions.jsx (100%) rename src/{layouts/dashboardLayouts/header => components/layouts/Dashboard/Header}/index.jsx (100%) rename src/{layouts/dashboardLayouts/sidebar => components/layouts/Dashboard/Sidebar}/SidebarDrawer.jsx (93%) rename src/{layouts/dashboardLayouts/sidebar => components/layouts/Dashboard/Sidebar}/SidebarList.jsx (98%) rename src/{layouts/dashboardLayouts/sidebar => components/layouts/Dashboard/Sidebar}/SidebarListItem.jsx (100%) rename src/{layouts/dashboardLayouts/sidebar => components/layouts/Dashboard/Sidebar}/SidebarListSubItem.jsx (100%) rename src/{layouts/dashboardLayouts/sidebar => components/layouts/Dashboard/Sidebar}/index.jsx (100%) rename src/{layouts/dashboardLayouts => components/layouts/Dashboard}/index.jsx (78%) create mode 100644 src/core/components/DialogTransition.jsx create mode 100644 src/core/components/StyledFab.jsx create mode 100644 src/layouts/DashboardLayout.jsx diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx index ae74de6..a69a969 100644 --- a/src/components/dashboard/change-password/index.jsx +++ b/src/components/dashboard/change-password/index.jsx @@ -1,5 +1,5 @@ import CenterLayout from "@/layouts/CenterLayout"; -import DashboardLayouts from "@/layouts/dashboardLayouts"; +import DashboardLayouts from "@/layouts/DashboardLayout"; import {Button, Container, Paper, Stack, Typography,} from "@mui/material"; import {Formik} from "formik"; import * as Yup from "yup"; diff --git a/src/components/dashboard/edit-profile/index.jsx b/src/components/dashboard/edit-profile/index.jsx index 3018aa3..394a7b0 100644 --- a/src/components/dashboard/edit-profile/index.jsx +++ b/src/components/dashboard/edit-profile/index.jsx @@ -1,6 +1,6 @@ import StyledForm from "@/core/components/StyledForm"; import CenterLayout from "@/layouts/CenterLayout"; -import DashboardLayouts from "@/layouts/dashboardLayouts"; +import DashboardLayouts from "@/layouts/DashboardLayout"; import useUser from "@/lib/app/hooks/useUser"; import {Box, Container, Grid, Paper, Stack, TextField, Typography,} from "@mui/material"; import * as Yup from "yup"; diff --git a/src/components/dashboard/first/index.jsx b/src/components/dashboard/first/index.jsx index ee4f80a..1785a67 100644 --- a/src/components/dashboard/first/index.jsx +++ b/src/components/dashboard/first/index.jsx @@ -1,4 +1,4 @@ -import DashboardLayouts from "@/layouts/dashboardLayouts"; +import DashboardLayouts from "@/layouts/DashboardLayout"; const DashboardFirstComponent = () => { return ; diff --git a/src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx b/src/components/layouts/Dashboard/Breadcrumbs/BreadcrumbItem.jsx similarity index 100% rename from src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx rename to src/components/layouts/Dashboard/Breadcrumbs/BreadcrumbItem.jsx diff --git a/src/layouts/dashboardLayouts/breadcrumbs/index.jsx b/src/components/layouts/Dashboard/Breadcrumbs/index.jsx similarity index 100% rename from src/layouts/dashboardLayouts/breadcrumbs/index.jsx rename to src/components/layouts/Dashboard/Breadcrumbs/index.jsx diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx new file mode 100644 index 0000000..416e5c8 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx @@ -0,0 +1,13 @@ +import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg'; +import StyledFab from "@/core/components/StyledFab"; + +const CallWidgetButton = ({setOpen}) => { + return ( + setOpen(true)}> + + + ) +} + +export default CallWidgetButton \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx new file mode 100644 index 0000000..77ed57d --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx @@ -0,0 +1,24 @@ +import {Dialog} from "@mui/material"; +import {DialogTransition} from "@/core/components/DialogTransition"; + +const CallWidgetDialog = ({open, setOpen}) => { + + const handleClose = () => { + setOpen(false); + }; + + return ( + <> + + + + + ) +} + +export default CallWidgetDialog \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/index.jsx b/src/components/layouts/Dashboard/CallWidget/index.jsx new file mode 100644 index 0000000..4d871b2 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/index.jsx @@ -0,0 +1,15 @@ +import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton"; +import CallWidgetDialog from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog"; +import {useState} from "react"; + +const CallWidget = () => { + const [open, setOpen] = useState(false) + return ( + <> + + + + ) +} + +export default CallWidget \ No newline at end of file diff --git a/src/layouts/dashboardLayouts/header/ProfileData.jsx b/src/components/layouts/Dashboard/Header/ProfileData.jsx similarity index 100% rename from src/layouts/dashboardLayouts/header/ProfileData.jsx rename to src/components/layouts/Dashboard/Header/ProfileData.jsx diff --git a/src/layouts/dashboardLayouts/header/ProfileMenu.jsx b/src/components/layouts/Dashboard/Header/ProfileMenu.jsx similarity index 100% rename from src/layouts/dashboardLayouts/header/ProfileMenu.jsx rename to src/components/layouts/Dashboard/Header/ProfileMenu.jsx diff --git a/src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx b/src/components/layouts/Dashboard/Header/ProfileOptionLogOut.jsx similarity index 100% rename from src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx rename to src/components/layouts/Dashboard/Header/ProfileOptionLogOut.jsx diff --git a/src/layouts/dashboardLayouts/header/ProfileOptions.jsx b/src/components/layouts/Dashboard/Header/ProfileOptions.jsx similarity index 100% rename from src/layouts/dashboardLayouts/header/ProfileOptions.jsx rename to src/components/layouts/Dashboard/Header/ProfileOptions.jsx diff --git a/src/layouts/dashboardLayouts/header/index.jsx b/src/components/layouts/Dashboard/Header/index.jsx similarity index 100% rename from src/layouts/dashboardLayouts/header/index.jsx rename to src/components/layouts/Dashboard/Header/index.jsx diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx similarity index 93% rename from src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx rename to src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx index f155dce..33348a5 100644 --- a/src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx +++ b/src/components/layouts/Dashboard/Sidebar/SidebarDrawer.jsx @@ -14,7 +14,7 @@ const SidebarDrawer = ({handleDrawerToggle}) => { {t("app_short_name")} - {user.name} | {user.position} + {user.full_name} | {user.position} diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarList.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx similarity index 98% rename from src/layouts/dashboardLayouts/sidebar/SidebarList.jsx rename to src/components/layouts/Dashboard/Sidebar/SidebarList.jsx index 12d268d..040b455 100644 --- a/src/layouts/dashboardLayouts/sidebar/SidebarList.jsx +++ b/src/components/layouts/Dashboard/Sidebar/SidebarList.jsx @@ -18,7 +18,6 @@ function reducer(state, action) { case "SELECTED": return state.map((itemsArr) => itemsArr.map((item) => { - console.log(item) if (item.type === "page") { if (action.route === item.route) return {...item, selected: true} diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarListItem.jsx similarity index 100% rename from src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx rename to src/components/layouts/Dashboard/Sidebar/SidebarListItem.jsx diff --git a/src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx b/src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx similarity index 100% rename from src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx rename to src/components/layouts/Dashboard/Sidebar/SidebarListSubItem.jsx diff --git a/src/layouts/dashboardLayouts/sidebar/index.jsx b/src/components/layouts/Dashboard/Sidebar/index.jsx similarity index 100% rename from src/layouts/dashboardLayouts/sidebar/index.jsx rename to src/components/layouts/Dashboard/Sidebar/index.jsx diff --git a/src/layouts/dashboardLayouts/index.jsx b/src/components/layouts/Dashboard/index.jsx similarity index 78% rename from src/layouts/dashboardLayouts/index.jsx rename to src/components/layouts/Dashboard/index.jsx index fae51d2..3ee24c6 100644 --- a/src/layouts/dashboardLayouts/index.jsx +++ b/src/components/layouts/Dashboard/index.jsx @@ -1,13 +1,13 @@ -import {useState} from "react"; -import FullPageLayout from "../FullPageLayout"; -import Header from "./header"; -import Sidebar from "./sidebar"; +import Header from "src/components/layouts/Dashboard/Header"; +import Sidebar from "src/components/layouts/Dashboard/Sidebar"; +import FullPageLayout from "@/layouts/FullPageLayout"; import {Toolbar} from "@mui/material"; -import BreadCrumbs from "./breadcrumbs"; +import BreadCrumbs from "src/components/layouts/Dashboard/Breadcrumbs"; +import {useState} from "react"; const drawerWidth = 240; -const DashboardLayouts = (props) => { +const Dashboard = (props) => { const {window} = props; const [mobileOpen, setMobileOpen] = useState(false); const container = @@ -39,7 +39,7 @@ const DashboardLayouts = (props) => { - ); -}; + ) +} -export default DashboardLayouts; +export default Dashboard \ No newline at end of file diff --git a/src/core/components/DialogTransition.jsx b/src/core/components/DialogTransition.jsx new file mode 100644 index 0000000..d94765f --- /dev/null +++ b/src/core/components/DialogTransition.jsx @@ -0,0 +1,6 @@ +import {forwardRef} from "react"; +import {Slide} from "@mui/material"; + +export const DialogTransition = forwardRef(function DialogTransition(props, ref) { + return ; +}); diff --git a/src/core/components/StyledFab.jsx b/src/core/components/StyledFab.jsx new file mode 100644 index 0000000..dd935cc --- /dev/null +++ b/src/core/components/StyledFab.jsx @@ -0,0 +1,11 @@ +import {Fab, styled} from "@mui/material"; + +const StyledFab = styled(Fab)` + position: absolute; + bottom: 16px; + /* @noflip */ + left: 16px; + z-index: 1500; +`; + +export default StyledFab; diff --git a/src/layouts/DashboardLayout.jsx b/src/layouts/DashboardLayout.jsx new file mode 100644 index 0000000..40b722d --- /dev/null +++ b/src/layouts/DashboardLayout.jsx @@ -0,0 +1,14 @@ +import CallWidget from "src/components/layouts/Dashboard/CallWidget"; +import Dashboard from "src/components/layouts/Dashboard"; + +const DashboardLayouts = (props) => { + + return ( + <> + + + + ); +}; + +export default DashboardLayouts; From 7902e92df2270bd618aba279f78948dd2b495b49 Mon Sep 17 00:00:00 2001 From: Yasiu1376 Date: Sat, 7 Oct 2023 11:39:43 +0330 Subject: [PATCH 09/14] CFE-12 complete change password test and mock for handle api --- mocks/handler.js | 21 +- .../__test__/index.test.js | 185 ++++++++---------- src/core/data/apiRoutes.js | 2 +- 3 files changed, 100 insertions(+), 108 deletions(-) diff --git a/mocks/handler.js b/mocks/handler.js index 49e0f99..65f5398 100644 --- a/mocks/handler.js +++ b/mocks/handler.js @@ -1,8 +1,17 @@ -import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; +import {GET_USER_ROUTE, SET_USER_PASSWORD} from "@/core/data/apiRoutes"; import {rest} from "msw"; -export const handler = [rest.get(GET_USER_ROUTE, (req, res, ctx) => { - return res(ctx.json({ - id: 10 - })) -})] \ No newline at end of file +export const handler = [ + rest.get(GET_USER_ROUTE, (req, res, ctx) => { + return res(ctx.json({ + data:{ + id: 10 + } + })) + }), + rest.post(SET_USER_PASSWORD, (req, res, ctx) => { + return res( + ctx.status(200), + ); + }), +] \ No newline at end of file diff --git a/src/components/dashboard/change-password/change-password-form/__test__/index.test.js b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js index 4a63240..503b571 100644 --- a/src/components/dashboard/change-password/change-password-form/__test__/index.test.js +++ b/src/components/dashboard/change-password/change-password-form/__test__/index.test.js @@ -1,6 +1,10 @@ import { render, screen, waitFor, fireEvent , act } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; +import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; +import {server} from "../../../../../../mocks/server"; +import {rest} from "msw"; describe('Change Password Form Component From Change Password Page', () => { describe('Rendering', () => { @@ -11,17 +15,17 @@ describe('Change Password Form Component From Change Password Page', () => { }); it('Should see the label for current password field', () => { render(); - const currentPasswordLabel = screen.getByLabelText('رمز عبور فعلی'); + const currentPasswordLabel = screen.queryByLabelText('رمز عبور فعلی'); expect(currentPasswordLabel).toBeInTheDocument(); }); it('Should see the label for new password field', () => { render(); - const newPasswordLabel = screen.getByLabelText('رمز عبور جدید'); + const newPasswordLabel = screen.queryByLabelText('رمز عبور جدید'); expect(newPasswordLabel).toBeInTheDocument(); }); it('Should see the label for confirm password field', () => { render(); - const confirmPasswordLabel = screen.getByLabelText('تکرار رمز عبور جدید'); + const confirmPasswordLabel = screen.queryByLabelText('تکرار رمز عبور جدید'); expect(confirmPasswordLabel).toBeInTheDocument(); }); it('Should see the submit button with the submit label and should be disable', () => { @@ -32,9 +36,9 @@ describe('Change Password Form Component From Change Password Page', () => { }); it('Should have empty input fields and not see any validation error for current password, new password, and confirm password', () => { render(); - const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); - const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); expect(currentPasswordInput).toHaveValue(''); expect(newPasswordInput).toHaveValue(''); @@ -47,10 +51,10 @@ describe('Change Password Form Component From Change Password Page', () => { }); }); describe("Behavior", ()=>{ - it('Should fill the current password field', async () => { + it('Should fill the current password field and not see any error while its not empty', async () => { render(); - const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); // Simulate typing something in the current password input fireEvent.change(currentPasswordInput, { target: { value: 'witel@fani0' } }); @@ -60,10 +64,10 @@ describe('Change Password Form Component From Change Password Page', () => { expect(currentPasswordInput).toHaveValue('witel@fani0'); }); }); - it('Should fill the new password field', async () => { + it('Should fill the new password field and not see any error while its not empty', async () => { render(); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); // Simulate typing something in the new password input fireEvent.change(newPasswordInput, { target: { value: 'witel@fani1' } }); @@ -73,10 +77,10 @@ describe('Change Password Form Component From Change Password Page', () => { expect(newPasswordInput).toHaveValue('witel@fani1'); }); }); - it('Should fill the confirm new password field', async () => { + it('Should fill the confirm new password field and not see any error while its not empty', async () => { render(); - const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); // Simulate typing something in the confirmation new password input fireEvent.change(confirmPasswordInput, { target: { value: 'witel@fani1' } }); @@ -88,12 +92,11 @@ describe('Change Password Form Component From Change Password Page', () => { }); }); describe("Validation", ()=>{ - it('Should see error while current password is empty on submit click or on blur event and not see any error while its not empty', async () => { + it('Should see error while current password is empty on blur event', async () => { render(); // Simulate blur event on the current password input - const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); - const submitButton = screen.getByRole('button', { name: 'ثبت' }); + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); // Clear the input field fireEvent.change(currentPasswordInput, { target: { value: '' } }); @@ -103,20 +106,12 @@ describe('Change Password Form Component From Change Password Page', () => { await waitFor(()=>{ expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument() }); - - // Simulate clicking the submit button - fireEvent.click(submitButton); - - await waitFor(() => { - expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument(); - }); }); - it('Should see error while new password is empty on submit click or on blur event and not see any error while its not empty', async () => { + it('Should see error while new password is empty on blur event', async () => { render(); // Simulate blur event on the new password input - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); - const submitButton = screen.getByRole('button', { name: 'ثبت' }); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); // Clear the input field fireEvent.change(newPasswordInput, { target: { value: '' } }); @@ -126,20 +121,12 @@ describe('Change Password Form Component From Change Password Page', () => { await waitFor(()=>{ expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument() }); - - // Simulate clicking the submit button - fireEvent.click(submitButton); - - await waitFor(() => { - expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument(); - }); }); - it('Should see error while confirm new password is empty on submit click or on blur event and not see any error while its not empty', async () => { + it('Should see error while confirm new password is empty on blur event', async () => { render(); // Simulate blur event on the new password input - const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); - const submitButton = screen.getByRole('button', { name: 'ثبت' }); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); // Clear the input field fireEvent.change(confirmPasswordInput, { target: { value: '' } }); @@ -149,18 +136,11 @@ describe('Change Password Form Component From Change Password Page', () => { await waitFor(()=>{ expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument() }); - - // Simulate clicking the submit button - fireEvent.click(submitButton); - - await waitFor(() => { - expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument(); - }); }); it('Should see error when new password is less than 8 characters', async () => { render(); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); const submitButton = screen.getByRole('button', { name: 'ثبت' }); // Simulate entering a short new password @@ -168,20 +148,20 @@ describe('Change Password Form Component From Change Password Page', () => { fireEvent.blur(newPasswordInput); await waitFor(() => { - expect(screen.getByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); }); // Simulate clicking the submit button fireEvent.click(submitButton); await waitFor(() => { - expect(screen.getByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); + expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument(); }); }); it('Should not see error when new password are 8 or more characters', async () => { render(); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); const submitButton = screen.getByRole('button', { name: 'ثبت' }); // Simulate entering a valid new password @@ -202,8 +182,8 @@ describe('Change Password Form Component From Change Password Page', () => { it('Should see error when new password and confirm password do not match on blur event and submit click', async () => { render(); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); - const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); const submitButton = screen.getByRole('button', { name: 'ثبت' }); // Simulate entering a valid new password @@ -215,21 +195,21 @@ describe('Change Password Form Component From Change Password Page', () => { fireEvent.blur(confirmPasswordInput); await waitFor(() => { - expect(screen.getByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); }); // Simulate clicking the submit button fireEvent.click(submitButton); await waitFor(() => { - expect(screen.getByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); + expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument(); }); }); it('Should not see error when new password and confirm password match on blur event and submit click', async () => { render(); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); - const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); const submitButton = screen.getByRole('button', { name: 'ثبت' }); // Simulate entering a valid new password @@ -259,9 +239,9 @@ describe('Change Password Form Component From Change Password Page', () => { const submitButton = screen.getByRole('button', { name: 'ثبت' }); // Simulate entering valid values in the form fields - const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); - const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); - const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); @@ -272,51 +252,54 @@ describe('Change Password Form Component From Change Password Page', () => { expect(submitButton).not.toBeDisabled(); }); }); - // it('Should submit the form and reset it on success', async () => { - // server.use([rest.get(SET_USER_PASSWORD, (req, res, ctx) => { - // return res(ctx.status(200),) - // })]) - // render(); - // - // // Mock the requestServer function to simulate a successful API response - // const originalRequestServer = global.requestServer; - // global.requestServer = jest.fn().mockResolvedValue({}); - // - // const submitButton = screen.getByRole('button', { name: 'ثبت' }); - // - // // Simulate entering valid values in the form fields - // const currentPasswordInput = screen.getByLabelText('رمز عبور فعلی'); - // const newPasswordInput = screen.getByLabelText('رمز عبور جدید'); - // const confirmPasswordInput = screen.getByLabelText('تکرار رمز عبور جدید'); - // - // fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); - // fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); - // fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); - // - // // Check if the submit button is enabled - // await waitFor(() => { - // expect(submitButton).not.toBeDisabled(); - // }); - // - // // Simulate form submission - // fireEvent.click(submitButton); - // - // // Wait for success message - // await waitFor(() => { - // // const successMessage = screen.getByText("Password changed successfully"); - // // expect(successMessage).toBeInTheDocument(); - // // Check if the form is reset - // - // }); - // - // expect(currentPasswordInput).toHaveValue(''); - // expect(newPasswordInput).toHaveValue(''); - // expect(confirmPasswordInput).toHaveValue(''); - // - // - // - // // Restore the original requestServer function - // global.requestServer = originalRequestServer; - // }); + it('Should request to api and resetform in success response', async () => { + // Render the component + render(); + + // Fill in the form fields + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + fireEvent.change(currentPasswordInput, { target: { value: 'Witel@fani0' } }); + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + + // Submit the form + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + await userEvent.click(submitButton); + + await waitFor(() => { + expect(currentPasswordInput).toHaveValue(''); + expect(newPasswordInput).toHaveValue(''); + expect(confirmPasswordInput).toHaveValue(''); + }); + }); + it('Should request to api and not resetform in error response', async () => { + // Render the component + render(); + server.use([rest.get(SET_USER_PASSWORD, (req, res, ctx) => { + return res(ctx.status(422)) + })]) + + // Fill in the form fields wrong + const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی'); + const newPasswordInput = screen.queryByLabelText('رمز عبور جدید'); + const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید'); + + fireEvent.change(currentPasswordInput, { target: { value: 'incorrect' } }); + fireEvent.change(newPasswordInput, { target: { value: 'Witel@fani1' } }); + fireEvent.change(confirmPasswordInput, { target: { value: 'Witel@fani1' } }); + + // Submit the form + const submitButton = screen.getByRole('button', { name: 'ثبت' }); + await userEvent.click(submitButton); + + await waitFor(() => { + expect(currentPasswordInput.value).toBe('incorrect'); + expect(newPasswordInput.value).toBe('Witel@fani1'); + expect(confirmPasswordInput.value).toBe('Witel@fani1'); + }); + }); }); }) \ No newline at end of file diff --git a/src/core/data/apiRoutes.js b/src/core/data/apiRoutes.js index 06dd4e9..4d2d8c0 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -5,7 +5,7 @@ export const GET_USER_TOKEN = BASE_URL + "/dashboard/login"; //end login //change password -export const SET_USER_PASSWORD = BASE_URL + "/dashboard/profile/change_password"; +export const SET_USER_PASSWORD = BASE_URL + "/api/profile/change_password"; //end change password //user data From 84ce95b2723a0fea5c90bd4ddb88c768f256d6b7 Mon Sep 17 00:00:00 2001 From: Yasiu1376 Date: Sat, 7 Oct 2023 13:47:57 +0330 Subject: [PATCH 10/14] CFE-23 fix call DashboardLayout in change password form --- src/components/dashboard/change-password/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx index b616274..771915b 100644 --- a/src/components/dashboard/change-password/index.jsx +++ b/src/components/dashboard/change-password/index.jsx @@ -1,5 +1,5 @@ import CenterLayout from "@/layouts/CenterLayout"; -import DashboardLayouts from "@/layouts/dashboardLayouts"; +import DashboardLayouts from "@/layouts/DashboardLayout"; import {Container} from "@mui/material"; import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; From 96e8d9220894bd73f6c85d588fd4883cf6a49114 Mon Sep 17 00:00:00 2001 From: Yasiu1376 Date: Sat, 7 Oct 2023 14:01:38 +0330 Subject: [PATCH 11/14] CFE-23 change name of DashboardLayouts to DashboardLayout --- src/components/dashboard/change-password/index.jsx | 6 +++--- src/components/dashboard/edit-profile/index.jsx | 6 +++--- src/components/dashboard/first/index.jsx | 4 ++-- src/layouts/DashboardLayout.jsx | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx index 771915b..74896a6 100644 --- a/src/components/dashboard/change-password/index.jsx +++ b/src/components/dashboard/change-password/index.jsx @@ -1,18 +1,18 @@ import CenterLayout from "@/layouts/CenterLayout"; -import DashboardLayouts from "@/layouts/DashboardLayout"; +import DashboardLayout from "@/layouts/DashboardLayout"; import {Container} from "@mui/material"; import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; const DashboardChangePasswordComponent = () => { return ( - + - + ); }; export default DashboardChangePasswordComponent; diff --git a/src/components/dashboard/edit-profile/index.jsx b/src/components/dashboard/edit-profile/index.jsx index 394a7b0..e35e3bf 100644 --- a/src/components/dashboard/edit-profile/index.jsx +++ b/src/components/dashboard/edit-profile/index.jsx @@ -1,6 +1,6 @@ import StyledForm from "@/core/components/StyledForm"; import CenterLayout from "@/layouts/CenterLayout"; -import DashboardLayouts from "@/layouts/DashboardLayout"; +import DashboardLayout from "@/layouts/DashboardLayout"; import useUser from "@/lib/app/hooks/useUser"; import {Box, Container, Grid, Paper, Stack, TextField, Typography,} from "@mui/material"; import * as Yup from "yup"; @@ -54,7 +54,7 @@ const DashboardEditProfile = () => { const validationSchema = Yup.object().shape({}); return ( - + { - + ); }; diff --git a/src/components/dashboard/first/index.jsx b/src/components/dashboard/first/index.jsx index 1785a67..841d677 100644 --- a/src/components/dashboard/first/index.jsx +++ b/src/components/dashboard/first/index.jsx @@ -1,7 +1,7 @@ -import DashboardLayouts from "@/layouts/DashboardLayout"; +import DashboardLayout from "@/layouts/DashboardLayout"; const DashboardFirstComponent = () => { - return ; + return ; }; export default DashboardFirstComponent; diff --git a/src/layouts/DashboardLayout.jsx b/src/layouts/DashboardLayout.jsx index 40b722d..3c62a77 100644 --- a/src/layouts/DashboardLayout.jsx +++ b/src/layouts/DashboardLayout.jsx @@ -1,7 +1,7 @@ import CallWidget from "src/components/layouts/Dashboard/CallWidget"; import Dashboard from "src/components/layouts/Dashboard"; -const DashboardLayouts = (props) => { +const DashboardLayout = (props) => { return ( <> @@ -11,4 +11,4 @@ const DashboardLayouts = (props) => { ); }; -export default DashboardLayouts; +export default DashboardLayout; From 77f5c86a0d31635da6bae9309879269e5ec165af Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Sat, 7 Oct 2023 15:03:20 +0330 Subject: [PATCH 12/14] CFE-22 fixed bug network hook and component --- src/core/components/NetworkComponent.jsx | 7 +++---- src/lib/app/hooks/useNetwork.jsx | 26 +----------------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/src/core/components/NetworkComponent.jsx b/src/core/components/NetworkComponent.jsx index 8ebbb68..22d4620 100644 --- a/src/core/components/NetworkComponent.jsx +++ b/src/core/components/NetworkComponent.jsx @@ -6,13 +6,13 @@ import WifiOffIcon from '@mui/icons-material/WifiOff'; import {useTranslations} from "next-intl"; const NetworkComponent = () => { - const toastId = useRef(null); + const networkToastId = useRef(null); const network = useNetwork() const t = useTranslations() useEffect(() => { if (network.online) { - toast.update(toastId.current, { + toast.update(networkToastId.current, { type: toast.TYPE.SUCCESS, render: t('online_message'), autoClose: 2000, @@ -22,8 +22,7 @@ const NetworkComponent = () => { }); return } - toast.dismiss() - toastId.current = toast.warn(t('offline_message'), { + networkToastId.current = toast.warn(t('offline_message'), { autoClose: false, closeButton: false, closeOnClick: false, icon: }) }, [network.online]); diff --git a/src/lib/app/hooks/useNetwork.jsx b/src/lib/app/hooks/useNetwork.jsx index 84e2950..9c436a5 100644 --- a/src/lib/app/hooks/useNetwork.jsx +++ b/src/lib/app/hooks/useNetwork.jsx @@ -1,29 +1,5 @@ import {useEffect, useState} from "react"; -function getNetworkConnection() { - return ( - navigator.connection || - navigator.mozConnection || - navigator.webkitConnection || - null - ); -} - -function getNetworkConnectionInfo() { - const connection = getNetworkConnection(); - if (!connection) { - return {}; - } - return { - rtt: connection.rtt, - type: connection.type, - saveData: connection.saveData, - downLink: connection.downLink, - downLinkMax: connection.downLinkMax, - effectiveType: connection.effectiveType, - }; -} - function useNetwork() { const [state, setState] = useState(() => { return { @@ -51,7 +27,7 @@ function useNetwork() { window.removeEventListener("offline", handleOffline); }; }, []); - + return state; } From 05c5e13c6386e229ad4e53171af8bcf9ffc086f8 Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Sat, 7 Oct 2023 15:03:57 +0330 Subject: [PATCH 13/14] CFE-22 fixed bug project --- src/layouts/MuiLayout.jsx | 15 ++++++--------- src/lib/app/contexts/user.jsx | 6 ------ src/pages/_app.jsx | 30 +++++++++++++++--------------- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/layouts/MuiLayout.jsx b/src/layouts/MuiLayout.jsx index 6dcd3ed..eba3449 100644 --- a/src/layouts/MuiLayout.jsx +++ b/src/layouts/MuiLayout.jsx @@ -22,26 +22,23 @@ const MuiLayout = ({children, isBot}) => { { export const UserContext = createContext(); export const UserProvider = ({children}) => { - const socket = useSocket() const [state, dispatch] = useReducer(reducer, initialUser); const clearUser = useCallback(() => { @@ -102,16 +100,12 @@ export const UserProvider = ({children}) => { clearUser(); changeAuthState(false); changeLanguageState(false); - - socket.auth.token = "" return; } getUser((data) => { changeUser(data); changeAuthState(true); changeLanguageState(true); - - socket.auth.token = data.telephone_id }); }, [state.token]); diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index fe249a6..c5fad77 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -12,22 +12,22 @@ import {SocketProvider} from "@/lib/app/contexts/socket"; const App = ({Component, pageProps}) => { return (<> - - - - - - {pageProps.messages ? : ''} - - + + + + + {pageProps.messages ? : ''} + + + - - - - - - - + + + + + + + ) }; From b0766f13461f1c337b8bb2ad1c2e80cd085e89a1 Mon Sep 17 00:00:00 2001 From: AmirHossein Mahmoodi Date: Sat, 7 Oct 2023 15:04:40 +0330 Subject: [PATCH 14/14] CFE-22 implementation of socket and dialog and button call widget --- public/locales/fa/app.json | 2 + .../Dashboard/CallWidget/CallWidgetButton.jsx | 6 ++- .../CallTabPanel/CallActions/index.jsx | 7 +++ .../CallTabPanel/CallHistory/index.jsx | 7 +++ .../CallTabs/CallTabPanel/index.jsx | 19 +++++++ .../CallTabs/CallTabsList/CallTabLabel.jsx | 23 ++++++++ .../CallTabs/CallTabsList/index.jsx | 27 ++++++++++ .../CallWidgetDialog/CallTabs/index.jsx | 17 ++++++ .../CallWidget/CallWidgetDialog/index.jsx | 52 +++++++++++++----- .../layouts/Dashboard/CallWidget/index.jsx | 25 ++++++--- src/core/components/StyledFab.jsx | 1 - src/lib/app/contexts/socket.jsx | 54 ++++++++++++++++++- src/lib/callWidget/contexts/answers.jsx | 31 +++++++++++ src/lib/callWidget/hooks/useAnswers.jsx | 38 +++++++++++++ src/lib/callWidget/hooks/useCallDialog.jsx | 10 ++++ 15 files changed, 295 insertions(+), 24 deletions(-) create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx create mode 100644 src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx create mode 100644 src/lib/callWidget/contexts/answers.jsx create mode 100644 src/lib/callWidget/hooks/useAnswers.jsx create mode 100644 src/lib/callWidget/hooks/useCallDialog.jsx diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index e813849..7366af7 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -15,6 +15,8 @@ "between": "میان", "online_message": "شما به اینترنت وصل هستید", "offline_message": "اتصال شما به اینترنت قطع شده است", + "socket_is_connect_message": "شما به سوکت وصل هستید", + "socket_is_not_connect_message": "اتصال شما به سوکت قطع شده است", "header": { "open_profile": "پروفایل", "edit_profile": " پروفایل", diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx index 416e5c8..76af95c 100644 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetButton.jsx @@ -1,10 +1,12 @@ import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg'; import StyledFab from "@/core/components/StyledFab"; +import useCallDialog from "@/lib/callWidget/hooks/useCallDialog"; -const CallWidgetButton = ({setOpen}) => { +const CallWidgetButton = () => { + const {setOpenCallDialog} = useCallDialog() return ( setOpen(true)}> + onClick={() => setOpenCallDialog(true)}> ) diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx new file mode 100644 index 0000000..5b9326f --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions/index.jsx @@ -0,0 +1,7 @@ +const CallActions = ({tab}) => { + return ( + <>CallActions - {tab.id} + ) +} + +export default CallActions \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx new file mode 100644 index 0000000..292c640 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory/index.jsx @@ -0,0 +1,7 @@ +const CallHistory = ({tab}) => { + return ( + <>CallHistory - {tab.id} + ) +} + +export default CallHistory \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx new file mode 100644 index 0000000..79dc736 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/index.jsx @@ -0,0 +1,19 @@ +import {Grid} from "@mui/material"; +import CallActions from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallActions"; +import CallHistory from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel/CallHistory"; + +const CallTabPanel = ({tab}) => { + return ( + + + + + + + + + ) +} + +export default CallTabPanel \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx new file mode 100644 index 0000000..7a9b703 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel.jsx @@ -0,0 +1,23 @@ +import {IconButton, Stack, Typography, Zoom} from "@mui/material"; +import CloseIcon from '@mui/icons-material/Close'; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabLabel = ({tab}) => { + const {closeAnswerTab} = useAnswers() + const handleCloseClick = (id) => { + closeAnswerTab(id) + }; + + return ( + + {tab.phone_number} - {tab.id} + + handleCloseClick(tab.id)}> + + + + + ) +} + +export default CallTabLabel \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx new file mode 100644 index 0000000..70fccd9 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/index.jsx @@ -0,0 +1,27 @@ +import {Box, Tab, Tabs} from "@mui/material"; +import CallTabLabel + from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList/CallTabLabel"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabsList = () => { + const {answersList, changeActiveTabAnswers, activeTab} = useAnswers() + const handleChange = (event, newValue) => { + changeActiveTabAnswers(newValue) + }; + + return ( + + + {answersList.map((answer) => ( + } key={answer.id}/> + ))} + + + ) +} + +export default CallTabsList \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx new file mode 100644 index 0000000..4e1d2f1 --- /dev/null +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/index.jsx @@ -0,0 +1,17 @@ +import CallTabsList from "src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabsList"; +import CallTabPanel from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs/CallTabPanel"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; + +const CallTabs = () => { + const {answersList} = useAnswers() + + return ( + <> + + {answersList.map((answer) => ( + + ))} + + ) +} +export default CallTabs \ No newline at end of file diff --git a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx index 77ed57d..5afb0a1 100644 --- a/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx +++ b/src/components/layouts/Dashboard/CallWidget/CallWidgetDialog/index.jsx @@ -1,23 +1,47 @@ import {Dialog} from "@mui/material"; import {DialogTransition} from "@/core/components/DialogTransition"; +import {useEffect} from "react"; +import useCallDialog from "@/lib/callWidget/hooks/useCallDialog"; +import CallTabs from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog/CallTabs"; +import useAnswers from "@/lib/callWidget/hooks/useAnswers"; +import useSocket from "@/lib/app/hooks/useSocket"; -const CallWidgetDialog = ({open, setOpen}) => { +const CallWidgetDialog = () => { + const {openCallDialog, setOpenCallDialog} = useCallDialog() + const {answersList, newAnswerTab} = useAnswers() + const socket = useSocket() - const handleClose = () => { - setOpen(false); - }; + useEffect(() => { + const answerCall = (data, act) => { + newAnswerTab({ + id: data.id, + phone_number: data.phone_number + }) + act() + } + socket.on('answer', answerCall) + + return () => { + socket.off('answer', answerCall) + } + }, []); + + useEffect(() => { + if (answersList.length === 0) { + setOpenCallDialog(false) + return + } + setOpenCallDialog(true) + }, [answersList.length]); return ( - <> - - - - + + + ) } diff --git a/src/components/layouts/Dashboard/CallWidget/index.jsx b/src/components/layouts/Dashboard/CallWidget/index.jsx index 4d871b2..d6fe5a0 100644 --- a/src/components/layouts/Dashboard/CallWidget/index.jsx +++ b/src/components/layouts/Dashboard/CallWidget/index.jsx @@ -1,14 +1,27 @@ import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton"; import CallWidgetDialog from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog"; -import {useState} from "react"; +import {AnswersProvider} from "@/lib/callWidget/contexts/answers"; +import useSocket from "@/lib/app/hooks/useSocket"; +import {useEffect} from "react"; const CallWidget = () => { - const [open, setOpen] = useState(false) + const socket = useSocket() + + useEffect(() => { + if (socket.connected) return + + socket.connect() + + return () => { + socket.disconnect() + } + }, []); + return ( - <> - - - + + + + ) } diff --git a/src/core/components/StyledFab.jsx b/src/core/components/StyledFab.jsx index dd935cc..95c5ff7 100644 --- a/src/core/components/StyledFab.jsx +++ b/src/core/components/StyledFab.jsx @@ -5,7 +5,6 @@ const StyledFab = styled(Fab)` bottom: 16px; /* @noflip */ left: 16px; - z-index: 1500; `; export default StyledFab; diff --git a/src/lib/app/contexts/socket.jsx b/src/lib/app/contexts/socket.jsx index 7da4f43..47154c8 100644 --- a/src/lib/app/contexts/socket.jsx +++ b/src/lib/app/contexts/socket.jsx @@ -1,9 +1,18 @@ -import {createContext, useMemo} from "react"; +import {createContext, useEffect, useMemo, useRef, useState} from "react"; import {io} from "socket.io-client"; +import useUser from "@/lib/app/hooks/useUser"; +import {useTranslations} from "next-intl"; +import {toast} from "react-toastify"; +import PowerIcon from "@mui/icons-material/Power"; +import PowerOffIcon from "@mui/icons-material/PowerOff"; export const SocketContext = createContext() export const SocketProvider = ({children}) => { + const {user, isAuth} = useUser() + const [connectionError, setConnectionError] = useState(false) + const socketToastId = useRef(null); + const t = useTranslations() const socket = useMemo(() => io(process.env.NEXT_PUBLIC_SERVER_SOCKET_URL, { autoConnect: false, auth: { @@ -11,5 +20,48 @@ export const SocketProvider = ({children}) => { } }), []) + useEffect(() => { + if (isAuth) { + socket.auth.token = user.telephone_id + return + } + socket.auth.token = '' + }, [isAuth]); + + useEffect(() => { + const connectErrorHandler = () => { + setConnectionError(true) + } + const connectHandler = () => { + setConnectionError(false) + } + socket.on("connect_error", connectErrorHandler); + socket.on("connect", connectHandler); + + return () => { + socket.off("connect_error", connectErrorHandler); + socket.off("connect", connectHandler); + } + }, []); + + useEffect(() => { + if (!connectionError) { + toast.update(socketToastId.current, { + type: toast.TYPE.SUCCESS, + render: t('socket_is_connect_message'), + autoClose: 2000, + closeButton: true, + closeOnClick: true, + icon: + }); + return + } + socketToastId.current = toast.warn(t('socket_is_not_connect_message'), { + draggable: false, + autoClose: false, closeButton: false, closeOnClick: false, icon: + }) + + }, [connectionError]); + return {children} } \ No newline at end of file diff --git a/src/lib/callWidget/contexts/answers.jsx b/src/lib/callWidget/contexts/answers.jsx new file mode 100644 index 0000000..a9f7ae2 --- /dev/null +++ b/src/lib/callWidget/contexts/answers.jsx @@ -0,0 +1,31 @@ +import {createContext, useReducer, useState} from "react"; + +const reducer = (state, action) => { + switch (action.type) { + case "CHANGE_ACTIVE_TAB": + return state.map((answer, index) => action.newValue === index ? {...answer, active: true} : { + ...answer, + active: false + }); + case "CHANGE_LIST": + return [...action.newList] + default: + return state; + } +} +export const AnswersContext = createContext() +export const AnswersProvider = ({children}) => { + const [openCallDialog, setOpenCallDialog] = useState(false) + const [activeTab, setActiveTab] = useState(0) + const [state, dispatch] = useReducer(reducer, []); + + return {children} +} diff --git a/src/lib/callWidget/hooks/useAnswers.jsx b/src/lib/callWidget/hooks/useAnswers.jsx new file mode 100644 index 0000000..2155cd7 --- /dev/null +++ b/src/lib/callWidget/hooks/useAnswers.jsx @@ -0,0 +1,38 @@ +import {useContext} from "react"; +import {AnswersContext} from "@/lib/callWidget/contexts/answers"; + +const useAnswers = () => { + const {state, dispatch, activeTab, setActiveTab} = useContext(AnswersContext) + + const changeActiveTabAnswers = (newValue) => { + dispatch({type: 'CHANGE_ACTIVE_TAB', newValue}) + setActiveTab(newValue) + } + + const newAnswerTab = (data) => { + const newAnswer = { + id: data.id, + phone_number: data.phone_number, + active: true + } + const newList = [...state, newAnswer] + dispatch({type: 'CHANGE_LIST', newList}) + changeActiveTabAnswers(newList.length - 1) + } + + const closeAnswerTab = (id) => { + const index = state.findIndex(answer => answer.id === id) + const newIndex = index === 0 ? 0 : index - 1 + const newList = [...state] + newList.splice(index, 1); + if (newList.length !== 0) { + newList[newIndex].active = true + setActiveTab(newIndex) + } + dispatch({type: 'CHANGE_LIST', newList}) + } + + return {answersList: state, activeTab, setActiveTab, changeActiveTabAnswers, closeAnswerTab, newAnswerTab} +} + +export default useAnswers \ No newline at end of file diff --git a/src/lib/callWidget/hooks/useCallDialog.jsx b/src/lib/callWidget/hooks/useCallDialog.jsx new file mode 100644 index 0000000..30dd599 --- /dev/null +++ b/src/lib/callWidget/hooks/useCallDialog.jsx @@ -0,0 +1,10 @@ +import {useContext} from "react"; +import {AnswersContext} from "@/lib/callWidget/contexts/answers"; + +const useCallDialog = () => { + const {openCallDialog, setOpenCallDialog} = useContext(AnswersContext) + + return {openCallDialog, setOpenCallDialog} +} + +export default useCallDialog \ No newline at end of file