You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
967 lines
38 KiB
967 lines
38 KiB
"use client";
|
|
|
|
import Image from "next/image";
|
|
import { useRouter } from "next/navigation";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import { useHabibWebReady } from "@/hooks/use-habib-web-ready";
|
|
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 CallResultSheet from "@/components/Componentes/call-result-sheet";
|
|
import DismissReasonSheet from "@/components/Componentes/dismiss-reason-sheet";
|
|
import FemaleConsentSheet from "@/components/Componentes/female-consent-sheet";
|
|
import FemaleOutcomeSheet from "@/components/Componentes/female-outcome-sheet";
|
|
import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot";
|
|
import OutcomeSelectionSheet from "@/components/Componentes/outcome-selection-sheet";
|
|
import { PageBackground } from "@/components/Componentes/page-background";
|
|
import PageHeader from "@/components/Componentes/page-header";
|
|
import SubscriptionRequiredSheet from "@/components/Componentes/subscription-required-sheet";
|
|
import SwipeButton from "@/components/Componentes/swipe-button";
|
|
import { LoadingBorderSpinner } from "@/components/ui/loading-border-spinner";
|
|
import type {
|
|
MarriageField,
|
|
MarriagePhoneFieldValue,
|
|
} from "@/hooks/marriage/types";
|
|
import { useMarriageContactInfoQuery } from "@/hooks/marriage/use-contact-info";
|
|
import {
|
|
useSubmitMarriageContactStatusMutation,
|
|
useSubmitMarriageOutcomeMutation,
|
|
} from "@/hooks/marriage/use-contact-status";
|
|
import {
|
|
extractHabcoinPaymentUrl,
|
|
useHabcoinPaymentMutation,
|
|
} from "@/hooks/marriage/use-habcoin-payment";
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
|
|
import { getSubmitPath } from "@/lib/get-submit-path";
|
|
import { copyToClipboard } from "@/lib/webview-actions";
|
|
import { Ic } from "@/icons";
|
|
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/Avatar Image.png" },
|
|
{ id: "advisor-tertiary", src: "/assets/images/Avatar Image.png" },
|
|
];
|
|
|
|
type ContactInfoPhoneItem = {
|
|
key: string;
|
|
label: string;
|
|
phoneNumber: string;
|
|
};
|
|
|
|
function sanitizePhoneNumber(value: MarriageField["value"]) {
|
|
if (value === null || value === "") {
|
|
return null;
|
|
}
|
|
|
|
if (isMarriagePhoneFieldValue(value)) {
|
|
const digits = value.phoneNumber.replace(/\D/g, "");
|
|
|
|
return digits ? `+${value.countryCode}${digits}` : null;
|
|
}
|
|
|
|
const trimmedValue = String(value).trim();
|
|
|
|
if (!trimmedValue) {
|
|
return null;
|
|
}
|
|
|
|
const digits = trimmedValue.replace(/\D/g, "");
|
|
|
|
if (!digits) {
|
|
return null;
|
|
}
|
|
|
|
return trimmedValue.startsWith("+") ? `+${digits}` : digits;
|
|
}
|
|
|
|
function isMarriagePhoneFieldValue(
|
|
value: unknown,
|
|
): value is MarriagePhoneFieldValue {
|
|
if (!value || typeof value !== "object") {
|
|
return false;
|
|
}
|
|
|
|
const phoneValue = value as Partial<MarriagePhoneFieldValue>;
|
|
|
|
return (
|
|
typeof phoneValue.countryCode === "string" &&
|
|
typeof phoneValue.phoneNumber === "string"
|
|
);
|
|
}
|
|
|
|
function getContactInfoPhoneItems(
|
|
contactInfoFields: any[] | null | undefined,
|
|
): ContactInfoPhoneItem[] {
|
|
if (!contactInfoFields || !Array.isArray(contactInfoFields)) {
|
|
return [];
|
|
}
|
|
|
|
// Look for contextual representative info if available
|
|
const repNameField = contactInfoFields.find(
|
|
(f) =>
|
|
typeof f?.key === "string" &&
|
|
f.key.toLowerCase().includes("representative_s_full_name") &&
|
|
f.value,
|
|
);
|
|
const repRelationField = contactInfoFields.find(
|
|
(f) =>
|
|
typeof f?.key === "string" &&
|
|
f.key.toLowerCase().includes("relationship_to_representative") &&
|
|
f.value,
|
|
);
|
|
|
|
const repName = repNameField?.value ? String(repNameField.value).trim() : "";
|
|
const repRelation = repRelationField?.value
|
|
? String(repRelationField.value).trim()
|
|
: "";
|
|
|
|
return contactInfoFields
|
|
.map((field) => {
|
|
if (!field) return null;
|
|
|
|
// Extract phone number from field properties
|
|
let phoneNumber: string | null = null;
|
|
|
|
if (field.phone_number && typeof field.phone_number === "string") {
|
|
phoneNumber = field.phone_number.trim();
|
|
} else if (field.raw_number && typeof field.raw_number === "string") {
|
|
const countryCode = field.country_code
|
|
? String(field.country_code).replace(/\D/g, "")
|
|
: "98";
|
|
phoneNumber = `+${countryCode} ${field.raw_number.trim()}`;
|
|
} else if (field.raw_value && typeof field.raw_value === "object") {
|
|
phoneNumber = sanitizePhoneNumber(field.raw_value);
|
|
} else if (field.value) {
|
|
phoneNumber = sanitizePhoneNumber(field.value);
|
|
}
|
|
|
|
// If this field is not a phone field, skip it
|
|
if (
|
|
!phoneNumber ||
|
|
(field.type &&
|
|
field.type !== "phone" &&
|
|
!field.phone_number &&
|
|
!field.raw_number)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const key = String(field.key || "");
|
|
const rawLabel = String(field.label || field.key || "");
|
|
let label = rawLabel
|
|
.replace(/\s+with\s+Country\s+Code/gi, "")
|
|
.replace(/\s+با\s+کد\s+کشور/g, "")
|
|
.trim();
|
|
|
|
// Add representative context to label if applicable
|
|
if (key.includes("representative")) {
|
|
const contextParts = [repRelation, repName].filter(Boolean);
|
|
if (contextParts.length > 0 && !label.includes(contextParts[0])) {
|
|
label = `${label} (${contextParts.join(" - ")})`;
|
|
}
|
|
}
|
|
|
|
return {
|
|
key: key || phoneNumber,
|
|
label,
|
|
phoneNumber,
|
|
};
|
|
})
|
|
.filter((item): item is ContactInfoPhoneItem => item !== null);
|
|
}
|
|
|
|
function ContactInfoPhoneCard({ item }: { item: ContactInfoPhoneItem }) {
|
|
const [isCopied, setIsCopied] = useState(false);
|
|
|
|
const handleCopy = async () => {
|
|
try {
|
|
await copyToClipboard(item.phoneNumber, item.label);
|
|
setIsCopied(true);
|
|
setTimeout(() => {
|
|
setIsCopied(false);
|
|
}, 1500);
|
|
} catch (err) {
|
|
console.error("Error copying contact to clipboard", err);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="flex items-center justify-between rounded-[20px] bg-[#FFF5F6] border border-[#FFE4E8] px-5 py-4 shadow-sm transition-all hover:border-[#FFD0D8]">
|
|
<div className="text-start min-w-0 flex-1 pe-3">
|
|
<p className="text-[19px] font-black text-[#1F2937] tracking-wider dir-ltr text-start select-all leading-tight">
|
|
{item.phoneNumber}
|
|
</p>
|
|
<p className="mt-1 text-[13px] font-semibold text-[#F0445B] text-start truncate leading-snug">
|
|
{item.label}
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={handleCopy}
|
|
title="Copy"
|
|
aria-label="Copy phone number"
|
|
className="size-[44px] rounded-[14px] bg-[linear-gradient(180deg,#F0445B_0%,#F54B64_100%)] text-white flex items-center justify-center shrink-0 shadow-[0_4px_12px_rgba(240,68,91,0.25)] active:scale-[0.90] transition-all duration-150 cursor-pointer hover:opacity-95"
|
|
>
|
|
{isCopied ? (
|
|
<Ic name="check" className="size-5 text-white transition-transform scale-110" />
|
|
) : (
|
|
<Ic name="copy" className="size-5 text-white" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default function RequestAcceptedClient() {
|
|
const { dictionary: t, locale } = useI18n();
|
|
const router = useRouter();
|
|
const { isAdvisorOpen, openAdvisors, closeAdvisors } =
|
|
useMarriageAdvisorsOverlay();
|
|
const { isProfileOpen, openProfile, closeProfile } =
|
|
useMatchProfileOverlay();
|
|
const [isCallResultSheetOpen, setIsCallResultSheetOpen] = useState(false);
|
|
const [isDismissReasonSheetOpen, setIsDismissReasonSheetOpen] =
|
|
useState(false);
|
|
const [isContactInfoSheetOpen, setIsContactInfoSheetOpen] = useState(false);
|
|
const [isSubscriptionSheetOpen, setIsSubscriptionSheetOpen] = useState(false);
|
|
const [paymentError, setPaymentError] = useState<string | null>(null);
|
|
const [isInsufficientCoins, setIsInsufficientCoins] = useState(false);
|
|
const [isOutcomeSheetOpen, setIsOutcomeSheetOpen] = useState(false);
|
|
const [isNoContactConfirmOpen, setIsNoContactConfirmOpen] = useState(false);
|
|
const [isContactReceivedConfirmOpen, setIsContactReceivedConfirmOpen] =
|
|
useState(false);
|
|
const [noContactReportedSuccess, setNoContactReportedSuccess] =
|
|
useState(false);
|
|
const [hasConfirmedFemaleContact, setHasConfirmedFemaleContact] =
|
|
useState(false);
|
|
const [showPaymentSuccessToast, setShowPaymentSuccessToast] = useState(false);
|
|
const [isOpeningProfile, setIsOpeningProfile] = useState(false);
|
|
const {
|
|
data: profile,
|
|
isLoading,
|
|
isFetched,
|
|
isFetching,
|
|
refetch: refetchProfile,
|
|
} = useMarriageProfileQuery();
|
|
|
|
const isFemaleProfile = profile?.gender === "female";
|
|
const contactSharedAtStr = profile?.active_case?.contact_shared_at;
|
|
|
|
useEffect(() => {
|
|
if (!profile || !isFetched || noContactReportedSuccess) {
|
|
return;
|
|
}
|
|
const targetPath = getSubmitPath(profile);
|
|
if (targetPath !== "/request-accepted") {
|
|
router.replace(localizePath(targetPath, locale));
|
|
}
|
|
}, [profile, isFetched, router, locale, noContactReportedSuccess]);
|
|
|
|
// Signal Flutter to lift its loading cover immediately on mount
|
|
useHabibWebReady(true);
|
|
|
|
const handleOpenProfile = async () => {
|
|
if (!isFetched || isFetching) {
|
|
setIsOpeningProfile(true);
|
|
try {
|
|
await refetchProfile();
|
|
} finally {
|
|
setIsOpeningProfile(false);
|
|
openProfile();
|
|
}
|
|
return;
|
|
}
|
|
openProfile();
|
|
};
|
|
|
|
const isRedirecting = useMemo(() => {
|
|
if (!profile) return false;
|
|
return getSubmitPath(profile) !== "/request-accepted";
|
|
}, [profile]);
|
|
|
|
const caseId = profile?.active_case?.case_id;
|
|
const caseStatus = profile?.active_case?.status;
|
|
const isFemaleContactConfirmed =
|
|
isFemaleProfile &&
|
|
(caseStatus === "contacted" || hasConfirmedFemaleContact);
|
|
const recommendedPlanId = profile?.recommended_plan?.id;
|
|
const paymentMutation = useHabcoinPaymentMutation();
|
|
const outcomeMutation = useSubmitMarriageOutcomeMutation(caseId ?? "");
|
|
const contactStatusMutation = useSubmitMarriageContactStatusMutation(
|
|
caseId ?? "",
|
|
{
|
|
onSuccess: (_data, variables) => {
|
|
if (variables?.action === "no_contact") {
|
|
setNoContactReportedSuccess(true);
|
|
} else {
|
|
if (!isFemaleProfile) {
|
|
router.push(localizePath("/finding-match", locale));
|
|
}
|
|
}
|
|
},
|
|
},
|
|
);
|
|
const [isFetchingContact, setIsFetchingContact] = useState(false);
|
|
const isMalePaymentDone =
|
|
!isFemaleProfile &&
|
|
(caseStatus === "payment_done" || caseStatus === "contacted");
|
|
|
|
const contactInfoQuery = useMarriageContactInfoQuery(caseId, {
|
|
enabled: false,
|
|
});
|
|
|
|
const titleText = isFemaleProfile
|
|
? noContactReportedSuccess
|
|
? t["Report Registered"] || "Report Registered"
|
|
: t["Request Approved"]
|
|
: caseStatus === "payment_done" || caseStatus === "contacted"
|
|
? t["Contact info released"]
|
|
: t["Request approved!"];
|
|
|
|
const primaryActionText = isFemaleProfile
|
|
? t["No Contact"] || t["No Contact Received"] || "No Contact"
|
|
: t["View all detail"] || "View all detail";
|
|
const secondaryActionText = isFemaleProfile
|
|
? t["Contact Received"]
|
|
: caseStatus === "payment_done" || caseStatus === "contacted"
|
|
? t["Contact"]
|
|
: t["Pay and get contact"];
|
|
const contactInfoPhoneItems = getContactInfoPhoneItems(
|
|
contactInfoQuery.data?.contact_info,
|
|
);
|
|
|
|
const handlePrimaryAction = () => {
|
|
if (isFemaleProfile) {
|
|
setIsDismissReasonSheetOpen(true);
|
|
} else {
|
|
openProfile();
|
|
}
|
|
};
|
|
|
|
const handleSecondaryAction = async () => {
|
|
if (isFemaleProfile) {
|
|
setIsContactReceivedConfirmOpen(true);
|
|
return;
|
|
}
|
|
|
|
if (caseStatus === "female_accepted" || caseStatus === "payment_pending") {
|
|
setIsSubscriptionSheetOpen(true);
|
|
return;
|
|
}
|
|
|
|
if (caseStatus === "payment_done" || caseStatus === "contacted") {
|
|
if (!caseId) {
|
|
return;
|
|
}
|
|
|
|
if (!contactInfoQuery.data) {
|
|
try {
|
|
setIsFetchingContact(true);
|
|
await contactInfoQuery.refetch();
|
|
} finally {
|
|
setIsFetchingContact(false);
|
|
}
|
|
}
|
|
|
|
setIsContactInfoSheetOpen(true);
|
|
}
|
|
};
|
|
|
|
const handlePayment = async () => {
|
|
const planId = recommendedPlanId ?? 1;
|
|
if (paymentMutation.isPending) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setPaymentError(null);
|
|
setIsInsufficientCoins(false);
|
|
const paymentResponse =
|
|
await paymentMutation.mutateAsync(planId);
|
|
const paymentUrl = extractHabcoinPaymentUrl(paymentResponse);
|
|
|
|
if (paymentUrl) {
|
|
window.location.assign(paymentUrl);
|
|
return;
|
|
}
|
|
|
|
setIsSubscriptionSheetOpen(false);
|
|
setShowPaymentSuccessToast(true);
|
|
|
|
if (caseId) {
|
|
await contactInfoQuery.refetch();
|
|
setIsContactInfoSheetOpen(true);
|
|
}
|
|
} catch (err: any) {
|
|
console.error("Habcoin payment request failed", err);
|
|
const msg =
|
|
err?.response?.data?.error ||
|
|
err?.response?.data?.detail ||
|
|
err?.response?.data?.message ||
|
|
err?.message ||
|
|
"Payment failed";
|
|
if (msg === "Not enough coins" || msg?.includes?.("Not enough coins")) {
|
|
setIsInsufficientCoins(true);
|
|
setPaymentError(
|
|
t["Insufficient coin balance. Please recharge your account."] ||
|
|
"Insufficient coin balance. Please recharge your account.",
|
|
);
|
|
} else {
|
|
setPaymentError(msg);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleNoContactReport = async () => {
|
|
if (!caseId || contactStatusMutation.isPending) return;
|
|
await contactStatusMutation.mutateAsync({
|
|
action: "no_contact",
|
|
custom_note:
|
|
"No contact reported by female candidate after decision window",
|
|
});
|
|
};
|
|
|
|
const isFinalized =
|
|
caseStatus === "finalized" || profile?.status === "matched";
|
|
|
|
return (
|
|
<>
|
|
<PageBackground />
|
|
|
|
{isCallResultSheetOpen ? (
|
|
<CallResultSheet
|
|
onClose={() => setIsCallResultSheetOpen(false)}
|
|
onOtherReasonsClick={() => setIsDismissReasonSheetOpen(true)}
|
|
onSubmit={async (value) => {
|
|
if (caseId) {
|
|
await contactStatusMutation.mutateAsync({
|
|
action: "contacted",
|
|
custom_note: value,
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
) : null}
|
|
|
|
{isContactReceivedConfirmOpen ? (
|
|
<FemaleConsentSheet
|
|
title={t.Confirm}
|
|
description={
|
|
<p className="font-bold text-[#00AC78] text-center my-4 text-base">
|
|
{t["Are you sure contact has been made?"]}
|
|
</p>
|
|
}
|
|
buttons={
|
|
<SwipeButton
|
|
theme="green"
|
|
text={t["Confirm"]}
|
|
isSubmitting={contactStatusMutation.isPending}
|
|
disabled={contactStatusMutation.isPending}
|
|
onCancel={() => {
|
|
if (!contactStatusMutation.isPending) {
|
|
setIsContactReceivedConfirmOpen(false);
|
|
}
|
|
}}
|
|
onSuccess={async () => {
|
|
if (!caseId) {
|
|
setIsContactReceivedConfirmOpen(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await contactStatusMutation.mutateAsync({
|
|
action: "contacted",
|
|
custom_note:
|
|
"Contact received confirmed by female candidate",
|
|
});
|
|
setHasConfirmedFemaleContact(true);
|
|
setIsContactReceivedConfirmOpen(false);
|
|
} catch (error) {
|
|
console.error("Unable to persist received contact", error);
|
|
}
|
|
}}
|
|
/>
|
|
}
|
|
onClose={() => {
|
|
if (!contactStatusMutation.isPending) {
|
|
setIsContactReceivedConfirmOpen(false);
|
|
}
|
|
}}
|
|
closeOnOutside={!contactStatusMutation.isPending}
|
|
/>
|
|
) : null}
|
|
|
|
{isDismissReasonSheetOpen ? (
|
|
<DismissReasonSheet
|
|
onClose={() => setIsDismissReasonSheetOpen(false)}
|
|
onSubmit={async (value) => {
|
|
if (caseId) {
|
|
await outcomeMutation.mutateAsync({
|
|
status: "failure",
|
|
custom_note: value,
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
) : null}
|
|
|
|
{isOutcomeSheetOpen ? (
|
|
isFemaleProfile ? (
|
|
<FemaleOutcomeSheet
|
|
onClose={() => setIsOutcomeSheetOpen(false)}
|
|
onSubmit={async (status, reason) => {
|
|
if (status === "success") {
|
|
// If they confirm they are in the acquaintance/proposal process and nothing is finalized yet:
|
|
// No change is made to the profile, we just close the sheet.
|
|
setIsOutcomeSheetOpen(false);
|
|
} else {
|
|
// If they cancel:
|
|
if (caseId) {
|
|
await outcomeMutation.mutateAsync({
|
|
status: "failure",
|
|
custom_note: reason,
|
|
});
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
) : (
|
|
<OutcomeSelectionSheet
|
|
onClose={() => setIsOutcomeSheetOpen(false)}
|
|
onSubmit={async (status) => {
|
|
if (status === "success") {
|
|
if (caseId) {
|
|
await outcomeMutation.mutateAsync({
|
|
status: "success",
|
|
});
|
|
}
|
|
} else {
|
|
setIsDismissReasonSheetOpen(true);
|
|
}
|
|
}}
|
|
/>
|
|
)
|
|
) : null}
|
|
|
|
{isContactInfoSheetOpen ? (
|
|
<FemaleConsentSheet
|
|
title={t["Contact Detail"]}
|
|
description={
|
|
t[
|
|
"Please mention during the call that you were introduced by the Habib Marriage app."
|
|
]
|
|
}
|
|
buttons={
|
|
contactInfoPhoneItems.length ? (
|
|
<div className="space-y-4">
|
|
{contactInfoPhoneItems.map((item) => (
|
|
<ContactInfoPhoneCard key={item.key} item={item} />
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="rounded-[12px] bg-[#ECECEC] px-4 py-3 group-12 font-semibold text-[#555]">
|
|
{t["Contact information is not available yet."]}
|
|
</div>
|
|
)
|
|
}
|
|
onClose={() => setIsContactInfoSheetOpen(false)}
|
|
/>
|
|
) : null}
|
|
|
|
{isSubscriptionSheetOpen ? (
|
|
<SubscriptionRequiredSheet
|
|
onClose={() => {
|
|
setIsSubscriptionSheetOpen(false);
|
|
setPaymentError(null);
|
|
setIsInsufficientCoins(false);
|
|
}}
|
|
planId={recommendedPlanId}
|
|
onPayment={handlePayment}
|
|
isPaymentPending={!recommendedPlanId || paymentMutation.isPending}
|
|
errorMessage={paymentError}
|
|
showBuyCoins={isInsufficientCoins}
|
|
/>
|
|
) : null}
|
|
|
|
{isNoContactConfirmOpen ? (
|
|
<FemaleConsentSheet
|
|
title={t.Confirm}
|
|
description={
|
|
<p className="font-bold text-[#E03950] text-center my-4 text-base">
|
|
{
|
|
t[
|
|
"No contact has been made with you in any way or by any party."
|
|
]
|
|
}
|
|
</p>
|
|
}
|
|
buttons={
|
|
<SwipeButton
|
|
theme="default"
|
|
text={t["Confirm"]}
|
|
isSubmitting={contactStatusMutation.isPending}
|
|
disabled={contactStatusMutation.isPending}
|
|
onCancel={() => {
|
|
if (!contactStatusMutation.isPending) {
|
|
setIsNoContactConfirmOpen(false);
|
|
}
|
|
}}
|
|
onSuccess={async () => {
|
|
try {
|
|
await handleNoContactReport();
|
|
setIsNoContactConfirmOpen(false);
|
|
} catch (err) {
|
|
console.error("Failed to report no contact", err);
|
|
}
|
|
}}
|
|
/>
|
|
}
|
|
onClose={() => {
|
|
if (!contactStatusMutation.isPending) {
|
|
setIsNoContactConfirmOpen(false);
|
|
}
|
|
}}
|
|
closeOnOutside={!contactStatusMutation.isPending}
|
|
/>
|
|
) : null}
|
|
|
|
<PageHeader profile={profile} sticky />
|
|
|
|
<main className="flex min-h-0 flex-1 flex-col pb-[calc(20px+var(--safe-bottom))] text-center">
|
|
<div className="flex flex-1 flex-col justify-between gap-6 pt-4">
|
|
<section className="flex flex-col items-center">
|
|
{isFinalized ? (
|
|
<div className="flex flex-col items-center max-w-[320px] text-center my-auto py-12">
|
|
<div className="relative flex items-center justify-center text-[70px] animate-bounce">
|
|
🎉
|
|
</div>
|
|
<h1 className="mt-8 text-center text-[24px] leading-[1.25] font-bold text-[#E03950]">
|
|
{t["Congratulations! 🎉"]}
|
|
</h1>
|
|
<p className="mt-6 text-[#4A4A4A] group-14 font-medium leading-relaxed">
|
|
{
|
|
t[
|
|
"Wishing you a lifetime of love, joy, and happiness. Your profile has been successfully closed."
|
|
]
|
|
}
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<>
|
|
{/* Illustration */}
|
|
<div className="relative mt-6 flex items-center justify-center">
|
|
{/* soft glow */}
|
|
<div className="absolute h-[115px] w-[115px] rounded-full bg-[#FF5C7D]/10 blur-xl" />
|
|
|
|
<div className="relative z-10">
|
|
<Image
|
|
src="/assets/images/Group 15978804fdasf68.svg"
|
|
alt={t["Request accepted"]}
|
|
width={131}
|
|
height={125}
|
|
priority
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<h1 className="mt-8 text-[20px] leading-none font-black tracking-[0.03em] text-[#171717] uppercase">
|
|
{titleText}
|
|
</h1>
|
|
|
|
{caseStatus === "contacted" ||
|
|
isFemaleContactConfirmed ||
|
|
(isFemaleProfile && contactStatusMutation.isPending) ? (
|
|
<div className="w-full border border-[#E2E8F0] bg-white/90 backdrop-blur-sm rounded-[20px] mt-6 px-5 py-4 text-center shadow-sm max-w-[340px] flex flex-col items-center justify-center min-h-[90px]">
|
|
{isFemaleProfile && contactStatusMutation.isPending ? (
|
|
<LoadingThreeDot className="text-[#E03950]" />
|
|
) : (
|
|
<p className="text-[#475569] text-[14px] font-medium leading-relaxed">
|
|
{isFemaleProfile
|
|
? t[
|
|
"Thank you for your feedback. To complete the process, please submit the final outcome of this introduction/contact so that the final status can be determined. If the final status is not yet determined, you can stay in this state until it is finalized."
|
|
]
|
|
: t[
|
|
"Thank you for giving us feedback, we would be very happy if you also let us know the final result."
|
|
]}
|
|
</p>
|
|
)}
|
|
</div>
|
|
) : noContactReportedSuccess ? (
|
|
<p className="mt-4 max-w-[340px] text-[14.5px] leading-[1.6] font-semibold text-[#10B981]">
|
|
{t["Your report has been submitted to support."]}
|
|
</p>
|
|
) : (
|
|
<p className="mt-5 max-w-[340px] text-[15px] leading-[1.6] font-medium text-[#777777]">
|
|
{isFemaleProfile
|
|
? t[
|
|
"The selected candidate will contact your family shortly."
|
|
]
|
|
: t[
|
|
"You can now view their family's contact details and arrange further steps."
|
|
]}
|
|
</p>
|
|
)}
|
|
|
|
<>
|
|
{caseStatus === "contacted" ||
|
|
isFemaleContactConfirmed ||
|
|
(isFemaleProfile && contactStatusMutation.isPending) ? (
|
|
<div className="flex mt-8 w-full gap-3 justify-center max-w-[350px] mx-auto">
|
|
{isFemaleProfile &&
|
|
contactStatusMutation.isPending ? null : isFemaleProfile ? (
|
|
<>
|
|
<button
|
|
type="button"
|
|
disabled={isOpeningProfile}
|
|
onClick={handleOpenProfile}
|
|
className="flex-1 h-[50px] px-3 rounded-[14px] border border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#334155] font-bold text-[14px] shadow-sm flex items-center justify-center transition-transform active:scale-[0.98] cursor-pointer hover:bg-[#F8FAFC]"
|
|
>
|
|
{isOpeningProfile ? (
|
|
<LoadingThreeDot />
|
|
) : (
|
|
t["View all detail"] || "View all detail"
|
|
)}
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOutcomeSheetOpen(true)}
|
|
disabled={outcomeMutation.isPending}
|
|
className="flex-1 h-[50px] px-3 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[14px] flex items-center justify-center shadow-sm transition-transform active:scale-[0.98] cursor-pointer hover:opacity-95 disabled:opacity-50"
|
|
>
|
|
{outcomeMutation.isPending ? (
|
|
<LoadingThreeDot className="text-white" />
|
|
) : (
|
|
t["Share Result"]
|
|
)}
|
|
</button>
|
|
</>
|
|
) : (
|
|
<>
|
|
<button
|
|
type="button"
|
|
disabled={isOpeningProfile}
|
|
onClick={handleOpenProfile}
|
|
className="flex-1 h-[50px] px-3 rounded-[14px] border border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#334155] font-bold text-[14px] shadow-sm flex items-center justify-center transition-transform active:scale-[0.98] cursor-pointer hover:bg-[#F8FAFC]"
|
|
>
|
|
{isOpeningProfile ? (
|
|
<LoadingThreeDot />
|
|
) : (
|
|
t["View all detail"] || "View all detail"
|
|
)}
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsOutcomeSheetOpen(true)}
|
|
disabled={outcomeMutation.isPending}
|
|
className="flex-1 h-[50px] px-3 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[14px] flex items-center justify-center shadow-sm transition-transform active:scale-[0.98] cursor-pointer hover:opacity-95 disabled:opacity-50"
|
|
>
|
|
{outcomeMutation.isPending ? (
|
|
<LoadingThreeDot className="text-white" />
|
|
) : (
|
|
t["Submit Final Outcome"]
|
|
)}
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
) : noContactReportedSuccess ? (
|
|
<div className="flex flex-col mt-8 w-full gap-3.5 max-w-md mx-auto">
|
|
<div className="w-full border border-[#E2E8F0] bg-white/95 backdrop-blur-sm rounded-[20px] px-5 py-6 text-center shadow-sm flex flex-col items-center justify-center">
|
|
<div className="size-12 rounded-full bg-[#ECFDF5] flex items-center justify-center mb-3 text-[#10B981]">
|
|
<Ic name="check" className="size-6 text-[#10B981]" />
|
|
</div>
|
|
<h3 className="font-bold text-[#1F2937] text-[16px] mb-2">
|
|
{t["Report Registered"] || "Report Registered"}
|
|
</h3>
|
|
<p className="text-[#64748B] text-[13.5px] leading-relaxed">
|
|
{t[
|
|
"Thank you for your feedback. Our support team will investigate the matter and notify you of the result. Please wait patiently during the review; our support will contact you."
|
|
]}
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
disabled={isOpeningProfile}
|
|
onClick={handleOpenProfile}
|
|
className="w-full min-h-[48px] px-3.5 py-2.5 rounded-[14px] border border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#475569] font-bold text-[14px] leading-tight shadow-sm flex items-center justify-center text-center whitespace-nowrap transition-all hover:bg-[#F8FAFC] active:scale-[0.98] cursor-pointer"
|
|
>
|
|
{isOpeningProfile ? (
|
|
<LoadingBorderSpinner size="sm" variant="muted" />
|
|
) : (
|
|
t["View all detail"] || "View all detail"
|
|
)}
|
|
</button>
|
|
</div>
|
|
) : (
|
|
<div className="flex flex-col mt-8 w-full gap-3.5 max-w-md mx-auto">
|
|
{isFemaleProfile && (
|
|
<button
|
|
type="button"
|
|
disabled={isOpeningProfile}
|
|
onClick={handleOpenProfile}
|
|
className="w-full min-h-[48px] px-3.5 py-2.5 rounded-[14px] border border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#475569] font-bold text-[14px] leading-tight shadow-sm flex items-center justify-center text-center whitespace-nowrap transition-all hover:bg-[#F8FAFC] active:scale-[0.98] cursor-pointer"
|
|
>
|
|
{isOpeningProfile ? (
|
|
<LoadingThreeDot />
|
|
) : (
|
|
t["View all detail"] || "View all detail"
|
|
)}
|
|
</button>
|
|
)}
|
|
|
|
<div className="flex w-full justify-center gap-3.5">
|
|
{isFemaleProfile ? (
|
|
<button
|
|
type="button"
|
|
onClick={() => setIsNoContactConfirmOpen(true)}
|
|
disabled={
|
|
contactStatusMutation.isPending ||
|
|
noContactReportedSuccess
|
|
}
|
|
className="flex-1 min-h-[48px] px-3.5 py-2.5 rounded-[14px] border border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#475569] font-bold text-[13.5px] leading-tight shadow-sm flex items-center justify-center text-center whitespace-nowrap transition-all cursor-pointer hover:bg-[#F8FAFC] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{contactStatusMutation.isPending ? (
|
|
<LoadingThreeDot />
|
|
) : (
|
|
primaryActionText
|
|
)}
|
|
</button>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
disabled={isOpeningProfile}
|
|
onClick={handleOpenProfile}
|
|
className="flex-1 min-h-[48px] px-3.5 py-2.5 rounded-[14px] border border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#475569] font-bold text-[14px] leading-tight shadow-sm flex items-center justify-center text-center whitespace-nowrap transition-all hover:bg-[#F8FAFC] active:scale-[0.98] cursor-pointer"
|
|
>
|
|
{isOpeningProfile ? (
|
|
<LoadingThreeDot />
|
|
) : (
|
|
primaryActionText
|
|
)}
|
|
</button>
|
|
)}
|
|
|
|
<button
|
|
type="button"
|
|
onClick={() => {
|
|
void handleSecondaryAction();
|
|
}}
|
|
disabled={
|
|
paymentMutation.isPending ||
|
|
isFetchingContact
|
|
}
|
|
className={
|
|
isFemaleProfile
|
|
? "flex-1 min-h-[48px] px-3.5 py-2.5 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[13.5px] leading-tight flex items-center justify-center text-center whitespace-nowrap shadow-sm transition-all duration-150 cursor-pointer hover:opacity-95 active:scale-[0.96] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
: "flex-1 min-h-[48px] appearance-none border-0 bg-transparent p-0 text-center cursor-pointer transition-transform duration-150 active:scale-[0.96] disabled:opacity-50 disabled:cursor-not-allowed"
|
|
}
|
|
>
|
|
{isFemaleProfile ? (
|
|
paymentMutation.isPending ? (
|
|
<LoadingThreeDot className="text-white" />
|
|
) : (
|
|
secondaryActionText
|
|
)
|
|
) : (
|
|
<div className="w-full h-full min-h-[48px] px-3.5 py-2.5 rounded-[14px] bg-gradient-to-r from-[#FF6687] to-[#FF456C] text-white font-bold text-[14px] leading-tight flex items-center justify-center text-center whitespace-nowrap shadow-sm hover:opacity-95 active:shadow-inner">
|
|
{paymentMutation.isPending || isFetchingContact ? (
|
|
<LoadingThreeDot className="text-white" />
|
|
) : (
|
|
secondaryActionText
|
|
)}
|
|
</div>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{caseStatus !== "contacted" &&
|
|
!isFemaleContactConfirmed &&
|
|
!noContactReportedSuccess &&
|
|
!(isFemaleProfile && contactStatusMutation.isPending) ? (
|
|
<div className="border border-[#FDA4AF]/60 bg-[#FFF1F2]/80 backdrop-blur-sm rounded-[16px] mt-6 p-4 max-w-md mx-auto shadow-sm">
|
|
<p className="text-[#BE123C] text-[12px] font-medium leading-[1.65] whitespace-pre-line text-justify">
|
|
{
|
|
t[
|
|
"Please note that after a match introduction is made, you and your family have a maximum of 48 hours to make at least an initial contact with the introduced person or their family and express your willingness to begin the acquaintance process. Failure to establish communication or provide any update within this timeframe will result in the removal of this introduction, and according to the platform’s policies, your account and subscription will be permanently suspended"
|
|
]
|
|
}
|
|
</p>
|
|
</div>
|
|
) : null}
|
|
</>
|
|
</>
|
|
)}
|
|
</section>
|
|
|
|
<div className="space-y-8 pb-20">
|
|
{/* Advisor section */}
|
|
<AdvisorActionsCard
|
|
title={t["Get an advisor"]}
|
|
description={
|
|
t[
|
|
"Not sure what to do next? Our psychology section is here to guide you at every step."
|
|
]
|
|
}
|
|
avatars={advisorAvatars}
|
|
extraCount={7}
|
|
getAdvisorLabel={t["Get Advisor"]}
|
|
onGetAdvisor={openAdvisors}
|
|
/>
|
|
|
|
<section className="pointer-events-none fixed bottom-0 left-1/2 z-20 w-full max-w-[834px] -translate-x-1/2 bg-[#F5F5F5]">
|
|
<div
|
|
style={{ paddingBottom: "calc(20px + var(--safe-bottom))" }}
|
|
className="pointer-events-auto px-[17px] md:px-8 pt-3"
|
|
>
|
|
<div className="flex items-center justify-center w-full gap-1 rounded-[11px] bg-[#DBDBDB] py-3.5">
|
|
<Image
|
|
src="/assets/images/material-symbols_lock.svg"
|
|
width={24}
|
|
height={24}
|
|
alt="lock"
|
|
/>
|
|
|
|
<h2 className="group-14 leading-none font-semibold text-[#747474]">
|
|
{t["Profile is locked"]}
|
|
</h2>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<MarriageAdvisorsOverlay
|
|
open={isAdvisorOpen}
|
|
onClose={closeAdvisors}
|
|
/>
|
|
|
|
<MatchProfileOverlay
|
|
open={isProfileOpen}
|
|
profile={profile}
|
|
onClose={closeProfile}
|
|
/>
|
|
|
|
{showPaymentSuccessToast && (
|
|
<ErrorToast
|
|
variant="success"
|
|
message={t["Payment successful"] || "Payment successful"}
|
|
onClose={() => setShowPaymentSuccessToast(false)}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|