From 8cab9d04c82de030619fdf50c1f1dd8234c312fe Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Fri, 3 Jul 2026 17:16:11 +0330 Subject: [PATCH] remove hard reload --- src/App.tsx | 26 ++++++++-------- src/config/navigation.tsx | 30 +++++++++++++++++++ src/config/sessionAuth.ts | 19 ++++++++++++ .../auth/components/CompleteProfileForm.tsx | 3 +- src/pages/auth/components/LoginStep1.tsx | 7 +++-- src/pages/auth/components/LoginStep2.tsx | 26 ++++++++++++---- src/pages/auth/store/AuthStore.ts | 4 +++ src/pages/auth/types/AuthTypes.ts | 3 ++ src/pages/criticisms/Add.tsx | 7 +++-- src/pages/invoice/types/InvoiceTypes.ts | 1 - src/pages/order/type/Types.ts | 1 - src/pages/request/type/Types.ts | 1 - src/shared/SidebarItem.tsx | 20 ++++++------- 13 files changed, 111 insertions(+), 37 deletions(-) create mode 100644 src/config/navigation.tsx create mode 100644 src/config/sessionAuth.ts diff --git a/src/App.tsx b/src/App.tsx index b89c080..8a8cb7b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,11 +6,13 @@ import { BrowserRouter } from 'react-router-dom' import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query' import MainRouter from './router/MainRouter' import ToastContainer from './components/Toast' -import { getRefreshToken, getToken, removeRefreshToken, removeToken, setRefreshToken, setToken } from './config/func'; +import { getRefreshToken, setRefreshToken, setToken } from './config/func'; import AuthRouter from './router/AuthRouter'; import CompleteProfileRouter from './router/CompleteProfileRouter'; import type { IApiErrorRepsonse } from './shared/types/Types'; import { Paths } from './config/Paths'; +import { useSessionAuth } from './config/sessionAuth'; +import { appNavigate, AppNavigation } from './config/navigation'; import { refreshToken } from './pages/auth/service/AuthService'; import { useGetMe } from './pages/user/hooks/useUserData'; import { needsProfileCompletion } from './pages/user/utils/profileUtils'; @@ -38,9 +40,9 @@ const queryClient = new QueryClient({ // اگر خطا از refresh endpoint است، refresh token منقضی شده if (isRefreshEndpoint) { window.isRefreshTokenExpired = true; - await removeToken(); - await removeRefreshToken(); - window.location.href = Paths.auth.login; + useSessionAuth.getState().clearSession(); + queryClient.clear(); + appNavigate(Paths.auth.login, { replace: true }); return; } @@ -67,9 +69,9 @@ const queryClient = new QueryClient({ if (!refreshTokenValue) { window.isRefreshTokenExpired = true; - await removeToken(); - await removeRefreshToken(); - window.location.href = Paths.auth.login; + useSessionAuth.getState().clearSession(); + queryClient.clear(); + appNavigate(Paths.auth.login, { replace: true }); return; } @@ -108,10 +110,9 @@ const queryClient = new QueryClient({ window.isRefreshTokenExpired = true; } - // Clear tokens and redirect to login - await removeToken(); - await removeRefreshToken(); - window.location.href = Paths.auth.login; + useSessionAuth.getState().clearSession(); + queryClient.clear(); + appNavigate(Paths.auth.login, { replace: true }); } } } @@ -126,7 +127,7 @@ const queryClient = new QueryClient({ }); const AppRoutes: FC = () => { - const isLoggedIn = !!getToken() + const isLoggedIn = useSessionAuth((state) => state.isAuthenticated) const { data: user, isLoading } = useGetMe({ enabled: isLoggedIn }) const profileIncomplete = isLoggedIn && needsProfileCompletion(user) @@ -140,6 +141,7 @@ const AppRoutes: FC = () => { return ( + {!isLoggedIn ? ( ) : profileIncomplete ? ( diff --git a/src/config/navigation.tsx b/src/config/navigation.tsx new file mode 100644 index 0000000..c96bbc5 --- /dev/null +++ b/src/config/navigation.tsx @@ -0,0 +1,30 @@ +import { useEffect, type FC } from "react"; +import { useNavigate, type NavigateOptions } from "react-router-dom"; + +let navigateRef: ReturnType | null = null; + +export const setAppNavigate = (navigate: ReturnType) => { + navigateRef = navigate; +}; + +export const appNavigate = (to: string, options?: NavigateOptions) => { + if (navigateRef) { + navigateRef(to, options); + return; + } + + window.location.assign(to); +}; + +export const AppNavigation: FC = () => { + const navigate = useNavigate(); + + useEffect(() => { + setAppNavigate(navigate); + return () => { + navigateRef = null; + }; + }, [navigate]); + + return null; +}; diff --git a/src/config/sessionAuth.ts b/src/config/sessionAuth.ts new file mode 100644 index 0000000..6161ba9 --- /dev/null +++ b/src/config/sessionAuth.ts @@ -0,0 +1,19 @@ +import { create } from "zustand"; +import { getToken, removeRefreshToken, removeToken } from "./func"; + +type SessionAuthStore = { + isAuthenticated: boolean; + setAuthenticated: (value: boolean) => void; + clearSession: () => void; +}; + +export const useSessionAuth = create((set) => ({ + isAuthenticated: !!getToken(), + setAuthenticated: (value) => set({ isAuthenticated: value }), + clearSession: () => { + removeToken(); + removeRefreshToken(); + window.isRefreshTokenExpired = false; + set({ isAuthenticated: false }); + }, +})); diff --git a/src/pages/auth/components/CompleteProfileForm.tsx b/src/pages/auth/components/CompleteProfileForm.tsx index 0ad389f..0f61773 100644 --- a/src/pages/auth/components/CompleteProfileForm.tsx +++ b/src/pages/auth/components/CompleteProfileForm.tsx @@ -8,6 +8,7 @@ import { useGetMe, useUpdateProfile } from '@/pages/user/hooks/useUserData' import { toast } from '@/shared/toast' import { extractErrorMessage } from '@/config/func' import { Paths } from '@/config/Paths' +import { appNavigate } from '@/config/navigation' import type { UpdateProfileType } from '@/pages/user/types/Types' const CompleteProfileForm: FC = () => { @@ -37,7 +38,7 @@ const CompleteProfileForm: FC = () => { { onSuccess() { toast('اطلاعات شما با موفقیت ثبت شد', 'success') - window.location.href = Paths.home + appNavigate(Paths.home, { replace: true }) }, onError(error) { toast(extractErrorMessage(error), 'error') diff --git a/src/pages/auth/components/LoginStep1.tsx b/src/pages/auth/components/LoginStep1.tsx index 1904586..1b2b396 100644 --- a/src/pages/auth/components/LoginStep1.tsx +++ b/src/pages/auth/components/LoginStep1.tsx @@ -12,7 +12,7 @@ import { extractErrorMessage } from '@/config/func' const LoginStep1: FC = () => { - const { phone, setPhone, setStepLogin } = useAuthStore() + const { phone, setPhone, setStepLogin, setDevOtpCode } = useAuthStore() const loginWithOtp = useLoginWithOtp() const checkHasAccount = useCheckHasAccount() @@ -27,7 +27,10 @@ const LoginStep1: FC = () => { onSubmit(values) { setPhone(values.phone_email) loginWithOtp.mutate({ phone: values.phone_email }, { - onSuccess() { + onSuccess(data) { + // TODO: remove before production — dev-only OTP autofill + const code = data?.data?.code + if (code) setDevOtpCode(String(code)) setStepLogin(2) toast('کد تایید ارسال شد', 'success') }, diff --git a/src/pages/auth/components/LoginStep2.tsx b/src/pages/auth/components/LoginStep2.tsx index 2719679..10c8c50 100644 --- a/src/pages/auth/components/LoginStep2.tsx +++ b/src/pages/auth/components/LoginStep2.tsx @@ -13,10 +13,12 @@ import { setToken } from '../../../config/func' import { toast } from '@/shared/toast' import { Paths } from '@/config/Paths' import { needsProfileCompletion } from '@/pages/user/utils/profileUtils' +import { useSessionAuth } from '@/config/sessionAuth' +import { appNavigate } from '@/config/navigation' const LoginStep2: FC = () => { - const { phone, setStepLogin } = useAuthStore() + const { phone, setStepLogin, devOtpCode, setDevOtpCode } = useAuthStore() const { value, reset } = useCountDown(2, true) const otpVerify = useOtpVerify() const loginWithOtp = useLoginWithOtp() @@ -25,7 +27,8 @@ const LoginStep2: FC = () => { const formik = useFormik({ initialValues: { phone: phone, - otp: '' + // TODO: remove before production — dev-only OTP autofill + otp: devOtpCode, }, validationSchema: Yup.object({ otp: Yup.string() @@ -40,10 +43,14 @@ const LoginStep2: FC = () => { onSuccess(data) { setToken(data?.data?.tokens?.accessToken?.token) setRefreshToken(data?.data?.tokens?.refreshToken?.token) + useSessionAuth.getState().setAuthenticated(true) const user = data?.data?.user - window.location.href = needsProfileCompletion(user) - ? Paths.auth.completeProfile - : Paths.home + appNavigate( + needsProfileCompletion(user) + ? Paths.auth.completeProfile + : Paths.home, + { replace: true }, + ) }, onError(error) { toast(extractErrorMessage(error), 'error') @@ -84,7 +91,14 @@ const LoginStep2: FC = () => { const reSend = () => { loginWithOtp.mutate({ phone: phone }, { - onSuccess: () => { + onSuccess: (data) => { + // TODO: remove before production — dev-only OTP autofill + const code = data?.data?.code + if (code) { + const otp = String(code) + setDevOtpCode(otp) + formik.setFieldValue('otp', otp) + } reset() toast('کد مجدد ارسال شد', 'success') }, diff --git a/src/pages/auth/store/AuthStore.ts b/src/pages/auth/store/AuthStore.ts index c6751b4..99d2418 100644 --- a/src/pages/auth/store/AuthStore.ts +++ b/src/pages/auth/store/AuthStore.ts @@ -14,4 +14,8 @@ export const useAuthStore = create((set) => ({ setStepLogin(value) { set({ stepLogin: value }); }, + devOtpCode: "", + setDevOtpCode(value) { + set({ devOtpCode: value }); + }, })); diff --git a/src/pages/auth/types/AuthTypes.ts b/src/pages/auth/types/AuthTypes.ts index dc3b6fe..e7ae709 100644 --- a/src/pages/auth/types/AuthTypes.ts +++ b/src/pages/auth/types/AuthTypes.ts @@ -12,6 +12,9 @@ export type AuthStoreType = { setEmail: (value: string) => void; stepLogin: number; setStepLogin: (value: number) => void; + /** TODO: remove before production — dev-only OTP from API response */ + devOtpCode: string; + setDevOtpCode: (value: string) => void; }; export type LoginWithPasswordType = { diff --git a/src/pages/criticisms/Add.tsx b/src/pages/criticisms/Add.tsx index 010310c..f53201c 100644 --- a/src/pages/criticisms/Add.tsx +++ b/src/pages/criticisms/Add.tsx @@ -16,6 +16,7 @@ import { useMultiUpload } from '../uploader/hooks/useUploader' const AddCriticisms: FC = () => { const [files, setFiles] = useState([]) + const [uploadKey, setUploadKey] = useState(0) const multiUpload = useMultiUpload() const createCriticisms = useCreateCriticisms() @@ -49,9 +50,8 @@ const AddCriticisms: FC = () => { onSuccess: () => { toast(t('success'), 'success') formik.resetForm() - setTimeout(() => { - window.location.reload() - }, 1000); + setFiles([]) + setUploadKey((key) => key + 1) }, onError: (error: ErrorType) => { toast(error.response?.data?.error?.message[0], 'error') @@ -96,6 +96,7 @@ const AddCriticisms: FC = () => {
= (props: Props) => { - // const logout = useLogout() + const queryClient = useQueryClient() - const handleLogout = async () => { - // await logout.mutateAsync() - removeToken() - removeRefreshToken() - window.location.href = Paths.auth.login + const handleLogout = (event: MouseEvent) => { + event.preventDefault() + useSessionAuth.getState().clearSession() + queryClient.clear() + appNavigate(Paths.auth.login, { replace: true }) } return (