import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { updatePersonalDetails, clearAllStepsFrom } from "../redux/registrationFormSlice"; import { Grid, TextField, FormControl, InputLabel, Select, MenuItem, Button, Box, Typography, InputAdornment, IconButton, FormHelperText, CircularProgress, Autocomplete, } from "@mui/material"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import Visibility from "@mui/icons-material/Visibility"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import AdvancedDropzone from "./AdvancedDropzone"; import toast from "react-hot-toast"; import { usePersonalDetailsMasters, useCasteMasters, useSubCasteMasters, } from "../hooks/useDependentMasters"; import { useSendOtp, useVerifyOtp } from "../hooks/useAuth"; const OTP_LENGTH = 4; const OTP_TIMER_SEC = 60; const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChange, isEditMode }) => { const dispatch = useDispatch(); const data = useSelector((state) => state.registerform.personalDetails); const nameInputRef = useRef(null); const mobileInputRef = useRef(null); const requiredMark = *; const [showOtp, setShowOtp] = useState(false); const [otp, setOtp] = useState(new Array(OTP_LENGTH).fill("")); const [otpTimer, setOtpTimer] = useState(0); const [otpError, setOtpError] = useState(""); const [mobileOtpVerified, setMobileOtpVerified] = useState(false); const [mobileNumberError, setMobileNumberError] = useState(""); const [showPassword, setShowPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [localErrors, setLocalErrors] = useState({}); const sendOtp = useSendOtp(); const verifyOtp = useVerifyOtp(); const errors = useMemo(() => ({ ...externalErrors, ...localErrors }), [externalErrors, localErrors]); const { data: personalMasters, isLoading: isPersonalMastersLoading } = usePersonalDetailsMasters(); const casteQuery = useCasteMasters(data.religion); const subCasteQuery = useSubCasteMasters(data.caste); const genderOptions = useMemo(() => personalMasters?.gender ?? ["Male", "Female"], [personalMasters]); const maritalStatusOptions = useMemo(() => personalMasters?.marital_status ?? [], [personalMasters]); const religionOptions = useMemo(() => personalMasters?.religion ?? [], [personalMasters]); const profileForOptions = useMemo(() => personalMasters?.profileCreatedFor ?? [], [personalMasters]); const languageOptions = useMemo(() => personalMasters?.languages ?? [], [personalMasters]); const heightOptions = useMemo(() => personalMasters?.heights ?? [], [personalMasters]); const complexionOptions = useMemo(() => personalMasters?.complextions ?? [], [personalMasters]); const physicalStatusOptions = useMemo(() => personalMasters?.physicalStatus ?? [], [personalMasters]); const casteOptions = useMemo(() => { const raw = casteQuery.data; if (!raw) return []; return Array.isArray(raw) ? raw : raw.caste || raw.data || []; }, [casteQuery.data]); const subCasteOptions = useMemo(() => { const raw = subCasteQuery.data; if (!raw) return []; return Array.isArray(raw) ? raw : raw.sub_caste || raw.subCaste || raw.data || []; }, [subCasteQuery.data]); useEffect(() => { if (!isEditMode) { if (!data.religion) dispatch(updatePersonalDetails({ religion: 1 })); if (!data.caste) dispatch(updatePersonalDetails({ caste: 1 })); } }, [isEditMode, data.religion, data.caste, dispatch]); useEffect(() => { if (nameInputRef.current) { nameInputRef.current.focus(); } }, []); const handleChange = (field, value) => { const updates = { [field]: value }; const fieldsToClear = [field]; if (field === "name" && !/^[a-zA-Z\s]*$/.test(value)) return; if (field === "mobile") { setMobileNumberError(""); if (data.verifiedMobileNumber && value === data.verifiedMobileNumber) { setMobileOtpVerified(true); setShowOtp(false); } else { setMobileOtpVerified(false); setShowOtp(false); setOtp(new Array(OTP_LENGTH).fill("")); setOtpError(""); setOtpTimer(0); } } if (field === "religion") { updates.caste = ""; updates.sub_caste = ""; fieldsToClear.push("caste", "sub_caste"); } if (field === "caste") { updates.sub_caste = ""; fieldsToClear.push("sub_caste"); } dispatch(updatePersonalDetails(updates)); if (onFieldChange) onFieldChange(fieldsToClear); if (!isEditMode) { dispatch(clearAllStepsFrom(2)); } if (localErrors[field]) { setLocalErrors(prev => ({ ...prev, [field]: "" })); } }; const handleMobileSubmit = async () => { if (!data.mobile || data.mobile.length !== 10) { setMobileNumberError("Please enter a valid 10-digit mobile number"); return; } try { setMobileNumberError(""); const res = await sendOtp.mutateAsync(data.mobile); toast.success(res?.message || "OTP sent successfully"); setShowOtp(true); setOtpTimer(OTP_TIMER_SEC); setOtp(new Array(OTP_LENGTH).fill("")); setMobileOtpVerified(false); } catch (error) { const msg = error?.response?.data?.message || "Failed to send OTP"; setMobileNumberError(msg); toast.error(msg); } }; const handleOtpChange = (index, value) => { if (!/^\d*$/.test(value)) return; const newOtp = [...otp]; newOtp[index] = value.slice(-1); setOtp(newOtp); if (value && index < OTP_LENGTH - 1) { document.getElementById(`otp-${index + 1}`)?.focus(); } }; const handleOtpSubmit = async () => { if (otp.some(d => d === "")) { setOtpError("Complete OTP is required"); return; } try { const res = await verifyOtp.mutateAsync({ mobile: data.mobile, otp: otp.join(""), }); toast.success(res?.message || "OTP verified successfully"); setMobileOtpVerified(true); dispatch(updatePersonalDetails({ verifiedMobileNumber: data.mobile })); setOtpError(""); } catch (error) { setOtpError("Invalid or expired OTP"); } }; useEffect(() => { if (otpTimer <= 0) { if (showOtp && !mobileOtpVerified) { setOtpError("OTP expired, please resend"); setOtp(new Array(OTP_LENGTH).fill("")); } return; } const timerId = setInterval(() => setOtpTimer(t => t - 1), 1000); return () => clearInterval(timerId); }, [otpTimer, showOtp, mobileOtpVerified]); const validateForm = () => { const newErrors = {}; if (!data.profile_for) newErrors.profile_for = "Required"; if (!data.gender) newErrors.gender = "Required"; if (!data.name) newErrors.name = "Required"; if (!data.mobile) newErrors.mobile = "Required"; if (!data.email) newErrors.email = "Required"; if (!isEditMode && !data.password) newErrors.password = "Required"; if (!isEditMode && data.password !== data.confirmPassword) newErrors.confirmPassword = "Passwords do not match"; if (!data.marital_status) newErrors.marital_status = "Required"; if (!data.height) newErrors.height = "Required"; if (!data.complexion) newErrors.complexion = "Required"; if (!data.physical_status) newErrors.physical_status = "Required"; if (!data.mother_language) newErrors.mother_language = "Required"; if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Required"; if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Required"; if (data.known_languages.length === 0) newErrors.known_languages = "Required"; setLocalErrors(newErrors); return Object.keys(newErrors).length === 0; }; const scrollToError = (errorMap) => { const errorFields = Object.keys(errorMap); if (errorFields.length > 0) { const fieldId = errorFields[0]; const element = document.getElementById(fieldId); if (element) { element.scrollIntoView({ behavior: "smooth", block: "center" }); // Use a timeout to allow the smooth scroll to complete setTimeout(() => { // For MUI Select, the focusable element is often the div with role="combobox" or the button const focusable = element.querySelector('[role="combobox"]') || element.querySelector('[role="button"]') || element.querySelector("input") || element.querySelector("select") || element; if (focusable && typeof focusable.focus === "function") { focusable.focus(); } }, 500); } } }; const handleSubmit = async () => { // 1. Prioritize OTP verification if showOtp is active if (!mobileOtpVerified && !isEditMode && showOtp) { setOtpError("Please enter and verify OTP"); const firstOtp = document.getElementById("otp-0"); if (firstOtp) { firstOtp.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { const input = firstOtp.querySelector("input") || firstOtp; if (input && typeof input.focus === "function") input.focus(); }, 500); } return; } // 2. Perform general form validation if (!validateForm()) { const newErrors = {}; if (!data.profile_for) newErrors.profile_for = "Required"; if (!data.gender) newErrors.gender = "Required"; if (!data.name) newErrors.name = "Required"; if (!data.mobile) newErrors.mobile = "Required"; if (!data.email) newErrors.email = "Required"; if (!isEditMode && !data.password) newErrors.password = "Required"; if (!isEditMode && data.password !== data.confirmPassword) newErrors.confirmPassword = "Passwords do not match"; if (!data.marital_status) newErrors.marital_status = "Required"; if (!data.height) newErrors.height = "Required"; if (!data.complexion) newErrors.complexion = "Required"; if (!data.physical_status) newErrors.physical_status = "Required"; if (!data.mother_language) newErrors.mother_language = "Required"; if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Required"; if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Required"; if (data.known_languages.length === 0) newErrors.known_languages = "Required"; toast.error("Please fill all mandatory fields"); scrollToError(newErrors); return; } // 3. Final mobile verification check if showOtp was not yet active if (!mobileOtpVerified && !isEditMode) { setMobileNumberError("Please verify your mobile number"); const mobileField = document.getElementById("mobile"); if (mobileField) { mobileField.scrollIntoView({ behavior: "smooth", block: "center" }); setTimeout(() => { const input = mobileField.querySelector("input") || mobileField; if (input && typeof input.focus === "function") input.focus(); }, 500); } return; } onSubmitStep(); }; return (
{/* 1. Profile Created for */}
Select Profile Created For {errors.profile_for && {errors.profile_for}}
{/* 2. Gender */}
Select Gender {errors.gender && {errors.gender}}
{/* 3. Name */}
handleChange("name", e.target.value)} error={Boolean(errors.name)} helperText={errors.name} variant="outlined" />
{/* 4. Mobile Number */}
handleChange("mobile", e.target.value)} error={Boolean(errors.mobile) || Boolean(mobileNumberError)} helperText={mobileNumberError || errors.mobile} inputProps={{ maxLength: 10 }} disabled={isEditMode} InputProps={{ endAdornment: mobileOtpVerified && ( ), }} /> {!isEditMode && !mobileOtpVerified && !showOtp && ( )} {showOtp && !mobileOtpVerified && ( Enter OTP {otp.map((digit, i) => ( handleOtpChange(i, e.target.value)} inputProps={{ maxLength: 1, style: { textAlign: "center", width: 40 } }} /> ))} {otpError && {otpError}} {otpTimer > 0 ? `Resend in ${otpTimer}s` : ( )} )}
{/* 5. Email Id */}
handleChange("email", e.target.value)} error={Boolean(errors.email)} helperText={errors.email} variant="outlined" />
{!isEditMode && ( <> {/* 6. Create Password */}
handleChange("password", e.target.value)} error={Boolean(errors.password)} helperText={errors.password} InputProps={{ endAdornment: ( setShowPassword(!showPassword)} edge="end"> {showPassword ? : } ), }} />
{/* 7. Confirm Password */}
handleChange("confirmPassword", e.target.value)} error={Boolean(errors.confirmPassword)} helperText={errors.confirmPassword} InputProps={{ endAdornment: ( setShowConfirmPassword(!showConfirmPassword)} edge="end"> {showConfirmPassword ? : } ), }} />
)} {/* 8. Marital Status */}
Select Marital Status {errors.marital_status && {errors.marital_status}}
{/* 9. Height */}
Select Height {errors.height && {errors.height}}
{/* 10. Weight */}
handleChange("weight", e.target.value)} />
{/* 11. Select Complexion */}
Select Complexion {errors.complexion && {errors.complexion}}
{/* 12. Select Physical Status */}
Select Physical Status {errors.physical_status && {errors.physical_status}}
{/* 13. Religion */}
Select Religion
{/* 14. Caste / Community */}
Select Caste
{/* 15. Sub-Sect */}
Select Sub-Sect
{/* 16. Willing to marry from the same sub sect */}
Select Option
{/* 17. Inter-Caste Parents */}
Select Option {errors.inter_caste_parents && {errors.inter_caste_parents}}
{/* 18. Parents Details */}
handleChange("inter_caste_parents_details", e.target.value)} />
{/* 19. Gothram (optional) */}
handleChange("gothram", e.target.value)} />
{/* 20. Do you speak Telugu */}
Select Option {errors.do_you_speak_telugu && {errors.do_you_speak_telugu}}
{/* 21. About you */}
handleChange("about_us", e.target.value)} />
{/* 22. Select Languages Spoken */}
opt.language || ""} value={languageOptions.filter(opt => data.known_languages.includes(opt.id))} onChange={(_, newValue) => handleChange("known_languages", newValue.map(v => v.id))} renderInput={(params) => ( )} />
{/* 23. Select Mother Tongue */}
Select Mother Tongue {errors.mother_language && {errors.mother_language}}
{/* 24. Upload Profile */}
handleChange("profiles", files)} />
); }; export default PersonalDetailsForm;