diff --git a/.DS_Store b/.DS_Store index fa93c62..1b4b45b 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 3c3629e..3907a5a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,137 @@ node_modules +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/src/App.tsx b/src/App.tsx index e31a380..c445c85 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,11 +5,8 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom' import 'swiper/swiper-bundle.css'; import FaJson from './langs/fa.json' import AuthRouter from './router/Auth'; -import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { IApiErrorRepsonse } from './types/error.types'; -import { refreshToken } from './pages/auth/service/AuthService'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import ToastContainer from './components/Toast'; -import { getRefreshToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func'; i18next.init({ interpolation: { escapeValue: false }, @@ -29,63 +26,6 @@ declare global { function App() { const queryClient = new QueryClient({ - queryCache: new QueryCache({ - onError: async (error: IApiErrorRepsonse, query) => { - if (error?.response?.status === 401) { - try { - // Use a flag to track if refresh is in progress - if (window.isRefreshingToken) { - // Wait for the refresh to complete - await new Promise(resolve => { - const checkComplete = setInterval(() => { - if (!window.isRefreshingToken) { - clearInterval(checkComplete); - resolve(true); - } - }, 100); - }); - return; - } - - // Set the flag to indicate refresh is in progress - window.isRefreshingToken = true; - - try { - const refreshTokenValue = await getRefreshToken(); - const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' }); - - if (data?.accessToken?.token) { - // Save the new token - await setToken(data?.accessToken?.token); - - // If refresh token also returned, update it - if (data.refreshToken?.token) { - await setRefreshToken(data.refreshToken?.token); - } - - // Retry the failed request, even if it was muted - if (query) { - queryClient.invalidateQueries({ queryKey: query.queryKey }); - } else { - queryClient.invalidateQueries(); - } - return; - } else { - throw new Error('Invalid token response'); - } - } finally { - window.isRefreshingToken = false; - } - } catch (refreshError: unknown) { - console.error('Token refresh failed:', refreshError); - // Clear tokens and redirect to login - await removeToken(); - await removeRefreshToken(); - // window.location.href = '/auth/login'; - } - } - } - }), defaultOptions: { queries: { refetchOnWindowFocus: false, diff --git a/src/config/axios.ts b/src/config/axios.ts index e0e5ddc..c32fe9a 100644 --- a/src/config/axios.ts +++ b/src/config/axios.ts @@ -38,11 +38,19 @@ axiosInstance.interceptors.response.use( try { const refreshTokenValue = await getRefreshToken(); + if (!refreshTokenValue) { + removeRefreshToken(); + removeToken(); + window.location.href = buildPath("auth/login"); + return; + } const { data } = await refreshToken({ - refreshToken: refreshTokenValue || "", + refreshToken: refreshTokenValue, }); + alert("1"); if (data?.accessToken?.token) { + alert("1"); await setToken(data?.accessToken?.token); if (data.refreshToken?.token) { await setRefreshToken(data.refreshToken?.token); @@ -50,6 +58,8 @@ axiosInstance.interceptors.response.use( originalRequest.headers.Authorization = `Bearer ${data.accessToken.token}`; return axiosInstance(originalRequest); + } else { + alert("2"); } } catch (refreshError) { console.error("Token refresh failed:", refreshError); diff --git a/src/config/func.ts b/src/config/func.ts index f9c9f9f..ba41302 100644 --- a/src/config/func.ts +++ b/src/config/func.ts @@ -36,26 +36,43 @@ export const getToken = async () => { return Cookie.get(import.meta.env.VITE_TOKEN_NAME); }; -export const getRefreshToken = () => { +export const getRefreshToken = async () => { return Cookie.get(import.meta.env.VITE_REFRESH_TOKEN_NAME); }; +const isProduction = import.meta.env.MODE === "production"; const domain = window.location.hostname; export const setToken = (token: string) => { - Cookie.set(import.meta.env.VITE_TOKEN_NAME, token, { domain }); + Cookie.set(import.meta.env.VITE_TOKEN_NAME, token, { + domain, + secure: isProduction, + sameSite: "Lax", + }); }; export const setRefreshToken = (refreshToken: string) => { - Cookie.set(import.meta.env.VITE_REFRESH_TOKEN_NAME, refreshToken, { domain }); + Cookie.set(import.meta.env.VITE_REFRESH_TOKEN_NAME, refreshToken, { + domain, + secure: isProduction, + sameSite: "Lax", + }); }; export const removeToken = () => { - Cookie.remove(import.meta.env.VITE_TOKEN_NAME, { domain, path: "/" }); + Cookie.remove(import.meta.env.VITE_TOKEN_NAME, { + domain, + secure: isProduction, + sameSite: "Lax", + }); }; export const removeRefreshToken = () => { - Cookie.remove(import.meta.env.VITE_REFRESH_TOKEN_NAME, { domain, path: "/" }); + Cookie.remove(import.meta.env.VITE_REFRESH_TOKEN_NAME, { + domain, + secure: isProduction, + sameSite: "Lax", + }); }; export const getRedirectUrl = () => { diff --git a/src/pages/profile/components/Username.tsx b/src/pages/profile/components/Username.tsx index 91e9137..2b5f2de 100644 --- a/src/pages/profile/components/Username.tsx +++ b/src/pages/profile/components/Username.tsx @@ -60,7 +60,7 @@ const Username: FC = (props: Props) => { setStatus('') }, onError(error: ErrorType) { - toast(error?.response?.data?.message || 'خطا در تغییر نام کاربری', 'error') + toast(error?.response?.data?.error?.message[0] || 'خطا در تغییر نام کاربری', 'error') }, }) } diff --git a/src/pages/receipts/Detail.tsx b/src/pages/receipts/Detail.tsx index 63a6475..ee2bf19 100644 --- a/src/pages/receipts/Detail.tsx +++ b/src/pages/receipts/Detail.tsx @@ -38,7 +38,7 @@ const ReceiptsDetail: FC = () => { getInvoce.refetch() }, onError: (error: ErrorType) => { - toast(error?.response?.data?.message || 'خطا در پرداخت فاکتور', 'error') + toast(error?.response?.data?.error?.message[0], 'error') } }) } @@ -50,7 +50,7 @@ const ReceiptsDetail: FC = () => { getInvoce.refetch() }, onError: (error: ErrorType) => { - toast(error?.response?.data?.message || 'خطا در پرداخت فاکتور', 'error') + toast(error?.response?.data?.error?.message[0], 'error') } }) } diff --git a/src/pages/receipts/components/ApproveInvoice.tsx b/src/pages/receipts/components/ApproveInvoice.tsx index 7489c2e..b0ed23f 100644 --- a/src/pages/receipts/components/ApproveInvoice.tsx +++ b/src/pages/receipts/components/ApproveInvoice.tsx @@ -36,7 +36,7 @@ const ApproveInvoice: FC = ({ id, refetch, status }) => { setShowModal(false) }, onError: (error: ErrorType) => { - toast(error?.response?.data?.message || 'خطا در تایید فاکتور', 'error') + toast(error?.response?.data?.error?.message[0], 'error') } }) } @@ -52,7 +52,7 @@ const ApproveInvoice: FC = ({ id, refetch, status }) => { reset() }, onError: (error: ErrorType) => { - toast(error?.response?.data?.message || 'خطا در تایید فاکتور', 'error') + toast(error?.response?.data?.error?.message[0] || 'خطا در تایید فاکتور', 'error') } }) } diff --git a/src/pages/request/Request.tsx b/src/pages/request/Request.tsx index f1038c0..553462a 100644 --- a/src/pages/request/Request.tsx +++ b/src/pages/request/Request.tsx @@ -116,7 +116,7 @@ const Request: FC = () => { navigate(buildPath(Pages.dashboard, slug)) }, onError: (error: ErrorType) => { - toast(error?.response?.data?.message || 'خطا در ارسال درخواست', 'error') + toast(error?.response?.data?.error?.message[0], 'error') } }) }