diff --git a/src/components/common/ProfileCardSkeleton.jsx b/src/components/common/ProfileCardSkeleton.jsx
new file mode 100644
index 0000000..eacea38
--- /dev/null
+++ b/src/components/common/ProfileCardSkeleton.jsx
@@ -0,0 +1,20 @@
+import React from "react";
+
+export default function ProfileCardSkeleton() {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/common/ProfileCardUI.jsx b/src/components/common/ProfileCardUI.jsx
new file mode 100644
index 0000000..09ab40c
--- /dev/null
+++ b/src/components/common/ProfileCardUI.jsx
@@ -0,0 +1,142 @@
+import React, { useState } from "react";
+import { Crown, Bookmark, Receipt, Sparkles, MoonStar, IdCard } from "lucide-react";
+import CakeIcon from "@mui/icons-material/Cake";
+import LocationOnIcon from "@mui/icons-material/LocationOn";
+import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
+import VisibilityIcon from "@mui/icons-material/Visibility";
+import { motion } from "framer-motion";
+import { useNavigate } from "react-router-dom";
+
+export default function ProfileCardUI({ profile }) {
+ const [isLiked, setIsLiked] = useState(false);
+ const navigate = useNavigate();
+
+ // Map API fields to UI, handling missing values
+ const imageSrc = profile.photo || profile.image || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
+
+ return (
+ navigate(`/profile-details/${profile.id}`)}
+ className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200 bg-white cursor-pointer hover:shadow-2xl transition-all duration-300"
+ >
+
+ {/* Premium Badge */}
+ {profile.isPremium && (
+
+
+
+ )}
+
+
{
+ e.stopPropagation();
+ // Shortlist logic here
+ }}
+ className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
+ >
+
+ Shortlist
+
+
+
+

{
+ e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
+ }}
+ />
+
+
+ {/* Gradient Overlay */}
+
+
+
+
{profile.name}
+
ID: {profile.member_id || profile.id}
+
+
+
+
+
+
+ Last seen: {profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"}
+
+
+
+
+
+ {profile.age ? `${profile.age} yrs` : "-"}
+
+
+
+
{profile.height ? `${profile.height} cm` : "-"}
+
+
+
+ {profile.annual_income_name || "N/A"}
+
+
+
+
+
+
+ {profile.raasi_name || "-"}
+
+
+
+ {profile.star_name || "-"}
+
+
+
+ {profile.caste_name || "-"}
+
+
+
+
+
+
+ {profile.district_name || profile.location || "-"}
+ {profile.state_name ? `, ${profile.state_name}` : ""}
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/common/VirtualizedSelect.jsx b/src/components/common/VirtualizedSelect.jsx
deleted file mode 100644
index b1e9230..0000000
--- a/src/components/common/VirtualizedSelect.jsx
+++ /dev/null
@@ -1,97 +0,0 @@
-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 897da7a..7b42494 100644
--- a/src/components/matches/MatchesProfilesTab.jsx
+++ b/src/components/matches/MatchesProfilesTab.jsx
@@ -1,15 +1,10 @@
-import React, { useState } from "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";
-import LocationOnIcon from "@mui/icons-material/LocationOn";
-import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
+import React, { useEffect } from "react";
+import { useInView } from "react-intersection-observer";
+import { RockingChair, LocateFixed, School, WorkflowIcon, Lock } from "lucide-react";
import PersonIcon from "@mui/icons-material/Person";
import StarIcon from "@mui/icons-material/Star";
import VisibilityIcon from "@mui/icons-material/Visibility";
import PersonAddIcon from "@mui/icons-material/PersonAdd";
-import { motion } from "framer-motion";
import FilterModal from "../../feature/FilterModal";
import bride1 from "../../assets/images/bride1.jpg";
import bride2 from "../../assets/images/bride2.jpg";
@@ -23,207 +18,75 @@ import groom4 from "../../assets/images/groom4.jpg";
import horoscope from "../../assets/images/horoscopeicon.png";
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);
- const navigate = useNavigate();
- return (
- navigate(`/profile-details/${profile.id}`)}
- className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200"
- >
-
-
-
-
-
-
-
- Shortlist
-
-
-

-
-
-
-
-
- {profile.name}
-
-
- Matrimony ID: {profile.id}
-
-
-
-
-
-
-
- {profile.lastseen}
-
-
-
-
-
- {profile.age} yr
-
-
-
-
-
-
- {profile.height} cm
-
-
-
-
-
-
- 5 - 10 LPA
-
-
-
-
-
-
-
- Aries
-
-
-
-
-
-
- Scorpio
-
-
-
-
-
-
- Bramin
-
-
-
-
-
-
-
- {profile.location}
-
-
-
-
-
-
-
-
-
- {/*
-
-
-
-
*/}
-
-
- );
-}
+import { useProfiles } from "../../hooks/useProfiles";
+import ProfileCardUI from "../common/ProfileCardUI";
+import ProfileCardSkeleton from "../common/ProfileCardSkeleton";
// Main Component
export default function MatchesInterface() {
+ const [showSkeleton, setShowSkeleton] = React.useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
- const filterType = useSelector((state) => state.filters.filter_type);
+ const filters = useSelector((state) => state.filters);
+ const filterType = filters.filter_type;
const selectedTab = filterType || "all_matches";
- const isPaidMember = useSelector((state) => state.filters.isPaidMember);
+ const isPaidMember = filters.isPaidMember;
+const { ref, inView } = useInView({
+ threshold: 0,
+ rootMargin: "300px"
+ });
+ // Fetch real profiles data
+ const {
+ data: profilesData,
+ isLoading,
+ fetchNextPage,
+ hasNextPage,
+ isFetchingNextPage,
+ } = useProfiles(filters);
+ const profiles =
+ profilesData?.pages.flatMap((page) => page?.data|| []) || [];
+ // const { ref, inView } = useInView();
+
+// useEffect(() => {
+// if (inView && hasNextPage && !isFetchingNextPage) {
+// fetchNextPage();
+// }
+// }, [inView, hasNextPage, isFetchingNextPage]);
+
+
+useEffect(() => {
+ if (inView && hasNextPage && !isFetchingNextPage) {
+
+ setShowSkeleton(true); // show skeleton
+
+ const timer = setTimeout(() => {
+ fetchNextPage();
+ setShowSkeleton(false); // hide skeleton after API call
+ }, 120); // 0.5 seconds
+
+ return () => clearTimeout(timer);
+ }
+}, [inView, hasNextPage, isFetchingNextPage, fetchNextPage]);
+
+
+
+
+
+
+
+
+
+ console.log("Fetched profiles:", profiles);
+
+ console.log({
+ inView,
+ hasNextPage,
+ isFetchingNextPage,
+});
const tabs = [
{
id: "all_matches",
@@ -290,89 +153,6 @@ export default function MatchesInterface() {
},
];
- const profiles = [
- {
- id: "JB2847593",
- name: "Jerome Bell",
- age: 22,
- height: "5.2",
- lastseen: "Last seen 14 Nov 2025",
- education: "BCA / Data analyst",
- location: "Chennai",
- image: bride1,
- },
- {
- id: "SA8392847",
- name: "Sarah Anderson",
- age: 24,
- height: "5.4",
- lastseen: "Last seen 14 Nov 2025",
- education: "MBA / Marketing Manager",
- location: "Bangalore",
- image: bride4,
- },
- {
- id: "PR9384756",
- name: "Priya Reddy",
- age: 23,
- height: "5.3",
- lastseen: "Last seen 14 Nov 2025",
- education: "B.Tech / Software Engineer",
- location: "Hyderabad",
- image: bride2,
- },
- {
- id: "AN4758392",
- name: "Ananya Krishnan",
- age: 25,
- height: "5.5",
- lastseen: "Last seen 14 Nov 2025",
- education: "MD / Doctor",
- location: "Kochi",
- image: bride3,
- },
- {
- id: "AN4758392",
- name: "Ananya Krishnan",
- age: 25,
- height: "5.5",
- lastseen: "Last seen 14 Nov 2025",
- education: "MD / Doctor",
- location: "Kochi",
- image: groom1,
- },
- {
- id: "AN4758392",
- name: "Ananya Krishnan",
- age: 25,
- height: "5.5",
- lastseen: "Last seen 14 Nov 2025",
- education: "MD / Doctor",
- location: "Kochi",
- image: groom2,
- },
- {
- id: "AN4758392",
- name: "Ananya Krishnan",
- age: 25,
- height: "5.5",
- lastseen: "Last seen 14 Nov 2025",
- education: "MD / Doctor",
- location: "Kochi",
- image: groom4,
- },
- {
- id: "AN4758392",
- name: "Ananya Krishnan",
- age: 25,
- height: "5.5",
- lastseen: "Last seen 14 Nov 2025",
- education: "MD / Doctor",
- location: "Kochi",
- image: groom3,
- },
- ];
-
let currentCategory = "";
return (
@@ -501,9 +281,37 @@ export default function MatchesInterface() {
- {profiles.map((profile) => (
-
- ))}
+ {isLoading && !isFetchingNextPage ? (
+ [...Array(6)].map((_, i) =>
)
+ ) : profiles.length > 0 ? (
+ profiles.map((profile) => (
+
+ ))
+ ) : !isLoading && !isFetchingNextPage ? (
+
+ No profiles found
+
+ ) : null}
+
+
+ {/* {isFetchingNextPage &&
+ [...Array(5)].map((_, i) => (
+
+ ))} */}
+
+
+ {(isFetchingNextPage || showSkeleton) &&
+ [...Array(6)].map((_, i) => (
+
+ ))}
+
+
+
+ {!isLoading && !hasNextPage && profiles.length > 0 && (
+
+ You've reached the end.
+
+ )}
diff --git a/src/components/matches/SearchUI.jsx b/src/components/matches/SearchUI.jsx
index 230a34d..9620d8d 100644
--- a/src/components/matches/SearchUI.jsx
+++ b/src/components/matches/SearchUI.jsx
@@ -1,9 +1,23 @@
-import React, { useState } from 'react';
+import React, { useState, useEffect } from 'react';
import { Search } from 'lucide-react';
+import { useDispatch, useSelector } from 'react-redux';
+import { updateFilter } from '../../redux/filterSlice';
+import useDebounce from '../../hooks/useDebounce.jsx';
export default function SearchUI() {
+ const dispatch = useDispatch();
+ const searchFromStore = useSelector((state) => state.filters.search);
const [searchValue, setSearchValue] = useState('');
const [showSuggestions, setShowSuggestions] = useState(false);
+ const debouncedSearchValue = useDebounce(searchValue, 500);
+
+ useEffect(() => {
+ setSearchValue(searchFromStore || '');
+ }, [searchFromStore]);
+
+ useEffect(() => {
+ dispatch(updateFilter({ search: debouncedSearchValue }));
+ }, [debouncedSearchValue, dispatch]);
// Sample suggestions data - you can replace with dynamic data
const allSuggestions = [
diff --git a/src/hooks/useDebounce.js b/src/hooks/useDebounce.js
new file mode 100644
index 0000000..599de9f
--- /dev/null
+++ b/src/hooks/useDebounce.js
@@ -0,0 +1,14 @@
+import { useEffect, useState } from "react";
+
+
+export function useDebounce(value, delay=500) {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+ useEffect(()=>{
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ },delay);
+return () => clearTimeout(handler);
+ },[value, delay]);
+
+ return debouncedValue;
+};
\ No newline at end of file
diff --git a/src/hooks/useDebounce.jsx b/src/hooks/useDebounce.jsx
new file mode 100644
index 0000000..440ecc0
--- /dev/null
+++ b/src/hooks/useDebounce.jsx
@@ -0,0 +1,19 @@
+import { useState, useEffect } from "react";
+
+const useDebounce = (value, delay) => {
+ const [debouncedValue, setDebouncedValue] = useState(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+};
+
+export default useDebounce;
\ No newline at end of file
diff --git a/src/hooks/useProfiles.js b/src/hooks/useProfiles.js
index 9b9b6bf..17ae0ed 100644
--- a/src/hooks/useProfiles.js
+++ b/src/hooks/useProfiles.js
@@ -1,10 +1,62 @@
-import { useQuery } from "@tanstack/react-query";
-import { getProfilesFilterList, getProfilesFilterMasters } from "../api/masters.api";
+import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
+import {
+ getProfilesFilterList,
+ getProfilesFilterMasters,
+} from "../api/masters.api";
+
+export const useProfiles = (filters = {}) => {
+
+ // Remove empty filters
+ const cleanFilters = Object.entries(filters).reduce((acc, [key, value]) => {
+ if (
+ value !== "" &&
+ value !== null &&
+ value !== undefined &&
+ !(Array.isArray(value) && value.length === 0)
+ ) {
+ acc[key] = value;
+ }
+ return acc;
+ }, {});
+
+ return useInfiniteQuery({
+ queryKey: ["profiles-filter-list", cleanFilters],
+
+ queryFn: ({ pageParam = 1 }) =>
+ getProfilesFilterList({
+ ...cleanFilters,
+ page: pageParam,
+ }),
+
+ staleTime: 1000 * 60 * 2,
+ refetchOnWindowFocus: false,
+
+
+getNextPageParam: (lastPage, allPages) => {
+
+ const currentPageData =
+ lastPage?.data?.data || lastPage?.data || [];
+
+ const totalLoaded = allPages.reduce((acc, page) => {
+ const pageData = page?.data?.data || page?.data || [];
+ return acc + pageData.length;
+ }, 0);
+
+ const totalRecords =
+ lastPage?.data?.recordsFiltered ||
+ lastPage?.recordsFiltered ||
+ 0;
+
+ console.log("totalLoaded:", totalLoaded);
+ console.log("totalRecords:", totalRecords);
+
+ if (totalLoaded < totalRecords) {
+ return allPages.length + 1;
+ }
+
+ return undefined;
+}
-export const useProfiles = (filters) => {
- return useQuery({
- queryKey: ["profiles-filter-list", filters],
- queryFn: () => getProfilesFilterList(filters),
});
};
diff --git a/src/redux/filterSlice.jsx b/src/redux/filterSlice.jsx
index c5a3eba..01f8367 100644
--- a/src/redux/filterSlice.jsx
+++ b/src/redux/filterSlice.jsx
@@ -21,6 +21,7 @@ const initialState = {
filter_type: "all_matches",
page: 1,
isPaidMember: false,
+ search: "",
};
const filterSlice = createSlice({