Add react-toastify for notifications and enhance OTP handling in LoginPage
This commit is contained in:
parent
38263c7a74
commit
d9d97da7da
14
package-lock.json
generated
14
package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-i18next": "^16.3.5",
|
"react-i18next": "^16.3.5",
|
||||||
"react-router-dom": "^7.9.6",
|
"react-router-dom": "^7.9.6",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
|
@ -6563,6 +6564,19 @@
|
||||||
"react-dom": ">=18"
|
"react-dom": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-toastify": {
|
||||||
|
"version": "11.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
|
||||||
|
"integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"clsx": "^2.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18 || ^19",
|
||||||
|
"react-dom": "^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-i18next": "^16.3.5",
|
"react-i18next": "^16.3.5",
|
||||||
"react-router-dom": "^7.9.6",
|
"react-router-dom": "^7.9.6",
|
||||||
|
"react-toastify": "^11.0.5",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ const CustomInput = React.forwardRef<HTMLInputElement, CustomInputProps>(
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{label && (
|
{label && (
|
||||||
<label className="mb-2 block text-sm font-medium text-foreground">
|
<label className="mb-2 block text-sm font-medium text-foreground text-right">
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
)}
|
)}
|
||||||
|
|
@ -48,20 +48,7 @@ const CustomInput = React.forwardRef<HTMLInputElement, CustomInputProps>(
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
{error && (
|
{error && (
|
||||||
<p className="mt-2 text-sm text-red-600 flex items-center gap-1">
|
<p className="justify-end mt-2 text-sm text-red-600 flex items-center gap-1">
|
||||||
<svg
|
|
||||||
className="h-4 w-4"
|
|
||||||
fill="none"
|
|
||||||
strokeWidth="2"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
{error}
|
{error}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
13
src/core/service/api-address.ts
Normal file
13
src/core/service/api-address.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
export const API_ADDRESS = {
|
||||||
|
BASE_URL: "https://yarigaran-back.pelekan.org",
|
||||||
|
auth: {
|
||||||
|
otp: "/api/SignUpLoginBySMS",
|
||||||
|
verifyOtp: "/api/verifyloginbysms",
|
||||||
|
},
|
||||||
|
// LOGIN: "/auth/login",
|
||||||
|
// VERIFY_OTP: "/auth/verify-otp",
|
||||||
|
// REGISTER: "/auth/register",
|
||||||
|
// REFRESH_TOKEN: "/auth/refresh-token",
|
||||||
|
// USER_PROFILE: "/user/profile",
|
||||||
|
// UPDATE_PROFILE: "/user/update-profile",
|
||||||
|
};
|
||||||
13
src/main.tsx
13
src/main.tsx
|
|
@ -1,6 +1,7 @@
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||||
|
import { ToastContainer } from "react-toastify";
|
||||||
import "./core/config/i18n.ts";
|
import "./core/config/i18n.ts";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { rootRoutes } from "./router/rootRoutes.ts";
|
import { rootRoutes } from "./router/rootRoutes.ts";
|
||||||
|
|
@ -10,5 +11,17 @@ const router = createBrowserRouter(rootRoutes);
|
||||||
createRoot(document.getElementById("root")!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
|
<ToastContainer
|
||||||
|
position="top-right"
|
||||||
|
autoClose={4000}
|
||||||
|
hideProgressBar={false}
|
||||||
|
newestOnTop={false}
|
||||||
|
closeOnClick
|
||||||
|
rtl={true}
|
||||||
|
pauseOnFocusLoss
|
||||||
|
draggable
|
||||||
|
pauseOnHover
|
||||||
|
theme="light"
|
||||||
|
/>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export const OTPDialog: FC<OTPDialogProps> = ({
|
||||||
onClose,
|
onClose,
|
||||||
onChange,
|
onChange,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
|
submitLoading,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Dialog isOpen={isOpen} onClose={onClose}>
|
<Dialog isOpen={isOpen} onClose={onClose}>
|
||||||
|
|
@ -35,6 +36,7 @@ export const OTPDialog: FC<OTPDialogProps> = ({
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
|
disabled={submitLoading}
|
||||||
>
|
>
|
||||||
تایید
|
تایید
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
|
|
|
||||||
|
|
@ -4,4 +4,5 @@ export interface OTPDialogProps {
|
||||||
onClose: () => void; // برای بستن دیالوگ
|
onClose: () => void; // برای بستن دیالوگ
|
||||||
onChange: (value: string) => void; // تغییر OTP
|
onChange: (value: string) => void; // تغییر OTP
|
||||||
onSubmit: () => void; // تایید OTP
|
onSubmit: () => void; // تایید OTP
|
||||||
|
submitLoading: boolean; // وضعیت بارگذاری تایید
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { useRef, useState, type ChangeEvent, type KeyboardEvent } from "react";
|
import { useRef, useState, type ChangeEvent, type KeyboardEvent } from "react";
|
||||||
import type { OTPReceiverProps } from "../../pages/login/login.type";
|
import type { OTPReceiverProps } from "../../pages/login/login.type";
|
||||||
|
|
||||||
export function OTPReceiver({ length = 4, onChange }: OTPReceiverProps) {
|
export function OTPReceiver({ length = 5, onChange }: OTPReceiverProps) {
|
||||||
const [values, setValues] = useState(Array(length).fill(""));
|
const [values, setValues] = useState(Array(length).fill(""));
|
||||||
const inputsRef = useRef<HTMLInputElement[]>([]);
|
const inputsRef = useRef<HTMLInputElement[]>([]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ import {
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from "@/core/components/base/dialog";
|
} from "@/core/components/base/dialog";
|
||||||
import { CustomInput } from "@/core/components/base/input";
|
import { CustomInput } from "@/core/components/base/input";
|
||||||
|
import { API_ADDRESS } from "@/core/service/api-address";
|
||||||
|
import API from "@/core/service/axios";
|
||||||
import { OTPDialog } from "@modules/auth/components/otp/opt-dialog";
|
import { OTPDialog } from "@modules/auth/components/otp/opt-dialog";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
import to from "await-to-js";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function LoginPage() {
|
export function LoginPage() {
|
||||||
|
|
@ -23,6 +28,7 @@ export function LoginPage() {
|
||||||
const [phoneNumber, setPhoneNumber] = useState("");
|
const [phoneNumber, setPhoneNumber] = useState("");
|
||||||
const [otp, setOtp] = useState("");
|
const [otp, setOtp] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const validatePhoneNumber = (value: string) => {
|
const validatePhoneNumber = (value: string) => {
|
||||||
const phoneRegex = /^[0-9]{10,11}$/;
|
const phoneRegex = /^[0-9]{10,11}$/;
|
||||||
|
|
@ -37,9 +43,24 @@ export function LoginPage() {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = async () => {
|
||||||
if (validatePhoneNumber(phoneNumber)) {
|
if (validatePhoneNumber(phoneNumber)) {
|
||||||
console.log("Phone number submitted:", phoneNumber);
|
setSubmitLoading(true);
|
||||||
|
const [err, res] = await to(API.post(API_ADDRESS.auth.otp, phoneNumber));
|
||||||
|
setSubmitLoading(false);
|
||||||
|
|
||||||
|
if (res?.data.resultType !== 0) {
|
||||||
|
toast.error(res?.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res?.data.resultType === 0) {
|
||||||
|
toast.success("ورود با موفقیت انجام شد");
|
||||||
|
localStorage.setItem("token", res.data.data.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsDialogOpen(false);
|
setIsDialogOpen(false);
|
||||||
setOtpDialog(true);
|
setOtpDialog(true);
|
||||||
setPhoneNumber("");
|
setPhoneNumber("");
|
||||||
|
|
@ -51,6 +72,27 @@ export function LoginPage() {
|
||||||
setError("");
|
setError("");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleOtpSubmit = async () => {
|
||||||
|
setSubmitLoading(true);
|
||||||
|
const [err, res] = await to(
|
||||||
|
API.post(API_ADDRESS.auth.verifyOtp, { phoneNumber, otp })
|
||||||
|
);
|
||||||
|
if (res?.data.resultType !== 0) {
|
||||||
|
toast.error(res?.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res?.data.resultType === 0) {
|
||||||
|
toast.success("ورود با موفقیت انجام شد");
|
||||||
|
localStorage.setItem("token", res.data.data.token);
|
||||||
|
}
|
||||||
|
setSubmitLoading(false);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setOtpDialog(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
||||||
{/* Card */}
|
{/* Card */}
|
||||||
|
|
@ -109,6 +151,7 @@ export function LoginPage() {
|
||||||
<CustomButton
|
<CustomButton
|
||||||
variant="primary"
|
variant="primary"
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
|
disabled={submitLoading}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
تایید
|
تایید
|
||||||
|
|
@ -122,7 +165,8 @@ export function LoginPage() {
|
||||||
otpValue={otp}
|
otpValue={otp}
|
||||||
onChange={setOtp}
|
onChange={setOtp}
|
||||||
onClose={() => setIsDialogOpen(false)}
|
onClose={() => setIsDialogOpen(false)}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleOtpSubmit}
|
||||||
|
submitLoading={submitLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
export const AUTH_ROUTE = {
|
||||||
|
LOGIN: "login",
|
||||||
|
REGISTER: "register",
|
||||||
|
};
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import type { AppRoute } from "@core/types/router.type";
|
import type { AppRoute } from "@core/types/router.type";
|
||||||
import LoginPage from "../pages/login";
|
import LoginPage from "../pages/login";
|
||||||
import RegisterPage from "../pages/register";
|
import RegisterPage from "../pages/register";
|
||||||
|
import { AUTH_ROUTE } from "./route.constant";
|
||||||
|
|
||||||
export const authRoutes: AppRoute[] = [
|
export const authRoutes: AppRoute[] = [
|
||||||
{
|
{
|
||||||
path: "/auth",
|
path: "/auth",
|
||||||
children: [
|
children: [
|
||||||
{ path: "login", element: <LoginPage /> },
|
{ path: AUTH_ROUTE.LOGIN, element: <LoginPage /> },
|
||||||
{ path: "register", element: <RegisterPage /> },
|
{ path: AUTH_ROUTE.REGISTER, element: <RegisterPage /> },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user