From 642ef7a51704cfde828f6f41bad5496e381d6d30 Mon Sep 17 00:00:00 2001 From: Fredrik Rombach Ekelund Date: Thu, 7 Mar 2024 11:32:19 +0100 Subject: [PATCH] Pattern Library: Use new API endpoint in `usePatternCategories` (#88197) * Use `/ptk/categories` API endpoint in `usePatternCategories` * Fix 404 page * Fix test * No fiddling with aspect-ratios in this PR * Address review suggestions * Fix import * Use `label` instead of `title` * More label --- .../hooks/use-assembler-patterns.ts | 2 +- .../components/grid-gallery/index.tsx | 7 +- client/my-sites/patterns/controller.tsx | 6 -- client/my-sites/patterns/home/index.tsx | 21 +++--- .../hooks/test/use-pattern-categories.tsx | 9 +-- .../patterns/hooks/use-pattern-categories.ts | 69 ++++--------------- .../my-sites/patterns/hooks/use-patterns.ts | 2 +- client/my-sites/patterns/index.node.tsx | 47 +++++++------ client/my-sites/patterns/index.web.tsx | 50 ++++++++++---- client/my-sites/patterns/types.ts | 23 +++++-- 10 files changed, 117 insertions(+), 119 deletions(-) diff --git a/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/hooks/use-assembler-patterns.ts b/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/hooks/use-assembler-patterns.ts index b7c67201a79025..bd8937b8cc6d58 100644 --- a/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/hooks/use-assembler-patterns.ts +++ b/client/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/hooks/use-assembler-patterns.ts @@ -9,7 +9,7 @@ const useAssemblerPatterns = ( queryOptions: Omit< UseQueryOptions< any, unknown, Pattern[] >, 'queryKey' > = {} ): Pattern[] => { const { data } = useQuery< any, unknown, Pattern[] >( { - queryKey: [ 'patterns', 'assembler', lang ], + queryKey: [ 'pattern-assembler', lang ], queryFn: () => { return wpcomRequest( { path: `/ptk/patterns/${ lang }`, diff --git a/client/my-sites/patterns/components/grid-gallery/index.tsx b/client/my-sites/patterns/components/grid-gallery/index.tsx index 55621f0a76e0bb..e2207aebe9d970 100644 --- a/client/my-sites/patterns/components/grid-gallery/index.tsx +++ b/client/my-sites/patterns/components/grid-gallery/index.tsx @@ -1,5 +1,4 @@ import { PatternsSection } from 'calypso/my-sites/patterns/components/section'; -import type { CSSProperties } from 'react'; import './style.scss'; @@ -9,7 +8,7 @@ type PatternsGridGalleryProps = { columnCount?: number; list?: { name?: string; - label?: string; + label: string; number: number; image: string; link: string; @@ -30,14 +29,14 @@ export const PatternsGridGallery = ( {
{ list.map( ( { name, label, number, image, link } ) => (
{
-
{ name }
+
{ label }
{ number } patterns
) ) } diff --git a/client/my-sites/patterns/controller.tsx b/client/my-sites/patterns/controller.tsx index 91dba9fc0d3d9c..88834c1372f12f 100644 --- a/client/my-sites/patterns/controller.tsx +++ b/client/my-sites/patterns/controller.tsx @@ -1,7 +1 @@ -import { PATTERN_CATEGORIES } from 'calypso/my-sites/patterns/hooks/use-pattern-categories'; - export const RENDERER_SITE_ID = 226011606; // assemblerdemo - -export function getPatternCategorySlugs() { - return PATTERN_CATEGORIES.join( '|' ); -} diff --git a/client/my-sites/patterns/home/index.tsx b/client/my-sites/patterns/home/index.tsx index ea44562d97e324..b84f667c0f2ab7 100644 --- a/client/my-sites/patterns/home/index.tsx +++ b/client/my-sites/patterns/home/index.tsx @@ -6,7 +6,6 @@ import { PatternsGetStarted } from 'calypso/my-sites/patterns/components/get-sta import { PatternsGridGallery } from 'calypso/my-sites/patterns/components/grid-gallery'; import { PatternsHeader } from 'calypso/my-sites/patterns/components/header'; import { PatternsSection } from 'calypso/my-sites/patterns/components/section'; -import { RENDERER_SITE_ID } from 'calypso/my-sites/patterns/controller'; import { usePatternCategories } from 'calypso/my-sites/patterns/hooks/use-pattern-categories'; import { usePatterns } from 'calypso/my-sites/patterns/hooks/use-patterns'; import { useSelector } from 'calypso/state'; @@ -35,7 +34,7 @@ export const PatternsHomePage = ( { const locale = useLocale(); const isLoggedIn = useSelector( isUserLoggedIn ); - const { data: categories } = usePatternCategories( locale, RENDERER_SITE_ID ); + const { data: categories } = usePatternCategories( locale ); const { data: patterns } = usePatterns( locale, category ); return ( @@ -56,11 +55,12 @@ export const PatternsHomePage = ( { list={ categories?.map( ( category ) => ( { name: category.name, label: category.label, - number: 15, + number: category.regularPatternCount, image: ImgPattern, link: `/patterns/${ category.name }`, } ) ) } /> + ( { - name: 'About', - number: 7, - image: ImgLayout, - link: '#', - } ) ) } + list={ categories + ?.filter( ( { pagePatternCount } ) => pagePatternCount ) + .map( ( category ) => ( { + name: category.name, + label: category.label, + number: category.pagePatternCount, + image: ImgLayout, + link: `/patterns/${ category.name }`, + } ) ) } /> diff --git a/client/my-sites/patterns/hooks/test/use-pattern-categories.tsx b/client/my-sites/patterns/hooks/test/use-pattern-categories.tsx index 960f4ae1c991b9..998cfe70b46cd5 100644 --- a/client/my-sites/patterns/hooks/test/use-pattern-categories.tsx +++ b/client/my-sites/patterns/hooks/test/use-pattern-categories.tsx @@ -30,14 +30,11 @@ describe( 'usePatternCategories', () => { test( 'calls the API endpoint with the right parameters', async () => { ( wpcom.req.get as jest.MockedFunction< typeof wpcom.req.get > ).mockResolvedValue( [] ); - const { result } = renderHook( () => usePatternCategories( 'fr', 12345 ), { wrapper } ); + const { result } = renderHook( () => usePatternCategories( 'fr' ), { wrapper } ); await waitFor( () => expect( result.current.isSuccess ).toBe( true ) ); - expect( wpcom.req.get ).toHaveBeenCalledWith( '/sites/12345/block-patterns/categories', { - apiNamespace: 'wp/v2', - _locale: 'fr', - } ); + expect( wpcom.req.get ).toHaveBeenCalledWith( '/ptk/categories/fr' ); expect( result.current.data ).toEqual( [] ); } ); @@ -54,7 +51,7 @@ describe( 'usePatternCategories', () => { categories ); - const { result } = renderHook( () => usePatternCategories( 'fr', 12345 ), { wrapper } ); + const { result } = renderHook( () => usePatternCategories( 'fr' ), { wrapper } ); await waitFor( () => expect( result.current.isSuccess ).toBe( true ) ); diff --git a/client/my-sites/patterns/hooks/use-pattern-categories.ts b/client/my-sites/patterns/hooks/use-pattern-categories.ts index b13eade6349320..8aa44b256a6715 100644 --- a/client/my-sites/patterns/hooks/use-pattern-categories.ts +++ b/client/my-sites/patterns/hooks/use-pattern-categories.ts @@ -1,75 +1,34 @@ import { useQuery, UseQueryOptions } from '@tanstack/react-query'; import wpcom from 'calypso/lib/wp'; -import type { Category } from 'calypso/my-sites/patterns/types'; - -export const PATTERN_CATEGORIES = [ - 'featured', - 'intro', - 'about', - // 'buttons', - // 'banner', - // 'query', - 'blog', - 'posts', - // 'call-to-action', - // 'columns', - // 'coming-soon', - 'contact', - 'footer', - 'forms', - 'gallery', - 'header', - // 'link-in-bio', - // 'media', - 'newsletter', - // 'podcast', - 'portfolio', // For page patterns only in v1 - // 'quotes', - 'services', - 'store', - // 'team', - 'testimonials', // Reused as "Quotes" - // 'text', -]; +import type { Category, CategorySnakeCase } from 'calypso/my-sites/patterns/types'; export function getPatternCategoriesQueryOptions( locale: string, - siteId: undefined | number, queryOptions: Omit< UseQueryOptions< Category[] >, 'queryKey' > = {} ): UseQueryOptions< Category[] > { return { - queryKey: [ locale, siteId, 'pattern-library', 'categories' ], + queryKey: [ 'pattern-library', 'categories', locale ], queryFn() { - return wpcom.req.get( - `/sites/${ encodeURIComponent( siteId ?? '' ) }/block-patterns/categories`, - { - apiNamespace: 'wp/v2', - _locale: locale, - } - ); - }, - select( categories ) { - const result = []; - - for ( const name of PATTERN_CATEGORIES ) { - const category = categories.find( ( category ) => category.name === name ); - if ( category ) { - result.push( category ); - } - } - - return result; + return wpcom.req + .get( `/ptk/categories/${ locale }` ) + .then( ( categories: CategorySnakeCase[] ) => { + return categories.map( + ( { regular_cattern_count, page_pattern_count, ...restCategory } ) => ( { + ...restCategory, + pagePatternCount: page_pattern_count, + regularPatternCount: regular_cattern_count, + } ) + ); + } ); }, staleTime: Infinity, ...queryOptions, - enabled: !! siteId, }; } export function usePatternCategories( locale: string, - siteId: undefined | number, queryOptions: Omit< UseQueryOptions< Category[] >, 'queryKey' > = {} ) { - return useQuery< Category[] >( getPatternCategoriesQueryOptions( locale, siteId, queryOptions ) ); + return useQuery< Category[] >( getPatternCategoriesQueryOptions( locale, queryOptions ) ); } diff --git a/client/my-sites/patterns/hooks/use-patterns.ts b/client/my-sites/patterns/hooks/use-patterns.ts index 9b172f586c02b9..4ea3fc64caf176 100644 --- a/client/my-sites/patterns/hooks/use-patterns.ts +++ b/client/my-sites/patterns/hooks/use-patterns.ts @@ -8,7 +8,7 @@ export function getPatternsQueryOptions( queryOptions: Omit< UseQueryOptions< Pattern[] >, 'queryKey' > = {} ) { return { - queryKey: [ 'patterns', 'library', locale, category ], + queryKey: [ 'pattern-library', 'patterns', locale, category ], queryFn: () => { return wpcom.req.get( `/ptk/patterns/${ locale }`, { categories: category, diff --git a/client/my-sites/patterns/index.node.tsx b/client/my-sites/patterns/index.node.tsx index 6625d64d31cf77..5d6ca150f1e142 100644 --- a/client/my-sites/patterns/index.node.tsx +++ b/client/my-sites/patterns/index.node.tsx @@ -2,13 +2,12 @@ import { getLanguageRouteParam } from '@automattic/i18n-utils'; import { makeLayout, ssrSetupLocale } from 'calypso/controller'; import { setHrefLangLinks, setLocalizedCanonicalUrl } from 'calypso/controller/localized-links'; import { PatternGalleryServer } from 'calypso/my-sites/patterns/components/pattern-gallery/server'; -import { RENDERER_SITE_ID, getPatternCategorySlugs } from 'calypso/my-sites/patterns/controller'; import { PatternsHomePage } from 'calypso/my-sites/patterns/home'; import { getPatternCategoriesQueryOptions } from 'calypso/my-sites/patterns/hooks/use-pattern-categories'; import { getPatternsQueryOptions } from 'calypso/my-sites/patterns/hooks/use-patterns'; import { serverRouter } from 'calypso/server/isomorphic-routing'; import { getCurrentUserLocale } from 'calypso/state/current-user/selectors'; -import type { RouterContext, RouterNext, Category, Pattern } from 'calypso/my-sites/patterns/types'; +import type { RouterContext, RouterNext, Pattern } from 'calypso/my-sites/patterns/types'; function renderPatterns( context: RouterContext, next: RouterNext ) { context.primary = ( @@ -22,7 +21,7 @@ function renderPatterns( context: RouterContext, next: RouterNext ) { next(); } -function fetchPatterns( context: RouterContext, next: RouterNext ) { +function fetchCategoriesAndPatterns( context: RouterContext, next: RouterNext ) { const { cachedMarkup, queryClient, lang, params, store } = context; if ( cachedMarkup ) { @@ -32,19 +31,31 @@ function fetchPatterns( context: RouterContext, next: RouterNext ) { const locale = getCurrentUserLocale( store.getState() ) || lang || 'en'; - const categoryPromise = queryClient.fetchQuery< Category[] >( - getPatternCategoriesQueryOptions( locale, RENDERER_SITE_ID, { - staleTime: 10 * 60 * 1000, - } ) - ); + // Fetches the list of categories first, then fetches patterns if a specific category was requested + queryClient + .fetchQuery( + getPatternCategoriesQueryOptions( locale, { + staleTime: 10 * 60 * 1000, + } ) + ) + .then( ( categories ) => { + if ( ! params.category ) { + return; + } - const patternPromise = params.category - ? queryClient.fetchQuery< Pattern[] >( - getPatternsQueryOptions( locale, params.category, { staleTime: 10 * 60 * 1000 } ) - ) - : Promise.resolve(); + const categoryNames = categories.map( ( category ) => category.name ); - Promise.all( [ categoryPromise, patternPromise ] ) + if ( ! categoryNames.includes( params.category ) ) { + throw { + status: 404, + message: 'Category Not Found', + }; + } + + return queryClient.fetchQuery< Pattern[] >( + getPatternsQueryOptions( locale, params.category, { staleTime: 10 * 60 * 1000 } ) + ); + } ) .then( () => { next(); } ) @@ -55,17 +66,13 @@ function fetchPatterns( context: RouterContext, next: RouterNext ) { export default function ( router: ReturnType< typeof serverRouter > ) { const langParam = getLanguageRouteParam(); - const categorySlugs = getPatternCategorySlugs(); router( - [ - `/${ langParam }/patterns/:category(${ categorySlugs })?`, - `/patterns/:category(${ categorySlugs })?`, - ], + [ `/${ langParam }/patterns/:category?`, `/patterns/:category?` ], ssrSetupLocale, setHrefLangLinks, setLocalizedCanonicalUrl, - fetchPatterns, + fetchCategoriesAndPatterns, renderPatterns, makeLayout ); diff --git a/client/my-sites/patterns/index.web.tsx b/client/my-sites/patterns/index.web.tsx index 43a32a0cfbded1..3335d90fda5bc0 100644 --- a/client/my-sites/patterns/index.web.tsx +++ b/client/my-sites/patterns/index.web.tsx @@ -4,33 +4,59 @@ import { makeLayout, redirectWithoutLocaleParamInFrontIfLoggedIn, render as clientRender, + notFound, } from 'calypso/controller/index.web'; import { PatternGalleryClient } from 'calypso/my-sites/patterns/components/pattern-gallery/client'; -import { getPatternCategorySlugs } from 'calypso/my-sites/patterns/controller'; import { PatternsHomePage } from 'calypso/my-sites/patterns/home'; +import { getCurrentUserLocale } from 'calypso/state/current-user/selectors'; +import { getPatternCategoriesQueryOptions } from './hooks/use-pattern-categories'; import type { RouterContext, RouterNext } from 'calypso/my-sites/patterns/types'; function renderPatterns( context: RouterContext, next: RouterNext ) { - context.primary = ( - - ); + if ( ! context.primary ) { + context.primary = ( + + ); + } next(); } +function checkCategorySlug( context: RouterContext, next: RouterNext ) { + const { queryClient, lang, params, store } = context; + const locale = getCurrentUserLocale( store.getState() ) || lang || 'en'; + + queryClient + .fetchQuery( getPatternCategoriesQueryOptions( locale ) ) + .then( ( categories ) => { + if ( params.category ) { + const categoryNames = categories.map( ( category ) => category.name ); + + if ( ! categoryNames.includes( params.category ) ) { + notFound( context, next ); + return; + } + } + + next(); + } ) + .catch( ( error ) => { + next( error ); + } ); +} + export default function ( router: typeof clientRouter ) { const langParam = getLanguageRouteParam(); - const categorySlugs = getPatternCategorySlugs(); - const middleware = [ renderPatterns, makeLayout, clientRender ]; + const middleware = [ checkCategorySlug, renderPatterns, makeLayout, clientRender ]; router( - `/${ langParam }/patterns/:category(${ categorySlugs })?`, + `/${ langParam }/patterns/:category?`, redirectWithoutLocaleParamInFrontIfLoggedIn, ...middleware ); - router( `/patterns/:category(${ categorySlugs })?`, ...middleware ); + router( `/patterns/:category?`, ...middleware ); } diff --git a/client/my-sites/patterns/types.ts b/client/my-sites/patterns/types.ts index 959ac9f27dc6ba..a990df62107ae3 100644 --- a/client/my-sites/patterns/types.ts +++ b/client/my-sites/patterns/types.ts @@ -1,9 +1,6 @@ import type { Context } from '@automattic/calypso-router'; import type { QueryClient } from '@tanstack/react-query'; -import type { - Category, - Pattern, -} from 'calypso/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/types'; +import type { Pattern } from 'calypso/landing/stepper/declarative-flow/internals/steps-repository/pattern-assembler/types'; export type RouterNext = ( error?: Error ) => void; @@ -12,7 +9,23 @@ export type RouterContext = Context & { queryClient: QueryClient; }; -export type { Category, Pattern }; +export type { Pattern }; + +type CategoryBase = { + name: string; + label: string; + description: string; +}; + +export type CategorySnakeCase = CategoryBase & { + page_pattern_count: number; + regular_cattern_count: number; +}; + +export type Category = CategoryBase & { + pagePatternCount: number; + regularPatternCount: number; +}; export type PatternGalleryProps = { isGridView?: boolean;