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.
256 lines
9.0 KiB
256 lines
9.0 KiB
"use client";
|
|
|
|
import Image from "next/image";
|
|
import { useRouter } from "next/navigation";
|
|
import { useMemo, useState } from "react";
|
|
import { IoClose } from "react-icons/io5";
|
|
import QuestionCard from "@/components/questions/question-card";
|
|
import RequiredStepsCard from "@/components/questions/required-steps-card";
|
|
import Button from "@/components/ui/button";
|
|
import InformationSheet from "@/components/ui/information-sheet";
|
|
import NavigationButton from "@/components/ui/navigation-button";
|
|
import { PageBackground } from "@/components/utils/page-background";
|
|
import {
|
|
getQuestionListItems,
|
|
isQuestionListItemVisibleForProfile,
|
|
isQuestionRequiredForProfile,
|
|
isQuestionVisibleForProfile,
|
|
type QuestionListItem,
|
|
} from "@/data/question-data";
|
|
import { hasQuestionAnswerValue } from "@/components/questions/question-answer-storage";
|
|
import { useStartMarriageMatchMutation } from "@/hooks/marriage/use-match-start";
|
|
import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main";
|
|
import { useMarriageSectionsQuery } from "@/hooks/marriage/use-sections";
|
|
import { localizePath } from "@/i18n/config";
|
|
import { useI18n } from "@/i18n/provider";
|
|
import { toFrontendSlug } from "@/data/section-slug-map";
|
|
import SectionsRequest from "./sections-request";
|
|
import { getStoredAge, getLocalSectionProgress } from "@/components/questions/progress-helper";
|
|
|
|
export default function QuestionsListPage() {
|
|
const { dictionary: t, locale } = useI18n();
|
|
const router = useRouter();
|
|
const { data: profile } = useMarriageProfileQuery();
|
|
const { data: sections } = useMarriageSectionsQuery({
|
|
refetchOnMount: "always",
|
|
});
|
|
const startMatchMutation = useStartMarriageMatchMutation({
|
|
onSuccess: () => {
|
|
router.push(localizePath("/finding-match", locale));
|
|
},
|
|
});
|
|
const [isOptionalInfoSheetOpen, setIsOptionalInfoSheetOpen] = useState(false);
|
|
const [selectedSection, setSelectedSection] =
|
|
useState<QuestionListItem | null>(null);
|
|
const questionListItems = useMemo(
|
|
() =>
|
|
getQuestionListItems(locale).filter((item) =>
|
|
isQuestionListItemVisibleForProfile(item, {
|
|
gender: profile?.gender,
|
|
}),
|
|
),
|
|
[locale, profile?.gender],
|
|
);
|
|
|
|
const sectionProgressBySlug = useMemo(() => {
|
|
const progressBySlug = new Map<string, number>();
|
|
const age = getStoredAge();
|
|
|
|
sections?.forEach((section) => {
|
|
const frontendSlug = toFrontendSlug(section.slug);
|
|
const progress = Math.max(0, Math.min(100, Math.round(section.completion_percent)));
|
|
progressBySlug.set(frontendSlug, progress);
|
|
progressBySlug.set(section.slug, progress);
|
|
});
|
|
|
|
questionListItems.forEach((item) => {
|
|
const localProgress = getLocalSectionProgress(item, profile, age);
|
|
if (localProgress !== null) {
|
|
progressBySlug.set(item.slug, localProgress);
|
|
}
|
|
});
|
|
|
|
return progressBySlug;
|
|
}, [sections, questionListItems, profile]);
|
|
|
|
const allRequiredSectionsCompleted = useMemo(() => {
|
|
if (!sections?.length) {
|
|
return false;
|
|
}
|
|
|
|
return sections
|
|
.filter((section) => section.is_required)
|
|
.every((section) => {
|
|
const frontendSlug = toFrontendSlug(section.slug);
|
|
const progress = sectionProgressBySlug.get(frontendSlug) ?? Math.max(0, Math.min(100, Math.round(section.completion_percent)));
|
|
return progress >= 100;
|
|
});
|
|
}, [sections, sectionProgressBySlug]);
|
|
|
|
const profileStatus = profile?.status;
|
|
const isProfileSuspended = profileStatus === "suspended";
|
|
const canStartMatch =
|
|
profileStatus === "pending_info" &&
|
|
!isProfileSuspended &&
|
|
allRequiredSectionsCompleted;
|
|
const isStartMatchDisabled = startMatchMutation.isPending || !canStartMatch;
|
|
const hasIncompleteOptionalSections = useMemo(() => {
|
|
if (!sections?.length) {
|
|
return false;
|
|
}
|
|
|
|
return sections.some(
|
|
(section) => !section.is_required && (sectionProgressBySlug.get(toFrontendSlug(section.slug)) ?? section.completion_percent) < 100,
|
|
);
|
|
}, [sections, sectionProgressBySlug]);
|
|
|
|
const handleStartMatch = () => {
|
|
if (!canStartMatch) {
|
|
return;
|
|
}
|
|
|
|
startMatchMutation.mutate();
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{isOptionalInfoSheetOpen ? (
|
|
<InformationSheet
|
|
icon="warning"
|
|
title={t.questions.optionalInfoPromptTitle}
|
|
description={
|
|
<p className="px-0.5 text-sm leading-[1.45] text-[#2D2D2D]">
|
|
{t.questions.optionalInfoPromptDescription}
|
|
</p>
|
|
}
|
|
onClose={() => setIsOptionalInfoSheetOpen(false)}
|
|
buttons={({ close }) => (
|
|
<div className="grid w-full grid-cols-[1fr_2fr] gap-3">
|
|
<Button
|
|
className="rounded-[11px] border border-[#9B9B9B] bg-transparent py-[18px] text-[#8B8B8B]"
|
|
onClick={close}
|
|
variant="outlined"
|
|
>
|
|
{t.common.cancel}
|
|
</Button>
|
|
<Button
|
|
className="rounded-[11px] py-[18px]"
|
|
disabled={isStartMatchDisabled}
|
|
onClick={() => {
|
|
close();
|
|
handleStartMatch();
|
|
}}
|
|
>
|
|
{t.questions.findingMatch}
|
|
</Button>
|
|
</div>
|
|
)}
|
|
/>
|
|
) : null}
|
|
{selectedSection ? (
|
|
<InformationSheet
|
|
icon={null}
|
|
title={({ close }) => (
|
|
<span className="flex w-full items-start justify-between gap-3 text-left">
|
|
<span className="group-14 leading-5 font-bold tracking-normal text-[#8B8B8B]">
|
|
{selectedSection.title}
|
|
</span>
|
|
<button
|
|
type="button"
|
|
aria-label={`Close ${selectedSection.title} explanation`}
|
|
className="-mt-0.5 flex size-6 shrink-0 items-center justify-center text-[#8F8F8F]"
|
|
onClick={close}
|
|
>
|
|
<IoClose aria-hidden="true" className="text-[22px]" />
|
|
</button>
|
|
</span>
|
|
)}
|
|
description={
|
|
<p className="px-0.5 group-14 leading-[1.45] text-[#2D2D2D]">
|
|
{selectedSection.summary}
|
|
</p>
|
|
}
|
|
onClose={() => setSelectedSection(null)}
|
|
className="text-left"
|
|
/>
|
|
) : null}
|
|
<SectionsRequest />
|
|
<PageBackground disabled />
|
|
|
|
<main
|
|
style={{ paddingBottom: "calc(100px + var(--safe-bottom))" }}
|
|
className="-mx-[17px] relative min-h-screen overflow-x-clip bg-[#F5F5F5] px-[17px] pt-0"
|
|
>
|
|
<div className="pointer-events-none absolute inset-x-0 top-0 h-[240px] bg-[radial-gradient(circle_at_top,rgba(255,255,255,0.98)_0%,rgba(255,255,255,0.7)_42%,rgba(255,255,255,0)_100%)]" />
|
|
<div className="pointer-events-none absolute -top-16 left-1/2 h-[220px] w-[220px] -translate-x-1/2 rounded-full bg-white/70 blur-3xl" />
|
|
|
|
<header
|
|
style={{ paddingTop: "calc(var(--safe-top) + 16px)" }}
|
|
className="sticky top-0 z-20 -mx-[17px] flex items-center justify-between bg-[#F5F5F5]/90 px-[17px] pb-3 backdrop-blur-md"
|
|
>
|
|
<NavigationButton
|
|
icon="back"
|
|
iconLabel={t.questions.closeQuestionsList}
|
|
/>
|
|
<h1 className="group-16 font-semibold text-[#151515]">
|
|
{t.questions.profileRegistration}
|
|
</h1>
|
|
<NavigationButton
|
|
icon="document"
|
|
iconLabel={t.common.support}
|
|
className="shadow-[0_10px_26px_rgba(15,23,42,0.05)]"
|
|
/>
|
|
</header>
|
|
|
|
<div className="relative">
|
|
<div className="mt-4">
|
|
<RequiredStepsCard />
|
|
</div>
|
|
|
|
<section className="mt-5 space-y-3">
|
|
{questionListItems.map((item) => (
|
|
<QuestionCard
|
|
key={item.slug}
|
|
item={item}
|
|
progress={sectionProgressBySlug.get(item.slug) ?? null}
|
|
onInfoClick={(section) => setSelectedSection(section)}
|
|
/>
|
|
))}
|
|
</section>
|
|
</div>
|
|
|
|
<div
|
|
style={{ paddingBottom: "calc(16px + var(--safe-bottom))" }}
|
|
className="fixed inset-x-0 bottom-0 z-20 mx-auto w-full max-w-[375px] bg-[#F5F5F5]/95 px-[17px] pt-3 backdrop-blur-md"
|
|
>
|
|
<Button
|
|
aria-label={t.questions.findMatches}
|
|
disabled={isStartMatchDisabled}
|
|
onClick={() => {
|
|
if (hasIncompleteOptionalSections) {
|
|
setIsOptionalInfoSheetOpen(true);
|
|
return;
|
|
}
|
|
|
|
handleStartMatch();
|
|
}}
|
|
>
|
|
<span className="flex items-center justify-center gap-2.5">
|
|
<Image
|
|
src="/assets/images/noun-wedding-rings-6540466 1.svg"
|
|
alt=""
|
|
aria-hidden="true"
|
|
width={28}
|
|
height={28}
|
|
className="shrink-0"
|
|
/>
|
|
<span className="leading-none font-semibold">
|
|
{t.questions.findingMatch}
|
|
</span>
|
|
</span>
|
|
</Button>
|
|
</div>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|