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 (