diff --git a/example.env.local b/example.env.local index 554476c..0883de5 100644 --- a/example.env.local +++ b/example.env.local @@ -1,11 +1,11 @@ NEXT_PUBLIC_API_NAME = "Loan Facilities Dashboard" -NEXT_PUBLIC_API_VERSION = "1.7.1" +NEXT_PUBLIC_API_VERSION = "1.7.3" NEXT_PUBLIC_DEFAULT_LANGUAGE = "fa" NEXT_PUBLIC_DEFAULT_DIRECTION = "rtl" NEXT_PUBLIC_PRIMARY_MAIN = "#084070" NEXT_PUBLIC_SECONDARY_MAIN = "#FF4E00" -NEXT_PUBLIC_BASE_URL = "deploy api" +NEXT_PUBLIC_BASE_URL = "https://loan.witel.ir" NODE_ENV = "development" \ No newline at end of file diff --git a/public/icons/favicon.png b/public/icons/favicon.png new file mode 100644 index 0000000..c82bf9a Binary files /dev/null and b/public/icons/favicon.png differ diff --git a/public/locales/fa/app.json b/public/locales/fa/app.json index 435a0c7..0c9d741 100644 --- a/public/locales/fa/app.json +++ b/public/locales/fa/app.json @@ -15,6 +15,8 @@ "Resend_code_in": "ارسال مجدد کد در", "seconds_later": "ثانیه دیگر", "upload_file_text": "بارگذاری فایل", + "online_message": "شما به اینترنت وصل هستید", + "offline_message": "اتصال شما به اینترنت قطع شده است", "header": { "open_profile": "پروفایل", "edit_profile": "ویرایش پروفایل", @@ -111,7 +113,8 @@ "loan_follow_up_page": "پیگیری درخواست", "loan_request_correction": "اصلاح درخواست", "loan_request_detail": "مشاهده جزئیات", - "request_number": "درخواست شماره" + "request_number": "درخواست شماره", + "no_request_found": "درخواستی یافت نشد" }, "LoanRequest": { "loan_request_page": "ثبت درخواست وام", @@ -122,8 +125,8 @@ "error_message_vehicle_type": "لطفا نوع وسیله نقلیه خود را وارد کنید!", "error_message_plate_number": "لطفا شماره پلاک خود را وارد کنید!", "error_message_navgan_id": "لطفا کد ناوگان خود را وارد کنید!", - "error_message_province": "لطفا استان خود را وارد کنید!", - "error_message_national_code": "لطفا کد ملی خود را وارد کنید!", + "error_message_province_id": "لطفا استان خود را وارد کنید!", + "error_message_national_id": "لطفا کد ملی خود را وارد کنید!", "error_message_shenasname_id": "لطفا شماره شناسنامه خود را وارد کنید!", "text_field_phone_number": "شماره تلفن", "text_field_enter_your_phone_number": "شماره تلفن خود را وارد کنید", @@ -131,12 +134,14 @@ "text_field_enter_your_name": "نام خود را وارد کنید", "text_field_vehicle_type": "نام وسیله نقلیه", "text_field_enter_your_vehicle_type": "نام وسیله نقلیه خود را وارد کنید", - "text_field_province": "استان", - "text_field_enter_your_province": "استان خود را وارد کنید", + "text_field_province_id": "استان", + "text_field_loading_provinces_list": "درحال دریافت استان ها", + "text_field_error_fetching_provinces": "خطا در دریافت استان ها", + "text_field_enter_your_province_id": "استان خود را وارد کنید", "text_field_navgan_id": "شماره ناوگان", "text_field_enter_your_navgan_id": "شماره ناوگان خود را وارد کنید", - "text_field_national_code": "کد ملی", - "text_field_enter_your_national_code": "کد ملی خود را وارد کنید", + "text_field_national_id": "کد ملی", + "text_field_enter_your_national_id": "کد ملی خود را وارد کنید", "text_field_shenasname_id": "شماره شناسنامه", "text_field_enter_your_shenasname_id": "شماره شناسنامه خود را وارد کنید", "text_field_plate_number": "شماره پلاک", @@ -153,7 +158,8 @@ "file_field_payan_khedmat_image": "تصویر کارت پایان خدمت", "error_message_national_card_image": "تصویر کارت ملی خود را وارد کنید", "error_message_shenasname_image": "تصویر شناسنامه خود را وارد کنید", - "error_message_payan_khedmat_image": "تصویر کارت پایان خدمت خود را وارد کنید" + "error_message_payan_khedmat_image": "تصویر کارت پایان خدمت خود را وارد کنید", + "add_loan_request_permission": "اکنون امکان ثبت درخواست جدید برای شما مقدور نیست" }, "EditLoanRequest": { "edit_loan_request_page": "ویرایش درخواست وام" diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..32bacd5 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,57 @@ +{ + "short_name": "Loan Facilities Dashboard", + "name": "Loan Facilities User Dashboard", + "description": "Loan Facilities", + "version": "1.0.0", + "version_name": "1.0.0 beta", + "start_url": "/dashboard", + "orientation": "portrait", + "display": "standalone", + "theme_color": "#084070", + "background_color": "#084070", + "icons": [ + { + "src": "/icons/maskable_icon.png", + "sizes": "any", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x48.png", + "sizes": "48x48", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x72.png", + "sizes": "72x72", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x96.png", + "sizes": "96x96", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x128.png", + "sizes": "128x128", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "/icons/maskable_icon_x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "categories": [ + "government" + ] +} diff --git a/src/components/dashboard/refahi/add-request-loan/accessibility/index.jsx b/src/components/dashboard/refahi/add-request-loan/accessibility/index.jsx new file mode 100644 index 0000000..3618d0b --- /dev/null +++ b/src/components/dashboard/refahi/add-request-loan/accessibility/index.jsx @@ -0,0 +1,32 @@ +import {Button, Typography} from "@mui/material"; +import {useTranslations} from "next-intl"; +import CenterLayout from "@/layouts/CenterLayout"; +import {useRouter} from "next/router"; +import {NextLinkComposed} from "@/core/components/LinkRouting"; + +const AccessibilityComponent = () => { + const t = useTranslations(); + const router = useRouter(); + + return ( + + + {t("LoanRequest.add_loan_request_permission")} + + + + ); +}; + +export default AccessibilityComponent; diff --git a/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx b/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx index 06a40fc..4051854 100644 --- a/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx +++ b/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx @@ -1,46 +1,39 @@ import StyledForm from "@/core/components/StyledForm"; import UploadSystem from "@/core/components/UploadSystem"; -import {GET_PROVINCE_LIST, SEND_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes"; -import useDirection from "@/lib/app/hooks/useDirection"; -import useUser from "@/lib/app/hooks/useUser"; +import {SEND_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes"; import DataSaverOnIcon from "@mui/icons-material/DataSaverOn"; -import {Box, Button, Chip, Divider, Stack, TextField} from "@mui/material"; +import { + Box, + Button, + Chip, + Divider, + FormControl, + FormHelperText, + InputLabel, + MenuItem, + Select, + Stack, + TextField +} from "@mui/material"; import {Field, Formik} from "formik"; import {useTranslations} from "next-intl"; -import {useEffect, useState} from "react"; import * as Yup from "yup"; -import SelectBox from "@/core/components/SelectBox"; import useRequest from "@/lib/app/hooks/useRequest"; +import useProvince from "@/lib/app/hooks/useProvince"; +import {useRouter} from "next/router"; const AddFormComponent = () => { const t = useTranslations(); - const {directionApp} = useDirection(); - const {token} = useUser(); const requestServer = useRequest() - - const [provinceList, setProvinceList] = useState([]); - - // get province list - useEffect(() => { - requestServer(GET_PROVINCE_LIST, "get", {auth: true, notification: false}) - .then(function ({data}) { - const formattedData = data.map((province, index) => ({ - id: index, - name: province.name, - value: province.id, - })); - setProvinceList(formattedData); - }).catch(() => { - }) - }, []); - // end get province list + const {provinceList, isLoadingProvinceList, errorProvinceList} = useProvince(); + const router = useRouter(); // initial values, validation and request action of form const initialValues = { name: "", phone_number: "", - province: "", - national_code: "", + province_id: "", + national_id: "", shenasname_id: "", sherkat_naft_doc: null, estate_doc: null, @@ -55,9 +48,9 @@ const AddFormComponent = () => { phone_number: Yup.string().required( t("LoanRequest.error_message_phone_number") ), - province: Yup.string().required(t("LoanRequest.error_message_province")), - national_code: Yup.string().required( - t("LoanRequest.error_message_national_code") + province_id: Yup.string().required(t("LoanRequest.error_message_province_id")), + national_id: Yup.string().required( + t("LoanRequest.error_message_national_id") ), shenasname_id: Yup.string().required( t("LoanRequest.error_message_shenasname_id") @@ -73,8 +66,8 @@ const AddFormComponent = () => { const formData = new FormData(); formData.append("name", values.name); formData.append("phone_number", values.phone_number); - formData.append("province_id", values.province); - formData.append("national_id", values.national_code); + formData.append("province_id", values.province_id); + formData.append("national_id", values.national_id); formData.append("shenasname_id", values.shenasname_id); if (values.sherkat_naft_doc != null) formData.append("sherkat_naft_doc", values.sherkat_naft_doc); @@ -90,6 +83,7 @@ const AddFormComponent = () => { formData.append("payan_khedmat_image", values.payan_khedmat_image); requestServer(SEND_LOAN_REQUEST_WELFARE, "post", {auth: true, data: formData}) .then(function (response) { + router.replace('/dashboard/refahi/followUp-loan'); }) .catch(function (error) { }) @@ -183,27 +177,48 @@ const AddFormComponent = () => { - + > + {t("LoanRequest.text_field_province_id")} + + + {props.touched.province_id && props.errors.province_id ? props.errors.province_id : ""} + + { diff --git a/src/components/dashboard/refahi/add-request-loan/index.jsx b/src/components/dashboard/refahi/add-request-loan/index.jsx index feb8df0..bc82ac7 100644 --- a/src/components/dashboard/refahi/add-request-loan/index.jsx +++ b/src/components/dashboard/refahi/add-request-loan/index.jsx @@ -1,16 +1,15 @@ import DashboardLayouts from "@/layouts/dashboardLayouts"; import useUser from "@/lib/app/hooks/useUser"; -import {Box, Chip, Divider} from "@mui/material"; import {useTranslations} from "next-intl"; import AddFormComponent from "@/components/dashboard/refahi/add-request-loan/forms/AddForm"; +import AccessibilityComponent from "@/components/dashboard/refahi/add-request-loan/accessibility"; const LoanRequestComponent = () => { const t = useTranslations(); const {user} = useUser(); - return ( - + {user.can_request_a_new_loan ? : } ); }; diff --git a/src/components/dashboard/refahi/edit-request-loan/forms/EditForm.jsx b/src/components/dashboard/refahi/edit-request-loan/forms/EditForm.jsx index 9ea22c2..36f5aa0 100644 --- a/src/components/dashboard/refahi/edit-request-loan/forms/EditForm.jsx +++ b/src/components/dashboard/refahi/edit-request-loan/forms/EditForm.jsx @@ -1,72 +1,23 @@ import StyledForm from "@/core/components/StyledForm"; import UploadSystem from "@/core/components/UploadSystem"; -import {GET_PROVINCE_LIST, UPDATE_LOAN_REQUEST_WELFARE, DETAILS_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes"; +import {UPDATE_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes"; import useDirection from "@/lib/app/hooks/useDirection"; import useUser from "@/lib/app/hooks/useUser"; import DataSaverOnIcon from "@mui/icons-material/DataSaverOn"; import {Box, Button, Chip, Divider, Stack, TextField} from "@mui/material"; -import axios from "axios"; import {Field, Formik} from "formik"; import {useTranslations} from "next-intl"; -import {useEffect, useMemo, useState} from "react"; import * as Yup from "yup"; import SelectBox from "@/core/components/SelectBox"; import useRequest from "@/lib/app/hooks/useRequest"; import {useRouter} from "next/router"; import useLoading from "@/lib/app/hooks/useLoading"; -const EditFormComponent = () => { +const EditFormComponent = ({provinceList, initialValues}) => { const t = useTranslations(); - const {directionApp} = useDirection(); - const {token} = useUser(); const requestServer = useRequest(); const router = useRouter(); - const {setLoadingPage} = useLoading(); - const [provinceList, setProvinceList] = useState(null); - const [initialValues, setInitialValues] = useState(null); - - // get province list - useEffect(() => { - setLoadingPage(true) - requestServer(GET_PROVINCE_LIST, "get", {auth: true, notification: false}) - .then(function ({data}) { - const formattedData = data.map((province, index) => ({ - id: index, - name: province.name, - value: province.id, - })); - setProvinceList(formattedData); - }).catch(() => { - }); - }, []); - - - // get form data - useEffect(() => { - if (!provinceList) return; - requestServer(DETAILS_LOAN_REQUEST_WELFARE + router.query.id, "get", {auth: true, notification: false}) - .then(function ({data}) { - const item = data.data; - const formattedData = { - name: item.name, - phone_number: item.phone_number, - province_id: item.province_id, - national_id: item.national_id, - shenasname_id: item.shenasname_id, - sherkat_naft_doc: item.sherkat_naft_doc, - estate_doc: item.estate_doc, - agreement_doc: item.agreement_doc, - national_card_image: item.national_card_image, - shenasname_image: item.shenasname_image, - payan_khedmat_image: item.payan_khedmat_image, - } - setInitialValues(formattedData); - setLoadingPage(false) - }) - .catch(() => { - }) - }, [provinceList]); // validation and request action of form const validationSchema = Yup.object().shape({ @@ -74,8 +25,8 @@ const EditFormComponent = () => { phone_number: Yup.string().required( t("LoanRequest.error_message_phone_number") ), - province_id: Yup.string().required(t("LoanRequest.error_message_province")), - national_id: Yup.string().required(t("LoanRequest.error_message_national_code")), + province_id: Yup.string().required(t("LoanRequest.error_message_province_id")), + national_id: Yup.string().required(t("LoanRequest.error_message_national_id")), shenasname_id: Yup.string().required(t("LoanRequest.error_message_shenasname_id")), sherkat_naft_doc: Yup.mixed().required(t("LoanRequest.error_message_sherkat_naft_doc")), estate_doc: Yup.mixed().required(t("LoanRequest.error_message_estate_doc")), @@ -205,7 +156,7 @@ const EditFormComponent = () => { > { name="national_id" variant="outlined" size="small" - label={t("LoanRequest.text_field_national_code")} + label={t("LoanRequest.text_field_national_id")} placeholder={t( - "LoanRequest.text_field_enter_your_national_code" + "LoanRequest.text_field_enter_your_national_id" )} type={"text"} error={ @@ -307,7 +258,7 @@ const EditFormComponent = () => { setFieldValue={props.setFieldValue} default_image={initialValues.sherkat_naft_doc} fieldName="sherkat_naft_doc" - imageAlt={t("app_name")} + imageAlt={t("LoanRequest.file_field_sherkat_naft_doc")} label={t("LoanRequest.file_field_sherkat_naft_doc")} enableDelete={false} error={ @@ -326,7 +277,7 @@ const EditFormComponent = () => { setFieldValue={props.setFieldValue} default_image={initialValues.estate_doc} fieldName="estate_doc" - imageAlt={t("app_name")} + imageAlt={t("LoanRequest.file_field_estate_doc")} label={t("LoanRequest.file_field_estate_doc")} enableDelete={false} error={ @@ -345,7 +296,7 @@ const EditFormComponent = () => { setFieldValue={props.setFieldValue} default_image={initialValues.agreement_doc} fieldName="agreement_doc" - imageAlt={t("app_name")} + imageAlt={t("LoanRequest.file_field_agreement_doc")} label={t("LoanRequest.file_field_agreement_doc")} enableDelete={false} error={ @@ -364,7 +315,7 @@ const EditFormComponent = () => { setFieldValue={props.setFieldValue} default_image={initialValues.shenasname_image} fieldName="shenasname_image" - imageAlt={t("app_name")} + imageAlt={t("LoanRequest.file_field_shenasname_image")} label={t("LoanRequest.file_field_shenasname_image")} enableDelete={false} error={ @@ -383,7 +334,7 @@ const EditFormComponent = () => { setFieldValue={props.setFieldValue} default_image={initialValues.payan_khedmat_image} fieldName="payan_khedmat_image" - imageAlt={t("app_name")} + imageAlt={t("LoanRequest.file_field_payan_khedmat_image")} label={t("LoanRequest.file_field_payan_khedmat_image")} enableDelete={false} error={ diff --git a/src/components/dashboard/refahi/edit-request-loan/index.jsx b/src/components/dashboard/refahi/edit-request-loan/index.jsx index 4be62ff..89de937 100644 --- a/src/components/dashboard/refahi/edit-request-loan/index.jsx +++ b/src/components/dashboard/refahi/edit-request-loan/index.jsx @@ -1,16 +1,80 @@ import DashboardLayouts from "@/layouts/dashboardLayouts"; -import useUser from "@/lib/app/hooks/useUser"; -import {useTranslations} from "next-intl"; import EditFormComponent from "@/components/dashboard/refahi/edit-request-loan/forms/EditForm"; +import {useEffect, useState} from "react"; +import {DETAILS_LOAN_REQUEST_WELFARE, GET_PROVINCE_LIST} from "@/core/data/apiRoutes"; +import useLoading from "@/lib/app/hooks/useLoading"; +import useRequest from "@/lib/app/hooks/useRequest"; +import {useRouter} from "next/router"; +import {log} from "next/dist/server/typescript/utils"; const LoanRequestComponent = () => { - const t = useTranslations(); - const {user} = useUser(); + const requestServer = useRequest(); + const {setLoadingPage} = useLoading(); + const router = useRouter(); + + const [provinceList, setProvinceList] = useState(null); + const [initialValues, setInitialValues] = useState(null); + + // get province list + useEffect(() => { + setLoadingPage(true); + requestServer(GET_PROVINCE_LIST, "get", {auth: true, notification: false}) + .then(function ({data}) { + const formattedData = data.map((province, index) => ({ + id: index, + name: province.name, + value: province.id, + })); + setProvinceList(formattedData); + }).catch(() => { + setLoadingPage(false); + }); + }, []); + + // get form data + useEffect(() => { + if (!provinceList) return; + setLoadingPage(true); + requestServer(DETAILS_LOAN_REQUEST_WELFARE + router.query.id, "get", {auth: true, notification: false}) + .then(function ({data}) { + const item = data.data; + const formattedData = { + name: item.name, + phone_number: item.phone_number, + province_id: item.province_id, + national_id: item.national_id, + shenasname_id: item.shenasname_id, + sherkat_naft_doc: item.sherkat_naft_doc, + estate_doc: item.estate_doc, + agreement_doc: item.agreement_doc, + national_card_image: item.national_card_image, + shenasname_image: item.shenasname_image, + payan_khedmat_image: item.payan_khedmat_image, + state_id: item.state_id + }; + setInitialValues(formattedData); + }) + .catch(() => { + setLoadingPage(false); + }); + }, [provinceList, router]); + + // Redirect to /dashboard/refahi/followUp-loan if state_id is not 7 + useEffect(() => { + if (!initialValues) return + if (initialValues.state_id === 7) return + + router.replace('/dashboard/refahi/followUp-loan'); + }, [initialValues]); return ( - - - + <> + {initialValues && initialValues.state_id === 7 && ( + + + + )} + ); }; diff --git a/src/components/dashboard/refahi/followUp-loan/index.jsx b/src/components/dashboard/refahi/followUp-loan/index.jsx index 331bab0..32b121d 100644 --- a/src/components/dashboard/refahi/followUp-loan/index.jsx +++ b/src/components/dashboard/refahi/followUp-loan/index.jsx @@ -1,19 +1,18 @@ import DashboardLayouts from "@/layouts/dashboardLayouts"; -import { - Grid, - Stack, -} from "@mui/material"; +import {Grid, Stack, Typography,} from "@mui/material"; import {useEffect, useState} from "react"; import {SHOW_LOAN_REQUEST_WELFARE} from "@/core/data/apiRoutes"; import useLoading from "@/lib/app/hooks/useLoading"; import useRequest from "@/lib/app/hooks/useRequest"; import moment from "jalali-moment"; import RequestCard from "@/components/dashboard/refahi/followUp-loan/RequestCard"; +import {useTranslations} from "next-intl"; const LoanFollowUpComponent = () => { + const t = useTranslations() const requestServer = useRequest(); const {setLoadingPage} = useLoading(); - const [requestsList, setRequestsList] = useState([]); + const [requestsList, setRequestsList] = useState(null); // get form data useEffect(() => { setLoadingPage(true) @@ -33,28 +32,23 @@ const LoanFollowUpComponent = () => { }) }, []); - return ( - - + + {requestsList == null ? '' : requestsList.length ? ( - - {requestsList.map((item, index) => ( - - ))} - - - - ); + {requestsList.map((item, index) => ())} + ) : ({t('LoanFollowUp.no_request_found')})} + + ); }; export default LoanFollowUpComponent; diff --git a/src/core/components/GlobalHead.jsx b/src/core/components/GlobalHead.jsx new file mode 100644 index 0000000..5d6186d --- /dev/null +++ b/src/core/components/GlobalHead.jsx @@ -0,0 +1,58 @@ +import Head from "next/head"; + +const GlobalHead = () => { + return ( + + + + + + + + + + + + + + + + + + + + + ) +} + +export default GlobalHead \ No newline at end of file diff --git a/src/core/components/NetworkComponent.jsx b/src/core/components/NetworkComponent.jsx new file mode 100644 index 0000000..8ebbb68 --- /dev/null +++ b/src/core/components/NetworkComponent.jsx @@ -0,0 +1,34 @@ +import useNetwork from "@/lib/app/hooks/useNetwork"; +import {useEffect, useRef} from "react"; +import {toast} from "react-toastify"; +import WifiIcon from '@mui/icons-material/Wifi'; +import WifiOffIcon from '@mui/icons-material/WifiOff'; +import {useTranslations} from "next-intl"; + +const NetworkComponent = () => { + const toastId = useRef(null); + const network = useNetwork() + const t = useTranslations() + + useEffect(() => { + if (network.online) { + toast.update(toastId.current, { + type: toast.TYPE.SUCCESS, + render: t('online_message'), + autoClose: 2000, + closeButton: true, + closeOnClick: true, + icon: + }); + return + } + toast.dismiss() + toastId.current = toast.warn(t('offline_message'), { + autoClose: false, closeButton: false, closeOnClick: false, icon: + }) + }, [network.online]); + + return '' +} + +export default NetworkComponent \ No newline at end of file diff --git a/src/core/components/notifications/ErrorNotification.jsx b/src/core/components/notifications/ErrorNotification.jsx index 4c2e857..05e2fb9 100644 --- a/src/core/components/notifications/ErrorNotification.jsx +++ b/src/core/components/notifications/ErrorNotification.jsx @@ -4,7 +4,7 @@ import {toast} from "react-toastify"; const ErrorNotification = (t, status, message) => { toast( - ({closeToast}) => ( + () => ( <> { ); }; -export default ErrorNotification; +export default ErrorNotification; \ No newline at end of file diff --git a/src/core/components/notifications/PendingNotification.jsx b/src/core/components/notifications/PendingNotification.jsx index f3a0af4..6183f6b 100644 --- a/src/core/components/notifications/PendingNotification.jsx +++ b/src/core/components/notifications/PendingNotification.jsx @@ -3,9 +3,10 @@ import {toast} from "react-toastify"; const PendingNotification = (t) => { toast(t("notifications.pending"), { autoClose: false, + closeButton: false, closeOnClick: false, draggable: false, }); }; -export default PendingNotification; +export default PendingNotification; \ No newline at end of file diff --git a/src/core/components/notifications/SuccessNotification.jsx b/src/core/components/notifications/SuccessNotification.jsx index 998deaa..3507cef 100644 --- a/src/core/components/notifications/SuccessNotification.jsx +++ b/src/core/components/notifications/SuccessNotification.jsx @@ -39,4 +39,4 @@ const SuccessNotification = (t, status) => { ); }; -export default SuccessNotification; +export default SuccessNotification; \ No newline at end of file diff --git a/src/core/components/notifications/WarningNotification.jsx b/src/core/components/notifications/WarningNotification.jsx index 5e931f0..19d6a2f 100644 --- a/src/core/components/notifications/WarningNotification.jsx +++ b/src/core/components/notifications/WarningNotification.jsx @@ -1,10 +1,10 @@ import ReportIcon from "@mui/icons-material/Report"; -import {Box, Divider, Typography} from "@mui/material"; +import {Box, Typography} from "@mui/material"; import {toast} from "react-toastify"; const WarningNotification = (t, status) => { toast( - ({closeToast}) => ( + () => ( <> { - {t("warning")} ({t("code")}: {status}) + {t("notifications.warning")} ({t("notifications.code")}:{" "} + {status}) - {t("warning_static_text")} + {t("notifications.warning_static_text")} - ), { @@ -37,4 +37,4 @@ const WarningNotification = (t, status) => { ); }; -export default WarningNotification; +export default WarningNotification; \ No newline at end of file diff --git a/src/core/utils/errorHandler.js b/src/core/utils/errorHandler.js index ec5858a..8dbd364 100644 --- a/src/core/utils/errorHandler.js +++ b/src/core/utils/errorHandler.js @@ -3,11 +3,11 @@ import WarningNotification from "@/core/components/notifications/WarningNotifica import {toast} from "react-toastify"; export const errorSetting = (t, notification) => { - //todo + if (notification) toast.dismiss(); } export const errorRequest = (t, notification) => { - //todo + if (notification) toast.dismiss(); } export const errorResponse = (response, clearToken, t, notification) => { @@ -48,7 +48,7 @@ const isServerError = status => status >= 500 && status <= 599; const isClientError = status => status >= 400 && status <= 499; const errorLogic = (response, t, notification) => { - if (notification) ErrorNotification(t, response.status) + if (notification) ErrorNotification(t, response.status, response.data.message) } const errorValidation = (response, t, notification) => { if (notification) { @@ -59,4 +59,4 @@ const errorValidation = (response, t, notification) => { ErrorNotification(t, response.status, errorsArray[item][0]); }) } -} +} \ No newline at end of file diff --git a/src/layouts/AppLayout.jsx b/src/layouts/AppLayout.jsx index 611e707..2b4593b 100644 --- a/src/layouts/AppLayout.jsx +++ b/src/layouts/AppLayout.jsx @@ -2,12 +2,13 @@ import theme from "@/core/utils/theme"; import useLanguage from "@/lib/app/hooks/useLanguage"; 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"; import {ToastContainer} from "react-toastify"; import useDirection from "@/lib/app/hooks/useDirection"; import "react-toastify/dist/ReactToastify.css"; +import NetworkComponent from "@/core/components/NetworkComponent"; +import GlobalHead from "@/core/components/GlobalHead"; function AppLayout({children, isBot}) { const {languageIsReady} = useLanguage(); @@ -40,66 +41,16 @@ function AppLayout({children, isBot}) { if (!languageIsReady) return; } - return ( - <> - - - - - - - - - - - - - - - - - - - - - - - {children} - - ); + return (<> + + + + + {children} + ); } -export default AppLayout; +export default AppLayout; \ No newline at end of file diff --git a/src/lib/app/hooks/useNetwork.jsx b/src/lib/app/hooks/useNetwork.jsx new file mode 100644 index 0000000..4fff7fe --- /dev/null +++ b/src/lib/app/hooks/useNetwork.jsx @@ -0,0 +1,58 @@ +import {useEffect, useState} from "react"; + +function getNetworkConnection() { + return ( + navigator.connection || + navigator.mozConnection || + navigator.webkitConnection || + null + ); +} + +function getNetworkConnectionInfo() { + const connection = getNetworkConnection(); + if (!connection) { + return {}; + } + return { + rtt: connection.rtt, + type: connection.type, + saveData: connection.saveData, + downLink: connection.downLink, + downLinkMax: connection.downLinkMax, + effectiveType: connection.effectiveType, + }; +} + +function useNetwork() { + const [state, setState] = useState(() => { + return { + online: navigator.onLine, + }; + }); + useEffect(() => { + const handleOnline = () => { + setState((prevState) => ({ + ...prevState, + online: true, + })); + }; + const handleOffline = () => { + setState((prevState) => ({ + ...prevState, + online: false, + })); + }; + + window.addEventListener("online", handleOnline); + window.addEventListener("offline", handleOffline); + return () => { + window.removeEventListener("online", handleOnline); + window.removeEventListener("offline", handleOffline); + }; + }, []); + + return state; +} + +export default useNetwork; \ No newline at end of file diff --git a/src/lib/app/hooks/useProvince.jsx b/src/lib/app/hooks/useProvince.jsx new file mode 100644 index 0000000..d0fbda0 --- /dev/null +++ b/src/lib/app/hooks/useProvince.jsx @@ -0,0 +1,29 @@ +import {GET_PROVINCE_LIST} from "@/core/data/apiRoutes"; +import useRequest from "@/lib/app/hooks/useRequest"; +import useSWR from "swr"; + +const useProvince = () => { + const requestServer = useRequest({auth: true, notification: false}) + + //swr config + const fetcher = (...args) => { + return requestServer(args, 'get').then(({data}) => { + return data; + }).catch(() => { + }) + }; + + const {data, isLoading} = useSWR(GET_PROVINCE_LIST, fetcher, { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false + }); + + return { + provinceList: data, + isLoadingProvinceList: isLoading, + errorProvinceList: !data + } +}; + +export default useProvince; \ No newline at end of file diff --git a/src/lib/app/hooks/useRequest.js b/src/lib/app/hooks/useRequest.js index 5c567ca..d361e08 100644 --- a/src/lib/app/hooks/useRequest.js +++ b/src/lib/app/hooks/useRequest.js @@ -4,27 +4,23 @@ import PendingNotification from "@/core/components/notifications/PendingNotifica import {useTranslations} from "next-intl"; import useUser from "@/lib/app/hooks/useUser"; import {errorRequest, errorResponse, errorSetting} from "@/core/utils/errorHandler"; +import useNetwork from "@/lib/app/hooks/useNetwork"; const defaultOptions = { - auth: false, - data: {}, - requestOptions: { + auth: false, data: {}, requestOptions: { headers: {} - }, - notification: true, - pending: true, - success: { + }, notification: true, pending: true, success: { notification: { show: true, }, - }, - failed: { + }, failed: { notification: { show: true, }, }, } const useRequest = (initOptions) => { + const network = useNetwork() const t = useTranslations() const {token, clearToken} = useUser() let _options = {...defaultOptions, ...initOptions} @@ -32,20 +28,20 @@ const useRequest = (initOptions) => { function requestServer(url = '', method = 'get', options) { _options = {..._options, ...options} if (_options.auth) _options = { - ..._options, - requestOptions: { + ..._options, requestOptions: { ..._options.requestOptions, headers: {..._options.requestOptions.headers, authorization: `Bearer ${token}`} } } return new Promise((resolve, reject) => { + if (!network.online) { + reject() + return + } if (_options.notification && _options.failed.notification.show && _options.pending) PendingNotification(t) axios({ - url: url, - method: method, - data: _options.data, - ..._options.requestOptions + url: url, method: method, data: _options.data, ..._options.requestOptions }) .then(response => { successRequest(response, t, _options) diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx index c9c28bb..474e29b 100644 --- a/src/pages/_document.jsx +++ b/src/pages/_document.jsx @@ -11,7 +11,7 @@ export default function MyDocument(props) { - + {emotionStyleTags}