This commit is contained in:
+14
-12
@@ -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 ? (
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
@@ -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')
|
||||||
|
|||||||
@@ -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')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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(
|
||||||
? Paths.auth.completeProfile
|
needsProfileCompletion(user)
|
||||||
: Paths.home
|
? Paths.auth.completeProfile
|
||||||
|
: 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')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 });
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
|||||||
@@ -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[]
|
||||||
|
|||||||
@@ -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
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user