diff --git a/src/App.tsx b/src/App.tsx index 07cc833..b89c080 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,15 +4,17 @@ import 'swiper/swiper-bundle.css'; import 'react-loading-skeleton/dist/skeleton.css' import { BrowserRouter } from 'react-router-dom' import { QueryCache, QueryClient, QueryClientProvider } from '@tanstack/react-query' -// import { getToken } from './config/func' import MainRouter from './router/MainRouter' -// import AuthRouter from './router/AuthRouter' import ToastContainer from './components/Toast' import { getRefreshToken, getToken, removeRefreshToken, removeToken, 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 { refreshToken } from './pages/auth/service/AuthService'; +import { useGetMe } from './pages/user/hooks/useUserData'; +import { needsProfileCompletion } from './pages/user/utils/profileUtils'; +import MoonLoader from 'react-spinners/MoonLoader'; declare global { interface Window { @@ -123,17 +125,37 @@ const queryClient = new QueryClient({ } }); -const App: FC = () => { - - const isLoggedIn = getToken() +const AppRoutes: FC = () => { + const isLoggedIn = !!getToken() + const { data: user, isLoading } = useGetMe({ enabled: isLoggedIn }) + const profileIncomplete = isLoggedIn && needsProfileCompletion(user) + if (isLoggedIn && isLoading) { + return ( +
+ +
+ ) + } + return ( + + {!isLoggedIn ? ( + + ) : profileIncomplete ? ( + + ) : ( + + )} + + + ) +} + +const App: FC = () => { return ( - - {isLoggedIn ? : } - - + ) } diff --git a/src/config/Paths.tsx b/src/config/Paths.tsx index 2a0aba5..78f7037 100644 --- a/src/config/Paths.tsx +++ b/src/config/Paths.tsx @@ -29,6 +29,7 @@ export const Paths = { }, auth: { login: '/login', + completeProfile: '/complete-profile', }, payment: { payInvoice: '/payments/invoice/', diff --git a/src/config/statusRegistry.ts b/src/config/statusRegistry.ts index 1d864ea..ff5e0a9 100644 --- a/src/config/statusRegistry.ts +++ b/src/config/statusRegistry.ts @@ -22,10 +22,8 @@ export const statusRegistry: Record = { [ProformaInvoiceStatusEnum.PARTIALLY_CONFIRMED]: { label: 'تایید جزئی', variant: 'info' }, [ProformaInvoiceStatusEnum.CONFIRMED]: { label: 'تایید شده', variant: 'success' }, - // --- Invoice confirm status (from API) --- + // --- Invoice confirm status (from API; partially_confirmed/confirmed covered by enum above) --- pending: { label: 'تایید نشده', variant: 'warning' }, - partially_confirmed: { label: 'تایید جزئی', variant: 'info' }, - confirmed: { label: 'تایید شده', variant: 'success' }, // --- OrderStatusEnum --- [OrderStatusEnum.CREATED]: { label: 'ایجاد شده', variant: 'info' }, diff --git a/src/pages/auth/CompleteProfile.tsx b/src/pages/auth/CompleteProfile.tsx new file mode 100644 index 0000000..9ebc1a0 --- /dev/null +++ b/src/pages/auth/CompleteProfile.tsx @@ -0,0 +1,28 @@ +import { type FC } from 'react' +import LogoImage from '../../assets/images/logo.svg' +import LogoSmallImage from '../../assets/images/logo-small.svg' +import CompleteProfileForm from './components/CompleteProfileForm' + +const CompleteProfile: FC = () => { + return ( +
+
+
+
+
+ + + +
+
+
+ +
+ +
+
+
+ ) +} + +export default CompleteProfile diff --git a/src/pages/auth/components/CompleteProfileForm.tsx b/src/pages/auth/components/CompleteProfileForm.tsx new file mode 100644 index 0000000..0b8a72f --- /dev/null +++ b/src/pages/auth/components/CompleteProfileForm.tsx @@ -0,0 +1,101 @@ +import { type FC } from 'react' +import Input from '@/components/Input' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import Button from '@/components/Button' +import Error from '@/components/Error' +import { useGetMe, useUpdateProfile } from '@/pages/user/hooks/useUserData' +import { toast } from '@/shared/toast' +import { extractErrorMessage } from '@/config/func' +import { Paths } from '@/config/Paths' +import type { UpdateProfileType } from '@/pages/user/types/Types' + +const CompleteProfileForm: FC = () => { + const { data: user } = useGetMe() + const updateProfile = useUpdateProfile() + + const formik = useFormik({ + enableReinitialize: true, + initialValues: { + firstName: user?.firstName ?? '', + lastName: user?.lastName ?? '', + }, + validationSchema: Yup.object({ + firstName: Yup.string() + .trim() + .required('نام اجباری است.'), + lastName: Yup.string() + .trim() + .required('نام خانوادگی اجباری است.'), + }), + onSubmit(values) { + updateProfile.mutate( + { + firstName: values.firstName.trim(), + lastName: values.lastName.trim(), + }, + { + onSuccess() { + toast('اطلاعات شما با موفقیت ثبت شد', 'success') + window.location.href = Paths.home + }, + onError(error) { + toast(extractErrorMessage(error), 'error') + }, + }, + ) + }, + }) + + return ( +
+
+

+ تکمیل اطلاعات +

+

+ لطفاً نام و نام خانوادگی خود را وارد کنید . +

+
+ +
+
+ + {formik.touched.firstName && formik.errors.firstName && ( + + )} +
+ +
+ + {formik.touched.lastName && formik.errors.lastName && ( + + )} +
+
+ +
+ ) +} + +export default CompleteProfileForm diff --git a/src/pages/auth/components/LoginStep2.tsx b/src/pages/auth/components/LoginStep2.tsx index 5d4d7fa..57f3005 100644 --- a/src/pages/auth/components/LoginStep2.tsx +++ b/src/pages/auth/components/LoginStep2.tsx @@ -12,6 +12,7 @@ import { extractErrorMessage, setRefreshToken } from '../../../config/func' import { setToken } from '../../../config/func' import { toast } from '@/shared/toast' import { Paths } from '@/config/Paths' +import { needsProfileCompletion } from '@/pages/user/utils/profileUtils' const LoginStep2: FC = () => { @@ -38,8 +39,10 @@ const LoginStep2: FC = () => { onSuccess(data) { setToken(data?.data?.tokens?.accessToken?.token) setRefreshToken(data?.data?.tokens?.refreshToken?.token) - // Check if there's a redirect parameter in the URL - window.location.href = Paths.profile + const user = data?.data?.user + window.location.href = needsProfileCompletion(user) + ? Paths.auth.completeProfile + : Paths.home }, onError(error) { toast(extractErrorMessage(error), 'error') diff --git a/src/pages/user/hooks/useUserData.ts b/src/pages/user/hooks/useUserData.ts index c1292db..ebad736 100644 --- a/src/pages/user/hooks/useUserData.ts +++ b/src/pages/user/hooks/useUserData.ts @@ -1,9 +1,22 @@ -import { useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import * as api from "../service/UserService"; +import type { UpdateProfileType } from "../types/Types"; -export const useGetMe = () => { +export const useGetMe = (options?: { enabled?: boolean }) => { return useQuery({ queryKey: ["me"], queryFn: api.getMe, + enabled: options?.enabled ?? true, + }); +}; + +export const useUpdateProfile = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (variables: UpdateProfileType) => api.updateProfile(variables), + onSuccess: (data) => { + queryClient.setQueryData(["me"], data); + }, }); }; diff --git a/src/pages/user/service/UserService.ts b/src/pages/user/service/UserService.ts index e539ac9..d9b5903 100644 --- a/src/pages/user/service/UserService.ts +++ b/src/pages/user/service/UserService.ts @@ -1,7 +1,17 @@ import axios from "@/config/axios"; -import type { GetMeResponseType } from "@/pages/user/types/Types"; +import type { GetMeResponseType, UpdateProfileType } from "@/pages/user/types/Types"; export const getMe = async (): Promise => { const { data } = await axios.get("/public/users/me"); return data.data; }; + +export const updateProfile = async ( + params: UpdateProfileType, +): Promise => { + const { data } = await axios.patch( + "/public/users/update", + params, + ); + return data.data; +}; diff --git a/src/pages/user/types/Types.ts b/src/pages/user/types/Types.ts index e916604..ec58df0 100644 --- a/src/pages/user/types/Types.ts +++ b/src/pages/user/types/Types.ts @@ -14,3 +14,8 @@ export interface UserMeType { } export type GetMeResponseType = BaseResponse; + +export type UpdateProfileType = { + firstName: string; + lastName: string; +}; diff --git a/src/pages/user/utils/profileUtils.ts b/src/pages/user/utils/profileUtils.ts new file mode 100644 index 0000000..d098aa5 --- /dev/null +++ b/src/pages/user/utils/profileUtils.ts @@ -0,0 +1,9 @@ +type ProfileNameFields = { + firstName?: string | null; + lastName?: string | null; +}; + +export const needsProfileCompletion = (user?: ProfileNameFields | null): boolean => { + if (!user) return false; + return !user.firstName?.trim() || !user.lastName?.trim(); +}; diff --git a/src/router/CompleteProfileRouter.tsx b/src/router/CompleteProfileRouter.tsx new file mode 100644 index 0000000..08bf13d --- /dev/null +++ b/src/router/CompleteProfileRouter.tsx @@ -0,0 +1,13 @@ +import CompleteProfile from '@/pages/auth/CompleteProfile' +import { type FC } from 'react' +import { Route, Routes } from 'react-router-dom' + +const CompleteProfileRouter: FC = () => { + return ( + + } /> + + ) +} + +export default CompleteProfileRouter