matrimony
This commit is contained in:
parent
cd880e10e5
commit
68f97c40dc
@ -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;
|
||||
}
|
||||
BIN
src/assets/images/kiridam.png
Normal file
BIN
src/assets/images/kiridam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 569 B |
@ -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>
|
||||
|
||||
@ -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]"
|
||||
>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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} />
|
||||
|
||||
26
src/services/shortlistapi.js
Normal file
26
src/services/shortlistapi.js
Normal 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;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user