write some behavioral test and rendered test for create form component
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -27,7 +27,7 @@ node_modules
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
.next
|
||||
.env.local
|
||||
.env*
|
||||
.env
|
||||
.idea
|
||||
package-lock.json
|
||||
@@ -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"
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}))
|
||||
})]
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const CreateContent = () => {
|
||||
return (
|
||||
<>
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateContent
|
||||
@@ -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(<MockAppWithProviders><CreateForm/></MockAppWithProviders>);
|
||||
const versionControler = screen.queryByTitle("ثبت کارشناس");
|
||||
expect(versionControler).toBeInTheDocument();
|
||||
const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"});
|
||||
expect(CreateButton).toBeInTheDocument();
|
||||
});
|
||||
it("Dialog Header Rendered", () => {
|
||||
render(<MockAppWithProviders><CreateForm/></MockAppWithProviders>);
|
||||
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(<MockAppWithProviders><CreateForm/></MockAppWithProviders>);
|
||||
const CreateButton = screen.getByRole('button', {name: "افزودن کارشناس"});
|
||||
fireEvent.click(CreateButton);
|
||||
expect(setOpenCreateDialogMock).toHaveBeenCalledWith(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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 (
|
||||
<>
|
||||
<Stack direction={"row"} spacing={2}>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
||||
import DashboardLayouts from "@/layouts/DashboardLayout";
|
||||
|
||||
const DashboardFirstComponent = () => {
|
||||
return <DashboardLayouts></DashboardLayouts>;
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import PermPhoneMsgIcon from '@mui/icons-material/PermPhoneMsg';
|
||||
import StyledFab from "@/core/components/StyledFab";
|
||||
|
||||
const CallWidgetButton = ({setOpen}) => {
|
||||
return (
|
||||
<StyledFab color="primary" aria-label="open-dialog"
|
||||
onClick={() => setOpen(true)}>
|
||||
<PermPhoneMsgIcon/>
|
||||
</StyledFab>
|
||||
)
|
||||
}
|
||||
|
||||
export default CallWidgetButton
|
||||
@@ -0,0 +1,24 @@
|
||||
import {Dialog} from "@mui/material";
|
||||
import {DialogTransition} from "@/core/components/DialogTransition";
|
||||
|
||||
const CallWidgetDialog = ({open, setOpen}) => {
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
TransitionComponent={DialogTransition}
|
||||
>
|
||||
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CallWidgetDialog
|
||||
15
src/components/layouts/Dashboard/CallWidget/index.jsx
Normal file
15
src/components/layouts/Dashboard/CallWidget/index.jsx
Normal file
@@ -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 (
|
||||
<>
|
||||
<CallWidgetButton setOpen={setOpen}/>
|
||||
<CallWidgetDialog open={open} setOpen={setOpen}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CallWidget
|
||||
@@ -14,7 +14,7 @@ const SidebarDrawer = ({handleDrawerToggle}) => {
|
||||
{t("app_short_name")}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
{user.name} | {user.position}
|
||||
{user.full_name} | {user.position}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
@@ -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}
|
||||
@@ -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) => {
|
||||
</FullPageLayout>
|
||||
</FullPageLayout>
|
||||
</FullPageLayout>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardLayouts;
|
||||
export default Dashboard
|
||||
123
src/components/login-expert/LoginExpertComponent.jsx
Normal file
123
src/components/login-expert/LoginExpertComponent.jsx
Normal file
@@ -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 (
|
||||
<Container maxWidth="sm">
|
||||
<Paper elevation={0}>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(props) => (
|
||||
<Stack spacing={2} sx={{p: 2}}>
|
||||
<Stack
|
||||
sx={{width: "100%"}}
|
||||
alignItems='center'
|
||||
>
|
||||
<SvgLogin width={300} height={200}/>
|
||||
</Stack>
|
||||
<Typography margin={2} variant="h4" textAlign="center">
|
||||
{t("login_expert")}
|
||||
</Typography>
|
||||
<StyledForm sx={{width: "100%"}}>
|
||||
<Stack spacing={3} sx={{p: 2}}>
|
||||
<Field
|
||||
as={TextField}
|
||||
name="username"
|
||||
variant="outlined"
|
||||
label={t("LoginPage.text_field_user_name")}
|
||||
placeholder={t(
|
||||
"LoginPage.text_field_enter_your_username"
|
||||
)}
|
||||
type={"text"}
|
||||
error={
|
||||
props.touched.username && props.errors.username
|
||||
? true
|
||||
: false
|
||||
}
|
||||
fullWidth
|
||||
helperText={
|
||||
props.touched.username ? props.errors.username : null
|
||||
}
|
||||
/>
|
||||
<PasswordField
|
||||
name="password"
|
||||
label={t("LoginPage.text_field_password")}
|
||||
error={
|
||||
props.touched.password && props.errors.password
|
||||
? true
|
||||
: false
|
||||
}
|
||||
helperText={
|
||||
props.touched.password ? props.errors.password : null
|
||||
}
|
||||
placeholder={t(
|
||||
"LoginPage.text_field_enter_your_password"
|
||||
)}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="medium"
|
||||
endIcon={<LoginIcon/>}
|
||||
disabled={props.isSubmitting || !(props.values.username && props.values.password)}
|
||||
>
|
||||
{t("LoginPage.button_submit")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
</Paper>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
export default LoginExpertComponent
|
||||
28
src/components/login-expert/LoginLinkRouting.jsx
Normal file
28
src/components/login-expert/LoginLinkRouting.jsx
Normal file
@@ -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 (
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<LinkRouting
|
||||
data-testid="link_routing"
|
||||
sx={{margin: 2}}
|
||||
href={
|
||||
backUrlDecodedPath ? decodeURIComponent(backUrlDecodedPath) : "/"
|
||||
}
|
||||
>
|
||||
{t("LoginPage.link_routing_back_to")}{" "}
|
||||
{backUrlDecodedPath
|
||||
? t("LoginPage.link_routing_previuos_page")
|
||||
: t("LoginPage.link_routing_main_page")}
|
||||
</LinkRouting>
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
export default LoginLinkRouting
|
||||
@@ -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(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent />
|
||||
</MockAppWithProviders>
|
||||
);
|
||||
const loginExpertElement = screen.getByRole('heading', { level: 4 });
|
||||
expect(loginExpertElement).toHaveTextContent('ورود کارشناس');
|
||||
});
|
||||
|
||||
it('LoginExpertComponent from LoginComponent Button should render login text', () => {
|
||||
render(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent />
|
||||
</MockAppWithProviders>
|
||||
);
|
||||
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(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent />
|
||||
</MockAppWithProviders>
|
||||
);
|
||||
|
||||
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(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent />
|
||||
</MockAppWithProviders>
|
||||
);
|
||||
|
||||
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(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent />
|
||||
</MockAppWithProviders>
|
||||
);
|
||||
// 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(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent />
|
||||
</MockAppWithProviders>
|
||||
);
|
||||
// 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(
|
||||
<MockAppWithProviders>
|
||||
<LoginLinkRouting/>
|
||||
</MockAppWithProviders>
|
||||
)
|
||||
const linkElement = await screen.findByTestId("link_routing")
|
||||
expect(linkElement).toHaveTextContent("بازگشت به صفحه اصلی")
|
||||
});
|
||||
|
||||
})
|
||||
describe("validation", ()=>{
|
||||
it("Disabled submit button until fields completed", async ()=> {
|
||||
render(
|
||||
<MockAppWithProviders>
|
||||
<LoginExpertComponent/>
|
||||
</MockAppWithProviders>
|
||||
)
|
||||
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();
|
||||
});
|
||||
})
|
||||
})
|
||||
});
|
||||
@@ -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(
|
||||
<MockAppWithProviders>
|
||||
<LoginLinkRouting/>
|
||||
</MockAppWithProviders>
|
||||
)
|
||||
const linkElement = await screen.findByTestId("link_routing")
|
||||
expect(linkElement).toHaveTextContent("بازگشت به صفحه اصلی")
|
||||
});
|
||||
})
|
||||
})
|
||||
@@ -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 (
|
||||
<FullPageLayout sx={{p: 1}}>
|
||||
<CenterLayout>
|
||||
<Container maxWidth="sm">
|
||||
<Paper elevation={0}>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSubmit}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(props) => (
|
||||
<Stack spacing={2} sx={{p: 2}}>
|
||||
<Stack
|
||||
sx={{width: "100%"}}
|
||||
alignItems='center'
|
||||
>
|
||||
<SvgLogin width={300} height={200}/>
|
||||
</Stack>
|
||||
<Typography margin={2} variant="h4" textAlign="center">
|
||||
{t("login_expert")}
|
||||
</Typography>
|
||||
<StyledForm sx={{width: "100%"}}>
|
||||
<Stack spacing={3} sx={{p: 2}}>
|
||||
<Field
|
||||
as={TextField}
|
||||
name="username"
|
||||
variant="outlined"
|
||||
label={t("LoginPage.text_field_user_name")}
|
||||
placeholder={t(
|
||||
"LoginPage.text_field_enter_your_username"
|
||||
)}
|
||||
type={"text"}
|
||||
error={
|
||||
props.touched.username && props.errors.username
|
||||
? true
|
||||
: false
|
||||
}
|
||||
fullWidth
|
||||
helperText={
|
||||
props.touched.username ? props.errors.username : null
|
||||
}
|
||||
/>
|
||||
<PasswordField
|
||||
name="password"
|
||||
label={t("LoginPage.text_field_password")}
|
||||
error={
|
||||
props.touched.password && props.errors.password
|
||||
? true
|
||||
: false
|
||||
}
|
||||
helperText={
|
||||
props.touched.password ? props.errors.password : null
|
||||
}
|
||||
placeholder={t(
|
||||
"LoginPage.text_field_enter_your_password"
|
||||
)}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="medium"
|
||||
endIcon={<LoginIcon/>}
|
||||
disabled={props.isSubmitting}
|
||||
>
|
||||
{t("LoginPage.button_submit")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
</Paper>
|
||||
</Container>
|
||||
<LoginExpertComponent />
|
||||
</CenterLayout>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<LinkRouting
|
||||
sx={{margin: 2}}
|
||||
href={
|
||||
backUrlDecodedPath ? decodeURIComponent(backUrlDecodedPath) : "/"
|
||||
}
|
||||
>
|
||||
{t("LoginPage.link_routing_back_to")}{"Witel"}
|
||||
{backUrlDecodedPath
|
||||
? t("LoginPage.link_routing_previuos_page")
|
||||
: t("LoginPage.link_routing_main_page")}
|
||||
</LinkRouting>
|
||||
</Stack>
|
||||
<LoginLinkRouting />
|
||||
</FullPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
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}/>;
|
||||
});
|
||||
31
src/core/components/PasswordField/__test__/index.test.js
Normal file
31
src/core/components/PasswordField/__test__/index.test.js
Normal file
@@ -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 (
|
||||
<Formik initialValues={{password:''}} onSubmit={()=>{}}>
|
||||
{(props)=>(
|
||||
<PasswordField label="رمز عبور" />
|
||||
)}
|
||||
</Formik>
|
||||
)
|
||||
|
||||
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');
|
||||
})
|
||||
})
|
||||
})
|
||||
11
src/core/components/StyledFab.jsx
Normal file
11
src/core/components/StyledFab.jsx
Normal file
@@ -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;
|
||||
@@ -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,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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
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 DashboardLayouts = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dashboard {...props}/>
|
||||
<CallWidget/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayouts;
|
||||
15
src/lib/app/contexts/socket.jsx
Normal file
15
src/lib/app/contexts/socket.jsx
Normal file
@@ -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 <SocketContext.Provider value={{socket}}>{children}</SocketContext.Provider>
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
|
||||
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
|
||||
@@ -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 (<>
|
||||
<UserProvider>
|
||||
<LanguageProvider>
|
||||
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
|
||||
<MuiLayout isBot={pageProps.isBot}>
|
||||
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
|
||||
<LoadingProvider>
|
||||
<AppLayout isBot={pageProps.isBot}>
|
||||
<Component {...pageProps} />
|
||||
</AppLayout>
|
||||
</LoadingProvider>
|
||||
</MuiLayout>
|
||||
</NextIntlProvider>
|
||||
</LanguageProvider>
|
||||
</UserProvider>
|
||||
<SocketProvider>
|
||||
<UserProvider>
|
||||
<LanguageProvider>
|
||||
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
|
||||
<MuiLayout isBot={pageProps.isBot}>
|
||||
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
|
||||
<LoadingProvider>
|
||||
<AppLayout isBot={pageProps.isBot}>
|
||||
<Component {...pageProps} />
|
||||
</AppLayout>
|
||||
</LoadingProvider>
|
||||
</MuiLayout>
|
||||
</NextIntlProvider>
|
||||
</LanguageProvider>
|
||||
</UserProvider>
|
||||
</SocketProvider>
|
||||
</>)
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user