814 lines
24 KiB
JavaScript
814 lines
24 KiB
JavaScript
import React, { useState, useEffect } from "react";
|
|
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 } from "react-router-dom";
|
|
import {
|
|
useRegisterStep1,
|
|
useRegisterStep2,
|
|
useRegisterStep3,
|
|
useRegisterStep4,
|
|
useRegisterStep5,
|
|
} from "../hooks/useRegister";
|
|
import { setAccessToken } from "../api/axiosInstance";
|
|
import toast from "react-hot-toast";
|
|
|
|
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, 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) => (
|
|
<React.Fragment key={step.num}>
|
|
<div
|
|
className="flex flex-col items-center cursor-pointer"
|
|
onClick={() => 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>
|
|
);
|
|
};
|
|
|
|
const StepperForm = () => {
|
|
const dispatch = useDispatch();
|
|
const location = useLocation();
|
|
|
|
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 initialStep = location.state?.step || 1;
|
|
|
|
const [currentStep, setCurrentStep] = useState(initialStep);
|
|
const [errors, setErrors] = useState({});
|
|
|
|
const registerStep1 = useRegisterStep1();
|
|
const registerStep2 = useRegisterStep2();
|
|
const registerStep3 = useRegisterStep3();
|
|
const registerStep4 = useRegisterStep4();
|
|
const registerStep5 = useRegisterStep5();
|
|
|
|
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);
|
|
window.scrollTo(0, 0);
|
|
}
|
|
}, [location.state?.step]);
|
|
|
|
|
|
|
|
|
|
const validateStep = (step) => {
|
|
const newErrors = {};
|
|
|
|
if (step === 1) {
|
|
const required = [
|
|
"name",
|
|
"mobileNumber",
|
|
"gender",
|
|
"dob",
|
|
"maritalStatus",
|
|
"profileFor",
|
|
"caste",
|
|
"email",
|
|
"password",
|
|
"confirmPassword",
|
|
"state",
|
|
"city",
|
|
"pincode",
|
|
];
|
|
|
|
required.forEach((field) => {
|
|
if (!personalDetails[field]) {
|
|
newErrors[field] = "This field 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",
|
|
"occupation",
|
|
"organization",
|
|
"employeeType",
|
|
"income",
|
|
];
|
|
required.forEach((field) => {
|
|
if (!educationalDetails[field]) {
|
|
newErrors[field] = "This field is required";
|
|
}
|
|
});
|
|
} else if (step === 3) {
|
|
const required = [
|
|
"fatherName",
|
|
"motherName",
|
|
"familyStatus",
|
|
];
|
|
required.forEach((field) => {
|
|
if (!familyDetails[field]) {
|
|
newErrors[field] = "This field is required";
|
|
}
|
|
});
|
|
} 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) newErrors[field] = "This field is required";
|
|
} else if (!value) {
|
|
newErrors[field] = "This field 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";
|
|
}
|
|
return;
|
|
}
|
|
if (!value) {
|
|
newErrors[field] = "This field 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("fcm_token", localStorage.getItem("fcm_token") || "");
|
|
|
|
if (personalDetails.profiles && Array.isArray(personalDetails.profiles)) {
|
|
for (const [index, item] of personalDetails.profiles.entries()) {
|
|
// If the file object is intact (e.g., just added), use it directly.
|
|
if (item.file && (item.file instanceof Blob || (typeof item.file === 'object' && item.file !== null && item.file.name))) {
|
|
formData.append(`profile_images[${index}]`, item.file);
|
|
}
|
|
// If the file object was lost due to Redux serialization,
|
|
// try to recover it from the blob URL preview.
|
|
else if (
|
|
item.preview &&
|
|
typeof item.preview === "string"
|
|
) {
|
|
try {
|
|
const response = await fetch(item.preview);
|
|
const blob = await response.blob();
|
|
const fileName = item.name || `profile_image_${index}.jpg`;
|
|
const fileType = item.type || "image/jpeg";
|
|
const recoveredFile = new File([blob], fileName, { type: fileType });
|
|
formData.append(`profile_images[${index}]`, recoveredFile);
|
|
} catch (e) {
|
|
console.error(`Could not recover file from blob URL: ${item.preview}`, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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 || "");
|
|
|
|
(lifestyleDetails.diets || []).forEach((id, index) => {
|
|
formData.append(`diets[${index}]`, id);
|
|
});
|
|
|
|
(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 {
|
|
if (currentStep === 1) {
|
|
const payload = await buildRegisterStep1Payload();
|
|
const res = await registerStep1.mutateAsync(payload);
|
|
const token = extractAccessToken(res);
|
|
if (token) {
|
|
setAccessToken(token);
|
|
}
|
|
} else if (currentStep === 2) {
|
|
const payload = buildRegisterStep2Payload();
|
|
await registerStep2.mutateAsync(payload);
|
|
} else if (currentStep === 3) {
|
|
const payload = buildRegisterStep3Payload();
|
|
await registerStep3.mutateAsync(payload);
|
|
} else if (currentStep === 4) {
|
|
const payload = buildRegisterStep4Payload();
|
|
await registerStep4.mutateAsync(payload);
|
|
} else if (currentStep === 5) {
|
|
const payload = buildRegisterStep5Payload();
|
|
await registerStep5.mutateAsync(payload);
|
|
}
|
|
setErrors({});
|
|
setCurrentStep((prev) => Math.min(prev + 1, 6));
|
|
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]);
|
|
// For step 1, we know mapping is correct so field errors are sufficient feedback
|
|
if (currentStep === 1) 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) => Math.min(prev + 1, 6));
|
|
window.scrollTo(0, 0);
|
|
};
|
|
|
|
const handleStepClick = (step) => {
|
|
setCurrentStep(step);
|
|
setErrors({});
|
|
window.scrollTo(0, 0);
|
|
};
|
|
|
|
const handleEdit = (step) => {
|
|
setCurrentStep(step);
|
|
setErrors({});
|
|
window.scrollTo(0, 0);
|
|
};
|
|
|
|
const handleFinalSubmit = async () => {
|
|
for (let i = 1; i <= 5; i++) {
|
|
const ok = validateStep(i);
|
|
if (!ok) {
|
|
setCurrentStep(i);
|
|
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" });
|
|
} 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}
|
|
/>
|
|
);
|
|
case 2:
|
|
return (
|
|
<EducationalDetailsForm
|
|
onSubmitStep={handleStepSubmit}
|
|
onSkipStep={handleSkip}
|
|
errors={errors}
|
|
onFieldChange={clearFieldErrors}
|
|
/>
|
|
);
|
|
case 3:
|
|
return (
|
|
<FamilyDetailsForm
|
|
onSubmitStep={handleStepSubmit}
|
|
onSkipStep={handleSkip}
|
|
errors={errors}
|
|
onFieldChange={clearFieldErrors}
|
|
/>
|
|
);
|
|
case 4:
|
|
return (
|
|
<LifestyleDetailsForm
|
|
onSubmitStep={handleStepSubmit}
|
|
onSkipStep={handleSkip}
|
|
errors={errors}
|
|
onFieldChange={clearFieldErrors}
|
|
/>
|
|
);
|
|
case 5:
|
|
return (
|
|
<PartnerPreferencesForm
|
|
onSubmitStep={handleStepSubmit}
|
|
onSkipStep={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 bg-[#e3ffed] w-full max-w-[1200px] mx-auto">
|
|
|
|
<Stepper currentStep={currentStep} onStepClick={handleStepClick} />
|
|
<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 bg-[#fff2f2] py-2 px-3 rounded-5">{getTitle()}</h1>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="pb-6">{renderStepContent()}</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StepperForm;
|