diff --git a/src/app/request-accepted/request-accepted-client.test.tsx b/src/app/request-accepted/request-accepted-client.test.tsx
index 14b97a2..c771b4b 100644
--- a/src/app/request-accepted/request-accepted-client.test.tsx
+++ b/src/app/request-accepted/request-accepted-client.test.tsx
@@ -339,4 +339,77 @@ describe("RequestAcceptedClient", () => {
expect(screen.queryByText(/acquaintance concluded/i)).not.toBeInTheDocument();
expect(sessionStorage.getItem("recent_declined_outcome")).toBeNull();
});
+
+ it("handles female confirming 'Contact Received', invokes mutation with contacted action, and transitions cleanly to outcome reporting state", async () => {
+ mockContactStatusMutateAsync.mockResolvedValueOnce({ success: true });
+
+ mockProfileState.data = {
+ id: 2,
+ gender: "female",
+ is_registering_for_self: true,
+ status: "in_case",
+ active_case: {
+ case_id: 45,
+ status: "female_accepted",
+ has_outcome_reported: false,
+ },
+ };
+
+ render( );
+
+ // Click "Contact Received" button
+ const contactReceivedBtn = screen.getByRole("button", {
+ name: "Contact Received",
+ });
+ expect(contactReceivedBtn).toBeInTheDocument();
+ await userEvent.click(contactReceivedBtn);
+
+ // Confirmation modal should appear
+ expect(
+ screen.getByText("Are you sure contact has been made?"),
+ ).toBeInTheDocument();
+
+ // Confirm button in the modal
+ const confirmBtn = screen.getByRole("button", { name: "Confirm" });
+ await userEvent.click(confirmBtn);
+
+ // Mutation should have been called with action: 'contacted'
+ expect(mockContactStatusMutateAsync).toHaveBeenCalledWith({
+ action: "contacted",
+ custom_note: "Contact received confirmed by female candidate",
+ });
+
+ // Modal should be closed and outcome state should be rendered
+ expect(
+ screen.queryByText("Are you sure contact has been made?"),
+ ).not.toBeInTheDocument();
+ expect(screen.getByText("Share Result")).toBeInTheDocument();
+ });
+
+ it("persists 'Acquaintance Concluded' across reload when active_case has has_outcome_reported: true in post-contact stage", () => {
+ mockProfileState.data = {
+ id: 2,
+ gender: "female",
+ is_registering_for_self: true,
+ status: "in_case",
+ active_case: {
+ case_id: 45,
+ status: "contacted",
+ has_outcome_reported: true,
+ outcome_reason: "personal_withdrawal",
+ },
+ };
+
+ render( );
+
+ expect(screen.getByText(/acquaintance concluded/i)).toBeInTheDocument();
+ expect(
+ screen.getByText(
+ "This acquaintance has not reached a conclusion and the process has been stopped. Please contact support for information on the next steps.",
+ ),
+ ).toBeInTheDocument();
+ expect(screen.getByText("Contact Support")).toBeInTheDocument();
+ expect(screen.getByText("View all detail")).toBeInTheDocument();
+ expect(screen.queryByText("Share Result")).not.toBeInTheDocument();
+ });
});
diff --git a/src/app/request-accepted/request-accepted-client.tsx b/src/app/request-accepted/request-accepted-client.tsx
index e10a48c..521c48a 100644
--- a/src/app/request-accepted/request-accepted-client.tsx
+++ b/src/app/request-accepted/request-accepted-client.tsx
@@ -628,6 +628,7 @@ export default function RequestAcceptedClient() {
"Contact received confirmed by female candidate",
});
setHasConfirmedFemaleContact(true);
+ await refetchProfile();
setIsContactReceivedConfirmOpen(false);
} catch (error) {
console.error("Unable to persist received contact", error);
@@ -756,6 +757,7 @@ export default function RequestAcceptedClient() {
onSuccess={async () => {
try {
await handleNoContactReport();
+ await refetchProfile();
setIsNoContactConfirmOpen(false);
} catch (err) {
console.error("Failed to report no contact", err);
@@ -825,36 +827,30 @@ export default function RequestAcceptedClient() {
}
- ) : caseStatus === "contacted" ||
- isFemaleContactConfirmed ||
- (isFemaleProfile && contactStatusMutation.isPending) ? (
+ ) : caseStatus === "contacted" || isFemaleContactConfirmed ? (
- {isFemaleProfile && contactStatusMutation.isPending ? (
-
- ) : (
-
- {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."
- ]}
- {!canReportOutcome && (
- <>
- {" "}
- {t[
- "The 'Share Result' option will become active after 48 hours."
- ] ||
- "The 'Share Result' option will become active after 48 hours."}
- >
- )}
- >
- )}
-
- )}
+
+ {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."
+ ]}
+ {!canReportOutcome && (
+ <>
+ {" "}
+ {t[
+ "The 'Share Result' option will become active after 48 hours."
+ ] ||
+ "The 'Share Result' option will become active after 48 hours."}
+ >
+ )}
+ >
+ )}
+
) : noContactReportedSuccess ? (
@@ -896,56 +892,49 @@ export default function RequestAcceptedClient() {
{t["Contact Support"] || "Contact Support"}
- ) : caseStatus === "contacted" ||
- isFemaleContactConfirmed ||
- (isFemaleProfile && contactStatusMutation.isPending) ? (
+ ) : caseStatus === "contacted" || isFemaleContactConfirmed ? (
- {isFemaleProfile &&
- contactStatusMutation.isPending ? null : (
- <>
-
- {isOpeningProfile ? (
-
- ) : (
- t["View all detail"] || "View all detail"
- )}
-
+
+ {isOpeningProfile ? (
+
+ ) : (
+ t["View all detail"] || "View all detail"
+ )}
+
- {
- if (isOutcomeDisabled) return;
- setIsOutcomeSheetOpen(true);
- }}
- disabled={isOutcomeDisabled}
- aria-disabled={isOutcomeDisabled}
- title={
- !canReportOutcome && !isFemaleProfile
- ? t[
- "This option will become active 48 hours after contact details are shared."
- ] ||
- "This option will become active 48 hours after contact details are shared."
- : undefined
- }
- 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 transition-all ${
- isOutcomeDisabled
- ? "opacity-50 cursor-not-allowed shadow-none"
- : "hover:opacity-95 active:scale-[0.98] cursor-pointer shadow-sm"
- }`}
- >
- {outcomeMutation.isPending ? (
-
- ) : (
- t["Share Result"] || "Share Result"
- )}
-
- >
- )}
+ {
+ if (isOutcomeDisabled) return;
+ setIsOutcomeSheetOpen(true);
+ }}
+ disabled={isOutcomeDisabled}
+ aria-disabled={isOutcomeDisabled}
+ title={
+ !canReportOutcome && !isFemaleProfile
+ ? t[
+ "This option will become active 48 hours after contact details are shared."
+ ] ||
+ "This option will become active 48 hours after contact details are shared."
+ : undefined
+ }
+ 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 transition-all ${
+ isOutcomeDisabled
+ ? "opacity-50 cursor-not-allowed shadow-none"
+ : "hover:opacity-95 active:scale-[0.98] cursor-pointer shadow-sm"
+ }`}
+ >
+ {outcomeMutation.isPending ? (
+
+ ) : (
+ t["Share Result"] || "Share Result"
+ )}
+
) : noContactReportedSuccess ? (
@@ -1009,11 +998,7 @@ export default function RequestAcceptedClient() {
: "border-[#E2E8F0] bg-white/95 backdrop-blur-sm text-[#475569] shadow-sm cursor-pointer hover:bg-[#F8FAFC] active:scale-[0.98]"
}`}
>
- {contactStatusMutation.isPending ? (
-
- ) : (
- primaryActionText
- )}
+ {primaryActionText}
) : (
{
diff --git a/src/hooks/marriage/types.ts b/src/hooks/marriage/types.ts
index 6cc4951..fd6847a 100644
--- a/src/hooks/marriage/types.ts
+++ b/src/hooks/marriage/types.ts
@@ -82,6 +82,7 @@ export type MarriageActiveCase = {
status: MarriageCaseStatus;
introduced_at: string | null;
contact_shared_at?: string | null;
+ payment_done_at?: string | null;
minutes_since_contact_shared?: number | null;
can_report_no_contact?: boolean;
can_report_outcome?: boolean;
diff --git a/src/lib/marriage-profile-contract.ts b/src/lib/marriage-profile-contract.ts
index 844ac54..510a151 100644
--- a/src/lib/marriage-profile-contract.ts
+++ b/src/lib/marriage-profile-contract.ts
@@ -116,6 +116,10 @@ export function normalizeMarriageProfile(
rawActiveCase.contact_shared_at ??
rawActiveCase.contactSharedAt ??
null,
+ payment_done_at:
+ rawActiveCase.payment_done_at ??
+ rawActiveCase.paymentDoneAt ??
+ null,
minutes_since_contact_shared:
rawActiveCase.minutes_since_contact_shared ??
rawActiveCase.minutesSinceContactShared ??
@@ -125,6 +129,28 @@ export function normalizeMarriageProfile(
rawActiveCase.canReportNoContact ??
false,
),
+ can_report_outcome: Boolean(
+ rawActiveCase.can_report_outcome ??
+ rawActiveCase.canReportOutcome ??
+ false,
+ ),
+ has_outcome_reported: Boolean(
+ rawActiveCase.has_outcome_reported ??
+ rawActiveCase.hasOutcomeReported ??
+ false,
+ ),
+ outcome_reason:
+ rawActiveCase.outcome_reason ??
+ rawActiveCase.outcomeReason ??
+ null,
+ outcome_note:
+ rawActiveCase.outcome_note ??
+ rawActiveCase.outcomeNote ??
+ null,
+ outcome_reported_by:
+ rawActiveCase.outcome_reported_by ??
+ rawActiveCase.outcomeReportedBy ??
+ null,
my_action:
rawActiveCase.my_action ?? rawActiveCase.myAction ?? "pending",
other_action: