Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | import { TabPanel } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate, useParams } from 'react-router';
import useAnalytics from '../../hooks/use-analytics';
import useIsJetpackUserNew from '../../hooks/use-is-jetpack-user-new';
import { MY_JETPACK_SECTION_OVERVIEW, MY_JETPACK_SECTION_PRODUCTS } from './constants';
import { FullWidthSeparator } from './full-width-separator';
import styles from './styles.module.scss';
import { TabContent } from './tab-content';
import { MyJetpackSection } from './types';
import { getMyJetpackSections, isValidMyJetpackSection } from './utils';
import type { ReactNode } from 'react';
/**
* My Jetpack Tab panel component.
*
* @param {object} root0 - Component props.
* @param {ReactNode} root0.beforeContent - Content to render between the tab separator and tab content.
* @param {boolean} root0.productsOnly - When true, lock to the Products tab and hide the tab strip.
* @return The rendered component.
*/
export function MyJetpackTabPanel( {
beforeContent,
productsOnly = false,
}: {
beforeContent?: ReactNode;
productsOnly?: boolean;
} ) {
const params = useParams();
const navigate = useNavigate();
const { recordEvent } = useAnalytics();
const isNewUser = useIsJetpackUserNew();
const tabStartTimeRef = useRef< number >( Date.now() );
const [ tabKey, setTabKey ] = useState( 0 );
const lastNavigationSourceRef = useRef< 'internal' | 'external' >( 'external' );
// If the tab is not valid, use the default one. In products-only mode we lock to the
// Products tab (the tab strip itself is hidden via CSS) so the layout chrome — width,
// background and the separator below the header — is preserved without showing tabs.
const currentTab = useMemo( () => {
if ( productsOnly ) {
return MY_JETPACK_SECTION_PRODUCTS;
}
const validTab = isValidMyJetpackSection( params.section );
return validTab ? params.section : MY_JETPACK_SECTION_OVERVIEW;
}, [ params.section, productsOnly ] );
const onTabSelect = useCallback(
( tabName: string ) => {
if ( tabName !== params.section ) {
// Mark this as an internal navigation (user clicked a tab)
lastNavigationSourceRef.current = 'internal';
// Calculate session duration on previous tab
const sessionDuration = Math.floor( ( Date.now() - tabStartTimeRef.current ) / 1000 );
// Record tab click event
recordEvent( 'jetpack_myjetpack_tab_click', {
tab_name: tabName,
previous_tab: params.section || MY_JETPACK_SECTION_OVERVIEW,
session_duration: sessionDuration,
user_type: isNewUser ? 'new' : 'returning',
} );
// Reset the timer for the new tab
tabStartTimeRef.current = Date.now();
navigate( `/${ tabName }` );
}
},
[ navigate, params.section, recordEvent, isNewUser ]
);
const tabRenderer = useCallback(
( tab: { name: string } ) => {
return (
<>
<FullWidthSeparator />
{ beforeContent }
<TabContent name={ tab.name as MyJetpackSection } />
</>
);
},
[ beforeContent ]
);
// Handle external navigation (URL changes not from tab clicks)
useEffect( () => {
// If this was an external navigation (browser back/forward, direct URL access)
if ( lastNavigationSourceRef.current === 'external' ) {
// Force remount to sync with URL
setTabKey( prev => prev + 1 );
}
// Reset navigation source for next change
lastNavigationSourceRef.current = 'external';
// Reset timer when tab changes
tabStartTimeRef.current = Date.now();
}, [ currentTab ] );
useEffect( () => {
// Track tab view event
recordEvent( 'jetpack_myjetpack_tab_view', {
tab_name: currentTab,
user_type: isNewUser ? 'new' : 'returning',
navigation_source: lastNavigationSourceRef.current,
} );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [] ); // track this only on page load
const tabs = useMemo( () => {
// Build the Products tab directly in products-only mode: getMyJetpackSections() strips
// it for non-admins, and the tab strip is hidden anyway.
if ( productsOnly ) {
return [
{ name: MY_JETPACK_SECTION_PRODUCTS, title: __( 'Products', 'jetpack-my-jetpack' ) },
];
}
return getMyJetpackSections();
}, [ productsOnly ] );
return (
<TabPanel
key={ tabKey }
className={ clsx(
styles[ 'tab-panel' ],
styles[ 'my-jetpack-tab-panel--full-width' ],
productsOnly && styles[ 'my-jetpack-tab-panel--products-only' ],
'jetpack-my-jetpack-tab-panel'
) }
initialTabName={ currentTab }
onSelect={ onTabSelect }
children={ tabRenderer }
tabs={ tabs }
/>
);
}
|