From b8ee286b76f1da35e67674ad7dac24124bf1410a Mon Sep 17 00:00:00 2001 From: chenhang Date: Thu, 25 Jun 2026 17:03:09 +0800 Subject: [PATCH] fix(subscription): sync vip state before chat return --- src/app/subscription/subscription-screen.tsx | 74 +++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/src/app/subscription/subscription-screen.tsx b/src/app/subscription/subscription-screen.tsx index 2a7adcaf..7e7f36c6 100644 --- a/src/app/subscription/subscription-screen.tsx +++ b/src/app/subscription/subscription-screen.tsx @@ -68,8 +68,11 @@ export function SubscriptionScreen({ const refreshedPaidOrderRef = useRef(null); const resumedPendingOrderRef = useRef(null); const successDialogShownOrderRef = useRef(null); + const syncedVipOrderRef = useRef(null); const [showPaymentSuccessDialog, setShowPaymentSuccessDialog] = useState(false); + const [pendingChatReturnAfterVipSync, setPendingChatReturnAfterVipSync] = + useState(false); useEffect(() => { if (payment.status === "idle") { @@ -84,6 +87,35 @@ export function SubscriptionScreen({ userDispatch({ type: "UserFetch" }); }, [payment.currentOrderId, payment.isPaid, userDispatch]); + useEffect(() => { + const currentUser = user.currentUser; + const canMarkVipSynced = + subscriptionType === "vip" && + payment.isPaid && + payment.currentOrderId && + payment.vipStatus?.isVip === true && + currentUser && + currentUser.isVip !== true; + if (!canMarkVipSynced) return; + if (syncedVipOrderRef.current === payment.currentOrderId) return; + + syncedVipOrderRef.current = payment.currentOrderId; + userDispatch({ + type: "UserUpdate", + user: { + ...currentUser, + isVip: true, + }, + }); + }, [ + payment.currentOrderId, + payment.isPaid, + payment.vipStatus?.isVip, + subscriptionType, + user.currentUser, + userDispatch, + ]); + useEffect(() => { if (!payment.isPaid || !payment.currentOrderId) return; if (successDialogShownOrderRef.current === payment.currentOrderId) return; @@ -181,7 +213,8 @@ export function SubscriptionScreen({ ? "Choose a voice package" : "Choose a subscription plan"; - const handlePaymentSuccessClose = () => { + const finishPaymentSuccessClose = () => { + setPendingChatReturnAfterVipSync(false); setShowPaymentSuccessDialog(false); paymentDispatch({ type: "PaymentReset" }); if (returnTo === "chat") { @@ -189,6 +222,45 @@ export function SubscriptionScreen({ } }; + const handlePaymentSuccessClose = () => { + const shouldWaitForVipSync = + returnTo === "chat" && + subscriptionType === "vip" && + user.currentUser?.isVip !== true; + + if (shouldWaitForVipSync) { + setPendingChatReturnAfterVipSync(true); + if (payment.vipStatus?.isVip === true && user.currentUser) { + userDispatch({ + type: "UserUpdate", + user: { + ...user.currentUser, + isVip: true, + }, + }); + } else { + userDispatch({ type: "UserFetch" }); + } + return; + } + + finishPaymentSuccessClose(); + }; + + useEffect(() => { + if (!pendingChatReturnAfterVipSync) return; + if (subscriptionType === "vip" && user.currentUser?.isVip !== true) return; + + const timer = window.setTimeout(() => { + finishPaymentSuccessClose(); + }, 0); + + return () => window.clearTimeout(timer); + // `finishPaymentSuccessClose` intentionally stays local to this component; + // this effect is gated by pendingChatReturnAfterVipSync to avoid repeated closes. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pendingChatReturnAfterVipSync, subscriptionType, user.currentUser?.isVip]); + useEffect(() => { if (payment.isLoadingPlans || payment.plans.length === 0) return; if (selectedPlan !== null) return;