api registration done
This commit is contained in:
1 parent
fd0a327b13
commit
653055debc
38 files changed
+3411
-1385
No files matched your search
@@ -1,19 +1,102 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateEducationalDetails } from "../redux/registrationFormSlice";
|
||||
import { TextField, Button, Grid } from "@mui/material";
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
Grid,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
} from "@mui/material";
|
||||
import { useEducationMasters, useEducationList } from "../hooks/useMasters";
|
||||
|
||||
const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const EducationalDetailsForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.educationalDetails);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: educationMasters, isLoading: isEducationMastersLoading } =
|
||||
useEducationMasters();
|
||||
const educationListQuery = useEducationList(data.fieldOfStudy);
|
||||
|
||||
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 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 handleChange = (field, value) => {
|
||||
dispatch(updateEducationalDetails({ [field]: value }));
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
if (field === "fieldOfStudy") {
|
||||
updates.qualification = "";
|
||||
fieldsToClear.push("qualification");
|
||||
}
|
||||
dispatch(updateEducationalDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -26,58 +109,168 @@ const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] 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">
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Highest Qualification</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="qualification"
|
||||
label="Highest Qualification"
|
||||
value={data.qualification}
|
||||
onChange={(e) => handleChange("qualification", e.target.value)}
|
||||
error={Boolean(errors.qualification)}
|
||||
helperText={errors.qualification}
|
||||
placeholder="Enter Highest Qualification"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Field of Study */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Field of Study</label>
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Field of Study{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.fieldOfStudy)}
|
||||
>
|
||||
<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="fieldOfStudy"
|
||||
label="Field of Study"
|
||||
value={data.fieldOfStudy}
|
||||
onChange={(e) => handleChange("fieldOfStudy", e.target.value)}
|
||||
error={Boolean(errors.fieldOfStudy)}
|
||||
helperText={errors.fieldOfStudy}
|
||||
placeholder="Enter Field of Study"
|
||||
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]">Enter Occupation</label>
|
||||
<TextField
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
name="occupation"
|
||||
label="Occupation"
|
||||
value={data.occupation}
|
||||
onChange={(e) => handleChange("occupation", e.target.value)}
|
||||
error={Boolean(errors.occupation)}
|
||||
helperText={errors.occupation}
|
||||
placeholder="Enter Occupation"
|
||||
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]">Enter Company / Organization Name</label>
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Company / Organization Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="organization"
|
||||
@@ -91,36 +284,156 @@ const EducationalDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Employee Type */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Employee Type{requiredMark}
|
||||
</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]">Enter Annual Income</label>
|
||||
<TextField
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Annual Income{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
name="income"
|
||||
label="Annual Income"
|
||||
value={data.income}
|
||||
onChange={(e) => handleChange("income", e.target.value)}
|
||||
error={Boolean(errors.income)}
|
||||
helperText={errors.income}
|
||||
placeholder="Enter Annual Income"
|
||||
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]">Enter Work Location</label>
|
||||
<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"
|
||||
/>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
+378
-132
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateFamilyDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
@@ -9,19 +9,91 @@ import {
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
Box,
|
||||
} from "@mui/material";
|
||||
import { useFamilyMasters } from "../hooks/useMasters";
|
||||
|
||||
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.familyDetails);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: familyMasters, isLoading: isFamilyMastersLoading } =
|
||||
useFamilyMasters();
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const maritalStatusOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.maritalStatus || raw.marital_status || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
const familyStatusOptions = useMemo(() => {
|
||||
const raw = familyMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return [];
|
||||
return raw.familyStatus || raw.family_status || [];
|
||||
}, [familyMasters]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const createSibling = () => ({
|
||||
name: "",
|
||||
occupation: "",
|
||||
maritalStatus: "",
|
||||
haveChildrens: "",
|
||||
});
|
||||
|
||||
const syncSiblingArray = (arr, count) => {
|
||||
const target = Math.max(0, Number(count) || 0);
|
||||
const next = [...(arr || [])];
|
||||
while (next.length < target) {
|
||||
next.push(createSibling());
|
||||
}
|
||||
if (next.length > target) {
|
||||
next.length = target;
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateFamilyDetails({ [field]: value }));
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
|
||||
if (field === "brotherCount") {
|
||||
const count = Number(value) || 0;
|
||||
updates.brotherCount = count;
|
||||
updates.brothers = syncSiblingArray(data.brothers, count);
|
||||
fieldsToClear.push("brothers");
|
||||
}
|
||||
|
||||
if (field === "sisterCount") {
|
||||
const count = Number(value) || 0;
|
||||
updates.sisterCount = count;
|
||||
updates.sisters = syncSiblingArray(data.sisters, count);
|
||||
fieldsToClear.push("sisters");
|
||||
}
|
||||
|
||||
dispatch(updateFamilyDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const handleSiblingChange = (type, index, field, value) => {
|
||||
const list = [...(data[type] || [])];
|
||||
if (!list[index]) list[index] = createSibling();
|
||||
list[index] = { ...list[index], [field]: value };
|
||||
dispatch(updateFamilyDetails({ [type]: list }));
|
||||
if (onFieldChange) onFieldChange(type);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -29,155 +101,329 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
return (
|
||||
const countOptions = Array.from({ length: 11 }, (_, i) => i);
|
||||
|
||||
<>
|
||||
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] 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">
|
||||
{/* Highest Qualification */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Father Name</label>
|
||||
<TextField
|
||||
const renderSiblingCard = (type, index) => {
|
||||
const sibling = (data[type] || [])[index] || createSibling();
|
||||
const labelPrefix = type === "brothers" ? "Brother" : "Sister";
|
||||
return (
|
||||
<Box
|
||||
key={`${type}-${index}`}
|
||||
sx={{
|
||||
border: "1px solid #e5e7eb",
|
||||
borderRadius: 2,
|
||||
padding: 2,
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="fatherName"
|
||||
label="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>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Father Occupation</label>
|
||||
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
name="fatherOccupation"
|
||||
label="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">
|
||||
<label className="text-gray-900 text-[15px]">Enter Mother Name</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherName"
|
||||
label="Mother Name"
|
||||
value={data.motherName}
|
||||
onChange={(e) => handleChange("motherName", e.target.value)}
|
||||
error={Boolean(errors.motherName)}
|
||||
helperText={errors.motherName}
|
||||
placeholder="Enter Mother Name"
|
||||
label="Name"
|
||||
value={sibling.name}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(type, index, "name", e.target.value)
|
||||
}
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Mother Occupation</label>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherOccupation"
|
||||
label="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>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Enter Number of Brothers / Sisters</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="siblings"
|
||||
label="Number of Brothers / Sisters"
|
||||
value={data.siblings}
|
||||
onChange={(e) => handleChange("siblings", e.target.value)}
|
||||
error={Boolean(errors.siblings)}
|
||||
helperText={errors.siblings}
|
||||
placeholder="Enter Number of Brothers / Sisters"
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]"> Brothers / Sisters (Married / Unmarried)</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.siblingsStatus)}
|
||||
>
|
||||
<InputLabel id="siblingsStatus-label">
|
||||
Brothers / Sisters (Married / Unmarried)
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-occupation-label`}>
|
||||
Occupation
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="siblingsStatus-label"
|
||||
label="Brothers / Sisters (Married / Unmarried)"
|
||||
name="siblingsStatus"
|
||||
value={data.siblingsStatus}
|
||||
onChange={(e) => handleChange("siblingsStatus", e.target.value)}
|
||||
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",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem value="All Married">All Married</MenuItem>
|
||||
<MenuItem value="All Unmarried">All Unmarried</MenuItem>
|
||||
<MenuItem value="Mixed">Mixed</MenuItem>
|
||||
{occupationOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.siblingsStatus && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.siblingsStatus}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
</div>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-marital-label`}>
|
||||
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",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{maritalStatusOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id={`${type}-${index}-children-label`}>
|
||||
Have Children
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId={`${type}-${index}-children-label`}
|
||||
label="Have Children"
|
||||
value={sibling.haveChildrens}
|
||||
onChange={(e) =>
|
||||
handleSiblingChange(
|
||||
type,
|
||||
index,
|
||||
"haveChildrens",
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuItem value={1}>Yes</MenuItem>
|
||||
<MenuItem value={0}>No</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] 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">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Father Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
inputRef={inputRef}
|
||||
name="fatherName"
|
||||
label="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>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Father Occupation
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="fatherOccupation"
|
||||
label="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">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Mother Name{requiredMark}
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherName"
|
||||
label="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>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Mother Occupation
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="motherOccupation"
|
||||
label="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>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Brother Count
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined">
|
||||
<InputLabel id="brotherCount-label">Select Brother Count</InputLabel>
|
||||
<Select
|
||||
labelId="brotherCount-label"
|
||||
label="Select Brother Count"
|
||||
name="brotherCount"
|
||||
value={data.brotherCount}
|
||||
onChange={(e) => handleChange("brotherCount", e.target.value)}
|
||||
>
|
||||
{countOptions.map((count) => (
|
||||
<MenuItem key={count} value={count}>
|
||||
{count}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</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>
|
||||
<Select
|
||||
labelId="sisterCount-label"
|
||||
label="Select Sister Count"
|
||||
name="sisterCount"
|
||||
value={data.sisterCount}
|
||||
onChange={(e) => handleChange("sisterCount", e.target.value)}
|
||||
>
|
||||
{countOptions.map((count) => (
|
||||
<MenuItem key={count} value={count}>
|
||||
{count}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<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>
|
||||
<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>
|
||||
))}
|
||||
</Select>
|
||||
{errors.familyStatus && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.familyStatus}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<label className="text-gray-900 text-[15px]">Native Place</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="nativePlace"
|
||||
label="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>
|
||||
|
||||
{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>
|
||||
)}
|
||||
|
||||
<Grid item
|
||||
xs={12} sx={{ marginTop: 10, display: "flex", gap: 4 , justifyContent: "center"}}>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
{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" }}
|
||||
>
|
||||
<Button variant="outlined" onClick={onSkipStep}>
|
||||
Skip
|
||||
</Button>
|
||||
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
||||
Submit
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updateLifestyleDetails } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
@@ -8,12 +8,50 @@ import {
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
TextField,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { useLifestyleMasters } from "../hooks/useMasters";
|
||||
|
||||
const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const LifestyleDetailsForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: lifestyleMasters, isLoading: isLifestyleMastersLoading } =
|
||||
useLifestyleMasters();
|
||||
|
||||
const dietOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.diet || raw.diets || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const hobbyOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.hobbies || raw.hobby || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const grahaOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.grahas || raw.graha || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
@@ -21,6 +59,27 @@ const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
};
|
||||
|
||||
const handleMultiChange = (field, value) => {
|
||||
const nextValue = Array.isArray(value) ? value : String(value).split(",");
|
||||
dispatch(updateLifestyleDetails({ [field]: nextValue }));
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
};
|
||||
|
||||
const handleGrahaChange = (house, value) => {
|
||||
const next = { ...(data.graha || {}) };
|
||||
next[house] = value;
|
||||
dispatch(updateLifestyleDetails({ graha: next }));
|
||||
if (onFieldChange) onFieldChange("graha");
|
||||
};
|
||||
|
||||
const handleAmsamChange = (house, value) => {
|
||||
const next = { ...(data.amsam || {}) };
|
||||
next[house] = value;
|
||||
dispatch(updateLifestyleDetails({ amsam: next }));
|
||||
if (onFieldChange) onFieldChange("amsam");
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -28,67 +87,302 @@ const LifestyleDetailsForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
const fields = [
|
||||
{ name: "diet", label: "Diet", options: ["Veg", "Non-Veg", "Eggetarian"] },
|
||||
// {
|
||||
// name: "drinking",
|
||||
// label: "Drinking Habits",
|
||||
// options: ["No", "Occasionally", "Regularly"],
|
||||
// },
|
||||
// {
|
||||
// name: "smoking",
|
||||
// label: "Smoking Habits",
|
||||
// options: ["No", "Occasionally", "Regularly"],
|
||||
// },
|
||||
{
|
||||
name: "hobbies",
|
||||
label: "Hobbies & Interests",
|
||||
options: ["Song", "Reading", "Sports", "Travel"],
|
||||
},
|
||||
];
|
||||
const parseDateValue = (value) => {
|
||||
if (!value) return null;
|
||||
const date = new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? null : date;
|
||||
};
|
||||
|
||||
const parseTimeValue = (value) => {
|
||||
if (!value || typeof value !== "string") return null;
|
||||
const [hours, minutes] = value.split(":").map(Number);
|
||||
if (Number.isNaN(hours) || Number.isNaN(minutes)) return null;
|
||||
const date = new Date();
|
||||
date.setHours(hours, minutes, 0, 0);
|
||||
return date;
|
||||
};
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!(value instanceof Date) || Number.isNaN(value)) return "";
|
||||
const y = value.getFullYear();
|
||||
const m = String(value.getMonth() + 1).padStart(2, "0");
|
||||
const d = String(value.getDate()).padStart(2, "0");
|
||||
return `${y}-${m}-${d}`;
|
||||
};
|
||||
|
||||
const formatTime = (value) => {
|
||||
if (!(value instanceof Date) || Number.isNaN(value)) return "";
|
||||
const h = String(value.getHours()).padStart(2, "0");
|
||||
const m = String(value.getMinutes()).padStart(2, "0");
|
||||
return `${h}:${m}`;
|
||||
};
|
||||
|
||||
const renderChartCell = (label, value, onChange) => (
|
||||
<div className="bg-white border border-gray-300 rounded-lg p-2 flex flex-col items-center justify-center min-h-[70px]">
|
||||
<span className="text-[10px] font-medium text-gray-700 text-center">
|
||||
{label}
|
||||
</span>
|
||||
<FormControl fullWidth variant="standard" sx={{ mt: 0.5 }}>
|
||||
<Select
|
||||
multiple
|
||||
displayEmpty
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
renderValue={(selected) => {
|
||||
if (!selected || selected.length === 0) return `+ Add ${label}`;
|
||||
return selected.join(", ");
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: { style: { maxHeight: 280 } },
|
||||
}}
|
||||
>
|
||||
{grahaOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
<Checkbox checked={value.indexOf(opt) > -1} />
|
||||
<ListItemText primary={opt} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
);
|
||||
|
||||
const renderChartGrid = (type) => {
|
||||
const getValue = (house) =>
|
||||
(type === "graha" ? data.graha : data.amsam)?.[house] || [];
|
||||
const onChange = (house, value) =>
|
||||
type === "graha"
|
||||
? handleGrahaChange(house, value)
|
||||
: handleAmsamChange(house, value);
|
||||
const label = type === "graha" ? "Rasi" : "Navamsam";
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
{renderChartCell(label, getValue(1), (value) => onChange(1, value))}
|
||||
{renderChartCell(label, getValue(2), (value) => onChange(2, value))}
|
||||
{renderChartCell(label, getValue(3), (value) => onChange(3, value))}
|
||||
{renderChartCell(label, getValue(4), (value) => onChange(4, value))}
|
||||
|
||||
{renderChartCell(label, getValue(5), (value) => onChange(5, value))}
|
||||
<div className="col-span-2 row-span-2 flex items-center justify-center">
|
||||
<div className="w-full aspect-square rounded-full overflow-hidden border-4 border-gray-200 shadow-lg bg-white" />
|
||||
</div>
|
||||
{renderChartCell(label, getValue(6), (value) => onChange(6, value))}
|
||||
|
||||
{renderChartCell(label, getValue(7), (value) => onChange(7, value))}
|
||||
{renderChartCell(label, getValue(8), (value) => onChange(8, value))}
|
||||
|
||||
{renderChartCell(label, getValue(9), (value) => onChange(9, value))}
|
||||
{renderChartCell(label, getValue(10), (value) => onChange(10, value))}
|
||||
{renderChartCell(label, getValue(11), (value) => onChange(11, value))}
|
||||
{renderChartCell(label, getValue(12), (value) => onChange(12, value))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] 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">
|
||||
{fields.map(({ name, label, options }) => (
|
||||
<div key={name} className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">{label}</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors[name])}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Diet (Multi-select){requiredMark}
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
||||
<InputLabel id="diets-label">Select Diet</InputLabel>
|
||||
<Select
|
||||
labelId="diets-label"
|
||||
label="Select Diet"
|
||||
name="diets"
|
||||
multiple
|
||||
value={data.diets}
|
||||
onChange={(e) => handleMultiChange("diets", e.target.value)}
|
||||
inputRef={inputRef}
|
||||
disabled={isLifestyleMastersLoading}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = dietOptions.find(
|
||||
(opt) => (opt.id ?? opt) === id
|
||||
);
|
||||
return item?.diet_name || item?.name || id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<InputLabel id={`${name}-label`}>Select {label}</InputLabel>
|
||||
<Select
|
||||
labelId={`${name}-label`}
|
||||
label={`Select ${label}`}
|
||||
name={name}
|
||||
value={data[name]}
|
||||
onChange={(e) => handleChange(name, e.target.value)}
|
||||
inputRef={name === "diet" ? inputRef : null}
|
||||
>
|
||||
|
||||
{options.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
{dietOptions.map((opt) => {
|
||||
const value = opt.id ?? opt;
|
||||
const label = opt.diet_name || opt.name || String(opt);
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
<Checkbox checked={data.diets.indexOf(value) > -1} />
|
||||
<ListItemText primary={label} />
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors[name] && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors[name]}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors.diets && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.diets}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Hobbies & Interests (Multi-select){requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.hobbies)}
|
||||
>
|
||||
<InputLabel id="hobbies-label">
|
||||
Select Hobbies & Interests
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="hobbies-label"
|
||||
label="Select Hobbies & Interests"
|
||||
name="hobbies"
|
||||
multiple
|
||||
value={data.hobbies}
|
||||
onChange={(e) => handleMultiChange("hobbies", e.target.value)}
|
||||
disabled={isLifestyleMastersLoading}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = hobbyOptions.find(
|
||||
(opt) => (opt.id ?? opt) === id
|
||||
);
|
||||
return item?.hobby_name || item?.name || id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{hobbyOptions.map((opt) => {
|
||||
const value = opt.id ?? opt;
|
||||
const label = opt.hobby_name || opt.name || String(opt);
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
<Checkbox checked={data.hobbies.indexOf(value) > -1} />
|
||||
<ListItemText primary={label} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors.hobbies && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.hobbies}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center py-4">
|
||||
<h2 className="text-[18px] font-semibold text-gray-800">
|
||||
Astrology / Horoscope
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[900px] mx-auto">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Date of Birth{requiredMark}
|
||||
</label>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<DatePicker
|
||||
format="dd/MM/yyyy"
|
||||
value={parseDateValue(data.dob)}
|
||||
onChange={(value) => handleChange("dob", formatDate(value))}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "dob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.dob),
|
||||
helperText: errors.dob,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Time of Birth{requiredMark}
|
||||
</label>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<TimePicker
|
||||
value={parseTimeValue(data.tob)}
|
||||
onChange={(value) => handleChange("tob", formatTime(value))}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "tob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.tob),
|
||||
helperText: errors.tob,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Place of Birth</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="placeOfBirth"
|
||||
value={data.placeOfBirth}
|
||||
onChange={(e) => handleChange("placeOfBirth", e.target.value)}
|
||||
error={Boolean(errors.placeOfBirth)}
|
||||
helperText={errors.placeOfBirth}
|
||||
placeholder="Enter Place of Birth"
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[950px] mx-auto mt-6">
|
||||
<div>
|
||||
<div className="py-4">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
||||
Add Rasi
|
||||
</h3>
|
||||
{renderChartGrid("graha")}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="py-4">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
||||
Add Navamsam
|
||||
</h3>
|
||||
{renderChartGrid("amsam")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Grid
|
||||
|
||||
@@ -1,27 +1,137 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { updatePartnerPreferences } from "../redux/registrationFormSlice";
|
||||
import {
|
||||
Grid,
|
||||
TextField,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
Button,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
} from "@mui/material";
|
||||
import { usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||
import { useCityMasters, useSubCasteMasters } from "../hooks/useDependentMasters";
|
||||
|
||||
const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
const PartnerPreferencesForm = ({
|
||||
onSubmitStep,
|
||||
onSkipStep,
|
||||
errors,
|
||||
onFieldChange,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.partnerPreferences);
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: masters, isLoading: isPartnerMastersLoading } =
|
||||
usePartnerPreferenceMasters();
|
||||
const subCasteQuery = useSubCasteMasters(data.castes);
|
||||
const cityQuery = useCityMasters(data.states);
|
||||
|
||||
const ageRangeOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.ageRange || raw.age_range || [];
|
||||
}, [masters]);
|
||||
|
||||
const casteOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.caste || raw.castes || [];
|
||||
}, [masters]);
|
||||
|
||||
const occupationOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.occupation || raw.occupations || [];
|
||||
}, [masters]);
|
||||
|
||||
const educationOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.education || raw.educations || [];
|
||||
}, [masters]);
|
||||
|
||||
const hobbyOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.hobbies || raw.hobby || [];
|
||||
}, [masters]);
|
||||
|
||||
const annualIncomeOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.annual_income || raw.annualIncome || [];
|
||||
}, [masters]);
|
||||
|
||||
const stateOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.state || raw.states || [];
|
||||
}, [masters]);
|
||||
|
||||
const subCasteOptions = useMemo(() => {
|
||||
const raw = subCasteQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) {
|
||||
const merged = raw.flatMap((entry) => {
|
||||
if (!entry) return [];
|
||||
if (Array.isArray(entry)) return entry;
|
||||
return entry.sub_caste || entry.subCaste || entry.data || [];
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
return raw.sub_caste || raw.subCaste || raw.data || [];
|
||||
}, [subCasteQuery.data]);
|
||||
|
||||
const cityOptions = useMemo(() => {
|
||||
const raw = cityQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.subCaste || raw.district || raw.data || [];
|
||||
}, [cityQuery.data]);
|
||||
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updatePartnerPreferences({ [field]: value }));
|
||||
const arrayFields = new Set([
|
||||
"castes",
|
||||
"subCastes",
|
||||
"occupations",
|
||||
"educations",
|
||||
"hobbies",
|
||||
"states",
|
||||
"districts",
|
||||
]);
|
||||
const nextValue =
|
||||
arrayFields.has(field) && typeof value === "string"
|
||||
? value.split(",").filter(Boolean)
|
||||
: value;
|
||||
const updates = { [field]: nextValue };
|
||||
const fieldsToClear = [field];
|
||||
|
||||
if (field === "castes") {
|
||||
updates.subCastes = [];
|
||||
fieldsToClear.push("subCastes");
|
||||
}
|
||||
if (field === "states") {
|
||||
updates.districts = [];
|
||||
fieldsToClear.push("districts");
|
||||
}
|
||||
|
||||
dispatch(updatePartnerPreferences(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -29,36 +139,98 @@ const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
onSubmitStep();
|
||||
};
|
||||
|
||||
const renderMultiSelect = ({
|
||||
name,
|
||||
label,
|
||||
options,
|
||||
value,
|
||||
getLabel,
|
||||
getValue,
|
||||
disabled,
|
||||
}) => (
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors[name])}>
|
||||
<InputLabel id={`${name}-label`}>Select {label}</InputLabel>
|
||||
<Select
|
||||
labelId={`${name}-label`}
|
||||
label={`Select ${label}`}
|
||||
name={name}
|
||||
multiple
|
||||
value={value}
|
||||
onChange={(e) => handleChange(name, e.target.value)}
|
||||
disabled={disabled}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = options.find((opt) => getValue(opt) === id);
|
||||
return item ? getLabel(item) : id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{options.map((opt) => {
|
||||
const optValue = getValue(opt);
|
||||
const optLabel = getLabel(opt);
|
||||
return (
|
||||
<MenuItem key={optValue} value={optValue}>
|
||||
<Checkbox checked={value.indexOf(optValue) > -1} />
|
||||
<ListItemText primary={optLabel} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors[name] && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors[name]}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto bg-[#fff2f2] 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">
|
||||
{/* Preferred Age Range */}
|
||||
{/* Age Range */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Preferred Age Range
|
||||
Age Range{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.ageRange)}
|
||||
>
|
||||
<InputLabel id="ageRange-label">
|
||||
Select Preferred Age Range
|
||||
</InputLabel>
|
||||
<InputLabel id="ageRange-label">Select Age Range</InputLabel>
|
||||
<Select
|
||||
labelId="ageRange-label"
|
||||
label="Select Preferred Age Range"
|
||||
label="Select Age Range"
|
||||
name="ageRange"
|
||||
value={data.ageRange}
|
||||
onChange={(e) => handleChange("ageRange", e.target.value)}
|
||||
|
||||
inputRef={inputRef}
|
||||
disabled={isPartnerMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem value="25 - 28">25 - 28</MenuItem>
|
||||
<MenuItem value="29 - 32">29 - 32</MenuItem>
|
||||
<MenuItem value="33 - 36">33 - 36</MenuItem>
|
||||
{ageRangeOptions.map((opt) => (
|
||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
||||
{opt.name || opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.ageRange && (
|
||||
<p
|
||||
@@ -74,36 +246,120 @@ const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Religion / Caste Preference */}
|
||||
{/* Caste */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Religion / Caste Preference
|
||||
Caste{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "castes",
|
||||
label: "Caste",
|
||||
options: casteOptions,
|
||||
value: data.castes,
|
||||
getLabel: (opt) => opt.caste_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Sub Caste */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Sub Caste{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "subCastes",
|
||||
label: "Sub Caste",
|
||||
options: subCasteOptions,
|
||||
value: data.subCastes,
|
||||
getLabel: (opt) =>
|
||||
opt.sub_caste_name || opt.subCaste_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: data.castes.length === 0 || subCasteQuery.isLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Occupation */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "occupations",
|
||||
label: "Occupation",
|
||||
options: occupationOptions,
|
||||
value: data.occupations,
|
||||
getLabel: (opt) => opt.occupation_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Qualification{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "educations",
|
||||
label: "Qualification",
|
||||
options: educationOptions,
|
||||
value: data.educations,
|
||||
getLabel: (opt) => opt.education_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Lifestyle and Hobbies */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Lifestyle & Hobbies{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "hobbies",
|
||||
label: "Lifestyle & Hobbies",
|
||||
options: hobbyOptions,
|
||||
value: data.hobbies,
|
||||
getLabel: (opt) => opt.hobby_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Annual Income */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Annual Income{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.religionCaste)}
|
||||
error={Boolean(errors.annualIncome)}
|
||||
>
|
||||
<InputLabel id="religionCaste-label">
|
||||
Select Religion / Caste Preference
|
||||
<InputLabel id="annualIncome-label">
|
||||
Select Annual Income
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="religionCaste-label"
|
||||
label="Select Religion / Caste Preference"
|
||||
name="religionCaste"
|
||||
value={data.religionCaste}
|
||||
onChange={(e) =>
|
||||
handleChange("religionCaste", e.target.value)
|
||||
}
|
||||
|
||||
labelId="annualIncome-label"
|
||||
label="Select Annual Income"
|
||||
name="annualIncome"
|
||||
value={data.annualIncome}
|
||||
onChange={(e) => handleChange("annualIncome", e.target.value)}
|
||||
disabled={isPartnerMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
||||
<MenuItem value="Hindu">Hindu</MenuItem>
|
||||
<MenuItem value="Muslim">Muslim</MenuItem>
|
||||
<MenuItem value="Christian">Christian</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
{annualIncomeOptions.map((opt) => (
|
||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
||||
{opt.annual_income_name || opt.name || opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.religionCaste && (
|
||||
{errors.annualIncome && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
@@ -111,167 +367,43 @@ const PartnerPreferencesForm = ({ onSubmitStep, onSkipStep, errors }) => {
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.religionCaste}
|
||||
{errors.annualIncome}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Occupation Preference */}
|
||||
{/* State */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation Preference
|
||||
State{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.occupationPref)}
|
||||
>
|
||||
<InputLabel id="occupationPref-label">
|
||||
Select Occupation Preference
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="occupationPref-label"
|
||||
label="Select Occupation Preference"
|
||||
name="occupationPref"
|
||||
value={data.occupationPref}
|
||||
onChange={(e) =>
|
||||
handleChange("occupationPref", e.target.value)
|
||||
}
|
||||
|
||||
>
|
||||
|
||||
<MenuItem value="IT, Business">IT, Business</MenuItem>
|
||||
<MenuItem value="Doctor">Doctor</MenuItem>
|
||||
<MenuItem value="Engineer">Engineer</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
</Select>
|
||||
{errors.occupationPref && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.occupationPref}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
{renderMultiSelect({
|
||||
name: "states",
|
||||
label: "State",
|
||||
options: stateOptions,
|
||||
value: data.states,
|
||||
getLabel: (opt) => opt.state_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Lifestyle & Habits Preference */}
|
||||
{/* City */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Lifestyle & Habits Preference
|
||||
City{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.lifestylePref)}
|
||||
>
|
||||
<InputLabel id="lifestylePref-label">
|
||||
Select Lifestyle & Habits Preference
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="lifestylePref-label"
|
||||
label="Select Lifestyle & Habits Preference"
|
||||
name="lifestylePref"
|
||||
value={data.lifestylePref}
|
||||
onChange={(e) =>
|
||||
handleChange("lifestylePref", e.target.value)
|
||||
}
|
||||
|
||||
>
|
||||
|
||||
<MenuItem value="Non-Smoking, Non-Drinking">
|
||||
Non-Smoking, Non-Drinking
|
||||
</MenuItem>
|
||||
<MenuItem value="Social Drinker">Social Drinker</MenuItem>
|
||||
<MenuItem value="Any">Any</MenuItem>
|
||||
</Select>
|
||||
{errors.lifestylePref && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.lifestylePref}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* Prefer Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Prefer Qualification
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="qualificationPref"
|
||||
label="Enter prefer Qualification"
|
||||
value={data.qualificationPref}
|
||||
onChange={(e) =>
|
||||
handleChange("qualificationPref", e.target.value)
|
||||
}
|
||||
error={Boolean(errors.qualificationPref)}
|
||||
helperText={errors.qualificationPref}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Occupation (text) */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Occupation</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="occupationText"
|
||||
label="Enter Occupation"
|
||||
value={data.occupationText}
|
||||
onChange={(e) =>
|
||||
handleChange("occupationText", e.target.value)
|
||||
}
|
||||
error={Boolean(errors.occupationText)}
|
||||
helperText={errors.occupationText}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Prefer Annual Income */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Prefer Annual Income
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="incomePref"
|
||||
label="Enter Annual Income"
|
||||
value={data.incomePref}
|
||||
onChange={(e) => handleChange("incomePref", e.target.value)}
|
||||
error={Boolean(errors.incomePref)}
|
||||
helperText={errors.incomePref}
|
||||
variant="outlined"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Prefer Location */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Prefer Location
|
||||
</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="locationPref"
|
||||
label="Enter Prefer Location"
|
||||
value={data.locationPref}
|
||||
onChange={(e) => handleChange("locationPref", e.target.value)}
|
||||
error={Boolean(errors.locationPref)}
|
||||
helperText={errors.locationPref}
|
||||
variant="outlined"
|
||||
/>
|
||||
{renderMultiSelect({
|
||||
name: "districts",
|
||||
label: "City",
|
||||
options: cityOptions,
|
||||
value: data.districts,
|
||||
getLabel: (opt) =>
|
||||
opt.district_name || opt.city_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: data.states.length === 0 || cityQuery.isLoading,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import { useSendOtp, useVerifyOtp } from "../hooks/useAuth";
|
||||
const OTP_LENGTH = 4;
|
||||
const OTP_TIMER_SEC = 120; // 2 minutes
|
||||
|
||||
const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange }) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.personalDetails);
|
||||
const nameInputRef = useRef(null);
|
||||
@@ -141,6 +141,52 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
setOtpTimer(OTP_TIMER_SEC);
|
||||
}, []);
|
||||
|
||||
const getApiErrorMessage = useCallback((error, fallback) => {
|
||||
const data = error?.response?.data ?? error?.data ?? error;
|
||||
if (!data) return fallback;
|
||||
if (typeof data === "string") return data;
|
||||
|
||||
const directMessage =
|
||||
data.message || data.error || data.detail || data.msg;
|
||||
if (directMessage) return directMessage;
|
||||
|
||||
if (Array.isArray(data.errors)) {
|
||||
const first = data.errors[0];
|
||||
if (typeof first === "string") return first;
|
||||
if (first && typeof first === "object") {
|
||||
return (
|
||||
first.message ||
|
||||
first.msg ||
|
||||
first.error ||
|
||||
first.detail ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.errors && typeof data.errors === "object") {
|
||||
const firstValue = Object.values(data.errors)[0];
|
||||
if (Array.isArray(firstValue)) {
|
||||
const joined = firstValue.filter(Boolean).join(" ");
|
||||
if (joined) return joined;
|
||||
}
|
||||
if (typeof firstValue === "string") return firstValue;
|
||||
if (firstValue && typeof firstValue === "object") {
|
||||
return (
|
||||
firstValue.message ||
|
||||
firstValue.msg ||
|
||||
firstValue.error ||
|
||||
firstValue.detail ||
|
||||
fallback
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.otp) return String(data.otp);
|
||||
if (error?.message) return error.message;
|
||||
return fallback;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (otpTimer <= 0) return;
|
||||
const timerId = setInterval(() => {
|
||||
@@ -207,18 +253,18 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
resetOtp();
|
||||
setMobileOtpVerified(false);
|
||||
} catch (error) {
|
||||
setMobileNumberError("Failed to send OTP. Please try again.");
|
||||
const message =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.otp ||
|
||||
error?.message ||
|
||||
"Failed to send OTP";
|
||||
const message = getApiErrorMessage(
|
||||
error,
|
||||
"Failed to send OTP. Please try again."
|
||||
);
|
||||
setMobileNumberError(message);
|
||||
toast.error(message, { position: "top-right" });
|
||||
}
|
||||
};
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
const updates = { [field]: value };
|
||||
const fieldsToClear = [field];
|
||||
if (field === "mobileNumber") {
|
||||
setMobileNumberError("");
|
||||
setShowOtp(false);
|
||||
@@ -230,21 +276,61 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
if (field === "religion") {
|
||||
updates.caste = "";
|
||||
updates.subCaste = "";
|
||||
fieldsToClear.push("caste", "subCaste");
|
||||
}
|
||||
if (field === "caste") {
|
||||
updates.subCaste = "";
|
||||
fieldsToClear.push("subCaste");
|
||||
}
|
||||
if (field === "state") {
|
||||
updates.city = "";
|
||||
fieldsToClear.push("city");
|
||||
}
|
||||
if (field === "raasi") {
|
||||
updates.star = "";
|
||||
fieldsToClear.push("star");
|
||||
}
|
||||
if (field === "password") {
|
||||
fieldsToClear.push("confirmPassword");
|
||||
}
|
||||
dispatch(updatePersonalDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
};
|
||||
|
||||
const isOtpComplete = otp.every((digit) => digit !== "");
|
||||
|
||||
const passwordStrength = useMemo(() => {
|
||||
const value = data.password || "";
|
||||
if (!value) return null;
|
||||
const rules = [
|
||||
{ key: "length", label: "At least 8 characters", ok: value.length >= 8 },
|
||||
{ key: "upper", label: "Uppercase letter", ok: /[A-Z]/.test(value) },
|
||||
{ key: "lower", label: "Lowercase letter", ok: /[a-z]/.test(value) },
|
||||
{ key: "number", label: "Number", ok: /\d/.test(value) },
|
||||
{ key: "symbol", label: "Symbol", ok: /[^A-Za-z0-9]/.test(value) },
|
||||
{ key: "length12", label: "12+ characters (recommended)", ok: value.length >= 12 },
|
||||
];
|
||||
const score = rules.filter((rule) => rule.ok).length;
|
||||
const percent = Math.round((score / rules.length) * 100);
|
||||
|
||||
let label = "Weak";
|
||||
let color = "#d32f2f";
|
||||
if (score >= 5) {
|
||||
label = "Strong";
|
||||
color = "#2e7d32";
|
||||
} else if (score >= 3) {
|
||||
label = "Medium";
|
||||
color = "#ed6c02";
|
||||
}
|
||||
|
||||
return {
|
||||
label,
|
||||
color,
|
||||
percent,
|
||||
rules,
|
||||
};
|
||||
}, [data.password]);
|
||||
|
||||
const handleOtpSubmit = async () => {
|
||||
if (!isOtpComplete) {
|
||||
setOtpError("Complete OTP is required");
|
||||
@@ -265,11 +351,10 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
setMobileNumberError("");
|
||||
setOtpError("");
|
||||
} catch (error) {
|
||||
const message =
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.otp ||
|
||||
error?.message ||
|
||||
"Invalid or expired OTP";
|
||||
const message = getApiErrorMessage(
|
||||
error,
|
||||
"Invalid or expired OTP"
|
||||
);
|
||||
setOtpError(message);
|
||||
toast.error(message, { position: "top-right" });
|
||||
}
|
||||
@@ -576,6 +661,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
name: "dob",
|
||||
fullWidth: true,
|
||||
error: Boolean(errors.dob),
|
||||
helperText: errors.dob,
|
||||
@@ -954,6 +1040,64 @@ const PersonalDetailsForm = ({ onSubmitStep, errors }) => {
|
||||
}}
|
||||
variant="outlined"
|
||||
/>
|
||||
{passwordStrength && (
|
||||
<div className="rounded-lg border border-gray-200 bg-white/70 p-3">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-gray-600">Password strength</span>
|
||||
<span
|
||||
style={{
|
||||
color: passwordStrength.color,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{passwordStrength.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 h-2 w-full rounded-full bg-gray-200 overflow-hidden">
|
||||
<div
|
||||
style={{
|
||||
width: `${passwordStrength.percent}%`,
|
||||
height: "100%",
|
||||
background: passwordStrength.color,
|
||||
transition: "width 200ms ease",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 text-xs text-gray-500">
|
||||
Recommended: 12+ characters with a mix of upper/lowercase,
|
||||
numbers, and symbols.
|
||||
</div>
|
||||
<div className="mt-2 grid grid-cols-1 sm:grid-cols-2 gap-y-1 gap-x-3 text-xs">
|
||||
{passwordStrength.rules.map((rule) => (
|
||||
<div
|
||||
key={rule.key}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{rule.ok ? (
|
||||
<CheckCircleIcon sx={{ fontSize: 14, color: "#2e7d32" }} />
|
||||
) : (
|
||||
<span
|
||||
style={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: "999px",
|
||||
border: "1px solid #d1d5db",
|
||||
display: "inline-block",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<span
|
||||
style={{
|
||||
color: rule.ok ? "#111827" : "#6b7280",
|
||||
}}
|
||||
>
|
||||
{rule.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Confirm Password */}
|
||||
|
||||
+162
-82
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Edit2,Info } from 'lucide-react';
|
||||
import {
|
||||
@@ -12,44 +12,154 @@ import {
|
||||
Button,
|
||||
Grid,
|
||||
} from '@mui/material';
|
||||
import { usePreviewDetails } from "../hooks/usePreview";
|
||||
|
||||
const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
const formData = useSelector((state) => state.registerform);
|
||||
const { data: previewData, isLoading, isError } = usePreviewDetails();
|
||||
|
||||
const sections = [
|
||||
{
|
||||
title: 'Personal Details',
|
||||
step: 1,
|
||||
data: formData.personalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Educational & Professional Details',
|
||||
step: 2,
|
||||
data: formData.educationalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Family Details',
|
||||
step: 3,
|
||||
data: formData.familyDetails,
|
||||
},
|
||||
{
|
||||
title: 'Lifestyle & Habits',
|
||||
step: 4,
|
||||
data: formData.lifestyleDetails,
|
||||
},
|
||||
{
|
||||
title: 'Partner Preferences',
|
||||
step: 5,
|
||||
data: formData.partnerPreferences,
|
||||
},
|
||||
];
|
||||
const sections = previewData?.personal_details
|
||||
? [
|
||||
{
|
||||
title: "Personal Details",
|
||||
step: 1,
|
||||
data: previewData.personal_details,
|
||||
},
|
||||
{
|
||||
title: "Educational & Professional Details",
|
||||
step: 2,
|
||||
data: previewData.educational_details,
|
||||
},
|
||||
{
|
||||
title: "Family Details",
|
||||
step: 3,
|
||||
data: previewData.family_details,
|
||||
},
|
||||
{
|
||||
title: "Lifestyle & Habits",
|
||||
step: 4,
|
||||
data: previewData.lifestyle_details,
|
||||
},
|
||||
{
|
||||
title: "Partner Preferences",
|
||||
step: 5,
|
||||
data: previewData.partner_preferences,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'Personal Details',
|
||||
step: 1,
|
||||
data: formData.personalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Educational & Professional Details',
|
||||
step: 2,
|
||||
data: formData.educationalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Family Details',
|
||||
step: 3,
|
||||
data: formData.familyDetails,
|
||||
},
|
||||
{
|
||||
title: 'Lifestyle & Habits',
|
||||
step: 4,
|
||||
data: formData.lifestyleDetails,
|
||||
},
|
||||
{
|
||||
title: 'Partner Preferences',
|
||||
step: 5,
|
||||
data: formData.partnerPreferences,
|
||||
},
|
||||
];
|
||||
|
||||
const renderValue = (key, value) => {
|
||||
if (key === "profiles" || key === "profile") {
|
||||
const list = Array.isArray(value) ? value : [];
|
||||
return (
|
||||
<Box display="flex" gap={0} flexWrap="wrap">
|
||||
{list.length > 0 ? (
|
||||
list.map((imgObj, index) => {
|
||||
const src =
|
||||
typeof imgObj === "string"
|
||||
? imgObj
|
||||
: imgObj?.preview ||
|
||||
imgObj?.url ||
|
||||
(imgObj?.file ? URL.createObjectURL(imgObj.file) : null);
|
||||
if (!src) return null;
|
||||
return (
|
||||
<img
|
||||
key={index}
|
||||
src={src}
|
||||
alt="Profile"
|
||||
style={{
|
||||
width: "90px",
|
||||
height: "90px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "50%",
|
||||
border: "1px solid #ccc",
|
||||
marginLeft: "-20px",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Box display="flex" alignItems="center" gap={1} sx={{ color: "#888" }}>
|
||||
<Info size={16} />
|
||||
<Typography>No photos uploaded</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return "-";
|
||||
if (typeof value[0] === "object") {
|
||||
return value.map((item, idx) => (
|
||||
<div key={idx}>{JSON.stringify(item)}</div>
|
||||
));
|
||||
}
|
||||
return value.join(", ");
|
||||
}
|
||||
|
||||
if (value && typeof value === "object") {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return value || "-";
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={3} sx={{maxWidth:"1400px", width:"100%"}} mx="auto" >
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 my-10'>
|
||||
|
||||
{sections.map((section) => (
|
||||
{isLoading && (
|
||||
<Box
|
||||
py={4}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
sx={{ color: "#666" }}
|
||||
>
|
||||
Loading preview...
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<Box
|
||||
py={4}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
sx={{ color: "#d32f2f" }}
|
||||
>
|
||||
Failed to load preview.
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
sections.map((section) => (
|
||||
|
||||
<Card key={section.title} variant="outlined" sx={{ borderRadius: 2, background:"#fff5ed" }}>
|
||||
<CardHeader
|
||||
@@ -72,7 +182,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
/>
|
||||
<Divider />
|
||||
<CardContent sx={{ pt: 1, }}>
|
||||
{Object.entries(section.data).map(([key, value]) => {
|
||||
{Object.entries(section.data || {}).map(([key, value]) => {
|
||||
// if (value && key !== 'profiles') {
|
||||
|
||||
|
||||
@@ -82,9 +192,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
||||
.trim();
|
||||
const isImage =
|
||||
typeof value === "string" &&
|
||||
(value.startsWith("http") || value.startsWith("data:image"));
|
||||
const content = renderValue(key, value);
|
||||
return (
|
||||
|
||||
<Box
|
||||
@@ -106,56 +214,28 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
</Typography>
|
||||
|
||||
{/* Special Case: Profiles Image Preview */}
|
||||
{key === "profiles" ? (
|
||||
<Box display="flex" gap={0} flexWrap="wrap">
|
||||
{value && value.length > 0 ? (
|
||||
value.map((imgObj, index) => (
|
||||
<img
|
||||
key={index}
|
||||
src={URL.createObjectURL(imgObj.file)}
|
||||
alt="Profile"
|
||||
style={{
|
||||
width: "90px",
|
||||
height: "90px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "50%",
|
||||
border: "1px solid #ccc",
|
||||
marginLeft:"-20px"
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<Box display="flex" alignItems="center" gap={1} sx={{ color: "#888" }}>
|
||||
<Info size={16} />
|
||||
<Typography>No photos uploaded</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
{key === "profiles" || key === "profile" ? (
|
||||
content
|
||||
) : value ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
wordBreak: "break-word",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</Typography>
|
||||
) : (
|
||||
// Text OR No Data
|
||||
<>
|
||||
{value ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
wordBreak: "break-word",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</Typography>
|
||||
) : (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
sx={{ color: "#888", fontStyle: "italic" }}
|
||||
>
|
||||
<Info size={16} />
|
||||
<Typography>No data available</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</>
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
sx={{ color: "#888", fontStyle: "italic" }}
|
||||
>
|
||||
<Info size={16} />
|
||||
<Typography>No data available</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
@@ -234,8 +234,10 @@ const ProfilePreviewPage = () => {
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Qualification", educationalDetails.qualification, 2)}
|
||||
{renderField("Field of Study", educationalDetails.fieldOfStudy, 2)}
|
||||
{renderField("College Name", educationalDetails.collegeName, 2)}
|
||||
{renderField("Occupation", educationalDetails.occupation, 2)}
|
||||
{renderField("Organization", educationalDetails.organization, 2)}
|
||||
{renderField("Employee Type", educationalDetails.employeeType, 2)}
|
||||
{renderField("Income", educationalDetails.income, 2)}
|
||||
{renderField("Work Location", educationalDetails.workLocation, 2)}
|
||||
</CardContent>
|
||||
@@ -269,8 +271,10 @@ const ProfilePreviewPage = () => {
|
||||
{renderField("Father Occupation", familyDetails.fatherOccupation)}
|
||||
{renderField("Mother Name", familyDetails.motherName)}
|
||||
{renderField("Mother Occupation", familyDetails.motherOccupation)}
|
||||
{renderField("Number of Siblings", familyDetails.siblings)}
|
||||
{renderField("Siblings Marital status", familyDetails.siblingsStatus)}
|
||||
{renderField("Brother Count", familyDetails.brotherCount)}
|
||||
{renderField("Sister Count", familyDetails.sisterCount)}
|
||||
{renderField("Family Status", familyDetails.familyStatus)}
|
||||
{renderField("Native Place", familyDetails.nativePlace)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -298,11 +302,21 @@ const ProfilePreviewPage = () => {
|
||||
<Divider />
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Diet", lifestyleDetails.diet)}
|
||||
{/* {renderField("Drinking", lifestyleDetails.drinking)}
|
||||
{renderField("Smoking", lifestyleDetails.smoking)} */}
|
||||
{renderField("Hobbies", lifestyleDetails.hobbies)}
|
||||
|
||||
{renderField(
|
||||
"Diet",
|
||||
Array.isArray(lifestyleDetails.diets)
|
||||
? lifestyleDetails.diets.join(", ")
|
||||
: lifestyleDetails.diets
|
||||
)}
|
||||
{renderField(
|
||||
"Hobbies",
|
||||
Array.isArray(lifestyleDetails.hobbies)
|
||||
? lifestyleDetails.hobbies.join(", ")
|
||||
: lifestyleDetails.hobbies
|
||||
)}
|
||||
{renderField("Date of Birth", lifestyleDetails.dob)}
|
||||
{renderField("Time of Birth", lifestyleDetails.tob)}
|
||||
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -331,14 +345,50 @@ const ProfilePreviewPage = () => {
|
||||
<Divider />
|
||||
|
||||
<CardContent sx={{ pt: 1 }}>
|
||||
{renderField("Age", partnerPreferences.ageRange)}
|
||||
{renderField("Religion", partnerPreferences.religionCaste)}
|
||||
{renderField("Occupation", partnerPreferences.occupationPref)}
|
||||
{renderField("Life style", partnerPreferences.lifestylePref)}
|
||||
{renderField("Qualification", partnerPreferences.qualificationPref)}
|
||||
{renderField("Occupation", partnerPreferences.occupationText)}
|
||||
{renderField("Income", partnerPreferences.incomePref)}
|
||||
{renderField("Location", partnerPreferences.locationPref)}
|
||||
{renderField("Age Range", partnerPreferences.ageRange)}
|
||||
{renderField(
|
||||
"Caste",
|
||||
Array.isArray(partnerPreferences.castes)
|
||||
? partnerPreferences.castes.join(", ")
|
||||
: partnerPreferences.castes
|
||||
)}
|
||||
{renderField(
|
||||
"Sub Caste",
|
||||
Array.isArray(partnerPreferences.subCastes)
|
||||
? partnerPreferences.subCastes.join(", ")
|
||||
: partnerPreferences.subCastes
|
||||
)}
|
||||
{renderField(
|
||||
"Occupation",
|
||||
Array.isArray(partnerPreferences.occupations)
|
||||
? partnerPreferences.occupations.join(", ")
|
||||
: partnerPreferences.occupations
|
||||
)}
|
||||
{renderField(
|
||||
"Qualification",
|
||||
Array.isArray(partnerPreferences.educations)
|
||||
? partnerPreferences.educations.join(", ")
|
||||
: partnerPreferences.educations
|
||||
)}
|
||||
{renderField(
|
||||
"Lifestyle & Hobbies",
|
||||
Array.isArray(partnerPreferences.hobbies)
|
||||
? partnerPreferences.hobbies.join(", ")
|
||||
: partnerPreferences.hobbies
|
||||
)}
|
||||
{renderField("Annual Income", partnerPreferences.annualIncome)}
|
||||
{renderField(
|
||||
"State",
|
||||
Array.isArray(partnerPreferences.states)
|
||||
? partnerPreferences.states.join(", ")
|
||||
: partnerPreferences.states
|
||||
)}
|
||||
{renderField(
|
||||
"City",
|
||||
Array.isArray(partnerPreferences.districts)
|
||||
? partnerPreferences.districts.join(", ")
|
||||
: partnerPreferences.districts
|
||||
)}
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
+420
-23
@@ -1,4 +1,4 @@
|
||||
import React, { useState,useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { ChevronLeft } from "lucide-react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {
|
||||
@@ -16,7 +16,105 @@ import LifestyleDetailsForm from "./LifestyleDetailsForm";
|
||||
import PartnerPreferencesForm from "./PartnerPreferencesForm";
|
||||
import PreviewScreen from "./PreviewScreen";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useRegisterStep1 } from "../hooks/useRegister";
|
||||
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",
|
||||
};
|
||||
const Stepper = ({ currentStep, onStepClick }) => {
|
||||
|
||||
|
||||
@@ -84,6 +182,136 @@ const StepperForm = () => {
|
||||
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;
|
||||
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(() => {
|
||||
@@ -154,8 +382,8 @@ const StepperForm = () => {
|
||||
"fieldOfStudy",
|
||||
"occupation",
|
||||
"organization",
|
||||
"employeeType",
|
||||
"income",
|
||||
"workLocation",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!educationalDetails[field]) {
|
||||
@@ -165,12 +393,8 @@ const StepperForm = () => {
|
||||
} else if (step === 3) {
|
||||
const required = [
|
||||
"fatherName",
|
||||
"fatherOccupation",
|
||||
"motherName",
|
||||
"motherOccupation",
|
||||
"siblings",
|
||||
"siblingsStatus",
|
||||
|
||||
"familyStatus",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!familyDetails[field]) {
|
||||
@@ -178,25 +402,36 @@ const StepperForm = () => {
|
||||
}
|
||||
});
|
||||
} else if (step === 4) {
|
||||
const required = ["diet", "drinking", "smoking", "hobbies"];
|
||||
const required = ["diets", "hobbies", "dob", "tob"];
|
||||
required.forEach((field) => {
|
||||
if (!lifestyleDetails[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",
|
||||
"religionCaste",
|
||||
"occupationPref",
|
||||
"lifestylePref",
|
||||
"qualificationPref",
|
||||
"occupationText",
|
||||
"incomePref",
|
||||
"locationPref",
|
||||
"castes",
|
||||
"subCastes",
|
||||
"occupations",
|
||||
"educations",
|
||||
"hobbies",
|
||||
"annualIncome",
|
||||
"states",
|
||||
"districts",
|
||||
];
|
||||
required.forEach((field) => {
|
||||
if (!partnerPreferences[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";
|
||||
}
|
||||
});
|
||||
@@ -204,11 +439,8 @@ const StepperForm = () => {
|
||||
|
||||
setErrors(newErrors);
|
||||
|
||||
// Autofocus first invalid field
|
||||
if (Object.keys(newErrors).length > 0) {
|
||||
const firstKey = Object.keys(newErrors)[0];
|
||||
const el = document.querySelector(`[name="${firstKey}"]`);
|
||||
if (el) el.focus();
|
||||
focusFirstError(newErrors, STEP_FIELD_ORDER[step] || []);
|
||||
}
|
||||
|
||||
return Object.keys(newErrors).length === 0;
|
||||
@@ -237,6 +469,132 @@ const StepperForm = () => {
|
||||
fcm_token: localStorage.getItem("fcm_token") || "",
|
||||
});
|
||||
|
||||
const buildRegisterStep2Payload = () => ({
|
||||
college_name: educationalDetails.collegeName || "",
|
||||
study_field: educationalDetails.fieldOfStudy,
|
||||
education: educationalDetails.qualification,
|
||||
occupation: educationalDetails.occupation,
|
||||
company_name: educationalDetails.organization || "",
|
||||
employee_type: educationalDetails.employeeType,
|
||||
annual_income: educationalDetails.income,
|
||||
work_location: educationalDetails.workLocation || "",
|
||||
});
|
||||
|
||||
const buildRegisterStep3Payload = () => {
|
||||
const payload = {
|
||||
father_name: familyDetails.fatherName,
|
||||
father_occupation: familyDetails.fatherOccupation || "",
|
||||
mother_name: familyDetails.motherName,
|
||||
mother_occupation: familyDetails.motherOccupation || "",
|
||||
family_status: familyDetails.familyStatus,
|
||||
native_place: familyDetails.nativePlace || "",
|
||||
brother_count: familyDetails.brotherCount || 0,
|
||||
sister_count: familyDetails.sisterCount || 0,
|
||||
};
|
||||
|
||||
(familyDetails.brothers || []).forEach((brother, index) => {
|
||||
payload[`brothers[${index}][name]`] = brother?.name || "";
|
||||
payload[`brothers[${index}][occupation]`] = brother?.occupation || "";
|
||||
payload[`brothers[${index}][marital_status]`] =
|
||||
brother?.maritalStatus || "";
|
||||
payload[`brothers[${index}][have_childrens]`] =
|
||||
brother?.haveChildrens ?? "";
|
||||
});
|
||||
|
||||
(familyDetails.sisters || []).forEach((sister, index) => {
|
||||
payload[`sisters[${index}][name]`] = sister?.name || "";
|
||||
payload[`sisters[${index}][occupation]`] = sister?.occupation || "";
|
||||
payload[`sisters[${index}][marital_status]`] =
|
||||
sister?.maritalStatus || "";
|
||||
payload[`sisters[${index}][have_childrens]`] =
|
||||
sister?.haveChildrens ?? "";
|
||||
});
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const buildRegisterStep4Payload = () => {
|
||||
const payload = {
|
||||
dob: lifestyleDetails.dob,
|
||||
tob: lifestyleDetails.tob,
|
||||
};
|
||||
|
||||
(lifestyleDetails.diets || []).forEach((id, index) => {
|
||||
payload[`diets[${index}]`] = id;
|
||||
});
|
||||
|
||||
(lifestyleDetails.hobbies || []).forEach((id, index) => {
|
||||
payload[`hobbies[${index}]`] = id;
|
||||
});
|
||||
|
||||
const graha = lifestyleDetails.graha || {};
|
||||
Object.keys(graha).forEach((house) => {
|
||||
const values = graha[house] || [];
|
||||
values.forEach((value, index) => {
|
||||
payload[`graha_${house}[${index}]`] = value;
|
||||
});
|
||||
});
|
||||
|
||||
const amsam = lifestyleDetails.amsam || {};
|
||||
Object.keys(amsam).forEach((house) => {
|
||||
const values = amsam[house] || [];
|
||||
values.forEach((value, index) => {
|
||||
payload[`amsam_${house}[${index}]`] = value;
|
||||
});
|
||||
});
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
const buildRegisterStep5Payload = () => {
|
||||
const payload = {
|
||||
age_range: partnerPreferences.ageRange,
|
||||
annual_income: partnerPreferences.annualIncome,
|
||||
};
|
||||
|
||||
(partnerPreferences.castes || []).forEach((id, index) => {
|
||||
payload[`castes[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.subCastes || []).forEach((id, index) => {
|
||||
payload[`sub_castes[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.occupations || []).forEach((id, index) => {
|
||||
payload[`occupations[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.educations || []).forEach((id, index) => {
|
||||
payload[`educations[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.hobbies || []).forEach((id, index) => {
|
||||
payload[`hobbies[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.states || []).forEach((id, index) => {
|
||||
payload[`states[${index}]`] = id;
|
||||
});
|
||||
|
||||
(partnerPreferences.districts || []).forEach((id, index) => {
|
||||
payload[`districts[${index}]`] = id;
|
||||
});
|
||||
|
||||
return payload;
|
||||
};
|
||||
|
||||
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;
|
||||
@@ -244,11 +602,45 @@ const StepperForm = () => {
|
||||
try {
|
||||
if (currentStep === 1) {
|
||||
const payload = buildRegisterStep1Payload();
|
||||
await registerStep1.mutateAsync(payload);
|
||||
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) {
|
||||
if (currentStep === 1) {
|
||||
const serverErrors = mapServerErrors(e);
|
||||
if (Object.keys(serverErrors).length > 0) {
|
||||
const hasFieldErrors = Object.keys(serverErrors).some(
|
||||
(key) => key !== "_form"
|
||||
);
|
||||
if (hasFieldErrors) {
|
||||
setErrors(serverErrors);
|
||||
focusFirstError(serverErrors, STEP_FIELD_ORDER[1]);
|
||||
return;
|
||||
}
|
||||
if (serverErrors._form) {
|
||||
toast.error(serverErrors._form, { position: "top-right" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
alert("Failed to submit step. Please try again.");
|
||||
}
|
||||
};
|
||||
@@ -296,6 +688,7 @@ const StepperForm = () => {
|
||||
<PersonalDetailsForm
|
||||
onSubmitStep={handleStepSubmit}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
@@ -304,6 +697,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
@@ -312,6 +706,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 4:
|
||||
@@ -320,6 +715,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 5:
|
||||
@@ -328,6 +724,7 @@ const StepperForm = () => {
|
||||
onSubmitStep={handleStepSubmit}
|
||||
onSkipStep={handleSkip}
|
||||
errors={errors}
|
||||
onFieldChange={clearFieldErrors}
|
||||
/>
|
||||
);
|
||||
case 6:
|
||||
|
||||
Reference in new issue
Block a user