yari-garan/src/modules/auth/pages/login/index.tsx
MehrdadAdabi 518650ccd5 feat(auth): Enhance login flow and user profile management
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.
2025-11-27 16:25:27 +03:30

209 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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;