edit api integrate

This commit is contained in:
Meenadeveloper 2026-03-03 18:14:58 +05:30
parent 81c014f94c
commit 8b52dd4a29
10 changed files with 592 additions and 199 deletions

View File

@ -65,7 +65,7 @@ const addErrorInterceptor = (instance) => {
instance.interceptors.response.use(
(response) => response,
(error) => {
if (error.response && error.response.status === 401) {
if (error.response && error.response.status === 401 && !error.config?.url?.includes("/login") && !error.config?.url?.includes("/forgot_password_send_otp") && !error.config?.url?.includes("/forgot_password_verify_otp") && !error.config?.url?.includes("/forgot_password_update")) {
console.error("Unauthorized access - logging out...");
localStorage.removeItem("access_token");
// window.location.href = "/";
@ -168,19 +168,15 @@ const clearFcmToken = () => {
};
export const logoutAPI = async () => {
console.log("Calling logout API...");
try {
const res = await axiosInstance.post(API_ENDPOINTS.LOGOUT);
return res.data; // assuming API returns { message: "Logout successful" }
await axiosInstance.post(API_ENDPOINTS.LOGOUT);
} catch (error) {
console.error("Logout API failed:", error);
// even if API fails, still clear local storage
} finally {
console.log("Clearing local storage...");
// always clear local storage
localStorage.removeItem("access_token");
clearFcmToken();
window.location.replace("/");
setAccessToken(null);
localStorage.clear();
sessionStorage.clear();
window.location.replace("/login");
}
};

View File

@ -20,6 +20,9 @@ import CheckIcon from '@mui/icons-material/Check'
import CloseIcon from '@mui/icons-material/Close'
import { keyframes } from '@mui/system'
import MuiDynamicInput from '../../utills/MuiDynamicInput'
import { useMutation } from '@tanstack/react-query'
import axiosInstance from '../../api/axiosInstance'
import toast from 'react-hot-toast'
// Keyframe animations
const scaleIn = keyframes`
@ -237,11 +240,9 @@ function PasswordRequirements({ password }) {
}
const ChangePasswordPage = () => {
const [loading, setLoading] = React.useState(false)
const [successModal, setSuccessModal] = React.useState(false)
const [formData, setFormData] = React.useState({
currentPassword: '',
newPassword: '',
confirmPassword: '',
})
@ -259,10 +260,6 @@ const ChangePasswordPage = () => {
const validateForm = () => {
const newErrors = {}
if (!formData.currentPassword) {
newErrors.currentPassword = 'Current password is required'
}
if (!formData.newPassword) {
newErrors.newPassword = 'New password is required'
} else if (formData.newPassword.length < 8) {
@ -277,31 +274,34 @@ const ChangePasswordPage = () => {
newErrors.confirmPassword = 'Passwords do not match'
}
if (formData.currentPassword === formData.newPassword) {
newErrors.newPassword = 'New password must be different from current'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
}
const handleSubmit = async () => {
if (!validateForm()) return
setLoading(true)
setTimeout(() => {
setLoading(false)
const { mutate, isPending } = useMutation({
mutationFn: async (password) => {
const response = await axiosInstance.post(`/change_password?password=${encodeURIComponent(password)}`)
return response.data
},
onSuccess: (data) => {
setSuccessModal(true)
console.log('Change Password Data:', {
currentPassword: formData.currentPassword,
newPassword: formData.newPassword,
toast.success(data.message || "Password changed successfully")
},
onError: (error) => {
console.error("Change password error:", error)
const msg = error.response?.data?.message || "Failed to update password"
toast.error(msg)
}
})
}, 1500)
const handleSubmit = () => {
if (!validateForm()) return
mutate(formData.newPassword)
}
const handleSuccessClose = () => {
setSuccessModal(false)
setFormData({ currentPassword: '', newPassword: '', confirmPassword: '' })
setFormData({ newPassword: '', confirmPassword: '' })
console.log('Navigate to profile or dashboard...')
}
@ -476,16 +476,6 @@ const ChangePasswordPage = () => {
{/* Form Fields */}
<Box sx={{ flex: 1 }}>
<MuiDynamicInput
type="password"
name="currentPassword"
label="Current Password"
value={formData.currentPassword}
onChange={handleChange}
error={errors.currentPassword}
showPasswordToggle
required
/>
<MuiDynamicInput
type="password"
@ -550,7 +540,7 @@ const ChangePasswordPage = () => {
variant="contained"
onClick={handleSubmit}
disabled={loading}
disabled={isPending}
sx={{
width:"fit-content",
mx: 'auto',
@ -573,7 +563,7 @@ const ChangePasswordPage = () => {
},
}}
>
{loading ? (
{isPending ? (
<CircularProgress size={24} sx={{ color: 'white' }} />
) : (
'Update Password'

View File

@ -3,7 +3,7 @@ import {
Card,
Typography,
Button,
Link,
// Link,
Container,
Dialog,
DialogContent,
@ -18,12 +18,14 @@ import {
import CheckCircleIcon from '@mui/icons-material/CheckCircle'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import LockResetIcon from '@mui/icons-material/LockReset'
import EmailIcon from '@mui/icons-material/Email'
import PhoneAndroidIcon from '@mui/icons-material/PhoneAndroid'
import VpnKeyIcon from '@mui/icons-material/VpnKey'
import { keyframes } from '@mui/system'
import { useEffect, useRef, useState } from 'react'
import MuiDynamicInput from '../../utills/MuiDynamicInput'
import axiosInstance from '../../api/axiosInstance'
import toast from 'react-hot-toast'
import { Link } from 'react-router-dom'
// Keyframe animations
const scaleIn = keyframes`
0% { transform: scale(0); opacity: 0; }
@ -151,7 +153,7 @@ function SuccessModal({ open, onClose, title, message }) {
// OTP Input Component
function OTPInput({ value, onChange, error, disabled }) {
const inputRefs = useRef([])
const otpLength = 6
const otpLength = 4
const handleChange = (index, e) => {
const val = e.target.value
@ -245,7 +247,7 @@ function OTPInput({ value, onChange, error, disabled }) {
const ForgotPassworForm = () => {
const steps = ['Enter Email', 'Verify OTP', 'Reset Password']
const steps = ['Enter Mobile', 'Verify OTP', 'Reset Password']
const [activeStep, setActiveStep] = useState(0)
const [loading, setLoading] = useState(false)
@ -253,7 +255,7 @@ const ForgotPassworForm = () => {
const [resendTimer, setResendTimer] = useState(0)
const [formData, setFormData] = useState({
email: '',
mobile: '',
otp: '',
newPassword: '',
confirmPassword: '',
@ -280,13 +282,13 @@ const ForgotPassworForm = () => {
}
}
// Step 1: Validate Email
const validateEmail = () => {
// Step 1: Validate Mobile
const validateMobile = () => {
const newErrors = {}
if (!formData.email) {
newErrors.email = 'Email is required'
} else if (!/^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(formData.email)) {
newErrors.email = 'Enter a valid email address'
if (!formData.mobile) {
newErrors.mobile = 'Mobile number is required'
} else if (!/^\d{10}$/.test(formData.mobile)) {
newErrors.mobile = 'Enter a valid 10-digit mobile number'
}
setErrors(newErrors)
return Object.keys(newErrors).length === 0
@ -295,7 +297,7 @@ const ForgotPassworForm = () => {
// Step 2: Validate OTP
const validateOTP = () => {
const newErrors = {}
if (!formData.otp || formData.otp.length !== 6) {
if (!formData.otp || formData.otp.length !== 4) {
newErrors.otp = 'Please enter the 6-digit OTP'
}
setErrors(newErrors)
@ -323,17 +325,23 @@ const ForgotPassworForm = () => {
return Object.keys(newErrors).length === 0
}
// Submit Email
const handleEmailSubmit = async () => {
if (!validateEmail()) return
// Submit Mobile
const handleMobileSubmit = async () => {
if (!validateMobile()) return
setLoading(true)
setTimeout(() => {
try {
const response = await axiosInstance.post(`/forgot_password_send_otp?mobile=${formData.mobile}`)
toast.success(response.data?.message || "OTP sent successfully")
setLoading(false)
setActiveStep(1)
setResendTimer(120) // 2 minutes timer
console.log('OTP sent to:', formData.email)
}, 1500)
} catch (error) {
setLoading(false)
console.error("Forgot password error:", error)
const errorMessage = error.response?.data?.message || error.response?.data?.error || "Failed to send OTP"
toast.error(errorMessage)
}
}
// Verify OTP
@ -341,18 +349,22 @@ const ForgotPassworForm = () => {
if (!validateOTP()) return
setLoading(true)
setTimeout(() => {
try {
const response = await axiosInstance.post(`/forgot_password_verify_otp?mobile=${formData.mobile}&otp=${formData.otp}`)
toast.success(response.data?.message || "OTP Verified Successfully")
setLoading(false)
setActiveStep(2)
console.log('OTP Verified:', formData.otp)
}, 1500)
} catch (error) {
setLoading(false)
console.error("OTP verification error:", error)
const errorMessage = error.response?.data?.message || error.response?.data?.error || "Invalid OTP"
toast.error(errorMessage)
}
}
// Resend OTP
const handleResendOTP = () => {
setResendTimer(120)
setFormData((prev) => ({ ...prev, otp: '' }))
console.log('OTP Resent to:', formData.email)
handleMobileSubmit()
}
// Submit New Password
@ -360,15 +372,18 @@ const ForgotPassworForm = () => {
if (!validatePassword()) return
setLoading(true)
setTimeout(() => {
try {
const encodedPassword = encodeURIComponent(formData.newPassword)
const response = await axiosInstance.post(`/forgot_password_update?mobile=${formData.mobile}&otp=${formData.otp}&password=${encodedPassword}`)
toast.success(response.data?.message || "Password updated successfully")
setLoading(false)
setSuccessModal(true)
console.log('Password Reset Data:', {
email: formData.email,
otp: formData.otp,
newPassword: formData.newPassword,
})
}, 1500)
} catch (error) {
setLoading(false)
console.error("Password update error:", error)
const errorMessage = error.response?.data?.message || error.response?.data?.error || "Failed to update password"
toast.error(errorMessage)
}
}
const handleBack = () => {
@ -381,7 +396,7 @@ const ForgotPassworForm = () => {
const handleSuccessClose = () => {
setSuccessModal(false)
setActiveStep(0)
setFormData({ email: '', otp: '', newPassword: '', confirmPassword: '' })
setFormData({ mobile: '', otp: '', newPassword: '', confirmPassword: '' })
// Navigate to login page
console.log('Navigate to login...')
}
@ -396,7 +411,7 @@ const ForgotPassworForm = () => {
const getStepIcon = (step) => {
switch (step) {
case 0:
return <EmailIcon />
return <PhoneAndroidIcon />
case 1:
return <VpnKeyIcon />
case 2:
@ -484,7 +499,7 @@ const ForgotPassworForm = () => {
{/* Form Content */}
<Box sx={{ p: 4 }}>
{/* Step 1: Email */}
{/* Step 1: Mobile */}
{activeStep === 0 && (
<Fade in={activeStep === 0}>
<Box>
@ -506,7 +521,7 @@ const ForgotPassworForm = () => {
justifyContent: 'center',
}}
>
<EmailIcon sx={{ fontSize: 40, color: '#A70710' }} />
<PhoneAndroidIcon sx={{ fontSize: 40, color: '#A70710' }} />
</Box>
</Box>
@ -514,17 +529,18 @@ const ForgotPassworForm = () => {
variant="body1"
sx={{ textAlign: 'center', mb: 3, color: 'text.secondary' }}
>
Enter your email address and we'll send you an OTP to reset
Enter your mobile number and we'll send you an OTP to reset
your password.
</Typography>
<MuiDynamicInput
type="email"
name="email"
label="Email Address"
value={formData.email}
type="tel"
name="mobile"
label="Mobile Number"
value={formData.mobile}
onChange={handleChange}
error={errors.email}
error={errors.mobile}
inputProps={{ maxLength: 10 }}
autoFocus
required
/>
@ -532,7 +548,7 @@ const ForgotPassworForm = () => {
<Button
fullWidth
variant="contained"
onClick={handleEmailSubmit}
onClick={handleMobileSubmit}
disabled={loading}
sx={{
mt: 2,
@ -554,7 +570,7 @@ const ForgotPassworForm = () => {
<Box sx={{ mt: 3, textAlign: 'center' }}>
<Link
href="/login"
to="/login"
sx={{
color: '#A70710',
fontWeight: 500,
@ -599,7 +615,7 @@ const ForgotPassworForm = () => {
variant="body1"
sx={{ textAlign: 'center', mb: 1, color: 'text.secondary' }}
>
We've sent a 6-digit OTP to
We've sent a 4-digit OTP to
</Typography>
<Typography
variant="body1"
@ -610,7 +626,7 @@ const ForgotPassworForm = () => {
color: '#4CAF50',
}}
>
{formData.email}
{formData.mobile}
</Typography>
<OTPInput
@ -652,7 +668,7 @@ const ForgotPassworForm = () => {
fullWidth
variant="contained"
onClick={handleOTPSubmit}
disabled={loading || formData.otp.length !== 6}
disabled={loading || formData.otp.length !== 4}
sx={{
py: 1.5,
borderRadius: 50,

View File

@ -0,0 +1,125 @@
import React, { useState } from "react";
import {
TextField,
Button,
Typography,
Box,
Link as MuiLink,
} from "@mui/material";
import { useNavigate, Link } from "react-router-dom";
import toast from "react-hot-toast";
import axiosInstance from "../../api/axiosInstance";
const ForgotPasswordForm = () => {
const navigate = useNavigate();
const [mobile, setMobile] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const handleChange = (e) => {
const value = e.target.value;
// Allow only numbers
if (/^\d*$/.test(value)) {
setMobile(value);
if (error) setError("");
}
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!mobile) {
setError("Mobile number is required");
return;
}
if (mobile.length !== 10) {
setError("Enter a valid 10-digit mobile number");
return;
}
setLoading(true);
try {
// API call with query parameter as requested
const response = await axiosInstance.post(`/forgot_password_send_otp?mobile=${mobile}`);
const successMessage = response.data?.message || "OTP sent successfully!";
toast.success(successMessage);
// Optional: Navigate to verify OTP page if needed
// navigate('/verify-otp', { state: { mobile } });
} catch (err) {
console.error("Forgot password error:", err);
const errorMessage = err.response?.data?.message ||
err.response?.data?.error ||
"Failed to send OTP. Please try again.";
toast.error(errorMessage);
} finally {
setLoading(false);
}
};
return (
<div className="bg-white p-8 rounded-lg shadow-sm h-full flex flex-col justify-center border border-gray-100">
<Typography variant="h4" fontWeight="700" color="primary" gutterBottom align="center">
Forgot Password
</Typography>
<Typography variant="body1" color="text.secondary" mb={4} align="center">
Enter your mobile number to receive an OTP
</Typography>
<form onSubmit={handleSubmit} autoComplete="off">
<Box display="flex" flexDirection="column" gap={3}>
<TextField
fullWidth
label="Mobile Number"
name="mobile"
value={mobile}
onChange={handleChange}
error={Boolean(error)}
helperText={error}
variant="outlined"
placeholder="Enter 10-digit mobile number"
inputProps={{ maxLength: 10, inputMode: "numeric" }}
disabled={loading}
/>
<Button
type="submit"
variant="contained"
color="primary"
size="large"
fullWidth
disabled={loading}
sx={{
py: 1.5,
fontSize: "1rem",
fontWeight: "bold",
textTransform: "none",
borderRadius: 2,
}}
>
{loading ? "Sending..." : "Send OTP"}
</Button>
<Box mt={2} textAlign="center">
<Typography variant="body2" color="text.secondary">
Remember your password?{" "}
<MuiLink
component={Link}
to="/login"
underline="hover"
fontWeight="bold"
color="primary"
>
Login
</MuiLink>
</Typography>
</Box>
</Box>
</form>
</div>
);
};
export default ForgotPasswordForm;

View File

@ -24,6 +24,9 @@ import { useTheme, useMediaQuery, ListItemIcon } from "@mui/material";
import { Home, Users, Heart, MessageCircle, Search, Bell } from "lucide-react";
import { isAuthenticated } from "../../utills/auth";
import userimg from "../../assets/images/bride1.jpg"
import axiosInstance, { logoutAPI } from "../../api/axiosInstance";
import toast from "react-hot-toast";
import { API_ENDPOINTS } from "../../api/apiEndpoints";
const NAV_LINKS = [
// { label: "Home", path: "/" },
{ label: "Matches", path: "/matches" },
@ -187,10 +190,8 @@ const ProfileHeader = () => {
};
const handleLogout = () => {
// Add your logout logic here
console.log("Logged out");
setLogoutModalOpen(false);
navigate("/login");
logoutAPI();
};
const modalStyle = {

View File

@ -1,17 +1,11 @@
import React from "react";
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react";
// Import your images
import Profile1 from "../../assets/images/bride1.jpg";
import Profile2 from "../../assets/images/bride2.jpg";
import Profile3 from "../../assets/images/bride3.jpg";
import Profile4 from "../../assets/images/bride4.jpg";
import { motion } from "framer-motion";
import ProfileCardItem from "../profiledashboard/ProfileCardItem";
import CakeIcon from "@mui/icons-material/Cake";
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
import LocationOnIcon from "@mui/icons-material/LocationOn";
import GroupsIcon from "@mui/icons-material/Groups";
import AutoAwesomeIcon from "@mui/icons-material/AutoAwesome";
export default function ProfileCard() {
@ -75,47 +69,9 @@ export default function ProfileCard() {
},
];
const buildProfileRows = (profile) => [
[
{
icon: <CakeIcon className="w-4 h-4 text-gray-700" />,
text: profile?.age || "-",
},
{
icon: <AccessibilityNewIcon className="w-4 h-4 text-gray-700" />,
text: profile?.height || "-",
},
],
[
{
icon: <AccountBalanceWalletIcon className="w-4 h-4 text-gray-700" />,
text: profile?.salary ? `${profile.salary} LPA` : "-",
},
{
icon: <LocationOnIcon className="w-4 h-4 text-gray-700" />,
text: profile?.location || "-",
},
],
[
{
icon: <GroupsIcon className="w-4 h-4 text-gray-700" />,
text: profile?.caste || "-",
},
{
icon: <AutoAwesomeIcon className="w-4 h-4 text-gray-700" />,
text: profile?.zodiac1 || "-",
},
],
[
{
icon: <AutoAwesomeIcon className="w-4 h-4 text-gray-700" />,
text: profile?.zodiac2 || "-",
},
],
];
return (
<div className="h-auto py-8 px-4">
{/* HEADING */}
<motion.div
initial={{ opacity: 0, y: -20 }}
@ -135,11 +91,70 @@ export default function ProfileCard() {
<div className="flex justify-center">
<div className="w-full max-w-[1400px] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{profiles.map((profile) => (
<ProfileCardItem
<div
key={profile.id}
profile={profile}
metaRows={buildProfileRows(profile)}
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md"
>
{/* IMAGE SECTION */}
<div className="relative">
<img
src={profile.image}
alt="profile"
className="w-full h-[320px] object-cover"
/>
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
<Crown size={18} color="#fff" />
</div>
<div className="absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg">
<Bookmark size={14} /> Shortlist
</div>
</div>
{/* CONTENT */}
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
<h2 className="text-center text-[22px] font-semibold mb-1">
{profile.name}
</h2>
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
<p>ID: {profile.idNumber}</p>
<p className="flex items-center gap-0.5">
<Eye size={12} /> {profile.lastSeen}
</p>
</div>
<div className="flex flex-wrap justify-center gap-2">
{[
profile.age,
profile.height,
profile.salary + " LPA",
profile.location,
profile.caste,
profile.zodiac1,
profile.zodiac2,
].map((v, i) => (
<span
key={i}
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
>
{v}
</span>
))}
</div>
<div className="flex gap-4 mt-[15px] justify-center">
<button className="px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900">
<X size={18} /> Decline
</button>
<button className="px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold">
<Heart size={18} /> Interest
</button>
</div>
</div>
</div>
))}
</div>
</div>

View File

@ -35,7 +35,7 @@ import { useSendOtp, useVerifyOtp } from "../hooks/useAuth";
const OTP_LENGTH = 4;
const OTP_TIMER_SEC = 120; // 2 minutes
const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Update }) => {
const dispatch = useDispatch();
const data = useSelector((state) => state.registerform.personalDetails);
const nameInputRef = useRef(null);
@ -1012,6 +1012,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
</div>
{/* Password */}
{!isStep1Update && (
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">
Password{requiredMark}
@ -1099,8 +1100,10 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
</div>
)}
</div>
)}
{/* Confirm Password */}
{!isStep1Update && (
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">
Confirm Password{requiredMark}
@ -1130,6 +1133,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
variant="outlined"
/>
</div>
)}
{/* State */}
<div className="flex flex-col gap-2">

View File

@ -23,7 +23,7 @@ import {
useRegisterStep4,
useRegisterStep5,
} from "../hooks/useRegister";
import { setAccessToken } from "../api/axiosInstance";
import axiosInstance, { setAccessToken } from "../api/axiosInstance";
import toast from "react-hot-toast";
const STEP_FIELD_ORDER = {
@ -180,6 +180,7 @@ const StepperForm = () => {
const initialStep = location.state?.step || 1;
const [currentStep, setCurrentStep] = useState(initialStep);
const [isStep1Update, setIsStep1Update] = useState(false);
const [errors, setErrors] = useState({});
const registerStep1 = useRegisterStep1();
@ -329,6 +330,53 @@ const StepperForm = () => {
}
}, [location.state?.step]);
useEffect(() => {
const fetchPersonalDetails = async () => {
try {
const response = await axiosInstance.get("/get_personal_details");
const data = response.data;
if (data.status === "success" && data.personal_details) {
const pd = data.personal_details;
setIsStep1Update(true);
const mappedImages = (pd.images || []).map((url, index) => ({
id: `server-${index}`,
preview: url,
name: `image-${index}.png`,
}));
const formattedDob = pd.dob ? pd.dob.split("T")[0] : "";
dispatch(
updatePersonalDetails({
name: pd.name || "",
mobileNumber: pd.mobile || "",
email: pd.email || "",
gender: pd.gender || "",
dob: formattedDob,
height: pd.height || "",
weight: pd.weight || "",
maritalStatus: pd.marital_status_id || "",
religion: pd.religion_id || "",
profileFor: pd.profile_for_id || "",
caste: pd.caste_id || "",
subCaste: pd.sub_caste_id || "",
gothram: pd.gothram_id || "",
raasi: pd.raasi_id || "",
star: pd.star_id || "",
state: pd.state_id || "",
city: pd.district_id || "",
pincode: pd.pincode || "",
profiles: mappedImages,
})
);
}
} catch (error) {
console.error("Error fetching personal details:", error);
}
};
fetchPersonalDetails();
}, [dispatch]);
@ -345,8 +393,6 @@ const StepperForm = () => {
"profileFor",
"caste",
"email",
"password",
"confirmPassword",
"state",
"city",
"pincode",
@ -358,6 +404,11 @@ const StepperForm = () => {
}
});
if (!isStep1Update) {
if (!personalDetails.password) newErrors.password = "This field is required";
if (!personalDetails.confirmPassword) newErrors.confirmPassword = "This field is required";
}
if (
personalDetails.email &&
!/\S+@\S+\.\S+/.test(personalDetails.email)
@ -474,7 +525,7 @@ const StepperForm = () => {
formData.append("state", personalDetails.state);
formData.append("district", personalDetails.city);
formData.append("password", personalDetails.password || "");
formData.append("fcm_token", localStorage.getItem("fcm_token") || "");
formData.append("web_fcm_token", localStorage.getItem("fcm_token") || "");
if (personalDetails.profiles && Array.isArray(personalDetails.profiles)) {
for (const [index, item] of personalDetails.profiles.entries()) {
@ -633,8 +684,14 @@ const StepperForm = () => {
try {
if (currentStep === 1) {
const payload = await buildRegisterStep1Payload();
const res = await registerStep1.mutateAsync(payload);
const token = extractAccessToken(res);
let res;
if (isStep1Update) {
res = await axiosInstance.post("/update_personal_details", payload);
} else {
res = await registerStep1.mutateAsync(payload);
}
const token = extractAccessToken(res.data || res);
if (token) {
setAccessToken(token);
}
@ -725,6 +782,7 @@ const StepperForm = () => {
onSubmitStep={handleStepSubmit}
errors={errors}
onFieldChange={clearFieldErrors}
isStep1Update={isStep1Update}
/>
);
case 2:

View File

@ -1,25 +1,203 @@
import LoginPanel from "../../components/auth/LoginPanel"
import PromoPanel from "../../components/auth/PromoPanel"
import React, { useState } from "react";
import {
Box,
Button,
TextField,
Typography,
InputAdornment,
IconButton,
Link as MuiLink,
} from "@mui/material";
import { Visibility, VisibilityOff } from "@mui/icons-material";
import { useNavigate, Link } from "react-router-dom";
import toast from "react-hot-toast";
import axiosInstance, { setAccessToken } from "../../api/axiosInstance";
import PromoPanel from "../../components/auth/PromoPanel";
const LoginPage = () => {
return (
<>
<div className="h-full max-h-dvh w-full max-w-[1100px] mx-auto">
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
mobile: "",
password: "",
});
const [errors, setErrors] = useState({});
<div class="my-6 grid grid-cols-1 md:grid-cols-[60%_40%] gap-2 ">
<div class="">
{/* Left: Promo */}
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
if (errors[name]) {
setErrors((prev) => ({ ...prev, [name]: "" }));
}
};
const validate = () => {
const newErrors = {};
if (!formData.mobile) newErrors.mobile = "Mobile number is required";
else if (!/^\d{10}$/.test(formData.mobile))
newErrors.mobile = "Enter a valid 10-digit mobile number";
if (!formData.password) newErrors.password = "Password is required";
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleLogin = async (e) => {
e.preventDefault();
if (!validate()) return;
setLoading(true);
// Retrieve FCM token from localStorage
const fcmToken = localStorage.getItem("fcm_token") || "";
const payload = {
mobile: formData.mobile,
password: formData.password,
web_fcm_token: fcmToken,
};
try {
const response = await axiosInstance.post("/login", payload);
const data = response.data;
// Extract token from response
const token = data?.access_token || data?.token || data?.accessToken || data?.data?.access_token;
if (token) {
// Store token in localStorage and axios instance
localStorage.setItem("access_token", token);
setAccessToken(token);
toast.success("Login Successful!");
navigate("/dashboard-home");
} else {
toast.error("Login failed: No access token received.");
}
} catch (error) {
console.error("Login error:", error);
const errorMessage =
error?.response?.data?.message ||
error?.response?.data?.error ||
"Login failed. Please check your credentials.";
toast.error(errorMessage);
} finally {
setLoading(false);
}
};
return (
<div className="h-full max-h-dvh w-full max-w-[1100px] mx-auto">
<div className="my-6 grid grid-cols-1 md:grid-cols-[60%_40%] gap-2">
<div className="">
<PromoPanel />
</div>
<div class="">
{/* Right: Login */}
<LoginPanel />
</div>
</div>
</div>
</>
)
}
<div className="">
<div className="bg-white p-8 rounded-lg shadow-sm h-full flex flex-col justify-center border border-gray-100">
<Typography variant="h4" fontWeight="700" color="primary" gutterBottom align="center">
Welcome Back
</Typography>
<Typography variant="body1" color="text.secondary" mb={4} align="center">
Please login to your account
</Typography>
export default LoginPage
<form onSubmit={handleLogin}>
<Box display="flex" flexDirection="column" gap={3}>
<TextField
fullWidth
label="Mobile Number"
name="mobile"
value={formData.mobile}
onChange={handleChange}
error={Boolean(errors.mobile)}
helperText={errors.mobile}
variant="outlined"
placeholder="Enter 10-digit mobile number"
inputProps={{ maxLength: 10 }}
/>
<TextField
fullWidth
label="Password"
name="password"
type={showPassword ? "text" : "password"}
value={formData.password}
onChange={handleChange}
error={Boolean(errors.password)}
helperText={errors.password}
variant="outlined"
placeholder="Enter your password"
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>
<Box display="flex" justifyContent="flex-end">
<MuiLink
component={Link}
to="/forgot-password"
underline="hover"
variant="body2"
color="primary"
>
Forgot Password?
</MuiLink>
</Box>
<Button
type="submit"
variant="contained"
color="primary"
size="large"
fullWidth
disabled={loading}
sx={{
py: 1.5,
fontSize: "1rem",
fontWeight: "bold",
textTransform: "none",
borderRadius: 2,
}}
>
{loading ? "Logging in..." : "Login"}
</Button>
<Box mt={2} textAlign="center">
<Typography variant="body2" color="text.secondary">
Don't have an account?{" "}
<MuiLink
component={Link}
to="/registration"
underline="hover"
fontWeight="bold"
color="primary"
>
Register Now
</MuiLink>
</Typography>
</Box>
</Box>
</form>
</div>
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@ -1,23 +1,33 @@
import { lazy } from "react";
import { Route } from "react-router-dom";
import { Route, Navigate, Outlet } from "react-router-dom";
import HomeLayout from "../layout/HomeLayout";
import { isAuthenticated } from "../utills/auth";
const HomePage = lazy(() => import("../pages/HomePage"));
const LoginPage = lazy(() => import("../pages/auth/LoginPage"));
const ForgotPasswordPage = lazy(() => import("../pages/auth/ForgotPasswordPage"));
const StepperForm = lazy(() => import("../feature/StepperForm"));
const NotFound = lazy(() => import("../pages/NotFound"));
const PublicGuard = () => {
const auth = isAuthenticated();
return auth ? <Navigate to="/dashboard-home" replace /> : <Outlet />;
};
const PublicRoutes = () => {
return (
<>
<Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} />
<Route element={<PublicGuard />}>
<Route path="/registration" element={<StepperForm />} />
</Route>
</Route>
<Route element={<PublicGuard />}>
<Route path="/login" element={<LoginPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
</Route>
{/* 404 route MUST be last */}
<Route path="*" element={<NotFound />} />