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.
297 lines
9.6 KiB
297 lines
9.6 KiB
"use client";
|
|
|
|
import Image from "next/image";
|
|
import { useMemo } from "react";
|
|
import { FaBell } from "react-icons/fa6";
|
|
import Button from "@/components/ui/button";
|
|
import { PageBackground } from "@/components/utils/page-background";
|
|
import type {
|
|
MarriageField,
|
|
MarriageFieldValue,
|
|
MarriageMatchSummary,
|
|
} from "@/hooks/marriage/types";
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
|
|
import { useI18n } from "@/i18n/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 fieldCandidates = {
|
|
name: ["name", "full_name", "fullname", "first_name", "display_name"],
|
|
occupation: ["occupation", "job", "profession", "career", "work"],
|
|
age: ["age"],
|
|
city: ["city", "current_city", "residence_city", "location", "residence"],
|
|
maritalStatus: ["marital_status", "maritalstatus", "relationship_status"],
|
|
cityPreference: [
|
|
"city_preference",
|
|
"citypreference",
|
|
"preferred_city",
|
|
"preferred_location",
|
|
"future_residence",
|
|
],
|
|
} 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 formatFieldValue(value: MarriageFieldValue) {
|
|
if (value === null || value === "") {
|
|
return null;
|
|
}
|
|
|
|
if (typeof value === "boolean") {
|
|
return value ? "Yes" : "No";
|
|
}
|
|
|
|
return String(value);
|
|
}
|
|
|
|
function titleFromKey(key: string) {
|
|
return key
|
|
.replace(/^q\d+[_-]?/i, "")
|
|
.replace(/[_-]+/g, " ")
|
|
.replace(/\s+/g, " ")
|
|
.trim()
|
|
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
}
|
|
|
|
function toDisplayField(field: MarriageField): DisplayField | null {
|
|
const value = formatFieldValue(field.value);
|
|
|
|
if (!value) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: field.key || field.label || value,
|
|
label: field.label || titleFromKey(field.key),
|
|
value,
|
|
};
|
|
}
|
|
|
|
function pickField(
|
|
fields: MarriageField[],
|
|
candidates: readonly string[],
|
|
usedIndexes: Set<number>,
|
|
) {
|
|
const candidateSet = new Set(candidates.map(normalizeFieldName));
|
|
for (const [fieldIndex, field] of fields.entries()) {
|
|
if (usedIndexes.has(fieldIndex)) {
|
|
continue;
|
|
}
|
|
|
|
const displayField = toDisplayField(field);
|
|
|
|
if (
|
|
displayField &&
|
|
[field.key, field.label].some((value) =>
|
|
candidateSet.has(normalizeFieldName(value)),
|
|
)
|
|
) {
|
|
usedIndexes.add(fieldIndex);
|
|
return displayField;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function useMatchSummaryDisplay(matchSummary: MarriageMatchSummary | null) {
|
|
return useMemo(() => {
|
|
const fields = matchSummary?.public_info ?? [];
|
|
const usedIndexes = new Set<number>();
|
|
const name = pickField(fields, fieldCandidates.name, usedIndexes);
|
|
const occupation = pickField(
|
|
fields,
|
|
fieldCandidates.occupation,
|
|
usedIndexes,
|
|
);
|
|
const age = pickField(fields, fieldCandidates.age, usedIndexes);
|
|
const city = pickField(fields, fieldCandidates.city, usedIndexes);
|
|
const maritalStatus = pickField(
|
|
fields,
|
|
fieldCandidates.maritalStatus,
|
|
usedIndexes,
|
|
);
|
|
const cityPreference = pickField(
|
|
fields,
|
|
fieldCandidates.cityPreference,
|
|
usedIndexes,
|
|
);
|
|
const extraFields = fields
|
|
.filter((_, index) => !usedIndexes.has(index))
|
|
.map(toDisplayField)
|
|
.filter((field): field is DisplayField => Boolean(field))
|
|
.slice(0, 4);
|
|
|
|
return {
|
|
age,
|
|
city,
|
|
cityPreference,
|
|
extraFields,
|
|
maritalStatus,
|
|
name:
|
|
name?.value ??
|
|
(matchSummary?.id ? `Profile #${matchSummary.id}` : null),
|
|
occupation,
|
|
};
|
|
}, [matchSummary]);
|
|
}
|
|
|
|
function FieldLine({ field }: { field: DisplayField }) {
|
|
return (
|
|
<p className="break-words text-[10px] leading-[1.85] font-medium text-white">
|
|
<span>{field.label}: </span>
|
|
<span>{field.value}</span>
|
|
</p>
|
|
);
|
|
}
|
|
|
|
export default function NewMatchPage() {
|
|
const { dictionary: t } = useI18n();
|
|
const { data: profile, isError, isLoading } = useMarriageProfileQuery();
|
|
const matchSummary = profile?.match_summary ?? null;
|
|
const matchDisplay = useMatchSummaryDisplay(matchSummary);
|
|
const pairedFields = [matchDisplay.age, matchDisplay.city].filter(
|
|
(field): field is DisplayField => Boolean(field),
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<PageBackground />
|
|
|
|
<main className="-mx-[17px] flex min-h-screen flex-col px-[5px] pt-5 pb-5 text-center">
|
|
<section className="flex flex-col items-center">
|
|
<div
|
|
aria-hidden="true"
|
|
className="relative flex h-[60px] w-[60px] items-center justify-center rounded-full bg-[#FF4E67] shadow-[0_12px_28px_rgba(240,68,91,0.22)]"
|
|
>
|
|
<span className="absolute top-3 right-3 h-2 w-2 rounded-full border-2 border-[#FFD54B] border-b-0 border-l-0" />
|
|
<span className="absolute top-4 right-2 h-3 w-3 rounded-full border-2 border-[#FFD54B] border-b-0 border-l-0" />
|
|
<FaBell className="size-8 -rotate-12 text-[#FFD84A] drop-shadow-[0_2px_0_rgba(151,92,0,0.45)]" />
|
|
</div>
|
|
|
|
<h1 className="mt-[15px] text-[17px] leading-none font-black tracking-[0.02em] text-[#111111] uppercase">
|
|
You have a new match!
|
|
</h1>
|
|
|
|
<p className="mt-[11px] max-w-[322px] text-[12px] leading-[1.35] font-semibold text-[#7C7C7C]">
|
|
A matching profile has been found. Information is provided by the
|
|
girl's family or introducers. If you approve, we'll share
|
|
your profile with her family
|
|
</p>
|
|
</section>
|
|
|
|
<section className="mt-[36px] rounded-[11px] bg-[linear-gradient(180deg,#F0445B_0%,#F4556E_100%)] px-[17px] pt-[18px] pb-[17px] text-white shadow-[0_18px_38px_rgba(240,68,91,0.25)]">
|
|
{isLoading ? (
|
|
<p className="py-8 text-[13px] font-semibold">
|
|
Loading match summary...
|
|
</p>
|
|
) : isError ? (
|
|
<p className="py-8 text-[13px] font-semibold">
|
|
Unable to load match summary.
|
|
</p>
|
|
) : matchSummary ? (
|
|
<>
|
|
<h2 className="break-words text-[13px] leading-[1.4] font-bold">
|
|
<span>Name: </span>
|
|
<span>{matchDisplay.name}</span>
|
|
</h2>
|
|
|
|
<div className="mt-[3px] min-h-[68px]">
|
|
{matchDisplay.occupation ? (
|
|
<FieldLine field={matchDisplay.occupation} />
|
|
) : null}
|
|
|
|
{pairedFields.length ? (
|
|
<p className="break-words text-[10px] leading-[1.85] font-medium text-white">
|
|
{pairedFields.map((field, index) => (
|
|
<span key={field.id}>
|
|
{index > 0 ? <span> | </span> : null}
|
|
<span>
|
|
{field.label}: {field.value}
|
|
</span>
|
|
</span>
|
|
))}
|
|
</p>
|
|
) : null}
|
|
|
|
{matchDisplay.maritalStatus ? (
|
|
<FieldLine field={matchDisplay.maritalStatus} />
|
|
) : null}
|
|
{matchDisplay.cityPreference ? (
|
|
<FieldLine field={matchDisplay.cityPreference} />
|
|
) : null}
|
|
{matchDisplay.extraFields.map((field) => (
|
|
<FieldLine key={field.id} field={field} />
|
|
))}
|
|
</div>
|
|
|
|
<Button
|
|
className="mt-[15px] rounded-[7px] border-none bg-white bg-none py-[12px] text-[#F0445B]! shadow-none"
|
|
href="/new-match/profile"
|
|
>
|
|
View Profile
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<p className="py-8 text-[13px] font-semibold">
|
|
No match summary is available yet.
|
|
</p>
|
|
)}
|
|
</section>
|
|
|
|
<section className="mt-[29px] rounded-[12px] border border-white/80 bg-white/78 px-3 py-3.5 text-left shadow-[0_18px_45px_rgba(15,23,42,0.06)] backdrop-blur-sm">
|
|
<h2 className="text-[16px] leading-none font-bold text-[#1C1C1C]">
|
|
{t.findingMatch.advisorTitle}
|
|
</h2>
|
|
<p className="mt-2 max-w-[280px] text-[11px] leading-[1.45] font-semibold text-[#8A8A8A]">
|
|
{t.findingMatch.advisorDescription}
|
|
</p>
|
|
|
|
<div className="mt-4 flex items-center justify-between gap-3">
|
|
<div className="flex items-center pl-1">
|
|
{advisorAvatars.map((avatar) => (
|
|
<span
|
|
key={avatar.id}
|
|
className="-ml-1.5 flex h-[30px] w-[30px] overflow-hidden rounded-full border-2 border-white bg-[#E7E7E7] first:ml-0"
|
|
>
|
|
<Image
|
|
src={avatar.src}
|
|
alt=""
|
|
width={30}
|
|
height={30}
|
|
className="h-full w-full object-cover"
|
|
/>
|
|
</span>
|
|
))}
|
|
<span className="-ml-1.5 flex h-[30px] w-[30px] items-center justify-center rounded-full border-2 border-white bg-[#EDEEF1] text-[12px] font-semibold text-[#1C1C1C]">
|
|
+7
|
|
</span>
|
|
</div>
|
|
|
|
<Button
|
|
className="w-auto rounded-[9px] border-none bg-[#EBEDF0] bg-none px-5 py-[13px] text-[#111111]! shadow-none"
|
|
href="/questions-list"
|
|
>
|
|
{t.findingMatch.getAdvisor}
|
|
</Button>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|