remove hard reload
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-03 17:16:11 +03:30
parent c22c37894c
commit 8cab9d04c8
13 changed files with 111 additions and 37 deletions
+14 -12
View File
@@ -6,11 +6,13 @@ import { BrowserRouter } from 'react-router-dom'
import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query' import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query'
import MainRouter from './router/MainRouter' import MainRouter from './router/MainRouter'
import ToastContainer from './components/Toast' 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 AuthRouter from './router/AuthRouter';
import CompleteProfileRouter from './router/CompleteProfileRouter'; import CompleteProfileRouter from './router/CompleteProfileRouter';
import type { IApiErrorRepsonse } from './shared/types/Types'; import type { IApiErrorRepsonse } from './shared/types/Types';
import { Paths } from './config/Paths'; import { Paths } from './config/Paths';
import { useSessionAuth } from './config/sessionAuth';
import { appNavigate, AppNavigation } from './config/navigation';
import { refreshToken } from './pages/auth/service/AuthService'; import { refreshToken } from './pages/auth/service/AuthService';
import { useGetMe } from './pages/user/hooks/useUserData'; import { useGetMe } from './pages/user/hooks/useUserData';
import { needsProfileCompletion } from './pages/user/utils/profileUtils'; import { needsProfileCompletion } from './pages/user/utils/profileUtils';
@@ -38,9 +40,9 @@ const queryClient = new QueryClient({
// اگر خطا از refresh endpoint است، refresh token منقضی شده // اگر خطا از refresh endpoint است، refresh token منقضی شده
if (isRefreshEndpoint) { if (isRefreshEndpoint) {
window.isRefreshTokenExpired = true; window.isRefreshTokenExpired = true;
await removeToken(); useSessionAuth.getState().clearSession();
await removeRefreshToken(); queryClient.clear();
window.location.href = Paths.auth.login; appNavigate(Paths.auth.login, { replace: true });
return; return;
} }
@@ -67,9 +69,9 @@ const queryClient = new QueryClient({
if (!refreshTokenValue) { if (!refreshTokenValue) {
window.isRefreshTokenExpired = true; window.isRefreshTokenExpired = true;
await removeToken(); useSessionAuth.getState().clearSession();
await removeRefreshToken(); queryClient.clear();
window.location.href = Paths.auth.login; appNavigate(Paths.auth.login, { replace: true });
return; return;
} }
@@ -108,10 +110,9 @@ const queryClient = new QueryClient({
window.isRefreshTokenExpired = true; window.isRefreshTokenExpired = true;
} }
// Clear tokens and redirect to login useSessionAuth.getState().clearSession();
await removeToken(); queryClient.clear();
await removeRefreshToken(); appNavigate(Paths.auth.login, { replace: true });
window.location.href = Paths.auth.login;
} }
} }
} }
@@ -126,7 +127,7 @@ const queryClient = new QueryClient({
}); });
const AppRoutes: FC = () => { const AppRoutes: FC = () => {
const isLoggedIn = !!getToken() const isLoggedIn = useSessionAuth((state) => state.isAuthenticated)
const { data: user, isLoading } = useGetMe({ enabled: isLoggedIn }) const { data: user, isLoading } = useGetMe({ enabled: isLoggedIn })
const profileIncomplete = isLoggedIn && needsProfileCompletion(user) const profileIncomplete = isLoggedIn && needsProfileCompletion(user)
@@ -140,6 +141,7 @@ const AppRoutes: FC = () => {
return ( return (
<BrowserRouter> <BrowserRouter>
<AppNavigation />
{!isLoggedIn ? ( {!isLoggedIn ? (
<AuthRouter /> <AuthRouter />
) : profileIncomplete ? ( ) : profileIncomplete ? (
+30
View File
@@ -0,0 +1,30 @@
import { useEffect, type FC } from "react";
import { useNavigate, type NavigateOptions } from "react-router-dom";
let navigateRef: ReturnType<typeof useNavigate> | null = null;
export const setAppNavigate = (navigate: ReturnType<typeof useNavigate>) => {
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;
};
+19
View File
@@ -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<SessionAuthStore>((set) => ({
isAuthenticated: !!getToken(),
setAuthenticated: (value) => set({ isAuthenticated: value }),
clearSession: () => {
removeToken();
removeRefreshToken();
window.isRefreshTokenExpired = false;
set({ isAuthenticated: false });
},
}));
@@ -8,6 +8,7 @@ import { useGetMe, useUpdateProfile } from '@/pages/user/hooks/useUserData'
import { toast } from '@/shared/toast' import { toast } from '@/shared/toast'
import { extractErrorMessage } from '@/config/func' import { extractErrorMessage } from '@/config/func'
import { Paths } from '@/config/Paths' import { Paths } from '@/config/Paths'
import { appNavigate } from '@/config/navigation'
import type { UpdateProfileType } from '@/pages/user/types/Types' import type { UpdateProfileType } from '@/pages/user/types/Types'
const CompleteProfileForm: FC = () => { const CompleteProfileForm: FC = () => {
@@ -37,7 +38,7 @@ const CompleteProfileForm: FC = () => {
{ {
onSuccess() { onSuccess() {
toast('اطلاعات شما با موفقیت ثبت شد', 'success') toast('اطلاعات شما با موفقیت ثبت شد', 'success')
window.location.href = Paths.home appNavigate(Paths.home, { replace: true })
}, },
onError(error) { onError(error) {
toast(extractErrorMessage(error), 'error') toast(extractErrorMessage(error), 'error')
+5 -2
View File
@@ -12,7 +12,7 @@ import { extractErrorMessage } from '@/config/func'
const LoginStep1: FC = () => { const LoginStep1: FC = () => {
const { phone, setPhone, setStepLogin } = useAuthStore() const { phone, setPhone, setStepLogin, setDevOtpCode } = useAuthStore()
const loginWithOtp = useLoginWithOtp() const loginWithOtp = useLoginWithOtp()
const checkHasAccount = useCheckHasAccount() const checkHasAccount = useCheckHasAccount()
@@ -27,7 +27,10 @@ const LoginStep1: FC = () => {
onSubmit(values) { onSubmit(values) {
setPhone(values.phone_email) setPhone(values.phone_email)
loginWithOtp.mutate({ phone: 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) setStepLogin(2)
toast('کد تایید ارسال شد', 'success') toast('کد تایید ارسال شد', 'success')
}, },
+19 -5
View File
@@ -13,10 +13,12 @@ import { setToken } from '../../../config/func'
import { toast } from '@/shared/toast' import { toast } from '@/shared/toast'
import { Paths } from '@/config/Paths' import { Paths } from '@/config/Paths'
import { needsProfileCompletion } from '@/pages/user/utils/profileUtils' import { needsProfileCompletion } from '@/pages/user/utils/profileUtils'
import { useSessionAuth } from '@/config/sessionAuth'
import { appNavigate } from '@/config/navigation'
const LoginStep2: FC = () => { const LoginStep2: FC = () => {
const { phone, setStepLogin } = useAuthStore() const { phone, setStepLogin, devOtpCode, setDevOtpCode } = useAuthStore()
const { value, reset } = useCountDown(2, true) const { value, reset } = useCountDown(2, true)
const otpVerify = useOtpVerify() const otpVerify = useOtpVerify()
const loginWithOtp = useLoginWithOtp() const loginWithOtp = useLoginWithOtp()
@@ -25,7 +27,8 @@ const LoginStep2: FC = () => {
const formik = useFormik<OtpVerifyType>({ const formik = useFormik<OtpVerifyType>({
initialValues: { initialValues: {
phone: phone, phone: phone,
otp: '' // TODO: remove before production — dev-only OTP autofill
otp: devOtpCode,
}, },
validationSchema: Yup.object({ validationSchema: Yup.object({
otp: Yup.string() otp: Yup.string()
@@ -40,10 +43,14 @@ const LoginStep2: FC = () => {
onSuccess(data) { onSuccess(data) {
setToken(data?.data?.tokens?.accessToken?.token) setToken(data?.data?.tokens?.accessToken?.token)
setRefreshToken(data?.data?.tokens?.refreshToken?.token) setRefreshToken(data?.data?.tokens?.refreshToken?.token)
useSessionAuth.getState().setAuthenticated(true)
const user = data?.data?.user const user = data?.data?.user
window.location.href = needsProfileCompletion(user) appNavigate(
needsProfileCompletion(user)
? Paths.auth.completeProfile ? Paths.auth.completeProfile
: Paths.home : Paths.home,
{ replace: true },
)
}, },
onError(error) { onError(error) {
toast(extractErrorMessage(error), 'error') toast(extractErrorMessage(error), 'error')
@@ -84,7 +91,14 @@ const LoginStep2: FC = () => {
const reSend = () => { const reSend = () => {
loginWithOtp.mutate({ phone: phone }, { 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() reset()
toast('کد مجدد ارسال شد', 'success') toast('کد مجدد ارسال شد', 'success')
}, },
+4
View File
@@ -14,4 +14,8 @@ export const useAuthStore = create<AuthStoreType>((set) => ({
setStepLogin(value) { setStepLogin(value) {
set({ stepLogin: value }); set({ stepLogin: value });
}, },
devOtpCode: "",
setDevOtpCode(value) {
set({ devOtpCode: value });
},
})); }));
+3
View File
@@ -12,6 +12,9 @@ export type AuthStoreType = {
setEmail: (value: string) => void; setEmail: (value: string) => void;
stepLogin: number; stepLogin: number;
setStepLogin: (value: number) => void; setStepLogin: (value: number) => void;
/** TODO: remove before production — dev-only OTP from API response */
devOtpCode: string;
setDevOtpCode: (value: string) => void;
}; };
export type LoginWithPasswordType = { export type LoginWithPasswordType = {
+4 -3
View File
@@ -16,6 +16,7 @@ import { useMultiUpload } from '../uploader/hooks/useUploader'
const AddCriticisms: FC = () => { const AddCriticisms: FC = () => {
const [files, setFiles] = useState<File[]>([]) const [files, setFiles] = useState<File[]>([])
const [uploadKey, setUploadKey] = useState(0)
const multiUpload = useMultiUpload() const multiUpload = useMultiUpload()
const createCriticisms = useCreateCriticisms() const createCriticisms = useCreateCriticisms()
@@ -49,9 +50,8 @@ const AddCriticisms: FC = () => {
onSuccess: () => { onSuccess: () => {
toast(t('success'), 'success') toast(t('success'), 'success')
formik.resetForm() formik.resetForm()
setTimeout(() => { setFiles([])
window.location.reload() setUploadKey((key) => key + 1)
}, 1000);
}, },
onError: (error: ErrorType) => { onError: (error: ErrorType) => {
toast(error.response?.data?.error?.message[0], 'error') toast(error.response?.data?.error?.message[0], 'error')
@@ -96,6 +96,7 @@ const AddCriticisms: FC = () => {
<div className='mt-7'> <div className='mt-7'>
<UploadBox <UploadBox
key={uploadKey}
label={t('attach')} label={t('attach')}
isMultiple isMultiple
onChange={setFiles} onChange={setFiles}
-1
View File
@@ -20,7 +20,6 @@ export type InvoiceProduct = {
category: string; category: string;
title: string; title: string;
desc: string; desc: string;
quantities: number[];
linkUrl: string | null; linkUrl: string | null;
isActive: boolean; isActive: boolean;
images: string[]; images: string[];
-1
View File
@@ -14,7 +14,6 @@ export type OrderProductType = {
category: string category: string
title: string title: string
desc: string desc: string
quantities: number[]
linkUrl: string | null linkUrl: string | null
isActive: boolean isActive: boolean
images?: string[] images?: string[]
-1
View File
@@ -55,7 +55,6 @@ export type ProductType = {
isActive: boolean; isActive: boolean;
linkUrl: string; linkUrl: string;
order: number; order: number;
quantities: number[];
title: string; title: string;
}; };
+10 -10
View File
@@ -1,10 +1,10 @@
import type { FC, ReactNode } from 'react' import type { FC, MouseEvent, ReactNode } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useQueryClient } from '@tanstack/react-query'
import { clx } from '../helpers/utils' import { clx } from '../helpers/utils'
import { Paths } from '../config/Paths' import { Paths } from '../config/Paths'
// import { useLogout } from '../pages/auth/hooks/useAuthData' import { useSessionAuth } from '../config/sessionAuth'
import { removeToken } from '../config/func' import { appNavigate } from '../config/navigation'
import { removeRefreshToken } from '../config/func'
type Props = { type Props = {
icon: ReactNode, icon: ReactNode,
@@ -18,13 +18,13 @@ type Props = {
const SideBarItem: FC<Props> = (props: Props) => { const SideBarItem: FC<Props> = (props: Props) => {
// const logout = useLogout() const queryClient = useQueryClient()
const handleLogout = async () => { const handleLogout = (event: MouseEvent<HTMLAnchorElement>) => {
// await logout.mutateAsync() event.preventDefault()
removeToken() useSessionAuth.getState().clearSession()
removeRefreshToken() queryClient.clear()
window.location.href = Paths.auth.login appNavigate(Paths.auth.login, { replace: true })
} }
return ( return (