Implement Subscription History and fix Interest Status Update flow
This commit is contained in:
parent
4ba4ce1e1b
commit
b3d33aca9c
BIN
dist_new.zip
Normal file
BIN
dist_new.zip
Normal file
Binary file not shown.
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||
<link rel="icon" type="image/png" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> -->
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
/>
|
||||
|
||||
<title>thirukalyanam</title>
|
||||
<script src="https://checkout.razorpay.com/v1/checkout.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -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",
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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",
|
||||
});
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
27
src/api/subscription.api.js
Normal file
27
src/api/subscription.api.js
Normal file
@ -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;
|
||||
};
|
||||
3
src/assets/images/cashicon.svg
Normal file
3
src/assets/images/cashicon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.8716 6.28503V4.88837C11.8716 4.51795 11.7245 4.1627 11.4626 3.90077C11.2006 3.63885 10.8454 3.4917 10.475 3.4917H3.49164C3.12122 3.4917 2.76597 3.63885 2.50404 3.90077C2.24212 4.1627 2.09497 4.51795 2.09497 4.88837V9.07837C2.09497 9.44878 2.24212 9.80403 2.50404 10.066C2.76597 10.3279 3.12122 10.475 3.49164 10.475H4.8883M6.28497 13.2684H13.2683C13.6387 13.2684 13.994 13.1212 14.2559 12.8593C14.5178 12.5974 14.665 12.2421 14.665 11.8717V7.6817C14.665 7.31128 14.5178 6.95603 14.2559 6.69411C13.994 6.43218 13.6387 6.28503 13.2683 6.28503H6.28497C5.91455 6.28503 5.5593 6.43218 5.29738 6.69411C5.03545 6.95603 4.8883 7.31128 4.8883 7.6817V11.8717C4.8883 12.2421 5.03545 12.5974 5.29738 12.8593C5.5593 13.1212 5.91455 13.2684 6.28497 13.2684ZM11.1733 9.7767C11.1733 10.1471 11.0262 10.5024 10.7642 10.7643C10.5023 11.0262 10.1471 11.1734 9.77664 11.1734C9.40622 11.1734 9.05097 11.0262 8.78905 10.7643C8.52712 10.5024 8.37997 10.1471 8.37997 9.7767C8.37997 9.40628 8.52712 9.05103 8.78905 8.78911C9.05097 8.52718 9.40622 8.38003 9.77664 8.38003C10.1471 8.38003 10.5023 8.52718 10.7642 8.78911C11.0262 9.05103 11.1733 9.40628 11.1733 9.7767Z" stroke="#AA5806" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
3
src/assets/images/locationicon.svg
Normal file
3
src/assets/images/locationicon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.86372 15.4545C7.86372 15.4545 2.79321 11.1842 2.79321 6.98228C2.79321 5.50084 3.38171 4.08007 4.42926 3.03253C5.4768 1.98499 6.89756 1.39648 8.37901 1.39648C9.86046 1.39648 11.2812 1.98499 12.3288 3.03253C13.3763 4.08007 13.9648 5.50084 13.9648 6.98228C13.9648 11.1842 8.8943 15.4545 8.8943 15.4545C8.61222 15.7143 8.1479 15.7115 7.86372 15.4545ZM8.37901 9.42607C8.69993 9.42607 9.01771 9.36286 9.31421 9.24005C9.6107 9.11724 9.8801 8.93723 10.107 8.7103C10.334 8.48338 10.514 8.21397 10.6368 7.91748C10.7596 7.62099 10.8228 7.30321 10.8228 6.98228C10.8228 6.66136 10.7596 6.34358 10.6368 6.04709C10.514 5.75059 10.334 5.48119 10.107 5.25427C9.8801 5.02734 9.6107 4.84733 9.31421 4.72452C9.01771 4.60171 8.69993 4.5385 8.37901 4.5385C7.73088 4.5385 7.10929 4.79597 6.65099 5.25427C6.19269 5.71256 5.93522 6.33415 5.93522 6.98228C5.93522 7.63042 6.19269 8.252 6.65099 8.7103C7.10929 9.1686 7.73088 9.42607 8.37901 9.42607Z" fill="#DF1D46"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
4
src/assets/images/personicon.svg
Normal file
4
src/assets/images/personicon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.79346 12.5679C2.79346 11.8272 3.08771 11.1168 3.61148 10.593C4.13525 10.0693 4.84563 9.77502 5.58636 9.77502H11.1722C11.9129 9.77502 12.6233 10.0693 13.147 10.593C13.6708 11.1168 13.9651 11.8272 13.9651 12.5679C13.9651 12.9383 13.8179 13.2935 13.556 13.5554C13.2942 13.8172 12.939 13.9644 12.5686 13.9644H4.18991C3.81954 13.9644 3.46435 13.8172 3.20247 13.5554C2.94058 13.2935 2.79346 12.9383 2.79346 12.5679Z" stroke="#0E69BE" stroke-width="1.57101" stroke-linejoin="round"/>
|
||||
<path d="M8.3791 6.9822C9.53596 6.9822 10.4738 6.04438 10.4738 4.88752C10.4738 3.73066 9.53596 2.79285 8.3791 2.79285C7.22224 2.79285 6.28442 3.73066 6.28442 4.88752C6.28442 6.04438 7.22224 6.9822 8.3791 6.9822Z" stroke="#0E69BE" stroke-width="1.57101"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 847 B |
3
src/assets/images/religonicon.svg
Normal file
3
src/assets/images/religonicon.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.18966 15.361C3.80564 15.361 3.47701 15.2244 3.20377 14.9511C2.93053 14.6779 2.79368 14.349 2.79321 13.9645V2.79293C2.79321 2.40891 2.93006 2.08028 3.20377 1.80704C3.47747 1.5338 3.8061 1.39695 4.18966 1.39648H12.5684C12.9524 1.39648 13.2812 1.53334 13.5549 1.80704C13.8287 2.08074 13.9653 2.40938 13.9648 2.79293V13.9645C13.9648 14.3486 13.8282 14.6774 13.5549 14.9511C13.2817 15.2248 12.9528 15.3614 12.5684 15.361H4.18966ZM4.18966 13.9645H12.5684V2.79293H11.1719V7.68051L9.42635 6.63317L7.68079 7.68051V2.79293H4.18966V13.9645Z" fill="#D00768"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 663 B |
@ -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 (
|
||||
<div
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="relative">
|
||||
<>
|
||||
<UpgradeModal
|
||||
isOpen={isUpgradeModalOpen}
|
||||
onClose={() => setIsUpgradeModalOpen(false)}
|
||||
/>
|
||||
|
||||
{/* View Contact Confirmation Modal */}
|
||||
<AnimatePresence>
|
||||
{isViewContactModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center relative"
|
||||
>
|
||||
<button onClick={() => setIsViewContactModalOpen(false)} className="absolute top-6 left-6 text-gray-400 hover:text-gray-600">
|
||||
<ChevronLeft size={24} />
|
||||
</button>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Note</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||
You need to view this profile's contact details. If you choose to <span className="text-pink-600 font-bold">"Proceed"</span> one count will be deducted from your subscription.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => setIsViewContactModalOpen(false)} className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500 hover:bg-gray-50">Cancel</button>
|
||||
<button onClick={_handleViewContact} className="flex-1 py-4 bg-[#DF1D46] text-white rounded-xl font-bold hover:bg-red-700 shadow-lg shadow-red-200">Proceed</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Interest Status Modal (Pending) */}
|
||||
{isInterestStatusModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||
>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">{modalTitle}</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">{modalMessage}</p>
|
||||
<button onClick={() => setIsInterestStatusModalOpen(false)} className="w-full py-4 bg-[#DF1D46] text-white rounded-xl font-bold hover:bg-red-700">OK</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Interest Rejected Modal */}
|
||||
{isInterestRejectedModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||
>
|
||||
<div className="w-20 h-20 bg-red-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<X size={40} className="text-[#DF1D46]" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-4">Note</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||
Your previous interest request was declined. You may resend your interest to this profile
|
||||
</p>
|
||||
<button onClick={() => setIsInterestRejectedModalOpen(false)} className="w-full py-4 bg-[#DF1D46] text-white rounded-xl font-bold hover:bg-red-700">OK</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Contact Success Modal */}
|
||||
{isContactSuccessModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||
>
|
||||
<div className="w-20 h-20 bg-green-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<Phone size={40} className="text-[#0C8626]" />
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">Success!</h3>
|
||||
<p className="text-gray-500 mb-6">The contact number has been unlocked.</p>
|
||||
|
||||
<div className="bg-gray-50 rounded-2xl p-6 mb-8 border border-gray-100 min-h-[80px] flex items-center justify-center">
|
||||
<span className="text-3xl font-black text-gray-900 tracking-wider">
|
||||
{unlockedMobile || (!profile.mobile_number?.toLowerCase().includes("view") ? profile.mobile_number : "Fetching...")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={() => setIsContactSuccessModalOpen(false)}
|
||||
className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
const finalMobile = unlockedMobile || profile.mobile_number;
|
||||
if (finalMobile) {
|
||||
const cleanMobile = finalMobile.replace(/[^0-9+]/g, '');
|
||||
window.location.href = `tel:${cleanMobile}`;
|
||||
}
|
||||
}}
|
||||
className="flex-1 py-4 bg-[#0C8626] text-white rounded-xl font-bold hover:bg-green-700 shadow-lg shadow-green-200 flex items-center justify-center gap-2"
|
||||
>
|
||||
<Phone size={18} /> Call Now
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Chat Confirmation Modal */}
|
||||
{isChatConfirmModalOpen && (() => {
|
||||
const currentMobile = profile.mobile_number || "";
|
||||
const mobileLower = currentMobile.toLowerCase();
|
||||
const isMobileVisible = currentMobile !== "" &&
|
||||
!mobileLower.includes("upgrade") &&
|
||||
!mobileLower.includes("view contact");
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center"
|
||||
>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
{isMobileVisible ? "Ready to Chat?" : "Note"}
|
||||
</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||
{isMobileVisible
|
||||
? `Are you ready to chat with ${currentMobile}?`
|
||||
: "Starting a conversation will use 1 chat count. Are you ready to proceed?"}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={() => setIsChatConfirmModalOpen(false)}
|
||||
className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500 hover:bg-gray-50"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={_handleCreateChat}
|
||||
className={`flex-1 py-4 text-white rounded-xl font-bold transition-all shadow-lg ${isCreatingChat ? "bg-gray-400" : "bg-[#DF1D46] hover:bg-red-700 shadow-red-200"}`}
|
||||
>
|
||||
{isCreatingChat ? "Starting..." : "Proceed"}
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
</AnimatePresence>
|
||||
|
||||
|
||||
|
||||
<div
|
||||
|
||||
onClick={() => 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"
|
||||
>
|
||||
<div className="relative">
|
||||
|
||||
{/* Premium Badge */}
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
@ -35,16 +425,16 @@ export default function ProfileCardUI({ profile }) {
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={(e) => {
|
||||
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"
|
||||
>
|
||||
<Bookmark className="w-4 h-4 text-gray-600" />
|
||||
<span className="text-[12px] font-medium text-gray-700">Shortlist</span>
|
||||
<Bookmark className={`w-4 h-4 transition-colors ${isShortlisted ? "text-black fill-black" : "text-gray-600"}`} />
|
||||
<span className="text-[12px] font-medium text-gray-700">
|
||||
{isShortlisted ? "Shortlisted" : "Shortlist"}
|
||||
</span>
|
||||
</motion.button>
|
||||
|
||||
|
||||
<div className="bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]">
|
||||
<img
|
||||
src={imageSrc}
|
||||
@ -66,77 +456,129 @@ export default function ProfileCardUI({ profile }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-4 pt-2 pb-4 flex flex-col gap-3 bg-white">
|
||||
<div className="flex items-center gap-2 text-gray-600">
|
||||
<VisibilityIcon sx={{ fontSize: 18 }} />
|
||||
<span className="text-[13px] font-medium">Last seen: {profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"}</span>
|
||||
</div>
|
||||
<div className="px-5 py-5 -mt-8 bg-white rounded-t-[32px] relative z-[2] shadow-[0_-12px_40px_rgba(0,0,0,0.1)] min-h-[280px] flex flex-col">
|
||||
|
||||
<div className="text-left mb-4">
|
||||
|
||||
<div className="grid grid-cols-2 gap-y-2 gap-x-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon sx={{ fontSize: 18, color: "#374151" }} />
|
||||
<span className="text-[14px] font-semibold text-gray-900">{profile.age ? `${profile.age} yrs` : "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<AccessibilityNewIcon sx={{ fontSize: 18, color: "#374151" }} />
|
||||
<span className="text-[14px] font-semibold text-gray-900">{profile.height ? `${profile.height} cm` : "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 col-span-2">
|
||||
<Receipt className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-semibold text-gray-900 truncate">{profile.annual_income_name || "N/A"}</span>
|
||||
<h2 className="text-[20px] font-bold text-gray-900 leading-tight">
|
||||
{profile.name}
|
||||
</h2>
|
||||
<div className="flex items-center justify-start gap-3 mt-1.5 text-[11px] font-semibold text-gray-400 uppercase tracking-wider">
|
||||
<span>ID: {profile.member_id || profile.id}</span>
|
||||
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||
<span className="flex items-center gap-1">
|
||||
<VisibilityIcon sx={{ fontSize: 14 }} />
|
||||
{profile.last_seen_at && profile.last_seen_at !== "-" ? profile.last_seen_at : "Recently"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4 text-gray-700">
|
||||
<div className="flex items-center gap-1.5" title="Raasi">
|
||||
<MoonStar className="w-4 h-4" />
|
||||
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.raasi_name || "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5" title="Star">
|
||||
<Sparkles className="w-4 h-4" />
|
||||
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.star_name || "-"}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5" title="Caste">
|
||||
<IdCard className="w-4 h-4" />
|
||||
<span className="text-[13px] font-medium truncate max-w-[80px]">{profile.caste_name || "-"}</span>
|
||||
</div>
|
||||
<div className="space-y-3 mt-2 flex-1">
|
||||
{(profile.age || profile.height) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={personIcon} alt="Person" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700">
|
||||
{profile.age ? `${profile.age} yrs` : ""}
|
||||
{profile.age && profile.height ? ", " : ""}
|
||||
{profile.height ? `${profile.height} cm` : ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(profile.religion_name || profile.caste_name) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-pink-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{profile.religion_name}
|
||||
{profile.religion_name && profile.caste_name ? " / " : ""}
|
||||
{profile.caste_name}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(profile.annual_income_name || profile.income) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-green-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={cashIcon} alt="Cash" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{profile.annual_income_name || profile.income}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(profile.district_name || profile.location) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={locationIcon} alt="Location" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{profile.district_name || profile.location}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon sx={{ fontSize: 18, color: "#DC2626" }} />
|
||||
<span className="text-[14px] font-semibold text-gray-900 truncate">
|
||||
{profile.district_name || profile.location || "-"}
|
||||
{profile.state_name ? `, ${profile.state_name}` : ""}
|
||||
</span>
|
||||
<div className="flex gap-3 mt-6">
|
||||
{profile.is_send_interest_received && profile.statusReceived?.toLowerCase() === 'pending' ? (
|
||||
<>
|
||||
<button
|
||||
onClick={handleInterest}
|
||||
className="flex-1 flex items-center justify-center gap-2 py-3 bg-[#0C8626] text-white rounded-2xl font-bold text-[13px] hover:bg-green-700 transition-all active:scale-[0.98]"
|
||||
>
|
||||
<Heart size={16} fill="white" /> Accept
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDecline}
|
||||
className="flex-1 flex items-center justify-center gap-2 py-3 bg-white border border-red-200 text-red-600 rounded-2xl font-bold text-[13px] hover:bg-red-50 transition-all active:scale-[0.98]"
|
||||
>
|
||||
<X size={16} /> Reject
|
||||
</button>
|
||||
</>
|
||||
) : (!(profile.is_send_interest || isLiked) && !profile.is_send_interest_received) ? (
|
||||
<>
|
||||
<button
|
||||
onClick={handleInterest}
|
||||
className={`flex-[1.2] flex items-center justify-center gap-2 py-3 rounded-2xl font-bold text-[13px] transition-all active:scale-[0.98] shadow-lg shadow-green-900/10 ${isLiked ? "bg-green-100 text-green-700 border border-green-200" : "bg-[#034E08] text-white hover:bg-green-800"}`}
|
||||
>
|
||||
<Heart size={16} fill={isLiked ? "#034E08" : "white"} />
|
||||
{isLiked ? "Sent" : "Interest"}
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); navigate(`/profile-details/${profile.id}`); }}
|
||||
className="flex-1 py-3 bg-gray-50 border border-gray-100 text-gray-600 rounded-2xl font-bold text-[13px] flex items-center justify-center gap-2 hover:bg-gray-100 transition-all active:scale-[0.98]"
|
||||
>
|
||||
<Eye size={16} /> Detail
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={handleCall}
|
||||
className="flex-1 flex items-center justify-center gap-2 py-3 bg-white border border-green-200 text-green-700 rounded-2xl font-bold text-[13px] hover:bg-green-50 transition-all active:scale-[0.98]"
|
||||
>
|
||||
<Phone size={16} /> Call
|
||||
</button>
|
||||
<button
|
||||
onClick={handleMessage}
|
||||
className="flex-1 flex items-center justify-center gap-2 py-3 bg-white border border-red-200 text-red-600 rounded-2xl font-bold text-[13px] hover:bg-red-50 transition-all active:scale-[0.98]"
|
||||
>
|
||||
<MessageSquare size={16} /> Message
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 mt-2">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); }}
|
||||
className="flex-1 flex items-center justify-center gap-2 px-4 py-2 bg-red-50 border border-red-200 text-red-700 rounded-full font-medium text-sm hover:bg-red-100 transition-colors active:scale-95"
|
||||
>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M18 6L6 18M6 6l12 12" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className={`flex-1 flex items-center justify-center gap-2 px-4 py-2 rounded-full font-medium text-sm border transition-colors active:scale-95 ${isLiked ? "bg-green-100 border-green-300 text-green-700" : "bg-green-50 border-green-200 text-green-700 hover:bg-green-100"}`}
|
||||
onClick={(e) =>{ e.stopPropagation(); setIsLiked(!isLiked); }}
|
||||
>
|
||||
{isLiked ? (
|
||||
<>
|
||||
<svg className="w-4 h-4 text-red-500 fill-current" viewBox="0 0 24 24"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg>
|
||||
Sent
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" strokeLinecap="round" strokeLinejoin="round"/></svg>
|
||||
Interest
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
75
src/components/common/TinderCard.jsx
Normal file
75
src/components/common/TinderCard.jsx
Normal file
@ -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 (
|
||||
<div
|
||||
className={className}
|
||||
style={{
|
||||
transform: `translate(${pos.x}px, ${pos.y}px) rotate(${rotation}deg)`,
|
||||
transition: dragging ? 'none' : 'transform 0.3s ease-out',
|
||||
touchAction: 'none'
|
||||
}}
|
||||
onMouseDown={(e) => 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 && (
|
||||
<div className="absolute top-8 left-8 z-10 border-4 border-green-500 text-green-500 px-4 py-2 rounded-lg text-2xl font-bold rotate-[-20deg]" style={{ opacity }}>
|
||||
LIKE
|
||||
</div>
|
||||
)}
|
||||
{pos.x < -50 && (
|
||||
<div className="absolute top-8 right-8 z-10 border-4 border-red-500 text-red-500 px-4 py-2 rounded-lg text-2xl font-bold rotate-[20deg]" style={{ opacity }}>
|
||||
NOPE
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TinderCard;
|
||||
62
src/components/common/UpgradeModal.jsx
Normal file
62
src/components/common/UpgradeModal.jsx
Normal file
@ -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 (
|
||||
<AnimatePresence>
|
||||
{isOpen && (
|
||||
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-4">
|
||||
{/* Backdrop */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
onClick={onClose}
|
||||
className="absolute inset-0 bg-black/40 backdrop-blur-sm"
|
||||
/>
|
||||
|
||||
{/* Modal Content */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
className="relative bg-white rounded-[32px] shadow-2xl w-full max-w-sm overflow-hidden p-10 flex flex-col items-center text-center"
|
||||
>
|
||||
<h4 className="text-[22px] font-bold text-gray-900 mb-6">Note</h4>
|
||||
|
||||
<h3 className="text-[20px] font-bold text-gray-900 mb-4 leading-tight">
|
||||
Membership Upgrade Required
|
||||
</h3>
|
||||
|
||||
<p className="text-[#888] text-[15px] leading-relaxed mb-10 px-2 font-medium">
|
||||
You are currently a Free Member. Upgrade to a Premium Plan to start sending interests and connecting with your perfect match.
|
||||
</p>
|
||||
|
||||
<div className="flex w-full gap-4">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="flex-1 py-4 border border-gray-200 text-gray-500 rounded-2xl font-bold text-[16px] hover:bg-gray-50 transition-all active:scale-[0.98]"
|
||||
>
|
||||
Later
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate('/subscription-plan');
|
||||
onClose();
|
||||
}}
|
||||
className="flex-1 py-4 bg-[#e91e4a] text-white rounded-2xl font-bold text-[16px] shadow-lg shadow-red-200 hover:bg-[#d81b45] transition-all active:scale-[0.98]"
|
||||
>
|
||||
Upgrade Now
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpgradeModal;
|
||||
@ -92,68 +92,69 @@ useEffect(() => {
|
||||
{
|
||||
id: "all_matches",
|
||||
icon: <PersonIcon className="w-6 h-6" />,
|
||||
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: <RockingChair className="w-6 h-6" />,
|
||||
title: "Newly Joined",
|
||||
description: "Joined within the last 30 days",
|
||||
category: "Primary Matches",
|
||||
},
|
||||
{
|
||||
id: "shorlisted_by_you",
|
||||
icon: <StarIcon className="w-6 h-6" />,
|
||||
title: "Shortlisted by you",
|
||||
description: "Matches you have shortlisted",
|
||||
category: "Based on activity",
|
||||
category: "Your Activity",
|
||||
},
|
||||
{
|
||||
id: "viewed_you",
|
||||
icon: <VisibilityIcon className="w-6 h-6" />,
|
||||
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: <PersonAddIcon className="w-6 h-6" />,
|
||||
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: <VisibilityIcon className="w-6 h-6" />,
|
||||
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: <RockingChair className="w-6 h-6" />,
|
||||
title: "Newly Joined",
|
||||
description: "Matches who Joined within the last 30 days",
|
||||
category: "Based on activity",
|
||||
},
|
||||
{
|
||||
id: "location_matches",
|
||||
icon: <LocateFixed className="w-6 h-6" />,
|
||||
title: "Location matches",
|
||||
description: "Matches near your location",
|
||||
category: "Based on activity",
|
||||
category: "Smart Matches",
|
||||
},
|
||||
{
|
||||
{
|
||||
id: "education_matches",
|
||||
icon: <School className="w-6 h-6" />,
|
||||
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: <WorkflowIcon className="w-6 h-6" />,
|
||||
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(() => {
|
||||
|
||||
<div className="w-full md:w-80">
|
||||
<div
|
||||
className="relative rounded-[10px] border border-gray-200 bg-white my-6
|
||||
shadow-lg h-[400px] md:h-[600px] overflow-y-auto md:sticky md:top-[150px]"
|
||||
className="relative rounded-[15px] border border-gray-200 bg-white my-6
|
||||
shadow-xl h-[450px] md:h-[calc(100vh-200px)] overflow-y-auto md:sticky md:top-[120px] custom-sidebar-scrollbar"
|
||||
>
|
||||
|
||||
|
||||
<div className="py-2 px-4 sticky top-0 bg-[#fff] ">
|
||||
<h2 className="text-xl font-bold text-green-900 mb-4 mt-6 first:mt-0">
|
||||
Filter Matches </h2>
|
||||
@ -188,14 +190,17 @@ useEffect(() => {
|
||||
return (
|
||||
<div key={tab.id}>
|
||||
{showCategory && (
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-4 mt-6 first:mt-0">
|
||||
<h2 className="text-xs font-bold text-green-700 uppercase tracking-widest mb-3 mt-6 first:mt-0 px-2 opacity-80">
|
||||
{tab.category}
|
||||
</h2>
|
||||
)}
|
||||
|
||||
<div
|
||||
onClick={() => {
|
||||
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(() => {
|
||||
|
||||
|
||||
|
||||
<style>{`
|
||||
.custom-sidebar-scrollbar::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
.custom-sidebar-scrollbar::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.custom-sidebar-scrollbar::-webkit-scrollbar-thumb {
|
||||
background: #e2e8f0;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.custom-sidebar-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||
background: #cbd5e1;
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
|
||||
|
||||
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/60 backdrop-blur-md p-4 overflow-y-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, y: 40 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
className="bg-white rounded-[40px] shadow-2xl w-full max-w-lg overflow-hidden p-10 flex flex-col items-center text-center my-auto"
|
||||
>
|
||||
<div className="w-24 h-24 bg-blue-50 rounded-full flex items-center justify-center mb-8">
|
||||
<div className="w-16 h-16 rounded-full border-2 border-blue-100 flex items-center justify-center">
|
||||
<Timer className="w-10 h-10 text-blue-900" strokeWidth={1.5} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-[28px] font-bold text-[#1e3a8a] mb-4">Daily Limit Exceeded</h3>
|
||||
<p className="text-gray-500 text-[16px] leading-relaxed mb-10 px-4">
|
||||
You've viewed all recommended profiles for today. Your daily limit will reset in:
|
||||
</p>
|
||||
|
||||
<div className="flex items-start justify-center gap-4 mb-12">
|
||||
{[
|
||||
{ label: 'HOURS', value: timeLeft.hours },
|
||||
{ label: 'MINS', value: timeLeft.minutes },
|
||||
{ label: 'SECS', value: timeLeft.seconds }
|
||||
].map((u, i) => (
|
||||
<div key={i} className="flex flex-col items-center">
|
||||
<div className="w-20 h-20 bg-white rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.06)] border border-gray-50 flex items-center justify-center mb-3">
|
||||
<span className="text-[32px] font-bold text-[#1e3a8a]">{fmt(u.value)}</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-bold text-gray-400 tracking-widest">{u.label}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={onContinue}
|
||||
className="w-full py-5 bg-[#009944] text-white rounded-2xl font-bold text-[18px] shadow-lg shadow-green-900/20 hover:bg-[#00883d] transition-all active:scale-[0.98]"
|
||||
>
|
||||
Continue to Dashboard
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
whileInView={{ opacity: 1, scale: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.5 }}
|
||||
onClick={() => navigate(`/profile-details/${profile.id}`)}
|
||||
className="w-full max-w-sm rounded-[10px] shadow-xl overflow-hidden border-1 border-green-200"
|
||||
className="w-full max-w-sm rounded-[24px] shadow-xl overflow-hidden border border-green-100 bg-white group hover:shadow-2xl transition-all"
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="relative overflow-hidden">
|
||||
{profile.isPremium && (
|
||||
<motion.div
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2, type: 'spring' }}
|
||||
className="absolute top-4 left-4 z-10 bg-orange-500 rounded-full p-2 shadow-lg"
|
||||
>
|
||||
<Crown className="w-5 h-5 text-white" />
|
||||
@ -128,150 +204,103 @@ const DailyRecommendedCard = () => {
|
||||
)}
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
className="absolute top-4 right-4 z-10 bg-white rounded-full px-4 py-2 shadow-lg flex items-center space-x-2 hover:bg-gray-50 transition-colors"
|
||||
onClick={(e) => {
|
||||
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}
|
||||
>
|
||||
<Bookmark className="w-4 h-4" />
|
||||
<span className="text-[12px] font-medium">Shortlist</span>
|
||||
<Bookmark
|
||||
className={`w-4 h-4 transition-colors ${isShortlisted ? "text-black fill-black" : "text-green-700"}`}
|
||||
/>
|
||||
<span className="text-[12px] font-bold text-gray-700">
|
||||
{isShortlisted ? "Shortlisted" : "Shortlist"}
|
||||
</span>
|
||||
</motion.button>
|
||||
|
||||
<div
|
||||
className=" bg-gray-200 overflow-hidden w-full max-w-sm h-[300px]"
|
||||
style={{ height: "300px" }}
|
||||
>
|
||||
<div className="bg-gray-200 overflow-hidden w-full h-[300px]">
|
||||
<img
|
||||
src={profile.image}
|
||||
src={image}
|
||||
alt={profile.name}
|
||||
className="w-full h-full object-cover"
|
||||
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="absolute bottom-0 left-0 right-0 h-35 pointer-events-none"
|
||||
className="absolute bottom-0 left-0 right-0 h-32 pointer-events-none"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.6) 40%, rgb(255, 255, 255) 100%)",
|
||||
background: "linear-gradient(to top, white 0%, rgba(255,255,255,0.8) 40%, transparent 100%)",
|
||||
}}
|
||||
></div>
|
||||
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-1 text-gray-900">
|
||||
<h1 className="text-[18px] text-green-900 font-bold mb-2">
|
||||
<div className="absolute bottom-0 left-0 right-0 p-6 pb-2">
|
||||
<h1 className="text-[20px] text-black-900 font-bold mb-1 truncate">
|
||||
{profile.name}
|
||||
</h1>
|
||||
<p className="text-[14px] text-gray-700 leading-relaxed">
|
||||
Matrimony ID: {profile.userId}
|
||||
<p className="text-[13px] text-gray-500 font-medium">
|
||||
ID: {memberId}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="px-4 pt-[-2px] pb-4 flex flex-col gap-2"
|
||||
style={{ background: "rgb(255, 255, 255)" }}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="px-6 pb-6 pt-2 space-y-3 bg-white min-h-[160px]">
|
||||
{(profile.age || profile.height) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<CakeIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Age : {profile.age}
|
||||
<img src={personIcon} alt="Person" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700">
|
||||
{profile.age ? `${profile.age} Yrs` : ''}
|
||||
{profile.age && profile.height ? ', ' : ''}
|
||||
{profile.height || ''}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{religion && (
|
||||
<div className="flex items-center gap-2">
|
||||
<AccessibilityNewIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
Height: {profile.height}
|
||||
<img src={religionIcon} alt="Religion" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{religion}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{(education || occupation) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<GroupsIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.religion}
|
||||
<SchoolIcon sx={{ fontSize: 18 }} className="text-gray-400" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{education || occupation}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{location && (
|
||||
<div className="flex items-center gap-2">
|
||||
<SchoolIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.education}
|
||||
<img src={locationIcon} alt="Location" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{location}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
{income && (
|
||||
<div className="flex items-center gap-2">
|
||||
<LocationOnIcon className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-[14px] font-600 text-gray-900">
|
||||
{profile.location}
|
||||
<img src={cashIcon} alt="Income" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{income}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3 my-2 justify-between w-full px-[0px]">
|
||||
<div className="flex gap-3 pt-4 justify-between">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
className="gap-2 px-3 w-[fit-content] bg-[#A70710] hover:bg-red-600 text-white
|
||||
font-semibold text-base py-2 rounded-[20px] shadow-md
|
||||
hover:shadow-lg transition-all duration-300 flex items-center justify-center transform hover:scale-95"
|
||||
onClick={(e) => handleDecline(e, profile.id)}
|
||||
className="flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-600 font-bold text-sm rounded-full transition-all flex items-center justify-center gap-2 active:scale-95"
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M18 6L6 18M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
Decline
|
||||
<X size={16} /> Decline
|
||||
</button>
|
||||
|
||||
<button
|
||||
className="w-[fit-content] bg-[#034E08] hover:bg-green-700 text-white font-semibold text-base
|
||||
rounded-[20px] px-3 gap-2 py-1 shadow-lg hover:shadow-xl transition-all duration-300
|
||||
transform hover:scale-105 flex items-center justify-center"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsLiked(!isLiked);
|
||||
}}
|
||||
className="flex-1 px-4 py-2 bg-[#034E08] hover:bg-green-800 text-white font-bold text-sm rounded-full transition-all flex items-center justify-center gap-2 shadow-lg shadow-green-900/20 active:scale-95"
|
||||
onClick={(e) => handleInterest(e, profile.id, profile.name)}
|
||||
>
|
||||
{isLiked ? (
|
||||
<svg className="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||
fill="#EF4444"
|
||||
/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path
|
||||
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
Interest
|
||||
<Heart size={16} fill="white" /> Interest
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -279,127 +308,70 @@ const DailyRecommendedCard = () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
if (isHidden) return null;
|
||||
|
||||
return (
|
||||
<div className=" py-10">
|
||||
<div className="w-[100%] max-w-[1400px] mx-auto">
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
<h1 className="text-[20px] text-[#00000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||
Daily Recommended
|
||||
</h1>
|
||||
<p className="text-gray-900 text-[12px]">Find your perfect match today</p>
|
||||
</motion.div>
|
||||
|
||||
{/* Swiper Container */}
|
||||
<div className="relative px-0 sm:px-0">
|
||||
<Swiper
|
||||
ref={swiperRef}
|
||||
modules={[Navigation, Pagination, Autoplay, EffectCoverflow]}
|
||||
spaceBetween={10}
|
||||
slidesPerView={1}
|
||||
autoplay={{
|
||||
delay: 5000,
|
||||
disableOnInteraction: false,
|
||||
pauseOnMouseEnter: true
|
||||
}}
|
||||
// pagination={{
|
||||
// clickable: true,
|
||||
// dynamicBullets: true
|
||||
// }}
|
||||
loop={true}
|
||||
speed={800}
|
||||
breakpoints={{
|
||||
640: {
|
||||
slidesPerView: 2,
|
||||
spaceBetween: 10
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 3,
|
||||
spaceBetween: 10
|
||||
},
|
||||
1280: {
|
||||
slidesPerView: 4,
|
||||
spaceBetween: 5
|
||||
}
|
||||
}}
|
||||
className="pb-16"
|
||||
<div className="py-12">
|
||||
<UpgradeModal
|
||||
isOpen={isUpgradeModalOpen}
|
||||
onClose={() => setIsUpgradeModalOpen(false)}
|
||||
/>
|
||||
|
||||
{activeProfiles.length === 0 ? (
|
||||
<CountdownTimer onContinue={handleContinueToDashboard} />
|
||||
) : (
|
||||
<div className="w-full max-w-[1400px] mx-auto px-4">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
>
|
||||
{profiles.map((profile) => (
|
||||
<SwiperSlide key={profile.id}>
|
||||
<ProfileCard profile={profile} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
<h2 className="text-3xl font-extrabold text-gray-900 mb-2">
|
||||
Daily <span className="text-green-700">Recommended</span>
|
||||
</h2>
|
||||
<p className="text-gray-500 font-medium">Handpicked matches just for you</p>
|
||||
</motion.div>
|
||||
|
||||
<div className="relative group">
|
||||
<Swiper
|
||||
ref={swiperRef}
|
||||
modules={[Navigation, Pagination, Autoplay, EffectCoverflow]}
|
||||
spaceBetween={24}
|
||||
slidesPerView={1}
|
||||
autoplay={{ delay: 4000, disableOnInteraction: false }}
|
||||
breakpoints={{
|
||||
640: { slidesPerView: 2 },
|
||||
1024: { slidesPerView: 3 },
|
||||
1280: { slidesPerView: 4 }
|
||||
}}
|
||||
className="!pb-12"
|
||||
>
|
||||
{activeProfiles.map((profile) => (
|
||||
<SwiperSlide key={profile.id}>
|
||||
<ProfileCard profile={profile} />
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
|
||||
<button
|
||||
onClick={() => swiperRef.current?.swiper.slidePrev()}
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 z-10 -translate-x-4 bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all hidden lg:flex border border-gray-100"
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6 text-green-800" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => swiperRef.current?.swiper.slideNext()}
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 z-10 translate-x-4 bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all hidden lg:flex border border-gray-100"
|
||||
>
|
||||
<ChevronRight className="w-6 h-6 text-green-800" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Custom Navigation Buttons */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, x: -5 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={() => 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 "
|
||||
>
|
||||
<ChevronLeft className="w-6 h-6 text-gray-700" />
|
||||
</motion.button>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.1, x: 5 }}
|
||||
whileTap={{ scale: 0.9 }}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<ChevronRight className="w-6 h-6 text-gray-700" />
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* View All Button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="text-center mt-12"
|
||||
>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="px-6 py-3 bg-[#034E08] text-white rounded-full font-semibold text-lg shadow-lg hover:shadow-xl transition-shadow"
|
||||
onClick={() => navigate("/matches")}
|
||||
|
||||
>
|
||||
View All Recommendations
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Custom Swiper Styles */}
|
||||
<style>{`
|
||||
.swiper-pagination-bullet {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #A70710;
|
||||
opacity: 0.5;
|
||||
|
||||
}
|
||||
|
||||
.swiper-pagination-bullet-active {
|
||||
opacity: 1;
|
||||
width: 30px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
// .swiper-slide {
|
||||
// // height: auto;
|
||||
// // display: flex;
|
||||
// }
|
||||
|
||||
`}</style>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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 }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} className={isShortlisted ? "text-black" : ""} />
|
||||
{shortlistMutation.isPending ? "..." : (isShortlisted ? "Shortlisted" : "Shortlist")}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||
{name}
|
||||
</h2>
|
||||
|
||||
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||
<p>ID: {idNumber}</p>
|
||||
<p className="flex items-center gap-0.5">
|
||||
<Eye size={12} /> {lastSeen}
|
||||
</p>
|
||||
<div className="px-5 py-5 -mt-8 bg-white rounded-t-[32px] relative z-[2] shadow-[0_-12px_40px_rgba(0,0,0,0.1)] h-[240px] flex flex-col">
|
||||
<div className="text-left mb-4">
|
||||
<h2 className="text-[20px] font-bold text-gray-900 leading-tight">
|
||||
{name}
|
||||
</h2>
|
||||
<div className="flex items-center justify-start gap-3 mt-1.5 text-[11px] font-semibold text-gray-400 uppercase tracking-wider">
|
||||
<span>ID: {idNumber}</span>
|
||||
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Eye size={12} /> {lastSeen}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
age,
|
||||
height,
|
||||
salary,
|
||||
location,
|
||||
caste,
|
||||
zodiac1,
|
||||
zodiac2,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
<div className="space-y-3 mt-2">
|
||||
{(age || height) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={personIcon} alt="Person" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700">
|
||||
{age || ""}
|
||||
{age && height ? ", " : ""}
|
||||
{height || ""}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||
}}
|
||||
disabled={declineMutation.isPending}
|
||||
>
|
||||
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||
</button>
|
||||
{(caste || zodiac1) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-pink-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{caste}
|
||||
{caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||
}}
|
||||
disabled={interestMutation.isPending}
|
||||
>
|
||||
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||
</button>
|
||||
{location && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={locationIcon} alt="Location" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{location}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -210,16 +213,19 @@ const MatchingList = ({ matches }) => {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
className="text-center mb-10"
|
||||
>
|
||||
<h1 className="text-[20px] text-[#00000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||
Your Matching List
|
||||
</h1>
|
||||
<p className="text-gray-900 text-[12px]">
|
||||
Find your perfect match today
|
||||
<h2 className="text-[28px] font-extrabold text-gray-900 mb-1">
|
||||
All Matches <span className="text-green-700">List</span>
|
||||
</h2>
|
||||
|
||||
<p className="text-gray-500 font-medium">
|
||||
We've found these profiles that match your preferences
|
||||
</p>
|
||||
</motion.div>
|
||||
|
||||
|
||||
|
||||
{/* Swiper Container */}
|
||||
<div className="relative">
|
||||
<Swiper
|
||||
|
||||
@ -11,6 +11,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';
|
||||
@ -119,67 +126,63 @@ const ProfileCard = ({ profile }) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} />
|
||||
{shortlistMutation.isPending ? "..." : "Shortlist"}
|
||||
<Bookmark size={14} fill={isShortlisted ? "#000" : "none"} className={isShortlisted ? "text-black" : ""} />
|
||||
{shortlistMutation.isPending ? "..." : (isShortlisted ? "Shortlisted" : "Shortlist")}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* CONTENT */}
|
||||
<div className="px-4 py-4 -mt-[60px] bg-white/65 backdrop-blur-[25px] rounded-t-[15px] shadow-[0_-10px_30px_rgba(0,0,0,0.15)] relative z-[2]">
|
||||
<h2 className="text-center text-[22px] font-semibold mb-1">
|
||||
{name}
|
||||
</h2>
|
||||
|
||||
<div className="flex justify-between items-center mb-2 text-[11px] text-gray-600 px-8">
|
||||
<p>ID: {idNumber}</p>
|
||||
<p className="flex items-center gap-0.5">
|
||||
<Eye size={12} /> {lastSeen}
|
||||
</p>
|
||||
<div className="px-5 py-5 -mt-8 bg-white rounded-t-[32px] relative z-[2] shadow-[0_-12px_40px_rgba(0,0,0,0.1)] h-[240px] flex flex-col">
|
||||
<div className="text-left mb-4">
|
||||
<h2 className="text-[20px] font-bold text-gray-900 leading-tight">
|
||||
{name}
|
||||
</h2>
|
||||
<div className="flex items-center justify-start gap-3 mt-1.5 text-[11px] font-semibold text-gray-400 uppercase tracking-wider">
|
||||
<span>ID: {idNumber}</span>
|
||||
<span className="w-1 h-1 bg-gray-300 rounded-full"></span>
|
||||
<span className="flex items-center gap-1">
|
||||
<Eye size={12} /> {lastSeen}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
age,
|
||||
height,
|
||||
salary,
|
||||
location,
|
||||
caste,
|
||||
zodiac1,
|
||||
zodiac2,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
<div className="space-y-3 mt-2">
|
||||
{(age || height) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={personIcon} alt="Person" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700">
|
||||
{age || ""}
|
||||
{age && height ? ", " : ""}
|
||||
{height || ""}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
{/* <button
|
||||
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!declineMutation.isPending) declineMutation.mutate(id);
|
||||
}}
|
||||
disabled={declineMutation.isPending}
|
||||
>
|
||||
<X size={18} /> {declineMutation.isPending ? "..." : "Decline"}
|
||||
</button> */}
|
||||
{(caste || zodiac1) && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-pink-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{caste}
|
||||
{caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-green-200 bg-green-50 text-green-900 flex items-center gap-1.5 font-semibold hover:bg-green-100 transition-colors ${interestMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (!interestMutation.isPending) interestMutation.mutate(id);
|
||||
}}
|
||||
disabled={interestMutation.isPending}
|
||||
>
|
||||
<Heart size={18} /> {interestMutation.isPending ? "..." : "Interest"}
|
||||
</button>
|
||||
{location && (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-orange-50 flex items-center justify-center flex-shrink-0">
|
||||
<img src={locationIcon} alt="Location" className="w-4 h-4 opacity-80" />
|
||||
</div>
|
||||
<span className="text-[13px] font-bold text-gray-700 truncate">
|
||||
{location}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
@ -208,14 +211,16 @@ const NewJoinedProfile = ({ profiles }) => {
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="text-center mb-12"
|
||||
className="text-center mb-10"
|
||||
>
|
||||
<h1 className="text-[20px] text-[#000000] sm:text-[22px] lg:text-[24px] font-semibold mb-3">
|
||||
New Joined
|
||||
</h1>
|
||||
<p className="text-gray-900 text-[12px]">Find your perfect match today</p>
|
||||
<h2 className="text-[28px] font-extrabold text-gray-900 mb-1">
|
||||
Newly <span className="text-green-700">Joined</span>
|
||||
</h2>
|
||||
<p className="text-gray-500 font-medium">Be the first to connect with our newest members</p>
|
||||
</motion.div>
|
||||
|
||||
|
||||
|
||||
{/* Swiper Container */}
|
||||
<div className="relative">
|
||||
<Swiper
|
||||
|
||||
@ -1,47 +1,75 @@
|
||||
import { useState } from "react";
|
||||
import React, { useState } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { Crown, Bookmark } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import CakeIcon from "@mui/icons-material/Cake";
|
||||
import AccessibilityNewIcon from "@mui/icons-material/AccessibilityNew";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
// 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 VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import SchoolIcon from "@mui/icons-material/School";
|
||||
import LocationOnIcon from "@mui/icons-material/LocationOn";
|
||||
|
||||
const buildDefaultRows = (profile) => [
|
||||
[
|
||||
{
|
||||
icon: <CakeIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: `Age: ${profile?.age ?? "-"}`,
|
||||
},
|
||||
{
|
||||
icon: <AccessibilityNewIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: `Height: ${profile?.height ?? "-"}`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <GroupsIcon className="w-4 h-4 text-gray-700" />,
|
||||
text:
|
||||
profile?.religion ||
|
||||
profile?.caste ||
|
||||
profile?.community ||
|
||||
"-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <SchoolIcon className="w-4 h-4 text-gray-700" />,
|
||||
text: profile?.education || profile?.qualification || "-",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
icon: <LocationOnIcon className="w-4 h-4 text-gray-700" />,
|
||||
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: <img src={personIcon} alt="Person" className="w-4 h-4 opacity-70" />,
|
||||
text: `${profile.age} Yrs`,
|
||||
});
|
||||
}
|
||||
if (profile?.height && profile?.height !== "-") {
|
||||
row.push({
|
||||
icon: row.length === 0 ? <img src={personIcon} alt="Person" className="w-4 h-4 opacity-70" /> : 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: <img src={religionIcon} alt="Religion" className="w-4 h-4 opacity-70" />,
|
||||
text: religionText,
|
||||
}]);
|
||||
}
|
||||
|
||||
// Row 3: Education
|
||||
const eduText = profile?.education || profile?.qualification || profile?.education_name;
|
||||
if (eduText && eduText !== "-") {
|
||||
rows.push([{
|
||||
icon: <SchoolIcon sx={{ fontSize: 16 }} className="text-gray-400" />,
|
||||
text: eduText,
|
||||
}]);
|
||||
}
|
||||
|
||||
// Row 4: Income
|
||||
const incomeText = profile?.income || profile?.annual_income || profile?.annual_income_name;
|
||||
if (incomeText && incomeText !== "-") {
|
||||
rows.push([{
|
||||
icon: <img src={cashIcon} alt="Cash" className="w-4 h-4 opacity-70" />,
|
||||
text: incomeText,
|
||||
}]);
|
||||
}
|
||||
|
||||
// Row 5: Location
|
||||
const locText = profile?.location || profile?.district_name;
|
||||
if (locText && locText !== "-") {
|
||||
rows.push([{
|
||||
icon: <img src={locationIcon} alt="Location" className="w-4 h-4 opacity-70" />,
|
||||
text: locText,
|
||||
}]);
|
||||
}
|
||||
|
||||
return rows;
|
||||
};
|
||||
|
||||
|
||||
const getProfileIdText = (profile) =>
|
||||
profile?.userId ||
|
||||
|
||||
@ -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 (
|
||||
<>
|
||||
<div className="max-w-[1400px] w-[100%] mx-auto my-10 px-2 sm:p-2 lg:p-2">
|
||||
<div className="max-w-[1400px] w-full mx-auto my-10 px-4">
|
||||
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
@ -111,66 +114,59 @@ const ProfileCompletion = ({ percentage = 0, missingDetails,becomePaidMember })
|
||||
</div>
|
||||
|
||||
{/* Cards Section */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2 ">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||||
{/* Left Side: Astro Chat */}
|
||||
<div className="w-full">
|
||||
<AstroChatUI />
|
||||
</div>
|
||||
|
||||
{/* Desktop Logo */}
|
||||
{/* <Box sx={{ display: { xs: "none", md: "flex" }, mr: 1 }}>
|
||||
<LazyImage
|
||||
src={Logo}
|
||||
className="w-full h-[50px] rounded-lg object-cover"
|
||||
/>
|
||||
</Box> */}
|
||||
|
||||
<AstroChatUI/>
|
||||
|
||||
<div className="my-4 rounded-2xl bg-green-50 border border-1 border-green-200 p-4 ">
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
||||
>
|
||||
{/* 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) => (
|
||||
<div
|
||||
key={card.id}
|
||||
onClick={() => 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 */}
|
||||
<div className="w-full">
|
||||
<div className="rounded-2xl bg-green-50 border border-green-200 p-6">
|
||||
<motion.div
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"
|
||||
>
|
||||
{/* Icon Container */}
|
||||
<motion.div
|
||||
initial={{ rotate: -180, opacity: 0 }}
|
||||
animate={{ rotate: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.9 + index * 0.1, duration: 0.5 }}
|
||||
// className={`${card.bgColor} p-0 rounded-xl border ${card.borderColor}`}
|
||||
>
|
||||
<LazyImage
|
||||
src={card.icon}
|
||||
alt={card.title}
|
||||
className="w-ful h-full "
|
||||
/>
|
||||
</motion.div>
|
||||
{cards.map((card, index) => (
|
||||
<div
|
||||
key={card.id}
|
||||
onClick={() => 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"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ rotate: -180, opacity: 0 }}
|
||||
animate={{ rotate: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.9 + index * 0.1, duration: 0.5 }}
|
||||
>
|
||||
<LazyImage
|
||||
src={card.icon}
|
||||
alt={card.title}
|
||||
className="w-full h-full"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Text */}
|
||||
<motion.h3
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 1 + index * 0.1, duration: 0.4 }}
|
||||
className="text-[14px] sm:text-[16px] font-semibold text-gray-900"
|
||||
>
|
||||
{card.title}
|
||||
</motion.h3>
|
||||
<motion.h3
|
||||
initial={{ opacity: 0, x: -10 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: 1 + index * 0.1, duration: 0.4 }}
|
||||
className="text-[14px] sm:text-[16px] font-semibold text-gray-900"
|
||||
>
|
||||
{card.title}
|
||||
</motion.h3>
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-6">
|
||||
<MembershipCard becomePaidMember={becomePaidMember} />
|
||||
</div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
<MembershipCard becomePaidMember={becomePaidMember} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
{/* Additional Info Section */}
|
||||
{/* <motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Heart, X, Crown, Bookmark, Eye, Clock, ChevronLeft, ChevronRight } 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 SchoolIcon from "@mui/icons-material/School";
|
||||
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import { Navigation, Pagination } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
@ -11,6 +18,7 @@ import toast from "react-hot-toast";
|
||||
import { shortlistProfile, sendInterest, declineProfile } from "../../services/shortlistapi";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
|
||||
const ProfileCardItem = ({ profile }) => {
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
@ -118,27 +126,48 @@ const ProfileCardItem = ({ profile }) => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap justify-center gap-2">
|
||||
{[
|
||||
age,
|
||||
height,
|
||||
salary,
|
||||
location,
|
||||
caste,
|
||||
zodiac1,
|
||||
zodiac2,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((v, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className="px-1.5 py-1.5 rounded-[20px] bg-white/70 border border-black/8 text-[13px]"
|
||||
>
|
||||
{v}
|
||||
<div className="flex flex-col gap-2 mt-4 min-h-[140px]">
|
||||
{(age || height) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={personIcon} alt="Person" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700">
|
||||
{age || ""}
|
||||
{age && height ? ", " : ""}
|
||||
{height || ""}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(caste || zodiac1) && (
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={religionIcon} alt="Religion" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{caste}
|
||||
{caste && zodiac1 ? ` (${zodiac1})` : zodiac1 || ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{salary && (
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={cashIcon} alt="Cash" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{salary}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{location && (
|
||||
<div className="flex items-center gap-2">
|
||||
<img src={locationIcon} alt="Location" className="w-5 h-5 opacity-70" />
|
||||
<span className="text-[14px] font-medium text-gray-700 truncate">
|
||||
{location}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex gap-4 mt-[15px] justify-center">
|
||||
<button
|
||||
className={`px-2 py-1 rounded-[20px] border border-red-200 bg-red-50 flex items-center gap-1.5 font-semibold text-red-900 hover:bg-red-100 transition-colors ${declineMutation.isPending ? "opacity-50 cursor-wait" : ""}`}
|
||||
|
||||
@ -73,35 +73,31 @@ const EducationalDetailsForm = ({
|
||||
|
||||
dispatch(updateEducationalDetails(updates));
|
||||
setLocalErrors((prev) => ({ ...prev, [field]: "" }));
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(3));
|
||||
}
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
if (!data.study_field) newErrors.study_field = "Required";
|
||||
if (!data.education) newErrors.education = "Required";
|
||||
if (!data.education_detail) newErrors.education_detail = "Required";
|
||||
if (!data.employee_type) newErrors.employee_type = "Required";
|
||||
if (!data.study_field) newErrors.study_field = "Field of study is required";
|
||||
if (!data.education) newErrors.education = "Highest qualification is required";
|
||||
if (!data.education_detail) newErrors.education_detail = "Education detail is required";
|
||||
if (!data.employee_type) newErrors.employee_type = "Employee type is required";
|
||||
|
||||
if (!isUnemployed) {
|
||||
if (!data.occupation) newErrors.occupation = "Required";
|
||||
if (!data.occupation_detail) newErrors.occupation_detail = "Required";
|
||||
if (!data.income_currency) newErrors.income_currency = "Required";
|
||||
if (!data.annual_income) newErrors.annual_income = "Required";
|
||||
if (!data.work_country) newErrors.work_country = "Required";
|
||||
if (!data.occupation) newErrors.occupation = "Occupation is required";
|
||||
if (!data.occupation_detail) newErrors.occupation_detail = "Occupation detail is required";
|
||||
if (!data.income_currency) newErrors.income_currency = "Income currency is required";
|
||||
if (!data.annual_income) newErrors.annual_income = "Annual income is required";
|
||||
if (!data.work_country) newErrors.work_country = "Work country is required";
|
||||
|
||||
if (isIndia) {
|
||||
if (!data.work_state) newErrors.work_state = "Required";
|
||||
if (!data.work_district) newErrors.work_district = "Required";
|
||||
if (!data.work_state) newErrors.work_state = "Work state is required";
|
||||
if (!data.work_district) newErrors.work_district = "Work city is required";
|
||||
} else {
|
||||
if (!data.work_city) newErrors.work_city = "Required";
|
||||
if (!data.work_city) newErrors.work_city = "Work city is required";
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.address) newErrors.address = "Required";
|
||||
if (!data.address) newErrors.address = "Work address is required";
|
||||
|
||||
setLocalErrors(newErrors);
|
||||
return newErrors;
|
||||
|
||||
@ -119,10 +119,6 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange, is
|
||||
|
||||
dispatch(updateFamilyDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(4));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSiblingChange = (type, index, field, value) => {
|
||||
@ -131,10 +127,6 @@ const FamilyDetailsForm = ({ onSubmitStep, onSkipStep, errors, onFieldChange, is
|
||||
list[index] = { ...list[index], [field]: value };
|
||||
dispatch(updateFamilyDetails({ [type]: list }));
|
||||
if (onFieldChange) onFieldChange(type);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(4));
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToError = (errorMap) => {
|
||||
|
||||
@ -18,11 +18,11 @@ import {
|
||||
DialogActions,
|
||||
Tooltip,
|
||||
} from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||
import { LocalizationProvider, DatePicker, TimePicker } from "@mui/x-date-pickers";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
|
||||
import { format } from "date-fns";
|
||||
import { useLifestyleMasters } from "../hooks/useMasters";
|
||||
import { useStarMasters, usePathamMasters } from "../hooks/useDependentMasters";
|
||||
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||
|
||||
const LifestyleDetailsForm = ({
|
||||
@ -34,30 +34,47 @@ const LifestyleDetailsForm = ({
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const data = useSelector((state) => state.registerform.lifestyleDetails);
|
||||
|
||||
if (!data) return null;
|
||||
|
||||
const inputRef = useRef(null);
|
||||
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
||||
|
||||
const { data: lifestyleMasters, isLoading: isLifestyleMastersLoading } =
|
||||
useLifestyleMasters();
|
||||
|
||||
const dietOptions = useMemo(() => {
|
||||
const raasiOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.diet || raw.diets || [];
|
||||
return raw.raasis || raw.raasi || raw.rasi || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const hobbyOptions = useMemo(() => {
|
||||
const panjangamOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.hobbies || raw.hobby || [];
|
||||
return raw.panjangam_types || raw.panjangamTypes || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
const { data: starData } = useStarMasters(data.raasi);
|
||||
const starOptions = useMemo(() => {
|
||||
if (!starData) return [];
|
||||
return starData.stars || starData.star || starData.data || (Array.isArray(starData) ? starData : []);
|
||||
}, [starData]);
|
||||
|
||||
const { data: pathamData } = usePathamMasters(data.star);
|
||||
const pathamOptions = useMemo(() => {
|
||||
if (!pathamData) return [];
|
||||
return (
|
||||
pathamData.pathams ||
|
||||
pathamData.patham ||
|
||||
pathamData.data ||
|
||||
(Array.isArray(pathamData) ? pathamData : [])
|
||||
);
|
||||
}, [pathamData]);
|
||||
|
||||
const grahaOptions = useMemo(() => {
|
||||
const raw = lifestyleMasters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.grahas || raw.graha || [];
|
||||
}, [lifestyleMasters]);
|
||||
|
||||
@ -74,13 +91,21 @@ const LifestyleDetailsForm = ({
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (data.dob) {
|
||||
const date = new Date(data.dob);
|
||||
if (!isNaN(date.getTime())) {
|
||||
const day = format(date, "EEEE");
|
||||
if (data.dayOfBirth !== day) {
|
||||
handleChange("dayOfBirth", day);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [data.dob]);
|
||||
|
||||
const handleChange = (field, value) => {
|
||||
dispatch(updateLifestyleDetails({ [field]: value }));
|
||||
if (onFieldChange) onFieldChange(field);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(5));
|
||||
}
|
||||
};
|
||||
|
||||
const handleMultiChange = (field, value) => {
|
||||
@ -189,7 +214,7 @@ const LifestyleDetailsForm = ({
|
||||
return `${h}:${m}`;
|
||||
};
|
||||
|
||||
const renderChartCell = (label, value, onChange) => (
|
||||
const renderChartCell = (label, value = [], onChange) => (
|
||||
<Tooltip title={value && value.length > 0 ? value.join(", ") : ""} arrow placement="top">
|
||||
<div className="bg-white border border-gray-300 rounded-lg p-2 flex flex-col items-center justify-center min-h-[70px]">
|
||||
<span className="text-[10px] font-medium text-gray-700 text-center">
|
||||
@ -221,7 +246,7 @@ const LifestyleDetailsForm = ({
|
||||
>
|
||||
{grahaOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
<Checkbox checked={value.indexOf(opt) > -1} />
|
||||
<Checkbox checked={Array.isArray(value) && value.indexOf(opt) > -1} />
|
||||
<ListItemText primary={opt} />
|
||||
</MenuItem>
|
||||
))}
|
||||
@ -267,120 +292,13 @@ const LifestyleDetailsForm = ({
|
||||
return (
|
||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Diet{requiredMark}
|
||||
</label>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
|
||||
<InputLabel id="diets-label">Select Diet</InputLabel>
|
||||
<Select
|
||||
labelId="diets-label"
|
||||
label="Select Diet"
|
||||
name="diets"
|
||||
value={data.diets}
|
||||
onChange={(e) => handleChange("diets", e.target.value)}
|
||||
inputRef={inputRef}
|
||||
disabled={isLifestyleMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{dietOptions.map((opt) => {
|
||||
const value = opt.id ?? opt;
|
||||
const label = opt.diet_name || opt.name || String(opt);
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors.diets && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.diets}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Hobbies & Interests (Multi-select){requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.hobbies)}
|
||||
>
|
||||
<InputLabel id="hobbies-label">
|
||||
Select Hobbies & Interests
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="hobbies-label"
|
||||
label="Select Hobbies & Interests"
|
||||
name="hobbies"
|
||||
multiple
|
||||
value={data.hobbies}
|
||||
onChange={(e) => handleMultiChange("hobbies", e.target.value)}
|
||||
disabled={isLifestyleMastersLoading}
|
||||
renderValue={(selected) =>
|
||||
selected
|
||||
.map((id) => {
|
||||
const item = hobbyOptions.find(
|
||||
(opt) => (opt.id ?? opt) === id
|
||||
);
|
||||
return item?.hobby_name || item?.name || id;
|
||||
})
|
||||
.join(", ")
|
||||
}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{hobbyOptions.map((opt) => {
|
||||
const value = opt.id ?? opt;
|
||||
const label = opt.hobby_name || opt.name || String(opt);
|
||||
return (
|
||||
<MenuItem key={value} value={value}>
|
||||
<Checkbox checked={data.hobbies.indexOf(value) > -1} />
|
||||
<ListItemText primary={label} />
|
||||
</MenuItem>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
{errors.hobbies && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.hobbies}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center py-4">
|
||||
<h2 className="text-[18px] font-semibold text-gray-800">
|
||||
Astrology / Horoscope
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[900px] mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 w-full max-w-[1200px] mx-auto">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Date of Birth{requiredMark}
|
||||
@ -403,6 +321,19 @@ const LifestyleDetailsForm = ({
|
||||
</LocalizationProvider>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Day of Birth</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="dayOfBirth"
|
||||
value={data.dayOfBirth}
|
||||
readOnly
|
||||
variant="outlined"
|
||||
placeholder="Day of Week"
|
||||
InputProps={{ readOnly: true }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Time of Birth{requiredMark}
|
||||
@ -424,7 +355,7 @@ const LifestyleDetailsForm = ({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Place of Birth</label>
|
||||
<label className="text-gray-900 text-[15px]">Place of Birth{requiredMark}</label>
|
||||
<TextField
|
||||
fullWidth
|
||||
name="placeOfBirth"
|
||||
@ -438,32 +369,186 @@ const LifestyleDetailsForm = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[950px] mx-auto mt-6">
|
||||
<div>
|
||||
<div className="py-4">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
||||
Add Rasi
|
||||
</h3>
|
||||
{renderChartGrid("graha")}
|
||||
{errors.graha_duplicate && (
|
||||
<p style={{ color: "#d32f2f", fontSize: "0.75rem", marginTop: "4px" }}>
|
||||
{errors.graha_duplicate}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[1200px] mx-auto mt-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Select Raasi{requiredMark}</label>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.raasi)} id="raasi">
|
||||
<InputLabel id="raasi-label">Select Raasi</InputLabel>
|
||||
<Select
|
||||
labelId="raasi-label"
|
||||
label="Select Raasi"
|
||||
name="raasi"
|
||||
value={data.raasi}
|
||||
onChange={(e) => {
|
||||
handleChange("raasi", e.target.value);
|
||||
handleChange("star", "");
|
||||
handleChange("patham", "");
|
||||
}}
|
||||
>
|
||||
{raasiOptions.map((opt) => (
|
||||
<MenuItem key={opt.id} value={opt.id}>
|
||||
{opt.raasi_name || opt.rasi_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="py-4">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">
|
||||
Add Navamsam
|
||||
</h3>
|
||||
{renderChartGrid("amsam")}
|
||||
{errors.amsam_duplicate && (
|
||||
<p style={{ color: "#d32f2f", fontSize: "0.75rem", marginTop: "4px" }}>
|
||||
{errors.amsam_duplicate}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Select Birth Star{requiredMark}</label>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.star)} id="star">
|
||||
<InputLabel id="star-label">Select Birth Star</InputLabel>
|
||||
<Select
|
||||
labelId="star-label"
|
||||
label="Select Birth Star"
|
||||
name="star"
|
||||
value={data.star}
|
||||
onChange={(e) => {
|
||||
handleChange("star", e.target.value);
|
||||
handleChange("patham", "");
|
||||
}}
|
||||
>
|
||||
{starOptions.map((opt) => (
|
||||
<MenuItem key={opt.id} value={opt.id}>
|
||||
{opt.star_name || opt.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Select Padham</label>
|
||||
<FormControl fullWidth variant="outlined" id="patham">
|
||||
<InputLabel id="patham-label">Select Padham</InputLabel>
|
||||
<Select
|
||||
labelId="patham-label"
|
||||
label="Select Padham"
|
||||
name="patham"
|
||||
value={data.patham}
|
||||
onChange={(e) => handleChange("patham", e.target.value)}
|
||||
>
|
||||
{pathamOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Select Lagnam</label>
|
||||
<FormControl fullWidth variant="outlined" id="lagnam">
|
||||
<InputLabel id="lagnam-label">Select Lagnam</InputLabel>
|
||||
<Select
|
||||
labelId="lagnam-label"
|
||||
label="Select Lagnam"
|
||||
name="lagnam"
|
||||
value={data.lagnam}
|
||||
onChange={(e) => handleChange("lagnam", e.target.value)}
|
||||
>
|
||||
{raasiOptions.map((opt) => (
|
||||
<MenuItem key={opt.id} value={opt.id}>
|
||||
{opt.raasi_name || opt.rasi_name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Panjangam Type</label>
|
||||
<FormControl fullWidth variant="outlined" id="panjangam_type">
|
||||
<InputLabel id="panjangam-label">Select Panjangam Type</InputLabel>
|
||||
<Select
|
||||
labelId="panjangam-label"
|
||||
label="Select Panjangam Type"
|
||||
name="panjangam_type"
|
||||
value={data.panjangam_type}
|
||||
onChange={(e) => handleChange("panjangam_type", e.target.value)}
|
||||
>
|
||||
{panjangamOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[1200px] mx-auto mt-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">Add Rasi</h3>
|
||||
{renderChartGrid("graha")}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-3">Add Navamsam</h3>
|
||||
{renderChartGrid("amsam")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 w-full max-w-[1200px] mx-auto p-6 border border-gray-200 rounded-lg bg-gray-50">
|
||||
<h3 className="text-base font-semibold text-gray-800 mb-4">Dasa Details</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Dasa Type</label>
|
||||
<FormControl fullWidth variant="outlined" id="dasa_balance">
|
||||
<InputLabel id="dasa-label">Select Dasa Type</InputLabel>
|
||||
<Select
|
||||
labelId="dasa-label"
|
||||
label="Select Dasa Type"
|
||||
name="dasa_balance"
|
||||
value={data.dasa_balance}
|
||||
onChange={(e) => handleChange("dasa_balance", e.target.value)}
|
||||
>
|
||||
{grahaOptions.map((opt) => (
|
||||
<MenuItem key={opt} value={opt}>
|
||||
{opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Dasa Year</label>
|
||||
<TextField
|
||||
id="dasa_years"
|
||||
fullWidth
|
||||
name="dasa_years"
|
||||
value={data.dasa_years}
|
||||
onChange={(e) => handleChange("dasa_years", e.target.value)}
|
||||
placeholder="Enter Year"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Dasa Month</label>
|
||||
<TextField
|
||||
id="dasa_months"
|
||||
fullWidth
|
||||
name="dasa_months"
|
||||
value={data.dasa_months}
|
||||
onChange={(e) => handleChange("dasa_months", e.target.value)}
|
||||
placeholder="Enter Month"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Dasa Day</label>
|
||||
<TextField
|
||||
id="dasa_days"
|
||||
fullWidth
|
||||
name="dasa_days"
|
||||
value={data.dasa_days}
|
||||
onChange={(e) => handleChange("dasa_days", e.target.value)}
|
||||
placeholder="Enter Day"
|
||||
variant="outlined"
|
||||
type="number"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
Button,
|
||||
Checkbox,
|
||||
ListItemText,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||
import { useCityMasters, useSubCasteMasters } from "../hooks/useDependentMasters";
|
||||
@ -30,11 +31,30 @@ const PartnerPreferencesForm = ({
|
||||
const subCasteQuery = useSubCasteMasters(data.castes);
|
||||
const cityQuery = useCityMasters(data.states);
|
||||
|
||||
const ageRangeOptions = useMemo(() => {
|
||||
const ageOptions = useMemo(() => Array.from({ length: 53 }, (_, i) => i + 18), []);
|
||||
|
||||
const heightOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.ageRange || raw.age_range || [];
|
||||
return raw.heights || raw.height || [];
|
||||
}, [masters]);
|
||||
|
||||
const maritalStatusOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
return raw.maritalStatus || raw.marital_status || [];
|
||||
}, [masters]);
|
||||
|
||||
const starOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
return raw.stars || raw.star || [];
|
||||
}, [masters]);
|
||||
|
||||
const employeeTypeOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
return raw.employeeTypes || raw.employee_type || [];
|
||||
}, [masters]);
|
||||
|
||||
const casteOptions = useMemo(() => {
|
||||
@ -72,6 +92,8 @@ const PartnerPreferencesForm = ({
|
||||
return raw.annual_income || raw.annualIncome || [];
|
||||
}, [masters]);
|
||||
|
||||
const currencyOptions = useMemo(() => ["INR", "USD"], []);
|
||||
|
||||
const stateOptions = useMemo(() => {
|
||||
const raw = masters;
|
||||
if (!raw) return [];
|
||||
@ -96,8 +118,15 @@ const PartnerPreferencesForm = ({
|
||||
const cityOptions = useMemo(() => {
|
||||
const raw = cityQuery.data;
|
||||
if (!raw) return [];
|
||||
if (Array.isArray(raw)) return raw;
|
||||
return raw.subCaste || raw.district || raw.data || [];
|
||||
if (Array.isArray(raw)) {
|
||||
const merged = raw.flatMap((entry) => {
|
||||
if (!entry) return [];
|
||||
if (Array.isArray(entry)) return entry;
|
||||
return entry.district || entry.districts || entry.data || [];
|
||||
});
|
||||
return merged;
|
||||
}
|
||||
return raw.district || raw.districts || raw.data || [];
|
||||
}, [cityQuery.data]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -107,12 +136,15 @@ const PartnerPreferencesForm = ({
|
||||
const handleChange = (field, value) => {
|
||||
const arrayFields = new Set([
|
||||
"castes",
|
||||
"subCastes",
|
||||
"sub_castes",
|
||||
"occupations",
|
||||
"educations",
|
||||
"hobbies",
|
||||
"states",
|
||||
"districts",
|
||||
"marital_statuses",
|
||||
"birth_stars",
|
||||
"employee_types",
|
||||
"currencies",
|
||||
]);
|
||||
const nextValue =
|
||||
arrayFields.has(field) && typeof value === "string"
|
||||
@ -122,8 +154,8 @@ const PartnerPreferencesForm = ({
|
||||
const fieldsToClear = [field];
|
||||
|
||||
if (field === "castes") {
|
||||
updates.subCastes = [];
|
||||
fieldsToClear.push("subCastes");
|
||||
updates.sub_castes = [];
|
||||
fieldsToClear.push("sub_castes");
|
||||
}
|
||||
if (field === "states") {
|
||||
updates.districts = [];
|
||||
@ -201,215 +233,297 @@ const PartnerPreferencesForm = ({
|
||||
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
||||
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
||||
{/* Age Range */}
|
||||
{/* 1. Age Range */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Age Range{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.ageRange)}
|
||||
>
|
||||
<InputLabel id="ageRange-label">Select Age Range</InputLabel>
|
||||
<Select
|
||||
labelId="ageRange-label"
|
||||
label="Select Age Range"
|
||||
name="ageRange"
|
||||
value={data.ageRange}
|
||||
onChange={(e) => handleChange("ageRange", e.target.value)}
|
||||
inputRef={inputRef}
|
||||
disabled={isPartnerMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{ageRangeOptions.map((opt) => (
|
||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
||||
{opt.name || opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.ageRange && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
<label className="text-gray-900 text-[15px]">Age Range (From - To)</label>
|
||||
<div className="flex gap-2">
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.age_from)}>
|
||||
<InputLabel>From Age</InputLabel>
|
||||
<Select
|
||||
id="age_from"
|
||||
name="age_from"
|
||||
value={data.age_from}
|
||||
label="From Age"
|
||||
inputRef={inputRef}
|
||||
onChange={(e) => handleChange("age_from", e.target.value)}
|
||||
>
|
||||
{errors.ageRange}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
{ageOptions.map((age) => (
|
||||
<MenuItem key={age} value={age}>{age}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.age_from && (
|
||||
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||
{errors.age_from}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.age_to)}>
|
||||
<InputLabel>To Age</InputLabel>
|
||||
<Select
|
||||
id="age_to"
|
||||
name="age_to"
|
||||
value={data.age_to}
|
||||
label="To Age"
|
||||
onChange={(e) => handleChange("age_to", e.target.value)}
|
||||
>
|
||||
{ageOptions.map((age) => (
|
||||
<MenuItem key={age} value={age}>{age}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.age_to && (
|
||||
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||
{errors.age_to}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Caste */}
|
||||
{/* 2. Height Range */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Caste{requiredMark}
|
||||
</label>
|
||||
<label className="text-gray-900 text-[15px]">Height Range (From - To)</label>
|
||||
<div className="flex gap-2">
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.height_from)}>
|
||||
<InputLabel>From Height</InputLabel>
|
||||
<Select
|
||||
id="height_from"
|
||||
name="height_from"
|
||||
value={data.height_from}
|
||||
label="From Height"
|
||||
onChange={(e) => handleChange("height_from", e.target.value)}
|
||||
>
|
||||
{heightOptions.map((h) => (
|
||||
<MenuItem key={h.id} value={h.id}>{h.height_text}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.height_from && (
|
||||
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||
{errors.height_from}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormControl fullWidth variant="outlined" error={Boolean(errors.height_to)}>
|
||||
<InputLabel>To Height</InputLabel>
|
||||
<Select
|
||||
id="height_to"
|
||||
name="height_to"
|
||||
value={data.height_to}
|
||||
label="To Height"
|
||||
onChange={(e) => handleChange("height_to", e.target.value)}
|
||||
>
|
||||
{heightOptions.map((h) => (
|
||||
<MenuItem key={h.id} value={h.id}>{h.height_text}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.height_to && (
|
||||
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
||||
{errors.height_to}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3. Marital Status */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Marital Status</label>
|
||||
{renderMultiSelect({
|
||||
name: "marital_statuses",
|
||||
label: "Marital Status",
|
||||
options: maritalStatusOptions,
|
||||
value: data.marital_statuses,
|
||||
getLabel: (opt) => opt.marital_status_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 4. Birth Star */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Birth Star</label>
|
||||
{renderMultiSelect({
|
||||
name: "birth_stars",
|
||||
label: "Birth Star",
|
||||
options: starOptions,
|
||||
value: data.birth_stars,
|
||||
getLabel: (opt) => opt.star_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 5. Caste */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Caste</label>
|
||||
{renderMultiSelect({
|
||||
name: "castes",
|
||||
label: "Caste",
|
||||
options: casteOptions,
|
||||
value: data.castes,
|
||||
getLabel: (opt) => opt.caste_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
getLabel: (opt) => opt.caste_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Sub Caste */}
|
||||
{/* 6. Sub-Sect */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Sub Caste{requiredMark}
|
||||
</label>
|
||||
<label className="text-gray-900 text-[15px]">Sub-Sect</label>
|
||||
{renderMultiSelect({
|
||||
name: "subCastes",
|
||||
label: "Sub Caste",
|
||||
name: "sub_castes",
|
||||
label: "Sub-Sect",
|
||||
options: subCasteOptions,
|
||||
value: data.subCastes,
|
||||
getLabel: (opt) =>
|
||||
opt.sub_caste_name || opt.subCaste_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
value: data.sub_castes,
|
||||
getLabel: (opt) => opt.sub_caste_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: data.castes.length === 0 || subCasteQuery.isLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Occupation */}
|
||||
{/* 7. Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Occupation{requiredMark}
|
||||
</label>
|
||||
{renderMultiSelect({
|
||||
name: "occupations",
|
||||
label: "Occupation",
|
||||
options: occupationOptions,
|
||||
value: data.occupations,
|
||||
getLabel: (opt) => opt.occupation_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Qualification */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Qualification{requiredMark}
|
||||
</label>
|
||||
<label className="text-gray-900 text-[15px]">Qualification</label>
|
||||
{renderMultiSelect({
|
||||
name: "educations",
|
||||
label: "Qualification",
|
||||
options: educationOptions,
|
||||
value: data.educations,
|
||||
getLabel: (opt) => opt.education_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
getLabel: (opt) => opt.education_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Lifestyle and Hobbies */}
|
||||
{/* 8. Occupation */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Lifestyle & Hobbies{requiredMark}
|
||||
</label>
|
||||
<label className="text-gray-900 text-[15px]">Occupation</label>
|
||||
{renderMultiSelect({
|
||||
name: "hobbies",
|
||||
label: "Lifestyle & Hobbies",
|
||||
options: hobbyOptions,
|
||||
value: data.hobbies,
|
||||
getLabel: (opt) => opt.hobby_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
name: "occupations",
|
||||
label: "Occupation",
|
||||
options: occupationOptions,
|
||||
value: data.occupations,
|
||||
getLabel: (opt) => opt.occupation_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Annual Income */}
|
||||
{/* 9. Employee Type */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
Annual Income{requiredMark}
|
||||
</label>
|
||||
<FormControl
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
error={Boolean(errors.annualIncome)}
|
||||
>
|
||||
<InputLabel id="annualIncome-label">
|
||||
Select Annual Income
|
||||
</InputLabel>
|
||||
<Select
|
||||
labelId="annualIncome-label"
|
||||
label="Select Annual Income"
|
||||
name="annualIncome"
|
||||
value={data.annualIncome}
|
||||
onChange={(e) => handleChange("annualIncome", e.target.value)}
|
||||
disabled={isPartnerMastersLoading}
|
||||
sx={{
|
||||
"& .MuiSelect-select.Mui-disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{annualIncomeOptions.map((opt) => (
|
||||
<MenuItem key={opt.id ?? opt} value={opt.id ?? opt}>
|
||||
{opt.annual_income_name || opt.name || opt}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.annualIncome && (
|
||||
<p
|
||||
style={{
|
||||
color: "#d32f2f",
|
||||
margin: "3px 14px 0 14px",
|
||||
fontSize: "0.75rem",
|
||||
}}
|
||||
>
|
||||
{errors.annualIncome}
|
||||
</p>
|
||||
)}
|
||||
</FormControl>
|
||||
<label className="text-gray-900 text-[15px]">Employee Type</label>
|
||||
{renderMultiSelect({
|
||||
name: "employee_types",
|
||||
label: "Employee Type",
|
||||
options: employeeTypeOptions,
|
||||
value: data.employee_types,
|
||||
getLabel: (opt) => opt.employee_type_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* State */}
|
||||
{/* 10. Currency Type */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
State{requiredMark}
|
||||
</label>
|
||||
<label className="text-gray-900 text-[15px]">Income Currency Type</label>
|
||||
{renderMultiSelect({
|
||||
name: "currencies",
|
||||
label: "Currency",
|
||||
options: currencyOptions,
|
||||
value: data.currencies,
|
||||
getLabel: (opt) => opt,
|
||||
getValue: (opt) => opt,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* 11. Income Range */}
|
||||
{data.currencies.includes("INR") && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Annual Income (INR Range)</label>
|
||||
<div className="flex gap-2">
|
||||
<TextField
|
||||
id="inr_from"
|
||||
name="inr_from"
|
||||
fullWidth
|
||||
label="From (INR)"
|
||||
value={data.inr_from}
|
||||
error={Boolean(errors.inr_from)}
|
||||
helperText={errors.inr_from}
|
||||
onChange={(e) => handleChange("inr_from", e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
id="inr_to"
|
||||
name="inr_to"
|
||||
fullWidth
|
||||
label="To (INR)"
|
||||
value={data.inr_to}
|
||||
error={Boolean(errors.inr_to)}
|
||||
helperText={errors.inr_to}
|
||||
onChange={(e) => handleChange("inr_to", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.currencies.includes("USD") && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Annual Income (USD Range)</label>
|
||||
<div className="flex gap-2">
|
||||
<TextField
|
||||
id="usd_from"
|
||||
name="usd_from"
|
||||
fullWidth
|
||||
label="From (USD)"
|
||||
value={data.usd_from}
|
||||
error={Boolean(errors.usd_from)}
|
||||
helperText={errors.usd_from}
|
||||
onChange={(e) => handleChange("usd_from", e.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
id="usd_to"
|
||||
name="usd_to"
|
||||
fullWidth
|
||||
label="To (USD)"
|
||||
value={data.usd_to}
|
||||
error={Boolean(errors.usd_to)}
|
||||
helperText={errors.usd_to}
|
||||
onChange={(e) => handleChange("usd_to", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 12. State */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">State</label>
|
||||
{renderMultiSelect({
|
||||
name: "states",
|
||||
label: "State",
|
||||
options: stateOptions,
|
||||
value: data.states,
|
||||
getLabel: (opt) => opt.state_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
getLabel: (opt) => opt.state_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: isPartnerMastersLoading,
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* City */}
|
||||
{/* 13. City */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">
|
||||
City{requiredMark}
|
||||
</label>
|
||||
<label className="text-gray-900 text-[15px]">City</label>
|
||||
{renderMultiSelect({
|
||||
name: "districts",
|
||||
label: "City",
|
||||
options: cityOptions,
|
||||
value: data.districts,
|
||||
getLabel: (opt) =>
|
||||
opt.district_name || opt.city_name || opt.name || String(opt),
|
||||
getValue: (opt) => opt.id ?? opt,
|
||||
getLabel: (opt) => opt.district_name || opt.city_name || opt.name,
|
||||
getValue: (opt) => opt.id,
|
||||
disabled: data.states.length === 0 || cityQuery.isLoading,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
size={12}
|
||||
style={{
|
||||
marginTop: 40,
|
||||
display: "flex",
|
||||
|
||||
@ -126,13 +126,20 @@ const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChan
|
||||
dispatch(updatePersonalDetails(updates));
|
||||
if (onFieldChange) onFieldChange(fieldsToClear);
|
||||
|
||||
if (!isEditMode) {
|
||||
dispatch(clearAllStepsFrom(2));
|
||||
}
|
||||
|
||||
if (localErrors[field]) {
|
||||
setLocalErrors(prev => ({ ...prev, [field]: "" }));
|
||||
}
|
||||
|
||||
// Real-time password match validation
|
||||
if (field === "confirmPassword" || field === "password") {
|
||||
const p = field === "password" ? value : data.password;
|
||||
const cp = field === "confirmPassword" ? value : data.confirmPassword;
|
||||
if (p && cp && p !== cp) {
|
||||
setLocalErrors(prev => ({ ...prev, confirmPassword: "Passwords do not match" }));
|
||||
} else if (p && cp && p === cp) {
|
||||
setLocalErrors(prev => ({ ...prev, confirmPassword: "" }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMobileSubmit = async () => {
|
||||
@ -198,21 +205,27 @@ const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChan
|
||||
|
||||
const validateForm = () => {
|
||||
const newErrors = {};
|
||||
if (!data.profile_for) newErrors.profile_for = "Required";
|
||||
if (!data.gender) newErrors.gender = "Required";
|
||||
if (!data.name) newErrors.name = "Required";
|
||||
if (!data.mobile) newErrors.mobile = "Required";
|
||||
if (!data.email) newErrors.email = "Required";
|
||||
if (!isEditMode && !data.password) newErrors.password = "Required";
|
||||
if (!isEditMode && data.password !== data.confirmPassword) newErrors.confirmPassword = "Passwords do not match";
|
||||
if (!data.marital_status) newErrors.marital_status = "Required";
|
||||
if (!data.height) newErrors.height = "Required";
|
||||
if (!data.complexion) newErrors.complexion = "Required";
|
||||
if (!data.physical_status) newErrors.physical_status = "Required";
|
||||
if (!data.mother_language) newErrors.mother_language = "Required";
|
||||
if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Required";
|
||||
if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Required";
|
||||
if (data.known_languages.length === 0) newErrors.known_languages = "Required";
|
||||
if (!data.profile_for) newErrors.profile_for = "Profile created for is required";
|
||||
if (!data.gender) newErrors.gender = "Gender is required";
|
||||
if (!data.name) newErrors.name = "Name is required";
|
||||
if (!data.mobile) newErrors.mobile = "Mobile number is required";
|
||||
if (!data.email) newErrors.email = "Email is required";
|
||||
if (!isEditMode && !data.password) newErrors.password = "Password is required";
|
||||
if (!isEditMode && !data.confirmPassword) newErrors.confirmPassword = "Confirm Password is required";
|
||||
if (!isEditMode && data.password && data.confirmPassword && data.password !== data.confirmPassword) {
|
||||
newErrors.confirmPassword = "Passwords do not match";
|
||||
}
|
||||
if (!data.caste) newErrors.caste = "Caste is required";
|
||||
if (!data.sub_caste) newErrors.sub_caste = "Sub-Sect is required";
|
||||
|
||||
if (!data.marital_status) newErrors.marital_status = "Marital status is required";
|
||||
if (!data.height) newErrors.height = "Height is required";
|
||||
if (!data.complexion) newErrors.complexion = "Complexion is required";
|
||||
if (!data.physical_status) newErrors.physical_status = "Physical status is required";
|
||||
if (!data.mother_language) newErrors.mother_language = "Mother tongue is required";
|
||||
if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Telugu speaking status is required";
|
||||
if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Inter-caste parents status is required";
|
||||
if (data.known_languages.length === 0) newErrors.known_languages = "Known languages are required";
|
||||
|
||||
setLocalErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
@ -260,21 +273,27 @@ const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChan
|
||||
// 2. Perform general form validation
|
||||
if (!validateForm()) {
|
||||
const newErrors = {};
|
||||
if (!data.profile_for) newErrors.profile_for = "Required";
|
||||
if (!data.gender) newErrors.gender = "Required";
|
||||
if (!data.name) newErrors.name = "Required";
|
||||
if (!data.mobile) newErrors.mobile = "Required";
|
||||
if (!data.email) newErrors.email = "Required";
|
||||
if (!isEditMode && !data.password) newErrors.password = "Required";
|
||||
if (!isEditMode && data.password !== data.confirmPassword) newErrors.confirmPassword = "Passwords do not match";
|
||||
if (!data.marital_status) newErrors.marital_status = "Required";
|
||||
if (!data.height) newErrors.height = "Required";
|
||||
if (!data.complexion) newErrors.complexion = "Required";
|
||||
if (!data.physical_status) newErrors.physical_status = "Required";
|
||||
if (!data.mother_language) newErrors.mother_language = "Required";
|
||||
if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Required";
|
||||
if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Required";
|
||||
if (data.known_languages.length === 0) newErrors.known_languages = "Required";
|
||||
if (!data.profile_for) newErrors.profile_for = "Profile created for is required";
|
||||
if (!data.gender) newErrors.gender = "Gender is required";
|
||||
if (!data.name) newErrors.name = "Name is required";
|
||||
if (!data.mobile) newErrors.mobile = "Mobile number is required";
|
||||
if (!data.email) newErrors.email = "Email is required";
|
||||
if (!isEditMode && !data.password) newErrors.password = "Password is required";
|
||||
if (!isEditMode && !data.confirmPassword) newErrors.confirmPassword = "Confirm Password is required";
|
||||
if (!isEditMode && data.password && data.confirmPassword && data.password !== data.confirmPassword) {
|
||||
newErrors.confirmPassword = "Passwords do not match";
|
||||
}
|
||||
if (!data.caste) newErrors.caste = "Caste is required";
|
||||
if (!data.sub_caste) newErrors.sub_caste = "Sub-Sect is required";
|
||||
|
||||
if (!data.marital_status) newErrors.marital_status = "Marital status is required";
|
||||
if (!data.height) newErrors.height = "Height is required";
|
||||
if (!data.complexion) newErrors.complexion = "Complexion is required";
|
||||
if (!data.physical_status) newErrors.physical_status = "Physical status is required";
|
||||
if (!data.mother_language) newErrors.mother_language = "Mother tongue is required";
|
||||
if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Telugu speaking status is required";
|
||||
if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Inter-caste parents status is required";
|
||||
if (data.known_languages.length === 0) newErrors.known_languages = "Known languages are required";
|
||||
|
||||
toast.error("Please fill all mandatory fields");
|
||||
scrollToError(newErrors);
|
||||
@ -581,8 +600,8 @@ const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChan
|
||||
|
||||
{/* 14. Caste / Community */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Caste / Community</label>
|
||||
<FormControl fullWidth disabled={casteQuery.isLoading}>
|
||||
<label className="text-gray-900 text-[15px]">Caste / Community{requiredMark}</label>
|
||||
<FormControl fullWidth disabled={casteQuery.isLoading} error={Boolean(errors.caste)} id="caste">
|
||||
<InputLabel>Select Caste</InputLabel>
|
||||
<Select
|
||||
value={data.caste}
|
||||
@ -593,13 +612,14 @@ const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChan
|
||||
<MenuItem key={opt.id} value={opt.id}>{opt.caste_name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.caste && <FormHelperText>{errors.caste}</FormHelperText>}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
{/* 15. Sub-Sect */}
|
||||
<div className="flex flex-col gap-2">
|
||||
<label className="text-gray-900 text-[15px]">Sub-Sect{requiredMark}</label>
|
||||
<FormControl fullWidth disabled={!data.caste || subCasteQuery.isLoading} id="sub_caste">
|
||||
<FormControl fullWidth disabled={!data.caste || subCasteQuery.isLoading} error={Boolean(errors.sub_caste)} id="sub_caste">
|
||||
<InputLabel>Select Sub-Sect</InputLabel>
|
||||
<Select
|
||||
value={data.sub_caste}
|
||||
@ -610,6 +630,7 @@ const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChan
|
||||
<MenuItem key={opt.id} value={opt.id}>{opt.sub_caste_name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
{errors.sub_caste && <FormHelperText>{errors.sub_caste}</FormHelperText>}
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { Navigation, Pagination } from "swiper/modules";
|
||||
import "swiper/css";
|
||||
import "swiper/css/navigation";
|
||||
import "swiper/css/pagination";
|
||||
import { Edit2,Info } from 'lucide-react';
|
||||
import { Edit2, Info } from 'lucide-react';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@ -25,6 +25,7 @@ import {
|
||||
} from '@mui/material';
|
||||
import { usePreviewDetails } from "../hooks/usePreview";
|
||||
import { isAuthenticated } from "../utills/auth";
|
||||
import { usePersonalMasters, usePartnerPreferenceMasters } from "../hooks/useMasters";
|
||||
import horoscopeImg from "../assets/images/horoscopeimg.png";
|
||||
import { updatePersonalDetails } from "../redux/registrationFormSlice";
|
||||
|
||||
@ -42,8 +43,7 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
if (pd) {
|
||||
const images = pd.images || pd.profile_images;
|
||||
if (images && images.length > 0) {
|
||||
// Map images to ensure they have a consistent format for Redux
|
||||
const formattedImages = images.map(img =>
|
||||
const formattedImages = images.map(img =>
|
||||
typeof img === 'string' ? { url: img, preview: img } : img
|
||||
);
|
||||
dispatch(updatePersonalDetails({ profiles: formattedImages }));
|
||||
@ -66,124 +66,50 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
setOpenConfirmDialog(false);
|
||||
};
|
||||
|
||||
const sections = previewData?.personal_details
|
||||
? [
|
||||
{
|
||||
title: "Personal Details",
|
||||
step: 1,
|
||||
data: previewData.personal_details,
|
||||
},
|
||||
{
|
||||
title: "Educational & Professional Details",
|
||||
step: 2,
|
||||
data: previewData.educational_details,
|
||||
},
|
||||
{
|
||||
title: "Family Details",
|
||||
step: 3,
|
||||
data: previewData.family_details,
|
||||
},
|
||||
{
|
||||
title: "Lifestyle & Habits",
|
||||
step: 4,
|
||||
data: previewData.lifestyle_details,
|
||||
},
|
||||
{
|
||||
title: "Partner Preferences",
|
||||
step: 5,
|
||||
data: previewData.partner_preferences,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: 'Personal Details',
|
||||
step: 1,
|
||||
data: formData.personalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Educational & Professional Details',
|
||||
step: 2,
|
||||
data: formData.educationalDetails,
|
||||
},
|
||||
{
|
||||
title: 'Family Details',
|
||||
step: 3,
|
||||
data: formData.familyDetails,
|
||||
},
|
||||
{
|
||||
title: 'Lifestyle & Habits',
|
||||
step: 4,
|
||||
data: formData.lifestyleDetails,
|
||||
},
|
||||
{
|
||||
title: 'Partner Preferences',
|
||||
step: 5,
|
||||
data: formData.partnerPreferences,
|
||||
},
|
||||
];
|
||||
|
||||
const renderValue = (key, value) => {
|
||||
if (key === "profiles" || key === "profile" || key === "profile_images" || key === "images") {
|
||||
const list = Array.isArray(value) ? value : (value ? [value] : []);
|
||||
return (
|
||||
<Box display="flex" gap={1} flexWrap="wrap">
|
||||
{list.length > 0 ? (
|
||||
list.map((imgObj, index) => {
|
||||
const src =
|
||||
typeof imgObj === "string"
|
||||
? imgObj
|
||||
: imgObj?.preview ||
|
||||
imgObj?.url ||
|
||||
(imgObj?.file ? URL.createObjectURL(imgObj.file) : null);
|
||||
if (!src) return null;
|
||||
return (
|
||||
<img
|
||||
key={index}
|
||||
src={src}
|
||||
alt="Profile"
|
||||
style={{
|
||||
width: "90px",
|
||||
height: "90px",
|
||||
objectFit: "cover",
|
||||
borderRadius: "50%",
|
||||
border: "1px solid #ccc",
|
||||
// marginLeft: "-20px",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<Box display="flex" alignItems="center" gap={1} sx={{ color: "#888" }}>
|
||||
<Info size={16} />
|
||||
<Typography>No photos uploaded</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const renderValue = (value) => {
|
||||
if (value === null || value === undefined || value === "") return "-";
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) return "-";
|
||||
if (typeof value[0] === "object") {
|
||||
return value.map((item, idx) => (
|
||||
<div key={idx}>{JSON.stringify(item)}</div>
|
||||
));
|
||||
}
|
||||
return value.join(", ");
|
||||
}
|
||||
|
||||
if (value && typeof value === "object") {
|
||||
return "-";
|
||||
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 || "-";
|
||||
return value;
|
||||
};
|
||||
|
||||
const DetailRow = ({ label, value }) => (
|
||||
<Box display="grid" gridTemplateColumns="45% 55%" gap={1} mb={0.8} alignItems="start">
|
||||
<Typography color="text.secondary" sx={{ fontWeight: 500, fontSize: '0.875rem' }}>
|
||||
{label}:
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight={600} sx={{ wordBreak: 'break-word' }}>
|
||||
{renderValue(value)}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const SectionTitle = ({ title, mt = 2 }) => (
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
fontSize: '18px',
|
||||
color: '#111827',
|
||||
mt: mt,
|
||||
mb: 2
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
);
|
||||
|
||||
const renderChartGrid = (getDataForCell, title) => {
|
||||
const renderCell = (i) => {
|
||||
const items = getDataForCell(i);
|
||||
const label = Array.isArray(items) ? items.join(', ') : '';
|
||||
|
||||
|
||||
return (
|
||||
<Tooltip key={i} title={label} arrow placement="top" disableHoverListener={!items || items.length === 0}>
|
||||
<Box
|
||||
@ -204,12 +130,12 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
cursor: items && items.length > 0 ? 'help' : 'default'
|
||||
}}
|
||||
>
|
||||
{items && items.length > 2 ? (
|
||||
<Box>
|
||||
{items.slice(0, 2).join(', ')}
|
||||
<Box component="span" sx={{ color: 'primary.main', display: 'block', fontSize:'0.6rem' }}>+{items.length - 2}</Box>
|
||||
</Box>
|
||||
) : label}
|
||||
{items && items.length > 2 ? (
|
||||
<Box>
|
||||
{items.slice(0, 2).join(', ')}
|
||||
<Box component="span" sx={{ color: 'primary.main', display: 'block', fontSize: '0.6rem' }}>+{items.length - 2}</Box>
|
||||
</Box>
|
||||
) : label}
|
||||
</Box>
|
||||
</Tooltip>
|
||||
);
|
||||
@ -217,315 +143,299 @@ const PreviewScreen = ({ onEdit, onSubmit }) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="subtitle2" align="center" gutterBottom sx={{ fontWeight: 600 }}>{title}</Typography>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0.5, maxWidth: '300px', mx: 'auto', p: 1, bgcolor: '#fff3e0', borderRadius: 2 }}>
|
||||
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
|
||||
{renderCell(5)}
|
||||
<Box sx={{ gridColumn: 'span 2', gridRow: 'span 2', bgcolor: '#fff', border: '1px solid #eee', display:'flex', alignItems:'center', justifyContent:'center', fontSize:'0.7rem', fontWeight:'bold', color:'#aaa', overflow: 'hidden' }}>
|
||||
<img src={horoscopeImg} alt={title} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||
</Box>
|
||||
{renderCell(6)}
|
||||
{renderCell(7)} {renderCell(8)}
|
||||
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
||||
<Typography variant="subtitle2" align="center" gutterBottom sx={{ fontWeight: 600, color: '#CC1F1F' }}>{title}</Typography>
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 0.5, maxWidth: '300px', mx: 'auto', p: 1, bgcolor: '#fff3e0', borderRadius: 2 }}>
|
||||
{renderCell(1)} {renderCell(2)} {renderCell(3)} {renderCell(4)}
|
||||
{renderCell(5)}
|
||||
<Box sx={{ gridColumn: 'span 2', gridRow: 'span 2', bgcolor: '#fff', border: '1px solid #eee', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '0.7rem', fontWeight: 'bold', color: '#aaa', overflow: 'hidden' }}>
|
||||
<img src={horoscopeImg} alt={title} style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
|
||||
</Box>
|
||||
{renderCell(6)}
|
||||
{renderCell(7)} {renderCell(8)}
|
||||
{renderCell(9)} {renderCell(10)} {renderCell(11)} {renderCell(12)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={3} sx={{maxWidth:"1400px", width:"100%"}} mx="auto" >
|
||||
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-4 my-10'>
|
||||
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;
|
||||
|
||||
// Check if the first element is already a name
|
||||
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(", ");
|
||||
};
|
||||
|
||||
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;
|
||||
// Helper to check if partner preferences from API are actually empty
|
||||
const isPartnerApiDataEmpty = (pp) => {
|
||||
if (!pp) return true;
|
||||
// Check for common fields that should have values if data was saved
|
||||
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 prInfo = (!isPartnerApiDataEmpty(previewData?.partner_preferences)) ? previewData.partner_preferences :
|
||||
(!isPartnerApiDataEmpty(previewData?.preferred_details)) ? previewData.preferred_details :
|
||||
(!isPartnerApiDataEmpty(previewData?.partner_details)) ? previewData.partner_details :
|
||||
(formData.partnerPreferences || {});
|
||||
|
||||
return (
|
||||
<Box p={3} sx={{ maxWidth: "1400px", width: "100%" }} mx="auto">
|
||||
<div className='grid grid-cols-1 md:grid-cols-2 gap-6 my-10'>
|
||||
{isLoading && (
|
||||
<Box
|
||||
py={4}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
sx={{ color: "#666" }}
|
||||
>
|
||||
<Box py={4} display="flex" justifyContent="center" sx={{ color: "#666", gridColumn: 'span 2' }}>
|
||||
Loading preview...
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{isError && (
|
||||
<Box
|
||||
py={4}
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
sx={{ color: "#d32f2f" }}
|
||||
>
|
||||
<Box py={4} display="flex" justifyContent="center" sx={{ color: "#d32f2f", gridColumn: 'span 2' }}>
|
||||
Failed to load preview.
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!isLoading &&
|
||||
sections.map((section) => (
|
||||
|
||||
<Card key={section.title} variant="outlined"
|
||||
sx={{ borderRadius: 2,
|
||||
// background:"#fff5ed"
|
||||
}}>
|
||||
{!isLoading && (
|
||||
<>
|
||||
{/* 1. Personal Details */}
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardHeader
|
||||
title={
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{section.title}
|
||||
</Typography>
|
||||
}
|
||||
action={
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
color="primary"
|
||||
onClick={() => onEdit(section.step)}
|
||||
size="large"
|
||||
>
|
||||
<Edit2 size={20} />
|
||||
</IconButton>
|
||||
}
|
||||
sx={{padding:"15px 15px", background:"#f5fbff" }}
|
||||
title={<Typography variant="h6" fontWeight="bold">Personal Details</Typography>}
|
||||
action={<IconButton color="primary" onClick={() => onEdit(1)} size="large"><Edit2 size={20} /></IconButton>}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
/>
|
||||
{/* <Divider /> */}
|
||||
<CardContent sx={{ pt: 1, }}>
|
||||
{Object.entries(section.data || {}).map(([key, value]) => {
|
||||
// Filter out ID fields
|
||||
const hiddenFields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"phone_number_visibility",
|
||||
"chat_alert_notification",
|
||||
"chat_protection",
|
||||
"profile_photo_protect",
|
||||
"call_protection",
|
||||
"match_alert_preference",
|
||||
"who_can_message",
|
||||
"who_can_message_categories",
|
||||
"user_status",
|
||||
];
|
||||
if (hiddenFields.includes(key) || key.endsWith('_id') || key.endsWith('Id') || key.endsWith('_ids') || key.endsWith('Ids')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle Horoscope Chart (Server Data)
|
||||
if (key === 'horoscope' && value && typeof value === 'object') {
|
||||
return (
|
||||
<Box key={key} sx={{ width: '100%', mt: 2, mb: 2, borderTop: '1px solid #e0e0e0', pt: 2, gridColumn: '1 / -1' }}>
|
||||
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>Horoscope Details</Typography>
|
||||
<Grid container spacing={4} justifyContent="center">
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderChartGrid((i) => {
|
||||
const val = value[`graha_${i}`];
|
||||
return val ? val.split(',') : [];
|
||||
}, 'Rasi')}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
{renderChartGrid((i) => {
|
||||
const val = value[`amsam_${i}`];
|
||||
return val ? val.split(',') : [];
|
||||
}, 'Navamsam')}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle Horoscope Chart (Redux Data)
|
||||
if (key === 'graha' && value) {
|
||||
return (
|
||||
<Box key={key} sx={{ width: '100%', mt: 2, mb: 2, gridColumn: '1 / -1' }}>
|
||||
{renderChartGrid((i) => value[i] || [], 'Rasi')}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
if (key === 'amsam' && value) {
|
||||
return (
|
||||
<Box key={key} sx={{ width: '100%', mt: 2, mb: 2, gridColumn: '1 / -1' }}>
|
||||
{renderChartGrid((i) => value[i] || [], 'Navamsam')}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle brothers and sisters arrays specifically
|
||||
if ((key === 'brothers' || key === 'sisters') && Array.isArray(value) && value.length > 0) {
|
||||
return (
|
||||
<Box key={key} sx={{ py: 2, borderBottom: "1px solid #e0e0e0" }}>
|
||||
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1 }}>
|
||||
{key === 'brothers' ? 'Brothers Details' : 'Sisters Details'}
|
||||
</Typography>
|
||||
<style>
|
||||
{`
|
||||
.custom-swiper-${key} .swiper-button-next,
|
||||
.custom-swiper-${key} .swiper-button-prev {
|
||||
color: #d32f2f;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
overflow: 'hidden';
|
||||
padding:5px;
|
||||
display: 'flex';
|
||||
align-items: 'center';
|
||||
justify-content: 'center';
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.custom-swiper-${key} .swiper-button-next::after,
|
||||
.custom-swiper-${key} .swiper-button-prev::after {
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
width:20px;
|
||||
|
||||
}
|
||||
.custom-swiper-${key} .swiper-pagination-bullet-active {
|
||||
background-color: #d32f2f;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<Swiper
|
||||
className={`custom-swiper-${key}`}
|
||||
modules={[Navigation, Pagination]}
|
||||
spaceBetween={15}
|
||||
slidesPerView={1}
|
||||
navigation
|
||||
pagination={{ clickable: true }}
|
||||
style={{ padding: "4px 4px 30px 4px" }}
|
||||
>
|
||||
{value.map((sibling, idx) => (
|
||||
<SwiperSlide key={idx}>
|
||||
<Card variant="outlined"
|
||||
sx={{ bgcolor: '#fff', height: '100%' }}>
|
||||
<CardContent sx={{ p: 2, '&:last-child': { pb: 2 } }}>
|
||||
{Object.entries(sibling).map(([sKey, sVal]) => {
|
||||
if (sKey === 'id' || sKey.endsWith('_id') || sKey.endsWith('Id') || sKey === 'created_at' || sKey === 'updated_at') return null;
|
||||
let displayValue = sVal;
|
||||
if (sKey === 'haveChildrens' || sKey === 'have_childrens' || sKey === 'hasChildren' || sKey === 'has_children') {
|
||||
if (sVal === true || sVal === 1 || sVal === '1' || sVal === 'Yes') {
|
||||
displayValue = 'Yes';
|
||||
} else if (sVal === false || sVal === 0 || sVal === '0' || sVal === 'No') {
|
||||
displayValue = 'No';
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Box key={sKey} display="grid" gridTemplateColumns="45% 55%" gap={1} mb={0.8} alignItems="start">
|
||||
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 500 }}>
|
||||
{sKey
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
||||
.trim()}
|
||||
</Typography>
|
||||
<Typography variant="body2" fontWeight={600} sx={{ wordBreak: 'break-word' }}>
|
||||
{displayValue ?? '-'}
|
||||
</Typography>
|
||||
</Box>
|
||||
)})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
// Convert camelCase or camel_Snake_case to readable words
|
||||
const formattedKey = key
|
||||
.replace(/([A-Z])/g, ' $1')
|
||||
.replace(/_/g, ' ')
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase())
|
||||
.trim();
|
||||
const content = renderValue(key, value);
|
||||
return (
|
||||
|
||||
<Box
|
||||
key={key}
|
||||
py={0.7}
|
||||
// borderBottom="1px solid #e0e0e0"
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: {
|
||||
xs: "1fr",
|
||||
sm: "1fr 1fr",
|
||||
},
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography color="text.secondary" sx={{ fontWeight: 500 }}>
|
||||
{formattedKey}:
|
||||
</Typography>
|
||||
|
||||
{/* Special Case: Profiles Image Preview */}
|
||||
{key === "profiles" || key === "profile" || key === "profile_images" || key === "images" ? (
|
||||
content
|
||||
) : value ? (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
wordBreak: "break-word",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</Typography>
|
||||
) : (
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
sx={{ color: "#888", fontStyle: "italic" }}
|
||||
>
|
||||
<Info size={16} />
|
||||
<Typography>No data available</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
);
|
||||
|
||||
|
||||
})}
|
||||
<CardContent>
|
||||
<Box mb={2}>
|
||||
{/* Photos Preview */}
|
||||
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1, fontSize: '0.75rem' }}>Profile Photos:</Typography>
|
||||
<Box display="flex" gap={1} flexWrap="wrap">
|
||||
{(pInfo.images || pInfo.profile_images || pInfo.profiles || []).map((imgObj, index) => {
|
||||
const src = typeof imgObj === "string" ? imgObj : imgObj?.preview || imgObj?.url;
|
||||
return src ? (
|
||||
<img key={index} src={src} alt="Profile" style={{ width: "80px", height: "80px", objectFit: "cover", borderRadius: "50%", border: "1px solid #ccc" }} />
|
||||
) : null;
|
||||
})}
|
||||
{!(pInfo.images || pInfo.profile_images || pInfo.profiles || []).length && <Typography variant="caption">No photos</Typography>}
|
||||
</Box>
|
||||
</Box>
|
||||
<DetailRow label="Profile Created For" value={pInfo.profile_for} />
|
||||
<DetailRow label="Gender" value={pInfo.gender} />
|
||||
<DetailRow label="Name" value={pInfo.name} />
|
||||
<DetailRow label="Mobile Number" value={pInfo.mobile || pInfo.mobileNumber || pInfo.mobile_number} />
|
||||
<DetailRow label="Email Id" value={pInfo.email || pInfo.emailId || pInfo.email_id} />
|
||||
<DetailRow label="Marital Status" value={getNamesFromIds(pInfo.marital_status, pm?.marital_status, 'marital_status_name')} />
|
||||
<DetailRow label="Height" value={getNamesFromIds(pInfo.height, pm?.heights, 'height_text')} />
|
||||
<DetailRow label="Weight" value={pInfo.weight} />
|
||||
<DetailRow label="Complexion" value={getNamesFromIds(pInfo.complexion, pm?.complexion, 'complexion_name')} />
|
||||
<DetailRow label="Physical Status" value={getNamesFromIds(pInfo.physical_status, pm?.physicalStatus, 'physical_status_name')} />
|
||||
<DetailRow label="Religion" value={getNamesFromIds(pInfo.religion, pm?.religion, 'religion_name')} />
|
||||
<DetailRow label="Caste / Community" value={getNamesFromIds(pInfo.caste, pm?.caste, 'caste_name')} />
|
||||
<DetailRow label="Sub-Sect" value={getNamesFromIds(pInfo.sub_caste, pm?.subCaste, 'sub_caste_name')} />
|
||||
<DetailRow label="Willing to marry from same sub-sect" value={pInfo.willing_to_marry} />
|
||||
<DetailRow label="Inter-Caste Parents" value={pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1" ? "Yes" : "No"} />
|
||||
{ (pInfo.inter_caste_parents === 1 || pInfo.inter_caste_parents === "1") && <DetailRow label="Inter-Caste Parents Details" value={pInfo.inter_caste_parents_details} />}
|
||||
<DetailRow label="Gothram" value={pInfo.gothram} />
|
||||
<DetailRow label="Do you speak Telugu" value={pInfo.do_you_speak_telugu === 1 || pInfo.do_you_speak_telugu === "1" ? "Yes" : "No"} />
|
||||
<DetailRow label="About" value={pInfo.about_us} />
|
||||
<DetailRow label="Language Spoken" value={pInfo.known_languages} />
|
||||
<DetailRow label="Mother Tongue" value={getNamesFromIds(pInfo.mother_language, pm?.languages, 'language')} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
))}
|
||||
|
||||
</div>
|
||||
{/* 2. Educational & Professional Details */}
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardHeader
|
||||
title={<Typography variant="h6" fontWeight="bold">Educational & Professional Details</Typography>}
|
||||
action={<IconButton color="primary" onClick={() => onEdit(2)} size="large"><Edit2 size={20} /></IconButton>}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
/>
|
||||
<CardContent>
|
||||
<DetailRow label="Highest Qualification" value={getNamesFromIds(eInfo.education, ppm?.education || ppm?.educations, 'education_name')} />
|
||||
<DetailRow label="Field of Study" value={getNamesFromIds(eInfo.study_field, ppm?.study_fields || ppm?.studyFields, 'study_field_name')} />
|
||||
<DetailRow label="About" value={eInfo.education_detail || eInfo.educationDetail || eInfo.about} />
|
||||
<DetailRow label="College Name" value={eInfo.college_name} />
|
||||
<DetailRow label="Employee type" value={getNamesFromIds(eInfo.employee_type, ppm?.employeeTypes || ppm?.employee_type, 'employee_type_name')} />
|
||||
<DetailRow label="Occupation" value={getNamesFromIds(eInfo.occupation, ppm?.occupation || ppm?.occupations, 'occupation_name')} />
|
||||
<DetailRow label="Occupation Details" value={eInfo.occupation_detail || eInfo.occupationDetail} />
|
||||
<DetailRow label="Organization Name" value={eInfo.company_name || eInfo.organizationName || eInfo.companyName} />
|
||||
<DetailRow label="Income Currency Type" value={eInfo.income_currency} />
|
||||
<DetailRow label="Annual Income" value={eInfo.annual_income} />
|
||||
|
||||
<SectionTitle title="Work Location" />
|
||||
<DetailRow label="Country" value={eInfo.work_country_name || eInfo.work_country} />
|
||||
<DetailRow label="State" value={eInfo.work_state_name || eInfo.work_state} />
|
||||
<DetailRow label="City" value={eInfo.work_district_name || eInfo.work_city} />
|
||||
<DetailRow label="Address" value={eInfo.work_location || eInfo.address} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Grid item xs={12} sx={{display:"flex", justifyContent:"center"}}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
|
||||
size="large"
|
||||
onClick={handleConfirmSubmit}
|
||||
sx={{ borderRadius: 2 }}
|
||||
>
|
||||
Submit full Completed Data
|
||||
</Button>
|
||||
</Grid>
|
||||
{/* 3. Family Details */}
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardHeader
|
||||
title={<Typography variant="h6" fontWeight="bold">Family Details</Typography>}
|
||||
action={<IconButton color="primary" onClick={() => onEdit(3)} size="large"><Edit2 size={20} /></IconButton>}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
/>
|
||||
<CardContent>
|
||||
<DetailRow label="Father Name" value={fInfo.father_name || fInfo.fatherName} />
|
||||
<DetailRow label="Father Occupation" value={fInfo.father_occupation || fInfo.father_occupational || fInfo.fatherOccupation || fInfo.fatherOccupational || fInfo.father_occupation_name} />
|
||||
<DetailRow label="Mother Name" value={fInfo.mother_name || fInfo.motherName} />
|
||||
<DetailRow label="Mother Occupation" value={fInfo.mother_occupation || fInfo.mother_occupational || fInfo.motherOccupation || fInfo.motherOccupational || fInfo.mother_occupation_name} />
|
||||
|
||||
<Dialog
|
||||
open={openConfirmDialog}
|
||||
onClose={handleCancelSubmit}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
{/* Siblings */}
|
||||
{['brothers', 'sisters'].map(type => (
|
||||
(fInfo[type] && fInfo[type].length > 0) && (
|
||||
<Box key={type} mt={2} mb={2}>
|
||||
<Typography color="text.secondary" sx={{ fontWeight: 600, mb: 1, textTransform: 'capitalize' }}>{type} Details:</Typography>
|
||||
<Swiper modules={[Navigation, Pagination]} spaceBetween={10} slidesPerView={1} navigation pagination={{ clickable: true }} style={{ paddingBottom: '30px' }}>
|
||||
{fInfo[type].map((sib, i) => (
|
||||
<SwiperSlide key={i}>
|
||||
<Card variant="outlined" sx={{ p: 2, bgcolor: '#f9f9f9' }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>{type.slice(0, -1)} {i + 1}</Typography>
|
||||
<DetailRow label="Name" value={sib.name} />
|
||||
<DetailRow label={type === 'brothers' ? 'Brother Type' : 'Sister Type'} value={sib.type} />
|
||||
<DetailRow label="Occupation" value={sib.occupation || sib.occupational || sib.occupation_name} />
|
||||
<DetailRow label="Marital Status" value={sib.marital_status || sib.maritalStatus || sib.marital_status_id} />
|
||||
<DetailRow label="Has Children" value={sib.has_children || sib.hasChildren || sib.thereHaveChildren} />
|
||||
<DetailRow label="Additional Details" value={sib.details || sib.additionalDetails || sib.additional_details} />
|
||||
</Card>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</Box>
|
||||
)
|
||||
))}
|
||||
|
||||
<DetailRow label="Family Status" value={fInfo.family_status || fInfo.familyStatus} />
|
||||
<DetailRow label="Native Place" value={fInfo.native_place || fInfo.nativePlace} />
|
||||
|
||||
<SectionTitle title="Location Living" />
|
||||
<DetailRow label="Country Living" value={fInfo.family_country_name || fInfo.living_country_name} />
|
||||
<DetailRow label="Residing State" value={fInfo.family_state_name || fInfo.living_state_name} />
|
||||
<DetailRow label="Residing City" value={fInfo.family_district_name || fInfo.family_city || fInfo.living_city_name} />
|
||||
<DetailRow label="Address" value={fInfo.address} />
|
||||
<DetailRow label="Expectations / Requirements Details" value={fInfo.expectation_details || fInfo.expectationDetails} />
|
||||
<DetailRow label="Willing to go abroad" value={fInfo.willing_to_go_abroad || fInfo.willingToGoAbroad} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 4. Lifestyle & Horoscope */}
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardHeader
|
||||
title={<Typography variant="h6" fontWeight="bold">Lifestyle & Horoscope</Typography>}
|
||||
action={<IconButton color="primary" onClick={() => onEdit(4)} size="large"><Edit2 size={20} /></IconButton>}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
/>
|
||||
<CardContent>
|
||||
<DetailRow label="Time of Birth" value={lInfo.time_of_birth_formated || lInfo.tob} />
|
||||
<DetailRow label="Place of Birth" value={lInfo.place_of_birth || lInfo.placeOfBirth} />
|
||||
<DetailRow label="Birth Star" value={lInfo.star_name || lInfo.star} />
|
||||
<DetailRow label="Padham" value={lInfo.patham} />
|
||||
<DetailRow label="Rasi" value={lInfo.raasi_name || lInfo.raasi} />
|
||||
<DetailRow label="Lagnam" value={lInfo.lagnam_name || lInfo.lagnam} />
|
||||
<DetailRow label="Panjangam" value={lInfo.panjangam_type} />
|
||||
<DetailRow label="Date of Birth" value={lInfo.date_of_birth_formated || lInfo.dob} />
|
||||
<DetailRow label="Dasa Balance" value={lInfo.dasa_balance} />
|
||||
<DetailRow label="Dasa Duration" value={(lInfo.dasa_years || lInfo.dasa_months || lInfo.dasa_days) ? `${lInfo.dasa_years || 0} Years, ${lInfo.dasa_months || 0} Months, ${lInfo.dasa_days || 0} Days` : "-"} />
|
||||
|
||||
{/* Horoscope Charts */}
|
||||
{lInfo.horoscope && (
|
||||
<Box mt={3}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
{renderChartGrid((i) => lInfo.horoscope[`graha_${i}`]?.split(',') || (lInfo.graha && lInfo.graha[i]) || [], 'Rasi')}
|
||||
</Grid>
|
||||
<Grid size={{ xs: 12, sm: 6 }}>
|
||||
{renderChartGrid((i) => lInfo.horoscope[`amsam_${i}`]?.split(',') || (lInfo.amsam && lInfo.amsam[i]) || [], 'Navamsam')}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* 5. What am I looking for in a Partner */}
|
||||
<Card variant="outlined" sx={{ borderRadius: 2 }}>
|
||||
<CardHeader
|
||||
title={<Typography variant="h6" fontWeight="bold">What am I looking for in a Partner</Typography>}
|
||||
action={<IconButton color="primary" onClick={() => onEdit(5)} size="large"><Edit2 size={20} /></IconButton>}
|
||||
sx={{ padding: "15px 15px", background: "#f5fbff" }}
|
||||
/>
|
||||
<CardContent>
|
||||
<DetailRow label="Age Range" value={(prInfo.preferred_age_from || prInfo.age_from) && (prInfo.preferred_age_to || prInfo.age_to) ? `${prInfo.preferred_age_from || prInfo.age_from} - ${prInfo.preferred_age_to || prInfo.age_to}` : "-"} />
|
||||
<DetailRow label="Height" value={(getNamesFromIds(prInfo.preferred_height_from_id || prInfo.height_from, ppm?.heights, 'height_text') && getNamesFromIds(prInfo.preferred_height_to_id || prInfo.height_to, ppm?.heights, 'height_text')) ? `${getNamesFromIds(prInfo.preferred_height_from_id || prInfo.height_from, ppm?.heights, 'height_text')} - ${getNamesFromIds(prInfo.preferred_height_to_id || prInfo.height_to, ppm?.heights, 'height_text')}` : "-"} />
|
||||
<DetailRow label="Marital Status" value={getNamesFromIds(prInfo.preferred_marital_statuses || prInfo.marital_statuses || prInfo.preferred_marital_status || prInfo.marital_status, ppm?.maritalStatus || ppm?.marital_status, 'marital_status_name')} />
|
||||
<DetailRow label="Birth Star" value={getNamesFromIds(prInfo.preferred_birth_stars || prInfo.birth_stars || prInfo.preferred_birth_star || prInfo.birth_star, ppm?.stars || ppm?.star, 'star_name')} />
|
||||
<DetailRow label="Caste" value={getNamesFromIds(prInfo.preferred_castes || prInfo.castes || prInfo.preferred_caste || prInfo.caste, ppm?.caste || ppm?.castes, 'caste_name')} />
|
||||
<DetailRow label="Sub - Sect" value={getNamesFromIds(prInfo.preferred_sub_castes || prInfo.sub_castes || prInfo.preferred_sub_caste || prInfo.sub_caste, ppm?.sub_caste || ppm?.sub_castes, 'sub_caste_name')} />
|
||||
<DetailRow label="Qualification" value={getNamesFromIds(prInfo.preferred_educations || prInfo.educations || prInfo.preferred_education || prInfo.education, ppm?.education || ppm?.educations, 'education_name')} />
|
||||
<DetailRow label="Occupation" value={getNamesFromIds(prInfo.preferred_occupations || prInfo.occupations || prInfo.preferred_occupation || prInfo.occupation, ppm?.occupation || ppm?.occupations, 'occupation_name')} />
|
||||
|
||||
<DetailRow label="Employee Type" value={getNamesFromIds(prInfo.preferred_employee_types || prInfo.employee_types || prInfo.preferred_employee_type || prInfo.employee_type, ppm?.employeeTypes || ppm?.employee_type, 'employee_type_name')} />
|
||||
|
||||
<SectionTitle title="Prefer Annual Income" />
|
||||
{(prInfo.preferred_currencies || prInfo.currencies || []).includes("INR") && (
|
||||
<DetailRow label="Prefer Annual Income (INR)" value={(prInfo.preferred_inr_from || prInfo.inr_from || prInfo.preferred_inr_range) ? `From : ${prInfo.preferred_inr_from || prInfo.inr_from || ""} \nTo : ${prInfo.preferred_inr_to || prInfo.inr_to || ""}` : "-"} />
|
||||
)}
|
||||
{(prInfo.preferred_currencies || prInfo.currencies || []).includes("USD") && (
|
||||
<DetailRow label="Prefer Annual Income (USD)" value={(prInfo.preferred_usd_from || prInfo.usd_from || prInfo.preferred_usd_range) ? `From : ${prInfo.preferred_usd_from || prInfo.usd_from || ""} \nTo : ${prInfo.preferred_usd_to || prInfo.usd_to || ""}` : "-"} />
|
||||
)}
|
||||
|
||||
<DetailRow label="State" value={getNamesFromIds(prInfo.preferred_states || prInfo.states || prInfo.preferred_state || prInfo.state, ppm?.states, 'state_name')} />
|
||||
<DetailRow label="City" value={getNamesFromIds(prInfo.preferred_districts || prInfo.districts || prInfo.preferred_district || prInfo.district, ppm?.districts, 'district_name')} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Grid size={12} sx={{ display: "flex", justifyContent: "center" }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
size="large"
|
||||
onClick={handleConfirmSubmit}
|
||||
sx={{ borderRadius: 2 }}
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">
|
||||
{"Confirm Submission"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Once you submit your details, you will not be able to edit the following fields: Place of Birth, Date of Birth, Rasi and Navamsam.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCancelSubmit}>Cancel</Button>
|
||||
<Button onClick={handleProceedSubmit} autoFocus>
|
||||
OK
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
Submit Full Completed Data
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Dialog
|
||||
open={openConfirmDialog}
|
||||
onClose={handleCancelSubmit}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">{"Confirm Submission"}</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Once you submit your details, you will not be able to edit the following fields: Place of Birth, Date of Birth, Rasi and Navamsam.
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleCancelSubmit}>Cancel</Button>
|
||||
<Button onClick={handleProceedSubmit} autoFocus>OK</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 */}
|
||||
<div className="max-w-7xl mx-auto px-4 py-8">
|
||||
<div className="max-w-7xl mx-auto px-4 py-8 pb-32">
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-6">
|
||||
{selectedTabIndex === 0 && `Matches yet to respond (${profiles.length})`}
|
||||
{selectedTabIndex === 1 && `You request sent to others (${profiles.length})`}
|
||||
@ -177,18 +199,139 @@ const InterestSendPage = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<UpgradeModal
|
||||
isOpen={isGlobalUpgradeModalOpen}
|
||||
onClose={() => setIsGlobalUpgradeModalOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div
|
||||
onClick={() => 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"
|
||||
>
|
||||
|
||||
<div className="flex gap-4">
|
||||
{/* Profile Image */}
|
||||
<div className="relative w-32 h-40 flex-shrink-0">
|
||||
@ -252,36 +395,152 @@ const ProfileCard = ({ profile, tabIndex, onStatusUpdate }) => {
|
||||
{tabIndex === 0 && profile.statusReceived === 'pending' ? (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onStatusUpdate(profile.id, 'reject'); }}
|
||||
onClick={(e) => { e.stopPropagation(); setStatusConfirm({ open: true, status: 'reject' }); }}
|
||||
className="flex-1 py-2 rounded-lg font-bold border border-[#DF1D46] text-[#DF1D46] hover:bg-[#DF1D46]/5 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<XCircle className="w-5 h-5" /> Reject Request
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); onStatusUpdate(profile.id, 'accept'); }}
|
||||
onClick={(e) => { e.stopPropagation(); setStatusConfirm({ open: true, status: 'accept' }); }}
|
||||
className="flex-1 py-2 rounded-lg font-bold bg-[#00903F] text-white hover:bg-[#00903F]/90 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<CheckCircle className="w-5 h-5" /> Accept Request
|
||||
</button>
|
||||
</>
|
||||
) : (tabIndex === 1 || tabIndex === 2 || (tabIndex === 0 && profile.statusReceived === 'accept')) ? (
|
||||
) : (tabIndex === 1 || tabIndex === 2 || (tabIndex === 0 && profile.statusReceived !== 'pending')) ? (
|
||||
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); navigate("/chat"); }}
|
||||
onClick={handleMessage}
|
||||
className="flex-1 py-2 rounded-lg font-bold border border-[#DF1D46] text-[#DF1D46] hover:bg-[#DF1D46]/5 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<MessageCircle className="w-5 h-5" /> Message
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); /* Call logic */ }}
|
||||
onClick={handleCall}
|
||||
className="flex-1 py-2 rounded-lg font-bold bg-[#DF1D46] text-white hover:bg-[#DF1D46]/90 transition-all flex items-center justify-center gap-2"
|
||||
>
|
||||
<Phone className="w-5 h-5" /> Call
|
||||
</button>
|
||||
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UpgradeModal isOpen={isUpgradeModalOpen} onClose={() => setIsUpgradeModalOpen(false)} />
|
||||
|
||||
<AnimatePresence>
|
||||
{/* Accept/Reject Status Confirmation Dialog */}
|
||||
{statusConfirm.open && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center">
|
||||
<div className={`w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-6 ${statusConfirm.status === 'accept' ? 'bg-green-50' : 'bg-red-50'}`}>
|
||||
{statusConfirm.status === 'accept' ? (
|
||||
<CheckCircle className="w-10 h-10 text-[#00903F]" />
|
||||
) : (
|
||||
<XCircle className="w-10 h-10 text-[#DF1D46]" />
|
||||
)}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Note</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">
|
||||
{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?"}
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<button
|
||||
onClick={() => setStatusConfirm({ open: false, status: null })}
|
||||
className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onStatusUpdate(profile.id, statusConfirm.status);
|
||||
setStatusConfirm({ open: false, status: null });
|
||||
}}
|
||||
className={`flex-1 py-4 text-white rounded-xl font-bold ${statusConfirm.status === 'accept' ? 'bg-[#00903F]' : 'bg-[#DF1D46]'}`}
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isViewContactModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Note</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">You need to view this profile's contact details. If you choose to <span className="text-[#DF1D46] font-bold">"Proceed"</span> one count will be deducted from your subscription.</p>
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => setIsViewContactModalOpen(false)} className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500">Cancel</button>
|
||||
<button onClick={_handleViewContact} className="flex-1 py-4 bg-[#DF1D46] text-white rounded-xl font-bold">Proceed</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isChatConfirmModalOpen && (() => {
|
||||
const currentMobile = profile.mobile_number || "";
|
||||
const isMobileVisible = currentMobile !== "" && !currentMobile.toLowerCase().includes("view") && !currentMobile.toLowerCase().includes("upgrade");
|
||||
return (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">{isMobileVisible ? "Ready to Chat?" : "Note"}</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">{isMobileVisible ? `Are you ready to chat with ${currentMobile}?` : "Starting a conversation will use 1 chat count. Are you ready to proceed?"}</p>
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => setIsChatConfirmModalOpen(false)} className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500">Cancel</button>
|
||||
<button onClick={_handleCreateChat} className="flex-1 py-4 bg-[#DF1D46] text-white rounded-xl font-bold">{isCreatingChat ? "..." : "Proceed"}</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{isContactSuccessModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center">
|
||||
<div className="w-20 h-20 bg-green-50 rounded-full flex items-center justify-center mx-auto mb-6"><Phone size={40} className="text-[#0C8626]" /></div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-2">Success!</h3>
|
||||
<p className="text-gray-500 mb-6">The contact number has been unlocked.</p>
|
||||
<div className="bg-gray-50 rounded-2xl p-6 mb-8 border border-gray-100 flex items-center justify-center">
|
||||
<span className="text-3xl font-black text-gray-900 tracking-wider">
|
||||
{unlockedMobile || (!profile.mobile_number?.toLowerCase().includes("view") ? profile.mobile_number : "Fetching...")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<button onClick={() => setIsContactSuccessModalOpen(false)} className="flex-1 py-4 border border-gray-200 rounded-xl font-bold text-gray-500">Close</button>
|
||||
<button onClick={() => { const clean = (unlockedMobile || profile.mobile_number).replace(/[^0-9+]/g, ''); window.location.href = `tel:${clean}`; }} className="flex-1 py-4 bg-[#0C8626] text-white rounded-xl font-bold">Call Now</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isInterestStatusModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Note</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">An interest request has already been sent for this profile. Please wait for their response.</p>
|
||||
<button onClick={() => setIsInterestStatusModalOpen(false)} className="w-full py-4 bg-[#0C8626] text-white rounded-xl font-bold">OK</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isInterestRejectedModalOpen && (
|
||||
<div className="fixed inset-0 z-[10001] flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
||||
<motion.div initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} className="bg-white rounded-[32px] p-8 max-w-md w-full shadow-2xl text-center">
|
||||
<h3 className="text-2xl font-bold text-gray-900 mb-6">Sorry</h3>
|
||||
<p className="text-gray-600 mb-8 leading-relaxed">Your interest request was rejected by this user. You cannot initiate a chat at this time.</p>
|
||||
<button onClick={() => setIsInterestRejectedModalOpen(false)} className="w-full py-4 bg-gray-500 text-white rounded-xl font-bold">Close</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
whileHover={{ y: -8, transition: { duration: 0.3 } }}
|
||||
transition={{ delay: index * 0.1, duration: 0.5 }}
|
||||
className={`group relative rounded-[32px] overflow-hidden border transition-all duration-500 ${
|
||||
isActive
|
||||
? 'border-amber-300 shadow-[0_20px_50px_rgba(255,193,7,0.15)] bg-white'
|
||||
: 'border-gray-100 bg-white/80 backdrop-blur-sm shadow-xl'
|
||||
}`}
|
||||
>
|
||||
{/* Dynamic Background Pattern */}
|
||||
<div className={`absolute inset-0 opacity-[0.03] pointer-events-none ${isActive ? 'bg-[radial-gradient(circle_at_50%_50%,#ffc107_0%,transparent_70%)]' : ''}`} />
|
||||
|
||||
{/* Decorative Gradient Glow */}
|
||||
{isActive && (
|
||||
<div className="absolute -right-20 -top-20 w-40 h-40 bg-amber-400/20 blur-[80px] rounded-full pointer-events-none" />
|
||||
)}
|
||||
|
||||
{/* Header Section */}
|
||||
<div className={`relative px-6 pt-12 pb-6 text-center ${isActive ? 'bg-gradient-to-b from-amber-50/50 to-transparent' : ''}`}>
|
||||
{/* Status Badge */}
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 flex items-center gap-2">
|
||||
{isActive ? (
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.05, 1] }}
|
||||
transition={{ repeat: Infinity, duration: 2 }}
|
||||
className="bg-gradient-to-r from-amber-500 to-orange-600 text-white px-5 py-1.5 rounded-full text-[10px] font-black tracking-widest shadow-lg flex items-center gap-2"
|
||||
>
|
||||
<Zap size={12} fill="white" /> ACTIVE PLAN
|
||||
</motion.div>
|
||||
) : (
|
||||
<div className="bg-gray-100 text-gray-500 px-4 py-1 rounded-full text-[10px] font-bold tracking-widest flex items-center gap-2">
|
||||
<History size={12} /> COMPLETED
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 className="text-2xl font-black text-gray-900 mb-1">{sub.plan_name}</h3>
|
||||
<p className="text-[10px] text-gray-400 font-bold uppercase tracking-[0.2em]">Transaction ID: #TK{sub.id}</p>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-8">
|
||||
{/* Stats Grid */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-6">
|
||||
<div className="relative group/stat p-4 rounded-2xl bg-gray-50 border border-gray-100 transition-colors hover:bg-amber-50/50 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-2 opacity-10 group-hover/stat:opacity-20 transition-opacity">
|
||||
<Person />
|
||||
</div>
|
||||
<p className="text-2xl font-black text-gray-900 mb-0.5">
|
||||
{sub.count_of_profile_view === -1 ? '∞' : sub.count_of_profile_view}
|
||||
</p>
|
||||
<p className="text-[9px] font-bold text-gray-400 uppercase tracking-wider">Profile Views</p>
|
||||
</div>
|
||||
<div className="relative group/stat p-4 rounded-2xl bg-gray-50 border border-gray-100 transition-colors hover:bg-green-50/50 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 p-2 opacity-10 group-hover/stat:opacity-20 transition-opacity">
|
||||
<Visibility />
|
||||
</div>
|
||||
<p className="text-2xl font-black text-gray-900 mb-0.5">{sub.used_count_of_profile_view || 0}</p>
|
||||
<p className="text-[9px] font-bold text-gray-400 uppercase tracking-wider">Used Count</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Benefits/Details List */}
|
||||
<div className="space-y-2 mb-8">
|
||||
<DetailRow icon={<CalendarToday className="text-amber-500" />} label="Activated" value={new Date(sub.supscription_start_date).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })} />
|
||||
<DetailRow icon={<AccessTime className="text-red-500" />} label="Expiry" value={new Date(sub.supscription_expiry_date).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })} />
|
||||
<DetailRow icon={<ShieldCheck className="text-blue-500" size={16} />} label="Duration" value={sub.plan_validity_days === -1 ? 'Lifetime Access' : `${sub.plan_validity_days} Days`} />
|
||||
</div>
|
||||
|
||||
{/* Price Tag */}
|
||||
<div className={`p-4 rounded-2xl flex items-center justify-between ${isActive ? 'bg-amber-50 border border-amber-100' : 'bg-gray-50 border border-gray-100'}`}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className={`p-2 rounded-lg ${isActive ? 'bg-amber-500 text-white' : 'bg-gray-200 text-gray-500'}`}>
|
||||
<CreditCard fontSize="small" />
|
||||
</div>
|
||||
<span className="text-xs font-bold text-gray-500">Total Paid</span>
|
||||
</div>
|
||||
<span className={`text-xl font-black ${isActive ? 'text-amber-600' : 'text-gray-900'}`}>
|
||||
₹{sub.plan_amount?.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
const DetailRow = ({ icon, label, value }) => (
|
||||
<div className="flex items-center justify-between py-2.5 px-4 rounded-xl hover:bg-gray-50 transition-colors">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex-shrink-0 opacity-80">{icon}</span>
|
||||
<span className="text-[11px] font-bold text-gray-500 uppercase tracking-wider">{label}</span>
|
||||
</div>
|
||||
<span className="text-xs font-bold text-gray-900">{value}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default function SubscriptionHistory() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { data: historyData, isLoading, error } = useQuery({
|
||||
queryKey: ['subscriptionHistory'],
|
||||
queryFn: getSubscriptionHistory,
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center justify-center bg-white">
|
||||
<motion.div
|
||||
animate={{ scale: [1, 1.1, 1], rotate: 360 }}
|
||||
transition={{ repeat: Infinity, duration: 2 }}
|
||||
className="mb-6"
|
||||
>
|
||||
<WorkspacePremium sx={{ fontSize: 60, color: '#A70710' }} />
|
||||
</motion.div>
|
||||
<div className="w-48 h-1 bg-gray-100 rounded-full overflow-hidden relative">
|
||||
<motion.div
|
||||
animate={{ left: ['-100%', '100%'] }}
|
||||
transition={{ repeat: Infinity, duration: 1.5, ease: "linear" }}
|
||||
className="absolute top-0 bottom-0 w-full bg-[#A70710]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const currentSub = historyData?.current_subscription;
|
||||
const history = historyData?.subscription_history || [];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen ">
|
||||
{/* Royal Header */}
|
||||
<div className="min-h-screen bg-[#FAFAFA] text-black">
|
||||
{/* Glass Header */}
|
||||
<motion.header
|
||||
initial={{ y: -100 }}
|
||||
animate={{ y: 0 }}
|
||||
className="bg-[#034E08] text-white shadow-2xl rounded-[10px]"
|
||||
initial={{ y: -20, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
className="sticky top-0 z-[100] bg-white/80 backdrop-blur-xl border-b border-gray-100"
|
||||
>
|
||||
<div className="flex items-center justify-between px-5 py-8 safe-area-top relative">
|
||||
|
||||
<h1 className="text-2xl font-bold">Subscription History</h1>
|
||||
<div className="w-12" />
|
||||
<div className="max-w-2xl mx-auto px-4 h-20 flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="w-10 h-10 flex items-center justify-center rounded-2xl hover:bg-gray-100 transition-colors text-gray-600"
|
||||
>
|
||||
<ArrowBackIosNew fontSize="small" />
|
||||
</button>
|
||||
<div className="text-center">
|
||||
<h1 className="text-lg font-black tracking-tight">Subscriptions</h1>
|
||||
<p className="text-[9px] font-bold text-amber-600 tracking-[0.3em] uppercase">Thirukalyanam Premium</p>
|
||||
</div>
|
||||
<div className="w-10 h-10 flex items-center justify-center rounded-2xl bg-amber-50 text-amber-600">
|
||||
<ReceiptLong size={20} />
|
||||
</div>
|
||||
</div>
|
||||
</motion.header>
|
||||
|
||||
{/* Current Plan Title */}
|
||||
<div className="px-6 pt-8">
|
||||
<motion.h2
|
||||
initial={{ opacity: 0, x: -50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
className="text-3xl font-bold text-gray-900 flex items-center gap-3"
|
||||
>
|
||||
<CrownIcon className="text-amber-500 text-4xl" />
|
||||
Current Plan
|
||||
</motion.h2>
|
||||
</div>
|
||||
<main className="max-w-7xl mx-auto px-4 py-10 pb-32">
|
||||
{/* Hero Active Section */}
|
||||
<div className="mb-16">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="w-14 h-14 rounded-[20px] bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center text-white shadow-lg shadow-orange-100">
|
||||
<WorkspacePremium fontSize="large" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-black text-gray-900">Current Status</h2>
|
||||
<p className="text-xs font-bold text-gray-400 uppercase tracking-widest">Active Plan Details</p>
|
||||
</div>
|
||||
</div>
|
||||
{currentSub && (
|
||||
<div className="bg-green-50 text-green-600 px-4 py-2 rounded-xl hidden sm:flex items-center gap-2 border border-green-100">
|
||||
<CheckCircle2 size={16} />
|
||||
<span className="text-[10px] font-black uppercase tracking-wider">Verified Membership</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className=''>
|
||||
{/* Subscription Cards */}
|
||||
<div className="px-2 py-8 pb-32 grid grid-cols-1 md:grid-cols-3 gap-2">
|
||||
{subscriptions.map((sub, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.2 }}
|
||||
className={`relative rounded-3xl overflow-hidden border border-1 border-gray-200 w-full max-w-md ${
|
||||
sub.isActive
|
||||
? 'border-amber-400 bg-gradient-to-br from-amber-50 to-pink-50'
|
||||
: 'border-pink-200 bg-gradient-to-br from-pink-50 to-rose-50 opacity-90'
|
||||
}`}
|
||||
>
|
||||
{/* Crown Badge */}
|
||||
<div className={` text-[12px] absolute z-[999] -top-1 left-1/2 -translate-x-1/2 px-8 py-2 rounded-b-3xl font-bold text-white shadow-xl flex items-center gap-2 ${
|
||||
sub.isActive ? 'bg-gradient-to-r from-amber-500 to-red-600' : 'bg-gradient-to-r from-pink-500 to-rose-600'
|
||||
}`}>
|
||||
<CrownIcon className="text-[12px]" />
|
||||
{sub.plan}
|
||||
{sub.isActive && <span className="ml-2 text-[12px] bg-green-400 text-black px-3 py-1 rounded-full">ACTIVE</span>}
|
||||
</div>
|
||||
|
||||
<div className="pt-14 pb-8 px-8">
|
||||
{/* Subscription ID */}
|
||||
<div className="text-center mb-6">
|
||||
<p className="text-sm text-gray-600">Subscription ID</p>
|
||||
<p className="text-[18px] font-bold text-gray-900">{sub.id}</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Row */}
|
||||
<div className="grid grid-cols-2 gap-6 mb-4">
|
||||
<div className="bg-white/70 backdrop-blur rounded-2xl p-5 text-center ">
|
||||
<Person className="text-4xl text-amber-600 mx-auto mb-2" />
|
||||
<p className="text-[18px] font-bold text-gray-900">{sub.profileCount}</p>
|
||||
<p className="text-sm text-gray-600">Profile Count</p>
|
||||
</div>
|
||||
<div className="bg-white/70 backdrop-blur rounded-2xl p-5 text-center ">
|
||||
<Visibility className="text-4xl text-green-600 mx-auto mb-2" />
|
||||
<p className="text-[18px] font-bold text-gray-900">{sub.usedCount}</p>
|
||||
<p className="text-sm text-gray-600">Used Count</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Details Grid */}
|
||||
<div className="space-y-2 text-lg">
|
||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
||||
<CalendarToday className="text-amber-600" /> Billing Cycle
|
||||
</span>
|
||||
<span className="font-bold text-gray-900 text-[14px]">{sub.billingCycle}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
||||
<CalendarToday className="text-red-600" /> Expire Date
|
||||
</span>
|
||||
<span className="font-bold text-red-600 text-[14px]">{sub.expireDate}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
||||
<AccessTime className="text-green-600" /> Start Date & Time
|
||||
</span>
|
||||
<span className="font-bold text-gray-900 text-[14px]">{sub.startDate} | {sub.startTime}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center bg-white/60 backdrop-blur rounded-xl px-5 py-3">
|
||||
<span className="flex items-center gap-3 text-gray-700 text-[14px]">
|
||||
<CreditCard className="text-purple-600" /> Payment Method
|
||||
</span>
|
||||
<span className="font-bold text-purple-700 text-[14px]">{sub.paymentMethod}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Total Amount */}
|
||||
<div className="mt-8 text-center flex gap-4 items-center justify-center flex-wrap">
|
||||
<p className="text-lg text-gray-600">Total Plan Amount</p>
|
||||
<p className="text-[18px] font-bold text-gray-900 bg-white/70 backdrop-blur px-4 py-2 rounded-[10px]">
|
||||
₹{sub.amount.toLocaleString()}
|
||||
</p>
|
||||
{currentSub ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
<PlanCard sub={currentSub} isActive={true} />
|
||||
<div className="hidden lg:block lg:col-span-2 bg-gradient-to-br from-gray-900 to-black rounded-[40px] p-10 relative overflow-hidden text-white shadow-2xl">
|
||||
<div className="absolute top-0 right-0 p-10 opacity-20 scale-150">
|
||||
<CrownIcon size={120} />
|
||||
</div>
|
||||
<div className="relative z-10 h-full flex flex-col justify-center">
|
||||
<h3 className="text-3xl font-black mb-4 tracking-tight">Premium Benefits Active</h3>
|
||||
<p className="text-gray-400 mb-8 max-w-md leading-relaxed text-sm">
|
||||
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.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{['Verified Profile', 'Priority Search', 'Direct Contact', 'Chat Enabled'].map((item) => (
|
||||
<div key={item} className="px-4 py-2 bg-white/10 backdrop-blur-md rounded-xl text-[10px] font-bold tracking-widest uppercase flex items-center gap-2">
|
||||
<CheckCircle2 size={12} className="text-green-400" /> {item}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{/* Final Message */}
|
||||
{/* <motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.6 }}
|
||||
className="fixed bottom-0 left-0 right-0 bg-gradient-to-r from-red-600 to-rose-700 text-white py-8 text-center shadow-2xl"
|
||||
>
|
||||
<p className="text-xl font-bold">Your journey to forever is fully powered</p>
|
||||
<p className="text-green-200 mt-2">Enjoy unlimited matches with your Gold Plan</p>
|
||||
</motion.div> */}
|
||||
) : (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="p-10 rounded-[40px] bg-white border-2 border-dashed border-gray-100 text-center max-w-xl mx-auto"
|
||||
>
|
||||
<div className="w-20 h-20 bg-gray-50 rounded-full flex items-center justify-center mx-auto mb-6">
|
||||
<ShieldCheck size={40} className="text-gray-200" />
|
||||
</div>
|
||||
<h3 className="text-lg font-black text-gray-900 mb-2">No Active Membership</h3>
|
||||
<p className="text-sm text-gray-400 mb-8 max-w-[240px] mx-auto leading-relaxed">
|
||||
Unlock premium matchmaking features to find your soulmate faster.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => navigate('/subscription-plan')}
|
||||
className="group relative w-full py-4 bg-gray-900 text-white rounded-2xl font-black flex items-center justify-center gap-2 overflow-hidden"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-amber-400 to-orange-500 translate-y-full group-hover:translate-y-0 transition-transform duration-500" />
|
||||
<span className="relative z-10">UPGRADE NOW</span>
|
||||
<ArrowRight size={18} className="relative z-10 transition-transform group-hover:translate-x-1" />
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Timeline History Section */}
|
||||
<div className="relative">
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-14 h-14 rounded-[20px] bg-white shadow-xl flex items-center justify-center text-gray-400">
|
||||
<History fontSize="large" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-black text-gray-900">Purchase History</h2>
|
||||
<p className="text-xs font-bold text-gray-400 uppercase tracking-widest">Your payment journey</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{history.length > 0 ? (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{history.map((sub, index) => (
|
||||
<PlanCard key={sub.id} sub={sub} index={index + 1} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-20 text-center">
|
||||
<div className="inline-block p-6 rounded-full bg-gray-50 mb-4">
|
||||
<LocalActivity className="text-gray-200" sx={{ fontSize: 40 }} />
|
||||
</div>
|
||||
<p className="text-sm font-bold text-gray-300 uppercase tracking-widest">No Previous History</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -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 (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<CircularProgress sx={{ color: '#A70710' }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center text-red-500 text-center px-4">
|
||||
<div>
|
||||
<p className="text-xl font-bold mb-2">Error loading plans</p>
|
||||
<p>Please check your connection or try again later.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen mt-4 text-black overflow-hidden relative z-20">
|
||||
@ -70,113 +183,130 @@ export default function SubscriptionPlan() {
|
||||
>
|
||||
<div className="absolute inset-0 bg-[#034E08] rounded-[15px]" />
|
||||
<div className="relative flex items-center justify-between px-5 py-8 safe-area-top">
|
||||
|
||||
<button onClick={() => navigate(-1)} className="text-white">
|
||||
<ArrowBackIosNew />
|
||||
</button>
|
||||
<h1 className="text-2xl font-bold text-center text-white">Subscription Plan</h1>
|
||||
<div className="w-12" />
|
||||
</div>
|
||||
</motion.header>
|
||||
|
||||
{/* Annual / Monthly Toggle */}
|
||||
<div className="flex justify-center -mt-6 relative z-10">
|
||||
<motion.div
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="bg-[#A70710] backdrop-blur-xl rounded-full p-2 flex gap-2 shadow-2xl"
|
||||
// style={{background:"linear-gradient(98.05deg, #FAFBFF 0%, #FCC4FF 100%)"}}
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsAnnual(true)}
|
||||
className={`px-8 py-4 rounded-full font-bold text-lg transition-all ${isAnnual ? 'bg-white text-purple-900 shadow-lg' : 'text-white/80'}`}
|
||||
>
|
||||
ANNUAL PLAN
|
||||
<span className="block text-xs font-normal mt-1 text-green-300">Save up to 40%</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsAnnual(false)}
|
||||
className={`px-8 py-4 rounded-full font-bold text-lg transition-all ${!isAnnual ? 'bg-white text-purple-900 shadow-lg' : 'text-white/80'}`}
|
||||
>
|
||||
MONTHLY PLAN
|
||||
</button>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
{/* Pricing Cards */}
|
||||
<div className="px-5 py-10 max-w-7xl mx-auto">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
{plans.map((plan, index) => (
|
||||
<motion.div
|
||||
key={index}
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.2 }}
|
||||
className={`relative backdrop-blur-xl rounded-3xl overflow-hidden border ${
|
||||
plan.popular ? 'border-yellow-400 shadow-2xl shadow-yellow-500/30 scale-105' : 'border-white/30'
|
||||
}`}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||
{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 && (
|
||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 bg-gradient-to-r from-yellow-400 to-amber-500 text-purple-900 px-8 py-2 rounded-b-2xl font-bold text-sm flex items-center gap-2">
|
||||
<AutoAwesome className="text-lg" /> MOST POPULAR <AutoAwesome className="text-lg" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 p-8 text-center">
|
||||
<h3 className="text-3xl font-bold mb-4">{plan.name}</h3>
|
||||
<div className="text-[25px] font-bold mb-2">
|
||||
₹{isAnnual ? plan.annualPrice.toLocaleString() : plan.monthlyPrice.toLocaleString()}
|
||||
<span className="text-[18px] font-normal text-black"> /{isAnnual ? 'month' : 'month'}</span>
|
||||
</div>
|
||||
{isAnnual && (
|
||||
<p className="text-[#00903F] font-bold text-lg mb-6">
|
||||
Billed annually @ ₹{(plan.annualPrice * 12).toLocaleString()}
|
||||
</p>
|
||||
return (
|
||||
<motion.div
|
||||
key={plan.id}
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className={`relative backdrop-blur-xl rounded-3xl overflow-hidden border ${
|
||||
plan.is_current_plan ? 'border-yellow-400 shadow-2xl scale-105' : 'border-white/30'
|
||||
}`}
|
||||
style={{ background: "linear-gradient(98.05deg, #FAFBFF 0%, #FCC4FF 100%)" }}
|
||||
>
|
||||
{plan.is_current_plan && (
|
||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 bg-gradient-to-r from-yellow-400 to-amber-500 text-purple-900 px-8 py-2 rounded-b-2xl font-bold text-sm flex items-center gap-2">
|
||||
<Star className="text-lg" /> CURRENT PLAN <Star className="text-lg" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className={`w-[fit-content] py-4 px-8 rounded-full text-white font-semibold text-[16px] shadow-xl bg-gradient-to-r ${plan.button} hover:shadow-2xl transition-all`}
|
||||
style={{background:"#A70710"}}
|
||||
>
|
||||
Select Plan
|
||||
</motion.button>
|
||||
<div className="mt-6 p-8 text-center">
|
||||
<h3 className="text-2xl font-bold mb-4">{plan.plan_name}</h3>
|
||||
<div className="text-[32px] font-bold mb-2">
|
||||
₹{plan.plan_amount}
|
||||
<span className="text-[18px] font-normal text-gray-600">
|
||||
/{plan.plan_validity_days === -1 ? 'Lifetime' : `${plan.plan_validity_days} days`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-left space-y-4">
|
||||
<h4 className="text-xl font-bold text-black">Features</h4>
|
||||
{plan.features.map((feature, i) => (
|
||||
<div key={i} className="flex items-center gap-3">
|
||||
<Check className="text-green-400 text-2xl" />
|
||||
<span className="text-black">{feature}</span>
|
||||
<motion.button
|
||||
disabled={!isBuyable || purchaseLoading}
|
||||
whileHover={isBuyable ? { scale: 1.05 } : {}}
|
||||
whileTap={isBuyable ? { scale: 0.95 } : {}}
|
||||
onClick={() => 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 ? <CircularProgress size={24} color="inherit" /> :
|
||||
plan.is_current_plan ? "Current Plan" : "Select Plan"}
|
||||
</motion.button>
|
||||
|
||||
<div className="mt-8 text-left space-y-3">
|
||||
<h4 className="text-lg font-bold text-black border-b border-gray-200 pb-2">Features</h4>
|
||||
<div className="max-h-[300px] overflow-y-auto pr-2 custom-scrollbar">
|
||||
{features.map((feature, i) => (
|
||||
<div key={i} className="flex items-center gap-3 py-1">
|
||||
<div className={`rounded-full p-0.5 ${feature.available ? 'bg-green-100' : 'bg-red-100'}`}>
|
||||
<Check className={`text-xl ${feature.available ? 'text-green-600' : 'text-red-600'}`}
|
||||
sx={{ fontSize: 16, visibility: feature.available ? 'visible' : 'hidden' }} />
|
||||
{!feature.available && <span className="w-4 h-4 flex items-center justify-center text-red-600 font-bold text-xs">✕</span>}
|
||||
</div>
|
||||
<span className={`text-sm ${feature.available ? 'text-black' : 'text-gray-400 line-through'}`}>
|
||||
{feature.text}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Final CTA */}
|
||||
{/* <motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.8 }}
|
||||
className="text-center mt-16"
|
||||
>
|
||||
<h2 className="text-3xl font-bold mb-4">Find Your Perfect Life Partner</h2>
|
||||
<p className="text-xl text-white/80 max-w-2xl mx-auto mb-10">
|
||||
Choose the plan that’s right for your sacred journey. Every premium member gets closer to their soulmate.
|
||||
<div className="text-center mt-12 mb-8">
|
||||
<p className="text-gray-600 max-w-2xl mx-auto italic">
|
||||
Choose the plan that's right for you. Find your perfect life partner with our premium plans.
|
||||
</p>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
className="bg-gradient-to-r from-red-600 to-rose-700 px-12 py-6 rounded-full text-2xl font-bold shadow-2xl hover:shadow-red-500/50 transition-all"
|
||||
>
|
||||
Choose This Plan
|
||||
</motion.button>
|
||||
</motion.div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-32" />
|
||||
{/* Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={confirmDialog.open}
|
||||
onClose={() => setConfirmDialog({ open: false, plan: null })}
|
||||
PaperProps={{
|
||||
sx: { borderRadius: '20px', padding: '10px' }
|
||||
}}
|
||||
>
|
||||
<DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 1, color: '#1B5E20', fontWeight: 'bold' }}>
|
||||
<InfoOutlined /> Note
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<p className="text-[#DF1D46] font-semibold text-center text-lg">
|
||||
If you choose a new plan, your current plan will expire and the new plan will be activated.
|
||||
</p>
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ justifyContent: 'center', gap: 2, pb: 3 }}>
|
||||
<Button
|
||||
onClick={() => setConfirmDialog({ open: false, plan: null })}
|
||||
variant="outlined"
|
||||
sx={{ borderRadius: '10px', color: 'gray', borderColor: 'gray' }}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const plan = confirmDialog.plan;
|
||||
setConfirmDialog({ open: false, plan: null });
|
||||
handlePurchase(plan);
|
||||
}}
|
||||
variant="contained"
|
||||
sx={{ borderRadius: '10px', bgcolor: '#A70710', '&:hover': { bgcolor: '#8e060d' } }}
|
||||
>
|
||||
Ok
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<div className="h-20" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
|
||||
<style>{`
|
||||
.custom-swiper-hero .swiper {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.custom-swiper-hero .swiper {
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* ================= MOBILE (≤768px) ================= */
|
||||
@media (max-width: 768px) {
|
||||
.custom-swiper-hero .swiper {
|
||||
overflow: hidden;
|
||||
}
|
||||
/* ================= MOBILE (≤768px) ================= */
|
||||
@media (max-width: 768px) {
|
||||
.custom-swiper-hero .swiper {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.custom-swiper-hero .swiper-slide {
|
||||
width: 100% !important;
|
||||
height: 200px;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
.custom-swiper-hero .swiper-slide {
|
||||
width: 100% !important;
|
||||
height: 200px;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.custom-swiper-hero .swiper-slide-active {
|
||||
width: 100% !important;
|
||||
height: 200px !important;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.custom-swiper-hero .swiper-slide-active {
|
||||
width: 100% !important;
|
||||
height: 200px !important;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= TABLET & DESKTOP (≥769px) ================= */
|
||||
@media (min-width: 769px) {
|
||||
.custom-swiper-hero .swiper {
|
||||
overflow: visible;
|
||||
}
|
||||
/* ================= TABLET & DESKTOP (≥769px) ================= */
|
||||
@media (min-width: 769px) {
|
||||
.custom-swiper-hero .swiper {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.custom-swiper-hero .swiper-wrapper {
|
||||
align-items: center;
|
||||
}
|
||||
.custom-swiper-hero .swiper-wrapper {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.custom-swiper-hero .swiper-slide {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
transform: scale(1); /* ❌ removed zoom out */
|
||||
opacity: 1; /* ❌ removed fade */
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
.custom-swiper-hero .swiper-slide {
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
transform: scale(1); /* ❌ removed zoom out */
|
||||
opacity: 1; /* ❌ removed fade */
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
||||
.custom-swiper-hero .swiper-slide-active {
|
||||
width: 630px !important; /* center slide bigger */
|
||||
height: 320px !important;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
.custom-swiper-hero .swiper-slide-active {
|
||||
width: 630px !important; /* center slide bigger */
|
||||
height: 320px !important;
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= COMMON ================= */
|
||||
.custom-swiper-hero .swiper-slide > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
/* ================= COMMON ================= */
|
||||
.custom-swiper-hero .swiper-slide > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.custom-swiper-hero .swiper-slide img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.custom-swiper-hero .swiper-slide img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Desktop Styles */
|
||||
// @media (min-width: 1024px) {
|
||||
// .custom-swiper-hero .swiper-slide {
|
||||
// width: 100%;
|
||||
// height: 400px; /* Adjust the height for desktop */
|
||||
// }
|
||||
/* Desktop Styles */
|
||||
// @media (min-width: 1024px) {
|
||||
// .custom-swiper-hero .swiper-slide {
|
||||
// width: 100%;
|
||||
// height: 400px; /* Adjust the height for desktop */
|
||||
// }
|
||||
|
||||
// .custom-swiper-hero .swiper-slide-active {
|
||||
// width: 100% !important;
|
||||
// height: 400px !important;
|
||||
// }
|
||||
// }
|
||||
// .custom-swiper-hero .swiper-slide-active {
|
||||
// width: 100% !important;
|
||||
// height: 400px !important;
|
||||
// }
|
||||
// }
|
||||
|
||||
`}</style>
|
||||
</div>
|
||||
<Suspense fallback={<SectionFallback height={320} />}>
|
||||
<Profilecardemo profiles={dashboardData?.daily_recommended} />
|
||||
<DailyRecommendedCard profiles={dashboardData?.daily_recommended} />
|
||||
</Suspense>
|
||||
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
<Suspense fallback={<SectionFallback height={220} />}>
|
||||
<ProfileCompletion
|
||||
percentage={dashboardData?.profile_complete_percentage}
|
||||
@ -228,7 +228,8 @@ const UserDashboardHome = () => {
|
||||
becomePaidMember={dashboardData?.become_paid_member}
|
||||
/>
|
||||
</Suspense>
|
||||
{/* <DailyRecommendedCard/> */}
|
||||
|
||||
|
||||
|
||||
<Suspense fallback={<SectionFallback height={280} />}>
|
||||
<MatrimonyArticles articles={dashboardData?.blogs} />
|
||||
|
||||
@ -18,7 +18,8 @@ const initialState = {
|
||||
district: [],
|
||||
diet: "",
|
||||
family_type: [],
|
||||
filter_type: "all_matches",
|
||||
filter_type: "",
|
||||
|
||||
page: 1,
|
||||
isPaidMember: false,
|
||||
search: "",
|
||||
|
||||
@ -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],
|
||||
};
|
||||
|
||||
@ -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"],
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@ -55,8 +55,11 @@ const UserRoutes = () => {
|
||||
|
||||
<Route element={<ProfileLayout />}>
|
||||
<Route path="/chat" element={<ChatUI />} />
|
||||
<Route path="/chat/:chatId" element={<ChatUI />} />
|
||||
</Route>
|
||||
|
||||
|
||||
|
||||
<Route element={<ProfileLayout />}>
|
||||
<Route path="/horoscoper-generate" element={<HoroscopeGenerator />} />
|
||||
</Route>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user