import { ExternalLink, Modal, TabPanel, Button } from '@wordpress/components'; import { useState, useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import PageAnalyticsOverview from './PageAnalyticsOverview'; import PageABTesting from './PageABTesting'; import PagePersonalization from './PagePersonalization'; import { decodeHtmlEntities } from './utils'; export default function PageAnalytics( props ) { const { buttons, onClose, data } = props; const [ dataCache, setDataCache ] = useState( {} ); const tabListRef = useRef( null ); const isInitialRenderRef = useRef( true ); const getCachedData = ( endpoint ) => { return dataCache[ endpoint ] || null; }; const setCachedData = ( endpoint, newData, error = null ) => { setDataCache( ( prevCache ) => ( { ...prevCache, [ endpoint ]: { data: newData, error, isFetched: true }, } ) ); }; const [ postId, setPostId ] = useState( data?.postId ?? null ); const [ postTitle, setPostTitle ] = useState( data?.title ?? null ); const [ postPermalink, setPostPermalink ] = useState( data?.permalink ?? null ); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [ postStatusLabel, setPostStatusLabel ] = useState( data?.statusLabel ?? null ); const [ postDateString, setPostDateString ] = useState( data?.date ?? null ); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [ postIsPublished, setPostIsPublished ] = useState( data?.isPublished ?? false ); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [ endpointPageOverview, setEndpointPageOverview ] = useState( data?.endpointPageOverview ?? null ); const [ hasABTests, setHasABTests ] = useState( data?.hasABTests ?? false ); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [ hasPersonalizations, setHasPersonalizations ] = useState( data?.hasPersonalizations ?? false ); const [ personalizationCount, setPersonalizationCount ] = useState( data?.personalizationCount ?? 0 ); const [ abTestId, setABTestId ] = useState( data?.abTestId ?? null ); const [ abTestPublishedAt, setABTestPublishedAt ] = useState( data?.abTestPublishedAt ?? null ); const [ supportsABTesting, setSupportsABTesting ] = useState( data?.supportsABTesting ?? false ); const [ supportsPersonalization, setSupportsPersonalization ] = useState( data?.supportsPersonalization ?? false ); const [ editLink, setEditLink ] = useState( data?.editLink ?? '' ); const [ activeTab, setActiveTab ] = useState( data?.activeTab ?? 'overview' ); const [ isOpen, setOpen ] = useState( props.isOpen ?? false ); // Update CSS indicator variables when active tab changes useEffect( () => { if ( ! tabListRef.current || ! isOpen ) { return; } const tabList = tabListRef.current; const updateIndicator = () => { if ( ! tabList ) { return; } const activeButton = tabList.querySelector( 'button.active-tab' ); if ( ! activeButton ) { return; } const tabListRect = tabList.getBoundingClientRect(); const buttonRect = activeButton.getBoundingClientRect(); // Account for the 32px padding on the tablist const paddingLeft = 32; const left = buttonRect.left - tabListRect.left + paddingLeft; const right = tabListRect.right + buttonRect.right; const width = buttonRect.width; // Disable transitions for initial render if ( isInitialRenderRef.current ) { const tabListElement = tabList.querySelector( '[role="tablist"]' ); if ( tabListElement ) { tabListElement.setAttribute( 'data-disable-transition', 'true' ); } } tabList.style.setProperty( '--indicator-left', String( left ) ); tabList.style.setProperty( '--indicator-right', String( right ) ); tabList.style.setProperty( '--indicator-width', String( width ) ); // Re-enable transitions after initial render if ( isInitialRenderRef.current ) { setTimeout( () => { requestAnimationFrame( () => { const tabListElement = tabList.querySelector( '[role="tablist"]' ); if ( tabListElement ) { tabListElement.removeAttribute( 'data-disable-transition' ); } isInitialRenderRef.current = false; } ); }, 100 ); } }; // Use MutationObserver to detect when active-tab class is applied const observer = new MutationObserver( () => { updateIndicator(); } ); // Observe the tab list for class changes on buttons const tabButtons = tabList.querySelectorAll( 'button' ); tabButtons.forEach( ( button ) => { observer.observe( button, { attributes: true, attributeFilter: [ 'class' ], } ); } ); // Initial update with multiple attempts to ensure DOM is ready updateIndicator(); const timeoutId1 = setTimeout( updateIndicator, 0 ); const timeoutId2 = setTimeout( updateIndicator, 50 ); const timeoutId3 = setTimeout( updateIndicator, 100 ); // Update on window resize window.addEventListener( 'resize', updateIndicator ); return () => { observer.disconnect(); clearTimeout( timeoutId1 ); clearTimeout( timeoutId2 ); clearTimeout( timeoutId3 ); window.removeEventListener( 'resize', updateIndicator ); }; }, [ activeTab, isOpen ] ); const openModal = () => setOpen( true ); const closeModal = () => { setOpen( false ); if ( typeof onClose === 'function' ) { onClose(); } }; if ( buttons && buttons.length > 0 ) { buttons.forEach( ( growthstackButton ) => { growthstackButton.addEventListener( 'click', ( event ) => { event.preventDefault(); const args = JSON.parse( growthstackButton.dataset.args ); setPostId( args.postId ); setPostTitle( args.title ); setPostPermalink( args.permalink ); setPostStatusLabel( args.statusLabel ); setPostIsPublished( args.isPublished ); setPostDateString( args.date ); setEndpointPageOverview( args.endpointPageOverview ); setHasABTests( args.hasABTests ); setPersonalizationCount( args.personalizationCount || 0 ); setABTestId( args.abTestId ); setABTestPublishedAt( args.abTestPublishedAt ); setEditLink( args.editLink ); setActiveTab( args.activeTab ); setSupportsABTesting( args.supportsABTesting ); setSupportsPersonalization( args.supportsPersonalization ); openModal(); } ); } ); } if ( ! postId ) { return null; } const tabs = [ { name: 'overview', title: __( 'Page Analytics', 'liana-with-growthstack' ), content: ( ), }, ]; if ( supportsPersonalization ) { tabs.push( { name: 'personalization', title: ( { __( 'Personalization', 'liana-with-growthstack' ) } { personalizationCount > 0 && ( { personalizationCount } ) } ), content: ( ), } ); } if ( supportsABTesting ) { tabs.push( { name: 'abtest', title: ( { __( 'A/B Tests', 'liana-with-growthstack' ) } { hasABTests && 1 } ), content: ( ), } ); } return ( <> { isOpen && ( { editLink && ( { __( 'Edit', 'liana-with-growthstack' ) } ) } { /* { postPermalink && ( { __( 'View', 'liana-with-growthstack' ) } )} */ } } > { /* { postTitle } { postStatusLabel } */ } { __( 'URL:', 'liana-with-growthstack' ) } { postPermalink } { __( 'Published:', 'liana-with-growthstack' ) } { postDateString } setActiveTab( tabName ) } > { ( tab ) => ( { tab.content } ) } ) } > ); }