@@ -13,7 +13,6 @@ import {
1313 getSEOFromTag ,
1414 toFilters ,
1515 toProduct ,
16- typeTagExtractor ,
1716} from "../utils/transform.ts" ;
1817
1918export const VNDA_SORT_OPTIONS : SortOption [ ] = [
@@ -87,6 +86,42 @@ const handleOperator = (
8786 [ `${ key } _operator` ] : filterOperators ?. [ key ] ?? defaultValue ?? "and" ,
8887} ) ;
8988
89+ const fetchTag = (
90+ api : AppContext [ "api" ] ,
91+ name : string ,
92+ ) : Promise < Tag | undefined > =>
93+ api [ "GET /api/v2/tags/:name" ] ( { name } , STALE )
94+ . then ( ( res ) => res . json ( ) as Promise < Tag > )
95+ . catch ( ( ) : undefined => undefined ) ;
96+
97+ interface TypeTag {
98+ key : string ;
99+ value : string ;
100+ isProperty : boolean ;
101+ }
102+
103+ const parseTypeTagsFromUrl = ( url : URL ) : { typeTags : TypeTag [ ] ; cleanUrl : URL } => {
104+ const TYPE_TAG_PATTERN = / ^ t y p e _ t a g s \[ ( .+ ) \] \[ \] $ / ;
105+
106+ const typeTags = [ ...url . searchParams . entries ( ) ]
107+ . filter ( ( [ key ] ) => TYPE_TAG_PATTERN . test ( key ) )
108+ . map ( ( [ key , value ] ) => {
109+ const keyName = key . match ( TYPE_TAG_PATTERN ) ?. [ 1 ] ?? "" ;
110+ return {
111+ key,
112+ value,
113+ isProperty : / ^ p r o p e r t y \d + $ / . test ( keyName ) ,
114+ } ;
115+ } ) ;
116+
117+ const cleanUrl = new URL ( url . href ) ;
118+ [ ...cleanUrl . searchParams . keys ( ) ]
119+ . filter ( ( k ) => k . startsWith ( "type_tags" ) )
120+ . forEach ( ( k ) => cleanUrl . searchParams . delete ( k ) ) ;
121+
122+ return { typeTags, cleanUrl } ;
123+ } ;
124+
90125/**
91126 * @title VNDA Integration
92127 * @description Product Listing Page loader
@@ -108,44 +143,40 @@ const searchLoader = async (
108143 const isSearchPage = ctx . searchPagePath
109144 ? ctx . searchPagePath === url . pathname
110145 : url . pathname === "/busca" || url . pathname === "/s" ;
146+
111147 const qQueryString = url . searchParams . get ( "q" ) ;
112- const term = props . term || props . slug || qQueryString ||
113- undefined ;
148+ const term = props . term || props . slug || qQueryString || undefined ;
114149
115150 const priceFilterRegex = / d e - ( \d + ) - a - ( \d + ) / ;
116151 const filterMatch = url . href . match ( priceFilterRegex ) ?? [ ] ;
117152
118- const categoryTagName = ( props . term || url . pathname . slice ( 1 ) || "" ) . split (
119- "/" ,
120- ) ;
153+ const categoryTagName = ( props . term || url . pathname . slice ( 1 ) || "" ) . split ( "/" ) ;
121154
122155 const properties1 = url . searchParams . getAll ( "type_tags[property1][]" ) ;
123156 const properties2 = url . searchParams . getAll ( "type_tags[property2][]" ) ;
124157 const properties3 = url . searchParams . getAll ( "type_tags[property3][]" ) ;
125158
126- const categoryTagNames = Array . from ( url . searchParams . values ( ) ) ;
159+ const uniquePathNames = [
160+ ...new Set (
161+ categoryTagName . filter ( ( item ) : item is string => typeof item === "string" ) ,
162+ ) ,
163+ ] ;
127164
128- const tags = await Promise . all ( [
129- ...categoryTagNames ,
130- ...categoryTagName . filter ( ( item ) : item is string =>
131- typeof item === "string"
165+ const tagByName = new Map < string , Tag | undefined > (
166+ await Promise . all (
167+ uniquePathNames . map (
168+ async ( name ) => [ name , await fetchTag ( api , name ) ] as const ,
169+ ) ,
132170 ) ,
133- ] . map ( ( name ) =>
134- api [ "GET /api/v2/tags/:name" ] ( { name } , STALE )
135- . then ( ( res ) => res . json ( ) )
136- . catch ( ( ) => undefined )
137- ) ) ;
138-
139- const categories = tags
140- . slice ( - categoryTagName . length )
171+ ) ;
172+
173+ const categories = categoryTagName
174+ . map ( ( name ) => tagByName . get ( name ) )
141175 . filter ( ( tag ) : tag is Tag =>
142176 typeof tag !== "undefined" && typeof tag . name !== "undefined"
143177 ) ;
144178
145- const filteredTags = tags
146- . filter ( ( tag ) : tag is Tag => typeof tag !== "undefined" ) ;
147-
148- const { cleanUrl, typeTags } = typeTagExtractor ( url , filteredTags ) ;
179+ const { typeTags, cleanUrl } = parseTypeTagsFromUrl ( url ) ;
149180
150181 const initialTags = props . tags && props . tags ?. length > 0
151182 ? props . tags
@@ -165,7 +196,7 @@ const searchLoader = async (
165196 const tag = categories . at ( - 1 ) ;
166197
167198 const [ response , seo = [ ] ] = await Promise . all ( [
168- await api [ "GET /api/v2/products/search" ] ( {
199+ api [ "GET /api/v2/products/search" ] ( {
169200 term : term ?? preference ,
170201 sort,
171202 page,
@@ -211,19 +242,15 @@ const searchLoader = async (
211242 ) as ProductSearchResult [ "pagination" ] | null ;
212243
213244 const search = await response . json ( ) ;
214-
215245 const { results : searchResults = [ ] } = search ;
216246
217- const validProducts = searchResults . filter ( ( { variants } ) => {
218- return variants . length !== 0 ;
219- } ) ;
247+ const validProducts = searchResults . filter ( ( { variants } ) =>
248+ variants . length !== 0
249+ ) ;
220250
221- const products = validProducts . map ( ( product ) => {
222- return toProduct ( product , null , {
223- url,
224- priceCurrency : "BRL" ,
225- } ) ;
226- } ) ;
251+ const products = validProducts . map ( ( product ) =>
252+ toProduct ( product , null , { url, priceCurrency : "BRL" } )
253+ ) ;
227254
228255 const nextPage = new URLSearchParams ( url . searchParams ) ;
229256 const previousPage = new URLSearchParams ( url . searchParams ) ;
@@ -247,11 +274,7 @@ const searchLoader = async (
247274 "@type" : "ProductListingPage" ,
248275 seo : getSEOFromTag ( categories , url , seo . at ( - 1 ) , hasTypeTags , isSearchPage ) ,
249276 breadcrumb : isSearchPage
250- ? {
251- "@type" : "BreadcrumbList" ,
252- itemListElement : [ ] ,
253- numberOfItems : 0 ,
254- }
277+ ? { "@type" : "BreadcrumbList" , itemListElement : [ ] , numberOfItems : 0 }
255278 : getBreadcrumbList ( categories , url ) ,
256279 filters : toFilters ( search . aggregations , typeTags , cleanUrl ) ,
257280 products,
@@ -270,12 +293,9 @@ export const cache = "stale-while-revalidate";
270293export const cacheKey = ( props : Props , req : Request , _ctx : AppContext ) => {
271294 const url = new URL ( props . pageHref || req . url ) ;
272295 const qQueryString = url . searchParams . get ( "q" ) ;
273- const term = props . term || qQueryString ||
274- undefined ;
296+ const term = props . term || qQueryString || undefined ;
275297
276- if ( term ) {
277- return null ;
278- }
298+ if ( term ) return null ;
279299
280300 const typeTags = [ ...url . searchParams . entries ( ) ]
281301 . filter ( ( [ key ] ) => key . includes ( "type_tags" ) )
@@ -299,19 +319,15 @@ export const cacheKey = (props: Props, req: Request, _ctx: AppContext) => {
299319 [ "sort" , url . searchParams . get ( "sort" ) ?? props . sort ?? "" ] ,
300320 [ "type_tags" , typeTags ] ,
301321 [ "tags" , props ?. tags ?. join ( "\\" ) ?? "" ] ,
302- [
303- "price" ,
304- filterMatch ? `min:${ filterMatch [ 1 ] } _max:${ filterMatch [ 2 ] } ` : "" ,
305- ] ,
322+ [ "price" , filterMatch ? `min:${ filterMatch [ 1 ] } _max:${ filterMatch [ 2 ] } ` : "" ] ,
306323 [ "filterByTags" , props . filterByTags ? "true" : "false" ] ,
307324 [ "filterOperator" , filterOperators . join ( "\\" ) ] ,
308325 [ "page" , ( url . searchParams . get ( "page" ) ?? 1 ) . toString ( ) ] ,
309326 ] ) ;
310327
311328 params . sort ( ) ;
312-
313329 url . search = params . toString ( ) ;
314330 return url . href ;
315331} ;
316332
317- export default searchLoader ;
333+ export default searchLoader ;
0 commit comments