diff --git a/.gitignore b/.gitignore index 78b977c..1e62a6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,34 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory node_modules +cypress/videos +cypress/screenshots .next -.env.local +.env* +.env .idea -package-lock.json \ No newline at end of file +package-lock.json +ecosystem.config.js \ No newline at end of file diff --git a/ecosystem.config.js.example b/ecosystem.config.js.example new file mode 100644 index 0000000..983fa1c --- /dev/null +++ b/ecosystem.config.js.example @@ -0,0 +1,18 @@ +module.exports = { + apps: [{ + name: "loan facilities expert", + script: 'node_modules/next/dist/bin/next', // cluster mode run with node only, not npm + args: 'start', + exec_mode: "cluster", // default fork + instances: "max", + kill_timeout: 4000, + wait_ready: true, + autorestart: true, + watch: false, + max_memory_restart: "1G", + log_date_format: "YYYY-MM-DD HH:mm Z", + env_prod: { + APP_ENV: 'prod' // APP_ENV=prod + } + }], +}; \ No newline at end of file diff --git a/example.env.local b/example.env.local index 24d5b8e..b9a7e4c 100644 --- a/example.env.local +++ b/example.env.local @@ -1,5 +1,5 @@ NEXT_PUBLIC_API_NAME = "Loan Facilities Dashboard" -NEXT_PUBLIC_API_VERSION = "1.8.6" +NEXT_PUBLIC_API_VERSION = "1.8.10" NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa" NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl" @@ -8,4 +8,7 @@ NEXT_PUBLIC_SECONDARY_MAIN = "#FF4E00" NEXT_PUBLIC_BASE_URL = "https://loan.witel.ir" +#["NEXT_PUBLIC_HAS_WIDGET" , "NEXT_PUBLIC_HAS_NOTIFICATION"] +NEXT_PUBLIC_HAS_VALUE=["NEXT_PUBLIC_HAS_NOTIFICATION"] + NODE_ENV = "development" \ No newline at end of file diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index f4df724..2a47ea5 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -64,7 +64,7 @@ "typography_your_login_is_valid_and_you_do_not_need_to_login_again": "شما دسترسی لازم به این صفحه را دارید نیاز به ورود مجدد نیست.", "typography_your_access_to_this_page_has_expired_Please_login_again": "دسترسی شما منقضی شده است لطفا دوباره ورود نمایید.", "typography_redirect_to": "درحال رفتن به صفحه", - "typography_routing_previuos_page": "صفحه قبل...", + "typography_routing_previuos_page": " قبل...", "typography_routing_dashbaord_page": "داشبورد..." }, "Permission": { diff --git a/src/components/dashboard/change-password/change-password-form/index.jsx b/src/components/dashboard/change-password/change-password-form/index.jsx new file mode 100644 index 0000000..838b16d --- /dev/null +++ b/src/components/dashboard/change-password/change-password-form/index.jsx @@ -0,0 +1,140 @@ +import {Formik} from "formik"; +import {useTranslations} from "next-intl"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; +import {Button, Paper, Stack, Typography} from "@mui/material"; +import * as Yup from "yup"; +import StyledForm from "@/core/components/StyledForm"; +import PasswordField from "@/core/components/PasswordField"; + +const ChangePasswordForm = ({onSubmit}) => { + const t = useTranslations(); + const requestServer = useRequest({auth: true}) + + const handleSubmit = (values, {setSubmitting, resetForm}) => { + requestServer(SET_USER_PASSWORD, 'post', { + data: { + current_password: values.current_password, + new_password: values.new_password, + new_password_confirmation: values.new_password_confirmation, + }, + }).then((response) => { + resetForm(); + }).catch(() => { + }).finally(() => { + setSubmitting(false); + }); + }; + const initialValues = { + current_password: "", + new_password: "", + new_password_confirmation: "", + }; + const validationSchema = Yup.object().shape({ + current_password: Yup.string().required( + t("ChangePassword.error_message_current_password") + ), + new_password: Yup.string() + .required(t("ExpertMangement.error_message_new_password")) + .matches( + /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, + t("ExpertMangement.error_message_new_password_regex") + ), + new_password_confirmation: Yup.string() + .required(t("ChangePassword.error_message_confirm_password")) + .test( + t("ChangePassword.error_message_password_match"), + t("ChangePassword.error_message_password_not_match"), + function (value) { + return this.parent.new_password === value; + } + ), + }); + + return ( + + {(props) => ( + { + e.preventDefault(); + props.handleSubmit(); + }} + > + + + + {t("ChangePassword.typography_change_password")} + + + + + + + + + + + + + + + )} + + ); +}; + +export default ChangePasswordForm; \ No newline at end of file diff --git a/src/components/dashboard/change-password/index.jsx b/src/components/dashboard/change-password/index.jsx index 07213e6..b6337d4 100644 --- a/src/components/dashboard/change-password/index.jsx +++ b/src/components/dashboard/change-password/index.jsx @@ -1,149 +1,14 @@ import CenterLayout from "@/layouts/CenterLayout"; import DashboardLayouts from "@/layouts/dashboardLayouts"; -import {Button, Container, Paper, Stack, Typography,} from "@mui/material"; -import {Formik} from "formik"; -import * as Yup from "yup"; -import {useTranslations} from "next-intl"; -import PasswordField from "@/core/components/PasswordField"; -import StyledForm from "@/core/components/StyledForm"; -import {SET_USER_PASSWORD} from "@/core/data/apiRoutes"; -import SvgChangePassword from "@/core/components/svgs/SvgChangePassword"; -import useRequest from "@/lib/app/hooks/useRequest"; +import {Container} from "@mui/material"; +import ChangePasswordForm from "@/components/dashboard/change-password/change-password-form"; const DashboardChangePasswordComponent = () => { - const t = useTranslations(); - const requestServer = useRequest({auth: true}) - - const handleSubmit = (values, {setSubmitting, resetForm}) => { - requestServer(SET_USER_PASSWORD, 'post', { - data: { - current_password: values.current_password, - new_password: values.new_password, - new_password_confirmation: values.new_password_confirmation, - }, - }).then((response) => { - resetForm(); - }).catch(() => { - }).finally(() => { - setSubmitting(false); - }); - }; - const initialValues = { - current_password: "", - new_password: "", - new_password_confirmation: "", - }; - const validationSchema = Yup.object().shape({ - current_password: Yup.string().required( - t("ChangePassword.error_message_current_password") - ), - new_password: Yup.string() - .required(t("ExpertMangement.error_message_new_password")) - .matches( - /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/, - t("ExpertMangement.error_message_new_password_regex") - ), - new_password_confirmation: Yup.string() - .required(t("ChangePassword.error_message_confirm_password")) - .test( - t("ChangePassword.error_message_password_match"), - t("ChangePassword.error_message_password_not_match"), - function (value) { - return this.parent.new_password === value; - } - ), - }); - return ( - - {(props) => ( - { - e.preventDefault(); - props.handleSubmit(); - }} - > - - - - - {t("ChangePassword.typography_change_password")} - - - - - - - - - - - - - - - )} - + diff --git a/src/core/components/Middleware/RolePermissionComponent.jsx b/src/core/components/Middleware/RolePermissionComponent.jsx new file mode 100644 index 0000000..a445008 --- /dev/null +++ b/src/core/components/Middleware/RolePermissionComponent.jsx @@ -0,0 +1,33 @@ +import { Button, Typography } from "@mui/material"; +import {useTranslations} from "next-intl"; +import {NextLinkComposed} from "@/core/components/LinkRouting"; +import Message from "@/core/components/Messages"; + +const RolePermissionComponent = () => { + const t = useTranslations(); + + return ( + + {t("Permission.typography_you_dont_have_access")} + + } + actions={ + <> + + + } + /> + ); +}; + +export default RolePermissionComponent; diff --git a/src/core/components/Middleware/WithAuthComponent.jsx b/src/core/components/Middleware/WithAuthComponent.jsx new file mode 100644 index 0000000..9a6ff5d --- /dev/null +++ b/src/core/components/Middleware/WithAuthComponent.jsx @@ -0,0 +1,36 @@ +import { Button, Typography } from "@mui/material"; +import { useRouter } from "next/router"; +import Message from "@/core/components/Messages"; +import { NextLinkComposed } from "@/core/components/LinkRouting"; +import {useTranslations} from "next-intl"; + +const WithAuthComponent = () => { + const router = useRouter(); + const t = useTranslations(); + + return ( + + {t("Authorization.typography_your_access_to_this_page_has_expired_Please_login_again")} + + } + actions={ + <> + + + } + /> + ); +}; + +export default WithAuthComponent; diff --git a/src/core/components/Middleware/WithoutAuthComponent.jsx b/src/core/components/Middleware/WithoutAuthComponent.jsx new file mode 100644 index 0000000..59b7cbf --- /dev/null +++ b/src/core/components/Middleware/WithoutAuthComponent.jsx @@ -0,0 +1,25 @@ +import {Stack, Typography} from "@mui/material"; +import Message from "@/core/components/Messages"; +import {useTranslations} from "next-intl"; +const WithoutAuthComponent = ({ backUrlDecodedPath }) => { + const t = useTranslations(); + + return ( + + + {t("Authorization.typography_your_login_is_valid_and_you_do_not_need_to_login_again")} + + + {t("Authorization.typography_redirect_to")}{" "} + {backUrlDecodedPath + ? t("Authorization.typography_routing_previuos_page") + : t("Authorization.typography_routing_dashbaord_page")} + + + } + /> + ); +}; +export default WithoutAuthComponent; diff --git a/src/lib/app/hooks/useNotification.jsx b/src/lib/app/hooks/useNotification.jsx index e32f829..8685227 100644 --- a/src/lib/app/hooks/useNotification.jsx +++ b/src/lib/app/hooks/useNotification.jsx @@ -1,8 +1,12 @@ import useSWR from 'swr' import {GET_SIDEBAR_NOTIFICATION} from "@/core/data/apiRoutes"; import useRequest from "@/lib/app/hooks/useRequest"; +const GLOBAL_HAS_VALUE = process.env.NEXT_PUBLIC_HAS_VALUE; +const hasPermissionsValue = GLOBAL_HAS_VALUE ? JSON.parse(GLOBAL_HAS_VALUE) : []; +const isNotificationEnabled = hasPermissionsValue.includes("NEXT_PUBLIC_HAS_NOTIFICATION"); const useNotification = () => { + const requestServer = useRequest({auth: true, notification: false}) //swr config @@ -13,7 +17,7 @@ const useNotification = () => { }) }; - const {data, mutate} = useSWR(GET_SIDEBAR_NOTIFICATION, fetcher) + const {data, mutate} = useSWR( isNotificationEnabled ? GET_SIDEBAR_NOTIFICATION : '', fetcher , {keepPreviousData:true}) const notification_count = data //swr config diff --git a/src/middlewares/RolePermission.jsx b/src/middlewares/RolePermission.jsx index 67fc3f1..4b1cc8a 100644 --- a/src/middlewares/RolePermission.jsx +++ b/src/middlewares/RolePermission.jsx @@ -1,43 +1,14 @@ -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 RolePermissionComponent from "@/core/components/Middleware/RolePermissionComponent"; const RolePermissionMiddleware = ({children, requiredPermissions}) => { const {user} = useUser(); - const t = useTranslations(); const hasPermission = requiredPermissions.some((permission) => user?.permissions?.includes(permission) ); - if (!hasPermission) { - return ( - - {t("Permission.typography_you_dont_have_access")} - - } - actions={ - <> - - - } - /> - ); - } - - return <>{children}; + return !hasPermission ? : <>{children}; }; export default RolePermissionMiddleware; diff --git a/src/middlewares/WithAuth.jsx b/src/middlewares/WithAuth.jsx index b20d8b3..4f55660 100644 --- a/src/middlewares/WithAuth.jsx +++ b/src/middlewares/WithAuth.jsx @@ -1,42 +1,10 @@ -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"; +import WithAuthComponent from "@/core/components/Middleware/WithAuthComponent"; const WithAuthMiddleware = ({children}) => { const {isAuth} = useUser(); - const t = useTranslations(); - const router = useRouter(); - if (!isAuth) - return ( - - {t( - "Authorization.typography_your_access_to_this_page_has_expired_Please_login_again" - )} - - } - actions={ - <> - - - } - /> - ); - return <>{children}; + return isAuth ? <>{children} : ; }; export default WithAuthMiddleware; diff --git a/src/middlewares/WithoutAuth.jsx b/src/middlewares/WithoutAuth.jsx index 594ffc5..80169c8 100644 --- a/src/middlewares/WithoutAuth.jsx +++ b/src/middlewares/WithoutAuth.jsx @@ -1,19 +1,14 @@ -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"; +import WithoutAuthComponent from "@/core/components/Middleware/WithoutAuthComponent"; const WithoutAuthMiddleware = ({children}) => { + const {isAuth} = useUser(); - const t = useTranslations(); const router = useRouter(); - // gettin url query - const searchParams = useSearchParams(); - const backUrlDecodedPath = searchParams.get("back_url"); + const backUrlDecodedPath = router.query?.back_url; useEffect(() => { if (!isAuth) return; @@ -30,27 +25,11 @@ const WithoutAuthMiddleware = ({children}) => { }; }, [isAuth]); - if (isAuth) - return ( - - - {t( - "Authorization.typography_your_login_is_valid_and_you_do_not_need_to_login_again" - )} - - - {t("Authorization.typography_redirect_to")}{" "} - {backUrlDecodedPath - ? t("Authorization.typography_routing_previuos_page") - : t("Authorization.typography_routing_dashbaord_page")} - - - } - /> - ); - return <>{children}; + return isAuth ? ( + + ) : ( + <>{children} + ); }; export default WithoutAuthMiddleware;