From 3b88869a5729094e0e666a8b7b0295a8d58aee20 Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 29 Jun 2026 18:27:53 +0800 Subject: [PATCH] feat: enhance StripePaymentDialog with document handling and portal rendering --- .../components/stripe-payment-dialog.tsx | 66 ++++++++++++------- 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/src/app/subscription/components/stripe-payment-dialog.tsx b/src/app/subscription/components/stripe-payment-dialog.tsx index f0fc66ec..12c093aa 100644 --- a/src/app/subscription/components/stripe-payment-dialog.tsx +++ b/src/app/subscription/components/stripe-payment-dialog.tsx @@ -4,7 +4,8 @@ * * 后端当前返回 PaymentIntent clientSecret,因此这里直接用它完成前端确认。 */ -import { useState, type FormEvent } from "react"; +import { useEffect, useState, type FormEvent } from "react"; +import { createPortal } from "react-dom"; import { Elements, PaymentElement, @@ -35,31 +36,44 @@ export function StripePaymentDialog({ onClose, onConfirmed, }: StripePaymentDialogProps) { - if (!stripePromise) { - return ( -
-
-
-

Payment unavailable

-

- Stripe publishable key is not configured for this build. -

-
-
- -
+ useEffect(() => { + if (typeof document === "undefined") return; + + const prevOverflow = document.body.style.overflow; + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") onClose(); + }; + + document.body.style.overflow = "hidden"; + document.addEventListener("keydown", handleKeyDown); + + return () => { + document.body.style.overflow = prevOverflow; + document.removeEventListener("keydown", handleKeyDown); + }; + }, [onClose]); + + const dialog = !stripePromise ? ( +
+
+
+

Payment unavailable

+

+ Stripe publishable key is not configured for this build. +

+
+
+
- ); - } - - return ( +
+ ) : (
); + + if (typeof document === "undefined") return null; + + return createPortal(dialog, document.body); } function StripePaymentForm({