forked from 77media/video-flow
139 lines
4.4 KiB
TypeScript
139 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useRef } from 'react';
|
|
import { createRoot, Root } from 'react-dom/client';
|
|
import { BadgeCent } from 'lucide-react';
|
|
|
|
interface InsufficientDetail {
|
|
current_balance?: number;
|
|
required_tokens?: number;
|
|
message?: string;
|
|
}
|
|
|
|
interface InsufficientModalProps {
|
|
detail?: InsufficientDetail;
|
|
onClose?: () => void;
|
|
}
|
|
|
|
function InsufficientPointsModal(props: InsufficientModalProps) {
|
|
const { detail, onClose } = props;
|
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
const originalOverflow = document.body.style.overflow;
|
|
document.body.style.overflow = 'hidden';
|
|
return () => {
|
|
document.body.style.overflow = originalOverflow;
|
|
};
|
|
}, []);
|
|
|
|
const titleText = 'Insufficient credits reminder';
|
|
const descriptionText =
|
|
detail?.message || 'Your credits are insufficient, please upgrade to continue.';
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
data-alt="insufficient-overlay"
|
|
className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/60"
|
|
>
|
|
<div
|
|
data-alt="insufficient-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"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="insufficient-title"
|
|
>
|
|
<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 text-[#ab50d0]">
|
|
<BadgeCent />
|
|
</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.
|
|
*/
|
|
export function showInsufficientPointsNotification(detail?: InsufficientDetail): void {
|
|
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} />
|
|
);
|
|
}
|
|
|