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..6031f3e 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": "ثبت درخواست وام", @@ -132,6 +135,8 @@ "text_field_vehicle_type": "نام وسیله نقلیه", "text_field_enter_your_vehicle_type": "نام وسیله نقلیه خود را وارد کنید", "text_field_province": "استان", + "text_field_loading_provinces_list": "درحال دریافت استان ها", + "text_field_error_fetching_provinces": "خطا در دریافت استان ها", "text_field_enter_your_province": "استان خود را وارد کنید", "text_field_navgan_id": "شماره ناوگان", "text_field_enter_your_navgan_id": "شماره ناوگان خود را وارد کنید", 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/forms/AddForm.jsx b/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx index 06a40fc..4c67ee3 100644 --- a/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx +++ b/src/components/dashboard/refahi/add-request-loan/forms/AddForm.jsx @@ -1,39 +1,30 @@ 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"; 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() // initial values, validation and request action of form const initialValues = { @@ -183,27 +174,48 @@ const AddFormComponent = () => { - + > + {t("LoanRequest.text_field_province")} + + + {props.touched.province && props.errors.province ? props.errors.province : ""} + + { + 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}