Merge branch 'develop' of https://gitlab.com/witel-front-end/crm-app into feature/CFE-12_change_password_test

This commit is contained in:
Yasiu1376
2023-10-02 15:14:58 +03:30
23 changed files with 477 additions and 187 deletions

View File

@@ -7,5 +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"
NODE_ENV = "development" NODE_ENV = "development"

View File

@@ -1,5 +1,6 @@
import '@testing-library/jest-dom' import '@testing-library/jest-dom'
import {server} from "./mocks/server"; import {server} from "./mocks/server";
import mockRouter from 'next-router-mock'
require('dotenv').config({path: './.env.local'}); require('dotenv').config({path: './.env.local'});
@@ -10,6 +11,8 @@ beforeAll(() => {
}) })
beforeEach(() => { beforeEach(() => {
localStorage.clear(); localStorage.clear();
mockRouter.query = {}
}) })
afterEach(() => { afterEach(() => {

View File

@@ -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",

View File

@@ -2,6 +2,7 @@
"app_name": "سامانه CRM", "app_name": "سامانه CRM",
"app_short_name": "سامانه CRM", "app_short_name": "سامانه CRM",
"dashboard": "داشبورد", "dashboard": "داشبورد",
"powered_by_witel": "توسعه یافته توسط وایتل",
"first_page": "خوش آمدید", "first_page": "خوش آمدید",
"login": "ورود", "login": "ورود",
"login_expert": "ورود کارشناس", "login_expert": "ورود کارشناس",

View File

@@ -1,42 +1,61 @@
import {render, screen, waitFor} from "@testing-library/react";
import FirstComponent from "@/components/first"; import FirstComponent from "@/components/first";
import {render, screen} from "@testing-library/react";
import MockAppWithProviders from "../../../../mocks/AppWithProvider"; import MockAppWithProviders from "../../../../mocks/AppWithProvider";
import {server} from "../../../../mocks/server"; import {server} from "../../../../mocks/server";
import {rest} from "msw"; import {rest} from "msw";
import {GET_USER_ROUTE} from "@/core/data/apiRoutes"; import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
describe('First page component', () => { describe("First Component From First Page", () => {
describe('Rendering', () => { describe("Rendering", () => {
it('Should see app name text', () => { it("App Name Text Rendered", () => {
render(<FirstComponent/>, {wrapper: MockAppWithProviders}) render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const appNameElement = screen.getByTestId('app-name') const appNameElement = screen.queryByText(/سامانه CRM/i);
expect(appNameElement).toHaveTextContent('سامانه CRM') expect(appNameElement).toBeInTheDocument()
});
it("App version Text Rendered", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const versionControler = screen.queryByText(process.env.NEXT_PUBLIC_API_VERSION, {exact: false});
expect(versionControler).toBeInTheDocument()
});
it("Powered By Rendered With Currect URL", () => {
render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const linkElement = screen.queryByText('توسعه یافته توسط وایتل');
expect(linkElement).toBeInTheDocument()
expect(linkElement).toHaveAttribute('href', process.env.NEXT_PUBLIC_POWERED_BY_URL);
}); });
}); });
describe('behavior', () => { describe("Behavioral", () => {
it('Should see login button when is not auth', async () => { it("Show Login Button And Do Not Show Dashboard Button When User Is Not Authenticated", async () => {
render(<FirstComponent/>, {wrapper: MockAppWithProviders}) render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const buttonLoginOrDashboard = await screen.findByTestId('button-login-or-dashboard') await waitFor(() => {
expect(buttonLoginOrDashboard).toHaveTextContent('ورود کارشناس') const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
expect(buttonLoginOrDashboard).not.toHaveTextContent('داشبورد') const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).toBeInTheDocument()
expect(authenticationButtonDashboard).not.toBeInTheDocument()
})
}); });
it('Should see dashboard button when is auth', async () => { it("Show Dashboard Button And Do Not Show Login Button When User Is Authenticated", async () => {
localStorage.setItem("_token", 'token'); localStorage.setItem("_token", 'token');
render(<FirstComponent/>, {wrapper: MockAppWithProviders}) render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const buttonLoginOrDashboard = await screen.findByTestId('button-login-or-dashboard') await waitFor(() => {
expect(buttonLoginOrDashboard).not.toHaveTextContent('ورود کارشناس') const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
expect(buttonLoginOrDashboard).toHaveTextContent('داشبورد') const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).not.toBeInTheDocument()
expect(authenticationButtonDashboard).toBeInTheDocument()
})
}); });
it('Should see login button when token expire', async () => { it("Show Login Button And Do Not Show Dashboard Button When User Authentication Is Expired", async () => {
localStorage.setItem("_token", 'token'); localStorage.setItem("_token", 'token');
server.use([rest.get(GET_USER_ROUTE, (req, res, ctx) => { server.use([rest.get(GET_USER_ROUTE, (req, res, ctx) => {
return res(ctx.status(403)) return res(ctx.status(403))
})]) })])
render(<FirstComponent/>, {wrapper: MockAppWithProviders}) render(<MockAppWithProviders><FirstComponent/></MockAppWithProviders>);
const buttonLoginOrDashboard = await screen.findByTestId('button-login-or-dashboard') await waitFor(() => {
expect(buttonLoginOrDashboard).toHaveTextContent('ورود کارشناس') const authenticationButtonLogin = screen.queryByText(/ورود کارشناس/i)
expect(buttonLoginOrDashboard).not.toHaveTextContent('داشبورد') const authenticationButtonDashboard = screen.queryByText(/داشبورد/i)
expect(authenticationButtonLogin).toBeInTheDocument()
expect(authenticationButtonDashboard).not.toBeInTheDocument()
})
}); });
}); });
}); });

View File

@@ -1,4 +1,4 @@
import {NextLinkComposed} from "@/core/components/LinkRouting"; import LinkRouting, {NextLinkComposed} from "@/core/components/LinkRouting";
import CenterLayout from "@/layouts/CenterLayout"; import CenterLayout from "@/layouts/CenterLayout";
import FullPageLayout from "@/layouts/FullPageLayout"; import FullPageLayout from "@/layouts/FullPageLayout";
import useUser from "@/lib/app/hooks/useUser"; import useUser from "@/lib/app/hooks/useUser";
@@ -14,7 +14,7 @@ const FirstComponent = () => {
<FullPageLayout sx={{p: 1}}> <FullPageLayout sx={{p: 1}}>
<CenterLayout spacing={3}> <CenterLayout spacing={3}>
<SvgDashboard width={300} height={200}/> <SvgDashboard width={300} height={200}/>
<Typography variant="h5" data-testid="app-name" sx={{textAlign: "center"}}> <Typography variant="h5" sx={{textAlign: "center"}}>
{t("app_name")} {t("app_name")}
</Typography> </Typography>
<Button <Button
@@ -34,7 +34,19 @@ const FirstComponent = () => {
color: 'primary.main', color: 'primary.main',
fontFamily: 'Arial', fontFamily: 'Arial',
fontWeight: 'bold' fontWeight: 'bold'
}}>v{process.env.NEXT_PUBLIC_API_VERSION}</Typography> }}
>
v{process.env.NEXT_PUBLIC_API_VERSION}
</Typography>
</Stack>
<Stack direction="row" alignItems="center" justifyContent="center">
<LinkRouting
sx={{margin: 0.5, fontSize: "14px"}}
href={process.env.NEXT_PUBLIC_POWERED_BY_URL}
target="_blank"
>
{t("powered_by_witel")}
</LinkRouting>
</Stack> </Stack>
</FullPageLayout> </FullPageLayout>
); );

View 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.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

View 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

View File

@@ -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();
});
})
})
});

View File

@@ -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("بازگشت به صفحه اصلی")
});
})
})

View File

@@ -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 CenterLayout from "@/layouts/CenterLayout";
import FullPageLayout from "@/layouts/FullPageLayout"; import FullPageLayout from "@/layouts/FullPageLayout";
import useUser from "@/lib/app/hooks/useUser"; import LoginLinkRouting from "@/components/login-expert/LoginLinkRouting";
import LoginIcon from "@mui/icons-material/Login"; import LoginExpertComponent from "@/components/login-expert/LoginExpertComponent";
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";
const LoginComponent = () => { 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 ( return (
<FullPageLayout sx={{p: 1}}> <FullPageLayout sx={{p: 1}}>
<CenterLayout> <CenterLayout>
<Container maxWidth="sm"> <LoginExpertComponent />
<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>
</CenterLayout> </CenterLayout>
<Stack direction="row" alignItems="center" justifyContent="center"> <LoginLinkRouting />
<LinkRouting
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>
</FullPageLayout> </FullPageLayout>
); );
}; };

View 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');
})
})
})

View File

@@ -36,4 +36,4 @@ const ErrorNotification = (t, status, message) => {
); );
}; };
export default ErrorNotification; export default ErrorNotification;

View File

@@ -9,4 +9,4 @@ const PendingNotification = (t) => {
}); });
}; };
export default PendingNotification; export default PendingNotification;

View File

@@ -39,4 +39,4 @@ const SuccessNotification = (t, status) => {
); );
}; };
export default SuccessNotification; export default SuccessNotification;

View File

@@ -35,4 +35,4 @@ const UploadFileNotification = (t) => {
); );
}; };
export default UploadFileNotification; export default UploadFileNotification;

View File

@@ -37,4 +37,4 @@ const WarningNotification = (t, status) => {
); );
}; };
export default WarningNotification; export default WarningNotification;

View File

@@ -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) {

View 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>
}

View File

@@ -2,6 +2,7 @@ import {GET_USER_ROUTE} from "@/core/data/apiRoutes";
import axios from "axios"; import axios from "axios";
import {createContext, useCallback, useEffect, useReducer} from "react"; import {createContext, useCallback, useEffect, useReducer} from "react";
import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler"; import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler";
import useSocket from "@/lib/app/hooks/useSocket";
const initialUser = { const initialUser = {
isAuth: false, isAuth: false,
@@ -39,6 +40,7 @@ const reducer = (state, action) => {
export const UserContext = createContext(); export const UserContext = createContext();
export const UserProvider = ({children}) => { export const UserProvider = ({children}) => {
const socket = useSocket()
const [state, dispatch] = useReducer(reducer, initialUser); const [state, dispatch] = useReducer(reducer, initialUser);
const clearUser = useCallback(() => { const clearUser = useCallback(() => {
@@ -75,7 +77,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) {
@@ -100,12 +102,16 @@ export const UserProvider = ({children}) => {
clearUser(); clearUser();
changeAuthState(false); changeAuthState(false);
changeLanguageState(false); changeLanguageState(false);
socket.auth.token = ""
return; return;
} }
getUser((data) => { getUser((data) => {
changeUser(data); changeUser(data);
changeAuthState(true); changeAuthState(true);
changeLanguageState(true); changeLanguageState(true);
socket.auth.token = data.telephone_id
}); });
}, [state.token]); }, [state.token]);

View 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

View File

@@ -7,24 +7,27 @@ 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}) => {
return (<> return (<>
<UserProvider> <SocketProvider>
<LanguageProvider> <UserProvider>
<NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}> <LanguageProvider>
<MuiLayout isBot={pageProps.isBot}> <NextIntlProvider locale={pageProps.locale} messages={pageProps.messages || {}}>
{pageProps.messages ? <TitlePage text={pageProps.title}/> : ''} <MuiLayout isBot={pageProps.isBot}>
<LoadingProvider> {pageProps.messages ? <TitlePage text={pageProps.title}/> : ''}
<AppLayout isBot={pageProps.isBot}> <LoadingProvider>
<Component {...pageProps} /> <AppLayout isBot={pageProps.isBot}>
</AppLayout> <Component {...pageProps} />
</LoadingProvider> </AppLayout>
</MuiLayout> </LoadingProvider>
</NextIntlProvider> </MuiLayout>
</LanguageProvider> </NextIntlProvider>
</UserProvider> </LanguageProvider>
</UserProvider>
</SocketProvider>
</>) </>)
}; };