thirukalyanamweb/src/feature/StepperForm.jsx
2026-03-09 18:16:38 +05:30

1284 lines
38 KiB
JavaScript

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,
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: [
"name",
"gender",
"mobileNumber",
"dob",
"height",
"weight",
"maritalStatus",
"religion",
"profileFor",
"caste",
"subCaste",
"gothram",
"raasi",
"star",
"email",
"password",
"confirmPassword",
"state",
"city",
"pincode",
"profiles",
],
2: [
"fieldOfStudy",
"qualification",
"collegeName",
"occupation",
"organization",
"employeeType",
"income",
"workLocation",
],
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: "mobileNumber",
mobile_number: "mobileNumber",
mobileNumber: "mobileNumber",
phone: "mobileNumber",
email: "email",
gender: "gender",
dob: "dob",
height: "height",
weight: "weight",
marital_status: "maritalStatus",
maritalStatus: "maritalStatus",
religion: "religion",
profile_for: "profileFor",
profileFor: "profileFor",
caste: "caste",
sub_caste: "subCaste",
subCaste: "subCaste",
gothram: "gothram",
raasi: "raasi",
star: "star",
state: "state",
district: "city",
city: "city",
pincode: "pincode",
password: "password",
confirm_password: "confirmPassword",
confirmPassword: "confirmPassword",
profiles: "profiles",
profile_images: "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 (
// <div className="flex items-center justify-between px-4 py-6">
// {steps.map((step, index) => {
// const isEnabled = enabledSteps.includes(step.num);
// return (
// <React.Fragment key={step.num}>
// <div
// className={`flex flex-col items-center ${
// isEnabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"
// }`}
// onClick={() => isEnabled && onStepClick(step.num)}
// >
// <div
// className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
// currentStep >= step.num
// ? "bg-red-600 text-white"
// : "bg-gray-300 text-gray-600"
// }`}
// >
// {step.num}
// </div>
// </div>
// {index < steps.length - 1 && (
// <div
// className={`flex-1 h-0.5 mx-1 ${
// currentStep > step.num ? "bg-red-600" : "bg-gray-300"
// }`}
// />
// )}
// </React.Fragment>
// );
// })}
// </div>
// );
// };
import { Check } from "lucide-react";
const Stepper = ({ currentStep, enabledSteps, completedSteps, onStepClick }) => {
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 (
<div className="flex items-center justify-between px-4 py-6">
{steps.map((step, index) => {
const isEnabled = enabledSteps.includes(step.num);
const isCompleted = completedSteps.includes(step.num);
return (
<React.Fragment key={step.num}>
<div
className={`flex flex-col items-center ${
isEnabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"
}`}
onClick={() => isEnabled && onStepClick(step.num)}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
isCompleted
? "bg-green-600 text-white"
: currentStep === step.num
? "bg-red-600 text-white"
: "bg-gray-300 text-gray-600"
}`}
>
{isCompleted ? <Check size={16} /> : step.num}
</div>
<span className="text-xs mt-1">{step.label}</span>
</div>
{index < steps.length - 1 && (
<div
className={`flex-1 h-0.5 mx-1 ${
completedSteps.includes(step.num)
? "bg-green-600"
: "bg-gray-300"
}`}
/>
)}
</React.Fragment>
);
})}
</div>
);
};
const StepperForm = () => {
const dispatch = useDispatch();
const queryClient = useQueryClient();
const location = useLocation();
const navigate = useNavigate();
const hideStepperRoutes = ["/profile-edit"];
const shouldHideStepper = hideStepperRoutes.includes(location.pathname);
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([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 byAria = document.querySelector(
`[aria-labelledby~="${firstKey}-label"]`
);
if (byAria && typeof byAria.focus === "function") {
byAria.focus();
return;
}
const byName = document.querySelector(`[name="${firstKey}"]`);
if (byName && typeof byName.focus === "function") {
byName.focus();
return;
}
const byId = document.getElementById(firstKey);
if (byId && typeof byId.focus === "function") {
byId.focus();
}
}, 0);
};
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]);
useEffect(() => {
if (!isAuth) return;
const fetchPersonalDetails = async () => {
try {
const response = await axiosInstance.get(API_ENDPOINTS.EDIT_PERSONAL_DETAILS);
const data = response.data;
if (data.status === "success" && data.personal_details) {
const pd = data.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 {
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 || "",
mobileNumber: pd.mobile || "",
email: pd.email || "",
gender: pd.gender || "",
dob: formattedDob,
height: pd.height || "",
weight: pd.weight || "",
maritalStatus: pd.marital_status_id || "",
religion: pd.religion_id || "",
profileFor: pd.profile_for_id || "",
caste: pd.caste_id || "",
subCaste: pd.sub_caste_id || "",
gothram: pd.gothram_id || "",
raasi: pd.raasi_id || "",
star: pd.star_id || "",
state: pd.state_id || "",
city: pd.district_id || "",
pincode: pd.pincode || "",
profiles: mappedImages,
})
);
}
} catch (error) {
console.error("Error fetching personal details:", error);
}
};
fetchPersonalDetails();
}, [dispatch, isAuth]);
// 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,
retry: false,
refetchOnWindowFocus: false,
});
useEffect(() => {
if (educationalData?.status === "success" && educationalData?.educational_details) {
const ed = educationalData.educational_details;
dispatch(
updateEducationalDetails({
fieldOfStudy: ed.study_field_id || "",
qualification: ed.education_id || "",
collegeName: ed.college_name || "",
occupation: ed.occupation_id || "",
organization: ed.company_name || "",
employeeType: ed.employee_type_id || "",
income: ed.annual_income_id || "",
workLocation: 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,
retry:false,
refetchOnWindowFocus:false,
});
useEffect(()=>{
if (familyData?.status === "success" && familyData?.family_details) {
const fd = familyData.family_details;
const mappedBrothers = (fd.brothers || []).map((b) => ({
name: b.name || "",
occupation: b.occupation_name || "",
maritalStatus: b.marital_status || "",
haveChildrens: b.have_childrens === true ? 1 : (b.have_childrens === false ? 0 : b.have_childrens),
}));
const mappedSisters = (fd.sisters || []).map((s) => ({
name: s.name || "",
occupation: s.occupation_name || "",
maritalStatus: s.marital_status || "",
haveChildrens: s.have_childrens === true ? 1 : (s.have_childrens === false ? 0 : s.have_childrens),
}));
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 || "",
brotherCount: fd.brother_count || 0,
sisterCount: fd.sister_count || 0,
brothers: mappedBrothers,
sisters: mappedSisters,
})
);
}
},[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,
retry: false,
refetchOnWindowFocus: false,
});
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 || "",
tob: ld.date_of_birth ? ld.date_of_birth.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,
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",
"mobileNumber",
"gender",
"dob",
"height",
"maritalStatus",
"profileFor",
"caste",
"email",
"state",
"city",
"pincode",
];
required.forEach((field) => {
if (!personalDetails[field]) {
const label = field
.replace(/([A-Z])/g, " $1")
.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.mobileNumber &&
personalDetails.mobileNumber.length !== 10
) {
newErrors.mobileNumber = "Mobile number must be 10 digits";
}
if (personalDetails.height && Number(personalDetails.height) > 10) {
newErrors.height = "Height must be 10 or less";
}
if (personalDetails.weight && Number(personalDetails.weight) > 300) {
newErrors.weight = "Weight must be 300 or less";
}
if (
personalDetails.password &&
personalDetails.confirmPassword &&
personalDetails.password !== personalDetails.confirmPassword
) {
newErrors.confirmPassword = "Passwords do not match";
}
} else if (step === 2) {
const required = [
"qualification",
"fieldOfStudy",
];
required.forEach((field) => {
if (!educationalDetails[field]) {
const label = field
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
}
});
} else if (step === 3) {
const required = [
"fatherName",
"motherName",
"familyStatus",
];
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.mobileNumber);
formData.append("email", personalDetails.email);
formData.append("pincode", personalDetails.pincode);
formData.append("gender", personalDetails.gender);
formData.append("dob", personalDetails.dob);
formData.append("height", personalDetails.height || "");
formData.append("weight", personalDetails.weight || "");
formData.append("marital_status", personalDetails.maritalStatus);
formData.append("religion", personalDetails.religion);
formData.append("profile_for", personalDetails.profileFor || "");
formData.append("caste", personalDetails.caste);
formData.append("sub_caste", personalDetails.subCaste || "");
formData.append("gothram", personalDetails.gothram || "");
formData.append("raasi", personalDetails.raasi || "");
formData.append("star", personalDetails.star || "");
formData.append("state", personalDetails.state);
formData.append("district", personalDetails.city);
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);
}
})
);
}
return formData;
};
const buildRegisterStep2Payload = () => {
const formData = new FormData();
formData.append("college_name", educationalDetails.collegeName || "");
formData.append("study_field", educationalDetails.fieldOfStudy || "");
formData.append("education", educationalDetails.qualification || "");
formData.append("occupation", educationalDetails.occupation || "");
formData.append("company_name", educationalDetails.organization || "");
formData.append("employee_type", educationalDetails.employeeType || "");
formData.append("annual_income", educationalDetails.income || "");
formData.append("work_location", educationalDetails.workLocation || "");
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);
(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}][have_childrens]`, brother?.haveChildrens ?? "");
});
(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}][have_childrens]`, sister?.haveChildrens ?? "");
});
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("diet", 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();
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: ["previewDetails"] });
} 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);
break;
case 2:
payload = buildRegisterStep2Payload();
await registerStep2.mutateAsync(payload);
break;
case 3:
payload = buildRegisterStep3Payload();
await registerStep3.mutateAsync(payload);
break;
case 4:
payload = buildRegisterStep4Payload();
await registerStep4.mutateAsync(payload);
break;
case 5:
payload = buildRegisterStep5Payload();
await registerStep5.mutateAsync(payload);
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({});
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 (!enabledSteps.includes(step)) return;
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 (
<PersonalDetailsForm
onSubmitStep={handleStepSubmit}
errors={errors}
onFieldChange={clearFieldErrors}
isStep1Update={isStep1Update}
/>
);
case 2:
return (
<EducationalDetailsForm
onSubmitStep={handleStepSubmit}
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
/>
);
case 3:
return (
<FamilyDetailsForm
onSubmitStep={handleStepSubmit}
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
/>
);
case 4:
return (
<LifestyleDetailsForm
onSubmitStep={handleStepSubmit}
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
/>
);
case 5:
return (
<PartnerPreferencesForm
onSubmitStep={handleStepSubmit}
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
/>
);
case 6:
return <PreviewScreen onEdit={handleEdit} onSubmit={handleFinalSubmit} />;
default:
return null;
}
};
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 (
<div className="">
<div className="max-w-[1400px] mx-auto bg-white ">
{/* Header */}
<div className="my-4 rounded-[10px] py-4 w-full max-w-[1200px] mx-auto">
{!shouldHideStepper && (
<Stepper
currentStep={currentStep}
onStepClick={handleStepClick}
enabledSteps={enabledSteps}
completedSteps={completedSteps}
/> )}
<div className="flex items-center p-4 justify-center">
{/* {currentStep > 1 && currentStep < 6 && (
<button
onClick={() => setCurrentStep((prev) => prev - 1)}
className="mr-3"
>
<ChevronLeft size={24} />
</button>
)} */}
<h1 className="text-[24px] font-semibold text-center uppercase py-2 px-3 rounded-5">{getTitle()}</h1>
</div>
</div>
{/* Content */}
<div className="pb-6">{renderStepContent()}</div>
</div>
</div>
);
};
export default StepperForm;