diff --git a/package-lock.json b/package-lock.json
index baef904..b16a66d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -31,6 +31,9 @@
"react-lazy-load-image-component": "^1.6.3",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.6",
+ "react-select": "^5.10.2",
+ "react-window": "^2.2.7",
+ "redux-persist": "^6.0.0",
"swiper": "^12.0.3",
"tailwindcss": "^4.1.17"
},
@@ -1719,6 +1722,31 @@
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
"license": "Apache-2.0"
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.5",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz",
+ "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz",
+ "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.5",
+ "@floating-ui/utils": "^0.2.11"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.11",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz",
+ "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
+ "license": "MIT"
+ },
"node_modules/@grpc/grpc-js": {
"version": "1.9.15",
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
@@ -7139,6 +7167,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -8053,6 +8087,27 @@
"react-dom": ">=18"
}
},
+ "node_modules/react-select": {
+ "version": "5.10.2",
+ "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.10.2.tgz",
+ "integrity": "sha512-Z33nHdEFWq9tfnfVXaiM12rbJmk+QjFEztWLtmXqQhz6Al4UZZ9xc0wiatmGtUOCCnHN0WizL3tCMYRENX4rVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.0",
+ "@emotion/cache": "^11.4.0",
+ "@emotion/react": "^11.8.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@types/react-transition-group": "^4.4.0",
+ "memoize-one": "^6.0.0",
+ "prop-types": "^15.6.0",
+ "react-transition-group": "^4.3.0",
+ "use-isomorphic-layout-effect": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/react-smooth": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz",
@@ -8116,6 +8171,16 @@
}
}
},
+ "node_modules/react-window": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.7.tgz",
+ "integrity": "sha512-SH5nvfUQwGHYyriDUAOt7wfPsfG9Qxd6OdzQxl5oQ4dsSsUicqQvjV7dR+NqZ4coY0fUn3w1jnC5PwzIUWEg5w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -8205,6 +8270,15 @@
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT"
},
+ "node_modules/redux-persist": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
+ "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "redux": ">4.0.0"
+ }
+ },
"node_modules/redux-thunk": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
@@ -9069,6 +9143,20 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-isomorphic-layout-effect": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz",
+ "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
diff --git a/package.json b/package.json
index 1345bf1..ad96a48 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,9 @@
"react-lazy-load-image-component": "^1.6.3",
"react-redux": "^9.2.0",
"react-router-dom": "^7.9.6",
+ "react-select": "^5.10.2",
+ "react-window": "^2.2.7",
+ "redux-persist": "^6.0.0",
"swiper": "^12.0.3",
"tailwindcss": "^4.1.17"
},
diff --git a/src/api/apiEndpoints.js b/src/api/apiEndpoints.js
index d872116..4ded3e8 100644
--- a/src/api/apiEndpoints.js
+++ b/src/api/apiEndpoints.js
@@ -53,5 +53,9 @@ BE_SAFE_ONLINE:"get_be_safe_online",
NOTIFICATION_LIST:"notification/lists",
NOTIFICATION_COUNT:"notification/un_read_count",
+// filter with profiles list api's
+PROFILES_FILTER_LIST:"profiles/lists",
+PROFILES_FILTER_MASTER:"profiles/filter/masters",
+
};
diff --git a/src/api/masters.api.js b/src/api/masters.api.js
index c7ea4a6..1e9296c 100644
--- a/src/api/masters.api.js
+++ b/src/api/masters.api.js
@@ -63,3 +63,17 @@ export const getPartnerPreferenceMasters = async () => {
);
return res.data;
};
+
+
+// profile filter masters
+export const getProfilesFilterList = async (filters) => {
+ const res = await axiosInstance.get(API_ENDPOINTS.PROFILES_FILTER_LIST, {
+ params: filters,
+ });
+ return res.data;
+};
+
+export const getProfilesFilterMasters = async () => {
+ const res = await axiosInstance.get(API_ENDPOINTS.PROFILES_FILTER_MASTER);
+ return res.data;
+};
\ No newline at end of file
diff --git a/src/components/common/VirtualizedSelect.jsx b/src/components/common/VirtualizedSelect.jsx
new file mode 100644
index 0000000..b1e9230
--- /dev/null
+++ b/src/components/common/VirtualizedSelect.jsx
@@ -0,0 +1,97 @@
+import React from "react";
+import Autocomplete from "@mui/material/Autocomplete";
+import TextField from "@mui/material/TextField";
+import ReactWindow from "react-window";
+
+const FixedSizeList = ReactWindow?.FixedSizeList || ReactWindow?.default?.FixedSizeList;
+
+const LISTBOX_PADDING = 8; // px
+
+function renderRow(props) {
+ const { data, index, style } = props;
+ const dataSet = data[index];
+ const inlineStyle = {
+ ...style,
+ top: style.top + LISTBOX_PADDING,
+ };
+
+ return (
+
+ {dataSet}
+
+ );
+}
+
+const OuterElementContext = React.createContext({});
+
+const OuterElementType = React.forwardRef((props, ref) => {
+ const outerProps = React.useContext(OuterElementContext);
+ return ;
+});
+
+const ListboxComponent = React.forwardRef(function ListboxComponent(props, ref) {
+ const { children, ...other } = props;
+
+ if (!FixedSizeList) {
+ return ;
+ }
+
+ const itemData = [];
+
+ React.Children.forEach(children, (item) => {
+ itemData.push(item);
+ itemData.push(...(item.children || []));
+ });
+
+ const itemCount = itemData.length;
+ const itemSize = 48;
+
+ const getHeight = () => {
+ if (itemCount > 8) {
+ return 8 * itemSize;
+ }
+ return itemCount * itemSize;
+ };
+
+ return (
+
+
+
+ {renderRow}
+
+
+
+ );
+});
+
+const VirtualizedSelect = ({ options, value, onChange, label, placeholder, isMulti, ...props }) => {
+ return (
+ {
+ onChange(newValue);
+ }}
+ disableListWrap
+ ListboxComponent={ListboxComponent}
+ renderInput={(params) => (
+
+ )}
+ isOptionEqualToValue={(option, value) => option.value === value.value}
+ getOptionLabel={(option) => option.label || ""}
+ {...props}>
+
+ )
+}
+
+export default VirtualizedSelect
\ No newline at end of file
diff --git a/src/components/matches/MatchesProfilesTab.jsx b/src/components/matches/MatchesProfilesTab.jsx
index 023a036..897da7a 100644
--- a/src/components/matches/MatchesProfilesTab.jsx
+++ b/src/components/matches/MatchesProfilesTab.jsx
@@ -1,5 +1,5 @@
import React, { useState } from "react";
-import { Crown, Bookmark, CurrencyIcon, Currency, Wallet, Receipt, Sparkles, MoonStar, IdCard, RockingChair, LocateFixed, School, WorkflowIcon } from "lucide-react";
+import { Crown, Bookmark, CurrencyIcon, Currency, Wallet, Receipt, Sparkles, MoonStar, IdCard, RockingChair, LocateFixed, School, WorkflowIcon, Lock } from "lucide-react";
import CakeIcon from "@mui/icons-material/Cake";
import GroupsIcon from "@mui/icons-material/Groups";
import SchoolIcon from "@mui/icons-material/School";
@@ -26,6 +26,9 @@ import { useNavigate } from "react-router-dom";
import { Button, Fab } from "@mui/material";
import MessageIcon from "@mui/icons-material/Message";
import PhoneIcon from "@mui/icons-material/Phone";
+import toast from "react-hot-toast";
+import { useSelector, useDispatch } from "react-redux";
+import { updateFilter } from "../../redux/filterSlice";
// Profile Card Component
function ProfileCard({ profile }) {
const [isLiked, setIsLiked] = useState(false);
@@ -216,67 +219,70 @@ function ProfileCard({ profile }) {
// Main Component
export default function MatchesInterface() {
const navigate = useNavigate();
- const [selectedTab, setSelectedTab] = useState("your-matches");
+ const dispatch = useDispatch();
+ const filterType = useSelector((state) => state.filters.filter_type);
+ const selectedTab = filterType || "all_matches";
+ const isPaidMember = useSelector((state) => state.filters.isPaidMember);
const tabs = [
{
- id: "your-matches",
+ id: "all_matches",
icon: ,
title: "Your Matches",
description: "View all the profiles that match your preferences",
category: "All Matches",
},
{
- id: "shortlisted-by-you",
+ id: "shorlisted_by_you",
icon: ,
title: "Shortlisted by you",
description: "Matches you have shortlisted",
category: "Based on activity",
},
{
- id: "viewed-you",
+ id: "viewed_you",
icon: ,
title: "Viewed you",
description: "Matches who have viewed your profile",
category: "Based on activity",
},
{
- id: "shortlisted-you",
+ id: "shorlisted_you",
icon: ,
title: "Shortlisted you",
description: "Matches who have shortlisted your profile",
category: "Based on activity",
},
{
- id: "viewed-by-you",
+ id: "viewed_by_you",
icon: ,
title: "Viewed by you",
description: "Matches you have viewed",
category: "Based on activity",
},
{
- id: "newly-joined",
+ id: "newly_joined",
icon: ,
title: "Newly Joined",
description: "Matches who Joined within the last 30 days",
category: "Based on activity",
},
{
- id: "location",
+ id: "location_matches",
icon: ,
title: "Location matches",
description: "Matches near your location",
category: "Based on activity",
},
{
- id: "education",
+ id: "education_matches",
icon: ,
title: "Education matches",
description: "Matches near your education match",
category: "Based on activity",
},
{
- id: "job",
+ id: "job_matches",
icon: ,
title: "Job matches",
description: "Matches near your job",
@@ -406,7 +412,9 @@ export default function MatchesInterface() {
)}
setSelectedTab(tab.id)}
+ onClick={() => {
+ dispatch(updateFilter({ filter_type: tab.id }));
+ }}
className={`p-4 rounded-lg mb-3 cursor-pointer transition-all ${
selectedTab === tab.id
? "bg-green-50 border-l-4 border-green-600"
@@ -472,12 +480,23 @@ export default function MatchesInterface() {
{tabs.find((t) => t.id === selectedTab)?.title}
-

{
+
{
+ if (isPaidMember) {
navigate("/horoscoper-generate");
- }}
- />
+ } else {
+ toast.error("Star Matching is locked for free members");
+ }
+ }}>
+

+ {!isPaidMember && (
+
+
+
+ )}
+
diff --git a/src/feature/AdvancedDropzone.jsx b/src/feature/AdvancedDropzone.jsx
index 02f1e35..6c87e3b 100644
--- a/src/feature/AdvancedDropzone.jsx
+++ b/src/feature/AdvancedDropzone.jsx
@@ -107,14 +107,14 @@ const AdvancedDropzone = ({ value, onChange }) => {
{hoveredId === file.id && (
{
const dispatch = useDispatch();
@@ -40,34 +41,61 @@ const FilterForm = () => {
location: false,
lifestyle: false,
family: false,
+ paidBenefit: false,
});
+ const { data: filterMasters, isLoading, isError } = useProfilesFilterMasters();
+
+ const { data: cityData } = useCityMasters(
+ filters.state && filters.state.length > 0 ? filters.state : null
+ );
+
+ const cityOptions = useMemo(() => {
+ const raw = cityData;
+ if (!raw) return [];
+ if (Array.isArray(raw)) return raw;
+ return raw.subCaste || raw.district || raw.data || [];
+ }, [cityData]);
+
const handleAccordionChange = (section) => (event, isExpanded) => {
setExpandedSections((prev) => ({ ...prev, [section]: isExpanded }));
};
- const casteOptions = ["Agamudayar", "Pillai", "Vellalar"];
- const motherTongueOptions = [
- "Tamil",
- "Telugu",
- "Malayalam",
- "Kannada",
- "Hindi",
- ];
const handleSubmit = () => {
console.log("Filter Values:", filters);
};
+ const handleClear = () => {
+ dispatch(resetFilters());
+ toast.success("Filters cleared");
+ };
+
const handleSelectionChange = (field, value) => {
console.log(`${field} selected:`, value);
};
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+ Failed to load filter options
+
+ );
+ }
+
return (
{/* Header */}
-
+
Partner Filter preference
@@ -98,7 +126,7 @@ const FilterForm = () => {
Age
{
dispatch(setAge(newValue));
handleSelectionChange("Age", newValue);
@@ -119,7 +147,7 @@ const FilterForm = () => {
Height
{
dispatch(setHeight(newValue));
handleSelectionChange("Height", newValue);
@@ -141,46 +169,40 @@ const FilterForm = () => {
Marital Status
-
-
- {/* Mother Tongue */}
-
- Mother Tongue
-
+
+
+
@@ -207,61 +229,69 @@ const FilterForm = () => {
Religion
- {/* Matches with Horoscope */}
- {
- dispatch(setMatchesWithHoroscope(e.target.checked));
- handleSelectionChange(
- "Matches with Horoscope",
- e.target.checked
- );
- }}
- />
- }
- label="Matches with horoscope"
- />
-
{/* Caste */}
- Caste (Multi Select)
+ Caste
@@ -269,21 +299,19 @@ const FilterForm = () => {
{/* Sub-Caste */}
- Sub-Caste (Multi Select)
+ Sub-Caste
- {/* Star */}
-
- Star
-
-
- {/* Dasham */}
-
- Dasham
-
-
@@ -356,66 +352,108 @@ const FilterForm = () => {
Occupation
Annual Income
Employee Type
Education
@@ -443,50 +481,59 @@ const FilterForm = () => {
State
- Country
+ City
-
- Citizenship
-
-
+
@@ -512,54 +559,22 @@ const FilterForm = () => {
Eating Habits
- {/*
- Smoking Habits
-
-
-
-
- Drinking Habits
-
- */}
+
@@ -585,64 +600,116 @@ const FilterForm = () => {
Family Type
-
- Family Status
-
-
+
+
+
+ {/* Paid Membership Benefit Section */}
+
{
+ if (!filters.isPaidMember) {
+ toast.error("This feature is locked for paid members only", {
+ icon: "🔒",
+ });
+ return;
+ }
+ handleAccordionChange("paidBenefit")(e, isExpanded);
+ }}
+ className="mb-4"
+ >
+ : }
+ sx={{
+ backgroundColor: "#f5fbff",
+ "&:hover": {
+ backgroundColor: "#fff6f0",
+ },
+ }}
+ >
+
+
+
+ Paid Membership Benefit
+
+
+
+
+
+ {/* Star Filter */}
- Family Value
+ Star
- {/* Submit Button */}
-
+ {/* Sticky Footer Actions */}
+
+
}
+ >
+ Clear Filters
+
}
>
Apply Filters
diff --git a/src/feature/FilterModal.jsx b/src/feature/FilterModal.jsx
index a4716a6..84bb9b9 100644
--- a/src/feature/FilterModal.jsx
+++ b/src/feature/FilterModal.jsx
@@ -27,7 +27,7 @@ const FilterModal = () => {
open={open}
onClose={handleClose}
fullWidth
- maxWidth="lg" // adjust: "sm" | "md" | "lg"
+ maxWidth="md" // adjust: "sm" | "md" | "lg"
>
diff --git a/src/feature/LifestyleDetailsForm.jsx b/src/feature/LifestyleDetailsForm.jsx
index 15915c8..020493d 100644
--- a/src/feature/LifestyleDetailsForm.jsx
+++ b/src/feature/LifestyleDetailsForm.jsx
@@ -16,6 +16,7 @@ import {
DialogContent,
DialogContentText,
DialogActions,
+ Tooltip,
} from "@mui/material";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
@@ -184,6 +185,7 @@ const LifestyleDetailsForm = ({
};
const renderChartCell = (label, value, onChange) => (
+ 0 ? value.join(", ") : ""} arrow placement="top">
{label}
@@ -196,7 +198,17 @@ const LifestyleDetailsForm = ({
onChange={(e) => onChange(e.target.value)}
renderValue={(selected) => {
if (!selected || selected.length === 0) return `+ Add ${label}`;
- return selected.join(", ");
+ return (
+
+ {selected.join(", ")}
+
+ );
}}
MenuProps={{
PaperProps: { style: { maxHeight: 280 } },
@@ -211,6 +223,7 @@ const LifestyleDetailsForm = ({
+
);
const renderChartGrid = (type) => {
diff --git a/src/feature/PersonalDetailsForm.jsx b/src/feature/PersonalDetailsForm.jsx
index 50e77f8..89a67ea 100644
--- a/src/feature/PersonalDetailsForm.jsx
+++ b/src/feature/PersonalDetailsForm.jsx
@@ -39,6 +39,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
const dispatch = useDispatch();
const data = useSelector((state) => state.registerform.personalDetails);
const nameInputRef = useRef(null);
+ const mobileInputRef = useRef(null);
const requiredMark = *;
const [showOtp, setShowOtp] = useState(false);
@@ -56,15 +57,23 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
if (isStep1Update && data.mobileNumber && !data.verifiedMobileNumber) {
dispatch(updatePersonalDetails({ verifiedMobileNumber: data.mobileNumber }));
setMobileOtpVerified(true);
+ setMobileNumberError("");
}
}, [isStep1Update, data.mobileNumber, data.verifiedMobileNumber, dispatch]);
useEffect(() => {
if (data.verifiedMobileNumber && data.mobileNumber === data.verifiedMobileNumber) {
setMobileOtpVerified(true);
+ setMobileNumberError("");
}
}, [data.verifiedMobileNumber, data.mobileNumber]);
+ useEffect(() => {
+ if (!isStep1Update && !data.religion) {
+ dispatch(updatePersonalDetails({ religion: 1 }));
+ }
+ }, [isStep1Update, data.religion, dispatch]);
+
const { data: personalMasters, isLoading: isPersonalMastersLoading } =
usePersonalDetailsMasters();
@@ -120,7 +129,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
const raw = cityQuery.data;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
- return raw.subCaste || raw.district || raw.data || [];
+ return raw.subCaste || raw.sub_caste || raw.district || raw.data || [];
}, [cityQuery.data]);
const starOptions = useMemo(() => {
@@ -384,12 +393,6 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
}
};
- useEffect(() => {
- if (showOtp && !mobileOtpVerified && isOtpComplete) {
- handleOtpSubmit();
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [otp]);
// file upload
@@ -426,32 +429,31 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
const handleSubmit = async () => {
- if (showOtp && !isOtpComplete) {
- setOtpError("OTP is required and must be complete");
- return;
- }
-
- if (showOtp && !mobileOtpVerified) {
- try {
- await verifyOtp.mutateAsync({
- mobile: data.mobileNumber,
- otp: otp.join(""),
- });
- setMobileOtpVerified(true);
- setOtpError("");
- console.log("Submitting personal details:", data); // log here
- onSubmitStep();
- console.log("OTP verified on submit");
- } catch (err) {
- setOtpError(err || "OTP verification failed");
+ // For new registrations, mobile verification is mandatory.
+ // For updates, it's also mandatory if the number was changed.
+ // The `mobileOtpVerified` state covers both scenarios.
+ if (!mobileOtpVerified) {
+ // Provide a more specific error message.
+ if (showOtp) {
+ if (!isOtpComplete) {
+ setOtpError("OTP is required and must be complete");
+ } else {
+ setOtpError("Please submit the OTP to verify your number.");
+ }
+ const firstOtpInput = document.getElementById("otp-0");
+ if (firstOtpInput) {
+ firstOtpInput.focus();
+ }
+ } else {
+ setMobileNumberError("Please verify your mobile number to proceed.");
+ if (mobileInputRef.current) {
+ mobileInputRef.current.focus();
+ }
+ }
+ return;
}
- return;
- }
-
- // no OTP or already verified
- console.log("Submitting personal details:", data); // log here
- onSubmitStep();
-};
+ onSubmitStep();
+ };
const formatTimer = (sec) => {
@@ -532,6 +534,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
-
+
{otpTimer > 0 ? (
` ${formatTimer(otpTimer) } Seconds`
) : (
@@ -712,6 +715,8 @@ const PersonalDetailsForm = ({ onSubmitStep, errors, onFieldChange, isStep1Updat
fullWidth
name="height"
label="Enter Height"
+
+ max={3}
type="number"
value={data.height}
onChange={(e) => handleChange("height", e.target.value)}
diff --git a/src/feature/StepperForm.jsx b/src/feature/StepperForm.jsx
index 3f4b727..8d3ac05 100644
--- a/src/feature/StepperForm.jsx
+++ b/src/feature/StepperForm.jsx
@@ -233,8 +233,7 @@ const StepperForm = () => {
const location = useLocation();
const navigate = useNavigate();
const hideStepperRoutes = ["/profile-edit"];
-
-const shouldHideStepper = hideStepperRoutes.includes(location.pathname);
+const shouldHideStepper = hideStepperRoutes.some((route) => location.pathname.startsWith(route));
const personalDetails = useSelector(
(state) => state.registerform.personalDetails
);
@@ -421,78 +420,84 @@ const [completedSteps, setCompletedSteps] = useState([]);
}
}, [location.state, navigate, location.pathname]);
+ const { data: personalDetailsData } = useQuery({
+ queryKey: ["personalDetails"],
+ queryFn: async () => {
+ const response = await axiosInstance.get(API_ENDPOINTS.EDIT_PERSONAL_DETAILS);
+ return response.data;
+ },
+ enabled: isAuth || shouldHideStepper,
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
useEffect(() => {
- if (!isAuth) return;
- const fetchPersonalDetails = async () => {
- try {
- const response = await axiosInstance.get(API_ENDPOINTS.EDIT_PERSONAL_DETAILS);
- const data = response.data;
- if (data.status === "success" && data.personal_details) {
- const pd = data.personal_details;
- setIsStep1Update(true);
+ const processData = async () => {
+ if (personalDetailsData?.status === "success" && personalDetailsData?.personal_details) {
+ const pd = personalDetailsData.personal_details;
+ setIsStep1Update(true);
- const rawImages = pd.profile_images || pd.images || [];
- const mappedImages = await Promise.all(
- rawImages.map(async (url, index) => {
- const imageUrl = typeof url === "string" ? url : url?.url;
- let file = null;
- let mimeType = "image/jpeg";
- let fileName = `image-${index}.jpg`;
+ const rawImages = pd.profile_images || pd.images || [];
+ const mappedImages = await Promise.all(
+ rawImages.map(async (url, index) => {
+ const imageUrl = typeof url === "string" ? url : url?.url;
+ let file = null;
+ let mimeType = "image/jpeg";
+ let fileName = `image-${index}.jpg`;
- try {
+ try {
+ if (imageUrl) {
const response = await fetch(imageUrl);
const blob = await response.blob();
if (blob.type) mimeType = blob.type;
const ext = mimeType.split("/")[1] || "jpg";
fileName = `image-${index}.${ext}`;
file = new File([blob], fileName, { type: mimeType });
- } catch (error) {
- console.error("Error converting image URL to File:", error);
}
- return {
- id: `server-${index}`,
- preview: imageUrl,
- imageUrl: imageUrl,
- file: file,
- name: fileName,
- type: mimeType,
- valid: true,
- };
- })
- );
+ } catch (error) {
+ console.error("Error converting image URL to File:", error);
+ }
+ return {
+ id: `server-${index}`,
+ preview: imageUrl,
+ imageUrl: imageUrl,
+ file: file,
+ name: fileName,
+ type: mimeType,
+ valid: true,
+ };
+ })
+ );
- const formattedDob = pd.dob ? pd.dob.split("T")[0] : "";
+ const formattedDob = pd.dob ? pd.dob.split("T")[0] : "";
- dispatch(
- updatePersonalDetails({
- name: pd.name || "",
- mobileNumber: pd.mobile || "",
- email: pd.email || "",
- gender: pd.gender || "",
- dob: formattedDob,
- height: pd.height || "",
- weight: pd.weight || "",
- maritalStatus: pd.marital_status_id || "",
- religion: pd.religion_id || "",
- profileFor: pd.profile_for_id || "",
- caste: pd.caste_id || "",
- subCaste: pd.sub_caste_id || "",
- gothram: pd.gothram_id || "",
- raasi: pd.raasi_id || "",
- star: pd.star_id || "",
- state: pd.state_id || "",
- city: pd.district_id || "",
- pincode: pd.pincode || "",
- profiles: mappedImages,
- })
- );
- }
- } catch (error) {
- console.error("Error fetching personal details:", error);
+ dispatch(
+ updatePersonalDetails({
+ name: pd.name || "",
+ mobileNumber: pd.mobile || "",
+ email: pd.email || "",
+ gender: pd.gender || "",
+ dob: formattedDob,
+ height: pd.height || "",
+ weight: pd.weight || "",
+ maritalStatus: pd.marital_status_id || "",
+ religion: pd.religion_id || "",
+ profileFor: pd.profile_for_id || "",
+ caste: pd.caste_id || "",
+ subCaste: pd.sub_caste_id || "",
+ gothram: pd.gothram_id || "",
+ raasi: pd.raasi_id || "",
+ star: pd.star_id || "",
+ state: pd.state_id || "",
+ city: pd.district_id || "",
+ pincode: pd.pincode || "",
+ profiles: mappedImages,
+ })
+ );
}
};
- fetchPersonalDetails();
- }, [dispatch, isAuth]);
+ processData();
+ }, [personalDetailsData, dispatch]);
// Fetch Educational Details
@@ -502,7 +507,7 @@ const [completedSteps, setCompletedSteps] = useState([]);
const response = await axiosInstance.get(API_ENDPOINTS.EDIT_EDUCATION_DETAILS);
return response.data;
},
- enabled: isAuth,
+ enabled: isAuth || shouldHideStepper,
retry: false,
refetchOnWindowFocus: false,
});
@@ -535,7 +540,7 @@ const {data:familyData} = useQuery({
const response = await axiosInstance.get(API_ENDPOINTS.EDIT_FAMILY_DETAILS);
return response.data;
},
- enabled: isAuth,
+ enabled: isAuth || shouldHideStepper,
retry:false,
refetchOnWindowFocus:false,
});
@@ -584,9 +589,10 @@ useEffect(()=>{
const response = await axiosInstance.get(API_ENDPOINTS.EDIT_LIFESTYLE_DETAILS);
return response.data;
},
- enabled: isAuth,
+ enabled: isAuth || shouldHideStepper,
retry: false,
refetchOnWindowFocus: false,
+ refetchOnMount: true,
});
useEffect(() => {
@@ -608,8 +614,8 @@ useEffect(()=>{
updateLifestyleDetails({
diets: ld.diet_id || "",
hobbies: ld.hobbies_ids || [],
- dob: ld.time_of_birth || "",
- tob: ld.date_of_birth ? ld.date_of_birth.substring(0, 5) : "",
+ dob: ld.time_of_birth ? ld.time_of_birth.split("T")[0] : "",
+ tob: ld.time_of_birth_formated ? ld.time_of_birth_formated.substring(0, 5) : "",
placeOfBirth: ld.place_of_birth || "",
graha: mapChart("graha"),
amsam: mapChart("amsam"),
@@ -626,7 +632,7 @@ useEffect(()=>{
const response = await axiosInstance.get(API_ENDPOINTS.EDIT_PREFERED_PARTNER_DETAILS);
return response.data;
},
- enabled: isAuth,
+ enabled: isAuth || shouldHideStepper,
retry: false,
refetchOnWindowFocus: false,
});
@@ -878,6 +884,8 @@ useEffect(()=>{
if (fileToAppend) {
formData.append(`profile_images[${index}]`, fileToAppend);
+ } else if (item.preview && typeof item.preview === "string") {
+ formData.append(`profile_images[${index}]`, item.preview);
}
})
);
@@ -932,7 +940,7 @@ useEffect(()=>{
formData.append("tob", lifestyleDetails.tob || "");
formData.append("place_of_birth", lifestyleDetails.placeOfBirth || "");
- formData.append("diet", lifestyleDetails.diets || "");
+ formData.append("diets", lifestyleDetails.diets || "");
(lifestyleDetails.hobbies || []).forEach((id, index) => {
formData.append(`hobbies[${index}]`, id);
@@ -1022,6 +1030,9 @@ useEffect(()=>{
try {
const previewData = await getPreviewDetails();
+ queryClient.invalidateQueries({
+ queryKey: ["preview-details"]
+ });
if (previewData?.personal_details) {
const pd = previewData.personal_details;
const images = pd.images || pd.profile_images;
@@ -1032,7 +1043,7 @@ useEffect(()=>{
dispatch(updatePersonalDetails({ profiles: formattedImages }));
}
}
- queryClient.invalidateQueries({ queryKey: ["previewDetails"] });
+ queryClient.invalidateQueries({ queryKey: ["personalDetails"] });
} catch (error) {
console.error("Error refreshing preview details:", error);
}
@@ -1047,21 +1058,29 @@ useEffect(()=>{
case 2:
payload = buildRegisterStep2Payload();
await registerStep2.mutateAsync(payload);
+ queryClient.invalidateQueries({ queryKey: ["educationalDetails"] });
+ queryClient.invalidateQueries({ queryKey: ["preview-details"] });
break;
case 3:
payload = buildRegisterStep3Payload();
await registerStep3.mutateAsync(payload);
+ queryClient.invalidateQueries({ queryKey: ["familyDetails"] })
+ queryClient.invalidateQueries({ queryKey: ["preview-details"] });
break;
case 4:
payload = buildRegisterStep4Payload();
await registerStep4.mutateAsync(payload);
+ queryClient.invalidateQueries({ queryKey: ["lifestyleDetails"] });
+ queryClient.invalidateQueries({ queryKey: ["preview-details"] });
break;
case 5:
payload = buildRegisterStep5Payload();
await registerStep5.mutateAsync(payload);
+ queryClient.invalidateQueries({ queryKey: ["partnerPreferences"] });
+ queryClient.invalidateQueries({ queryKey: ["preview-details"] });
break;
default:
diff --git a/src/hooks/useProfiles.js b/src/hooks/useProfiles.js
new file mode 100644
index 0000000..9b9b6bf
--- /dev/null
+++ b/src/hooks/useProfiles.js
@@ -0,0 +1,15 @@
+import { useQuery } from "@tanstack/react-query";
+import { getProfilesFilterList, getProfilesFilterMasters } from "../api/masters.api";
+
+export const useProfiles = (filters) => {
+ return useQuery({
+ queryKey: ["profiles-filter-list", filters],
+ queryFn: () => getProfilesFilterList(filters),
+ });
+};
+
+export const useProfilesFilterMasters = () =>
+ useQuery({
+ queryKey: ["profiles-filter-masters"],
+ queryFn: getProfilesFilterMasters,
+ });
\ No newline at end of file
diff --git a/src/main.jsx b/src/main.jsx
index d647b16..456cf95 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -7,8 +7,9 @@ import { ThemeProvider } from "@mui/material/styles";
import theme from "./theme";
import { Provider } from "react-redux";
-import { store } from "./redux/store.js";
+import { persistor, store } from "./redux/store.js";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { PersistGate } from "redux-persist/integration/react";
// Disable noisy logs in production while keeping warnings/errors.
if (import.meta.env.PROD) {
console.log = () => {};
@@ -29,12 +30,14 @@ const queryClient = new QueryClient({
ReactDOM.createRoot(document.getElementById("root")).render(
-
-
-
-
-
+
+
+
+
+
+
+
-
+
);
diff --git a/src/redux/filterSlice.jsx b/src/redux/filterSlice.jsx
index 8f38444..c5a3eba 100644
--- a/src/redux/filterSlice.jsx
+++ b/src/redux/filterSlice.jsx
@@ -1,29 +1,26 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
- age: [18, 70],
- height: [4.0, 7.11],
- maritalStatus: "All Profile",
- motherTongue: [],
- religion: "Hindu",
- matchesWithHoroscope: false,
+ from_age: 18,
+ to_age: 70,
+ from_height: 4.0,
+ to_height: 7.11,
+ marital_status: [],
+ religion: [],
caste: [],
- subCaste: [],
- star: "Any",
- dasham: "Doesn't matter",
- occupation: "Any",
- annualIncome: "Any",
- employeeType: "Any",
- education: "Any",
- state: "Any",
- country: "Any",
- citizenship: "Any",
- eatingHabits: "Any",
- smokingHabits: "Doesn't matter",
- drinkingHabits: "Doesn't matter",
- familyType: "Any",
- familyStatus: "Any",
- familyValue: "Any",
+ sub_caste: [],
+ star: [],
+ occupation: [],
+ annual_income: [],
+ employee_type: [],
+ education: [],
+ state: [],
+ district: [],
+ diet: "",
+ family_type: [],
+ filter_type: "all_matches",
+ page: 1,
+ isPaidMember: false,
};
const filterSlice = createSlice({
@@ -31,73 +28,24 @@ const filterSlice = createSlice({
initialState,
reducers: {
setAge: (state, action) => {
- state.age = action.payload;
+ state.from_age = action.payload[0];
+ state.to_age = action.payload[1];
},
setHeight: (state, action) => {
- state.height = action.payload;
+ state.from_height = action.payload[0];
+ state.to_height = action.payload[1];
},
setMaritalStatus: (state, action) => {
- state.maritalStatus = action.payload;
- },
- setMotherTongue: (state, action) => {
- state.motherTongue = action.payload;
+ state.marital_status = Array.isArray(action.payload) ? action.payload : [action.payload];
},
setReligion: (state, action) => {
- state.religion = action.payload;
- },
- setMatchesWithHoroscope: (state, action) => {
- state.matchesWithHoroscope = action.payload;
+ state.religion = Array.isArray(action.payload) ? action.payload : [action.payload];
},
setCaste: (state, action) => {
state.caste = action.payload;
},
setSubCaste: (state, action) => {
- state.subCaste = action.payload;
- },
- setStar: (state, action) => {
- state.star = action.payload;
- },
- setDasham: (state, action) => {
- state.dasham = action.payload;
- },
- setOccupation: (state, action) => {
- state.occupation = action.payload;
- },
- setAnnualIncome: (state, action) => {
- state.annualIncome = action.payload;
- },
- setEmployeeType: (state, action) => {
- state.employeeType = action.payload;
- },
- setEducation: (state, action) => {
- state.education = action.payload;
- },
- setState: (state, action) => {
- state.state = action.payload;
- },
- setCountry: (state, action) => {
- state.country = action.payload;
- },
- setCitizenship: (state, action) => {
- state.citizenship = action.payload;
- },
- setEatingHabits: (state, action) => {
- state.eatingHabits = action.payload;
- },
- setSmokingHabits: (state, action) => {
- state.smokingHabits = action.payload;
- },
- setDrinkingHabits: (state, action) => {
- state.drinkingHabits = action.payload;
- },
- setFamilyType: (state, action) => {
- state.familyType = action.payload;
- },
- setFamilyStatus: (state, action) => {
- state.familyStatus = action.payload;
- },
- setFamilyValue: (state, action) => {
- state.familyValue = action.payload;
+ state.sub_caste = action.payload;
},
// universal update
@@ -105,7 +53,16 @@ const filterSlice = createSlice({
return { ...state, ...action.payload };
},
- resetFilters: () => initialState,
+ resetFilters: (state) => {
+ // Reset all filters but preserve the paid member status
+ return { ...initialState, isPaidMember: state.isPaidMember };
+ },
+ setPage: (state, action) => {
+ state.page = action.payload;
+ },
+ setPaidMemberStatus: (state, action) => {
+ state.isPaidMember = action.payload;
+ },
},
});
@@ -113,28 +70,13 @@ export const {
setAge,
setHeight,
setMaritalStatus,
- setMotherTongue,
setReligion,
- setMatchesWithHoroscope,
setCaste,
setSubCaste,
- setStar,
- setDasham,
- setOccupation,
- setAnnualIncome,
- setEmployeeType,
- setEducation,
- setState,
- setCountry,
- setCitizenship,
- setEatingHabits,
- setSmokingHabits,
- setDrinkingHabits,
- setFamilyType,
- setFamilyStatus,
- setFamilyValue,
updateFilter,
resetFilters,
+ setPage,
+ setPaidMemberStatus,
} = filterSlice.actions;
export default filterSlice.reducer;
diff --git a/src/redux/store.js b/src/redux/store.js
index 92e486c..6161766 100644
--- a/src/redux/store.js
+++ b/src/redux/store.js
@@ -1,9 +1,36 @@
import { configureStore } from "@reduxjs/toolkit";
import registerformReducer from "./registrationFormSlice";
import filtersReducer from "./filterSlice";
+import {
+ persistStore,
+ persistReducer,
+ FLUSH,
+ REHYDRATE,
+ PAUSE,
+ PERSIST,
+ PURGE,
+ REGISTER,
+} from "redux-persist";
+import storage from "redux-persist/lib/storage"; // defaults to localStorage for web
+
+const persistConfig = {
+ key: "filters",
+ storage,
+};
+
+const persistedFiltersReducer = persistReducer(persistConfig, filtersReducer);
+
export const store = configureStore({
reducer: {
- registerform:registerformReducer,
- filters:filtersReducer,
+ registerform: registerformReducer,
+ filters: persistedFiltersReducer,
},
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ serializableCheck: {
+ ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
+ },
+ }),
});
+
+export const persistor = persistStore(store);