edit api integrate
This commit is contained in:
parent
81c014f94c
commit
8b52dd4a29
@ -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");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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,
|
||||
|
||||
125
src/components/auth/ForgotPasswordForm.jsx
Normal file
125
src/components/auth/ForgotPasswordForm.jsx
Normal 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;
|
||||
@ -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 = {
|
||||
|
||||
@ -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,15 +91,74 @@ 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>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
|
||||
}
|
||||
@ -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">
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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;
|
||||
@ -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 />} />
|
||||
|
||||
Loading…
Reference in New Issue
Block a user