diff --git a/.gitignore b/.gitignore index 3dfb407..662e667 100644 --- a/.gitignore +++ b/.gitignore @@ -27,7 +27,7 @@ node_modules cypress/videos cypress/screenshots .next -.env.local +.env* .env .idea package-lock.json \ No newline at end of file diff --git a/example.env.local b/example.env.local index 0be16f0..922eec0 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://crmws.witel.ir" NEXT_PUBLIC_POWERED_BY_URL = "https://witel.ir" NODE_ENV = "development" \ No newline at end of file diff --git a/jest.setup.js b/jest.setup.js index 4298493..fadd766 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,7 +1,6 @@ import '@testing-library/jest-dom' import {server} from "./mocks/server"; - -require('dotenv').config({path: './.env.local'}); +import mockRouter from 'next-router-mock' jest.mock('next/router', () => jest.requireActual('next-router-mock')) @@ -10,6 +9,8 @@ beforeAll(() => { }) beforeEach(() => { localStorage.clear(); + mockRouter.query = {} + }) afterEach(() => { 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/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/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/expert-management/Form/CreateForm/CreateContent/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/__tests__/index.test.js deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx index e69de29..7ce4fcf 100644 --- a/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx +++ b/src/components/dashboard/expert-management/Form/CreateForm/CreateContent/index.jsx @@ -0,0 +1,9 @@ +const CreateContent = () => { + return ( + <> + + + ) +} + +export default CreateContent \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js b/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js index 20feced..d036cee 100644 --- a/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js +++ b/src/components/dashboard/expert-management/Form/CreateForm/__tests__/index.test.js @@ -1,18 +1,31 @@ -import {render, screen} from "@testing-library/react"; -import MockAppWithProviders from "../../../../../../mocks/AppWithProvider"; +import {fireEvent, render, screen} from "@testing-library/react"; +import React, {useState} from 'react'; +import MockAppWithProviders from "../../../../../../../mocks/AppWithProvider"; import CreateForm from "../../CreateForm"; +jest.spyOn(React, 'useState'); + describe("CreateForm Component From Expert Management", () => { describe("Rendering", () => { it("Create Button Rendered", () => { render(); - const versionControler = screen.queryByTitle("ثبت کارشناس"); - expect(versionControler).toBeInTheDocument(); + const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"}); + expect(CreateButton).toBeInTheDocument(); }); it("Dialog Header Rendered", () => { render(); - const versionControler = screen.queryByText("ثبت کارشناس"); - expect(versionControler).toBeInTheDocument(); + const CreateDialogHeader = screen.queryByText("افزودن کارشناس"); + expect(CreateDialogHeader).toBeInTheDocument(); + }); + }); + describe("Behavioral", () => { + it("by Clicking Create Button Dialog State Should Change (true/false)", () => { + const setOpenCreateDialogMock = jest.fn(); + useState.mockReturnValue([false, setOpenCreateDialogMock]); + render(); + const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"}); + fireEvent.click(CreateButton); + expect(setOpenCreateDialogMock).toHaveBeenCalledWith(true); }); }); }); \ No newline at end of file diff --git a/src/components/dashboard/expert-management/Form/CreateForm/index.jsx b/src/components/dashboard/expert-management/Form/CreateForm/index.jsx index 09c6a5b..4b732aa 100644 --- a/src/components/dashboard/expert-management/Form/CreateForm/index.jsx +++ b/src/components/dashboard/expert-management/Form/CreateForm/index.jsx @@ -1,4 +1,13 @@ -const Create = () => { +import {Button, Dialog, DialogTitle, Stack, Tooltip} from "@mui/material"; +import {useTranslations} from "next-intl"; +import DataSaverOnIcon from "@mui/icons-material/DataSaverOn"; +import {useState} from "react"; +import CreateContent from "./CreateContent"; + +const Create = ({mutate, fetchUrl}) => { + const t = useTranslations(); + const [openCreateDialog, setOpenCreateDialog] = useState(false); + return ( <> 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/components/login-expert/LoginExpertComponent.jsx b/src/components/login-expert/LoginExpertComponent.jsx new file mode 100644 index 0000000..d2a2d13 --- /dev/null +++ b/src/components/login-expert/LoginExpertComponent.jsx @@ -0,0 +1,123 @@ +import {Box, Button, Container, Paper, Stack, TextField, Typography} from "@mui/material"; +import {Field, Formik} from "formik"; +import SvgLogin from "@/core/components/svgs/SvgLogin"; +import StyledForm from "@/core/components/StyledForm"; +import PasswordField from "@/core/components/PasswordField"; +import LoginIcon from "@mui/icons-material/Login"; +import {GET_USER_TOKEN} from "@/core/data/apiRoutes"; +import * as Yup from "yup"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useUser from "@/lib/app/hooks/useUser"; +import {useTranslations} from "next-intl"; + +const LoginExpertComponent = () => { + const t = useTranslations(); + const requestServer = useRequest() + const {setToken} = useUser(); // pass token to set token + const handleSubmit = (values, props) => { + requestServer(GET_USER_TOKEN, 'post', { + data: { + username: values.username, + password: values.password, + }, + success: { + notification: {show: false} + } + }).then((response) => { + setToken(response.data.data.token) + }).catch(() => { + props.setSubmitting(false) + }) + }; + const initialValues = { + username: "", + password: "", + }; + const validationSchema = Yup.object().shape({ + username: Yup.string().required(t("LoginPage.username_error_message_required")), + password: Yup.string().required(t("LoginPage.password_error_message_required")), + }); + + return ( + + + + {(props) => ( + + + + + + {t("login_expert")} + + + + + + + + + + + + )} + + + + ) +} +export default LoginExpertComponent \ No newline at end of file diff --git a/src/components/login-expert/LoginLinkRouting.jsx b/src/components/login-expert/LoginLinkRouting.jsx new file mode 100644 index 0000000..1930b3c --- /dev/null +++ b/src/components/login-expert/LoginLinkRouting.jsx @@ -0,0 +1,28 @@ +import LinkRouting from "@/core/components/LinkRouting"; +import {Stack} from "@mui/material"; +import {useRouter} from "next/router"; +import {useTranslations} from "next-intl"; + +const LoginLinkRouting = () => { + const t = useTranslations(); + const router = useRouter() + const backUrlDecodedPath = router.query?.back_url + + return ( + + + {t("LoginPage.link_routing_back_to")}{" "} + {backUrlDecodedPath + ? t("LoginPage.link_routing_previuos_page") + : t("LoginPage.link_routing_main_page")} + + + ) +} +export default LoginLinkRouting \ No newline at end of file diff --git a/src/components/login-expert/__test__/LoginExpertComponent.test.js b/src/components/login-expert/__test__/LoginExpertComponent.test.js new file mode 100644 index 0000000..d76e49d --- /dev/null +++ b/src/components/login-expert/__test__/LoginExpertComponent.test.js @@ -0,0 +1,153 @@ +import {act, findByText, fireEvent, getByText, render, screen, waitFor} from '@testing-library/react'; +import MockAppWithProviders from '../../../../mocks/AppWithProvider'; +import mockRouter from 'next-router-mock' +import LoginExpertComponent from "@/components/login-expert/LoginExpertComponent"; +import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting"; + +describe('Login expert component from login page', () => { + + describe('Rendering', () => { + it('LoginExpertComponent should see login expert text on the page',() => { + render( + + + + ); + const loginExpertElement = screen.getByRole('heading', { level: 4 }); + expect(loginExpertElement).toHaveTextContent('ورود کارشناس'); + }); + + it('LoginExpertComponent from LoginComponent Button should render login text', () => { + render( + + + + ); + const button = screen.getByText("ورود"); + // Assertions for button props + expect(button).toHaveAttribute('type', 'submit'); + expect(button).toHaveTextContent('ورود'); + }); + + it("Show error when username is empty", async ()=>{ + render( + + + + ); + + const usernameInput = screen.getByLabelText("نام کاربری") + + expect(screen.queryByText("وارد کردن نام کاربری الزامیست")).not.toBeInTheDocument() + + fireEvent.blur(usernameInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن نام کاربری الزامیست")).toBeInTheDocument() + }) + + fireEvent.change(usernameInput, {target : {value : "testuser"}}) + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن نام کاربری الزامیست")).not.toBeInTheDocument() + }) + }) + it("Show error when password is empty", async ()=>{ + render( + + + + ); + + const passwordInput = screen.getByLabelText("رمز عبور") + + expect(screen.queryByText("وارد کردن رمز عبور الزامیست")).not.toBeInTheDocument() + + fireEvent.blur(passwordInput); + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور الزامیست")).toBeInTheDocument() + }) + + fireEvent.change(passwordInput, {target : {value : "testuser"}}) + + await waitFor(()=>{ + expect(screen.queryByText("وارد کردن رمز عبور الزامیست")).not.toBeInTheDocument() + }) + }) + }); + + describe("Behavior", ()=>{ + it('Should fill the username field', async () => { + render( + + + + ); + // Get username and password input elements by their name attributes + const usernameInput = screen.getByLabelText('نام کاربری'); + + fireEvent.change(usernameInput, {target: {value: 'testuser'}}); + await act(async ()=>{ + // Simulate user input + expect(usernameInput).toHaveValue('testuser'); + }) + }); + + it('Should fill the password fields', async () => { + render( + + + + ); + // Get username and password input elements by their name attributes + const passwordInput = screen.getByLabelText('رمز عبور'); + fireEvent.change(passwordInput, {target: {value: 'password123'}}); + + await act(async ()=>{ + // Simulate user input + expect(passwordInput).toHaveValue('password123'); + }) + }); + + // this test is for when back url does not exist to run this test you should comment if in mock func + it('Should get main page when back url dos not exist', async () => { + render( + + + + ) + const linkElement = await screen.findByTestId("link_routing") + expect(linkElement).toHaveTextContent("بازگشت به صفحه اصلی") + }); + + }) + describe("validation", ()=>{ + it("Disabled submit button until fields completed", async ()=> { + render( + + + + ) + const usernameInput = screen.getByLabelText('نام کاربری'); + const passwordInput = screen.getByLabelText('رمز عبور'); + const submitButton = screen.getByText('ورود'); + + expect(submitButton).toBeDisabled(); + + fireEvent.change(usernameInput, {target: {value: 'testuser'}}); + + await waitFor(async () => { + // Now, the submit button should be enabled because both fields are completed + expect(submitButton).toBeDisabled(); + }); + + fireEvent.change(passwordInput, {target: {value: 'password123'}}); + + await waitFor(async () => { + // Now, the submit button should be enabled because both fields are completed + expect(submitButton).toBeEnabled(); + }); + }) + }) +}); \ No newline at end of file diff --git a/src/components/login-expert/__test__/LoginLinkRouting.test.js b/src/components/login-expert/__test__/LoginLinkRouting.test.js new file mode 100644 index 0000000..a63d314 --- /dev/null +++ b/src/components/login-expert/__test__/LoginLinkRouting.test.js @@ -0,0 +1,18 @@ +import {render, screen} from "@testing-library/react"; +import MockAppWithProviders from "../../../../mocks/AppWithProvider"; +import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting"; + +describe("Login expert component from login page",()=>{ + + describe('Rendering', () => { + it('LoginLinkRouting from LoginComponent Should get main page when back url dos not exist', async () => { + render( + + + + ) + const linkElement = await screen.findByTestId("link_routing") + expect(linkElement).toHaveTextContent("بازگشت به صفحه اصلی") + }); + }) +}) diff --git a/src/components/login-expert/index.jsx b/src/components/login-expert/index.jsx index 45d897b..dc36d9a 100644 --- a/src/components/login-expert/index.jsx +++ b/src/components/login-expert/index.jsx @@ -1,151 +1,16 @@ -import LinkRouting from "@/core/components/LinkRouting"; -import PasswordField from "@/core/components/PasswordField"; -import StyledForm from "@/core/components/StyledForm"; -import {GET_USER_TOKEN} from "@/core/data/apiRoutes"; import CenterLayout from "@/layouts/CenterLayout"; import FullPageLayout from "@/layouts/FullPageLayout"; -import useUser from "@/lib/app/hooks/useUser"; -import LoginIcon from "@mui/icons-material/Login"; -import {Box, Button, Container, Paper, Stack, TextField, Typography,} from "@mui/material"; -import {Field, Formik} from "formik"; -import {useTranslations} from "next-intl"; -import {useSearchParams} from "next/navigation"; -import * as Yup from "yup"; -import useDirection from "@/lib/app/hooks/useDirection"; -import SvgLogin from "@/core/components/svgs/SvgLogin"; -import useRequest from "@/lib/app/hooks/useRequest"; +import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting"; +import LoginExpertComponent from "@/components/login-expert/LoginExpertComponent"; const LoginComponent = () => { - const t = useTranslations(); - const {directionApp} = useDirection(); // should delete because we don't have direction anymore - const {setToken} = useUser(); // pass token to set token - const requestServer = useRequest() - - // getting url query - const searchParams = useSearchParams(); - const backUrlDecodedPath = searchParams.get("back_url"); - - //formik properties - const handleSubmit = (values, props) => { - requestServer(GET_USER_TOKEN, 'post', { - data: { - username: values.username, - password: values.password, - }, - success: { - notification: {show: false} - } - }).then((response) => { - setToken(response.data.token) - }).catch(() => { - props.setSubmitting(false) - }) - }; - const initialValues = { - username: "", - password: "", - }; - const validationSchema = Yup.object().shape({ - username: Yup.string().required(t("LoginPage.username_error_message_required")), - password: Yup.string().required(t("LoginPage.password_error_message_required")), - }); return ( - - - - {(props) => ( - - - - - - {t("login_expert")} - - - - - - - - - - - - )} - - - + - - - {t("LoginPage.link_routing_back_to")}{"Witel"} - {backUrlDecodedPath - ? t("LoginPage.link_routing_previuos_page") - : t("LoginPage.link_routing_main_page")} - - + ); }; 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/PasswordField/__test__/index.test.js b/src/core/components/PasswordField/__test__/index.test.js new file mode 100644 index 0000000..479f91f --- /dev/null +++ b/src/core/components/PasswordField/__test__/index.test.js @@ -0,0 +1,31 @@ +import {fireEvent, getByRole, render, screen} from "@testing-library/react"; +import PasswordField from "@/core/components/PasswordField"; +import {Formik} from "formik"; + +describe('Password Field component in core folder', () => { + describe('Rendering', () => { + it('Show password when visibility icon clicked',() => { + render ( + {}}> + {(props)=>( + + )} + + ) + + const iconElement = screen.getByLabelText("رمز عبور") + const iconButton = screen.getByRole("button") + + + expect(iconElement).toHaveAttribute('type', 'password'); + + fireEvent.click(iconButton); + + expect(iconElement).toHaveAttribute("type", "text"); + + fireEvent.click(iconButton); + + expect(iconElement).toHaveAttribute('type', 'password'); + }) + }) +}) \ No newline at end of file diff --git a/src/core/components/PasswordField.jsx b/src/core/components/PasswordField/index.jsx similarity index 100% rename from src/core/components/PasswordField.jsx rename to src/core/components/PasswordField/index.jsx 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/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/data/apiRoutes.js b/src/core/data/apiRoutes.js index e9351ad..543e9a4 100644 --- a/src/core/data/apiRoutes.js +++ b/src/core/data/apiRoutes.js @@ -1,7 +1,7 @@ const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; //login -export const GET_USER_TOKEN = BASE_URL + "/dashboard/login"; +export const GET_USER_TOKEN = BASE_URL + "/api/auth/login"; //end login //change password @@ -9,7 +9,7 @@ export const SET_USER_PASSWORD = BASE_URL + "/dashboard/profile/change_password" //end change password //user data -export const GET_USER_ROUTE = BASE_URL + "/dashboard/profile/info"; +export const GET_USER_ROUTE = BASE_URL + "/api/profile/info"; //user data //expert management 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) { 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; diff --git a/src/lib/app/contexts/socket.jsx b/src/lib/app/contexts/socket.jsx new file mode 100644 index 0000000..7da4f43 --- /dev/null +++ b/src/lib/app/contexts/socket.jsx @@ -0,0 +1,15 @@ +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/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]); diff --git a/src/lib/app/hooks/useSocket.jsx b/src/lib/app/hooks/useSocket.jsx new file mode 100644 index 0000000..3b9855d --- /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 ? : ''} + + + + + + + + + + ) };