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;