edit api integrate
This commit is contained in:
parent
81c014f94c
commit
8b52dd4a29
@ -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("/");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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'
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
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 { 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 = {
|
||||||
|
|||||||
@ -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>
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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">
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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;
|
||||||
@ -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 />} />
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user