Refactor user information handling and improve the post-login experience.
* **Login Flow & User Profile:**
* Introduced `UserInfoService` to centralize user data storage and retrieval from local storage, replacing direct `localStorage` access for user `person` data.
* Modified the `LoginPage` to use `userInfoService.updateUserInfo()` after successful OTP verification.
* Implemented immediate fetching of the full user profile (`fetchUserProfile`) after login to ensure up-to-date user details.
* Adjusted post-login navigation logic: users with a complete profile (indicated by `userInfo?.username`) are now directed to the campaigns page, while those with incomplete profiles are guided to the profile page for completion.
* **UI/UX Improvements:**
* The `CustomRadio` component now displays its `value` as a fallback if no `label` is explicitly provided, improving usability for radio buttons.
* Adjusted styling for form field labels in `DynamicForm` to include a bottom margin, enhancing visual separation and readability.
209 lines
6.0 KiB
TypeScript
209 lines
6.0 KiB
TypeScript
"use client";
|
||
|
||
import { CustomButton } from "@/core/components/base/button";
|
||
import {
|
||
Card,
|
||
CardContent,
|
||
CardHeader,
|
||
CardTitle,
|
||
} from "@/core/components/base/card";
|
||
import {
|
||
Dialog,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
} from "@/core/components/base/dialog";
|
||
import { CustomInput } from "@/core/components/base/input";
|
||
import { OTPDialog } from "@modules/auth/components/otp/opt-dialog";
|
||
import { toast } from "react-toastify";
|
||
|
||
import { userInfoService } from "@/core/service/user-info.service";
|
||
import { DASHBOARD_ROUTE } from "@/modules/dashboard/routes/route.constant";
|
||
import { fetchUserProfile } from "@/modules/dashboard/service/user.service";
|
||
import { sendOtpService, verifyOtpService } from "@modules/auth/service/auth.service";
|
||
import { useMutation } from "@tanstack/react-query";
|
||
import { useState } from "react";
|
||
import { useNavigate } from "react-router-dom";
|
||
|
||
export function LoginPage() {
|
||
const navigate = useNavigate();
|
||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||
const [otpDialog, setOtpDialog] = useState(false);
|
||
const [phoneNumber, setPhoneNumber] = useState<string>("");
|
||
const [otp, setOtp] = useState<string>("");
|
||
const [error, setError] = useState("");
|
||
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
|
||
|
||
const validatePhoneNumber = (value: string) => {
|
||
const phoneRegex = /^[0-9]{10,11}$/;
|
||
if (!value) {
|
||
setError("شماره تلفن الزامی است");
|
||
return false;
|
||
} else if (!phoneRegex.test(value)) {
|
||
setError("شماره تلفن باید 10 یا 11 رقم باشد");
|
||
return false;
|
||
}
|
||
setError("");
|
||
return true;
|
||
};
|
||
|
||
const sendOtpMutation = useMutation({
|
||
mutationFn: sendOtpService,
|
||
|
||
onSuccess: (data) => {
|
||
setSubmitLoading(false);
|
||
if (data.resultType !== 0) {
|
||
toast.error(data.message);
|
||
return;
|
||
}
|
||
|
||
toast.success("کد یکبار مصرف ارسال شد");
|
||
setIsDialogOpen(false);
|
||
setOtpDialog(true);
|
||
// setPhoneNumber("");
|
||
},
|
||
|
||
onError: (error: any) => {
|
||
setSubmitLoading(false);
|
||
toast.error("مشکلی رخ داد");
|
||
console.log(error);
|
||
},
|
||
});
|
||
|
||
const verifyOtpMutation = useMutation({
|
||
mutationFn: verifyOtpService,
|
||
|
||
onSuccess: async(data) => {
|
||
setSubmitLoading(false);
|
||
if (data.resultType !== 0) {
|
||
toast.error(data.message);
|
||
return;
|
||
}
|
||
toast.success("ورود با موفقیت انجام شد");
|
||
const person = JSON.parse(data.data).Person;
|
||
const token = JSON.parse(data.data).Token;
|
||
localStorage.setItem("token", JSON.stringify(token));
|
||
userInfoService.updateUserInfo(person)
|
||
await fetchUserProfile()
|
||
const userInfo = userInfoService.getUserInfo()
|
||
setOtpDialog(false);
|
||
if (userInfo?.username) {
|
||
navigate(`${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.campaigns}`, {
|
||
replace: true,
|
||
});
|
||
} else {
|
||
navigate(`${DASHBOARD_ROUTE.sub}/${DASHBOARD_ROUTE.profile}`, {
|
||
replace: true,
|
||
});
|
||
}
|
||
},
|
||
|
||
onError: () => {
|
||
setSubmitLoading(false);
|
||
toast.error("خطا در تایید کد");
|
||
},
|
||
});
|
||
|
||
const handleSubmit = () => {
|
||
if (!validatePhoneNumber(phoneNumber)) return;
|
||
setSubmitLoading(true);
|
||
sendOtpMutation.mutate(phoneNumber);
|
||
};
|
||
|
||
const handleCancel = () => {
|
||
setPhoneNumber("");
|
||
setError("");
|
||
setIsDialogOpen(false);
|
||
};
|
||
|
||
const handleOtpSubmit = () => {
|
||
setSubmitLoading(true);
|
||
verifyOtpMutation.mutate({
|
||
mobile: phoneNumber,
|
||
code: otp,
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="flex min-h-screen items-center justify-center bg-gray-50 p-4">
|
||
{/* Card */}
|
||
<Card className="w-full max-w-md rounded-lg border border-gray-200 bg-white shadow-md sm:p-6">
|
||
<CardHeader>
|
||
<CardTitle className="text-center sm:text-left text-xl font-bold">
|
||
ورود به سامانه
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p className="mb-4 text-center text-sm text-gray-600 sm:text-left">
|
||
برای ورود به سامانه بر روی دکمه ورود کلیک نمایید.{" "}
|
||
</p>
|
||
<div className="flex justify-center sm:justify-start">
|
||
<CustomButton
|
||
variant="primary"
|
||
className="w-full sm:w-auto"
|
||
onClick={() => setIsDialogOpen(true)}
|
||
>
|
||
ورود
|
||
</CustomButton>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Dialog
|
||
isOpen={isDialogOpen}
|
||
onClose={() => {
|
||
handleCancel();
|
||
}}
|
||
>
|
||
<div className="w-full rounded-lg bg-white ">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-lg font-semibold text-center sm:text-left">
|
||
شماره تلفن خود را وارد کنید
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
|
||
<div className="my-4">
|
||
<CustomInput
|
||
label="شماره تلفن"
|
||
type="tel"
|
||
placeholder="09123456789"
|
||
value={phoneNumber}
|
||
onChange={(e) => {
|
||
setPhoneNumber(e.target.value);
|
||
if (error) validatePhoneNumber(e.target.value);
|
||
}}
|
||
error={error}
|
||
variant={error ? "error" : "primary"}
|
||
className="w-full"
|
||
/>
|
||
</div>
|
||
|
||
<DialogFooter className="flex flex-col gap-2 sm:flex-row sm:justify-end">
|
||
<CustomButton
|
||
variant="primary"
|
||
className="w-full sm:w-auto"
|
||
disabled={submitLoading}
|
||
onClick={handleSubmit}
|
||
>
|
||
تایید
|
||
</CustomButton>
|
||
</DialogFooter>
|
||
</div>
|
||
</Dialog>
|
||
|
||
<OTPDialog
|
||
isOpen={otpDialog}
|
||
otpValue={otp}
|
||
onChange={setOtp}
|
||
onClose={() => {
|
||
setIsDialogOpen(false);
|
||
}}
|
||
onSubmit={handleOtpSubmit}
|
||
submitLoading={submitLoading}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default LoginPage;
|