form update

This commit is contained in:
Meenadeveloper 2026-03-07 17:09:56 +05:30
parent 135f6bba48
commit 8f6ddbcb2c
8 changed files with 509 additions and 167 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

View File

@ -447,11 +447,13 @@ const EducationalDetailsForm = ({
justifyContent: "center", justifyContent: "center",
}} }}
> >
<Button variant="outlined" onClick={onSkipStep}> {onSkipStep && (
Next <Button variant="outlined" onClick={onSkipStep}>
</Button> Skip
</Button>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}> <Button variant="contained" color="primary" onClick={handleSubmit}>
Submit {onSkipStep ? "Next" : "Update"}
</Button> </Button>
</Grid> </Grid>
</form> </form>

View File

@ -415,11 +415,13 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange })
xs={12} xs={12}
sx={{ marginTop: 10, display: "flex", gap: 4, justifyContent: "center" }} sx={{ marginTop: 10, display: "flex", gap: 4, justifyContent: "center" }}
> >
<Button variant="outlined" onClick={onSkipStep}> {onSkipStep && (
Skip <Button variant="outlined" onClick={onSkipStep}>
</Button> Skip
</Button>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}> <Button variant="contained" color="primary" onClick={handleSubmit}>
Submit {onSkipStep ? "Next" : "Update"}
</Button> </Button>
</Grid> </Grid>
</form> </form>

View File

@ -186,7 +186,7 @@ const LifestyleDetailsForm = ({
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]"> <label className="text-gray-900 text-[15px]">
Diet (Multi-select){requiredMark} Diet{requiredMark}
</label> </label>
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}> <FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
<InputLabel id="diets-label">Select Diet</InputLabel> <InputLabel id="diets-label">Select Diet</InputLabel>
@ -194,21 +194,10 @@ const LifestyleDetailsForm = ({
labelId="diets-label" labelId="diets-label"
label="Select Diet" label="Select Diet"
name="diets" name="diets"
multiple
value={data.diets} value={data.diets}
onChange={(e) => handleMultiChange("diets", e.target.value)} onChange={(e) => handleChange("diets", e.target.value)}
inputRef={inputRef} inputRef={inputRef}
disabled={isLifestyleMastersLoading} 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={{ sx={{
"& .MuiSelect-select.Mui-disabled": { "& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed", cursor: "not-allowed",
@ -220,8 +209,7 @@ const LifestyleDetailsForm = ({
const label = opt.diet_name || opt.name || String(opt); const label = opt.diet_name || opt.name || String(opt);
return ( return (
<MenuItem key={value} value={value}> <MenuItem key={value} value={value}>
<Checkbox checked={data.diets.indexOf(value) > -1} /> {label}
<ListItemText primary={label} />
</MenuItem> </MenuItem>
); );
})} })}
@ -406,11 +394,13 @@ const LifestyleDetailsForm = ({
justifyContent: "center", justifyContent: "center",
}} }}
> >
<Button variant="outlined" onClick={onSkipStep}> {onSkipStep && (
Skip <Button variant="outlined" onClick={onSkipStep}>
</Button> Skip
</Button>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}> <Button variant="contained" color="primary" onClick={handleSubmit}>
Next {onSkipStep ? "Next" : "Update"}
</Button> </Button>
</Grid> </Grid>
</form> </form>

View File

@ -417,11 +417,13 @@ const PartnerPreferencesForm = ({
justifyContent: "center", justifyContent: "center",
}} }}
> >
<Button variant="outlined" onClick={onSkipStep}> {onSkipStep && (
Skip <Button variant="outlined" onClick={onSkipStep}>
</Button> Skip
</Button>
)}
<Button variant="contained" color="primary" onClick={handleSubmit}> <Button variant="contained" color="primary" onClick={handleSubmit}>
Next {onSkipStep ? "Next" : "Update"}
</Button> </Button>
</Grid> </Grid>
</form> </form>

View File

@ -1290,7 +1290,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
onClick={handleSubmit} onClick={handleSubmit}
// disabled={!mobileOtpVerified} // disabled={!mobileOtpVerified}
> >
Submit Next
</Button> </Button>
</Grid> </Grid>
</form> </form>

View File

@ -2,7 +2,6 @@ import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Autoplay } from "swiper/modules"; import { Navigation, Autoplay } from "swiper/modules";
import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react"; import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react";
import { useSelector, useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import "swiper/css"; import "swiper/css";
import "swiper/css/navigation"; import "swiper/css/navigation";
@ -13,9 +12,8 @@ import weddingpromo2 from "../assets/images/weddingpromo2.jpg";
import weddingpromo3 from "../assets/images/weddingpromo3.jpg"; import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
import weddingpromo4 from "../assets/images/weddingpromo4.jpg"; import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
import horoscopeImg from "../assets/images/horoscopeimg.png";
import ProfileCompletion from "../components/profiledashboard/ProfileCompletion"; import ProfileCompletion from "../components/profiledashboard/ProfileCompletion";
import { preloadDummyProfile } from "../redux/registrationFormSlice";
import { useEffect } from "react";
import { import {
Box, Box,
Button, Button,
@ -25,8 +23,11 @@ import {
Divider, Divider,
IconButton, IconButton,
Typography, Typography,
Grid,
Tooltip,
} from "@mui/material"; } from "@mui/material";
import { Info } from "@mui/icons-material"; import { Info } from "@mui/icons-material";
import { usePreviewDetails } from "../hooks/usePreview";
const images = [ const images = [
weddingpromo1, // bride in saree weddingpromo1, // bride in saree
@ -42,20 +43,81 @@ const images = [
]; ];
const ProfilePreviewPage = () => { const ProfilePreviewPage = () => {
const dispatch = useDispatch();
const navigate = useNavigate(); const navigate = useNavigate();
const { const { data, isLoading } = usePreviewDetails();
personalDetails,
educationalDetails,
familyDetails,
lifestyleDetails,
partnerPreferences,
} = useSelector((state) => state.registerform);
// For dummy data on first visit const personalDetails = data?.personal_details
useEffect(() => { ? {
dispatch(preloadDummyProfile()); name: data.personal_details.name,
}, [dispatch]); mobileNumber: data.personal_details.mobile,
gender: data.personal_details.gender,
dob: data.personal_details.dob
? data.personal_details.dob.split("T")[0]
: "",
height: data.personal_details.height,
weight: data.personal_details.weight,
maritalStatus: data.personal_details.marital_status,
religion: data.personal_details.religion,
caste: data.personal_details.caste,
email: data.personal_details.email,
state: data.personal_details.state,
city: data.personal_details.district,
pincode: data.personal_details.pincode,
profiles:
data.personal_details.images?.map((url) => ({ preview: url })) || [],
}
: {};
const educationalDetails = data?.educational_details
? {
qualification: data.educational_details.education,
fieldOfStudy: data.educational_details.study_field,
collegeName: data.educational_details.college_name,
occupation: data.educational_details.occupation,
organization: data.educational_details.company_name,
employeeType: data.educational_details.employee_type,
income: data.educational_details.annual_income,
workLocation: data.educational_details.work_location,
}
: {};
const familyDetails = data?.family_details
? {
fatherName: data.family_details.father_name,
fatherOccupation: data.family_details.father_occupation,
motherName: data.family_details.mother_name,
motherOccupation: data.family_details.mother_occupation,
brotherCount: data.family_details.brother_count,
sisterCount: data.family_details.sister_count,
familyStatus: data.family_details.family_status,
nativePlace: data.family_details.native_place,
}
: {};
const lifestyleDetails = data?.lifestyle_details
? {
diets: data.lifestyle_details.diet,
hobbies: data.lifestyle_details.hobbies,
dob: data.lifestyle_details.date_of_birth_formated,
tob: data.lifestyle_details.time_of_birth_formated,
placeOfBirth: data.lifestyle_details.place_of_birth,
horoscope: data.lifestyle_details.horoscope,
}
: {};
const partnerPreferences = data?.partner_preferences
? {
ageRange: data.partner_preferences.preferred_age_range,
castes: data.partner_preferences.preferred_castes,
subCastes: data.partner_preferences.preferred_sub_castes,
occupations: data.partner_preferences.preferred_occupations,
educations: data.partner_preferences.preferred_educations,
hobbies: data.partner_preferences.preferred_hobbies,
annualIncome: data.partner_preferences.preferred_annual_income,
states: data.partner_preferences.preferred_states,
districts: data.partner_preferences.preferred_districts,
}
: {};
const handleEditSection = (stepNum) => { const handleEditSection = (stepNum) => {
navigate("/profile-edit", { state: { step: stepNum } }); navigate("/profile-edit", { state: { step: stepNum } });
@ -64,7 +126,7 @@ const ProfilePreviewPage = () => {
const renderField = (label, value, stepNum = 1) => ( const renderField = (label, value, stepNum = 1) => (
<Box <Box
py={0.7} py={0.7}
borderBottom="1px solid #e0e0e0" // borderBottom="1px solid #e0e0e0"
sx={{ sx={{
display: "grid", display: "grid",
gridTemplateColumns: { gridTemplateColumns: {
@ -102,8 +164,119 @@ const ProfilePreviewPage = () => {
</Box> </Box>
); );
const renderChartGrid = (getDataForCell, title) => {
const renderCell = (i) => {
const items = getDataForCell(i);
const label = Array.isArray(items) ? items.join(", ") : "";
return (
<Tooltip
key={i}
title={label}
arrow
placement="top"
disableHoverListener={!items || items.length === 0}
>
<Box
sx={{
border: "1px solid #ccc",
bgcolor: "#fff",
height: "60px",
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
fontSize: "0.65rem",
fontWeight: "bold",
overflow: "hidden",
p: 0.5,
wordBreak: "break-word",
lineHeight: 1.1,
cursor: items && items.length > 0 ? "help" : "default",
}}
>
{items && items.length > 2 ? (
<Box>
{items.slice(0, 2).join(", ")}
<Box
component="span"
sx={{ color: "primary.main", display: "block", fontSize: "0.6rem" }}
>
+{items.length - 2}
</Box>
</Box>
) : (
label
)}
</Box>
</Tooltip>
);
};
return (
<Box>
<Typography
variant="subtitle2"
align="center"
gutterBottom
sx={{ fontWeight: 600 }}
>
{title}
</Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: 0.5,
maxWidth: "300px",
mx: "auto",
p: 1,
bgcolor: "#fff3e0",
borderRadius: 2,
}}
>
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
{renderCell(5)}
<Box
sx={{
gridColumn: "span 2",
gridRow: "span 2",
bgcolor: "#fff",
border: "1px solid #eee",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: "0.7rem",
fontWeight: "bold",
color: "#aaa",
}}
>
<Box
component="img"
src={horoscopeImg}
alt={title}
sx={{
width: "70%",
height: "70%",
objectFit: "cover",
animation: "spin 20s linear infinite",
"@keyframes spin": {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" },
},
}}
/>
</Box>
{renderCell(6)}
{renderCell(7)} {renderCell(8)}
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
</Box>
</Box>
);
};
const renderPersonalSection = () => ( const renderPersonalSection = () => (
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}> <Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
<CardHeader <CardHeader
title={ title={
<Typography variant="h6" fontWeight="bold"> <Typography variant="h6" fontWeight="bold">
@ -120,9 +293,9 @@ const ProfilePreviewPage = () => {
<Edit2 size={20} /> <Edit2 size={20} />
</IconButton> </IconButton>
} }
sx={{ padding: "15px 15px", background: "#ffff" }} sx={{ padding: "15px 15px", background: "#f2f2f2" }}
/> />
<Divider /> {/* <Divider /> */}
<CardContent sx={{ pt: 1 }}> <CardContent sx={{ pt: 1 }}>
{renderField("Name", personalDetails.name, 1)} {renderField("Name", personalDetails.name, 1)}
@ -142,7 +315,7 @@ const ProfilePreviewPage = () => {
{/* Profile Images */} {/* Profile Images */}
<Box <Box
py={0.7} py={0.7}
borderBottom="1px solid #e0e0e0" // borderBottom="1px solid #e0e0e0"
sx={{ sx={{
display: "grid", display: "grid",
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" }, gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
@ -210,7 +383,7 @@ const ProfilePreviewPage = () => {
); );
const renderEducationalSection = () => ( const renderEducationalSection = () => (
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}> <Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
<CardHeader <CardHeader
title={ title={
<Typography variant="h6" fontWeight="bold"> <Typography variant="h6" fontWeight="bold">
@ -227,9 +400,9 @@ const ProfilePreviewPage = () => {
<Edit2 size={20} /> <Edit2 size={20} />
</IconButton> </IconButton>
} }
sx={{ padding: "15px 15px", background: "#f5fbff" }} sx={{ padding: "15px 15px", background: "#f2f2f2" }}
/> />
<Divider /> {/* <Divider /> */}
<CardContent sx={{ pt: 1 }}> <CardContent sx={{ pt: 1 }}>
{renderField("Qualification", educationalDetails.qualification, 2)} {renderField("Qualification", educationalDetails.qualification, 2)}
@ -245,7 +418,7 @@ const ProfilePreviewPage = () => {
); );
const renderFamilySection = () => ( const renderFamilySection = () => (
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}> <Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
<CardHeader <CardHeader
title={ title={
<Typography variant="h6" fontWeight="bold"> <Typography variant="h6" fontWeight="bold">
@ -256,15 +429,15 @@ const ProfilePreviewPage = () => {
<IconButton <IconButton
aria-label="edit" aria-label="edit"
color="primary" color="primary"
onClick={() => handleEditSection(2)} onClick={() => handleEditSection(3)}
size="large" size="large"
> >
<Edit2 size={20} /> <Edit2 size={20} />
</IconButton> </IconButton>
} }
sx={{ padding: "15px 15px", background: "#f5fbff" }} sx={{ padding: "15px 15px", background: "#f2f2f2" }}
/> />
<Divider /> {/* <Divider /> */}
<CardContent sx={{ pt: 1 }}> <CardContent sx={{ pt: 1 }}>
{renderField("Father Name", familyDetails.fatherName)} {renderField("Father Name", familyDetails.fatherName)}
@ -280,7 +453,7 @@ const ProfilePreviewPage = () => {
); );
const renderLifestyleSection = () => ( const renderLifestyleSection = () => (
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}> <Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
<CardHeader <CardHeader
title={ title={
<Typography variant="h6" fontWeight="bold"> <Typography variant="h6" fontWeight="bold">
@ -291,15 +464,15 @@ const ProfilePreviewPage = () => {
<IconButton <IconButton
aria-label="edit" aria-label="edit"
color="primary" color="primary"
onClick={() => handleEditSection(2)} onClick={() => handleEditSection(4)}
size="large" size="large"
> >
<Edit2 size={20} /> <Edit2 size={20} />
</IconButton> </IconButton>
} }
sx={{ padding: "15px 15px", background: "#f5fbff" }} sx={{ padding: "15px 15px", background: "#f2f2f2" }}
/> />
<Divider /> {/* <Divider /> */}
<CardContent sx={{ pt: 1 }}> <CardContent sx={{ pt: 1 }}>
{renderField( {renderField(
@ -317,32 +490,61 @@ const ProfilePreviewPage = () => {
{renderField("Date of Birth", lifestyleDetails.dob)} {renderField("Date of Birth", lifestyleDetails.dob)}
{renderField("Time of Birth", lifestyleDetails.tob)} {renderField("Time of Birth", lifestyleDetails.tob)}
{renderField("Place of Birth", lifestyleDetails.placeOfBirth)} {renderField("Place of Birth", lifestyleDetails.placeOfBirth)}
{lifestyleDetails.horoscope && (
<Box
sx={{
width: "100%",
mt: 2,
mb: 2,
borderTop: "1px solid #e0e0e0",
pt: 2,
}}
>
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
Horoscope Details
</Typography>
<Grid container spacing={4} justifyContent="center">
<Grid item xs={12} md={6}>
{renderChartGrid((i) => {
const val = lifestyleDetails.horoscope[`graha_${i}`];
return val ? val.split(",") : [];
}, "Rasi")}
</Grid>
<Grid item xs={12} md={6}>
{renderChartGrid((i) => {
const val = lifestyleDetails.horoscope[`amsam_${i}`];
return val ? val.split(",") : [];
}, "Navamsam")}
</Grid>
</Grid>
</Box>
)}
</CardContent> </CardContent>
</Card> </Card>
); );
const renderPreferenceSection = () => ( const renderPreferenceSection = () => (
<Card variant="outlined" sx={{ borderRadius: 2, background: "#fff5ed" }}> <Card variant="outlined" sx={{ borderRadius: 2, background: "#ffff" }}>
<CardHeader <CardHeader
title={ title={
<Typography variant="h6" fontWeight="bold"> <Typography variant="h6" fontWeight="bold">
Lifestyle Details Partner Preferences
</Typography> </Typography>
} }
action={ action={
<IconButton <IconButton
aria-label="edit" aria-label="edit"
color="primary" color="primary"
onClick={() => handleEditSection(2)} onClick={() => handleEditSection(5)}
size="large" size="large"
> >
<Edit2 size={20} /> <Edit2 size={20} />
</IconButton> </IconButton>
} }
sx={{ padding: "15px 15px", background: "#f5fbff" }} sx={{ padding: "15px 15px", background: "#f2f2f2" }}
/> />
<Divider /> {/* <Divider /> */}
<CardContent sx={{ pt: 1 }}> <CardContent sx={{ pt: 1 }}>
{renderField("Age Range", partnerPreferences.ageRange)} {renderField("Age Range", partnerPreferences.ageRange)}
@ -394,6 +596,14 @@ const ProfilePreviewPage = () => {
</Card> </Card>
); );
if (isLoading) {
return (
<Box display="flex" justifyContent="center" alignItems="center" minHeight="50vh">
<Typography>Loading Profile...</Typography>
</Box>
);
}
return ( return (
<> <>
<div className="custom-swiper-hero flex items-center justify-center p-4"> <div className="custom-swiper-hero flex items-center justify-center p-4">

View File

@ -28,7 +28,6 @@ import axiosInstance, { apiForFiles, setAccessToken } from "../api/axiosInstance
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { API_ENDPOINTS } from "../api/apiEndpoints"; import { API_ENDPOINTS } from "../api/apiEndpoints";
import { isAuthenticated } from "../utills/auth"; import { isAuthenticated } from "../utills/auth";
const STEP_FIELD_ORDER = { const STEP_FIELD_ORDER = {
1: [ 1: [
"name", "name",
@ -119,8 +118,59 @@ const STEP1_SERVER_FIELD_MAP = {
profiles: "profiles", profiles: "profiles",
profile_images: "profiles", profile_images: "profiles",
}; };
const Stepper = ({ currentStep, onStepClick }) => {
// const Stepper = ({ currentStep, enabledSteps, onStepClick,completedSteps }) => {
// const steps = [
// { num: 1, label: "Personal" },
// { num: 2, label: "Educational" },
// { num: 3, label: "Family" },
// { num: 4, label: "Lifestyle" },
// { num: 5, label: "Partner" },
// { num: 6, label: "Preview" },
// ];
// return (
// <div className="flex items-center justify-between px-4 py-6">
// {steps.map((step, index) => {
// const isEnabled = enabledSteps.includes(step.num);
// return (
// <React.Fragment key={step.num}>
// <div
// className={`flex flex-col items-center ${
// isEnabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"
// }`}
// onClick={() => isEnabled && onStepClick(step.num)}
// >
// <div
// className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
// currentStep >= step.num
// ? "bg-red-600 text-white"
// : "bg-gray-300 text-gray-600"
// }`}
// >
// {step.num}
// </div>
// </div>
// {index < steps.length - 1 && (
// <div
// className={`flex-1 h-0.5 mx-1 ${
// currentStep > step.num ? "bg-red-600" : "bg-gray-300"
// }`}
// />
// )}
// </React.Fragment>
// );
// })}
// </div>
// );
// };
import { Check } from "lucide-react";
const Stepper = ({ currentStep, enabledSteps, completedSteps, onStepClick }) => {
const steps = [ const steps = [
{ num: 1, label: "Personal" }, { num: 1, label: "Personal" },
@ -133,31 +183,45 @@ const Stepper = ({ currentStep, onStepClick }) => {
return ( return (
<div className="flex items-center justify-between px-4 py-6"> <div className="flex items-center justify-between px-4 py-6">
{steps.map((step, index) => ( {steps.map((step, index) => {
<React.Fragment key={step.num}> const isEnabled = enabledSteps.includes(step.num);
<div const isCompleted = completedSteps.includes(step.num);
className="flex flex-col items-center cursor-pointer"
onClick={() => onStepClick(step.num)} return (
> <React.Fragment key={step.num}>
<div <div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${ className={`flex flex-col items-center ${
currentStep >= step.num isEnabled ? "cursor-pointer" : "cursor-not-allowed opacity-50"
? "bg-red-600 text-white"
: "bg-gray-300 text-gray-600"
}`} }`}
onClick={() => isEnabled && onStepClick(step.num)}
> >
{step.num} <div
className={`w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ${
isCompleted
? "bg-green-600 text-white"
: currentStep === step.num
? "bg-red-600 text-white"
: "bg-gray-300 text-gray-600"
}`}
>
{isCompleted ? <Check size={16} /> : step.num}
</div>
<span className="text-xs mt-1">{step.label}</span>
</div> </div>
</div>
{index < steps.length - 1 && ( {index < steps.length - 1 && (
<div <div
className={`flex-1 h-0.5 mx-1 ${ className={`flex-1 h-0.5 mx-1 ${
currentStep > step.num ? "bg-red-600" : "bg-gray-300" completedSteps.includes(step.num)
}`} ? "bg-green-600"
/> : "bg-gray-300"
)} }`}
</React.Fragment> />
))} )}
</React.Fragment>
);
})}
</div> </div>
); );
}; };
@ -166,7 +230,9 @@ const StepperForm = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const hideStepperRoutes = ["/profile-edit"];
const shouldHideStepper = hideStepperRoutes.includes(location.pathname);
const personalDetails = useSelector( const personalDetails = useSelector(
(state) => state.registerform.personalDetails (state) => state.registerform.personalDetails
); );
@ -188,7 +254,8 @@ const StepperForm = () => {
const savedStep = localStorage.getItem("registration_current_step"); const savedStep = localStorage.getItem("registration_current_step");
return savedStep ? Number(savedStep) : 1; return savedStep ? Number(savedStep) : 1;
}); });
const [enabledSteps, setEnabledSteps] = useState([1]);
const [completedSteps, setCompletedSteps] = useState([]);
const [isStep1Update, setIsStep1Update] = useState(false); const [isStep1Update, setIsStep1Update] = useState(false);
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const isAuth = isAuthenticated(); const isAuth = isAuthenticated();
@ -340,6 +407,7 @@ const StepperForm = () => {
// in case user comes again with a different step // in case user comes again with a different step
if (location.state?.step) { if (location.state?.step) {
setCurrentStep(location.state.step); setCurrentStep(location.state.step);
setEnabledSteps([1, 2, 3, 4, 5, 6]);
window.scrollTo(0, 0); window.scrollTo(0, 0);
} }
}, [location.state?.step]); }, [location.state?.step]);
@ -530,7 +598,7 @@ useEffect(()=>{
dispatch( dispatch(
updateLifestyleDetails({ updateLifestyleDetails({
diets: ld.diet_id ? [ld.diet_id] : [], diets: ld.diet_id || "",
hobbies: ld.hobbies_ids || [], hobbies: ld.hobbies_ids || [],
dob: ld.date_of_birth || "", dob: ld.date_of_birth || "",
tob: ld.time_of_birth ? ld.time_of_birth.substring(0, 5) : "", tob: ld.time_of_birth ? ld.time_of_birth.substring(0, 5) : "",
@ -644,7 +712,10 @@ useEffect(()=>{
]; ];
required.forEach((field) => { required.forEach((field) => {
if (!educationalDetails[field]) { if (!educationalDetails[field]) {
newErrors[field] = "This field is required"; const label = field
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
} }
}); });
} else if (step === 3) { } else if (step === 3) {
@ -655,7 +726,11 @@ useEffect(()=>{
]; ];
required.forEach((field) => { required.forEach((field) => {
if (!familyDetails[field]) { if (!familyDetails[field]) {
newErrors[field] = "This field is required"; // newErrors[field] = "This field is required";
const label = field
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
} }
}); });
} else if (step === 4) { } else if (step === 4) {
@ -665,7 +740,12 @@ useEffect(()=>{
if (Array.isArray(value)) { if (Array.isArray(value)) {
if (value.length === 0) newErrors[field] = "This field is required"; if (value.length === 0) newErrors[field] = "This field is required";
} else if (!value) { } else if (!value) {
newErrors[field] = "This field is required"; // newErrors[field] = "This field is required";
const label = field
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
} }
}); });
} else if (step === 5) { } else if (step === 5) {
@ -689,7 +769,11 @@ useEffect(()=>{
return; return;
} }
if (!value) { if (!value) {
newErrors[field] = "This field is required"; // newErrors[field] = "This field is required";
const label = field
.replace(/([A-Z])/g, " $1")
.replace(/^./, (str) => str.toUpperCase());
newErrors[field] = `${label} is required`;
} }
}); });
} }
@ -806,9 +890,7 @@ useEffect(()=>{
formData.append("tob", lifestyleDetails.tob || ""); formData.append("tob", lifestyleDetails.tob || "");
formData.append("place_of_birth", lifestyleDetails.placeOfBirth || ""); formData.append("place_of_birth", lifestyleDetails.placeOfBirth || "");
(lifestyleDetails.diets || []).forEach((id, index) => { formData.append("diet", lifestyleDetails.diets || "");
formData.append(`diets[${index}]`, id);
});
(lifestyleDetails.hobbies || []).forEach((id, index) => { (lifestyleDetails.hobbies || []).forEach((id, index) => {
formData.append(`hobbies[${index}]`, id); formData.append(`hobbies[${index}]`, id);
@ -882,77 +964,128 @@ useEffect(()=>{
null; null;
const handleStepSubmit = async () => { const handleStepSubmit = async () => {
const isValid = validateStep(currentStep); const isValid = validateStep(currentStep);
if (!isValid) return; if (!isValid) return;
try {
let payload;
let res;
switch (currentStep) {
case 1:
payload = await buildRegisterStep1Payload();
try {
if (currentStep === 1) {
const payload = await buildRegisterStep1Payload();
let res;
if (isStep1Update) { if (isStep1Update) {
res = await apiForFiles.post("/update_personal_details", payload); res = await apiForFiles.post("/update_personal_details", payload);
} else { } else {
res = await registerStep1.mutateAsync(payload); res = await registerStep1.mutateAsync(payload);
} }
const token = extractAccessToken(res.data || res); const token = extractAccessToken(res.data || res);
if (token) { if (token) setAccessToken(token);
setAccessToken(token); break;
}
} else if (currentStep === 2) { case 2:
const payload = buildRegisterStep2Payload(); payload = buildRegisterStep2Payload();
await registerStep2.mutateAsync(payload); await registerStep2.mutateAsync(payload);
} else if (currentStep === 3) { break;
const payload = buildRegisterStep3Payload();
case 3:
payload = buildRegisterStep3Payload();
await registerStep3.mutateAsync(payload); await registerStep3.mutateAsync(payload);
} else if (currentStep === 4) { break;
const payload = buildRegisterStep4Payload();
case 4:
payload = buildRegisterStep4Payload();
await registerStep4.mutateAsync(payload); await registerStep4.mutateAsync(payload);
} else if (currentStep === 5) { break;
const payload = buildRegisterStep5Payload();
case 5:
payload = buildRegisterStep5Payload();
await registerStep5.mutateAsync(payload); await registerStep5.mutateAsync(payload);
} break;
setErrors({});
setCurrentStep((prev) => Math.min(prev + 1, 6));
window.scrollTo(0, 0);
} catch (e) {
const serverErrors = mapServerErrors(e);
let handled = false;
if (Object.keys(serverErrors).length > 0) { default:
const hasFieldErrors = Object.keys(serverErrors).some( break;
(key) => key !== "_form" }
);
if (hasFieldErrors) { // Mark step completed (tick icon)
setErrors(serverErrors); setCompletedSteps((prev) =>
focusFirstError(serverErrors, STEP_FIELD_ORDER[currentStep]); prev.includes(currentStep) ? prev : [...prev, currentStep]
// For step 1, we know mapping is correct so field errors are sufficient feedback );
if (currentStep === 1) handled = true;
} const nextStep = Math.min(currentStep + 1, 6);
if (serverErrors._form) {
toast.error(serverErrors._form, { position: "top-right" }); // Enable next step navigation
handled = true; setEnabledSteps((prev) =>
} prev.includes(nextStep) ? prev : [...prev, nextStep]
);
setErrors({});
setCurrentStep(nextStep);
window.scrollTo(0, 0);
} catch (e) {
const serverErrors = mapServerErrors(e);
let handled = false;
if (Object.keys(serverErrors).length > 0) {
const hasFieldErrors = Object.keys(serverErrors).some(
(key) => key !== "_form"
);
if (hasFieldErrors) {
setErrors(serverErrors);
focusFirstError(serverErrors, STEP_FIELD_ORDER[currentStep]);
handled = true;
} }
if (!handled) { if (serverErrors._form) {
const msg = e?.response?.data?.message || e?.message || "Failed to submit step. Please try again."; toast.error(serverErrors._form, { position: "top-right" });
toast.error(msg, { position: "top-right" }); handled = true;
} }
} }
};
const handleSkip = () => { if (!handled) {
setErrors({}); const msg =
setCurrentStep((prev) => Math.min(prev + 1, 6)); e?.response?.data?.message ||
window.scrollTo(0, 0); e?.message ||
}; "Failed to submit step. Please try again.";
const handleStepClick = (step) => { toast.error(msg, { position: "top-right" });
setCurrentStep(step); }
setErrors({}); }
window.scrollTo(0, 0); };
};
const handleSkip = () => {
setErrors({});
setCurrentStep((prev) => {
const nextStep = Math.min(prev + 1, 6);
setEnabledSteps((steps) =>
steps.includes(nextStep) ? steps : [...steps, nextStep]
);
return nextStep;
});
window.scrollTo(0, 0);
};
const handleStepClick = (step) => {
if (!enabledSteps.includes(step)) return;
setCurrentStep(step);
setErrors({});
window.scrollTo(0, 0);
};
useEffect(() => {
if (currentStep === 6) {
setEnabledSteps([1, 2, 3, 4, 5, 6]);
}
}, [currentStep]);
const handleEdit = (step) => { const handleEdit = (step) => {
setCurrentStep(step); setCurrentStep(step);
@ -961,12 +1094,10 @@ useEffect(()=>{
}; };
const handleFinalSubmit = async () => { const handleFinalSubmit = async () => {
for (let i = 1; i <= 5; i++) { const ok = validateStep(1);
const ok = validateStep(i); if (!ok) {
if (!ok) { setCurrentStep(1);
setCurrentStep(i); return;
return;
}
} }
try { try {
@ -995,7 +1126,7 @@ useEffect(()=>{
return ( return (
<EducationalDetailsForm <EducationalDetailsForm
onSubmitStep={handleStepSubmit} onSubmitStep={handleStepSubmit}
onSkipStep={handleSkip} onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors} errors={errors}
onFieldChange={clearFieldErrors} onFieldChange={clearFieldErrors}
/> />
@ -1004,7 +1135,7 @@ useEffect(()=>{
return ( return (
<FamilyDetailsForm <FamilyDetailsForm
onSubmitStep={handleStepSubmit} onSubmitStep={handleStepSubmit}
onSkipStep={handleSkip} onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors} errors={errors}
onFieldChange={clearFieldErrors} onFieldChange={clearFieldErrors}
/> />
@ -1013,7 +1144,7 @@ useEffect(()=>{
return ( return (
<LifestyleDetailsForm <LifestyleDetailsForm
onSubmitStep={handleStepSubmit} onSubmitStep={handleStepSubmit}
onSkipStep={handleSkip} onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors} errors={errors}
onFieldChange={clearFieldErrors} onFieldChange={clearFieldErrors}
/> />
@ -1022,7 +1153,7 @@ useEffect(()=>{
return ( return (
<PartnerPreferencesForm <PartnerPreferencesForm
onSubmitStep={handleStepSubmit} onSubmitStep={handleStepSubmit}
onSkipStep={handleSkip} onSkipStep={shouldHideStepper ? null : handleSkip}
errors={errors} errors={errors}
onFieldChange={clearFieldErrors} onFieldChange={clearFieldErrors}
/> />
@ -1051,8 +1182,13 @@ useEffect(()=>{
<div className="max-w-[1400px] mx-auto bg-white "> <div className="max-w-[1400px] mx-auto bg-white ">
{/* Header */} {/* Header */}
<div className="my-4 rounded-[10px] py-4 w-full max-w-[1200px] mx-auto"> <div className="my-4 rounded-[10px] py-4 w-full max-w-[1200px] mx-auto">
{!shouldHideStepper && (
<Stepper currentStep={currentStep} onStepClick={handleStepClick} /> <Stepper
currentStep={currentStep}
onStepClick={handleStepClick}
enabledSteps={enabledSteps}
completedSteps={completedSteps}
/> )}
<div className="flex items-center p-4 justify-center"> <div className="flex items-center p-4 justify-center">
{currentStep > 1 && currentStep < 6 && ( {currentStep > 1 && currentStep < 6 && (
<button <button
@ -1062,7 +1198,7 @@ useEffect(()=>{
<ChevronLeft size={24} /> <ChevronLeft size={24} />
</button> </button>
)} )}
<h1 className="text-[24px] font-semibold text-center uppercase bg-[#fff2f2] py-2 px-3 rounded-5">{getTitle()}</h1> <h1 className="text-[24px] font-semibold text-center uppercase py-2 px-3 rounded-5">{getTitle()}</h1>
</div> </div>
</div> </div>