import { useEffect, useState } from 'react'; import { __ } from '@wordpress/i18n'; import clsx from 'clsx'; import Icon from '@/utils/Icon'; interface PageSummaryPreviewProps { pageUrl: string; } /** * Appends a query parameter to a URL string. * * @param url - Target URL string. * @param key - Query param name. * @param value - Query param value. * @return URL string with parameter appended. */ const addQueryParam = ( url: string, key: string, value: string ): string => { if ( ! url ) { return ''; } try { const parsed = new URL( url, window.location.origin ); parsed.searchParams.set( key, value ); return parsed.toString(); } catch { const separator = url.includes( '?' ) ? '&' : '?'; return `${ url }${ separator }${ key }=${ value }`; } }; /** * Renders a progressively loaded, non-interactive preview of the selected page. * Handles loading timeouts, connection errors, and security header restrictions * gracefully with a clean skeleton fallback. * * @param props - Component properties. * @param props.pageUrl - Absolute page URL to preview. * @return The scaled page preview. */ export const PageSummaryPreview = ({ pageUrl }: PageSummaryPreviewProps ): JSX.Element => { const [ loadedPageUrl, setLoadedPageUrl ] = useState( '' ); const [ hasError, setHasError ] = useState( false ); const hasPageUrl = '' !== pageUrl; const isPreviewLoaded = hasPageUrl && loadedPageUrl === pageUrl && ! hasError; const isLoading = hasPageUrl && ! isPreviewLoaded && ! hasError; const nonce = window.burst_settings?.burst_nonce || window.burst_settings?.nonce; let iframePreviewSrc = addQueryParam( pageUrl, 'burst_preview', '1' ); iframePreviewSrc = addQueryParam( iframePreviewSrc, 'burst_force_logged_out', '1' ); if ( nonce ) { iframePreviewSrc = addQueryParam( iframePreviewSrc, 'nonce', nonce ); } // Reset error and loaded states when target page URL changes. useEffect( () => { setLoadedPageUrl( '' ); setHasError( false ); }, [ pageUrl ]); // Fallback timeout for host connection blocks, cross-origin blocks, or X-Frame-Options. useEffect( () => { if ( ! hasPageUrl || isPreviewLoaded || hasError ) { return; } const timeoutId = setTimeout( () => { setHasError( true ); }, 12000 ); return () => clearTimeout( timeoutId ); }, [ pageUrl, hasPageUrl, isPreviewLoaded, hasError ]); return (
{/* Skeleton loading indicator */} {isLoading && (
)} {/* Fallback skeleton layout if connection blocked / error occurs */} {hasError && (
{__( 'Preview unavailable', 'burst-statistics' )}
)} {/* Non-interactive sandboxed iframe */} {hasPageUrl && ! hasError && (