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); const res = await axiosInstance.get(API_ENDPOINTS.PREVIEW_DETAILS);
return res.data; 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 { API_ENDPOINTS } from "../../api/apiEndpoints";
import { useMutation, useQuery } from "@tanstack/react-query"; import { useMutation, useQuery } from "@tanstack/react-query";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { getHeaderDetails } from "../../api/preview.api";
const NAV_LINKS = [ const NAV_LINKS = [
// { label: "Home", path: "/" }, // { label: "Home", path: "/" },
{ label: "Matches", path: "/matches" }, { 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) => { const handleMenuClick = (item) => {
if (item.action === "delete") { if (item.action === "delete") {
setDeleteModalOpen(true); setDeleteModalOpen(true);
@ -381,7 +389,7 @@ const ProfileHeader = () => {
<Box sx={{ flexGrow: 0 }}> <Box sx={{ flexGrow: 0 }}>
<Tooltip title="Account Menu"> <Tooltip title="Account Menu">
<IconButton onClick={toggleProfileDrawer(true)}> <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> </IconButton>

View File

@ -34,6 +34,7 @@ export default function MatchesInterface() {
const filterType = filters.filter_type; const filterType = filters.filter_type;
const selectedTab = filterType || "all_matches"; const selectedTab = filterType || "all_matches";
const isPaidMember = filters.isPaidMember; const isPaidMember = filters.isPaidMember;
const { ref, inView } = useInView({ const { ref, inView } = useInView({
threshold: 0, threshold: 0,
rootMargin: "300px" rootMargin: "300px"
@ -167,7 +168,7 @@ useEffect(() => {
<div className="w-full md:w-80"> <div className="w-full md:w-80">
<div <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]" 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 { motion } from "framer-motion";
import { Swiper, SwiperSlide } from "swiper/react"; import { Swiper, SwiperSlide } from "swiper/react";
import { import {
@ -10,141 +10,84 @@ import {
import { import {
Crown, Crown,
Bookmark, Bookmark,
User,
Briefcase,
MapPin,
X, X,
Send,
ChevronLeft, ChevronLeft,
ChevronRight, ChevronRight,
Heart,
Eye,
} from "lucide-react"; } 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 styles
import "swiper/css"; import "swiper/css";
import "swiper/css/navigation"; import "swiper/css/navigation";
import "swiper/css/pagination"; import "swiper/css/pagination";
import "swiper/css/effect-coverflow"; import "swiper/css/effect-coverflow";
import { useNavigate } from "react-router-dom"; 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 ProfileCard = ({ profile }) => {
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 navigate = useNavigate(); 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 ( return (
<motion.div <motion.div
@ -152,140 +95,110 @@ const MatchingList = () => {
whileInView={{ opacity: 1, scale: 1 }} whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
onClick={() => navigate(`/profile-details/${profile.id}`)} onClick={() => navigate(`/profile-details/${id}`)}
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-2 border-gray-200" className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
> >
{/* Profile Image Section */} {/* IMAGE SECTION */}
<div className="relative"> <div className="relative">
{/* Premium Badge */} <img
{profile.isPremium && ( src={image}
<motion.div alt="profile"
initial={{ scale: 0 }} className="w-full h-[320px] object-cover"
animate={{ scale: 1 }} onError={(e) => {
transition={{ delay: 0.2, type: "spring" }} e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
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> {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 */} <div
<motion.button 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"}`}
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"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
// shortlist logic if (!shortlistMutation.isPending) {
shortlistMutation.mutate(id);
}
}} }}
> >
<Bookmark className="w-4 h-4" /> <Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
<span className="text-[12px] font-medium">Shortlist</span> {shortlistMutation.isPending ? "..." : "Shortlist"}
</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>
</div> </div>
</div> </div>
{/* Stats and Follow Section */} {/* CONTENT */}
<div <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]">
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2 " <h2 className="text-center text-[22px] font-semibold mb-1">
style={{ {name}
background: "rgb(255, 255, 255)", </h2>
}}
>
<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>
<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>
<div className="flex items-center gap-4"> <div className="flex flex-wrap justify-center gap-2">
<div className="flex items-center gap-2"> {[
<AccountBalanceWalletIcon className="w-4 h-4 text-gray-700" /> age,
<span className="text-[14px] font-600 text-gray-900"> height,
{profile.salary} salary,
</span> location,
</div> caste,
<div className="flex items-center gap-2"> zodiac1,
<LocationOnIcon className="w-4 h-4 text-gray-700" /> zodiac2,
<span className="text-[14px] font-600 text-gray-900"> ]
{profile.location} .filter(Boolean)
</span> .map((v, i) => (
</div> <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>
<div className="flex items-center gap-4"> <div className="flex gap-4 mt-[15px] justify-center">
<div className="flex items-center gap-2"> <button
<TempleHinduIcon className="w-4 h-4 text-gray-700" /> 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" : ""}`}
<span className="text-[14px] font-600 text-gray-900"> onClick={(e) => {
{profile.caste} e.stopPropagation();
</span> if (!declineMutation.isPending) declineMutation.mutate(id);
</div> }}
<div className="flex items-center gap-2"> disabled={declineMutation.isPending}
<img src={Image} alt="" className="w-4 h-4 text-gray-700" /> >
<span className="text-[14px] font-600 text-gray-900"> <X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
{profile.zodiac1} </button>
</span>
</div> <button
</div> 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" : ""}`}
<div className="flex items-center gap-4"> onClick={(e) => {
<div className="flex items-center gap-2"> e.stopPropagation();
<img src={Image1} alt="" className="w-4 h-4 text-gray-700" /> if (!interestMutation.isPending) interestMutation.mutate(id);
<span className="text-[14px] font-600 text-gray-900"> }}
{profile.zodiac2} disabled={interestMutation.isPending}
</span> >
</div> <Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
</button>
</div> </div>
</div> </div>
</motion.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 ( return (
<> <>
<div <div
@ -341,8 +254,8 @@ const MatchingList = () => {
}} }}
className="pb-16" className="pb-16"
> >
{profiles.map((profile) => ( {displayProfiles.map((profile, index) => (
<SwiperSlide key={profile.id}> <SwiperSlide key={profile.id || index}>
<ProfileCard profile={profile} /> <ProfileCard profile={profile} />
</SwiperSlide> </SwiperSlide>
))} ))}
@ -383,7 +296,10 @@ const MatchingList = () => {
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} 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" 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 View All Matches
</motion.button> </motion.button>

View File

@ -5,48 +5,13 @@ import "swiper/css/navigation";
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react'; import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react';
import { useRef } from "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 MatrimonyArticles = ({ articles }) => {
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 swiperRef = useRef(null); const swiperRef = useRef(null);
const displayArticles = articles || [];
if (displayArticles.length === 0) return null;
return ( return (
<> <>
<div className="custom-article-swiper py-10 px-2 max-w-[1400px] mx-auto my-10"> <div className="custom-article-swiper py-10 px-2 max-w-[1400px] mx-auto my-10">
@ -70,51 +35,19 @@ const MatrimonyArticles = () => {
loop={true} loop={true}
className="mySwiper" className="mySwiper"
> >
{articleData.map((item, index) => ( {displayArticles.map((item) => (
<SwiperSlide key={index}> <SwiperSlide key={item.id}>
<div className="w-full max-w-[600px] rounded-[10px] overflow-hidden border border-2 border-[#f2f2f2] bg-white"> <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="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 <img
src={item.img} src={item.image}
className="w-full h-88 object-cover" className="w-full h-full object-cover"
alt={item.title} alt={`Article ${item.id}`}
/> />
</div> </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>
</div> </div>

View File

@ -1,7 +1,19 @@
import { Phone, MessageCircle, ThumbsUp, Eye } from 'lucide-react'; import { Phone, MessageCircle, ThumbsUp, Eye } from 'lucide-react';
import promogirl from "../../assets/images/mobile.png" 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 ( return (
<> <>
@ -19,50 +31,31 @@ return (
{/* Subheading with offer */} {/* Subheading with offer */}
<p className="text-[16px] lg:text-[16px] text-gray-900 mb-4"> <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> </p>
{/* Features List */} {/* Features List */}
<div className="space-y-2 mb-2"> <div className="space-y-2 mb-2">
<div className="flex items-start gap-4"> {points.map((point, index) => {
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1"> const Icon = icons[index % icons.length];
<Phone className="w-5 h-5 text-gray-700" /> return (
</div> <div key={index} className="flex items-start gap-4">
<p className="text-[16px] lg:text-[16px] text-gray-900 font-medium"> <div className="w-4 h-4 flex-shrink-0 mt-0 py-1">
Call/WhatsApp matches <Icon className="w-5 h-5 text-gray-700" />
</p> </div>
</div> <p className="text-[16px] lg:text-[16px] text-gray-900 font-medium">
{point}
<div className="flex items-start gap-4"> </p>
<div className="w-4 h-4 flex-shrink-0 mt-0 py-1"> </div>
<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>
</div> </div>
{/* CTA Button */} {/* 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 See membership plans
</button> </button>
</div> </div>
@ -86,4 +79,3 @@ return (
</>); </>);
}; };
export default MembershipCard 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 { motion } from 'framer-motion';
import { Swiper, SwiperSlide } from 'swiper/react'; import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay, EffectCoverflow } from 'swiper/modules'; import { Navigation, Pagination, Autoplay, EffectCoverflow } from 'swiper/modules';
import { Crown, Bookmark, User, Briefcase, MapPin, X, Send, ChevronLeft, ChevronRight } from 'lucide-react'; import {
import CakeIcon from "@mui/icons-material/Cake"; Crown,
import HeightIcon from "@mui/icons-material/Height"; Bookmark,
import GroupsIcon from "@mui/icons-material/Groups"; X,
import TempleHinduIcon from "@mui/icons-material/TempleHindu"; ChevronLeft,
import SchoolIcon from "@mui/icons-material/School"; ChevronRight,
import LocationOnIcon from "@mui/icons-material/LocationOn"; Heart,
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew"; Eye,
import profilebg from "../../assets/images/profilebg.jpg"; } from 'lucide-react';
// Import Swiper styles // Import Swiper styles
import 'swiper/css'; import 'swiper/css';
import 'swiper/css/navigation'; import 'swiper/css/navigation';
import 'swiper/css/pagination'; import 'swiper/css/pagination';
import 'swiper/css/effect-coverflow'; import 'swiper/css/effect-coverflow';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';
const NewJoinedProfile = () => { import { updateFilter } from "../../redux/filterSlice";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import toast from "react-hot-toast";
const swiperRef = useRef(null); import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
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
const ProfileCard = ({ profile }) => { 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 ( return (
<motion.div <motion.div
@ -118,166 +98,95 @@ const ProfileCard = ({ profile }) => {
whileInView={{ opacity: 1, scale: 1 }} whileInView={{ opacity: 1, scale: 1 }}
viewport={{ once: true }} viewport={{ once: true }}
transition={{ duration: 0.5 }} transition={{ duration: 0.5 }}
onClick={() => navigate(`/profile-details/${profile.id}`)} onClick={() => navigate(`/profile-details/${id}`)}
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200" className="w-full rounded-[28px] overflow-hidden bg-white shadow-md cursor-pointer"
> >
{/* IMAGE SECTION */}
<div className="relative"> <div className="relative">
{profile.isPremium && ( <img
<motion.div src={image}
initial={{ scale: 0 }} alt="profile"
animate={{ scale: 1 }} className="w-full h-[320px] object-cover"
transition={{ delay: 0.2, type: 'spring' }} onError={(e) => {
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg" e.target.src = "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png";
> }}
<Crown className="w-5 h-5 text-white" /> />
</motion.div>
{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 <div
whileHover={{ scale: 1 }} 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"}`}
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"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (!shortlistMutation.isPending) {
shortlistMutation.mutate(id);
}
}} }}
> >
<Bookmark className="w-4 h-4" /> <Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
<span className="text-[12px] font-medium">Shortlist</span> {shortlistMutation.isPending ? "..." : "Shortlist"}
</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>
</div> </div>
</div> </div>
<div {/* CONTENT */}
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2" <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]">
style={{ background: "rgb(255, 255, 255)" }} <h2 className="text-center text-[22px] font-semibold mb-1">
> {name}
<div className="flex items-center gap-4"> </h2>
<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>
<div className="flex items-center gap-2"> <div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" /> <p>ID: {idNumber}</p>
<span className="text-[14px] font-600 text-gray-900"> <p className="flex items-center gap-0.5">
Height: {profile.height} <Eye size={12} /> {lastSeen}
</span> </p>
</div>
</div> </div>
<div className="flex items-center gap-4"> <div className="flex flex-wrap justify-center gap-2">
<div className="flex items-center gap-2"> {[
<GroupsIcon className="w-4 h-4 text-gray-700" /> age,
<span className="text-[14px] font-600 text-gray-900"> height,
{profile.religion} salary,
</span> location,
</div> 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>
<div className="flex items-center gap-4"> <div className="flex gap-4 mt-[15px] justify-center">
<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 <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) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
if (!declineMutation.isPending) declineMutation.mutate(id);
}} }}
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white disabled={declineMutation.isPending}
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"
> >
<svg <X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
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
</button> </button>
<button <button
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base 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" : ""}`}
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"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
setIsLiked(!isLiked); if (!interestMutation.isPending) interestMutation.mutate(id);
}} }}
disabled={interestMutation.isPending}
> >
{isLiked ? ( <Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
<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
</button> </button>
</div> </div>
</div> </div>
@ -301,10 +210,10 @@ const ProfileCard = ({ profile }) => {
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="text-center mb-12" 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 New Joined
</h1> </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> </motion.div>
{/* Swiper Container */} {/* Swiper Container */}
@ -319,10 +228,6 @@ const ProfileCard = ({ profile }) => {
disableOnInteraction: false, disableOnInteraction: false,
pauseOnMouseEnter: true pauseOnMouseEnter: true
}} }}
pagination={{
clickable: true,
dynamicBullets: true
}}
loop={true} loop={true}
speed={800} speed={800}
breakpoints={{ breakpoints={{
@ -341,8 +246,8 @@ const ProfileCard = ({ profile }) => {
}} }}
className="pb-16" className="pb-16"
> >
{profiles.map((profile) => ( {displayProfiles.map((profile, index) => (
<SwiperSlide key={profile.id}> <SwiperSlide key={profile.id || index}>
<ProfileCard profile={profile} /> <ProfileCard profile={profile} />
</SwiperSlide> </SwiperSlide>
))} ))}
@ -383,8 +288,12 @@ const ProfileCard = ({ profile }) => {
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} 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" 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.button>
</motion.div> </motion.div>
</div> </div>

View File

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

View File

@ -2,113 +2,31 @@ import React, { useState, useRef } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Swiper, SwiperSlide } from 'swiper/react'; import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay } from 'swiper/modules'; import { Navigation, Pagination, Autoplay } from 'swiper/modules';
import { Play, X, Heart, Share2, Eye, ChevronLeft, ChevronRight } from 'lucide-react'; import { Play, X, 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 Swiper styles // Import Swiper styles
import 'swiper/css'; import 'swiper/css';
import 'swiper/css/navigation'; import 'swiper/css/navigation';
import 'swiper/css/pagination'; import 'swiper/css/pagination';
const VideoSwiperGallery = () => { const VideoSwiperGallery = ({ videos }) => {
const [selectedVideo, setSelectedVideo] = useState(null); const [selectedVideo, setSelectedVideo] = useState(null);
const swiperRef = useRef(null); const swiperRef = useRef(null);
// Video data with online sources const displayVideos = videos || [];
const videos = [
{ if (displayVideos.length === 0) return null;
id: 1,
title: 'Priya & Rahul - Wedding Story', const getEmbedUrl = (url) => {
thumbnail: weddingImg1, if (!url) return '';
videoUrl: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', let embedUrl = url;
views: '2.4K', if (url.includes('youtu.be/')) {
likes: '142', embedUrl = url.replace('youtu.be/', 'www.youtube.com/embed/');
duration: '3:45' } else if (url.includes('youtube.com/watch?v=')) {
}, embedUrl = url.replace('youtube.com/watch?v=', 'youtube.com/embed/');
{
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'
} }
]; return embedUrl.split('?')[0] + '?autoplay=1';
};
const VideoCard = ({ video }) => { const VideoCard = ({ video }) => {
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
@ -129,12 +47,12 @@ const VideoSwiperGallery = () => {
<div className="relative aspect-video"> <div className="relative aspect-video">
<img <img
src={video.thumbnail} src={video.thumbnail}
alt={video.title} alt="Video Thumbnail"
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110" className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
/> />
{/* Overlay */} {/* 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 */} {/* Play Button */}
<motion.div <motion.div
@ -146,28 +64,6 @@ const VideoSwiperGallery = () => {
<Play className="w-7 h-7 text-[#034E08] group-hover:text-white ml-1" fill="currentColor" /> <Play className="w-7 h-7 text-[#034E08] group-hover:text-white ml-1" fill="currentColor" />
</div> </div>
</motion.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>
</div> </div>
</motion.div> </motion.div>
@ -202,36 +98,13 @@ const VideoSwiperGallery = () => {
</button> </button>
{/* Video Player */} {/* Video Player */}
<div className="bg-[#034E08] rounded-2xl overflow-y-scroll h-[100%] max-h-[480px] shadow-2xl"> <div className="bg-black rounded-2xl overflow-hidden shadow-2xl">
<video <iframe
src={selectedVideo.videoUrl} src={getEmbedUrl(selectedVideo.youtube_url)}
controls allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
autoPlay allowFullScreen
className="w-full aspect-video" className="w-full aspect-video"
> ></iframe>
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>
</div> </div>
</motion.div> </motion.div>
</motion.div> </motion.div>
@ -290,7 +163,7 @@ const VideoSwiperGallery = () => {
}} }}
className="pb-16" className="pb-16"
> >
{videos.map((video) => ( {displayVideos.map((video) => (
<SwiperSlide key={video.id}> <SwiperSlide key={video.id}>
<VideoCard video={video} /> <VideoCard video={video} />
</SwiperSlide> </SwiperSlide>

View File

@ -1,73 +1,222 @@
import React from "react"; import React, { useState, useEffect } from "react";
import { Heart, X, Crown, Bookmark, Eye } from "lucide-react"; import { Heart, X, Crown, Bookmark, Eye, Clock, ChevronLeft, ChevronRight } from "lucide-react";
// Import your images import { Swiper, SwiperSlide } from "swiper/react";
import Profile1 from "../../assets/images/bride1.jpg"; import { Navigation, Pagination } from "swiper/modules";
import Profile2 from "../../assets/images/bride2.jpg"; import "swiper/css";
import Profile3 from "../../assets/images/bride3.jpg"; import "swiper/css/navigation";
import Profile4 from "../../assets/images/bride4.jpg"; import "swiper/css/pagination";
import { motion } from "framer-motion"; 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() { useEffect(() => {
// Sample data for multiple cards with image paths setIsShortlisted(profile?.is_shortlisted === 1);
const profiles = [ }, [profile?.is_shortlisted]);
{
id: 1, const shortlistMutation = useMutation({
name: "Jerome Bell", mutationFn: shortlistProfile,
idNumber: "KI2847596", onMutate: () => {
lastSeen: "4 Nov 2025", setIsShortlisted((prev) => !prev);
salary: "5-10",
age: "22 yrs",
height: "5'2\"",
location: "Chennai",
caste: "Brahmin",
zodiac1: "Aries",
zodiac2: "Scorpio",
image: Profile1,
}, },
{ onSuccess: (data) => {
id: 2, toast.success(data.message || "Profile shortlisted successfully.");
name: "Neha Singh", queryClient.invalidateQueries();
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,
}, },
{ onError: (error) => {
id: 3, setIsShortlisted(profile?.is_shortlisted === 1);
name: "Priya Sharma", toast.error(error.message || "Failed to update shortlist status.");
idNumber: "KI2847598", }
lastSeen: "3 Nov 2025", });
salary: "6-11",
age: "24 yrs", const interestMutation = useMutation({
height: "5'4\"", mutationFn: sendInterest,
location: "Mumbai", onSuccess: (data) => {
caste: "Brahmin", toast.success(data.message || "Interest sent successfully.");
zodiac1: "Aries", queryClient.invalidateQueries();
zodiac2: "Scorpio",
image: Profile3,
}, },
{ onError: (error) => {
id: 4, toast.error(error.message || "Failed to send interest.");
name: "Kavya Iyer", }
idNumber: "KI2847599", });
lastSeen: "2 Nov 2025",
salary: "7-10", const declineMutation = useMutation({
age: "23 yrs", mutationFn: declineProfile,
height: "5'3\"", onSuccess: (data) => {
location: "Bangalore", toast.success(data.message || "Profile declined.");
caste: "Brahmin", queryClient.invalidateQueries();
zodiac1: "Aries",
zodiac2: "Scorpio",
image: Profile4,
}, },
]; 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 ( return (
<div className="h-auto py-8 px-4"> <div className="h-auto py-8 px-4">
@ -90,73 +239,73 @@ export default function ProfileCard() {
{/* CARDS GRID */} {/* CARDS GRID */}
<div className="flex justify-center"> <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"> <div className="w-full max-w-[1400px] relative px-2 md:px-12">
{profiles.map((profile) => ( {!showTimer ? (
<div <>
key={profile.id} {/* Custom Navigation Arrows */}
className="w-full rounded-[28px] overflow-hidden bg-white shadow-md" <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 */} {displayProfiles.map((profile, index) => (
<div className="relative"> <SwiperSlide key={profile.id || index}>
<img <ProfileCardItem profile={profile} />
src={profile.image} </SwiperSlide>
alt="profile" ))}
className="w-full h-[320px] object-cover" {/* 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">
<div className="absolute top-4 left-4 w-9 h-9 rounded-full bg-[#8b0000] flex items-center justify-center"> <h3 className="text-xl font-bold mb-4 text-gray-800 text-center">You've seen all matches!</h3>
<Crown size={18} color="#fff" /> <button
</div> onClick={handleShowTimer}
className="px-6 py-2 bg-[#8b0000] text-white rounded-full font-semibold shadow-lg hover:bg-red-800 transition"
<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 View Next Batch Timer
</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
</button> </button>
</div> </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>
</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> </div>
</div> </div>

View File

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