"use client"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { useHabibWebReady } from "@/hooks/use-habib-web-ready"; import { FaCalendarDays, FaLocationDot, FaGraduationCap, FaBookOpen, FaBriefcase, FaStar, FaLock, } from "react-icons/fa6"; import { LuEye } from "react-icons/lu"; import { IoHeartOutline } from "react-icons/io5"; import AdvisorActionsCard from "@/components/Componentes/advisor-actions-card"; import MarriageAdvisorsOverlay, { useMarriageAdvisorsOverlay, } from "@/components/Componentes/marriage-advisors-overlay"; import ErrorToast from "@/components/Componentes/error-toast"; import MatchProfileOverlay, { useMatchProfileOverlay, } from "@/components/Componentes/match-profile-overlay"; import PageHeader from "@/components/Componentes/page-header"; import { PageBackground } from "@/components/Componentes/page-background"; import InformationSheet from "@/components/Componentes/information-sheet"; import SwipeButton from "@/components/Componentes/swipe-button"; import { LoadingSkeleton } from "@/components/Componentes/loading-skeleton"; import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import { DiscountWidget } from "@/components/Componentes/discount-widget"; import type { CheckDiscountResult } from "@/hooks/marriage/use-validate-discount"; import { useHabcoinPaymentMutation } from "@/hooks/marriage/use-habcoin-payment"; import { useHabcoinInventoryQuery } from "@/hooks/marriage/use-habcoin-inventory"; import { useRespondToMarriageCaseMutation } from "@/hooks/marriage/use-case-respond"; import type { MarriageField, MarriageFieldValue, MarriageMatchSummary, MarriagePhoneFieldValue, } from "@/hooks/marriage/types"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { useViewPaddings } from "@/hooks/use-view-paddings"; import { getSubmitPath } from "@/lib/get-submit-path"; import { buyHabibCoinPackages, isInFlutterWebView, } from "@/lib/webview-actions"; import { localizePath } from "@/translations/config"; import { useI18n } from "@/translations/provider"; const advisorAvatars = [ { id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, { id: "advisor-secondary", src: "/assets/images/Ellipse 370.png" }, { id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" }, ]; const fieldCandidateMatchers = { name: [ "name", "full_name", "fullname", "first_name", "last_name", "display_name", "q1_full_name", ], age: ["age", "date_of_birth", "birth_date", "dob", "birth_year"], currentCountry: [ "country_of_current_residence", "current_country", "residence_country", "country", "current_residence_country", ], currentCity: [ "city_state_of_current_residence", "city_of_current_residence", "current_city", "residence_city", "city", "city_state", "state_city", "state", "province", ], residence: [ "current_residence", "residence", "current_country_city", "location", "current_city", "residence_city", "city", "country", "current_country", ], educationLevel: [ "highest_level_of_education", "highest_education_level", "education_level", "education", ], fieldOfStudy: [ "field_of_study", "study_field", "study", ], jobTitle: [ "job_title", "job_title_and_description", "job_position", "employment_status", "job", "occupation", "profession", "career", ], hobbies: [ "your_hobbies_and_main_interests", "hobbies_and_interests", "hobbies", "interests", ], } as const; type DisplayField = { id: string; label: string; value: string; }; function normalizeFieldName(value: string) { return value .toLowerCase() .replace(/^q\d+[_-]?/, "") .replace(/[^a-z0-9]/g, ""); } function matchesCandidate( field: MarriageField, candidates: readonly string[], ): boolean { const rawKey = (field.key || "").toLowerCase(); const keyParts = rawKey.split("."); const suffix = keyParts[keyParts.length - 1]; const normalizedKey = rawKey.replace(/[^a-z0-9]/g, ""); const normalizedSuffix = suffix.replace(/[^a-z0-9]/g, ""); for (const c of candidates) { const normC = c.toLowerCase().replace(/[^a-z0-9]/g, ""); if ( normalizedSuffix === normC || normalizedKey.endsWith(normC) || rawKey === c.toLowerCase() || suffix === c.toLowerCase() ) { return true; } } return false; } function calculateAgeFromDob(dobString: string): number | null { if (!dobString) return null; const match = dobString.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); if (!match) return null; const year = parseInt(match[1], 10); const month = parseInt(match[2], 10) - 1; const day = parseInt(match[3], 10); const birthDate = new Date(year, month, day); if (isNaN(birthDate.getTime())) return null; const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); const m = today.getMonth() - birthDate.getMonth(); if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) { age--; } return age > 0 && age < 120 ? age : null; } import { formatFieldLabel, formatFieldValue, formatOptionValue, isMarriagePhoneFieldValue, titleFromKey, } from "@/lib/marriage-field-formatter"; function toDisplayField( field: MarriageField, dictionary?: Record, ): DisplayField | null { const value = formatOptionValue(field.value, dictionary); if (!value) { return null; } return { id: field.key || field.label || value, label: formatFieldLabel(field, dictionary), value, }; } function pickField( fields: MarriageField[], candidates: readonly string[], usedIndexes: Set, dictionary?: Record, ): DisplayField | null { for (const [fieldIndex, field] of fields.entries()) { if (usedIndexes.has(fieldIndex)) { continue; } if (matchesCandidate(field, candidates)) { const displayField = toDisplayField(field, dictionary); if (displayField) { usedIndexes.add(fieldIndex); return displayField; } } } return null; } function useMatchSummaryDisplay( matchSummary: MarriageMatchSummary | null, t?: Record, ) { return useMemo(() => { const fields = matchSummary?.public_info ?? []; const usedIndexes = new Set(); // 1. Name: Show only first_name (do not show last_name) let displayName: string | null = null; const firstNameIdx = fields.findIndex( (f) => f.key === "personal_identity.first_name" || f.key?.endsWith(".first_name") || f.key === "first_name", ); const lastNameIdx = fields.findIndex( (f) => f.key === "personal_identity.last_name" || f.key?.endsWith(".last_name") || f.key === "last_name", ); if (lastNameIdx !== -1) { usedIndexes.add(lastNameIdx); } if (firstNameIdx !== -1 && fields[firstNameIdx].value) { usedIndexes.add(firstNameIdx); displayName = formatFieldValue(fields[firstNameIdx].value); } else { const nameField = pickField( fields, fieldCandidateMatchers.name, usedIndexes, t, ); if (nameField) { const rawName = String(nameField.value).trim(); displayName = rawName.split(/\s+/)[0] || rawName; } } if (!displayName && matchSummary?.id) { displayName = `Profile #${matchSummary.id}`; } // 2. Age (extract and calculate from date_of_birth if available) const dobIdx = fields.findIndex( (f) => f.key === "personal_identity.date_of_birth" || f.key?.endsWith(".date_of_birth") || f.key?.toLowerCase().includes("date_of_birth") || f.key?.toLowerCase().includes("birth_date"), ); let age: DisplayField | null = null; if (dobIdx !== -1 && fields[dobIdx].value) { usedIndexes.add(dobIdx); const calculatedAge = calculateAgeFromDob(String(fields[dobIdx].value)); if (calculatedAge) { age = { id: fields[dobIdx].key, label: t ? t["Age"] || "Age" : "Age", value: `${calculatedAge}`, }; } } if (!age) { age = pickField(fields, fieldCandidateMatchers.age, usedIndexes, t); } // 3. Country of Current Residence let currentCountry = pickField( fields, fieldCandidateMatchers.currentCountry, usedIndexes, t, ); // 4. City / State of Current Residence let currentCity = pickField( fields, fieldCandidateMatchers.currentCity, usedIndexes, t, ); // If either country or city was not matched as a standalone field, check composite residence if (!currentCountry || !currentCity) { const residenceIdx = fields.findIndex( (f, idx) => !usedIndexes.has(idx) && matchesCandidate(f, fieldCandidateMatchers.residence), ); if (residenceIdx !== -1) { const residenceField = fields[residenceIdx]; if ( typeof residenceField.value === "object" && residenceField.value !== null && !Array.isArray(residenceField.value) ) { const valObj = residenceField.value as { country?: string; city?: string; state?: string; }; if (valObj.country && !currentCountry) { currentCountry = { id: `${residenceField.key}.country`, label: (t && (t["Current Country of Residence"] || t["Country of Residence"] || t["Country"])) || "Country of Residence", value: formatOptionValue(valObj.country, t) || valObj.country, }; } const cityVal = [valObj.city, valObj.state].filter(Boolean).join(", "); if (cityVal && !currentCity) { currentCity = { id: `${residenceField.key}.city`, label: (t && (t["Current City / State of Residence"] || t["City / State of Residence"] || t["City"])) || "City / State of Residence", value: formatOptionValue(cityVal, t) || cityVal, }; } usedIndexes.add(residenceIdx); } else if (!currentCountry && !currentCity) { const disp = toDisplayField(residenceField, t); if (disp) { usedIndexes.add(residenceIdx); currentCountry = disp; } } } } // 5. Highest level of education const educationLevel = pickField( fields, fieldCandidateMatchers.educationLevel, usedIndexes, t, ); // 6. Field of study const fieldOfStudy = pickField( fields, fieldCandidateMatchers.fieldOfStudy, usedIndexes, t, ); // 7. Job Title const jobTitle = pickField( fields, fieldCandidateMatchers.jobTitle, usedIndexes, t, ); // 8. Hobbies & Main Interests const hobbies = pickField( fields, fieldCandidateMatchers.hobbies, usedIndexes, t, ); return { name: displayName, age, currentCountry, currentCity, educationLevel, fieldOfStudy, jobTitle, hobbies, }; }, [matchSummary, t]); } function ProfileInfoItem({ icon, field, }: { icon: React.ReactNode; field: DisplayField; }) { return (
{icon}
{field.label} {field.value}
); } export default function NewMatchClient() { const router = useRouter(); const { dictionary: t, locale } = useI18n(); const { top, bottom } = useViewPaddings(); const { isAdvisorOpen, openAdvisors, closeAdvisors } = useMarriageAdvisorsOverlay(); const { isProfileOpen, openProfile, closeProfile } = useMatchProfileOverlay(); const { data: profile, isError, isLoading } = useMarriageProfileQuery(); const [isPaymentSheetOpen, setIsPaymentSheetOpen] = useState(false); const [isDeclineConfirmOpen, setIsDeclineConfirmOpen] = useState(false); const [paymentError, setPaymentError] = useState(null); const [isInsufficientCoins, setIsInsufficientCoins] = useState(false); const [showPaymentSuccessToast, setShowPaymentSuccessToast] = useState(false); const [appliedDiscount, setAppliedDiscount] = useState(null); const { data: inventory, isLoading: isInventoryLoading } = useHabcoinInventoryQuery({ enabled: isPaymentSheetOpen, }); const planPrice = Number(profile?.recommended_plan?.price) || 50; const finalPrice = appliedDiscount?.valid ? appliedDiscount.discountedPrice : planPrice; const coinBalance = inventory?.coin_balance ?? 0; const hasEnoughCoins = !isInsufficientCoins && (inventory === undefined ? true : coinBalance >= finalPrice); const paymentMutation = useHabcoinPaymentMutation(); const caseId = profile?.active_case?.case_id; const respondMutation = useRespondToMarriageCaseMutation(caseId ?? "", { onSuccess: () => { router.replace(localizePath("/finding-match", locale)); }, }); const handlePayment = async () => { const recommendedPlanId = profile?.recommended_plan?.id; if (!recommendedPlanId) return; try { setPaymentError(null); setIsInsufficientCoins(false); await paymentMutation.mutateAsync({ objectId: recommendedPlanId, discountCode: appliedDiscount?.valid ? appliedDiscount.code : undefined, }); setIsPaymentSheetOpen(false); setShowPaymentSuccessToast(true); openProfile(); } catch (err: any) { console.error("Payment failed", err); const msg = err?.response?.data?.error || err?.message || "Payment failed"; const modalT = (t as any).paymentModal || {}; if (msg === "Not enough coins") { setIsInsufficientCoins(true); setPaymentError( modalT.insufficientCoins || "Insufficient coin balance. Please recharge your account.", ); } else { setPaymentError(msg); } } }; const handleDecline = async () => { if (!caseId) return; try { await respondMutation.mutateAsync({ action: "reject" }); } catch (err) { console.error("Decline failed", err); } }; const openDeclineConfirm = () => { setIsPaymentSheetOpen(false); setIsDeclineConfirmOpen(true); }; const cancelDeclineConfirm = () => { setIsDeclineConfirmOpen(false); setIsPaymentSheetOpen(true); }; useEffect(() => { console.log("🔍 [NewMatchClient] Current React Query Profile State:", { isLoading, isError, hasProfile: Boolean(profile), profileId: profile?.id, status: profile?.status, active_case: profile?.active_case, hasMatchSummary: Boolean(profile?.match_summary), matchSummaryId: profile?.match_summary?.id, publicInfoCount: profile?.match_summary?.public_info?.length, fullProfileObject: profile, }); if (!profile) { return; } const targetPath = getSubmitPath(profile); if (targetPath !== "/new-match") { router.replace(localizePath(targetPath, locale)); } }, [profile, locale, router, isLoading, isError]); // Signal Flutter to lift its loading cover once the profile is available. useHabibWebReady(!!profile && !isLoading); const isRedirecting = useMemo(() => { if (!profile) return false; return getSubmitPath(profile) !== "/new-match"; }, [profile]); const matchSummary = profile?.match_summary ?? null; const matchDisplay = useMatchSummaryDisplay(matchSummary, t); if (typeof window !== "undefined") { if (!matchSummary && !isLoading && !isRedirecting) { console.warn( "⚠️ [NewMatchClient] match_summary is null on profile! 'No match summary is available yet.' will be displayed. Profile:", profile, ); } else if (matchSummary) { console.log( "✅ [NewMatchClient] Rendering match summary card with:", matchDisplay, ); } } if (isLoading || isRedirecting) { return ( <>
{/* 1. Header Section Skeleton */}
{/* 2. Match Card Skeleton */}
{/* Name line */} {/* Subtitle / Details lines */}
{/* Button skeleton */}
{/* 3. Advisor Card Skeleton */}
); } const isFemaleProfile = profile?.gender === "female"; const matchHeadingTitle = isFemaleProfile ? t["New Marriage Proposal"] : t["YOU HAVE A NEW MATCH!"]; const matchHeadingDescription = isFemaleProfile ? t[ "A suitable match has been found for you. If approved, your profile will be evaluated to proceed with the introduction process." ] : t[ "If you proceed, we will notify the other party, and upon their approval, you can view each other's contact information." ]; const isMale = profile?.gender === "male"; const hasActiveSub = !!profile?.active_subscription && profile.active_subscription.is_active !== false && profile.active_subscription.is_valid !== false; const isMatchAvailable = !!profile?.match_summary; return ( <>
{/* 1. Header Section */}

{matchHeadingTitle}

{matchHeadingDescription}

{/* 2. Match Summary Card Section */}
{isLoading ? (
{[1, 2, 3, 4, 5, 6, 7].map((i) => (
))}
) : (
{isError ? (

Unable to load match summary.

) : matchSummary ? ( <> {/* Top Curved Banner */}
{/* Bottom Curve */}
{/* Circular Overlapping Avatar */}
candidate avatar
{/* Candidate Name & Heart Divider */}

{matchDisplay.name}

{/* Info Items List */}
{matchDisplay.age && ( } /> )} {matchDisplay.currentCountry && ( } /> )} {matchDisplay.currentCity && ( } /> )} {matchDisplay.educationLevel && ( } /> )} {matchDisplay.fieldOfStudy && ( } /> )} {matchDisplay.jobTitle && ( } /> )} {matchDisplay.hobbies && ( } /> )} {/* Button */}
) : (

No match summary is available yet.

)}
)}
{/* 3. Advisor Section */}
{profile?.can_edit_profile === false && (
)} {isPaymentSheetOpen && isInventoryLoading && ( 0 ? `${14 + bottom}px` : undefined, }} onClose={() => { setIsPaymentSheetOpen(false); setPaymentError(null); setIsInsufficientCoins(false); }} /> )} {isPaymentSheetOpen && !isInventoryLoading && ( 0 ? `${14 + bottom}px` : undefined, }} icon="coin" title={ !hasEnoughCoins ? ( {t["You do not have enough Habib Coins"] || "You do not have enough Habib Coins"} ) : ( t["Verification & Subscription Activation"] || "Verification & Subscription Activation" ) } description={ !hasEnoughCoins ? (

{t[ "Please add more Habib Coins to your balance (or use discount code), so that you can use them to access this content" ] || "Please add more Habib Coins to your balance (or use discount code), so that you can use them to access this content"}

) : (

{t[ "This payment acts as a verification process for your account and activates a 3-month subscription to receive new case introductions. The cost of this subscription is 50 Habib Coins." ] || "This payment acts as a verification process for your account and activates a 3-month subscription to receive new case introductions. The cost of this subscription is 50 Habib Coins."}

{t["Valid for 3 months"] || "Valid for 3 months"} {finalPrice} {t["Habib Coins"] || "Habib Coins"}

{t[ "Please note that there is no guarantee for a specific number of cases, and the volume of incoming cases is solely subject to profile compatibility with other users." ] || "Please note that there is no guarantee for a specific number of cases, and the volume of incoming cases is solely subject to profile compatibility with other users."}

) } onClose={() => { setIsPaymentSheetOpen(false); setPaymentError(null); setIsInsufficientCoins(false); setAppliedDiscount(null); }} buttons={
{profile?.recommended_plan?.id ? ( setAppliedDiscount(res)} onDiscountCleared={() => setAppliedDiscount(null)} /> ) : null} {paymentError && (
{paymentError}
)}
{!hasEnoughCoins ? ( ) : ( )}
} /> )} {isDeclineConfirmOpen && (

{t[ "Are you sure you've fully reviewed the profile and want to reject this profile?" ] || (locale === "fa" ? "آیا از رد این پیشنهاد مطمئن هستید؟ در صورت رد، این مورد دیگر در دسترس نخواهد بود." : "Are you sure you want to decline this proposal? Once declined, this match will no longer be available.")}

} buttons={({ close }) => ( { close(); cancelDeclineConfirm(); }} onSuccess={async () => { await handleDecline(); close(); setIsDeclineConfirmOpen(false); setIsPaymentSheetOpen(false); }} /> )} onClose={cancelDeclineConfirm} /> )} {showPaymentSuccessToast && ( setShowPaymentSuccessToast(false)} /> )} ); }