Refine profile interaction logic, synchronize UI components, and update API request formatting to use FormData

This commit is contained in:
MAGESHWARAN 2026-04-29 10:56:58 +05:30
parent b3d33aca9c
commit 30bea864e5
7 changed files with 1114 additions and 305 deletions

View File

@ -26,6 +26,7 @@ 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 [isLimitModalOpen, setIsLimitModalOpen] = useState(false);
const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
const [isViewContactModalOpen, setIsViewContactModalOpen] = useState(false);
const [isInterestStatusModalOpen, setIsInterestStatusModalOpen] = useState(false);
@ -60,14 +61,19 @@ export default function ProfileCardUI({ profile }) {
return;
}
try {
await axiosInstance.post(`${API_ENDPOINTS.INTEREST_SEND}?profile_id=${profile.id}`);
await sendInterest(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 msg = error.message || "";
if (msg.toLowerCase().includes("limit") || msg.toLowerCase().includes("reached")) {
setIsLimitModalOpen(true);
} else {
toast.error(msg || "Failed to send interest");
}
}
};
@ -227,8 +233,8 @@ export default function ProfileCardUI({ profile }) {
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") {
const res = await shortlistProfile(profile.id);
if (res.status === "success") {
setIsShortlisted(!isShortlisted);
toast.success(res.data.message || "Updated shortlist status");
}
@ -401,10 +407,12 @@ export default function ProfileCardUI({ profile }) {
</AnimatePresence>
<LimitReachedModal
isOpen={isLimitModalOpen}
onClose={() => setIsLimitModalOpen(false)}
/>
<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"
>
@ -572,13 +580,41 @@ export default function ProfileCardUI({ profile }) {
</>
)}
</div>
</div>
</div>
</>
);
}
const LimitReachedModal = ({ isOpen, onClose }) => {
if (!isOpen) return null;
return (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-[10000] p-4">
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className="bg-white rounded-3xl p-8 max-w-sm w-full text-center shadow-2xl relative"
>
<button
onClick={onClose}
className="absolute top-4 right-4 text-gray-400 hover:text-gray-600"
>
<X className="w-6 h-6" />
</button>
<div className="w-20 h-20 bg-amber-50 rounded-full flex items-center justify-center mx-auto mb-6">
<Clock className="w-10 h-10 text-amber-600" />
</div>
<h3 className="text-2xl font-bold text-gray-900 mb-3">Limit Reached!</h3>
<p className="text-gray-600 mb-8 leading-relaxed">
You've reached your daily limit for sending interests. Please try again tomorrow or upgrade your plan for more daily connections!
</p>
<button
onClick={onClose}
className="w-full py-4 bg-[#034E08] text-white rounded-2xl font-bold hover:bg-green-800 transition-colors"
>
Got it
</button>
</motion.div>
</div>
);
};

View File

@ -439,14 +439,13 @@ const ProfileHeader = () => {
return (
<>
<AppBar position="sticky" sx={{ backgroundColor: "#fff" }}>
<AppBar position="sticky" sx={{ backgroundColor: "#fff", zIndex: 1000 }}>
<Container maxWidth="xl">
<Toolbar disableGutters>
<Box onClick={() => navigate("/")} sx={{ display: { xs: "none", md: "flex" }, mr: 1 }}>
<LazyImage
src={Logo}
className="w-full h-[70px] my-2 rounded-lg object-cover cursor-pointer"
className="h-[50px] w-auto my-1 rounded-lg object-contain cursor-pointer"
/>
</Box>
@ -462,8 +461,7 @@ const ProfileHeader = () => {
>
<LazyImage
src={Logo}
className="w-full h-[50px] rounded-lg object-cover"
className="h-[40px] w-auto rounded-lg object-contain"
/>
</Box>

View File

@ -7,6 +7,7 @@ import { useNavigate } from 'react-router-dom';
import { toast } from 'react-hot-toast';
import axiosInstance from "../../api/axiosInstance";
import { API_ENDPOINTS } from "../../api/apiEndpoints";
import { sendInterest, shortlistProfile } from '../../services/shortlistapi';
import UpgradeModal from '../common/UpgradeModal';
// Custom Icons
@ -137,14 +138,19 @@ const DailyRecommendedCard = ({ profiles: initialProfiles = [] }) => {
return;
}
try {
await axiosInstance.post(`${API_ENDPOINTS.INTEREST_SEND}?profile_id=${profileId}`);
await sendInterest(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 msg = error.message || "";
if (msg.toLowerCase().includes("limit") || msg.toLowerCase().includes("reached")) {
setIsUpgradeModalOpen(true);
} else {
toast.error(msg || "Failed to send interest. Please try again.");
}
}
};
@ -173,8 +179,8 @@ const DailyRecommendedCard = ({ profiles: initialProfiles = [] }) => {
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") {
const res = await shortlistProfile(profile.id);
if (res.status === "success") {
setIsShortlisted(!isShortlisted);
toast.success(res.data.message || "Updated shortlist status");
}

File diff suppressed because it is too large Load Diff

View File

@ -81,26 +81,14 @@ const PartnerPreferences = ({ data }) => {
</div>
</div>
<div className='grid grid-cols-1 gap-2 md:grid-cols-2 mb-8 pt-4'>
{/* Basic Preferences Section */}
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
<div className="flex items-center justify-between mb-4 bg-[#f5fbff] pt-4 pb-4 px-6">
<h2 className="text-lg font-bold text-gray-800">Basic Preferences</h2>
</div>
<div className="space-y-1 p-6">
{basicPreferences.map((pref, index) => (
<PreferenceItem key={index} {...pref} />
))}
</div>
</div>
{/* Other Preferences Section */}
<div className="bg-white rounded-2xl shadow-lg overflow-hidden">
<div className="flex items-center justify-between mb-4 bg-[#f5fbff] pt-4 pb-4 px-6">
<h2 className="text-lg font-bold text-gray-800">Professional & Location</h2>
<div className='max-w-3xl mx-auto mb-8 pt-4'>
{/* Partner Preferences Section */}
<div className="bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-100">
<div className="flex items-center justify-between bg-[#f5fbff] py-4 px-6 border-b border-gray-100">
<h2 className="text-lg font-bold text-gray-800">Partner Preference Details</h2>
</div>
<div className="space-y-1 p-6">
{religiousPreferences.map((pref, index) => (
{[...basicPreferences, ...religiousPreferences].map((pref, index) => (
<PreferenceItem key={index} {...pref} />
))}
</div>

View File

@ -11,18 +11,19 @@ const ProfileDetailPage = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const fetchDetail = async (showLoading = true) => {
if (showLoading) setLoading(true);
try {
const res = await getProfileDetail(id);
setData(res);
} catch (error) {
console.error("Failed to fetch profile details:", error);
} finally {
if (showLoading) setLoading(false);
}
};
useEffect(() => {
const fetchDetail = async () => {
setLoading(true);
try {
const res = await getProfileDetail(id);
setData(res);
} catch (error) {
console.error("Failed to fetch profile details:", error);
} finally {
setLoading(false);
}
};
if (id) {
fetchDetail();
}
@ -47,7 +48,7 @@ const ProfileDetailPage = () => {
return (
<>
<div className="w-[100%] max-w-[1400px] mx-auto my-10">
<MatrimonyProfile data={data} />
<MatrimonyProfile data={data} onRefresh={fetchDetail} />
<PartnerPreferences data={data} />
<MatchingList matches={data.all_matches} />
</div>

View File

@ -1,22 +1,38 @@
import axiosInstance from "../api/axiosInstance";
import axiosInstance, { apiForFiles } from "../api/axiosInstance";
import { API_ENDPOINTS } from "../api/apiEndpoints";
export const shortlistProfile = async (profileId) => {
const response = await axiosInstance.post(`${API_ENDPOINTS.SHORTLIST_API}?profile_id=${profileId}`);
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to shortlist");
try {
const formData = new FormData();
formData.append("profile_id", profileId);
const response = await apiForFiles.post(API_ENDPOINTS.SHORTLIST_API, formData);
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to shortlist");
}
return response.data;
} catch (error) {
const message = error.response?.data?.message || error.message || "Failed to shortlist";
throw new Error(message);
}
return response.data;
};
export const sendInterest = async (profileId) => {
const response = await axiosInstance.post(`interest_send`, {
profile_id: profileId // ✅ sent in request body
});
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to send interest");
try {
const formData = new FormData();
formData.append("profile_id", profileId);
// Using apiForFiles which is pre-configured for multipart/form-data
// This is more robust for PHP backends expecting form data
const response = await apiForFiles.post(API_ENDPOINTS.INTEREST_SEND, formData);
if (response.data?.status === "error") {
throw new Error(response.data.message || "Failed to send interest");
}
return response.data;
} catch (error) {
const message = error.response?.data?.message || error.message || "Failed to send interest";
throw new Error(message);
}
return response.data;
};
export const declineProfile = async (profileId) => {