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.
 
 
 
 
 

373 lines
13 KiB

"use client";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import Button from "@/components/ui/button";
import DismissReasonSheet from "@/components/ui/dismiss-reason-sheet";
import FemaleConsentSheet from "@/components/ui/female-consent-sheet";
import InformationSheet from "@/components/ui/information-sheet";
import NavigationButton from "@/components/ui/navigation-button";
import StickyHeader from "@/components/ui/sticky-header";
import { PageBackground } from "@/components/utils/page-background";
import type {
MarriageCaseStatus,
MarriageField,
MarriageFieldValue,
MarriageGender,
MarriagePhoneFieldValue,
} from "@/hooks/marriage/types";
import { useRespondToMarriageCaseMutation } from "@/hooks/marriage/use-case-respond";
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
import { localizePath } from "@/translations/config";
import { useI18n } from "@/translations/provider";
function formatFieldValue(value: MarriageFieldValue) {
if (value === null || value === "") {
return null;
}
if (isMarriagePhoneFieldValue(value)) {
return `+${value.countryCode}${value.phoneNumber}`;
}
if (typeof value === "boolean") {
return value ? "Yes" : "No";
}
return String(value);
}
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 titleFromKey(key: string) {
return key
.replace(/^q\d+[_-]?/i, "")
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function isImageField(field: MarriageField) {
return /(avatar|image|photo|picture|portrait|upload)/i.test(
`${field.key} ${field.label}`,
);
}
function canAcceptProfile(
gender: MarriageGender | null | undefined,
status: MarriageCaseStatus | null | undefined,
) {
if (!gender || !status) {
return false;
}
if (gender === "female") {
return status === "introduced" || status === "male_accepted";
}
return status === "introduced";
}
function MatchField({ field }: { field: MarriageField }) {
const value = formatFieldValue(field.value);
if (!value || isImageField(field)) {
return null;
}
const label = field.label || titleFromKey(field.key);
return (
<div className="mb-3.5 space-y-2 border-b border-[#000000]/10 pb-2.5">
<p className="text-xs font-semibold text-[#978787]">{label}</p>
<p className="font-semibold text-[#111111]">{value}</p>
</div>
);
}
export default function NewMatchProfilePage() {
const { dictionary: t, locale } = useI18n();
const router = useRouter();
const [isRequestSheetOpen, setIsRequestSheetOpen] = useState(false);
const [isFemaleConsentChecked, setIsFemaleConsentChecked] = useState(false);
const [isRejectSheetOpen, setIsRejectSheetOpen] = useState(false);
const [isDismissReasonSheetOpen, setIsDismissReasonSheetOpen] =
useState(false);
const { data: profile } = useMarriageProfileQuery();
const caseId = profile?.active_case?.case_id;
const caseStatus = profile?.active_case?.status;
const isFemaleProfile = profile?.gender === "female";
const isMaleAccepted = caseStatus === "male_accepted";
const respondMutation = useRespondToMarriageCaseMutation(caseId ?? "", {
onSuccess: async (_, variables) => {
if (variables.action === "accept") {
router.push(localizePath("/request-sent", locale));
return;
}
router.push(localizePath("/candidate-contact", locale));
},
});
const isSubmitting = respondMutation.isPending;
const isAcceptProfileEnabled =
Boolean(caseId) &&
!isSubmitting &&
canAcceptProfile(profile?.gender, caseStatus);
return (
<>
<PageBackground />
{isRequestSheetOpen ? (
isFemaleProfile ? (
<FemaleConsentSheet
title="Final Confirmation & Consent"
description="By approving this profile, the male candidate will be notified to proceed with acquiring your contact information for further communication. Please ensure full family alignment before proceeding"
buttons={({ close }) => (
<div className="space-y-5">
<button
type="button"
onClick={() => setIsFemaleConsentChecked((value) => !value)}
className="flex w-full items-start gap-4 rounded-[15px] border-2 border-white bg-white/50 px-5 py-5 text-left shadow-[0_8px_24px_rgba(0,0,0,0.05)]"
>
<span
className={[
"mt-1 flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full border-1",
isFemaleConsentChecked
? "border-[#F0445B] bg-[#F0445B]"
: "border-[#2B2B2B] bg-white",
].join(" ")}
aria-hidden="true"
>
{isFemaleConsentChecked ? (
<span className="h-3 w-3 rounded-full bg-[#F0445B]" />
) : null}
</span>
<span className="text-xs leading-[1.35] text-[#3F3F3F]">
I confirm that the female candidate and her family have
reviewed this profile and tentatively agree to further
communication
</span>
</button>
<div className="grid w-full grid-cols-2 gap-3">
<Button
variant="outlined"
className="py-[18px] text-[18px]"
onClick={close}
>
{t.common.cancel}
</Button>
<Button
className="py-[18px] text-[18px]"
disabled={
!caseId ||
isSubmitting ||
!isAcceptProfileEnabled ||
!isFemaleConsentChecked
}
onClick={async () => {
close();
if (!isAcceptProfileEnabled) {
return;
}
await respondMutation.mutateAsync({ action: "accept" });
}}
>
{t.common.confirm}
</Button>
</div>
</div>
)}
onClose={() => {
setIsFemaleConsentChecked(false);
setIsRequestSheetOpen(false);
}}
/>
) : (
<InformationSheet
icon="check"
title="Request to Proceed"
description="With your approval, we will approach their family on your behalf to propose marriage. After their family agrees, you will be introduced to each other for further acquaintance."
buttons={({ close }) => (
<div className="grid w-full grid-cols-2 gap-3">
<Button
variant="outlined"
className="py-[18px]"
onClick={close}
>
{t.common.cancel}
</Button>
<Button
className="py-[18px]"
disabled={!isAcceptProfileEnabled}
onClick={async () => {
close();
if (!isAcceptProfileEnabled) {
return;
}
await respondMutation.mutateAsync({ action: "accept" });
}}
>
{t.common.confirm}
</Button>
</div>
)}
onClose={() => setIsRequestSheetOpen(false)}
/>
)
) : null}
{isRejectSheetOpen ? (
<InformationSheet
icon="warning"
title="Reject Profile"
description="Are you sure you've fully reviewed the profile and want to reject this profile?"
buttons={({ close }) => (
<div className="grid w-full grid-cols-2 gap-3">
<Button variant="outlined" className="py-[18px]" onClick={close}>
{t.common.cancel}
</Button>
<Button
className="py-[18px]"
onClick={() => {
close();
setIsDismissReasonSheetOpen(true);
}}
>
Reject
</Button>
</div>
)}
onClose={() => setIsRejectSheetOpen(false)}
/>
) : null}
{isDismissReasonSheetOpen ? (
<DismissReasonSheet
onClose={() => setIsDismissReasonSheetOpen(false)}
onSubmit={async (reason) => {
if (!caseId) {
return;
}
await respondMutation.mutateAsync({
action: "reject",
custom_note: reason,
});
}}
/>
) : null}
<main className="-mx-[17px] flex min-h-screen flex-col bg-[linear-gradient(180deg,rgba(255,197,196,0.2)_0%,rgba(251,237,237,0.7)_100%)] pb-10">
<StickyHeader>
<div className="flex items-center gap-4">
<NavigationButton
variant="transparent"
icon="close"
iconLabel={t.match.goBack}
/>
<h1 className="min-w-0 flex-1 text-center text-white">
{t.match.title}
</h1>
<div className="size-[74px] shrink-0" />
</div>
</StickyHeader>
<section className="px-[17px] pb-32 pt-5">
<div>
<Image
src={"/assets/images/Group 1597880481.png"}
alt=""
width={90}
height={90}
className="rounded-full"
/>
<div className="relative inline-block mt-2">
<p
className="text-[25px] leading-none text-white/80"
style={{ letterSpacing: "-2.3px", fontWeight: "1000" }}
>
{formatFieldValue(
profile?.match_summary?.public_info.find(
(field) => field.key === "q1_full_name",
)?.value ?? null,
) || "Name not available"}
</p>
<p className="absolute inset-0 whitespace-nowrap text-[22px] font-bold leading-none text-[#F0445B]">
{formatFieldValue(
profile?.match_summary?.public_info.find(
(field) => field.key === "q1_full_name",
)?.value ?? null,
) || "Name not available"}
</p>
</div>
</div>
<div className="mt-6">
{profile?.match_summary?.public_info.map((field) => (
<MatchField key={field.key} field={field} />
))}
</div>
</section>
<div
style={{ paddingBottom: `var(--safe-bottom)` }}
className="fixed inset-x-0 bottom-0 z-30 px-[17px]"
>
<div className="mx-auto w-full sm:max-w-[375px] rounded-t-[24px] bg-white px-4 py-4 shadow-[0_12px_30px_rgba(0,0,0,0.14)]">
<div className="flex gap-3">
<button
type="button"
disabled={!caseId || isSubmitting || isMaleAccepted}
onClick={() => setIsRejectSheetOpen(true)}
className="inline-flex w-1/3 items-center justify-center rounded-[12px] border border-[#BFBFBF] bg-white px-4 py-[13px] text-[16px] font-semibold text-[#9A9A9A]"
>
Reject
</button>
<button
type="button"
disabled={!isAcceptProfileEnabled}
onClick={() => {
if (!isAcceptProfileEnabled) {
return;
}
if (isFemaleProfile) {
setIsFemaleConsentChecked(false);
}
setIsRequestSheetOpen(true);
}}
className="inline-flex w-2/3 whitespace-nowrap items-center justify-center gap-1 rounded-[12px] bg-[#F0445B] px-4 py-[13px] text-[16px] font-semibold text-white shadow-[0_8px_16px_rgba(240,68,91,0.24)] disabled:cursor-not-allowed disabled:opacity-50"
>
<Image
src="/assets/images/Icfdason.svg"
alt=""
width={28}
height={28}
/>
<span>{t.match.acceptProfile}</span>
</button>
</div>
</div>
</div>
</main>
</>
);
}