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

View File

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

View File

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

View File

@ -1,17 +1,11 @@
import React from "react"; import React from "react";
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react";
// Import your images // Import your images
import Profile1 from "../../assets/images/bride1.jpg"; import Profile1 from "../../assets/images/bride1.jpg";
import Profile2 from "../../assets/images/bride2.jpg"; import Profile2 from "../../assets/images/bride2.jpg";
import Profile3 from "../../assets/images/bride3.jpg"; import Profile3 from "../../assets/images/bride3.jpg";
import Profile4 from "../../assets/images/bride4.jpg"; import Profile4 from "../../assets/images/bride4.jpg";
import { motion } from "framer-motion"; 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() { export default function ProfileCard() {
@ -75,75 +69,96 @@ export default function ProfileCard() {
}, },
]; ];
const buildProfileRows = (profile) => [ return (
[ <div className="h-auto py-8 px-4">
{
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 ( {/* HEADING */}
<div className="h-auto py-8 px-4"> <motion.div
{/* HEADING */} initial={{ opacity: 0, y: -20 }}
<motion.div animate={{ opacity: 1, y: 0 }}
initial={{ opacity: 0, y: -20 }} transition={{ duration: 0.5 }}
animate={{ opacity: 1, y: 0 }} className="text-center mb-12"
transition={{ duration: 0.5 }} >
className="text-center mb-12" <h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
> Daily Recommended
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3"> </h1>
Daily Recommended <p className="text-gray-900 text-[12px]">
</h1> Find your perfect match today
<p className="text-gray-900 text-[12px]"> </p>
Find your perfect match today </motion.div>
</p>
</motion.div>
{/* CARDS GRID */} {/* CARDS GRID */}
<div className="flex justify-center"> <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"> <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) => ( {profiles.map((profile) => (
<ProfileCardItem <div
key={profile.id} key={profile.id}
profile={profile} className="w-full rounded-[28px] overflow-hidden bg-white shadow-md"
metaRows={buildProfileRows(profile)} >
/> {/* IMAGE SECTION */}
))} <div className="relative">
</div> <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>
</div> </div>
); </div>
);
} }

View File

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

View File

@ -23,7 +23,7 @@ import {
useRegisterStep4, useRegisterStep4,
useRegisterStep5, useRegisterStep5,
} from "../hooks/useRegister"; } from "../hooks/useRegister";
import { setAccessToken } from "../api/axiosInstance"; import axiosInstance, { setAccessToken } from "../api/axiosInstance";
import toast from "react-hot-toast"; import toast from "react-hot-toast";
const STEP_FIELD_ORDER = { const STEP_FIELD_ORDER = {
@ -180,6 +180,7 @@ const StepperForm = () => {
const initialStep = location.state?.step || 1; const initialStep = location.state?.step || 1;
const [currentStep, setCurrentStep] = useState(initialStep); const [currentStep, setCurrentStep] = useState(initialStep);
const [isStep1Update, setIsStep1Update] = useState(false);
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const registerStep1 = useRegisterStep1(); const registerStep1 = useRegisterStep1();
@ -329,6 +330,53 @@ const StepperForm = () => {
} }
}, [location.state?.step]); }, [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", "profileFor",
"caste", "caste",
"email", "email",
"password",
"confirmPassword",
"state", "state",
"city", "city",
"pincode", "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 ( if (
personalDetails.email && personalDetails.email &&
!/\S+@\S+\.\S+/.test(personalDetails.email) !/\S+@\S+\.\S+/.test(personalDetails.email)
@ -474,7 +525,7 @@ const StepperForm = () => {
formData.append("state", personalDetails.state); formData.append("state", personalDetails.state);
formData.append("district", personalDetails.city); formData.append("district", personalDetails.city);
formData.append("password", personalDetails.password || ""); 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)) { if (personalDetails.profiles && Array.isArray(personalDetails.profiles)) {
for (const [index, item] of personalDetails.profiles.entries()) { for (const [index, item] of personalDetails.profiles.entries()) {
@ -633,8 +684,14 @@ const StepperForm = () => {
try { try {
if (currentStep === 1) { if (currentStep === 1) {
const payload = await buildRegisterStep1Payload(); const payload = await buildRegisterStep1Payload();
const res = await registerStep1.mutateAsync(payload); let res;
const token = extractAccessToken(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) { if (token) {
setAccessToken(token); setAccessToken(token);
} }
@ -725,6 +782,7 @@ const StepperForm = () => {
onSubmitStep={handleStepSubmit} onSubmitStep={handleStepSubmit}
errors={errors} errors={errors}
onFieldChange={clearFieldErrors} onFieldChange={clearFieldErrors}
isStep1Update={isStep1Update}
/> />
); );
case 2: case 2:

View File

@ -1,25 +1,203 @@
import LoginPanel from "../../components/auth/LoginPanel" import React, { useState } from "react";
import PromoPanel from "../../components/auth/PromoPanel" 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 = () => { const LoginPage = () => {
const navigate = useNavigate();
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState({
mobile: "",
password: "",
});
const [errors, setErrors] = useState({});
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 ( return (
<> <div className="h-full max-h-dvh w-full max-w-[1100px] mx-auto">
<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="">
<div class="my-6 grid grid-cols-1 md:grid-cols-[60%_40%] gap-2 ">
<div class="">
{/* Left: Promo */}
<PromoPanel /> <PromoPanel />
</div> </div>
<div class=""> <div className="">
{/* Right: Login */} <div className="bg-white p-8 rounded-lg shadow-sm h-full flex flex-col justify-center border border-gray-100">
<LoginPanel /> <Typography variant="h4" fontWeight="700" color="primary" gutterBottom align="center">
</div> Welcome Back
</div> </Typography>
</div> <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 { lazy } from "react";
import { Route } from "react-router-dom"; import { Route, Navigate, Outlet } from "react-router-dom";
import HomeLayout from "../layout/HomeLayout"; import HomeLayout from "../layout/HomeLayout";
import { isAuthenticated } from "../utills/auth";
const HomePage = lazy(() => import("../pages/HomePage")); const HomePage = lazy(() => import("../pages/HomePage"));
const LoginPage = lazy(() => import("../pages/auth/LoginPage")); const LoginPage = lazy(() => import("../pages/auth/LoginPage"));
const ForgotPasswordPage = lazy(() => import("../pages/auth/ForgotPasswordPage")); const ForgotPasswordPage = lazy(() => import("../pages/auth/ForgotPasswordPage"));
const StepperForm = lazy(() => import("../feature/StepperForm")); const StepperForm = lazy(() => import("../feature/StepperForm"));
const NotFound = lazy(() => import("../pages/NotFound")); const NotFound = lazy(() => import("../pages/NotFound"));
const PublicGuard = () => {
const auth = isAuthenticated();
return auth ? <Navigate to="/dashboard-home" replace /> : <Outlet />;
};
const PublicRoutes = () => { const PublicRoutes = () => {
return ( return (
<> <>
<Route element={<HomeLayout />}> <Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/registration" element={<StepperForm />} /> <Route element={<PublicGuard />}>
<Route path="/registration" element={<StepperForm />} />
</Route>
</Route> </Route>
<Route path="/login" element={<LoginPage />} /> <Route element={<PublicGuard />}>
<Route path="/forgot-password" element={<ForgotPasswordPage />} /> <Route path="/login" element={<LoginPage />} />
<Route path="/forgot-password" element={<ForgotPasswordPage />} />
</Route>
{/* 404 route MUST be last */} {/* 404 route MUST be last */}
<Route path="*" element={<NotFound />} /> <Route path="*" element={<NotFound />} />