From e06f5bd344186dd366b249e20f9bfda59897162d Mon Sep 17 00:00:00 2001 From: mortezaei Date: Sun, 30 Aug 2026 03:42:31 +0330 Subject: [PATCH] fix: prevent premature redirects using stale initialData and improve payment error handling for Habcoin transactions --- src/app/api/proxy/route.ts | 2 +- src/app/new-match/new-match-client.tsx | 22 ++++++++++++++----- .../request-accepted-client.tsx | 20 ++++++++++++----- src/app/request-sent/request-sent-client.tsx | 7 ++++-- src/hooks/marriage/use-profile-main.ts | 5 +++++ 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/app/api/proxy/route.ts b/src/app/api/proxy/route.ts index c007bf1..c33d945 100644 --- a/src/app/api/proxy/route.ts +++ b/src/app/api/proxy/route.ts @@ -150,7 +150,7 @@ function getRequestHeaders(request: NextRequest, targetUrl: URL) { const securityKey = process.env.NEXT_PUBLIC_SECURITY_KEY || process.env.SECURITY_KEY || - "t5yugymks5458fd4ghfg6h6"; + "t5yugymks5458fd4ghfg6h6fg"; if (securityKey && !headers.has("security-key")) { headers.set("security-key", securityKey); } diff --git a/src/app/new-match/new-match-client.tsx b/src/app/new-match/new-match-client.tsx index 53fdf95..8578b76 100644 --- a/src/app/new-match/new-match-client.tsx +++ b/src/app/new-match/new-match-client.tsx @@ -489,7 +489,8 @@ export default function NewMatchClient() { useMarriageAdvisorsOverlay(); const { isProfileOpen, openProfile, closeProfile } = useMatchProfileOverlay(); const [isOpeningProfile, setIsOpeningProfile] = useState(false); - const { data: profile, isError, isLoading } = useMarriageProfileQuery(); + const { data: profile, isError, isLoading, isFetched } = + useMarriageProfileQuery(); const [isPaymentSheetOpen, setIsPaymentSheetOpen] = useState(false); const [isDeclineConfirmOpen, setIsDeclineConfirmOpen] = useState(false); const [paymentError, setPaymentError] = useState(null); @@ -523,8 +524,7 @@ export default function NewMatchClient() { }); const handlePayment = async () => { - const recommendedPlanId = profile?.recommended_plan?.id; - if (!recommendedPlanId) return; + const recommendedPlanId = profile?.recommended_plan?.id ?? 1; try { setPaymentError(null); @@ -539,9 +539,13 @@ export default function NewMatchClient() { } catch (err: any) { console.error("Payment failed", err); const msg = - err?.response?.data?.error || err?.message || "Payment failed"; + err?.response?.data?.error || + err?.response?.data?.detail || + err?.response?.data?.message || + err?.message || + "Payment failed"; const modalT = (t as any).paymentModal || {}; - if (msg === "Not enough coins") { + if (msg === "Not enough coins" || msg?.includes?.("Not enough coins")) { setIsInsufficientCoins(true); setPaymentError( modalT.insufficientCoins || @@ -575,6 +579,7 @@ export default function NewMatchClient() { useEffect(() => { console.log("🔍 [NewMatchClient] Current React Query Profile State:", { isLoading, + isFetched, isError, hasProfile: Boolean(profile), profileId: profile?.id, @@ -589,11 +594,16 @@ export default function NewMatchClient() { if (!profile) { return; } + // Don't redirect based on stale native-injected initialData that may be + // missing active_case; wait for the first real server response. + if (!isFetched && profile.status === "in_case" && !profile.active_case) { + return; + } const targetPath = getSubmitPath(profile); if (targetPath !== "/new-match") { router.replace(localizePath(targetPath, locale)); } - }, [profile, locale, router, isLoading, isError]); + }, [profile, locale, router, isLoading, isFetched, isError]); // Signal Flutter to lift its loading cover immediately with 0ms latency. useHabibWebReady(true); diff --git a/src/app/request-accepted/request-accepted-client.tsx b/src/app/request-accepted/request-accepted-client.tsx index 2354021..0e9b40d 100644 --- a/src/app/request-accepted/request-accepted-client.tsx +++ b/src/app/request-accepted/request-accepted-client.tsx @@ -243,7 +243,7 @@ export default function RequestAcceptedClient() { useState(false); const [showPaymentSuccessToast, setShowPaymentSuccessToast] = useState(false); const [isOpeningProfile, setIsOpeningProfile] = useState(false); - const { data: profile, isLoading } = useMarriageProfileQuery(); + const { data: profile, isLoading, isFetched } = useMarriageProfileQuery(); const isFemaleProfile = profile?.gender === "female"; const contactSharedAtStr = profile?.active_case?.contact_shared_at; @@ -252,11 +252,14 @@ export default function RequestAcceptedClient() { if (!profile || noContactReportedSuccess) { return; } + if (!isFetched && profile.status === "in_case" && !profile.active_case) { + return; + } const targetPath = getSubmitPath(profile); if (targetPath !== "/request-accepted") { router.replace(localizePath(targetPath, locale)); } - }, [profile, router, locale, noContactReportedSuccess]); + }, [profile, isFetched, router, locale, noContactReportedSuccess]); // Signal Flutter to lift its loading cover immediately with 0ms latency. useHabibWebReady(true); @@ -368,7 +371,8 @@ export default function RequestAcceptedClient() { }; const handlePayment = async () => { - if (!recommendedPlanId || paymentMutation.isPending) { + const planId = recommendedPlanId ?? 1; + if (paymentMutation.isPending) { return; } @@ -376,7 +380,7 @@ export default function RequestAcceptedClient() { setPaymentError(null); setIsInsufficientCoins(false); const paymentResponse = - await paymentMutation.mutateAsync(recommendedPlanId); + await paymentMutation.mutateAsync(planId); const paymentUrl = extractHabcoinPaymentUrl(paymentResponse); if (paymentUrl) { @@ -394,8 +398,12 @@ export default function RequestAcceptedClient() { } catch (err: any) { console.error("Habcoin payment request failed", err); const msg = - err?.response?.data?.error || err?.message || "Payment failed"; - if (msg === "Not enough coins") { + err?.response?.data?.error || + err?.response?.data?.detail || + err?.response?.data?.message || + err?.message || + "Payment failed"; + if (msg === "Not enough coins" || msg?.includes?.("Not enough coins")) { setIsInsufficientCoins(true); setPaymentError( t["Insufficient coin balance. Please recharge your account."] || diff --git a/src/app/request-sent/request-sent-client.tsx b/src/app/request-sent/request-sent-client.tsx index 6f1d08f..4b26ad6 100644 --- a/src/app/request-sent/request-sent-client.tsx +++ b/src/app/request-sent/request-sent-client.tsx @@ -34,7 +34,7 @@ export default function RequestSentClient() { const { isProfileOpen, openProfile, closeProfile } = useMatchProfileOverlay(); const [isOpeningProfile, setIsOpeningProfile] = useState(false); - const { data: profile, isLoading } = useMarriageProfileQuery({ + const { data: profile, isLoading, isFetched } = useMarriageProfileQuery({ refetchInterval: 3000, }); @@ -42,11 +42,14 @@ export default function RequestSentClient() { if (!profile) { return; } + if (!isFetched && profile.status === "in_case" && !profile.active_case) { + return; + } const targetPath = getSubmitPath(profile); if (targetPath !== "/request-sent") { router.replace(localizePath(targetPath, locale)); } - }, [profile, locale, router]); + }, [profile, isFetched, locale, router]); // Signal Flutter to lift its loading cover immediately with 0ms latency. useHabibWebReady(true); diff --git a/src/hooks/marriage/use-profile-main.ts b/src/hooks/marriage/use-profile-main.ts index 4cd6f59..4cb411e 100644 --- a/src/hooks/marriage/use-profile-main.ts +++ b/src/hooks/marriage/use-profile-main.ts @@ -79,6 +79,7 @@ export function useMarriageProfileQuery( return { ...old, ...initial, + active_case: initial.active_case ?? old.active_case, match_summary: initial.match_summary ?? old.match_summary, }; }); @@ -95,6 +96,10 @@ export function useMarriageProfileQuery( staleTime: 5 * 60 * 1000, refetchOnWindowFocus: false, initialData: getInitialMarriageProfile as any, + // Native-injected initialData is a snapshot from app-launch time and can be + // incomplete (e.g. missing active_case). Marking it stale forces a fresh + // fetch on mount instead of trusting it for the whole staleTime window. + initialDataUpdatedAt: 0, ...options, queryFn: getMarriageProfile, queryKey: marriageQueryKeys.profile(),