From 088261a52cff3438b078ff50250de768df37422d Mon Sep 17 00:00:00 2001 From: ghorbani Date: Sat, 8 Aug 2026 15:01:58 +0330 Subject: [PATCH] feat: initialize application structure with new UI components, internationalization support, and core matchmaking pages --- next.config.ts | 16 + src/app/[lang]/candidate-contact/page.tsx | 1 - src/app/api/proxy/route.ts | 20 +- src/app/candidate-contact/page.tsx | 25 +- src/app/finding-match/page.tsx | 118 +- src/app/new-match/page.tsx | 43 +- src/app/new-match/profile/page.tsx | 153 +-- src/app/page.tsx | 4 +- src/app/providers.tsx | 2 + .../[slug]/question-detail-client.tsx | 20 +- src/app/questions-list/page.tsx | 2 +- src/app/request-accepted/page.tsx | 416 +++++-- src/app/request-sent/page.tsx | 11 +- src/components/Componentes/button.tsx | 15 +- .../Componentes/dev-click-to-component.tsx | 23 +- .../Componentes/dismiss-reason-sheet.tsx | 15 +- .../Componentes/female-outcome-sheet.tsx | 388 +++++++ .../Componentes/flutter-locale-sync.tsx | 38 + .../Componentes/loading-border-spinner.tsx | 18 + .../Componentes/loading-icon-spinner.tsx | 16 + .../Componentes/loading-pulse-text.tsx | 20 + .../Componentes/loading-select-spinner.tsx | 7 + .../Componentes/loading-three-dot.tsx | 16 + .../Componentes/navigation-button.tsx | 1 - src/components/Componentes/page-header.tsx | 6 +- .../Componentes/page-loading-skeleton.tsx | 49 + .../Componentes/question-answer-storage.tsx | 56 +- .../Componentes/question-birthplace.tsx | 43 +- src/components/Componentes/question-file.tsx | 47 +- src/components/Componentes/question-phone.tsx | 7 +- src/components/Componentes/question-photo.tsx | 3 +- .../Componentes/question-slider.tsx | 10 +- .../subscription-required-sheet.tsx | 32 +- src/components/Componentes/swipe-button.tsx | 3 +- .../Componentes/test-loading-screen.tsx | 171 +-- src/data/questions/en.json | 12 - src/data/questions/fa.json | 12 - src/lib/get-submit-path.ts | 8 - src/lib/http.ts | 36 +- src/lib/view-paddings.ts | 49 +- src/translations/locales/ar.json | 10 +- src/translations/locales/az.json | 10 +- src/translations/locales/bn.json | 10 +- src/translations/locales/da.json | 10 +- src/translations/locales/de.json | 10 +- src/translations/locales/en.json | 37 +- src/translations/locales/es.json | 10 +- src/translations/locales/fa.json | 23 +- src/translations/locales/fr.json | 10 +- src/translations/locales/gu.json | 10 +- src/translations/locales/ha.json | 10 +- src/translations/locales/he.json | 274 ++--- src/translations/locales/hi.json | 1017 ++++++++++++----- src/translations/locales/id.json | 274 ++--- src/translations/locales/ks.json | 274 ++--- src/translations/locales/pt.json | 274 ++--- src/translations/locales/ru.json | 274 ++--- src/translations/locales/sw.json | 274 ++--- src/translations/locales/tg.json | 274 ++--- src/translations/locales/tr.json | 274 ++--- src/translations/locales/ul.json | 274 ++--- src/translations/locales/ur.json | 274 ++--- src/translations/locales/uz.json | 274 ++--- src/translations/locales/zh.json | 1017 ++++++++++++----- src/types/window.d.ts | 2 + 65 files changed, 4424 insertions(+), 2708 deletions(-) delete mode 100644 src/app/[lang]/candidate-contact/page.tsx create mode 100644 src/components/Componentes/female-outcome-sheet.tsx create mode 100644 src/components/Componentes/flutter-locale-sync.tsx create mode 100644 src/components/Componentes/loading-border-spinner.tsx create mode 100644 src/components/Componentes/loading-icon-spinner.tsx create mode 100644 src/components/Componentes/loading-pulse-text.tsx create mode 100644 src/components/Componentes/loading-select-spinner.tsx create mode 100644 src/components/Componentes/loading-three-dot.tsx create mode 100644 src/components/Componentes/page-loading-skeleton.tsx diff --git a/next.config.ts b/next.config.ts index 905b4b6..7324719 100644 --- a/next.config.ts +++ b/next.config.ts @@ -39,6 +39,22 @@ const nextConfig: NextConfig = { return config; }, + // Keep bookmarked links to the removed intermediate page functional. + async redirects() { + return [ + { + source: "/candidate-contact", + destination: "/request-accepted", + permanent: false, + }, + { + source: "/:lang/candidate-contact", + destination: "/:lang/request-accepted", + permanent: false, + }, + ]; + }, + // Headers for caching and preload async headers() { return [ diff --git a/src/app/[lang]/candidate-contact/page.tsx b/src/app/[lang]/candidate-contact/page.tsx deleted file mode 100644 index 0354a6e..0000000 --- a/src/app/[lang]/candidate-contact/page.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from "@/app/candidate-contact/page"; diff --git a/src/app/api/proxy/route.ts b/src/app/api/proxy/route.ts index 28cd2bf..cecf4ca 100644 --- a/src/app/api/proxy/route.ts +++ b/src/app/api/proxy/route.ts @@ -150,15 +150,17 @@ function getRequestHeaders(request: NextRequest, targetUrl: URL) { } // Dynamically set language headers - let lang = - getCookieValue(cookieHeader, "HABIB_LANGUAGE") ?? - getCookieValue(cookieHeader, "habib_language") ?? - request.headers.get("x-user-language") ?? - request.headers.get("accept-language")?.split(",")[0]?.split("-")[0]; - - if (!isLocale(lang)) { - lang = "fa"; - } + const requestedLanguages = [ + request.headers.get("x-user-language"), + getCookieValue(cookieHeader, "HABIB_LANGUAGE"), + getCookieValue(cookieHeader, "habib_language"), + request.headers.get("accept-language")?.split(",")[0]?.split("-")[0], + ]; + const lang = + requestedLanguages.find( + (candidate): candidate is string => + candidate !== null && isLocale(candidate), + ) ?? "en"; headers.set("accept-encoding", "identity"); headers.set("accept-language", lang); diff --git a/src/app/candidate-contact/page.tsx b/src/app/candidate-contact/page.tsx index a2f6f84..aefa130 100644 --- a/src/app/candidate-contact/page.tsx +++ b/src/app/candidate-contact/page.tsx @@ -3,7 +3,8 @@ import Image from "next/image"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; -import { DotsLoader } from "@/components/Componentes/button"; +import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; +import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; import CallResultSheet from "@/components/Componentes/call-result-sheet"; import DismissReasonSheet from "@/components/Componentes/dismiss-reason-sheet"; import OutcomeSelectionSheet from "@/components/Componentes/outcome-selection-sheet"; @@ -75,14 +76,7 @@ export default function CandidateContactPage() { }; if (isProfileLoading || isRedirecting) { - return ( - <> - -
- -
- - ); + return ; } // If female, render the beautiful, customized layout matching the design @@ -123,7 +117,10 @@ export default function CandidateContactPage() { ) : null}
- +
@@ -186,7 +183,7 @@ export default function CandidateContactPage() { className="flex-1 h-[52px] rounded-[15px] bg-gradient-to-r from-[#FE6F82] to-[#E03950] text-white font-semibold group-14 flex items-center justify-center shadow-lg shadow-[#FE6F82]/30 transition-transform active:scale-[0.98] cursor-pointer hover:opacity-95 disabled:opacity-50" > {outcomeMutation.isPending ? ( - + ) : ( t["Submit Final Outcome"] )} @@ -204,7 +201,7 @@ export default function CandidateContactPage() { className="flex-1 h-[52px] rounded-[15px] bg-[#F5F5F7] text-[#8E8E93] font-semibold group-14 flex items-center justify-center transition-transform active:scale-[0.98] cursor-pointer hover:bg-[#EAEAEF] disabled:opacity-50" > {contactStatusMutation.isPending ? ( - + ) : ( t["Report No Contact"] )} @@ -223,7 +220,7 @@ export default function CandidateContactPage() { className="flex-1 h-[52px] rounded-[15px] bg-gradient-to-r from-[#FE6F82] to-[#E03950] text-white font-semibold group-14 flex items-center justify-center shadow-lg shadow-[#FE6F82]/30 transition-transform active:scale-[0.98] cursor-pointer hover:opacity-95 disabled:opacity-50" > {contactStatusMutation.isPending ? ( - + ) : ( t["Confirm Contacted"] )} @@ -349,7 +346,7 @@ export default function CandidateContactPage() { className="flex-1 h-[52px] rounded-[11px] border border-[#8B8B8B] bg-transparent text-[#8B8B8B] font-semibold flex items-center justify-center cursor-pointer transition-opacity active:opacity-90 disabled:opacity-50" > {contactStatusMutation.isPending ? ( - + ) : ( t["Report No Contact"] )} diff --git a/src/app/finding-match/page.tsx b/src/app/finding-match/page.tsx index bf9046a..ab1f991 100644 --- a/src/app/finding-match/page.tsx +++ b/src/app/finding-match/page.tsx @@ -1,21 +1,21 @@ "use client"; import Image from "next/image"; -import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useMemo } from "react"; -import Button, { DotsLoader } from "@/components/Componentes/button"; import { FaLock, FaPen } from "react-icons/fa6"; +import { IoAlertCircle } from "react-icons/io5"; import AdvisorActionsCard from "@/components/Componentes/advisor-actions-card"; -import PageHeader from "@/components/Componentes/page-header"; +import Button from "@/components/Componentes/button"; +import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; +import { FixToTheEnd } from "@/components/Componentes/fix-to-the-end"; import { PageBackground } from "@/components/Componentes/page-background"; +import PageHeader from "@/components/Componentes/page-header"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; +import { useRejectionSeenMutation } from "@/hooks/marriage/use-rejection-seen"; import { getSubmitPath } from "@/lib/get-submit-path"; import { localizePath } from "@/translations/config"; import { useI18n } from "@/translations/provider"; -import { FixToTheEnd } from "@/components/Componentes/fix-to-the-end"; -import { useRejectionSeenMutation } from "@/hooks/marriage/use-rejection-seen"; -import { IoAlertCircle } from "react-icons/io5"; const advisorAvatars = [ { id: "advisor-primary", src: "/assets/images/Avatar Image.png" }, @@ -49,25 +49,28 @@ export default function FindingMatchPage() { }, [profile]); if (isLoading || isRedirecting) { - return ( - <> - -
- -
- - ); + return ; } const copy = { title: t["SEARCH IN PROGRESS"], - description: t["Our system is actively looking for compatible partners based on your criteria. This process requires time and patience. We will notify you immediately once a profile is ready for your review."], + description: + t[ + "Our system is actively looking for compatible partners based on your criteria. This process requires time and patience. We will notify you immediately once a profile is ready for your review." + ], advisorTitle: t["Get an advisor"], - advisorDescription: t["Not sure what to do next? Our psychology section is here to guide you at every step."], + advisorDescription: + t[ + "Not sure what to do next? Our psychology section is here to guide you at every step." + ], getAdvisor: t["Get Advisor"], - editProfile: t["Edit Profile"] + editProfile: t["Edit Profile"], }; const matchImageSrc = "/assets/images/Group 1597880466.svg"; + // This notice belongs exclusively to the gentleman whose accepted request + // was later rejected by the lady. The API enforces the same rule. + const unseenRejection = + profile?.gender === "male" ? profile.unseen_rejection : null; return ( <> @@ -82,7 +85,7 @@ export default function FindingMatchPage() { rightButton={{ icon: "subscription", iconLabel: "Subscribe" }} /> -
+
-

{copy.title}

-

{copy.description}

+ {unseenRejection && ( + + )}{" "}
- - {profile?.unseen_rejection && ( -
-
-
- -
- -

- {t["Your request was rejected"]} -

- -

- {t["Your request was rejected by the lady. You will be introduced to other candidates in the future."]} -

- -
- -
-
-
- )} ); } diff --git a/src/app/new-match/page.tsx b/src/app/new-match/page.tsx index eae551b..2844ad1 100644 --- a/src/app/new-match/page.tsx +++ b/src/app/new-match/page.tsx @@ -6,11 +6,11 @@ import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import { FaLock } from "react-icons/fa6"; import AdvisorActionsCard from "@/components/Componentes/advisor-actions-card"; -import { DotsLoader } from "@/components/Componentes/button"; import PageHeader from "@/components/Componentes/page-header"; import { PageBackground } from "@/components/Componentes/page-background"; import { IoClose } from "react-icons/io5"; import { LoadingSkeleton } from "@/components/Componentes/loading-skeleton"; +import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import { useHabcoinPaymentMutation } from "@/hooks/marriage/use-habcoin-payment"; import { useRespondToMarriageCaseMutation } from "@/hooks/marriage/use-case-respond"; import type { @@ -573,9 +573,11 @@ export default function NewMatchPage() { onClick={handleDecline} >
- - {t["Decline"] || "Decline"} - + {respondMutation.isPending ? ( + + ) : ( + {t["Decline"] || "Decline"} + )}
@@ -588,21 +590,24 @@ export default function NewMatchPage() { onClick={handlePayment} >
- - {t["Pay"] || "Pay"} - - - - - 50 - + {paymentMutation.isPending ? ( + + ) : ( + <> + {t["Pay"] || "Pay"} + + + 50 + + + )}
diff --git a/src/app/new-match/profile/page.tsx b/src/app/new-match/profile/page.tsx index 8dce08c..157e643 100644 --- a/src/app/new-match/profile/page.tsx +++ b/src/app/new-match/profile/page.tsx @@ -8,10 +8,10 @@ import DismissReasonSheet from "@/components/Componentes/dismiss-reason-sheet"; import FemaleConsentSheet from "@/components/Componentes/female-consent-sheet"; import InformationSheet from "@/components/Componentes/information-sheet"; import { LoadingSkeleton } from "@/components/Componentes/loading-skeleton"; +import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import NavigationButton from "@/components/Componentes/navigation-button"; import { PageBackground } from "@/components/Componentes/page-background"; import StickyHeader from "@/components/Componentes/sticky-header"; -import SwipeButton from "@/components/Componentes/swipe-button"; import type { MarriageCaseStatus, MarriageField, @@ -182,7 +182,11 @@ function MatchPublicProfileFields({ ); } -function NewMatchProfileSkeleton() { +function NewMatchProfileSkeleton({ + hideBackButton = false, +}: { + hideBackButton?: boolean; +}) { const { dictionary: t } = useI18n(); return ( @@ -191,11 +195,15 @@ function NewMatchProfileSkeleton() {
- + {!hideBackButton ? ( + + ) : ( +
+ )}

{t["New Match"]}

@@ -210,7 +218,7 @@ function NewMatchProfileSkeleton() {
- +
@@ -243,7 +251,7 @@ function NewMatchProfileSkeleton() {
- +
@@ -359,11 +367,11 @@ export default function NewMatchProfilePage() { ); }, [profile]); - if (isLoading || !profile || isRedirecting) { - return ; - } - const isSubmitting = respondMutation.isPending; + + if (isLoading || !profile || isRedirecting || isSubmitting) { + return ; + } const isAcceptProfileEnabled = Boolean(caseId) && !isSubmitting && @@ -371,8 +379,8 @@ export default function NewMatchProfilePage() { const isRejectProfileEnabled = isAcceptProfileEnabled; const nameParts = candidateName.trim().split(/\s+/); - const firstName = nameParts[0] || ""; - const lastName = nameParts.slice(1).join(" ") || ""; + const _firstName = nameParts[0] || ""; + const _lastName = nameParts.slice(1).join(" ") || ""; const mainClass = "-mx-[17px] flex min-h-screen flex-col pb-10"; @@ -390,7 +398,11 @@ export default function NewMatchProfilePage() { isFemaleProfile ? ( (
@@ -422,7 +438,7 @@ export default function NewMatchProfilePage() { className="py-[18px] text-[18px]" onClick={close} > - {t["Cancel"]} + {t.Cancel}
@@ -455,7 +471,11 @@ export default function NewMatchProfilePage() { (
)} @@ -492,11 +512,15 @@ export default function NewMatchProfilePage() { (
)} @@ -516,50 +540,23 @@ export default function NewMatchProfilePage() { icon="warning" title={t["Rejection Warning"]} description={ -
-

- {t["Before making a final decision, please carefully review the other person's profile again completely to make an informed choice."]} -

-
- 💡 -

- {t["Please note that rejecting this case might cause a delay in recommending the next match, but there is absolutely no obligation to accept and you are fully free to choose."]} -

-
-
- -

- {t["Confirming this rejection will not result in any penalty. Instead, it simply enters your status into a 2-day decision window to finalize the case."]} -

-
-
+ t[ + "Please review the person’s full profile once more before making your final decision." + ] } buttons={({ close }) => ( -
- { +
+ +
)} @@ -594,7 +591,7 @@ export default function NewMatchProfilePage() { className="min-w-0 flex-1 text-center font-semibold text-[14px] leading-[16px] text-white" style={{ fontFamily: "'Segoe UI', sans-serif" }} > - {t["New Match"]} + {t["More detail"]}
@@ -669,13 +666,13 @@ export default function NewMatchProfilePage() { onClick={() => router.back()} className="w-full h-[52px] flex items-center justify-center rounded-[12px] bg-[#F5F5F5] text-[#36363C] font-semibold text-[16px] transition-transform active:scale-[0.98] cursor-pointer hover:bg-[#EBEBEB]" > - {t["Back"]} + {t.Back} ) : (
)} diff --git a/src/app/page.tsx b/src/app/page.tsx index da09b03..8fb58de 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ -import { redirect } from "next/navigation"; import { cookies, headers } from "next/headers"; -import { isLocale, defaultLocale } from "@/translations/config"; +import { redirect } from "next/navigation"; +import { defaultLocale, isLocale } from "@/translations/config"; export const dynamic = "force-dynamic"; diff --git a/src/app/providers.tsx b/src/app/providers.tsx index 0e213f9..11b50bc 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -6,6 +6,7 @@ import { useQueryClient, } from "@tanstack/react-query"; import { type ReactNode, useEffect, useState } from "react"; +import FlutterLocaleSync from "@/components/Componentes/flutter-locale-sync"; import { ViewPaddingsProvider } from "@/components/Componentes/view-paddings-provider"; function AppFocusReloader({ children }: { children: ReactNode }) { @@ -86,6 +87,7 @@ export default function Providers({ children }: ProvidersProps) { + {children} diff --git a/src/app/questions-list/[slug]/question-detail-client.tsx b/src/app/questions-list/[slug]/question-detail-client.tsx index ef3fea6..66c92d6 100644 --- a/src/app/questions-list/[slug]/question-detail-client.tsx +++ b/src/app/questions-list/[slug]/question-detail-client.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; -import { DotsLoader } from "@/components/Componentes/button"; +import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; import NavigationButton from "@/components/Componentes/navigation-button"; import { PageBackground } from "@/components/Componentes/page-background"; import { @@ -622,14 +622,7 @@ export default function QuestionDetailClient({ }, [isProfileLoading, item, profileContext, questionsListHref, router]); if (isProfileLoading && item) { - return ( - <> - -
- -
- - ); + return ; } else if ( !item || !isQuestionListItemVisibleForProfile(item, profileContext) @@ -646,14 +639,7 @@ export default function QuestionDetailClient({ : false; if (isQuestionsLoading) { - return ( - <> - -
- -
- - ); + return ; } const activeTestQuestions = isCattellSlug diff --git a/src/app/questions-list/page.tsx b/src/app/questions-list/page.tsx index 97e530b..2083d3f 100644 --- a/src/app/questions-list/page.tsx +++ b/src/app/questions-list/page.tsx @@ -262,7 +262,7 @@ export default function QuestionsListPage() {
{/* Required Steps Card Skeleton */} - + {/* Section Cards Skeletons */}
diff --git a/src/app/request-accepted/page.tsx b/src/app/request-accepted/page.tsx index 6121206..0c6518c 100644 --- a/src/app/request-accepted/page.tsx +++ b/src/app/request-accepted/page.tsx @@ -5,14 +5,17 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import AdvisorActionsCard from "@/components/Componentes/advisor-actions-card"; -import { DotsLoader } from "@/components/Componentes/button"; import CallResultSheet from "@/components/Componentes/call-result-sheet"; import DismissReasonSheet from "@/components/Componentes/dismiss-reason-sheet"; import FemaleConsentSheet from "@/components/Componentes/female-consent-sheet"; +import FemaleOutcomeSheet from "@/components/Componentes/female-outcome-sheet"; +import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import OutcomeSelectionSheet from "@/components/Componentes/outcome-selection-sheet"; import { PageBackground } from "@/components/Componentes/page-background"; import PageHeader from "@/components/Componentes/page-header"; +import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; import SubscriptionRequiredSheet from "@/components/Componentes/subscription-required-sheet"; +import SwipeButton from "@/components/Componentes/swipe-button"; import type { MarriageField, MarriagePhoneFieldValue, @@ -176,27 +179,62 @@ export default function RequestAcceptedPage() { const [isContactInfoSheetOpen, setIsContactInfoSheetOpen] = useState(false); const [isSubscriptionSheetOpen, setIsSubscriptionSheetOpen] = useState(false); const [isOutcomeSheetOpen, setIsOutcomeSheetOpen] = useState(false); + const [isTimeElapsed, setIsTimeElapsed] = useState(false); + const [isNoContactConfirmOpen, setIsNoContactConfirmOpen] = useState(false); + const [isContactReceivedConfirmOpen, setIsContactReceivedConfirmOpen] = + useState(false); + const [noContactReportedSuccess, setNoContactReportedSuccess] = + useState(false); const profileHref = localizePath("/new-match/profile", locale); const { data: profile, isLoading } = useMarriageProfileQuery({ refetchInterval: 3000, }); + const isFemaleProfile = profile?.gender === "female"; + const contactSharedAtStr = profile?.active_case?.contact_shared_at; + useEffect(() => { - if (!profile) { + if (!profile || noContactReportedSuccess) { return; } const targetPath = getSubmitPath(profile); if (targetPath !== "/request-accepted") { router.replace(localizePath(targetPath, locale)); } - }, [profile, router, locale]); + }, [profile, router, locale, noContactReportedSuccess]); + + useEffect(() => { + if (!isFemaleProfile || !contactSharedAtStr) { + setIsTimeElapsed(true); + return; + } + + const checkTime = () => { + const contactSharedTime = new Date(contactSharedAtStr).getTime(); + const elapsed = Date.now() - contactSharedTime; + const isPast = elapsed >= 2 * 60 * 1000; + setIsTimeElapsed(isPast); + return isPast; + }; + + const isPast = checkTime(); + if (isPast) return; + + const timer = setInterval(() => { + const isPastNow = checkTime(); + if (isPastNow) { + clearInterval(timer); + } + }, 1000); + + return () => clearInterval(timer); + }, [isFemaleProfile, contactSharedAtStr]); const isRedirecting = useMemo(() => { if (!profile) return false; return getSubmitPath(profile) !== "/request-accepted"; }, [profile]); - const isFemaleProfile = profile?.gender === "female"; const caseId = profile?.active_case?.case_id; const caseStatus = profile?.active_case?.status; const recommendedPlanId = profile?.recommended_plan?.id; @@ -205,8 +243,14 @@ export default function RequestAcceptedPage() { const contactStatusMutation = useSubmitMarriageContactStatusMutation( caseId ?? "", { - onSuccess: () => { - router.push(localizePath("/finding-match", locale)); + onSuccess: (_data, variables) => { + if (variables?.action === "no_contact") { + setNoContactReportedSuccess(true); + } else { + if (!isFemaleProfile) { + router.push(localizePath("/finding-match", locale)); + } + } }, }, ); @@ -215,14 +259,7 @@ export default function RequestAcceptedPage() { }); if (isLoading || isRedirecting) { - return ( - <> - -
- -
- - ); + return ; } const titleText = isFemaleProfile @@ -231,10 +268,10 @@ export default function RequestAcceptedPage() { ? t["Contact info released"] : t["Request approved!"]; const primaryActionText = isFemaleProfile - ? t["Report no contact"] + ? t["No Contact Received"] : t["View profile"]; const secondaryActionText = isFemaleProfile - ? t["Record call result"] + ? t["Contact Received"] : caseStatus === "payment_done" || caseStatus === "contacted" ? t["View contact number"] : t["Pay and get contact"]; @@ -244,7 +281,7 @@ export default function RequestAcceptedPage() { const handleSecondaryAction = async () => { if (isFemaleProfile) { - setIsCallResultSheetOpen(true); + setIsContactReceivedConfirmOpen(true); return; } @@ -323,6 +360,35 @@ export default function RequestAcceptedPage() { /> ) : null} + {isContactReceivedConfirmOpen ? ( + + {t["Are you sure contact has been made?"]} +

+ } + buttons={ + { + setIsContactReceivedConfirmOpen(false); + if (caseId) { + await contactStatusMutation.mutateAsync({ + action: "contacted", + custom_note: + "Contact received confirmed by female candidate", + }); + } + }} + /> + } + onClose={() => setIsContactReceivedConfirmOpen(false)} + closeOnOutside={true} + /> + ) : null} + {isDismissReasonSheetOpen ? ( setIsDismissReasonSheetOpen(false)} @@ -338,26 +404,51 @@ export default function RequestAcceptedPage() { ) : null} {isOutcomeSheetOpen ? ( - setIsOutcomeSheetOpen(false)} - onSubmit={async (status) => { - if (status === "success") { - if (caseId) { - await outcomeMutation.mutateAsync({ - status: "success", - }); + isFemaleProfile ? ( + setIsOutcomeSheetOpen(false)} + onSubmit={async (status, reason) => { + if (status === "success") { + // If they confirm they are in the acquaintance/proposal process and nothing is finalized yet: + // No change is made to the profile, we just close the sheet. + setIsOutcomeSheetOpen(false); + } else { + // If they cancel: + if (caseId) { + await outcomeMutation.mutateAsync({ + status: "failure", + custom_note: reason, + }); + } } - } else { - setIsDismissReasonSheetOpen(true); - } - }} - /> + }} + /> + ) : ( + setIsOutcomeSheetOpen(false)} + onSubmit={async (status) => { + if (status === "success") { + if (caseId) { + await outcomeMutation.mutateAsync({ + status: "success", + }); + } + } else { + setIsDismissReasonSheetOpen(true); + } + }} + /> + ) ) : null} {isContactInfoSheetOpen ? ( @@ -383,6 +474,35 @@ export default function RequestAcceptedPage() { /> ) : null} + {isNoContactConfirmOpen ? ( + + { + t[ + "No contact has been made with you in any way or by any party." + ] + } +

+ } + buttons={ + + } + onClose={() => setIsNoContactConfirmOpen(false)} + closeOnOutside={true} + /> + ) : null} +
@@ -397,7 +517,11 @@ export default function RequestAcceptedPage() { {t["Congratulations! 🎉"]}

- {t["Wishing you a lifetime of love, joy, and happiness. Your profile has been successfully closed."]} + { + t[ + "Wishing you a lifetime of love, joy, and happiness. Your profile has been successfully closed." + ] + }

) : ( @@ -417,96 +541,170 @@ export default function RequestAcceptedPage() { {titleText} - {caseStatus === "contacted" ? ( -
-

- {t["Thank you for giving us feedback, we would be very happy if you also let us know the final result."]} -

+ {caseStatus === "contacted" || + (isFemaleProfile && contactStatusMutation.isPending) ? ( +
+ {isFemaleProfile && contactStatusMutation.isPending ? ( + + ) : ( +

+ {isFemaleProfile + ? t[ + "Thank you for your feedback. To complete the process, please submit the final outcome of this introduction/contact so that the final status can be determined. If the final status is not yet determined, you can stay in this state until it is finalized." + ] + : t[ + "Thank you for giving us feedback, we would be very happy if you also let us know the final result." + ]} +

+ )}
) : (

{isFemaleProfile - ? t["The selected candidate will contact your family shortly."] - : t["You can now view their family's contact details and arrange further steps."]} + ? t[ + "The selected candidate will contact your family shortly." + ] + : t[ + "You can now view their family's contact details and arrange further steps." + ]}

)} - {caseStatus === "contacted" ? ( -
- - - +

) : ( -
- {isFemaleProfile ? ( - + ) : ( + <> + + + + + )} +
+ ) : ( +
+ {isFemaleProfile ? ( + + ) : ( + +
+ {primaryActionText} +
+ + )} + +
- - ) : ( - -
- {primaryActionText} -
- + +
)} - -
+ ) : null} + )} - - {caseStatus !== "contacted" ? ( -
-

- {t["Please be informed that from the time of this introduction, you have 48 hours (2 days) to contact the person or their respected family to declare your readiness and begin the acquaintance process. At this stage, merely an initial call to announce your presence is sufficient, and planning for further steps (such as an in-person meeting) depends entirely on your subsequent mutual agreements. Since failing to make contact within the specified time might be considered socially disrespectful, if no action is taken within these 2 days, the introduced match will be removed according to the platform's rules. We also remind you that this issue may lead to restrictions such as delays in future introductions and financial penalties."]} -

-
- ) : null} )} @@ -515,7 +713,11 @@ export default function RequestAcceptedPage() { {/* Advisor section */} - -
- -
- - ); + return ; } const copy = { diff --git a/src/components/Componentes/button.tsx b/src/components/Componentes/button.tsx index 5efce95..1f43d0b 100644 --- a/src/components/Componentes/button.tsx +++ b/src/components/Componentes/button.tsx @@ -11,6 +11,7 @@ import { import { GoArrowRight } from "react-icons/go"; import { localizePath } from "@/translations/config"; import { useI18n } from "@/translations/provider"; +import { LoadingThreeDot } from "./loading-three-dot"; type ButtonVariant = | "default" @@ -33,18 +34,6 @@ export type ButtonProps = Omit< isLoading?: boolean; }; -export function DotsLoader({ className = "" }: { className?: string }) { - return ( - - - - - - ); -} - const FILLED_STROKE = "#FFFFFF"; const EMPTY_STROKE = "rgba(255, 255, 255, 0.5)"; const RADIUS = 18; @@ -171,7 +160,7 @@ export function Button({ className={baseClassName} > {isLoading ? ( - + ) : ( {renderArrow("left")} diff --git a/src/components/Componentes/dev-click-to-component.tsx b/src/components/Componentes/dev-click-to-component.tsx index a808144..0868491 100644 --- a/src/components/Componentes/dev-click-to-component.tsx +++ b/src/components/Componentes/dev-click-to-component.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect, useRef, useState } from "react"; +import { LoadingThreeDot } from "./loading-three-dot"; const IDE_SCHEMES = [ { @@ -596,27 +597,7 @@ export function DevClickToComponent() { title="Send LocalStorage to Terminal API" > {isSendingStorage ? ( - - - - + ) : sendSuccess ? ( -
-
+
+

{t["Dismiss reasons"]}

@@ -157,12 +157,15 @@ export function DismissReasonSheet({ {t["Please provide the full reason for rejecting the submitted item"]}

-
+
{t["Dismiss reasons"]} -
+
{options.map((option) => { const checked = selectedReasons.includes(option); const showTextArea = option === options[options.length - 1]; @@ -239,7 +242,7 @@ export function DismissReasonSheet({
-
+
+ + {isReasonChecked && isOtherReason ? ( +