matrimony

This commit is contained in:
Meenadeveloper 2026-03-14 17:07:39 +05:30
parent cd880e10e5
commit 68f97c40dc
13 changed files with 700 additions and 886 deletions

View File

@ -5,3 +5,9 @@ export const getPreviewDetails = async () => {
const res = await axiosInstance.get(API_ENDPOINTS.PREVIEW_DETAILS);
return res.data;
};
export const getHeaderDetails = async () => {
const res = await axiosInstance.get(API_ENDPOINTS.HEADER_API);
return res.data;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

View File

@ -29,6 +29,7 @@ import toast from "react-hot-toast";
import { API_ENDPOINTS } from "../../api/apiEndpoints";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useSelector } from "react-redux";
import { getHeaderDetails } from "../../api/preview.api";
const NAV_LINKS = [
// { label: "Home", path: "/" },
{ label: "Matches", path: "/matches" },
@ -194,6 +195,13 @@ const ProfileHeader = () => {
const { data: headerData } = useQuery({
queryKey: ["headerDetails"],
queryFn: getHeaderDetails,
enabled: !!auth,
});
const apiProfileImage = headerData?.myDetails?.profile;
const handleMenuClick = (item) => {
if (item.action === "delete") {
setDeleteModalOpen(true);
@ -381,7 +389,7 @@ const ProfileHeader = () => {
<Box sx={{ flexGrow: 0 }}>
<Tooltip title="Account Menu">
<IconButton onClick={toggleProfileDrawer(true)}>
<Avatar sx={{width:"50px", height:"50px"}} src={profileImage || userimg || "/static/images/avatar/2.jpg" }/>
<Avatar sx={{width:"50px", height:"50px"}} src={apiProfileImage || profileImage || userimg || "/static/images/avatar/2.jpg" }/>
</IconButton>

View File

@ -34,6 +34,7 @@ export default function MatchesInterface() {
const filterType = filters.filter_type;
const selectedTab = filterType || "all_matches";
const isPaidMember = filters.isPaidMember;
const { ref, inView } = useInView({
threshold: 0,
rootMargin: "300px"
@ -167,7 +168,7 @@ useEffect(() => {
<div className="w-full md:w-80">
<div
className="rounded-[10px] border border-gray-200 bg-white my-6
className="relative rounded-[10px] border border-gray-200 bg-white my-6
shadow-lg h-[400px] md:h-[600px] overflow-y-auto md:sticky md:top-[150px]"
>

View File

@ -1,4 +1,4 @@
import { useRef, useState } from "react";
import { useRef, useState, useEffect } from "react";
import { motion } from "framer-motion";
import { Swiper, SwiperSlide } from "swiper/react";
import {
@ -10,141 +10,84 @@ import {
import {
Crown,
Bookmark,
User,
Briefcase,
MapPin,
X,
Send,
ChevronLeft,
ChevronRight,
Heart,
Eye,
} from "lucide-react";
import CakeIcon from "@mui/icons-material/Cake";
import HeightIcon from "@mui/icons-material/Height";
import GroupsIcon from "@mui/icons-material/Groups";
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
import SchoolIcon from "@mui/icons-material/School";
import LocationOnIcon from "@mui/icons-material/LocationOn";
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet";
import profilebg from "../../assets/images/profilebg.jpg";
import Image from "../../assets/images/astrology-horoscope-svgrepo-com.svg";
import Image1 from "../../assets/images/scorpio-svgrepo-com.svg";
// Import Swiper styles
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import "swiper/css/effect-coverflow";
import { useNavigate } from "react-router-dom";
import { useDispatch } from "react-redux";
import { updateFilter } from "../../redux/filterSlice";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
const MatchingList = () => {
const swiperRef = useRef(null);
const navigate = useNavigate();
// Sample profile data
const profiles = [
{
id: 1,
name: "Selva Kumar . R",
userId: "TK52586A",
lastSeen: "14 Nov 25",
age: 23,
height: "5'2\"",
salary: "5-10 LPA",
location: "chennai",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=500&fit=crop",
isPremium: true,
},
{
id: 2,
name: "Priya Sharma",
userId: "TK52587B",
lastSeen: "15 Nov 25",
age: 25,
height: "5'4\"",
salary: "8-12 LPA",
location: "hyderabad",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=500&fit=crop",
isPremium: true,
},
{
id: 3,
name: "Rahul Venkat",
userId: "TK52588C",
lastSeen: "16 Nov 25",
age: 28,
height: "5'10\"",
salary: "6-11 LPA",
location: "Mumbai",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image:
"https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=500&fit=crop",
isPremium: false,
},
{
id: 4,
name: "Aishwarya Reddy",
userId: "TK52589D",
lastSeen: "17 Nov 25",
age: 26,
height: "5'5\"",
salary: "7-11 LPA",
location: "Bangalore",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image:
"https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=500&fit=crop",
isPremium: true,
},
{
id: 5,
name: "Karthik Mohan",
userId: "TK52590E",
lastSeen: "18 Nov 25",
age: 27,
height: "5'8\"",
salary: "9-14 LPA",
location: "kerala",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image:
"https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=400&h=500&fit=crop",
isPremium: false,
},
{
id: 6,
name: "Divya Lakshmi",
userId: "TK52591F",
lastSeen: "19 Nov 25",
age: 24,
height: "5'3\"",
salary: "5-10 LPA",
location: "madya pradesh",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image:
"https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=500&fit=crop",
isPremium: true,
},
];
// Profile Card Component
const ProfileCard = ({ profile }) => {
const [isLiked, setIsLiked] = useState(false);
const ProfileCard = ({ profile }) => {
const navigate = useNavigate();
const queryClient = useQueryClient();
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
useEffect(() => {
setIsShortlisted(profile?.is_shortlisted === 1);
}, [profile?.is_shortlisted]);
const shortlistMutation = useMutation({
mutationFn: shortlistProfile,
onMutate: () => {
setIsShortlisted((prev) => !prev);
},
onSuccess: (data) => {
toast.success(data.message || "Profile shortlisted successfully.");
// Invalidating queries will refetch data and update all profile cards simultaneously
queryClient.invalidateQueries();
},
onError: (error) => {
setIsShortlisted(profile?.is_shortlisted === 1);
toast.error(error.message || "Failed to update shortlist status.");
}
});
const interestMutation = useMutation({
mutationFn: sendInterest,
onSuccess: (data) => {
toast.success(data.message || "Interest sent successfully.");
queryClient.invalidateQueries();
},
onError: (error) => {
toast.error(error.message || "Failed to send interest.");
}
});
const declineMutation = useMutation({
mutationFn: declineProfile,
onSuccess: (data) => {
toast.success(data.message || "Profile declined.");
queryClient.invalidateQueries();
},
onError: (error) => {
toast.error(error.message || "Failed to decline profile.");
}
});
const id = profile.id;
const image = profile.photo || profile.image;
const name = profile.name || "Unknown";
const idNumber = profile.member_id || profile.userId || "N/A";
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
const age = profile.age ? `${profile.age} yrs` : null;
const height = profile.height || null;
const salary = profile.annual_income_name || profile.salary || null;
const location = profile.district_name || profile.location || null;
const caste = profile.caste_name || profile.caste || null;
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
const zodiac2 = profile.star_name || profile.zodiac2 || null;
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : profile.isPremium;
return (
<motion.div
@ -152,140 +95,110 @@ const MatchingList = () => {
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
onClick={() => navigate(`/profile-details/${profile.id}`)}
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-2 border-gray-200"
onClick={() => navigate(`/profile-details/${id}`)}
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
>
{/* Profile Image Section */}
{/* IMAGE SECTION */}
<div className="relative">
{/* Premium Badge */}
{profile.isPremium && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, type: "spring" }}
className="absolute top-4 left-4 z-10 bg-red-900 rounded-full p-2 shadow-lg"
>
<Crown className="w-5 h-5 text-white" />
</motion.div>
<img
src={image}
alt="profile"
className="w-full h-[320px] object-cover"
onError={(e) => {
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
}}
/>
{isPremium && (
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
<Crown size={18} color="#fff" />
</div>
)}
{/* Shortlist Button */}
<motion.button
whileHover={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
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"
<div
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
onClick={(e) => {
e.stopPropagation();
// shortlist logic
if (!shortlistMutation.isPending) {
shortlistMutation.mutate(id);
}
}}
>
<Bookmark className="w-4 h-4" />
<span className="text-[12px] font-medium">Shortlist</span>
</motion.button>
<div
classname=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
style={{ height: "300px" }}
>
<img
src={profile.image}
alt={profile.name}
className="w-full h-full object-cover"
/>
</div>
{/* <LazyImage
src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=600&h=800&fit=crop&crop=faces,top"
alt="Profile"
className="w-full h-90 object-cover"
/> */}
{/* White Gradient Overlay at bottom of image */}
<div
className="absolute bottom-0 left-0 right-0 h-25 pointer-events-none"
style={{
background:
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.75) 50%, rgb(255, 255, 255) 100%)",
}}
></div>
{/* Profile Info Overlay - positioned at bottom */}
<div className="absolute bottom-1 left-0 right-0 p-6 pb-1 text-gray-900">
<h1 className="text-[18px] text-green-900 font-bold mb-2">
{profile.name}
</h1>
<p className="text-[14px] text-gray-700 leading-relaxed">
Matrimony ID: {profile.userId}
</p>
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
{shortlistMutation.isPending ? "..." : "Shortlist"}
</div>
</div>
{/* Stats and Follow Section */}
<div
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 "
style={{
background: "rgb(255, 255, 255)",
}}
>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<CakeIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
Age : {profile.age}
</span>
</div>
{/* CONTENT */}
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
<h2 className="text-center text-[22px] font-semibold mb-1">
{name}
</h2>
<div className="flex items-center gap-2">
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
Height: {profile.height}
</span>
</div>
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
<p>ID: {idNumber}</p>
<p className="flex items-center gap-0.5">
<Eye size={12} /> {lastSeen}
</p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<AccountBalanceWalletIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.salary}
</span>
</div>
<div className="flex items-center gap-2">
<LocationOnIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.location}
</span>
</div>
<div className="flex flex-wrap justify-center gap-2">
{[
age,
height,
salary,
location,
caste,
zodiac1,
zodiac2,
]
.filter(Boolean)
.map((v, i) => (
<span
key={i}
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
>
{v}
</span>
))}
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<TempleHinduIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.caste}
</span>
</div>
<div className="flex items-center gap-2">
<img src={Image} alt="" className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.zodiac1}
</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<img src={Image1} alt="" className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.zodiac2}
</span>
</div>
<div className="flex gap-4 mt-[15px] justify-center">
<button
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
onClick={(e) => {
e.stopPropagation();
if (!declineMutation.isPending) declineMutation.mutate(id);
}}
disabled={declineMutation.isPending}
>
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
</button>
<button
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
onClick={(e) => {
e.stopPropagation();
if (!interestMutation.isPending) interestMutation.mutate(id);
}}
disabled={interestMutation.isPending}
>
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
</button>
</div>
</div>
</motion.div>
);
};
const MatchingList = ({ matches }) => {
const swiperRef = useRef(null);
const navigate = useNavigate();
const dispatch = useDispatch();
const displayProfiles = matches || [];
if (displayProfiles.length === 0) return null;
return (
<>
<div
@ -341,8 +254,8 @@ const MatchingList = () => {
}}
className="pb-16"
>
{profiles.map((profile) => (
<SwiperSlide key={profile.id}>
{displayProfiles.map((profile, index) => (
<SwiperSlide key={profile.id || index}>
<ProfileCard profile={profile} />
</SwiperSlide>
))}
@ -383,7 +296,10 @@ const MatchingList = () => {
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
onClick={() => navigate("/matches")}
onClick={() => {
dispatch(updateFilter({ filter_type: "all_matches" }));
navigate("/matches", { state: { activeTab: "allmatches", filter_type: "all_matches" } });
}}
>
View All Matches
</motion.button>

View File

@ -5,48 +5,13 @@ import "swiper/css/navigation";
import { motion } from 'framer-motion';
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
import { useRef } from "react";
import LazyImage from "../common/LazyImage";
import weddingImg1 from "../../assets/images/wedding6.jpeg";
import weddingImg2 from "../../assets/images/wedding8.jpg";
import weddingImg3 from "../../assets/images/wedding7.jpg";
const articleData = [
{
title: "Marriage is not just finding the right partner, it's creating a lifetime of moments together Find someone who understands your heart and walks with you in every season.",
img: weddingImg1,
},
{
title: "Top 10 Qualities for a Happy Marriage, A perfect match begins with trust, respect, and shared dreams",
img: weddingImg2
},
{
title: "Expert Tips for a Strong Relationship, A perfect match begins with trust, respect, and shared dreams",
img: weddingImg3
},
{
title: "How to Build Trust in Marriage, A perfect match begins with trust, respect, and shared dreams",
img: weddingImg1
},
{
title: "Communication Secrets for Couples, A perfect match begins with trust, respect, and shared dreams,A perfect match begins with trust, respect, and shared dreams.A perfect match begins with trust, respect, and shared dreams.A perfect match begins with trust, respect, and shared dreams,Real relationships are built on honesty, compassion, and understanding.,Real relationships are built on honesty, compassion, and understanding.",
img: weddingImg1
}
];
const twoLineStyle = {
display: '-webkit-box',
WebkitBoxOrient: 'vertical',
WebkitLineClamp: 2,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'normal'
};
const MatrimonyArticles = () => {
const MatrimonyArticles = ({ articles }) => {
const swiperRef = useRef(null);
const displayArticles = articles || [];
if (displayArticles.length === 0) return null;
return (
<>
<div className="custom-article-swiper py-10 px-2 max-w-[1400px] mx-auto my-10">
@ -70,51 +35,19 @@ const MatrimonyArticles = () => {
loop={true}
className="mySwiper"
>
{articleData.map((item, index) => (
<SwiperSlide key={index}>
{displayArticles.map((item) => (
<SwiperSlide key={item.id}>
<div className="w-full max-w-[600px] rounded-[10px] overflow-hidden border border-2 border-[#f2f2f2] bg-white">
<div className="relative w-full">
<div classname=" bg-gray-200 overflow-hidden w-full max-w-sm " style={{height:"240px"}}>
<div className="bg-gray-200 overflow-hidden w-full max-w-sm" style={{height:"240px"}}>
<img
src={item.img}
className="w-full h-88 object-cover"
alt={item.title}
src={item.image}
className="w-full h-full object-cover"
alt={`Article ${item.id}`}
/>
</div>
{/* <LazyImage
src={item.img}
className="w-full h-48 object-cover"
alt={item.title}
/> */}
{/* White Gradient Overlay at bottom of image */}
{/* Profile Info Overlay - positioned at bottom */}
<div
className="px-4 pb-4 flex flex-col gap-2 relative "
style={{
background: "rgb(255, 255, 255)",
}}
>
<div
className="z-9 absolute top-[-40px] left-0 right-0 h-10 pointer-events-none"
style={{
background:
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
}}
>
</div>
<div className="z-10 relative pb-4" style={{
background: "rgb(255, 255, 255)",
}}>
<h3 className="text-[16px] font-semibold two-line-ellipsis" style={twoLineStyle} >{item.title}</h3>
</div>
</div>
</div>
</div>

View File

@ -1,7 +1,19 @@
import { Phone, MessageCircle, ThumbsUp, Eye } from 'lucide-react';
import promogirl from "../../assets/images/mobile.png"
const MembershipCard = ()=>{
import { useNavigate } from 'react-router-dom';
const MembershipCard = ({ becomePaidMember })=>{
const navigate = useNavigate();
const offerPercentage = becomePaidMember?.offer_percentage
? parseInt(becomePaidMember.offer_percentage)
: 58;
const points = becomePaidMember?.points || [];
const icons = [Phone, MessageCircle, Eye, ThumbsUp];
return (
<>
@ -19,50 +31,31 @@ return (
{/* Subheading with offer */}
<p className="text-[16px] lg:text-[16px] text-gray-900 mb-4">
Get up to <span className="text-red-600 font-bold">58% OFF</span> on paid membership!
Get up to <span className="text-red-600 font-bold">{offerPercentage}% OFF</span> on paid membership!
</p>
{/* Features List */}
<div className="space-y-2 mb-2">
<div className="flex items-start gap-4">
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
<Phone className="w-5 h-5 text-gray-700" />
</div>
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
Call/WhatsApp matches
</p>
</div>
<div className="flex items-start gap-4">
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
<MessageCircle className="w-5 h-5 text-gray-700" />
</div>
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
Unlimited messages
</p>
</div>
<div className="flex items-start gap-4">
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
<ThumbsUp className="w-5 h-5 text-gray-700" />
</div>
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
Higher chances of response
</p>
</div>
<div className="flex items-start gap-4">
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
<Eye className="w-5 h-5 text-gray-700" />
</div>
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
View and match horoscopes
</p>
</div>
{points.map((point, index) => {
const Icon = icons[index % icons.length];
return (
<div key={index} className="flex items-start gap-4">
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
<Icon className="w-5 h-5 text-gray-700" />
</div>
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
{point}
</p>
</div>
);
})}
</div>
{/* CTA Button */}
<button className="mt-2 bg-[#034E08] hover:bg-[#A70710] text-white font-bold text-[14px] lg:text-[14px] py-4 px-6 rounded-full shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105">
<button
onClick={() => navigate('/subscription-plan')}
className="mt-2 bg-[#034E08] hover:bg-[#A70710] text-white font-bold text-[14px] lg:text-[14px] py-4 px-6 rounded-full shadow-lg hover:shadow-xl transition-all duration-300 transform hover:scale-105"
>
See membership plans
</button>
</div>
@ -86,4 +79,3 @@ return (
</>);
};
export default MembershipCard

View File

@ -1,116 +1,96 @@
import { useRef, useState } from 'react';
import { useRef, useState, useEffect } from 'react';
import { motion } from 'framer-motion';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay, EffectCoverflow } from 'swiper/modules';
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
import CakeIcon from "@mui/icons-material/Cake";
import HeightIcon from "@mui/icons-material/Height";
import GroupsIcon from "@mui/icons-material/Groups";
import TempleHinduIcon from "@mui/icons-material/TempleHindu";
import SchoolIcon from "@mui/icons-material/School";
import LocationOnIcon from "@mui/icons-material/LocationOn";
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
import profilebg from "../../assets/images/profilebg.jpg";
import {
Crown,
Bookmark,
X,
ChevronLeft,
ChevronRight,
Heart,
Eye,
} from 'lucide-react';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import 'swiper/css/effect-coverflow';
import { useNavigate } from 'react-router-dom';
const NewJoinedProfile = () => {
const swiperRef = useRef(null);
const navigate = useNavigate();
// Sample profile data
const profiles = [
{
id: 1,
name: 'Selva Kumar . R',
userId: 'TK52586A',
lastSeen: '14 Nov 25',
age: 23,
height: '5\'2"',
religion: 'Hindu / Agamudayar / Thuluva Vellal',
education: 'BCA, Data Analyst',
location: 'Vellore, Tamil Nadu',
image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=500&fit=crop',
isPremium: true
},
{
id: 2,
name: 'Priya Sharma',
userId: 'TK52587B',
lastSeen: '15 Nov 25',
age: 25,
height: '5\'4"',
religion: 'Hindu / Brahmin / Iyer',
education: 'MBA, Marketing Manager',
location: 'Chennai, Tamil Nadu',
image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=500&fit=crop',
isPremium: true
},
{
id: 3,
name: 'Rahul Venkat',
userId: 'TK52588C',
lastSeen: '16 Nov 25',
age: 28,
height: '5\'10"',
religion: 'Hindu / Mudaliar / Arcot',
education: 'B.Tech, Software Engineer',
location: 'Bangalore, Karnataka',
image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=400&h=500&fit=crop',
isPremium: false
},
{
id: 4,
name: 'Aishwarya Reddy',
userId: 'TK52589D',
lastSeen: '17 Nov 25',
age: 26,
height: '5\'5"',
religion: 'Hindu / Reddy / Telangana',
education: 'CA, Chartered Accountant',
location: 'Hyderabad, Telangana',
image: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=400&h=500&fit=crop',
isPremium: true
},
{
id: 5,
name: 'Karthik Mohan',
userId: 'TK52590E',
lastSeen: '18 Nov 25',
age: 27,
height: '5\'8"',
religion: 'Hindu / Nadar / Tamil',
education: 'M.Tech, Civil Engineer',
location: 'Madurai, Tamil Nadu',
image: 'https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=400&h=500&fit=crop',
isPremium: false
},
{
id: 6,
name: 'Divya Lakshmi',
userId: 'TK52591F',
lastSeen: '19 Nov 25',
age: 24,
height: '5\'3"',
religion: 'Hindu / Pillai / Tamil',
education: 'B.Com, HR Executive',
location: 'Coimbatore, Tamil Nadu',
image: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=400&h=500&fit=crop',
isPremium: true
}
];
// Profile Card Component
import { useDispatch } from 'react-redux';
import { updateFilter } from "../../redux/filterSlice";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
const ProfileCard = ({ profile }) => {
const [isLiked, setIsLiked] = useState(false);
const navigate = useNavigate();
const queryClient = useQueryClient();
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
useEffect(() => {
setIsShortlisted(profile?.is_shortlisted === 1);
}, [profile?.is_shortlisted]);
const shortlistMutation = useMutation({
mutationFn: shortlistProfile,
onMutate: () => {
setIsShortlisted((prev) => !prev);
},
onSuccess: (data) => {
toast.success(data.message || "Profile shortlisted successfully.");
// Invalidating queries will refetch data and update all profile cards simultaneously
queryClient.invalidateQueries();
},
onError: (error) => {
setIsShortlisted(profile?.is_shortlisted === 1);
toast.error(error.message || "Failed to update shortlist status.");
}
});
const interestMutation = useMutation({
mutationFn: sendInterest,
onSuccess: (data) => {
toast.success(data.message || "Interest sent successfully.");
queryClient.invalidateQueries();
},
onError: (error) => {
toast.error(error.message || "Failed to send interest.");
}
});
const declineMutation = useMutation({
mutationFn: declineProfile,
onSuccess: (data) => {
toast.success(data.message || "Profile declined.");
queryClient.invalidateQueries();
},
onError: (error) => {
toast.error(error.message || "Failed to decline profile.");
}
});
const id = profile.id;
const image = profile.photo || profile.image;
const name = profile.name || "Unknown";
const idNumber = profile.member_id || profile.userId || "N/A";
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
const age = profile.age ? `${profile.age} yrs` : null;
const height = profile.height || null;
const salary = profile.annual_income_name || profile.salary || null;
const location = profile.district_name || profile.location || null;
const caste = profile.caste_name || profile.caste || null;
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
const zodiac2 = profile.star_name || profile.zodiac2 || null;
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : profile.isPremium;
const NewJoinedProfile = ({ profiles }) => {
const swiperRef = useRef(null);
const navigate = useNavigate();
const dispatch = useDispatch();
const displayProfiles = profiles || [];
if (displayProfiles.length === 0) return null;
return (
<motion.div
@ -118,166 +98,95 @@ const ProfileCard = ({ profile }) => {
whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
onClick={() => navigate(`/profile-details/${profile.id}`)}
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
onClick={() => navigate(`/profile-details/${id}`)}
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
>
{/* IMAGE SECTION */}
<div className="relative">
{profile.isPremium && (
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ delay: 0.2, type: 'spring' }}
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
>
<Crown className="w-5 h-5 text-white" />
</motion.div>
<img
src={image}
alt="profile"
className="w-full h-[320px] object-cover"
onError={(e) => {
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
}}
/>
{isPremium && (
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
<Crown size={18} color="#fff" />
</div>
)}
<motion.button
whileHover={{ scale: 1 }}
whileTap={{ scale: 0.9 }}
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"
<div
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
onClick={(e) => {
e.stopPropagation();
if (!shortlistMutation.isPending) {
shortlistMutation.mutate(id);
}
}}
>
<Bookmark className="w-4 h-4" />
<span className="text-[12px] font-medium">Shortlist</span>
</motion.button>
<div
className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
style={{ height: "300px" }}
>
<img
src={profile.image}
alt={profile.name}
className="w-full h-full object-cover"
/>
</div>
<div
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
style={{
background:
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
}}
></div>
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
<h1 className="text-[18px] text-green-900 font-bold mb-2">
{profile.name}
</h1>
<p className="text-[14px] text-gray-700 leading-relaxed">
Matrimony ID: {profile.userId}
</p>
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
{shortlistMutation.isPending ? "..." : "Shortlist"}
</div>
</div>
<div
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
style={{ background: "rgb(255, 255, 255)" }}
>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<CakeIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
Age : {profile.age}
</span>
</div>
{/* CONTENT */}
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
<h2 className="text-center text-[22px] font-semibold mb-1">
{name}
</h2>
<div className="flex items-center gap-2">
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
Height: {profile.height}
</span>
</div>
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
<p>ID: {idNumber}</p>
<p className="flex items-center gap-0.5">
<Eye size={12} /> {lastSeen}
</p>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<GroupsIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.religion}
</span>
</div>
<div className="flex flex-wrap justify-center gap-2">
{[
age,
height,
salary,
location,
caste,
zodiac1,
zodiac2,
]
.filter(Boolean)
.map((v, i) => (
<span
key={i}
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
>
{v}
</span>
))}
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<SchoolIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.education}
</span>
</div>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<LocationOnIcon className="w-4 h-4 text-gray-700" />
<span className="text-[14px] font-600 text-gray-900">
{profile.location}
</span>
</div>
</div>
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
<button
<div className="flex gap-4 mt-[15px] justify-center">
<button
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
onClick={(e) => {
e.stopPropagation();
if (!declineMutation.isPending) declineMutation.mutate(id);
}}
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
font-semibold text-base py-2 rounded-[20px] shadow-md
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
disabled={declineMutation.isPending}
>
<svg
className="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path
d="M18 6L6 18M6 6l12 12"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Decline
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
</button>
<button
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
transform hover:scale-105 flex items-center justify-center"
<button
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
onClick={(e) => {
e.stopPropagation();
setIsLiked(!isLiked);
if (!interestMutation.isPending) interestMutation.mutate(id);
}}
disabled={interestMutation.isPending}
>
{isLiked ? (
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
fill="#EF4444"
/>
</svg>
) : (
<svg
className="w-4 h-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
Interest
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
</button>
</div>
</div>
@ -301,10 +210,10 @@ const ProfileCard = ({ profile }) => {
animate={{ opacity: 1, y: 0 }}
className="text-center mb-12"
>
<h1 className="text-[20px] text-[#034E08] sm:text-[22px] lg:text-[24px] font-bold mb-3">
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
New Joined
</h1>
<p className="text-gray-600 text-[12px]">Find your perfect match today</p>
<p className="text-gray-900 text-[12px]">Find your perfect match today</p>
</motion.div>
{/* Swiper Container */}
@ -319,10 +228,6 @@ const ProfileCard = ({ profile }) => {
disableOnInteraction: false,
pauseOnMouseEnter: true
}}
pagination={{
clickable: true,
dynamicBullets: true
}}
loop={true}
speed={800}
breakpoints={{
@ -341,8 +246,8 @@ const ProfileCard = ({ profile }) => {
}}
className="pb-16"
>
{profiles.map((profile) => (
<SwiperSlide key={profile.id}>
{displayProfiles.map((profile, index) => (
<SwiperSlide key={profile.id || index}>
<ProfileCard profile={profile} />
</SwiperSlide>
))}
@ -383,8 +288,12 @@ const ProfileCard = ({ profile }) => {
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
onClick={() => {
dispatch(updateFilter({ filter_type: "newly_joined" }));
navigate("/matches", { state: { activeTab: "newly_joined", filter_type: "newly_joined" } });
}}
>
View All
View All Matches
</motion.button>
</motion.div>
</div>

View File

@ -9,7 +9,7 @@ import { useNavigate } from "react-router-dom";
import AstroChatUI from "./AstroChatUI";
import MembershipCard from "./MembershipCard";
const ProfileCompletion = ({ percentage = 0, missingDetails }) => {
const ProfileCompletion = ({ percentage = 0, missingDetails,becomePaidMember }) => {
const navigate = useNavigate();
const cards = [
@ -167,7 +167,7 @@ const ProfileCompletion = ({ percentage = 0, missingDetails }) => {
))}
</motion.div>
<MembershipCard />
<MembershipCard becomePaidMember={becomePaidMember} />
</div>
</div>

View File

@ -2,113 +2,31 @@ import React, { useState, useRef } from 'react';
import { motion } from 'framer-motion';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay } from 'swiper/modules';
import { Play, X, Heart, Share2, Eye, ChevronLeft, ChevronRight } from 'lucide-react';
import weddingImg1 from "../../assets/images/wedding6.jpeg";
import weddingImg2 from "../../assets/images/wedding8.jpg";
import weddingImg3 from "../../assets/images/wedding6.jpeg";
import { Play, X, ChevronLeft, ChevronRight } from 'lucide-react';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
const VideoSwiperGallery = () => {
const VideoSwiperGallery = ({ videos }) => {
const [selectedVideo, setSelectedVideo] = useState(null);
const swiperRef = useRef(null);
// Video data with online sources
const videos = [
{
id: 1,
title: 'Priya & Rahul - Wedding Story',
thumbnail: weddingImg1,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
views: '2.4K',
likes: '142',
duration: '3:45'
},
{
id: 2,
title: 'Aisha - Profile Introduction',
thumbnail: weddingImg2,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
views: '1.8K',
likes: '98',
duration: '2:30'
},
{
id: 3,
title: 'Rohan - Life Journey',
thumbnail: weddingImg3,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
views: '3.2K',
likes: '256',
duration: '4:15'
},
{
id: 4,
title: 'Divya - Family Values',
thumbnail: weddingImg1,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4',
views: '1.5K',
likes: '87',
duration: '3:00'
},
{
id: 5,
title: 'Karthik & Meera - First Meet',
thumbnail: weddingImg2,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4',
views: '4.1K',
likes: '312',
duration: '5:20'
},
{
id: 6,
title: 'Sneha - Hobbies & Interests',
thumbnail: weddingImg3,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4',
views: '2.7K',
likes: '178',
duration: '2:45'
},
{
id: 7,
title: 'Arjun - Career & Dreams',
thumbnail: weddingImg1,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4',
views: '1.9K',
likes: '134',
duration: '3:30'
},
{
id: 8,
title: 'Lakshmi - Traditional Values',
thumbnail: weddingImg2,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/Sintel.mp4',
views: '3.5K',
likes: '267',
duration: '4:00'
},
{
id: 9,
title: 'Vikram - Adventure Life',
thumbnail: weddingImg3,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
views: '5.2K',
likes: '423',
duration: '4:30'
},
{
id: 10,
title: 'Anjali - Creative Journey',
thumbnail: weddingImg1,
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
views: '3.8K',
likes: '289',
duration: '3:15'
const displayVideos = videos || [];
if (displayVideos.length === 0) return null;
const getEmbedUrl = (url) => {
if (!url) return '';
let embedUrl = url;
if (url.includes('youtu.be/')) {
embedUrl = url.replace('youtu.be/', 'www.youtube.com/embed/');
} else if (url.includes('youtube.com/watch?v=')) {
embedUrl = url.replace('youtube.com/watch?v=', 'youtube.com/embed/');
}
];
return embedUrl.split('?')[0] + '?autoplay=1';
};
const VideoCard = ({ video }) => {
const [isHovered, setIsHovered] = useState(false);
@ -129,12 +47,12 @@ const VideoSwiperGallery = () => {
<div className="relative aspect-video">
<img
src={video.thumbnail}
alt={video.title}
alt="Video Thumbnail"
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
/>
{/* Overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent opacity-60 group-hover:opacity-80 transition-opacity" />
<div className="absolute inset-0 bg-black/30 group-hover:bg-black/50 transition-colors" />
{/* Play Button */}
<motion.div
@ -146,28 +64,6 @@ const VideoSwiperGallery = () => {
<Play className="w-7 h-7 text-[#034E08] group-hover:text-white ml-1" fill="currentColor" />
</div>
</motion.div>
{/* Duration Badge */}
<div className="absolute top-2 right-2 bg-black/70 text-white text-xs font-semibold px-2 py-1 rounded">
{video.duration}
</div>
{/* Stats at bottom */}
<div className="absolute bottom-0 left-0 right-0 p-3">
<h3 className="text-white font-semibold text-sm mb-2 line-clamp-2">
{video.title}
</h3>
<div className="flex items-center gap-3 text-white/90 text-xs">
<div className="flex items-center gap-1">
<Eye className="w-3 h-3" />
<span>{video.views}</span>
</div>
<div className="flex items-center gap-1">
<Heart className="w-3 h-3" />
<span>{video.likes}</span>
</div>
</div>
</div>
</div>
</div>
</motion.div>
@ -202,36 +98,13 @@ const VideoSwiperGallery = () => {
</button>
{/* Video Player */}
<div className="bg-[#034E08] rounded-2xl overflow-y-scroll h-[100%] max-h-[480px] shadow-2xl">
<video
src={selectedVideo.videoUrl}
controls
autoPlay
<div className="bg-black rounded-2xl overflow-hidden shadow-2xl">
<iframe
src={getEmbedUrl(selectedVideo.youtube_url)}
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
className="w-full aspect-video"
>
Your browser does not support the video tag.
</video>
{/* Video Info */}
<div className="bg-gray-900 p-6">
<h2 className="text-white text-2xl font-bold mb-3">
{selectedVideo.title}
</h2>
<div className="flex items-center gap-6 text-gray-300">
<div className="flex items-center gap-2">
<Eye className="w-5 h-5" />
<span>{selectedVideo.views} views</span>
</div>
<div className="flex items-center gap-2">
<Heart className="w-5 h-5" />
<span>{selectedVideo.likes} likes</span>
</div>
<button className="flex items-center gap-2 ml-auto bg-[#034E08] hover:bg-green-700 text-white px-6 py-2 rounded-full transition-colors">
<Share2 className="w-4 h-4" />
<span>Share</span>
</button>
</div>
</div>
></iframe>
</div>
</motion.div>
</motion.div>
@ -290,7 +163,7 @@ const VideoSwiperGallery = () => {
}}
className="pb-16"
>
{videos.map((video) => (
{displayVideos.map((video) => (
<SwiperSlide key={video.id}>
<VideoCard video={video} />
</SwiperSlide>

View File

@ -1,73 +1,222 @@
import React from "react";
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react";
// Import your images
import Profile1 from "../../assets/images/bride1.jpg";
import Profile2 from "../../assets/images/bride2.jpg";
import Profile3 from "../../assets/images/bride3.jpg";
import Profile4 from "../../assets/images/bride4.jpg";
import React, { useState, useEffect } from "react";
import { Heart, X, Crown, Bookmark, Eye, Clock, ChevronLeft, ChevronRight } from "lucide-react";
import { Swiper, SwiperSlide } from "swiper/react";
import { Navigation, Pagination } from "swiper/modules";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import { motion } from "framer-motion";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
import { useNavigate } from "react-router-dom";
const ProfileCardItem = ({ profile }) => {
const navigate = useNavigate();
const queryClient = useQueryClient();
const [isShortlisted, setIsShortlisted] = useState(profile?.is_shortlisted === 1);
export default function ProfileCard() {
// Sample data for multiple cards with image paths
const profiles = [
{
id: 1,
name: "Jerome Bell",
idNumber: "KI2847596",
lastSeen: "4 Nov 2025",
salary: "5-10",
age: "22 yrs",
height: "5'2\"",
location: "Chennai",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image: Profile1,
useEffect(() => {
setIsShortlisted(profile?.is_shortlisted === 1);
}, [profile?.is_shortlisted]);
const shortlistMutation = useMutation({
mutationFn: shortlistProfile,
onMutate: () => {
setIsShortlisted((prev) => !prev);
},
{
id: 2,
name: "Neha Singh",
idNumber: "KI2847597",
lastSeen: "5 Nov 2025",
salary: "8-12",
age: "26 yrs",
height: "5'6\"",
location: "hyderabad",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image: Profile2,
onSuccess: (data) => {
toast.success(data.message || "Profile shortlisted successfully.");
queryClient.invalidateQueries();
},
{
id: 3,
name: "Priya Sharma",
idNumber: "KI2847598",
lastSeen: "3 Nov 2025",
salary: "6-11",
age: "24 yrs",
height: "5'4\"",
location: "Mumbai",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image: Profile3,
onError: (error) => {
setIsShortlisted(profile?.is_shortlisted === 1);
toast.error(error.message || "Failed to update shortlist status.");
}
});
const interestMutation = useMutation({
mutationFn: sendInterest,
onSuccess: (data) => {
toast.success(data.message || "Interest sent successfully.");
queryClient.invalidateQueries();
},
{
id: 4,
name: "Kavya Iyer",
idNumber: "KI2847599",
lastSeen: "2 Nov 2025",
salary: "7-10",
age: "23 yrs",
height: "5'3\"",
location: "Bangalore",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image: Profile4,
onError: (error) => {
toast.error(error.message || "Failed to send interest.");
}
});
const declineMutation = useMutation({
mutationFn: declineProfile,
onSuccess: (data) => {
toast.success(data.message || "Profile declined.");
queryClient.invalidateQueries();
},
];
onError: (error) => {
toast.error(error.message || "Failed to decline profile.");
}
});
const id = profile.id;
const image = profile.photo || profile.image;
const name = profile.name || "Unknown";
const idNumber = profile.member_id || profile.idNumber || "N/A";
const lastSeen = profile.last_seen_at || profile.lastSeen || "Recently";
const age = profile.age ? `${profile.age} yrs` : null;
const height = profile.height || null;
const salary = profile.annual_income_name || (profile.salary ? `${profile.salary} LPA` : null);
const location = profile.district_name || profile.location || null;
const caste = profile.caste_name || profile.caste || null;
const zodiac1 = profile.raasi_name || profile.zodiac1 || null;
const zodiac2 = profile.star_name || profile.zodiac2 || null;
const isPremium = profile.is_paid_member !== undefined ? profile.is_paid_member === 1 : true;
return (
<div
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
onClick={() => navigate(`/profile-details/${id}`)}
>
{/* IMAGE SECTION */}
<div className="relative">
<img
src={image}
alt="profile"
className="w-full h-[320px] object-cover"
onError={(e) => {
e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
}}
/>
{isPremium && (
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
<Crown size={18} color="#fff" />
</div>
)}
<div
className={`absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg hover:bg-gray-50 transition-colors ${shortlistMutation.isPending ? "opacity-50 cursor-wait" : "cursor-pointer"}`}
onClick={(e) => {
e.stopPropagation();
if (!shortlistMutation.isPending) shortlistMutation.mutate(id);
}}
>
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
{shortlistMutation.isPending ? "..." : "Shortlist"}
</div>
</div>
{/* CONTENT */}
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
<h2 className="text-center text-[22px] font-semibold mb-1">
{name}
</h2>
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
<p>ID: {idNumber}</p>
<p className="flex items-center gap-0.5">
<Eye size={12} /> {lastSeen}
</p>
</div>
<div className="flex flex-wrap justify-center gap-2">
{[
age,
height,
salary,
location,
caste,
zodiac1,
zodiac2,
]
.filter(Boolean)
.map((v, i) => (
<span
key={i}
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
>
{v}
</span>
))}
</div>
<div className="flex gap-4 mt-[15px] justify-center">
<button
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
onClick={(e) => {
e.stopPropagation();
if (!declineMutation.isPending) declineMutation.mutate(id);
}}
disabled={declineMutation.isPending}
>
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
</button>
<button
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
onClick={(e) => {
e.stopPropagation();
if (!interestMutation.isPending) interestMutation.mutate(id);
}}
disabled={interestMutation.isPending}
>
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
</button>
</div>
</div>
</div>
);
};
export default function ProfileCard({ profiles }) {
const displayProfiles = profiles || [];
const [showTimer, setShowTimer] = useState(false);
const [timeLeft, setTimeLeft] = useState(24 * 60 * 60); // 24 hours in seconds
useEffect(() => {
const storedEndTime = localStorage.getItem("profileTimerEnd");
if (storedEndTime) {
const endTime = parseInt(storedEndTime, 10);
const now = Date.now();
if (endTime > now) {
setShowTimer(true);
setTimeLeft(Math.floor((endTime - now) / 1000));
} else {
localStorage.removeItem("profileTimerEnd");
}
}
}, []);
useEffect(() => {
let timer;
if (showTimer && timeLeft > 0) {
timer = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
setShowTimer(false);
localStorage.removeItem("profileTimerEnd");
return 0;
}
return prev - 1;
});
}, 1000);
}
return () => clearInterval(timer);
}, [showTimer, timeLeft]);
const handleShowTimer = () => {
const endTime = Date.now() + 24 * 60 * 60 * 1000; // 24 hours from now
localStorage.setItem("profileTimerEnd", endTime.toString());
setTimeLeft(24 * 60 * 60);
setShowTimer(true);
};
const formatTime = (seconds) => {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = seconds % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
};
return (
<div className="h-auto py-8 px-4">
@ -90,73 +239,73 @@ export default function ProfileCard() {
{/* CARDS GRID */}
<div className="flex justify-center">
<div className="w-full max-w-[1400px] grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
{profiles.map((profile) => (
<div
key={profile.id}
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md"
<div className="w-full max-w-[1400px] relative px-2 md:px-12">
{!showTimer ? (
<>
{/* Custom Navigation Arrows */}
<button className="profile-swiper-prev absolute left-0 md:left-2 top-[40%] -translate-y-1/2 z-20 w-10 h-10 bg-white shadow-xl border border-gray-100 rounded-full flex items-center justify-center text-[#8b0000] hover:bg-[#8b0000] hover:text-white transition-all disabled:opacity-30 disabled:cursor-not-allowed">
<ChevronLeft size={24} />
</button>
<button className="profile-swiper-next absolute right-0 md:right-2 top-[40%] -translate-y-1/2 z-20 w-10 h-10 bg-white shadow-xl border border-gray-100 rounded-full flex items-center justify-center text-[#8b0000] hover:bg-[#8b0000] hover:text-white transition-all disabled:opacity-30 disabled:cursor-not-allowed">
<ChevronRight size={24} />
</button>
<Swiper
modules={[Navigation, Pagination]}
spaceBetween={24}
slidesPerView={1}
navigation={{
prevEl: '.profile-swiper-prev',
nextEl: '.profile-swiper-next',
}}
pagination={{ clickable: true }}
breakpoints={{
768: { slidesPerView: 2 },
1024: { slidesPerView: 3 },
1280: { slidesPerView: 4 },
}}
onReachEnd={() => {
if (displayProfiles.length > 0) {
setTimeout(handleShowTimer, 2500); // Wait 2.5s on the last slide before hiding
}
}}
className="pb-12"
>
{/* IMAGE SECTION */}
<div className="relative">
<img
src={profile.image}
alt="profile"
className="w-full h-[320px] object-cover"
/>
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center">
<Crown size={18} color="#fff" />
</div>
<div className="absolute top-4 right-4 px-3 py-1.5 rounded-[20px] bg-white flex items-center gap-1.5 text-[13px] font-medium shadow-lg">
<Bookmark size={14} /> Shortlist
</div>
</div>
{/* CONTENT */}
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
<h2 className="text-center text-[22px] font-semibold mb-1">
{profile.name}
</h2>
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
<p>ID: {profile.idNumber}</p>
<p className="flex items-center gap-0.5">
<Eye size={12} /> {profile.lastSeen}
</p>
</div>
<div className="flex flex-wrap justify-center gap-2">
{[
profile.age,
profile.height,
profile.salary + " LPA",
profile.location,
profile.caste,
profile.zodiac1,
profile.zodiac2,
].map((v, i) => (
<span
key={i}
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
>
{v}
</span>
))}
</div>
<div className="flex gap-4 mt-[15px] justify-center">
<button className="px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900">
<X size={18} /> Decline
</button>
<button className="px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold">
<Heart size={18} /> Interest
{displayProfiles.map((profile, index) => (
<SwiperSlide key={profile.id || index}>
<ProfileCardItem profile={profile} />
</SwiperSlide>
))}
{/* END SLIDE (BUFFER) */}
<SwiperSlide>
<div className="w-full h-full min-h-[400px] flex flex-col items-center justify-center bg-white rounded-[28px] shadow-md border border-gray-100 p-6">
<h3 className="text-xl font-bold mb-4 text-gray-800 text-center">You've seen all matches!</h3>
<button
onClick={handleShowTimer}
className="px-6 py-2 bg-[#8b0000] text-white rounded-full font-semibold shadow-lg hover:bg-red-800 transition"
>
View Next Batch Timer
</button>
</div>
</SwiperSlide>
</Swiper>
</>
) : (
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
className="flex flex-col items-center justify-center bg-white rounded-[28px] shadow-xl max-w-lg mx-auto py-12 px-6 border border-gray-100"
>
<Clock size={48} className="text-[#8b0000] mb-4" />
<h2 className="text-2xl font-bold mb-2 text-gray-800 text-center">Next Recommendations In</h2>
<div className="text-5xl font-extrabold text-[#8b0000] tracking-widest my-4 tabular-nums">
{formatTime(timeLeft)}
</div>
</div>
))}
<p className="text-gray-500 text-center mb-6">
We are curating the best matches for you. Please check back when the timer ends!
</p>
</motion.div>
)}
</div>
</div>
</div>

View File

@ -12,7 +12,7 @@ import weddingpromo3 from "../assets/images/weddingpromo3.jpg";
import weddingpromo4 from "../assets/images/weddingpromo4.jpg";
import { useDashboardQuery } from "../hooks/useDashboardQuery";
const NewJoinedProfile = lazy(() => import("../components/profiledashboard/NewJoinedProfile"));
const ProfileCompletion = lazy(() => import("../components/profiledashboard/ProfileCompletion"));
const MatrimonyArticles = lazy(() => import("../components/profiledashboard/MatrimonyArticles"));
const MatchingList = lazy(() => import("../components/profiledashboard/MatchingList"));
@ -225,6 +225,7 @@ const UserDashboardHome = () => {
<ProfileCompletion
percentage={dashboardData?.profile_complete_percentage}
missingDetails={dashboardData?.non_filled_sections}
becomePaidMember={dashboardData?.become_paid_member}
/>
</Suspense>
{/* <DailyRecommendedCard/> */}
@ -235,9 +236,9 @@ const UserDashboardHome = () => {
<Suspense fallback={<SectionFallback height={320} />}>
<MatchingList matches={dashboardData?.all_matches} />
</Suspense>
{/* <PaidMemberCard/> */}
{/* <NewJoinedProfile/> */}
<Suspense fallback={<SectionFallback height={320} />}>
<NewJoinedProfile profiles={dashboardData?.new_joined} />
</Suspense>
{/* <CustomerSupportCard/> */}
<Suspense fallback={<SectionFallback height={240} />}>
<VideoSwiperGallery videos={dashboardData?.youtube_videos} />

View File

@ -0,0 +1,26 @@
import axiosInstance from "../api/axiosInstance";
import { API_ENDPOINTS } from "../api/apiEndpoints";
export const shortlistProfile = async (profileId) => {
const response = await axiosInstance.post(`${API_ENDPOINTS.SHORTLIST_API}?profile_id=${profileId}`);
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to shortlist");
}
return response.data;
};
export const sendInterest = async (profileId) => {
const response = await axiosInstance.post(`interest?profile_id=${profileId}`);
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to send interest");
}
return response.data;
};
export const declineProfile = async (profileId) => {
const response = await axiosInstance.post(`decline?profile_id=${profileId}`);
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to decline profile");
}
return response.data;
};