Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef } from 'react';
Comment thread
gmjuhasz marked this conversation as resolved.
import { useSearchParams } from 'react-router';
import { FilteredPlans } from './filtered-plans';
import { FilteredProducts } from './filtered-products';
import { Filters } from './filters';
import { ProductFiltersProvider, useProductFiltersContext } from './products-tracking-context';
import styles from './styles.module.scss';
import { ProductFilter } from './types';
import { isValidFilter } from './utils';

/**
* Inner component that consumes the ProductFiltering context.
Expand Down Expand Up @@ -105,14 +107,40 @@ const ProductsContent = ( {
* @return The rendered component.
*/
export const Products = () => {
const [ selectedFilter, setSelectedFilter ] = useState< ProductFilter >( 'all' );
const [ search, setSearch ] = useState< string >( '' );
const [ searchParams, setSearchParams ] = useSearchParams();
const filterParam = searchParams.get( 'filter' );
const selectedFilter: ProductFilter = isValidFilter( filterParam ) ? filterParam : 'all';
const search = searchParams.get( 'search' ) || '';

Comment thread
gmjuhasz marked this conversation as resolved.
// Update URL when filter changes
const handleSetSelectedFilter = useCallback(
( filter: ProductFilter ) => {
const newSearchParams = new URLSearchParams( searchParams );
newSearchParams.set( 'filter', filter );
setSearchParams( newSearchParams, { replace: true } );
},
[ searchParams, setSearchParams ]
);

// Update URL when search changes
const setSearch = useCallback(
( searchTerm: string ) => {
const newSearchParams = new URLSearchParams( searchParams );
if ( searchTerm ) {
newSearchParams.set( 'search', searchTerm );
} else {
newSearchParams.delete( 'search' );
Comment thread
manzoorwanijk marked this conversation as resolved.
}
setSearchParams( newSearchParams, { replace: true } );
},
[ searchParams, setSearchParams ]
);

return (
<ProductFiltersProvider currentFilter={ selectedFilter } searchTerm={ search }>
<ProductsContent
selectedFilter={ selectedFilter }
setSelectedFilter={ setSelectedFilter }
setSelectedFilter={ handleSetSelectedFilter }
search={ search }
setSearch={ setSearch }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,16 @@ export function getSectionTitle( section: string ) {
return option?.label;
}

/**
* Check if a string is a valid ProductFilter.
*
* @param {string | null} value - The value to check.
* @return True if the value is a valid ProductFilter.
Comment thread
manzoorwanijk marked this conversation as resolved.
Comment thread
manzoorwanijk marked this conversation as resolved.
*/
export function isValidFilter( value: string | null ): value is ProductFilter {
return getProductsFilterChoices().some( item => item.value === value );
}
Comment on lines +68 to +70

Copilot AI Jan 12, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isValidFilter function calls getProductsFilterChoices() each time it runs, creating a new array on every invocation. Consider memoizing the filter choices or checking against a static set of valid values for better performance, especially since this function is called during component initialization.

Copilot uses AI. Check for mistakes.

/**
* Filter sections based on the search term by matching the card and module data with the search term.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ const PlanSectionFooter: FC< PlanSectionHeaderAndFooterProps > = ( { numberOfPur
recordEvent( 'jetpack_myjetpack_plans_manage_click' );
}, [ recordEvent ] );

const viewIncludedFeaturesClickHandler = useCallback( () => {
recordEvent( 'jetpack_myjetpack_plans_view_included_features_click' );
}, [ recordEvent ] );

const planPurchaseClickHandler = useCallback( () => {
recordEvent( 'jetpack_myjetpack_plans_purchase_click' );
}, [ recordEvent ] );
Expand Down Expand Up @@ -236,6 +240,18 @@ const PlanSectionFooter: FC< PlanSectionHeaderAndFooterProps > = ( { numberOfPur
</ExternalLink>
</li>
) }
{ numberOfPurchases > 0 && (
<li className={ styles[ 'actions-list-item' ] }>
<Button
onClick={ viewIncludedFeaturesClickHandler }
href={ getMyJetpackUrl( '#/products?filter=included' ) }
variant="link"
weight="regular"
>
{ __( 'View included features', 'jetpack-my-jetpack' ) }
</Button>
</li>
) }
{ ! hasComplete && (
<li className={ styles[ 'actions-list-item' ] }>
<Button
Expand Down
4 changes: 4 additions & 0 deletions projects/packages/my-jetpack/changelog/MYJP-287
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: added

My Jetpack: add 'View included features' link to Plans section for quick access to plan features.
Loading