Build and deployment update: Refined registration forms, stepper navigation, and chat synchronization fixes

This commit is contained in:
mageshwaran committed 2026-04-25 10:22:35 +05:30
1 parent 68f97c40dc
commit c467271927
27 files changed
+3461 -4178

No files matched your search

+377 -421
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateEducationalDetails } from "../redux/registrationFormSlice";
import { updateEducationalDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
import {
TextField,
Button,
@@ -9,456 +9,412 @@ import {
InputLabel,
Select,
MenuItem,
FormHelperText,
InputAdornment,
Box,
Typography,
} from "@mui/material";
import { useEducationMasters, useEducationList } from "../hooks/useMasters";
import { useCityMasters } from "../hooks/useDependentMasters";
import { toast } from "react-hot-toast";
const EducationalDetailsForm = ({
onSubmitStep,
onSkipStep,
errors,
onFieldChange,
errors: externalErrors,
isEditMode,
}) => {
const dispatch = useDispatch();
const data = useSelector((state) => state.registerform.educationalDetails);
const inputRef = useRef(null);
const [localErrors, setLocalErrors] = useState({});
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
const { data: educationMasters, isLoading: isEducationMastersLoading } =
useEducationMasters();
const educationListQuery = useEducationList(data.fieldOfStudy);
const educationListQuery = useEducationList(data.study_field);
const districtQuery = useCityMasters(data.work_state);
const studyFieldOptions = useMemo(() => {
const raw = educationMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.studyFields || raw.study_fields || raw.fieldOfStudy || [];
}, [educationMasters]);
const studyFieldOptions = educationMasters?.studyFields || [];
const qualificationOptions = educationListQuery.data?.education || educationListQuery.data?.data || [];
const occupationOptions = educationMasters?.occupation || [];
const employeeTypeOptions = educationMasters?.employeeType || [];
const countryOptions = educationMasters?.country || [];
const stateOptions = educationMasters?.state || [];
const districtOptions = districtQuery.data?.districts || districtQuery.data || [];
const qualificationOptions = useMemo(() => {
const raw = educationListQuery.data;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.education || raw.data || [];
}, [educationListQuery.data]);
const occupationOptions = useMemo(() => {
const raw = educationMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.occupation || raw.occupations || [];
}, [educationMasters]);
const employeeTypeOptions = useMemo(() => {
const raw = educationMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.employeeType || raw.employee_type || [];
}, [educationMasters]);
const annualIncomeOptions = useMemo(() => {
const raw = educationMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.annualIncome || raw.annual_income || [];
}, [educationMasters]);
const workLocationOptions = useMemo(() => {
const raw = educationMasters;
if (!raw) return [];
if (Array.isArray(raw)) return [];
return raw.workLocation || raw.work_location || raw.workLocations || [];
}, [educationMasters]);
const getOptionLabel = (item, fallback = "") => {
if (!item) return fallback;
if (typeof item === "string") return item;
return (
item.study_field_name ||
item.education_name ||
item.occupation_name ||
item.employee_type_name ||
item.annual_income_name ||
item.work_location_name ||
item.name ||
fallback
);
};
useEffect(() => {
inputRef.current?.focus();
}, []);
const isUnemployed = data.employee_type === 11;
const isIndia = Number(data.work_country) === 1;
const handleChange = (field, value) => {
const updates = { [field]: value };
const fieldsToClear = [field];
if (field === "fieldOfStudy") {
updates.qualification = "";
fieldsToClear.push("qualification");
if (field === "study_field") {
updates.education = "";
}
if (field === "work_country") {
updates.work_state = "";
updates.work_district = "";
updates.work_city = "";
}
if (field === "work_state") {
updates.work_district = "";
}
if (field === "employee_type" && value === 11) {
// Clear fields that will be hidden
updates.occupation = "";
updates.occupation_detail = "";
updates.company_name = "";
updates.annual_income = "";
updates.work_country = "";
updates.work_state = "";
updates.work_district = "";
updates.work_city = "";
}
dispatch(updateEducationalDetails(updates));
if (onFieldChange) onFieldChange(fieldsToClear);
setLocalErrors((prev) => ({ ...prev, [field]: "" }));
if (!isEditMode) {
dispatch(clearAllStepsFrom(3));
}
};
const validateForm = () => {
const newErrors = {};
if (!data.study_field) newErrors.study_field = "Required";
if (!data.education) newErrors.education = "Required";
if (!data.education_detail) newErrors.education_detail = "Required";
if (!data.employee_type) newErrors.employee_type = "Required";
if (!isUnemployed) {
if (!data.occupation) newErrors.occupation = "Required";
if (!data.occupation_detail) newErrors.occupation_detail = "Required";
if (!data.income_currency) newErrors.income_currency = "Required";
if (!data.annual_income) newErrors.annual_income = "Required";
if (!data.work_country) newErrors.work_country = "Required";
if (isIndia) {
if (!data.work_state) newErrors.work_state = "Required";
if (!data.work_district) newErrors.work_district = "Required";
} else {
if (!data.work_city) newErrors.work_city = "Required";
}
}
if (!data.address) newErrors.address = "Required";
setLocalErrors(newErrors);
return newErrors;
};
const scrollToError = (errorMap) => {
const errorFields = Object.keys(errorMap);
if (errorFields.length > 0) {
const fieldId = errorFields[0];
const element = document.getElementById(fieldId);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => {
const focusable = element.querySelector('[role="combobox"]') ||
element.querySelector('[role="button"]') ||
element.querySelector("input") ||
element.querySelector("select") ||
element;
if (focusable && typeof focusable.focus === "function") {
focusable.focus();
}
}, 300); // Reduced delay slightly for snappier feel
}
}
};
const handleSubmit = () => {
console.log("Submitting educational details:", data);
const freshErrors = validateForm();
if (Object.keys(freshErrors).length > 0) {
toast.error("Please fill all mandatory fields");
scrollToError(freshErrors);
return;
}
onSubmitStep();
};
return (
<>
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
<form noValidate autoComplete="off" style={{ padding: 16 }}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
{/* Field of Study */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Field of Study{requiredMark}
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.fieldOfStudy)}
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
<form noValidate autoComplete="off" style={{ padding: 16 }}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
{/* 1. Field of Study */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Field of Study{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.study_field)} id="study_field">
<InputLabel>Select Field of Study</InputLabel>
<Select
value={data.study_field}
label="Select Field of Study"
onChange={(e) => handleChange("study_field", e.target.value)}
>
<InputLabel id="fieldOfStudy-label">
Select Field of Study
</InputLabel>
<Select
labelId="fieldOfStudy-label"
label="Select Field of Study"
name="fieldOfStudy"
value={data.fieldOfStudy}
onChange={(e) => handleChange("fieldOfStudy", e.target.value)}
inputRef={inputRef}
disabled={isEducationMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{studyFieldOptions.map((field) => (
<MenuItem key={field.id ?? field} value={field.id ?? field}>
{getOptionLabel(field, "Field of Study")}
</MenuItem>
))}
</Select>
{errors.fieldOfStudy && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.fieldOfStudy}
</p>
)}
</FormControl>
</div>
{/* Highest Qualification */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Highest Educational Qualification{requiredMark}
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.qualification)}
>
<InputLabel id="qualification-label">
Select Highest Qualification
</InputLabel>
<Select
labelId="qualification-label"
label="Select Highest Qualification"
name="qualification"
value={data.qualification}
onChange={(e) => handleChange("qualification", e.target.value)}
disabled={
!data.fieldOfStudy || educationListQuery.isLoading
}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{qualificationOptions.map((item) => (
<MenuItem key={item.id ?? item} value={item.id ?? item}>
{getOptionLabel(item, "Qualification")}
</MenuItem>
))}
</Select>
{errors.qualification && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.qualification}
</p>
)}
</FormControl>
</div>
{/* College Name */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
College Name
</label>
<TextField
fullWidth
name="collegeName"
label="College Name"
value={data.collegeName}
onChange={(e) => handleChange("collegeName", e.target.value)}
error={Boolean(errors.collegeName)}
helperText={errors.collegeName}
placeholder="Enter College Name"
variant="outlined"
/>
</div>
{/* Occupation */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Occupation
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.occupation)}
>
<InputLabel id="occupation-label">Select Occupation</InputLabel>
<Select
labelId="occupation-label"
label="Select Occupation"
name="occupation"
value={data.occupation}
onChange={(e) => handleChange("occupation", e.target.value)}
disabled={isEducationMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{occupationOptions.map((item) => (
<MenuItem key={item.id ?? item} value={item.id ?? item}>
{getOptionLabel(item, "Occupation")}
</MenuItem>
))}
</Select>
{errors.occupation && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.occupation}
</p>
)}
</FormControl>
</div>
{/* Company / Organization Name */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Company / Organization Name
</label>
<TextField
fullWidth
name="organization"
label="Company / Organization Name"
value={data.organization}
onChange={(e) => handleChange("organization", e.target.value)}
error={Boolean(errors.organization)}
helperText={errors.organization}
placeholder="Enter Company / Organization Name"
variant="outlined"
/>
</div>
{/* Employee Type */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Employee Type
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.employeeType)}
>
<InputLabel id="employeeType-label">
Select Employee Type
</InputLabel>
<Select
labelId="employeeType-label"
label="Select Employee Type"
name="employeeType"
value={data.employeeType}
onChange={(e) => handleChange("employeeType", e.target.value)}
disabled={isEducationMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{employeeTypeOptions.map((item) => (
<MenuItem key={item.id ?? item} value={item.id ?? item}>
{getOptionLabel(item, "Employee Type")}
</MenuItem>
))}
</Select>
{errors.employeeType && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.employeeType}
</p>
)}
</FormControl>
</div>
{/* Annual Income */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Annual Income
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.income)}
>
<InputLabel id="income-label">Select Annual Income</InputLabel>
<Select
labelId="income-label"
label="Select Annual Income"
name="income"
value={data.income}
onChange={(e) => handleChange("income", e.target.value)}
disabled={isEducationMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{annualIncomeOptions.map((item) => (
<MenuItem key={item.id ?? item} value={item.id ?? item}>
{getOptionLabel(item, "Annual Income")}
</MenuItem>
))}
</Select>
{errors.income && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.income}
</p>
)}
</FormControl>
</div>
{/* Work Location */}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Work Location
</label>
{workLocationOptions.length > 0 ? (
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.workLocation)}
>
<InputLabel id="workLocation-label">
Select Work Location
</InputLabel>
<Select
labelId="workLocation-label"
label="Select Work Location"
name="workLocation"
value={data.workLocation}
onChange={(e) =>
handleChange("workLocation", e.target.value)
}
disabled={isEducationMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{workLocationOptions.map((item) => (
<MenuItem key={item.id ?? item} value={item.id ?? item}>
{getOptionLabel(item, "Work Location")}
</MenuItem>
))}
</Select>
{errors.workLocation && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.workLocation}
</p>
)}
</FormControl>
) : (
<TextField
fullWidth
name="workLocation"
label="Work Location"
value={data.workLocation}
onChange={(e) => handleChange("workLocation", e.target.value)}
error={Boolean(errors.workLocation)}
helperText={errors.workLocation}
placeholder="Enter Work Location"
variant="outlined"
/>
)}
</div>
{studyFieldOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.study_field_name}</MenuItem>
))}
</Select>
{localErrors.study_field && <FormHelperText>{localErrors.study_field}</FormHelperText>}
</FormControl>
</div>
<Grid
item
xs={12}
style={{
marginTop: "40px",
display: "flex",
gap: 16,
justifyContent: "center",
}}
>
{onSkipStep && (
<Button variant="outlined" onClick={onSkipStep}>
Skip
</Button>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}>
{onSkipStep ? "Next" : "Update"}
{/* 2. Highest Qualification */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Highest Qualification{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.education)} id="education" disabled={!data.study_field || educationListQuery.isLoading}>
<InputLabel>Select Qualification</InputLabel>
<Select
value={data.education}
label="Select Qualification"
onChange={(e) => handleChange("education", e.target.value)}
>
{qualificationOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.education_name || opt.name}</MenuItem>
))}
</Select>
{localErrors.education && <FormHelperText>{localErrors.education}</FormHelperText>}
</FormControl>
</div>
{/* 3. Education in Detail */}
<div className="flex flex-col gap-2 md:col-span-2">
<label className="text-gray-900 text-[15px]">Education in Detail{requiredMark}</label>
<TextField
id="education_detail"
fullWidth
multiline
rows={3}
placeholder="Enter your education details"
value={data.education_detail}
onChange={(e) => handleChange("education_detail", e.target.value)}
error={Boolean(localErrors.education_detail)}
helperText={localErrors.education_detail}
/>
</div>
{/* 4. College Name */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Enter College Name</label>
<TextField
id="college_name"
fullWidth
placeholder="Enter College Name"
value={data.college_name}
onChange={(e) => handleChange("college_name", e.target.value)}
/>
</div>
{/* 5. Employee Type */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Employee type{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.employee_type)} id="employee_type">
<InputLabel>Select Employee Type</InputLabel>
<Select
value={data.employee_type}
label="Select Employee Type"
onChange={(e) => handleChange("employee_type", e.target.value)}
>
{employeeTypeOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.employee_type_name}</MenuItem>
))}
</Select>
{localErrors.employee_type && <FormHelperText>{localErrors.employee_type}</FormHelperText>}
</FormControl>
</div>
{!isUnemployed && (
<>
{/* 6. Occupation */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Occupation{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.occupation)} id="occupation">
<InputLabel>Select Occupation</InputLabel>
<Select
value={data.occupation}
label="Select Occupation"
onChange={(e) => handleChange("occupation", e.target.value)}
>
{occupationOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.occupation_name}</MenuItem>
))}
</Select>
{localErrors.occupation && <FormHelperText>{localErrors.occupation}</FormHelperText>}
</FormControl>
</div>
{/* 7. Occupation in Detail */}
<div className="flex flex-col gap-2 md:col-span-2">
<label className="text-gray-900 text-[15px]">Occupation in Detail{requiredMark}</label>
<TextField
id="occupation_detail"
fullWidth
multiline
rows={3}
placeholder="Enter your occupation details"
value={data.occupation_detail}
onChange={(e) => handleChange("occupation_detail", e.target.value)}
error={Boolean(localErrors.occupation_detail)}
helperText={localErrors.occupation_detail}
/>
</div>
{/* 8. Company / Organization Name */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Company / Organization Name</label>
<TextField
id="company_name"
fullWidth
placeholder="Enter Company Name"
value={data.company_name}
onChange={(e) => handleChange("company_name", e.target.value)}
/>
</div>
{/* 9. Income Currency Type */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Income Currency Type{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.income_currency)} id="income_currency">
<InputLabel>Select Currency</InputLabel>
<Select
value={data.income_currency}
label="Select Currency"
onChange={(e) => handleChange("income_currency", e.target.value)}
>
<MenuItem value="INR">INR</MenuItem>
<MenuItem value="USD">USD</MenuItem>
</Select>
{localErrors.income_currency && <FormHelperText>{localErrors.income_currency}</FormHelperText>}
</FormControl>
</div>
{/* 10. Annual Income */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Annual Income{requiredMark}</label>
<TextField
id="annual_income"
fullWidth
placeholder="Enter Annual Income"
value={data.annual_income}
onChange={(e) => handleChange("annual_income", e.target.value)}
error={Boolean(localErrors.annual_income)}
helperText={localErrors.annual_income}
InputProps={{
startAdornment: (
<InputAdornment position="start">
{data.income_currency === "USD" ? "$" : "₹"}
</InputAdornment>
),
}}
/>
</div>
{/* 11. Country */}
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Country{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.work_country)} id="work_country">
<InputLabel>Select Country</InputLabel>
<Select
value={data.work_country}
label="Select Country"
onChange={(e) => handleChange("work_country", e.target.value)}
>
{countryOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.country_name}</MenuItem>
))}
</Select>
{localErrors.work_country && <FormHelperText>{localErrors.work_country}</FormHelperText>}
</FormControl>
</div>
{/* 12. City/Town (Only if NOT India) */}
{!isIndia && (
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">City / Town{requiredMark}</label>
<TextField
id="work_city"
fullWidth
placeholder="Enter City / Town"
value={data.work_city}
onChange={(e) => handleChange("work_city", e.target.value)}
error={Boolean(localErrors.work_city)}
helperText={localErrors.work_city}
/>
</div>
)}
{/* 13. State (Only if India) */}
{isIndia && (
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">State{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.work_state)} id="work_state">
<InputLabel>Select State</InputLabel>
<Select
value={data.work_state}
label="Select State"
onChange={(e) => handleChange("work_state", e.target.value)}
>
{stateOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.state_name}</MenuItem>
))}
</Select>
{localErrors.work_state && <FormHelperText>{localErrors.work_state}</FormHelperText>}
</FormControl>
</div>
)}
{/* 14. City (District) (Only if India) */}
{isIndia && (
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">City{requiredMark}</label>
<FormControl fullWidth error={Boolean(localErrors.work_district)} id="work_district" disabled={!data.work_state || districtQuery.isLoading}>
<InputLabel>Select City</InputLabel>
<Select
value={data.work_district}
label="Select City"
onChange={(e) => handleChange("work_district", e.target.value)}
>
{districtOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.district_name || opt.name}</MenuItem>
))}
</Select>
{localErrors.work_district && <FormHelperText>{localErrors.work_district}</FormHelperText>}
</FormControl>
</div>
)}
</>
)}
{/* 15. Address */}
<div className="flex flex-col gap-2 md:col-span-2">
<label className="text-gray-900 text-[15px]">Address{requiredMark}</label>
<TextField
id="address"
fullWidth
multiline
rows={2}
placeholder="Enter your address"
value={data.address}
onChange={(e) => handleChange("address", e.target.value)}
error={Boolean(localErrors.address)}
helperText={localErrors.address}
/>
</div>
</div>
<Box sx={{ mt: 5, display: "flex", gap: 2, justifyContent: "center" }}>
{onSkipStep && (
<Button variant="outlined" size="large" onClick={onSkipStep} sx={{ minWidth: 120 }}>
Skip
</Button>
</Grid>
</form>
</div>
</>
)}
<Button variant="contained" size="large" onClick={handleSubmit} sx={{ minWidth: 120 }}>
{onSkipStep ? "Next" : "Update"}
</Button>
</Box>
</form>
</div>
);
};
+284 -155
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateFamilyDetails } from "../redux/registrationFormSlice";
import { updateFamilyDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
import {
Grid,
TextField,
@@ -12,46 +12,55 @@ import {
Box,
} from "@mui/material";
import { useFamilyMasters } from "../hooks/useMasters";
import { useCityMasters } from "../hooks/useDependentMasters";
import { toast } from "react-hot-toast";
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange }) => {
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange, isEditMode }) => {
const dispatch = useDispatch();
const data = useSelector((state) => state.registerform.familyDetails);
const inputRef = useRef(null);
const brotherSectionRef = useRef(null);
const sisterSectionRef = useRef(null);
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
const { data: familyMasters, isLoading: isFamilyMastersLoading } =
useFamilyMasters();
const { data: familyMasters, isLoading: isFamilyMastersLoading } = useFamilyMasters();
// District query for India
const districtQuery = useCityMasters(data.familyState);
const occupationOptions = useMemo(() => {
const raw = familyMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.occupation || raw.occupations || [];
return raw.occupation || [];
}, [familyMasters]);
const maritalStatusOptions = useMemo(() => {
const raw = familyMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.maritalStatus || raw.marital_status || [];
return raw.maritalStatus || [];
}, [familyMasters]);
const familyStatusOptions = useMemo(() => {
const raw = familyMasters;
if (!raw) return [];
if (Array.isArray(raw)) return [];
return raw.familyStatus || raw.family_status || [];
return raw.familyStatus || [];
}, [familyMasters]);
const countryOptions = useMemo(() => familyMasters?.country || [], [familyMasters]);
const stateOptions = useMemo(() => familyMasters?.state || [], [familyMasters]);
const districtOptions = useMemo(() => districtQuery.data?.districts || districtQuery.data || [], [districtQuery.data]);
useEffect(() => {
inputRef.current?.focus();
}, []);
const createSibling = () => ({
type: "",
name: "",
occupation: "",
maritalStatus: "",
haveChildrens: "",
hasChildren: "",
details: "",
});
const syncSiblingArray = (arr, count) => {
@@ -72,20 +81,48 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
if (field === "brotherCount") {
const count = Number(value) || 0;
updates.brotherCount = count;
updates.brotherCount = value;
updates.brothers = syncSiblingArray(data.brothers, count);
fieldsToClear.push("brothers");
if (count > 0) {
setTimeout(() => {
brotherSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
const firstInput = brotherSectionRef.current?.querySelector('.first-sibling-name');
if (firstInput) firstInput.focus();
}, 500);
}
}
if (field === "sisterCount") {
const count = Number(value) || 0;
updates.sisterCount = count;
updates.sisterCount = value;
updates.sisters = syncSiblingArray(data.sisters, count);
fieldsToClear.push("sisters");
if (count > 0) {
setTimeout(() => {
sisterSectionRef.current?.scrollIntoView({ behavior: "smooth", block: "start" });
const firstInput = sisterSectionRef.current?.querySelector('.first-sibling-name');
if (firstInput) firstInput.focus();
}, 500);
}
}
if (field === "familyCountry") {
updates.familyState = "";
updates.familyDistrict = "";
updates.familyCity = "";
}
if (field === "familyState") {
updates.familyDistrict = "";
}
dispatch(updateFamilyDetails(updates));
if (onFieldChange) onFieldChange(fieldsToClear);
if (!isEditMode) {
dispatch(clearAllStepsFrom(4));
}
};
const handleSiblingChange = (type, index, field, value) => {
@@ -94,14 +131,40 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
list[index] = { ...list[index], [field]: value };
dispatch(updateFamilyDetails({ [type]: list }));
if (onFieldChange) onFieldChange(type);
if (!isEditMode) {
dispatch(clearAllStepsFrom(4));
}
};
const handleSubmit = () => {
console.log("Submitting family details:", data);
const scrollToError = (errorMap) => {
const errorFields = Object.keys(errorMap);
if (errorFields.length > 0) {
const fieldId = errorFields[0];
const element = document.getElementById(fieldId);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "center" });
setTimeout(() => {
const focusable = element.querySelector('[role="combobox"]') ||
element.querySelector('[role="button"]') ||
element.querySelector("input") ||
element.querySelector("select") ||
element;
if (focusable && typeof focusable.focus === "function") {
focusable.focus();
}
}, 300);
}
}
};
const handleSubmit = (e) => {
if (e) e.preventDefault();
onSubmitStep();
};
const countOptions = Array.from({ length: 11 }, (_, i) => i);
const isIndia = Number(data.familyCountry) === 1;
const renderSiblingCard = (type, index) => {
const sibling = (data[type] || [])[index] || createSibling();
@@ -114,95 +177,88 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
borderRadius: 2,
padding: 2,
backgroundColor: "#fff",
mb: 4
}}
>
<div className="text-gray-900 text-[14px] font-semibold mb-3">
{labelPrefix} {index + 1}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
<FormControl fullWidth variant="outlined">
<InputLabel>Type</InputLabel>
<Select
label="Type"
value={sibling.type}
onChange={(e) => handleSiblingChange(type, index, "type", e.target.value)}
>
<MenuItem value=""><em>Select</em></MenuItem>
<MenuItem value="Elder">Elder</MenuItem>
<MenuItem value="Younger">Younger</MenuItem>
</Select>
</FormControl>
<TextField
fullWidth
label="Name"
placeholder="Enter Name"
value={sibling.name}
onChange={(e) =>
handleSiblingChange(type, index, "name", e.target.value)
}
onChange={(e) => handleSiblingChange(type, index, "name", e.target.value)}
variant="outlined"
inputProps={{ className: index === 0 ? "first-sibling-name" : "" }}
/>
<FormControl fullWidth variant="outlined">
<InputLabel id={`${type}-${index}-occupation-label`}>
Occupation
</InputLabel>
<InputLabel>Occupation</InputLabel>
<Select
labelId={`${type}-${index}-occupation-label`}
label="Occupation"
value={sibling.occupation}
onChange={(e) =>
handleSiblingChange(type, index, "occupation", e.target.value)
}
disabled={isFamilyMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
onChange={(e) => handleSiblingChange(type, index, "occupation", e.target.value)}
>
<MenuItem value=""><em>Select</em></MenuItem>
{occupationOptions.map((opt) => (
<MenuItem key={opt} value={opt}>
{opt}
</MenuItem>
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
))}
</Select>
</FormControl>
<FormControl fullWidth variant="outlined">
<InputLabel id={`${type}-${index}-marital-label`}>
Marital Status
</InputLabel>
<InputLabel>Marital Status</InputLabel>
<Select
labelId={`${type}-${index}-marital-label`}
label="Marital Status"
value={sibling.maritalStatus}
onChange={(e) =>
handleSiblingChange(type, index, "maritalStatus", e.target.value)
}
disabled={isFamilyMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
onChange={(e) => handleSiblingChange(type, index, "maritalStatus", e.target.value)}
>
<MenuItem value=""><em>Select</em></MenuItem>
{maritalStatusOptions.map((opt) => (
<MenuItem key={opt} value={opt}>
{opt}
</MenuItem>
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
))}
</Select>
</FormControl>
<FormControl fullWidth variant="outlined">
<InputLabel id={`${type}-${index}-children-label`}>
Have Children
</InputLabel>
<InputLabel>Have Children</InputLabel>
<Select
labelId={`${type}-${index}-children-label`}
label="Have Children"
value={sibling.haveChildrens}
onChange={(e) =>
handleSiblingChange(
type,
index,
"haveChildrens",
e.target.value
)
}
value={sibling.hasChildren}
onChange={(e) => handleSiblingChange(type, index, "hasChildren", e.target.value)}
>
<MenuItem value={1}>Yes</MenuItem>
<MenuItem value={0}>No</MenuItem>
<MenuItem value=""><em>Select</em></MenuItem>
<MenuItem value="Yes">Yes</MenuItem>
<MenuItem value="No">No</MenuItem>
</Select>
</FormControl>
<div className="md:col-span-2">
<TextField
fullWidth
multiline
rows={2}
label="Additional Details"
value={sibling.details}
onChange={(e) => handleSiblingChange(type, index, "details", e.target.value)}
variant="outlined"
/>
</div>
</div>
</Box>
);
@@ -212,20 +268,18 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
<form noValidate autoComplete="off" style={{ padding: 16 }}>
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4" id="fatherName">
<label className="text-gray-900 text-[15px]">
Father Name{requiredMark}
</label>
<TextField
fullWidth
inputRef={inputRef}
name="fatherName"
label="Father Name"
placeholder="Enter Father Name"
value={data.fatherName}
onChange={(e) => handleChange("fatherName", e.target.value)}
error={Boolean(errors.fatherName)}
helperText={errors.fatherName}
placeholder="Enter Father Name"
variant="outlined"
/>
</div>
@@ -236,30 +290,24 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
</label>
<TextField
fullWidth
name="fatherOccupation"
label="Father Occupation"
placeholder="Enter Father Occupation"
value={data.fatherOccupation}
onChange={(e) => handleChange("fatherOccupation", e.target.value)}
error={Boolean(errors.fatherOccupation)}
helperText={errors.fatherOccupation}
placeholder="Enter Father Occupation"
variant="outlined"
/>
</div>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4" id="motherName">
<label className="text-gray-900 text-[15px]">
Mother Name{requiredMark}
</label>
<TextField
fullWidth
name="motherName"
label="Mother Name"
placeholder="Enter Mother Name"
value={data.motherName}
onChange={(e) => handleChange("motherName", e.target.value)}
error={Boolean(errors.motherName)}
helperText={errors.motherName}
placeholder="Enter Mother Name"
variant="outlined"
/>
</div>
@@ -270,13 +318,9 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
</label>
<TextField
fullWidth
name="motherOccupation"
label="Mother Occupation"
placeholder="Enter Mother Occupation"
value={data.motherOccupation}
onChange={(e) => handleChange("motherOccupation", e.target.value)}
error={Boolean(errors.motherOccupation)}
helperText={errors.motherOccupation}
placeholder="Enter Mother Occupation"
variant="outlined"
/>
</div>
@@ -286,14 +330,13 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
Brother Count
</label>
<FormControl fullWidth variant="outlined">
<InputLabel id="brotherCount-label">Select Brother Count</InputLabel>
<InputLabel>Select Brother Count</InputLabel>
<Select
labelId="brotherCount-label"
label="Select Brother Count"
name="brotherCount"
value={data.brotherCount}
onChange={(e) => handleChange("brotherCount", e.target.value)}
>
<MenuItem value=""><em>Select</em></MenuItem>
{countOptions.map((count) => (
<MenuItem key={count} value={count}>
{count}
@@ -303,19 +346,27 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
</FormControl>
</div>
{Number(data.brotherCount) > 0 && (
<div className="md:col-span-2 mt-2" ref={brotherSectionRef}>
<div className="text-gray-900 text-[16px] font-semibold mb-3">
Brother Details
</div>
{data.brothers.map((_, i) => renderSiblingCard("brothers", i))}
</div>
)}
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Sister Count
</label>
<FormControl fullWidth variant="outlined">
<InputLabel id="sisterCount-label">Select Sister Count</InputLabel>
<InputLabel>Select Sister Count</InputLabel>
<Select
labelId="sisterCount-label"
label="Select Sister Count"
name="sisterCount"
value={data.sisterCount}
onChange={(e) => handleChange("sisterCount", e.target.value)}
>
<MenuItem value=""><em>Select</em></MenuItem>
{countOptions.map((count) => (
<MenuItem key={count} value={count}>
{count}
@@ -325,105 +376,183 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
</FormControl>
</div>
<div className="flex flex-col gap-4">
{Number(data.sisterCount) > 0 && (
<div className="md:col-span-2 mt-2" ref={sisterSectionRef}>
<div className="text-gray-900 text-[16px] font-semibold mb-3">
Sister Details
</div>
{data.sisters.map((_, i) => renderSiblingCard("sisters", i))}
</div>
)}
<div className="flex flex-col gap-4" id="familyStatus">
<label className="text-gray-900 text-[15px]">
Family Status{requiredMark}
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.familyStatus)}
>
<InputLabel id="familyStatus-label">Select Family Status</InputLabel>
<FormControl fullWidth variant="outlined" error={Boolean(errors.familyStatus)}>
<InputLabel>Select Family Status</InputLabel>
<Select
labelId="familyStatus-label"
label="Select Family Status"
name="familyStatus"
value={data.familyStatus}
onChange={(e) => handleChange("familyStatus", e.target.value)}
disabled={isFamilyMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{familyStatusOptions.map((item) => (
<MenuItem key={item.id ?? item} value={item.id ?? item}>
{item.family_type_name || item.name || item}
<MenuItem key={item.id} value={item.id}>
{item.family_type_name}
</MenuItem>
))}
</Select>
{errors.familyStatus && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.familyStatus}
</p>
<p className="text-[#d32f2f] text-[0.75rem] mt-1 ml-3">{errors.familyStatus}</p>
)}
</FormControl>
</div>
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">Native Place</label>
<div className="flex flex-col gap-4" id="nativePlace">
<label className="text-gray-900 text-[15px]">
Native Place{requiredMark}
</label>
<TextField
fullWidth
name="nativePlace"
label="Native Place"
placeholder="Enter Native Place"
value={data.nativePlace}
onChange={(e) => handleChange("nativePlace", e.target.value)}
error={Boolean(errors.nativePlace)}
helperText={errors.nativePlace}
placeholder="Enter Native Place"
variant="outlined"
/>
</div>
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Country Living
</label>
<FormControl fullWidth variant="outlined">
<InputLabel>Select Country</InputLabel>
<Select
label="Select Country"
value={data.familyCountry}
onChange={(e) => handleChange("familyCountry", e.target.value)}
>
{countryOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.country_name}</MenuItem>
))}
</Select>
</FormControl>
</div>
{isIndia ? (
<>
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Residing State
</label>
<FormControl fullWidth variant="outlined" disabled={!data.familyCountry}>
<InputLabel>Select State</InputLabel>
<Select
label="Select State"
value={data.familyState}
onChange={(e) => handleChange("familyState", e.target.value)}
>
{stateOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.state_name}</MenuItem>
))}
</Select>
</FormControl>
</div>
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Residing City
</label>
<FormControl fullWidth variant="outlined" disabled={!data.familyState || districtQuery.isLoading}>
<InputLabel>Select City</InputLabel>
<Select
label="Select City"
value={data.familyDistrict}
onChange={(e) => handleChange("familyDistrict", e.target.value)}
>
{districtOptions.map((opt) => (
<MenuItem key={opt.id} value={opt.id}>{opt.district_name || opt.name}</MenuItem>
))}
</Select>
</FormControl>
</div>
</>
) : data.familyCountry ? (
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
City / Town
</label>
<TextField
fullWidth
placeholder="Enter City / Town"
value={data.familyCity}
onChange={(e) => handleChange("familyCity", e.target.value)}
variant="outlined"
/>
</div>
) : null}
<div className="flex flex-col gap-4 md:col-span-2">
<label className="text-gray-900 text-[15px]">
Address
</label>
<TextField
fullWidth
multiline
rows={3}
placeholder="Enter complete address"
value={data.address}
onChange={(e) => handleChange("address", e.target.value)}
variant="outlined"
/>
</div>
<div className="flex flex-col gap-4 md:col-span-2">
<label className="text-gray-900 text-[15px]">
Expectations / Requirements Details
</label>
<TextField
fullWidth
multiline
rows={4}
placeholder="Describe your expectations"
value={data.expectationDetails}
onChange={(e) => handleChange("expectationDetails", e.target.value)}
variant="outlined"
/>
</div>
<div className="flex flex-col gap-4">
<label className="text-gray-900 text-[15px]">
Willing to go abroad
</label>
<FormControl fullWidth variant="outlined">
<InputLabel>Select Option</InputLabel>
<Select
label="Select Option"
value={data.willingToGoAbroad}
onChange={(e) => handleChange("willingToGoAbroad", e.target.value)}
>
<MenuItem value="Yes">Yes</MenuItem>
<MenuItem value="No">No</MenuItem>
<MenuItem value="Any">Any</MenuItem>
</Select>
</FormControl>
</div>
</div>
{Number(data.brotherCount) > 0 && (
<div className="mt-6">
<div className="text-gray-900 text-[16px] font-semibold mb-3">
Brother Details
</div>
<div className="grid grid-cols-1 gap-4">
{Array.from({ length: Number(data.brotherCount) }).map(
(_, index) => renderSiblingCard("brothers", index)
)}
</div>
</div>
)}
{Number(data.sisterCount) > 0 && (
<div className="mt-6">
<div className="text-gray-900 text-[16px] font-semibold mb-3">
Sister Details
</div>
<div className="grid grid-cols-1 gap-4">
{Array.from({ length: Number(data.sisterCount) }).map(
(_, index) => renderSiblingCard("sisters", index)
)}
</div>
</div>
)}
<Grid
item
xs={12}
sx={{ marginTop: 10, display: "flex", gap: 4, justifyContent: "center" }}
>
<div className="mt-10 flex gap-4 justify-center">
{onSkipStep && (
<Button variant="outlined" onClick={onSkipStep}>
<Button variant="outlined" onClick={onSkipStep} sx={{ minWidth: 120 }}>
Skip
</Button>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}>
{onSkipStep ? "Next" : "Update"}
<Button variant="contained" color="primary" onClick={handleSubmit} sx={{ minWidth: 120 }}>
{onSkipStep ? "Next" : "Update"}
</Button>
</Grid>
</div>
</form>
</div>
);
+6 -1
View File
@@ -1,6 +1,6 @@
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateLifestyleDetails } from "../redux/registrationFormSlice";
import { updateLifestyleDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
import {
Grid,
FormControl,
@@ -30,6 +30,7 @@ const LifestyleDetailsForm = ({
onSkipStep,
errors,
onFieldChange,
isEditMode,
}) => {
const dispatch = useDispatch();
const data = useSelector((state) => state.registerform.lifestyleDetails);
@@ -76,6 +77,10 @@ const LifestyleDetailsForm = ({
const handleChange = (field, value) => {
dispatch(updateLifestyleDetails({ [field]: value }));
if (onFieldChange) onFieldChange(field);
if (!isEditMode) {
dispatch(clearAllStepsFrom(5));
}
};
const handleMultiChange = (field, value) => {
File diff suppressed because it is too large. Load diff
+3 -3
View File
@@ -397,10 +397,10 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
{Object.entries(sibling).map(([sKey, sVal]) => {
if (sKey === 'id' || sKey.endsWith('_id') || sKey.endsWith('Id') || sKey === 'created_at' || sKey === 'updated_at') return null;
let displayValue = sVal;
if (sKey === 'haveChildrens' || sKey === 'have_childrens') {
if (sVal === true || sVal === 1 || sVal === '1') {
if (sKey === 'haveChildrens' || sKey === 'have_childrens' || sKey === 'hasChildren' || sKey === 'has_children') {
if (sVal === true || sVal === 1 || sVal === '1' || sVal === 'Yes') {
displayValue = 'Yes';
} else if (sVal === false || sVal === 0 || sVal === '0') {
} else if (sVal === false || sVal === 0 || sVal === '0' || sVal === 'No') {
displayValue = 'No';
}
}
+334 -188
View File
@@ -8,6 +8,7 @@ import {
updateFamilyDetails,
updateLifestyleDetails,
updatePartnerPreferences,
clearAllStepsFrom,
submitForm,
} from "../redux/registrationFormSlice";
import PersonalDetailsForm from "./PersonalDetailsForm";
@@ -31,37 +32,47 @@ import { isAuthenticated } from "../utills/auth";
import { getPreviewDetails } from "../api/preview.api";
const STEP_FIELD_ORDER = {
1: [
"name",
"profile_for",
"gender",
"mobileNumber",
"dob",
"height",
"weight",
"maritalStatus",
"religion",
"profileFor",
"caste",
"subCaste",
"gothram",
"raasi",
"star",
"name",
"mobile",
"email",
"password",
"confirmPassword",
"state",
"city",
"pincode",
"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: [
"fieldOfStudy",
"qualification",
"collegeName",
"study_field",
"education",
"education_detail",
"college_name",
"employee_type",
"occupation",
"organization",
"employeeType",
"income",
"workLocation",
"occupation_detail",
"company_name",
"income_currency",
"annual_income",
"work_country",
"work_city",
"work_state",
"work_district",
"address",
],
3: [
"fatherName",
@@ -89,35 +100,29 @@ const STEP_FIELD_ORDER = {
const STEP1_SERVER_FIELD_MAP = {
name: "name",
mobile: "mobileNumber",
mobile_number: "mobileNumber",
mobileNumber: "mobileNumber",
phone: "mobileNumber",
mobile: "mobile",
email: "email",
gender: "gender",
dob: "dob",
height: "height",
weight: "weight",
marital_status: "maritalStatus",
maritalStatus: "maritalStatus",
marital_status: "marital_status",
religion: "religion",
profile_for: "profileFor",
profileFor: "profileFor",
profile_for: "profile_for",
caste: "caste",
sub_caste: "subCaste",
subCaste: "subCaste",
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",
raasi: "raasi",
star: "star",
state: "state",
district: "city",
city: "city",
pincode: "pincode",
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",
confirmPassword: "confirmPassword",
profiles: "profiles",
profile_images: "profiles",
};
@@ -171,8 +176,7 @@ const STEP1_SERVER_FIELD_MAP = {
import { Check } from "lucide-react";
const Stepper = ({ currentStep, enabledSteps, completedSteps, onStepClick }) => {
const Stepper = ({ currentStep, enabledSteps, completedSteps, onStepClick, checkStepValidity }) => {
const steps = [
{ num: 1, label: "Personal" },
{ num: 2, label: "Educational" },
@@ -183,40 +187,57 @@ const Stepper = ({ currentStep, enabledSteps, completedSteps, onStepClick }) =>
];
return (
<div className="flex items-center justify-between px-4 py-6">
<div className="flex items-center justify-between px-4 py-6 overflow-x-auto">
{steps.map((step, index) => {
const isEnabled = enabledSteps.includes(step.num);
const isCompleted = completedSteps.includes(step.num);
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 (
<React.Fragment key={step.num}>
<div
className={`flex flex-col items-center ${
isEnabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"
className={`flex flex-col items-center min-w-[70px] ${
isEnabled ? "cursor-pointer" : "cursor-not-allowed opacity-60"
}`}
onClick={() => isEnabled && onStepClick(step.num)}
>
<div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold transition-all duration-300 ${
isCompleted
? "bg-green-600 text-white"
: currentStep === step.num
? "bg-red-600 text-white"
: "bg-gray-300 text-gray-600"
? "bg-green-600 text-white shadow-md"
: isActive
? "bg-red-600 text-white ring-4 ring-red-100 shadow-md"
: isSkipped
? "bg-blue-500 text-white shadow-md"
: "bg-gray-200 text-gray-500"
}`}
>
{isCompleted ? <Check size={16} /> : step.num}
{isCompleted ? <Check size={18} /> : step.num}
</div>
<span className="text-xs mt-1">{step.label}</span>
<span
className={`text-[10px] mt-2 font-medium text-center uppercase tracking-wider ${
isActive ? "text-red-600 font-bold" : isCompleted ? "text-green-600" : isSkipped ? "text-blue-500" : "text-gray-400"
}`}
>
{step.label}
</span>
</div>
{index < steps.length - 1 && (
<div
className={`flex-1 h-0.5 mx-1 ${
completedSteps.includes(step.num)
className={`flex-1 h-[2px] mx-1 mb-6 transition-colors duration-500 ${
(completedSteps.includes(step.num) || (checkStepValidity && checkStepValidity(step.num)))
? "bg-green-600"
: "bg-gray-300"
: (enabledSteps.includes(step.num) && enabledSteps.includes(step.num + 1))
? "bg-blue-300"
: "bg-gray-200"
}`}
/>
)}
@@ -255,7 +276,14 @@ const shouldHideStepper = hideStepperRoutes.some((route) => location.pathname.st
const savedStep = localStorage.getItem("registration_current_step");
return savedStep ? Number(savedStep) : 1;
});
const [enabledSteps, setEnabledSteps] = useState([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({});
@@ -362,24 +390,23 @@ const [completedSteps, setCompletedSteps] = useState([]);
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 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();
}
}
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);
}, 100);
};
const clearFieldErrors = (fields) => {
@@ -474,18 +501,27 @@ const [completedSteps, setCompletedSteps] = useState([]);
dispatch(
updatePersonalDetails({
name: pd.name || "",
mobileNumber: pd.mobile || "",
mobile: pd.mobile || "",
email: pd.email || "",
gender: pd.gender || "",
dob: formattedDob,
height: pd.height || "",
height: pd.height_id || "",
weight: pd.weight || "",
maritalStatus: pd.marital_status_id || "",
marital_status: pd.marital_status_id || "",
religion: pd.religion_id || "",
profileFor: pd.profile_for_id || "",
profile_for: pd.profile_for_id || "",
caste: pd.caste_id || "",
subCaste: pd.sub_caste_id || "",
gothram: pd.gothram_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 || "",
@@ -518,14 +554,21 @@ useEffect(() => {
const ed = educationalData.educational_details;
dispatch(
updateEducationalDetails({
fieldOfStudy: ed.study_field_id || "",
qualification: ed.education_id || "",
collegeName: ed.college_name || "",
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 || "",
organization: ed.company_name || "",
employeeType: ed.employee_type_id || "",
income: ed.annual_income_id || "",
workLocation: ed.work_location || "",
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 || "",
})
);
}
@@ -546,41 +589,46 @@ const {data:familyData} = useQuery({
});
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])
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({
@@ -668,23 +716,24 @@ useEffect(()=>{
if (step === 1) {
const required = [
"name",
"mobileNumber",
"mobile",
"gender",
"dob",
"height",
"maritalStatus",
"profileFor",
"marital_status",
"profile_for",
"caste",
"email",
"state",
"city",
"pincode",
"mother_language",
"complexion",
"physical_status",
"inter_caste_parents",
"do_you_speak_telugu",
];
required.forEach((field) => {
if (!personalDetails[field]) {
if (!personalDetails[field] && personalDetails[field] !== 0) {
const label = field
.replace(/([A-Z])/g, " $1")
.replace(/_/g, " ")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
}
@@ -702,16 +751,10 @@ useEffect(()=>{
newErrors.email = "Invalid email format";
}
if (
personalDetails.mobileNumber &&
personalDetails.mobileNumber.length !== 10
personalDetails.mobile &&
personalDetails.mobile.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";
newErrors.mobile = "Mobile number must be 10 digits";
}
if (
personalDetails.password &&
@@ -721,16 +764,24 @@ useEffect(()=>{
newErrors.confirmPassword = "Passwords do not match";
}
} else if (step === 2) {
const required = [
"qualification",
"fieldOfStudy",
];
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(/([A-Z])/g, " $1")
.replace(/_/g, " ")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
}
@@ -740,6 +791,7 @@ useEffect(()=>{
"fatherName",
"motherName",
"familyStatus",
"nativePlace",
];
required.forEach((field) => {
if (!familyDetails[field]) {
@@ -836,24 +888,33 @@ useEffect(()=>{
const buildRegisterStep1Payload = async () => {
const formData = new FormData();
formData.append("name", personalDetails.name);
formData.append("mobile", personalDetails.mobileNumber);
formData.append("mobile", personalDetails.mobile);
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("marital_status", personalDetails.marital_status);
formData.append("religion", personalDetails.religion);
formData.append("profile_for", personalDetails.profileFor || "");
formData.append("profile_for", personalDetails.profile_for || "");
formData.append("caste", personalDetails.caste);
formData.append("sub_caste", personalDetails.subCaste || "");
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("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("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)) {
@@ -895,14 +956,23 @@ useEffect(()=>{
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("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("company_name", educationalDetails.organization || "");
formData.append("employee_type", educationalDetails.employeeType || "");
formData.append("annual_income", educationalDetails.income || "");
formData.append("work_location", educationalDetails.workLocation || "");
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;
};
@@ -916,19 +986,30 @@ useEffect(()=>{
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}][have_childrens]`, brother?.haveChildrens ?? "");
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}][have_childrens]`, sister?.haveChildrens ?? "");
formData.append(`sisters[${index}][type]`, sister?.type || "");
formData.append(`sisters[${index}][has_children]`, sister?.hasChildren || "");
formData.append(`sisters[${index}][details]`, sister?.details || "");
});
return formData;
@@ -1053,6 +1134,12 @@ useEffect(()=>{
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:
@@ -1141,29 +1228,43 @@ useEffect(()=>{
}
};
const handleSkip = () => {
setErrors({});
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);
setCurrentStep((prev) => {
const nextStep = Math.min(prev + 1, 6);
setEnabledSteps((steps) =>
steps.includes(nextStep) ? steps : [...steps, nextStep]
);
setEnabledSteps((steps) =>
steps.includes(nextStep) ? steps : [...steps, nextStep]
);
return nextStep;
});
return nextStep;
});
window.scrollTo(0, 0);
};
window.scrollTo(0, 0);
};
const handleStepClick = (step) => {
if (!enabledSteps.includes(step)) return;
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;
}
setCurrentStep(step);
setErrors({});
window.scrollTo(0, 0);
};
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) {
@@ -1203,7 +1304,7 @@ useEffect(() => {
onSubmitStep={handleStepSubmit}
errors={errors}
onFieldChange={clearFieldErrors}
isStep1Update={isStep1Update}
isEditMode={isStep1Update || shouldHideStepper}
/>
);
case 2:
@@ -1213,6 +1314,7 @@ useEffect(() => {
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
isEditMode={shouldHideStepper}
/>
);
case 3:
@@ -1222,6 +1324,7 @@ useEffect(() => {
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
isEditMode={shouldHideStepper}
/>
);
case 4:
@@ -1231,6 +1334,7 @@ useEffect(() => {
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
isEditMode={shouldHideStepper}
/>
);
case 5:
@@ -1240,6 +1344,7 @@ useEffect(() => {
onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors}
onFieldChange={clearFieldErrors}
isEditMode={shouldHideStepper}
/>
);
case 6:
@@ -1249,6 +1354,46 @@ useEffect(() => {
}
};
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",
@@ -1272,6 +1417,7 @@ useEffect(() => {
onStepClick={handleStepClick}
enabledSteps={enabledSteps}
completedSteps={completedSteps}
checkStepValidity={checkStepValidity}
/> )}