CFE-4 Merging from develop
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -27,7 +27,7 @@ node_modules
|
|||||||
cypress/videos
|
cypress/videos
|
||||||
cypress/screenshots
|
cypress/screenshots
|
||||||
.next
|
.next
|
||||||
.env.local
|
.env*
|
||||||
.env
|
.env
|
||||||
.idea
|
.idea
|
||||||
package-lock.json
|
package-lock.json
|
||||||
@@ -7,6 +7,7 @@ NEXT_PUBLIC_PRIMARY_MAIN = "#1b4332"
|
|||||||
NEXT_PUBLIC_SECONDARY_MAIN = "#800e13"
|
NEXT_PUBLIC_SECONDARY_MAIN = "#800e13"
|
||||||
|
|
||||||
NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir"
|
NEXT_PUBLIC_BASE_URL = "https://crm.witel.ir"
|
||||||
|
NEXT_PUBLIC_SERVER_SOCKET_URL = "wss://crmws.witel.ir"
|
||||||
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
|
NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir"
|
||||||
|
|
||||||
NODE_ENV = "development"
|
NODE_ENV = "development"
|
||||||
@@ -2,8 +2,6 @@ import '@testing-library/jest-dom'
|
|||||||
import {server} from "./mocks/server";
|
import {server} from "./mocks/server";
|
||||||
import mockRouter from 'next-router-mock'
|
import mockRouter from 'next-router-mock'
|
||||||
|
|
||||||
require('dotenv').config({path: './.env.local'});
|
|
||||||
|
|
||||||
jest.mock('next/router', () => jest.requireActual('next-router-mock'))
|
jest.mock('next/router', () => jest.requireActual('next-router-mock'))
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import {GET_PERMISSIONS_LIST, GET_USER_ROUTE} from "@/core/data/apiRoutes";
|
import {GET_PERMISSIONS_LIST, GET_USER_ROUTE,SET_USER_PASSWORD} from "@/core/data/apiRoutes";
|
||||||
import {rest} from "msw";
|
import {rest} from "msw";
|
||||||
|
|
||||||
export const handler = [
|
export const handler = [
|
||||||
rest.get(GET_USER_ROUTE, (req, res, ctx) => {
|
rest.get(GET_USER_ROUTE, (req, res, ctx) => {
|
||||||
return res(ctx.json({
|
return res(ctx.json({
|
||||||
id: 10
|
data:{
|
||||||
|
id: 10
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
|
rest.post(SET_USER_PASSWORD, (req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.status(200),
|
||||||
|
);
|
||||||
|
}),
|
||||||
rest.get(GET_PERMISSIONS_LIST, (req, res, ctx) => {
|
rest.get(GET_PERMISSIONS_LIST, (req, res, ctx) => {
|
||||||
return res(ctx.json(
|
return res(ctx.json(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,6 +46,7 @@
|
|||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
|
"socket.io-client": "^4.7.2",
|
||||||
"stylis": "^4.1.3",
|
"stylis": "^4.1.3",
|
||||||
"stylis-plugin-rtl": "^2.1.1",
|
"stylis-plugin-rtl": "^2.1.1",
|
||||||
"swr": "^2.1.5",
|
"swr": "^2.1.5",
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
"between": "میان",
|
"between": "میان",
|
||||||
"online_message": "شما به اینترنت وصل هستید",
|
"online_message": "شما به اینترنت وصل هستید",
|
||||||
"offline_message": "اتصال شما به اینترنت قطع شده است",
|
"offline_message": "اتصال شما به اینترنت قطع شده است",
|
||||||
|
"socket_is_connect_message": "شما به سوکت وصل هستید",
|
||||||
|
"socket_is_not_connect_message": "اتصال شما به سوکت قطع شده است",
|
||||||
"header": {
|
"header": {
|
||||||
"open_profile": "پروفایل",
|
"open_profile": "پروفایل",
|
||||||
"edit_profile": " پروفایل",
|
"edit_profile": " پروفایل",
|
||||||
@@ -81,7 +83,9 @@
|
|||||||
"label_current_password": "رمز عبور فعلی",
|
"label_current_password": "رمز عبور فعلی",
|
||||||
"label_new_password": "رمز عبور جدید",
|
"label_new_password": "رمز عبور جدید",
|
||||||
"label_confirm_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_length": "رمز عبور باید حداقل 8 کاراکتر باشد.",
|
||||||
"error_message_password_match": "پسورد ها یکسان هستند",
|
"error_message_password_match": "پسورد ها یکسان هستند",
|
||||||
"error_message_password_not_match": "پسورد و تکرار رمز عبور باید یکسان باشد"
|
"error_message_password_not_match": "پسورد و تکرار رمز عبور باید یکسان باشد"
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
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', () => {
|
||||||
|
it('Should see change password heading',() => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
const changePasswordElement = screen.getByRole('heading', { level: 4 });
|
||||||
|
expect(changePasswordElement).toHaveTextContent('تغییر رمز عبور');
|
||||||
|
});
|
||||||
|
it('Should see the label for current password field', () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
const currentPasswordLabel = screen.queryByLabelText('رمز عبور فعلی');
|
||||||
|
expect(currentPasswordLabel).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should see the label for new password field', () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
const newPasswordLabel = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
expect(newPasswordLabel).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should see the label for confirm password field', () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
const confirmPasswordLabel = screen.queryByLabelText('تکرار رمز عبور جدید');
|
||||||
|
expect(confirmPasswordLabel).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it('Should see the submit button with the submit label and should be disable', () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
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(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
|
||||||
|
|
||||||
|
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 and not see any error while its not empty', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
|
||||||
|
|
||||||
|
// 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 and not see any error while its not empty', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
|
||||||
|
// 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 and not see any error while its not empty', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
|
||||||
|
|
||||||
|
// 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 blur event', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
// Simulate blur event on the current password input
|
||||||
|
const currentPasswordInput = screen.queryByLabelText('رمز عبور فعلی');
|
||||||
|
|
||||||
|
// Clear the input field
|
||||||
|
fireEvent.change(currentPasswordInput, { target: { value: '' } });
|
||||||
|
|
||||||
|
fireEvent.blur(currentPasswordInput);
|
||||||
|
|
||||||
|
await waitFor(()=>{
|
||||||
|
expect(screen.queryByText("وارد کردن رمز عبور فعلی الزامیست!")).toBeInTheDocument()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should see error while new password is empty on blur event', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
// Simulate blur event on the new password input
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
|
||||||
|
// Clear the input field
|
||||||
|
fireEvent.change(newPasswordInput, { target: { value: '' } });
|
||||||
|
|
||||||
|
fireEvent.blur(newPasswordInput);
|
||||||
|
|
||||||
|
await waitFor(()=>{
|
||||||
|
expect(screen.queryByText("وارد کردن رمز عبور جدید الزامیست!")).toBeInTheDocument()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should see error while confirm new password is empty on blur event', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
// Simulate blur event on the new password input
|
||||||
|
const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
|
||||||
|
|
||||||
|
// Clear the input field
|
||||||
|
fireEvent.change(confirmPasswordInput, { target: { value: '' } });
|
||||||
|
|
||||||
|
fireEvent.blur(confirmPasswordInput);
|
||||||
|
|
||||||
|
await waitFor(()=>{
|
||||||
|
expect(screen.queryByText("وارد کردن تکرار رمز عبور جدید الزامیست!")).toBeInTheDocument()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should see error when new password is less than 8 characters', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
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.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate clicking the submit button
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText("رمز عبور باید حداقل 8 کاراکتر باشد.")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should not see error when new password are 8 or more characters', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
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(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
|
||||||
|
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.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate clicking the submit button
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText("پسورد و تکرار رمز عبور باید یکسان باشد")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should not see error when new password and confirm password match on blur event and submit click', async () => {
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const newPasswordInput = screen.queryByLabelText('رمز عبور جدید');
|
||||||
|
const confirmPasswordInput = screen.queryByLabelText('تکرار رمز عبور جدید');
|
||||||
|
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(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
const submitButton = screen.getByRole('button', { name: 'ثبت' });
|
||||||
|
|
||||||
|
// Simulate entering valid values 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' } });
|
||||||
|
|
||||||
|
// Check if the submit button is enabled
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(submitButton).not.toBeDisabled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Should request to api and resetform in success response', async () => {
|
||||||
|
// Render the component
|
||||||
|
render(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
|
||||||
|
// 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(<MockAppWithProviders><ChangePasswordForm /></MockAppWithProviders>);
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
@@ -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 (
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
>
|
||||||
|
{(props) => (
|
||||||
|
<StyledForm
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
props.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Paper elevation={0}>
|
||||||
|
<Stack spacing={3} sx={{p: 5}} component="div">
|
||||||
|
<Typography margin={2} variant="h4" textAlign="center">
|
||||||
|
{t("ChangePassword.typography_change_password")}
|
||||||
|
</Typography>
|
||||||
|
<Stack spacing={1} component="div">
|
||||||
|
<PasswordField
|
||||||
|
name="current_password"
|
||||||
|
label={t("ChangePassword.label_current_password")}
|
||||||
|
error={
|
||||||
|
props.touched.current_password &&
|
||||||
|
props.errors.current_password
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
props.touched.current_password
|
||||||
|
? props.errors.current_password
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={1} component="div">
|
||||||
|
<PasswordField
|
||||||
|
name="new_password"
|
||||||
|
label={t("ChangePassword.label_new_password")}
|
||||||
|
error={
|
||||||
|
props.touched.new_password && props.errors.new_password
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
props.touched.new_password ? props.errors.new_password : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Stack spacing={1} component="div">
|
||||||
|
<PasswordField
|
||||||
|
name="new_password_confirmation"
|
||||||
|
label={t("ChangePassword.label_confirm_password")}
|
||||||
|
error={
|
||||||
|
props.touched.new_password_confirmation &&
|
||||||
|
props.errors.new_password_confirmation
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
props.touched.new_password_confirmation
|
||||||
|
? props.errors.new_password_confirmation
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
fullWidth
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
disabled={props.isSubmitting || !(props.values.current_password && props.values.new_password && props.values.new_password_confirmation)}
|
||||||
|
>
|
||||||
|
{props.isSubmitting
|
||||||
|
? t("SubmitButton.button_while_submit")
|
||||||
|
: t("SubmitButton.button_submit")}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
</StyledForm>
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChangePasswordForm;
|
||||||
@@ -1,149 +1,18 @@
|
|||||||
import CenterLayout from "@/layouts/CenterLayout";
|
import CenterLayout from "@/layouts/CenterLayout";
|
||||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
import DashboardLayout from "@/layouts/DashboardLayout";
|
||||||
import {Button, Container, Paper, Stack, Typography,} from "@mui/material";
|
import {Container} from "@mui/material";
|
||||||
import {Formik} from "formik";
|
import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form";
|
||||||
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";
|
|
||||||
|
|
||||||
const DashboardChangePasswordComponent = () => {
|
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 (
|
return (
|
||||||
<DashboardLayouts>
|
<DashboardLayout>
|
||||||
<CenterLayout>
|
<CenterLayout>
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Formik
|
<ChangePasswordForm />
|
||||||
initialValues={initialValues}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
validationSchema={validationSchema}
|
|
||||||
>
|
|
||||||
{(props) => (
|
|
||||||
<StyledForm
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
props.handleSubmit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper elevation={0}>
|
|
||||||
<Stack spacing={3} sx={{p: 5}} component="div">
|
|
||||||
<SvgChangePassword width={300} height={200}/>
|
|
||||||
<Typography margin={2} variant="h4" textAlign="center">
|
|
||||||
{t("ChangePassword.typography_change_password")}
|
|
||||||
</Typography>
|
|
||||||
<Stack spacing={1} component="div">
|
|
||||||
<PasswordField
|
|
||||||
name="current_password"
|
|
||||||
label={t("ChangePassword.label_current_password")}
|
|
||||||
error={
|
|
||||||
props.touched.current_password &&
|
|
||||||
props.errors.current_password
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
helperText={
|
|
||||||
props.touched.current_password
|
|
||||||
? props.errors.current_password
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Stack spacing={1} component="div">
|
|
||||||
<PasswordField
|
|
||||||
name="new_password"
|
|
||||||
label={t("ChangePassword.label_new_password")}
|
|
||||||
error={
|
|
||||||
props.touched.new_password &&
|
|
||||||
props.errors.new_password
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
helperText={
|
|
||||||
props.touched.new_password
|
|
||||||
? props.errors.new_password
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Stack spacing={1} component="div">
|
|
||||||
<PasswordField
|
|
||||||
name="new_password_confirmation"
|
|
||||||
label={t("ChangePassword.label_confirm_password")}
|
|
||||||
error={
|
|
||||||
props.touched.new_password_confirmation &&
|
|
||||||
props.errors.new_password_confirmation
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
}
|
|
||||||
helperText={
|
|
||||||
props.touched.new_password_confirmation
|
|
||||||
? props.errors.new_password_confirmation
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
disabled={props.isSubmitting}
|
|
||||||
>
|
|
||||||
{props.isSubmitting
|
|
||||||
? t("SubmitButton.button_while_submit")
|
|
||||||
: t("SubmitButton.button_submit")}
|
|
||||||
</Button>
|
|
||||||
</Stack>
|
|
||||||
</Paper>
|
|
||||||
</StyledForm>
|
|
||||||
)}
|
|
||||||
</Formik>
|
|
||||||
</Container>
|
</Container>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
</DashboardLayouts>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default DashboardChangePasswordComponent;
|
export default DashboardChangePasswordComponent;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import StyledForm from "@/core/components/StyledForm";
|
import StyledForm from "@/core/components/StyledForm";
|
||||||
import CenterLayout from "@/layouts/CenterLayout";
|
import CenterLayout from "@/layouts/CenterLayout";
|
||||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
import DashboardLayout from "@/layouts/DashboardLayout";
|
||||||
import useUser from "@/lib/app/hooks/useUser";
|
import useUser from "@/lib/app/hooks/useUser";
|
||||||
import {Box, Container, Grid, Paper, Stack, TextField, Typography,} from "@mui/material";
|
import {Box, Container, Grid, Paper, Stack, TextField, Typography,} from "@mui/material";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
@@ -54,7 +54,7 @@ const DashboardEditProfile = () => {
|
|||||||
const validationSchema = Yup.object().shape({});
|
const validationSchema = Yup.object().shape({});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardLayouts>
|
<DashboardLayout>
|
||||||
<CenterLayout>
|
<CenterLayout>
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Formik
|
<Formik
|
||||||
@@ -216,7 +216,7 @@ const DashboardEditProfile = () => {
|
|||||||
</Formik>
|
</Formik>
|
||||||
</Container>
|
</Container>
|
||||||
</CenterLayout>
|
</CenterLayout>
|
||||||
</DashboardLayouts>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
import DashboardLayout from "@/layouts/DashboardLayout";
|
||||||
|
|
||||||
const DashboardFirstComponent = () => {
|
const DashboardFirstComponent = () => {
|
||||||
return <DashboardLayouts></DashboardLayouts>;
|
return <DashboardLayout></DashboardLayout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DashboardFirstComponent;
|
export default DashboardFirstComponent;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg';
|
||||||
|
import StyledFab from "@/core/components/StyledFab";
|
||||||
|
import useCallDialog from "@/lib/callWidget/hooks/useCallDialog";
|
||||||
|
|
||||||
|
const CallWidgetButton = () => {
|
||||||
|
const {setOpenCallDialog} = useCallDialog()
|
||||||
|
return (
|
||||||
|
<StyledFab color="primary" aria-label="open-dialog"
|
||||||
|
onClick={() => setOpenCallDialog(true)}>
|
||||||
|
<PermPhoneMsgIcon/>
|
||||||
|
</StyledFab>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallWidgetButton
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const CallActions = ({tab}) => {
|
||||||
|
return (
|
||||||
|
<>CallActions - {tab.id}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallActions
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
const CallHistory = ({tab}) => {
|
||||||
|
return (
|
||||||
|
<>CallHistory - {tab.id}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallHistory
|
||||||
@@ -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 (
|
||||||
|
<Grid container columns={{xs: 3}} sx={{display: !tab.active && 'none'}} role="tabpanel"
|
||||||
|
id={`answer-tabpanel-${tab.id}`} aria-labelledby={`answer-tab-${tab.id}`}>
|
||||||
|
<Grid item xs={2}>
|
||||||
|
<CallActions tab={tab}/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={1}>
|
||||||
|
<CallHistory tab={tab}/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallTabPanel
|
||||||
@@ -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 (
|
||||||
|
<Stack sx={{width: '100%'}} direction={'row'} alignItems={'center'} justifyContent={'space-between'}>
|
||||||
|
<Typography>{tab.phone_number} - {tab.id}</Typography>
|
||||||
|
<Zoom in={tab.active}>
|
||||||
|
<IconButton onClick={() => handleCloseClick(tab.id)}>
|
||||||
|
<CloseIcon/>
|
||||||
|
</IconButton>
|
||||||
|
</Zoom>
|
||||||
|
</Stack>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallTabLabel
|
||||||
@@ -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 (
|
||||||
|
<Box sx={{borderBottom: 1, borderColor: 'divider'}}>
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant={'fullWidth'}
|
||||||
|
>
|
||||||
|
{answersList.map((answer) => (
|
||||||
|
<Tab component={'div'} sx={{py: 0.5, pr: 0,}} label={<CallTabLabel tab={answer}/>} key={answer.id}/>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallTabsList
|
||||||
@@ -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 (
|
||||||
|
<>
|
||||||
|
<CallTabsList/>
|
||||||
|
{answersList.map((answer) => (
|
||||||
|
<CallTabPanel key={answer.id} tab={answer}/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default CallTabs
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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 = () => {
|
||||||
|
const {openCallDialog, setOpenCallDialog} = useCallDialog()
|
||||||
|
const {answersList, newAnswerTab} = useAnswers()
|
||||||
|
const socket = useSocket()
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<Dialog
|
||||||
|
fullScreen
|
||||||
|
open={openCallDialog}
|
||||||
|
TransitionComponent={DialogTransition}
|
||||||
|
>
|
||||||
|
<CallTabs/>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallWidgetDialog
|
||||||
28
src/components/layouts/Dashboard/CallWidget/index.jsx
Normal file
28
src/components/layouts/Dashboard/CallWidget/index.jsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import CallWidgetButton from "@/components/layouts/Dashboard/CallWidget/CallWidgetButton";
|
||||||
|
import CallWidgetDialog from "@/components/layouts/Dashboard/CallWidget/CallWidgetDialog";
|
||||||
|
import {AnswersProvider} from "@/lib/callWidget/contexts/answers";
|
||||||
|
import useSocket from "@/lib/app/hooks/useSocket";
|
||||||
|
import {useEffect} from "react";
|
||||||
|
|
||||||
|
const CallWidget = () => {
|
||||||
|
const socket = useSocket()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (socket.connected) return
|
||||||
|
|
||||||
|
socket.connect()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect()
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnswersProvider>
|
||||||
|
<CallWidgetButton/>
|
||||||
|
<CallWidgetDialog/>
|
||||||
|
</AnswersProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CallWidget
|
||||||
@@ -14,7 +14,7 @@ const SidebarDrawer = ({handleDrawerToggle}) => {
|
|||||||
{t("app_short_name")}
|
{t("app_short_name")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
{user.name} | {user.position}
|
{user.full_name} | {user.position}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
@@ -18,7 +18,6 @@ function reducer(state, action) {
|
|||||||
case "SELECTED":
|
case "SELECTED":
|
||||||
return state.map((itemsArr) =>
|
return state.map((itemsArr) =>
|
||||||
itemsArr.map((item) => {
|
itemsArr.map((item) => {
|
||||||
console.log(item)
|
|
||||||
if (item.type === "page") {
|
if (item.type === "page") {
|
||||||
if (action.route === item.route)
|
if (action.route === item.route)
|
||||||
return {...item, selected: true}
|
return {...item, selected: true}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import {useState} from "react";
|
import Header from "src/components/layouts/Dashboard/Header";
|
||||||
import FullPageLayout from "../FullPageLayout";
|
import Sidebar from "src/components/layouts/Dashboard/Sidebar";
|
||||||
import Header from "./header";
|
import FullPageLayout from "@/layouts/FullPageLayout";
|
||||||
import Sidebar from "./sidebar";
|
|
||||||
import {Toolbar} from "@mui/material";
|
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 drawerWidth = 240;
|
||||||
|
|
||||||
const DashboardLayouts = (props) => {
|
const Dashboard = (props) => {
|
||||||
const {window} = props;
|
const {window} = props;
|
||||||
const [mobileOpen, setMobileOpen] = useState(false);
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
const container =
|
const container =
|
||||||
@@ -39,7 +39,7 @@ const DashboardLayouts = (props) => {
|
|||||||
</FullPageLayout>
|
</FullPageLayout>
|
||||||
</FullPageLayout>
|
</FullPageLayout>
|
||||||
</FullPageLayout>
|
</FullPageLayout>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default DashboardLayouts;
|
export default Dashboard
|
||||||
@@ -10,7 +10,7 @@ import useRequest from "@/lib/app/hooks/useRequest";
|
|||||||
import useUser from "@/lib/app/hooks/useUser";
|
import useUser from "@/lib/app/hooks/useUser";
|
||||||
import {useTranslations} from "next-intl";
|
import {useTranslations} from "next-intl";
|
||||||
|
|
||||||
const LoginExpertComponent = () =>{
|
const LoginExpertComponent = () => {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const requestServer = useRequest()
|
const requestServer = useRequest()
|
||||||
const {setToken} = useUser(); // pass token to set token
|
const {setToken} = useUser(); // pass token to set token
|
||||||
@@ -24,7 +24,7 @@ const LoginExpertComponent = () =>{
|
|||||||
notification: {show: false}
|
notification: {show: false}
|
||||||
}
|
}
|
||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
setToken(response.data.token)
|
setToken(response.data.data.token)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
props.setSubmitting(false)
|
props.setSubmitting(false)
|
||||||
})
|
})
|
||||||
@@ -38,7 +38,7 @@ const LoginExpertComponent = () =>{
|
|||||||
password: Yup.string().required(t("LoginPage.password_error_message_required")),
|
password: Yup.string().required(t("LoginPage.password_error_message_required")),
|
||||||
});
|
});
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Paper elevation={0}>
|
<Paper elevation={0}>
|
||||||
<Formik
|
<Formik
|
||||||
|
|||||||
6
src/core/components/DialogTransition.jsx
Normal file
6
src/core/components/DialogTransition.jsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import {forwardRef} from "react";
|
||||||
|
import {Slide} from "@mui/material";
|
||||||
|
|
||||||
|
export const DialogTransition = forwardRef(function DialogTransition(props, ref) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props}/>;
|
||||||
|
});
|
||||||
@@ -6,13 +6,13 @@ import WifiOffIcon from '@mui/icons-material/WifiOff';
|
|||||||
import {useTranslations} from "next-intl";
|
import {useTranslations} from "next-intl";
|
||||||
|
|
||||||
const NetworkComponent = () => {
|
const NetworkComponent = () => {
|
||||||
const toastId = useRef(null);
|
const networkToastId = useRef(null);
|
||||||
const network = useNetwork()
|
const network = useNetwork()
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (network.online) {
|
if (network.online) {
|
||||||
toast.update(toastId.current, {
|
toast.update(networkToastId.current, {
|
||||||
type: toast.TYPE.SUCCESS,
|
type: toast.TYPE.SUCCESS,
|
||||||
render: t('online_message'),
|
render: t('online_message'),
|
||||||
autoClose: 2000,
|
autoClose: 2000,
|
||||||
@@ -22,8 +22,7 @@ const NetworkComponent = () => {
|
|||||||
});
|
});
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
toast.dismiss()
|
networkToastId.current = toast.warn(t('offline_message'), {
|
||||||
toastId.current = toast.warn(t('offline_message'), {
|
|
||||||
autoClose: false, closeButton: false, closeOnClick: false, icon: <WifiOffIcon/>
|
autoClose: false, closeButton: false, closeOnClick: false, icon: <WifiOffIcon/>
|
||||||
})
|
})
|
||||||
}, [network.online]);
|
}, [network.online]);
|
||||||
|
|||||||
10
src/core/components/StyledFab.jsx
Normal file
10
src/core/components/StyledFab.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {Fab, styled} from "@mui/material";
|
||||||
|
|
||||||
|
const StyledFab = styled(Fab)`
|
||||||
|
position: absolute;
|
||||||
|
bottom: 16px;
|
||||||
|
/* @noflip */
|
||||||
|
left: 16px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledFab;
|
||||||
@@ -36,4 +36,4 @@ const ErrorNotification = (t, status, message) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ErrorNotification;
|
export default ErrorNotification;
|
||||||
@@ -9,4 +9,4 @@ const PendingNotification = (t) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PendingNotification;
|
export default PendingNotification;
|
||||||
@@ -39,4 +39,4 @@ const SuccessNotification = (t, status) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SuccessNotification;
|
export default SuccessNotification;
|
||||||
@@ -35,4 +35,4 @@ const UploadFileNotification = (t) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UploadFileNotification;
|
export default UploadFileNotification;
|
||||||
@@ -37,4 +37,4 @@ const WarningNotification = (t, status) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WarningNotification;
|
export default WarningNotification;
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
|
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
|
||||||
|
|
||||||
//login
|
//login
|
||||||
export const GET_USER_TOKEN = BASE_URL + "/dashboard/login";
|
export const GET_USER_TOKEN = BASE_URL + "/api/auth/login";
|
||||||
//end login
|
//end login
|
||||||
|
|
||||||
//change password
|
//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
|
//end change password
|
||||||
|
|
||||||
//user data
|
//user data
|
||||||
export const GET_USER_ROUTE = BASE_URL + "/dashboard/profile/info";
|
export const GET_USER_ROUTE = BASE_URL + "/api/profile/info";
|
||||||
//user data
|
//user data
|
||||||
|
|
||||||
// role management
|
// role management
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const isServerError = status => status >= 500 && status <= 599;
|
|||||||
const isClientError = status => status >= 400 && status <= 499;
|
const isClientError = status => status >= 400 && status <= 499;
|
||||||
|
|
||||||
const errorLogic = (response, t, notification) => {
|
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) => {
|
const errorValidation = (response, t, notification) => {
|
||||||
if (notification) {
|
if (notification) {
|
||||||
|
|||||||
14
src/layouts/DashboardLayout.jsx
Normal file
14
src/layouts/DashboardLayout.jsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import CallWidget from "src/components/layouts/Dashboard/CallWidget";
|
||||||
|
import Dashboard from "src/components/layouts/Dashboard";
|
||||||
|
|
||||||
|
const DashboardLayout = (props) => {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dashboard {...props}/>
|
||||||
|
<CallWidget/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DashboardLayout;
|
||||||
@@ -22,26 +22,23 @@ const MuiLayout = ({children, isBot}) => {
|
|||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<GlobalStyles
|
<GlobalStyles
|
||||||
styles={{
|
styles={{
|
||||||
"*:not(.MuiTableContainer-root)::-webkit-scrollbar": {
|
|
||||||
display: "none",
|
|
||||||
},
|
|
||||||
"*::-webkit-scrollbar": {
|
"*::-webkit-scrollbar": {
|
||||||
height: "8px",
|
height: "8px",
|
||||||
},
|
},
|
||||||
|
"*:not(.MuiTableContainer-root)::-webkit-scrollbar": {
|
||||||
|
display: "none",
|
||||||
|
},
|
||||||
"*::-webkit-scrollbar-thumb": {
|
"*::-webkit-scrollbar-thumb": {
|
||||||
background: `${theme.palette.primary.light}80`,
|
background: `${theme.palette.primary.light}80`,
|
||||||
borderRadius: "3px",
|
borderRadius: "3px",
|
||||||
},
|
},
|
||||||
"*:not(.MuiTableContainer-root)": {
|
|
||||||
scrollbarWidth: "thin",
|
|
||||||
scrollbarColor: "transparent transparent",
|
|
||||||
},
|
|
||||||
|
|
||||||
"*": {
|
"*": {
|
||||||
scrollbarWidth: "thin",
|
scrollbarWidth: "thin",
|
||||||
scrollbarColor: `${theme.palette.primary.light}80 transparent`,
|
scrollbarColor: `${theme.palette.primary.light}80 transparent`,
|
||||||
},
|
},
|
||||||
|
"*:not(.MuiTableContainer-root)": {
|
||||||
|
scrollbarWidth: "none",
|
||||||
|
},
|
||||||
"*::-moz-scrollbar-thumb": {
|
"*::-moz-scrollbar-thumb": {
|
||||||
backgroundColor: `${theme.palette.primary.light}80`,
|
backgroundColor: `${theme.palette.primary.light}80`,
|
||||||
},
|
},
|
||||||
|
|||||||
67
src/lib/app/contexts/socket.jsx
Normal file
67
src/lib/app/contexts/socket.jsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
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: {
|
||||||
|
token: ""
|
||||||
|
}
|
||||||
|
}), [])
|
||||||
|
|
||||||
|
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: <PowerIcon/>
|
||||||
|
});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socketToastId.current = toast.warn(t('socket_is_not_connect_message'), {
|
||||||
|
draggable: false,
|
||||||
|
autoClose: false, closeButton: false, closeOnClick: false, icon: <PowerOffIcon/>
|
||||||
|
})
|
||||||
|
|
||||||
|
}, [connectionError]);
|
||||||
|
|
||||||
|
return <SocketContext.Provider value={{socket}}>{children}</SocketContext.Provider>
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ export const UserProvider = ({children}) => {
|
|||||||
headers: {authorization: `Bearer ${state.token}`},
|
headers: {authorization: `Bearer ${state.token}`},
|
||||||
})
|
})
|
||||||
.then(({data}) => {
|
.then(({data}) => {
|
||||||
if (typeof callback === "function") callback(data);
|
if (typeof callback === "function") callback(data.data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
|
|||||||
@@ -1,29 +1,5 @@
|
|||||||
import {useEffect, useState} from "react";
|
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() {
|
function useNetwork() {
|
||||||
const [state, setState] = useState(() => {
|
const [state, setState] = useState(() => {
|
||||||
return {
|
return {
|
||||||
@@ -51,7 +27,7 @@ function useNetwork() {
|
|||||||
window.removeEventListener("offline", handleOffline);
|
window.removeEventListener("offline", handleOffline);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/lib/app/hooks/useSocket.jsx
Normal file
10
src/lib/app/hooks/useSocket.jsx
Normal file
@@ -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
|
||||||
31
src/lib/callWidget/contexts/answers.jsx
Normal file
31
src/lib/callWidget/contexts/answers.jsx
Normal file
@@ -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 <AnswersContext.Provider
|
||||||
|
value={{
|
||||||
|
openCallDialog,
|
||||||
|
setOpenCallDialog,
|
||||||
|
state,
|
||||||
|
dispatch,
|
||||||
|
activeTab,
|
||||||
|
setActiveTab
|
||||||
|
}}>{children}</AnswersContext.Provider>
|
||||||
|
}
|
||||||
38
src/lib/callWidget/hooks/useAnswers.jsx
Normal file
38
src/lib/callWidget/hooks/useAnswers.jsx
Normal file
@@ -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
|
||||||
10
src/lib/callWidget/hooks/useCallDialog.jsx
Normal file
10
src/lib/callWidget/hooks/useCallDialog.jsx
Normal file
@@ -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
|
||||||
@@ -7,6 +7,7 @@ import {UserProvider} from "@/lib/app/contexts/user";
|
|||||||
import "moment/locale/fa";
|
import "moment/locale/fa";
|
||||||
import {NextIntlProvider} from "next-intl";
|
import {NextIntlProvider} from "next-intl";
|
||||||
import TitlePage from "@/core/components/TitlePage";
|
import TitlePage from "@/core/components/TitlePage";
|
||||||
|
import {SocketProvider} from "@/lib/app/contexts/socket";
|
||||||
|
|
||||||
const App = ({Component, pageProps}) => {
|
const App = ({Component, pageProps}) => {
|
||||||
|
|
||||||
@@ -18,7 +19,9 @@ const App = ({Component, pageProps}) => {
|
|||||||
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
|
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
|
||||||
<LoadingProvider>
|
<LoadingProvider>
|
||||||
<AppLayout isBot={pageProps.isBot}>
|
<AppLayout isBot={pageProps.isBot}>
|
||||||
<Component {...pageProps} />
|
<SocketProvider>
|
||||||
|
<Component {...pageProps} />
|
||||||
|
</SocketProvider>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</LoadingProvider>
|
</LoadingProvider>
|
||||||
</MuiLayout>
|
</MuiLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user