init project
This commit is contained in:
7
src/components/dashboard/first/index.jsx
Normal file
7
src/components/dashboard/first/index.jsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
||||
|
||||
const DashboardFirstComponent = () => {
|
||||
return <DashboardLayouts></DashboardLayouts>;
|
||||
};
|
||||
|
||||
export default DashboardFirstComponent;
|
||||
19
src/components/dashboard/loan-follow-up/index.jsx
Normal file
19
src/components/dashboard/loan-follow-up/index.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import { useTranslations } from "next-intl";
|
||||
import React from "react";
|
||||
|
||||
const DashboardLoanFollowUpComponent = () => {
|
||||
const t = useTranslations();
|
||||
const { token } = useUser();
|
||||
return (
|
||||
<DashboardLayouts>
|
||||
<CenterLayout>
|
||||
<p>چه کشکی چه پشمی</p>
|
||||
</CenterLayout>
|
||||
</DashboardLayouts>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLoanFollowUpComponent;
|
||||
19
src/components/dashboard/loan-request/index.jsx
Normal file
19
src/components/dashboard/loan-request/index.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import DashboardLayouts from "@/layouts/dashboardLayouts";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import { useTranslations } from "next-intl";
|
||||
import React from "react";
|
||||
|
||||
const DashboardLoanRequestComponent = () => {
|
||||
const t = useTranslations();
|
||||
const { token } = useUser();
|
||||
return (
|
||||
<DashboardLayouts>
|
||||
<CenterLayout>
|
||||
<p>نکنه وامم میخوای؟؟؟</p>
|
||||
</CenterLayout>
|
||||
</DashboardLayouts>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLoanRequestComponent;
|
||||
66
src/components/first/index.jsx
Normal file
66
src/components/first/index.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import dahsboardImage from "&/images/dashboard.svg";
|
||||
import { NextLinkComposed } from "@/core/components/LinkRouting";
|
||||
import StyledImage from "@/core/components/StyledImage";
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import FullPageLayout from "@/layouts/FullPageLayout";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import { Box, Button, Divider, Typography } from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
const FirstComponent = () => {
|
||||
const t = useTranslations();
|
||||
const { isAuth } = useUser();
|
||||
|
||||
return (
|
||||
<FullPageLayout sx={{ p: 1 }}>
|
||||
<CenterLayout spacing={3}>
|
||||
<StyledImage
|
||||
src={dahsboardImage}
|
||||
alt={t("app_name")}
|
||||
width={300}
|
||||
height={200}
|
||||
/>
|
||||
<Typography variant="h5" sx={{ textAlign: "center" }}>
|
||||
{t("app_name")}
|
||||
</Typography>
|
||||
{isAuth ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: "/dashboard",
|
||||
}}
|
||||
>
|
||||
{t("dashboard")}
|
||||
</Button>
|
||||
) : (
|
||||
<Box sx={{ display: "flex" }}>
|
||||
<Button
|
||||
sx={{ mx: 2 }}
|
||||
variant="contained"
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: "/login-navy",
|
||||
}}
|
||||
>
|
||||
{t("login_navy")}
|
||||
</Button>
|
||||
<Divider orientation="vertical" flexItem />
|
||||
<Button
|
||||
sx={{ mx: 2 }}
|
||||
variant="contained"
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: "/login-welfare-services",
|
||||
}}
|
||||
>
|
||||
{t("login_welfare_services")}
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
</CenterLayout>
|
||||
</FullPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default FirstComponent;
|
||||
170
src/components/login-navy/index.jsx
Normal file
170
src/components/login-navy/index.jsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import LinkRouting from "@/core/components/LinkRouting";
|
||||
// import Notifications from "@/core/components/notifications";
|
||||
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 useDirection from "@/lib/app/hooks/useDirection";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Paper,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
// import axios from "axios";
|
||||
import { Field, Formik } from "formik";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const LoginComponent = () => {
|
||||
const t = useTranslations();
|
||||
const { directionApp } = useDirection();
|
||||
const { setToken } = useUser(); // pass token to set token
|
||||
|
||||
// gettin url query
|
||||
const searchParams = useSearchParams();
|
||||
const backUrlDecodedPath = searchParams.get("back_url");
|
||||
|
||||
// formik properties
|
||||
// const handleSubmit = async (values, props) => {
|
||||
// await axios
|
||||
// .post(GET_USER_TOKEN, {
|
||||
// username: values.username,
|
||||
// password: values.password,
|
||||
// })
|
||||
// .then(function (response) {
|
||||
// setToken(response.data.token);
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// Notifications(directionApp, error.response, t);
|
||||
// props.setSubmitting(false);
|
||||
// });
|
||||
// };
|
||||
const initialValues = {
|
||||
username: "",
|
||||
password: "",
|
||||
};
|
||||
const validationSchema = Yup.object().shape({
|
||||
username: Yup.string().required(t("LoginPage.error_message_required")),
|
||||
password: Yup.string().required(t("LoginPage.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 }}>
|
||||
<Box
|
||||
sx={{ position: "relative", width: "100%", height: 200 }}
|
||||
>
|
||||
<Image fill src="/images/login.svg" alt={t("app_name")} />
|
||||
</Box>
|
||||
<Typography margin={2} variant="h4" textAlign="center">
|
||||
{t("login_navy")}
|
||||
</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",
|
||||
}}
|
||||
>
|
||||
<LinkRouting href={"/register-navy"}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
endIcon={<PersonAddIcon />}
|
||||
disabled={props.isSubmitting ? true : false}
|
||||
>
|
||||
</Button>
|
||||
</LinkRouting>
|
||||
{t("LoginPage.button_make_account")}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
size="medium"
|
||||
endIcon={<LoginIcon />}
|
||||
disabled={props.isSubmitting ? true : false}
|
||||
>
|
||||
{t("LoginPage.button_submit")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
</Paper>
|
||||
</Container>
|
||||
</CenterLayout>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginComponent;
|
||||
170
src/components/login-welfare-services/index.jsx
Normal file
170
src/components/login-welfare-services/index.jsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import LinkRouting from "@/core/components/LinkRouting";
|
||||
// import Notifications from "@/core/components/notifications";
|
||||
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 useDirection from "@/lib/app/hooks/useDirection";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import PersonAddIcon from "@mui/icons-material/PersonAdd";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Container,
|
||||
Paper,
|
||||
Stack,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
// import axios from "axios";
|
||||
import { Field, Formik } from "formik";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Image from "next/image";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import * as Yup from "yup";
|
||||
|
||||
const LoginComponent = () => {
|
||||
const t = useTranslations();
|
||||
const { directionApp } = useDirection();
|
||||
const { setToken } = useUser(); // pass token to set token
|
||||
|
||||
// gettin url query
|
||||
const searchParams = useSearchParams();
|
||||
const backUrlDecodedPath = searchParams.get("back_url");
|
||||
|
||||
// formik properties
|
||||
// const handleSubmit = async (values, props) => {
|
||||
// await axios
|
||||
// .post(GET_USER_TOKEN, {
|
||||
// username: values.username,
|
||||
// password: values.password,
|
||||
// })
|
||||
// .then(function (response) {
|
||||
// setToken(response.data.token);
|
||||
// })
|
||||
// .catch(function (error) {
|
||||
// Notifications(directionApp, error.response, t);
|
||||
// props.setSubmitting(false);
|
||||
// });
|
||||
// };
|
||||
const initialValues = {
|
||||
username: "",
|
||||
password: "",
|
||||
};
|
||||
const validationSchema = Yup.object().shape({
|
||||
username: Yup.string().required(t("LoginPage.error_message_required")),
|
||||
password: Yup.string().required(t("LoginPage.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 }}>
|
||||
<Box
|
||||
sx={{ position: "relative", width: "100%", height: 200 }}
|
||||
>
|
||||
<Image fill src="/images/login.svg" alt={t("app_name")} />
|
||||
</Box>
|
||||
<Typography margin={2} variant="h4" textAlign="center">
|
||||
{t("login_welfare_services")}
|
||||
</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",
|
||||
}}
|
||||
>
|
||||
<LinkRouting href={"/register-welfare-services"}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
endIcon={<PersonAddIcon />}
|
||||
disabled={props.isSubmitting ? true : false}
|
||||
>
|
||||
{t("LoginPage.button_make_account")}
|
||||
</Button>
|
||||
</LinkRouting>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
size="medium"
|
||||
endIcon={<LoginIcon />}
|
||||
disabled={props.isSubmitting ? true : false}
|
||||
>
|
||||
{t("LoginPage.button_submit")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
</StyledForm>
|
||||
</Stack>
|
||||
)}
|
||||
</Formik>
|
||||
</Paper>
|
||||
</Container>
|
||||
</CenterLayout>
|
||||
<Stack direction="row" alignItems="center" justifyContent="center">
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginComponent;
|
||||
0
src/components/register-navy/index.jsx
Normal file
0
src/components/register-navy/index.jsx
Normal file
0
src/components/register-welfare-services/index.jsx
Normal file
0
src/components/register-welfare-services/index.jsx
Normal file
116
src/core/components/LinkRouting.jsx
Normal file
116
src/core/components/LinkRouting.jsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import MuiLink from "@mui/material/Link";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import clsx from "clsx";
|
||||
import NextLink from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import * as React from "react";
|
||||
|
||||
// Add support for the sx prop for consistency with the other branches.
|
||||
const Anchor = styled("a")({});
|
||||
|
||||
export const NextLinkComposed = React.forwardRef(function NextLinkComposed(
|
||||
props,
|
||||
ref
|
||||
) {
|
||||
const {
|
||||
to,
|
||||
linkAs,
|
||||
replace,
|
||||
scroll,
|
||||
shallow,
|
||||
prefetch,
|
||||
legacyBehavior = true,
|
||||
locale,
|
||||
...other
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<NextLink
|
||||
href={to}
|
||||
prefetch={prefetch}
|
||||
as={linkAs}
|
||||
replace={replace}
|
||||
scroll={scroll}
|
||||
shallow={shallow}
|
||||
passHref
|
||||
locale={locale}
|
||||
legacyBehavior={legacyBehavior}
|
||||
>
|
||||
<Anchor ref={ref} {...other} />
|
||||
</NextLink>
|
||||
);
|
||||
});
|
||||
|
||||
// A styled version of the Next.js Link component:
|
||||
// https://nextjs.org/docs/api-reference/next/link
|
||||
const LinkRouting = React.forwardRef(function Link(props, ref) {
|
||||
const {
|
||||
activeClassName = "active",
|
||||
as,
|
||||
className: classNameProps,
|
||||
href,
|
||||
legacyBehavior,
|
||||
linkAs: linkAsProp,
|
||||
locale,
|
||||
noLinkStyle,
|
||||
prefetch,
|
||||
replace,
|
||||
role, // Link don't have roles.
|
||||
scroll,
|
||||
shallow,
|
||||
...other
|
||||
} = props;
|
||||
|
||||
const router = useRouter();
|
||||
const pathname = typeof href === "string" ? href : href.pathname;
|
||||
const className = clsx(classNameProps, {
|
||||
[activeClassName]: router.pathname === pathname && activeClassName,
|
||||
});
|
||||
|
||||
const isExternal =
|
||||
typeof href === "string" &&
|
||||
(href.indexOf("http") === 0 || href.indexOf("mailto:") === 0);
|
||||
|
||||
if (isExternal) {
|
||||
if (noLinkStyle) {
|
||||
return <Anchor className={className} href={href} ref={ref} {...other} />;
|
||||
}
|
||||
|
||||
return <MuiLink className={className} href={href} ref={ref} {...other} />;
|
||||
}
|
||||
|
||||
const linkAs = linkAsProp || as;
|
||||
const nextjsProps = {
|
||||
to: href,
|
||||
linkAs,
|
||||
replace,
|
||||
scroll,
|
||||
shallow,
|
||||
prefetch,
|
||||
legacyBehavior,
|
||||
locale,
|
||||
};
|
||||
|
||||
if (noLinkStyle) {
|
||||
return (
|
||||
<NextLinkComposed
|
||||
className={className}
|
||||
ref={ref}
|
||||
{...nextjsProps}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<MuiLink
|
||||
component={NextLinkComposed}
|
||||
className={className}
|
||||
ref={ref}
|
||||
{...nextjsProps}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default LinkRouting;
|
||||
44
src/core/components/LoadingHardPage.jsx
Normal file
44
src/core/components/LoadingHardPage.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Backdrop, Fade, styled } from "@mui/material";
|
||||
import StyledImage from "./StyledImage";
|
||||
|
||||
const LoadingImage = styled(StyledImage)({
|
||||
"@keyframes load": {
|
||||
"0%": {
|
||||
// opacity: 0,
|
||||
transform: "scale(1)",
|
||||
},
|
||||
"50%": {
|
||||
// opacity: 1,
|
||||
transform: "scale(2)",
|
||||
},
|
||||
"100%": {
|
||||
// opacity: 0,
|
||||
transform: "scale(1)",
|
||||
},
|
||||
},
|
||||
animation: "load 2s infinite",
|
||||
});
|
||||
|
||||
const LoadingHardPage = ({ children, loading }) => {
|
||||
return (
|
||||
<>
|
||||
<Backdrop
|
||||
sx={{ bgcolor: "#fff", zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||
open={loading}
|
||||
>
|
||||
<Fade in={true}>
|
||||
<LoadingImage
|
||||
src={"/images/logo.svg"}
|
||||
alt="loading marhaba"
|
||||
priority
|
||||
width={100}
|
||||
height={100}
|
||||
/>
|
||||
</Fade>
|
||||
</Backdrop>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoadingHardPage;
|
||||
22
src/core/components/Messages.jsx
Normal file
22
src/core/components/Messages.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import CenterLayout from "@/layouts/CenterLayout";
|
||||
import FullPageLayout from "@/layouts/FullPageLayout";
|
||||
import StyledImage from "./StyledImage";
|
||||
|
||||
const Message = ({ text, actions }) => {
|
||||
return (
|
||||
<FullPageLayout sx={{ p: 1 }}>
|
||||
<CenterLayout spacing={3}>
|
||||
<StyledImage
|
||||
src={"/images/logo.svg"}
|
||||
alt="loading loan facilities"
|
||||
width={100}
|
||||
height={100}
|
||||
/>
|
||||
{text}
|
||||
{actions}
|
||||
</CenterLayout>
|
||||
</FullPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Message;
|
||||
33
src/core/components/PasswordField.jsx
Normal file
33
src/core/components/PasswordField.jsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useState } from "react";
|
||||
import { IconButton, InputAdornment, TextField } from "@mui/material";
|
||||
import { Visibility, VisibilityOff } from "@mui/icons-material";
|
||||
import { Field } from "formik";
|
||||
|
||||
const PasswordField = (props) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const handleClickShowPassword = () => {
|
||||
setShowPassword(!showPassword);
|
||||
};
|
||||
|
||||
return (
|
||||
<Field
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
as={TextField}
|
||||
{...props}
|
||||
type={showPassword ? "text" : "password"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={handleClickShowPassword}>
|
||||
{showPassword ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordField;
|
||||
6
src/core/components/StyledForm.jsx
Normal file
6
src/core/components/StyledForm.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { styled } from "@mui/material";
|
||||
import { Form } from "formik";
|
||||
|
||||
const StyledForm = styled(Form)``;
|
||||
|
||||
export default StyledForm;
|
||||
6
src/core/components/StyledImage.jsx
Normal file
6
src/core/components/StyledImage.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { styled } from "@mui/material";
|
||||
import Image from "next/image";
|
||||
|
||||
const StyledImage = styled(Image)``;
|
||||
|
||||
export default StyledImage;
|
||||
15
src/core/components/TitlePage.jsx
Normal file
15
src/core/components/TitlePage.jsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import Head from "next/head";
|
||||
|
||||
const TitlePage = ({ text }) => {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<Head>
|
||||
<title>
|
||||
{text ? `${t("app_short_name")} | ${t(text)}` : t("app_short_name")}
|
||||
</title>
|
||||
</Head>
|
||||
);
|
||||
};
|
||||
|
||||
export default TitlePage;
|
||||
8
src/core/components/isBotHandler.jsx
Normal file
8
src/core/components/isBotHandler.jsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { NoSsr } from "@mui/material";
|
||||
|
||||
const NoSsrHandler = ({ isBot, children }) => {
|
||||
if (isBot) return children;
|
||||
return <NoSsr>{children}</NoSsr>;
|
||||
};
|
||||
|
||||
export default NoSsrHandler;
|
||||
0
src/core/data/apiRoutes.js
Normal file
0
src/core/data/apiRoutes.js
Normal file
19
src/core/data/headerProfileItems.jsx
Normal file
19
src/core/data/headerProfileItems.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import BorderColorIcon from "@mui/icons-material/BorderColor";
|
||||
import PasswordIcon from "@mui/icons-material/Password";
|
||||
|
||||
const headerProfileItems = [
|
||||
{
|
||||
key: 0,
|
||||
name: "header.edit_profile",
|
||||
route: "/dashboard/edit-profile",
|
||||
icon: <BorderColorIcon />,
|
||||
},
|
||||
{
|
||||
key: 1,
|
||||
name: "header.change_password",
|
||||
route: "/dashboard/change-password",
|
||||
icon: <PasswordIcon />,
|
||||
},
|
||||
];
|
||||
|
||||
export default headerProfileItems;
|
||||
31
src/core/data/sidebarMenu.jsx
Normal file
31
src/core/data/sidebarMenu.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import SpaceDashboardIcon from "@mui/icons-material/SpaceDashboard";
|
||||
import BookmarkAddedIcon from "@mui/icons-material/BookmarkAdded";
|
||||
import DataSaverOnIcon from "@mui/icons-material/DataSaverOn";
|
||||
|
||||
const sidebarMenu = [
|
||||
[
|
||||
{
|
||||
key: "sidebar.dashboard",
|
||||
type: "page",
|
||||
route: "/dashboard",
|
||||
icon: <SpaceDashboardIcon />,
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
key: "sidebar.loan-request",
|
||||
type: "page",
|
||||
route: "/dashboard/loan-request",
|
||||
icon: <DataSaverOnIcon />,
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
key: "sidebar.loan-follow-up",
|
||||
type: "page",
|
||||
route: "/dashboard/loan-follow-up",
|
||||
icon: <BookmarkAddedIcon />,
|
||||
selected: false,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
||||
export default sidebarMenu;
|
||||
38
src/core/utils/createEmotionCache.jsx
Normal file
38
src/core/utils/createEmotionCache.jsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import createCache from "@emotion/cache";
|
||||
import { prefixer } from "stylis";
|
||||
import stylisRTLPlugin from "stylis-plugin-rtl";
|
||||
|
||||
const isBrowser = typeof document !== "undefined";
|
||||
|
||||
export const createEmotionCacheLtr = () => {
|
||||
let insertionPoint;
|
||||
|
||||
if (isBrowser) {
|
||||
const emotionInsertionPoint = document.querySelector(
|
||||
'meta[name="emotion-insertion-point"]'
|
||||
);
|
||||
insertionPoint = emotionInsertionPoint ?? undefined;
|
||||
}
|
||||
|
||||
return createCache({
|
||||
key: "mui-style",
|
||||
insertionPoint,
|
||||
});
|
||||
};
|
||||
|
||||
export const createEmotionCacheRtl = () => {
|
||||
let insertionPoint;
|
||||
|
||||
if (isBrowser) {
|
||||
const emotionInsertionPoint = document.querySelector(
|
||||
'meta[name="emotion-insertion-point"]'
|
||||
);
|
||||
insertionPoint = emotionInsertionPoint ?? undefined;
|
||||
}
|
||||
|
||||
return createCache({
|
||||
key: "muirtl",
|
||||
stylisPlugins: [prefixer, stylisRTLPlugin],
|
||||
insertionPoint,
|
||||
});
|
||||
};
|
||||
12
src/core/utils/theme-rtl.jsx
Normal file
12
src/core/utils/theme-rtl.jsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
import theme from "./theme";
|
||||
|
||||
const themeRtl = createTheme({
|
||||
direction: "rtl",
|
||||
typography: {
|
||||
fontFamily: `IRANSans, sans-serif`,
|
||||
},
|
||||
...theme,
|
||||
});
|
||||
|
||||
export default themeRtl;
|
||||
18
src/core/utils/theme.jsx
Normal file
18
src/core/utils/theme.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
const theme = {
|
||||
palette: {
|
||||
primary: {
|
||||
main: "#084070",
|
||||
contrastText: "#fff",
|
||||
light: "#2c6291",
|
||||
dark: "#11293e",
|
||||
},
|
||||
secondary: {
|
||||
main: "#FF4E00",
|
||||
contrastText: "#fff",
|
||||
light: "#ed743e",
|
||||
dark: "#ad3a07",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default theme;
|
||||
85
src/layouts/AppLayout.jsx
Normal file
85
src/layouts/AppLayout.jsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import theme from "@/core/utils/theme";
|
||||
import useLoading from "@/lib/app/hooks/useLoading";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import Head from "next/head";
|
||||
import NextNProgress from "nextjs-progressbar";
|
||||
import { useEffect } from "react";
|
||||
|
||||
function AppLayout({ children, isBot }) {
|
||||
const { setLoadingPage } = useLoading();
|
||||
const { token, isAuth } = useUser();
|
||||
useEffect(() => {
|
||||
if (token) {
|
||||
if (isAuth) {
|
||||
setLoadingPage(false);
|
||||
return;
|
||||
}
|
||||
setLoadingPage(true);
|
||||
return;
|
||||
}
|
||||
setLoadingPage(false);
|
||||
return;
|
||||
}, [token, isAuth]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<meta
|
||||
name="application-name"
|
||||
content={process.env.NEXT_PUBLIC_API_NAME}
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta
|
||||
name="apple-mobile-web-app-status-bar-style"
|
||||
content="black-translucent"
|
||||
/>
|
||||
<meta
|
||||
name="apple-mobile-web-app-title"
|
||||
content={process.env.NEXT_PUBLIC_API_NAME}
|
||||
/>
|
||||
<meta name="description" content="Marhaba does it for you" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta name="format-detection" content="date=no" />
|
||||
<meta name="format-detection" content="address=no" />
|
||||
<meta name="format-detection" content="email=no" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" href="/icons/maskable_icon_x512.png" />
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="120x120"
|
||||
href="/icons/maskable_icon_x128.png"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/icons/maskable_icon_x192.png"
|
||||
/>
|
||||
<meta name="google" content="notranslate" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/svg"
|
||||
sizes="32x32"
|
||||
href="/images/logo.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/svg"
|
||||
sizes="16x16"
|
||||
href="/images/logo.png"
|
||||
/>
|
||||
</Head>
|
||||
<NextNProgress
|
||||
color={theme.palette.secondary.dark}
|
||||
options={{ showSpinner: false }}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppLayout;
|
||||
18
src/layouts/CenterLayout.jsx
Normal file
18
src/layouts/CenterLayout.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Fade, Stack } from "@mui/material";
|
||||
|
||||
const CenterLayout = (props) => {
|
||||
return (
|
||||
<Fade in={true}>
|
||||
<Stack
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
spacing={props?.spacing}
|
||||
sx={{ flex: 1, ...props?.sx, py: 3 }}
|
||||
>
|
||||
{props.children}
|
||||
</Stack>
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export default CenterLayout;
|
||||
21
src/layouts/FullPageLayout.jsx
Normal file
21
src/layouts/FullPageLayout.jsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Stack } from "@mui/material";
|
||||
|
||||
const FullPageLayout = (props) => {
|
||||
return (
|
||||
<Stack
|
||||
spacing={props?.spacing}
|
||||
direction={props?.direction}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
overflowY: "scroll",
|
||||
overflowX: "scroll",
|
||||
...props?.sx,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default FullPageLayout;
|
||||
76
src/layouts/MuiLayout.jsx
Normal file
76
src/layouts/MuiLayout.jsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import NoSsrHandler from "@/core/components/isBotHandler";
|
||||
import {
|
||||
createEmotionCacheRtl
|
||||
} from "@/core/utils/createEmotionCache";
|
||||
import themeRtl from "@/core/utils/theme-rtl";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import { GlobalStyles } from "@mui/material";
|
||||
import CssBaseline from "@mui/material/CssBaseline";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import Head from "next/head";
|
||||
|
||||
const clientSideEmotionCacheRtl = createEmotionCacheRtl();
|
||||
|
||||
const MuiLayout = ({ children, isBot }) => {
|
||||
|
||||
const emotionCache = clientSideEmotionCacheRtl;
|
||||
|
||||
const theme = themeRtl;
|
||||
return (
|
||||
<NoSsrHandler isBot={isBot}>
|
||||
<CacheProvider value={emotionCache}>
|
||||
<Head>
|
||||
<meta name="viewport" content="initial-scale=1, width=device-width" />
|
||||
</Head>
|
||||
<ThemeProvider theme={theme}>
|
||||
<GlobalStyles
|
||||
styles={{
|
||||
"*:not(.MuiTableContainer-root)::-webkit-scrollbar": {
|
||||
display: "none",
|
||||
},
|
||||
"*::-webkit-scrollbar": {
|
||||
height: "8px",
|
||||
},
|
||||
"*::-webkit-scrollbar-thumb": {
|
||||
background: "#e1e1e1",
|
||||
borderRadius: "3px",
|
||||
},
|
||||
"*:not(.MuiTableContainer-root)": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "transparent transparent",
|
||||
},
|
||||
|
||||
"*": {
|
||||
scrollbarWidth: "thin",
|
||||
scrollbarColor: "#e1e1e1 transparent",
|
||||
},
|
||||
|
||||
"*::-moz-scrollbar-thumb": {
|
||||
backgroundColor: "#e1e1e1",
|
||||
},
|
||||
[`@media (max-width: ${theme.breakpoints.values.sm}px)`]: {
|
||||
"*::-webkit-scrollbar": {
|
||||
height: "4px",
|
||||
},
|
||||
},
|
||||
|
||||
body: {
|
||||
width: "100vw",
|
||||
height: "100vh",
|
||||
},
|
||||
"#__next": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<CssBaseline />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
</NoSsrHandler>
|
||||
);
|
||||
};
|
||||
|
||||
export default MuiLayout;
|
||||
25
src/layouts/ScrollableLayout.jsx
Normal file
25
src/layouts/ScrollableLayout.jsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Box, Fade } from "@mui/material";
|
||||
|
||||
const ScrollableLayout = (props) => {
|
||||
const overflowY = props?.y || "hidden";
|
||||
const overflowX = props?.x || "hidden";
|
||||
|
||||
return (
|
||||
<Fade in={true}>
|
||||
<Box
|
||||
spacing={props?.spacing}
|
||||
sx={{
|
||||
width: "auto",
|
||||
height: "auto",
|
||||
overflowY: overflowY,
|
||||
overflowX: overflowX,
|
||||
...props?.sx,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScrollableLayout;
|
||||
19
src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx
Normal file
19
src/layouts/dashboardLayouts/breadcrumbs/BreadcrumbItem.jsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import LinkRouting from "@/core/components/LinkRouting";
|
||||
import { Typography } from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function BreadcrumbItem(props) {
|
||||
const t = useTranslations();
|
||||
const isLast = props.index === props.RouterArray.length - 1;
|
||||
const url = `/${props.RouterArray.slice(0, props.index + 1).join("/")}`;
|
||||
|
||||
return isLast ? (
|
||||
<Typography variant="body2" color="primary">
|
||||
{t("sidebar." + props.label)}
|
||||
</Typography>
|
||||
) : (
|
||||
<LinkRouting underline="hover" color="inherit" passHref href={url}>
|
||||
<Typography variant="body2">{t("sidebar." + props.label)}</Typography>
|
||||
</LinkRouting>
|
||||
);
|
||||
}
|
||||
49
src/layouts/dashboardLayouts/breadcrumbs/index.jsx
Normal file
49
src/layouts/dashboardLayouts/breadcrumbs/index.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { Box, Breadcrumbs } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { NavigateBefore, NavigateNext } from "@mui/icons-material";
|
||||
import BreadcrumbItem from "./BreadcrumbItem";
|
||||
|
||||
const BreadCrumbs = (props) => {
|
||||
const { isVisible } = props;
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { pathname } = router;
|
||||
const RouterArray = pathname.split("/").filter((segment) => segment !== "");
|
||||
|
||||
if (RouterArray.length === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box p={3} component="span">
|
||||
<Breadcrumbs
|
||||
maxItems={2}
|
||||
separator={
|
||||
theme.direction === "ltr" ? (
|
||||
<NavigateNext fontSize="small" />
|
||||
) : (
|
||||
<NavigateBefore fontSize="small" />
|
||||
)
|
||||
}
|
||||
aria-label="breadcrumb"
|
||||
>
|
||||
{RouterArray.map((segment, index) => (
|
||||
<BreadcrumbItem
|
||||
RouterArray={RouterArray}
|
||||
label={segment}
|
||||
key={segment}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</Breadcrumbs>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default BreadCrumbs;
|
||||
18
src/layouts/dashboardLayouts/header/ProfileData.jsx
Normal file
18
src/layouts/dashboardLayouts/header/ProfileData.jsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Avatar, Stack, Typography } from "@mui/material";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
export default function ProfileData() {
|
||||
const { user } = useUser();
|
||||
return (
|
||||
<Stack alignItems="center" spacing={2} sx={{ p: 3 }}>
|
||||
<Avatar
|
||||
sx={{ width: "80px", height: "80px" }}
|
||||
alt="User Image"
|
||||
src={user.user_avatar}
|
||||
/>
|
||||
<Typography sx={{ fontSize: 15, fontWeight: 600 }} textAlign="center">
|
||||
{user.user_username}
|
||||
</Typography>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
61
src/layouts/dashboardLayouts/header/ProfileMenu.jsx
Normal file
61
src/layouts/dashboardLayouts/header/ProfileMenu.jsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Avatar, Menu, IconButton, Tooltip } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import ProfileData from "./ProfileData";
|
||||
import ProfileOptions from "./ProfileOptions";
|
||||
import { useTranslations } from "next-intl";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
|
||||
function ProfileMenu() {
|
||||
const t = useTranslations();
|
||||
const [anchorElUser, setAnchorElUser] = useState(null);
|
||||
const { user } = useUser();
|
||||
|
||||
const handleOpenUserMenu = (event) => {
|
||||
setAnchorElUser(event.currentTarget);
|
||||
};
|
||||
const handleCloseUserMenu = () => {
|
||||
setAnchorElUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip title={t("header.open_profile")} arrow>
|
||||
<IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
backgroundColor: "#fff",
|
||||
color: "primary.main",
|
||||
}}
|
||||
alt="User Image"
|
||||
src={user.user_avatar}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
MenuListProps={{ sx: { py: 0 } }}
|
||||
sx={{
|
||||
mt: 6,
|
||||
}}
|
||||
id="menu-appbar"
|
||||
anchorEl={anchorElUser}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
keepMounted
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "right",
|
||||
}}
|
||||
open={Boolean(anchorElUser)}
|
||||
onClose={handleCloseUserMenu}
|
||||
>
|
||||
<ProfileData />
|
||||
<ProfileOptions handleCloseUserMenu={handleCloseUserMenu} />
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default ProfileMenu;
|
||||
49
src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx
Normal file
49
src/layouts/dashboardLayouts/header/ProfileOptionLogOut.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Box, Button, MenuItem, Typography } from "@mui/material";
|
||||
import MeetingRoomIcon from "@mui/icons-material/MeetingRoom";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function ProfileOptionLogOut({ handleCloseUserMenu }) {
|
||||
const t = useTranslations();
|
||||
const { clearToken } = useUser();
|
||||
const handleClickLogOut = () => {
|
||||
handleCloseUserMenu();
|
||||
clearToken();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<MenuItem
|
||||
component={Button}
|
||||
to={{
|
||||
pathname: "/dashboard/logout",
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderTop: 1,
|
||||
px: 3,
|
||||
py: 1.5,
|
||||
borderColor: "#e1e1e1",
|
||||
textTransform: "unset",
|
||||
}}
|
||||
onClick={handleClickLogOut}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", flex: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
<MeetingRoomIcon />
|
||||
</Box>
|
||||
<Typography sx={{ flex: 1 }} textAlign="start">
|
||||
{t("header.logout")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
49
src/layouts/dashboardLayouts/header/ProfileOptions.jsx
Normal file
49
src/layouts/dashboardLayouts/header/ProfileOptions.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Box, MenuItem, Typography } from "@mui/material";
|
||||
import { NextLinkComposed } from "@/core/components/LinkRouting";
|
||||
import { useTranslations } from "next-intl";
|
||||
import headerProfileItems from "@/core/data/headerProfileItems";
|
||||
import ProfileOptionLogOut from "./ProfileOptionLogOut";
|
||||
|
||||
export default function ProfileOptions({ handleCloseUserMenu }) {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{headerProfileItems.map((profile_item) => (
|
||||
<MenuItem
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: profile_item.route,
|
||||
}}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderTop: 1,
|
||||
px: 3,
|
||||
py: 1.5,
|
||||
borderColor: "#e1e1e1",
|
||||
}}
|
||||
key={profile_item.key}
|
||||
onClick={handleCloseUserMenu}
|
||||
>
|
||||
<Box sx={{ display: "flex", alignItems: "center", flex: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{profile_item.icon}
|
||||
</Box>
|
||||
<Typography sx={{ flex: 1 }} textAlign="start">
|
||||
{t(profile_item.name)}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
))}
|
||||
<ProfileOptionLogOut handleCloseUserMenu={handleCloseUserMenu} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
80
src/layouts/dashboardLayouts/header/index.jsx
Normal file
80
src/layouts/dashboardLayouts/header/index.jsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import {
|
||||
AppBar,
|
||||
Box,
|
||||
Container,
|
||||
CssBaseline,
|
||||
IconButton,
|
||||
Stack,
|
||||
Toolbar,
|
||||
useTheme
|
||||
} from "@mui/material";
|
||||
import ProfileMenu from "./ProfileMenu";
|
||||
|
||||
function Header({ drawerWidth, handleDrawerToggle }) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
width: { md: `calc(100% - ${drawerWidth}px)` },
|
||||
ml: { md: `${drawerWidth}px` },
|
||||
}}
|
||||
>
|
||||
<Container maxWidth="xxl">
|
||||
<Toolbar
|
||||
disableGutters
|
||||
sx={{
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<Stack direction="row" justifyContent="flex-start" sx={{ flex: 1 }}>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
aria-label="open drawer"
|
||||
onClick={handleDrawerToggle}
|
||||
edge="start"
|
||||
sx={{ display: { md: "none" }, m: 0 }}
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
sx={{
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
...theme.mixins.toolbar,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
my: 1,
|
||||
width: 56,
|
||||
height: 56,
|
||||
"@media (min-width:600px)": { maxWidth: 64, maxHeight: 64 },
|
||||
"@media (min-width:0px)": {
|
||||
"@media (orientation: landscape)": {
|
||||
width: 48,
|
||||
height: 48,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Stack direction="row" justifyContent="flex-end" sx={{ flex: 1 }}>
|
||||
<ProfileMenu />
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
</AppBar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default Header;
|
||||
50
src/layouts/dashboardLayouts/index.jsx
Normal file
50
src/layouts/dashboardLayouts/index.jsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useState } from "react";
|
||||
import FullPageLayout from "../FullPageLayout";
|
||||
import Header from "./header";
|
||||
import Sidebar from "./sidebar";
|
||||
import { Toolbar } from "@mui/material";
|
||||
import BreadCrumbs from "./breadcrumbs";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
|
||||
const drawerWidth = 240;
|
||||
|
||||
const DashboardLayouts = (props) => {
|
||||
const { directionApp } = useDirection();
|
||||
const { window } = props;
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const container =
|
||||
window !== undefined ? () => window().document.body : undefined;
|
||||
|
||||
const handleDrawerToggle = () => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
return (
|
||||
<FullPageLayout direction="row">
|
||||
<Header
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
<Sidebar
|
||||
container={container}
|
||||
mobileOpen={mobileOpen}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
drawerWidth={drawerWidth}
|
||||
/>
|
||||
<FullPageLayout
|
||||
component="main"
|
||||
sx={{ flexGrow: 1, width: { sm: `calc(100% - ${drawerWidth}px)` } }}
|
||||
>
|
||||
<Toolbar />
|
||||
<FullPageLayout sx={{ mt: 3 }}>
|
||||
<BreadCrumbs isVisible={true} />
|
||||
{props.children}
|
||||
</FullPageLayout>
|
||||
</FullPageLayout>
|
||||
{directionApp === "rtl" ? <ToastContainer rtl /> : <ToastContainer ltr />}
|
||||
</FullPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayouts;
|
||||
29
src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx
Normal file
29
src/layouts/dashboardLayouts/sidebar/SidebarDrawer.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Divider, Stack, Toolbar, Typography } from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
import SidebarList from "./SidebarList";
|
||||
import StyledImage from "@/core/components/StyledImage";
|
||||
|
||||
const SidebarDrawer = ({ handleDrawerToggle }) => {
|
||||
const t = useTranslations();
|
||||
return (
|
||||
<>
|
||||
<Toolbar>
|
||||
<Stack>
|
||||
<Typography variant="h6" sx={{ color: "primary.main" }}>
|
||||
{t("app_short_name")}
|
||||
</Typography>
|
||||
<Typography variant="caption">
|
||||
{
|
||||
t("user_navy") /* make if on thease t("user_welfare_services")
|
||||
t("user_navy") */
|
||||
}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Toolbar>
|
||||
<Divider />
|
||||
<SidebarList handleDrawerToggle={handleDrawerToggle} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarDrawer;
|
||||
56
src/layouts/dashboardLayouts/sidebar/SidebarList.jsx
Normal file
56
src/layouts/dashboardLayouts/sidebar/SidebarList.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Divider, List } from "@mui/material";
|
||||
import { Fragment, useEffect, useReducer } from "react";
|
||||
import SidebarListItem from "./SidebarListItem";
|
||||
import sidebarMenu from "@/core/data/sidebarMenu";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case "COLLAPSE_MENU":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) =>
|
||||
action.key == item.key
|
||||
? { ...item, showSubItem: !item.showSubItem }
|
||||
: item
|
||||
)
|
||||
);
|
||||
case "SELECTED":
|
||||
return state.map((itemsArr) =>
|
||||
itemsArr.map((item) =>
|
||||
action.route == item.route
|
||||
? { ...item, selected: true }
|
||||
: { ...item, selected: false }
|
||||
)
|
||||
);
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
export default function SidebarList({ handleDrawerToggle }) {
|
||||
const [itemMenu, dispatch] = useReducer(reducer, sidebarMenu);
|
||||
|
||||
// activate current page in menu
|
||||
const router = useRouter();
|
||||
useEffect(() => {
|
||||
dispatch({ type: "SELECTED", route: router.pathname });
|
||||
}, [router.pathname]);
|
||||
|
||||
return (
|
||||
<List>
|
||||
{itemMenu.map((itemArr, index) => (
|
||||
<Fragment key={index}>
|
||||
{itemArr.map((item) => (
|
||||
<SidebarListItem
|
||||
item={item}
|
||||
dispatch={dispatch}
|
||||
key={item.key}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
/>
|
||||
))}
|
||||
<Divider />
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}
|
||||
64
src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx
Normal file
64
src/layouts/dashboardLayouts/sidebar/SidebarListItem.jsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import { NextLinkComposed } from "@/core/components/LinkRouting";
|
||||
import ExpandLess from "@mui/icons-material/ExpandLess";
|
||||
import ExpandMore from "@mui/icons-material/ExpandMore";
|
||||
import {
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Fragment } from "react";
|
||||
import SidebarListSubItem from "./SidebarListSubItem";
|
||||
|
||||
const SidebarListItem = ({ item, dispatch, handleDrawerToggle }) => {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<Fragment key={item.key}>
|
||||
<ListItem disablePadding sx={{ display: "block" }}>
|
||||
<ListItemButton
|
||||
selected={item.selected}
|
||||
{...(item.type == "page" && {
|
||||
component: NextLinkComposed,
|
||||
to: {
|
||||
pathname: item.route,
|
||||
},
|
||||
})}
|
||||
onClick={() => {
|
||||
if (item.type == "menu") {
|
||||
dispatch({ type: "COLLAPSE_MENU", key: item.key });
|
||||
}
|
||||
handleDrawerToggle();
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
color: "primary.main",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t(item.key)} />
|
||||
|
||||
{item.type == "menu" &&
|
||||
(item.showSubItem ? <ExpandLess /> : <ExpandMore />)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{item.subItem && (
|
||||
<SidebarListSubItem
|
||||
item={item}
|
||||
handleDrawerToggle={handleDrawerToggle}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarListItem;
|
||||
54
src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx
Normal file
54
src/layouts/dashboardLayouts/sidebar/SidebarListSubItem.jsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextLinkComposed } from "@/core/components/LinkRouting";
|
||||
import InboxIcon from "@mui/icons-material/MoveToInbox";
|
||||
import {
|
||||
Collapse,
|
||||
List,
|
||||
ListItemButton,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Fragment } from "react";
|
||||
|
||||
const SidebarListSubItem = ({ item, handleDrawerToggle }) => {
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<Collapse in={item.showSubItem} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding sx={{ bgcolor: "#f6f6f6" }}>
|
||||
{item.subItem.map((subitem, index) => (
|
||||
<Fragment key={subitem.key}>
|
||||
<ListItemButton
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: subitem.route,
|
||||
}}
|
||||
sx={{
|
||||
minHeight: 48,
|
||||
}}
|
||||
onClick={(event) => {
|
||||
if (item.type == "menu") {
|
||||
dispatch({ type: "COLLAPSE_MENU", key: item.key });
|
||||
}
|
||||
handleDrawerToggle();
|
||||
}}
|
||||
>
|
||||
<ListItemIcon
|
||||
sx={{
|
||||
minWidth: 0,
|
||||
justifyContent: "center",
|
||||
pr: 2,
|
||||
}}
|
||||
>
|
||||
<InboxIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={t(subitem.key)} />
|
||||
</ListItemButton>
|
||||
</Fragment>
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
);
|
||||
};
|
||||
|
||||
export default SidebarListSubItem;
|
||||
46
src/layouts/dashboardLayouts/sidebar/index.jsx
Normal file
46
src/layouts/dashboardLayouts/sidebar/index.jsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Box, Drawer } from "@mui/material";
|
||||
import SidebarDrawer from "./SidebarDrawer";
|
||||
|
||||
const Sidebar = (props) => {
|
||||
return (
|
||||
<Box
|
||||
component="nav"
|
||||
sx={{ width: { md: props.drawerWidth }, flexShrink: { sm: 0 } }}
|
||||
aria-label="mailbox folders"
|
||||
>
|
||||
<Drawer
|
||||
container={props.container}
|
||||
variant="temporary"
|
||||
open={props.mobileOpen}
|
||||
onClose={props.handleDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true,
|
||||
}}
|
||||
sx={{
|
||||
display: { xs: "block", md: "none" },
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle} />
|
||||
</Drawer>
|
||||
<Drawer
|
||||
variant="permanent"
|
||||
sx={{
|
||||
display: { xs: "none", md: "block" },
|
||||
"& .MuiDrawer-paper": {
|
||||
boxSizing: "border-box",
|
||||
width: props.drawerWidth,
|
||||
},
|
||||
}}
|
||||
open
|
||||
>
|
||||
<SidebarDrawer handleDrawerToggle={props.handleDrawerToggle} />
|
||||
</Drawer>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
99
src/lib/app/contexts/language.jsx
Normal file
99
src/lib/app/contexts/language.jsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { FA_DATATABLE_LOCALIZATION } from "&/locales/fa/datatable";
|
||||
import { useRouter } from "next/router";
|
||||
import { createContext, useEffect, useState } from "react";
|
||||
import useUser from "../hooks/useUser";
|
||||
|
||||
export const LanguageContext = createContext();
|
||||
|
||||
export const LanguageProvider = ({ children }) => {
|
||||
const router = useRouter();
|
||||
const languageList = [
|
||||
{
|
||||
key: "fa",
|
||||
dir: "rtl",
|
||||
name: "فارسی",
|
||||
fontFamily: `IRANSans, sans-serif`,
|
||||
tableLocalization: FA_DATATABLE_LOCALIZATION,
|
||||
}
|
||||
];
|
||||
const { user, userChangedLanguage, changeLanguageState } = useUser();
|
||||
const [languageIsReady, setLanguageIsReady] = useState(false);
|
||||
const [languageApp, setLanguageApp] = useState();
|
||||
const [directionApp, setDirectionApp] = useState(
|
||||
process.env.NEXT_PUBLIC_DEFAULT_DIRECTION
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const lang = localStorage.getItem("_language");
|
||||
|
||||
if (!lang && !languageApp) {
|
||||
setLanguageApp(process.env.NEXT_PUBLIC_DEFAULT_LANGUAGE);
|
||||
} else if (lang) {
|
||||
setLanguageApp(lang);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!languageApp) return;
|
||||
|
||||
const lang = localStorage.getItem("_language");
|
||||
|
||||
if (!lang) {
|
||||
localStorage.setItem("_language", languageApp);
|
||||
} else {
|
||||
if (languageApp != lang) {
|
||||
localStorage.setItem("_language", languageApp);
|
||||
}
|
||||
}
|
||||
}, [languageApp]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return;
|
||||
if (user.user_language) {
|
||||
if (user.user_language != languageApp) {
|
||||
setLanguageApp(user.user_language);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (languageApp != router.locale) {
|
||||
router.replace(
|
||||
{ pathname: router.pathname, query: router.query },
|
||||
router.asPath,
|
||||
{
|
||||
locale: languageApp,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const lang of languageList) {
|
||||
if (languageApp != lang.key) continue;
|
||||
setDirectionApp(lang.dir);
|
||||
document.dir = lang.dir;
|
||||
}
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
changeLanguageState(false);
|
||||
setLanguageIsReady(true);
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [router.locale, router.isReady, userChangedLanguage, languageApp]);
|
||||
|
||||
return (
|
||||
<LanguageContext.Provider
|
||||
value={{
|
||||
languageApp,
|
||||
setLanguageApp,
|
||||
directionApp,
|
||||
languageIsReady,
|
||||
setLanguageIsReady,
|
||||
languageList,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LanguageContext.Provider>
|
||||
);
|
||||
};
|
||||
14
src/lib/app/contexts/loading.jsx
Normal file
14
src/lib/app/contexts/loading.jsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import LoadingHardPage from "@/core/components/LoadingHardPage";
|
||||
import { createContext, useState } from "react";
|
||||
|
||||
export const LoadingContext = createContext();
|
||||
|
||||
export const LoadingProvider = ({ children }) => {
|
||||
const [loadingPage, setLoadingPage] = useState(false);
|
||||
return (
|
||||
<LoadingContext.Provider value={{ loadingPage, setLoadingPage }}>
|
||||
<LoadingHardPage loading={loadingPage} />
|
||||
{children}
|
||||
</LoadingContext.Provider>
|
||||
);
|
||||
};
|
||||
144
src/lib/app/contexts/user.jsx
Normal file
144
src/lib/app/contexts/user.jsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import {
|
||||
GET_USER_ROUTE,
|
||||
SET_LANGUAGE
|
||||
} from "@/core/data/apiRoutes";
|
||||
import axios from "axios";
|
||||
import { createContext, useCallback, useEffect, useReducer } from "react";
|
||||
|
||||
const initialUser = {
|
||||
isAuth: false,
|
||||
userChangedLanguage: false,
|
||||
token: null,
|
||||
user: {},
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case "CLEAR_USER":
|
||||
return { ...state, user: {} };
|
||||
case "CHANGE_USER":
|
||||
return { ...state, user: action.user };
|
||||
case "CHANGE_USER_LANGUAGE":
|
||||
return {
|
||||
...state,
|
||||
user: { ...state.user, user_language: action.language },
|
||||
};
|
||||
case "CHANGE_AUTH_STATE":
|
||||
return { ...state, isAuth: action.isAuth };
|
||||
case "CHANGE_LANGUAGE_STATE":
|
||||
return { ...state, userChangedLanguage: action.userChangedLanguage };
|
||||
case "CLEAR_TOKEN":
|
||||
localStorage.removeItem("_token");
|
||||
return { ...state, token: null };
|
||||
case "SET_TOKEN":
|
||||
localStorage.setItem("_token", action.token);
|
||||
return { ...state, token: action.token };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export const UserContext = createContext();
|
||||
|
||||
export const UserProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, initialUser);
|
||||
|
||||
const clearUser = useCallback(() => {
|
||||
dispatch({ type: "CLEAR_USER" });
|
||||
}, []);
|
||||
|
||||
const changeUser = useCallback((user) => {
|
||||
dispatch({ type: "CHANGE_USER", user });
|
||||
}, []);
|
||||
|
||||
const changeUserLanguage = useCallback(
|
||||
(language) => {
|
||||
dispatch({ type: "CHANGE_USER_LANGUAGE", language });
|
||||
axios
|
||||
.post(
|
||||
SET_LANGUAGE,
|
||||
{ language: language },
|
||||
{
|
||||
headers: {
|
||||
authorization: `Bearer ${state.token}`,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
getUser((data) => {
|
||||
changeUser(data);
|
||||
});
|
||||
});
|
||||
},
|
||||
[state.token]
|
||||
);
|
||||
|
||||
const changeAuthState = useCallback((isAuth) => {
|
||||
dispatch({ type: "CHANGE_AUTH_STATE", isAuth });
|
||||
}, []);
|
||||
|
||||
const changeLanguageState = useCallback((userChangedLanguage) => {
|
||||
dispatch({ type: "CHANGE_LANGUAGE_STATE", userChangedLanguage });
|
||||
}, []);
|
||||
|
||||
const clearToken = useCallback(() => {
|
||||
dispatch({ type: "CLEAR_TOKEN" });
|
||||
}, []);
|
||||
|
||||
const setToken = useCallback((token) => {
|
||||
dispatch({ type: "SET_TOKEN", token });
|
||||
}, []);
|
||||
|
||||
const getUser = useCallback(
|
||||
(callback = () => {}) => {
|
||||
axios
|
||||
.get(GET_USER_ROUTE, {
|
||||
headers: { authorization: `Bearer ${state.token}` },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (typeof callback === "function") callback(data);
|
||||
});
|
||||
},
|
||||
[state.token]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const localToken = localStorage.getItem("_token");
|
||||
if (localToken) dispatch({ type: "SET_TOKEN", token: localToken });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.token) {
|
||||
clearUser();
|
||||
changeAuthState(false);
|
||||
changeLanguageState(false);
|
||||
return;
|
||||
}
|
||||
getUser((data) => {
|
||||
changeUser(data);
|
||||
changeAuthState(true);
|
||||
changeLanguageState(true);
|
||||
});
|
||||
}, [state.token]);
|
||||
|
||||
return (
|
||||
<UserContext.Provider
|
||||
value={{
|
||||
isAuth: state.isAuth,
|
||||
userChangedLanguage: state.userChangedLanguage,
|
||||
token: state.token,
|
||||
user: state.user,
|
||||
getUser,
|
||||
clearUser,
|
||||
changeUser,
|
||||
changeUserLanguage,
|
||||
changeAuthState,
|
||||
changeLanguageState,
|
||||
clearToken,
|
||||
setToken,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UserContext.Provider>
|
||||
);
|
||||
};
|
||||
10
src/lib/app/hooks/useDirection.jsx
Normal file
10
src/lib/app/hooks/useDirection.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
import { LanguageContext } from "../contexts/language";
|
||||
|
||||
const useDirection = () => {
|
||||
const { directionApp } = useContext(LanguageContext);
|
||||
|
||||
return { directionApp };
|
||||
};
|
||||
|
||||
export default useDirection;
|
||||
28
src/lib/app/hooks/useLanguage.jsx
Normal file
28
src/lib/app/hooks/useLanguage.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useContext } from "react";
|
||||
import { LanguageContext } from "../contexts/language";
|
||||
import useUser from "./useUser";
|
||||
|
||||
const useLanguage = () => {
|
||||
const { isAuth, changeUserLanguage } = useUser();
|
||||
const {
|
||||
languageApp,
|
||||
setLanguageApp,
|
||||
languageIsReady,
|
||||
setLanguageIsReady,
|
||||
languageList,
|
||||
} = useContext(LanguageContext);
|
||||
|
||||
const changeLanguage = (lang) => {
|
||||
if (lang == languageApp) return;
|
||||
|
||||
setLanguageIsReady(false);
|
||||
setLanguageApp(lang);
|
||||
if (isAuth) {
|
||||
changeUserLanguage(lang);
|
||||
}
|
||||
};
|
||||
|
||||
return { languageApp, changeLanguage, languageIsReady, languageList };
|
||||
};
|
||||
|
||||
export default useLanguage;
|
||||
10
src/lib/app/hooks/useLoading.jsx
Normal file
10
src/lib/app/hooks/useLoading.jsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { useContext } from "react";
|
||||
import { LoadingContext } from "../contexts/loading";
|
||||
|
||||
const useLoading = () => {
|
||||
const { setLoadingPage } = useContext(LoadingContext);
|
||||
|
||||
return { setLoadingPage };
|
||||
};
|
||||
|
||||
export default useLoading;
|
||||
36
src/lib/app/hooks/useUser.jsx
Normal file
36
src/lib/app/hooks/useUser.jsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useContext } from "react";
|
||||
import { UserContext } from "../contexts/user";
|
||||
|
||||
const useUser = () => {
|
||||
const {
|
||||
isAuth,
|
||||
userChangedLanguage,
|
||||
token,
|
||||
user,
|
||||
getUser,
|
||||
clearUser,
|
||||
changeUser,
|
||||
changeUserLanguage,
|
||||
changeAuthState,
|
||||
changeLanguageState,
|
||||
clearToken,
|
||||
setToken,
|
||||
} = useContext(UserContext);
|
||||
|
||||
return {
|
||||
isAuth,
|
||||
userChangedLanguage,
|
||||
token,
|
||||
user,
|
||||
getUser,
|
||||
clearUser,
|
||||
changeUser,
|
||||
changeUserLanguage,
|
||||
changeAuthState,
|
||||
changeLanguageState,
|
||||
clearToken,
|
||||
setToken,
|
||||
};
|
||||
};
|
||||
|
||||
export default useUser;
|
||||
42
src/middlewares/WithAuth.jsx
Normal file
42
src/middlewares/WithAuth.jsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { NextLinkComposed } from "@/core/components/LinkRouting";
|
||||
import Message from "@/core/components/Messages";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import { Button, Typography } from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const WithAuthMiddleware = ({ children }) => {
|
||||
const { isAuth } = useUser();
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
|
||||
if (!isAuth)
|
||||
return (
|
||||
<Message
|
||||
text={
|
||||
<Typography sx={{ textAlign: "center" }}>
|
||||
{t(
|
||||
"typography_your_access_to_this_page_has_expired_Please_login_again"
|
||||
)}
|
||||
</Typography>
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
<Button
|
||||
variant="contained"
|
||||
component={NextLinkComposed}
|
||||
to={{
|
||||
pathname: "/login",
|
||||
query: { back_url: encodeURIComponent(router.asPath) },
|
||||
}}
|
||||
>
|
||||
{t("login")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default WithAuthMiddleware;
|
||||
56
src/middlewares/WithoutAuth.jsx
Normal file
56
src/middlewares/WithoutAuth.jsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import Message from "@/core/components/Messages";
|
||||
import useUser from "@/lib/app/hooks/useUser";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect } from "react";
|
||||
|
||||
const WithoutAuthMiddleware = ({ children }) => {
|
||||
const { isAuth } = useUser();
|
||||
const t = useTranslations();
|
||||
const router = useRouter();
|
||||
|
||||
// gettin url query
|
||||
const searchParams = useSearchParams();
|
||||
const backUrlDecodedPath = searchParams.get("back_url");
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuth) return;
|
||||
const timer = setTimeout(() => {
|
||||
router.replace(
|
||||
backUrlDecodedPath
|
||||
? decodeURIComponent(backUrlDecodedPath)
|
||||
: "/dashboard"
|
||||
);
|
||||
}, 2000);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}, [isAuth]);
|
||||
|
||||
if (isAuth)
|
||||
return (
|
||||
<Message
|
||||
text={
|
||||
<Stack alignItems="center" spacing={2}>
|
||||
<Typography sx={{ textAlign: "center" }}>
|
||||
{t(
|
||||
"typography_your_login_is_valid_and_you_do_not_need_to_login_again"
|
||||
)}
|
||||
</Typography>
|
||||
<Typography>
|
||||
{t("typography_redirect_to")}{" "}
|
||||
{backUrlDecodedPath
|
||||
? t("typography_routing_previuos_page")
|
||||
: t("typography_routing_dashbaord_page")}
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <>{children}</>;
|
||||
};
|
||||
|
||||
export default WithoutAuthMiddleware;
|
||||
30
src/pages/_app.jsx
Normal file
30
src/pages/_app.jsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import "&/fontiran.scss";
|
||||
import AppLayout from "@/layouts/AppLayout";
|
||||
import MuiLayout from "@/layouts/MuiLayout";
|
||||
import { LanguageProvider } from "@/lib/app/contexts/language";
|
||||
import { LoadingProvider } from "@/lib/app/contexts/loading";
|
||||
import { UserProvider } from "@/lib/app/contexts/user";
|
||||
import "moment/locale/fa";
|
||||
import { NextIntlProvider } from "next-intl";
|
||||
|
||||
const App = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<>
|
||||
<UserProvider>
|
||||
<LanguageProvider>
|
||||
<NextIntlProvider messages={pageProps.messages}>
|
||||
<MuiLayout isBot={pageProps.isBot}>
|
||||
<LoadingProvider>
|
||||
<AppLayout isBot={pageProps.isBot}>
|
||||
<Component {...pageProps} />
|
||||
</AppLayout>
|
||||
</LoadingProvider>
|
||||
</MuiLayout>
|
||||
</NextIntlProvider>
|
||||
</LanguageProvider>
|
||||
</UserProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
67
src/pages/_document.jsx
Normal file
67
src/pages/_document.jsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
createEmotionCacheLtr
|
||||
} from "@/core/utils/createEmotionCache";
|
||||
import theme from "@/core/utils/theme";
|
||||
import createEmotionServer from "@emotion/server/create-instance";
|
||||
import Document, { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
export default function MyDocument(props) {
|
||||
const { emotionStyleTags } = props;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<meta name="theme-color" content={theme.palette.primary.main} />
|
||||
<meta name="emotion-insertion-point" content="" />
|
||||
<link rel="shortcut icon" href="/icons/favicon.ico" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
|
||||
/>
|
||||
{emotionStyleTags}
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
|
||||
MyDocument.getInitialProps = async (ctx) => {
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
let cache;
|
||||
switch (ctx.locale) {
|
||||
case "fa":
|
||||
cache = createEmotionCacheLtr();
|
||||
break;
|
||||
}
|
||||
|
||||
const { extractCriticalToChunks } = createEmotionServer(cache);
|
||||
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: (App) =>
|
||||
function EnhanceApp(props) {
|
||||
return <App emotionCache={cache} {...props} />;
|
||||
},
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
|
||||
const emotionStyles = extractCriticalToChunks(initialProps.html);
|
||||
const emotionStyleTags = emotionStyles.styles.map((style) => (
|
||||
<style
|
||||
data-emotion={`${style.key} ${style.ids.join(" ")}`}
|
||||
key={style.key}
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: style.css }}
|
||||
/>
|
||||
));
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
emotionStyleTags,
|
||||
};
|
||||
};
|
||||
23
src/pages/dashboard/index.jsx
Normal file
23
src/pages/dashboard/index.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import DashboardFirstComponent from "@/components/dashboard/first";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
<WithAuthMiddleware>
|
||||
<TitlePage text="dashboard.dashboard_page" />
|
||||
<DashboardFirstComponent />
|
||||
</WithAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
23
src/pages/dashboard/loan-follow-up/index.jsx
Normal file
23
src/pages/dashboard/loan-follow-up/index.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import DashboardLoanFollowUpComponent from "@/components/dashboard/loan-follow-up";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
|
||||
export default function LoanFollowUp() {
|
||||
return (
|
||||
<WithAuthMiddleware>
|
||||
<TitlePage text="dashboard.dashboard_page" />
|
||||
<DashboardLoanFollowUpComponent />
|
||||
</WithAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
23
src/pages/dashboard/loan-request/index.jsx
Normal file
23
src/pages/dashboard/loan-request/index.jsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import DashboardLoanRequestComponent from "@/components/dashboard/loan-request";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
|
||||
export default function LoanRequest() {
|
||||
return (
|
||||
<WithAuthMiddleware>
|
||||
<TitlePage text="dashboard.dashboard_page" />
|
||||
<DashboardLoanRequestComponent />
|
||||
</WithAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
22
src/pages/index.jsx
Normal file
22
src/pages/index.jsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import FirstComponent from "@/components/first";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import { parse } from "next-useragent";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<TitlePage text="first_page" />
|
||||
<FirstComponent />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
28
src/pages/login-navy.jsx
Normal file
28
src/pages/login-navy.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import LoginComponent from "@/components/login-navy";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithoutAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
|
||||
export default function LoginNavy() {
|
||||
const { directionApp } = useDirection();
|
||||
return (
|
||||
<WithoutAuthMiddleware>
|
||||
<TitlePage text="Titles.title_login_navy_page" />
|
||||
<LoginComponent />
|
||||
{directionApp === "rtl" ? <ToastContainer rtl /> : <ToastContainer ltr />}
|
||||
</WithoutAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
28
src/pages/login-welfare-services.jsx
Normal file
28
src/pages/login-welfare-services.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import LoginComponent from "@/components/login-welfare-services";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithoutAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
|
||||
export default function Login() {
|
||||
const { directionApp } = useDirection();
|
||||
return (
|
||||
<WithoutAuthMiddleware>
|
||||
<TitlePage text="Titles.title_login_welfare_services_page" />
|
||||
<LoginComponent />
|
||||
{directionApp === "rtl" ? <ToastContainer rtl /> : <ToastContainer ltr />}
|
||||
</WithoutAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
28
src/pages/register-navy.jsx
Normal file
28
src/pages/register-navy.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import RegisterComponent from "@/components/register-navy";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithoutAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
|
||||
export default function RegisterNavy() {
|
||||
const { directionApp } = useDirection();
|
||||
return (
|
||||
<WithoutAuthMiddleware>
|
||||
<TitlePage text="Titles.title_register_navy_page" />
|
||||
<RegisterComponent />
|
||||
{directionApp === "rtl" ? <ToastContainer rtl /> : <ToastContainer ltr />}
|
||||
</WithoutAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
28
src/pages/register-welfare-services.jsx
Normal file
28
src/pages/register-welfare-services.jsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import RegisterComponent from "@/components/register-welfare-services";
|
||||
import TitlePage from "@/core/components/TitlePage";
|
||||
import WithoutAuthMiddleware from "@/middlewares/WithoutAuth";
|
||||
import { parse } from "next-useragent";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import useDirection from "@/lib/app/hooks/useDirection";
|
||||
|
||||
export default function RegisterWelfareServices() {
|
||||
const { directionApp } = useDirection();
|
||||
return (
|
||||
<WithoutAuthMiddleware>
|
||||
<TitlePage text="Titles.title_register_welfare_services_page" />
|
||||
<RegisterComponent />
|
||||
{directionApp === "rtl" ? <ToastContainer rtl /> : <ToastContainer ltr />}
|
||||
</WithoutAuthMiddleware>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ req, locale }) {
|
||||
const { isBot } = parse(req.headers["user-agent"]);
|
||||
return {
|
||||
props: {
|
||||
messages: (await import(`&/locales/${locale}/app.json`)).default,
|
||||
isBot,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user