diff --git a/dist_new.zip b/dist_new.zip new file mode 100644 index 0000000..c0a6f13 Binary files /dev/null and b/dist_new.zip differ diff --git a/index.html b/index.html index 1edc375..d799b1d 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + @@ -14,6 +14,7 @@ /> thirukalyanam +
diff --git a/src/api/apiEndpoints.js b/src/api/apiEndpoints.js index 7400150..632f3e9 100644 --- a/src/api/apiEndpoints.js +++ b/src/api/apiEndpoints.js @@ -7,6 +7,7 @@ export const API_ENDPOINTS = { SUB_CASTE_MASTER: "get_sub_caste_masters", CITY_MASTER: "get_district_masters", STAR_MASTER: "get_star_masters", + PATHAM_MASTER: "get_patham_masters", MOBILE_SEND_OTP: "send_otp", MOBILE_VERIFY_OTP: "verify_otp", @@ -64,8 +65,18 @@ export const API_ENDPOINTS = { REPORT_PROFILE_LIST: "report_profile_list", PROFILE_DETAIL: "profiles/detail", INTEREST_LIST: "interest_lists", - UPDATE_INTEREST_STATUS: "update_interest_status", + UPDATE_INTEREST_STATUS: "interest_status_update", CHAT_LIST: "chat/lists", CHAT_MESSAGES: (id) => `chat/${id}/messages`, UNREAD_CHAT_COUNT: "chat/un_read_chat_count", + INTEREST_SEND: "interest_send", + DAILY_RECOMMENDED_DONT_SHOW: "daily_recomended-dont_show", + VIEW_CONTACT: "profiles/view_contact", + CHAT_CREATE: "chat/create", + SUBSCRIPTION_PLANS: "subscription-plans", + SUBSCRIPTION_PURCHASE_RAZORPAY: "subscription-purchase-razorpay", + SUBSCRIPTION_HISTORY: "subscription-history", }; + + + diff --git a/src/api/axiosInstance.js b/src/api/axiosInstance.js index bda533b..25383b6 100644 --- a/src/api/axiosInstance.js +++ b/src/api/axiosInstance.js @@ -7,8 +7,9 @@ import { API_ENDPOINTS } from "./apiEndpoints"; * and default headers for JSON communication. */ const axiosInstance = axios.create({ - baseURL: import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL || - "https://www.thirukalyanam.amrithaa.net/backend/api/" , + baseURL: (import.meta.env.DEV) + ? "/backend/api/" + : (import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL || "https://www.thirukalyanam.amrithaa.net/backend/api/"), headers: { "Content-Type": "application/json", }, @@ -19,7 +20,9 @@ const axiosInstance = axios.create({ * while sharing the same base URL and authorization mechanism. */ const apiForFiles = axios.create({ - baseURL: import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL, + baseURL: (import.meta.env.DEV) + ? "/backend/api/" + : (import.meta.env.VITE_THIRUKALYANAM_API_BASE_URL || "https://www.thirukalyanam.amrithaa.net/backend/api/"), headers: { "Content-Type": "multipart/form-data", }, @@ -123,8 +126,13 @@ export const clearUserData = () => { export const urlToFile = async (url, filename = "file") => { try { + // Rewrite URL to use proxy in development to avoid CORS + const finalUrl = (import.meta.env.DEV && url && url.startsWith('https://www.thirukalyanam.amrithaa.net/backend')) + ? url.replace('https://www.thirukalyanam.amrithaa.net/backend', '/backend') + : url; + // Use your axios instance to request the URL as a Blob - const response = await apiForFiles.get(url, { + const response = await apiForFiles.get(finalUrl, { responseType: "blob", }); diff --git a/src/api/masters.api.js b/src/api/masters.api.js index 1e9296c..3d36721 100644 --- a/src/api/masters.api.js +++ b/src/api/masters.api.js @@ -35,6 +35,13 @@ export const getStarMasters = async (raasi_id) => { return res.data; }; +export const getPathamMasters = async (star_id) => { + const res = await axiosInstance.get(API_ENDPOINTS.PATHAM_MASTER, { + params: { star_id }, + }); + return res.data; +}; + export const getEducationMasters = async () => { const res = await axiosInstance.get(API_ENDPOINTS.EDUCATION_DETAILS_MASTER); return res.data; diff --git a/src/api/subscription.api.js b/src/api/subscription.api.js new file mode 100644 index 0000000..1224d1c --- /dev/null +++ b/src/api/subscription.api.js @@ -0,0 +1,27 @@ +import axiosInstance from "./axiosInstance"; +import { API_ENDPOINTS } from "./apiEndpoints"; + +export const getSubscriptionPlans = async () => { + const response = await axiosInstance.get(API_ENDPOINTS.SUBSCRIPTION_PLANS); + return response.data; +}; + +export const purchaseSubscription = async (planId, paymentData = null) => { + if (!paymentData) { + // Step 1: Initiate purchase to get order details + const response = await axiosInstance.post(`${API_ENDPOINTS.SUBSCRIPTION_PURCHASE_RAZORPAY}?supscription_plan_id=${planId}`); + return response.data; + } else { + // Step 2: Verify payment + const response = await axiosInstance.post(API_ENDPOINTS.SUBSCRIPTION_PURCHASE_RAZORPAY, { + ...paymentData, + supscription_plan_id: planId + }); + return response.data; + } +}; + +export const getSubscriptionHistory = async () => { + const response = await axiosInstance.get(API_ENDPOINTS.SUBSCRIPTION_HISTORY); + return response.data; +}; diff --git a/src/assets/images/cashicon.svg b/src/assets/images/cashicon.svg new file mode 100644 index 0000000..4bd1334 --- /dev/null +++ b/src/assets/images/cashicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/locationicon.svg b/src/assets/images/locationicon.svg new file mode 100644 index 0000000..f0a1a24 --- /dev/null +++ b/src/assets/images/locationicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/personicon.svg b/src/assets/images/personicon.svg new file mode 100644 index 0000000..14e232d --- /dev/null +++ b/src/assets/images/personicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/religonicon.svg b/src/assets/images/religonicon.svg new file mode 100644 index 0000000..4ed3d9d --- /dev/null +++ b/src/assets/images/religonicon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/common/ProfileCardUI.jsx b/src/components/common/ProfileCardUI.jsx index 09ab40c..84c65c9 100644 --- a/src/components/common/ProfileCardUI.jsx +++ b/src/components/common/ProfileCardUI.jsx @@ -1,25 +1,415 @@ -import React, { useState } from "react"; -import { Crown, Bookmark, Receipt, Sparkles, MoonStar, IdCard } from "lucide-react"; -import CakeIcon from "@mui/icons-material/Cake"; -import LocationOnIcon from "@mui/icons-material/LocationOn"; -import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew"; -import VisibilityIcon from "@mui/icons-material/Visibility"; -import { motion } from "framer-motion"; +import React, { useState, useEffect } from "react"; +import { motion, AnimatePresence } from "framer-motion"; import { useNavigate } from "react-router-dom"; +import { Crown, Bookmark, X, Heart, Eye, Phone, MessageSquare, ChevronLeft } from "lucide-react"; + +import VisibilityIcon from "@mui/icons-material/Visibility"; +import axiosInstance, { apiForFiles } from "../../api/axiosInstance"; + +import { API_ENDPOINTS } from "../../api/apiEndpoints"; +import UpgradeModal from "./UpgradeModal"; +import toast from "react-hot-toast"; + +// Custom Icons +import personIcon from "../../assets/images/personicon.svg"; +import religionIcon from "../../assets/images/religonicon.svg"; +import locationIcon from "../../assets/images/locationicon.svg"; +import cashIcon from "../../assets/images/cashicon.svg"; + +import { useQuery, useQueryClient } from "@tanstack/react-query"; + +import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi"; +import { sendMessage } from "../../services/chatApi"; + +import { getHeaderDetails } from "../../api/preview.api"; export default function ProfileCardUI({ profile }) { const [isLiked, setIsLiked] = useState(false); + const [isShortlisted, setIsShortlisted] = useState(profile.is_shortlisted === 1); + const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); + const [isViewContactModalOpen, setIsViewContactModalOpen] = useState(false); + const [isInterestStatusModalOpen, setIsInterestStatusModalOpen] = useState(false); + const [isInterestRejectedModalOpen, setIsInterestRejectedModalOpen] = useState(false); + const [isContactSuccessModalOpen, setIsContactSuccessModalOpen] = useState(false); + const [isChatConfirmModalOpen, setIsChatConfirmModalOpen] = useState(false); + const [isCreatingChat, setIsCreatingChat] = useState(false); + const [modalTitle, setModalTitle] = useState(""); + + + const [modalMessage, setModalMessage] = useState(""); + const [unlockedMobile, setUnlockedMobile] = useState(null); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + + + + const { data: headerData } = useQuery({ + queryKey: ["headerDetails"], + queryFn: getHeaderDetails, + staleTime: 60000, // Reuse data for 1 minute + }); + + const isUserPaid = headerData?.myDetails?.is_paid_member === true; + + const handleInterest = async (e) => { + e.stopPropagation(); + if (!isUserPaid) { + setIsUpgradeModalOpen(true); + return; + } + try { + await axiosInstance.post(`${API_ENDPOINTS.INTEREST_SEND}?profile_id=${profile.id}`); + setIsLiked(true); + toast.success(`Interest sent to ${profile.name}!`, { + icon: '❤️', + style: { borderRadius: '10px', background: '#333', color: '#fff' } + }); + } catch (error) { + toast.error("Failed to send interest."); + } + }; + + const handleDecline = async (e) => { + e.stopPropagation(); + try { + await axiosInstance.post(`${API_ENDPOINTS.UPDATE_INTEREST_STATUS}?profile_id=${profile.id}&status=reject`); + toast.success("Profile declined"); + // Optionally hide card or trigger refresh + } catch (error) { + toast.error("Failed to decline profile."); + } + }; + + + const handleCall = (e) => { + e.stopPropagation(); + if (!isUserPaid) { + setIsUpgradeModalOpen(true); + return; + } + + const interestStatus = (profile.is_send_interest_status || "").toLowerCase(); + + if (profile.call_protection === 1) { + if (profile.is_send_interest) { + if (interestStatus === 'pending') { + setModalTitle("Note"); + setModalMessage("An interest request has already been sent for this profile. Please wait for their response"); + setIsInterestStatusModalOpen(true); + return; + } else if (interestStatus === 'reject' || interestStatus === 'rejected') { + setIsInterestRejectedModalOpen(true); + return; + } + } + } + + _handleCallTap(); + }; + + const _handleCallTap = () => { + const currentMobile = unlockedMobile || profile.mobile_number || ""; + const mobile = currentMobile.toLowerCase(); + if (mobile.includes("upgrade to view")) { + setIsUpgradeModalOpen(true); + } else if (mobile.includes("view contact")) { + setIsViewContactModalOpen(true); + } else { + // It's a real number - Show the success modal with the number + setUnlockedMobile(currentMobile); + setIsContactSuccessModalOpen(true); + } + }; + + + + const handleMessage = (e) => { + e.stopPropagation(); + + // 1. Check if chat already exists + if (profile.chat_id) { + navigate(`/chat/${profile.chat_id}`); + return; + } + + // 2. Check membership + if (!isUserPaid) { + setIsUpgradeModalOpen(true); + return; + } + + const interestStatus = (profile.is_send_interest_status || "").toLowerCase(); + + // 3. Check protection & interest status + if (profile.chat_protection === 1 || profile.call_protection === 1) { + if (profile.is_send_interest) { + if (interestStatus === 'pending') { + setModalTitle("Note"); + setModalMessage("An interest request has already been sent for this profile. Please wait for their response"); + setIsInterestStatusModalOpen(true); + return; + } else if (interestStatus === 'reject' || interestStatus === 'rejected') { + setIsInterestRejectedModalOpen(true); + return; + } + } + } + + // 4. Show confirmation dialog + setIsChatConfirmModalOpen(true); + }; + + const _handleCreateChat = async () => { + if (isCreatingChat) return; + + setIsChatConfirmModalOpen(false); + setIsCreatingChat(true); + + try { + const response = await axiosInstance.post(API_ENDPOINTS.CHAT_CREATE, { profile_id: profile.id }); + + if (response.data?.status === true || response.data?.status === 'success') { + const newChatId = response.data?.chat_id; + + // Send default initiation message manually + try { + await sendMessage(newChatId, "This profile has initiated a chat with you"); + } catch (msgErr) { + console.error("Error sending initial message:", msgErr); + } + + toast.success("Chat initiated!"); + navigate(`/chat/${newChatId}`); + queryClient.invalidateQueries(); + } + else { + toast.error(response.data?.message || "Could not create chat."); + } + } catch (error) { + console.error("Error creating chat", error); + toast.error("Failed to start conversation"); + } finally { + setIsCreatingChat(false); + } + }; + + const _handleViewContact = async () => { + setIsViewContactModalOpen(false); + try { + const formData = new FormData(); + formData.append("profile_id", profile.id); + const response = await apiForFiles.post(API_ENDPOINTS.VIEW_CONTACT, formData); + + + + + if (response.data?.status === 'success') { + const newMobile = response.data?.mobile_number; + setUnlockedMobile(newMobile); + setIsContactSuccessModalOpen(true); + toast.success("Contact details unlocked!"); + // Refresh all queries to update the UI globally + queryClient.invalidateQueries(); + } else { + + + setIsUpgradeModalOpen(true); + } + } catch (error) { + + console.error("Error viewing contact", error); + toast.error("Failed to view contact"); + } + }; + + const handleShortlistClick = async (e) => { + e.stopPropagation(); + try { + const res = await axiosInstance.post(`${API_ENDPOINTS.SHORTLIST_API}?profile_id=${profile.id}`); + if (res.data?.status === "success") { + setIsShortlisted(!isShortlisted); + toast.success(res.data.message || "Updated shortlist status"); + } + } catch (error) { + toast.error("Failed to update shortlist"); + } + }; + + // Map API fields to UI, handling missing values const imageSrc = profile.photo || profile.image || "https://www.thirukalyanam.amrithaa.net/backend/app-assets/images/portrait/small/no-image.png"; return ( -
navigate(`/profile-details/${profile.id}`)} - className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200 bg-white cursor-pointer hover:shadow-2xl transition-all duration-300" - > -
+ <> + setIsUpgradeModalOpen(false)} + /> + + {/* View Contact Confirmation Modal */} + + {isViewContactModalOpen && ( +
+ + +

Note

+

+ You need to view this profile's contact details. If you choose to "Proceed" one count will be deducted from your subscription. +

+
+ + +
+
+
+ )} + + {/* Interest Status Modal (Pending) */} + {isInterestStatusModalOpen && ( +
+ +

{modalTitle}

+

{modalMessage}

+ +
+
+ )} + + {/* Interest Rejected Modal */} + {isInterestRejectedModalOpen && ( +
+ +
+ +
+

Note

+

+ Your previous interest request was declined. You may resend your interest to this profile +

+ +
+
+ )} + + {/* Contact Success Modal */} + {isContactSuccessModalOpen && ( +
+ +
+ +
+

Success!

+

The contact number has been unlocked.

+ +
+ + {unlockedMobile || (!profile.mobile_number?.toLowerCase().includes("view") ? profile.mobile_number : "Fetching...")} + +
+ +
+ + +
+
+
+ )} + + {/* Chat Confirmation Modal */} + {isChatConfirmModalOpen && (() => { + const currentMobile = profile.mobile_number || ""; + const mobileLower = currentMobile.toLowerCase(); + const isMobileVisible = currentMobile !== "" && + !mobileLower.includes("upgrade") && + !mobileLower.includes("view contact"); + + return ( +
+ +

+ {isMobileVisible ? "Ready to Chat?" : "Note"} +

+

+ {isMobileVisible + ? `Are you ready to chat with ${currentMobile}?` + : "Starting a conversation will use 1 chat count. Are you ready to proceed?"} +

+
+ + +
+
+
+ ); + })()} + +
+ + + +
navigate(`/profile-details/${profile.id}`)} + className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border border-green-200 bg-white cursor-pointer hover:shadow-2xl transition-all duration-300" + > +
+ {/* Premium Badge */} {profile.isPremium && ( { - e.stopPropagation(); - // Shortlist logic here - }} + onClick={handleShortlistClick} className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors" > - - Shortlist + + + {isShortlisted ? "Shortlisted" : "Shortlist"} + +
-
-
- - Last seen: {profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"} -
+
+ +
-
-
- - {profile.age ? `${profile.age} yrs` : "-"} -
-
- - {profile.height ? `${profile.height} cm` : "-"} -
-
- - {profile.annual_income_name || "N/A"} +

+ {profile.name} +

+
+ ID: {profile.member_id || profile.id} + + + + {profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"} +
-
-
- - {profile.raasi_name || "-"} -
-
- - {profile.star_name || "-"} -
-
- - {profile.caste_name || "-"} -
+
+ {(profile.age || profile.height) && ( +
+
+ Person +
+ + {profile.age ? `${profile.age} yrs` : ""} + {profile.age && profile.height ? ", " : ""} + {profile.height ? `${profile.height} cm` : ""} + +
+ )} + + {(profile.religion_name || profile.caste_name) && ( +
+
+ Religion +
+ + {profile.religion_name} + {profile.religion_name && profile.caste_name ? " / " : ""} + {profile.caste_name} + +
+ )} + + {(profile.annual_income_name || profile.income) && ( +
+
+ Cash +
+ + {profile.annual_income_name || profile.income} + +
+ )} + + {(profile.district_name || profile.location) && ( +
+
+ Location +
+ + {profile.district_name || profile.location} + +
+ )}
-
- - - {profile.district_name || profile.location || "-"} - {profile.state_name ? `, ${profile.state_name}` : ""} - +
+ {profile.is_send_interest_received && profile.statusReceived?.toLowerCase() === 'pending' ? ( + <> + + + + ) : (!(profile.is_send_interest || isLiked) && !profile.is_send_interest_received) ? ( + <> + + + + ) : ( + <> + + + + )}
-
- - - -
+ + +
- ); -} \ No newline at end of file + +); +} + diff --git a/src/components/common/TinderCard.jsx b/src/components/common/TinderCard.jsx new file mode 100644 index 0000000..640769f --- /dev/null +++ b/src/components/common/TinderCard.jsx @@ -0,0 +1,75 @@ +import { useState, useEffect } from 'react'; + +const TinderCard = ({ children, onSwipe, onCardLeftScreen, preventSwipe, className }) => { + const [pos, setPos] = useState({ x: 0, y: 0 }); + const [dragging, setDragging] = useState(false); + const [startPos, setStartPos] = useState({ x: 0, y: 0 }); + const [gone, setGone] = useState(false); + + useEffect(() => { + setPos({ x: 0, y: 0 }); + setDragging(false); + setGone(false); + }, []); + + const handleStart = (clientX, clientY) => { + if (gone) return; + setDragging(true); + setStartPos({ x: clientX - pos.x, y: clientY - pos.y }); + }; + + const handleMove = (clientX, clientY) => { + if (!dragging || gone) return; + setPos({ x: clientX - startPos.x, y: clientY - startPos.y }); + }; + + const handleEnd = () => { + if (gone) return; + setDragging(false); + if (Math.abs(pos.x) > 120) { + const dir = pos.x > 0 ? 'right' : 'left'; + if (onSwipe) onSwipe(dir); + setGone(true); + setTimeout(() => onCardLeftScreen && onCardLeftScreen(dir), 300); + } else { + setPos({ x: 0, y: 0 }); + } + }; + + if (gone) return null; + + const rotation = pos.x / 20; + const opacity = Math.min(Math.abs(pos.x) / 100, 1); + + return ( +
handleStart(e.clientX, e.clientY)} + onMouseMove={(e) => handleMove(e.clientX, e.clientY)} + onMouseUp={handleEnd} + onMouseLeave={() => dragging && handleEnd()} + onTouchStart={(e) => handleStart(e.touches[0].clientX, e.touches[0].clientY)} + onTouchMove={(e) => handleMove(e.touches[0].clientX, e.touches[0].clientY)} + onTouchEnd={handleEnd} + > + {pos.x > 50 && ( +
+ LIKE +
+ )} + {pos.x < -50 && ( +
+ NOPE +
+ )} + {children} +
+ ); +}; + +export default TinderCard; diff --git a/src/components/common/UpgradeModal.jsx b/src/components/common/UpgradeModal.jsx new file mode 100644 index 0000000..238989b --- /dev/null +++ b/src/components/common/UpgradeModal.jsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; + +const UpgradeModal = ({ isOpen, onClose }) => { + const navigate = useNavigate(); + + return ( + + {isOpen && ( +
+ {/* Backdrop */} + + + {/* Modal Content */} + +

Note

+ +

+ Membership Upgrade Required +

+ +

+ You are currently a Free Member. Upgrade to a Premium Plan to start sending interests and connecting with your perfect match. +

+ +
+ + +
+
+
+ )} +
+ ); +}; + +export default UpgradeModal; diff --git a/src/components/matches/MatchesProfilesTab.jsx b/src/components/matches/MatchesProfilesTab.jsx index 90d8065..96885d5 100644 --- a/src/components/matches/MatchesProfilesTab.jsx +++ b/src/components/matches/MatchesProfilesTab.jsx @@ -92,68 +92,69 @@ useEffect(() => { { id: "all_matches", icon: , - title: "Your Matches", - description: "View all the profiles that match your preferences", - category: "All Matches", + title: "All Matches", + description: "Profiles matching your preferences", + category: "Primary Matches", + }, + { + id: "newly_joined", + icon: , + title: "Newly Joined", + description: "Joined within the last 30 days", + category: "Primary Matches", }, { id: "shorlisted_by_you", icon: , title: "Shortlisted by you", description: "Matches you have shortlisted", - category: "Based on activity", + category: "Your Activity", }, { id: "viewed_you", icon: , title: "Viewed you", - description: "Matches who have viewed your profile", - category: "Based on activity", + description: "Profiles who viewed you", + category: "Your Activity", }, { id: "shorlisted_you", icon: , title: "Shortlisted you", - description: "Matches who have shortlisted your profile", - category: "Based on activity", + description: "Profiles who shortlisted you", + category: "Your Activity", }, { id: "viewed_by_you", icon: , title: "Viewed by you", - description: "Matches you have viewed", - category: "Based on activity", + description: "Profiles you have viewed", + category: "Your Activity", }, { - id: "newly_joined", - icon: , - title: "Newly Joined", - description: "Matches who Joined within the last 30 days", - category: "Based on activity", - }, - { id: "location_matches", icon: , title: "Location matches", description: "Matches near your location", - category: "Based on activity", + category: "Smart Matches", }, - { + { id: "education_matches", icon: , title: "Education matches", - description: "Matches near your education match", - category: "Based on activity", + description: "Profiles with similar education", + category: "Smart Matches", }, { id: "job_matches", icon: , title: "Job matches", - description: "Matches near your job", - category: "Based on activity", + description: "Profiles with similar profession", + category: "Smart Matches", }, ]; + let currentCategory = ""; return ( @@ -168,10 +169,11 @@ useEffect(() => {
+

Filter Matches

@@ -188,14 +190,17 @@ useEffect(() => { return (
{showCategory && ( -

+

{tab.category}

)} +
{ - dispatch(updateFilter({ filter_type: tab.id })); + const finalFilterType = tab.id === "all_matches" ? "" : tab.id; + dispatch(updateFilter({ filter_type: finalFilterType })); }} + className={`p-4 rounded-lg mb-3 cursor-pointer transition-all ${ selectedTab === tab.id ? "bg-green-50 border-l-4 border-green-600" @@ -331,10 +336,22 @@ useEffect(() => { + - - - - ); } + diff --git a/src/components/profiledashboard/DailyRecommendedCard.jsx b/src/components/profiledashboard/DailyRecommendedCard.jsx index 176c14b..72b4dc9 100644 --- a/src/components/profiledashboard/DailyRecommendedCard.jsx +++ b/src/components/profiledashboard/DailyRecommendedCard.jsx @@ -1,126 +1,202 @@ -import { useRef, useState } from 'react'; -import { motion } from 'framer-motion'; +import { useRef, useState, useEffect } from 'react'; +import { motion, AnimatePresence } 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 { Crown, Bookmark, X, ChevronLeft, ChevronRight, RotateCcw, Heart, Timer } from 'lucide-react'; +import { useNavigate } from 'react-router-dom'; +import { toast } from 'react-hot-toast'; +import axiosInstance from "../../api/axiosInstance"; +import { API_ENDPOINTS } from "../../api/apiEndpoints"; +import UpgradeModal from '../common/UpgradeModal'; + +// Custom Icons +import personIcon from "../../assets/images/personicon.svg"; +import religionIcon from "../../assets/images/religonicon.svg"; +import locationIcon from "../../assets/images/locationicon.svg"; +import cashIcon from "../../assets/images/cashicon.svg"; import SchoolIcon from "@mui/icons-material/School"; -import LocationOnIcon from "@mui/icons-material/LocationOn"; -import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew"; + + // 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 DailyRecommendedCard = () => { +/* ───────────────────────────────────────────── + Countdown Timer Component (Popup Style) +───────────────────────────────────────────── */ +const CountdownTimer = ({ onContinue }) => { + const [timeLeft, setTimeLeft] = useState({ hours: 0, minutes: 0, seconds: 0 }); + + useEffect(() => { + const calculateTimeLeft = () => { + const now = new Date(); + const endOfDay = new Date(); + endOfDay.setHours(23, 59, 59, 999); + + const diff = endOfDay - now; + if (diff <= 0) return { hours: 0, minutes: 0, seconds: 0 }; + + return { + hours: Math.floor((diff / (1000 * 60 * 60)) % 24), + minutes: Math.floor((diff / 1000 / 60) % 60), + seconds: Math.floor((diff / 1000) % 60) + }; + }; + + setTimeLeft(calculateTimeLeft()); + const timer = setInterval(() => { + setTimeLeft(calculateTimeLeft()); + }, 1000); + + return () => clearInterval(timer); + }, []); + + const fmt = n => String(n).padStart(2, '0'); + + return ( +
+ +
+
+ +
+
+ +

Daily Limit Exceeded

+

+ You've viewed all recommended profiles for today. Your daily limit will reset in: +

+ +
+ {[ + { label: 'HOURS', value: timeLeft.hours }, + { label: 'MINS', value: timeLeft.minutes }, + { label: 'SECS', value: timeLeft.seconds } + ].map((u, i) => ( +
+
+ {fmt(u.value)} +
+ {u.label} +
+ ))} +
+ + +
+
+ ); +}; + +import { useQuery } from '@tanstack/react-query'; +import { getHeaderDetails } from "../../api/preview.api"; + +const DailyRecommendedCard = ({ profiles: initialProfiles = [] }) => { const swiperRef = useRef(null); const navigate = useNavigate(); + const [activeProfiles, setActiveProfiles] = useState(initialProfiles); + const [isHidden, setIsHidden] = useState(() => { + const hiddenDate = localStorage.getItem('daily_limit_hidden_date'); + return hiddenDate === new Date().toDateString(); + }); + const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); - // 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 + const { data: headerData } = useQuery({ + queryKey: ["headerDetails"], + queryFn: getHeaderDetails, + staleTime: 60000, + }); + + const isUserPaid = headerData?.myDetails?.is_paid_member === true; + + useEffect(() => { + setActiveProfiles(initialProfiles); + }, [initialProfiles]); + + + const handleContinueToDashboard = () => { + localStorage.setItem('daily_limit_hidden_date', new Date().toDateString()); + setIsHidden(true); + }; + + const handleInterest = async (e, profileId, name) => { + e.stopPropagation(); + if (!isUserPaid) { + setIsUpgradeModalOpen(true); + return; } - ]; + try { + await axiosInstance.post(`${API_ENDPOINTS.INTEREST_SEND}?profile_id=${profileId}`); + toast.success(`Interest sent to ${name}!`, { + icon: '❤️', + style: { borderRadius: '10px', background: '#333', color: '#fff' } + }); + setActiveProfiles(prev => prev.filter(p => p.id !== profileId)); + } catch (error) { + toast.error("Failed to send interest. Please try again."); + } + }; + + const handleDecline = async (e, profileId) => { + e.stopPropagation(); + try { + await axiosInstance.post(`${API_ENDPOINTS.DAILY_RECOMMENDED_DONT_SHOW}?profile_id=${profileId}`); + setActiveProfiles(prev => prev.filter(p => p.id !== profileId)); + toast.success("Profile hidden"); + } catch (error) { + toast.error("Failed to hide profile."); + } + }; const ProfileCard = ({ profile }) => { - const [isLiked, setIsLiked] = useState(false); + const image = profile.photo || profile.image; + const memberId = profile.member_id || profile.userId; + const religion = profile.religion || (profile.religion_name ? `${profile.religion_name}${profile.caste_name ? ' / ' + profile.caste_name : ''}` : null); + const education = profile.education || profile.education_name; + const occupation = profile.occupation || profile.occupation_name; + const location = profile.location || profile.district_name || profile.city_name; + const income = profile.income || profile.annual_income || profile.salary; + + const [isShortlisted, setIsShortlisted] = useState(profile.is_shortlisted === 1); + + const handleShortlistClick = async (e) => { + e.stopPropagation(); + try { + const res = await axiosInstance.post(`${API_ENDPOINTS.SHORTLIST_API}?profile_id=${profile.id}`); + if (res.data?.status === "success") { + setIsShortlisted(!isShortlisted); + toast.success(res.data.message || "Updated shortlist status"); + } + } catch (error) { + toast.error("Failed to update shortlist"); + } + }; return ( navigate(`/profile-details/${profile.id}`)} - className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200" + className="w-full max-w-sm rounded-[24px] shadow-xl overflow-hidden border border-green-100 bg-white group hover:shadow-2xl transition-all" > -
+
{profile.isPremium && ( @@ -128,150 +204,103 @@ const DailyRecommendedCard = () => { )} { - e.stopPropagation(); - }} + className="absolute top-4 right-4 z-10 bg-white/90 backdrop-blur-sm rounded-full px-4 py-2 shadow-md flex items-center space-x-2 hover:bg-white transition-colors" + onClick={handleShortlistClick} > - - Shortlist + + + {isShortlisted ? "Shortlisted" : "Shortlist"} + -
+
{profile.name}
-
-

+
+

{profile.name}

-

- Matrimony ID: {profile.userId} +

+ ID: {memberId}

-
-
+
+ {(profile.age || profile.height) && (
- - - Age : {profile.age} + Person + + {profile.age ? `${profile.age} Yrs` : ''} + {profile.age && profile.height ? ', ' : ''} + {profile.height || ''}
+ )} + {religion && (
- - - Height: {profile.height} + Religion + + {religion}
-
+ )} -
+ {(education || occupation) && (
- - - {profile.religion} + + + {education || occupation}
-
+ )} -
+ {location && (
- - - {profile.education} + Location + + {location}
-
+ )} -
+ {income && (
- - - {profile.location} + Income + + {income}
-
+ )} -
+
-
@@ -279,127 +308,70 @@ const DailyRecommendedCard = () => { ); }; + + if (isHidden) return null; + return ( -
-
- {/* Header */} - -

- Daily Recommended -

-

Find your perfect match today

-
- - {/* Swiper Container */} -
- + setIsUpgradeModalOpen(false)} + /> + + {activeProfiles.length === 0 ? ( + + ) : ( +
+ - {profiles.map((profile) => ( - - - - ))} - +

+ Daily Recommended +

+

Handpicked matches just for you

+
+ +
+ + {activeProfiles.map((profile) => ( + + + + ))} + + + + + +
- {/* Custom Navigation Buttons */} - swiperRef.current?.swiper.slidePrev()} - className="hidden sm:flex absolute left-0 top-1/2 -translate-y-1/2 z-10 - bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all - w-12 h-12 rounded-full shadow-xl hover:shadow-2xl items-center justify-center " - > - - - swiperRef.current?.swiper.slideNext()} - className="hidden sm:flex absolute right-0 top-1/2 -translate-y-1/2 z-10 - bg-white/20 backdrop-blur-sm rounded-full flex items-center justify-center hover:bg-white/30 transition-all - w-12 h-12 rounded-full shadow-xl hover:shadow-2xl items-center justify-center transition-all" - > - -
- - {/* View All Button */} - - navigate("/matches")} - - > - View All Recommendations - - -
- - {/* Custom Swiper Styles */} - + )}
); }; diff --git a/src/components/profiledashboard/MatchingList.jsx b/src/components/profiledashboard/MatchingList.jsx index ea53a26..f5dcb9e 100644 --- a/src/components/profiledashboard/MatchingList.jsx +++ b/src/components/profiledashboard/MatchingList.jsx @@ -16,6 +16,13 @@ import { Heart, Eye, } from "lucide-react"; + +// Custom Icons +import personIcon from "../../assets/images/personicon.svg"; +import religionIcon from "../../assets/images/religonicon.svg"; +import locationIcon from "../../assets/images/locationicon.svg"; +import cashIcon from "../../assets/images/cashicon.svg"; + // Import Swiper styles import "swiper/css"; import "swiper/css/navigation"; @@ -124,67 +131,63 @@ const ProfileCard = ({ profile }) => { } }} > - - {shortlistMutation.isPending ? "..." : "Shortlist"} + + {shortlistMutation.isPending ? "..." : (isShortlisted ? "Shortlisted" : "Shortlist")}
+
{/* CONTENT */} -
-

- {name} -

- -
-

ID: {idNumber}

-

- {lastSeen} -

+
+
+

+ {name} +

+
+ ID: {idNumber} + + + {lastSeen} + +
-
- {[ - age, - height, - salary, - location, - caste, - zodiac1, - zodiac2, - ] - .filter(Boolean) - .map((v, i) => ( - - {v} +
+ {(age || height) && ( +
+
+ Person +
+ + {age || ""} + {age && height ? ", " : ""} + {height || ""} - ))} -
+
+ )} -
- + {(caste || zodiac1) && ( +
+
+ Religion +
+ + {caste} + {caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""} + +
+ )} - + {location && ( +
+
+ Location +
+ + {location} + +
+ )}
@@ -210,16 +213,19 @@ const MatchingList = ({ matches }) => { -

- Your Matching List -

-

- Find your perfect match today +

+ All Matches List +

+ +

+ We've found these profiles that match your preferences

+ + {/* Swiper Container */}
{ } }} > - - {shortlistMutation.isPending ? "..." : "Shortlist"} + + {shortlistMutation.isPending ? "..." : (isShortlisted ? "Shortlisted" : "Shortlist")}
+
{/* CONTENT */} -
-

- {name} -

- -
-

ID: {idNumber}

-

- {lastSeen} -

+
+
+

+ {name} +

+
+ ID: {idNumber} + + + {lastSeen} + +
-
- {[ - age, - height, - salary, - location, - caste, - zodiac1, - zodiac2, - ] - .filter(Boolean) - .map((v, i) => ( - - {v} +
+ {(age || height) && ( +
+
+ Person +
+ + {age || ""} + {age && height ? ", " : ""} + {height || ""} - ))} -
+
+ )} -
- {/* */} + {(caste || zodiac1) && ( +
+
+ Religion +
+ + {caste} + {caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""} + +
+ )} - + {location && ( +
+
+ Location +
+ + {location} + +
+ )}
@@ -208,14 +211,16 @@ const NewJoinedProfile = ({ profiles }) => { -

- New Joined -

-

Find your perfect match today

+

+ Newly Joined +

+

Be the first to connect with our newest members

+ + {/* Swiper Container */}
[ - [ - { - icon: , - text: `Age: ${profile?.age ?? "-"}`, - }, - { - icon: , - text: `Height: ${profile?.height ?? "-"}`, - }, - ], - [ - { - icon: , - text: - profile?.religion || - profile?.caste || - profile?.community || - "-", - }, - ], - [ - { - icon: , - text: profile?.education || profile?.qualification || "-", - }, - ], - [ - { - icon: , - text: profile?.location || "-", - }, - ], -]; +const buildDefaultRows = (profile) => { + const rows = []; + + // Row 1: Age & Height + if (profile?.age || profile?.height) { + const row = []; + if (profile?.age && profile?.age !== "-") { + row.push({ + icon: Person, + text: `${profile.age} Yrs`, + }); + } + if (profile?.height && profile?.height !== "-") { + row.push({ + icon: row.length === 0 ? Person : null, + text: profile.height, + }); + } + if (row.length > 0) rows.push(row); + } + + // Row 2: Religion / Caste + const religionText = profile?.religion || (profile?.religion_name ? `${profile.religion_name}${profile.caste_name ? ' / ' + profile.caste_name : ''}` : null); + if (religionText && religionText !== "-") { + rows.push([{ + icon: Religion, + text: religionText, + }]); + } + + // Row 3: Education + const eduText = profile?.education || profile?.qualification || profile?.education_name; + if (eduText && eduText !== "-") { + rows.push([{ + icon: , + text: eduText, + }]); + } + + // Row 4: Income + const incomeText = profile?.income || profile?.annual_income || profile?.annual_income_name; + if (incomeText && incomeText !== "-") { + rows.push([{ + icon: Cash, + text: incomeText, + }]); + } + + // Row 5: Location + const locText = profile?.location || profile?.district_name; + if (locText && locText !== "-") { + rows.push([{ + icon: Location, + text: locText, + }]); + } + + return rows; +}; + const getProfileIdText = (profile) => profile?.userId || diff --git a/src/components/profiledashboard/ProfileCompletion.jsx b/src/components/profiledashboard/ProfileCompletion.jsx index e5d309a..b6c6951 100644 --- a/src/components/profiledashboard/ProfileCompletion.jsx +++ b/src/components/profiledashboard/ProfileCompletion.jsx @@ -1,12 +1,13 @@ import React, { useState } from "react"; import { motion } from "framer-motion"; +import AstroChatUI from "./AstroChatUI"; import LazyImage from "../common/LazyImage"; import ProfileIcon from "../../assets/images/profileicon.png"; import HoroscodeIcon from "../../assets/images/horoscopericon.png"; import FamilyIcon from "../../assets/images/homeicon.png"; import Box from "@mui/material/Box"; import { useNavigate } from "react-router-dom"; -import AstroChatUI from "./AstroChatUI"; + import MembershipCard from "./MembershipCard"; const ProfileCompletion = ({ percentage = 0, missingDetails,becomePaidMember }) => { @@ -59,7 +60,9 @@ const ProfileCompletion = ({ percentage = 0, missingDetails,becomePaidMember }) return ( <> -
+
+ + {/* Cards Section */} -
+
+ {/* Left Side: Astro Chat */} +
+ +
- {/* Desktop Logo */} - {/* - - */} - - - -
- - {/* NOTE: The cards are static for now. To make them dynamic, - the `missingDetails` prop should be an array of objects to map over. */} - {cards.map((card, index) => ( -
navigate(card.url)} - className=" border border-1 border-red-50 bg-white rounded-3xl hover:bg-red-50 hover:border-2 - flex flex-col items-center space-x-2 h-32 justify-center transition-colors duration-500 - cursor-pointer " + {/* Right Side: Cards & Membership */} +
+
+ - {/* Icon Container */} - - - + {cards.map((card, index) => ( +
navigate(card.url)} + className="border border-red-50 bg-white rounded-3xl hover:bg-red-50 hover:border-2 flex flex-col items-center space-x-2 h-32 justify-center transition-colors duration-500 cursor-pointer" + > + + + - {/* Text */} - - {card.title} - + + {card.title} + +
+ ))} +
+ +
+
- ))} - - - +
+
- -
+ + {/* Additional Info Section */} {/* { const navigate = useNavigate(); const queryClient = useQueryClient(); @@ -118,27 +126,48 @@ const ProfileCardItem = ({ profile }) => {

-
- {[ - age, - height, - salary, - location, - caste, - zodiac1, - zodiac2, - ] - .filter(Boolean) - .map((v, i) => ( - - {v} +
+ {(age || height) && ( +
+ Person + + {age || ""} + {age && height ? ", " : ""} + {height || ""} - ))} +
+ )} + + {(caste || zodiac1) && ( +
+ Religion + + {caste} + {caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""} + +
+ )} + + {salary && ( +
+ Cash + + {salary} + +
+ )} + + {location && ( +
+ Location + + {location} + +
+ )}
+
- + {/* 3. Family Details */} + + Family Details} + action={ onEdit(3)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + - ( + (fInfo[type] && fInfo[type].length > 0) && ( + + {type} Details: + + {fInfo[type].map((sib, i) => ( + + + {type.slice(0, -1)} {i + 1} + + + + + + + + + ))} + + + ) + ))} + + + + + + + + + + + + + + + {/* 4. Lifestyle & Horoscope */} + + Lifestyle & Horoscope} + action={ onEdit(4)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + + + + + + + + + {/* Horoscope Charts */} + {lInfo.horoscope && ( + + + + {renderChartGrid((i) => lInfo.horoscope[`graha_${i}`]?.split(',') || (lInfo.graha && lInfo.graha[i]) || [], 'Rasi')} + + + {renderChartGrid((i) => lInfo.horoscope[`amsam_${i}`]?.split(',') || (lInfo.amsam && lInfo.amsam[i]) || [], 'Navamsam')} + + + + )} + + + + {/* 5. What am I looking for in a Partner */} + + What am I looking for in a Partner} + action={ onEdit(5)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + + + + + + + + + + {(prInfo.preferred_currencies || prInfo.currencies || []).includes("INR") && ( + + )} + {(prInfo.preferred_currencies || prInfo.currencies || []).includes("USD") && ( + + )} + + + + + + + )} +
+ + + - - - - + Submit Full Completed Data + + + + + {"Confirm Submission"} + + + Once you submit your details, you will not be able to edit the following fields: Place of Birth, Date of Birth, Rasi and Navamsam. + + + + + + + ); }; diff --git a/src/feature/ProfilePreviewPage.jsx b/src/feature/ProfilePreviewPage.jsx index e42f207..75b7c27 100644 --- a/src/feature/ProfilePreviewPage.jsx +++ b/src/feature/ProfilePreviewPage.jsx @@ -1,57 +1,75 @@ import { Swiper, SwiperSlide } from "swiper/react"; -import { Navigation, Autoplay } from "swiper/modules"; -import { ChevronLeft, ChevronRight, Edit2 } from "lucide-react"; +import { Navigation, Autoplay, Pagination } from "swiper/modules"; +import { ChevronLeft, ChevronRight, Edit2, Info, Image as ImageIcon } from "lucide-react"; import { useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import { useDispatch } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import "swiper/css"; import "swiper/css/navigation"; -import LazyImage from "../components/common/LazyImage"; +import "swiper/css/pagination"; import weddingpromo1 from "../assets/images/weddingpromo1.jpg"; import weddingpromo2 from "../assets/images/weddingpromo2.jpg"; - import weddingpromo3 from "../assets/images/weddingpromo3.jpg"; - import weddingpromo4 from "../assets/images/weddingpromo4.jpg"; import horoscopeImg from "../assets/images/horoscopeimg.png"; import ProfileCompletion from "../components/profiledashboard/ProfileCompletion"; import { Box, - Button, Card, CardContent, CardHeader, - Divider, IconButton, Typography, Grid, Tooltip, } from "@mui/material"; -import { Info } from "@mui/icons-material"; import { usePreviewDetails } from "../hooks/usePreview"; import { updatePersonalDetails } from "../redux/registrationFormSlice"; +import { usePersonalMasters, usePartnerPreferenceMasters } from "../hooks/useMasters"; -const images = [ - weddingpromo1, // bride in saree - weddingpromo2, // traditional jewellery - weddingpromo3, // flower details - weddingpromo4, // couple hands - weddingpromo1, // bride close-up - weddingpromo3, // groom + bride portrait - weddingpromo2, // wedding decor - weddingpromo4, // bride in temple - weddingpromo1, // traditional ceremony - weddingpromo3, // couple full shot +const promoImages = [ + weddingpromo1, + weddingpromo2, + weddingpromo3, + weddingpromo4, + weddingpromo1, + weddingpromo3, + weddingpromo2, + weddingpromo4, + weddingpromo1, + weddingpromo3, ]; const ProfilePreviewPage = () => { const navigate = useNavigate(); const dispatch = useDispatch(); - const { data, isLoading } = usePreviewDetails(); + const { data: previewData, isLoading } = usePreviewDetails(); + + const { data: pm } = usePersonalMasters(); + const { data: ppm } = usePartnerPreferenceMasters(); + + const getNamesFromIds = (ids, options, labelKey = 'name', valueKey = 'id') => { + if (!ids || (Array.isArray(ids) && ids.length === 0)) return null; + if (!options) return Array.isArray(ids) ? ids.join(", ") : ids; + + const idArray = Array.isArray(ids) ? ids : String(ids).split(",").map(v => v.trim()); + if (idArray.length === 0) return null; + + if (idArray[0] && typeof idArray[0] === 'string' && isNaN(Number(idArray[0]))) { + return idArray.join(", "); + } + + const names = idArray.map(id => { + const found = options.find(opt => String(opt[valueKey]) === String(id)); + if (found) return found[labelKey]; + return id; + }); + return names.filter(Boolean).join(", "); + }; useEffect(() => { - if (data?.personal_details) { - const pd = data.personal_details; + if (previewData?.personal_details) { + const pd = previewData.personal_details; const images = pd.images || pd.profile_images; if (images && images.length > 0) { const formattedImages = images.map((img) => @@ -60,170 +78,80 @@ const ProfilePreviewPage = () => { dispatch(updatePersonalDetails({ profiles: formattedImages })); } } - }, [data, dispatch]); + }, [previewData, dispatch]); - const personalDetails = data?.personal_details - ? { - name: data.personal_details.name, - mobileNumber: data.personal_details.mobile, - gender: data.personal_details.gender, - dob: data.personal_details.dob - ? data.personal_details.dob.split("T")[0] - : "", - height: data.personal_details.height, - weight: data.personal_details.weight, - maritalStatus: data.personal_details.marital_status, - religion: data.personal_details.religion, - caste: data.personal_details.caste, - email: data.personal_details.email, - state: data.personal_details.state, - city: data.personal_details.district, - pincode: data.personal_details.pincode, - profiles: - data.personal_details.images?.map((url) => ({ preview: url })) || [], - } - : {}; + const formData = useSelector((state) => state.registerform); - const educationalDetails = data?.educational_details - ? { - qualification: data.educational_details.education, - fieldOfStudy: data.educational_details.study_field, - collegeName: data.educational_details.college_name, - occupation: data.educational_details.occupation, - organization: data.educational_details.company_name, - employeeType: data.educational_details.employee_type, - income: data.educational_details.annual_income, - workLocation: data.educational_details.work_location, - } - : {}; - - const familyDetails = data?.family_details - ? { - fatherName: data.family_details.father_name, - fatherOccupation: data.family_details.father_occupation, - motherName: data.family_details.mother_name, - motherOccupation: data.family_details.mother_occupation, - brotherCount: data.family_details.brother_count, - sisterCount: data.family_details.sister_count, - familyStatus: data.family_details.family_status, - nativePlace: data.family_details.native_place, - } - : {}; - - const lifestyleDetails = data?.lifestyle_details - ? { - diets: data.lifestyle_details.diet, - hobbies: data.lifestyle_details.hobbies, - dob: data.lifestyle_details.date_of_birth_formated, - tob: data.lifestyle_details.time_of_birth_formated, - placeOfBirth: data.lifestyle_details.place_of_birth, - horoscope: data.lifestyle_details.horoscope, - } - : {}; - - const partnerPreferences = data?.partner_preferences - ? { - ageRange: data.partner_preferences.preferred_age_range, - castes: data.partner_preferences.preferred_castes, - subCastes: data.partner_preferences.preferred_sub_castes, - occupations: data.partner_preferences.preferred_occupations, - educations: data.partner_preferences.preferred_educations, - hobbies: data.partner_preferences.preferred_hobbies, - annualIncome: data.partner_preferences.preferred_annual_income, - states: data.partner_preferences.preferred_states, - districts: data.partner_preferences.preferred_districts, - } - : {}; - - const handleEditSection = (stepNum) => { - navigate("/profile-edit", { state: { step: stepNum } }); + const renderValue = (value) => { + if (value === null || value === undefined || value === "") return "-"; + if (Array.isArray(value)) { + if (value.length === 0) return "-"; + return value.join(", "); + } + if (typeof value === "boolean" || value === 1 || value === "1" || value === 0 || value === "0") { + if (value === true || value === 1 || value === "1") return "Yes"; + if (value === false || value === 0 || value === "0") return "No"; + } + return value; }; - const renderField = (label, value, stepNum = 1) => ( - - + const DetailRow = ({ label, value }) => ( + + {label}: - {value ? ( - - {value} - - ) : ( - - - No data available - - )} + + {renderValue(value)} + ); + const SectionTitle = ({ title, mt = 2 }) => ( + + {title} + + ); + const renderChartGrid = (getDataForCell, title) => { const renderCell = (i) => { const items = getDataForCell(i); - const label = Array.isArray(items) ? items.join(", ") : ""; + const label = Array.isArray(items) ? items.join(', ') : ''; return ( - + 0 ? "help" : "default", + cursor: items && items.length > 0 ? 'help' : 'default' }} > {items && items.length > 2 ? ( - {items.slice(0, 2).join(", ")} - - +{items.length - 2} - + {items.slice(0, 2).join(', ')} + +{items.length - 2} - ) : ( - label - )} + ) : label} ); @@ -231,57 +159,12 @@ const ProfilePreviewPage = () => { return ( - - {title} - - + {title} + {renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)} {renderCell(5)} - - + + {title} {renderCell(6)} {renderCell(7)} {renderCell(8)} @@ -291,326 +174,25 @@ const ProfilePreviewPage = () => { ); }; - const renderPersonalSection = () => ( - - - Personal Details - - } - action={ - handleEditSection(1)} - size="large" - > - - - } - sx={{ padding: "15px 15px", background: "#f2f2f2" }} - /> - {/* */} + const handleEditSection = (stepNum) => { + navigate("/profile-edit", { state: { step: stepNum } }); + }; - - {renderField("Name", personalDetails.name, 1)} - {renderField("Mobile Number", personalDetails.mobileNumber, 1)} - {renderField("Gender", personalDetails.gender, 1)} - {renderField("Date of Birth", personalDetails.dob, 1)} - {renderField("Height", personalDetails.height, 1)} - {renderField("Weight", personalDetails.weight, 1)} - {renderField("Marital Status", personalDetails.maritalStatus, 1)} - {renderField("Religion", personalDetails.religion, 1)} - {renderField("Caste", personalDetails.caste, 1)} - {renderField("Email", personalDetails.email, 1)} - {renderField("State", personalDetails.state, 1)} - {renderField("City", personalDetails.city, 1)} - {renderField("Pincode", personalDetails.pincode, 1)} + const pInfo = previewData?.personal_details || formData.personalDetails; + const eInfo = previewData?.educational_details || formData.educationalDetails; + const fInfo = previewData?.family_details || formData.familyDetails; + const lInfo = previewData?.lifestyle_details || formData.lifestyleDetails; - {/* Profile Images */} - - - Profile Photos: - - {personalDetails.profiles && personalDetails.profiles.length > 0 ? ( - - {personalDetails.profiles.slice(0, 4).map((profile, index) => ( - - {profile.preview || profile.url ? ( - {`Profile - ) : ( - - - - )} - - ))} - {personalDetails.profiles.length > 4 && ( - - +{personalDetails.profiles.length - 4} more - - )} - - ) : ( - - - No photos uploaded - - )} - - - - ); + const isPartnerApiDataEmpty = (pp) => { + if (!pp) return true; + const hasData = pp.preferred_age_from || pp.age_from || pp.preferred_marital_status_ids?.length > 0 || pp.marital_statuses?.length > 0 || pp.preferred_castes_ids?.length > 0 || pp.castes?.length > 0; + return !hasData; + }; - const renderEducationalSection = () => ( - - - Education Details - - } - action={ - handleEditSection(2)} - size="large" - > - - - } - sx={{ padding: "15px 15px", background: "#f2f2f2" }} - /> - {/* */} - - - {renderField("Qualification", educationalDetails.qualification, 2)} - {renderField("Field of Study", educationalDetails.fieldOfStudy, 2)} - {renderField("College Name", educationalDetails.collegeName, 2)} - {renderField("Occupation", educationalDetails.occupation, 2)} - {renderField("Organization", educationalDetails.organization, 2)} - {renderField("Employee Type", educationalDetails.employeeType, 2)} - {renderField("Income", educationalDetails.income, 2)} - {renderField("Work Location", educationalDetails.workLocation, 2)} - - - ); - - const renderFamilySection = () => ( - - - Family Details - - } - action={ - handleEditSection(3)} - size="large" - > - - - } - sx={{ padding: "15px 15px", background: "#f2f2f2" }} - /> - {/* */} - - - {renderField("Father Name", familyDetails.fatherName)} - {renderField("Father Occupation", familyDetails.fatherOccupation)} - {renderField("Mother Name", familyDetails.motherName)} - {renderField("Mother Occupation", familyDetails.motherOccupation)} - {renderField("Brother Count", familyDetails.brotherCount)} - {renderField("Sister Count", familyDetails.sisterCount)} - {renderField("Family Status", familyDetails.familyStatus)} - {renderField("Native Place", familyDetails.nativePlace)} - - - ); - - const renderLifestyleSection = () => ( - - - Lifestyle Details - - } - action={ - handleEditSection(4)} - size="large" - > - - - } - sx={{ padding: "15px 15px", background: "#f2f2f2" }} - /> - {/* */} - - - {renderField( - "Diet", - Array.isArray(lifestyleDetails.diets) - ? lifestyleDetails.diets.join(", ") - : lifestyleDetails.diets - )} - {renderField( - "Hobbies", - Array.isArray(lifestyleDetails.hobbies) - ? lifestyleDetails.hobbies.join(", ") - : lifestyleDetails.hobbies - )} - {renderField("Date of Birth", lifestyleDetails.dob)} - {renderField("Time of Birth", lifestyleDetails.tob)} - {renderField("Place of Birth", lifestyleDetails.placeOfBirth)} - {lifestyleDetails.horoscope && ( - - - Horoscope Details - - - - {renderChartGrid((i) => { - const val = lifestyleDetails.horoscope[`graha_${i}`]; - return val ? val.split(",") : []; - }, "Rasi")} - - - {renderChartGrid((i) => { - const val = lifestyleDetails.horoscope[`amsam_${i}`]; - return val ? val.split(",") : []; - }, "Navamsam")} - - - - )} - - - ); - - - const renderPreferenceSection = () => ( - - - Partner Preferences - - } - action={ - handleEditSection(5)} - size="large" - > - - - } - sx={{ padding: "15px 15px", background: "#f2f2f2" }} - /> - {/* */} - - - {renderField("Age Range", partnerPreferences.ageRange)} - {renderField( - "Caste", - Array.isArray(partnerPreferences.castes) - ? partnerPreferences.castes.join(", ") - : partnerPreferences.castes - )} - {renderField( - "Sub Caste", - Array.isArray(partnerPreferences.subCastes) - ? partnerPreferences.subCastes.join(", ") - : partnerPreferences.subCastes - )} - {renderField( - "Occupation", - Array.isArray(partnerPreferences.occupations) - ? partnerPreferences.occupations.join(", ") - : partnerPreferences.occupations - )} - {renderField( - "Qualification", - Array.isArray(partnerPreferences.educations) - ? partnerPreferences.educations.join(", ") - : partnerPreferences.educations - )} - {renderField( - "Lifestyle & Hobbies", - Array.isArray(partnerPreferences.hobbies) - ? partnerPreferences.hobbies.join(", ") - : partnerPreferences.hobbies - )} - {renderField("Annual Income", partnerPreferences.annualIncome)} - {renderField( - "State", - Array.isArray(partnerPreferences.states) - ? partnerPreferences.states.join(", ") - : partnerPreferences.states - )} - {renderField( - "City", - Array.isArray(partnerPreferences.districts) - ? partnerPreferences.districts.join(", ") - : partnerPreferences.districts - )} - - - - ); + const prInfo = (!isPartnerApiDataEmpty(previewData?.partner_preferences)) ? previewData.partner_preferences : + (!isPartnerApiDataEmpty(previewData?.preferred_details)) ? previewData.preferred_details : + (!isPartnerApiDataEmpty(previewData?.partner_details)) ? previewData.partner_details : + (formData.partnerPreferences || {}); if (isLoading) { return ( @@ -624,12 +206,9 @@ const ProfilePreviewPage = () => { <>
- {/* Left Arrow */} - - {/* Right Arrow */} @@ -651,138 +230,221 @@ const ProfilePreviewPage = () => { loop={true} className="mySwiper" > - {images.map((img, idx) => ( - -
- {`Slide - - {/* */} - {/*
- Slide {idx + 1} -
*/} -
-
- ))} + {(pInfo.images || pInfo.profile_images || pInfo.profiles || promoImages).map((imgObj, idx) => { + const src = typeof imgObj === "string" ? imgObj : imgObj?.preview || imgObj?.url; + return src ? ( + +
+ {`Slide +
+
+ ) : null; + })}
+ .custom-swiper-hero .swiper-slide > div { width: 100%; height: 100%; } + `}
+ - - - {/* personal details start */}
- {renderPersonalSection()} - {renderEducationalSection()} - {renderFamilySection()} - {renderLifestyleSection()} - {renderPreferenceSection()} + {/* 1. Personal Details */} + + Personal Details} + action={ handleEditSection(1)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + Profile Photos: + + {(pInfo.images || pInfo.profile_images || pInfo.profiles || []).map((imgObj, index) => { + const src = typeof imgObj === "string" ? imgObj : imgObj?.preview || imgObj?.url; + return src ? ( + Profile + ) : null; + })} + {!(pInfo.images || pInfo.profile_images || pInfo.profiles || []).length && No photos} + + + + + + + + + + + + + + + + + + { (pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1") && } + + + + + + + + + {/* 2. Educational & Professional Details */} + + Educational & Professional Details} + action={ handleEditSection(2)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + + + + + + + + + + + + + + + + + {/* 3. Family Details */} + + Family Details} + action={ handleEditSection(3)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + + + {['brothers', 'sisters'].map(type => ( + (fInfo[type] && fInfo[type].length > 0) && ( + + {type} Details: + + {fInfo[type].map((sib, i) => ( + + + {type.slice(0, -1)} {i + 1} + + + + + + + + + ))} + + + ) + ))} + + + + + + + + + + + + + + + {/* 4. Lifestyle & Horoscope */} + + Lifestyle & Horoscope} + action={ handleEditSection(4)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + + + + + + + + + {lInfo.horoscope && ( + + + + {renderChartGrid((i) => lInfo.horoscope[`graha_${i}`]?.split(',') || (lInfo.graha && lInfo.graha[i]) || [], 'Rasi')} + + + {renderChartGrid((i) => lInfo.horoscope[`amsam_${i}`]?.split(',') || (lInfo.amsam && lInfo.amsam[i]) || [], 'Navamsam')} + + + + )} + + + + {/* 5. What am I looking for in a Partner */} + + What am I looking for in a Partner} + action={ handleEditSection(5)} size="large">} + sx={{ padding: "15px 15px", background: "#f5fbff" }} + /> + + + + + + + + + + + + + {(prInfo.preferred_currencies || prInfo.currencies || []).includes("INR") && ( + + )} + {(prInfo.preferred_currencies || prInfo.currencies || []).includes("USD") && ( + + )} + + + + +
- - {/* personal details end */} ); }; diff --git a/src/feature/StepperForm.jsx b/src/feature/StepperForm.jsx index 5b0f603..2fba626 100644 --- a/src/feature/StepperForm.jsx +++ b/src/feature/StepperForm.jsx @@ -84,15 +84,24 @@ const STEP_FIELD_ORDER = { "familyStatus", "nativePlace", ], - 4: ["diets", "hobbies", "dob", "tob", "placeOfBirth"], + 4: ["dob", "tob", "placeOfBirth", "raasi", "star", "patham", "lagnam", "panjangam_type", "dasa_balance", "dasa_years", "dasa_months", "dasa_days"], 5: [ - "ageRange", + "age_from", + "age_to", + "height_from", + "height_to", + "marital_statuses", + "birth_stars", "castes", - "subCastes", - "occupations", + "sub_castes", "educations", - "hobbies", - "annualIncome", + "occupations", + "employee_types", + "currencies", + "inr_from", + "inr_to", + "usd_from", + "usd_to", "states", "districts", ], @@ -392,18 +401,23 @@ const [completedSteps, setCompletedSteps] = useState([]); if (!firstKey) return; setTimeout(() => { - const element = document.getElementById(firstKey) || - document.querySelector(`[name="${firstKey}"]`) || - document.querySelector(`[aria-labelledby~="${firstKey}-label"]`); + const element = + document.getElementById(firstKey) || + document.querySelector(`[name="${firstKey}"]`) || + document.querySelector(`[id^="${firstKey}"]`) || + document.querySelector(`[name^="${firstKey}"]`) || + document.querySelector(`[aria-labelledby~="${firstKey}-label"]`); if (element) { element.scrollIntoView({ behavior: "smooth", block: "center" }); // Try to find a focusable element within the container - const focusable = element.querySelector('input:not([type="hidden"]), select, textarea, [role="combobox"], [role="button"]') || element; + const focusable = + element.querySelector('input:not([type="hidden"]), select, textarea, [role="combobox"], [role="button"]') || + element; if (focusable && typeof focusable.focus === "function") { - focusable.focus(); + setTimeout(() => focusable.focus(), 300); } } }, 100); @@ -460,32 +474,36 @@ const [completedSteps, setCompletedSteps] = useState([]); useEffect(() => { const processData = async () => { - if (personalDetailsData?.status === "success" && personalDetailsData?.personal_details) { + const isSuccess = personalDetailsData?.status === "success" || personalDetailsData?.success === true; + if (isSuccess && personalDetailsData?.personal_details) { const pd = personalDetailsData.personal_details; setIsStep1Update(true); - const rawImages = pd.profile_images || pd.images || []; + const rawImages = pd.profile_images || pd.profiles || pd.images || []; const mappedImages = await Promise.all( - rawImages.map(async (url, index) => { - const imageUrl = typeof url === "string" ? url : url?.url; + rawImages.map(async (img, index) => { + const originalUrl = typeof img === "string" ? img : (img.url || img.preview || img.image_url); + if (!originalUrl) return null; + + // Rewrite URL to use proxy in development to avoid CORS + const imageUrl = (import.meta.env.DEV && originalUrl.startsWith('https://www.thirukalyanam.amrithaa.net/backend')) + ? originalUrl.replace('https://www.thirukalyanam.amrithaa.net/backend', '/backend') + : originalUrl; + + const fileName = (typeof img === "object" && img.name) || `image-${index}.jpg`; let file = null; let mimeType = "image/jpeg"; - let fileName = `image-${index}.jpg`; try { - if (imageUrl) { - const response = await fetch(imageUrl); - const blob = await response.blob(); - if (blob.type) mimeType = blob.type; - const ext = mimeType.split("/")[1] || "jpg"; - fileName = `image-${index}.${ext}`; - file = new File([blob], fileName, { type: mimeType }); - } + const response = await fetch(imageUrl); + const blob = await response.blob(); + if (blob.type) mimeType = blob.type; + file = new File([blob], fileName, { type: mimeType }); } catch (error) { console.error("Error converting image URL to File:", error); } return { - id: `server-${index}`, + id: (typeof img === "object" && img.id) || `server-${index}`, preview: imageUrl, imageUrl: imageUrl, file: file, @@ -494,42 +512,61 @@ const [completedSteps, setCompletedSteps] = useState([]); valid: true, }; }) - ); + ).then(res => res.filter(Boolean)); - const formattedDob = pd.dob ? pd.dob.split("T")[0] : ""; + const dobVal = pd.dob || pd.date_of_birth || ""; + const formattedDob = dobVal ? dobVal.split("T")[0] : ""; - dispatch( - updatePersonalDetails({ - name: pd.name || "", - mobile: pd.mobile || "", - email: pd.email || "", - gender: pd.gender || "", - dob: formattedDob, - height: pd.height_id || "", - weight: pd.weight || "", - marital_status: pd.marital_status_id || "", - religion: pd.religion_id || "", - profile_for: pd.profile_for_id || "", - caste: pd.caste_id || "", - sub_caste: pd.sub_caste_id || "", - willing_to_marry: pd.willing_to_marry || "", - inter_caste_parents: pd.inter_caste_parents || 0, - inter_caste_parents_details: pd.inter_caste_parents_details || "", - gothram: pd.gothram || "", - do_you_speak_telugu: pd.do_you_speak_telugu || 0, - about_us: pd.about_us || "", - known_languages: pd.known_language_ids || [], - mother_language: pd.mother_language_id || "", - complexion: pd.complexion_id || "", - physical_status: pd.physical_status_id || "", - raasi: pd.raasi_id || "", - star: pd.star_id || "", - state: pd.state_id || "", - city: pd.district_id || "", - pincode: pd.pincode || "", - profiles: mappedImages, - }) - ); + const updates = {}; + if (pd.name) updates.name = pd.name; + const mobile = pd.mobile || pd.mobileNumber || pd.mobile_number; + if (mobile) updates.mobile = mobile; + const email = pd.email || pd.emailId || pd.email_id; + if (email) updates.email = email; + if (pd.gender) updates.gender = pd.gender; + if (formattedDob) updates.dob = formattedDob; + const height = pd.height_id || pd.height; + if (height) updates.height = height; + if (pd.weight) updates.weight = pd.weight; + const marital_status = pd.marital_status_id || pd.marital_status; + if (marital_status) updates.marital_status = marital_status; + const religion = pd.religion_id || pd.religion; + if (religion) updates.religion = religion; + const profile_for = pd.profile_for_id || pd.profile_for; + if (profile_for) updates.profile_for = profile_for; + const caste = pd.caste_id || pd.caste; + if (caste) updates.caste = caste; + const sub_caste = pd.sub_caste_id || pd.sub_caste; + if (sub_caste) updates.sub_caste = sub_caste; + if (pd.willing_to_marry) updates.willing_to_marry = pd.willing_to_marry; + if (pd.inter_caste_parents !== undefined) updates.inter_caste_parents = pd.inter_caste_parents ?? 0; + if (pd.inter_caste_parents_details) updates.inter_caste_parents_details = pd.inter_caste_parents_details; + const gothram = pd.gothram || pd.gothram_id; + if (gothram) updates.gothram = gothram; + if (pd.do_you_speak_telugu !== undefined) updates.do_you_speak_telugu = pd.do_you_speak_telugu ?? 0; + if (pd.about_us) updates.about_us = pd.about_us; + const known_languages = pd.known_language_ids || pd.known_languages; + if (known_languages) updates.known_languages = known_languages; + const mother_language = pd.mother_language_id || pd.mother_language; + if (mother_language) updates.mother_language = mother_language; + const complexion = pd.complexion_id || pd.complexion; + if (complexion) updates.complexion = complexion; + const physical_status = pd.physical_status_id || pd.physical_status; + if (physical_status) updates.physical_status = physical_status; + const raasi = pd.raasi_id || pd.raasi; + if (raasi) updates.raasi = raasi; + const star = pd.star_id || pd.star; + if (star) updates.star = star; + const state = pd.state_id || pd.state; + if (state) updates.state = state; + const city = pd.district_id || pd.city; + if (city) updates.city = city; + if (pd.pincode) updates.pincode = pd.pincode; + if (mappedImages && mappedImages.length > 0) updates.profiles = mappedImages; + + if (Object.keys(updates).length > 0) { + dispatch(updatePersonalDetails(updates)); + } } }; processData(); @@ -546,31 +583,64 @@ const [completedSteps, setCompletedSteps] = useState([]); enabled: isAuth || shouldHideStepper, retry: false, refetchOnWindowFocus: false, + refetchOnMount: true, }); -useEffect(() => { - if (educationalData?.status === "success" && educationalData?.educational_details) { + useEffect(() => { + const isSuccess = educationalData?.status === "success" || educationalData?.success === true; + if (isSuccess && educationalData?.educational_details) { const ed = educationalData.educational_details; - dispatch( - updateEducationalDetails({ - study_field: ed.study_field_id || "", - education: ed.education_id || "", - education_detail: ed.education_detail || "", - college_name: ed.college_name || "", - employee_type: ed.employee_type_id || "", - occupation: ed.occupation_id || "", - occupation_detail: ed.occupation_detail || "", - company_name: ed.company_name || "", - income_currency: ed.income_currency || "INR", - annual_income: ed.annual_income || "", - work_country: ed.work_country_id || "", - work_state: ed.work_state_id || "", - work_district: ed.work_district_id || "", - work_city: ed.work_city || "", - address: ed.address || ed.work_location || "", - }) - ); + const updates = {}; + + const study_field = ed.study_field_id || ed.study_field || ed.education_id; + if (study_field) updates.study_field = study_field; + + const education = ed.education_id || ed.education || ed.qualification_id; + if (education) updates.education = education; + + const education_detail = ed.education_detail || ed.education_details || ed.educationDetail; + if (education_detail) updates.education_detail = education_detail; + + const college_name = ed.college_name || ed.collegeName || ed.college; + if (college_name) updates.college_name = college_name; + + const employee_type = ed.employee_type_id || ed.employee_type || ed.employeeType; + if (employee_type) updates.employee_type = employee_type; + + const occupation = ed.occupation_id || ed.occupation || ed.occupational; + if (occupation) updates.occupation = occupation; + + const occupation_detail = ed.occupation_detail || ed.occupationDetail || ed.occupation_details; + if (occupation_detail) updates.occupation_detail = occupation_detail; + + const company_name = ed.company_name || ed.companyName || ed.organization_name; + if (company_name) updates.company_name = company_name; + + const income_currency = ed.income_currency || ed.currency || "INR"; + if (income_currency) updates.income_currency = income_currency; + + const annual_income = ed.annual_income || ed.annual_income_id || ed.income; + if (annual_income) updates.annual_income = annual_income; + + const work_country = ed.work_country_id || ed.work_country || ed.country_id; + if (work_country) updates.work_country = work_country; + + const work_state = ed.work_state_id || ed.work_state || ed.state_id; + if (work_state) updates.work_state = work_state; + + const work_district = ed.work_district_id || ed.work_district || ed.district_id; + if (work_district) updates.work_district = work_district; + + const work_city = ed.work_city || ed.city || ed.work_location; + if (work_city) updates.work_city = work_city; + + const address = ed.address || ed.work_location || ""; + if (address) updates.address = address; + + if (Object.keys(updates).length > 0) { + dispatch(updateEducationalDetails(updates)); + } } }, [educationalData, dispatch]); @@ -586,49 +656,59 @@ const {data:familyData} = useQuery({ enabled: isAuth || shouldHideStepper, retry:false, refetchOnWindowFocus:false, + refetchOnMount: true, }); useEffect(() => { - if (familyData?.status === "success" && familyData?.family_details) { - const fd = familyData.family_details; - dispatch( - updateFamilyDetails({ - fatherName: fd.father_name || "", - fatherOccupation: fd.father_occupation || "", - motherName: fd.mother_name || "", - motherOccupation: fd.mother_occupation || "", - familyStatus: fd.family_status_id || "", - nativePlace: fd.native_place || "", - familyCountry: fd.family_country_id || "", - familyState: fd.family_state_id || "", - familyDistrict: fd.family_district_id || "", - familyCity: fd.family_city || "", - address: fd.address || "", - expectationDetails: fd.expectation_details || "", - willingToGoAbroad: fd.willing_to_go_abroad || "", - brotherCount: fd.brother_count || 0, - sisterCount: fd.sister_count || 0, - brothers: (fd.brothers || []).map((b) => ({ + const isSuccess = familyData?.status === "success" || familyData?.success === true; + if (isSuccess && familyData?.family_details) { + const fd = familyData.family_details; + const updates = {}; + + if (fd.father_name || fd.fatherName) updates.fatherName = fd.father_name || fd.fatherName || ""; + if (fd.father_occupation || fd.fatherOccupation || fd.father_occupation_id) updates.fatherOccupation = fd.father_occupation ?? fd.fatherOccupation ?? fd.father_occupation_id ?? ""; + if (fd.mother_name || fd.motherName) updates.motherName = fd.mother_name || fd.motherName || ""; + if (fd.mother_occupation || fd.motherOccupation || fd.mother_occupation_id) updates.motherOccupation = fd.mother_occupation ?? fd.motherOccupation ?? fd.mother_occupation_id ?? ""; + if (fd.family_status_id || fd.familyStatus || fd.family_status) updates.familyStatus = fd.family_status_id || fd.familyStatus || fd.family_status || ""; + if (fd.native_place || fd.nativePlace) updates.nativePlace = fd.native_place || fd.nativePlace || ""; + if (fd.brother_count !== undefined) updates.brotherCount = fd.brother_count || 0; + if (fd.sister_count !== undefined) updates.sisterCount = fd.sister_count || 0; + if (fd.family_country_id || fd.family_country_name || fd.familyCountry) updates.familyCountry = fd.family_country_id || fd.familyCountry || ""; + if (fd.family_state_id || fd.family_state_name || fd.familyState) updates.familyState = fd.family_state_id || fd.familyState || ""; + if (fd.family_district_id || fd.family_district_name || fd.familyDistrict) updates.familyDistrict = fd.family_district_id || fd.familyDistrict || ""; + if (fd.family_city || fd.familyCity) updates.familyCity = fd.family_city || fd.familyCity || ""; + if (fd.address) updates.address = fd.address || ""; + if (fd.expectation_details || fd.expectationDetails) updates.expectationDetails = fd.expectation_details || fd.expectationDetails || ""; + if (fd.willing_to_go_abroad !== undefined) updates.willingToGoAbroad = fd.willing_to_go_abroad || ""; + + if (fd.brothers || fd.brother_details) { + updates.brothers = (fd.brothers || fd.brother_details || []).map((b) => ({ name: b.name || "", - occupation: b.occupation_name || "", - maritalStatus: b.marital_status || "", + occupation: b.occupation || b.occupational || b.occupation_name || "", + maritalStatus: b.marital_status || b.maritalStatus || b.marital_status_id || "", type: b.type || "", - hasChildren: b.has_children || "", - details: b.additional_details || "" - })), - sisters: (fd.sisters || []).map((s) => ({ + hasChildren: b.has_children || b.hasChildren || b.thereHaveChildren || "", + details: b.details || b.additional_details || b.additionalDetails || "", + })); + } + + if (fd.sisters || fd.sister_details) { + updates.sisters = (fd.sisters || fd.sister_details || []).map((s) => ({ name: s.name || "", - occupation: s.occupation_name || "", - maritalStatus: s.marital_status || "", + occupation: s.occupation || s.occupational || s.occupation_name || "", + maritalStatus: s.marital_status || s.maritalStatus || s.marital_status_id || "", type: s.type || "", - hasChildren: s.has_children || "", - details: s.additional_details || "" - })), - }) - ); - } -}, [familyData, dispatch]); + hasChildren: s.has_children || s.hasChildren || s.thereHaveChildren || "", + details: s.details || s.additional_details || s.additionalDetails || "", + })); + } + + if (Object.keys(updates).length > 0) { + dispatch(updateFamilyDetails(updates)); + } + } + }, [familyData, dispatch]); // Fetch Lifestyle Details const { data: lifestyleData } = useQuery({ @@ -644,7 +724,8 @@ useEffect(() => { }); useEffect(() => { - if (lifestyleData?.status === "success" && lifestyleData?.lifestyle_details) { + const isSuccess = lifestyleData?.status === "success" || lifestyleData?.success === true; + if (isSuccess && lifestyleData?.lifestyle_details) { const ld = lifestyleData.lifestyle_details; const horoscope = ld.horoscope || {}; @@ -653,18 +734,35 @@ useEffect(() => { for (let i = 1; i <= 12; i++) { const key = `${prefix}_${i}`; const val = horoscope[key]; - chart[i] = val ? val.split(",") : []; + chart[i] = val ? (Array.isArray(val) ? val : val.split(",")) : []; } return chart; }; + const dobVal = ld.dob || ld.date_of_birth || ""; + const formattedDob = dobVal ? dobVal.split("T")[0] : ""; + + let formattedTob = ld.tob || ld.time_of_birth || ""; + if (!formattedTob && ld.time_of_birth_formated) { + if (ld.time_of_birth_formated.includes(":")) { + formattedTob = ld.time_of_birth_formated.substring(0, 5); + } + } + dispatch( updateLifestyleDetails({ - diets: ld.diet_id || "", - hobbies: ld.hobbies_ids || [], - dob: ld.time_of_birth ? ld.time_of_birth.split("T")[0] : "", - tob: ld.time_of_birth_formated ? ld.time_of_birth_formated.substring(0, 5) : "", - placeOfBirth: ld.place_of_birth || "", + dob: formattedDob, + tob: formattedTob, + placeOfBirth: ld.place_of_birth || ld.placeOfBirth || "", + raasi: ld.raasi_id || horoscope.raasi_id || ld.raasi || "", + star: ld.star_id || horoscope.star_id || ld.star || "", + patham: ld.patham || horoscope.patham || "", + lagnam: ld.lagnam_id || ld.lagnam || horoscope.lagnam || "", + panjangam_type: ld.panjangam_type || horoscope.panjangam_type || "", + dasa_balance: ld.dasa_balance || horoscope.dasa_balance || "", + dasa_years: ld.dasa_years || horoscope.dasa_years || "", + dasa_months: ld.dasa_months || horoscope.dasa_months || "", + dasa_days: ld.dasa_days || horoscope.dasa_days || "", graha: mapChart("graha"), amsam: mapChart("amsam"), }) @@ -686,21 +784,86 @@ useEffect(() => { }); useEffect(() => { - if (partnerData?.status === "success" && partnerData?.partner_preferences) { + const isSuccess = partnerData?.status === "success" || partnerData?.success === true; + if (isSuccess && partnerData?.partner_preferences) { const pp = partnerData.partner_preferences; - dispatch( - updatePartnerPreferences({ - ageRange: pp.preferred_age_range_id || "", - annualIncome: pp.preferred_annual_income_id || "", - castes: pp.preferred_castes_ids || [], - subCastes: pp.preferred_sub_castes_ids || [], - occupations: pp.preferred_occupations_ids || [], - educations: pp.preferred_educations_ids || [], - hobbies: pp.preferred_hobbies_ids || [], - states: pp.preferred_states_ids || [], - districts: pp.preferred_districts_ids || [], - }) - ); + const updates = {}; + + const age_from = pp.preferred_age_from ?? pp.age_from ?? pp.preferred_age_from_id ?? pp.age_from_id; + if (age_from !== undefined && age_from !== null) updates.age_from = age_from; + + const age_to = pp.preferred_age_to ?? pp.age_to ?? pp.preferred_age_to_id ?? pp.age_to_id; + if (age_to !== undefined && age_to !== null) updates.age_to = age_to; + + const height_from = pp.preferred_height_from_id ?? pp.height_from_id ?? pp.preferred_height_from ?? pp.height_from; + if (height_from !== undefined && height_from !== null) updates.height_from = height_from; + + const height_to = pp.preferred_height_to_id ?? pp.height_to_id ?? pp.preferred_height_to ?? pp.height_to; + if (height_to !== undefined && height_to !== null) updates.height_to = height_to; + + const mapMulti = (val) => { + if (!val) return []; + if (Array.isArray(val)) return val; + return String(val).split(",").map(v => v.trim()).filter(Boolean); + }; + + if (pp.preferred_marital_status_ids || pp.marital_status_ids || pp.preferred_marital_statuses || pp.marital_status) { + const val = mapMulti(pp.preferred_marital_status_ids || pp.marital_status_ids || pp.preferred_marital_statuses || pp.marital_status); + if (val.length > 0) updates.marital_statuses = val; + } + if (pp.preferred_birth_star_ids || pp.birth_star_ids || pp.preferred_birth_stars || pp.birth_star) { + const val = mapMulti(pp.preferred_birth_star_ids || pp.birth_star_ids || pp.preferred_birth_stars || pp.birth_star); + if (val.length > 0) updates.birth_stars = val; + } + if (pp.preferred_castes_ids || pp.castes_ids || pp.preferred_castes || pp.caste) { + const val = mapMulti(pp.preferred_castes_ids || pp.castes_ids || pp.preferred_castes || pp.caste); + if (val.length > 0) updates.castes = val; + } + if (pp.preferred_sub_castes_ids || pp.sub_castes_ids || pp.preferred_sub_castes || pp.sub_caste) { + const val = mapMulti(pp.preferred_sub_castes_ids || pp.sub_castes_ids || pp.preferred_sub_castes || pp.sub_caste); + if (val.length > 0) updates.sub_castes = val; + } + if (pp.preferred_occupations_ids || pp.occupations_ids || pp.preferred_occupations || pp.occupation) { + const val = mapMulti(pp.preferred_occupations_ids || pp.occupations_ids || pp.preferred_occupations || pp.occupation); + if (val.length > 0) updates.occupations = val; + } + if (pp.preferred_educations_ids || pp.educations_ids || pp.preferred_educations || pp.education) { + const val = mapMulti(pp.preferred_educations_ids || pp.educations_ids || pp.preferred_educations || pp.education); + if (val.length > 0) updates.educations = val; + } + if (pp.preferred_employee_type_ids || pp.employee_type_ids || pp.preferred_employee_types || pp.employee_type) { + const val = mapMulti(pp.preferred_employee_type_ids || pp.employee_type_ids || pp.preferred_employee_types || pp.employee_type); + if (val.length > 0) updates.employee_types = val; + } + if (pp.preferred_currencies || pp.currencies) { + const val = mapMulti(pp.preferred_currencies || pp.currencies); + if (val.length > 0) updates.currencies = val; + } + + const inr_from = pp.preferred_inr_from ?? pp.inr_from ?? pp.preferred_inr_from_id; + if (inr_from !== undefined && inr_from !== null) updates.inr_from = inr_from; + + const inr_to = pp.preferred_inr_to ?? pp.inr_to ?? pp.preferred_inr_to_id; + if (inr_to !== undefined && inr_to !== null) updates.inr_to = inr_to; + + const usd_from = pp.preferred_usd_from ?? pp.usd_from ?? pp.preferred_usd_from_id; + if (usd_from !== undefined && usd_from !== null) updates.usd_from = usd_from; + + const usd_to = pp.preferred_usd_to ?? pp.usd_to ?? pp.preferred_usd_to_id; + if (usd_to !== undefined && usd_to !== null) updates.usd_to = usd_to; + + if (pp.preferred_states_ids || pp.states_ids || pp.preferred_states || pp.state) { + const val = mapMulti(pp.preferred_states_ids || pp.states_ids || pp.preferred_states || pp.state); + if (val.length > 0) updates.states = val; + } + if (pp.preferred_districts_ids || pp.districts_ids || pp.preferred_districts || pp.district) { + const val = mapMulti(pp.preferred_districts_ids || pp.districts_ids || pp.preferred_districts || pp.district); + if (val.length > 0) updates.districts = val; + } + + if (Object.keys(updates).length > 0) { + dispatch(updatePartnerPreferences(updates)); + } } }, [partnerData, dispatch]); @@ -722,6 +885,7 @@ useEffect(() => { "marital_status", "profile_for", "caste", + "sub_caste", "email", "mother_language", "complexion", @@ -795,7 +959,7 @@ useEffect(() => { ]; required.forEach((field) => { if (!familyDetails[field]) { - // newErrors[field] = "This field is required"; + // newErrors[field] = `${label} is required`; const label = field .replace(/([A-Z])/g, " $1") .replace(/^./, (str) => str.toUpperCase()); @@ -810,7 +974,7 @@ useEffect(() => { newErrors.motherName = "Mother Name must contain only alphabets"; } } else if (step === 4) { - const required = ["diets", "hobbies", "dob", "tob"]; + const required = ["dob", "tob", "placeOfBirth", "raasi", "star"]; required.forEach((field) => { const value = lifestyleDetails[field]; if (Array.isArray(value)) { @@ -818,7 +982,10 @@ useEffect(() => { if (field === "hobbies") { newErrors[field] = "Hobbies and Interests is required"; } else { - newErrors[field] = "This field is required"; + const label = field + .replace(/([A-Z])/g, " $1") + .replace(/^./, (str) => str.toUpperCase()); + newErrors[field] = `${label} is required`; } } @@ -830,8 +997,11 @@ useEffect(() => { else if (field === "tob") { newErrors[field] = "Time of Birth is required"; } - else if (field === "diets") { - newErrors[field] = "Diet is required"; + else if (field === "raasi") { + newErrors[field] = "Raasi is required"; + } + else if (field === "star") { + newErrors[field] = "Birth Star is required"; } else { @@ -843,33 +1013,53 @@ useEffect(() => { } }); } else if (step === 5) { - const required = [ - "ageRange", - "castes", - "subCastes", - "occupations", - "educations", - "hobbies", - "annualIncome", - "states", - "districts", - ]; + // Range Validations + if (partnerPreferences.age_from && partnerPreferences.age_to) { + if (Number(partnerPreferences.age_from) > Number(partnerPreferences.age_to)) { + newErrors.age_from = "From Age cannot be greater than To Age"; + newErrors.age_to = "To Age cannot be less than From Age"; + } + } + + if (partnerPreferences.height_from && partnerPreferences.height_to) { + if (Number(partnerPreferences.height_from) > Number(partnerPreferences.height_to)) { + newErrors.height_from = "From Height cannot be greater than To Height"; + newErrors.height_to = "To Height cannot be less than From Height"; + } + } + + if (partnerPreferences.currencies.includes("INR")) { + const from = parseFloat(partnerPreferences.inr_from); + const to = parseFloat(partnerPreferences.inr_to); + if (!isNaN(from) && !isNaN(to) && from > to) { + newErrors.inr_from = "From Income cannot be greater than To Income"; + newErrors.inr_to = "To Income cannot be less than From Income"; + } + } + + if (partnerPreferences.currencies.includes("USD")) { + const from = parseFloat(partnerPreferences.usd_from); + const to = parseFloat(partnerPreferences.usd_to); + if (!isNaN(from) && !isNaN(to) && from > to) { + newErrors.usd_from = "From Income cannot be greater than To Income"; + newErrors.usd_to = "To Income cannot be less than From Income"; + } + } + + // Required fields + const required = ["age_from", "age_to", "height_from", "height_to", "castes", "educations", "states"]; required.forEach((field) => { const value = partnerPreferences[field]; + let isMissing = false; if (Array.isArray(value)) { - if (value.length === 0) { - // newErrors[field] = "This field is required"; - const label = field - .replace(/([A-Z])/g, " $1") - .replace(/^./, (str) => str.toUpperCase()); - newErrors[field] = `${label} is required`; - } - return; + isMissing = value.length === 0; + } else { + isMissing = !value && value !== 0; } - if (!value) { - // newErrors[field] = "This field is required"; + + if (isMissing) { const label = field - .replace(/([A-Z])/g, " $1") + .replace(/_/g, " ") .replace(/^./, (str) => str.toUpperCase()); newErrors[field] = `${label} is required`; } @@ -928,7 +1118,13 @@ useEffect(() => { if (!isValidFile && item.preview && typeof item.preview === "string") { try { - const response = await fetch(item.preview); + // Rewrite URL to use proxy in development to avoid CORS + const finalUrl = (import.meta.env.DEV && item.preview.startsWith('https://www.thirukalyanam.amrithaa.net/backend')) + ? item.preview.replace('https://www.thirukalyanam.amrithaa.net/backend', '/backend') + : item.preview; + + const response = await fetch(finalUrl); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const blob = await response.blob(); const mimeType = blob.type || "image/jpeg"; const ext = mimeType.split("/")[1] || "jpg"; @@ -983,12 +1179,16 @@ useEffect(() => { formData.append("mother_name", familyDetails.motherName); formData.append("mother_occupation", familyDetails.motherOccupation || ""); formData.append("family_status", familyDetails.familyStatus); + formData.append("family_status_id", familyDetails.familyStatus); formData.append("native_place", familyDetails.nativePlace || ""); formData.append("brother_count", familyDetails.brotherCount || 0); formData.append("sister_count", familyDetails.sisterCount || 0); formData.append("family_country", familyDetails.familyCountry || ""); + formData.append("family_country_id", familyDetails.familyCountry || ""); formData.append("family_state", familyDetails.familyState || ""); + formData.append("family_state_id", familyDetails.familyState || ""); formData.append("family_district", familyDetails.familyDistrict || ""); + formData.append("family_district_id", familyDetails.familyDistrict || ""); formData.append("family_city", familyDetails.familyCity || ""); formData.append("address", familyDetails.address || ""); formData.append("expectation_details", familyDetails.expectationDetails || ""); @@ -1001,6 +1201,7 @@ useEffect(() => { formData.append(`brothers[${index}][type]`, brother?.type || ""); formData.append(`brothers[${index}][has_children]`, brother?.hasChildren || ""); formData.append(`brothers[${index}][details]`, brother?.details || ""); + formData.append(`brothers[${index}][additional_details]`, brother?.details || ""); }); (familyDetails.sisters || []).forEach((sister, index) => { @@ -1010,6 +1211,7 @@ useEffect(() => { formData.append(`sisters[${index}][type]`, sister?.type || ""); formData.append(`sisters[${index}][has_children]`, sister?.hasChildren || ""); formData.append(`sisters[${index}][details]`, sister?.details || ""); + formData.append(`sisters[${index}][additional_details]`, sister?.details || ""); }); return formData; @@ -1020,12 +1222,17 @@ useEffect(() => { formData.append("dob", lifestyleDetails.dob || ""); formData.append("tob", lifestyleDetails.tob || ""); formData.append("place_of_birth", lifestyleDetails.placeOfBirth || ""); - - formData.append("diets", lifestyleDetails.diets || ""); - - (lifestyleDetails.hobbies || []).forEach((id, index) => { - formData.append(`hobbies[${index}]`, id); - }); + formData.append("raasi", lifestyleDetails.raasi || ""); + formData.append("raasi_id", lifestyleDetails.raasi || ""); + formData.append("star", lifestyleDetails.star || ""); + formData.append("star_id", lifestyleDetails.star || ""); + formData.append("patham", lifestyleDetails.patham || ""); + formData.append("lagnam", lifestyleDetails.lagnam || ""); + formData.append("panjangam_type", lifestyleDetails.panjangam_type || ""); + formData.append("dasa_balance", lifestyleDetails.dasa_balance || ""); + formData.append("dasa_years", lifestyleDetails.dasa_years || ""); + formData.append("dasa_months", lifestyleDetails.dasa_months || ""); + formData.append("dasa_days", lifestyleDetails.dasa_days || ""); const graha = lifestyleDetails.graha || {}; Object.keys(graha).forEach((house) => { @@ -1048,35 +1255,54 @@ useEffect(() => { const buildRegisterStep5Payload = () => { const formData = new FormData(); - formData.append("age_range", partnerPreferences.ageRange || ""); - formData.append("annual_income", partnerPreferences.annualIncome || ""); + + formData.append("preferred_age_from", partnerPreferences.age_from || ""); + formData.append("preferred_age_to", partnerPreferences.age_to || ""); + formData.append("preferred_height_from", partnerPreferences.height_from || ""); + formData.append("preferred_height_to", partnerPreferences.height_to || ""); + formData.append("preferred_inr_from", partnerPreferences.inr_from || ""); + formData.append("preferred_inr_to", partnerPreferences.inr_to || ""); + formData.append("preferred_usd_from", partnerPreferences.usd_from || ""); + formData.append("preferred_usd_to", partnerPreferences.usd_to || ""); - (partnerPreferences.castes || []).forEach((id, index) => { - formData.append(`castes[${index}]`, id); + (partnerPreferences.marital_statuses || []).forEach((id, index) => { + formData.append(`preferred_marital_status_ids[${index}]`, id); }); - (partnerPreferences.subCastes || []).forEach((id, index) => { - formData.append(`sub_castes[${index}]`, id); + (partnerPreferences.employee_types || []).forEach((id, index) => { + formData.append(`preferred_employee_type_ids[${index}]`, id); + }); + + (partnerPreferences.birth_stars || []).forEach((id, index) => { + formData.append(`preferred_birth_star_ids[${index}]`, id); + }); + + (partnerPreferences.currencies || []).forEach((curr, index) => { + formData.append(`preferred_currencies[${index}]`, curr); + }); + + (partnerPreferences.castes || []).forEach((id, index) => { + formData.append(`preferred_castes_ids[${index}]`, id); + }); + + (partnerPreferences.sub_castes || []).forEach((id, index) => { + formData.append(`preferred_sub_castes_ids[${index}]`, id); }); (partnerPreferences.occupations || []).forEach((id, index) => { - formData.append(`occupations[${index}]`, id); + formData.append(`preferred_occupations_ids[${index}]`, id); }); (partnerPreferences.educations || []).forEach((id, index) => { - formData.append(`educations[${index}]`, id); - }); - - (partnerPreferences.hobbies || []).forEach((id, index) => { - formData.append(`hobbies[${index}]`, id); + formData.append(`preferred_educations_ids[${index}]`, id); }); (partnerPreferences.states || []).forEach((id, index) => { - formData.append(`states[${index}]`, id); + formData.append(`preferred_states_ids[${index}]`, id); }); (partnerPreferences.districts || []).forEach((id, index) => { - formData.append(`districts[${index}]`, id); + formData.append(`preferred_districts_ids[${index}]`, id); }); return formData; @@ -1256,11 +1482,6 @@ useEffect(() => { if (!enabledSteps.includes(step)) return; - // If moving backwards during registration, clear the unsaved data of the step we are leaving - if (step < currentStep && !shouldHideStepper) { - dispatch(clearAllStepsFrom(currentStep)); - } - setCurrentStep(step); setErrors({}); window.scrollTo(0, 0); @@ -1378,7 +1599,7 @@ useEffect(() => { return required.every(field => familyDetails[field]); } if (stepNum === 4) { - const required = ["diets", "hobbies", "dob", "tob"]; + const required = ["dob", "tob", "placeOfBirth", "raasi", "star"]; return required.every(field => { const val = lifestyleDetails[field]; return Array.isArray(val) ? val.length > 0 : !!val; diff --git a/src/hooks/useDependentMasters.js b/src/hooks/useDependentMasters.js index ad0ed8c..83e126e 100644 --- a/src/hooks/useDependentMasters.js +++ b/src/hooks/useDependentMasters.js @@ -5,6 +5,7 @@ import { getSubCasteMasters, getCityMasters, getStarMasters, + getPathamMasters, } from "../api/masters.api"; /** Personal details masters (gender, marital status, religion, gothram, raasi, state, etc.) */ @@ -43,7 +44,15 @@ export const useSubCasteMasters = (caste_id) => export const useCityMasters = (state_id) => useQuery({ queryKey: ["city-masters", state_id], - queryFn: () => getCityMasters(state_id), + queryFn: async () => { + if (Array.isArray(state_id)) { + const results = await Promise.all( + state_id.map((id) => getCityMasters(id)) + ); + return results; + } + return getCityMasters(state_id); + }, enabled: Array.isArray(state_id) ? state_id.length > 0 : !!state_id, }); @@ -54,3 +63,11 @@ export const useStarMasters = (raasi_id) => queryFn: () => getStarMasters(raasi_id), enabled: !!raasi_id, }); + +/** Patham depends on star */ +export const usePathamMasters = (star_id) => + useQuery({ + queryKey: ["patham-masters", star_id], + queryFn: () => getPathamMasters(star_id), + enabled: !!star_id, + }); diff --git a/src/hooks/useProfiles.js b/src/hooks/useProfiles.js index 17ae0ed..1f3f0b0 100644 --- a/src/hooks/useProfiles.js +++ b/src/hooks/useProfiles.js @@ -8,6 +8,12 @@ export const useProfiles = (filters = {}) => { // Remove empty filters const cleanFilters = Object.entries(filters).reduce((acc, [key, value]) => { + // Skip default boundaries to prevent strict filtering on null values + if (key === "from_age" && value === 18) return acc; + if (key === "to_age" && value === 70) return acc; + if (key === "from_height" && value === 4.0) return acc; + if (key === "to_height" && value === 7.11) return acc; + if ( value !== "" && value !== null && @@ -19,14 +25,21 @@ export const useProfiles = (filters = {}) => { return acc; }, {}); + return useInfiniteQuery({ queryKey: ["profiles-filter-list", cleanFilters], - queryFn: ({ pageParam = 1 }) => - getProfilesFilterList({ + queryFn: ({ pageParam = 1 }) => { + console.log("[API-REQUEST] Calling profiles/lists with filters:", { ...cleanFilters, page: pageParam, - }), + }); + return getProfilesFilterList({ + ...cleanFilters, + page: pageParam, + }); + }, + staleTime: 1000 * 60 * 2, refetchOnWindowFocus: false, diff --git a/src/pages/ChatPage.jsx b/src/pages/ChatPage.jsx index 3aab332..b72e70b 100644 --- a/src/pages/ChatPage.jsx +++ b/src/pages/ChatPage.jsx @@ -1,4 +1,6 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { useParams } from 'react-router-dom'; + import { Search, MoreVertical, Send, Phone, Video, Check, CheckCheck, ArrowLeft, Star, Share2, Flag, Ban, Trash2, Loader2, MessageCircle } from 'lucide-react'; import { useQueryClient } from '@tanstack/react-query'; import ReportModal from '../components/common/ReportModal'; @@ -9,7 +11,9 @@ import { useWebSocket } from '../hooks/useWebSocket'; import { useSelector } from 'react-redux'; const ChatUI = () => { + const { chatId } = useParams(); const queryClient = useQueryClient(); + const { personalDetails } = useSelector((state) => state.registerform); const [selectedChat, setSelectedChat] = useState(null); const [message, setMessage] = useState(''); @@ -271,12 +275,20 @@ const ChatUI = () => { fetchContacts(); }, []); + useEffect(() => { + if (chatId) { + setSelectedChat(chatId); + setShowChatOnMobile(true); + } + }, [chatId]); + useEffect(() => { if (selectedChat) { fetchMessages(selectedChat); } }, [selectedChat]); + useEffect(() => { if (isPaginating.current) { if (scrollContainerRef.current) { diff --git a/src/pages/InterestSendPage.jsx b/src/pages/InterestSendPage.jsx index 9541a52..c90e137 100644 --- a/src/pages/InterestSendPage.jsx +++ b/src/pages/InterestSendPage.jsx @@ -18,6 +18,12 @@ import { import { useNavigate } from "react-router-dom"; import { getInterestList, updateInterestStatus } from "../services/profileActionApi"; import { toast } from "react-hot-toast"; +import { motion, AnimatePresence } from "framer-motion"; +import axiosInstance, { apiForFiles } from "../api/axiosInstance"; +import { API_ENDPOINTS } from "../api/apiEndpoints"; +import { sendMessage } from "../services/chatApi"; +import UpgradeModal from "../components/common/UpgradeModal"; + const InterestSendPage = () => { const navigate = useNavigate(); @@ -84,18 +90,34 @@ const InterestSendPage = () => { const handleStatusUpdate = async (profileId, status) => { try { const response = await updateInterestStatus(profileId, status); - if (response.status === "success" || response.success) { - toast.success(response.message || `Request ${status}ed successfully`); + const isSuccess = response.status === "success" || response.success === true || response.status === true; + const message = response.message || ""; + + if (isSuccess) { + toast.success(message || `Request ${status}ed successfully`); fetchInterests(); } else { - toast.error(response.message || "Failed to update status"); + // Check for daily interest accept limit (from Flutter code) + if (message.toLowerCase().includes("daily interest accept limit") || + message.toLowerCase().includes("limit has been reached")) { + // Trigger the Upgrade Modal via a state if needed, but here we can use a simpler approach + // or just find a way to open the modal from the ProfileCard. + // For now, let's assume we can trigger a global upgrade modal or just use toast. + // Actually, let's pass a function to show the modal or use a shared state. + // In this component, we can use a local state for the parent. + setIsGlobalUpgradeModalOpen(true); + } else { + toast.error(message || "Failed to update status"); + } } } catch (error) { console.error("Error updating status:", error); - toast.error("An error occurred"); + toast.error("An error occurred while updating status"); } }; + const [isGlobalUpgradeModalOpen, setIsGlobalUpgradeModalOpen] = useState(false); + const hasSubTabs = subTabs.hasOwnProperty(selectedTabIndex); return ( @@ -148,7 +170,7 @@ const InterestSendPage = () => { )} {/* Main Content */} -
+

{selectedTabIndex === 0 && `Matches yet to respond (${profiles.length})`} {selectedTabIndex === 1 && `You request sent to others (${profiles.length})`} @@ -177,18 +199,139 @@ const InterestSendPage = () => {

)}
+ + setIsGlobalUpgradeModalOpen(false)} + />
); }; const ProfileCard = ({ profile, tabIndex, onStatusUpdate }) => { const navigate = useNavigate(); + const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false); + const [isViewContactModalOpen, setIsViewContactModalOpen] = useState(false); + const [isChatConfirmModalOpen, setIsChatConfirmModalOpen] = useState(false); + const [isContactSuccessModalOpen, setIsContactSuccessModalOpen] = useState(false); + const [isInterestStatusModalOpen, setIsInterestStatusModalOpen] = useState(false); + const [isInterestRejectedModalOpen, setIsInterestRejectedModalOpen] = useState(false); + const [unlockedMobile, setUnlockedMobile] = useState(null); + const [isCreatingChat, setIsCreatingChat] = useState(false); + + // New state for Accept/Reject confirmation + const [statusConfirm, setStatusConfirm] = useState({ open: false, status: null }); + + + const handleCall = (e) => { + e.stopPropagation(); + + const interestStatus = (profile.is_send_interest_status || "").toLowerCase(); + + // 1. Check protection & interest status (same as handleMessage) + if (profile.chat_protection === 1 || profile.call_protection === 1) { + if (profile.is_send_interest) { + if (interestStatus === 'pending') { + setIsInterestStatusModalOpen(true); + return; + } else if (interestStatus === 'reject' || interestStatus === 'rejected') { + setIsInterestRejectedModalOpen(true); + return; + } + } + } + + const mobile = (profile.mobile_number || "").toLowerCase(); + + if (mobile.includes("upgrade")) { + setIsUpgradeModalOpen(true); + } else if (mobile.includes("view contact")) { + setIsViewContactModalOpen(true); + } else { + setUnlockedMobile(profile.mobile_number); + setIsContactSuccessModalOpen(true); + } + }; + + + const handleMessage = (e) => { + e.stopPropagation(); + + // 1. Check if chat already exists + if (profile.chat_id) { + navigate(`/chat/${profile.chat_id}`); + return; + } + + const interestStatus = (profile.is_send_interest_status || "").toLowerCase(); + + // 2. Check protection & interest status (same as ProfileCardUI) + if (profile.chat_protection === 1 || profile.call_protection === 1) { + if (profile.is_send_interest) { + if (interestStatus === 'pending') { + setIsInterestStatusModalOpen(true); + return; + } else if (interestStatus === 'reject' || interestStatus === 'rejected') { + setIsInterestRejectedModalOpen(true); + return; + } + } + } + + // 3. Show confirmation dialog + setIsChatConfirmModalOpen(true); + }; + + + const _handleCreateChat = async () => { + if (isCreatingChat) return; + setIsChatConfirmModalOpen(false); + setIsCreatingChat(true); + try { + const response = await axiosInstance.post(API_ENDPOINTS.CHAT_CREATE, { profile_id: profile.id }); + if (response.data?.status === true || response.data?.status === 'success') { + const newChatId = response.data?.chat_id; + try { + await sendMessage(newChatId, "This profile has initiated a chat with you"); + } catch (err) {} + toast.success("Chat initiated!"); + navigate(`/chat/${newChatId}`); + } else { + setIsUpgradeModalOpen(true); + } + } catch (error) { + toast.error("Failed to start conversation"); + } finally { + setIsCreatingChat(false); + } + }; + + const _handleViewContact = async () => { + setIsViewContactModalOpen(false); + try { + const formData = new FormData(); + formData.append("profile_id", profile.id); + const response = await apiForFiles.post(API_ENDPOINTS.VIEW_CONTACT, formData); + if (response.data?.status === 'success') { + setUnlockedMobile(response.data?.mobile_number); + setIsContactSuccessModalOpen(true); + toast.success("Contact details unlocked!"); + } else { + setIsUpgradeModalOpen(true); + } + } catch (error) { + toast.error("Failed to view contact"); + } + }; + return ( + <>
navigate(`/profile-details/${profile.id}`)} className="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden hover:shadow-md transition-shadow cursor-pointer p-4" > +
{/* Profile Image */}
@@ -252,36 +395,152 @@ const ProfileCard = ({ profile, tabIndex, onStatusUpdate }) => { {tabIndex === 0 && profile.statusReceived === 'pending' ? ( <> - ) : (tabIndex === 1 || tabIndex === 2 || (tabIndex === 0 && profile.statusReceived === 'accept')) ? ( + ) : (tabIndex === 1 || tabIndex === 2 || (tabIndex === 0 && profile.statusReceived !== 'pending')) ? ( + <> + ) : null}
+ + setIsUpgradeModalOpen(false)} /> + + + {/* Accept/Reject Status Confirmation Dialog */} + {statusConfirm.open && ( +
+ +
+ {statusConfirm.status === 'accept' ? ( + + ) : ( + + )} +
+

Note

+

+ {statusConfirm.status === 'accept' + ? "Are you sure you want to accept this interest request? By continuing, your photos, contact details, and chat access will be shared with this match." + : "Are you sure you want to reject this match?"} +

+
+ + +
+
+
+ )} + + {isViewContactModalOpen && ( +
+ +

Note

+

You need to view this profile's contact details. If you choose to "Proceed" one count will be deducted from your subscription.

+
+ + +
+
+
+ )} + + {isChatConfirmModalOpen && (() => { + const currentMobile = profile.mobile_number || ""; + const isMobileVisible = currentMobile !== "" && !currentMobile.toLowerCase().includes("view") && !currentMobile.toLowerCase().includes("upgrade"); + return ( +
+ +

{isMobileVisible ? "Ready to Chat?" : "Note"}

+

{isMobileVisible ? `Are you ready to chat with ${currentMobile}?` : "Starting a conversation will use 1 chat count. Are you ready to proceed?"}

+
+ + +
+
+
+ ); + })()} + + {isContactSuccessModalOpen && ( +
+ +
+

Success!

+

The contact number has been unlocked.

+
+ + {unlockedMobile || (!profile.mobile_number?.toLowerCase().includes("view") ? profile.mobile_number : "Fetching...")} + +
+ +
+ + +
+
+
+ )} + + {isInterestStatusModalOpen && ( +
+ +

Note

+

An interest request has already been sent for this profile. Please wait for their response.

+ +
+
+ )} + + {isInterestRejectedModalOpen && ( +
+ +

Sorry

+

Your interest request was rejected by this user. You cannot initiate a chat at this time.

+ +
+
+ )} +
+ + ); }; diff --git a/src/pages/SubscriptionHistory.jsx b/src/pages/SubscriptionHistory.jsx index 2eef6ff..17ffe4f 100644 --- a/src/pages/SubscriptionHistory.jsx +++ b/src/pages/SubscriptionHistory.jsx @@ -1,177 +1,268 @@ -// src/pages/SubscriptionHistory.jsx import React from 'react'; -import { ArrowBackIosNew, CalendarToday, AccessTime, CreditCard, Person, Visibility } from '@mui/icons-material'; +import { ArrowBackIosNew, CalendarToday, AccessTime, CreditCard, Person, Visibility, History, WorkspacePremium, ReceiptLong, LocalActivity } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; -import { motion } from 'framer-motion'; -import { CrownIcon } from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { CrownIcon, ShieldCheck, Zap, ArrowRight, CheckCircle2 } from 'lucide-react'; +import { useQuery } from '@tanstack/react-query'; +import { getSubscriptionHistory } from '../api/subscription.api'; +import { CircularProgress, Box, Typography, Chip } from '@mui/material'; -const subscriptions = [ - { - id: "SUB722HSN", - plan: "Gold Plan", - profileCount: 150, - usedCount: 70, - billingCycle: "Monthly", - expireDate: "19/11/2025", - startDate: "19/10/2025", - startTime: "10:00 AM", - paymentMethod: "UPI Mode", - amount: 1800, - isActive: true - }, - { - id: "SUB722HSN", - plan: "Gold Plan", - profileCount: 150, - usedCount: 70, - billingCycle: "Monthly", - expireDate: "19/11/2025", - startDate: "19/10/2025", - startTime: "10:00 AM", - paymentMethod: "UPI Mode", - amount: 1800, - isActive: false - }, - - { id: "SUB722HSN", - plan: "Gold Plan", - profileCount: 150, - usedCount: 70, - billingCycle: "Monthly", - expireDate: "19/11/2025", - startDate: "19/10/2025", - startTime: "10:00 AM", - paymentMethod: "UPI Mode", - amount: 1800, - isActive: false - } -]; +const PlanCard = ({ sub, isActive = false, index = 0 }) => { + if (!sub) return null; + + return ( + + {/* Dynamic Background Pattern */} +
+ + {/* Decorative Gradient Glow */} + {isActive && ( +
+ )} + + {/* Header Section */} +
+ {/* Status Badge */} +
+ {isActive ? ( + + ACTIVE PLAN + + ) : ( +
+ COMPLETED +
+ )} +
+ +

{sub.plan_name}

+

Transaction ID: #TK{sub.id}

+
+ +
+ {/* Stats Grid */} +
+
+
+ +
+

+ {sub.count_of_profile_view === -1 ? '∞' : sub.count_of_profile_view} +

+

Profile Views

+
+
+
+ +
+

{sub.used_count_of_profile_view || 0}

+

Used Count

+
+
+ + {/* Benefits/Details List */} +
+ } label="Activated" value={new Date(sub.supscription_start_date).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })} /> + } label="Expiry" value={new Date(sub.supscription_expiry_date).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })} /> + } label="Duration" value={sub.plan_validity_days === -1 ? 'Lifetime Access' : `${sub.plan_validity_days} Days`} /> +
+ + {/* Price Tag */} +
+
+
+ +
+ Total Paid +
+ + ₹{sub.plan_amount?.toLocaleString()} + +
+
+ + ); +}; + +const DetailRow = ({ icon, label, value }) => ( +
+
+ {icon} + {label} +
+ {value} +
+); export default function SubscriptionHistory() { const navigate = useNavigate(); + const { data: historyData, isLoading, error } = useQuery({ + queryKey: ['subscriptionHistory'], + queryFn: getSubscriptionHistory, + }); + + if (isLoading) { + return ( +
+ + + +
+ +
+
+ ); + } + + const currentSub = historyData?.current_subscription; + const history = historyData?.subscription_history || []; + return ( -
- {/* Royal Header */} +
+ {/* Glass Header */} -
- -

Subscription History

-
+
+ +
+

Subscriptions

+

Thirukalyanam Premium

+
+
+ +
- {/* Current Plan Title */} -
- - - Current Plan - -
+
+ {/* Hero Active Section */} +
+
+
+
+ +
+
+

Current Status

+

Active Plan Details

+
+
+ {currentSub && ( +
+ + Verified Membership +
+ )} +
-
- {/* Subscription Cards */} -
- {subscriptions.map((sub, index) => ( - - {/* Crown Badge */} -
- - {sub.plan} - {sub.isActive && ACTIVE} -
- -
- {/* Subscription ID */} -
-

Subscription ID

-

{sub.id}

-
- - {/* Stats Row */} -
-
- -

{sub.profileCount}

-

Profile Count

-
-
- -

{sub.usedCount}

-

Used Count

-
-
- - {/* Details Grid */} -
-
- - Billing Cycle - - {sub.billingCycle} -
-
- - Expire Date - - {sub.expireDate} -
-
- - Start Date & Time - - {sub.startDate} | {sub.startTime} -
-
- - Payment Method - - {sub.paymentMethod} -
-
- - {/* Total Amount */} -
-

Total Plan Amount

-

- ₹{sub.amount.toLocaleString()} -

+ {currentSub ? ( +
+ +
+
+ +
+
+

Premium Benefits Active

+

+ You are currently enjoying the full suite of Thirukalyanam premium features. + Your account is prioritized in searches and you have direct access to contact details. +

+
+ {['Verified Profile', 'Priority Search', 'Direct Contact', 'Chat Enabled'].map((item) => ( +
+ {item} +
+ ))} +
+
- - ))} -
-
- {/* Final Message */} - {/* -

Your journey to forever is fully powered

-

Enjoy unlimited matches with your Gold Plan

-
*/} + ) : ( + +
+ +
+

No Active Membership

+

+ Unlock premium matchmaking features to find your soulmate faster. +

+ +
+ )} +
+ + {/* Timeline History Section */} +
+
+
+ +
+
+

Purchase History

+

Your payment journey

+
+
+ + {history.length > 0 ? ( +
+ {history.map((sub, index) => ( + + ))} +
+ ) : ( +
+
+ +
+

No Previous History

+
+ )} +
+
+
); } \ No newline at end of file diff --git a/src/pages/SubscriptionPlan.jsx b/src/pages/SubscriptionPlan.jsx index 1bf3893..246da29 100644 --- a/src/pages/SubscriptionPlan.jsx +++ b/src/pages/SubscriptionPlan.jsx @@ -1,64 +1,177 @@ import React, { useState } from 'react'; -import { ArrowBackIosNew, Check, Star, AutoAwesome } from '@mui/icons-material'; +import { ArrowBackIosNew, Check, Star, AutoAwesome, InfoOutlined } from '@mui/icons-material'; import { useNavigate } from 'react-router-dom'; import { motion } from 'framer-motion'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { getSubscriptionPlans, purchaseSubscription } from '../api/subscription.api'; +import { toast } from 'react-hot-toast'; +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, CircularProgress } from '@mui/material'; + + + + + +const loadRazorpayScript = () => { + return new Promise((resolve) => { + if (window.Razorpay) { + resolve(true); + return; + } + const script = document.createElement("script"); + script.src = "https://checkout.razorpay.com/v1/checkout.js"; + script.onload = () => resolve(true); + script.onerror = () => resolve(false); + document.body.appendChild(script); + }); +}; export default function SubscriptionPlan() { const navigate = useNavigate(); - const [isAnnual, setIsAnnual] = useState(true); + const queryClient = useQueryClient(); + const [purchaseLoading, setPurchaseLoading] = useState(false); + const [confirmDialog, setConfirmDialog] = useState({ open: false, plan: null }); - const plans = [ - { - name: "Gold Plan", - monthlyPrice: 1800, - annualPrice: 1299, - color: "from-amber-400 to-yellow-600", - button: "from-amber-500 to-orange-600", - features: [ - "Unlimited Profile Views", - "Send Personalized Messages", - "Featured Profile for 30 Days", - "Priority Customer Support", - "Verified Badge", - "Horoscope Matching", - "100+ Daily Matches" - ] - }, - { - name: "Diamond Plan", - monthlyPrice: 2800, - annualPrice: 1999, - color: "from-purple-500 to-pink-600", - button: "from-purple-600 to-pink-700", - popular: true, - features: [ - "Everything in Gold", - "Top of Search Results", - "Profile Highlight in Gold Border", - "Personal Relationship Manager", - "Video Call with Matches", - "Astro Compatibility Report", - "200+ Premium Matches Daily" - ] - }, - { - name: "Platinum Plan", - monthlyPrice: 4800, - annualPrice: 3499, - color: "from-emerald-500 to-teal-600", - button: "from-emerald-600 to-teal-700", - features: [ - "Everything in Diamond", - "Guaranteed 10+ Interests/Month", - "Exclusive VIP Events Invite", - "Profile Boost Every Week", - "Family Member Login Access", - "Lifetime Profile Visibility", - "Dedicated Matchmaking Expert" - ] + const { data: plansData, isLoading, error } = useQuery({ + queryKey: ['subscriptionPlans'], + queryFn: getSubscriptionPlans, + }); + + const plans = plansData?.data || []; + + const handlePurchase = async (plan) => { + setPurchaseLoading(true); + try { + console.log("Initiating purchase for plan:", plan.id); + const res = await purchaseSubscription(plan.id); + console.log("Purchase Initiation Response:", res); + + // Support both res.success (older API) and res.status === 'success' (newer API) + if (res.success || res.status === 'success') { + const data = res.data; + + // Ensure Razorpay script is loaded + const isLoaded = await loadRazorpayScript(); + if (!isLoaded || !window.Razorpay) { + toast.error("Razorpay SDK failed to load. Please check your internet connection."); + setPurchaseLoading(false); + return; + } + + if (!data.order_id || !data.key_id) { + toast.error("Invalid order details received from server."); + setPurchaseLoading(false); + return; + } + + const options = { + key: data.key_id, + amount: data.amount, + currency: "INR", + name: "Thirukalyanam", + description: data.plan_name || plan.plan_name, + order_id: data.order_id, + handler: async (response) => { + try { + toast.loading("Verifying payment...", { id: "verify-payment" }); + const verifyRes = await purchaseSubscription(plan.id, { + razorpay_payment_id: response.razorpay_payment_id, + razorpay_order_id: response.razorpay_order_id, + razorpay_signature: response.razorpay_signature, + }); + + if (verifyRes.success || verifyRes.status === 'success') { + toast.success("Subscription activated successfully!", { id: "verify-payment" }); + queryClient.invalidateQueries(['subscriptionPlans']); + queryClient.invalidateQueries(['headerDetails']); + queryClient.invalidateQueries(['dashboardDetails']); + } else { + toast.error(verifyRes.message || "Verification failed", { id: "verify-payment" }); + } + } catch (err) { + console.error("Verification Error:", err); + toast.error("Verification failed", { id: "verify-payment" }); + } finally { + setPurchaseLoading(false); + } + }, + prefill: { + name: data.name || "", + email: data.email || "", + contact: data.mobile || data.mobile_number || "", + }, + theme: { + color: "#A70710", + }, + modal: { + ondismiss: function() { + console.log("Payment modal closed by user"); + setPurchaseLoading(false); + } + } + }; + + console.log("Opening Razorpay with options:", options); + const rzp = new window.Razorpay(options); + rzp.open(); + } else { + toast.error(res.message || "Failed to initiate purchase"); + setPurchaseLoading(false); + } + } catch (err) { + console.error("Purchase Error:", err); + toast.error(err.response?.data?.message || "Something went wrong during purchase initiation"); + setPurchaseLoading(false); } - ]; + }; + + const getPlanFeatures = (plan) => { + const features = [ + { text: `Profile Views: ${plan.count_of_profile_view === -1 ? 'Unlimited' : plan.count_of_profile_view}`, available: true }, + { text: `Validity: ${plan.plan_validity_days === -1 ? 'Unlimited' : `${plan.plan_validity_days} Days`}`, available: true }, + { text: "View Mobile Number", available: plan.can_view_mobile_number }, + { text: "View Email ID", available: plan.can_view_email }, + { text: "View Raasi", available: plan.can_view_raasi }, + { text: "View Star", available: plan.can_view_star }, + { text: "View Father Name", available: plan.can_view_father_name }, + { text: "View Mother Name", available: plan.can_view_mother_name }, + { text: "View DOB", available: plan.can_view_dob }, + { text: "View TOB", available: plan.can_view_tob }, + { text: "Start Chat", available: plan.can_start_chat }, + { text: "Send Interest", available: plan.can_send_interest }, + { text: "Accept Interest", available: plan.can_accept_interest }, + ]; + return features; + }; + + const getPlanColors = (index) => { + const colors = [ + { color: "from-amber-400 to-yellow-600", button: "bg-[#A70710]" }, + { color: "from-purple-500 to-pink-600", button: "bg-[#A70710]" }, + { color: "from-emerald-500 to-teal-600", button: "bg-[#A70710]" }, + { color: "from-blue-500 to-indigo-600", button: "bg-[#A70710]" }, + ]; + return colors[index % colors.length]; + }; + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+
+

Error loading plans

+

Please check your connection or try again later.

+
+
+ ); + } return (
@@ -70,113 +183,130 @@ export default function SubscriptionPlan() { >
- +

Subscription Plan

- {/* Annual / Monthly Toggle */} -
- - - - -
- {/* Pricing Cards */}
-
- {plans.map((plan, index) => ( - + {plans.map((plan, index) => { + const planColors = getPlanColors(index); + const features = getPlanFeatures(plan); + const isBuyable = plan.is_can_buyable === 1 && !plan.is_current_plan; - style={{background:"linear-gradient(98.05deg, #FAFBFF 0%, #FCC4FF 100%)"}} - > - {plan.popular && ( -
- MOST POPULAR -
- )} - -
-

{plan.name}

-
- ₹{isAnnual ? plan.annualPrice.toLocaleString() : plan.monthlyPrice.toLocaleString()} - /{isAnnual ? 'month' : 'month'} -
- {isAnnual && ( -

- Billed annually @ ₹{(plan.annualPrice * 12).toLocaleString()} -

+ return ( + + {plan.is_current_plan && ( +
+ CURRENT PLAN +
)} - - Select Plan - +
+

{plan.plan_name}

+
+ ₹{plan.plan_amount} + + /{plan.plan_validity_days === -1 ? 'Lifetime' : `${plan.plan_validity_days} days`} + +
-
-

Features

- {plan.features.map((feature, i) => ( -
- - {feature} + setConfirmDialog({ open: true, plan })} + className={`w-full py-4 px-8 rounded-xl text-white font-semibold text-[16px] shadow-xl transition-all ${ + isBuyable ? planColors.button : 'bg-gray-400 cursor-not-allowed opacity-70' + }`} + > + {purchaseLoading ? : + plan.is_current_plan ? "Current Plan" : "Select Plan"} + + +
+

Features

+
+ {features.map((feature, i) => ( +
+
+ + {!feature.available && } +
+ + {feature.text} + +
+ ))}
- ))} +
-
- - ))} + + ); + })}
- {/* Final CTA */} - {/* -

Find Your Perfect Life Partner

-

- Choose the plan that’s right for your sacred journey. Every premium member gets closer to their soulmate. +

+

+ Choose the plan that's right for you. Find your perfect life partner with our premium plans.

- - Choose This Plan - - */} +
-
+ {/* Confirmation Dialog */} + setConfirmDialog({ open: false, plan: null })} + PaperProps={{ + sx: { borderRadius: '20px', padding: '10px' } + }} + > + + Note + + +

+ If you choose a new plan, your current plan will expire and the new plan will be activated. +

+
+ + + + +
+ +
); -} \ No newline at end of file +} diff --git a/src/pages/UserDashboardHome.jsx b/src/pages/UserDashboardHome.jsx index 7792923..4ce3cbc 100644 --- a/src/pages/UserDashboardHome.jsx +++ b/src/pages/UserDashboardHome.jsx @@ -18,6 +18,8 @@ const MatrimonyArticles = lazy(() => import("../components/profiledashboard/Matr const MatchingList = lazy(() => import("../components/profiledashboard/MatchingList")); const VideoSwiperGallery = lazy(() => import("../components/profiledashboard/VideoSwiperGallery")); const Profilecardemo = lazy(() => import("../components/ui/ProfileCardDemo")); +const DailyRecommendedCard = lazy(() => import("../components/profiledashboard/DailyRecommendedCard")); + const images = [ @@ -130,97 +132,95 @@ const UserDashboardHome = () => {
}> - + - {/* */} - }> { becomePaidMember={dashboardData?.become_paid_member} /> - {/* */} + + }> diff --git a/src/redux/filterSlice.jsx b/src/redux/filterSlice.jsx index 01f8367..127e634 100644 --- a/src/redux/filterSlice.jsx +++ b/src/redux/filterSlice.jsx @@ -18,7 +18,8 @@ const initialState = { district: [], diet: "", family_type: [], - filter_type: "all_matches", + filter_type: "", + page: 1, isPaidMember: false, search: "", diff --git a/src/redux/registrationFormSlice.jsx b/src/redux/registrationFormSlice.jsx index bb20037..006261f 100644 --- a/src/redux/registrationFormSlice.jsx +++ b/src/redux/registrationFormSlice.jsx @@ -73,8 +73,16 @@ const registrationformSlice = createSlice({ willingToGoAbroad: "", }, lifestyleDetails: { - diets: [], - hobbies: [], + dayOfBirth: "", + raasi: "", + star: "", + patham: "", + lagnam: "", + panjangam_type: "", + dasa_balance: "", + dasa_years: "", + dasa_months: "", + dasa_days: "", dob: "", tob: "", placeOfBirth: "", @@ -108,13 +116,22 @@ const registrationformSlice = createSlice({ }, }, partnerPreferences: { - ageRange: "", + age_from: "", + age_to: "", + height_from: "", + height_to: "", + marital_statuses: [], + birth_stars: [], castes: [], - subCastes: [], - occupations: [], + sub_castes: [], educations: [], - hobbies: [], - annualIncome: "", + occupations: [], + employee_types: [], + currencies: [], + inr_from: "", + inr_to: "", + usd_from: "", + usd_to: "", states: [], districts: [], }, @@ -158,15 +175,20 @@ const registrationformSlice = createSlice({ } if (step <= 4) { state.lifestyleDetails = { - diets: "", hobbies: [], dob: "", tob: "", placeOfBirth: "", + dayOfBirth: "", raasi: "", star: "", patham: "", lagnam: "", panjangam_type: "", + dasa_balance: "", dasa_years: "", dasa_months: "", dasa_days: "", + dob: "", tob: "", placeOfBirth: "", graha: { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [] }, amsam: { 1: [], 2: [], 3: [], 4: [], 5: [], 6: [], 7: [], 8: [], 9: [], 10: [], 11: [], 12: [] }, }; } if (step <= 5) { state.partnerPreferences = { - ageRange: "", castes: [], subCastes: [], occupations: [], educations: [], - hobbies: [], annualIncome: "", states: [], districts: [], + age_from: "", age_to: "", height_from: "", height_to: "", + marital_statuses: [], birth_stars: [], castes: [], sub_castes: [], + educations: [], occupations: [], employee_types: [], + currencies: [], inr_from: "", inr_to: "", usd_from: "", usd_to: "", + states: [], districts: [], }; } }, @@ -266,8 +288,16 @@ preloadDummyProfile: (state) => { }; state.lifestyleDetails = { ...state.lifestyleDetails, - diets: [1], - hobbies: [1, 3], + dayOfBirth: "Monday", + raasi: 1, + star: 1, + patham: "1", + lagnam: 1, + panjangam_type: "Thirukanitham", + dasa_balance: "SURIYAN", + dasa_years: "5", + dasa_months: "2", + dasa_days: "10", dob: "1995-05-01", tob: "09:30", placeOfBirth: "Chennai", @@ -302,13 +332,20 @@ preloadDummyProfile: (state) => { }; state.partnerPreferences = { ...state.partnerPreferences, - ageRange: 2, + age_from: 25, + age_to: 30, + height_from: 1, + height_to: 10, + marital_statuses: [11], + birth_stars: [1, 2], castes: [1], - subCastes: [1], + sub_castes: [1], occupations: [57], educations: [14], - hobbies: [1, 3], - annualIncome: 1, + employee_types: [3], + currencies: ["INR"], + inr_from: "100000", + inr_to: "120000", states: [31], districts: [1], }; diff --git a/src/redux/store.js b/src/redux/store.js index 6161766..eab3332 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -28,7 +28,9 @@ export const store = configureStore({ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { - ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], + ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, "registerform/updatePersonalDetails"], + ignoredActionPaths: ["payload.profiles", "payload.file"], + ignoredPaths: ["registerform.personalDetails.profiles"], }, }), }); diff --git a/src/routes/UserRoutes.jsx b/src/routes/UserRoutes.jsx index 3ac4718..2ef7808 100644 --- a/src/routes/UserRoutes.jsx +++ b/src/routes/UserRoutes.jsx @@ -55,8 +55,11 @@ const UserRoutes = () => { }> } /> + } /> + + }> } /> diff --git a/src/services/profileActionApi.js b/src/services/profileActionApi.js index 87c40ae..78400e6 100644 --- a/src/services/profileActionApi.js +++ b/src/services/profileActionApi.js @@ -51,15 +51,13 @@ export const getInterestList = async (tab, type) => { } }; -export const updateInterestStatus = async (profile_id, status) => { +export const updateInterestStatus = async (id, status) => { try { - const response = await axiosInstance.post(API_ENDPOINTS.UPDATE_INTEREST_STATUS, { - profile_id, - status - }); + const response = await axiosInstance.post(`${API_ENDPOINTS.UPDATE_INTEREST_STATUS}?id=${id}&status=${status}`); return response.data; } catch (error) { console.error("Error updating interest status:", error); throw error; } }; + diff --git a/vite.config.js b/vite.config.js index 49e38a3..142fadf 100644 --- a/vite.config.js +++ b/vite.config.js @@ -15,5 +15,14 @@ export default defineConfig({ "@": path.resolve(__dirname, "src"), }, }, + server: { + proxy: { + '/backend': { + target: 'https://www.thirukalyanam.amrithaa.net', + changeOrigin: true, + // rewrite: (path) => path.replace(/^\/backend/, '/backend') // keep /backend + } + } + } })