feat: enhance StripePaymentDialog with document handling and portal rendering

This commit is contained in:
2026-06-29 18:27:53 +08:00
parent 9ad17379e8
commit 3b88869a57
@@ -4,7 +4,8 @@
* *
* 后端当前返回 PaymentIntent clientSecret,因此这里直接用它完成前端确认。 * 后端当前返回 PaymentIntent clientSecret,因此这里直接用它完成前端确认。
*/ */
import { useState, type FormEvent } from "react"; import { useEffect, useState, type FormEvent } from "react";
import { createPortal } from "react-dom";
import { import {
Elements, Elements,
PaymentElement, PaymentElement,
@@ -35,31 +36,44 @@ export function StripePaymentDialog({
onClose, onClose,
onConfirmed, onConfirmed,
}: StripePaymentDialogProps) { }: StripePaymentDialogProps) {
if (!stripePromise) { useEffect(() => {
return ( if (typeof document === "undefined") return;
<div className={styles.overlay} role="alertdialog" aria-modal="true">
<div className={styles.dialog}> const prevOverflow = document.body.style.overflow;
<div className={styles.header}> const handleKeyDown = (event: KeyboardEvent) => {
<h2 className={styles.title}>Payment unavailable</h2> if (event.key === "Escape") onClose();
<p className={styles.content}> };
Stripe publishable key is not configured for this build.
</p> document.body.style.overflow = "hidden";
</div> document.addEventListener("keydown", handleKeyDown);
<div className={styles.actions}>
<button return () => {
type="button" document.body.style.overflow = prevOverflow;
className={`${styles.button} ${styles.primary}`} document.removeEventListener("keydown", handleKeyDown);
onClick={onClose} };
> }, [onClose]);
OK
</button> const dialog = !stripePromise ? (
</div> <div className={styles.overlay} role="alertdialog" aria-modal="true">
<div className={styles.dialog}>
<div className={styles.header}>
<h2 className={styles.title}>Payment unavailable</h2>
<p className={styles.content}>
Stripe publishable key is not configured for this build.
</p>
</div>
<div className={styles.actions}>
<button
type="button"
className={`${styles.button} ${styles.primary}`}
onClick={onClose}
>
OK
</button>
</div> </div>
</div> </div>
); </div>
} ) : (
return (
<div <div
className={styles.overlay} className={styles.overlay}
role="dialog" role="dialog"
@@ -95,6 +109,10 @@ export function StripePaymentDialog({
</div> </div>
</div> </div>
); );
if (typeof document === "undefined") return null;
return createPortal(dialog, document.body);
} }
function StripePaymentForm({ function StripePaymentForm({