From 4c482380490336f72efe55780803d2ea74c01ea7 Mon Sep 17 00:00:00 2001 From: sina_sajjadi Date: Tue, 5 May 2026 11:53:30 +0330 Subject: [PATCH] feat: update InformationSheet styling and add phone number questions - Adjusted the border radius of the InformationSheet component for a more modern look. - Added new optional phone number fields for "Home Phone Number" and "Father's Phone Number" in English and Persian localization files. - Introduced new SVG assets for various UI components. - Created new components for handling phone number input and advisor actions. - Implemented a FemaleConsentSheet component for user consent interactions. - Added a terms page route and linked it to the main application. --- public/assets/images/Ellipse 1210.svg | 9 + public/assets/images/Frame 2095586fdas679.svg | 4 + public/assets/images/Grfdasfoup.svg | 9 + public/assets/images/Group 15978fdsa80467.svg | 21 ++ public/assets/images/Groupfdas 2.svg | 5 + public/assets/images/Vecfadsftor.svg | 3 + public/assets/images/Vecfdastor.svg | 3 + .../images/boxicons_education-filled.svg | 14 ++ .../assets/images/mingcute_user-info-fill.svg | 8 +- public/assets/images/noun-test-4525471 1.svg | 14 ++ public/assets/images/solar_user-id-bold.svg | 9 + src/app/[lang]/slider/page.tsx | 1 - src/app/[lang]/terms/page.tsx | 1 + src/app/finding-match/page.tsx | 75 ++---- src/app/intro/page.tsx | 29 ++- src/app/new-match/page.tsx | 232 ++++++++---------- src/app/new-match/profile/page.tsx | 167 ++++++++++--- src/app/questions-list/[slug]/page.tsx | 5 + src/app/questions-list/page.tsx | 37 ++- src/app/request-accepted/page.tsx | 207 +++++++++++++++- src/app/request-sent/page.tsx | 22 +- src/app/{slider => terms}/page.tsx | 2 +- src/components/dev/locator-paths.ts | 2 +- src/components/questions/question-card.tsx | 102 +++++--- src/components/questions/question-phone.tsx | 160 ++++++++++++ src/components/sliders/slider-slide-five.tsx | 4 +- src/components/ui/advisor-actions-card.tsx | 73 ++++++ src/components/ui/call-result-sheet.tsx | 24 +- src/components/ui/female-consent-sheet.tsx | 159 ++++++++++++ src/components/ui/information-sheet.tsx | 2 +- src/i18n/locales/en/questions.json | 24 ++ src/i18n/locales/fa/questions.json | 24 ++ 32 files changed, 1173 insertions(+), 278 deletions(-) create mode 100644 public/assets/images/Ellipse 1210.svg create mode 100644 public/assets/images/Frame 2095586fdas679.svg create mode 100644 public/assets/images/Grfdasfoup.svg create mode 100644 public/assets/images/Group 15978fdsa80467.svg create mode 100644 public/assets/images/Groupfdas 2.svg create mode 100644 public/assets/images/Vecfadsftor.svg create mode 100644 public/assets/images/Vecfdastor.svg create mode 100644 public/assets/images/boxicons_education-filled.svg create mode 100644 public/assets/images/noun-test-4525471 1.svg create mode 100644 public/assets/images/solar_user-id-bold.svg delete mode 100644 src/app/[lang]/slider/page.tsx create mode 100644 src/app/[lang]/terms/page.tsx rename src/app/{slider => terms}/page.tsx (68%) create mode 100644 src/components/questions/question-phone.tsx create mode 100644 src/components/ui/advisor-actions-card.tsx create mode 100644 src/components/ui/female-consent-sheet.tsx diff --git a/public/assets/images/Ellipse 1210.svg b/public/assets/images/Ellipse 1210.svg new file mode 100644 index 0000000..dbf142a --- /dev/null +++ b/public/assets/images/Ellipse 1210.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/images/Frame 2095586fdas679.svg b/public/assets/images/Frame 2095586fdas679.svg new file mode 100644 index 0000000..8206180 --- /dev/null +++ b/public/assets/images/Frame 2095586fdas679.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/images/Grfdasfoup.svg b/public/assets/images/Grfdasfoup.svg new file mode 100644 index 0000000..f4f53b7 --- /dev/null +++ b/public/assets/images/Grfdasfoup.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/images/Group 15978fdsa80467.svg b/public/assets/images/Group 15978fdsa80467.svg new file mode 100644 index 0000000..a9f7e16 --- /dev/null +++ b/public/assets/images/Group 15978fdsa80467.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/images/Groupfdas 2.svg b/public/assets/images/Groupfdas 2.svg new file mode 100644 index 0000000..33f39ba --- /dev/null +++ b/public/assets/images/Groupfdas 2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/images/Vecfadsftor.svg b/public/assets/images/Vecfadsftor.svg new file mode 100644 index 0000000..cf3a36e --- /dev/null +++ b/public/assets/images/Vecfadsftor.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/images/Vecfdastor.svg b/public/assets/images/Vecfdastor.svg new file mode 100644 index 0000000..8b52d33 --- /dev/null +++ b/public/assets/images/Vecfdastor.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/images/boxicons_education-filled.svg b/public/assets/images/boxicons_education-filled.svg new file mode 100644 index 0000000..4fc3499 --- /dev/null +++ b/public/assets/images/boxicons_education-filled.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/assets/images/mingcute_user-info-fill.svg b/public/assets/images/mingcute_user-info-fill.svg index b4e869d..822e77f 100644 --- a/public/assets/images/mingcute_user-info-fill.svg +++ b/public/assets/images/mingcute_user-info-fill.svg @@ -1,3 +1,9 @@ - + + + + + + + diff --git a/public/assets/images/noun-test-4525471 1.svg b/public/assets/images/noun-test-4525471 1.svg new file mode 100644 index 0000000..9f4b08f --- /dev/null +++ b/public/assets/images/noun-test-4525471 1.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/assets/images/solar_user-id-bold.svg b/public/assets/images/solar_user-id-bold.svg new file mode 100644 index 0000000..efa0015 --- /dev/null +++ b/public/assets/images/solar_user-id-bold.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/app/[lang]/slider/page.tsx b/src/app/[lang]/slider/page.tsx deleted file mode 100644 index 197a187..0000000 --- a/src/app/[lang]/slider/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from "@/app/slider/page"; diff --git a/src/app/[lang]/terms/page.tsx b/src/app/[lang]/terms/page.tsx new file mode 100644 index 0000000..1ce6625 --- /dev/null +++ b/src/app/[lang]/terms/page.tsx @@ -0,0 +1 @@ +export { default } from "@/app/terms/page"; diff --git a/src/app/finding-match/page.tsx b/src/app/finding-match/page.tsx index 59d3573..9e8c684 100644 --- a/src/app/finding-match/page.tsx +++ b/src/app/finding-match/page.tsx @@ -3,9 +3,10 @@ import Image from "next/image"; import Link from "next/link"; import { FaPen } from "react-icons/fa6"; -import Button from "@/components/ui/button"; +import AdvisorActionsCard from "@/components/ui/advisor-actions-card"; import NavigationButton from "@/components/ui/navigation-button"; import { PageBackground } from "@/components/utils/page-background"; +import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { localizePath } from "@/i18n/config"; import { useI18n } from "@/i18n/provider"; @@ -17,7 +18,12 @@ const advisorAvatars = [ export default function FindingMatchPage() { const { dictionary: t, locale } = useI18n(); + const { data: profile } = useMarriageProfileQuery(); const copy = t.findingMatch; + const matchImageSrc = + profile?.gender === "female" + ? "/assets/images/Group 15978fdsa80467.svg" + : "/assets/images/Group 159788fd0467.svg"; return ( <> @@ -33,7 +39,7 @@ export default function FindingMatchPage() {
-
-
-

- {copy.advisorTitle} -

-

- {copy.advisorDescription} -

+ -
-
- {advisorAvatars.map((avatar) => ( - - - - ))} - - +7 - -
- - -
-
- - - - - -
+ + + + ); diff --git a/src/app/intro/page.tsx b/src/app/intro/page.tsx index 9fea65a..5cfe712 100644 --- a/src/app/intro/page.tsx +++ b/src/app/intro/page.tsx @@ -10,20 +10,25 @@ import { useI18n } from "@/i18n/provider"; export default function Intro() { const { dictionary: t, locale } = useI18n(); const { data: profile } = useMarriageProfileQuery(); - const submitPath = - profile?.active_case?.status === "female_accepted" - ? "/request-accepted" - : profile?.active_case?.status === "male_accepted" + const isInCase = profile?.status === "in_case"; + const isFemaleAcceptedFlow = + isInCase && + (profile?.active_case?.status === "payment_pending" || + profile?.active_case?.status === "female_accepted" || + profile?.active_case?.status === "payment_done"); + const submitPath = isFemaleAcceptedFlow + ? "/request-accepted" + : isInCase && profile?.active_case?.status === "male_accepted" ? "/request-sent" : profile?.status === "pending_onboarding" - ? "/rules" - : profile?.status === "pending_info" - ? "/questions-list" - : profile?.status === "waiting" - ? "/finding-match" - : profile?.status === "in_case" || profile?.status === "matched" - ? "/new-match" - : "/slider"; + ? "/rules" + : profile?.status === "pending_info" + ? "/questions-list" + : profile?.status === "waiting" + ? "/finding-match" + : profile?.status === "in_case" || profile?.status === "matched" + ? "/new-match" + : "/terms"; const submitHref = localizePath(submitPath, locale); return ( diff --git a/src/app/new-match/page.tsx b/src/app/new-match/page.tsx index 6605ce6..6f26a27 100644 --- a/src/app/new-match/page.tsx +++ b/src/app/new-match/page.tsx @@ -2,7 +2,10 @@ import Image from "next/image"; import { useMemo } from "react"; -import { FaBell, FaLock } from "react-icons/fa6"; +import { FaLock } from "react-icons/fa6"; +import AdvisorActionsCard from "@/components/ui/advisor-actions-card"; +import NavigationButton from "@/components/ui/navigation-button"; +import StickyHeader from "@/components/ui/sticky-header"; import { PageBackground } from "@/components/utils/page-background"; import type { MarriageField, @@ -172,135 +175,112 @@ export default function NewMatchPage() { <> -
-
- - -

- You have a new match! -

- -

- 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 -

-
- -
- {isLoading ? ( -

- Loading match summary... -

- ) : isError ? ( -

- Unable to load match summary. +

+
+ +

{t.common.appName}

+ +
+ +
+
+ +

YOU HAVE A NEW MATCH!

+

+ 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

- ) : matchSummary ? ( - <> -

- Name: - {matchDisplay.name} -

- -
- {matchDisplay.occupation ? ( - - ) : null} - - {pairedFields.length ? ( -

- {pairedFields.map((field, index) => ( - - {index > 0 ? | : null} - - {field.label}: {field.value} - - +

+
+
+ {isLoading ? ( +

+ Loading match summary... +

+ ) : isError ? ( +

+ Unable to load match summary. +

+ ) : matchSummary ? ( + <> +

+ Name: + {matchDisplay.name} +

+ +
+ {matchDisplay.occupation ? ( + + ) : null} + + {pairedFields.length ? ( +

+ {pairedFields.map((field, index) => ( + + {index > 0 ? | : null} + + {field.label}: {field.value} + + + ))} +

+ ) : null} + + {matchDisplay.maritalStatus ? ( + + ) : null} + {matchDisplay.cityPreference ? ( + + ) : null} + {matchDisplay.extraFields.map((field) => ( + ))} -

- ) : null} - - {matchDisplay.maritalStatus ? ( - - ) : null} - {matchDisplay.cityPreference ? ( - - ) : null} - {matchDisplay.extraFields.map((field) => ( - - ))} +
+ + + View Profile + + + ) : ( +

+ No match summary is available yet. +

+ )} +
+
+ + +
+ + + + Profile locked + +
- - - View Profile - - - ) : ( -

- No match summary is available yet. -

- )} -
- -
-

- {t.findingMatch.advisorTitle} -

-

- {t.findingMatch.advisorDescription} -

- -
-
- {advisorAvatars.map((avatar) => ( - - - - ))} - - +7 -
- - - {t.findingMatch.getAdvisor} - -
-
- -
-
- - - - Profile locked - -
diff --git a/src/app/new-match/profile/page.tsx b/src/app/new-match/profile/page.tsx index e83e737..6bca6ae 100644 --- a/src/app/new-match/profile/page.tsx +++ b/src/app/new-match/profile/page.tsx @@ -5,13 +5,16 @@ 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 MarriageField, - type MarriageFieldValue, +import type { + MarriageCaseStatus, + MarriageField, + MarriageFieldValue, + MarriageGender, } from "@/hooks/marriage/types"; import { useRespondToMarriageCaseMutation } from "@/hooks/marriage/use-case-respond"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; @@ -45,6 +48,21 @@ function isImageField(field: MarriageField) { ); } +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); @@ -66,12 +84,15 @@ 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 isMaleAccepted = profile?.active_case?.status === "male_accepted"; + 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") { @@ -84,42 +105,114 @@ export default function NewMatchProfilePage() { }); const isSubmitting = respondMutation.isPending; + const isAcceptProfileEnabled = + Boolean(caseId) && + !isSubmitting && + canAcceptProfile(profile?.gender, caseStatus); return ( <> {isRequestSheetOpen ? ( - ( -
- - - if (isMaleAccepted) { - return; - } +
+ + -
- )} - onClose={() => setIsRequestSheetOpen(false)} - /> + await respondMutation.mutateAsync({ action: "accept" }); + }} + > + {t.common.confirm} + +
+ + )} + onClose={() => { + setIsFemaleConsentChecked(false); + setIsRequestSheetOpen(false); + }} + /> + ) : ( + ( +
+ + +
+ )} + onClose={() => setIsRequestSheetOpen(false)} + /> + ) ) : null} {isRejectSheetOpen ? ( + + )} + description={ +

+ {selectedSection.summary} +

+ } + onClose={() => setSelectedSection(null)} + className="text-left" + /> + ) : null} @@ -139,6 +173,7 @@ export default function QuestionsListPage() { key={item.slug} item={item} progress={sectionProgressBySlug.get(item.slug) ?? null} + onInfoClick={(section) => setSelectedSection(section)} /> ))} diff --git a/src/app/request-accepted/page.tsx b/src/app/request-accepted/page.tsx index ca1f897..31d3b63 100644 --- a/src/app/request-accepted/page.tsx +++ b/src/app/request-accepted/page.tsx @@ -4,9 +4,14 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState } from "react"; +import { FiCopy, FiPhone } from "react-icons/fi"; +import CallResultSheet from "@/components/ui/call-result-sheet"; +import FemaleConsentSheet from "@/components/ui/female-consent-sheet"; import NavigationButton from "@/components/ui/navigation-button"; import SubscriptionRequiredSheet from "@/components/ui/subscription-required-sheet"; import { PageBackground } from "@/components/utils/page-background"; +import type { MarriageField } from "@/hooks/marriage/types"; +import { useMarriageContactInfoQuery } from "@/hooks/marriage/use-contact-info"; import { extractHabcoinPaymentUrl, useHabcoinPaymentMutation, @@ -15,14 +20,160 @@ import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { localizePath } from "@/i18n/config"; import { useI18n } from "@/i18n/provider"; +type ContactInfoPhoneItem = { + key: string; + label: string; + phoneNumber: string; +}; + +function sanitizePhoneNumber(value: MarriageField["value"]) { + if (value === null || value === "") { + return null; + } + + const trimmedValue = String(value).trim(); + + if (!trimmedValue) { + return null; + } + + const digits = trimmedValue.replace(/\D/g, ""); + + if (!digits) { + return null; + } + + return trimmedValue.startsWith("+") ? `+${digits}` : digits; +} + +function titleFromKey(key: string) { + return key + .replace(/^q\d+[_-]?/i, "") + .replace(/[_-]+/g, " ") + .replace(/\s+/g, " ") + .trim() + .replace(/\b\w/g, (letter) => letter.toUpperCase()); +} + +function formatContactLabel(field: MarriageField) { + const resolvedLabel = (field.label || titleFromKey(field.key)).trim(); + + return resolvedLabel.replace(/\s+Number$/i, "").trim(); +} + +function getContactInfoPhoneItems(fields: MarriageField[] | null | undefined) { + return (fields ?? []).flatMap((field) => { + if (field.type.toLowerCase() !== "phone") { + return []; + } + + const phoneNumber = sanitizePhoneNumber(field.value); + + if (!phoneNumber) { + return []; + } + + return [ + { + key: field.key, + label: formatContactLabel(field), + phoneNumber, + }, + ]; + }); +} + +function ContactInfoPhoneCard({ item }: { item: ContactInfoPhoneItem }) { + return ( +
+
+

+ {item.phoneNumber} +

+

+ {item.label} +

+
+ +
+ + + + copy + +
+
+ ); +} + export default function RequestAcceptedPage() { const { locale } = useI18n(); const router = useRouter(); + const [isCallResultSheetOpen, setIsCallResultSheetOpen] = useState(false); + const [isContactInfoSheetOpen, setIsContactInfoSheetOpen] = useState(false); const [isSubscriptionSheetOpen, setIsSubscriptionSheetOpen] = useState(false); const profileHref = localizePath("/new-match/profile", locale); const { data: profile } = useMarriageProfileQuery(); + const isFemaleProfile = profile?.gender === "female"; + const caseId = profile?.active_case?.case_id; + const caseStatus = profile?.active_case?.status; const recommendedPlanId = profile?.recommended_plan?.id; const paymentMutation = useHabcoinPaymentMutation(); + const contactInfoQuery = useMarriageContactInfoQuery(caseId, { + enabled: false, + }); + const titleText = isFemaleProfile + ? "The selected candidate will contact your family shortly." + : "REQUEST ACCEPTED"; + const primaryActionText = isFemaleProfile + ? "No contact yet?" + : "Match Profile"; + const secondaryActionText = isFemaleProfile ? "Contacted" : "View Contact"; + const contactInfoPhoneItems = getContactInfoPhoneItems( + contactInfoQuery.data?.contact_info, + ); + + const handleSecondaryAction = async () => { + if (isFemaleProfile) { + setIsCallResultSheetOpen(true); + return; + } + + if (caseStatus === "female_accepted" || caseStatus === "payment_pending") { + setIsCallResultSheetOpen(true); + return; + } + + if (caseStatus === "payment_done") { + if (!caseId) { + return; + } + + if (!contactInfoQuery.data) { + await contactInfoQuery.refetch(); + } + + setIsContactInfoSheetOpen(true); + } + }; const handlePayment = async () => { if (!recommendedPlanId || paymentMutation.isPending) { @@ -49,6 +200,31 @@ export default function RequestAcceptedPage() { <> + {isCallResultSheetOpen ? ( + setIsCallResultSheetOpen(false)} /> + ) : null} + + {isContactInfoSheetOpen ? ( + + {contactInfoPhoneItems.map((item) => ( + + ))} + + ) : ( +
+ Contact information is not available yet. +
+ ) + } + onClose={() => setIsContactInfoSheetOpen(false)} + /> + ) : null} + {isSubscriptionSheetOpen ? ( setIsSubscriptionSheetOpen(false)} @@ -78,7 +254,7 @@ export default function RequestAcceptedPage() {

- REQUEST ACCEPTED + {titleText}

@@ -89,25 +265,44 @@ export default function RequestAcceptedPage() {

- Match Profile + {primaryActionText}

- Please note: if you do not make contact within 2 days, a penalty - may apply + If they don’t contact you within 2 days, please inform us.

diff --git a/src/app/request-sent/page.tsx b/src/app/request-sent/page.tsx index ec6e7a3..71ae3fc 100644 --- a/src/app/request-sent/page.tsx +++ b/src/app/request-sent/page.tsx @@ -1,17 +1,21 @@ "use client"; import Image from "next/image"; -import { useRouter } from "next/navigation"; -import Button from "@/components/ui/button"; +import AdvisorActionsCard from "@/components/ui/advisor-actions-card"; import NavigationButton from "@/components/ui/navigation-button"; import { PageBackground } from "@/components/utils/page-background"; -import { localizePath } from "@/i18n/config"; import { useI18n } from "@/i18n/provider"; import Link from "next/link"; +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" }, +]; + export default function RequestSentPage() { - const { locale } = useI18n(); - const router = useRouter(); + const { dictionary: t } = useI18n(); + const copy = t.findingMatch; return ( <> @@ -53,6 +57,14 @@ export default function RequestSentPage() {
+
diff --git a/src/app/slider/page.tsx b/src/app/terms/page.tsx similarity index 68% rename from src/app/slider/page.tsx rename to src/app/terms/page.tsx index bc51f22..1bd8c75 100644 --- a/src/app/slider/page.tsx +++ b/src/app/terms/page.tsx @@ -1,5 +1,5 @@ import SliderPage from "@/components/sliders/slider-page"; -export default function SliderRoute() { +export default function TermsRoute() { return ; } diff --git a/src/components/dev/locator-paths.ts b/src/components/dev/locator-paths.ts index c854c92..d6bf28a 100644 --- a/src/components/dev/locator-paths.ts +++ b/src/components/dev/locator-paths.ts @@ -4,7 +4,7 @@ export const LOCATORS = { appQuestionsListPage: "D:/sajjadi/marriage/src/app/questions-list/page.tsx", appQuestionDetailPage: "D:/sajjadi/marriage/src/app/questions-list/[slug]/page.tsx", - appSliderPage: "D:/sajjadi/marriage/src/app/slider/page.tsx", + appTermsPage: "D:/sajjadi/marriage/src/app/terms/page.tsx", bookingTermsCard: "D:/sajjadi/marriage/src/components/questions/booking-terms-card.tsx", questionCard: diff --git a/src/components/questions/question-card.tsx b/src/components/questions/question-card.tsx index 9d3c784..bc9e2f6 100644 --- a/src/components/questions/question-card.tsx +++ b/src/components/questions/question-card.tsx @@ -11,15 +11,25 @@ import { import type { QuestionCardIcon, QuestionListItem } from "@/data/question-data"; import { localizePath } from "@/i18n/config"; import { useI18n } from "@/i18n/provider"; +import Image from "next/image"; type QuestionCardProps = { item: QuestionListItem; progress?: number | null; + onInfoClick?: (item: QuestionListItem) => void; }; const RADIUS = 8; const CIRCUMFERENCE = 2 * Math.PI * RADIUS; +const iconAssetMap: Partial> = { + profile: "/assets/images/mingcute_user-info-fill.svg", + education: "/assets/images/boxicons_education-filled.svg", + details: "/assets/images/Grfdasfoup.svg", + checklist: "/assets/images/noun-test-4525471 1.svg", + contact: "/assets/images/solar_user-id-bold.svg", +}; + const iconMap: Record = { profile: IoPerson, education: IoSchool, @@ -31,6 +41,7 @@ const iconMap: Record = { export function QuestionCard({ item, progress = item.progress, + onInfoClick, }: QuestionCardProps) { const { dictionary: t, locale } = useI18n(); const hasProgress = typeof progress === "number" && Number.isFinite(progress); @@ -39,6 +50,7 @@ export function QuestionCard({ : 0; const dashOffset = CIRCUMFERENCE - (normalizedProgress / 100) * CIRCUMFERENCE; const CardIcon = iconMap[item.icon]; + const iconAsset = iconAssetMap[item.icon]; return (
-
-
diff --git a/src/components/ui/female-consent-sheet.tsx b/src/components/ui/female-consent-sheet.tsx new file mode 100644 index 0000000..1a562b8 --- /dev/null +++ b/src/components/ui/female-consent-sheet.tsx @@ -0,0 +1,159 @@ +"use client"; + +import type { HTMLAttributes, ReactNode } from "react"; +import { useEffect, useState } from "react"; +import { useI18n } from "@/i18n/provider"; +import Image from "next/image"; + +const EXIT_ANIMATION_MS = 220; + +export type FemaleConsentSheetProps = Omit< + HTMLAttributes, + "children" | "title" +> & { + title: ReactNode; + description?: ReactNode; + buttons?: ReactNode | ((controls: { close: () => void }) => ReactNode); + closeOnOutside?: boolean; + onClose?: () => void; +}; + +export function FemaleConsentSheet({ + title, + description, + buttons, + closeOnOutside = true, + onClose, + className, + ...props +}: FemaleConsentSheetProps) { + const { dictionary: t } = useI18n(); + const [isVisible, setIsVisible] = useState(true); + const [isEntering, setIsEntering] = useState(true); + const [isClosing, setIsClosing] = useState(false); + + const closeSheet = () => { + if (isClosing) { + return; + } + + setIsClosing(true); + }; + + const controls = { close: closeSheet }; + const resolvedButtons = + typeof buttons === "function" ? buttons(controls) : buttons; + + useEffect(() => { + const frameId = window.requestAnimationFrame(() => { + setIsEntering(false); + }); + + return () => { + window.cancelAnimationFrame(frameId); + }; + }, []); + + useEffect(() => { + if (!isVisible) { + return; + } + + const previousBodyOverflow = document.body.style.overflow; + const previousHtmlOverflow = document.documentElement.style.overflow; + + document.body.style.overflow = "hidden"; + document.documentElement.style.overflow = "hidden"; + + return () => { + document.body.style.overflow = previousBodyOverflow; + document.documentElement.style.overflow = previousHtmlOverflow; + }; + }, [isVisible]); + + useEffect(() => { + if (!isClosing) { + return; + } + + const timeoutId = window.setTimeout(() => { + setIsVisible(false); + onClose?.(); + }, EXIT_ANIMATION_MS); + + return () => { + window.clearTimeout(timeoutId); + }; + }, [isClosing, onClose]); + + if (!isVisible) { + return null; + } + + return ( +
{ + if (closeOnOutside && event.target === event.currentTarget) { + closeSheet(); + } + }} + onKeyDown={(event) => { + if ( + closeOnOutside && + event.target === event.currentTarget && + (event.key === "Escape" || event.key === "Enter" || event.key === " ") + ) { + event.preventDefault(); + closeSheet(); + } + }} + > +
+
+

+ {title} +

+ +
+ + {description ? ( +
+ {description} +
+ ) : null} + + {resolvedButtons ?
{resolvedButtons}
: null} +
+
+ ); +} + +export default FemaleConsentSheet; diff --git a/src/components/ui/information-sheet.tsx b/src/components/ui/information-sheet.tsx index 8b9af6a..0acd3d5 100644 --- a/src/components/ui/information-sheet.tsx +++ b/src/components/ui/information-sheet.tsx @@ -238,7 +238,7 @@ export function InformationSheet({