import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { updatePersonalDetails } from "../redux/registrationFormSlice"; import { Grid, TextField, FormControl, InputLabel, Select, MenuItem, Button, Box, Typography, Link, InputAdornment, IconButton, } 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 { DatePicker } from "@mui/x-date-pickers/DatePicker"; import toast from "react-hot-toast"; import { LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns"; import { usePersonalDetailsMasters, useCasteMasters, useSubCasteMasters, useCityMasters, useStarMasters, } from "../hooks/useDependentMasters"; import { useSendOtp, useVerifyOtp } from "../hooks/useAuth"; const OTP_LENGTH = 4; const OTP_TIMER_SEC = 120; // 2 minutes const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Update }) => { const dispatch = useDispatch(); const data = useSelector((state) => state.registerform.personalDetails); const nameInputRef = 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 sendOtp = useSendOtp(); const verifyOtp = useVerifyOtp(); const { data: personalMasters, isLoading: isPersonalMastersLoading } = usePersonalDetailsMasters(); const genderOptions = useMemo( () => personalMasters?.gender ?? [], [personalMasters] ); const maritalStatusOptions = useMemo( () => personalMasters?.marital_status ?? [], [personalMasters] ); const religionOptions = useMemo( () => personalMasters?.religion ?? [], [personalMasters] ); const profileCreatedForOptions = useMemo( () => personalMasters?.profileCreatedFor ?? [], [personalMasters] ); const gothramOptions = useMemo( () => personalMasters?.gothram ?? [], [personalMasters] ); const raasiOptions = useMemo( () => personalMasters?.raasi ?? [], [personalMasters] ); const stateOptions = useMemo( () => personalMasters?.state ?? [], [personalMasters] ); const casteQuery = useCasteMasters(data.religion); const subCasteQuery = useSubCasteMasters(data.caste); const cityQuery = useCityMasters(data.state); const starQuery = useStarMasters(data.raasi); const casteOptions = useMemo(() => { const raw = casteQuery.data; if (!raw) return []; if (Array.isArray(raw)) return raw; return raw.caste || raw.data || []; }, [casteQuery.data]); const subCasteOptions = useMemo(() => { const raw = subCasteQuery.data; if (!raw) return []; if (Array.isArray(raw)) return raw; return raw.sub_caste || raw.subCaste || raw.data || []; }, [subCasteQuery.data]); const cityOptions = useMemo(() => { const raw = cityQuery.data; if (!raw) return []; if (Array.isArray(raw)) return raw; return raw.subCaste || raw.district || raw.data || []; }, [cityQuery.data]); const starOptions = useMemo(() => { const raw = starQuery.data; if (!raw) return []; if (Array.isArray(raw)) return raw; return raw.star || raw.data || []; }, [starQuery.data]); const getOptionLabel = useCallback((item, fallback = "") => { if (!item) return fallback; if (typeof item === "string") return item; return ( item.name || item.caste_name || item.sub_caste_name || item.district_name || item.city_name || item.state_name || item.religion_name || item.marital_status_name || item.gothram_name || item.raasi_name || item.profile_for_name || item.star_name || fallback ); }, []); const startOtpTimer = useCallback(() => { setOtpTimer(OTP_TIMER_SEC); }, []); const getApiErrorMessage = useCallback((error, fallback) => { const data = error?.response?.data ?? error?.data ?? error; if (!data) return fallback; if (typeof data === "string") return data; const directMessage = data.message || data.error || data.detail || data.msg; if (directMessage) return directMessage; if (Array.isArray(data.errors)) { const first = data.errors[0]; if (typeof first === "string") return first; if (first && typeof first === "object") { return ( first.message || first.msg || first.error || first.detail || fallback ); } } if (data.errors && typeof data.errors === "object") { const firstValue = Object.values(data.errors)[0]; if (Array.isArray(firstValue)) { const joined = firstValue.filter(Boolean).join(" "); if (joined) return joined; } if (typeof firstValue === "string") return firstValue; if (firstValue && typeof firstValue === "object") { return ( firstValue.message || firstValue.msg || firstValue.error || firstValue.detail || fallback ); } } if (data.otp) return String(data.otp); if (error?.message) return error.message; return fallback; }, []); useEffect(() => { if (otpTimer <= 0) return; const timerId = setInterval(() => { setOtpTimer((sec) => sec - 1); }, 1000); return () => clearInterval(timerId); }, [otpTimer]); useEffect(() => { nameInputRef.current?.focus(); }, []); 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) { const next = document.getElementById(`otp-${index + 1}`); if (next) next.focus(); } }; const handleOtpKeyDown = (index, event) => { if (event.key !== "Backspace") return; event.preventDefault(); const newOtp = [...otp]; if (newOtp[index]) { newOtp[index] = ""; setOtp(newOtp); return; } if (index > 0) { newOtp[index - 1] = ""; setOtp(newOtp); const prev = document.getElementById(`otp-${index - 1}`); if (prev) prev.focus(); } }; const resetOtp = () => { setOtp(new Array(OTP_LENGTH).fill("")); setOtpError(""); startOtpTimer(); }; const handleMobileSubmit = async () => { if (!data.mobileNumber || data.mobileNumber.length !== 10) { setMobileNumberError( "Please enter a valid 10-digit mobile number before sending OTP" ); return; } try { setMobileNumberError(""); const res = await sendOtp.mutateAsync(data.mobileNumber); const successMessage = res?.message || res?.otp || res?.status || "OTP sent successfully"; toast.success(successMessage, { position: "top-right" }); setShowOtp(true); resetOtp(); setMobileOtpVerified(false); } catch (error) { const message = getApiErrorMessage( error, "Failed to send OTP. Please try again." ); setMobileNumberError(message); toast.error(message, { position: "top-right" }); } }; const handleChange = (field, value) => { const updates = { [field]: value }; const fieldsToClear = [field]; if (field === "mobileNumber") { setMobileNumberError(""); setShowOtp(false); setOtp(new Array(OTP_LENGTH).fill("")); setOtpError(""); setOtpTimer(0); setMobileOtpVerified(false); } if (field === "religion") { updates.caste = ""; updates.subCaste = ""; fieldsToClear.push("caste", "subCaste"); } if (field === "caste") { updates.subCaste = ""; fieldsToClear.push("subCaste"); } if (field === "state") { updates.city = ""; fieldsToClear.push("city"); } if (field === "raasi") { updates.star = ""; fieldsToClear.push("star"); } if (field === "password") { fieldsToClear.push("confirmPassword"); } dispatch(updatePersonalDetails(updates)); if (onFieldChange) onFieldChange(fieldsToClear); }; const isOtpComplete = otp.every((digit) => digit !== ""); const passwordStrength = useMemo(() => { const value = data.password || ""; if (!value) return null; const rules = [ { key: "length", label: "At least 8 characters", ok: value.length >= 8 }, { key: "upper", label: "Uppercase letter", ok: /[A-Z]/.test(value) }, { key: "lower", label: "Lowercase letter", ok: /[a-z]/.test(value) }, { key: "number", label: "Number", ok: /\d/.test(value) }, { key: "symbol", label: "Symbol", ok: /[^A-Za-z0-9]/.test(value) }, { key: "length12", label: "12+ characters (recommended)", ok: value.length >= 12 }, ]; const score = rules.filter((rule) => rule.ok).length; const percent = Math.round((score / rules.length) * 100); let label = "Weak"; let color = "#d32f2f"; if (score >= 5) { label = "Strong"; color = "#2e7d32"; } else if (score >= 3) { label = "Medium"; color = "#ed6c02"; } return { label, color, percent, rules, }; }, [data.password]); const handleOtpSubmit = async () => { if (!isOtpComplete) { setOtpError("Complete OTP is required"); return; } try { const res = await verifyOtp.mutateAsync({ mobile: data.mobileNumber, otp: otp.join(""), }); const successMessage = res?.message || res?.otp || res?.status || "OTP verified successfully"; toast.success(successMessage, { position: "top-right" }); setMobileOtpVerified(true); setMobileNumberError(""); setOtpError(""); } catch (error) { const message = getApiErrorMessage( error, "Invalid or expired OTP" ); setOtpError(message); toast.error(message, { position: "top-right" }); } }; // file upload // const handleSubmit = async () => { // if (showOtp && !isOtpComplete) { // setOtpError("OTP is required and must be complete"); // return; // } // if (showOtp && !mobileOtpVerified) { // try { // await verifyOtpApi(data.mobileNumber, otp.join("")); // setMobileOtpVerified(true); // setOtpError(""); // onSubmitStep(); // console.log("OTP verified on submit"); // } catch (err) { // setOtpError(err || "OTP verification failed"); // } // return; // } // onSubmitStep(); // }; const handleSubmit = async () => { if (showOtp && !isOtpComplete) { setOtpError("OTP is required and must be complete"); return; } if (showOtp && !mobileOtpVerified) { try { await verifyOtp.mutateAsync({ mobile: data.mobileNumber, otp: otp.join(""), }); setMobileOtpVerified(true); setOtpError(""); console.log("Submitting personal details:", data); // log here onSubmitStep(); console.log("OTP verified on submit"); } catch (err) { setOtpError(err || "OTP verification failed"); } return; } // no OTP or already verified console.log("Submitting personal details:", data); // log here onSubmitStep(); }; const formatTimer = (sec) => { const m = Math.floor(sec / 60) .toString() .padStart(2, "0"); const s = (sec % 60).toString().padStart(2, "0"); return `${m}:${s}`; }; const parseDobValue = data.dob ? new Date(data.dob) : null; return ( <>