forked from 77media/video-flow
优化队列样式
This commit is contained in:
parent
9e52b1c816
commit
5644f3f11b
@ -1,5 +1,5 @@
|
|||||||
import { ApiResponse } from './common';
|
import { ApiResponse } from './common';
|
||||||
import { showQueueNotification } from '../components/QueueBox/QueueNotification2';
|
import { showH5QueueNotification } from '../components/QueueBox/H5QueueNotication';
|
||||||
import { notification } from 'antd';
|
import { notification } from 'antd';
|
||||||
|
|
||||||
/** 队列状态枚举 */
|
/** 队列状态枚举 */
|
||||||
@ -66,6 +66,7 @@ export async function withQueuePolling<T>(
|
|||||||
let attempts = 0;
|
let attempts = 0;
|
||||||
let lastNotificationPosition: number | undefined;
|
let lastNotificationPosition: number | undefined;
|
||||||
let isCancelled = false;
|
let isCancelled = false;
|
||||||
|
let closeModal: (() => void) | null = null;
|
||||||
|
|
||||||
// 生成唯一的轮询ID
|
// 生成唯一的轮询ID
|
||||||
const pollId = Math.random().toString(36).substring(7);
|
const pollId = Math.random().toString(36).substring(7);
|
||||||
@ -73,7 +74,8 @@ export async function withQueuePolling<T>(
|
|||||||
// 创建取消函数
|
// 创建取消函数
|
||||||
const cancel = () => {
|
const cancel = () => {
|
||||||
isCancelled = true;
|
isCancelled = true;
|
||||||
notification.destroy(); // 关闭通知
|
try { closeModal?.(); } catch {}
|
||||||
|
notification.destroy(); // 兼容旧弹层
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
cancelTokens.delete(pollId);
|
cancelTokens.delete(pollId);
|
||||||
};
|
};
|
||||||
@ -100,7 +102,19 @@ export async function withQueuePolling<T>(
|
|||||||
// 如果位置发生变化,更新通知
|
// 如果位置发生变化,更新通知
|
||||||
if ((position !== lastNotificationPosition || status === QueueStatus.PROCESS) &&
|
if ((position !== lastNotificationPosition || status === QueueStatus.PROCESS) &&
|
||||||
position !== undefined && waiting !== undefined) {
|
position !== undefined && waiting !== undefined) {
|
||||||
showQueueNotification(position, waiting, status, cancel);
|
// 打开或更新 H5 弹窗(仅允许 Cancel 关闭,Refresh 触发刷新)
|
||||||
|
try { closeModal?.(); } catch {}
|
||||||
|
closeModal = showH5QueueNotification(
|
||||||
|
position,
|
||||||
|
waiting,
|
||||||
|
status,
|
||||||
|
cancel,
|
||||||
|
async () => {
|
||||||
|
// 触发一次立刻刷新:重置 attempts 的等待,直接递归调用 poll()
|
||||||
|
// 不关闭弹窗,由 showH5QueueNotification 保持打开
|
||||||
|
attempts = Math.max(0, attempts - 1);
|
||||||
|
}
|
||||||
|
);
|
||||||
lastNotificationPosition = position;
|
lastNotificationPosition = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,15 +134,18 @@ export async function withQueuePolling<T>(
|
|||||||
|
|
||||||
// 如果状态为ready,结束轮询
|
// 如果状态为ready,结束轮询
|
||||||
if (response.code !== 202 && response.data) {
|
if (response.code !== 202 && response.data) {
|
||||||
notification.destroy(); // 关闭通知
|
try { closeModal?.(); } catch {}
|
||||||
|
notification.destroy(); // 兼容旧弹层
|
||||||
onSuccess?.(response.data);
|
onSuccess?.(response.data);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.destroy(); // 关闭通知
|
try { closeModal?.(); } catch {}
|
||||||
|
notification.destroy(); // 兼容旧弹层
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.destroy(); // 关闭通知
|
try { closeModal?.(); } catch {}
|
||||||
|
notification.destroy(); // 兼容旧弹层
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
onError?.(error);
|
onError?.(error);
|
||||||
}
|
}
|
||||||
|
|||||||
167
components/QueueBox/H5QueueNotication.tsx
Normal file
167
components/QueueBox/H5QueueNotication.tsx
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { createRoot, Root } from 'react-dom/client';
|
||||||
|
|
||||||
|
type QueueStatus = 'waiting' | 'process' | string;
|
||||||
|
|
||||||
|
interface H5QueueNotificationProps {
|
||||||
|
position: number;
|
||||||
|
estimatedMinutes: number;
|
||||||
|
status: QueueStatus;
|
||||||
|
onCancel?: () => void;
|
||||||
|
onRefresh?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function H5QueueNotificationModal(props: H5QueueNotificationProps) {
|
||||||
|
const { position, estimatedMinutes, status, onCancel, onClose } = props;
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
// 禁用 ESC 关闭,保留空 effect 结构便于未来拓展
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Prevent background scroll on mobile while modal is open
|
||||||
|
const originalOverflow = document.body.style.overflow;
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
return () => {
|
||||||
|
document.body.style.overflow = originalOverflow;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const message = status === 'process'
|
||||||
|
? 'Your work is being produced. Please wait until it is completed before creating a new work.'
|
||||||
|
: `Your work is waiting for production at the ${position} position`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
data-alt="h5-queue-overlay"
|
||||||
|
className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/60"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-alt="h5-queue-modal"
|
||||||
|
className="relative w-11/12 max-w-sm rounded-2xl bg-white/5 border border-white/10 backdrop-blur-xl shadow-2xl p-6 text-white"
|
||||||
|
>
|
||||||
|
{/* 去除右上角关闭按钮,避免除取消以外的关闭路径 */}
|
||||||
|
|
||||||
|
<div data-alt="modal-header" className="flex flex-col items-center text-center gap-2">
|
||||||
|
<div data-alt="modal-icon" className="w-10 h-10 rounded-full bg-white/10 flex items-center justify-center text-lg">
|
||||||
|
🎬
|
||||||
|
</div>
|
||||||
|
<h3 data-alt="modal-title" className="text-base font-semibold">Queue Reminder</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-alt="modal-body" className="mt-4 space-y-4">
|
||||||
|
<p data-alt="queue-description" className="text-sm text-white/80">
|
||||||
|
{message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{status !== 'process' && (
|
||||||
|
<div data-alt="stats-grid" className="grid grid-cols-2 gap-3">
|
||||||
|
<div data-alt="stat-position" className="rounded-lg bg-white/5 border border-white/10 p-3">
|
||||||
|
<div className="text-[11px] text-white/60">Position</div>
|
||||||
|
<div className="text-sm font-medium">#{position}</div>
|
||||||
|
</div>
|
||||||
|
<div data-alt="stat-eta" className="rounded-lg bg-white/5 border border-white/10 p-3">
|
||||||
|
<div className="text-[11px] text-white/60">ETA</div>
|
||||||
|
<div className="text-sm font-medium">~{estimatedMinutes} min</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-alt="modal-actions" className="mt-6 space-y-3">
|
||||||
|
<button
|
||||||
|
data-alt="refresh-button"
|
||||||
|
className="w-full px-4 py-2 rounded-lg bg-[#9144b0] text-white font-medium transition-colors"
|
||||||
|
onClick={() => {
|
||||||
|
// 刷新仅触发回调,不关闭弹窗
|
||||||
|
if (status !== 'process') {
|
||||||
|
props.onRefresh?.();
|
||||||
|
} else {
|
||||||
|
onCancel?.();
|
||||||
|
onClose?.();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{status !== 'process' ? 'Refresh' : 'OK'}
|
||||||
|
</button>
|
||||||
|
{status !== 'process' && (
|
||||||
|
<button
|
||||||
|
data-alt="cancel-queue-button"
|
||||||
|
className="w-full text-sm text-[#ab50d0] underline underline-offset-2 decoration-[#9144b0]/60"
|
||||||
|
onClick={() => {
|
||||||
|
onCancel?.();
|
||||||
|
onClose?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel queue →
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a lightweight H5-styled dark modal queue notification.
|
||||||
|
* @param {number} position - Current queue position.
|
||||||
|
* @param {number} estimatedMinutes - Estimated waiting minutes.
|
||||||
|
* @param {QueueStatus} status - Queue status: 'waiting' | 'process' | string.
|
||||||
|
* @param {() => void} onCancel - Callback when user confirms cancel.
|
||||||
|
* @returns {() => void} - Close function to dismiss the modal programmatically.
|
||||||
|
*/
|
||||||
|
export function showH5QueueNotification(
|
||||||
|
position: number,
|
||||||
|
estimatedMinutes: number,
|
||||||
|
status: QueueStatus,
|
||||||
|
onCancel?: () => void,
|
||||||
|
onRefresh?: () => void
|
||||||
|
): () => void {
|
||||||
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const mount = document.createElement('div');
|
||||||
|
mount.setAttribute('data-alt', 'h5-queue-root');
|
||||||
|
document.body.appendChild(mount);
|
||||||
|
|
||||||
|
let root: Root | null = null;
|
||||||
|
try {
|
||||||
|
root = createRoot(mount);
|
||||||
|
} catch {
|
||||||
|
// Fallback cleanup if root creation fails
|
||||||
|
document.body.removeChild(mount);
|
||||||
|
return () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
try {
|
||||||
|
root?.unmount();
|
||||||
|
} finally {
|
||||||
|
if (mount.parentNode) {
|
||||||
|
mount.parentNode.removeChild(mount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<H5QueueNotificationModal
|
||||||
|
position={position}
|
||||||
|
estimatedMinutes={estimatedMinutes}
|
||||||
|
status={status}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onRefresh={onRefresh}
|
||||||
|
onClose={close}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return close;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -172,7 +172,10 @@ export default function H5TopBar({ onSelectHomeTab }: H5TopBarProps) {
|
|||||||
|
|
||||||
const handleCustomAmountBuy = async () => {
|
const handleCustomAmountBuy = async () => {
|
||||||
const amount = parseInt(customAmount);
|
const amount = parseInt(customAmount);
|
||||||
if (isNaN(amount) || amount <= 0) return;
|
if (isNaN(amount) || amount < 50) {
|
||||||
|
window.msg.warning("Minimum purchase is 50 credits.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
await handleBuyTokens(amount);
|
await handleBuyTokens(amount);
|
||||||
setCustomAmount("");
|
setCustomAmount("");
|
||||||
};
|
};
|
||||||
|
|||||||
@ -113,11 +113,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe
|
|||||||
const handleCustomAmountBuy = async () => {
|
const handleCustomAmountBuy = async () => {
|
||||||
const amount = parseInt(customAmount);
|
const amount = parseInt(customAmount);
|
||||||
if (isNaN(amount) || amount < 50) {
|
if (isNaN(amount) || amount < 50) {
|
||||||
showInsufficientPointsNotification({
|
window.msg.warning("Minimum purchase is 50 credits.");
|
||||||
current_balance: credits,
|
|
||||||
required_tokens: 50,
|
|
||||||
message: "Minimum purchase is 50 credits.",
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await handleBuyTokens(amount);
|
await handleBuyTokens(amount);
|
||||||
|
|||||||
@ -1,131 +1,138 @@
|
|||||||
import { notification } from 'antd';
|
"use client";
|
||||||
import { useRouter } from 'next/router';
|
|
||||||
|
|
||||||
type NotificationType = 'success' | 'info' | 'warning' | 'error';
|
import React, { useEffect, useRef } from 'react';
|
||||||
|
import { createRoot, Root } from 'react-dom/client';
|
||||||
|
import { BadgeCent } from 'lucide-react';
|
||||||
|
|
||||||
const darkGlassStyle = {
|
interface InsufficientDetail {
|
||||||
background: 'rgba(30, 32, 40, 0.95)',
|
|
||||||
backdropFilter: 'blur(10px)',
|
|
||||||
WebkitBackdropFilter: 'blur(10px)',
|
|
||||||
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
||||||
borderRadius: '8px',
|
|
||||||
boxShadow: '0 4px 16px rgba(0, 0, 0, 0.4)',
|
|
||||||
padding: '12px 16px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const messageStyle = {
|
|
||||||
fontSize: '13px',
|
|
||||||
fontWeight: 500,
|
|
||||||
color: '#ffffff',
|
|
||||||
marginBottom: '6px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '6px',
|
|
||||||
};
|
|
||||||
|
|
||||||
const iconStyle = {
|
|
||||||
color: '#F6B266', // 警告图标颜色
|
|
||||||
background: 'rgba(246, 178, 102, 0.15)',
|
|
||||||
padding: '4px',
|
|
||||||
borderRadius: '6px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
};
|
|
||||||
|
|
||||||
const descriptionStyle = {
|
|
||||||
fontSize: '12px',
|
|
||||||
color: 'rgba(255, 255, 255, 0.65)',
|
|
||||||
marginBottom: '12px',
|
|
||||||
lineHeight: '1.5',
|
|
||||||
};
|
|
||||||
|
|
||||||
const btnStyle = {
|
|
||||||
color: 'rgb(250 173 20 / 90%)',
|
|
||||||
background: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: 0,
|
|
||||||
fontSize: '12px',
|
|
||||||
fontWeight: 500,
|
|
||||||
textDecoration: 'underline',
|
|
||||||
textUnderlineOffset: '2px',
|
|
||||||
textDecorationColor: 'rgb(250 173 20 / 60%)',
|
|
||||||
transition: 'all 0.2s ease',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 显示积分不足通知
|
|
||||||
* @description 在右上角显示一个带有充值链接的积分不足提醒
|
|
||||||
*/
|
|
||||||
const INSUFFICIENT_POINTS_KEY = 'insufficient-points-notification';
|
|
||||||
|
|
||||||
export const showInsufficientPointsNotification = (detail?: {
|
|
||||||
current_balance?: number;
|
current_balance?: number;
|
||||||
required_tokens?: number;
|
required_tokens?: number;
|
||||||
message?: string;
|
message?: string;
|
||||||
}) => {
|
}
|
||||||
// 先关闭现有的通知
|
|
||||||
notification.destroy(INSUFFICIENT_POINTS_KEY);
|
|
||||||
|
|
||||||
// 每次都生成新的 key
|
|
||||||
const uniqueKey = `${INSUFFICIENT_POINTS_KEY}-${Date.now()}`;
|
|
||||||
|
|
||||||
notification.warning({
|
interface InsufficientModalProps {
|
||||||
key: uniqueKey,
|
detail?: InsufficientDetail;
|
||||||
message: null,
|
onClose?: () => void;
|
||||||
description: (
|
}
|
||||||
<div data-alt="insufficient-points-notification" style={{ minWidth: '280px' }}>
|
|
||||||
<h3 style={messageStyle}>
|
function InsufficientPointsModal(props: InsufficientModalProps) {
|
||||||
Insufficient credits reminder
|
const { detail, onClose } = props;
|
||||||
</h3>
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
<p style={descriptionStyle}>
|
|
||||||
{detail?.message || 'Your credits are insufficient, please upgrade to continue.'}
|
useEffect(() => {
|
||||||
{detail?.current_balance !== undefined && detail?.required_tokens !== undefined && (
|
const originalOverflow = document.body.style.overflow;
|
||||||
<>
|
document.body.style.overflow = 'hidden';
|
||||||
<br />
|
return () => {
|
||||||
<span style={{ color: 'rgba(255, 255, 255, 0.85)' }}>
|
document.body.style.overflow = originalOverflow;
|
||||||
Current balance: {detail.current_balance} / Required: {detail.required_tokens}
|
};
|
||||||
</span>
|
}, []);
|
||||||
</>
|
|
||||||
)}
|
const titleText = 'Insufficient credits reminder';
|
||||||
</p>
|
const descriptionText =
|
||||||
<button
|
detail?.message || 'Your credits are insufficient, please upgrade to continue.';
|
||||||
onClick={() => window.location.href = '/pricing'}
|
|
||||||
style={btnStyle}
|
return (
|
||||||
data-alt="recharge-button"
|
<div
|
||||||
>
|
ref={containerRef}
|
||||||
Upgrade to continue →
|
data-alt="insufficient-overlay"
|
||||||
</button>
|
className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/60"
|
||||||
</div>
|
>
|
||||||
),
|
<div
|
||||||
duration: 0,
|
data-alt="insufficient-modal"
|
||||||
placement: 'topRight',
|
className="relative w-11/12 max-w-sm rounded-2xl bg-white/5 border border-white/10 backdrop-blur-xl shadow-2xl p-6 text-white"
|
||||||
style: darkGlassStyle,
|
role="dialog"
|
||||||
className: 'dark-glass-notification',
|
aria-modal="true"
|
||||||
closeIcon: (
|
aria-labelledby="insufficient-title"
|
||||||
<button
|
|
||||||
className="hover:text-white"
|
|
||||||
style={{
|
|
||||||
background: 'transparent',
|
|
||||||
border: 'none',
|
|
||||||
padding: '2px',
|
|
||||||
cursor: 'pointer',
|
|
||||||
color: 'rgba(255, 255, 255, 0.45)',
|
|
||||||
transition: 'color 0.2s ease',
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<div data-alt="modal-header" className="flex flex-col items-center text-center gap-2">
|
||||||
<path d="M18 6L6 18M6 6L18 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
<div data-alt="modal-icon" className="w-10 h-10 rounded-full bg-white/10 flex items-center justify-center text-lg text-[#ab50d0]">
|
||||||
</svg>
|
<BadgeCent />
|
||||||
</button>
|
</div>
|
||||||
),
|
<h3 id="insufficient-title" data-alt="modal-title" className="text-base font-semibold">
|
||||||
});
|
{titleText}
|
||||||
};
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-alt="modal-body" className="mt-4 space-y-4">
|
||||||
|
<p data-alt="modal-description" className="text-sm text-white/80">
|
||||||
|
{descriptionText}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{(typeof detail?.current_balance !== 'undefined') && (typeof detail?.required_tokens !== 'undefined') && (
|
||||||
|
<div data-alt="stats-grid" className="grid grid-cols-2 gap-3">
|
||||||
|
<div data-alt="stat-balance" className="rounded-lg bg-white/5 border border-white/10 p-3">
|
||||||
|
<div className="text-[11px] text-white/60">Current balance</div>
|
||||||
|
<div className="text-sm font-medium">{detail?.current_balance}</div>
|
||||||
|
</div>
|
||||||
|
<div data-alt="stat-required" className="rounded-lg bg-white/5 border border-white/10 p-3">
|
||||||
|
<div className="text-[11px] text-white/60">Required</div>
|
||||||
|
<div className="text-sm font-medium">{detail?.required_tokens}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-alt="modal-actions" className="mt-6 space-y-3">
|
||||||
|
<button
|
||||||
|
data-alt="upgrade-button"
|
||||||
|
className="w-full px-4 py-2 rounded-lg bg-[#9144b0] text-white font-medium transition-colors"
|
||||||
|
onClick={() => {
|
||||||
|
window.location.href = '/pricing';
|
||||||
|
onClose?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Upgrade to continue →
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-alt="cancel-button"
|
||||||
|
className="w-full text-sm text-[#ab50d0] underline underline-offset-2 decoration-[#9144b0]/60"
|
||||||
|
onClick={() => {
|
||||||
|
onClose?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 全局配置通知样式
|
* Opens an H5-styled modal to notify insufficient credits.
|
||||||
|
* @param {InsufficientDetail} detail - optional detail including balances and message.
|
||||||
*/
|
*/
|
||||||
notification.config({
|
export function showInsufficientPointsNotification(detail?: InsufficientDetail): void {
|
||||||
maxCount: 3, // 最多同时显示3个通知
|
if (typeof window === 'undefined' || typeof document === 'undefined') {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mount = document.createElement('div');
|
||||||
|
mount.setAttribute('data-alt', 'insufficient-modal-root');
|
||||||
|
document.body.appendChild(mount);
|
||||||
|
|
||||||
|
let root: Root | null = null;
|
||||||
|
try {
|
||||||
|
root = createRoot(mount);
|
||||||
|
} catch {
|
||||||
|
if (mount.parentNode) {
|
||||||
|
mount.parentNode.removeChild(mount);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
try {
|
||||||
|
root?.unmount();
|
||||||
|
} finally {
|
||||||
|
if (mount.parentNode) {
|
||||||
|
mount.parentNode.removeChild(mount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<InsufficientPointsModal detail={detail} onClose={close} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user