From a13f7679f751164162eb3e4c8ed68784b41f6f68 Mon Sep 17 00:00:00 2001 From: arthur Date: Fri, 6 Dec 2024 17:31:23 +0800 Subject: [PATCH] Design Picker: Clean up filters when going back to previous step --- .../hooks/use-flow-navigation/index.tsx | 6 +- .../design-setup/hooks/use-recipe.ts | 38 ++++++---- .../design-setup/hooks/use-track-filters.ts | 5 +- .../design-setup/unified-design-picker.tsx | 4 ++ .../design-picker-tier-filter/index.tsx | 18 ++--- .../src/components/unified-design-picker.tsx | 2 +- .../src/hooks/use-categorization.ts | 44 ++++-------- .../src/hooks/use-design-picker-filters.ts | 69 +++++++++++++++++++ .../src/hooks/use-filtered-designs.ts | 15 ++-- packages/design-picker/src/index.ts | 1 + 10 files changed, 132 insertions(+), 70 deletions(-) create mode 100644 packages/design-picker/src/hooks/use-design-picker-filters.ts diff --git a/client/landing/stepper/declarative-flow/internals/hooks/use-flow-navigation/index.tsx b/client/landing/stepper/declarative-flow/internals/hooks/use-flow-navigation/index.tsx index 87b50a1068e281..7c73789802f668 100644 --- a/client/landing/stepper/declarative-flow/internals/hooks/use-flow-navigation/index.tsx +++ b/client/landing/stepper/declarative-flow/internals/hooks/use-flow-navigation/index.tsx @@ -44,7 +44,9 @@ export const useFlowNavigation = (): FlowNavigation => { const customNavigate = useCallback< Navigate< StepperStep[] > >( ( nextStep: string, extraData = {}, replace = false ) => { const hasQueryParams = nextStep.includes( '?' ); - const queryParams = ! hasQueryParams ? currentSearchParams : null; + + // Get the latest search params from the current location + const queryParams = ! hasQueryParams ? new URLSearchParams( window.location.search ) : null; setStepData( { path: nextStep, @@ -61,7 +63,7 @@ export const useFlowNavigation = (): FlowNavigation => { navigate( addQueryParams( newPath, queryParams ), { replace } ); }, - [ currentSearchParams, flow, intent, lang, navigate, setStepData, currentStepSlug ] + [ flow, intent, lang, navigate, setStepData, currentStepSlug ] ); return { diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-recipe.ts b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-recipe.ts index bf57454a02d6c9..c599ab9cc24a51 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-recipe.ts +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-recipe.ts @@ -12,6 +12,12 @@ import type { GlobalStyles, OnboardSelect, StarterDesigns } from '@automattic/da import type { Design, StyleVariation } from '@automattic/design-picker'; import type { GlobalStylesObject } from '@automattic/global-styles'; +// The `currentSearchParams` parameter from the callback of the `setSearchParams` function +// might not have the latest query parameter on multiple calls at the same time. +const makeSearchParams = ( + callback: ( currentSearchParams: URLSearchParams ) => URLSearchParams +) => callback( new URLSearchParams( window.location.search ) ); + const useRecipe = ( siteId = 0, allDesigns: StarterDesigns | undefined, @@ -92,22 +98,24 @@ const useRecipe = ( } if ( theme !== searchParams.get( 'theme' ) ) { - setSearchParams( ( currentSearchParams ) => { - if ( theme ) { - currentSearchParams.set( 'theme', theme ); - } else { - currentSearchParams.delete( 'theme' ); - } - - return currentSearchParams; - } ); + setSearchParams( + makeSearchParams( ( currentSearchParams ) => { + if ( theme ) { + currentSearchParams.set( 'theme', theme ); + } else { + currentSearchParams.delete( 'theme' ); + } + + return currentSearchParams; + } ) + ); } }; const handleSelectedStyleVariationChange = ( variation?: StyleVariation ) => { setSelectedStyleVariation( variation ); setSearchParams( - ( currentSearchParams ) => { + makeSearchParams( ( currentSearchParams ) => { if ( variation ) { currentSearchParams.set( 'style_variation', variation.slug ); } else { @@ -115,7 +123,7 @@ const useRecipe = ( } return currentSearchParams; - }, + } ), { replace: true } ); }; @@ -123,7 +131,7 @@ const useRecipe = ( const handleSelectedColorVariationChange = ( variation: GlobalStyles | null ) => { setSelectedColorVariation( variation ); setSearchParams( - ( currentSearchParams ) => { + makeSearchParams( ( currentSearchParams ) => { if ( variation && variation.title ) { currentSearchParams.set( 'color_variation_title', variation.title ); } else { @@ -131,7 +139,7 @@ const useRecipe = ( } return currentSearchParams; - }, + } ), { replace: true } ); }; @@ -139,7 +147,7 @@ const useRecipe = ( const handleSelectedFontVariationChange = ( variation: GlobalStyles | null ) => { setSelectedFontVariation( variation ); setSearchParams( - ( currentSearchParams ) => { + makeSearchParams( ( currentSearchParams ) => { if ( variation && variation.title ) { currentSearchParams.set( 'font_variation_title', variation.title ); } else { @@ -147,7 +155,7 @@ const useRecipe = ( } return currentSearchParams; - }, + } ), { replace: true } ); }; diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-track-filters.ts b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-track-filters.ts index 3d3b7a495d3fea..4d190b170ba685 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-track-filters.ts +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/hooks/use-track-filters.ts @@ -20,7 +20,10 @@ const useTrackFilters = ( { preselectedFilters, isBigSkyEligible, isMultiSelecti return selectedFilters.reduce( ( result, filterSlug, index ) => ( { ...result, - [ `filters_${ filterSlug }` ]: `${ getCategoryType( filterSlug ) }:${ index }`, + // The property cannot contain `-` character. + [ `filters_${ filterSlug.replace( '-', '_' ) }` ]: `${ getCategoryType( + filterSlug + ) }:${ index }`, } ), {} ); diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx index eb6ca16577c501..bdef8f272c391f 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/design-setup/unified-design-picker.tsx @@ -18,6 +18,7 @@ import { import { UnifiedDesignPicker, useCategorization, + useDesignPickerFilters, getDesignPreviewUrl, isAssemblerDesign, isAssemblerSupported, @@ -254,6 +255,8 @@ const UnifiedDesignPickerStep: Step = ( { navigation, flow, stepName } ) => { handleDeselect: handleDeselectFilter, } ); + const designPickerFilters = useDesignPickerFilters(); + const handleChangeTier = ( value: boolean ) => { if ( value ) { handleSelectFilter( 'free', 'included_with_plan' ); @@ -779,6 +782,7 @@ const UnifiedDesignPickerStep: Step = ( { navigation, flow, stepName } ) => { return; } + designPickerFilters.resetFilters(); goBack?.(); } diff --git a/packages/design-picker/src/components/design-picker-tier-filter/index.tsx b/packages/design-picker/src/components/design-picker-tier-filter/index.tsx index 097b619b3716b6..04455d81045c17 100644 --- a/packages/design-picker/src/components/design-picker-tier-filter/index.tsx +++ b/packages/design-picker/src/components/design-picker-tier-filter/index.tsx @@ -1,6 +1,6 @@ import { ToggleControl } from '@wordpress/components'; import { useTranslate } from 'i18n-calypso'; -import { useSearchParams } from 'react-router-dom'; +import { useDesignPickerFilters } from '../../hooks/use-design-picker-filters'; import './style.scss'; interface Props { @@ -9,21 +9,13 @@ interface Props { const DesignPickerTierFilter = ( { onChange }: Props ) => { const translate = useTranslate(); - const [ searchParams, setSearchParams ] = useSearchParams(); - const isFreeOnly = searchParams.get( 'tier' ) === 'free'; + const { selectedDesignTier, setSelectedDesignTier } = useDesignPickerFilters(); - const handleChange = ( value: boolean ) => { - setSearchParams( ( currentSearchParams: any ) => { - if ( value ) { - currentSearchParams.set( 'tier', 'free' ); - } else { - currentSearchParams.delete( 'tier' ); - } - - return currentSearchParams; - } ); + const isFreeOnly = selectedDesignTier === 'free'; + const handleChange = ( value: boolean ) => { + setSelectedDesignTier( value ? 'free' : '' ); onChange?.( value ); }; diff --git a/packages/design-picker/src/components/unified-design-picker.tsx b/packages/design-picker/src/components/unified-design-picker.tsx index f6928301edb34b..f7e2aeaf31ad36 100644 --- a/packages/design-picker/src/components/unified-design-picker.tsx +++ b/packages/design-picker/src/components/unified-design-picker.tsx @@ -228,7 +228,7 @@ const DesignPicker: React.FC< DesignPickerProps > = ( { onChangeTier, } ) => { const hasCategories = !! Object.keys( categorization?.categories || {} ).length; - const filteredDesigns = useFilteredDesigns( designs, categorization ); + const filteredDesigns = useFilteredDesigns( designs ); const features = [ 'blog', 'portfolio', 'podcast', 'store' ]; const featureCategories = useMemo( () => ( categorization?.categories || [] ).filter( ( { slug } ) => features.includes( slug ) ), diff --git a/packages/design-picker/src/hooks/use-categorization.ts b/packages/design-picker/src/hooks/use-categorization.ts index 13879b6a562cd9..bc7b6d48c92902 100644 --- a/packages/design-picker/src/hooks/use-categorization.ts +++ b/packages/design-picker/src/hooks/use-categorization.ts @@ -1,11 +1,11 @@ import { useEffect, useMemo, useCallback } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { Category } from '../types'; +import { useDesignPickerFilters } from './use-design-picker-filters'; +import type { Category } from '../types'; export interface Categorization { + categories: Category[]; selections: string[]; onSelect: ( selectedSlug: string ) => void; - categories: Category[]; } interface UseCategorizationOptions { @@ -26,8 +26,6 @@ export function useCategorization( handleDeselect, }: UseCategorizationOptions ): Categorization { - const [ searchParams, setSearchParams ] = useSearchParams(); - const categories = useMemo( () => { const categoryMapKeys = Object.keys( categoryMap ) || []; const result = categoryMapKeys.map( ( slug ) => ( { @@ -38,55 +36,43 @@ export function useCategorization( return result.sort( sort ); }, [ categoryMap, sort ] ); - const selections = searchParams.get( 'categories' )?.split( ',' ) || []; - - const setSelections = ( values: string[] ) => { - setSearchParams( ( currentSearchParams ) => { - if ( values.length > 0 ) { - currentSearchParams.set( 'categories', values.join( ',' ) ); - } else { - currentSearchParams.delete( 'categories' ); - } - return currentSearchParams; - } ); - }; + const { selectedCategories, setSelectedCategories } = useDesignPickerFilters(); const onSelect = useCallback( ( value: string ) => { if ( ! isMultiSelection ) { handleSelect?.( value ); - setSelections( [ value ] ); + setSelectedCategories( [ value ] ); return; } - const currentSelections = searchParams.get( 'categories' )?.split( ',' ) || []; - const index = currentSelections.findIndex( ( selection ) => selection === value ); + const index = selectedCategories.findIndex( ( selection ) => selection === value ); if ( index === -1 ) { handleSelect?.( value ); - return setSelections( [ ...currentSelections, value ] ); + return setSelectedCategories( [ ...selectedCategories, value ] ); } // The selections should at least have one. - if ( currentSelections.length > 1 ) { + if ( selectedCategories.length > 1 ) { handleDeselect?.( value ); - return setSelections( [ - ...currentSelections.slice( 0, index ), - ...currentSelections.slice( index + 1 ), + return setSelectedCategories( [ + ...selectedCategories.slice( 0, index ), + ...selectedCategories.slice( index + 1 ), ] ); } }, - [ searchParams, isMultiSelection, setSelections, handleSelect, handleDeselect ] + [ selectedCategories, isMultiSelection, setSelectedCategories, handleSelect, handleDeselect ] ); useEffect( () => { - if ( selections.length === 0 ) { - setSelections( chooseDefaultSelections( categories, defaultSelections ) ); + if ( selectedCategories.length === 0 ) { + setSelectedCategories( chooseDefaultSelections( categories, defaultSelections ) ); } }, [] ); return { categories, - selections, + selections: selectedCategories, onSelect, }; } diff --git a/packages/design-picker/src/hooks/use-design-picker-filters.ts b/packages/design-picker/src/hooks/use-design-picker-filters.ts new file mode 100644 index 00000000000000..f812eae825cc5d --- /dev/null +++ b/packages/design-picker/src/hooks/use-design-picker-filters.ts @@ -0,0 +1,69 @@ +import { useSearchParams } from 'react-router-dom'; + +// The `currentSearchParams` parameter from the callback of the `setSearchParams` function +// might not have the latest query parameter on multiple calls at the same time. +const makeSearchParams = ( + callback: ( currentSearchParams: URLSearchParams ) => URLSearchParams +) => callback( new URLSearchParams( window.location.search ) ); + +const useCategoriesFilter = () => { + const [ searchParams, setSearchParams ] = useSearchParams(); + const selectedCategories = searchParams.get( 'categories' )?.split( ',' ) || []; + const setSelectedCategories = ( values: string[] ) => { + setSearchParams( + makeSearchParams( ( currentSearchParams ) => { + if ( values.length > 0 ) { + currentSearchParams.set( 'categories', values.join( ',' ) ); + } else { + currentSearchParams.delete( 'categories' ); + } + return currentSearchParams; + } ), + { replace: true } + ); + }; + + return { selectedCategories, setSelectedCategories }; +}; + +const useDesignTierFilter = () => { + const [ searchParams, setSearchParams ] = useSearchParams(); + + const selectedDesignTier = searchParams.get( 'tier' ) ?? ''; + + const setSelectedDesignTier = ( value: string ) => { + setSearchParams( + makeSearchParams( ( currentSearchParams: any ) => { + if ( value ) { + currentSearchParams.set( 'tier', value ); + } else { + currentSearchParams.delete( 'tier' ); + } + + return currentSearchParams; + } ), + { replace: true } + ); + }; + + return { + selectedDesignTier, + setSelectedDesignTier, + }; +}; + +export const useDesignPickerFilters = () => { + const { selectedCategories, setSelectedCategories } = useCategoriesFilter(); + const { selectedDesignTier, setSelectedDesignTier } = useDesignTierFilter(); + + return { + selectedCategories, + selectedDesignTier, + setSelectedCategories, + setSelectedDesignTier, + resetFilters: () => { + setSelectedCategories( [] ); + setSelectedDesignTier( '' ); + }, + }; +}; diff --git a/packages/design-picker/src/hooks/use-filtered-designs.ts b/packages/design-picker/src/hooks/use-filtered-designs.ts index 5f966643b7fb42..8823110fbc970e 100644 --- a/packages/design-picker/src/hooks/use-filtered-designs.ts +++ b/packages/design-picker/src/hooks/use-filtered-designs.ts @@ -1,7 +1,6 @@ import { useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; import { isBlankCanvasDesign } from '../utils/available-designs'; -import type { Categorization } from './use-categorization'; +import { useDesignPickerFilters } from './use-design-picker-filters'; import type { Design } from '../types'; const getDesignSlug = ( design: Design ) => design.recipe?.slug ?? design.slug; @@ -51,18 +50,16 @@ export const filterDesigns = ( return filteredDesigns; }; -export const useFilteredDesigns = ( designs: Design[], categorization?: Categorization ) => { - const [ searchParams ] = useSearchParams(); - - const selectedDesignTier = searchParams.get( 'tier' ) ?? ''; +export const useFilteredDesigns = ( designs: Design[] ) => { + const { selectedCategories, selectedDesignTier } = useDesignPickerFilters(); const filteredDesigns = useMemo( () => { - if ( categorization?.selections || selectedDesignTier ) { - return filterDesigns( designs, categorization?.selections, selectedDesignTier ); + if ( selectedCategories.length > 0 || selectedDesignTier ) { + return filterDesigns( designs, selectedCategories, selectedDesignTier ); } return designs; - }, [ designs, categorization?.selections, selectedDesignTier ] ); + }, [ designs, selectedCategories, selectedDesignTier ] ); return filteredDesigns; }; diff --git a/packages/design-picker/src/index.ts b/packages/design-picker/src/index.ts index 018dbad9226a18..bf38cc8159e8af 100644 --- a/packages/design-picker/src/index.ts +++ b/packages/design-picker/src/index.ts @@ -34,4 +34,5 @@ export type { StyleVariationStylesColor, } from './types'; export { useCategorization } from './hooks/use-categorization'; +export { useDesignPickerFilters } from './hooks/use-design-picker-filters'; export { useThemeDesignsQuery } from './hooks/use-theme-designs-query';