diff --git a/src/components/common/button.tsx b/src/components/common/button.tsx index d35edc2..ed1e186 100644 --- a/src/components/common/button.tsx +++ b/src/components/common/button.tsx @@ -7,12 +7,14 @@ type Props = { type?: "reset" | "button" | "submit" | undefined, pending?: boolean, theme?: "primary" | "secondary" | "glass" | "light", - onClick?: () => void + onClick?: () => void, + disable?: boolean, + className?: string } -export const ButtonComponent: FC = ({ title, type = "button", pending = false, theme = "primary", onClick }) => { +export const ButtonComponent: FC = ({ title, type = "button", pending = false, theme = "primary", onClick, disable = false, className }) => { return ( - +

بازگشت

diff --git a/src/components/forms/login-with-code-form.tsx b/src/components/forms/login-with-code-form.tsx index 9607212..0a3b6d2 100644 --- a/src/components/forms/login-with-code-form.tsx +++ b/src/components/forms/login-with-code-form.tsx @@ -1,18 +1,39 @@ -import { Link } from "react-router-dom" +import { Link, useNavigate } from "react-router-dom" import { Input } from "../common/input" import { Controller, SubmitHandler, useForm } from "react-hook-form" import { LoginWithCodeInterface } from "../../types" import { yupResolver } from "@hookform/resolvers/yup" import { LoginWithCodeSchema } from "../../schema" import { ErrorComponent } from "../common/error" -import { Button } from "@headlessui/react" +import { ButtonComponent } from "../common/button" +import { useMutation } from "@tanstack/react-query" +import { sendOTPMutation } from "../../services/api/auth" +import toast from "react-hot-toast" export const LoginWithCodeForm = () => { + const navigate = useNavigate() + const { mutate, isPending } = useMutation({ + mutationFn: sendOTPMutation, + mutationKey: ["send-otp"] + }) const { handleSubmit, formState: { errors }, control } = useForm({ resolver: yupResolver(LoginWithCodeSchema) }) const handleLoginWithCode: SubmitHandler = async (data) => { - console.log("data=>", data) + mutate({ + ...data + }, + { + onSuccess: () => { + toast.success(`کد ورود برای شماره ${data?.mobile_number} ارسال شد`) + navigate(`/auth/send-code?phone_number=${data?.mobile_number}`) + }, + onError: (err: any) => { + console.log("error", err) + navigate(`/auth/send-code?phone_number=${data?.mobile_number}`) + } + } + ) } return ( {
- ( + (
- +
)} />
- +

بازگشت

diff --git a/src/components/forms/register-form.tsx b/src/components/forms/register-form.tsx index 2618942..a6b2b91 100644 --- a/src/components/forms/register-form.tsx +++ b/src/components/forms/register-form.tsx @@ -1,20 +1,75 @@ -import { Link } from "react-router-dom" +import { Link, useNavigate } from "react-router-dom" import { ComboBox } from "../common/combo-box" import { Input } from "../common/input" -import { Button } from "@headlessui/react" import { Controller, SubmitHandler, useForm } from "react-hook-form" -import { RegisterInterface } from "../../types" +import { CitiesType, ComboBoxItems, ProvinceType, RegisterInterface } from "../../types" import { yupResolver } from "@hookform/resolvers/yup" import { RegisterSchema } from "../../schema" import { ErrorComponent } from "../common/error" +import { getProvincesList } from "../../services/api/province" +import { useMutation, useQuery } from "@tanstack/react-query" +import { useEffect, useState } from "react" +import { getCitiesList } from "../../services/api/cities" +import { registerMutation } from "../../services/api/auth" +import { ButtonComponent } from "../common/button" +import toast from "react-hot-toast" export const RegisterForm = () => { - const { handleSubmit, formState: { errors }, control } = useForm({ + const [provinceData, setProvinceData] = useState([]) + const [citiesData, setCitiesData] = useState([]) + const navigate = useNavigate() + const { mutate, isPending } = useMutation({ + mutationFn: registerMutation, + mutationKey: ["register"] + }) + const { data: provinces } = useQuery({ + queryFn: getProvincesList, + queryKey: ["province"] + }) + const { data: cities } = useQuery({ + queryFn: getCitiesList, + queryKey: ["cities"] + }) + + const { handleSubmit, formState: { errors }, control, watch } = useForm({ resolver: yupResolver(RegisterSchema) }) + const provinceName: any = watch("province_name"); const handleRegister: SubmitHandler = async (data) => { - console.log("data=>", data) + mutate({ + ...data, + province_name: data?.province_name?.name, + city_name: data?.province_name?.name + }, + { + onSuccess: () => { + toast.success("با موفقیت ثبت نام شدید") + navigate("/auth/login") + }, + onError: (error: any) => { + console.log("error =>", error) + } + }) } + useEffect(() => { + const tempArray: ComboBoxItems[] = [...provinceData] + provinces?.data?.data?.map((province: ProvinceType) => tempArray.push({ + id: province?.ProvinceID, + name: province?.ProvinceName + })) + setProvinceData(tempArray) + }, [provinces]) + + useEffect(() => { + const tempCities: any = [] + const newCities = cities?.data?.data?.filter((city: CitiesType) => city?.ProvinceName.toLocaleLowerCase().includes(provinceName?.name.toLowerCase())) + newCities?.map((city: CitiesType) => tempCities?.push({ + id: city?.CityID, + name: city?.CityName + })) + setCitiesData(tempCities) + }, [provinceName]); + return ( @@ -31,43 +86,47 @@ export const RegisterForm = () => {
-
+
- +
} /> -
+
- +
} />
-
-
- - -
} /> -
- - -
} /> -
- (
- - + (
+ +
)} /> -
+
+
+ + +
} /> +
+ + +
} /> +
+ (
+ + +
)} /> +
- +
} />
} /> -
+
- +
} />
- +
) diff --git a/src/components/forms/reset-password-code-form.tsx b/src/components/forms/reset-password-code-form.tsx index 4a3d6d0..d6e251e 100644 --- a/src/components/forms/reset-password-code-form.tsx +++ b/src/components/forms/reset-password-code-form.tsx @@ -1,19 +1,79 @@ -import { Button } from "@headlessui/react"; import { Controller, SubmitHandler, useForm } from "react-hook-form"; import OTPInput from "react-otp-input" -import { Link } from "react-router-dom" +import { Link, useNavigate, useSearchParams } from "react-router-dom" import { ResetPasswordCodeInterface } from "../../types"; import { yupResolver } from "@hookform/resolvers/yup"; import { ResetPasswordCodeSchema } from "../../schema"; import { ErrorComponent } from "../common/error"; +import { useMutation } from "@tanstack/react-query"; +import { checkOTP, sendOTPMutation } from "../../services/api/auth"; +import toast from "react-hot-toast"; +import { useEffect, useState } from "react"; +import { ButtonComponent } from "../common/button"; export const ResetpasswordCodeForm = () => { + const navigate = useNavigate() + const [seconds, setSeconds] = useState(30); + const [isTimeUp, setIsTimeUp] = useState(false); + const [searchParams] = useSearchParams() + const { mutate, isPending } = useMutation({ + mutationFn: checkOTP, + mutationKey: ["check-otp"] + }) + + const { mutate: sendOTPMutate, isPending: sendOTPPending } = useMutation({ + mutationFn: sendOTPMutation, + mutationKey: ["send-otp"] + }) + const { handleSubmit, formState: { errors }, control } = useForm({ resolver: yupResolver(ResetPasswordCodeSchema) }) + const submitCode: SubmitHandler = async (data) => { - console.log("data=>", data) + await mutate({ + ...data, + mobile_number: searchParams.get("phone_number"), + }, + { + onSuccess: () => { + toast.success(`کد وارد شده مطابقت دارد`) + navigate(`/auth/reset-password?mobile_number=${searchParams.get("phone_number")}&otp=${data?.otp}`) + }, + onError: (err: any) => { + console.log("err=>", err) + navigate(`/auth/reset-password?mobile_number=${searchParams.get("phone_number")}&otp=${data?.otp}`) + } + }) } + + const handleSendOTPAgain = async () => { + await sendOTPMutate({ + mobile_number: searchParams.get("phone_number") + }, + { + onSuccess: () => { + toast.success(`کد ورود برای شماره ${searchParams.get("phone_number")} ارسال شد`) + setSeconds(30) + setIsTimeUp(false) + }, + onError: (err: any) => { + console.log("err=>", err) + } + }) + } + + useEffect(() => { + if (seconds > 0) { + const timer = setInterval(() => { + setSeconds(prevSeconds => prevSeconds - 1); + }, 1000); + return () => clearInterval(timer); + } else { + setIsTimeUp(true); + } + }, [seconds]); + return (
@@ -23,7 +83,7 @@ export const ResetpasswordCodeForm = () => { فراموشی رمز عبور
-

کد تایید ارسال شده به شماره 09376225448 را وارد کنید.

+

کد تایید ارسال شده به شماره {searchParams.get("phone_number")} را وارد کنید.

ویرایش

@@ -31,7 +91,7 @@ export const ResetpasswordCodeForm = () => {
- ( + (
{ renderInput={(props) => } inputStyle="otp-inputs" /> - +
)} />
-

30 ثانیه دیگر تا دریافت کد

- -

ارسال مجدد

+

{seconds} ثانیه دیگر تا دریافت کد

+ +
diff --git a/src/components/forms/reset-password.form.tsx b/src/components/forms/reset-password.form.tsx index 2b8be7b..07f2a64 100644 --- a/src/components/forms/reset-password.form.tsx +++ b/src/components/forms/reset-password.form.tsx @@ -1,14 +1,23 @@ -import { Link } from "react-router-dom" +import { Link, useNavigate, useSearchParams } from "react-router-dom" import { Input } from "../common/input" import { Controller, SubmitHandler, useForm } from "react-hook-form" import { ResetPasswordInterface } from "../../types" import { yupResolver } from "@hookform/resolvers/yup" import { resetPasswordSchema } from "../../schema" -import { Button } from "@headlessui/react" import { ErrorComponent } from "../common/error" +import { useMutation } from "@tanstack/react-query" +import { resetPasswordMutation } from "../../services/api/auth" +import { ButtonComponent } from "../common/button" +import toast from "react-hot-toast" export const ResetPasswordForm = () => { + const navigate = useNavigate() + const { mutate, isPending } = useMutation({ + mutationFn: resetPasswordMutation, + mutationKey: ["reset-password"] + }) + const [searchParams] = useSearchParams() const { handleSubmit, formState: { errors }, @@ -17,7 +26,18 @@ export const ResetPasswordForm = () => { resolver: yupResolver(resetPasswordSchema) }) const handleResetPassword: SubmitHandler = async (data) => { - console.log("data=>", data) + await mutate({ + ...data, + mobile_number: searchParams.get("mobile_number"), + otp: searchParams.get("otp") + }, + { + onSuccess: () => { + toast.success("تغییر رمز عبور با موفقیت انجام شد") + navigate("/auth/login") + }, + onError: (err: any) => console.log("err=>", err) + }) } return (
{
بازیابی رمز عبور
-

کد تایید ارسال شده به شماره 09376225448 را وارد کنید.

+

کد تایید ارسال شده به شماره {searchParams.get("mobile_number")} را وارد کنید.

ویرایش

@@ -38,12 +58,12 @@ export const ResetPasswordForm = () => {
)} /> - (
+ (
- +
)} />
- +
) diff --git a/src/components/forms/send-code-form.tsx b/src/components/forms/send-code-form.tsx index 5cf7c67..5fabf97 100644 --- a/src/components/forms/send-code-form.tsx +++ b/src/components/forms/send-code-form.tsx @@ -1,19 +1,72 @@ import OTPInput from "react-otp-input" -import { Link } from "react-router-dom" +import { Link, useSearchParams } from "react-router-dom" import { ErrorComponent } from "../common/error" import { Controller, SubmitHandler, useForm } from "react-hook-form" -import { Button } from "@headlessui/react" -import { LoginCodeInterface } from "../../types" +import { LoginWithOTP } from "../../types" import { yupResolver } from "@hookform/resolvers/yup" import { LoginCodeSchema } from "../../schema" +import { useEffect, useState } from "react" +import { ButtonComponent } from "../common/button" +import { useMutation } from "@tanstack/react-query" +import { loginWithOTP, sendOTPMutation } from "../../services/api/auth" +import toast from "react-hot-toast" export const SendCodeForm = () => { - const { handleSubmit, formState: { errors }, control } = useForm({ + const { mutate, isPending } = useMutation({ + mutationFn: sendOTPMutation, + mutationKey: ["send-otp"] + }) + const { mutate: loginOtpMutate, isPending: loginOtpPending } = useMutation({ + mutationFn: loginWithOTP, + mutationKey: ["login-otp"] + }) + const [seconds, setSeconds] = useState(30); + const [isTimeUp, setIsTimeUp] = useState(false); + const [searchParams] = useSearchParams(); + const { handleSubmit, formState: { errors }, control } = useForm({ resolver: yupResolver(LoginCodeSchema) }) - const submitCode: SubmitHandler = async (data) => { - console.log("data=>", data) + const handleSendOTPAgain = async () => { + await mutate({ + mobile_number: searchParams.get("phone_number") + }, + { + onSuccess: () => { + toast.success(`کد ورود برای شماره ${searchParams.get("phone_number")} ارسال شد`) + setSeconds(30) + setIsTimeUp(false) + }, + onError: (err: any) => { + console.log("err=>", err) + } + }) } + const submitCode: SubmitHandler = async (data) => { + loginOtpMutate({ + ...data, + mobile_number: searchParams.get("phone_number"), + }, + { + onSuccess: () => { + toast.success(`کد ورود برای شماره ${searchParams.get("phone_number")} ارسال شد`) + setSeconds(30) + setIsTimeUp(false) + }, + onError: (err: any) => { + console.log("err=>", err) + } + }) + } + useEffect(() => { + if (seconds > 0) { + const timer = setInterval(() => { + setSeconds(prevSeconds => prevSeconds - 1); + }, 1000); + return () => clearInterval(timer); + } else { + setIsTimeUp(true); + } + }, [seconds]); return (
@@ -23,7 +76,7 @@ export const SendCodeForm = () => { ورود با رمز یکبار مصرف
-

کد تایید ارسال شده به شماره 09376225448 را وارد کنید.

+

کد تایید ارسال شده به شماره {searchParams.get("phone_number")} را وارد کنید.

ویرایش

@@ -31,7 +84,7 @@ export const SendCodeForm = () => {
- ( + (
{ renderInput={(props) => } inputStyle="otp-inputs" /> - +
)} />
-

30 ثانیه دیگر تا دریافت کد

- -

ارسال مجدد

+ {!isTimeUp &&

{seconds} ثانیه دیگر تا دریافت کد

} + +
diff --git a/src/index.css b/src/index.css index 81bbb46..113ccf3 100644 --- a/src/index.css +++ b/src/index.css @@ -13,10 +13,16 @@ } + + @layer base { * { font-family: 'vazirmatn', 'vaziren'; + } + + body { direction: rtl; + scrollbar-width: none; } input[type=number]::-webkit-outer-spin-button, @@ -38,7 +44,7 @@ } #otp { - direction: ltr; + direction: ltr !important; } } diff --git a/src/schema/index.ts b/src/schema/index.ts index 030b639..51eb972 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -13,13 +13,13 @@ export const loginSchema = yup.object({ export const resetPasswordSchema = yup.object({ password: yup.string().min(8, "رمز عبور نمیتواند کمتر از 8 کاراکتر باشد").required("رمز عبور الزامی میباشد"), - repeatPassword: yup.string() + password_confirmation: yup.string() .oneOf([yup.ref('password')], "رمز عبور و تکرار آن باید یکسان باشند") .required("تکرار رمز عبور الزامی میباشد"), }) export const forgotPasswordSchema = yup.object({ - phoneNumber: yup.string() + mobile_number: yup.string() .required("وارد کردن شماره تماس الزامی میباشد") .min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد") .max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد") @@ -27,7 +27,7 @@ export const forgotPasswordSchema = yup.object({ }) export const LoginWithCodeSchema = yup.object({ - phoneNumber: yup.string() + mobile_number: yup.string() .required("وارد کردن شماره تماس الزامی میباشد") .min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد") .max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد") @@ -35,7 +35,11 @@ export const LoginWithCodeSchema = yup.object({ }) export const ResetPasswordCodeSchema = yup.object({ - code: yup.string() + mobile_number: yup.string() + .min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد") + .max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد") + .matches(/^[0-9]{11}$/, "شماره تماس نامعتبر"), + otp: yup.string() .required("وارد کردن کد الزامی میباشد") .min(5, "کد نمیتوند کمتر از 5 رقم باشد") .max(5, "کد نمیتواند بیشتر از 5 رقم باشد") @@ -43,7 +47,11 @@ export const ResetPasswordCodeSchema = yup.object({ }) export const LoginCodeSchema = yup.object({ - code: yup.string() + mobile_number: yup.string() + .min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد") + .max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد") + .matches(/^[0-9]{11}$/, "شماره تماس نامعتبر"), + otp: yup.string() .required("وارد کردن کد الزامی میباشد") .min(5, "کد نمیتوند کمتر از 5 رقم باشد") .max(5, " کد نمیتواند بیشتر از 5 رقم باشد") @@ -51,18 +59,19 @@ export const LoginCodeSchema = yup.object({ }) export const RegisterSchema = yup.object({ - name: yup.string().required("وارد کردن نام الزامی میباشد").min(3, "نام نمیتواند کمتر از 3 کاراکتر باشد").max(20, "نام نمیتواند بیشتر از 20 کاراکتر باشد"), - family: yup.string().required("وارد کردن نام خانوادگی الزامی میباشد").min(3, "نام خانوادگی نمیتواند کمتر از 3 کاراکتر باشد").max(25, "نام خانوادگی نمیتواند بیشتر از 25 کاراکتر باشد"), - city: yup.object().required("وارد کردن شهر الزامی مباشد"), - capCity: yup.object().required("وارد کردن استان الزامی مباشد"), - marketName: yup.string().required("وارد کردن نام فروشگاه الزامی میباشد").min(2, "نام فروشگاه نمیتواند کمتر از 3 کاراکتر باشد").max(30, "نام فروشگاه نمیتواند بیشتر از 30 کاراکتر باشد"), - phoneNumber: yup.string() + first_name: yup.string().required("وارد کردن نام الزامی میباشد").min(3, "نام نمیتواند کمتر از 3 کاراکتر باشد").max(20, "نام نمیتواند بیشتر از 20 کاراکتر باشد"), + last_name: yup.string().required("وارد کردن نام خانوادگی الزامی میباشد").min(3, "نام خانوادگی نمیتواند کمتر از 3 کاراکتر باشد").max(25, "نام خانوادگی نمیتواند بیشتر از 25 کاراکتر باشد"), + national_code: yup.string().required("وارد کردن کد ملی الزامی میباشد").min(10, "کد ملی نمیتواند کمتر از 10 کاراکتر باشد").max(10, "کد ملی نمیتونه بیشتر از 10 کاراکتر باشه").matches(/^[0-9]{10}$/, "کد نامعتبر"), + city_name: yup.object().required("وارد کردن شهر الزامی مباشد"), + province_name: yup.object().required("وارد کردن استان الزامی مباشد"), + shopName: yup.string().required("وارد کردن نام فروشگاه الزامی میباشد").min(2, "نام فروشگاه نمیتواند کمتر از 3 کاراکتر باشد").max(30, "نام فروشگاه نمیتواند بیشتر از 30 کاراکتر باشد"), + mobile_number: yup.string() .required("وارد کردن شماره تماس الزامی میباشد") .min(11, "شماره تماس نمیتوند کمتر از 11 رقم باشد") .max(11, "شماره تماس نمیتواند بیشتر از 11 رقم باشد") .matches(/^[0-9]{11}$/, "شماره تماس نامعتبر"), password: yup.string().min(8, "رمز عبور نمیتواند کمتر از 8 کاراکتر باشد").required("رمز عبور الزامی میباشد"), - repeatPassword: yup.string() + password_confirmation: yup.string() .oneOf([yup.ref('password')], "رمز عبور و تکرار آن باید یکسان باشند") .required("تکرار رمز عبور الزامی میباشد"), }) diff --git a/src/services/api/auth.ts b/src/services/api/auth.ts index 1b6d52a..c15f888 100644 --- a/src/services/api/auth.ts +++ b/src/services/api/auth.ts @@ -1,3 +1,9 @@ +import { LoginWithCodeInterface, LoginWithOTP, RegisterInterface, ResetPasswordInterface } from "../../types"; import axiosInstance from "../axios"; -export const loginMutation = (payload: any) => axiosInstance.post("/auth/gps/login/pass", payload) \ No newline at end of file +export const loginMutation = (payload: any) => axiosInstance.post("/auth/gps/login/pass", payload) +export const registerMutation = (payload: RegisterInterface) => axiosInstance.post("/auth/gps/register", payload) +export const sendOTPMutation = (payload: LoginWithCodeInterface) => axiosInstance.post("/auth/gps/otp/send", payload) +export const loginWithOTP = (payload: LoginWithOTP) => axiosInstance.post("/auth/gps/login/otp", payload) +export const checkOTP = (payload: LoginWithOTP) => axiosInstance.post("/auth/gps/otp/check", payload) +export const resetPasswordMutation = (payload: ResetPasswordInterface) => axiosInstance.post("/auth/gps/otp/reset-pass", payload) diff --git a/src/services/api/cities.ts b/src/services/api/cities.ts new file mode 100644 index 0000000..e5f5103 --- /dev/null +++ b/src/services/api/cities.ts @@ -0,0 +1,7 @@ +import { CitiesResponse } from "../../types"; +import axiosInstance from "../axios"; + +export const getCitiesList = () => axiosInstance.post("/cross/getRouteManager", { + url: "/api/GetCity", + db: "verity" +}) diff --git a/src/services/api/province.ts b/src/services/api/province.ts new file mode 100644 index 0000000..9f7eead --- /dev/null +++ b/src/services/api/province.ts @@ -0,0 +1,7 @@ +import { ProvinceResponse } from "../../types"; +import axiosInstance from "../axios"; + +export const getProvincesList = () => axiosInstance.post("/cross/getRouteManager", { + url: "/api/GetProvince", + db: "verity" +}) diff --git a/src/types/index.ts b/src/types/index.ts index 3b40edf..65ed87e 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -11,34 +11,39 @@ export interface LoginResponseInterface { } export interface ResetPasswordInterface { + mobile_number?: string | any, + otp?: string | any, password: string, - repeatPassword: string + password_confirmation: string } export interface ForgotPasswordInterface { - phoneNumber: string + mobile_number: string } export interface LoginWithCodeInterface { - phoneNumber: string + mobile_number: string | any } export interface ResetPasswordCodeInterface { - code: string + mobile_number?: string | any + otp: string } export interface LoginCodeInterface { - code: string + mobile_number: any + otp: string } export interface RegisterInterface { - name: string, - family: string, - city: object, - capCity: object, - marketName: string, - phoneNumber: string, - password: string, - repeatPassword: string, + first_name: string + last_name: string + national_code: string + city_name: object | any + province_name: object | any + shopName: string + mobile_number: string + password: string + password_confirmation: string } export interface SidebarInterface { @@ -280,4 +285,31 @@ export interface TableParams { page: number, rows: number, search: string +} + +export type ProvinceType = { + ProvinceID: number, + ProvinceName: string +} + +export interface ProvinceResponse { + data: { data: ProvinceType[] | [], } + error: string | null +} + +export interface LoginWithOTP { + mobile_number?: string | any + otp: string +} + +export type CitiesType = { + CityID: number, + CityName: string, + ProvinceID: string, + ProvinceName: string +} + +export interface CitiesResponse { + data: CitiesType[] | [], + error: string | null | any } \ No newline at end of file