From f70b3fd273c264067bb131889ba2c8505de66218 Mon Sep 17 00:00:00 2001 From: ghorbani Date: Sun, 9 Aug 2026 17:14:03 +0330 Subject: [PATCH] feat: implement interactive questions-list page with local progress tracking and sync-before-submission logic --- .../[slug]/answer-pace-sheet.tsx | 132 +++-------- .../[slug]/question-detail-client.tsx | 9 +- src/app/questions-list/page.tsx | 12 +- src/app/questions-list/sections-request.tsx | 18 +- src/components/Componentes/progress-helper.ts | 18 +- .../Componentes/question-answer-storage.tsx | 104 ++++++-- .../question-exit-navigation-button.tsx | 22 +- src/components/Componentes/question-phone.tsx | 222 ++++++++++-------- .../Componentes/question-section-flow.tsx | 151 ++++-------- .../Componentes/question-snap-list.tsx | 28 +-- .../Componentes/silent-reloader.tsx | 13 + src/components/Componentes/token-switcher.tsx | 14 +- src/hooks/marriage/use-form-schema.ts | 128 ++++++++++ src/hooks/marriage/use-profile-main.ts | 5 +- src/hooks/marriage/use-section-data.ts | 142 ++++++++--- src/hooks/marriage/use-sections.ts | 36 ++- src/lib/first-entry-helper.ts | 23 ++ src/lib/get-submit-path.ts | 11 +- 18 files changed, 672 insertions(+), 416 deletions(-) create mode 100644 src/hooks/marriage/use-form-schema.ts create mode 100644 src/lib/first-entry-helper.ts diff --git a/src/app/questions-list/[slug]/answer-pace-sheet.tsx b/src/app/questions-list/[slug]/answer-pace-sheet.tsx index 7514a52..11d8b46 100644 --- a/src/app/questions-list/[slug]/answer-pace-sheet.tsx +++ b/src/app/questions-list/[slug]/answer-pace-sheet.tsx @@ -1,134 +1,59 @@ "use client"; -import { useEffect, useMemo, useState } from "react"; -import { - getQuestionAnswersStorageKey, - hasQuestionAnswerValue, -} from "@/components/Componentes/question-answer-storage"; +import { useEffect, useState } from "react"; import InformationSheet from "@/components/Componentes/information-sheet"; -import type { - MarriageField, - MarriagePhoneFieldValue, -} from "@/hooks/marriage/types"; -import { useMarriageSectionsQuery } from "@/hooks/marriage/use-sections"; +import { isFirstEntryCompleted } from "@/lib/first-entry-helper"; + +const ANSWER_PACE_SHEET_SEEN_KEY = "marriage:answer-pace-sheet-seen"; type AnswerPaceSheetProps = { + activeQuestionIndex: number; continueLabel: string; description: string; - slug: string; title: string; }; -type StoredQuestionAnswers = { - fields?: unknown; -}; - -function isMarriageField(value: unknown): value is MarriageField { - if (!value || typeof value !== "object") { - return false; - } - - const field = value as Partial; - - return ( - typeof field.key === "string" && - typeof field.label === "string" && - typeof field.type === "string" && - (field.value === null || - typeof field.value === "string" || - typeof field.value === "number" || - typeof field.value === "boolean" || - isMarriagePhoneFieldValue(field.value)) - ); -} - -function isMarriagePhoneFieldValue( - value: unknown, -): value is MarriagePhoneFieldValue { - if (!value || typeof value !== "object") { +export function isAnswerPaceSheetSeen(): boolean { + if (typeof window === "undefined") return false; + try { + return window.localStorage.getItem(ANSWER_PACE_SHEET_SEEN_KEY) === "true"; + } catch { return false; } - - const phoneValue = value as Partial; - - return ( - typeof phoneValue.countryCode === "string" && - typeof phoneValue.phoneNumber === "string" - ); } -function hasStoredQuestionProgress(slugs: readonly string[]) { +export function markAnswerPaceSheetSeen(): void { + if (typeof window === "undefined") return; try { - return slugs.some((slug) => { - const rawValue = window.localStorage.getItem( - getQuestionAnswersStorageKey(slug), - ); - - if (!rawValue) { - return false; - } - - const storedValue = JSON.parse(rawValue) as StoredQuestionAnswers; - const fields = Array.isArray(storedValue.fields) - ? storedValue.fields.filter(isMarriageField) - : []; - - return fields.some((field) => hasQuestionAnswerValue(field.value)); - }); + window.localStorage.setItem(ANSWER_PACE_SHEET_SEEN_KEY, "true"); } catch { - return false; + // ignore } } export default function AnswerPaceSheet({ + activeQuestionIndex, continueLabel, description, - slug, title, }: AnswerPaceSheetProps) { - const { data: sections, isSuccess } = useMarriageSectionsQuery(); - const [hasSeenSheet, setHasSeenSheet] = useState(true); - const [hasLocalProgress, setHasLocalProgress] = useState(true); - const storageKey = `marriage:sections:${slug}:answer-pace-sheet-seen`; - const sectionSlugs = useMemo( - () => sections?.map((section) => section.slug) ?? [slug], - [sections, slug], - ); - - const hasAnySectionProgress = useMemo(() => { - if (!sections?.length) { - return true; - } - - return sections.some( - (section) => section.current_step > 0 || section.completion_percent >= 1, - ); - }, [sections]); - - const isOpen = - isSuccess && !hasSeenSheet && !hasLocalProgress && !hasAnySectionProgress; + const [isOpen, setIsOpen] = useState(false); useEffect(() => { - try { - setHasSeenSheet(window.sessionStorage.getItem(storageKey) === "true"); - } catch (e) { - console.warn("sessionStorage is not accessible:", e); - setHasSeenSheet(false); + // Show only once ever, during first entry flow, after 3rd question (activeQuestionIndex >= 3) + if ( + activeQuestionIndex >= 3 && + !isAnswerPaceSheetSeen() && + !isFirstEntryCompleted() + ) { + setIsOpen(true); } - setHasLocalProgress(hasStoredQuestionProgress(sectionSlugs)); - }, [sectionSlugs, storageKey]); + }, [activeQuestionIndex]); - useEffect(() => { - if (!isOpen) { - return; - } - - try { - window.sessionStorage.setItem(storageKey, "true"); - } catch (e) { - console.warn("sessionStorage is not accessible:", e); - } - }, [isOpen, storageKey]); + const handleClose = () => { + markAnswerPaceSheetSeen(); + setIsOpen(false); + }; if (!isOpen) { return null; @@ -140,6 +65,7 @@ export default function AnswerPaceSheet({ title={title} description={description} buttons={continueLabel} + onClose={handleClose} /> ); } diff --git a/src/app/questions-list/[slug]/question-detail-client.tsx b/src/app/questions-list/[slug]/question-detail-client.tsx index 474bd88..e044906 100644 --- a/src/app/questions-list/[slug]/question-detail-client.tsx +++ b/src/app/questions-list/[slug]/question-detail-client.tsx @@ -39,7 +39,6 @@ import { import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { defaultLocale, type Locale } from "@/translations/config"; import { useI18n } from "@/translations/provider"; -import AnswerPaceSheet from "./answer-pace-sheet"; type QuestionDetailClientProps = { closeLabel: string; @@ -655,7 +654,7 @@ export default function QuestionDetailClient({ router.replace(questionsListHref); }, [isProfileLoading, item, profileContext, questionsListHref, router]); - if (isProfileLoading && item) { + if (!profile && isProfileLoading && item) { return ( -
diff --git a/src/app/questions-list/page.tsx b/src/app/questions-list/page.tsx index 32ddc56..cf4c0d1 100644 --- a/src/app/questions-list/page.tsx +++ b/src/app/questions-list/page.tsx @@ -35,6 +35,7 @@ import { } from "@/lib/match-start-grace"; import { localizePath } from "@/translations/config"; import { useI18n } from "@/translations/provider"; +import { triggerSilentReload } from "@/components/Componentes/silent-reloader"; import SectionsRequest from "./sections-request"; export default function QuestionsListPage() { @@ -45,9 +46,12 @@ export default function QuestionsListPage() { const { data: profile, isLoading: isProfileLoading } = useMarriageProfileQuery(); const { data: sections, isLoading: isSectionsLoading } = - useMarriageSectionsQuery({ - refetchOnMount: "always", - }); + useMarriageSectionsQuery(); + + useEffect(() => { + triggerSilentReload(queryClient); + }, [queryClient]); + const startMatchMutation = useStartMarriageMatchMutation({ onSuccess: () => { @@ -231,7 +235,7 @@ export default function QuestionsListPage() { } }; - if (isProfileLoading || isSectionsLoading) { + if ((!profile || !sections) && (isProfileLoading || isSectionsLoading)) { return ( <> diff --git a/src/app/questions-list/sections-request.tsx b/src/app/questions-list/sections-request.tsx index 9e10a78..d3e429f 100644 --- a/src/app/questions-list/sections-request.tsx +++ b/src/app/questions-list/sections-request.tsx @@ -37,11 +37,13 @@ export default function SectionsRequest() { useEffect(() => { try { - setHasSeenSheet( - window.sessionStorage.getItem(FIRST_ENTRY_TERMS_SEEN_KEY) === "true", - ); + const seenLocal = + window.localStorage.getItem(FIRST_ENTRY_TERMS_SEEN_KEY) === "true"; + const seenSession = + window.sessionStorage.getItem(FIRST_ENTRY_TERMS_SEEN_KEY) === "true"; + setHasSeenSheet(seenLocal || seenSession); } catch (e) { - console.warn("sessionStorage is not accessible:", e); + console.warn("Storage is not accessible:", e); setHasSeenSheet(false); } }, []); @@ -52,9 +54,10 @@ export default function SectionsRequest() { } try { + window.localStorage.setItem(FIRST_ENTRY_TERMS_SEEN_KEY, "true"); window.sessionStorage.setItem(FIRST_ENTRY_TERMS_SEEN_KEY, "true"); } catch (e) { - console.warn("sessionStorage is not accessible:", e); + console.warn("Storage is not accessible:", e); } }, [isOpen]); @@ -68,11 +71,11 @@ export default function SectionsRequest() { title={({ close }) => ( - Bookings Terms & Conditions + Terms & Conditions -