123 lines
3.2 KiB
TypeScript
123 lines
3.2 KiB
TypeScript
/**
|
|
* PWA Loading Overlay Component
|
|
*
|
|
* Displays a "We prepare a demo" overlay during first load
|
|
* with pulsating animation and progress tracking for asset preloading.
|
|
* Uses project theme colors from theme_config_json when available.
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
type PWALoadingOverlayProps = {
|
|
isVisible: boolean;
|
|
progress: number; // 0-100
|
|
projectName?: string;
|
|
themeConfig?: {
|
|
primaryColor?: string;
|
|
backgroundColor?: string;
|
|
textColor?: string;
|
|
};
|
|
onComplete?: () => void;
|
|
};
|
|
|
|
const PWALoadingOverlay: React.FC<PWALoadingOverlayProps> = ({
|
|
isVisible,
|
|
progress,
|
|
projectName,
|
|
themeConfig,
|
|
onComplete,
|
|
}) => {
|
|
const [shouldRender, setShouldRender] = useState(isVisible);
|
|
|
|
// Handle fade-out animation before unmounting
|
|
useEffect(() => {
|
|
if (!isVisible && shouldRender) {
|
|
const timer = setTimeout(() => {
|
|
setShouldRender(false);
|
|
onComplete?.();
|
|
}, 500); // Match CSS transition duration
|
|
return () => clearTimeout(timer);
|
|
}
|
|
if (isVisible) {
|
|
setShouldRender(true);
|
|
}
|
|
}, [isVisible, shouldRender, onComplete]);
|
|
|
|
if (!shouldRender) return null;
|
|
|
|
const primaryColor = themeConfig?.primaryColor || '#3b82f6';
|
|
const backgroundColor = themeConfig?.backgroundColor || '#1f2937';
|
|
const textColor = themeConfig?.textColor || '#ffffff';
|
|
|
|
return (
|
|
<div
|
|
className={`fixed inset-0 z-[9999] flex flex-col items-center justify-center transition-opacity duration-500 ${
|
|
isVisible ? 'opacity-100' : 'opacity-0 pointer-events-none'
|
|
}`}
|
|
style={{ backgroundColor }}
|
|
>
|
|
{/* Pulsating logo/spinner */}
|
|
<div
|
|
className='mb-8 h-20 w-20 rounded-full animate-pulse'
|
|
style={{
|
|
backgroundColor: primaryColor,
|
|
boxShadow: `0 0 60px ${primaryColor}40`,
|
|
}}
|
|
/>
|
|
|
|
{/* Project name */}
|
|
{projectName && (
|
|
<h1
|
|
className='mb-4 text-2xl font-bold tracking-wide'
|
|
style={{ color: textColor }}
|
|
>
|
|
{projectName}
|
|
</h1>
|
|
)}
|
|
|
|
{/* Loading message */}
|
|
<p
|
|
className='mb-6 text-lg animate-pulse'
|
|
style={{ color: textColor, opacity: 0.9 }}
|
|
>
|
|
We prepare a demo
|
|
</p>
|
|
|
|
{/* Progress bar */}
|
|
<div
|
|
className='w-64 h-2 rounded-full overflow-hidden'
|
|
style={{ backgroundColor: `${textColor}20` }}
|
|
>
|
|
<div
|
|
className='h-full rounded-full transition-all duration-300 ease-out'
|
|
style={{
|
|
width: `${Math.min(100, Math.max(0, progress))}%`,
|
|
backgroundColor: primaryColor,
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* Progress percentage */}
|
|
<p className='mt-3 text-sm' style={{ color: textColor, opacity: 0.7 }}>
|
|
{Math.round(progress)}%
|
|
</p>
|
|
|
|
{/* Loading indicator dots */}
|
|
<div className='mt-8 flex gap-2'>
|
|
{[0, 1, 2].map((index) => (
|
|
<div
|
|
key={index}
|
|
className='h-2 w-2 rounded-full animate-bounce'
|
|
style={{
|
|
backgroundColor: primaryColor,
|
|
animationDelay: `${index * 0.15}s`,
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PWALoadingOverlay;
|