import React, { useState, useEffect } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { ChevronLeft } from "lucide-react"; import { useDispatch, useSelector } from "react-redux"; import { updatePersonalDetails, updateEducationalDetails, updateFamilyDetails, updateLifestyleDetails, updatePartnerPreferences, clearAllStepsFrom, submitForm, } from "../redux/registrationFormSlice"; import PersonalDetailsForm from "./PersonalDetailsForm"; import EducationalDetailsForm from "./EducationalDetailsForm"; import FamilyDetailsForm from "./FamilyDetailsForm"; import LifestyleDetailsForm from "./LifestyleDetailsForm"; import PartnerPreferencesForm from "./PartnerPreferencesForm"; import PreviewScreen from "./PreviewScreen"; import { useLocation, useNavigate } from "react-router-dom"; import { useRegisterStep1, useRegisterStep2, useRegisterStep3, useRegisterStep4, useRegisterStep5, } from "../hooks/useRegister"; import axiosInstance, { apiForFiles, setAccessToken } from "../api/axiosInstance"; import toast from "react-hot-toast"; import { API_ENDPOINTS } from "../api/apiEndpoints"; import { isAuthenticated } from "../utills/auth"; import { getPreviewDetails } from "../api/preview.api"; const STEP_FIELD_ORDER = { 1: [ "profile_for", "gender", "name", "mobile", "email", "password", "confirmPassword", "marital_status", "height", "weight", "complexion", "physical_status", "religion", "caste", "sub_caste", "willing_to_marry", "inter_caste_parents", "inter_caste_parents_details", "gothram", "do_you_speak_telugu", "about_us", "known_languages", "mother_language", "profiles", ], 2: [ "study_field", "education", "education_detail", "college_name", "employee_type", "occupation", "occupation_detail", "company_name", "income_currency", "annual_income", "work_country", "work_city", "work_state", "work_district", "address", ], 3: [ "fatherName", "fatherOccupation", "motherName", "motherOccupation", "brotherCount", "sisterCount", "familyStatus", "nativePlace", ], 4: ["diets", "hobbies", "dob", "tob", "placeOfBirth"], 5: [ "ageRange", "castes", "subCastes", "occupations", "educations", "hobbies", "annualIncome", "states", "districts", ], }; const STEP1_SERVER_FIELD_MAP = { name: "name", mobile: "mobile", email: "email", gender: "gender", height: "height", weight: "weight", marital_status: "marital_status", religion: "religion", profile_for: "profile_for", caste: "caste", sub_caste: "sub_caste", willing_to_marry: "willing_to_marry", inter_caste_parents: "inter_caste_parents", inter_caste_parents_details: "inter_caste_parents_details", gothram: "gothram", do_you_speak_telugu: "do_you_speak_telugu", about_us: "about_us", known_languages: "known_languages", mother_language: "mother_language", complexion: "complexion", physical_status: "physical_status", password: "password", confirm_password: "confirmPassword", profiles: "profiles", }; // const Stepper = ({ currentStep, enabledSteps, onStepClick,completedSteps }) => { // const steps = [ // { num: 1, label: "Personal" }, // { num: 2, label: "Educational" }, // { num: 3, label: "Family" }, // { num: 4, label: "Lifestyle" }, // { num: 5, label: "Partner" }, // { num: 6, label: "Preview" }, // ]; // return ( //
// {steps.map((step, index) => { // const isEnabled = enabledSteps.includes(step.num); // return ( // //
isEnabled && onStepClick(step.num)} // > //
= step.num // ? "bg-red-600 text-white" // : "bg-gray-300 text-gray-600" // }`} // > // {step.num} //
//
// {index < steps.length - 1 && ( //
step.num ? "bg-red-600" : "bg-gray-300" // }`} // /> // )} // // ); // })} //
// ); // }; import { Check } from "lucide-react"; const Stepper = ({ currentStep, enabledSteps, completedSteps, onStepClick, checkStepValidity }) => { const steps = [ { num: 1, label: "Personal" }, { num: 2, label: "Educational" }, { num: 3, label: "Family" }, { num: 4, label: "Lifestyle" }, { num: 5, label: "Partner" }, { num: 6, label: "Preview" }, ]; return (
{steps.map((step, index) => { const isSkippable = currentStep > 1 && currentStep < 6 && step.num === currentStep + 1; const isEnabled = enabledSteps.includes(step.num) || isSkippable; const isActive = currentStep === step.num; // Dynamic completion check: Green tick if all mandatory fields are filled const isValid = checkStepValidity && checkStepValidity(step.num); const isCompleted = completedSteps.includes(step.num) || isValid; // A step is Blue (Skipped) ONLY if it's a previous step and NOT completed const isSkipped = isEnabled && !isCompleted && !isActive && step.num < currentStep; return (
isEnabled && onStepClick(step.num)} >
{isCompleted ? : step.num}
{step.label}
{index < steps.length - 1 && (
)} ); })}
); }; const StepperForm = () => { const dispatch = useDispatch(); const queryClient = useQueryClient(); const location = useLocation(); const navigate = useNavigate(); const hideStepperRoutes = ["/profile-edit"]; const shouldHideStepper = hideStepperRoutes.some((route) => location.pathname.startsWith(route)); const personalDetails = useSelector( (state) => state.registerform.personalDetails ); const educationalDetails = useSelector( (state) => state.registerform.educationalDetails ); const familyDetails = useSelector((state) => state.registerform.familyDetails); const lifestyleDetails = useSelector( (state) => state.registerform.lifestyleDetails ); const partnerPreferences = useSelector( (state) => state.registerform.partnerPreferences ); const [currentStep, setCurrentStep] = useState(() => { if (location.state?.step) { return location.state.step; } const savedStep = localStorage.getItem("registration_current_step"); return savedStep ? Number(savedStep) : 1; }); const [enabledSteps, setEnabledSteps] = useState(() => { if (location.state?.step) { return Array.from({ length: 6 }, (_, i) => i + 1); // Enable all if coming from edit/preview } const savedStep = localStorage.getItem("registration_current_step"); const step = savedStep ? Number(savedStep) : 1; return Array.from({ length: step }, (_, i) => i + 1); }); const [completedSteps, setCompletedSteps] = useState([]); const [isStep1Update, setIsStep1Update] = useState(false); const [errors, setErrors] = useState({}); const isAuth = isAuthenticated(); const registerStep1 = useRegisterStep1(); const registerStep2 = useRegisterStep2(); const registerStep3 = useRegisterStep3(); const registerStep4 = useRegisterStep4(); const registerStep5 = useRegisterStep5(); useEffect(() => { localStorage.setItem("registration_current_step", currentStep); }, [currentStep]); const normalizeStep1Field = (key) => { if (!key) return key; const trimmed = String(key).trim(); if (!trimmed) return trimmed; // Map backend profile_images errors (e.g., profile_images.0) to the profiles field if (trimmed.startsWith("profile_images")) { return "profiles"; } return ( STEP1_SERVER_FIELD_MAP[trimmed] || STEP1_SERVER_FIELD_MAP[trimmed.toLowerCase()] || trimmed ); }; const coerceErrorMessage = (value) => { if (Array.isArray(value)) { return value.filter(Boolean).join(" "); } if (typeof value === "string") return value; if (value && typeof value === "object") { return ( value.message || value.msg || value.error || value.detail || "" ); } if (value === null || value === undefined) return ""; return String(value); }; const mapServerErrors = (error) => { const data = error?.response?.data ?? error?.data ?? error; if (!data) return {}; const payload = data.errors ?? data.error ?? data.data ?? data; if (typeof payload === "string") { return { _form: payload }; } if (Array.isArray(payload)) { const out = {}; payload.forEach((item) => { if (!item) return; if (typeof item === "string") { out._form = out._form ? `${out._form} ${item}` : item; return; } const key = item.field || item.name || item.param || item.key; const message = item.message || item.msg || item.error || item.detail || ""; if (key) { out[normalizeStep1Field(key)] = String(message || "Invalid value"); } }); return out; } if (typeof payload === "object") { const out = {}; Object.entries(payload).forEach(([key, value]) => { if ( (key === "message" || key === "error" || key === "detail") && typeof value === "string" ) { out._form = out._form ? `${out._form} ${value}` : value; return; } const normalizedKey = normalizeStep1Field(key); const message = coerceErrorMessage(value); if (!normalizedKey) return; out[normalizedKey] = message || "Invalid value"; }); return out; } return {}; }; const focusFirstError = (errorMap, fieldOrder = []) => { if (!errorMap || Object.keys(errorMap).length === 0) return; const order = fieldOrder.filter((key) => errorMap[key]); const firstKey = order[0] || Object.keys(errorMap).find((key) => key !== "_form"); if (!firstKey) return; setTimeout(() => { const element = document.getElementById(firstKey) || document.querySelector(`[name="${firstKey}"]`) || document.querySelector(`[aria-labelledby~="${firstKey}-label"]`); if (element) { element.scrollIntoView({ behavior: "smooth", block: "center" }); // Try to find a focusable element within the container const focusable = element.querySelector('input:not([type="hidden"]), select, textarea, [role="combobox"], [role="button"]') || element; if (focusable && typeof focusable.focus === "function") { focusable.focus(); } } }, 100); }; const clearFieldErrors = (fields) => { if (!fields) return; const list = Array.isArray(fields) ? fields : [fields]; setErrors((prev) => { if (!prev || Object.keys(prev).length === 0) return prev; let changed = false; const next = { ...prev }; list.forEach((field) => { if (field in next) { delete next[field]; changed = true; } }); if (next._form) { delete next._form; changed = true; } return changed ? next : prev; }); }; useEffect(() => { // in case user comes again with a different step if (location.state?.step) { setCurrentStep(location.state.step); setEnabledSteps([1, 2, 3, 4, 5, 6]); window.scrollTo(0, 0); } }, [location.state?.step]); useEffect(() => { // Clear the state so it doesn't persist on refresh if (location.state?.step) { navigate(location.pathname, { replace: true, state: {} }); } }, [location.state, navigate, location.pathname]); const { data: personalDetailsData } = useQuery({ queryKey: ["personalDetails"], queryFn: async () => { const response = await axiosInstance.get(API_ENDPOINTS.EDIT_PERSONAL_DETAILS); return response.data; }, enabled: isAuth || shouldHideStepper, retry: false, refetchOnWindowFocus: false, }); useEffect(() => { const processData = async () => { if (personalDetailsData?.status === "success" && personalDetailsData?.personal_details) { const pd = personalDetailsData.personal_details; setIsStep1Update(true); const rawImages = pd.profile_images || pd.images || []; const mappedImages = await Promise.all( rawImages.map(async (url, index) => { const imageUrl = typeof url === "string" ? url : url?.url; let file = null; let mimeType = "image/jpeg"; let fileName = `image-${index}.jpg`; try { if (imageUrl) { const response = await fetch(imageUrl); const blob = await response.blob(); if (blob.type) mimeType = blob.type; const ext = mimeType.split("/")[1] || "jpg"; fileName = `image-${index}.${ext}`; file = new File([blob], fileName, { type: mimeType }); } } catch (error) { console.error("Error converting image URL to File:", error); } return { id: `server-${index}`, preview: imageUrl, imageUrl: imageUrl, file: file, name: fileName, type: mimeType, valid: true, }; }) ); const formattedDob = pd.dob ? pd.dob.split("T")[0] : ""; dispatch( updatePersonalDetails({ name: pd.name || "", mobile: pd.mobile || "", email: pd.email || "", gender: pd.gender || "", dob: formattedDob, height: pd.height_id || "", weight: pd.weight || "", marital_status: pd.marital_status_id || "", religion: pd.religion_id || "", profile_for: pd.profile_for_id || "", caste: pd.caste_id || "", sub_caste: pd.sub_caste_id || "", willing_to_marry: pd.willing_to_marry || "", inter_caste_parents: pd.inter_caste_parents || 0, inter_caste_parents_details: pd.inter_caste_parents_details || "", gothram: pd.gothram || "", do_you_speak_telugu: pd.do_you_speak_telugu || 0, about_us: pd.about_us || "", known_languages: pd.known_language_ids || [], mother_language: pd.mother_language_id || "", complexion: pd.complexion_id || "", physical_status: pd.physical_status_id || "", raasi: pd.raasi_id || "", star: pd.star_id || "", state: pd.state_id || "", city: pd.district_id || "", pincode: pd.pincode || "", profiles: mappedImages, }) ); } }; processData(); }, [personalDetailsData, dispatch]); // Fetch Educational Details const { data: educationalData } = useQuery({ queryKey: ["educationalDetails"], queryFn: async () => { const response = await axiosInstance.get(API_ENDPOINTS.EDIT_EDUCATION_DETAILS); return response.data; }, enabled: isAuth || shouldHideStepper, retry: false, refetchOnWindowFocus: false, }); useEffect(() => { if (educationalData?.status === "success" && educationalData?.educational_details) { const ed = educationalData.educational_details; dispatch( updateEducationalDetails({ study_field: ed.study_field_id || "", education: ed.education_id || "", education_detail: ed.education_detail || "", college_name: ed.college_name || "", employee_type: ed.employee_type_id || "", occupation: ed.occupation_id || "", occupation_detail: ed.occupation_detail || "", company_name: ed.company_name || "", income_currency: ed.income_currency || "INR", annual_income: ed.annual_income || "", work_country: ed.work_country_id || "", work_state: ed.work_state_id || "", work_district: ed.work_district_id || "", work_city: ed.work_city || "", address: ed.address || ed.work_location || "", }) ); } }, [educationalData, dispatch]); // Fetch family details const {data:familyData} = useQuery({ queryKey:["familyDetails"], queryFn: async()=>{ const response = await axiosInstance.get(API_ENDPOINTS.EDIT_FAMILY_DETAILS); return response.data; }, enabled: isAuth || shouldHideStepper, retry:false, refetchOnWindowFocus:false, }); useEffect(() => { if (familyData?.status === "success" && familyData?.family_details) { const fd = familyData.family_details; dispatch( updateFamilyDetails({ fatherName: fd.father_name || "", fatherOccupation: fd.father_occupation || "", motherName: fd.mother_name || "", motherOccupation: fd.mother_occupation || "", familyStatus: fd.family_status_id || "", nativePlace: fd.native_place || "", familyCountry: fd.family_country_id || "", familyState: fd.family_state_id || "", familyDistrict: fd.family_district_id || "", familyCity: fd.family_city || "", address: fd.address || "", expectationDetails: fd.expectation_details || "", willingToGoAbroad: fd.willing_to_go_abroad || "", brotherCount: fd.brother_count || 0, sisterCount: fd.sister_count || 0, brothers: (fd.brothers || []).map((b) => ({ name: b.name || "", occupation: b.occupation_name || "", maritalStatus: b.marital_status || "", type: b.type || "", hasChildren: b.has_children || "", details: b.additional_details || "" })), sisters: (fd.sisters || []).map((s) => ({ name: s.name || "", occupation: s.occupation_name || "", maritalStatus: s.marital_status || "", type: s.type || "", hasChildren: s.has_children || "", details: s.additional_details || "" })), }) ); } }, [familyData, dispatch]); // Fetch Lifestyle Details const { data: lifestyleData } = useQuery({ queryKey: ["lifestyleDetails"], queryFn: async () => { const response = await axiosInstance.get(API_ENDPOINTS.EDIT_LIFESTYLE_DETAILS); return response.data; }, enabled: isAuth || shouldHideStepper, retry: false, refetchOnWindowFocus: false, refetchOnMount: true, }); useEffect(() => { if (lifestyleData?.status === "success" && lifestyleData?.lifestyle_details) { const ld = lifestyleData.lifestyle_details; const horoscope = ld.horoscope || {}; const mapChart = (prefix) => { const chart = {}; for (let i = 1; i <= 12; i++) { const key = `${prefix}_${i}`; const val = horoscope[key]; chart[i] = val ? val.split(",") : []; } return chart; }; dispatch( updateLifestyleDetails({ diets: ld.diet_id || "", hobbies: ld.hobbies_ids || [], dob: ld.time_of_birth ? ld.time_of_birth.split("T")[0] : "", tob: ld.time_of_birth_formated ? ld.time_of_birth_formated.substring(0, 5) : "", placeOfBirth: ld.place_of_birth || "", graha: mapChart("graha"), amsam: mapChart("amsam"), }) ); } }, [lifestyleData, dispatch]); // Fetch Partner Preferences const { data: partnerData } = useQuery({ queryKey: ["partnerPreferences"], queryFn: async () => { const response = await axiosInstance.get(API_ENDPOINTS.EDIT_PREFERED_PARTNER_DETAILS); return response.data; }, enabled: isAuth || shouldHideStepper, retry: false, refetchOnWindowFocus: false, }); useEffect(() => { if (partnerData?.status === "success" && partnerData?.partner_preferences) { const pp = partnerData.partner_preferences; dispatch( updatePartnerPreferences({ ageRange: pp.preferred_age_range_id || "", annualIncome: pp.preferred_annual_income_id || "", castes: pp.preferred_castes_ids || [], subCastes: pp.preferred_sub_castes_ids || [], occupations: pp.preferred_occupations_ids || [], educations: pp.preferred_educations_ids || [], hobbies: pp.preferred_hobbies_ids || [], states: pp.preferred_states_ids || [], districts: pp.preferred_districts_ids || [], }) ); } }, [partnerData, dispatch]); const validateStep = (step) => { const newErrors = {}; if (step === 1) { const required = [ "name", "mobile", "gender", "height", "marital_status", "profile_for", "caste", "email", "mother_language", "complexion", "physical_status", "inter_caste_parents", "do_you_speak_telugu", ]; required.forEach((field) => { if (!personalDetails[field] && personalDetails[field] !== 0) { const label = field .replace(/_/g, " ") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } }); if (!isStep1Update) { if (!personalDetails.password) newErrors.password = "Password is required"; if (!personalDetails.confirmPassword) newErrors.confirmPassword = "Confirm Password is required"; } if ( personalDetails.email && !/\S+@\S+\.\S+/.test(personalDetails.email) ) { newErrors.email = "Invalid email format"; } if ( personalDetails.mobile && personalDetails.mobile.length !== 10 ) { newErrors.mobile = "Mobile number must be 10 digits"; } if ( personalDetails.password && personalDetails.confirmPassword && personalDetails.password !== personalDetails.confirmPassword ) { newErrors.confirmPassword = "Passwords do not match"; } } else if (step === 2) { const isUnemployed = educationalDetails.employee_type === 11; const isIndia = educationalDetails.work_country === 1; const required = ["study_field", "education", "education_detail", "employee_type"]; if (!isUnemployed) { required.push("occupation", "occupation_detail", "income_currency", "annual_income", "work_country"); if (isIndia) { required.push("work_state", "work_district"); } else { required.push("work_city"); } } required.push("address"); required.forEach((field) => { if (!educationalDetails[field]) { const label = field .replace(/_/g, " ") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } }); } else if (step === 3) { const required = [ "fatherName", "motherName", "familyStatus", "nativePlace", ]; required.forEach((field) => { if (!familyDetails[field]) { // newErrors[field] = "This field is required"; const label = field .replace(/([A-Z])/g, " $1") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } }); if (familyDetails.fatherName && !/^[a-zA-Z\s]*$/.test(familyDetails.fatherName)) { newErrors.fatherName = "Father Name must contain only alphabets"; } if (familyDetails.motherName && !/^[a-zA-Z\s]*$/.test(familyDetails.motherName)) { newErrors.motherName = "Mother Name must contain only alphabets"; } } else if (step === 4) { const required = ["diets", "hobbies", "dob", "tob"]; required.forEach((field) => { const value = lifestyleDetails[field]; if (Array.isArray(value)) { if (value.length === 0) { if (field === "hobbies") { newErrors[field] = "Hobbies and Interests is required"; } else { newErrors[field] = "This field is required"; } } } else if (!value) { if (field === "dob") { newErrors[field] = "Date of Birth is required"; } else if (field === "tob") { newErrors[field] = "Time of Birth is required"; } else if (field === "diets") { newErrors[field] = "Diet is required"; } else { const label = field .replace(/([A-Z])/g, " $1") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } } }); } else if (step === 5) { const required = [ "ageRange", "castes", "subCastes", "occupations", "educations", "hobbies", "annualIncome", "states", "districts", ]; required.forEach((field) => { const value = partnerPreferences[field]; if (Array.isArray(value)) { if (value.length === 0) { // newErrors[field] = "This field is required"; const label = field .replace(/([A-Z])/g, " $1") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } return; } if (!value) { // newErrors[field] = "This field is required"; const label = field .replace(/([A-Z])/g, " $1") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } }); } setErrors(newErrors); if (Object.keys(newErrors).length > 0) { focusFirstError(newErrors, STEP_FIELD_ORDER[step] || []); } return Object.keys(newErrors).length === 0; }; const buildRegisterStep1Payload = async () => { const formData = new FormData(); formData.append("name", personalDetails.name); formData.append("mobile", personalDetails.mobile); formData.append("email", personalDetails.email); formData.append("gender", personalDetails.gender); formData.append("height", personalDetails.height || ""); formData.append("weight", personalDetails.weight || ""); formData.append("marital_status", personalDetails.marital_status); formData.append("religion", personalDetails.religion); formData.append("profile_for", personalDetails.profile_for || ""); formData.append("caste", personalDetails.caste); formData.append("sub_caste", personalDetails.sub_caste || ""); formData.append("willing_to_marry", personalDetails.willing_to_marry || ""); formData.append("inter_caste_parents", personalDetails.inter_caste_parents); formData.append("inter_caste_parents_details", personalDetails.inter_caste_parents_details || ""); formData.append("gothram", personalDetails.gothram || ""); formData.append("do_you_speak_telugu", personalDetails.do_you_speak_telugu); formData.append("about_us", personalDetails.about_us || ""); formData.append("mother_language", personalDetails.mother_language || ""); formData.append("complexion", personalDetails.complexion || ""); formData.append("physical_status", personalDetails.physical_status || ""); (personalDetails.known_languages || []).forEach((id, index) => { formData.append(`known_languages[${index}]`, id); }); if (!isStep1Update) { formData.append("password", personalDetails.password || ""); } formData.append("web_fcm_token", localStorage.getItem("fcm_token") || ""); if (personalDetails.profiles && Array.isArray(personalDetails.profiles)) { await Promise.all( personalDetails.profiles.map(async (item, index) => { let fileToAppend = item.file; // Check if it's a valid File/Blob. Redux might turn it into {} const isValidFile = fileToAppend && (fileToAppend instanceof Blob || (typeof fileToAppend === "object" && fileToAppend.name)); if (!isValidFile && item.preview && typeof item.preview === "string") { try { const response = await fetch(item.preview); const blob = await response.blob(); const mimeType = blob.type || "image/jpeg"; const ext = mimeType.split("/")[1] || "jpg"; const fileName = item.name || `profile_image_${index}.${ext}`; const fileType = item.type || mimeType; fileToAppend = new File([blob], fileName, { type: fileType }); } catch (e) { console.error(`Could not recover file from URL: ${item.preview}`, e); fileToAppend = null; } } else if (!isValidFile) { fileToAppend = null; } if (fileToAppend) { formData.append(`profile_images[${index}]`, fileToAppend); } else if (item.preview && typeof item.preview === "string") { formData.append(`profile_images[${index}]`, item.preview); } }) ); } return formData; }; const buildRegisterStep2Payload = () => { const formData = new FormData(); formData.append("study_field", educationalDetails.study_field || ""); formData.append("education", educationalDetails.education || ""); formData.append("education_detail", educationalDetails.education_detail || ""); formData.append("college_name", educationalDetails.college_name || ""); formData.append("employee_type", educationalDetails.employee_type || ""); formData.append("occupation", educationalDetails.occupation || ""); formData.append("occupation_detail", educationalDetails.occupation_detail || ""); formData.append("company_name", educationalDetails.company_name || ""); formData.append("income_currency", educationalDetails.income_currency || "INR"); formData.append("annual_income", educationalDetails.annual_income || ""); formData.append("work_country", educationalDetails.work_country || ""); formData.append("work_city", educationalDetails.work_city || ""); formData.append("work_state", educationalDetails.work_state || ""); formData.append("work_district", educationalDetails.work_district || ""); formData.append("address", educationalDetails.address || ""); // Also append work_location as some APIs might use it for address/city formData.append("work_location", educationalDetails.address || ""); return formData; }; const buildRegisterStep3Payload = () => { const formData = new FormData(); formData.append("father_name", familyDetails.fatherName); formData.append("father_occupation", familyDetails.fatherOccupation || ""); formData.append("mother_name", familyDetails.motherName); formData.append("mother_occupation", familyDetails.motherOccupation || ""); formData.append("family_status", familyDetails.familyStatus); formData.append("native_place", familyDetails.nativePlace || ""); formData.append("brother_count", familyDetails.brotherCount || 0); formData.append("sister_count", familyDetails.sisterCount || 0); formData.append("family_country", familyDetails.familyCountry || ""); formData.append("family_state", familyDetails.familyState || ""); formData.append("family_district", familyDetails.familyDistrict || ""); formData.append("family_city", familyDetails.familyCity || ""); formData.append("address", familyDetails.address || ""); formData.append("expectation_details", familyDetails.expectationDetails || ""); formData.append("willing_to_go_abroad", familyDetails.willingToGoAbroad || ""); (familyDetails.brothers || []).forEach((brother, index) => { formData.append(`brothers[${index}][name]`, brother?.name || ""); formData.append(`brothers[${index}][occupation]`, brother?.occupation || ""); formData.append(`brothers[${index}][marital_status]`, brother?.maritalStatus || ""); formData.append(`brothers[${index}][type]`, brother?.type || ""); formData.append(`brothers[${index}][has_children]`, brother?.hasChildren || ""); formData.append(`brothers[${index}][details]`, brother?.details || ""); }); (familyDetails.sisters || []).forEach((sister, index) => { formData.append(`sisters[${index}][name]`, sister?.name || ""); formData.append(`sisters[${index}][occupation]`, sister?.occupation || ""); formData.append(`sisters[${index}][marital_status]`, sister?.maritalStatus || ""); formData.append(`sisters[${index}][type]`, sister?.type || ""); formData.append(`sisters[${index}][has_children]`, sister?.hasChildren || ""); formData.append(`sisters[${index}][details]`, sister?.details || ""); }); return formData; }; const buildRegisterStep4Payload = () => { const formData = new FormData(); formData.append("dob", lifestyleDetails.dob || ""); formData.append("tob", lifestyleDetails.tob || ""); formData.append("place_of_birth", lifestyleDetails.placeOfBirth || ""); formData.append("diets", lifestyleDetails.diets || ""); (lifestyleDetails.hobbies || []).forEach((id, index) => { formData.append(`hobbies[${index}]`, id); }); const graha = lifestyleDetails.graha || {}; Object.keys(graha).forEach((house) => { const values = graha[house] || []; values.forEach((value, index) => { formData.append(`graha_${house}[${index}]`, value); }); }); const amsam = lifestyleDetails.amsam || {}; Object.keys(amsam).forEach((house) => { const values = amsam[house] || []; values.forEach((value, index) => { formData.append(`amsam_${house}[${index}]`, value); }); }); return formData; }; const buildRegisterStep5Payload = () => { const formData = new FormData(); formData.append("age_range", partnerPreferences.ageRange || ""); formData.append("annual_income", partnerPreferences.annualIncome || ""); (partnerPreferences.castes || []).forEach((id, index) => { formData.append(`castes[${index}]`, id); }); (partnerPreferences.subCastes || []).forEach((id, index) => { formData.append(`sub_castes[${index}]`, id); }); (partnerPreferences.occupations || []).forEach((id, index) => { formData.append(`occupations[${index}]`, id); }); (partnerPreferences.educations || []).forEach((id, index) => { formData.append(`educations[${index}]`, id); }); (partnerPreferences.hobbies || []).forEach((id, index) => { formData.append(`hobbies[${index}]`, id); }); (partnerPreferences.states || []).forEach((id, index) => { formData.append(`states[${index}]`, id); }); (partnerPreferences.districts || []).forEach((id, index) => { formData.append(`districts[${index}]`, id); }); return formData; }; const extractAccessToken = (res) => res?.access_token || res?.accessToken || res?.token || res?.data?.access_token || res?.data?.accessToken || res?.data?.token || res?.result?.access_token || res?.result?.accessToken || res?.result?.token || null; const handleStepSubmit = async () => { const isValid = validateStep(currentStep); if (!isValid) return; try { let payload; let res; switch (currentStep) { case 1: payload = await buildRegisterStep1Payload(); if (isStep1Update) { res = await apiForFiles.post("/update_personal_details", payload); try { const previewData = await getPreviewDetails(); queryClient.invalidateQueries({ queryKey: ["preview-details"] }); if (previewData?.personal_details) { const pd = previewData.personal_details; const images = pd.images || pd.profile_images; if (images && images.length > 0) { const formattedImages = images.map((img) => typeof img === "string" ? { url: img, preview: img } : img ); dispatch(updatePersonalDetails({ profiles: formattedImages })); } } queryClient.invalidateQueries({ queryKey: ["personalDetails"] }); } catch (error) { console.error("Error refreshing preview details:", error); } } else { res = await registerStep1.mutateAsync(payload); } const token = extractAccessToken(res.data || res); if (token) setAccessToken(token); // Store profile_id and user_id for WebSocket channels const profileId = res.data?.profile_id || res?.profile_id || res.data?.data?.profile_id; const userId = res.data?.user_id || res?.user_id || res.data?.data?.user_id; if (profileId) localStorage.setItem("profile_id", profileId); if (userId) localStorage.setItem("user_id", userId); break; case 2: payload = buildRegisterStep2Payload(); await registerStep2.mutateAsync(payload); queryClient.invalidateQueries({ queryKey: ["educationalDetails"] }); queryClient.invalidateQueries({ queryKey: ["preview-details"] }); break; case 3: payload = buildRegisterStep3Payload(); await registerStep3.mutateAsync(payload); queryClient.invalidateQueries({ queryKey: ["familyDetails"] }) queryClient.invalidateQueries({ queryKey: ["preview-details"] }); break; case 4: payload = buildRegisterStep4Payload(); await registerStep4.mutateAsync(payload); queryClient.invalidateQueries({ queryKey: ["lifestyleDetails"] }); queryClient.invalidateQueries({ queryKey: ["preview-details"] }); break; case 5: payload = buildRegisterStep5Payload(); await registerStep5.mutateAsync(payload); queryClient.invalidateQueries({ queryKey: ["partnerPreferences"] }); queryClient.invalidateQueries({ queryKey: ["preview-details"] }); break; default: break; } if (shouldHideStepper) { toast.success("Updated successfully", { position: "top-right" }); navigate("/profile"); return; } // Mark step completed (tick icon) setCompletedSteps((prev) => prev.includes(currentStep) ? prev : [...prev, currentStep] ); const nextStep = Math.min(currentStep + 1, 6); // Enable next step navigation setEnabledSteps((prev) => prev.includes(nextStep) ? prev : [...prev, nextStep] ); setErrors({}); setCurrentStep(nextStep); window.scrollTo(0, 0); } catch (e) { const serverErrors = mapServerErrors(e); let handled = false; if (Object.keys(serverErrors).length > 0) { const hasFieldErrors = Object.keys(serverErrors).some( (key) => key !== "_form" ); if (hasFieldErrors) { setErrors(serverErrors); focusFirstError(serverErrors, STEP_FIELD_ORDER[currentStep]); handled = true; } if (serverErrors._form) { toast.error(serverErrors._form, { position: "top-right" }); handled = true; } } if (!handled) { const msg = e?.response?.data?.message || e?.message || "Failed to submit step. Please try again."; toast.error(msg, { position: "top-right" }); } } }; const handleSkip = () => { setErrors({}); // Remove from completed if skipping (matching Flutter logic) setCompletedSteps((prev) => prev.filter(s => s !== currentStep)); setCurrentStep((prev) => { const nextStep = Math.min(prev + 1, 6); setEnabledSteps((steps) => steps.includes(nextStep) ? steps : [...steps, nextStep] ); return nextStep; }); window.scrollTo(0, 0); }; const handleStepClick = (step) => { // If clicking next step and current is skippable (not Step 1), trigger handleSkip if (step === currentStep + 1 && currentStep > 1 && currentStep < 6) { handleSkip(); return; } if (!enabledSteps.includes(step)) return; // If moving backwards during registration, clear the unsaved data of the step we are leaving if (step < currentStep && !shouldHideStepper) { dispatch(clearAllStepsFrom(currentStep)); } setCurrentStep(step); setErrors({}); window.scrollTo(0, 0); }; useEffect(() => { if (currentStep === 6) { setEnabledSteps([1, 2, 3, 4, 5, 6]); } }, [currentStep]); const handleEdit = (step) => { setCurrentStep(step); setErrors({}); window.scrollTo(0, 0); }; const handleFinalSubmit = async () => { const ok = validateStep(1); if (!ok) { setCurrentStep(1); return; } try { // final combined API call - replace with your final API await new Promise((resolve) => setTimeout(resolve, 500)); toast.success("Form submitted successfully!", { position: "top-right" }); localStorage.removeItem("registration_current_step"); navigate("/dashboard-home"); } catch (e) { toast.error("Failed to submit form.", { position: "top-right" }); } }; const renderStepContent = () => { switch (currentStep) { case 1: return ( ); case 2: return ( ); case 3: return ( ); case 4: return ( ); case 5: return ( ); case 6: return ; default: return null; } }; const checkStepValidity = (stepNum) => { if (stepNum === 1) { const required = ["name", "mobile", "gender", "height", "marital_status", "profile_for", "caste", "email", "mother_language", "complexion", "physical_status"]; return required.every(field => personalDetails[field] || personalDetails[field] === 0); } if (stepNum === 2) { const required = ["study_field", "education", "education_detail", "employee_type", "address"]; if (!educationalDetails.study_field || !educationalDetails.education || !educationalDetails.education_detail || !educationalDetails.employee_type || !educationalDetails.address) return false; if (educationalDetails.employee_type !== 11) { const workReq = ["occupation", "occupation_detail", "income_currency", "annual_income", "work_country"]; if (!workReq.every(f => educationalDetails[f])) return false; if (educationalDetails.work_country === 1) { if (!educationalDetails.work_state || !educationalDetails.work_district) return false; } else { if (!educationalDetails.work_city) return false; } } return true; } if (stepNum === 3) { const required = ["fatherName", "motherName", "familyStatus", "nativePlace"]; return required.every(field => familyDetails[field]); } if (stepNum === 4) { const required = ["diets", "hobbies", "dob", "tob"]; return required.every(field => { const val = lifestyleDetails[field]; return Array.isArray(val) ? val.length > 0 : !!val; }); } if (stepNum === 5) { const required = ["ageRange", "castes", "subCastes", "occupations", "educations", "hobbies", "annualIncome", "states", "districts"]; return required.every(field => { const val = partnerPreferences[field]; return Array.isArray(val) ? val.length > 0 : !!val; }); } return false; }; const getTitle = () => { const titles = { 1: "Personal Details", 2: "Educational & Professional Details", 3: "Family Details", 4: "Lifestyle & Habits", 5: "Partner Preferences", 6: "Details Preview", }; return titles[currentStep] || ""; }; return (
{/* Header */}
{!shouldHideStepper && ( )}
{/* {currentStep > 1 && currentStep < 6 && ( )} */}

{getTitle()}

{/* Content */}
{renderStepContent()}
); }; export default StepperForm;